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/models/engineering/circuits/_circuit.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

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 model, 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.models.engineering.circuits import chua
>>> chua(nonlinearity='sine').elements[0].alpha
10.82
Source code in src/skcomplex/models/engineering/circuits/_circuits.py
 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
@register_model(
    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 model, with the states `circuit.x`, `circuit.y`, and `circuit.z`.

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

    Examples:
        >>> from skcomplex.models.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)])

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 model, with the response circuit.x and its rate circuit.x_d1, kicked by the impulses impulse.

Examples:

>>> from skcomplex.models.engineering.circuits import impulsive_circuits
>>> impulsive_circuits(T=3.0).interactions[0].period
3.0
Source code in src/skcomplex/models/engineering/circuits/_circuits.py
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
386
387
@register_model(
    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 model, with the response `circuit.x` and its rate `circuit.x_d1`, kicked by the impulses `impulse`.

    Examples:
        >>> from skcomplex.models.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])

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 model, with the states circuit.v, circuit.i, and circuit.w.

Examples:

>>> from skcomplex.models.engineering.circuits import memristor_oscillator
>>> memristor_oscillator(L=2.0).elements[0].L
2.0
Source code in src/skcomplex/models/engineering/circuits/_circuits.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
@register_model(
    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 model, with the states `circuit.v`, `circuit.i`, and `circuit.w`.

    Examples:
        >>> from skcomplex.models.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 model, with the states circuit.x, circuit.y, and circuit.z.

Examples:

>>> from skcomplex.models.engineering.circuits import mixed_mode_chua
>>> mixed_mode_chua(c=0.0).elements[0].c
0.0
Source code in src/skcomplex/models/engineering/circuits/_circuits.py
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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
@register_model(
    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 model, with the states `circuit.x`, `circuit.y`, and `circuit.z`.

    Examples:
        >>> from skcomplex.models.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 model, with the circuit circuit, whose charge has order 2 and current circuit.q_d1.

Examples:

>>> from skcomplex.models.engineering.circuits import rlc_circuit
>>> rlc_circuit(R=0.5).elements[0].R
0.5
Source code in src/skcomplex/models/engineering/circuits/_circuits.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@register_model(
    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 model, with the circuit `circuit`, whose charge has order 2 and current `circuit.q_d1`.

    Examples:
        >>> from skcomplex.models.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 model, with the voltage circuit.V and the inductor current circuit.i.

Examples:

>>> from skcomplex.models.engineering.circuits import tunnel_diode_oscillator
>>> tunnel_diode_oscillator(alpha=2.0).elements[0].alpha
2.0
Source code in src/skcomplex/models/engineering/circuits/_circuits.py
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
318
319
320
321
322
323
324
325
326
327
328
329
@register_model(
    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 model, with the voltage `circuit.V` and the inductor current `circuit.i`.

    Examples:
        >>> from skcomplex.models.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 model, with the displacements harvester.y1 and harvester.y2, and the observable voltage.

Examples:

>>> from skcomplex.models.engineering.circuits import vibration_energy_harvester
>>> vibration_energy_harvester(b=1000.0).elements[0].b
1000.0
Source code in src/skcomplex/models/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_model(
    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 model, with the displacements `harvester.y1` and `harvester.y2`, and the observable `voltage`.

    Examples:
        >>> from skcomplex.models.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])