Skip to content

Refactor GID handling and synapse seeding to align with Neurodamus#73

Open
ilkilic wants to merge 9 commits intomainfrom
neurodamus-gid-alignment
Open

Refactor GID handling and synapse seeding to align with Neurodamus#73
ilkilic wants to merge 9 commits intomainfrom
neurodamus-gid-alignment

Conversation

@ilkilic
Copy link
Copy Markdown
Collaborator

@ilkilic ilkilic commented Mar 20, 2026

Refactored GID handling and synapse seeding to make simulations deterministic and consistent with Neurodamus. Global GIDs are now computed from SONATA node populations (1-based with 1000 block offsets). This introduces a breaking change: post_gid is now required when creating synapses.

@ilkilic ilkilic self-assigned this Mar 24, 2026
@ilkilic ilkilic requested a review from darshanmandge March 24, 2026 16:08
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 97.16981% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
bluecellulab/circuit_simulation.py 95.00% 2 Missing ⚠️
bluecellulab/synapse/synapse_types.py 90.00% 1 Missing ⚠️
Files with missing lines Coverage Δ
bluecellulab/cell/core.py 78.96% <100.00%> (+78.96%) ⬆️
bluecellulab/circuit/circuit_access/definition.py 94.44% <100.00%> (+94.44%) ⬆️
...ab/circuit/circuit_access/sonata_circuit_access.py 98.26% <100.00%> (+98.26%) ⬆️
bluecellulab/circuit/gid_resolver.py 100.00% <100.00%> (ø)
bluecellulab/connection.py 100.00% <100.00%> (+100.00%) ⬆️
bluecellulab/synapse/synapse_factory.py 90.80% <ø> (+90.80%) ⬆️
tests/test_circuit_simulation_mpi.py 96.00% <100.00%> (+78.66%) ⬆️
tests/test_synapse/test_synapse_factory.py 100.00% <100.00%> (+67.21%) ⬆️
bluecellulab/synapse/synapse_types.py 83.02% <90.00%> (+83.02%) ⬆️
bluecellulab/circuit_simulation.py 84.97% <95.00%> (+84.97%) ⬆️

... and 110 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.


If projections is None, all the synapses are extracted.
"""
"""Extract the synapses."""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is the behavior of this method changed? IF not, why change the description?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes we changed the behavior so that if projections is None, we don't add any.

self.source_popid, self.target_popid = popids

self.pre_local_id = int(self.syn_description[SynapseProperty.PRE_GID])
self.pre_gid = int(self.syn_description[SynapseProperty.PRE_GID])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this line can be removed I believe if we are using pre_local_id

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Actually I think it is better to keep pre_gid and remove pre_local_id since pre_gid is used everywhere.

if (
user_pre_spike_trains is not None
and pre_gid in user_pre_spike_trains
user_pre_spike_trains is not None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is overindented, no?

single integer GID across all ranks. In SONATA circuits, node ids are only
unique *within* a population, so we combine (population_name, node_id) into a
single integer:
if self.gids is None:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you add dosctrings to your new functions please?

else:
prev_count = int(max_raw[prev]) + 1
end_prev = pop_offset[prev] + prev_count
pop_offset[p] = ((end_prev + 999) // 1000) * 1000
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why 1000 here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It is the value that Neurodamus use to group each population to a 1000 sized block to keep gids non-overlapping.

Comment on lines +22 to +23
def global_gid(self, pop: str, local_id: int) -> int:
return int(self.pop_offset[pop]) + int(local_id) + 1 # 1-based indexing to mirror Neurodamus synapse seeding implementation
Copy link
Copy Markdown

@WeinaJi WeinaJi Mar 25, 2026

Choose a reason for hiding this comment

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

I understand the intention of +1 here to match the Neurodamus synapse seeding. However, the name global_gid makes me confused that the gid is now 1-based. Neurodamus is using 0-based gids, the +1 is only for synapse seeding.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ah good point, I will move the +1 out of global_gid and apply it only in the seeding.

rng_settings = RNGSettings.get_instance()
if rng_settings.mode == "Random123":
self.randseed1 = self.post_cell_id.id + 250
self.randseed1 = self.post_gid + 250
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Continuing my previous comment about global_gid. If it is only used here, can we use instead self.randseed1 = self.post_gid (0-based) + 251 ?
And what is the different between self.post_cell_id.id and self.post_gid ?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe we don't need the new self.post_gid , but derive from self.post_cell_id.id ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I have added the +1 here, thank you!

post_cell_id.id is the node id from sonata (local to a population), while post_gid is the globally unique id following the Neurodamus convention.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Then could you just pass the population offset instead of post_gid into this function?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I would prefer to keep the gid computation higher up where the namespace is available and just pass the final value down. We don’t really have the population offset here and the synapse would still need to rebuild the gid. I also refactored a bit to avoid passing post_gid through a long chain and keep it on the cell.

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