From f1f6060f3aa8f1a17252f3706981b44665e2e68f Mon Sep 17 00:00:00 2001 From: Yoav Haimov Date: Tue, 3 Nov 2026 07:22:56 +0200 Subject: [PATCH 1/2] Add live end-to-end test against the real neuPrint brain Fetches the mini brain from the server with an empty cache, builds the network and checks it settles to finite motor output. Skipped when no token is set, so offline runs are unchanged. Adds make test-live and passes the token through CI when the repo has one. --- .github/workflows/ci.yml | 2 ++ Makefile | 5 ++++- tests/test_live_brain.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/test_live_brain.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbacd3b..ca63f3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,3 +37,5 @@ jobs: run: make test-quiet PYTHON=python env: MPLBACKEND: Agg + # Optional repo secret; live-brain tests skip when it is empty. + NEUPRINT_APPLICATION_CREDENTIALS: ${{ secrets.NEUPRINT_TOKEN }} diff --git a/Makefile b/Makefile index 47f245b..0187d40 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ examples example-basic example-connectome example-navigation example-gui \ example-gui-smoke figures \ cli-list cli-simulate cli-simulate-offline cli-train cli-train-offline \ - test-neuprint test-all + test-neuprint test-live test-all PYTHON := .venv/bin/python PIP := .venv/bin/pip @@ -118,6 +118,9 @@ cli-train-offline: ## Train with synthetic brain (no token needed) # ── neuPrint (requires token) ───────────────────────────────────────────── +test-live: ## End-to-end pytest against the real brain (needs token) + $(PYTEST) tests/test_live_brain.py -q + test-neuprint: ## Test real neuPrint mini brain fetch + simulate ifndef NEUPRINT_APPLICATION_CREDENTIALS $(error NEUPRINT_APPLICATION_CREDENTIALS not set. Create .env or export it) diff --git a/tests/test_live_brain.py b/tests/test_live_brain.py new file mode 100644 index 0000000..3f6839d --- /dev/null +++ b/tests/test_live_brain.py @@ -0,0 +1,37 @@ +"""End-to-end check against the real neuPrint hemibrain/CNS data. + +Skipped unless NEUPRINT_APPLICATION_CREDENTIALS is set, so offline CI stays +hermetic. Run with ``make test-live``. +""" + +import os + +import numpy as np +import pytest + +from flynet import ConnectomeLoader, SpikingNetwork + +pytestmark = pytest.mark.skipif( + not os.environ.get("NEUPRINT_APPLICATION_CREDENTIALS"), + reason="needs a neuPrint token", +) + + +@pytest.fixture(scope="module") +def real_net(tmp_path_factory): + # Fresh cache dir so this really hits the server rather than a stale file. + loader = ConnectomeLoader(cache_dir=str(tmp_path_factory.mktemp("cache"))) + ids, edges, motors = loader.load_or_fetch_mini(neuron_type="DNge104") + assert loader.last_source == "neuprint" + return SpikingNetwork(ids, edges, motor_ids=motors) + + +def test_real_brain_loads(real_net): + assert len(real_net.ids) > 10 + assert len(real_net.motors) == 2 + + +def test_real_brain_settles_to_finite_motor_output(real_net): + real_net.settle(z_right=2.0, z_left=0.5) + drv, _ = real_net.turn(rho_right=2.0, rho_left=0.5) + assert np.all(np.isfinite(drv)) From b0419c54714ad88c2950133b9d4f21857dfe5330 Mon Sep 17 00:00:00 2001 From: Yoav Haimov Date: Tue, 3 Nov 2026 07:35:10 +0200 Subject: [PATCH 2/2] Fix ZeroDivisionError when training with --episodes 0 Lets you evaluate the untrained connectome as a baseline. --- src/flynet/jax_trainer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/flynet/jax_trainer.py b/src/flynet/jax_trainer.py index a1972e8..1707345 100644 --- a/src/flynet/jax_trainer.py +++ b/src/flynet/jax_trainer.py @@ -613,7 +613,7 @@ def train_navigation( total = time.time() - t0 print( f"\nTraining complete: {episodes} episodes in {total:.1f}s " - f"({total / episodes:.2f}s/ep)" + f"({total / max(episodes, 1):.2f}s/ep)" ) final_sr = log.episode_success_rate[-1] if log.episode_success_rate else 0.0 print(f"Final success rate (10-ep window): {final_sr:.0%}") @@ -1259,7 +1259,7 @@ def train_navigation( total = time.time() - t0 print( f"\nTraining complete: {episodes} episodes in {total:.1f}s " - f"({total / episodes:.2f}s/ep)" + f"({total / max(episodes, 1):.2f}s/ep)" ) final_sr = log.episode_success_rate[-1] if log.episode_success_rate else 0.0 print(f"Final success rate (10-ep window): {final_sr:.0%}")