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/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%}") 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))