Noisy Amplifier

Simulation of a nonlinear, noisy, and band-limited amplifier model.

The most simplistic model for an amplifier is just the product of a signal with some factor (gain). But we all know, real amplifiers are noisy and nonlinear. Internal physical processes or fluctiations in the power supply produce noise and if the amplitudes get too big, we run into saturations.

PathSim implements a number (WhiteNoiseSource and PinkNoiseSource) of broadband noise generators that produce accurate frequency domain noise spectra but for transient analysis.

In this example we are bringing that together to implement a simple model of a nonlinear, noisy and band limited amplifier shown in the figure below:

block diagram of nonlinear noisy amplifier
[1]:
import numpy as np
import matplotlib.pyplot as plt

# Apply PathSim docs matplotlib style
plt.style.use('../pathsim_docs.mplstyle')

from pathsim import Simulation, Connection, Subsystem, Interface

from pathsim.blocks import (
    Scope, Spectrum, Amplifier, Function, Adder, WhiteNoise,
    PinkNoise, SinusoidalSource, ButterworthLowpassFilter
    )

Noisy Amplifier Model as Subsystem

The nonlinear amplifier model is defined as a Subsystem, implementing the gain as an elementary Amplifier block, the saturation (nonlinearity) as a Function block, wrapping a hyperbolic tangent and a ButterworthLowpassFilter for the frequency dependency. On top of that, two noise sources (WhiteNoiseSource and PinkNoiseSource) are added to the input of the model.

[2]:
# System parameters

a  = 10   # gain
fc = 1e6  # bandwidth
n  = 2    # filter order

psd_w, psd_p = 1e-9, 1e-10
[3]:
# Internal subsystem blocks for the noisy amplifier

amp_int = Interface()
amp_wns = WhiteNoise(spectral_density=psd_w)
amp_pns = PinkNoise(spectral_density=psd_p)
amp_add = Adder()
amp_sat = Function(np.tanh)
amp_amp = Amplifier(a)
amp_flt = ButterworthLowpassFilter(fc, n)

amp_blocks = [amp_int, amp_wns, amp_pns, amp_add, amp_flt, amp_amp, amp_sat]

amp_connections = [
    Connection(amp_int, amp_add[0]),
    Connection(amp_wns, amp_add[1]),
    Connection(amp_pns, amp_add[2]),
    Connection(amp_add, amp_sat),
    Connection(amp_sat, amp_amp),
    Connection(amp_amp, amp_flt),
    Connection(amp_flt, amp_int)
    ]

AMP = Subsystem(amp_blocks, amp_connections)

Main System (Top Level)

The amplifier model is ready, we connect an sinusoidal source to its input and a spectrum analyzer (Spectrum) and Scope to its output.

[4]:
Src = SinusoidalSource(frequency=2e5, amplitude=1)
Sco = Scope(labels=["Src", "AMP"])
Spc = Spectrum(freq=np.logspace(4.1, 7, 250), labels=["Src", "AMP"])

blocks = [AMP, Src, Sco, Spc]

connections = [
    Connection(Src, AMP, Sco[0], Spc[0]),
    Connection(AMP, Sco[1], Spc[1])
    ]
[5]:
Sim = Simulation(
    blocks,
    connections,
    dt=3e-8,
    log=True
    )
11:40:45 - INFO - LOGGING (log: True)
11:40:45 - INFO - BLOCKS (total: 4, dynamic: 2, static: 2, eventful: 0)
11:40:45 - INFO - GRAPH (nodes: 4, edges: 5, alg. depth: 1, loop depth: 0, runtime: 0.023ms)

Simulation

Lets run the simulation for some duration. Here 100us, 20 periods of the sinusoidal source.

[6]:
Sim.run(1e-4);
11:40:45 - INFO - STARTING -> TRANSIENT (Duration: 0.00s)
11:40:45 - INFO - --------------------   1% | 0.0s<0.6s | 5413.2 it/s
11:40:45 - INFO - ####----------------  20% | 0.1s<0.5s | 5490.3 it/s
11:40:45 - INFO - ########------------  40% | 0.3s<0.4s | 5509.0 it/s
11:40:45 - INFO - ############--------  60% | 0.4s<0.2s | 5433.6 it/s
11:40:45 - INFO - ################----  80% | 0.5s<0.1s | 5255.3 it/s
11:40:46 - INFO - #################### 100% | 0.6s<--:-- | 5471.1 it/s
11:40:46 - INFO - FINISHED -> TRANSIENT (total steps: 3334, successful: 3334, runtime: 624.08 ms)

Results

We can read the time series results and see that we get the expected amplified sinusoid. The noise parameters we chose are pretty big, so we can see the fluctuations.

[7]:
time, [res_src, res_amp] = Sco.read()

fig, ax = plt.subplots(nrows=1, figsize=(8, 4), tight_layout=True, dpi=200)

ax.plot(time, res_src, label=Sco.labels[0])
ax.plot(time, res_amp, label=Sco.labels[1])
ax.set_xlabel("time [s]")
ax.legend();
../_images/examples_noisy_amplifier_15_0.svg

In the frequency domain we can see the second peak in te spectrum of the output signal. This indicates the nonlinearity.

[8]:
freq, [res_src, res_amp] = Spc.read()

fig, ax = plt.subplots(figsize=(8, 4), tight_layout=True, dpi=200)

ax.loglog(freq, abs(res_src), label=Spc.labels[0])
ax.loglog(freq, abs(res_amp), label=Spc.labels[1])

ax.set_xlabel("Freq [Hz]")
ax.set_ylabel("Magnitude")
ax.set_ylim(1e-6, None)
ax.legend();
../_images/examples_noisy_amplifier_17_0.svg