Conversation
- Add support for all 5 SONATA modification types: ttx, configure_all_sections,
section_list, section, and compartment_set
- Replace hardcoded MechanismConditions with generic mechanisms dict in Conditions
- Parse modifications from libsonata via SonataSimulationConfig.get_modifications()
- Apply modifications automatically during CircuitSimulation.instantiate_gids()
- Handle empty reports dict gracefully (libsonata returns None for reports: {})
- Add 40 unit tests for modifications with 100% coverage on modifications.py
- Require libsonata>=0.1.34 for snake_case modification type enums
- Add example notebook demonstrating all 5 modification types
bluecellulab/circuit_simulation.py
Outdated
| raise BluecellulabError( | ||
| "instantiate_gids() is called twice on the " | ||
| "same CircuitSimumation, this is not supported") | ||
| "same CircuitSimumation, this is not supported" |
There was a problem hiding this comment.
Thanks! Fixed in the latest commit.
| """Base class for all modification types.""" | ||
|
|
||
| name: str | ||
| type: str |
There was a problem hiding this comment.
Small thought: since the subclass already implies the modification kind, having a free type: str here means the instance can technically end up inconsistent with its class (e.g. a ModificationTTX whose type is "section"). Maybe we could make type fixed per subclass (or not part of the constructor) so it can’t drift?
There was a problem hiding this comment.
Good idea. I removed the free type: str from ModificationBase and explicitly defined it as a fixed Literal in each subclass (e.g., type: Literal["ttx"] = "ttx").
|
|
||
| def modification_from_libsonata(mod) -> ModificationBase: | ||
| """Convert a libsonata modification object to a BlueCelluLab dataclass.""" | ||
| type_name = mod.type.name # e.g. "ttx", "configure_all_sections", etc. |
There was a problem hiding this comment.
Maybe worth normalizing to lowercase before matching?
There was a problem hiding this comment.
Yes, thanks. I updated to use mod.type.name.lower().
| n_sections = 0 | ||
| for cell_id in target_cell_ids: | ||
| cell = cells[cell_id] | ||
| cell_applied = 0 |
There was a problem hiding this comment.
cell_applied reads a bit like a boolean but it actually counts how many sections in the cell were affected. Maybe something like sections_applied / sections_modified would make the later > 0 check clearer?
There was a problem hiding this comment.
Renamed cell_applied to sections_applied in all functions.
|
|
||
| # Extract list name from section_configure: e.g. "apical.gbar = 0" -> "apical" | ||
| # The format is "<list_name>.attr = value [; <list_name>.attr = value ...]" | ||
| match = re.match(r"^(\w+)\.", mod.section_configure) |
There was a problem hiding this comment.
From the SONATA spec it looks like a section_list entry should reference a single list. Since this only inspects the first prefix, could a snippet that accidentally mixes lists (e.g. apical, basal) pass here and only fail later during exec? Would it be worth checking all statements use the same prefix and raising a clearer config error?
There was a problem hiding this comment.
could a snippet that accidentally mixes lists (e.g. apical, basal)
Just to understand what you meant: you meant checking for entries such as apical.basal.gbar_NaTg =2 or apicalbasal.gbar_NaTg =2 or something else?
There was a problem hiding this comment.
I meant semicolon separated statements using different lists, e.g. apical.gbar_NaTg = 0.0; basal.cm = 1. Right now we only rewrite the first prefix so the basal. part would remain and likely fail during exec. Maybe we should either support mixed prefixes or detect them and raise a clearer error?
There was a problem hiding this comment.
Thanks for pointing that out. The SONATA documentation notes that the section_lists modification has the same section_list per modification entry in section_configure. So, this "apical.gbar_NaTg = 0.0; apical.cm = 1" is acceptable. The code will now raise an error for mixed prefixes for your example, apical.gbar_NaTg = 0.0; basal.cm = 1 as intended.
| section_configs: dict[str, tuple[str, set[str]]] = {} | ||
| for sec_name in section_names: | ||
| escaped = re.escape(sec_name) | ||
| config_str = re.sub(escaped + r"\.", "sec.", mod.section_configure) |
There was a problem hiding this comment.
I might be misunderstanding, but it looks like we replace
There was a problem hiding this comment.
I updated the code to only support one section per modification, as per the SONATA spec. Can you please check again?
|
|
||
| # Parse section_configure — bare format: "attr = value" | ||
| # Prefix with "seg." to make it executable | ||
| config_str = re.sub(r"(\b\w+)\s*=", r"seg.\1 =", mod.section_configure) |
There was a problem hiding this comment.
Small question: we build config_str with the regex rewrite but then overwrite it below by prefixing each full statement. Is the first transformation intentionally unused?
There was a problem hiding this comment.
Removed the confusing section_configs dict loop and the unused regex. Since the SONATA spec requires section modifications to reference only one section, the multi-section was not needed.
| pre_gids = None | ||
|
|
||
| # if pre_spike_trains take int as key then convert to CellId | ||
| normalized_pre_spike_trains: dict[CellId, Iterable] | None = None |
Summary
Implement SONATA condition modifications in BlueCelluLab, supporting all five modification types defined in the SONATA-extension specification.
Changes
New: SONATA Modifications (bluecellulab/simulation/modifications.py)
ttxTTXDynamicsSwitchconfigure_all_sectionssection_listsectiondend[0])compartment_setModifications are:
conditions.modificationsof the SONATA simulation configcreation, but before synapses/stimuli (matching neurodamus ordering)
Graceful Skipping & Logging
Modifications skip sections/cells that don't match rather than failing:
section_configuresyntax raisesValueErrorNew: Generic Mechanism Handling
MechanismConditionsfields (minis_single_vesicle,init_depleted) with a genericmechanisms: dictin theConditionsdataclass.set_global_condition_parameters()now iterates the dict and sets NEURON globals dynamically, supporting any mechanism parameter without code changes.This matches neurodamus behavior and is essential for heterogeneous node sets.
Files Changed
bluecellulab/circuit/config/sections.py— Modification dataclasses, generic mechanisms dictbluecellulab/circuit/config/definition.py— get_modifications() in SimulationConfig protocolbluecellulab/simulation/neuron_globals.py— Generic mechanism globals applicationlibsonata>=0.1.34Example
New notebook examples/2-sonata-network/sonata-modifications.ipynb demonstrates all modification types.
Bug Fix: Empty Reports Dict
get_report_entries() now handles
None(returned by libsonata/bluepysnap for"reports": {}) gracefully instead of raisingValueError.Dependencies
libsonata>=0.1.34(adds snake_case modification type enums)