test: add 3 EWMA IDF tests (smooth decay, no discontinuity, large halflife ≈ global)

41/41 total.
This commit is contained in:
Dispatch#70948f 2026-07-28 17:32:22 +00:00
parent f5cb39ba30
commit 134338ba4c
1 changed files with 83 additions and 0 deletions

View File

@ -922,3 +922,86 @@ def test_windowed_idf_monotonic_with_window_size():
assert s <= prev + 0.001, \
f"w={w}: score {s:.4f} increased from previous {prev:.4f} (should decrease)"
prev = s
def test_ewma_idf_smooth_decay():
"""EWMA IDF produces smooth decay, no step function."""
messages = []
# 4 agents use alpha in phase 1 (t=0-800)
messages.append({"from_id": "originator", "to_id": "_all",
"timestamp": 0.0, "concepts": ["alpha"]})
for i in range(20):
for j, agent in enumerate(["f1", "f2", "f3", "f4"]):
messages.append({
"from_id": agent, "to_id": "_all",
"timestamp": float(50 + i * 40 + j * 10),
"concepts": ["alpha"]
})
# Phase 2: fresh echoes at varying times after silence
scores_at_time = []
for delta in [100, 300, 500, 700, 1000]:
msgs = messages + [{"from_id": "fresh", "to_id": "_all",
"timestamp": float(800 + delta),
"concepts": ["alpha"]}]
s = score_echo(msgs, snapshot_idf=True, idf_decay_halflife=300)
scores_at_time.append(s.get("originator", 0))
# EWMA should be monotonically increasing as agents decay away
for i in range(len(scores_at_time) - 1):
assert scores_at_time[i] <= scores_at_time[i + 1] + 0.001, \
f"EWMA score should increase as old agents decay: {scores_at_time}"
def test_ewma_vs_hard_window_no_discontinuity():
"""EWMA produces smaller jumps between adjacent time points than hard window."""
messages = []
messages.append({"from_id": "originator", "to_id": "_all",
"timestamp": 0.0, "concepts": ["alpha"]})
for i in range(20):
for j, agent in enumerate(["f1", "f2", "f3"]):
messages.append({
"from_id": agent, "to_id": "_all",
"timestamp": float(50 + i * 40 + j * 10),
"concepts": ["alpha"]
})
# Measure max jump (discontinuity) for both methods
def max_jump(method_kwargs, times):
scores = []
for t in times:
msgs = messages + [{"from_id": "fresh", "to_id": "_all",
"timestamp": float(t), "concepts": ["alpha"]}]
s = score_echo(msgs, snapshot_idf=True, **method_kwargs)
scores.append(s.get("originator", 0))
jumps = [abs(scores[i+1] - scores[i]) for i in range(len(scores)-1)]
return max(jumps) if jumps else 0
times = list(range(850, 1600, 25)) # fine-grained sampling
hard_jump = max_jump({"idf_window_seconds": 500}, times)
ewma_jump = max_jump({"idf_decay_halflife": 300}, times)
assert ewma_jump <= hard_jump + 0.01, \
f"EWMA max jump ({ewma_jump:.4f}) should be <= hard window ({hard_jump:.4f})"
def test_ewma_approaches_global_with_large_halflife():
"""With very large half-life, EWMA ≈ global snapshot IDF."""
messages = []
messages.append({"from_id": "originator", "to_id": "_all",
"timestamp": 0.0, "concepts": ["alpha"]})
for i in range(10):
for agent in ["f1", "f2"]:
messages.append({
"from_id": agent, "to_id": "_all",
"timestamp": float(50 + i * 20),
"concepts": ["alpha"]
})
messages.append({"from_id": "fresh", "to_id": "_all",
"timestamp": 500.0, "concepts": ["alpha"]})
global_score = score_echo(messages, snapshot_idf=True)["originator"]
ewma_large = score_echo(messages, snapshot_idf=True,
idf_decay_halflife=100000)["originator"]
assert abs(ewma_large - global_score) < 0.01, \
f"Large half-life EWMA ({ewma_large:.4f}) should ≈ global ({global_score:.4f})"