.. include:: /defs.txt

.. currentmodule:: zero.components

Noise
=====

Some components in |Zero| produce noise, such as resistors (:ref:`components/noise:Johnson noise`)
and op-amps (:ref:`voltage <components/noise:Op-amp voltage noise>` and :ref:`current
<components/noise:Op-amp current noise>` noise). Other components such as :ref:`capacitors
<components/passive-components:Capacitors>` and :ref:`inductors
<components/passive-components:Inductors>` do not produce noise by default, although noise can be
:ref:`added by the user <components/noise:Defining new noise sources>`.

Johnson noise
-------------

`Johnson noise <https://en.wikipedia.org/wiki/Johnson%E2%80%93Nyquist_noise>`__ is a type of
voltage noise in resistors that arises from thermal agitation of charge carriers. This is a function
of temperature but has no dependence on applied voltage or current.

The default temperature assumed in |Zero| calculations is set in the :ref:`configuration
<configuration/index:Configuration>`.

Op-amp noise
------------

Op-amps produce voltage noise across their input and output nodes, and current noise is present at
their input nodes.

Op-amp voltage noise
~~~~~~~~~~~~~~~~~~~~

Op-amps produce voltage noise across their input and output nodes. The noise is a function of
frequency, usually with a flat component at all frequencies and a component rising towards low
frequencies. The cross-over between these two noise components is typically around 1 to 100 Hz,
though this varies depending on the type of op-amp. BJT-based op-amps typically have the lowest
voltage noise.

Op-amp current noise
~~~~~~~~~~~~~~~~~~~~

Current noise is present at op-amps' inputs. The noise is a function of frequency, usually with a
flat component at all frequencies and a component rising towards low frequencies. The cross-over
between these two noise components is typically around 100 Hz to 1 kHz, though this varies depending
on the type of op-amp. FET-based op-amps typically have the lowest current noise.

Current noise is converted to voltage noise by resistors connected to the op-amp inputs. That means
that in a standard op-amp circuit with a feedback resistor, the current noise scales with the
feedback resistance.

In |Zero|, current noise is considered identical for both input nodes. This is usually a valid
assumption for voltage-feedback op-amps, which are the type that |Zero| models.

Defining new noise sources
--------------------------

New noise sources can be defined in |Zero| and added to components. The noise will then appear in
:ref:`noise analyses <analyses/ac/noise:Small AC noise analysis>`.

Noise sources can be created by subclassing one of the available noise types: :class:`.VoltageNoise`
or :class:`.CurrentNoise`. The implementation must define a ``label`` property and set a method to
call when computing the noise. This method will receive the current frequency vector and it must
return the corresponding noise.

Here is an example of defining a resistor current noise source and using it in a circuit:

.. plot::
    :include-source:

    import numpy as np
    from zero import Circuit
    from zero.analysis import AcNoiseAnalysis
    from zero.noise import VoltageNoise

    # Create a new noise type.
    class ResistorCurrentNoise(VoltageNoise):
        """Resistor current noise source.

        This models resistor current noise. See e.g. https://dcc.ligo.org/LIGO-T0900200/public
        for more details. This noise depends on resistor composition and on its current. Be
        careful when using this noise - it generally does not transfer to different circuits
        with identical resistors as it depends on the voltage drop across the resistor.

        Parameters
        ----------
        vnoise : :class:`float`
            The voltage noise at the specified frequency (V/sqrt(Hz)).
        frequency : :class:`float`
            The frequency at which the specified voltage noise is defined (Hz).
        exponent : :class:`float`
            The frequency exponent to use for calculating the frequency response.
        """
        def __init__(self, vnoise, frequency=1.0, exponent=0.5, **kwargs):
            super().__init__(**kwargs)
            self.vnoise = vnoise
            self.frequency = frequency
            self.exponent = exponent

        def noise_voltage(self, frequencies, **kwargs):
            return self.vnoise * self.frequency / frequencies ** self.exponent

        @property
        def label(self):
            return f"RE({self.component.name})"


    # 1000 frequencies between 0.1 Hz to 10 kHz
    frequencies = np.logspace(-1, 4, 1000)

    # Create circuit object.
    circuit = Circuit()

    # Add components.
    circuit.add_capacitor(value="10u", node1="gnd", node2="n1")
    circuit.add_resistor(value="430", node1="n1", node2="nm", name="r1")
    circuit.add_resistor(value="43k", node1="nm", node2="nout")
    circuit.add_capacitor(value="47p", node1="nm", node2="nout")
    circuit.add_library_opamp(model="LT1124", node1="gnd", node2="nm", node3="nout")

    # Add resistor current noise to r1 with 10 nV/sqrt(Hz) at 1 Hz, with 1/f^2 drop-off.
    r1 = circuit["r1"]
    r1.add_noise(ResistorCurrentNoise(vnoise=1e-8, frequency=1.0, exponent=0.5))

    # Solve circuit.
    analysis = AcNoiseAnalysis(circuit=circuit)
    solution = analysis.calculate(frequencies=frequencies, input_type="voltage", node="n1",
                                  sink="nout", incoherent_sum=True)

    # Plot.
    solution.plot_noise(sink="nout")
    solution.show()