Source code for myogen.utils.currents

from typing import cast

import numpy as np
from beartype import beartype

from myogen.utils.types import INPUT_CURRENT__MATRIX


[docs] @beartype def create_sinusoidal_current( n_pools: int, t_points: int, timestep__ms: float, amplitudes__muV: float | list[float], frequencies__Hz: float | list[float], offsets__muV: float | list[float], phases__rad: float | list[float] = 0.0, ) -> INPUT_CURRENT__MATRIX: """Create a matrix of sinusoidal currents for multiple pools. Parameters ---------- n_pools : int Number of current pools to generate t_points : int Number of time points timestep__ms : float Time step in milliseconds amplitudes__muV : float | list[float] Amplitude(s) of the sinusoidal current(s) in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools frequencies__Hz : float | list[float] Frequency(s) of the sinusoidal current(s) in Hertz. Must be: - Single float: used for all pools - List of floats: must match n_pools offsets__muV : float | list[float] DC offset(s) to add to the sinusoidal current(s) in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools phases__rad : float | list[float] Phase(s) of the sinusoidal current(s) in radians. Must be: - Single float: used for all pools - List of floats: must match n_pools Raises ------ ValueError If the amplitudes, frequencies, offsets, or phases are lists and the length of the parameters does not match n_pools Returns ------- INPUT_CURRENT__MATRIX Matrix of shape (n_pools, t_points) containing sinusoidal currents """ t = np.arange(0, t_points * timestep__ms, timestep__ms) # Convert parameters to lists amplitudes_list = cast( list[float], [amplitudes__muV] * n_pools if isinstance(amplitudes__muV, float) else amplitudes__muV, ) frequencies_list = cast( list[float], [frequencies__Hz] * n_pools if isinstance(frequencies__Hz, float) else frequencies__Hz, ) offsets_list = cast( list[float], [offsets__muV] * n_pools if isinstance(offsets__muV, float) else offsets__muV, ) phases_list = cast( list[float], [phases__rad] * n_pools if isinstance(phases__rad, float) else phases__rad, ) if len(amplitudes_list) != n_pools: raise ValueError( f"Length of amplitudes__muV ({len(amplitudes_list)}) must match n_pools ({n_pools})" ) if len(frequencies_list) != n_pools: raise ValueError( f"Length of frequencies__Hz ({len(frequencies_list)}) must match n_pools ({n_pools})" ) if len(offsets_list) != n_pools: raise ValueError( f"Length of offsets__muV ({len(offsets_list)}) must match n_pools ({n_pools})" ) if len(phases_list) != n_pools: raise ValueError( f"Length of phases__rad ({len(phases_list)}) must match n_pools ({n_pools})" ) return np.array( [ ( amplitudes_list[i] * np.sin(2 * np.pi * frequencies_list[i] * t / 1000 + phases_list[i]) + offsets_list[i] ) for i in range(n_pools) ] )
[docs] @beartype def create_sawtooth_current( n_pools: int, t_points: int, timestep_ms: float, amplitudes__muV: float | list[float], frequencies__Hz: float | list[float], offsets__muV: float | list[float] = 0.0, widths: float | list[float] = 0.5, phases__rad: float | list[float] = 0.0, ) -> INPUT_CURRENT__MATRIX: """Create a matrix of sawtooth currents for multiple pools. Parameters ---------- n_pools : int Number of current pools to generate t_points : int Number of time points timestep_ms : float Time step in milliseconds amplitudes__muV : float | list[float] Amplitude(s) of the sawtooth current(s) in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools frequencies__Hz : float | list[float] Frequency(s) of the sawtooth current(s) in Hertz. Must be: - Single float: used for all pools - List of floats: must match n_pools offsets__muV : float | list[float] DC offset(s) to add to the sawtooth current(s) in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools widths : float | list[float] Width(s) of the rising edge as proportion of period (0 to 1). Must be: - Single float: used for all pools - List of floats: must match n_pools phases__rad : float | list[float] Phase(s) of the sawtooth current(s) in radians. Must be: - Single float: used for all pools - List of floats: must match n_pools Raises ------ ValueError If the parameters are lists and the length of the parameters does not match n_pools Returns ------- INPUT_CURRENT__MATRIX Matrix of shape (n_pools, t_points) containing sawtooth currents """ t = np.arange(0, t_points * timestep_ms, timestep_ms) # Convert parameters to lists amplitudes_list = cast( list[float], [amplitudes__muV] * n_pools if isinstance(amplitudes__muV, float) else amplitudes__muV, ) frequencies_list = cast( list[float], [frequencies__Hz] * n_pools if isinstance(frequencies__Hz, float) else frequencies__Hz, ) offsets_list = cast( list[float], [offsets__muV] * n_pools if isinstance(offsets__muV, float) else offsets__muV, ) widths_list = cast( list[float], [widths] * n_pools if isinstance(widths, float) else widths ) phases_list = cast( list[float], [phases__rad] * n_pools if isinstance(phases__rad, float) else phases__rad, ) if len(amplitudes_list) != n_pools: raise ValueError( f"Length of amplitudes__muV ({len(amplitudes_list)}) must match n_pools ({n_pools})" ) if len(frequencies_list) != n_pools: raise ValueError( f"Length of frequencies__Hz ({len(frequencies_list)}) must match n_pools ({n_pools})" ) if len(offsets_list) != n_pools: raise ValueError( f"Length of offsets__muV ({len(offsets_list)}) must match n_pools ({n_pools})" ) if len(widths_list) != n_pools: raise ValueError( f"Length of widths ({len(widths_list)}) must match n_pools ({n_pools})" ) if len(phases_list) != n_pools: raise ValueError( f"Length of phases__rad ({len(phases_list)}) must match n_pools ({n_pools})" ) input_current__matrix = np.zeros((n_pools, t_points)) for i in range(n_pools): phase_t = 2 * np.pi * frequencies_list[i] * t / 1000 + phases_list[i] sawtooth = (phase_t / (2 * np.pi)) % 1 sawtooth = np.where( sawtooth < widths_list[i], sawtooth / widths_list[i], (1 - sawtooth) / (1 - widths_list[i]), ) input_current__matrix[i] = amplitudes_list[i] * sawtooth + offsets_list[i] return input_current__matrix
[docs] @beartype def create_step_current( n_pools: int, t_points: int, timestep_ms: float, step_heights__muV: float | list[float], step_durations__ms: float | list[float], offsets__muV: float | list[float] = 0.0, ) -> INPUT_CURRENT__MATRIX: """Create a matrix of step currents for multiple pools. Parameters ---------- n_pools : int Number of current pools to generate t_points : int Number of time points timestep_ms : float Time step in milliseconds. step_heights__muV : float | list[float] Step height(s) for the current(s) in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools step_durations__ms : float | list[float] Step duration(s) in milliseconds. Must be: - Single float: used for all pools - List of floats: must match n_pools offsets__muV : float | list[float] DC offset(s) to add to the step current(s) in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools Raises ------ ValueError If the parameters are lists and the length of the parameters does not match n_pools Returns ------- INPUT_CURRENT__MATRIX Matrix of shape (n_pools, t_points) containing step currents """ # Convert parameters to lists step_heights_list = cast( list[float], [step_heights__muV] * n_pools if isinstance(step_heights__muV, float) else step_heights__muV, ) step_durations_list = cast( list[float], [step_durations__ms] * n_pools if isinstance(step_durations__ms, float) else step_durations__ms, ) offsets_list = cast( list[float], [offsets__muV] * n_pools if isinstance(offsets__muV, float) else offsets__muV, ) if len(step_heights_list) != n_pools: raise ValueError( f"Length of step_heights__muV ({len(step_heights_list)}) must match n_pools ({n_pools})" ) if len(step_durations_list) != n_pools: raise ValueError( f"Length of step_durations__ms ({len(step_durations_list)}) must match n_pools ({n_pools})" ) if len(offsets_list) != n_pools: raise ValueError( f"Length of offsets__muV ({len(offsets_list)}) must match n_pools ({n_pools})" ) input_current__matrix = np.zeros((n_pools, t_points)) for i in range(n_pools): current = np.zeros(t_points) # Create step: constant value for duration, then back to zero duration_points = int(step_durations_list[i] / timestep_ms) if duration_points > 0: end_idx = min(duration_points, t_points) current[:end_idx] = step_heights_list[i] input_current__matrix[i] = current + offsets_list[i] return input_current__matrix
[docs] @beartype def create_ramp_current( n_pools: int, t_points: int, start_currents__muV: float | list[float], end_currents__muV: float | list[float], offsets__muV: float | list[float] = 0.0, ) -> INPUT_CURRENT__MATRIX: """Create a matrix of ramp currents for multiple pools. Parameters ---------- n_pools : int Number of current pools to generate t_points : int Number of time points start_currents__muV : float | list[float] Starting current(s) for the ramp in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools end_currents__muV : float | list[float] Ending current(s) for the ramp in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools offsets__muV : float | list[float] DC offset(s) to add to the ramp current(s) in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools Raises ------ ValueError If the parameters are lists and the length of the parameters does not match n_pools Returns ------- INPUT_CURRENT__MATRIX Matrix of shape (n_pools, t_points) containing ramp currents """ # Convert parameters to lists start_currents_list = cast( list[float], [start_currents__muV] * n_pools if isinstance(start_currents__muV, float) else start_currents__muV, ) end_currents_list = cast( list[float], [end_currents__muV] * n_pools if isinstance(end_currents__muV, float) else end_currents__muV, ) offsets_list = cast( list[float], [offsets__muV] * n_pools if isinstance(offsets__muV, float) else offsets__muV, ) if len(start_currents_list) != n_pools: raise ValueError( f"Length of start_currents__muV ({len(start_currents_list)}) must match n_pools ({n_pools})" ) if len(end_currents_list) != n_pools: raise ValueError( f"Length of end_currents__muV ({len(end_currents_list)}) must match n_pools ({n_pools})" ) if len(offsets_list) != n_pools: raise ValueError( f"Length of offsets__muV ({len(offsets_list)}) must match n_pools ({n_pools})" ) input_current__matrix = np.zeros((n_pools, t_points)) for i in range(n_pools): ramp = np.linspace(start_currents_list[i], end_currents_list[i], t_points) input_current__matrix[i] = ramp + offsets_list[i] return input_current__matrix
[docs] @beartype def create_trapezoid_current( n_pools: int, t_points: int, timestep_ms: float, amplitudes__muV: float | list[float], rise_times__ms: float | list[float] = 100.0, plateau_times__ms: float | list[float] = 200.0, fall_times__ms: float | list[float] = 100.0, offsets__muV: float | list[float] = 0.0, delays__ms: float | list[float] = 0.0, ) -> INPUT_CURRENT__MATRIX: """Create a matrix of trapezoidal currents for multiple pools. Parameters ---------- n_pools : int Number of current pools to generate t_points : int Number of time points timestep_ms : float Time step in milliseconds amplitudes__muV : float | list[float] Amplitude(s) of the trapezoidal current(s) in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools rise_times__ms : float | list[float] Duration(s) of the rising phase in milliseconds. Must be: - Single float: used for all pools - List of floats: must match n_pools plateau_times__ms : float | list[float] Duration(s) of the plateau phase in milliseconds. Must be: - Single float: used for all pools - List of floats: must match n_pools fall_times__ms : float | list[float] Duration(s) of the falling phase in milliseconds. Must be: - Single float: used for all pools - List of floats: must match n_pools offsets__muV : float | list[float] DC offset(s) to add to the trapezoidal current(s) in microvolts. Must be: - Single float: used for all pools - List of floats: must match n_pools delays__ms : float | list[float] Delay(s) before starting the trapezoid in milliseconds. Must be: - Single float: used for all pools - List of floats: must match n_pools Raises ------ ValueError If the parameters are lists and the length of the parameters does not match n_pools Returns ------- INPUT_CURRENT__MATRIX Matrix of shape (n_pools, t_points) containing trapezoidal currents """ # Convert parameters to lists amplitudes_list = cast( list[float], [amplitudes__muV] * n_pools if isinstance(amplitudes__muV, float) else amplitudes__muV, ) rise_times_list = cast( list[float], [rise_times__ms] * n_pools if isinstance(rise_times__ms, float) else rise_times__ms, ) plateau_times_list = cast( list[float], [plateau_times__ms] * n_pools if isinstance(plateau_times__ms, float) else plateau_times__ms, ) fall_times_list = cast( list[float], [fall_times__ms] * n_pools if isinstance(fall_times__ms, float) else fall_times__ms, ) offsets_list = cast( list[float], [offsets__muV] * n_pools if isinstance(offsets__muV, float) else offsets__muV, ) delays_list = cast( list[float], [delays__ms] * n_pools if isinstance(delays__ms, float) else delays__ms, ) if len(amplitudes_list) != n_pools: raise ValueError( f"Length of amplitudes__muV ({len(amplitudes_list)}) must match n_pools ({n_pools})" ) if len(rise_times_list) != n_pools: raise ValueError( f"Length of rise_times__ms ({len(rise_times_list)}) must match n_pools ({n_pools})" ) if len(plateau_times_list) != n_pools: raise ValueError( f"Length of plateau_times__ms ({len(plateau_times_list)}) must match n_pools ({n_pools})" ) if len(fall_times_list) != n_pools: raise ValueError( f"Length of fall_times__ms ({len(fall_times_list)}) must match n_pools ({n_pools})" ) if len(offsets_list) != n_pools: raise ValueError( f"Length of offsets__muV ({len(offsets_list)}) must match n_pools ({n_pools})" ) if len(delays_list) != n_pools: raise ValueError( f"Length of delays__ms ({len(delays_list)}) must match n_pools ({n_pools})" ) input_current__matrix = np.zeros((n_pools, t_points)) for i in range(n_pools): # Calculate indices for each phase delay_points = int(delays_list[i] / timestep_ms) rise_points = int(rise_times_list[i] / timestep_ms) plateau_points = int(plateau_times_list[i] / timestep_ms) fall_points = int(fall_times_list[i] / timestep_ms) # Create the base trapezoid shape trapezoid = np.zeros(t_points) # Calculate start indices for each phase rise_start = delay_points plateau_start = rise_start + rise_points fall_start = plateau_start + plateau_points end_idx = fall_start + fall_points # Ensure we don't exceed array bounds if rise_start < t_points: # Rising phase (linear ramp up) rise_end = min(plateau_start, t_points) if rise_end > rise_start: points_to_fill = rise_end - rise_start trapezoid[rise_start:rise_end] = np.linspace(0, 1, points_to_fill) # Plateau phase (constant) if plateau_start < t_points: plateau_end = min(fall_start, t_points) if plateau_end > plateau_start: trapezoid[plateau_start:plateau_end] = 1 # Falling phase (linear ramp down) if fall_start < t_points: fall_end = min(end_idx, t_points) if fall_end > fall_start: points_to_fill = fall_end - fall_start trapezoid[fall_start:fall_end] = np.linspace( 1, 0, points_to_fill ) input_current__matrix[i] = amplitudes_list[i] * trapezoid + offsets_list[i] return input_current__matrix