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

add Zap and DelayedZap stimuli, and sinespec factory fct #1

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
126 changes: 126 additions & 0 deletions bluecellulab/stimulus/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,23 @@ def current(self) -> np.ndarray:
return np.linspace(self.amplitude_start, self.amplitude_end, len(self.time))


class Zap(Stimulus):
def __init__(self, dt: float, duration: float, amplitude: float) -> None:
super().__init__(dt)
self.duration = duration
self.amplitude = amplitude

@property
def time(self) -> np.ndarray:
return np.arange(0.0, self.duration, self.dt)

@property
def current(self) -> np.ndarray:
return self.amplitude * np.sin(
2.0 * np.pi * (1.0 + (1.0 / (5.15 - (self.time - 0.1)))) * (self.time - 0.1)
)


class Step(Stimulus):

def __init__(self):
Expand Down Expand Up @@ -276,6 +293,71 @@ def threshold_based(
return res


class DelayedZap(Stimulus):

def __init__(self):
raise NotImplementedError(
"This class cannot be instantiated directly. "
"Please use the class methods 'amplitude_based' "
"or 'threshold_based' to create objects."
)

@classmethod
def amplitude_based(
cls,
dt: float,
pre_delay: float,
duration: float,
post_delay: float,
amplitude: float,
) -> CombinedStimulus:
"""Create a DelayedZap stimulus from given time events and amplitude.
Args:
dt: The time step of the stimulus.
pre_delay: The delay before the start of the step.
duration: The duration of the step.
post_delay: The time to wait after the end of the step.
amplitude: The amplitude of the step.
"""
return (
Empty(dt, duration=pre_delay)
+ Zap(dt, duration=duration, amplitude=amplitude)
+ Empty(dt, duration=post_delay)
)

@classmethod
def threshold_based(
cls,
dt: float,
pre_delay: float,
duration: float,
post_delay: float,
threshold_current: float,
threshold_percentage: float,
) -> CombinedStimulus:
"""Creates a SineSpec stimulus with respect to the threshold current.
Args:
dt: The time step of the stimulus.
pre_delay: The delay before the start of the step.
duration: The duration of the step.
post_delay: The time to wait after the end of the step.
threshold_current: The threshold current of the Cell.
threshold_percentage: Percentage of desired threshold_current amplification.
"""
amplitude = threshold_current * threshold_percentage / 100
res = cls.amplitude_based(
dt,
pre_delay=pre_delay,
duration=duration,
post_delay=post_delay,
amplitude=amplitude,
)
return res


class StimulusFactory:
def __init__(self, dt: float):
self.dt = dt
Expand Down Expand Up @@ -561,3 +643,47 @@ def neg_cheops(
+ Empty(self.dt, duration=post_delay)
)
return result

def sinespec(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I hope all the parameters and formulae in sinespec are the same as those that we have in bluepyemodel ecode of the sinespec

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, thanks to the power of copy/pasting!

self,
threshold_current: Optional[float] = None,
threshold_percentage: Optional[float] = 60.0,
amplitude: Optional[float] = None,
pre_delay: float = 0,
) -> Stimulus:
"""Returns the SineSpec Stimulus object, a type of Zap stimulus.
Args:
threshold_current: The threshold current of the Cell.
threshold_percentage: Percentage of desired threshold_current amplification.
amplitude: Raw amplitude of input current.
pre_delay: delay before the start of the stimulus
"""
duration = 5000.0
post_delay = 0

if amplitude is not None:
if threshold_current is not None and threshold_current != 0 and threshold_percentage is not None:
logger.info(
"amplitude, threshold_current and threshold_percentage are all set in sinespec."
" Will only keep amplitude value."
)
return DelayedZap.amplitude_based(
self.dt,
pre_delay=pre_delay,
duration=duration,
post_delay=post_delay,
amplitude=amplitude,
)

if threshold_current is not None and threshold_current != 0 and threshold_percentage is not None:
return DelayedZap.threshold_based(
self.dt,
pre_delay=pre_delay,
duration=duration,
post_delay=post_delay,
threshold_current=threshold_current,
threshold_percentage=threshold_percentage,
)

raise TypeError("You have to give either threshold_current or amplitude")
1 change: 1 addition & 0 deletions docs/requirements_docs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ sphinx>=7.0.1
sphinx-bluebrain-theme>=0.4.1
sphinx_autodoc_typehints>=1.23.2
seaborn>=0.13.2
pydantic<=2.9.2
18 changes: 18 additions & 0 deletions tests/test_stimulus/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Step,
Ramp,
StimulusFactory,
Zap,
)


Expand Down Expand Up @@ -186,6 +187,23 @@ def test_create_neg_cheops(self):
with pytest.raises(TypeError, match="You have to give either threshold_current or amplitude"):
self.factory.neg_cheops()

def test_create_sinespec(self):
s = self.factory.sinespec(threshold_current=1)
assert isinstance(s, CombinedStimulus)
assert s.stimulus_time == 5000.0

test_amp = 0.1
s = self.factory.sinespec(amplitude=test_amp)
assert isinstance(s, CombinedStimulus)
assert s.stimulus_time == 5000.0
assert np.max(np.abs(s.current)) <= test_amp
assert (s.current == Zap(dt=self.dt, duration=5000.0, amplitude=test_amp).current).all()

with pytest.raises(TypeError, match="You have to give either threshold_current or amplitude"):
self.factory.sinespec(threshold_current=0.0)
with pytest.raises(TypeError, match="You have to give either threshold_current or amplitude"):
self.factory.sinespec()


def test_combined_stimulus():
"""Test combining Stimulus objects."""
Expand Down
Loading