Skip to content

Coupled Colpitts oscillators

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

  • Name: coupled_colpitts
  • Field: engineering/circuits
  • Formalism: ordinary differential equations
  • References: Kennedy 1994; Datseris and Parlitz 2022

Select this system

from skcomplex.systems import create_simulation, create_system, describe

system = create_system('coupled_colpitts')
entry = describe('coupled_colpitts')
trajectory = create_simulation('coupled_colpitts').prepare().run().trajectory_

Factory

@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)

Components

Elements:

  • ColpittsOscillatorElement, from Element

Interactions:

  • ColpittsCouplingInteraction, from RateInteraction
class ColpittsOscillatorElement(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`.

    Args:
        label:
            The label of the oscillator.

        visual:
            How the oscillator is drawn, or `None` for `default_visual_`.

        x:
            The initial first variable.

        y:
            The initial second variable.

        z:
            The initial third variable.

        omega:
            The natural frequency.

        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.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import ColpittsOscillatorElement
        >>> ColpittsOscillatorElement('oscillator').a
        30.0
    """

    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

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the rates of the three variables of the oscillator.

        Args:
            s:
                The namespace of the symbols the laws can use.

        Returns:
            laws:
                The laws of the component.
        """
        conduction = where(s.z < -1, -1 - s.z, 0)
        return [
            RateLaw(s.x, s.omega * s.y - s.a * conduction),
            RateLaw(s.y, s.c - s.omega * s.x - s.z - s.b * s.y),
            RateLaw(s.z, s.epsilon * (s.y - s.d)),
        ]

class ColpittsCouplingInteraction(RateInteraction):
    """Coupling of a Colpitts oscillator to another through their second variables, `y_t' += k (y_s - y_t)`.

    Args:
        label:
            The label of the interaction.

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

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

        k:
            The coupling strength.

    Examples:
        >>> from skcomplex.systems.engineering.circuits import ColpittsCouplingInteraction
        >>> ColpittsCouplingInteraction('coupling', source='a', target='b', k=0.4).k
        0.4
    """

    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

    def _get_rate(self, namespace: SimpleNamespace) -> Expression:
        """Return `k (y_s - y_t)`.

        Args:
            namespace:
                The namespace of the symbols the rate can use.

        Returns:
            rate:
                The rate of the second variable of the target.
        """
        rate: Expression = namespace.k * (namespace.source.y - namespace.target.y)
        return rate

Equations

oscillator_0.x: diff(x_oscillator_0(t), t) == -a_oscillator_0*where(z_oscillator_0(t) < -1, -z_oscillator_0(t) - 1, 0) + omega_oscillator_0*y_oscillator_0(t)
oscillator_0.y: diff(y_oscillator_0(t), t) == -k_coupling_oscillator_0*(y_oscillator_0(t) - y_oscillator_1(t)) - omega_oscillator_0*x_oscillator_0(t) - b_oscillator_0*y_oscillator_0(t) + c_oscillator_0 - z_oscillator_0(t)
oscillator_0.z: diff(z_oscillator_0(t), t) == -(d_oscillator_0 - y_oscillator_0(t))*epsilon_oscillator_0
oscillator_1.x: diff(x_oscillator_1(t), t) == -a_oscillator_1*where(z_oscillator_1(t) < -1, -z_oscillator_1(t) - 1, 0) + omega_oscillator_1*y_oscillator_1(t)
oscillator_1.y: diff(y_oscillator_1(t), t) == k_coupling_oscillator_1*(y_oscillator_0(t) - y_oscillator_1(t)) - omega_oscillator_1*x_oscillator_1(t) - b_oscillator_1*y_oscillator_1(t) + c_oscillator_1 - z_oscillator_1(t)
oscillator_1.z: diff(z_oscillator_1(t), t) == -(d_oscillator_1 - y_oscillator_1(t))*epsilon_oscillator_1