From 65dd021b0cc374c956a2402fb4bbcfbd6a274c58 Mon Sep 17 00:00:00 2001 From: samadpls Date: Sun, 11 Aug 2024 00:43:33 +0500 Subject: [PATCH 01/12] ENH BatchSimulate for JSON path handling Signed-off-by: samadpls --- hnn_core/batch_simulate.py | 19 +++++++++++++++++-- hnn_core/tests/test_batch_simulate.py | 26 ++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/hnn_core/batch_simulate.py b/hnn_core/batch_simulate.py index 4f153b40e..20db668a0 100644 --- a/hnn_core/batch_simulate.py +++ b/hnn_core/batch_simulate.py @@ -5,6 +5,7 @@ # Ryan Thorpe # Mainak Jas +import json import numpy as np import os from joblib import Parallel, delayed, parallel_config @@ -13,6 +14,7 @@ from .externals.mne import _validate_type, _check_option from .dipole import simulate_dipole from .network_models import jones_2009_model +from .hnn_io import dict_to_network class BatchSimulate(object): @@ -24,7 +26,7 @@ def __init__(self, set_params, net=jones_2009_model(), tstop=170, save_dpl=True, save_spiking=False, save_lfp=False, save_voltages=False, save_currents=False, save_calcium=False, - clear_cache=False): + clear_cache=False, net_json=None): """Initialize the BatchSimulate class. Parameters @@ -100,6 +102,9 @@ def __init__(self, set_params, net=jones_2009_model(), tstop=170, clear_cache : bool, optional Whether to clear the results cache after saving each batch. Default is False. + net_json : str, optional + The path to a JSON file to create the network model. If provided, + this will override the `net` parameter. Default is None. Notes ----- When `save_output=True`, the saved files will appear as @@ -127,6 +132,8 @@ def __init__(self, set_params, net=jones_2009_model(), tstop=170, _validate_type(save_currents, types=(bool,), item_name='save_currents') _validate_type(save_calcium, types=(bool,), item_name='save_calcium') _validate_type(clear_cache, types=(bool,), item_name='clear_cache') + _validate_type(net_json, types=('path-like', None), + item_name='net_json') if set_params is not None and not callable(set_params): raise TypeError("set_params must be a callable function") @@ -154,6 +161,7 @@ def __init__(self, set_params, net=jones_2009_model(), tstop=170, self.save_currents = save_currents self.save_calcium = save_calcium self.clear_cache = clear_cache + self.net_json = net_json def run(self, param_grid, return_output=True, combinations=True, n_jobs=1, backend='loky', @@ -295,7 +303,14 @@ def _run_single_sim(self, param_values): - `param_values`: The parameter values used for the simulation. """ - net = self.net.copy() + if isinstance(self.net_json, str): + with open(self.net_json, 'r') as file: + net_data = json.load(file) + net = dict_to_network(net_data) + else: + net = self.net + net = net.copy() + self.set_params(param_values, net) results = {'net': net, 'param_values': param_values} diff --git a/hnn_core/tests/test_batch_simulate.py b/hnn_core/tests/test_batch_simulate.py index 3b61ca221..c0df84771 100644 --- a/hnn_core/tests/test_batch_simulate.py +++ b/hnn_core/tests/test_batch_simulate.py @@ -3,6 +3,7 @@ # Ryan Thorpe # Mainak Jas +from pathlib import Path import pytest import numpy as np import os @@ -10,6 +11,9 @@ from hnn_core.batch_simulate import BatchSimulate from hnn_core import jones_2009_model +hnn_core_root = Path(__file__).parents[1] +assets_path = Path(hnn_core_root, 'tests', 'assets') + @pytest.fixture def batch_simulate_instance(tmp_path): @@ -33,9 +37,9 @@ def set_params(param_values, net): weights_ampa=weights_ampa, synaptic_delays=synaptic_delays) - net = jones_2009_model() + net = jones_2009_model(mesh_shape=(3, 3)) return BatchSimulate(net=net, set_params=set_params, - tstop=1., + tstop=10, save_folder=tmp_path, batch_size=3) @@ -75,6 +79,9 @@ def test_parameter_validation(): with pytest.raises(TypeError, match="net must be"): BatchSimulate(net="invalid_network", set_params=lambda x: x) + with pytest.raises(TypeError, match="net_json must be"): + BatchSimulate(net_json=123, set_params=lambda x: x) + def test_generate_param_combinations(batch_simulate_instance, param_grid): """Test generating parameter combinations.""" @@ -104,6 +111,21 @@ def test_run_single_sim(batch_simulate_instance): assert isinstance(result['net'], type(batch_simulate_instance.net)) +def test_net_json_loading(param_grid): + """Test loading the network from a JSON file.""" + json_path = assets_path / 'jones2009_3x3_drives.json' + + batch_simulate = BatchSimulate(net_json=str(json_path), + set_params=lambda x, y: x, + tstop=70) + + result = batch_simulate._run_single_sim(param_grid) + assert isinstance(result, dict) + assert 'net' in result + assert 'param_values' in result + assert 'dpl' in result + + def test_simulate_batch(batch_simulate_instance, param_grid): """Test simulating a batch of parameter sets.""" param_combinations = batch_simulate_instance._generate_param_combinations( From 8c10d95b65bf564e5a21f79f70d1904de203c2e2 Mon Sep 17 00:00:00 2001 From: samadpls Date: Sun, 11 Aug 2024 16:17:52 +0500 Subject: [PATCH 02/12] Fix: Docstring of dic_to_network Signed-off-by: samadpls --- hnn_core/hnn_io.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hnn_core/hnn_io.py b/hnn_core/hnn_io.py index 667cf82ab..c933ac5dd 100644 --- a/hnn_core/hnn_io.py +++ b/hnn_core/hnn_io.py @@ -397,12 +397,12 @@ def dict_to_network(net_data, Parameters ---------- - fname : str or Path - Path to configuration file - read_drives : bool - Read-in drives to Network object - read_external_biases - Read-in external biases to Network object + net_data : dict + Dictionary containing network configurations. + read_drives : bool, optional + Read-in drives to Network object. Default is True. + read_external_biases : bool, optional + Read-in external biases to Network object. Default is True. Returns : Network ------- From dcea3fab1d5142d6ca3283139f9029431ce23696 Mon Sep 17 00:00:00 2001 From: samadpls Date: Sun, 11 Aug 2024 17:04:53 +0500 Subject: [PATCH 03/12] ENH: Reorder the init params Signed-off-by: samadpls --- hnn_core/batch_simulate.py | 59 +++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/hnn_core/batch_simulate.py b/hnn_core/batch_simulate.py index 20db668a0..c75386a11 100644 --- a/hnn_core/batch_simulate.py +++ b/hnn_core/batch_simulate.py @@ -18,15 +18,14 @@ class BatchSimulate(object): - def __init__(self, set_params, net=jones_2009_model(), tstop=170, - dt=0.025, n_trials=1, record_vsec=False, - record_isec=False, postproc=False, save_outputs=False, + def __init__(self, set_params, net=jones_2009_model(), net_json=None, + tstop=170, dt=0.025, n_trials=1, save_folder='./sim_results', batch_size=100, - overwrite=True, summary_func=None, - save_dpl=True, save_spiking=False, - save_lfp=False, save_voltages=False, - save_currents=False, save_calcium=False, - clear_cache=False, net_json=None): + overwrite=True, save_outputs=False, save_dpl=True, + save_spiking=False, save_lfp=False, save_voltages=False, + save_currents=False, save_calcium=False, record_vsec=False, + record_isec=False, postproc=False, clear_cache=False, + summary_func=None): """Initialize the BatchSimulate class. Parameters @@ -42,31 +41,15 @@ def __init__(self, set_params, net=jones_2009_model(), tstop=170, The network model to use for simulations. Must be an instance of jones_2009_model, law_2021_model, or calcium_model. Default is jones_2009_model(). + net_json : str, optional + The path to a JSON file to create the network model. If provided, + this will override the `net` parameter. Default is None. tstop : float, optional The stop time for the simulation. Default is 170 ms. dt : float, optional The time step for the simulation. Default is 0.025 ms. n_trials : int, optional The number of trials for the simulation. Default is 1. - record_vsec : 'all' | 'soma' | False - Option to record voltages from all sections ('all'), or just - the soma ('soma'). Default: False. - record_isec : 'all' | 'soma' | False - Option to record voltages from all sections ('all'), or just - the soma ('soma'). Default: False. - postproc : bool - If True, smoothing (``dipole_smooth_win``) and scaling - (``dipole_scalefctr``) values are read from the parameter file, and - applied to the dipole objects before returning. - Note that this setting - only affects the dipole waveforms, and not somatic voltages, - possible extracellular recordings etc. - The preferred way is to use the - :meth:`~hnn_core.dipole.Dipole.smooth` and - :meth:`~hnn_core.dipole.Dipole.scale` methods instead. - Default: False. - save_outputs : bool, optional - Whether to save the simulation outputs to files. Default is False. save_folder : str, optional The path to save the simulation outputs. Default is './sim_results'. @@ -76,9 +59,8 @@ def __init__(self, set_params, net=jones_2009_model(), tstop=170, overwrite : bool, optional Whether to overwrite existing files and create file paths if they do not exist. Default is True. - summary_func : callable, optional - A function to calculate summary statistics from the simulation - results. Default is None. + save_outputs : bool, optional + Whether to save the simulation outputs to files. Default is False. save_dpl : bool If True, save dipole results. Note, `save_outputs` must be True. Default: True. @@ -99,12 +81,23 @@ def __init__(self, set_params, net=jones_2009_model(), tstop=170, If True, save calcium concentrations. Note, `save_outputs` must be True. Default: False. + record_vsec : 'all' | 'soma' | False + Option to record voltages from all sections ('all'), or just + the soma ('soma'). Default: False. + record_isec : 'all' | 'soma' | False + Option to record voltages from all sections ('all'), or just + the soma ('soma'). Default: False. + postproc : bool + If True, smoothing (``dipole_smooth_win``) and scaling + (``dipole_scalefctr``) values are read from the parameter file, and + applied to the dipole objects before returning. + Default: False. clear_cache : bool, optional Whether to clear the results cache after saving each batch. Default is False. - net_json : str, optional - The path to a JSON file to create the network model. If provided, - this will override the `net` parameter. Default is None. + summary_func : callable, optional + A function to calculate summary statistics from the simulation + results. Default is None. Notes ----- When `save_output=True`, the saved files will appear as From a565d2e5e45900c12a2bbec3df26cbd0a2f36735 Mon Sep 17 00:00:00 2001 From: samadpls Date: Tue, 13 Aug 2024 17:26:32 +0500 Subject: [PATCH 04/12] Added record_isec vsec validation test Signed-off-by: samadpls --- hnn_core/tests/test_batch_simulate.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hnn_core/tests/test_batch_simulate.py b/hnn_core/tests/test_batch_simulate.py index c0df84771..4604ade32 100644 --- a/hnn_core/tests/test_batch_simulate.py +++ b/hnn_core/tests/test_batch_simulate.py @@ -82,6 +82,12 @@ def test_parameter_validation(): with pytest.raises(TypeError, match="net_json must be"): BatchSimulate(net_json=123, set_params=lambda x: x) + with pytest.raises(ValueError, match="'record_vsec' parameter"): + BatchSimulate(set_params=lambda x: x, record_vsec="invalid") + + with pytest.raises(ValueError, match="'record_isec' parameter"): + BatchSimulate(set_params=lambda x: x, record_isec="invalid") + def test_generate_param_combinations(batch_simulate_instance, param_grid): """Test generating parameter combinations.""" From d12a0b5b3b4cb882d15d59bc46454670139c248a Mon Sep 17 00:00:00 2001 From: samadpls Date: Wed, 14 Aug 2024 18:42:56 +0500 Subject: [PATCH 05/12] Removed net_json param and update test Signed-off-by: samadpls --- hnn_core/batch_simulate.py | 27 ++++++++++----------------- hnn_core/tests/test_batch_simulate.py | 18 ------------------ 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/hnn_core/batch_simulate.py b/hnn_core/batch_simulate.py index c75386a11..73a6ca0e8 100644 --- a/hnn_core/batch_simulate.py +++ b/hnn_core/batch_simulate.py @@ -5,7 +5,6 @@ # Ryan Thorpe # Mainak Jas -import json import numpy as np import os from joblib import Parallel, delayed, parallel_config @@ -14,11 +13,10 @@ from .externals.mne import _validate_type, _check_option from .dipole import simulate_dipole from .network_models import jones_2009_model -from .hnn_io import dict_to_network class BatchSimulate(object): - def __init__(self, set_params, net=jones_2009_model(), net_json=None, + def __init__(self, set_params, net=jones_2009_model(), tstop=170, dt=0.025, n_trials=1, save_folder='./sim_results', batch_size=100, overwrite=True, save_outputs=False, save_dpl=True, @@ -41,9 +39,6 @@ def __init__(self, set_params, net=jones_2009_model(), net_json=None, The network model to use for simulations. Must be an instance of jones_2009_model, law_2021_model, or calcium_model. Default is jones_2009_model(). - net_json : str, optional - The path to a JSON file to create the network model. If provided, - this will override the `net` parameter. Default is None. tstop : float, optional The stop time for the simulation. Default is 170 ms. dt : float, optional @@ -125,8 +120,6 @@ def __init__(self, set_params, net=jones_2009_model(), net_json=None, _validate_type(save_currents, types=(bool,), item_name='save_currents') _validate_type(save_calcium, types=(bool,), item_name='save_calcium') _validate_type(clear_cache, types=(bool,), item_name='clear_cache') - _validate_type(net_json, types=('path-like', None), - item_name='net_json') if set_params is not None and not callable(set_params): raise TypeError("set_params must be a callable function") @@ -154,7 +147,6 @@ def __init__(self, set_params, net=jones_2009_model(), net_json=None, self.save_currents = save_currents self.save_calcium = save_calcium self.clear_cache = clear_cache - self.net_json = net_json def run(self, param_grid, return_output=True, combinations=True, n_jobs=1, backend='loky', @@ -296,14 +288,7 @@ def _run_single_sim(self, param_values): - `param_values`: The parameter values used for the simulation. """ - if isinstance(self.net_json, str): - with open(self.net_json, 'r') as file: - net_data = json.load(file) - net = dict_to_network(net_data) - else: - net = self.net - net = net.copy() - + net = self.net.copy() self.set_params(param_values, net) results = {'net': net, 'param_values': param_values} @@ -396,6 +381,14 @@ def _save(self, results, start_idx, end_idx): if getattr(self, f'save_{attr}') and attr in results[0]: save_data[attr] = [result[attr] for result in results] + metadata = { + 'batch_size': self.batch_size, + 'n_trials': self.n_trials, + 'tstop': self.tstop, + 'dt': self.dt + } + save_data['metadata'] = metadata + file_name = os.path.join(self.save_folder, f'sim_run_{start_idx}-{end_idx}.npz') if os.path.exists(file_name) and not self.overwrite: diff --git a/hnn_core/tests/test_batch_simulate.py b/hnn_core/tests/test_batch_simulate.py index 4604ade32..aa0266dcb 100644 --- a/hnn_core/tests/test_batch_simulate.py +++ b/hnn_core/tests/test_batch_simulate.py @@ -79,9 +79,6 @@ def test_parameter_validation(): with pytest.raises(TypeError, match="net must be"): BatchSimulate(net="invalid_network", set_params=lambda x: x) - with pytest.raises(TypeError, match="net_json must be"): - BatchSimulate(net_json=123, set_params=lambda x: x) - with pytest.raises(ValueError, match="'record_vsec' parameter"): BatchSimulate(set_params=lambda x: x, record_vsec="invalid") @@ -117,21 +114,6 @@ def test_run_single_sim(batch_simulate_instance): assert isinstance(result['net'], type(batch_simulate_instance.net)) -def test_net_json_loading(param_grid): - """Test loading the network from a JSON file.""" - json_path = assets_path / 'jones2009_3x3_drives.json' - - batch_simulate = BatchSimulate(net_json=str(json_path), - set_params=lambda x, y: x, - tstop=70) - - result = batch_simulate._run_single_sim(param_grid) - assert isinstance(result, dict) - assert 'net' in result - assert 'param_values' in result - assert 'dpl' in result - - def test_simulate_batch(batch_simulate_instance, param_grid): """Test simulating a batch of parameter sets.""" param_combinations = batch_simulate_instance._generate_param_combinations( From 11b50dd463928c17e8d5a652d07e5b00744db302 Mon Sep 17 00:00:00 2001 From: samadpls Date: Sat, 17 Aug 2024 23:08:32 +0500 Subject: [PATCH 06/12] Refactored Example and initialize parameters Co-authored-by: Nicholas Tolley <55253912+ntolley@users.noreply.github.com> Signed-off-by: samadpls --- examples/howto/plot_batch_simulate.py | 64 ++++++++++++++++++++++----- hnn_core/batch_simulate.py | 14 +++--- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/examples/howto/plot_batch_simulate.py b/examples/howto/plot_batch_simulate.py index f8018e731..6d08c4f89 100644 --- a/examples/howto/plot_batch_simulate.py +++ b/examples/howto/plot_batch_simulate.py @@ -26,11 +26,24 @@ # The number of cores may need modifying depending on your current machine. n_jobs = 10 ############################################################################### +# The `add_evoked_drive` function simulates external input to the network, +# mimicking sensory stimulation or other external events. +# +# - `evprox` indicates a proximal drive, targeting dendrites near the cell +# bodies. +# - `mu=40` and `sigma=5` define the timing (mean and spread) of the input. +# - `numspikes=1` means it's a single, brief stimulation. +# - `weights_ampa` and `synaptic_delays` control the strength and +# timing of the input. +# +# This evoked drive causes the initial positive deflection in the dipole +# signal, triggering a cascade of activity through the network and +# resulting in the complex waveforms observed. def set_params(param_values, net=None): """ - Set parameters in the network drives. + Set parameters for the network drives. Parameters ---------- @@ -57,16 +70,16 @@ def set_params(param_values, net=None): synaptic_delays=synaptic_delays) ############################################################################### -# Define a parameter grid for the batch simulation. +# Next, we define a parameter grid for the batch simulation. param_grid = { - 'weight_basket': np.logspace(-4 - 1, 10), + 'weight_basket': np.logspace(-4, -1, 10), 'weight_pyr': np.logspace(-4, -1, 10) } ############################################################################### -# Define a function to calculate summary statistics +# We then define a function to calculate summary statistics. def summary_func(results): @@ -95,12 +108,12 @@ def summary_func(results): ############################################################################### # Run the batch simulation and collect the results. -# Comment off this code, if dask and distributed Python packages are installed +# Uncomment this code if dask and distributed Python packages are installed. # from dask.distributed import Client # client = Client(threads_per_worker=1, n_workers=5, processes=False) -# Run the batch simulation and collect the results. +# Initialize the network model and run the batch simulation. net = jones_2009_model(mesh_shape=(3, 3)) batch_simulation = BatchSimulate(net=net, set_params=set_params, @@ -113,18 +126,47 @@ def summary_func(results): print("Simulation results:", simulation_results) ############################################################################### # This plot shows an overlay of all smoothed dipole waveforms from the -# batch simulation. Each line represents a different set of parameters, -# allowing us to visualize the range of responses across the parameter space. +# batch simulation. Each line represents a different set of synaptic strength +# parameters (`weight_basket`), allowing us to visualize the range of responses +# across the parameter space. +# The colormap represents different synaptic strengths, with purple indicating +# lower strengths and yellow indicating higher strengths. +# +# Key observations: +# +# - The dipole signal reflects the net current flow in the cortical column. +# - Initially, we see a positive deflection as excitatory input arrives at +# the proximal dendrites, causing current to flow upwards +# (away from the soma). +# - The subsequent negative deflection, despite continued excitatory input, +# occurs when action potentials are triggered, causing rapid current flow in +# the opposite direction as the cell bodies depolarize. +# - Inhibitory neurons, when they fire, can also contribute to negative +# deflections by causing hyperpolarization in their target neurons. +# - Later oscillations likely represent ongoing network activity and +# subthreshold membrane potential fluctuations. +# +# The y-axis represents dipole amplitude in nAm (nanoAmpere-meters), which is +# the product of current flow and distance in the neural tissue. +# +# Stronger synaptic connections (yellow lines) generally show larger +# amplitude responses and more pronounced features throughout the simulation. -dpl_waveforms = [] +dpl_waveforms, param_values = [], [] for data_list in simulation_results['simulated_data']: for data in data_list: dpl_smooth = data['dpl'][0].copy().smooth(window_len=30) dpl_waveforms.append(dpl_smooth.data['agg']) + param_values.append(data['param_values']['weight_basket']) plt.figure(figsize=(10, 6)) -for waveform in dpl_waveforms: - plt.plot(waveform, alpha=0.5, linewidth=3) +cmap = plt.get_cmap('viridis') +param_values = np.array(param_values) +norm = plt.Normalize(param_values.min(), param_values.max()) + +for waveform, param in zip(dpl_waveforms, param_values): + color = cmap(norm(param)) + plt.plot(waveform, color=color, alpha=0.7, linewidth=2) plt.title('Overlay of Dipole Waveforms') plt.xlabel('Time (ms)') plt.ylabel('Dipole Amplitude (nAm)') diff --git a/hnn_core/batch_simulate.py b/hnn_core/batch_simulate.py index 73a6ca0e8..b096819cc 100644 --- a/hnn_core/batch_simulate.py +++ b/hnn_core/batch_simulate.py @@ -16,7 +16,7 @@ class BatchSimulate(object): - def __init__(self, set_params, net=jones_2009_model(), + def __init__(self, set_params, net=None, tstop=170, dt=0.025, n_trials=1, save_folder='./sim_results', batch_size=100, overwrite=True, save_outputs=False, save_dpl=True, @@ -36,9 +36,11 @@ def __init__(self, set_params, net=jones_2009_model(), where ``net`` is a Network object and ``params`` is a dictionary of the parameters that will be set inside the function. net : Network object, optional - The network model to use for simulations. Must be an instance of - jones_2009_model, law_2021_model, or calcium_model. - Default is jones_2009_model(). + The network model to use for simulations. Examples include: + - `jones_2009_model`: A network model based on Jones et al. (2009). + - `law_2021_model`: A network model based on Law et al. (2021). + - `calcium_model`: A network model incorporating calcium dynamics. + Default is `jones_2009_model()` tstop : float, optional The stop time for the simulation. Default is 170 ms. dt : float, optional @@ -104,7 +106,7 @@ def __init__(self, set_params, net=jones_2009_model(), will be overwritten. """ - _validate_type(net, Network, 'net', 'Network') + _validate_type(net, (Network, None), 'net', 'Network') _validate_type(tstop, types='numeric', item_name='tstop') _validate_type(dt, types='numeric', item_name='dt') _validate_type(n_trials, types='int', item_name='n_trials') @@ -127,7 +129,7 @@ def __init__(self, set_params, net=jones_2009_model(), if summary_func is not None and not callable(summary_func): raise TypeError("summary_func must be a callable function") - self.net = net + self.net = net if net is not None else jones_2009_model() self.set_params = set_params self.tstop = tstop self.dt = dt From fffcb17b72d0dc80182a92ec824baff6fffea25a Mon Sep 17 00:00:00 2001 From: samadpls Date: Tue, 20 Aug 2024 16:28:16 +0500 Subject: [PATCH 07/12] Enh: visualization of dipole responses in plot_batch_simulate Co-authored-by: Nicholas Tolley <55253912+ntolley@users.noreply.github.com> Signed-off-by: samadpls --- examples/howto/plot_batch_simulate.py | 32 ++++++++++----------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/examples/howto/plot_batch_simulate.py b/examples/howto/plot_batch_simulate.py index 6d08c4f89..f683a6087 100644 --- a/examples/howto/plot_batch_simulate.py +++ b/examples/howto/plot_batch_simulate.py @@ -32,7 +32,6 @@ # - `evprox` indicates a proximal drive, targeting dendrites near the cell # bodies. # - `mu=40` and `sigma=5` define the timing (mean and spread) of the input. -# - `numspikes=1` means it's a single, brief stimulation. # - `weights_ampa` and `synaptic_delays` control the strength and # timing of the input. # @@ -129,22 +128,15 @@ def summary_func(results): # batch simulation. Each line represents a different set of synaptic strength # parameters (`weight_basket`), allowing us to visualize the range of responses # across the parameter space. -# The colormap represents different synaptic strengths, with purple indicating -# lower strengths and yellow indicating higher strengths. +# The colormap represents synaptic strengths, from weaker (purple) +# to stronger (yellow). # -# Key observations: -# -# - The dipole signal reflects the net current flow in the cortical column. -# - Initially, we see a positive deflection as excitatory input arrives at -# the proximal dendrites, causing current to flow upwards -# (away from the soma). -# - The subsequent negative deflection, despite continued excitatory input, -# occurs when action potentials are triggered, causing rapid current flow in -# the opposite direction as the cell bodies depolarize. -# - Inhibitory neurons, when they fire, can also contribute to negative -# deflections by causing hyperpolarization in their target neurons. -# - Later oscillations likely represent ongoing network activity and -# subthreshold membrane potential fluctuations. +# As drive strength increases, dipole responses show progressively larger +# amplitudes and more distinct features, reflecting heightened network +# activity. Weak drives (purple lines) produce smaller amplitude signals with +# simpler waveforms, while stronger drives (yellow lines) generate +# larger responses with more pronounced oscillatory features, indicating +# more robust network activity. # # The y-axis represents dipole amplitude in nAm (nanoAmpere-meters), which is # the product of current flow and distance in the neural tissue. @@ -161,11 +153,11 @@ def summary_func(results): plt.figure(figsize=(10, 6)) cmap = plt.get_cmap('viridis') -param_values = np.array(param_values) -norm = plt.Normalize(param_values.min(), param_values.max()) +log_param_values = np.log10(param_values) +norm = plt.Normalize(log_param_values.min(), log_param_values.max()) -for waveform, param in zip(dpl_waveforms, param_values): - color = cmap(norm(param)) +for waveform, log_param in zip(dpl_waveforms, log_param_values): + color = cmap(norm(log_param)) plt.plot(waveform, color=color, alpha=0.7, linewidth=2) plt.title('Overlay of Dipole Waveforms') plt.xlabel('Time (ms)') From 4759e3786a9e36f6160af0759369075a3ec1dfa5 Mon Sep 17 00:00:00 2001 From: samadpls Date: Sat, 19 Oct 2024 22:45:42 +0500 Subject: [PATCH 08/12] Refactor batch simulation parameters and backend Signed-off-by: samadpls --- examples/howto/plot_batch_simulate.py | 6 +++--- hnn_core/batch_simulate.py | 28 ++++++++++++++------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/howto/plot_batch_simulate.py b/examples/howto/plot_batch_simulate.py index f683a6087..470a7debb 100644 --- a/examples/howto/plot_batch_simulate.py +++ b/examples/howto/plot_batch_simulate.py @@ -73,8 +73,8 @@ def set_params(param_values, net=None): param_grid = { - 'weight_basket': np.logspace(-4, -1, 10), - 'weight_pyr': np.logspace(-4, -1, 10) + 'weight_basket': np.logspace(-4, -1, 20), + 'weight_pyr': np.logspace(-4, -1, 20) } ############################################################################### @@ -120,7 +120,7 @@ def summary_func(results): simulation_results = batch_simulation.run(param_grid, n_jobs=n_jobs, combinations=False, - backend='multiprocessing') + backend='loky') # backend='dask' if installed print("Simulation results:", simulation_results) ############################################################################### diff --git a/hnn_core/batch_simulate.py b/hnn_core/batch_simulate.py index b096819cc..89a70091b 100644 --- a/hnn_core/batch_simulate.py +++ b/hnn_core/batch_simulate.py @@ -9,6 +9,7 @@ import os from joblib import Parallel, delayed, parallel_config +from .parallel_backends import JoblibBackend from .network import Network from .externals.mne import _validate_type, _check_option from .dipole import simulate_dipole @@ -201,9 +202,9 @@ def run(self, param_grid, return_output=True, results = [] simulated_data = [] - for i in range(batch_size): - start_idx = i * num_sims_per_batch - end_idx = start_idx + num_sims_per_batch + for i in range(0, total_sims, num_sims_per_batch): + start_idx = i + end_idx = min(i + num_sims_per_batch, total_sims) if i == batch_size - 1: end_idx = len(param_combinations) batch_results = self.simulate_batch( @@ -269,10 +270,10 @@ def simulate_batch(self, param_combinations, n_jobs=1, with parallel_config(backend=backend): res = Parallel(n_jobs=n_jobs, verbose=verbose)( delayed(self._run_single_sim)( - params) for params in param_combinations) + params, n_jobs) for params in param_combinations) return res - def _run_single_sim(self, param_values): + def _run_single_sim(self, param_values, n_jobs=1): """Run a single simulation. Parameters @@ -296,14 +297,15 @@ def _run_single_sim(self, param_values): results = {'net': net, 'param_values': param_values} if self.save_dpl: - dpl = simulate_dipole(net, - tstop=self.tstop, - dt=self.dt, - n_trials=self.n_trials, - record_vsec=self.record_vsec, - record_isec=self.record_isec, - postproc=self.postproc) - results['dpl'] = dpl + with JoblibBackend(n_jobs=n_jobs): + dpl = simulate_dipole(net, + tstop=self.tstop, + dt=self.dt, + n_trials=self.n_trials, + record_vsec=self.record_vsec, + record_isec=self.record_isec, + postproc=self.postproc) + results['dpl'] = dpl if self.save_spiking: results['spiking'] = { From a6114c984c38692154d8525ed729b8f7e0576cb9 Mon Sep 17 00:00:00 2001 From: Nicholas Tolley <55253912+ntolley@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:24:22 -0500 Subject: [PATCH 09/12] [MRG] Fix indexing for batch simulations (#5) * ENH BatchSimulate for JSON path handling Signed-off-by: samadpls * Fix: Docstring of dic_to_network Signed-off-by: samadpls * ENH: Reorder the init params Signed-off-by: samadpls * Added record_isec vsec validation test Signed-off-by: samadpls * Removed net_json param and update test Signed-off-by: samadpls * Refactored Example and initialize parameters Co-authored-by: Nicholas Tolley <55253912+ntolley@users.noreply.github.com> Signed-off-by: samadpls * Enh: visualization of dipole responses in plot_batch_simulate Co-authored-by: Nicholas Tolley <55253912+ntolley@users.noreply.github.com> Signed-off-by: samadpls * Refactor batch simulation parameters and backend Signed-off-by: samadpls * batches run in parallel --------- Signed-off-by: samadpls Co-authored-by: samadpls --- hnn_core/batch_simulate.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/hnn_core/batch_simulate.py b/hnn_core/batch_simulate.py index 89a70091b..afd47ba13 100644 --- a/hnn_core/batch_simulate.py +++ b/hnn_core/batch_simulate.py @@ -197,16 +197,13 @@ def run(self, param_grid, return_output=True, param_combinations = self._generate_param_combinations( param_grid, combinations) total_sims = len(param_combinations) - num_sims_per_batch = max(total_sims // self.batch_size, 1) batch_size = min(self.batch_size, total_sims) results = [] simulated_data = [] - for i in range(0, total_sims, num_sims_per_batch): + for i in range(0, total_sims, batch_size): start_idx = i - end_idx = min(i + num_sims_per_batch, total_sims) - if i == batch_size - 1: - end_idx = len(param_combinations) + end_idx = min(i + batch_size, total_sims) batch_results = self.simulate_batch( param_combinations[start_idx:end_idx], n_jobs=n_jobs, From fb0b79e72638867821e46c527f1ea89f831e2378 Mon Sep 17 00:00:00 2001 From: samadpls Date: Wed, 27 Nov 2024 01:15:53 +0500 Subject: [PATCH 10/12] Refactor: Removed joblib from simulate_dipole, and added parallel execution test. Signed-off-by: samadpls --- examples/howto/plot_batch_simulate.py | 5 +++-- hnn_core/batch_simulate.py | 18 ++++++++--------- hnn_core/tests/test_batch_simulate.py | 28 ++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/examples/howto/plot_batch_simulate.py b/examples/howto/plot_batch_simulate.py index 470a7debb..c157f0751 100644 --- a/examples/howto/plot_batch_simulate.py +++ b/examples/howto/plot_batch_simulate.py @@ -24,7 +24,7 @@ from hnn_core import jones_2009_model # The number of cores may need modifying depending on your current machine. -n_jobs = 10 +n_jobs = 4 ############################################################################### # The `add_evoked_drive` function simulates external input to the network, # mimicking sensory stimulation or other external events. @@ -116,7 +116,8 @@ def summary_func(results): net = jones_2009_model(mesh_shape=(3, 3)) batch_simulation = BatchSimulate(net=net, set_params=set_params, - summary_func=summary_func) + summary_func=summary_func, + n_trials=10) simulation_results = batch_simulation.run(param_grid, n_jobs=n_jobs, combinations=False, diff --git a/hnn_core/batch_simulate.py b/hnn_core/batch_simulate.py index afd47ba13..424976187 100644 --- a/hnn_core/batch_simulate.py +++ b/hnn_core/batch_simulate.py @@ -9,7 +9,6 @@ import os from joblib import Parallel, delayed, parallel_config -from .parallel_backends import JoblibBackend from .network import Network from .externals.mne import _validate_type, _check_option from .dipole import simulate_dipole @@ -294,15 +293,14 @@ def _run_single_sim(self, param_values, n_jobs=1): results = {'net': net, 'param_values': param_values} if self.save_dpl: - with JoblibBackend(n_jobs=n_jobs): - dpl = simulate_dipole(net, - tstop=self.tstop, - dt=self.dt, - n_trials=self.n_trials, - record_vsec=self.record_vsec, - record_isec=self.record_isec, - postproc=self.postproc) - results['dpl'] = dpl + dpl = simulate_dipole(net, + tstop=self.tstop, + dt=self.dt, + n_trials=self.n_trials, + record_vsec=self.record_vsec, + record_isec=self.record_isec, + postproc=self.postproc) + results['dpl'] = dpl if self.save_spiking: results['spiking'] = { diff --git a/hnn_core/tests/test_batch_simulate.py b/hnn_core/tests/test_batch_simulate.py index aa0266dcb..71dac4c43 100644 --- a/hnn_core/tests/test_batch_simulate.py +++ b/hnn_core/tests/test_batch_simulate.py @@ -4,6 +4,7 @@ # Mainak Jas from pathlib import Path +import time import pytest import numpy as np import os @@ -41,7 +42,8 @@ def set_params(param_values, net): return BatchSimulate(net=net, set_params=set_params, tstop=10, save_folder=tmp_path, - batch_size=3) + batch_size=3, + n_trials=3,) @pytest.fixture @@ -290,3 +292,27 @@ def test_load_results(batch_simulate_instance, param_grid, tmp_path): # Validation Tests with pytest.raises(TypeError, match='results must be'): batch_simulate_instance._save("invalid_results", start_idx, end_idx) + + +def test_parallel_execution(batch_simulate_instance, param_grid): + """Test parallel execution of simulations and ensure speedup.""" + + param_combinations = batch_simulate_instance._generate_param_combinations( + param_grid) + + start_time = time.perf_counter() + _ = batch_simulate_instance.simulate_batch( + param_combinations, n_jobs=1, backend='loky') + end_time = time.perf_counter() + serial_time = end_time - start_time + + start_time = time.perf_counter() + _ = batch_simulate_instance.simulate_batch( + param_combinations, + n_jobs=2, + backend='loky') + end_time = time.perf_counter() + parallel_time = end_time - start_time + + assert (serial_time > parallel_time + ), "Parallel execution is not faster than serial execution!" From d014b3f6f7f5714e90801610c93207f15b3fbcd0 Mon Sep 17 00:00:00 2001 From: Nicholas Tolley <55253912+ntolley@users.noreply.github.com> Date: Tue, 3 Dec 2024 21:26:30 +0500 Subject: [PATCH 11/12] Refactor: Simplify BatchSimulate parameters by removing n_jobs Signed-off-by: samadpls --- examples/howto/plot_batch_simulate.py | 3 +-- hnn_core/batch_simulate.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/howto/plot_batch_simulate.py b/examples/howto/plot_batch_simulate.py index c157f0751..d6c378e65 100644 --- a/examples/howto/plot_batch_simulate.py +++ b/examples/howto/plot_batch_simulate.py @@ -116,8 +116,7 @@ def summary_func(results): net = jones_2009_model(mesh_shape=(3, 3)) batch_simulation = BatchSimulate(net=net, set_params=set_params, - summary_func=summary_func, - n_trials=10) + summary_func=summary_func) simulation_results = batch_simulation.run(param_grid, n_jobs=n_jobs, combinations=False, diff --git a/hnn_core/batch_simulate.py b/hnn_core/batch_simulate.py index 424976187..9c09a5f88 100644 --- a/hnn_core/batch_simulate.py +++ b/hnn_core/batch_simulate.py @@ -266,10 +266,10 @@ def simulate_batch(self, param_combinations, n_jobs=1, with parallel_config(backend=backend): res = Parallel(n_jobs=n_jobs, verbose=verbose)( delayed(self._run_single_sim)( - params, n_jobs) for params in param_combinations) + params) for params in param_combinations) return res - def _run_single_sim(self, param_values, n_jobs=1): + def _run_single_sim(self, param_values): """Run a single simulation. Parameters From a984e84e1588246f14dd0d02364421c357c28b20 Mon Sep 17 00:00:00 2001 From: samadpls Date: Thu, 12 Dec 2024 18:53:03 +0500 Subject: [PATCH 12/12] Remove unused Dask code and simplify BatchSimulate initialization Signed-off-by: samadpls --- examples/howto/plot_batch_simulate.py | 6 +----- hnn_core/batch_simulate.py | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/howto/plot_batch_simulate.py b/examples/howto/plot_batch_simulate.py index d6c378e65..35ac2a75c 100644 --- a/examples/howto/plot_batch_simulate.py +++ b/examples/howto/plot_batch_simulate.py @@ -107,10 +107,6 @@ def summary_func(results): ############################################################################### # Run the batch simulation and collect the results. -# Uncomment this code if dask and distributed Python packages are installed. -# from dask.distributed import Client -# client = Client(threads_per_worker=1, n_workers=5, processes=False) - # Initialize the network model and run the batch simulation. net = jones_2009_model(mesh_shape=(3, 3)) @@ -121,7 +117,7 @@ def summary_func(results): n_jobs=n_jobs, combinations=False, backend='loky') -# backend='dask' if installed + print("Simulation results:", simulation_results) ############################################################################### # This plot shows an overlay of all smoothed dipole waveforms from the diff --git a/hnn_core/batch_simulate.py b/hnn_core/batch_simulate.py index 9c09a5f88..168c3ea95 100644 --- a/hnn_core/batch_simulate.py +++ b/hnn_core/batch_simulate.py @@ -16,7 +16,7 @@ class BatchSimulate(object): - def __init__(self, set_params, net=None, + def __init__(self, set_params, net=jones_2009_model(), tstop=170, dt=0.025, n_trials=1, save_folder='./sim_results', batch_size=100, overwrite=True, save_outputs=False, save_dpl=True, @@ -106,7 +106,7 @@ def __init__(self, set_params, net=None, will be overwritten. """ - _validate_type(net, (Network, None), 'net', 'Network') + _validate_type(net, Network, 'net', 'Network') _validate_type(tstop, types='numeric', item_name='tstop') _validate_type(dt, types='numeric', item_name='dt') _validate_type(n_trials, types='int', item_name='n_trials') @@ -129,7 +129,7 @@ def __init__(self, set_params, net=None, if summary_func is not None and not callable(summary_func): raise TypeError("summary_func must be a callable function") - self.net = net if net is not None else jones_2009_model() + self.net = net self.set_params = set_params self.tstop = tstop self.dt = dt