bluecellulab 2.6.42__tar.gz → 2.6.44__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bluecellulab might be problematic. Click here for more details.

Files changed (106) hide show
  1. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/PKG-INFO +1 -1
  2. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/analysis/inject_sequence.py +4 -4
  3. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/injector.py +28 -6
  4. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/circuit_access/sonata_circuit_access.py +2 -1
  5. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit_simulation.py +16 -3
  6. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/stimulus/circuit_stimulus_definitions.py +20 -0
  7. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/stimulus/factory.py +289 -361
  8. bluecellulab-2.6.44/bluecellulab/stimulus/stimulus.py +782 -0
  9. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab.egg-info/PKG-INFO +1 -1
  10. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab.egg-info/SOURCES.txt +1 -0
  11. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/.compile_mod.sh +0 -0
  12. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/.gitattributes +0 -0
  13. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/.github/dependabot.yml +0 -0
  14. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/.github/workflows/release.yml +0 -0
  15. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/.github/workflows/test.yml +0 -0
  16. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/.gitignore +0 -0
  17. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/.gitlab-ci.yml +0 -0
  18. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/.readthedocs.yml +0 -0
  19. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/.zenodo.json +0 -0
  20. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/AUTHORS.txt +0 -0
  21. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/CHANGELOG.rst +0 -0
  22. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/CITATION.cff +0 -0
  23. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/CONTRIBUTING.rst +0 -0
  24. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/LICENSE +0 -0
  25. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/MANIFEST.in +0 -0
  26. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/Makefile +0 -0
  27. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/README.rst +0 -0
  28. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/__init__.py +0 -0
  29. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/analysis/__init__.py +0 -0
  30. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/analysis/analysis.py +0 -0
  31. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/analysis/plotting.py +0 -0
  32. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/__init__.py +0 -0
  33. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/ballstick/__init__.py +0 -0
  34. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/ballstick/emodel.hoc +0 -0
  35. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/ballstick/morphology.asc +0 -0
  36. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/cell_dict.py +0 -0
  37. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/core.py +0 -0
  38. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/plotting.py +0 -0
  39. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/random.py +0 -0
  40. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/recording.py +0 -0
  41. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/section_distance.py +0 -0
  42. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/serialized_sections.py +0 -0
  43. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/sonata_proxy.py +0 -0
  44. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/stimuli_generator.py +0 -0
  45. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/cell/template.py +0 -0
  46. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/__init__.py +0 -0
  47. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/circuit_access/__init__.py +0 -0
  48. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/circuit_access/bluepy_circuit_access.py +0 -0
  49. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/circuit_access/definition.py +0 -0
  50. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/config/__init__.py +0 -0
  51. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/config/bluepy_simulation_config.py +0 -0
  52. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/config/definition.py +0 -0
  53. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/config/sections.py +0 -0
  54. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/config/sonata_simulation_config.py +0 -0
  55. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/format.py +0 -0
  56. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/iotools.py +0 -0
  57. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/node_id.py +0 -0
  58. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/simulation_access.py +0 -0
  59. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/synapse_properties.py +0 -0
  60. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/circuit/validate.py +0 -0
  61. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/connection.py +0 -0
  62. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/dendrogram.py +0 -0
  63. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/exceptions.py +0 -0
  64. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/graph.py +0 -0
  65. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/hoc/Cell.hoc +0 -0
  66. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/hoc/RNGSettings.hoc +0 -0
  67. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/hoc/TDistFunc.hoc +0 -0
  68. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/hoc/TStim.hoc +0 -0
  69. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/hoc/fileUtils.hoc +0 -0
  70. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/importer.py +0 -0
  71. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/neuron_interpreter.py +0 -0
  72. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/plotwindow.py +0 -0
  73. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/psection.py +0 -0
  74. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/psegment.py +0 -0
  75. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/rngsettings.py +0 -0
  76. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/simulation/__init__.py +0 -0
  77. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/simulation/neuron_globals.py +0 -0
  78. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/simulation/parallel.py +0 -0
  79. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/simulation/simulation.py +0 -0
  80. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/stimulus/__init__.py +0 -0
  81. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/synapse/__init__.py +0 -0
  82. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/synapse/synapse_factory.py +0 -0
  83. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/synapse/synapse_types.py +0 -0
  84. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/tools.py +0 -0
  85. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/type_aliases.py +0 -0
  86. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/utils.py +0 -0
  87. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab/verbosity.py +0 -0
  88. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab.egg-info/dependency_links.txt +0 -0
  89. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab.egg-info/requires.txt +0 -0
  90. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/bluecellulab.egg-info/top_level.txt +0 -0
  91. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/Makefile +0 -0
  92. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/images/voltage-readme.png +0 -0
  93. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/make.bat +0 -0
  94. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/requirements_docs.txt +0 -0
  95. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/source/_static/.gitkeep +0 -0
  96. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/source/api.rst +0 -0
  97. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/source/changelog.rst +0 -0
  98. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/source/compiling-mechanisms.rst +0 -0
  99. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/source/conf.py +0 -0
  100. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/source/contributing.rst +0 -0
  101. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/source/index.rst +0 -0
  102. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/source/list_of_stim.rst +0 -0
  103. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/docs/source/logo/BlueCelluLabBanner.jpg +0 -0
  104. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/pyproject.toml +0 -0
  105. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/setup.cfg +0 -0
  106. {bluecellulab-2.6.42 → bluecellulab-2.6.44}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: bluecellulab
3
- Version: 2.6.42
3
+ Version: 2.6.44
4
4
  Summary: Biologically detailed neural network simulations and analysis.
5
5
  Author: Blue Brain Project, EPFL
6
6
  License: Apache2.0
@@ -30,7 +30,7 @@ class Recording(NamedTuple):
30
30
  current: np.ndarray
31
31
  voltage: np.ndarray
32
32
  time: np.ndarray
33
- spike: np.ndarray | None
33
+ spike: Optional[np.ndarray] = None
34
34
 
35
35
 
36
36
  StimulusRecordings = Dict[str, Recording]
@@ -46,7 +46,7 @@ def run_stimulus(
46
46
  recording_section: str = "soma[0]",
47
47
  recording_segment: float = 0.5,
48
48
  enable_spike_detection: bool = False,
49
- threshold_spike_detection: float = -30,
49
+ threshold_spike_detection: float = -30.0,
50
50
  ) -> Recording:
51
51
  """Creates a cell from template parameters, applies a stimulus, and records
52
52
  the response.
@@ -135,9 +135,9 @@ def apply_multiple_stimuli(
135
135
  stimulus_name: StimulusName,
136
136
  amplitudes: Sequence[float],
137
137
  threshold_based: bool = True,
138
- section_name: str | None = None,
138
+ section_name: Optional[str] = None,
139
139
  segment: float = 0.5,
140
- n_processes: int | None = None,
140
+ n_processes: Optional[int] = None,
141
141
  cvode: bool = True,
142
142
  add_hypamp: bool = True,
143
143
  ) -> StimulusRecordings:
@@ -30,6 +30,8 @@ from bluecellulab.exceptions import BluecellulabError
30
30
  from bluecellulab.rngsettings import RNGSettings
31
31
  from bluecellulab.stimulus.circuit_stimulus_definitions import (
32
32
  ClampMode,
33
+ Linear,
34
+ RelativeLinear,
33
35
  Hyperpolarizing,
34
36
  Noise,
35
37
  OrnsteinUhlenbeck,
@@ -259,12 +261,32 @@ class InjectableMixin:
259
261
  self.persistent.append(tstim) # type: ignore
260
262
  return tstim
261
263
 
262
- def add_replay_relativelinear(self, stimulus):
264
+ def add_replay_linear(self, stimulus: Linear):
265
+ """Add a linear stimulus."""
266
+
267
+ return self.add_ramp(
268
+ stimulus.delay,
269
+ stimulus.delay + stimulus.duration,
270
+ stimulus.amp_start,
271
+ stimulus.amp_end
272
+ )
273
+
274
+ def add_replay_relativelinear(self, stimulus: RelativeLinear):
263
275
  """Add a relative linear stimulus."""
264
- tstim = neuron.h.TStim(0.5, sec=self.soma)
265
- amp = stimulus.percent_start / 100.0 * self.threshold
266
- tstim.pulse(stimulus.delay, stimulus.duration, amp)
267
- self.persistent.append(tstim)
276
+ tstim = neuron.h.TStim(0.5, sec=self.soma) # type: ignore
277
+ amp_start = stimulus.percent_start / 100.0 * self.threshold # type: ignore
278
+ amp_end = stimulus.percent_end / 100.0 * self.threshold # type: ignore
279
+
280
+ tstim.ramp(
281
+ 0.0,
282
+ stimulus.delay, # Time when the ramp starts
283
+ amp_start, # Initial amplitude (amp_start)
284
+ amp_end, # Final amplitude (amp_end)
285
+ stimulus.duration, # Duration of the ramp
286
+ 0.0,
287
+ 0.0
288
+ )
289
+ self.persistent.append(tstim) # type: ignore
268
290
 
269
291
  return tstim
270
292
 
@@ -280,7 +302,7 @@ class InjectableMixin:
280
302
  rng = neuron.h.Random()
281
303
  rng.Random123(seed1, seed2, seed3)
282
304
  else:
283
- raise BluecellulabError("Shot noise stimulus requires Random123")
305
+ raise BluecellulabError("Ornstein-Uhlenbeck noise stimulus requires Random123")
284
306
 
285
307
  self.persistent.append(rng)
286
308
  return rng
@@ -152,8 +152,9 @@ class SonataCircuitAccess:
152
152
 
153
153
  # make multiindex
154
154
  synapses = synapses.reset_index(drop=True)
155
+ synapse_ids = list(synapses.index)
155
156
  synapses.index = pd.MultiIndex.from_tuples(
156
- zip([edge_population_name] * len(synapses), synapses.index),
157
+ [(edge_population_name, syn_id) for syn_id in synapse_ids],
157
158
  names=["edge_name", "synapse_id"],
158
159
  )
159
160
 
@@ -139,6 +139,7 @@ class CircuitSimulation:
139
139
  pre_spike_trains: None | dict[tuple[str, int], Iterable] | dict[int, Iterable] = None,
140
140
  add_shotnoise_stimuli: bool = False,
141
141
  add_ornstein_uhlenbeck_stimuli: bool = False,
142
+ add_linear_stimuli: bool = False,
142
143
  ):
143
144
  """Instantiate a list of cells.
144
145
 
@@ -209,6 +210,11 @@ class CircuitSimulation:
209
210
  of the simulation config,
210
211
  Setting add_stimuli=True,
211
212
  will automatically set this option to True.
213
+ add_linear_stimuli : Process the 'linear' stimuli
214
+ blocks of the simulation config.
215
+ Setting add_stimuli=True,
216
+ will automatically set this option to
217
+ True.
212
218
  """
213
219
  if not isinstance(cells, Iterable) or isinstance(cells, tuple):
214
220
  cells = [cells]
@@ -267,20 +273,23 @@ class CircuitSimulation:
267
273
  add_pulse_stimuli = True
268
274
  add_shotnoise_stimuli = True
269
275
  add_ornstein_uhlenbeck_stimuli = True
276
+ add_linear_stimuli = True
270
277
 
271
278
  if add_noise_stimuli or \
272
279
  add_hyperpolarizing_stimuli or \
273
280
  add_pulse_stimuli or \
274
281
  add_relativelinear_stimuli or \
275
282
  add_shotnoise_stimuli or \
276
- add_ornstein_uhlenbeck_stimuli:
283
+ add_ornstein_uhlenbeck_stimuli or \
284
+ add_linear_stimuli:
277
285
  self._add_stimuli(
278
286
  add_noise_stimuli=add_noise_stimuli,
279
287
  add_hyperpolarizing_stimuli=add_hyperpolarizing_stimuli,
280
288
  add_relativelinear_stimuli=add_relativelinear_stimuli,
281
289
  add_pulse_stimuli=add_pulse_stimuli,
282
290
  add_shotnoise_stimuli=add_shotnoise_stimuli,
283
- add_ornstein_uhlenbeck_stimuli=add_ornstein_uhlenbeck_stimuli
291
+ add_ornstein_uhlenbeck_stimuli=add_ornstein_uhlenbeck_stimuli,
292
+ add_linear_stimuli=add_linear_stimuli
284
293
  )
285
294
 
286
295
  def _add_stimuli(self, add_noise_stimuli=False,
@@ -288,7 +297,8 @@ class CircuitSimulation:
288
297
  add_relativelinear_stimuli=False,
289
298
  add_pulse_stimuli=False,
290
299
  add_shotnoise_stimuli=False,
291
- add_ornstein_uhlenbeck_stimuli=False
300
+ add_ornstein_uhlenbeck_stimuli=False,
301
+ add_linear_stimuli=False
292
302
  ):
293
303
  """Instantiate all the stimuli."""
294
304
  stimuli_entries = self.circuit_access.config.get_all_stimuli_entries()
@@ -316,6 +326,9 @@ class CircuitSimulation:
316
326
  elif isinstance(stimulus, circuit_stimulus_definitions.Pulse):
317
327
  if add_pulse_stimuli:
318
328
  self.cells[cell_id].add_pulse(stimulus)
329
+ elif isinstance(stimulus, circuit_stimulus_definitions.Linear):
330
+ if add_linear_stimuli:
331
+ self.cells[cell_id].add_replay_linear(stimulus)
319
332
  elif isinstance(stimulus, circuit_stimulus_definitions.RelativeLinear):
320
333
  if add_relativelinear_stimuli:
321
334
  self.cells[cell_id].add_replay_relativelinear(stimulus)
@@ -42,6 +42,7 @@ class Pattern(Enum):
42
42
  NOISE = "noise"
43
43
  HYPERPOLARIZING = "hyperpolarizing"
44
44
  PULSE = "pulse"
45
+ LINEAR = "linear"
45
46
  RELATIVE_LINEAR = "relative_linear"
46
47
  SYNAPSE_REPLAY = "synapse_replay"
47
48
  SHOT_NOISE = "shot_noise"
@@ -80,6 +81,8 @@ class Pattern(Enum):
80
81
  return Pattern.HYPERPOLARIZING
81
82
  elif pattern == "pulse":
82
83
  return Pattern.PULSE
84
+ elif pattern == "linear":
85
+ return Pattern.LINEAR
83
86
  elif pattern == "relative_linear":
84
87
  return Pattern.RELATIVE_LINEAR
85
88
  elif pattern == "synapse_replay":
@@ -141,6 +144,7 @@ class Stimulus:
141
144
  delay=stimulus_entry["Delay"],
142
145
  duration=stimulus_entry["Duration"],
143
146
  percent_start=stimulus_entry["PercentStart"],
147
+ percent_end=stimulus_entry["PercentEnd"],
144
148
  )
145
149
  elif pattern == Pattern.SYNAPSE_REPLAY:
146
150
  warnings.warn("Ignoring syanpse replay stimulus as it is not supported")
@@ -230,12 +234,21 @@ class Stimulus:
230
234
  width=stimulus_entry["width"],
231
235
  frequency=stimulus_entry["frequency"],
232
236
  )
237
+ elif pattern == Pattern.LINEAR:
238
+ return Linear(
239
+ target=stimulus_entry["node_set"],
240
+ delay=stimulus_entry["delay"],
241
+ duration=stimulus_entry["duration"],
242
+ amp_start=stimulus_entry["amp_start"],
243
+ amp_end=stimulus_entry["amp_end"],
244
+ )
233
245
  elif pattern == Pattern.RELATIVE_LINEAR:
234
246
  return RelativeLinear(
235
247
  target=stimulus_entry["node_set"],
236
248
  delay=stimulus_entry["delay"],
237
249
  duration=stimulus_entry["duration"],
238
250
  percent_start=stimulus_entry["percent_start"],
251
+ percent_end=stimulus_entry["percent_end"],
239
252
  )
240
253
  elif pattern == Pattern.SYNAPSE_REPLAY:
241
254
  return SynapseReplay(
@@ -322,9 +335,16 @@ class Pulse(Stimulus):
322
335
  frequency: float
323
336
 
324
337
 
338
+ @dataclass(frozen=True, config=dict(extra="forbid"))
339
+ class Linear(Stimulus):
340
+ amp_start: float
341
+ amp_end: float
342
+
343
+
325
344
  @dataclass(frozen=True, config=dict(extra="forbid"))
326
345
  class RelativeLinear(Stimulus):
327
346
  percent_start: float
347
+ percent_end: float
328
348
 
329
349
 
330
350
  @dataclass(frozen=True, config=dict(extra="forbid"))