amplifier sensitivity julia script

Below shows the contents of: amplifier_sensitivity.jl

using CedarEDA
using GF180MCUPDK

sm = SimManager(joinpath(@__DIR__, "amplifier.spice"))

# Define our simulation parameters
sp = SimParameterization(sm;
    # We're not really sweeping parameters here, just giving a default value.
    params = ProductSweep(; bias_current = [400e-6],),
    # Solve to these tolerances for DC and Transient values
    abstol_dc = 1e-14, abstol_tran = 1e-6,
    # Solve for this timescale
    tspan = (0.0, 2e-5),
    # Rosenbrock23 is the best solver for this problem, save time by setting it as highest preference (optional)
    preferred_solver = :Rosenbrock23,

set_saved_signals!(sp, [
    sp.probes.m2.var"I(di, si)",

# First, show the voltage of the `vout` net starting value
vout = tran!(sp).tran.node_vout[1]
vout_figure = inspect(vout, title="Starting Vout from netlist",
                            xlabel="Time (s)",
                            ylabel="Voltage (V)")
# Force plot in batch mode:

# Solve the sensitivities, `ssols` has the same shape as our parameter sweep
# (in this case, only a single element), and each element of contains both
# the primal solution and the sensitivities with respect to each parameter,
# for each saved signal.
ssols = sensitivities!(sp)

# This displays the sensitivity of `node_vout` with respect to `bias_current` at the first parameterization point.
sens_vout_vs_ibias = ssols.sensitivities.bias_current.node_vout[1]
sens_vout_vs_ibias_fig = inspect(sens_vout_vs_ibias, title="Sensitivity of Vout to Ibias",
                                                     xlabel="Time (s)",
                                                     ylabel="δVout/δIbias (V/A)")
display(sens_vout_vs_ibias_fig) # force display in batch mode

# This displays the sensitivity of the current out of `m2` with respect to `bias_current` at the first parameterization point.
sens_m2cur_vs_ibias = ssols.sensitivities.bias_current.m2.var"I(di, si)"[1]
sens_m2cur_vs_ibias_fig = inspect(sens_m2cur_vs_ibias, title="Sensitivity of M2 current to Ibias",
                                                           xlabel="Time (s)",
                                                           ylabel="δI(M2)/δIbias (A/A)")
display(sens_m2cur_vs_ibias_fig) # force display in batch mode

# We can check the derivative by running another simulation at a different (nearby) Ibias
# and checking that the derivative predicts the output accurately.
    using WGLMakie
    ΔIbias = 10e-6
    perturbed_sp = SimParameterization(sm;
        # add a little shift in bias_current
        params = ProductSweep(; bias_current = [400e-6 + ΔIbias],),
        abstol_dc = 1e-14, abstol_tran = 1e-6,
        tspan = (0.0, 2e-5),
    set_saved_signals!(perturbed_sp, [
    perturbed_sols = tran!(perturbed_sp)
    perturbed_signal = perturbed_sols.tran.node_vout[1]

    orig_sols = tran!(sp)
    orig_signal = orig_sols.tran.node_vout[1]
    orig_derivative = ssols.sensitivities.bias_current.node_vout[1]

    # Subtract the two signals, sampling the shifted signal at the timepoints
    # that `orig_signal` is defined on:
    predicted_signal = orig_signal + ΔIbias * sample(orig_derivative, xvals(orig_signal))

    # Display the error signal; note the relatively small error bounds
    error_signal = predicted_signal - sample(perturbed_signal, xvals(predicted_signal))

    fig = lines(xvals(orig_signal), yvals(orig_signal),
        label = "Original",
        axis = (
            title="Predicted output for Ibias=410uA vs Actual",
            xlabel = "Time (s)",
            ylabel = "Voltage (V)",
            xtickformat = CedarWaves.default_xtickformat(),
            ytickformat = CedarWaves.default_ytickformat(),
    lines!(xvals(predicted_signal), yvals(predicted_signal),
        label = "Predicted",

    lines!(xvals(perturbed_signal), yvals(perturbed_signal),
        label = "Actual"