Skip to content

Stepping, temperatures, Monte Carlo, optimizer

One analysis can run many times. Every run keeps what it used (run.params), and every plot is still MC9's own.

Stepping

c = mc9.library.rc_lowpass().build("rc")
c.step("R1", "1k", "3k", "1k")                 # R1 = 1k, 2k, 3k          (linear)
c.step("C1", "100n,159n")                      # a list
c.step("R1", 100, 1e5, 10, method="log")       # multiply by 10 each run
c.step("BF420", 100, 400, 100, param="BF", kind="model")   # a model parameter
r = c.ac(10, 1e5, 401, ["db(v(out))"])

for run in r.runs:
    print(run.params, run.cross("db(v(out))", -3.0103))      # {'Temperature': 25.0, 'R1': 1000.0} [1000.2]
r.select(R1=2000)                              # the runs where R1 was 2k
c.stepping_mode(nested=False)                  # all variables change together instead of every combination
c.clear_steps()                                # stepping off

analysis="ac" (or "transient", "dc", "distortion") limits a step to one analysis; the default is all four. Up to 20 variables. Stepping is written to the circuit as MC9's own [Stepping] sections, so it works with both engines.

Temperatures

c.dc("V2", 0, 20, 2, curves=["v(col)"], temp=[0, 25, 75])    # one run per temperature
c.transient(5e-3, temp=[0, 85])

A temperature list stays in the analysis settings until you give a single value again (temp=25). With stepping or Monte Carlo the runs are the product: 3 temperatures x 4 Monte Carlo cases = 12 runs.

Monte Carlo

c.tolerance("R1", lot="5%")                    # a resistor, capacitor or inductor (or dev="1%" per device)
c.monte_carlo(runs=50, distribution="normal", seed=1, analysis="transient")
r = c.transient(5e-3, curves=["v(out)"])       # 50 runs, 'Case=1' is the nominal circuit
spread = [run["v(out)"][-1] for run in r.runs]

distribution: "normal" or "worst case". c.model(".MODEL ...") adds any netlist text (models, .DEFINE, .PARAMETERS) to the circuit.

Optimizer (native engine)

res = c.optimize({"R1": (100, 5000)},                               # up to 4 parts and their limits
                 [("equals", "Y_Level(db(v(out)),1,1,2000)", -3.0103)],   # up to 4 goals
                 analysis="ac")
res["params"]["R1"]["value"]                                        # '500'  - also applied to the circuit

Goal kinds: "equals" (needs a target), "minimize", "maximize". The optimizer is MC9's own (Powell); it is driven through its dialog by the DLL, so it needs the native engine.

Curves and FFT

c.curves("ac", dict(y="db(v(out))", xrange=(10, 1e5), yrange=(-60, 5), color="#FF0000"))
c.transient(10e-3, curves=["v(out)", ("F", "db(mag(fft(v(out))))")])   # a second plot: MC9's FFT

Monte Carlo histogram, statistics and plot commands (native engine)

c.tolerance("R1", lot="10%"); c.tolerance("C1", lot="5%")
c.monte_carlo(runs=100, seed=1, analysis="transient")
c.histogram("Y_Level(v(out),1,1,10m)")                  # any MC9 performance expression
r = c.transient(10e-3, curves=["v(out)"])
c.show_histogram("hist.png")                            # MC9's own histogram window: bars, min / mean / max / sigma
from mc9 import measure
measure.statistics(measure.per_run(r, lambda run: run.peak("v(out)")[1]), lo=0.68, hi=0.78)   # your own numbers, with the yield

c.cursor_case(5)                # cursor on Monte Carlo / stepping case 5
c.autoscale(); c.restore_scale(); c.normalize_y("max")
c.cursors_y(0.2, 0.4)           # cursors at Y values
c.command("fill_scatter")       # any known command by name: see mc9.native.KNOWN_IDS

KNOWN_IDS also holds the numbers of the stepping (F11), limits (F9), watch window, breakpoints, operating-point methods, numeric output, initial-condition editor and "reduce points" commands; c.command(name) opens them, and tools/probe_menus.py maps further menu items (it clicks an item while the hook records the command and the dialogs).