Skip to main content
Glama
test_decay.pyβ€’14.4 kB
"""Tests for temporal decay functions.""" import time import pytest from mnemex.config import Config, set_config from mnemex.core.decay import ( calculate_decay_lambda, calculate_halflife, calculate_score, project_score_at_time, time_until_threshold, ) def test_calculate_score_basic(): """Test basic score calculation.""" now = int(time.time()) # Fresh memory with use_count=1 should have non-zero score score = calculate_score( use_count=1, last_used=now, strength=1.0, now=now, lambda_=2.673e-6, beta=0.6, ) assert score > 0 assert score == pytest.approx(1.0, rel=0.01) # Should be close to 1 when just accessed def test_calculate_score_decay(): """Test that score decays over time.""" now = int(time.time()) one_day_ago = now - 86400 score_fresh = calculate_score( use_count=1, last_used=now, strength=1.0, now=now, lambda_=2.673e-6, beta=0.6, ) score_old = calculate_score( use_count=1, last_used=one_day_ago, strength=1.0, now=now, lambda_=2.673e-6, beta=0.6, ) assert score_old < score_fresh # Older memory has lower score def test_calculate_score_use_count(): """Test that higher use count increases score.""" now = int(time.time()) score_low = calculate_score( use_count=1, last_used=now, strength=1.0, now=now, lambda_=2.673e-6, beta=0.6, ) score_high = calculate_score( use_count=10, last_used=now, strength=1.0, now=now, lambda_=2.673e-6, beta=0.6, ) assert score_high > score_low # Higher use count = higher score def test_calculate_decay_lambda(): """Test decay lambda calculation from half-life.""" # 3-day half-life lambda_3d = calculate_decay_lambda(3.0) assert lambda_3d == pytest.approx(2.673e-6, rel=0.01) # 7-day half-life lambda_7d = calculate_decay_lambda(7.0) assert lambda_7d < lambda_3d # Longer half-life = slower decay def test_calculate_halflife(): """Test half-life calculation from lambda.""" lambda_val = 2.673e-6 halflife = calculate_halflife(lambda_val) assert halflife == pytest.approx(3.0, rel=0.01) # Should be 3 days def test_time_until_threshold(): """Test calculation of time until score drops below threshold.""" now = int(time.time()) # Memory with current score of 1.0 remaining = time_until_threshold( current_score=1.0, threshold=0.5, # Half the score last_used=now, lambda_=calculate_decay_lambda(3.0), # 3-day half-life ) assert remaining is not None # Should be approximately 3 days (259200 seconds) assert remaining == pytest.approx(259200, rel=0.1) def test_time_until_threshold_already_below(): """Test time_until_threshold when already below threshold.""" now = int(time.time()) remaining = time_until_threshold( current_score=0.3, threshold=0.5, last_used=now, lambda_=2.673e-6, ) assert remaining is None # Already below threshold def test_project_score_at_time(): """Test score projection to future time.""" now = int(time.time()) future = now + 86400 # 1 day from now projected = project_score_at_time( use_count=5, last_used=now, strength=1.0, target_time=future, lambda_=2.673e-6, beta=0.6, ) current = calculate_score( use_count=5, last_used=now, strength=1.0, now=now, lambda_=2.673e-6, beta=0.6, ) assert projected < current # Future score should be lower due to decay def test_calculate_score_default_now(): """Test calculate_score with default now parameter (should use current time).""" current_time = int(time.time()) # Call without now parameter - should use current time score = calculate_score( use_count=1, last_used=current_time, strength=1.0, lambda_=2.673e-6, beta=0.6, ) # Score should be close to 1.0 since last_used is current time assert score > 0 assert score == pytest.approx(1.0, rel=0.01) def test_calculate_score_default_beta(): """Test calculate_score with default beta parameter (should use config default).""" config = Config() set_config(config) now = int(time.time()) # Call without beta parameter - should use config default (0.6) score = calculate_score( use_count=5, last_used=now, strength=1.0, now=now, lambda_=2.673e-6, ) assert score > 0 def test_calculate_score_power_law_model(): """Test calculate_score with power_law decay model.""" config = Config(decay_model="power_law", pl_alpha=1.1, pl_halflife_days=3.0) set_config(config) now = int(time.time()) one_day_ago = now - 86400 # Fresh memory score_fresh = calculate_score( use_count=1, last_used=now, strength=1.0, ) # Old memory score_old = calculate_score( use_count=1, last_used=one_day_ago, strength=1.0, ) assert score_fresh > 0 assert score_old > 0 assert score_old < score_fresh # Power law decay def test_calculate_score_power_law_different_alpha(): """Test power_law model with different alpha values.""" now = int(time.time()) one_day_ago = now - 86400 # Test with alpha = 0.8 (lighter tail) config1 = Config(decay_model="power_law", pl_alpha=0.8, pl_halflife_days=3.0) set_config(config1) score1 = calculate_score( use_count=1, last_used=one_day_ago, strength=1.0, ) # Test with alpha = 1.5 (heavier tail) config2 = Config(decay_model="power_law", pl_alpha=1.5, pl_halflife_days=3.0) set_config(config2) score2 = calculate_score( use_count=1, last_used=one_day_ago, strength=1.0, ) assert score1 > 0 assert score2 > 0 def test_calculate_score_two_component_model(): """Test calculate_score with two_component decay model.""" config = Config( decay_model="two_component", tc_lambda_fast=1.603e-5, tc_lambda_slow=1.147e-6, tc_weight_fast=0.7, ) set_config(config) now = int(time.time()) one_day_ago = now - 86400 # Fresh memory score_fresh = calculate_score( use_count=1, last_used=now, strength=1.0, ) # Old memory score_old = calculate_score( use_count=1, last_used=one_day_ago, strength=1.0, ) assert score_fresh > 0 assert score_old > 0 assert score_old < score_fresh # Two-component decay def test_calculate_score_two_component_different_weights(): """Test two_component model with different weight values.""" now = int(time.time()) one_day_ago = now - 86400 # Test with different weights - both should produce valid scores config1 = Config( decay_model="two_component", tc_lambda_fast=1.603e-5, tc_lambda_slow=1.147e-6, tc_weight_fast=0.9, ) set_config(config1) score1 = calculate_score( use_count=1, last_used=one_day_ago, strength=1.0, ) config2 = Config( decay_model="two_component", tc_lambda_fast=1.603e-5, tc_lambda_slow=1.147e-6, tc_weight_fast=0.3, ) set_config(config2) score2 = calculate_score( use_count=1, last_used=one_day_ago, strength=1.0, ) # Both should produce valid scores assert score1 > 0 assert score1 < 1.0 assert score2 > 0 assert score2 < 1.0 def test_calculate_score_exponential_model(): """Test calculate_score with exponential decay model via config.""" config = Config(decay_model="exponential", decay_lambda=2.673e-6) set_config(config) now = int(time.time()) one_day_ago = now - 86400 # Fresh memory score_fresh = calculate_score( use_count=1, last_used=now, strength=1.0, ) # Old memory score_old = calculate_score( use_count=1, last_used=one_day_ago, strength=1.0, ) assert score_fresh > 0 assert score_old > 0 assert score_old < score_fresh # Exponential decay def test_time_until_threshold_exponential_from_config(): """Test time_until_threshold with exponential model from config.""" config = Config(decay_model="exponential", decay_lambda=calculate_decay_lambda(3.0)) set_config(config) now = int(time.time()) # Test without lambda_ parameter - should use config remaining = time_until_threshold( current_score=1.0, threshold=0.5, last_used=now, ) assert remaining is not None # Should be approximately 3 days (259200 seconds) assert remaining == pytest.approx(259200, rel=0.1) def test_time_until_threshold_power_law(): """Test time_until_threshold with power_law model.""" config = Config(decay_model="power_law", pl_alpha=1.1, pl_halflife_days=3.0) set_config(config) now = int(time.time()) remaining = time_until_threshold( current_score=1.0, threshold=0.5, last_used=now, ) assert remaining is not None assert remaining > 0 def test_time_until_threshold_two_component(): """Test time_until_threshold with two_component model.""" config = Config( decay_model="two_component", tc_lambda_fast=1.603e-5, tc_lambda_slow=1.147e-6, tc_weight_fast=0.7, ) set_config(config) now = int(time.time()) remaining = time_until_threshold( current_score=1.0, threshold=0.5, last_used=now, ) assert remaining is not None assert remaining > 0 def test_time_until_threshold_different_thresholds(): """Test time_until_threshold with various threshold values.""" now = int(time.time()) # Higher threshold (0.9) - should reach it faster remaining_high = time_until_threshold( current_score=1.0, threshold=0.9, last_used=now, lambda_=2.673e-6, ) # Lower threshold (0.1) - should take longer remaining_low = time_until_threshold( current_score=1.0, threshold=0.1, last_used=now, lambda_=2.673e-6, ) assert remaining_high is not None assert remaining_low is not None assert remaining_high < remaining_low def test_time_until_threshold_never_reaches(): """Test time_until_threshold when score never reaches threshold (edge case).""" # Set up a scenario where the decay is extremely slow config = Config(decay_model="power_law", pl_alpha=0.01, pl_halflife_days=10000.0) set_config(config) now = int(time.time()) # Very high current score, very low threshold, but decay is so slow it might not reach remaining = time_until_threshold( current_score=1.0, threshold=0.00001, last_used=now, ) # Should either return a very large number or None # This tests the upper bound cap logic if remaining is None: assert True # Never reaches else: assert remaining >= 0 def test_lambda_halflife_roundtrip(): """Test roundtrip conversion: halflife -> lambda -> halflife.""" original_halflife = 5.0 # days # Convert to lambda lambda_val = calculate_decay_lambda(original_halflife) # Convert back to halflife result_halflife = calculate_halflife(lambda_val) assert result_halflife == pytest.approx(original_halflife, rel=1e-6) def test_halflife_lambda_roundtrip(): """Test roundtrip conversion: lambda -> halflife -> lambda.""" original_lambda = 1.5e-6 # Convert to halflife halflife = calculate_halflife(original_lambda) # Convert back to lambda result_lambda = calculate_decay_lambda(halflife) assert result_lambda == pytest.approx(original_lambda, rel=1e-6) def test_calculate_decay_lambda_different_halflifes(): """Test calculate_decay_lambda with different halflife values.""" lambda_1d = calculate_decay_lambda(1.0) lambda_7d = calculate_decay_lambda(7.0) lambda_30d = calculate_decay_lambda(30.0) # Longer halflife = smaller lambda (slower decay) assert lambda_1d > lambda_7d > lambda_30d def test_project_score_past_time(): """Test project_score_at_time with past time (should show higher score).""" now = int(time.time()) two_days_ago = now - 2 * 86400 one_day_ago = now - 86400 # Project to past time projected_past = project_score_at_time( use_count=5, last_used=two_days_ago, strength=1.0, target_time=one_day_ago, lambda_=2.673e-6, beta=0.6, ) # Current score (2 days after last_used) current = calculate_score( use_count=5, last_used=two_days_ago, strength=1.0, now=now, lambda_=2.673e-6, beta=0.6, ) # Past projection should have higher score assert projected_past > current def test_project_score_different_models(): """Test project_score_at_time with different decay models.""" now = int(time.time()) future = now + 86400 # Power law config_pl = Config(decay_model="power_law", pl_alpha=1.1, pl_halflife_days=3.0) set_config(config_pl) score_pl = project_score_at_time( use_count=5, last_used=now, strength=1.0, target_time=future, ) # Two component config_tc = Config( decay_model="two_component", tc_lambda_fast=1.603e-5, tc_lambda_slow=1.147e-6, tc_weight_fast=0.7, ) set_config(config_tc) score_tc = project_score_at_time( use_count=5, last_used=now, strength=1.0, target_time=future, ) # Exponential config_exp = Config(decay_model="exponential", decay_lambda=2.673e-6) set_config(config_exp) score_exp = project_score_at_time( use_count=5, last_used=now, strength=1.0, target_time=future, ) # All should produce valid scores assert score_pl > 0 assert score_tc > 0 assert score_exp > 0

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mnemexai/mnemex'

If you have feedback or need assistance with the MCP directory API, please join our Discord server