Skip to content

Circuits

Electric circuits.

CircuitElement(label, visual=None, *, q=1.0, q_d1=0.0, L=1.0, R=0.1, C=1.0, V=0.0, omega=1.0)

Bases: Element

RLC circuit whose charge follows L q'' + R q' + q / C = V cos(ω t).

Source code in src/skcomplex/systems/engineering/circuits/_circuits.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    q: Annotated[object, StateSpec(order=2)] = 1.0,
    q_d1: float = 0.0,
    L: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    R: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
    C: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    V: Annotated[object, ParameterSpec()] = 0.0,
    omega: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
) -> None:
    super().__init__(label, visual=visual)
    self.q = q
    self.q_d1 = q_d1
    self.L = L
    self.R = R
    self.C = C
    self.V = V
    self.omega = omega

ColpittsCouplingInteraction(label, *, source, target, k=0.4)

Bases: RateInteraction

Coupling of a Colpitts oscillator to another through their second variables, y_t' += k (y_s - y_t).

Parameters:

Name Type Description Default
label str

The label of the interaction.

required
source Annotated[str, RoleSpec()]

The path of the oscillator whose second variable the coupling reads.

required
target Annotated[str, RoleSpec()]

The path of the oscillator whose second variable the coupling changes.

required
k Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The coupling strength.

0.4

Examples:

>>> from skcomplex.systems.engineering.circuits import ColpittsCouplingInteraction
>>> ColpittsCouplingInteraction('coupling', source='a', target='b', k=0.4).k
0.4
Source code in src/skcomplex/systems/engineering/circuits/_coupled_colpitts.py
135
136
137
138
139
140
141
142
143
144
145
def __init__(
    self,
    label: str,
    *,
    source: Annotated[str, RoleSpec()],
    target: Annotated[str, RoleSpec()],
    k: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.4,
) -> None:
    super().__init__(label, target=target, state='y')
    self.source = source
    self.k = k

ColpittsOscillatorElement(label, visual=None, *, x=0.1, y=0.0, z=0.0, omega=1.0, a=30.0, b=1.0, c=20.0, d=1.0, epsilon=1.0)

Bases: Element

Colpitts oscillator in dimensionless form, whose transistor conducts below a threshold of its third variable.

The oscillator follows x' = omega y - a F(z), y' = c - omega x - z - b y, and z' = epsilon (y - d), with the transistor characteristic F(z) = max(0, -1 - z), which is chaotic for a = 30, b = 1, c = 20, d = 1, and epsilon = 1.

Parameters:

Name Type Description Default
label str

The label of the oscillator.

required
visual VisualSpec | None

How the oscillator is drawn, or None for default_visual_.

None
x Annotated[float, StateSpec()]

The initial first variable.

0.1
y Annotated[float, StateSpec()]

The initial second variable.

0.0
z Annotated[float, StateSpec()]

The initial third variable.

0.0
omega Annotated[float, ParameterSpec(domain=open(0, oo))]

The natural frequency.

1.0
a Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The gain of the transistor.

30.0
b Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The damping of the second variable.

1.0
c Annotated[float, ParameterSpec()]

The bias of the second variable.

20.0
d Annotated[float, ParameterSpec()]

The offset of the third variable.

1.0
epsilon Annotated[float, ParameterSpec(domain=open(0, oo))]

The time scale of the third variable.

1.0

Examples:

>>> from skcomplex.systems.engineering.circuits import ColpittsOscillatorElement
>>> ColpittsOscillatorElement('oscillator').a
30.0
Source code in src/skcomplex/systems/engineering/circuits/_coupled_colpitts.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    x: Annotated[float, StateSpec()] = 0.1,
    y: Annotated[float, StateSpec()] = 0.0,
    z: Annotated[float, StateSpec()] = 0.0,
    omega: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    a: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 30.0,
    b: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
    c: Annotated[float, ParameterSpec()] = 20.0,
    d: Annotated[float, ParameterSpec()] = 1.0,
    epsilon: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
) -> None:
    super().__init__(label, visual=visual)
    self.x = x
    self.y = y
    self.z = z
    self.omega = omega
    self.a = a
    self.b = b
    self.c = c
    self.d = d
    self.epsilon = epsilon

MemristiveCircuitElement(label, visual=None, *, x=0.0, y=0.0, z=0.0, w=0.0, epsilon=0.01, R=2.0, alpha=1.55, beta=0.15, e=-0.04, a=-0.34, b=0.05)

Bases: Element

Slow-fast circuit with a cubic flux-controlled memristor, whose oscillations mix small and large amplitudes.

The memristor has the charge q = a w + b w^3 of its flux w, so its memductance is a + 3 b w^2, and the circuit follows epsilon x' = -y - (a + 3 b w^2) x, y' = alpha (x - R y - z + e), z' = -β y, and w' = x.

Parameters:

Name Type Description Default
label str

The label of the circuit.

required
visual VisualSpec | None

How the circuit is drawn, or None for default_visual_.

None
x Annotated[float, StateSpec()]

The initial voltage of the memristor.

0.0
y Annotated[float, StateSpec()]

The initial current of the inductor.

0.0
z Annotated[float, StateSpec()]

The initial voltage of the second capacitor.

0.0
w Annotated[float, StateSpec()]

The initial flux of the memristor.

0.0
epsilon Annotated[float, ParameterSpec(domain=open(0, oo))]

The first capacitance, which sets the fast time scale.

0.01
R Annotated[float, ParameterSpec(domain=closed_open(0, oo))]

The resistance.

2.0
alpha Annotated[float, ParameterSpec(domain=open(0, oo))]

The inverse inductance.

1.55
beta Annotated[float, ParameterSpec(domain=open(0, oo))]

The gain of the controlled source over the second capacitance.

0.15
e Annotated[float, ParameterSpec()]

The voltage of the source.

-0.04
a Annotated[float, ParameterSpec()]

The linear coefficient of the memristor characteristic.

-0.34
b Annotated[float, ParameterSpec()]

The cubic coefficient of the memristor characteristic.

0.05

Examples:

>>> from skcomplex.systems.engineering.circuits import MemristiveCircuitElement
>>> MemristiveCircuitElement('circuit').a
-0.34
Source code in src/skcomplex/systems/engineering/circuits/_memristive_mixed_mode_oscillator.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def __init__(
    self,
    label: str,
    visual: VisualSpec | None = None,
    *,
    x: Annotated[float, StateSpec()] = 0.0,
    y: Annotated[float, StateSpec()] = 0.0,
    z: Annotated[float, StateSpec()] = 0.0,
    w: Annotated[float, StateSpec()] = 0.0,
    epsilon: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.01,
    R: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 2.0,
    alpha: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.55,
    beta: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 0.15,
    e: Annotated[float, ParameterSpec()] = -0.04,
    a: Annotated[float, ParameterSpec()] = -0.34,
    b: Annotated[float, ParameterSpec()] = 0.05,
) -> None:
    super().__init__(label, visual=visual)
    self.x = x
    self.y = y
    self.z = z
    self.w = w
    self.epsilon = epsilon
    self.R = R
    self.alpha = alpha
    self.beta = beta
    self.e = e
    self.a = a
    self.b = b

chua(x=None, y=None, z=0.0, *, nonlinearity='piecewise-linear', alpha=None, beta=None)

Return Chua's circuit, whose double-scroll or multiscroll attractor is chaotic.

The voltages of the two capacitors and the current of the inductor, in dimensionless form, follow x' = alpha (y - f(x)), y' = x - y + z, and z' = -β y. The piecewise-linear diode has f(x) = x + m1 x + (m0 - m1) (|x + 1| - |x - 1|) / 2, which gives the double scroll. The sine diode has f(x) = -b sin(π x / (2 a)), continued linearly beyond |x| = 2 a c, which gives several scrolls side by side.

Parameters:

Name Type Description Default
x float | None

The initial voltage of the first capacitor, or None for 0.1 with the piecewise-linear diode and 1 with the sine diode.

None
y float | None

The initial voltage of the second capacitor, or None for 0 with the piecewise-linear diode and 1 with the sine diode.

None
z float

The initial current of the inductor.

0.0
nonlinearity str

The characteristic of the diode, 'piecewise-linear' for the double scroll or 'sine' for multiple scrolls.

'piecewise-linear'
alpha float | None

The ratio of the capacitances, or None for 15.6 with the piecewise-linear diode and 10.82 with the sine diode.

None
beta float | None

The ratio of the inductance to the second capacitance, or None for 28 with the piecewise-linear diode and 14.286 with the sine diode.

None

Returns:

Name Type Description
system System

The system, with the states circuit.x, circuit.y, and circuit.z.

Raises:

Type Description
ParameterError

The nonlinearity is neither 'piecewise-linear' nor 'sine'.

Examples:

>>> from skcomplex.systems.engineering.circuits import chua
>>> chua(nonlinearity='sine').elements[0].alpha
10.82
Source code in src/skcomplex/systems/engineering/circuits/_circuits.py
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
@register_system(
    field='engineering/circuits',
    tags=('circuit', 'chaos', 'ode', 'attractor'),
    references=('Chua, Komuro, and Matsumoto 1986', 'Tang et al. 2001', 'Trzaska 2023'),
    defaults={'until': 100.0, 'dt': 0.05},
)
def chua(
    x: float | None = None,
    y: float | None = None,
    z: float = 0.0,
    *,
    nonlinearity: str = 'piecewise-linear',
    alpha: float | None = None,
    beta: float | None = None,
) -> System:
    """Return Chua's circuit, whose double-scroll or multiscroll attractor is chaotic.

    The voltages of the two capacitors and the current of the inductor, in dimensionless form, follow
    `x' = alpha (y - f(x))`, `y' = x - y + z`, and `z' = -β y`. The piecewise-linear diode has
    `f(x) = x + m1 x + (m0 - m1) (|x + 1| - |x - 1|) / 2`, which gives the double scroll. The sine diode has
    `f(x) = -b sin(π x / (2 a))`, continued linearly beyond `|x| = 2 a c`, which gives several scrolls side by side.

    Args:
        x:
            The initial voltage of the first capacitor, or `None` for 0.1 with the piecewise-linear diode and 1 with
            the sine diode.

        y:
            The initial voltage of the second capacitor, or `None` for 0 with the piecewise-linear diode and 1 with the
            sine diode.

        z:
            The initial current of the inductor.

        nonlinearity:
            The characteristic of the diode, `'piecewise-linear'` for the double scroll or `'sine'` for multiple
            scrolls.

        alpha:
            The ratio of the capacitances, or `None` for 15.6 with the piecewise-linear diode and 10.82 with the sine
            diode.

        beta:
            The ratio of the inductance to the second capacitance, or `None` for 28 with the piecewise-linear diode and
            14.286 with the sine diode.

    Returns:
        system:
            The system, with the states `circuit.x`, `circuit.y`, and `circuit.z`.

    Raises:
        ParameterError:
            The nonlinearity is neither `'piecewise-linear'` nor `'sine'`.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import chua
        >>> chua(nonlinearity='sine').elements[0].alpha
        10.82
    """
    if nonlinearity not in CHUA_NONLINEARITIES:
        error_msg = f"Unknown nonlinearity {nonlinearity!r}. Use 'piecewise-linear' or 'sine'."
        raise ParameterError(error_msg)
    element = ChuaCircuitElement if nonlinearity == 'piecewise-linear' else MultiscrollChuaCircuitElement
    initial = {'x': x, 'y': y, 'alpha': alpha, 'beta': beta}
    values: dict[str, float] = {name: value for name, value in initial.items() if value is not None}
    return System(elements=[element('circuit', z=z).set_params(**values)])

coupled_colpitts(k=0.4, *, omegas=(1.02, 0.98), a=30.0, b=1.0, c=20.0, d=1.0, epsilon=1.0)

Return two detuned chaotic Colpitts oscillators coupled through the difference of their second variables.

Oscillator i follows x' = omega_i y - a F(z), y' = c - omega_i x - z - b y + k (y_j - y_i), and z' = epsilon (y - d), with the transistor characteristic F(z) = max(0, -1 - z). The phase of an oscillator is the angle of its orbit in the plane of y and z, around the means of both. Without coupling the two oscillators run at different mean frequencies and their phases drift apart, while for k near 0.3 to 0.5 their phases lock although their amplitudes stay irregular.

Parameters:

Name Type Description Default
k float

The coupling strength.

0.4
omegas tuple[float, float]

The natural frequencies of the two oscillators.

(1.02, 0.98)
a float

The gain of the transistor.

30.0
b float

The damping of the second variable.

1.0
c float

The bias of the second variable.

20.0
d float

The offset of the third variable.

1.0
epsilon float

The time scale of the third variable.

1.0

Returns:

Name Type Description
system System

The system, with the states oscillator_0.x, oscillator_0.y, oscillator_0.z, and the same states of oscillator_1.

Examples:

>>> from skcomplex.systems.engineering.circuits import coupled_colpitts
>>> [element.omega for element in coupled_colpitts().elements]
[1.02, 0.98]
Source code in src/skcomplex/systems/engineering/circuits/_coupled_colpitts.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
@register_system(
    field='engineering/circuits',
    tags=('circuit', 'synchronization', 'chaos', 'ode'),
    references=('Kennedy 1994', 'Datseris and Parlitz 2022'),
    defaults={'until': 500.0, 'dt': 0.05},
)
def coupled_colpitts(
    k: float = 0.4,
    *,
    omegas: tuple[float, float] = (1.02, 0.98),
    a: float = 30.0,
    b: float = 1.0,
    c: float = 20.0,
    d: float = 1.0,
    epsilon: float = 1.0,
) -> System:
    """Return two detuned chaotic Colpitts oscillators coupled through the difference of their second variables.

    Oscillator `i` follows `x' = omega_i y - a F(z)`, `y' = c - omega_i x - z - b y + k (y_j - y_i)`, and
    `z' = epsilon (y - d)`, with the transistor characteristic `F(z) = max(0, -1 - z)`. The phase of an oscillator
    is the angle of its orbit in the plane of `y` and `z`, around the means of both. Without coupling the two
    oscillators run at different mean frequencies and their phases drift apart, while for `k` near 0.3 to 0.5 their
    phases lock although their amplitudes stay irregular.

    Args:
        k:
            The coupling strength.

        omegas:
            The natural frequencies of the two oscillators.

        a:
            The gain of the transistor.

        b:
            The damping of the second variable.

        c:
            The bias of the second variable.

        d:
            The offset of the third variable.

        epsilon:
            The time scale of the third variable.

    Returns:
        system:
            The system, with the states `oscillator_0.x`, `oscillator_0.y`, `oscillator_0.z`, and the same states of
            `oscillator_1`.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import coupled_colpitts
        >>> [element.omega for element in coupled_colpitts().elements]
        [1.02, 0.98]
    """
    starts = ((0.1, 0.0, 0.0), (0.0, 0.1, 0.2))
    oscillators = [
        ColpittsOscillatorElement(
            f'oscillator_{index}',
            x=start[0],
            y=start[1],
            z=start[2],
            omega=omega,
            a=a,
            b=b,
            c=c,
            d=d,
            epsilon=epsilon,
        )
        for index, (omega, start) in enumerate(zip(omegas, starts, strict=True))
    ]
    couplings = [
        ColpittsCouplingInteraction(f'coupling_{target}', source=source, target=target, k=k)
        for source, target in (('oscillator_1', 'oscillator_0'), ('oscillator_0', 'oscillator_1'))
    ]
    return System(elements=oscillators, interactions=couplings)

impulsive_circuits(x=0.0, current=0.0, *, L=1.0, R=0.1, C=1.0, A=1.0, T=2.0)

Return an RLC circuit driven by a periodic train of current impulses, L C x'' + R C x' + x = A Σ δ(t - k T).

Every impulse is an event that raises the rate of change x' by A / (L C), and between impulses the circuit rings down freely. The response settles on a periodic, non-sinusoidal waveform of period T.

Parameters:

Name Type Description Default
x float

The initial value of the response, the current of the coil.

0.0
current float

The initial rate of change of the response.

0.0
L float

The inductance.

1.0
R float

The resistance.

0.1
C float

The capacitance.

1.0
A float

The strength of every impulse.

1.0
T float

The period of the impulses.

2.0

Returns:

Name Type Description
system System

The system, with the response circuit.x and its rate circuit.x_d1, kicked by the impulses impulse.

Examples:

>>> from skcomplex.systems.engineering.circuits import impulsive_circuits
>>> impulsive_circuits(T=3.0).interactions[0].period
3.0
Source code in src/skcomplex/systems/engineering/circuits/_circuits.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
@register_system(
    field='engineering/circuits',
    tags=('circuit', 'impulses', 'events', 'hybrid'),
    references=('Trzaska 2023',),
    defaults={'until': 40.0, 'dt': 0.05},
)
def impulsive_circuits(
    x: float = 0.0,
    current: float = 0.0,
    *,
    L: float = 1.0,
    R: float = 0.1,
    C: float = 1.0,
    A: float = 1.0,
    T: float = 2.0,
) -> System:
    """Return an RLC circuit driven by a periodic train of current impulses, `L C x'' + R C x' + x = A Σ δ(t - k T)`.

    Every impulse is an event that raises the rate of change `x'` by `A / (L C)`, and between impulses the circuit
    rings down freely. The response settles on a periodic, non-sinusoidal waveform of period `T`.

    Args:
        x:
            The initial value of the response, the current of the coil.

        current:
            The initial rate of change of the response.

        L:
            The inductance.

        R:
            The resistance.

        C:
            The capacitance.

        A:
            The strength of every impulse.

        T:
            The period of the impulses.

    Returns:
        system:
            The system, with the response `circuit.x` and its rate `circuit.x_d1`, kicked by the impulses `impulse`.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import impulsive_circuits
        >>> impulsive_circuits(T=3.0).interactions[0].period
        3.0
    """
    circuit = KickedCircuitElement('circuit', x=x, x_d1=current, L=L, R=R, C=C, A=A)
    kick = {'x_d1': lambda s: s.target.x_d1 + s.target.A / (s.target.L * s.target.C)}
    impulse = ImpulseInteraction('impulse', target='circuit', reset=kick, period=T)
    return System(elements=[circuit], interactions=[impulse])

memristive_mixed_mode_oscillator(x=0.0, y=0.0, z=0.0, w=0.0, *, epsilon=0.01, R=2.0, alpha=1.55, beta=0.15, e=-0.04, a=-0.34, b=0.05)

Return a slow-fast circuit with a cubic flux-controlled memristor, whose oscillations mix two amplitudes.

The memristor has the charge q = a w + b w^3 of its flux w, and with the voltage x of the memristor, the current y of the inductor, and the voltage z of the second capacitor, the circuit follows epsilon x' = -y - (a + 3 b w^2) x, y' = alpha (x - R y - z + e), z' = -β y, and w' = x. Trzaska prints the memductance as a + 3 b w, but only the derivative a + 3 b w^2 of the characteristic gives the response he reports: for the defaults, from rest, the voltage settles on the mixed-mode oscillation 2^11, two large spikes and eleven small oscillations in a period of about 7.8.

Parameters:

Name Type Description Default
x float

The initial voltage of the memristor.

0.0
y float

The initial current of the inductor.

0.0
z float

The initial voltage of the second capacitor.

0.0
w float

The initial flux of the memristor.

0.0
epsilon float

The first capacitance, which sets the fast time scale.

0.01
R float

The resistance.

2.0
alpha float

The inverse inductance.

1.55
beta float

The gain of the controlled source over the second capacitance.

0.15
e float

The voltage of the source.

-0.04
a float

The linear coefficient of the memristor characteristic.

-0.34
b float

The cubic coefficient of the memristor characteristic.

0.05

Returns:

Name Type Description
system System

The system, with the states circuit.x, circuit.y, circuit.z, and circuit.w.

Examples:

>>> from skcomplex.systems.engineering.circuits import memristive_mixed_mode_oscillator
>>> memristive_mixed_mode_oscillator(a=-0.5).elements[0].a
-0.5
Source code in src/skcomplex/systems/engineering/circuits/_memristive_mixed_mode_oscillator.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
@register_system(
    field='engineering/circuits',
    tags=('circuit', 'memristor', 'mixed-mode oscillations', 'slow-fast', 'ode'),
    references=('Trzaska 2023',),
    defaults={'until': 150.0, 'dt': 0.02, 'solver': ScipyODESolver(method='LSODA')},
)
def memristive_mixed_mode_oscillator(
    x: float = 0.0,
    y: float = 0.0,
    z: float = 0.0,
    w: float = 0.0,
    *,
    epsilon: float = 0.01,
    R: float = 2.0,
    alpha: float = 1.55,
    beta: float = 0.15,
    e: float = -0.04,
    a: float = -0.34,
    b: float = 0.05,
) -> System:
    """Return a slow-fast circuit with a cubic flux-controlled memristor, whose oscillations mix two amplitudes.

    The memristor has the charge `q = a w + b w^3` of its flux `w`, and with the voltage `x` of the memristor, the
    current `y` of the inductor, and the voltage `z` of the second capacitor, the circuit follows
    `epsilon x' = -y - (a + 3 b w^2) x`, `y' = alpha (x - R y - z + e)`, `z' = -β y`, and `w' = x`. Trzaska prints the
    memductance as `a + 3 b w`, but only the derivative `a + 3 b w^2` of the characteristic gives the response he
    reports: for the defaults, from rest, the voltage settles on the mixed-mode oscillation `2^11`, two large spikes
    and eleven small oscillations in a period of about 7.8.

    Args:
        x:
            The initial voltage of the memristor.

        y:
            The initial current of the inductor.

        z:
            The initial voltage of the second capacitor.

        w:
            The initial flux of the memristor.

        epsilon:
            The first capacitance, which sets the fast time scale.

        R:
            The resistance.

        alpha:
            The inverse inductance.

        beta:
            The gain of the controlled source over the second capacitance.

        e:
            The voltage of the source.

        a:
            The linear coefficient of the memristor characteristic.

        b:
            The cubic coefficient of the memristor characteristic.

    Returns:
        system:
            The system, with the states `circuit.x`, `circuit.y`, `circuit.z`, and `circuit.w`.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import memristive_mixed_mode_oscillator
        >>> memristive_mixed_mode_oscillator(a=-0.5).elements[0].a
        -0.5
    """
    circuit = MemristiveCircuitElement(
        'circuit',
        x=x,
        y=y,
        z=z,
        w=w,
        epsilon=epsilon,
        R=R,
        alpha=alpha,
        beta=beta,
        e=e,
        a=a,
        b=b,
    )
    return System(elements=[circuit])

memristor_oscillator(v=0.1, i=0.0, w=0.1, *, C=1.0, L=3.0, alpha=0.6, beta=1.5)

Return the simplest memristor oscillator: a capacitor, an inductor, and a memristor in series.

The capacitor voltage, the inductor current, and the internal state of the memristor follow C v' = i, L i' = -v - β (w^2 - 1) i, and w' = -i - alpha w + w i. The memristor has the memristance β (w^2 - 1), and the circuit oscillates chaotically for the default values.

Parameters:

Name Type Description Default
v float

The initial voltage of the capacitor.

0.1
i float

The initial current.

0.0
w float

The initial internal state of the memristor.

0.1
C float

The capacitance.

1.0
L float

The inductance.

3.0
alpha float

The decay rate of the memristor state.

0.6
beta float

The scale of the memristance.

1.5

Returns:

Name Type Description
system System

The system, with the states circuit.v, circuit.i, and circuit.w.

Examples:

>>> from skcomplex.systems.engineering.circuits import memristor_oscillator
>>> memristor_oscillator(L=2.0).elements[0].L
2.0
Source code in src/skcomplex/systems/engineering/circuits/_circuits.py
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
@register_system(
    field='engineering/circuits',
    tags=('circuit', 'memristor', 'chaos', 'ode'),
    references=('Muthuswamy and Chua 2010', 'Trzaska 2023'),
    defaults={'until': 200.0, 'dt': 0.1},
)
def memristor_oscillator(
    v: float = 0.1,
    i: float = 0.0,
    w: float = 0.1,
    *,
    C: float = 1.0,
    L: float = 3.0,
    alpha: float = 0.6,
    beta: float = 1.5,
) -> System:
    """Return the simplest memristor oscillator: a capacitor, an inductor, and a memristor in series.

    The capacitor voltage, the inductor current, and the internal state of the memristor follow `C v' = i`,
    `L i' = -v - β (w^2 - 1) i`, and `w' = -i - alpha w + w i`. The memristor has the memristance `β (w^2 - 1)`, and the
    circuit oscillates chaotically for the default values.

    Args:
        v:
            The initial voltage of the capacitor.

        i:
            The initial current.

        w:
            The initial internal state of the memristor.

        C:
            The capacitance.

        L:
            The inductance.

        alpha:
            The decay rate of the memristor state.

        beta:
            The scale of the memristance.

    Returns:
        system:
            The system, with the states `circuit.v`, `circuit.i`, and `circuit.w`.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import memristor_oscillator
        >>> memristor_oscillator(L=2.0).elements[0].L
        2.0
    """
    return System(elements=[MemristorCircuitElement('circuit', v=v, i=i, w=w, C=C, L=L, alpha=alpha, beta=beta)])

mixed_mode_chua(x=0.01, y=0.0, z=0.0, *, epsilon=0.01, a=1.5, b=-1.0, alpha=1.0, R=0.0, beta=0.005, c=0.00053)

Return a modified Chua circuit on three time scales, whose oscillations mix small and large amplitudes.

The circuit follows ε x' = -y + (a + b x) x^2, y' = alpha (x - R y - z), and z' = c - β y. For ε = 0.01, a = 1.5, b = -1, and β = 0.005, the circuit has small oscillations only for c = 0, large ones only for c = 0.00105, and mixed-mode oscillations for c = 0.00053.

Parameters:

Name Type Description Default
x float

The initial voltage of the first capacitor.

0.01
y float

The initial current of the inductor.

0.0
z float

The initial voltage of the second capacitor.

0.0
epsilon float

The first capacitance, which sets the fast time scale.

0.01
a float

The quadratic coefficient of the diode.

1.5
b float

The cubic coefficient of the diode.

-1.0
alpha float

The inverse inductance.

1.0
R float

The resistance.

0.0
beta float

The gain of the controlled source over the second capacitance.

0.005
c float

The offset of the controlled source.

0.00053

Returns:

Name Type Description
system System

The system, with the states circuit.x, circuit.y, and circuit.z.

Examples:

>>> from skcomplex.systems.engineering.circuits import mixed_mode_chua
>>> mixed_mode_chua(c=0.0).elements[0].c
0.0
Source code in src/skcomplex/systems/engineering/circuits/_circuits.py
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
@register_system(
    field='engineering/circuits',
    tags=('circuit', 'mixed-mode oscillations', 'canards', 'slow-fast', 'ode'),
    references=('Krupa, Popović, and Kopell 2008', 'Trzaska 2023'),
    defaults={'until': 200.0, 'dt': 0.5, 'solver': ScipyODESolver(method='Radau')},
)
def mixed_mode_chua(
    x: float = 0.01,
    y: float = 0.0,
    z: float = 0.0,
    *,
    epsilon: float = 0.01,
    a: float = 1.5,
    b: float = -1.0,
    alpha: float = 1.0,
    R: float = 0.0,
    beta: float = 0.005,
    c: float = 0.00053,
) -> System:
    """Return a modified Chua circuit on three time scales, whose oscillations mix small and large amplitudes.

    The circuit follows `ε x' = -y + (a + b x) x^2`, `y' = alpha (x - R y - z)`, and `z' = c - β y`. For `ε = 0.01`,
    `a = 1.5`, `b = -1`, and `β = 0.005`, the circuit has small oscillations only for `c = 0`, large ones only for
    `c = 0.00105`, and mixed-mode oscillations for `c = 0.00053`.

    Args:
        x:
            The initial voltage of the first capacitor.

        y:
            The initial current of the inductor.

        z:
            The initial voltage of the second capacitor.

        epsilon:
            The first capacitance, which sets the fast time scale.

        a:
            The quadratic coefficient of the diode.

        b:
            The cubic coefficient of the diode.

        alpha:
            The inverse inductance.

        R:
            The resistance.

        beta:
            The gain of the controlled source over the second capacitance.

        c:
            The offset of the controlled source.

    Returns:
        system:
            The system, with the states `circuit.x`, `circuit.y`, and `circuit.z`.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import mixed_mode_chua
        >>> mixed_mode_chua(c=0.0).elements[0].c
        0.0
    """
    circuit = MixedModeChuaCircuitElement(
        'circuit',
        x=x,
        y=y,
        z=z,
        epsilon=epsilon,
        a=a,
        b=b,
        alpha=alpha,
        R=R,
        beta=beta,
        c=c,
    )
    return System(elements=[circuit])

rlc_circuit(q=1.0, current=0.0, *, L=1.0, R=0.1, C=1.0, V=0.0, omega=1.0)

Return a series RLC circuit written as Kirchhoff's voltage law, L q'' + R q' + q / C = V cos(ω t).

Parameters:

Name Type Description Default
q float

The initial charge of the capacitor.

1.0
current float

The initial current.

0.0
L float

The inductance.

1.0
R float

The resistance.

0.1
C float

The capacitance.

1.0
V float

The amplitude of the source voltage.

0.0
omega float

The angular frequency of the source.

1.0

Returns:

Name Type Description
system System

The system, with the circuit circuit, whose charge has order 2 and current circuit.q_d1.

Examples:

>>> from skcomplex.systems.engineering.circuits import rlc_circuit
>>> rlc_circuit(R=0.5).elements[0].R
0.5
Source code in src/skcomplex/systems/engineering/circuits/_circuits.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
@register_system(
    field='engineering/circuits',
    tags=('electromagnetism', 'circuit', 'ode', 'second order'),
    references=('Kirchhoff 1845',),
    defaults={'until': 50.0, 'dt': 0.05},
)
def rlc_circuit(
    q: float = 1.0,
    current: float = 0.0,
    *,
    L: float = 1.0,
    R: float = 0.1,
    C: float = 1.0,
    V: float = 0.0,
    omega: float = 1.0,
) -> System:
    """Return a series RLC circuit written as Kirchhoff's voltage law, `L q'' + R q' + q / C = V cos(ω t)`.

    Args:
        q:
            The initial charge of the capacitor.

        current:
            The initial current.

        L:
            The inductance.

        R:
            The resistance.

        C:
            The capacitance.

        V:
            The amplitude of the source voltage.

        omega:
            The angular frequency of the source.

    Returns:
        system:
            The system, with the circuit `circuit`, whose charge has order 2 and current `circuit.q_d1`.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import rlc_circuit
        >>> rlc_circuit(R=0.5).elements[0].R
        0.5
    """
    return System(elements=[CircuitElement('circuit', q=q, q_d1=current, L=L, R=R, C=C, V=V, omega=omega)])

tunnel_diode_oscillator(V=0.1, i=0.0, *, L=1.0, C=1.0, alpha=1.0, gamma=1.0)

Return a tunnel-diode oscillator: an inductor and a capacitor in parallel with a negative-resistance diode.

The diode current is h(V) = gamma V^3 - alpha V, so Kirchhoff's laws give C V' = -h(V) - i and L i' = V, which is the van der Pol oscillator with ε = alpha sqrt(L / C). The voltage settles on a limit cycle of amplitude near 2 sqrt(alpha / (3 gamma)).

Parameters:

Name Type Description Default
V float

The initial voltage.

0.1
i float

The initial current of the inductor.

0.0
L float

The inductance.

1.0
C float

The capacitance.

1.0
alpha float

The negative conductance of the diode at zero voltage.

1.0
gamma float

The cubic coefficient of the diode.

1.0

Returns:

Name Type Description
system System

The system, with the voltage circuit.V and the inductor current circuit.i.

Examples:

>>> from skcomplex.systems.engineering.circuits import tunnel_diode_oscillator
>>> tunnel_diode_oscillator(alpha=2.0).elements[0].alpha
2.0
Source code in src/skcomplex/systems/engineering/circuits/_circuits.py
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
@register_system(
    field='engineering/circuits',
    tags=('circuit', 'oscillation', 'limit cycle', 'ode'),
    references=('van der Pol 1926', 'Trzaska 2023'),
    defaults={'until': 50.0, 'dt': 0.05},
)
def tunnel_diode_oscillator(
    V: float = 0.1,
    i: float = 0.0,
    *,
    L: float = 1.0,
    C: float = 1.0,
    alpha: float = 1.0,
    gamma: float = 1.0,
) -> System:
    """Return a tunnel-diode oscillator: an inductor and a capacitor in parallel with a negative-resistance diode.

    The diode current is `h(V) = gamma V^3 - alpha V`, so Kirchhoff's laws give `C V' = -h(V) - i` and `L i' = V`,
    which is the van der Pol oscillator with `ε = alpha sqrt(L / C)`. The voltage settles on a limit cycle of
    amplitude near `2 sqrt(alpha / (3 gamma))`.

    Args:
        V:
            The initial voltage.

        i:
            The initial current of the inductor.

        L:
            The inductance.

        C:
            The capacitance.

        alpha:
            The negative conductance of the diode at zero voltage.

        gamma:
            The cubic coefficient of the diode.

    Returns:
        system:
            The system, with the voltage `circuit.V` and the inductor current `circuit.i`.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import tunnel_diode_oscillator
        >>> tunnel_diode_oscillator(alpha=2.0).elements[0].alpha
        2.0
    """
    return System(elements=[TunnelDiodeCircuitElement('circuit', V=V, i=i, L=L, C=C, alpha=alpha, gamma=gamma)])

vibration_energy_harvester(*, m1=40.0, m2=400.0, k1=200000.0, k2=20000.0, b=1500.0, A=0.01, omega=9.42, n=10.0, d=4e-10, C=1e-08)

Return a dual-mass piezoelectric harvester in a car suspension, driven by the roughness of the road.

The unsprung and sprung masses follow m1 y1'' + b (y1' - y2') + k2 (y1 - y2) + k1 (y1 - z(t)) = 0 and m2 y2'' + b (y2' - y1') + k2 (y2 - y1) = 0, with the road profile z(t) = A sin(ω t). A lever with arm ratio n presses a piezoelectric bar of constant d and capacitance C, whose charge is n d k2 (y2 - y1) and whose voltage is the observable voltage. The damper b stands for the energy the bar absorbs.

Parameters:

Name Type Description Default
m1 float

The unsprung mass.

40.0
m2 float

The sprung mass.

400.0
k1 float

The stiffness of the tyre.

200000.0
k2 float

The stiffness of the suspension spring.

20000.0
b float

The damping coefficient of the harvester.

1500.0
A float

The amplitude of the road profile.

0.01
omega float

The angular frequency of the road profile.

9.42
n float

The ratio of the arms of the lever.

10.0
d float

The piezoelectric constant of the bar.

4e-10
C float

The capacitance of the bar.

1e-08

Returns:

Name Type Description
system System

The system, with the displacements harvester.y1 and harvester.y2, and the observable voltage.

Examples:

>>> from skcomplex.systems.engineering.circuits import vibration_energy_harvester
>>> vibration_energy_harvester(b=1000.0).elements[0].b
1000.0
Source code in src/skcomplex/systems/engineering/circuits/_harvesters.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
@register_system(
    field='engineering/circuits',
    tags=('energy harvesting', 'piezoelectric', 'vibration', 'vehicles', 'ode'),
    references=('Trzaska 2023',),
    defaults={'until': 10.0, 'dt': 0.01},
    observables=lambda system: [ObservableSpec('voltage', _piezo_voltage)],
)
def vibration_energy_harvester(
    *,
    m1: float = 40.0,
    m2: float = 400.0,
    k1: float = 2.0e5,
    k2: float = 2.0e4,
    b: float = 1500.0,
    A: float = 0.01,
    omega: float = 9.42,
    n: float = 10.0,
    d: float = 4.0e-10,
    C: float = 1.0e-8,
) -> System:
    """Return a dual-mass piezoelectric harvester in a car suspension, driven by the roughness of the road.

    The unsprung and sprung masses follow `m1 y1'' + b (y1' - y2') + k2 (y1 - y2) + k1 (y1 - z(t)) = 0` and
    `m2 y2'' + b (y2' - y1') + k2 (y2 - y1) = 0`, with the road profile `z(t) = A sin(ω t)`. A lever with arm ratio
    `n` presses a piezoelectric bar of constant `d` and capacitance `C`, whose charge is `n d k2 (y2 - y1)` and whose
    voltage is the observable `voltage`. The damper `b` stands for the energy the bar absorbs.

    Args:
        m1:
            The unsprung mass.

        m2:
            The sprung mass.

        k1:
            The stiffness of the tyre.

        k2:
            The stiffness of the suspension spring.

        b:
            The damping coefficient of the harvester.

        A:
            The amplitude of the road profile.

        omega:
            The angular frequency of the road profile.

        n:
            The ratio of the arms of the lever.

        d:
            The piezoelectric constant of the bar.

        C:
            The capacitance of the bar.

    Returns:
        system:
            The system, with the displacements `harvester.y1` and `harvester.y2`, and the observable `voltage`.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import vibration_energy_harvester
        >>> vibration_energy_harvester(b=1000.0).elements[0].b
        1000.0
    """
    harvester = PiezoHarvesterElement('harvester', m1=m1, m2=m2, k1=k1, k2=k2, b=b, A=A, omega=omega, n=n, d=d, C=C)
    suspension = SpringDamperInteraction(
        'suspension',
        target='harvester',
        states=('y1', 'y2'),
        stiffness='k2',
        damping='b',
        divisor={'y1': 'm1', 'y2': 'm2'},
    )
    return System(elements=[harvester], interactions=[suspension])