Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] - Add functionality for managing sim params and simulating multiple signals together #329

Merged
merged 47 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
bbac0b4
add sim_combined_peak
TomDonoghue Apr 13, 2024
6df1cfa
add counter util
TomDonoghue Apr 13, 2024
03fc40b
add new udpate funcs
TomDonoghue Apr 13, 2024
2168f01
add multi sim funcs
TomDonoghue Apr 13, 2024
693769c
cleans / lints
TomDonoghue Apr 14, 2024
b26598d
minor tutorial fixes
TomDonoghue Apr 14, 2024
3702f27
sim/objs -> sim/params
TomDonoghue Apr 14, 2024
1ccfba7
use objs instead of wrappers
TomDonoghue Apr 14, 2024
36ba785
drop wrapper funcs (offer no new functionality)
TomDonoghue Apr 14, 2024
a2f2307
allow pass through of SimParams in derived objs
TomDonoghue Apr 14, 2024
3cea869
add sim params tutorial
TomDonoghue Apr 14, 2024
74d65f0
add sim multi tutorial
TomDonoghue Apr 14, 2024
5f6c349
add new sim things to API list
TomDonoghue Apr 14, 2024
de8e269
udpate docstrings
TomDonoghue Apr 14, 2024
8a8a09a
fix object initialization
TomDonoghue Apr 14, 2024
d36656c
add docstring examples
TomDonoghue Jul 19, 2024
2c7c371
Merge branch 'main' into simp
TomDonoghue Jul 19, 2024
ec90e54
udpate API list for new functionaloity
TomDonoghue Jul 19, 2024
09f137c
fix up docs & details of sig_sampler
TomDonoghue Jul 19, 2024
4b55936
add SimParams to_* methods
TomDonoghue Jul 20, 2024
6cfae41
drop allowing SimParams object into init of derived obks
TomDonoghue Jul 20, 2024
e24a0a9
add copy method
TomDonoghue Aug 17, 2024
b27848d
add new conftest objects for sim params
TomDonoghue Aug 17, 2024
12d4c6d
extend tests for sim params
TomDonoghue Aug 17, 2024
e2770f5
add docstring examples
TomDonoghue Aug 17, 2024
ce3b1f8
minor lints
TomDonoghue Aug 17, 2024
cd45e0f
add ParamIter as doc'd input to sim_multi
TomDonoghue Aug 17, 2024
260188f
add drop_base_params helper func
TomDonoghue Aug 19, 2024
d7b3450
add Simulations object
TomDonoghue Aug 19, 2024
1de2b4c
move drop_base_params func (circ import)
TomDonoghue Aug 19, 2024
cc3d76b
use sims object in sim_multiple
TomDonoghue Aug 19, 2024
9406855
add SampledSims object
TomDonoghue Aug 19, 2024
e9534a1
use SampledSims object in sim multi
TomDonoghue Aug 25, 2024
fbddfe3
add get_base_params helper
TomDonoghue Aug 28, 2024
eab6301
rework details of sims obejcts
TomDonoghue Aug 28, 2024
91e31d8
add/move listify helper func
TomDonoghue Aug 28, 2024
0f6c16b
extend tests & associated updates / fixes
TomDonoghue Aug 28, 2024
62e7938
add MultiSims object
TomDonoghue Aug 28, 2024
277a569
updates to sims objs
TomDonoghue Aug 29, 2024
68a6a6f
use object for sim_multi
TomDonoghue Aug 29, 2024
3a629da
sim/sims - sim/signals
TomDonoghue Aug 29, 2024
7792e7e
order sim tutorials
TomDonoghue Aug 29, 2024
da362f7
update tutorials for sim signal objects
TomDonoghue Aug 29, 2024
c56a1dc
allow for callable to be pased for sim_func
TomDonoghue Aug 29, 2024
1afb821
Merge branch 'main' into simp
TomDonoghue Sep 1, 2024
33c8c47
update sim init
TomDonoghue Sep 1, 2024
97ca7b2
Merge branch 'main' into simp
TomDonoghue Sep 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,65 @@ Combined Signals

sim_combined
sim_peak_oscillation
sim_combined_peak
sim_modulated_signal

Multiple Signals
~~~~~~~~~~~~~~~~

.. currentmodule:: neurodsp.sim.multi
.. autosummary::
:toctree: generated/

sim_multiple
sim_across_values
sim_from_sampler

Simulation Parameters
~~~~~~~~~~~~~~~~~~~~~

The following objects can be used to manage simulation parameters:

.. currentmodule:: neurodsp.sim.params
.. autosummary::
:toctree: generated/

SimParams
SimIters
SimSamplers

The following objects sample and iterate across parameters & simulations:

.. currentmodule:: neurodsp.sim.update
.. autosummary::
:toctree: generated/

ParamSampler
ParamIter
SigIter

The following functions can be used to update simulation parameters:

.. currentmodule:: neurodsp.sim.update
.. autosummary::
:toctree: generated/

create_updater
create_sampler

Simulated Signals
~~~~~~~~~~~~~~~~~

The following objects can be used to manage groups of simulated signals:

.. currentmodule:: neurodsp.sim.signals
.. autosummary::
:toctree: generated/

Simulations
SampledSimulations
MultiSimulations

Utilities
~~~~~~~~~

Expand Down
3 changes: 2 additions & 1 deletion neurodsp/sim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
sim_knee, sim_frac_gaussian_noise, sim_frac_brownian_motion)
from .cycles import sim_cycle
from .transients import sim_synaptic_kernel, sim_action_potential
from .combined import sim_combined, sim_peak_oscillation, sim_modulated_signal
from .combined import sim_combined, sim_peak_oscillation, sim_modulated_signal, sim_combined_peak
from .multi import sim_multiple, sim_across_values, sim_from_sampler
32 changes: 32 additions & 0 deletions neurodsp/sim/combined.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,38 @@ def sim_peak_oscillation(sig_ap, fs, freq, bw, height):
return sig


@normalize
def sim_combined_peak(n_seconds, fs, components):
"""Simulate a combined signal with an aperiodic component and a peak.

Parameters
----------
n_seconds : float
Simulation time, in seconds.
fs : float
Sampling rate of simulated signal, in Hz.
components : dict
A dictionary of simulation functions to run, with their desired parameters.

Returns
-------
sig : 1d array
Simulated combined peak signal.
"""

sim_names = list(components.keys())
assert len(sim_names) == 2, 'Expected only 2 components.'
assert sim_names[1] == 'sim_peak_oscillation', \
TomDonoghue marked this conversation as resolved.
Show resolved Hide resolved
'Expected `sim_peak_oscillation` as the second key.'

ap_func = get_sim_func(sim_names[0]) if isinstance(sim_names[0], str) else sim_names[0]

sig = sim_peak_oscillation(\
ap_func(n_seconds, fs, **components[sim_names[0]]), fs, **components[sim_names[1]])

return sig


@normalize
def sim_modulated_signal(n_seconds, fs, sig_func, sig_params, mod_func, mod_params):
"""Simulate an amplitude modulated signal.
Expand Down
213 changes: 213 additions & 0 deletions neurodsp/sim/multi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
"""Simulation functions that return multiple instances."""

from collections.abc import Sized

import numpy as np

from neurodsp.utils.core import counter
from neurodsp.sim.signals import Simulations, SampledSimulations, MultiSimulations

###################################################################################################
###################################################################################################

def sig_yielder(sim_func, sim_params, n_sims):
"""Generator to yield simulated signals from a given simulation function and parameters.

Parameters
----------
sim_func : callable
Function to create the simulated time series.
sim_params : dict
The parameters for the simulated signal, passed into `sim_func`.
n_sims : int, optional
Number of simulations to set as the max.
If None, creates an infinite generator.

Yields
------
sig : 1d array
Simulated time series.
"""

for _ in counter(n_sims):
yield sim_func(**sim_params)


def sig_sampler(sim_func, sim_params, return_sim_params=False, n_sims=None):
"""Generator to yield simulated signals from a parameter sampler.

Parameters
----------
sim_func : callable
Function to create the simulated time series.
sim_params : iterable
The parameters for the simulated signal, passed into `sim_func`.
return_sim_params : bool, optional, default: False
Whether to yield the simulation parameters as well as the simulated time series.
n_sims : int, optional
Number of simulations to set as the max.
If None, length is defined by the length of `sim_params`, and could be infinite.

Yields
------
sig : 1d array
Simulated time series.
sample_params : dict
Simulation parameters for the yielded time series.
Only returned if `return_sim_params` is True.
"""

# If `sim_params` has a size, and `n_sims` is defined, check that they are compatible
# To do so, we first check if the iterable has a __len__ attr, and if so check values
if isinstance(sim_params, Sized) and len(sim_params) and n_sims and n_sims > len(sim_params):
msg = 'Cannot simulate the requested number of sims with the given parameters.'
raise ValueError(msg)

Check warning on line 64 in neurodsp/sim/multi.py

View check run for this annotation

Codecov / codecov/patch

neurodsp/sim/multi.py#L63-L64

Added lines #L63 - L64 were not covered by tests
TomDonoghue marked this conversation as resolved.
Show resolved Hide resolved

for ind, sample_params in zip(counter(n_sims), sim_params):

if return_sim_params:
yield sim_func(**sample_params), sample_params
else:
yield sim_func(**sample_params)

if n_sims and ind >= n_sims:
break

Check warning on line 74 in neurodsp/sim/multi.py

View check run for this annotation

Codecov / codecov/patch

neurodsp/sim/multi.py#L74

Added line #L74 was not covered by tests


def sim_multiple(sim_func, sim_params, n_sims, return_type='object'):
"""Simulate multiple samples of a specified simulation.

Parameters
----------
sim_func : callable
Function to create the simulated time series.
sim_params : dict
The parameters for the simulated signal, passed into `sim_func`.
n_sims : int
Number of simulations to create.
return_type : {'object', 'array'}
Specifies the return type of the simulations.
If 'object', returns simulations and metadata in a 'Simulations' object.
If 'array', returns the simulations (no metadata) in an array.

Returns
-------
sigs : Simulations or 2d array
Simulations, return type depends on `return_type` argument.
Simulated time series are organized as [n_sims, sig length].

Examples
--------
Simulate multiple samples of a powerlaw signal:

>>> from neurodsp.sim.aperiodic import sim_powerlaw
>>> params = {'n_seconds' : 2, 'fs' : 250, 'exponent' : -1}
>>> sigs = sim_multiple(sim_powerlaw, params, n_sims=3)
"""
TomDonoghue marked this conversation as resolved.
Show resolved Hide resolved

sigs = np.zeros([n_sims, sim_params['n_seconds'] * sim_params['fs']])
for ind, sig in enumerate(sig_yielder(sim_func, sim_params, n_sims)):
sigs[ind, :] = sig

if return_type == 'object':
return Simulations(sigs, sim_params, sim_func)
else:
return sigs


def sim_across_values(sim_func, sim_params, n_sims, output='object'):
"""Simulate multiple signals across different parameter values.

Parameters
----------
sim_func : callable
Function to create the simulated time series.
sim_params : ParamIter or iterable or list of dict
Simulation parameters for `sim_func`.
n_sims : int
Number of simulations to create per parameter definition.
return_type : {'object', 'array'}
Specifies the return type of the simulations.
If 'object', returns simulations and metadata in a 'MultiSimulations' object.
If 'array', returns the simulations (no metadata) in an array.

Returns
-------
sims : MultiSimulations or array
Simulations, return type depends on `return_type` argument.
If array, signals are collected together as [n_sets, n_sims, sig_length].

Examples
--------
Simulate multiple powerlaw signals using a ParamIter object:

>>> from neurodsp.sim.aperiodic import sim_powerlaw
>>> from neurodsp.sim.params import ParamIter
>>> base_params = {'n_seconds' : 2, 'fs' : 250, 'exponent' : None}
>>> param_iter = ParamIter(base_params, 'exponent', [-2, 1, 0])
>>> sigs = sim_across_values(sim_powerlaw, param_iter, n_sims=2)

Simulate multiple powerlaw signals from manually defined set of simulation parameters:

>>> params = [{'n_seconds' : 2, 'fs' : 250, 'exponent' : -2},
... {'n_seconds' : 2, 'fs' : 250, 'exponent' : -1}]
>>> sigs = sim_across_values(sim_powerlaw, params, n_sims=2)
"""
TomDonoghue marked this conversation as resolved.
Show resolved Hide resolved

update = sim_params.update if \
not isinstance(sim_params, dict) and hasattr(sim_params, 'update') else None

sims = MultiSimulations(update=update)
for ind, cur_sim_params in enumerate(sim_params):
sims.add_signals(sim_multiple(sim_func, cur_sim_params, n_sims, 'object'))

if output == 'array':
sims = np.array([el.signals for el in sims])

return sims


def sim_from_sampler(sim_func, sim_sampler, n_sims, return_type='object'):
"""Simulate a set of signals from a parameter sampler.

Parameters
----------
sim_func : callable
Function to create the simulated time series.
sim_sampler : ParamSampler
Parameter definition to sample from.
n_sims : int
Number of simulations to create per parameter definition.
return_type : {'object', 'array'}
Specifies the return type of the simulations.
If 'object', returns simulations and metadata in a 'SampledSimulations' object.
If 'array', returns the simulations (no metadata) in an array.

Returns
-------
sigs : SampledSimulations or 2d array
Simulations, return type depends on `return_type` argument.
If array, simulations are organized as [n_sims, sig length].

Examples
--------
Simulate multiple powerlaw signals using a parameter sampler:

>>> from neurodsp.sim.aperiodic import sim_powerlaw
>>> from neurodsp.sim.update import create_updater, create_sampler, ParamSampler
>>> params = {'n_seconds' : 10, 'fs' : 250, 'exponent' : None}
>>> samplers = {create_updater('exponent') : create_sampler([-2, -1, 0])}
>>> param_sampler = ParamSampler(params, samplers)
>>> sigs = sim_from_sampler(sim_powerlaw, param_sampler, n_sims=2)
"""

all_params = [None] * n_sims
sigs = np.zeros([n_sims, sim_sampler.params['n_seconds'] * sim_sampler.params['fs']])
for ind, (sig, params) in enumerate(sig_sampler(sim_func, sim_sampler, True, n_sims)):
sigs[ind, :] = sig
all_params[ind] = params

if return_type == 'object':
return SampledSimulations(sigs, all_params, sim_func)
else:
return sigs
Loading