Skip to content

Commit 7b075d0

Browse files
cattabianiNghiV1412
authored andcommitted
coreneuron: test reports (mostly compartment and summation) (#337)
## Context This should finally take care of the compartment sets for coreneuron Fix: #345 #322 testing for: neuronsimulator/nrn#3507 neuronsimulator/nrn#3542 ## Scope - [x] rework report handling (mostly in node and target_manager this time. i.e. removal of `_report_setup`) - [x] new `CoreReportConfig` to finally handle in a proper way `report.conf` - [x] remove `write_sim_config`, `update_report_conf`, `write_report_conf` and similar functions in `CoreConfig`. Use `CoreSimulationConfig` and `CoreReportConfig` instead - [x] move `cell-permute` in another issue/pr. EDIT done: #367 - [x] scaling moved to `report_parameters.py` - [x] `CumulativeError` moved in `pyutils` - [x] `ReportParams` now has its own file alleviating the burden of the `node.py` file. `_report_build_params` (previously in `node.py`) is also inside the new file. - [x] `target_type` is no more. We do not need encoding/decoding. We just write `sections` and `compartments` in the `report.conf` - [x] streamlined `enable_reports`. It is still a complex function but it should be easier to read and with less edge cases - [x] use the decorator `@cache_errors` to just fill a `CumulativeError` instead of raising an error and raise it at the end - [x] `merge_dicts` (in `tests/utils.py`) allows for overrides and suppression of sections from the child dict with the special keywords: `override_field` and `delete_field` ### `report.conf` changes - [x] `type_name` has been split into `sections` and `compartments`. No need to encode everything in an enum to decode later. This simplifies code and makes the `report.conf`. The code is slightly slower `0(1)` in a part of the code that was not performance-critical - [x] `scaling` added at the end of the report line (after buffer size) - [x] add 2 additional binary lines in case of a compartment set report to pass the exact positions of the compartment sets. They are necessary if and only if the report type is `compartment_set`. Having them when it is not or not having them when it is is an error. ## Testing - [x] add integration-e2e tests in `test_reports.py` ## Testing - [x] test various bug fixes in coreneuron - [x] add lfp test. Check that it fails with neuron - [x] fix integration-e2e - [x] fix scientific - [x] fix unit
1 parent 1319ffd commit 7b075d0

48 files changed

Lines changed: 1714 additions & 930 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/simulation_test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ on:
3333
required: false
3434

3535
env:
36-
NEURON_COMMIT_ID: '7175203'
36+
NEURON_COMMIT_ID: '0d990513b'
3737
RDMAV_FORK_SAFE: '1'
3838

3939
jobs:
@@ -159,6 +159,7 @@ jobs:
159159
path: nrn
160160
key: ${{ matrix.os }}-neuron-${{ env.NEURON_BRANCH }}-${{ env.NEURON_COMMIT_ID }}-py${{ matrix.python-version }}
161161

162+
162163
- name: Install NEURON
163164
if: steps.cache-neuron.outputs.cache-hit != 'true'
164165
run: |

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,4 @@ dist/*
3333
sdist/*
3434
docs/build/
3535
cover/*
36-
MANIFEST
37-
removeme
36+
MANIFEST

neurodamus/core/coreneuron_configuration.py

Lines changed: 1 addition & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
import logging
2-
import os
31
from pathlib import Path
42

53
from . import NeuronWrapper as Nd
6-
from ._utils import run_only_rank0
7-
from .configuration import ConfigurationError, SimConfig
4+
from .configuration import SimConfig
85

96

107
class CompartmentMapping:
@@ -127,186 +124,6 @@ def restore_path(self):
127124
def instantiate_artificial_cell(self):
128125
self.artificial_cell_object = Nd.CoreNEURONArtificialCell()
129126

130-
@run_only_rank0
131-
def update_report_config(self, substitutions):
132-
"""Updates a report configuration (e.g., stop time).
133-
134-
Searches for the specified report and nodeset, updates the relevant parameters
135-
(currently only `tstop`), and writes the updated configuration to a new file.
136-
137-
Note: `report.conf` must already exist.
138-
"""
139-
report_conf = Path(self.report_config_file_save)
140-
141-
# Read all content
142-
with report_conf.open("rb") as f:
143-
lines = f.readlines()
144-
145-
# Track performed substitutions
146-
applied_subs = set()
147-
148-
# Find and update the matching line
149-
for i, line in enumerate(lines):
150-
try:
151-
parts = line.decode().split()
152-
key = tuple(parts[0:2]) # Report name and target name
153-
154-
if key in substitutions:
155-
# This is often but not always tstop:
156-
# new_tend = min(tstop, tend) where tend is the ending
157-
# of the report and tstop is the tstop of this simulation
158-
# (potentially between a restore and a save)
159-
new_tend = substitutions[key]
160-
parts[9] = f"{new_tend:.6f}"
161-
lines[i] = (" ".join(parts) + "\n").encode()
162-
applied_subs.add(key)
163-
except (UnicodeDecodeError, IndexError): # noqa: PERF203
164-
# Ignore lines that cannot be decoded (binary data)
165-
continue
166-
167-
# Find substitutions that were not applied
168-
missing_subs = set(substitutions.keys()) - applied_subs
169-
170-
if missing_subs:
171-
raise ConfigurationError(
172-
f"Some substitutions could not be applied for the following "
173-
f"(report, target) pairs: {missing_subs}"
174-
)
175-
176-
with report_conf.open("wb") as f:
177-
f.writelines(lines)
178-
179-
@run_only_rank0
180-
def write_report_config(
181-
self,
182-
report_name,
183-
target_name,
184-
report_type,
185-
report_variable,
186-
unit,
187-
report_format,
188-
target_type,
189-
dt,
190-
start_time,
191-
end_time,
192-
gids,
193-
buffer_size=8,
194-
):
195-
"""Here we append just one report entry to report.conf. We are not writing the full file as
196-
this is done incrementally in Node.enable_reports
197-
"""
198-
import struct
199-
200-
num_gids = len(gids)
201-
logging.info("Adding report %s for CoreNEURON with %s gids", report_name, num_gids)
202-
report_conf = Path(self.report_config_file_save)
203-
report_conf.parent.mkdir(parents=True, exist_ok=True)
204-
with report_conf.open("ab") as fp:
205-
# Write the formatted string to the file
206-
fp.write(
207-
(
208-
"%s %s %s %s %s %s %d %lf %lf %lf %d %d\n" # noqa: UP031
209-
% (
210-
report_name,
211-
target_name,
212-
report_type,
213-
report_variable,
214-
unit,
215-
report_format,
216-
target_type,
217-
dt,
218-
start_time,
219-
end_time,
220-
num_gids,
221-
buffer_size,
222-
)
223-
).encode()
224-
)
225-
# Write the array of integers to the file in binary format
226-
fp.write(struct.pack(f"{num_gids}i", *gids))
227-
fp.write(b"\n")
228-
229-
@run_only_rank0
230-
def write_sim_config(
231-
self,
232-
tstop: float,
233-
dt: float,
234-
prcellgid: int,
235-
celsius: float,
236-
v_init: float,
237-
pattern=None,
238-
seed=None,
239-
model_stats=False,
240-
enable_reports=True,
241-
):
242-
"""Writes the simulation configuration to a file.
243-
244-
Args:
245-
tstop (float): Simulation stop time.
246-
dt (float): Time step for the simulation.
247-
prcellgid (int): dump cell state GID. CoreNeuron allows only one
248-
cell to be dumped at a time.
249-
celsius (float): Temperature in Celsius.
250-
v_init (float): Initial voltage.
251-
pattern (str, optional): Pattern for the simulation. Defaults to None.
252-
seed (int, optional): Random seed for the simulation. Defaults to None.
253-
model_stats (bool, optional): Flag to enable model statistics. Defaults to False.
254-
enable_reports (bool, optional): Flag to enable reports. Defaults to True.
255-
"""
256-
simconf = Path(self.sim_config_file)
257-
logging.info("Writing sim config file: %s", simconf)
258-
simconf.parent.mkdir(parents=True, exist_ok=True)
259-
260-
with simconf.open("w", encoding="utf-8") as fp:
261-
fp.write(f"outpath='{os.path.abspath(self.output_root)}'\n")
262-
fp.write(f"datpath='{os.path.abspath(self.datadir)}'\n")
263-
fp.write(f"tstop={tstop}\n")
264-
fp.write(f"dt={dt}\n")
265-
fp.write(f"prcellgid={prcellgid}\n")
266-
fp.write(f"celsius={celsius}\n")
267-
fp.write(f"voltage={v_init}\n")
268-
fp.write(f"cell-permute={int(self.default_cell_permute)}\n")
269-
if pattern:
270-
fp.write(f"pattern='{pattern}'\n")
271-
if seed:
272-
fp.write(f"seed={int(seed)}\n")
273-
if model_stats:
274-
fp.write("'model-stats'\n")
275-
if enable_reports:
276-
fp.write(f"report-conf='{self.report_config_file_save}'\n")
277-
fp.write(f"mpi={os.environ.get('NEURON_INIT_MPI', '1')}\n")
278-
279-
logging.info(" => Dataset written to '%s'", simconf)
280-
281-
@run_only_rank0
282-
def write_report_count(self, count, mode="w"):
283-
report_config = Path(self.report_config_file_save)
284-
report_config.parent.mkdir(parents=True, exist_ok=True)
285-
with report_config.open(mode) as fp:
286-
fp.write(f"{count}\n")
287-
288-
@run_only_rank0
289-
def write_population_count(self, count):
290-
self.write_report_count(count, mode="a")
291-
292-
@run_only_rank0
293-
def write_spike_population(self, population_name, population_offset=None):
294-
report_config = Path(self.report_config_file_save)
295-
report_config.parent.mkdir(parents=True, exist_ok=True)
296-
with report_config.open("a", encoding="utf-8") as fp:
297-
fp.write(population_name)
298-
if population_offset is not None:
299-
fp.write(f" {int(population_offset)}")
300-
fp.write("\n")
301-
302-
@run_only_rank0
303-
def write_spike_filename(self, filename):
304-
report_config = Path(self.report_config_file_save)
305-
report_config.parent.mkdir(parents=True, exist_ok=True)
306-
with report_config.open("a", encoding="utf-8") as fp:
307-
fp.write(filename)
308-
fp.write("\n")
309-
310127
def psolve_core(self, coreneuron_direct_mode=False):
311128
from neuron import coreneuron
312129

0 commit comments

Comments
 (0)