Skip to content

Commit 1f8afb7

Browse files
committed
merge: integrate stochastic robustness experiment
2 parents 45c9167 + db7d2b4 commit 1f8afb7

7 files changed

Lines changed: 183 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ memory, convergence, and stopping behavior:
6565
11. [Trace diagnostics](docs/trace-diagnostics.md)
6666
12. [Diagnosis-driven repair loop](docs/diagnosis-repair-loop.md)
6767
13. [Multi-repair selection](docs/multi-repair-selection.md)
68-
14. [Trace difference analysis](docs/trace-diff-analysis.md)
68+
14. [Stochastic robustness experiment](docs/stochastic-robustness.md)
69+
15. [Trace difference analysis](docs/trace-diff-analysis.md)
6970
15. [Semantic regression gate](docs/regression-gate.md)
7071
15. [Architecture](docs/architecture.md)
7172
16. [Metrics](docs/metrics.md)

README.zh-CN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Loop Engineering Study
22

3+
[随机性与鲁棒性实验](docs/stochastic-robustness.md):以固定种子比较策略在随机扰动下的经验表现。
4+
35
[多修复方案选择](docs/multi-repair-selection.md):重跑多个确定性候选并按证据选优。
46

57
[Trace 差异分析](docs/trace-diff-analysis.md):对诊断修复前后的 Artifact 定位首个可观察分歧。

docs/experiments.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ python experiments/sensitivity_analysis.py
1818
python experiments/trace_diagnostics.py
1919
python experiments/diagnosis_repair_loop.py
2020
python experiments/multi_repair_selection.py
21+
python experiments/stochastic_robustness.py
2122
python experiments/trace_diff_analysis.py
2223
python experiments/regression_gate.py
2324
```

docs/stochastic-robustness.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# 随机性与鲁棒性实验
2+
3+
本实验以固定随机种子评估四种策略在随机动作失败与有界数值噪声下的经验鲁棒性。
4+
5+
## 运行实验
6+
7+
```powershell
8+
python experiments/stochastic_robustness.py
9+
```
10+
11+
报告写入 `.loop/runs/stochastic-robustness/report.json`,并保存每次运行的 Artifact。
12+
13+
## 扰动矩阵
14+
15+
实验运行低、中、高三档扰动,每档覆盖 `fixed``error_aware``memory_aware``adaptive` 四种策略及 8 个固定种子,共 96 次运行。
16+
17+
## 如何阅读报告
18+
19+
每组汇总包含成功率、平均/最差/P90 成本与平均/P90 步数。每个档位的排名优先成功率,再比较成本 P90、平均步数和策略声明顺序。
20+
21+
## 解释边界
22+
23+
结果仅描述固定扰动模型和有限种子集下的经验表现,不构成真实环境分布或统计显著性的证明。

docs/superpowers/sdd/progress.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@
2121
- Phase 2 semantic regression gate: complete (four semantic contracts, isolated child outputs, and 65 passing tests verified)
2222
- Phase 2 Trace difference analysis: complete (read-only first-difference comparison, three diagnosis-repair Artifact pairs, structured JSON report, and 73 passing tests verified)
2323
- Phase 2 multi-repair selection: complete (six deterministic candidate reruns across three cases, stable evidence-based ranking, and 77 passing tests verified)
24+
- Phase 2 stochastic robustness: complete (96 seeded stochastic runs, 12 strategy-level summaries, stable rankings, and 78 passing tests verified)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""Reproducible random-failure and noisy-execution robustness experiment."""
2+
3+
from __future__ import annotations
4+
5+
import random
6+
import json
7+
from pathlib import Path
8+
9+
if __package__:
10+
from ._bootstrap import prepare_script_imports
11+
else:
12+
from _bootstrap import prepare_script_imports
13+
14+
prepare_script_imports(__file__)
15+
16+
from loop_engineering.actions import Action, ActionResult
17+
from loop_engineering.artifacts import save_run_artifact
18+
from loop_engineering.evaluators import GoalEvaluator
19+
from loop_engineering.metrics import MetricReport
20+
from loop_engineering.models import LoopState
21+
from loop_engineering.policies import Decision
22+
from loop_engineering.runner import LoopRunner
23+
from loop_engineering.stopping import MaxSteps, SuccessReached
24+
25+
from experiments.adaptive_strategy import (
26+
AdaptivePolicy,
27+
ErrorAwarePolicy,
28+
FixedPolicy,
29+
MemoryAwarePolicy,
30+
)
31+
32+
STRATEGIES = ("fixed", "error_aware", "memory_aware", "adaptive")
33+
SEEDS = (101, 211, 307, 401, 503, 601, 701, 809)
34+
LEVELS = (("low", 0.05, 0.10), ("medium", 0.15, 0.30), ("high", 0.30, 0.60))
35+
36+
37+
class StochasticAction(Action):
38+
"""Apply an increment with independent random failure and bounded noise."""
39+
40+
def __init__(
41+
self, failure_rate: float, noise_amplitude: float, rng: random.Random
42+
) -> None:
43+
if not 0.0 <= failure_rate <= 1.0:
44+
raise ValueError("failure_rate must be between 0 and 1.")
45+
if noise_amplitude < 0.0:
46+
raise ValueError("noise_amplitude must be non-negative.")
47+
self._failure_rate = failure_rate
48+
self._noise_amplitude = noise_amplitude
49+
self._rng = rng
50+
51+
def apply(self, state: LoopState, decision: Decision) -> ActionResult:
52+
if self._rng.random() < self._failure_rate:
53+
return ActionResult(
54+
state=state.with_value(state.value, stochastic_failure=True),
55+
success=False,
56+
cost=0.0,
57+
)
58+
amount = float(decision.parameters["amount"])
59+
actual_amount = amount + self._rng.uniform(
60+
-self._noise_amplitude, self._noise_amplitude
61+
)
62+
return ActionResult(
63+
state=state.with_value(state.value + actual_amount),
64+
success=True,
65+
cost=abs(actual_amount),
66+
)
67+
68+
69+
def _policy_for(strategy: str):
70+
return {
71+
"fixed": FixedPolicy,
72+
"error_aware": ErrorAwarePolicy,
73+
"memory_aware": MemoryAwarePolicy,
74+
"adaptive": AdaptivePolicy,
75+
}[strategy]()
76+
77+
78+
def _run(root: Path, level: str, failure_rate: float, noise: float, strategy: str, seed: int) -> dict[str, object]:
79+
trace = LoopRunner(
80+
policy=_policy_for(strategy),
81+
action=StochasticAction(failure_rate, noise, random.Random(seed)),
82+
evaluator=GoalEvaluator(tolerance=0.25),
83+
stop_conditions=[SuccessReached(), MaxSteps(8)],
84+
).run(LoopState(step=0, value=0.0, goal=6.0))
85+
metrics = MetricReport.from_trace(trace)
86+
artifact = save_run_artifact(root / f"{level}--{strategy}--{seed}.json", trace, metrics)
87+
return {"level": level, "strategy": strategy, "seed": seed, "success": metrics.success, "cost": metrics.cost, "steps": metrics.steps, "final_score": metrics.final_score, "artifact_path": str(artifact)}
88+
89+
90+
def _summary(level: str, strategy: str, runs: list[dict[str, object]]) -> dict[str, object]:
91+
costs = sorted(float(item["cost"]) for item in runs)
92+
steps = sorted(int(item["steps"]) for item in runs)
93+
index = 7
94+
return {"level": level, "strategy": strategy, "run_count": len(runs), "success_count": sum(bool(item["success"]) for item in runs), "success_rate": sum(bool(item["success"]) for item in runs) / len(runs), "mean_cost": sum(costs) / len(costs), "worst_cost": costs[-1], "cost_p90": costs[index], "mean_steps": sum(steps) / len(steps), "steps_p90": steps[index]}
95+
96+
97+
def run_stochastic_robustness(output_dir: str | Path = ".loop/runs/stochastic-robustness") -> dict[str, object]:
98+
"""Run the fixed stochastic matrix and persist every run artifact."""
99+
root = Path(output_dir).resolve()
100+
runs = [_run(root, level, rate, noise, strategy, seed) for level, rate, noise in LEVELS for strategy in STRATEGIES for seed in SEEDS]
101+
summaries = [_summary(level, strategy, [item for item in runs if item["level"] == level and item["strategy"] == strategy]) for level, _, _ in LEVELS for strategy in STRATEGIES]
102+
rankings = {level: sorted([item for item in summaries if item["level"] == level], key=lambda item: (-float(item["success_rate"]), float(item["cost_p90"]), float(item["mean_steps"]), STRATEGIES.index(str(item["strategy"])))) for level, _, _ in LEVELS}
103+
result = {"levels": [item[0] for item in LEVELS], "strategies": list(STRATEGIES), "runs": runs, "summaries": summaries, "rankings": rankings}
104+
root.mkdir(parents=True, exist_ok=True)
105+
(root / "report.json").write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
106+
return result
107+
108+
109+
def main() -> None:
110+
"""Print the complete stochastic robustness report as JSON."""
111+
112+
print(json.dumps(run_stochastic_robustness(), ensure_ascii=False, indent=2))
113+
114+
115+
if __name__ == "__main__":
116+
main()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import random
2+
3+
import pytest
4+
5+
from experiments.stochastic_robustness import StochasticAction
6+
from loop_engineering.models import LoopState
7+
from loop_engineering.policies import Decision
8+
9+
10+
def test_stochastic_action_is_reproducible_for_the_same_seed() -> None:
11+
decision = Decision(name="increment", parameters={"amount": 1.0})
12+
first = StochasticAction(0.15, 0.30, random.Random(101))
13+
second = StochasticAction(0.15, 0.30, random.Random(101))
14+
15+
first_results = [first.apply(LoopState(0, 0.0, 6.0), decision) for _ in range(3)]
16+
second_results = [second.apply(LoopState(0, 0.0, 6.0), decision) for _ in range(3)]
17+
18+
assert first_results == second_results
19+
20+
21+
@pytest.mark.parametrize("failure_rate,noise", [(-0.01, 0.1), (1.01, 0.1), (0.1, -0.1)])
22+
def test_stochastic_action_rejects_invalid_parameters(failure_rate: float, noise: float) -> None:
23+
with pytest.raises(ValueError):
24+
StochasticAction(failure_rate, noise, random.Random(1))
25+
26+
27+
def test_robustness_matrix_is_complete_and_reproducible(tmp_path) -> None:
28+
from experiments.stochastic_robustness import run_stochastic_robustness
29+
30+
first = run_stochastic_robustness(tmp_path / "first")
31+
second = run_stochastic_robustness(tmp_path / "second")
32+
33+
assert len(first["runs"]) == 96
34+
assert len(first["summaries"]) == 12
35+
assert all(item["run_count"] == 8 for item in first["summaries"])
36+
assert [item["success"] for item in first["runs"]] == [
37+
item["success"] for item in second["runs"]
38+
]

0 commit comments

Comments
 (0)