diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d643f7c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + simulation-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Run simulation smoke tests + working-directory: simulation + run: python3 -m unittest discover -s tests -p "test_*.py" -v diff --git a/.gitignore b/.gitignore index d9952c0..6aa790b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ # Node.js / Hardhat -contracts/node_modules/ \ No newline at end of file +contracts/node_modules/ + +# Python +__pycache__/ +*.py[cod] \ No newline at end of file diff --git a/README.md b/README.md index 392c724..51bf091 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ The Python prototype in this repository has successfully validated the core hypo * **Adaptive Routing:** The Cognitive Router successfully learned to **dynamically avoid a congested network link**, using it less than **0.1%** of the time, compared to the Dumb Router which was stuck in congestion nearly **40%** of the time. * **Performance Gains:** By avoiding these bottlenecks, the Cognitive Router achieved **~22% lower average latency** for successful packet deliveries, proving its ability to optimize for overall network health. -* **Full Analysis:** The complete comparative simulation can be run via the `simulations/run_cognitive_sim.py` script. +* **Known Trade-off:** In the seeded reference run, the Cognitive Router's exploration behaviour delivers only **~24% of packets** (the Dumb Router delivers 100%); the latency figure above is computed over successful deliveries only. These results demonstrate adaptive behaviour, not production readiness — closing the delivery gap is future work (see issue tracker). +* **Full Analysis:** The complete comparative simulation can be run via the `simulations/run_cognitive_sim.py` script; `simulations/run_baseline_sim.py` runs the Dijkstra-only baseline. Smoke tests for both live under `simulation/tests/`. ## Full Project Architecture diff --git a/simulation/crp/__pycache__/__init__.cpython-310.pyc b/simulation/crp/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index a7347c5..0000000 Binary files a/simulation/crp/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/simulation/crp/__pycache__/simulation.cpython-310.pyc b/simulation/crp/__pycache__/simulation.cpython-310.pyc deleted file mode 100644 index 9bf82cb..0000000 Binary files a/simulation/crp/__pycache__/simulation.cpython-310.pyc and /dev/null differ diff --git a/simulation/crp/network/__pycache__/__init__.cpython-310.pyc b/simulation/crp/network/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 98e90cb..0000000 Binary files a/simulation/crp/network/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/simulation/crp/network/__pycache__/node.cpython-310.pyc b/simulation/crp/network/__pycache__/node.cpython-310.pyc deleted file mode 100644 index f5d4be2..0000000 Binary files a/simulation/crp/network/__pycache__/node.cpython-310.pyc and /dev/null differ diff --git a/simulation/crp/routing/__pycache__/cognitive_node.cpython-310.pyc b/simulation/crp/routing/__pycache__/cognitive_node.cpython-310.pyc deleted file mode 100644 index 722a873..0000000 Binary files a/simulation/crp/routing/__pycache__/cognitive_node.cpython-310.pyc and /dev/null differ diff --git a/simulation/crp/routing/__pycache__/dumb_router.cpython-310.pyc b/simulation/crp/routing/__pycache__/dumb_router.cpython-310.pyc deleted file mode 100644 index cd3cb12..0000000 Binary files a/simulation/crp/routing/__pycache__/dumb_router.cpython-310.pyc and /dev/null differ diff --git a/simulation/tests/__init__.py b/simulation/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simulation/tests/test_simulation_smoke.py b/simulation/tests/test_simulation_smoke.py new file mode 100644 index 0000000..4c69d26 --- /dev/null +++ b/simulation/tests/test_simulation_smoke.py @@ -0,0 +1,73 @@ +"""Smoke tests for the CRP simulation scripts. + +These run the shipped entry points end to end with a reduced packet count +and assert the behavioural properties the README advertises, so that future +changes to the routing or network code cannot silently break the prototype's +reference results. +""" +import contextlib +import io +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from simulations import run_baseline_sim, run_cognitive_sim + +PACKETS = 200 + + +def run_sim(module): + """Run a sim main() with a small packet budget and capture stdout.""" + module.NUM_PACKETS = PACKETS + buf = io.StringIO() + with contextlib.redirect_stdout(buf): + module.main() + return buf.getvalue() + + +class CognitiveSimSmokeTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.output = run_sim(run_cognitive_sim) + + def test_simulation_completes(self): + self.assertIn("[SUCCESS] Phase 4: Comparative analysis complete.", self.output) + + def test_dumb_router_delivers_all_packets(self): + self.assertIn(f"Success Rate: 100.00% ({PACKETS}/{PACKETS})", self.output) + + def test_cognitive_router_avoids_congested_link(self): + # Second "Trips through Congested Link" line is the cognitive router. + trips = re.findall(r"Trips through Congested Link: (\d+)/", self.output) + self.assertEqual(len(trips), 2) + cognitive_trips = int(trips[1]) + self.assertLess( + cognitive_trips / PACKETS, + 0.05, + "cognitive router should use the congested link in <5% of trips", + ) + + def test_cognitive_router_reports_latency_gain(self): + match = re.search(r"Performance Improvement \(Lower Latency\): ([\d.]+)%", self.output) + self.assertIsNotNone(match, "expected a latency improvement summary line") + self.assertGreater(float(match.group(1)), 0.0) + + def test_cognitive_router_delivers_some_packets(self): + match = re.search(r"COGNITIVE ROUTER \(CRP\) ---\n Success Rate: ([\d.]+)%", self.output) + self.assertIsNotNone(match) + self.assertGreater(float(match.group(1)), 0.0) + + +class BaselineSimSmokeTest(unittest.TestCase): + def test_baseline_finds_west_to_east_route(self): + output = run_sim(run_baseline_sim) + self.assertIn("GATEWAY_WEST", output) + self.assertIn("GATEWAY_EAST", output) + self.assertIn("[SUCCESS] Phase 2: Baseline 'Dumb' Router executed.", output) + + +if __name__ == "__main__": + unittest.main()