Skip to content

Implement SONATA condition modifications#63

Merged
darshanmandge merged 10 commits intomainfrom
condtions_modifications
Mar 9, 2026
Merged

Implement SONATA condition modifications#63
darshanmandge merged 10 commits intomainfrom
condtions_modifications

Conversation

@darshanmandge
Copy link
Collaborator

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)

Type Description
ttx Block Na channels via TTXDynamicsSwitch
configure_all_sections Apply a statement to all sections of target cells
section_list Apply to a named section list (somatic, basal, apical, axonal)
section Apply to specific named sections (e.g. dend[0])
compartment_set Apply to segments defined by a compartment set file

Modifications are:

Graceful Skipping & Logging

Modifications skip sections/cells that don't match rather than failing:

  • Sections missing a referenced attribute are silently skipped
  • Zero-match cases log a WARNING
  • Invalid section_configure syntax raises ValueError

New: Generic Mechanism Handling

  • Replaced the hardcoded MechanismConditions fields (minis_single_vesicle, init_depleted) with a generic mechanisms: dict in the Conditions dataclass.
  • 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

Example

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 raising ValueError.

Dependencies

  • Requires libsonata>=0.1.34 (adds snake_case modification type enums)

- 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
@darshanmandge darshanmandge self-assigned this Feb 16, 2026
@codecov
Copy link

codecov bot commented Feb 16, 2026

Codecov Report

❌ Patch coverage is 97.78052% with 18 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
bluecellulab/circuit_simulation.py 73.77% 16 Missing ⚠️
bluecellulab/circuit/config/sections.py 97.77% 1 Missing ⚠️
...ellulab/circuit/config/sonata_simulation_config.py 94.44% 1 Missing ⚠️
Files with missing lines Coverage Δ
bluecellulab/circuit/config/definition.py 100.00% <100.00%> (ø)
bluecellulab/simulation/modifications.py 100.00% <100.00%> (ø)
bluecellulab/simulation/neuron_globals.py 96.29% <100.00%> (+0.46%) ⬆️
tests/test_circuit/test_simulation_config.py 100.00% <100.00%> (ø)
tests/test_simulation/test_modifications.py 100.00% <100.00%> (ø)
tests/test_simulation/test_neuron_globals.py 100.00% <100.00%> (ø)
bluecellulab/circuit/config/sections.py 93.07% <97.77%> (+3.42%) ⬆️
...ellulab/circuit/config/sonata_simulation_config.py 97.40% <94.44%> (-0.46%) ⬇️
bluecellulab/circuit_simulation.py 85.20% <73.77%> (-0.19%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

raise BluecellulabError(
"instantiate_gids() is called twice on the "
"same CircuitSimumation, this is not supported")
"same CircuitSimumation, this is not supported"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: CircuitSimulation

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Fixed in the latest commit.

"""Base class for all modification types."""

name: str
type: str
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth normalizing to lowercase before matching?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed cell_applied to sections_applied in all functions.

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be misunderstanding, but it looks like we replace

. with sec. in the full string rather than isolating the statements for that section. If we references multiple sections (e.g. apic[10], dend[3]), would we still execute the other statements when applying to one section?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much clearer, thanks!

"======================================================================\n",
"\n",
"Cell CellId(population_name='NodeA', id=0):\n",
"soma[0] cm = {soma.cm}\n",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small bug: the variable here and below were not resolved, did you run the cell with f-string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was from an old run. I ran again, and I can see the values now.

"# Simulation WITHOUT modifications (baseline)\n",
"baseline_config = Path(\"sim_quick_scx_sonata_multicircuit\") / \"simulation_config_noinput.json\"\n",
"sim_baseline = CircuitSimulation(baseline_config)\n",
"sim_baseline.instantiate_gids(all_cell_ids, add_stimuli=False, add_synapses=False)\n",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why setting add_stimuli to false?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To check changes in baseline potentials.

Copy link
Collaborator

@ilkilic ilkilic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@darshanmandge darshanmandge merged commit 614b1d5 into main Mar 9, 2026
8 checks passed
@darshanmandge darshanmandge deleted the condtions_modifications branch March 19, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants