MelanoGraph

# MelanoGraph An executable, reproducible simulation substrate for the *Drosophila* whole-brain connectome, and a preregistered programme asking what circuit organisation adds to social learning. MelanoGraph loads the FlyWire-derived connectivity published with the Shiu et al. (2024) Drosophila brain model, runs a leaky integrate-and-fire network over it, accepts controlled sensory stimulation, supports causal silencing, records the resulting activity, and keeps a full provenance record. It is a substrate for experiments — not a mind. > **Naming.** The project is **MelanoGraph**. The Python package, the CLI entry point > and the compiled Rust extension are still called `flybrain`, because renaming 114 > import sites and a PyO3 module is a mechanical change with no scientific value and > real breakage risk. The GitHub repository is still `FlyBrain` for the same reason. > Nothing user-facing depends on the old name. ## The programme, in three pre-registered steps | | question | answer | |---|---|---| | **C1** | can a tabular agent learn a communication channel? | yes — gain 0.4881, 95 % CI [0.4874, 0.4887], 64/64 seeds | | **C2a** | how much cue information reaches the output of the **real** circuit? | more than any rewiring — A−B = +0.2695, CI [0.2129, 0.3223] | | **C2b** | does the real wiring **carry** the message in a social task? | yes — A−B = +0.4941, CI [0.4938, 0.4944] | Each step was preregistered and sealed by SHA-256 **before** it ran, and the run refuses to start if the document no longer matches its seal. [`docs/RESUMO_UM_PAGINA.md`](docs/RESUMO_UM_PAGINA.md) is the one-page summary. --- ## The substrate, demonstrated ``` Stimulate the 21 labelled sugar-sensing gustatory receptor neurons at 100 Hz for 1 s ↓ 376 of 127,400 neurons fire (mean 1.3 active per step) ↓ MN9 motor neurons are among the driven: 81.0 Hz left, 51.0 Hz right ↓ 177 monosynaptic edges run from the sugar GRNs to the responders ↓ Every number traceable to a dataset SHA-256, a git revision, and a seed ``` The published reference (the authors' own output, 30 trials) gives 404 active neurons and 67.0 / 48.6 Hz for MN9. The single-trial whole-brain run agrees to within 7 % on the active-neuron count and on MN9-right, and is 21 % high on MN9-left. That is **partial** agreement, not a reproduction, and [`docs/STATUS.md`](docs/STATUS.md) says so in detail rather than rounding it up. --- ## What this is, and is not **Is:** a validated connectome loader, a verified LIF implementation with the published constants, sparse whole-brain execution, subset execution for CPU, controlled stimulation, silencing, selective recording, plots, a command console, packaged experiments, a Rust kernel validated **bit-identically** against the NumPy reference, a degree-preserving rewiring control that preserves degree, weight and excitation/inhibition balance, and a test suite that checks the physics rather than the code's own output. **Is not:** a language model, an agent, a claims about consciousness, or a claim that simulated activity equals biological activity. Explicitly out of scope: LLM, JEV, natural-language interface, reinforcement learning, decoder, robotic body. See [`docs/SCIENTIFIC_BOUNDARIES.md`](docs/SCIENTIFIC_BOUNDARIES.md). **Not neurobiology.** No result here is a claim about how a real fly behaves. The circuit used in C2a and C2b is 10 201 of the 138 639 neurons of FlyWire v783 — two hops from a gustatory anchor — and saying "the connectome beats a random graph" from a subgraph would extrapolate further than these data allow. --- ## Install ```bash git clone FlyBrain && cd FlyBrain python -m venv .venv # Windows: .venv\Scripts\activate # Unix: source .venv/bin/activate pip install -e . pip install -e ".[plot]" # optional: matplotlib, for the figures ``` Requirements: Python ≥ 3.10, NumPy, pandas, pyarrow. Nothing else is needed for subset mode, and nothing downloads anything at run time. ### Get the data The dataset is FlyWire-derived, already preprocessed and published by the upstream authors. MelanoGraph does not download raw electron-microscopy volumes and never needs to. ```bash git clone --depth 1 https://github.com/philshiu/Drosophila_brain_model.git \ third_party/Drosophila_brain_model flybrain datasets --stage all # copies both versions into data/raw and validates ``` Two versions ship in that repository: `flywire_630` (127 400 neurons — the version the paper used, and MelanoGraph's primary) and `flywire_783` (138 639 neurons — registered and validated, not primary). Details and hashes: [`docs/DATA_PROVENANCE.md`](docs/DATA_PROVENANCE.md). --- ## Quick start ```bash flybrain info # hardware, backend, paths, datasets flybrain datasets # staged datasets and their SHA-256 flybrain populations --search sugar # curated neuron populations flybrain neuron 720575940660219265 # inspect one neuron by FlyWire ID flybrain run --mode subset --duration-ms 100 --stimulus sugar_grn flybrain run --mode subset --stimulus sugar_grn --duration-ms 1000 --record population flybrain run --mode subset --stimulus sugar_grn --duration-ms 1000 --silence mn9 flybrain experiments sugar_stimulation --mode subset --duration-ms 1000 flybrain interactive ``` Python API: ```python from flybrain import config from flybrain.data import load_connectome from flybrain.model import LIFNetwork, Stimulus, PopulationRegistry from flybrain.runtime import plan_run, run_experiment from flybrain.config import RunConfig conn = load_connectome("flywire_630", config.RAW_DIR, config.PROCESSED_DIR, config.METADATA_DIR) stim = Stimulus.population("sugar_grn", rate_hz=100.0) # or Stimulus([...], 150, 100, 500) cfg = RunConfig(mode="subset", duration_ms=1000.0, subset_hops=2, subset_max_neurons=8000) plan = plan_run(conn, cfg, [stim]) # decides subset vs whole-brain result = run_experiment(plan, cfg, [stim]) print(result.summary["total_spikes"], result.summary["population_rates_hz"]["mn9"]) ``` The network itself exposes exactly five operations, which are the boundary a future milestone would attach to: ```python brain.apply_stimulus(stim) # set the stimulus set brain.step() # advance one dt obs = brain.observe() # read state brain.silence("mn9") # remove a population's outgoing influence brain.reset(seed=0) # zero state, keep interventions ``` --- ## The two execution modes ### `subset` A bounded k-hop in-and-out neighbourhood around the stimulus neurons (plus any `--subset-seed`), relabelled but keeping FlyWire IDs. Fast enough to iterate on: milliseconds per simulated second on CPU. ```bash flybrain run --mode subset --duration-ms 1000 --stimulus sugar_grn --subset-max-neurons 8000 ``` ### `whole-brain` All 127 400 neurons and all 14 687 178 edges, measured on the development machine (Windows 11, i7-7700K, 16 GB RAM, no usable GPU): | | value | |---|---| | measured step cost | **200.6 ms/step** | | throughput | 4.98 steps/s | | 1000 ms of simulated time | **33.4 min** | | estimated peak memory | **0.366 GB** | | a dense connectivity matrix would need | **64.9 GB** | It runs; it is not fast. Two whole-brain runs under different commits produced an **identical spike digest**, and [`docs/STATUS.md`](docs/STATUS.md) has the full measurement, the reproducibility check, and the identified bottleneck. ```bash flybrain run --mode whole-brain --duration-ms 1000 --stimulus sugar_grn --max-minutes 120 ``` **If whole-brain cannot run safely, MelanoGraph refuses rather than quietly shrinking.** It estimates the memory requirement by component, compares it against 60 % of *available* (not total) RAM, and — for the runtime gate — against a per-step cost it measures by actually stepping the assembled network. It then prints the estimate, exits with code **3**, and simulates nothing. It does not even create the run directory, so nothing on disk can be mistaken for a result: ``` $ flybrain run --mode whole-brain --duration-ms 60000 --max-minutes 1 REFUSED: estimated runtime 2548.4 min exceeds the configured limit of 1.0 min (254.8 ms/step measured, 600000 steps x 1 trial(s)) ``` Pass `--allow-fallback` to get a subset instead — every artefact then records `requested_mode: whole-brain`, `effective_mode: subset`, `mode_changed: true`, and a note beginning "WHOLE-BRAIN RUN WAS REQUESTED AND WAS NOT DELIVERED." `--allow-fallback` covers *memory* infeasibility only. It deliberately does **not** override `--max-minutes`, because a user who set a time limit has stated a constraint, and silently running a different experiment instead is the failure mode this project exists to avoid. --- ## Where results go ``` outputs/runs// ├── config.json full configuration, stimuli, model parameters ├── environment.json hardware, backend, Python, dependency versions, git revision ├── dataset.json dataset card with per-file SHA-256, subset description ├── summary.json measured activity, timing, population rates, spike digest ├── spikes.parquet only when --record spikes was requested ├── population_activity.parquet ├── voltage_samples.parquet only when --record voltage was requested ├── run.log └── plots/ population rates, raster, top populations ``` Spike tables are **not** recorded by default on the command line: a whole-brain spike dump is unbounded output, so `flybrain run` records population rates unless `--record spikes` is passed. Through the Python API, `RecordingConfig.record_spikes` defaults to `True` and is bounded by `max_spike_rows` (20 M rows); pass `record_spikes=False` for rates only. When a cap is hit, the recorder stops and sets `spike_recording_truncated` in `summary.json` rather than silently truncating. --- ## Experiments | command | what it establishes | |---|---| | `flybrain experiments smoke_test` | initialises, stimulates, propagates, is deterministic — on a synthetic graph, so it needs no dataset | | `flybrain experiments baseline_activity` | with no stimulus the model is exactly quiescent, so later activity is attributable to the declared stimulus | | `flybrain experiments sugar_stimulation` | the known sensory population drives propagation into non-sensory downstream populations, with per-neuron MN9 rates comparable to the published reference | | `flybrain experiments silencing_test` | stimulate A, silence B, and measure the change in C against an un-silenced baseline | Each writes a JSON report containing its hypothesis, its measured numbers, and each check with the evidence behind it. Each run inside an experiment also writes its own `config.json`, `environment.json`, `dataset.json`, `summary.json`, spike and population tables, `run.log`, and a `plots/` directory. --- ## Tests ```bash pytest -q # full suite pytest -q -m "not slow" # skip the long ones ``` Dataset-dependent tests skip cleanly when the data is not staged, so the suite runs on a fresh checkout. The tests check physics and structure, not the code's own output: the integrator is verified against its exact closed-form solution and its fixed point; the threshold boundary is verified at `v == v_threshold`; refractoriness is verified to block a clamped suprathreshold neuron; propagation is verified to arrive at *exactly* the configured delay; and the NumPy and JAX backends are verified to produce bit-identical spike counts. --- ## Documentation | document | contents | |---|---| | [`docs/UPSTREAM_AUDIT.md`](docs/UPSTREAM_AUDIT.md) | what the three upstream projects contain, which parameters are theirs, and which are modelling assumptions | | [`docs/MODEL_ASSUMPTIONS.md`](docs/MODEL_ASSUMPTIONS.md) | every parameter with source, reason and confidence; every deliberate deviation | | [`docs/DATA_PROVENANCE.md`](docs/DATA_PROVENANCE.md) | dataset identity, SHA-256, measured content, validation performed | | [`docs/SCIENTIFIC_BOUNDARIES.md`](docs/SCIENTIFIC_BOUNDARIES.md) | what may and may not be concluded from the output | | [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | layering, backends, the refusal contract, determinism | | [`docs/STATUS.md`](docs/STATUS.md) | DONE / PARTIAL / BLOCKED / NOT STARTED for every v0 requirement, with evidence | --- ## Limits worth knowing before you trust a number * `w_syn = 0.275 mV` is labelled a **free parameter** upstream. Absolute firing rates inherit that uncertainty. * Sign is a **per-neuron** property in this dataset and is a *predicted* neurotransmitter identity, not a measurement. * The dataset ships **no cell-type annotation**; neuron types come only from 114 curated population lists. * A single presynaptic spike needs ≈162 synapses on one target to cross threshold, so downstream responses require convergent input. * JAX has no CUDA wheels for native Windows, so a GPU on a Windows host is not usable from this process. MelanoGraph reports this instead of printing "CUDA" ([`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) §4). --- ## License Apache-2.0. The dataset and the model derive from [philshiu/Drosophila_brain_model](https://github.com/philshiu/Drosophila_brain_model) (Apache-2.0); cite Shiu et al., *Nature* **634**, 210–219 (2024), [doi:10.1038/s41586-024-07763-9](https://doi.org/10.1038/s41586-024-07763-9).