photonforge 1.3.0__cp310-cp310-win_amd64.whl → 1.3.2__cp310-cp310-win_amd64.whl
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.
- photonforge/__init__.py +17 -12
- photonforge/_backend/default_project.py +398 -22
- photonforge/circuit_base.py +5 -40
- photonforge/extension.cp310-win_amd64.pyd +0 -0
- photonforge/live_viewer.py +2 -2
- photonforge/{analytic_models.py → models/analytic.py} +47 -23
- photonforge/models/circuit.py +684 -0
- photonforge/{data_model.py → models/data.py} +4 -4
- photonforge/{tidy3d_model.py → models/tidy3d.py} +772 -10
- photonforge/parametric.py +60 -28
- photonforge/plotting.py +1 -1
- photonforge/pretty.py +1 -1
- photonforge/thumbnails/electrical_absolute.svg +8 -0
- photonforge/thumbnails/electrical_adder.svg +9 -0
- photonforge/thumbnails/electrical_amplifier.svg +5 -0
- photonforge/thumbnails/electrical_differential.svg +6 -0
- photonforge/thumbnails/electrical_integral.svg +8 -0
- photonforge/thumbnails/electrical_multiplier.svg +9 -0
- photonforge/thumbnails/filter.svg +8 -0
- photonforge/thumbnails/optical_amplifier.svg +5 -0
- photonforge/thumbnails.py +10 -38
- photonforge/time_steppers/amplifier.py +353 -0
- photonforge/{analytic_time_steppers.py → time_steppers/analytic.py} +191 -2
- photonforge/{circuit_time_stepper.py → time_steppers/circuit.py} +6 -5
- photonforge/time_steppers/filter.py +400 -0
- photonforge/time_steppers/math.py +331 -0
- photonforge/{modulator_time_steppers.py → time_steppers/modulator.py} +9 -20
- photonforge/{s_matrix_time_stepper.py → time_steppers/s_matrix.py} +3 -3
- photonforge/{sink_time_steppers.py → time_steppers/sink.py} +6 -8
- photonforge/{source_time_steppers.py → time_steppers/source.py} +20 -18
- photonforge/typing.py +5 -0
- photonforge/utils.py +89 -15
- {photonforge-1.3.0.dist-info → photonforge-1.3.2.dist-info}/METADATA +2 -2
- {photonforge-1.3.0.dist-info → photonforge-1.3.2.dist-info}/RECORD +37 -27
- photonforge/circuit_model.py +0 -335
- photonforge/eme_model.py +0 -816
- {photonforge-1.3.0.dist-info → photonforge-1.3.2.dist-info}/WHEEL +0 -0
- {photonforge-1.3.0.dist-info → photonforge-1.3.2.dist-info}/entry_points.txt +0 -0
- {photonforge-1.3.0.dist-info → photonforge-1.3.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import numpy
|
|
5
|
+
from scipy import signal
|
|
6
|
+
|
|
7
|
+
from ..extension import (
|
|
8
|
+
Component,
|
|
9
|
+
Interpolator,
|
|
10
|
+
PoleResidueMatrix,
|
|
11
|
+
TimeDomainModel,
|
|
12
|
+
TimeStepper,
|
|
13
|
+
register_time_stepper_class,
|
|
14
|
+
)
|
|
15
|
+
from ..typing import (
|
|
16
|
+
Frequency,
|
|
17
|
+
Impedance,
|
|
18
|
+
Loss,
|
|
19
|
+
PositiveFloat,
|
|
20
|
+
PositiveInt,
|
|
21
|
+
TimeDelay,
|
|
22
|
+
annotate,
|
|
23
|
+
)
|
|
24
|
+
from .modulator import _get_impedance_runner
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FilterTimeStepper(TimeStepper):
|
|
28
|
+
"""Filter time stepper for electrical signals
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
family: Filter family.
|
|
32
|
+
shape: Filter shape.
|
|
33
|
+
f_cutoff: Cutoff frequency for low- and high-pass filter shapes. For
|
|
34
|
+
``family=="tunable_lp_rc"``, this can be an :class:`Interpolator`
|
|
35
|
+
to provide the dependency with the input voltage. For band-pass
|
|
36
|
+
and band-stop shapes, this is a 2-value sequence with low and high
|
|
37
|
+
cutoff frequencies.
|
|
38
|
+
order: Filter order.
|
|
39
|
+
ripple: Maximum ripple for Chebyshev filters.
|
|
40
|
+
window: Window specification for rectangular filters. Please consult
|
|
41
|
+
the help for ``scipy.signal.firwin`` for valid options.
|
|
42
|
+
a: Recursive (denominator) coefficients for a digital IIR filter.
|
|
43
|
+
b: Direct (numerator) coefficients for a digital FIR or IIR filter.
|
|
44
|
+
taps: Length of rectangular or Gaussian filters.
|
|
45
|
+
insertion_loss: Insertion loss added to the filter response.
|
|
46
|
+
ports: Input and output port names. If not set, the *sorted* list of
|
|
47
|
+
port names from the component is used.
|
|
48
|
+
z0: Characteristic impedance of the electrical port used to convert
|
|
49
|
+
the input control field amplitude to voltage for the
|
|
50
|
+
``"tunable_lp_rc"`` family. If ``None``, derived from port
|
|
51
|
+
impedance, calculated by mode-solving, or set to 50 Ω.
|
|
52
|
+
mesh_refinement: Minimal number of mesh elements per wavelength used
|
|
53
|
+
for mode solving.
|
|
54
|
+
verbose: Flag setting the verbosity of mode solver runs.
|
|
55
|
+
|
|
56
|
+
Note:
|
|
57
|
+
Filtering is applied to input signals from the first port (sorted
|
|
58
|
+
alphabetically) and output on the second port. For the
|
|
59
|
+
``"tunable_lp_rc"``, the third port contains the control signal.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
*,
|
|
65
|
+
family: Literal[
|
|
66
|
+
"tunable_lp_rc",
|
|
67
|
+
"digital",
|
|
68
|
+
"rc",
|
|
69
|
+
"butterworth",
|
|
70
|
+
"bessel",
|
|
71
|
+
"cheby1",
|
|
72
|
+
"rectangular",
|
|
73
|
+
"gaussian",
|
|
74
|
+
],
|
|
75
|
+
shape: Literal["lp", "hp", "bp", "bs"] = "lp",
|
|
76
|
+
f_cutoff: Frequency
|
|
77
|
+
| annotate(Sequence[Frequency], minItems=2, maxItems=2)
|
|
78
|
+
| Interpolator = 0,
|
|
79
|
+
order: PositiveInt = 1,
|
|
80
|
+
ripple: Loss = 0,
|
|
81
|
+
window: str
|
|
82
|
+
| tuple[str, float]
|
|
83
|
+
| tuple[str, float, float]
|
|
84
|
+
| tuple[str, Sequence[float]] = "hann",
|
|
85
|
+
a: Sequence[complex] = (1.0,),
|
|
86
|
+
b: Sequence[complex] = (),
|
|
87
|
+
taps: PositiveInt = 101,
|
|
88
|
+
insertion_loss: Loss = 0,
|
|
89
|
+
ports: annotate(Sequence[str], minItems=2, maxItems=2) | None = None,
|
|
90
|
+
z0: Impedance | Interpolator | None = None,
|
|
91
|
+
mesh_refinement: PositiveFloat | None = None,
|
|
92
|
+
verbose: bool = True,
|
|
93
|
+
):
|
|
94
|
+
super().__init__(
|
|
95
|
+
family=family,
|
|
96
|
+
shape=shape,
|
|
97
|
+
f_cutoff=f_cutoff,
|
|
98
|
+
order=order,
|
|
99
|
+
ripple=ripple,
|
|
100
|
+
window=window,
|
|
101
|
+
a=a,
|
|
102
|
+
b=b,
|
|
103
|
+
taps=taps,
|
|
104
|
+
insertion_loss=insertion_loss,
|
|
105
|
+
ports=ports,
|
|
106
|
+
z0=z0,
|
|
107
|
+
mesh_refinement=mesh_refinement,
|
|
108
|
+
verbose=verbose,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def setup_state(
|
|
112
|
+
self,
|
|
113
|
+
*,
|
|
114
|
+
component: Component,
|
|
115
|
+
time_step: TimeDelay,
|
|
116
|
+
verbose: bool | None = None,
|
|
117
|
+
**kwargs,
|
|
118
|
+
):
|
|
119
|
+
"""Initialize internal state.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
component: Component representing the filter.
|
|
123
|
+
time_step: The interval between time steps (in seconds).
|
|
124
|
+
kwargs: Unused.
|
|
125
|
+
"""
|
|
126
|
+
p = self.parametric_kwargs
|
|
127
|
+
self._family = p["family"]
|
|
128
|
+
|
|
129
|
+
required_ports = 3 if self._family == "tunable_lp_rc" else 2
|
|
130
|
+
|
|
131
|
+
ports = p["ports"]
|
|
132
|
+
component_ports = sorted(component.select_ports("electrical"))
|
|
133
|
+
if ports is None:
|
|
134
|
+
ports = component_ports
|
|
135
|
+
if len(ports) != required_ports:
|
|
136
|
+
raise RuntimeError(
|
|
137
|
+
f"FilterTimeStepper of family {self._family!r} can only be used in components "
|
|
138
|
+
f"with {required_ports} electrical ports."
|
|
139
|
+
)
|
|
140
|
+
elif len(ports) != 2:
|
|
141
|
+
raise ValueError("'Argument 'ports' must be a sequence of 2 port names.")
|
|
142
|
+
elif not all(name in component_ports for name in ports):
|
|
143
|
+
raise RuntimeError(
|
|
144
|
+
f"Not all port names defined in FilterTimeStepper match the electrical port "
|
|
145
|
+
f"names in component '{component.name}'."
|
|
146
|
+
)
|
|
147
|
+
elif required_ports == 3:
|
|
148
|
+
component_ports.remove(ports[0])
|
|
149
|
+
component_ports.remove(ports[1])
|
|
150
|
+
ports = (*ports, component_ports[0])
|
|
151
|
+
|
|
152
|
+
self.keys = tuple(p + "@0" for p in ports)
|
|
153
|
+
|
|
154
|
+
self._gain = 10.0 ** (p["insertion_loss"] / -20.0)
|
|
155
|
+
|
|
156
|
+
f_nyquist = 0.5 / time_step
|
|
157
|
+
|
|
158
|
+
if self._family == "tunable_lp_rc":
|
|
159
|
+
self._time_step = time_step
|
|
160
|
+
self._f_cutoff = p["f_cutoff"]
|
|
161
|
+
if not isinstance(self._f_cutoff, Interpolator):
|
|
162
|
+
self._f_cutoff = Interpolator([0], [self._f_cutoff])
|
|
163
|
+
|
|
164
|
+
if _get_impedance_runner(self, component, ports[2], time_step, verbose) is not None:
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
elif self._family == "digital":
|
|
168
|
+
a = numpy.asarray(p["a"], dtype=float).ravel()
|
|
169
|
+
b = numpy.asarray(p["b"], dtype=float).ravel()
|
|
170
|
+
if b.size < 1 or a.size < 1:
|
|
171
|
+
raise RuntimeError(
|
|
172
|
+
"Digital filter requires 'b' and 'a' coefficients of size greater than 0."
|
|
173
|
+
)
|
|
174
|
+
self._b = b / a[0]
|
|
175
|
+
self._a = a[1:] / a[0]
|
|
176
|
+
self._x = numpy.empty(b.size - 1, dtype=float)
|
|
177
|
+
self._y = numpy.empty(self._a.size, dtype=float)
|
|
178
|
+
|
|
179
|
+
elif self._family in ("rectangular", "gaussian"):
|
|
180
|
+
if self._family == "rectangular":
|
|
181
|
+
shape = p["shape"]
|
|
182
|
+
if shape == "lp":
|
|
183
|
+
b = signal.firwin(
|
|
184
|
+
p["taps"], p["f_cutoff"] / f_nyquist, pass_zero=True, window=p["window"]
|
|
185
|
+
)
|
|
186
|
+
elif shape == "hp":
|
|
187
|
+
b = signal.firwin(
|
|
188
|
+
p["taps"], p["f_cutoff"] / f_nyquist, pass_zero=False, window=p["window"]
|
|
189
|
+
)
|
|
190
|
+
elif shape == "bp":
|
|
191
|
+
f_low, f_high = p["f_cutoff"]
|
|
192
|
+
b = signal.firwin(
|
|
193
|
+
p["taps"],
|
|
194
|
+
[f_low / f_nyquist, f_high / f_nyquist],
|
|
195
|
+
pass_zero=False,
|
|
196
|
+
window=p["window"],
|
|
197
|
+
)
|
|
198
|
+
elif shape == "bs":
|
|
199
|
+
f_low, f_high = p["f_cutoff"]
|
|
200
|
+
b = signal.firwin(
|
|
201
|
+
p["taps"],
|
|
202
|
+
[f_low / f_nyquist, f_high / f_nyquist],
|
|
203
|
+
pass_zero=True,
|
|
204
|
+
window=p["window"],
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
raise ValueError(f"Unrecognized filter shape {shape!r}.")
|
|
208
|
+
|
|
209
|
+
else:
|
|
210
|
+
shape = p["shape"]
|
|
211
|
+
taps = p["taps"]
|
|
212
|
+
k = numpy.arange(taps)
|
|
213
|
+
fs = 1.0 / time_step
|
|
214
|
+
fpos = k / taps * fs
|
|
215
|
+
f_signed = numpy.where(k <= taps // 2, fpos, fpos - fs)
|
|
216
|
+
|
|
217
|
+
def lp(freqs, f_cutoff, m):
|
|
218
|
+
return numpy.exp(-0.5 * numpy.log(2.0) * numpy.abs(freqs / f_cutoff) ** m)
|
|
219
|
+
|
|
220
|
+
def hp(freqs, f_cutoff, m):
|
|
221
|
+
non_zero = freqs != 0
|
|
222
|
+
transformed = numpy.zeros(freqs.size)
|
|
223
|
+
transformed[non_zero] = f_cutoff**2 / numpy.abs(freqs[non_zero])
|
|
224
|
+
h = lp(transformed, f_cutoff, m)
|
|
225
|
+
h[numpy.logical_not(non_zero)] = 0.0
|
|
226
|
+
return h
|
|
227
|
+
|
|
228
|
+
m = 2 * p["order"]
|
|
229
|
+
if shape == "lp":
|
|
230
|
+
h = lp(f_signed, p["f_cutoff"], m)
|
|
231
|
+
elif shape == "hp":
|
|
232
|
+
h = hp(f_signed, p["f_cutoff"], m)
|
|
233
|
+
else:
|
|
234
|
+
f_low, f_high = p["f_cutoff"]
|
|
235
|
+
f0 = 0.5 * (f_high + f_low)
|
|
236
|
+
df = 0.5 * (f_high - f_low)
|
|
237
|
+
if shape == "bp":
|
|
238
|
+
h = lp(f_signed - f0, df, m) + lp(f_signed + f0, df, m)
|
|
239
|
+
elif shape == "bs":
|
|
240
|
+
h = hp(f_signed - f0, df, m) * hp(f_signed + f0, df, m)
|
|
241
|
+
else:
|
|
242
|
+
raise ValueError(f"Unrecognized filter shape {shape!r}.")
|
|
243
|
+
|
|
244
|
+
b = numpy.roll(numpy.fft.ifft(h).real, (taps - 1) // 2)
|
|
245
|
+
|
|
246
|
+
if b.size < 1:
|
|
247
|
+
raise RuntimeError("FIR kernel design failed.")
|
|
248
|
+
|
|
249
|
+
self._b = b.real
|
|
250
|
+
self._a = numpy.empty(0)
|
|
251
|
+
self._x = numpy.empty(b.size - 1, dtype=float)
|
|
252
|
+
self._y = numpy.empty(0, dtype=float)
|
|
253
|
+
|
|
254
|
+
self._family = "digital"
|
|
255
|
+
|
|
256
|
+
elif self._family in ("rc", "butterworth", "bessel", "cheby1"):
|
|
257
|
+
shape = p["shape"]
|
|
258
|
+
if shape == "lp":
|
|
259
|
+
f_cutoff = p["f_cutoff"]
|
|
260
|
+
if not (0 < f_cutoff < f_nyquist):
|
|
261
|
+
raise ValueError(
|
|
262
|
+
f"'f_cutoff' ({f_cutoff}) must be positive and less than "
|
|
263
|
+
f"'1 / (2 * time_step)' ({f_nyquist})"
|
|
264
|
+
)
|
|
265
|
+
w0 = 0.0
|
|
266
|
+
wc = 2.0 * numpy.pi * f_cutoff
|
|
267
|
+
elif shape == "hp":
|
|
268
|
+
f_cutoff = p["f_cutoff"]
|
|
269
|
+
if not (0 < f_cutoff < f_nyquist):
|
|
270
|
+
raise ValueError(
|
|
271
|
+
f"'f_cutoff' ({f_cutoff}) must be positive and less than "
|
|
272
|
+
f"'1 / (2 * time_step)' ({f_nyquist})"
|
|
273
|
+
)
|
|
274
|
+
w0 = 0.0
|
|
275
|
+
wc = 2.0 * numpy.pi * f_cutoff
|
|
276
|
+
elif shape == "bp":
|
|
277
|
+
f_low, f_high = p["f_cutoff"]
|
|
278
|
+
if not (0 < f_low < f_high < f_nyquist):
|
|
279
|
+
raise ValueError(
|
|
280
|
+
f"'f_low' ({f_low}) and 'f_high' ({f_high}) must be positive and "
|
|
281
|
+
f"satisfy 'f_low < f_high < 1 / (2 * time_step)' ({f_nyquist})"
|
|
282
|
+
)
|
|
283
|
+
w0 = numpy.pi * (f_low + f_high)
|
|
284
|
+
wc = numpy.pi * (f_high - f_low)
|
|
285
|
+
elif shape == "bs":
|
|
286
|
+
f_low, f_high = p["f_cutoff"]
|
|
287
|
+
if not (0 < f_low < f_high < f_nyquist):
|
|
288
|
+
raise ValueError(
|
|
289
|
+
f"'f_low' ({f_low}) and 'f_high' ({f_high}) must be positive and "
|
|
290
|
+
f"satisfy 'f_low < f_high < 1 / (2 * time_step)' ({f_nyquist})"
|
|
291
|
+
)
|
|
292
|
+
w0 = numpy.pi * (f_low + f_high)
|
|
293
|
+
wc = numpy.pi * (f_high - f_low)
|
|
294
|
+
else:
|
|
295
|
+
raise ValueError(f"Unrecognized filter shape {shape!r}.")
|
|
296
|
+
|
|
297
|
+
if self._family == "rc":
|
|
298
|
+
if shape == "lp":
|
|
299
|
+
poles = [-wc]
|
|
300
|
+
residues = [wc, 0.0]
|
|
301
|
+
elif shape == "hp":
|
|
302
|
+
poles = [-wc]
|
|
303
|
+
residues = [-wc, 1.0]
|
|
304
|
+
elif shape == "bp":
|
|
305
|
+
poles = [-wc - 1j * w0]
|
|
306
|
+
residues = [wc, 0.0]
|
|
307
|
+
elif shape == "bs":
|
|
308
|
+
poles = [-wc - 1j * w0]
|
|
309
|
+
residues = [-wc, 1.0]
|
|
310
|
+
|
|
311
|
+
else:
|
|
312
|
+
btype = "lowpass" if shape in ("lp", "bp") else "highpass"
|
|
313
|
+
if self._family == "butterworth":
|
|
314
|
+
b, a = signal.butter(p["order"], wc, btype=btype, analog=True)
|
|
315
|
+
elif self._family == "bessel":
|
|
316
|
+
b, a = signal.bessel(p["order"], wc, btype=btype, analog=True, norm="mag")
|
|
317
|
+
else:
|
|
318
|
+
b, a = signal.cheby1(p["order"], p["ripple"], wc, btype=btype, analog=True)
|
|
319
|
+
residues, poles, k = signal.residue(b, a)
|
|
320
|
+
if btype == "lowpass":
|
|
321
|
+
k = numpy.zeros(1)
|
|
322
|
+
residues = numpy.concatenate((residues, k))
|
|
323
|
+
poles = poles - 1j * w0
|
|
324
|
+
|
|
325
|
+
self._time_domain_model = TimeDomainModel(
|
|
326
|
+
PoleResidueMatrix(poles, {tuple(self.keys[:2]): residues}),
|
|
327
|
+
time_step,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
self._family = "time_domain_model"
|
|
331
|
+
|
|
332
|
+
else:
|
|
333
|
+
raise ValueError(f"Unrecognized filter family {self._family!r}.")
|
|
334
|
+
|
|
335
|
+
self.reset()
|
|
336
|
+
|
|
337
|
+
@property
|
|
338
|
+
def status(self):
|
|
339
|
+
if not self._setup_thread.is_alive() and self._status["message"] == "running":
|
|
340
|
+
self._status["message"] = "error"
|
|
341
|
+
with self._lock:
|
|
342
|
+
return self._status
|
|
343
|
+
|
|
344
|
+
def reset(self):
|
|
345
|
+
"""Reset internal state."""
|
|
346
|
+
if self._family == "tunable_lp_rc":
|
|
347
|
+
self._sqrt_r_in = numpy.real(self._z0) ** 0.5
|
|
348
|
+
self._y = 0.0
|
|
349
|
+
elif self._family == "digital":
|
|
350
|
+
self._x.fill(0.0)
|
|
351
|
+
self._y.fill(0.0)
|
|
352
|
+
elif self._family == "time_domain_model":
|
|
353
|
+
self._time_domain_model.reset()
|
|
354
|
+
|
|
355
|
+
def step_single(
|
|
356
|
+
self,
|
|
357
|
+
inputs: numpy.ndarray,
|
|
358
|
+
outputs: numpy.ndarray,
|
|
359
|
+
time_index: int,
|
|
360
|
+
update_state: bool,
|
|
361
|
+
shutdown: bool,
|
|
362
|
+
) -> None:
|
|
363
|
+
"""Take a single time step on the given inputs.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
inputs: Input values at the current time step. Must be a 1D array of
|
|
367
|
+
complex values ordered
|
|
368
|
+
according to :attr:`keys`.
|
|
369
|
+
outputs: Pre-allocated output array where results will be stored.
|
|
370
|
+
Same size and type as ``inputs``.
|
|
371
|
+
time_index: Time series index for the current input.
|
|
372
|
+
update_state: Whether to update the internal stepper state.
|
|
373
|
+
shutdown: Whether this is the last call to the single stepping
|
|
374
|
+
function for the provided :class:`TimeSeries`.
|
|
375
|
+
"""
|
|
376
|
+
if self._family == "tunable_lp_rc":
|
|
377
|
+
v_ctrl = self._sqrt_r_in * inputs[2].real
|
|
378
|
+
f_cutoff = self._f_cutoff(v_ctrl)
|
|
379
|
+
alpha = min(1.0, max(0.0, numpy.exp(-2.0 * numpy.pi * f_cutoff * self._time_step)))
|
|
380
|
+
y = self._y * alpha + inputs[0].real * (1.0 - alpha)
|
|
381
|
+
outputs[1] = self._gain * y
|
|
382
|
+
if update_state:
|
|
383
|
+
self._y = y
|
|
384
|
+
elif self._family == "digital":
|
|
385
|
+
x = inputs[0].real
|
|
386
|
+
y = self._b[0] * x + numpy.dot(self._b[1:], self._x) - numpy.dot(self._a, self._y)
|
|
387
|
+
outputs[1] = self._gain * y
|
|
388
|
+
if update_state:
|
|
389
|
+
if self._x.size > 0:
|
|
390
|
+
self._x[1:] = self._x[:-1]
|
|
391
|
+
self._x[0] = x
|
|
392
|
+
if self._y.size > 0:
|
|
393
|
+
self._y[1:] = self._y[:-1]
|
|
394
|
+
self._y[0] = y
|
|
395
|
+
elif self._family == "time_domain_model":
|
|
396
|
+
self._time_domain_model.step_single(inputs, outputs, time_index, update_state, shutdown)
|
|
397
|
+
outputs[1] *= self._gain
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
register_time_stepper_class(FilterTimeStepper)
|