Status: living document. Tracks both the architectural intent of the
braincellpackage and the current implementation state of every major subsystem. Status markers in this file follow:
[x]shipped — implemented, covered by*_test.py, exported from the public API.[~]partial — implementation exists but is missing functionality, tests, or runtime integration. Specific gaps are listed inline.[ ]planned — design agreed, code not yet written.
This working tree currently has the Cerebellum/PC comparison work in progress:
- Cerebellum channel/ion imports and tests expanded. The channel
catalogue now includes PC MA2024 channel variants and the calcium-ion
catalogue includes concrete Cerebellum kinetic-ion imports such as
CdpStC_*,CdpCAM_MA2024_PC, andCdpCR_MA2020_GrC, with co-located unit tests and NEURON-comparison notebooks underexamples/neuron_compare. - PC MA2024 assembly scaffold added.
examples/neuron_compare/cell/pc_ma2024contains the simplified NEURON assembly, the matching BrainCell assembly, shared parameter loading, debug variants, andrun.ipynbfor side-by-side simulation. - NEURON-style ion-current snapshot mode added.
Cell(..., cache_ion_total_current=True)caches the total ion current at the start of the staggered step, before voltage or ion state advances, so current-driven ion mechanisms can read the same precomputed current snapshot that NEURON-style scheduling expects. - Frozen voltage channel variants added where needed. Some PC calcium
channels now have
_Frozenvariants which stop differentiation through the voltage used inside the current expression, matching the intended NEURON semantics for those mechanisms during the comparison. - Two ion/channel update schedules are available.
ion_channel_update_order="family"restores the NEURON-like family ordering for ion/channel updates;"integration"keeps the previous BrainCell integration-oriented ordering. - Homogeneous multi-compartment
Cellpopulations now support multi-dimensionalpop_size.Cell(..., pop_size=(...))now expands runtime state topop_size + (n_cv,), point-space runtime arrays topop_size + (n_point,), and supports population-specificCurrentClamp(...)amplitudes such as(2,)or(2, 2)-shaped current grids. Regression coverage includes(2,)and(2, 2)populations, plus an example notebook atexamples/multi_compartment/pop_size_demo.ipynb.
BrainCell is a JAX-native library for biologically detailed single-cell
modelling. It targets the same workload as NEURON, Arbor, and BluePyOpt
but expresses everything as differentiable, vectorized JAX programs so that
multi-compartment cells can be simulated, batched, and trained inside the
broader brain* ecosystem (brainstate, brainunit, brainevent,
braintools, brainpy).
The library owns five concerns end-to-end:
- Morphology ingestion — read SWC / ASC / NeuroML2, validate, cache.
- Geometry & discretization — turn a morphology + a CV policy into immutable control-volume (CV) arrays suitable for vectorized solvers.
- Mechanism declaration — paint cable properties, density mechanisms, and ion channels onto regions; place point mechanisms onto locsets.
- Compilation — lower the declaration into a
HHTypedNeuronwith resolved ion species, channel state, and a DHS-ordered node tree. - Numerical integration — provide a registry of explicit, implicit, exponential, and staggered step functions, including a custom DHS voltage solver for branched cables.
Out of scope (for this iteration): network simulation, plasticity learning
rules, NEURON HOC compatibility, GUI tools, and stand-alone NMODL execution
(the previous mech/nmodl/ research tree has been removed; NMODL support,
if it returns, lives behind milestone M5 Phase 4).
┌──────────────────────────────────────────────────────────────────────┐
│ braincell.io │
│ SWC / ASC / NeuroML2 readers · checkpoints · NeuroMorpho client │
└─────────────────────────────┬────────────────────────────────────────┘
│ Morphology
▼
┌──────────────────────────────────────────────────────────────────────┐
│ braincell.morph │
│ Branch (frozen) · Morphology (mutable tree) │
└──────────────┬───────────────────────────────────┬───────────────────┘
│ Morphology │
▼ ▼
┌─────────────────────────────┐ ┌────────────────────────────────────┐
│ braincell.filter │ │ braincell.mech │
│ RegionExpr · LocsetExpr │ │ Mechanism · CableProperty │
│ SelectionCache │ │ Density · Point · Junction │
│ │ │ MechanismRegistry │
└──────────────┬──────────────┘ └─────────────────┬──────────────────┘
│ selection │ declarations
▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ braincell.cv │ braincell.compute │
│ CV · CVPolicy · │ NodeTree · NodeScheduling · │
│ PaintRule / PlaceRule │ CellRuntimeState · assignment table │
├─────────────────────────────┴────────────────────────────────────────┤
│ braincell._multi_compartment (Cell) │
│ declaration frontend · lazy rebuild · runtime facade │
└──────────────────────────────┬───────────────────────────────────────┘
│ HHTypedNeuron
▼
┌──────────────────────────────────────────────────────────────────────┐
│ braincell.quad │
│ IntegratorRegistry · explicit / implicit / exp_euler / staggered │
│ steps · dhs_voltage_step (branched-cable Hines solver) │
└──────────────────────────────┬───────────────────────────────────────┘
│ DiffEqState
▼
brainstate / JAX execution
┌──────────────────────────────────────────────────────────────────────┐
│ braincell.ion · braincell.channel · braincell.synapse │
│ concrete Ion species (Na, K, Ca) · IonChannel implementations │
│ (Na, K, Ca, Ih, K_Ca, leaky) · Markov synapse models │
└──────────────────────────────────────────────────────────────────────┘
(supply concrete mechanism objects consumed by mech.Density /
mech.Point declarations and installed inside braincell.Cell)
┌──────────────────────────────────────────────────────────────────────┐
│ braincell.vis │
│ 2D / 3D scenes · matplotlib & PyVista backends · │
│ region / locset / value overlays │
└──────────────────────────────────────────────────────────────────────┘
(consumes Branch / Morphology / Cell / RegionExpr / LocsetExpr)
The directional rule of thumb:
io → morph → {filter, mech} → cv → compute → _multi_compartment → quad,
with ion / channel / synapse as peer top-level modules supplying concrete
mechanism implementations that mech wraps into Density (Channel /
Ion) and Point (CurrentClamp, Synapse, Junction, …) declarations
at paint/place time, vis reading anything from morph upward, and
_base providing shared abstract types (HHTypedNeuron, IonChannel,
Ion, Channel, MixIons) for everything below Cell.
Each subsection lists: purpose · key types · public API surface · internal dependencies · status · open work.
- Purpose — owns the canonical in-memory representation of a neuron's
geometry. Splits cleanly into immutable per-branch geometry (
Branch) and a mutable owning tree (Morphology). - Key types
Branch(frozen dataclass) and typed subclassesSoma,Dendrite,Axon,BasalDendrite,ApicalDendrite,CustomBranch. Built viaBranch.from_lengths/Branch.from_points.branch_class_for_type(type_str)factory used by IO readers.Morphology— mutable owning tree, root attachment, attribute-style children (morpho.soma.dendrite = ...),topo()text rendering,branches,edges,branch_by_order.MorphoBranch— node view exposing parent / children navigation.MorphoEdge— frozen, read-only directed edge between twoMorphoBranchnodes.MorphoMetric— frozen snapshot ofn_branches,total_length,total_area,total_volume,max_path_distance,max_euclidean_distance,max_branch_order, range boxes, etc.
- Status
- Branch geometry, area, volume, point/length constructors.
- Morphology root construction,
attach, sugar attribute API, topology queries,topo()text tree. -
Morphology.from_swc/Morphology.from_ascconstructors. -
save_checkpoint/load_checkpoint(.bcmself-contained format) pluspickle/copy.deepcopysupport. -
MorphoMetriccovering total length / area / volume, branch order, path distance, Euclidean distance. - Tree editing primitives: delete subtree, splice subtree, merge two morphologies at a chosen attachment point, swap a branch with another while preserving orientation.
- In-place geometry transforms: translate / rotate / scale / align principal axis, with corresponding metric invalidation.
- Open risks
- Mutability of
Morphologyversus the immutability ofBranch(and downstream caches inCell) makes accidental aliasing easy. Tree-edit operations must follow the existingMorphology.clone()discipline used byCell.
- Mutability of
- Purpose — read morphologies from common neuroscience formats and
produce a
Morphologyplus a structured report describing parsing decisions and validation issues. - Key types
swc.SwcReader,SwcReadOptions,SwcReport,SwcIssueplus rulebook (rules.py) and soma reconstruction (soma.py).asc.AscReader,AscReport,AscIssue,AscMetadata.neuroml2.NeuroMlReader.neuromorphopackage — three-tier NeuroMorpho.Org integration:- Tier 1:
load_neuromorpho(also re-exported asbraincell.load_neuromorpho),fetch_neuromorpho, and theMorphology.from_neuromorphoclassmethod sibling tofrom_swc/from_asc. - Tier 2:
NeuroMorphoClient(typedsearch/iter_search,get_neuron,get_measurement,describe,downloadwithdry_run=True, configurableretries/backoff_base). - Tier 3:
NeuroMorphoCache,NeuroMorphoCacheLayout,NeuroMorphoQuery,NeuroMorphoMeasurement,NeuroMorphoFilePlan,NeuroMorphoUrls,NeuroMorphoCacheStatus,NeuroMorphoSearchPage,NeuroMorphoDetail,NeuroMorphoDownloadItem,NeuroMorphoDownloadRecord,NeuroMorphoNeuron, plus pure URL helpers (build_standard_swc_url,build_original_file_url,infer_original_extension,plan_neuron_files). - Errors:
NeuroMorphoError,NeuroMorphoHTTPError,NeuroMorphoNotFoundError.
- Tier 1:
io.checkpoint—save_branch/load_branch/save_morpho/load_morphoand the.bcmsingle-file format.
- Status
- SWC import + rulebook validation + report.
- [~] ASC import: most Neurolucida trees, metadata, and
Morphology.from_asc(..., return_report=True)work; gaps: spine markers, contour-only somas, and multi-tree files are still handled minimally — seeio/asc/test.pyskips. - NeuroML2 import — reader stub exists; needs cell, segment-group, biophysics decoding and round-trip tests.
- NEURON-based diff harness via
examples/neuron_compare/morph/neuron_diff.py. - NeuroMorpho.Org integration: Tier 1
load_neuromorpho/fetch_neuromorphoone-liners, Tier 2NeuroMorphoClientwith typediter_search/download/ retries, Tier 3NeuroMorphoCacheplus pure URL helpers, full NumPy-doc docstrings, andMorphology.from_neuromorphoclassmethod. Notebook walkthrough atexamples/multi_compartment/neuromorpho.ipynbshows the full search → cache → metric-diff loop. - Automated metric diff against published NeuroMorpho reference statistics promoted from the notebook into a pytest case (so the NeuroMorpho corpus becomes a wide regression net).
- Checkpoint API and
.bcmformat with notebook tutorial (examples/multi_compartment/morphology-checkpoint.ipynb). - NMODL parsing compiler — deferred. The previous
mech/nmodl/research tree has been removed from the working copy; if NMODL support returns it will land as a codegen pass targeting the mechanism registry (see §3.4 / M5 Phase 4).
- Open risks
- Format heterogeneity is the dominant source of bugs. Every reader
must produce a
Reportso user-facing tools can surface issues instead of silently massaging geometry.
- Format heterogeneity is the dominant source of bugs. Every reader
must produce a
- Purpose — declarative, composable selection of regions of a morphology and points on it. The cell layer consumes these to map user intent onto control volumes.
- Key types
RegionExprfamily:BranchSlice,branch_in(...)predicates for branch metadata / topology,branch_range(...)for scalar branch properties and metrics, set operations (union / intersection / difference / complement).LocsetExprfamily: root, branch points, terminals, region-driven uniform sampling, region-driven random sampling.SelectionCache— memoizes resolved index sets for stable Morphology objects.
- Status
- BranchSlice, broadcasted inputs, set algebra.
- Discrete predicates (type / name / branch_order / parent_id / n_children / n_tapers / branch_id).
- Continuous
branch_range(...)with both numeric andQuantitybounds. - Branch scalar metric filters:
length,mean_radius,area,volume. - Radius-range filter (e.g.,
radius_range(0.5*u.um, 2*u.um)). - Path-distance filter (graph distance from soma along the tree).
- Euclidean-distance filter (3-D distance from a chosen anchor point).
- Subtree region — everything reachable below a given branch
or locset; needs to interoperate with the planned
Morphologysubtree-edit operations. - Locset: root, branch points, terminals.
- Locset: uniform / random sampling driven by a region.
- Locset: explicit anchors and fixed-step sampling along a region.
- Open risks
- Distance-based filters require a precomputed
path_distance/euclidean_distancearray per branch; this needs to live onMorphoMetric(or a sibling object) and invalidate cleanly when theMorphologyis edited.
- Distance-based filters require a precomputed
- Purpose — strongly-typed, purely-declarative containers used by
the
Cellfrontend. Everything here describes what to install, not how to integrate: nobrainstate, no JAX, no runtime state. The concrete ion species, ion channels, and synapses live in peer top-level modules (braincell.ion,braincell.channel,braincell.synapse) and register themselves with theMechanismRegistryat import time via class-level decorators; the runtime lowering inbraincell.compute._runtimeresolves aDensity.class_namethrough the registry when it installs channels on a cell. - Key files & types
mech/_base.py—Mechanismmarker base class. Every mechanism declaration (density or point) inherits from it, so consumers can checkisinstance(x, Mechanism)without having to know whether they hold aDensityor aPoint.mech/_registry.py—MechanismEntry(category, name, cls, aliases)frozen dataclass,MechanismRegistrywithregister/unregister/add_alias/contains/get/entry/names/items/clear, the_REGISTRYsingleton accessed viaget_registry(), and the three class-level decoratorsregister_channel/register_ion/register_synapse. Unknown-name lookups raiseKeyErrorwith adifflib-based "did you mean ...?" suggestion (same pattern asbraincell.quad._registry). Three valid categories:"channel","ion","synapse".mech/_params.py—Params(Mapping[str, Any])frozen hashable mapping.__hash__usesfrozenset(self._items.items()), soChannel("IL", g_max=..., E=...)andChannel("IL", E=..., g_max=...)deduplicate into a single paint-layout group. Iteration order is the declared order sorepr()is stable. AcceptsMapping,(k,v)tuples, or anotherParamsin the constructor (Params.coerce(value)), supports**paramsunpacking via theMappingprotocol, and exposes non-mutatingwith_updates(...)/without(...).mech/_density.py—Density(Mechanism)abstract base plus the concrete subclassesChannel(Density)andIon(Density).Densityis a manually-immutable__slots__class (not a dataclass) with acategory: ClassVar[str]discriminator set by each subclass ("channel"/"ion"). The constructor acceptsclass_nameas either a string or a class (braincell.channel.IL); types are resolved to their canonical registry name via reverse lookup.coverage_area_fractionis a dedicated first-class field, not a pseudo-parameter.instance_namefalls back toclass_name,identity = (instance_name, class_name)drives paint-layout grouping, andwith_params(...)/with_coverage(...)/with_name(...)return non-mutating copies via an internalobject.__new__+object.__setattr__bypass.ChannelandIoncollect parameters via**paramskwargs.mech/_point.py—Point(Mechanism)plain base class (not aUnion; useisinstance(x, Point)in consumers) plus concrete frozen-dataclass subclassesCurrentClamp,SineClamp,FunctionClamp,ProbeMechanism, andSynapse.CurrentClamphas one canonical form(start, durations, amplitudes)and aCurrentClamp(delay=..., durations=duration, amplitudes=amplitude)classmethod shortcut.Synapseis itself a frozen dataclass (synapse_type,params,name); there is no separate factory function.mech/_junction.py—Junction(Point)frozen dataclass for gap-junction coupling declarations. Placeholder implementation (paramsfield only); lives in its own module so downstream work on gap-junction state and partner wiring has a clean home.mech/_cable.py—CablePropertyfrozen dataclass (resting_potential,membrane_capacitance,axial_resistivity,temperature, allbrainunitquantities; temperature defaults to 36 °C via adefault_factoryand is coerced to kelvin in__post_init__). Exposes non-mutatingwith_updates(**kwargs).mech/__init__.py— re-exports the public surface (Mechanism,Density,Channel,Ion,Point,CurrentClamp,SineClamp,FunctionClamp,ProbeMechanism,Synapse,Junction,CableProperty,Params, registry API).- Co-located tests:
_base_test.py,_registry_test.py,_params_test.py,_density_test.py,_point_test.py,_junction_test.py,_cable_test.py.
- Status
-
CableProperty,Density(withChannel/Ionsubclasses), and the fullPointfamily (CurrentClamp,SineClamp,FunctionClamp,ProbeMechanism,Synapse,Junction) withbrainunit-typed fields and co-located tests. Everything inherits from a sharedMechanismmarker base class. - One type per concept. The legacy
MechanismSpec/DensityMechanismduality and the eightdensity_*isinstance- dispatch helpers inspec.pyare gone. Every density declaration is aDensitysubclass (ChannelorIon) carrying acategoryClassVar; every point declaration is aPointsubclass. - Class-based
Channel/Ion.braincell.mech.Channelandbraincell.mech.Ionare real classes (not factory functions) inheriting fromDensity. They accept the target class as either a string name ("IL") or the concrete class object (braincell.channel.IL); the class form is reverse-looked-up in the registry to produce the canonical name so aliases continue to collapse into one identity. Top-levelbraincell.Channel/braincell.Ionstill point at the runtime base classes from_base.py; the declaration-layer classes are reached viabraincell.mech.Channel/braincell.mech.Ionto avoid the name collision. - Mechanism registry.
MechanismRegistry+ the@register_channel/@register_ion/@register_synapsedecorators ship inmech/_registry.py. ~49 concrete classes inbraincell.channel,braincell.ion, andbraincell.synapseself-register at import time.get_registry().get(category, class_name)is the single lookup path used bycompute/_runtime.pyto resolveDensity.class_nameinto a runtime class. Channel-to-ion binding is inferred fromissubclass(cls.root_type, Sodium / Potassium / Calcium), not from hardcoded class-name matching. Abstract base classes (LeakageChannel,SodiumChannel,Calcium, …) are deliberately not decorated. - Hash-stable Params.
Params.__hash__usesfrozenset(items)so twoChannel(...)calls with the same parameters in different keyword order compare equal and deduplicate into the same paint-layout group. Onlyparamsis hash-insensitive;class_name,name,category, andcoverage_area_fractionremain position-sensitive. -
coverage_area_fractionas a first-class field onDensity. The old abstraction leak wherecv/_mech._scale_density_for_coveragesmuggled the coverage fraction throughparams["coverage_area_fraction"]andcompute/_runtime._runtime_constructor_paramshad to filter it back out is gone. - Unified
CurrentClamp. One canonical frozen-dataclass form(delay, durations, amplitudes). The oldCurrentClamp(amplitude=, delay=, duration=)compatibility form is gone; useCurrentClamp(delay=..., durations=duration, amplitudes=amplitude). - Consumer simplification.
cv/_mech.py,compute/_runtime.py,compute/_assignment_table.pyhave been rewritten against the new types.mechanism_signaturecollapses to(type(m).__qualname__, m)(structural equality does the rest),_resolve_runtime_channel_specis deleted,mechanism_cell_keyis a straight attribute read. - Parameter-unit validation —
Paramscurrently stores values untyped. Needs compile-time validation that each value carries the brainunit dimension the target channel declares (e.g.g_maxmust be inS/cm²,EinmV), with an error that points at the offendingpaint(...)call. The infrastructure for this lives on the mechanism registry: each entry can declare the expected unit per parameter name. -
Junctionruntime wiring —Junctioncurrently ships as a placeholder frozen dataclass with only aparamsfield. It needs apartnerreference (locset or another placedJunction), symmetric pair resolution in the runtime, and a gap-junction current contribution in the voltage solve. Tracked as the first sub-task in milestone M5 Phase 3. -
ProbeMechanismvariable taxonomy —variableis currently a free-form string. Promote it to a typed enum of known probes ("v","ina","ik","ica","cai","cao", channel gate names, …) so user typos fail at declaration time rather than silently producing empty traces. - Mechanism validation harness — a structured comparison
against NEURON
.modreference traces for every channel inbraincell.channel. The previousmech/mod_validate/tree has been removed from the working copy; the harness needs to be re-introduced as a package underbraincell/mech/(or a sibling test package) and promoted to automated pytest cases. Tracked in milestone M5. - NMODL ingestion — deferred. If NMODL support returns it
must target the mechanism registry so generated channels land
under the standard naming convention in
braincell.channelrather than creating a parallel hierarchy.
-
- Open risks
- Hash-insensitive
Paramsequality only kicks in for theparamsfield;class_name,name,category, andcoverage_area_fractionstay position-sensitive. Do not extend the hash-insensitive treatment to other fields without first understanding the paint-layout grouping contract incv/_mech.py. - Class-level decorator ordering. Registration is a side
effect of importing
braincell.channel/braincell.ion/braincell.synapse. If a user importsbraincell.mechalone (without importing the concrete modules) the registry is empty — by design. The canonical entry points inbraincell/__init__.pyalready import all three, so normal users never see this. - Ion binding inference uses
issubclass(cls.root_type, Sodium/Potassium/Calcium)incompute/_runtime.py. New ion species must either setroot_typeon their channels or we extend the dispatch to walk a lookup table — do not hardcode class-name matching. - Name collision with
_base.Channel/_base.Ion. The declaration-layerChannel/Ionclasses live underbraincell.mech, not at the top level ofbraincell, becausebraincell.Channel/braincell.Ionalready resolve to the runtime base classes from_base.py. Tutorials and user code should use the fully-qualifiedbraincell.mech.Channel/braincell.mech.Ionwhen declaring mechanisms on aCell. - The module is intentionally free of
brainstate/ JAX state — keepingmechpurely declarative makes importingbraincell.mechcheap and keeps the declaration frontend usable even in environments where the numerical runtime is absent. Do not importbrainstate,jax, or any concrete channel/ion/synapse class insidebraincell/mech/. The one permitted dynamic import is inside_density._resolve_class_name, which consults the registry via a lazyfrom ._registry import get_registrylocal import when a user passes a class object instead of a name string.
- Hash-insensitive
- Purpose — the orchestration layer. Three co-operating pieces turn
(Morphology, CVPolicy, paint/place declarations) into a runnable
HHTypedNeuron:braincell.cvowns the pure control-volume layer (geometry + mechanism rules + policies).braincell.computeowns the execution-graph / runtime lowering built on top ofcv.braincell._multi_compartmentowns the publicCell(mutable declaration) andRunnableCell(frozenHHTypedNeuronruntime) classes. The split replaces the old monolithic, dirty-flag-drivenCellwith a one-way pipelineCell → RunnableCelldriven bycell.build().
- Key files
braincell/_multi_compartment/— package.cell.py—Celldeclaration frontend (mutable, no JAX state).runnable.py—RunnableCell(HHTypedNeuron)frozen runtime.build.py—cell.build()lowering pipeline.bridge.py—cv_to_point/point_to_cvscatter/gather.currents.py—total_membrane_currentpipeline (bugs #1/#2 fixed: external current routed throughsum_current_inputs(init=); ambiguous(n_point,)total current rejected).clamp_table.py—ClampActiveTableprecomputed once at build.probes.py—sample_probe(rcell, ...)/sample_probes(rcell).run.py—rcell.run(dt=, duration=)and theRunResultpytree-registered dataclass.*_test.py— one co-located test per source module.
braincell/cv/_discretization.py—CVdataclass plusassemble_cvto materialize the array-of-CVs view.braincell/cv/_geo.py—build_cv_georeduces aMorphology+CVPolicyinto per-CV geometry (length, area, volume, axial conductance, parent index).braincell/cv/_mech.py—PaintRule,PlaceRule, default rules, normalization,init_cv_mech, paint/place application.braincell/cv/_policy.py—CVPolicyABC plusCVPerBranch,MaxCVLen,DLambda,CVPolicyByTypeRule,CompositeByTypePolicy.braincell/_discretization/topology.py—NodeTree,Node,NodeEdge,build_node_tree,locate_node_on_branch.braincell/_compute/scheduling.py—NodeScheduling,build_node_scheduling(DHS grouping for vectorized parent traversal lives here too).braincell/compute/_assignment_table.py—MechanismObjectCell,MechanismObjectTablekeyed bymechanism_cell_key.braincell/compute/_runtime.py—CellRuntimeState,install_cell_runtime,cv_value_vector, midpoint scatter/gather utilities. Tests in_runtime_test.py.
- Status
-
Cell(morpho, cv_policy)declaration entry, morphology snapshotting,paint/placeAPI.Cellis pure declaration — no JAX state and no dirty flags;cell.cvs/cell.n_cvare lazy previews memoized on declaration state. - CV discretization:
CVPolicybase + concrete policies, CV geometry, axial-resistance partitioning across branch joints. - Mechanism mapping: cable paint, density paint, point place lowered into per-CV records.
- NodeTree: compute points, compute edges, attachment handling.
- Scheduling:
NodeScheduling+ DHS grouping for the voltage solver. - Execution layer:
cell.build()lowers a declaration into a frozenRunnableCell(HHTypedNeuron)via_multi_compartment/build.py. The pipeline allocatesV/spikeand per-channel / per-ionDiffEqState, installs aCellRuntimeStatewith a precomputedClampActiveTable, caches the axial operator, and wiresrcell.run(dt=, duration=)on top of thequad/registry (defaultstaggered). Trace recording usesStateProbe/MechanismProbe/CurrentProbesampled viarcell.sample_probe(...)/rcell.sample_probes(). Multi-compartment external current is represented by placed point clamps (CurrentClamp,SineClamp, orFunctionClamp) and converted from total current to point current density through the runtimeClampActiveTable. - Ion/channel scheduling controls.
Cellnow exposescache_ion_total_currentandion_channel_update_order. The current-cache path snapshots per-ion total current at the start of the staggered step, before voltage or ion state advances, which is needed for NEURON-compatible current-driven calcium-pool mechanisms. The update-order knob supports"family"for NEURON-like ion-family ordering and"integration"for the previous integration-grouped behavior. - Same-name channels on different layouts are preserved.
Runtime channel keys now distinguish layout instances, so painting
the same channel class/name onto disjoint regions (for example soma
and dendrite) no longer overwrites
Ion.channels. - PC MA2024 full-cell comparison scaffold.
examples/neuron_compare/cell/pc_ma2024assembles a simplified NEURON PC and a matching BrainCell PC from the same morphology and parameter table. The NEURON side removes manually constructed axon and spine sections so both sides follow the imported ASC/SWC morphology semantics.
-
- Open risks
- The
Cell → RunnableCellarrow is one-way;RunnableCellmust never mutate or consult the originatingCell. Any future convenience that tries to "rebuild in place" would re-introduce the dirty-flag surface that this refactor removed. - The runtime/state shape must remain stable across
jitre-traces — every change to declared mechanisms onCellis allowed to re-trace, but parameter updates on an existingRunnableCellmust not.
- The
- Purpose — provide a uniform registry of step functions over
DiffEqModuletargets, plus the specialized branched-cable voltage solver. - Key types
IntegratorRegistry,IntegratorEntry,register_integrator,get_registry,get_integrator. Decorator-based registration with canonical name, aliases, category, order, description, deprecation._RegistryDictViewexposes a read-onlyall_integratorsmapping for legacy callers.DiffEqModule,DiffEqState,IndependentIntegration— structural protocols and helpers for step functions.- Explicit families:
euler_step,rk2/3/4_step,heun2/3_step,midpoint_step,ralston2/3/4_step,ssprk3_step. - Implicit / mixed:
backward_euler_step,implicit_euler_step,implicit_rk4_step,implicit_exp_euler_step,cn_rk4_step,cn_exp_euler_step,exp_exp_euler_step,splitting_step. - Exponential Euler:
exp_euler_step,ind_exp_euler_step. - Staggered:
staggered_step(DHS voltage solve +ind_exp_eulerfor ion-channel state, the workhorse for full cells). - Voltage solvers:
dhs_voltage_step(DHS branched Hines),dense_voltage_step,sparse_voltage_step.
- Status
- Registry, alias resolution, "did you mean ...?" suggestions.
- Backwards-compatible
all_integratorsmapping view. - All explicit RK / Heun / Ralston / Midpoint / SSPRK families.
- Backward Euler, implicit Euler, implicit RK4, implicit exp Euler, CN variants, splitting, exp-exp Euler.
- Exponential Euler (
exp_euler_step,ind_exp_euler_step). - Staggered solver (
staggered_step). - The staggered full-cell path calls
cache_ion_total_currents(...)when the target supports it, so NEURON-compatible ion-current snapshot semantics can be selected at theCelllevel without changing the integrator API. - DHS voltage solver (
dhs_voltage_step). - Adaptive timestep wrapper that produces a registered integrator from any embedded RK pair.
- Convergence test matrix — pytest-driven order-of-accuracy checks for every registered integrator on a small set of reference ODEs (passive cable, single HH spike, two-branch Y).
- Performance benchmarks vs NEURON / Arbor on the standard
Mainen / Hay / L5PC cells, run nightly via
CI-daily.yml.
- Purpose — render morphologies and cell-level data with both an interactive 3D backend (PyVista) and a static / publication 2D backend (matplotlib), plus a dependency-light Plotly backend for interactive notebook 3D without VTK.
- Key types and files
scene.py— frozen dataclass primitives (Polyline2D,Polygon2D,Circle2D,Label2D,BranchPolyline3D,BranchTypeBatch3D),RenderScene2D/RenderScene3Dcontainers,RenderRequest,OverlaySpec.scene2d.py,scene3d.py— scene builders that strip brainunits (.to_decimal(u.um)) and translate morphology + layout into primitive tuples.plot2d.py,plot3d.py— high-level user entry points.backend.py—RenderBackendProtocol +BackendChooser.backend_matplotlib.py,backend_pyvista.py,backend_plotly.py— concrete backends with lazy optional imports. The matplotlib backend attaches per-artist pick metadata; the PyVista backend attaches a point→branch lookup soenable_point_pickingcan resolve clicks.hooks.py—VisHooks(on_pick=..., on_hover=..., on_leave=...)plus thePickInfopayload delivered to user callbacks (backend-agnostic; wired in both matplotlib and PyVista).export.py— unifiedsave_figure(figure, path, dpi=..., transparent=...)that dispatches on matplotlibAxes/Figure, pyvistaPlotter, or plotlyFigure.compare.py— generalizedcompare_morphologies([m1, m2, ...])andcompare_values(morpho, [values_a, values_b, ...])side-by-side helpers built on top ofplot2d.perf_benchmark_test.py—pytest-benchmarkbaselines for layout build, scene build, and end-to-end plot2d render on 50 / 500 / 2000-branch synthetic morphologies (skipped whenpytest-benchmarkis not installed).layout/— 2D tree-layout engine split across_common.py(shared dataclasses + tree helpers),_geometry.py(pure-numeric sampling and branch construction),_collision.py(spatial-hash collision scoring),_config.py(LayoutConfigfrozen dataclass, the tunable knobs),_cache.py(LayoutCacheLRU keyed on a morphology snapshot plus the layout config),_stem.py/_balloon.py/_radial.py/_legacy.py(layout families), and_dispatch.py(build_layout_branches_2dentry point, cache-aware). Each file ships with a sibling*_test.py.compare2d.py— side-by-side comparison of layout families on the same morphology (legacy, specific to layout-family gallery).config.py—VisDefaultsdataclass singleton plusconfigure_defaults/get_defaults/reset_defaults,theme(**overrides)scoped context manager, andPublicationTheme/publication_theme()which flips both vis defaults and matplotlibrcParamsfor LaTeX-friendly output._values.py— colour-by-values normalisation (per-branch / per-segment / per-centerline-point → per-point scalar arrays) plus :mod:brainunitunit-label extraction.movie.py—plot_movietime-varying colour-by-values animation (matplotlibFuncAnimation+ pyvistaPlotter.open_movie).traces.py—plot_tracesmorphology-synchronized time-series panels.morphometry.py—plot_dendrogram,plot_topology,plot_sholl,plot_branch_order_histogram, and thecompute_sholl_profile/ShollProfilehelpers._test_helper.py—FakeBackendscene-capturing double for unit tests.visual_regression_test.py—pytest-mplbaseline image regression suite (skipped whenpytest_mplis not installed).
- Status
- 3D rendering of
Branch/Morphologywith point geometry, scene composition, PyVista backend. - 2D projected mode driven by real points.
- 2D tree auto-layout.
- 2D frustum auto-layout.
- Stem / balloon / radial360 layout family with matplotlib comparison output.
-
OverlaySpecplumbed end-to-end forregion/locset/values, with per-CV value colormaps, locset scatter markers, and region recolor passes consumed by both backends. -
RenderRequestuses a neutralbackend_optionsmapping; backend-specific kwargs no longer pollute the shared schema. - Backend capability registry via
supported_scene_kinds: frozenset[str]so a future backend can declare multi-format support. -
plot3d(mode="skeleton")fast-preview path (centerline-only, no tube generation) alongside the default"geometry"mode. -
RenderScene2D.draw_orderhonored by the matplotlib backend (primitives sorted by draw_order →zorder=argument). -
braincell.vis.theme(**overrides)context manager for scoped style overrides; tests no longer need manualreset_defaults(). - Shared
vis/_testing.pyhelpers and parametrized layout-family tests covering the shared invariants across stem / balloon / radial_360. -
layout2d.pyrefactor intovis/layout/with separate files for_common.py,_dispatch.py,_stem.py,_balloon.py,_radial.py,_legacy.py,_collision.py,_geometry.py, and a_config.pyholding theLayoutConfigfrozen dataclass (M6 Phase 2). The legacy family now emits aDeprecationWarning, the collision backend uses a 2D spatial hash, andplot2dacceptslayout_config=as an optional user knob. - Color-by-values for 2D and 3D scenes: accept per-branch /
per-segment / per-centerline-point scalars. The matplotlib
backend uses vectorized
LineCollection/PolyCollection(10–50× speedup on dense scenes), the PyVista backend writespolydata.point_data["values"]and callsadd_mesh(scalars=..., cmap=..., scalar_bar_args=...). Proper colorbars with unit labels, plusvmin/vmax/cmap/normsurfaced throughplot2d/plot3d(M6 Phase 3). -
plot_movie— time-varying values over a morphology using matplotlibFuncAnimation(2D) orpyvista.Plotter.open_movie(3D). The 2D path builds the scene once and mutates theLineCollection/PolyCollectionscalar array per frame; the 3D path rewritespolydata.point_data["values"]and writes one frame per timestep. -
plot_traces— stacked time-series panels atlocsetlocations, color-synced with markers on a left-hand morphology view (optional). - Morphometry / topology plots:
plot_dendrogram,plot_topology,plot_sholl(withcompute_sholl_profileandShollProfilehelpers),plot_branch_order_histogram. - Layout caching —
LayoutCacheLRU keyed on a stable morphology snapshot plus theLayoutConfighash. The dispatcher consultsget_default_layout_cache()on every call; callers can pass a scopedcache=LayoutCache(...)or opt out withuse_cache=False. - Visual regression tests via
pytest-mplwith 12 baseline slots underbraincell/vis/_baseline_images/— the whole module skips whenpytest_mplis not installed so the base suite stays dependency-free. - Generalized comparison:
compare_morphologies([m1, m2, ...])andcompare_values(morpho, [values_a, values_b, ...])invis/compare.py(M6 Phase 4). - Interactivity:
VisHooks(on_pick=, on_hover=, on_leave=)+PickInfoinvis/hooks.py. The matplotlib backend attaches per-artist pick metadata and wirespick_event/motion_notify_eventhandlers; the PyVista backend builds a point→branch lookup and callsenable_point_picking(M6 Phase 4). - Plotly backend:
backend_plotly.pyrenders value scenes asScatter3dtraces with per-pointline.color/colorscaleand a shared scalar bar; gated onimportlib.util.find_spec("plotly")so the base install stays dependency-free (M6 Phase 4). - Export polish: unified
save_figure(figure, path, ...)invis/export.pythat dispatches on matplotlibAxes/Figure, pyvistaPlotter, or plotlyFigure;PublicationThemepreset pluspublication_theme()context manager inconfig.pythat flips both vis defaults and matplotlibrcParams(serif font, thicker lines, no grid, print-friendly palette) (M6 Phase 4). - Performance baselines via
pytest-benchmarkinvis/perf_benchmark_test.py— layout build, scene build, and plot2d render on 50 / 500 / 2000-branch synthetic morphologies, skipped when the plugin is absent (M6 Phase 4). - Narrative tutorial:
examples/multi_compartment/vis.ipynb— quick start, layout gallery, styling/themes, color-by-values, overlays, movie, trace panels, morphometry, interactivity, publication export, comparison (M6 Phase 4). - Sphinx autodoc wiring:
docs/apis/vis.rstexposes the whole public surface (plot entry points, morphometry helpers, comparison helpers, hooks, themes, layout engine) throughautosummaryand is linked fromdocs/index.rst(M6 Phase 4).
- 3D rendering of
- Open risks
- The stem layout family still holds the most bug-prone code
(heuristic collision avoidance, the multi-weight scoring
function). After the Phase 2 split it lives in
vis/layout/_stem.pybut remains the largest file in the package. Tuning individual scoring weights now goes throughLayoutConfigrather than editing module-level constants, which makes experiments safer. - Optional dependencies (
matplotlib,pyvista,plotly,pytest-mpl,pytest-benchmark) must stay lazy-imported inside the backend that uses them. The import-time test from §4.5 / risk #5 should grow to assert that none of the heavy optional deps are loaded afterimport braincell.vis. VisHookson the matplotlib backend relies onpick_eventandmotion_notify_event, which only fire with an interactive matplotlib backend. Notebook users should pick a GUI backend (e.g.%matplotlib widget) — the Agg backend used in tests will register the handlers but never deliver events, which the tests explicitly cover.
- The stem layout family still holds the most bug-prone code
(heuristic collision avoidance, the multi-weight scoring
function). After the Phase 2 split it lives in
- Purpose — concrete
Ionsubclasses modelling intra/extracellular concentration, reversal potential, and the container of ion-bearing channels that consume the species'IonInfo. Lives as a peer top-level module (not undermech) because the classes are runtime objects with JAX state, not declarations. - Key files & types
braincell/ion/sodium.py—Sodium(abstract base withroot_type = HHTypedNeuron) andSodiumFixed(constantE,C; parameterised viabraintools.init.param).braincell/ion/potassium.py—Potassiumabstract base andPotassiumFixedwith a customreset_statethat walks attached childChannelnodes.braincell/ion/calcium.py—Calciumbase class,CalciumFixed, the shared_CalciumDynamicsparent, and two concrete dynamics models:CalciumDetailed— Destexhe et al. 1993 thin-shell model with tunabled,tau,C_rest,C0,T.CalciumFirstOrder— Bazhenov et al. 1998 first-order pool (Ca' = α I_Ca − β Ca). Both exposeCas aDiffEqState, compute the Nernst reversalE = (RT/2F) log(C0/C)as a property, and forwardcompute_derivativeto every attachedChannelchild.
- Co-located tests:
sodium_test.py,potassium_test.py,calcium_test.py.
- Status
-
SodiumFixed/PotassiumFixed/CalciumFixedparameter storage, container (**channels) attachment, andpack_info()returning anIonInfo(C, E)tuple. -
CalciumDetailed/CalciumFirstOrderwith Nernst reversal and full derivative wiring to child calcium channels. -
KineticIon-based Cerebellum calcium-pool mechanisms imported for the current comparison work, includingCdpStC_MA2020_GoC,CdpStC_NoCAM_MA2020_GoC,CdpStC_CAMOnly_MA2020_GoC,CdpStC_MA2025_BC,CdpStC_RI2021_SC,CdpCAM_MA2024_PC, andCdpCR_MA2020_GrC. - Co-located unit tests (~75) covering defaults, custom
parameters, callable broadcasts,
init_state/reset_state/compute_derivative,pack_info, external-current registration, Nernst formula edge cases, and child-channel forwarding. -
SodiumDetailed/SodiumFirstOrder— activity- dependent Na⁺ accumulation (e.g., for spike-frequency adaptation driven by a Na/K pump). Parallel to the calcium dynamics pair and needed to reproduce several of the published cortical models inexamples/. -
PotassiumDetailed/PotassiumFirstOrder— activity- dependent intracellular / extracellular K⁺ accumulation for network-level effects and K-pump dynamics, with the same Nernst-reversal property as the calcium path. -
Chlorideion (Chloride,ChlorideFixed,ChlorideDynamics) in a newbraincell/ion/chloride.pyplus a siblingchloride_test.py. Needed for quantitative GABAa modelling and developmental E_Cl shifts. -
_IonDynamicsshared base — the current_CalciumDynamicsincalcium.pyis already a reusable pattern (DiffEqState concentration + Nernst reversal + forwardedcompute_derivative). Lift it to a package-privatebraincell/ion/_dynamics.pyand have the Na / K / Cl dynamics subclass it with their own valence (z) andderivative. -
__init__.pyhygiene —braincell/ion/__init__.pycurrently uses star imports plus_sodium_all/_potassium_all/_calcium_all. Switch to explicit re-exports sofrom braincell.ion import Calciumis a single lookup and so IDEs / Sphinx autodoc can see the symbols without running the star-import dance. - Mechanism-registry plumbing — every concrete
Ionsubclass now self-registers via@register_ion("CalciumFixed")/@register_ion("CalciumDetailed")/@register_ion("CalciumFirstOrder")/@register_ion("SodiumFixed")/@register_ion("PotassiumFixed")at import time, andbraincell.mech.Ion("CalciumFixed")resolves through the registry described in §3.4. - Current-driven ion dynamics can use cached ion current.
Kinetic ions that consume total calcium current can receive the
runtime snapshot created by
cache_ion_total_current=True, matching the NEURON-style separation between channel-current evaluation and ion-state integration. - Consistent external-current registration — audit that
every dynamics class honours
include_external=Truein itsderivative(the existingCalciumDetailed.derivativealready does; the contract must stay alive across future refactors).
-
- Open risks
- Nernst unit trap.
gas_constant * T / (2 * faraday_constant)only resolves to mV when every factor carries its brainunit quantity._CalciumDynamicscaches this factor on__init__; any change to howIon.__init__stores parameters must re-check that the cached constant survivesbrainstate.graphpytree flattening. reset_stateasymmetry.SodiumFixed.reset_stateinherits fromIon, butPotassiumFixed/CalciumFixedoverride it with an explicitcheck_hierarchiescall and_CalciumDynamics.reset_statere-samplesC.valuefrom the stored initializer. The four flavours must stay in sync or one species silently skips its child-channel resets. Covered by the existing lifecycle tests but worth watching every refactor.- Test-side coupling with
braincell.channel. The calcium tests instantiateICaT_HM1992to exercise child-channel forwarding, so a heavy top-level import inbraincell.channelwould drag through the ion suite. Keep the channel package tree-shakable (see §3.9 risks).
- Nernst unit trap.
- Purpose — the library's catalogue of ready-to-use HH-style and
Markov-kinetics ion channels. Every class is a subclass of
Channelfrom_base.py(so every instance is anIonChannelthat registers its gate state asDiffEqStates) and declaresroot_type = HHTypedNeuron. Channels are container children of anIonspecies or of aSingleCompartment/Celldirectly. - Key families
braincell/channel/sodium.py—SodiumChannelbase, theINa_p3q_markovtemplate (m³h), concrete modelsINa_Ba2002/INa_TM1991/INa_HH1952, and the resurgent-NaINa_Rsgwhich opts intoIndependentIntegrationbecause its Markov state is integrated with its own step inside the staggered solver.braincell/channel/potassium.py—PotassiumChannelbase, the delayed-rectifier templateIK_p4_markovwith concrete DR models (IKDR_Ba2002,IK_TM1991,IK_HH1952), A-type templates (IKA_p4q_sswithIKA1_HM1992/IKA2_HM1992), a second A-type (IKK2_pq_sswithIKK2A_HM1992/IKK2B_HM1992), the non-inactivatingIKNI_Ya1989, the potassium leakIK_Leak, and a Kv sub-type family (IKv11_Ak2007,IKv34_Ma2020,IKv43_Ma2020,IKM_Grc_Ma2020,IK_Kv_test).braincell/channel/calcium.py—CalciumChannelbase, the non-specificICaN_IS2008, the shared_ICa_p2q_ss/_ICa_p2q_markovtemplates, T-type models (ICaT_HM1992,ICaT_HP1992), high-threshold T (ICaHT_HM1992,ICaHT_Re1993), L-type (ICaL_IS2008), and the Ma et al. 2020 Cav family (ICav12_Ma2020,ICav13_Ma2020,ICav23_Ma2020,ICav31_Ma2020,ICaGrc_Ma2020).braincell/channel/leaky.py—LeakageChannelbase and the passive leakIL(g_L (E_L − V)).braincell/channel/hyperpolarization_activated.py—Ih_HM1992plus the Ma 2020 pair (Ih1_Ma2020,Ih2_Ma2020).braincell/channel/potassium_calcium.py—KCaChannelbase, calcium-activated afterhyperpolarizationIAHP_De1994, and the SK / IK / BK-type models from Ma et al. 2020 (IKca3_1_Ma2020,IKca2_2_Ma2020,IKca1_1_Ma2020). These consume anIonInfofrom the attached calcium ion alongside the membrane voltage.
- Status
- ~30 concrete channel classes across six families, named by
the convention
I<species><mechanism>_<Author><Year>, all documented with NumPy-doc docstrings. - Co-located unit tests (~170 across
sodium_test.py,potassium_test.py,calcium_test.py,leaky_test.py,hyperpolarization_activated_test.py,potassium_calcium_test.py) covering steady-state gates at reference voltages, current sign / shape, andreset_stateidempotency. - Mechanism-registry plumbing — every concrete
Channelsubclass (~40 classes across the six channel submodules) now self-registers via@register_channel("INa_Ba2002")at import time, with optional aliases (ILexposes"leaky"). The(category="channel", class_name="...")lookup in §3.4 resolves throughget_registry(). Abstract bases (SodiumChannel,PotassiumChannel,CalciumChannel,LeakageChannel,KCaChannel) are deliberately not decorated. - PC MA2024 channel set imported. Sodium, potassium,
calcium, calcium-activated potassium, and HCN PC variants have been
added and covered by targeted tests. The calcium channel set also
includes
_Frozenvariants for the NEURON-comparison path where the current expression must treat voltage as fixed with respect to differentiation. - Parameter metadata — each channel should declare the
unit of every user-facing parameter (
g_maxinS/cm²,EinmV, time constants inms, …) so thatDensity.paramsvalidation can produce an actionable error at paint time rather than an opaque JAX trace failure. Store the per-parameter unit onMechanismEntry.metadataand consult it duringDensity.__init__. - GHK current formulation — calcium channels currently
compute
I = g_max * p^a * q^b * (E − V)with a Nernst-derivedE. Add an opt-in GHK (Goldman–Hodgkin–Katz) formulation as a mixin so Cav channels can switch to the thermodynamically more accurate form without duplicating gate state. - Q10 temperature scaling audit — a handful of channels
(e.g.,
IKA_p4q_ss) already apply a Q10 factor while others do not. Normalise temperature handling via a shared helper in_base.pyand document the default Q10 per family. - NEURON
.modvalidation — for every channel in the catalogue, compare voltage-clamp and current-clamp traces against the reference.modimplementation within a tight tolerance. Requires re-introducing themech/mod_validate/harness (see §3.4) and wiring it into milestone M5. - Chloride channels — add a
braincell/channel/chloride.pymodule oncebraincell.ion.Chloridelands, covering the passive leak plus GABAa-reversal-driven phasic conductance. - Stiff-channel integrator audit —
INa_Rsgalready opts out of the default exponential-Euler path viaIndependentIntegration. Run the M7 convergence matrix over every channel to confirm none of the others silently need the same escape hatch. - Gate-variable naming convention — most channels use
p/qfor activation / inactivation and a handful use bespoke names (m,h,n,s, …). Tests already rely on thep/qconvention; unifying the rest will need a deprecation path because downstream code reaches intochannel.p.value.
- ~30 concrete channel classes across six families, named by
the convention
- Open risks
- Import cost. The package has thirty-plus classes and pulls
braintools.init,brainunit, andjax.numpyat import time. New families should stay in their own module so the package remains tree-shakable, and should avoid importing numpy at module top level beyond what is already there. - Cross-ion channels.
potassium_calcium.pychannels depend on the attached calcium pool'sCstate. Compile-time checks that the parentCellactually has a calcium ion attached would prevent silentKeyError/AttributeErrorat simulate time; this belongs on the mechanism registry in §3.4. - API drift vs NEURON naming. Upstream
.modfiles use lowercase suffixes (ih,ik,ikdr); BrainCell usesI<Species><Mechanism>_<Author><Year>. Any validation harness needs a stable alias table so the diff does not become a renaming exercise every time a new channel lands.
- Import cost. The package has thirty-plus classes and pulls
_base.py—HHTypedNeuron,IonChannel,Ion,IonInfo,Channel,MixIons,mix_ions. These are the abstract building blocks every concrete cell composes._single_compartment.py—SingleCompartment, the simplest concrete neuron, used as a sanity surface and example._multi_compartment/—Cell(mutable declaration) andRunnableCell(frozenHHTypedNeuronruntime produced bycell.build()), composed withcv+compute(see §3.5)._misc.py—normalize_param(the brainunit gatekeeper), helpers, decorators (set_module_as,deprecation_getattr),Container._typing.py— type aliases (Initializer,ArrayLike,T,DT).
brainunit is non-negotiable. Every public API that takes a physical
quantity routes through _misc.normalize_param, which rejects bare
numerics with TypeError. New modules must:
- accept inputs as
python_number/np.ndarray/jax.Array * brainunit_unit; - store quantities in canonical SI units internally;
- expose values back to users with units attached, never raw floats.
Branch,CV,MorphoEdge,MorphoMetric,IntegratorEntry,PaintRule,PlaceRule,CablePropertyare frozen dataclasses.Morphologyis mutable butCellalways works on aclone()of the morphology it was given. Tree-edit operations (planned in §3.1) must preserve this so thatCellrebuild flags can stay correct.IntegratorRegistryis the single mutable global; entries are added at import time via decorators and never mutated afterwards.
Cell is intentionally cheap to construct and mutate, and owns
no JAX state. All runtime state is produced by cell.build(),
which returns a frozen RunnableCell. The expected sequence is:
Cell(morpho, policy) # declaration only
→ cell.paint(region, density_mech) # cheap, mutates declaration
→ cell.place(locset, point_mech) # cheap, mutates declaration
→ rcell = cell.build() # lower into frozen RunnableCell
→ rcell.run(dt=..., duration=...) # JIT and step (returns RunResult)
Calling cell.build() twice produces two independent runnables. The
Cell remains safe to mutate afterwards; the RunnableCell never
consults or mutates the originating Cell. Execution-layer work must
preserve this one-way arrow — no dirty-flag state machine, no
in-place "rebuild on next use".
- pytest with
unittest.TestCase; tests live next to source as*_test.py(exception:io/swc/test.py,io/asc/test.py). conftest.pyforcesJAX_PLATFORMS=cpuandMPLBACKEND=Agg.- IO test fixtures live in
examples/multi_compartment/morpho_files/. - New code is expected to ship with co-located tests and to keep per-module test runtime under a few seconds on CPU.
- All public classes / methods / functions use NumPy-style docstrings (see CLAUDE.md for the canonical template).
- Examples must be
.. code-block:: pythonblocks compatible with doctest. - High-level narrative documentation lives under
docs/; design notebooks live underexamples/multi_compartment/.
| Layer | Type | Mutability | Lifetime | Owner |
|---|---|---|---|---|
| Geometry | Branch, Soma, Dendrite, ... |
frozen | per-build | user / IO reader |
| Geometry | Morphology |
mutable tree | until edited | user |
| Geometry view | MorphoBranch, MorphoEdge |
frozen view | follows tree | Morphology |
| Metrics | MorphoMetric |
frozen snapshot | recomputed on demand | Morphology |
| Selection | RegionExpr, LocsetExpr |
frozen expression | reusable | user |
| Selection cache | SelectionCache |
mutable | per-Morphology | filter layer |
| Mechanisms | CableProperty, Density (Channel, Ion), Point* (CurrentClamp, Synapse, Junction, …) |
frozen dataclass / slots | declaration | user |
| Mechanisms | Ion, Channel, IonChannel, MixIons |
hybrid (JAX state) | per-runnable | RunnableCell |
| Discretization | CV |
frozen | built at cell.build() |
Cell (preview) / RunnableCell |
| Discretization | PaintRule, PlaceRule |
frozen | declaration | Cell |
| Topology | NodeTree, Node, NodeEdge |
frozen | built at cell.build() |
RunnableCell |
| Scheduling | NodeScheduling |
frozen | built at cell.build() |
RunnableCell |
| Runtime | CellRuntimeState, ClampActiveTable |
brainstate-managed | per-step | RunnableCell |
| Numerics | IntegratorEntry |
frozen | process lifetime | IntegratorRegistry |
| Numerics | DiffEqState, IndependentIntegration |
brainstate-managed | per-step | step function |
The list below is the intended stable surface. Anything not on it is internal and may change without deprecation.
- Morphology layer:
Branch,Soma,Dendrite,Axon,BasalDendrite,ApicalDendrite,CustomBranch,branch_class_for_type,Morphology,MorphoBranch,MorphoEdge,MorphoMetric. TheMorphologyclass also exposes thefrom_swc/from_asc/from_neuromorphoclassmethod constructors. - External-data entry points (top-level re-exports):
load_neuromorpho. Tier-2 / Tier-3 NeuroMorpho.Org symbols (NeuroMorphoClient,NeuroMorphoCache,NeuroMorphoQuery,NeuroMorphoMeasurement,NeuroMorphoError, …) live underbraincell.io.neuromorphoandbraincell.io. - Filter layer:
RegionExpr,LocsetExpr,SelectionCache. - Mechanism declaration layer (
braincell.mech):Mechanism(marker base),CableProperty,Density(and its concrete subclassesChannel/Ion, which accept the target as either a string or a class object),Point(and its concrete subclassesCurrentClamp,SineClamp,FunctionClamp,ProbeMechanism,Synapse,Junction), the frozenParamsmapping, and the registry API (MechanismRegistry,MechanismEntry,get_registry,register_channel,register_ion,register_synapse). - Ion species (
braincell.ion):Sodium,SodiumFixed,Potassium,PotassiumFixed,Calcium,CalciumFixed,CalciumDetailed,CalciumFirstOrder. - Ion channels (
braincell.channel): everyI<Species>*concrete class exported from the six channel submodules (sodium, potassium, calcium, leaky, hyperpolarization_activated, potassium_calcium). Base classes (SodiumChannel,PotassiumChannel,CalciumChannel,LeakageChannel,KCaChannel) are public for subclassing. - Synapses (
braincell.synapse):AMPA,GABAa,NMDAfromsynapse.markov. - Cell layer:
Cell,RunnableCell,RunResult,CV,CVPolicy,CVPerBranch,MaxCVLen,DLambda,CVPolicyByTypeRule,CompositeByTypePolicy,NodeTree,NodeScheduling. - Numerics layer:
register_integrator,get_integrator,get_registry,IntegratorEntry,IntegratorRegistry,all_integrators, every*_stepfunction listed inbraincell/quad/__init__.py::__all__,DiffEqModule,DiffEqState,IndependentIntegration. - Neuron base:
HHTypedNeuron,IonChannel,Ion,IonInfo,Channel,MixIons,mix_ions,SingleCompartment. - Visualization: top-level
braincell.vis.plot2d/plot3dentry points (the imperative scene API stays internal until it stabilizes).
import braincell
import brainunit as u
morpho, report = braincell.Morphology.from_swc("cell.swc", return_report=True)
print(morpho.topo())
print(morpho.metric) # MorphoMetric snapshot
soma_region = braincell.RegionExpr.by_type("soma")
distal_region = braincell.RegionExpr.branch_range(50 * u.um, None)import braincell.mech as mech
cell = braincell.Cell(morpho, cv_policy=braincell.DLambda(0.1))
cell.paint(
braincell.RegionExpr.everywhere(),
mech.CableProperty(
membrane_capacitance=1.0 * (u.uF / u.cm ** 2),
axial_resistivity=100.0 * (u.ohm * u.cm),
resting_potential=-65 * u.mV,
),
)
cell.paint(soma_region, mech.Ion("SodiumFixed"))
# mech.Channel / mech.Ion accept either a registry name string or the
# concrete class itself — both route through the mechanism registry.
cell.paint(soma_region, mech.Channel(braincell.channel.INa_Ba2002, g_max=0.12 * u.S / u.cm ** 2))
cell.place(
braincell.LocsetExpr.root(),
mech.CurrentClamp(delay=10 * u.ms, durations=50 * u.ms, amplitudes=0.2 * u.nA),
)
cell.place(braincell.LocsetExpr.terminals(), mech.ProbeMechanism("v"))rcell = cell.build() # frozen RunnableCell(HHTypedNeuron)
result = rcell.run(dt=0.025 * u.ms,
duration=100 * u.ms)
braincell.vis.plot2d(rcell, values=result.traces["soma(0.5)_v"][-1])cell.build() lowers the declaration into a frozen RunnableCell —
all subsequent runtime inspection (rcell.layouts,
rcell.node_tree(), rcell.sample_probes(), rcell.current_time,
rcell.get_state(...)) lives on the runnable, not on Cell.
braincell.vis.compare2d(morpho_a, morpho_b, layout="frustum")| Package | Floor | Role |
|---|---|---|
python |
3.11 | language; tested 3.11–3.14 |
jax |
recent | autodiff, vmap, jit, GPU/TPU |
brainunit |
>= 0.0.8 | units (mandatory at every API boundary) |
brainstate |
>= 0.2.0 | stateful simulation framework |
brainevent |
>= 0.0.7 | sparse event / CSR ops |
braintools |
>= 0.1.0 | brain modeling utilities |
brainpy |
>= 2.7.5 | brain dynamics library |
numpy |
>= 1.15 | arrays |
scipy |
recent | scientific helpers |
pyvista |
optional | 3D visualization backend |
matplotlib |
optional | 2D visualization backend |
NEURON |
dev only | reference comparator under examples/multi_compartment/ |
Optional dependencies must be lazily imported so the base install
stays small — use importlib.util.find_spec plus PEP 562
__getattr__ for the visualization backends.
- CV (control volume) — atomic spatial unit produced by the discretization layer; the array-of-CVs is what the integrator sees.
- CV policy — rule that turns a
Branchinto a sequence of CVs (e.g.,DLambda(0.1),MaxCVLen(10*u.um),CVPerBranch(n)). - Paint — install a distributed mechanism (cable or density)
onto a
RegionExpr. - Place — install a point mechanism (clamp, probe, synapse,
gap junction) onto a
LocsetExpr. - DHS — Dependent Hines Solver: parent-pointer-driven elimination
ordering used by
dhs_voltage_step, designed to vectorize the classic Hines solver across batched cells. - Staggered step — split integrator that solves the voltage system implicitly (DHS) and the gating variables with exponential Euler in alternating half-steps.
.bcmfile — BrainCell Morphology, the self-contained checkpoint format produced byio/checkpoint.py.