sycan.mna

MNA infrastructure: component base, stamping context, and solvers.

Two analysis modes are supported:

  • "dc" — steady-state operating point. Inductors short, capacitors open. Components with a stamp_nonlinear hook (e.g. MOSFETs in sub-threshold) contribute transcendental terms and the solver falls back to cas.solve.

  • "ac" — small-signal frequency-domain analysis using a Laplace variable s. Capacitors stamp admittance sC; inductors stamp 1/(sL). Source AC values override DC values.

Functions

build_mna(circuit[, mode, s])

Assemble the linear symbolic MNA system A * x = b for mode.

build_residuals(circuit[, mode, s])

Assemble the full residual vector A*x - b + nonlinear(x).

solve(circuit, *[, mode, s, simplify, assume])

Unified DC / AC entry point with assumption support.

solve_ac(circuit[, s, simplify, assume])

Solve the small-signal AC response in the Laplace domain.

solve_dc(circuit[, simplify, assume])

Solve the DC operating point symbolically.

solve_dc_sweep(circuit, parameter, values[, ...])

Sweep one symbolic parameter across values and return per-point DC.

solve_impedance(circuit, port_name[, ...])

Small-signal impedance looking into a named Port.

solve_noise(circuit, output_node[, s, simplify])

Output-voltage noise PSD at output_node in the Laplace domain.

solve_pz(circuit, output_node[, ...])

Pole-zero analysis of a transfer function in the Laplace domain.

solve_sensitivity(circuit, output[, ...])

Symbolic small-signal sensitivity ∂V(out)/∂p per parameter.

solve_tf(circuit, output_node[, ...])

Symbolic small-signal transfer function and summary metrics.

Classes

Component()

Netlist element that can stamp itself into an MNA matrix.

NoiseSource(name, kind, n_plus, n_minus, psd)

Symbolic small-signal noise current source.

PZResult(H, poles, zeros, numerator, denominator)

Result of a pole-zero analysis.

StampContext(A, b, node_rows, aux_rows[, ...])

Mutable state handed to each component's stamp method.

class sycan.mna.NoiseSource(name, kind, n_plus, n_minus, psd)[source]

Bases: object

Symbolic small-signal noise current source.

A unit current with one-sided power spectral density psd (units: A²/Hz) flowing from n_plus to n_minus internally (same convention as CurrentSource). kind is one of "thermal", "shot", "flicker"; name is a unique string of the form "<component>.<kind>[.<tag>]" so that callers can dis-aggregate the contributions returned by solve_noise().

Parameters:
  • name (str)

  • kind (str)

  • n_plus (str)

  • n_minus (str)

  • psd (Expr)

name: str
kind: str
n_plus: str
n_minus: str
psd: Expr
class sycan.mna.StampContext(A, b, node_rows, aux_rows, mode='dc', s=None, x=None, residuals=None)[source]

Bases: object

Mutable state handed to each component’s stamp method.

Parameters:
  • A (MutableDenseMatrix)

  • b (MutableDenseMatrix)

  • node_rows (dict[str, int])

  • aux_rows (dict[str, int])

  • mode (str)

  • s (Expr | None)

  • x (MutableDenseMatrix | None)

  • residuals (list | None)

A: MutableDenseMatrix
b: MutableDenseMatrix
node_rows: dict[str, int]
aux_rows: dict[str, int]
mode: str = 'dc'
s: Expr | None = None
x: MutableDenseMatrix | None = None
residuals: list | None = None
n(node)[source]

MNA row of a node name; -1 for ground.

Parameters:

node (str)

Return type:

int

aux(name)[source]

MNA row of a component’s auxiliary branch current.

Parameters:

name (str)

Return type:

int

class sycan.mna.Component[source]

Bases: ABC

Netlist element that can stamp itself into an MNA matrix.

Subclasses that introduce a branch-current unknown set has_aux = True. Subclasses that add transcendental current contributions (e.g. MOSFETs) set has_nonlinear = True and implement stamp_nonlinear`(), which is called during the DC residual pass.

Concrete subclasses declare which of their dataclass fields name circuit nodes via ports — a class variable listing attribute names. Circuit walks this list to register a component’s nodes without hard-coding per-component attribute names.

Every concrete subclass is auto-registered into Component._registry when its module is imported, keyed by class name. The registry is queryable via Component.available().

name: str
ports: ClassVar[tuple[str, ...]] = ()
has_aux: ClassVar[bool] = False
has_nonlinear: ClassVar[bool] = False
SUPPORTED_NOISE: ClassVar[frozenset[str]] = frozenset({})
classmethod available()[source]

Return the registry of every concrete Component subclass.

Return type:

dict[str, type[Component]]

iter_node_names()[source]

Yield each circuit node name referenced by this component.

Return type:

Iterator[str]

aux_count(mode)[source]

Number of auxiliary branch currents the component needs in mode.

Parameters:

mode (str)

Return type:

int

noise_sources()[source]

Return the small-signal noise sources this component emits.

Default is no-op; concrete components override to emit thermal / shot / flicker contributions weighted by the include_noise selection.

Return type:

list[NoiseSource]

abstractmethod stamp(ctx)[source]
Parameters:

ctx (StampContext)

Return type:

None

stamp_nonlinear(ctx)[source]

Add transcendental terms to ctx.residuals at solve time.

Only invoked for DC analysis when the circuit contains at least one component with has_nonlinear = True. Default is no-op.

Parameters:

ctx (StampContext)

Return type:

None

sycan.mna.build_mna(circuit, mode='dc', s=None)[source]

Assemble the linear symbolic MNA system A * x = b for mode.

Parameters:
  • circuit (Circuit)

  • mode (str)

  • s (Optional[cas.Expr])

Return type:

tuple[cas.Matrix, cas.Matrix, cas.Matrix]

sycan.mna.build_residuals(circuit, mode='dc', s=None)[source]

Assemble the full residual vector A*x - b + nonlinear(x).

Useful as a starting point for custom nonlinear solvers (numerical Newton, continuation, noise sampling, etc.) that want the raw symbolic equations rather than the dict form produced by solve_dc().

Parameters:
  • circuit (Circuit)

  • mode (str)

  • s (Optional[cas.Expr])

Return type:

tuple[cas.Matrix, list[cas.Expr]]

sycan.mna.solve_dc(circuit, simplify=True, *, assume=None)[source]

Solve the DC operating point symbolically.

If any component reports has_nonlinear, the solver builds residuals = A·x b plus nonlinear contributions and calls sympy.solve(). Otherwise, LU on the linear system.

assume is an optional list of Assumption objects applied to the solution dict after the solve (in addition to any attached to the circuit via assume()).

Parameters:
  • circuit (Circuit)

  • simplify (bool)

  • assume (Optional[list])

Return type:

dict[cas.Symbol, cas.Expr]

sycan.mna.solve_impedance(circuit, port_name, termination='auto', s=None, simplify=False)[source]

Small-signal impedance looking into a named Port.

A unit AC voltage is applied across the target port and the resulting branch current is read out; Z = dv/di follows. Other ports in the netlist are terminated per termination:

  • "z" — all other ports open (Z-parameter convention).

  • "y" — all other ports shorted (Y-parameter convention).

  • "auto" — other input ports shorted, output ports opened (the usual “sources zeroed, loads open” convention for amplifier input / output impedance).

The test source and any termination wires are added to a throw-away copy of the circuit, so the caller’s circuit is left untouched.

Parameters:
  • circuit (Circuit)

  • port_name (str)

  • termination (str)

  • s (Optional[cas.Expr])

  • simplify (bool)

Return type:

cas.Expr

sycan.mna.solve_ac(circuit, s=None, simplify=False, *, assume=None)[source]

Solve the small-signal AC response in the Laplace domain.

Nonlinear components (e.g. MOSFETs) contribute no small-signal model yet and are treated as zero-current elements.

assume is an optional list of Assumption objects applied to the solution after the matrix solve (in addition to any attached to the circuit via assume()).

Parameters:
  • circuit (Circuit)

  • s (Optional[cas.Expr])

  • simplify (bool)

  • assume (Optional[list])

Return type:

dict[cas.Symbol, cas.Expr]

sycan.mna.solve(circuit, *, mode='dc', s=None, simplify=False, assume=None)[source]

Unified DC / AC entry point with assumption support.

mode="ac" returns the Laplace-domain small-signal solution (delegating to solve_ac()).

mode="dc" collapses the s-domain solution at s=0 for purely-linear circuits — this is the formal “DC is AC with ω → 0” unification. Circuits containing nonlinear devices fall back to solve_dc() (the Newton-Raphson / closed-form nonlinear path) because their stamps don’t carry an LTI small-signal model that can be evaluated at s=0.

Assumptions attached to the circuit (via assume()) and any passed in assume are applied to the resulting solution dict in order. For region-style assumptions, use check_assumptions() separately on the returned solution to verify the claim.

Parameters:
  • circuit (Circuit)

  • mode (str)

  • s (Optional[cas.Expr])

  • simplify (bool)

  • assume (Optional[list])

Return type:

dict[cas.Symbol, cas.Expr]

sycan.mna.solve_noise(circuit, output_node, s=None, simplify=False)[source]

Output-voltage noise PSD at output_node in the Laplace domain.

Walks every component, asks for its Component.noise_sources(), and superposes their contributions:

S_V_out(s) = Σ_k  H_k(s) · H_k(-s) · S_k

where H_k(s) = V(output_node) / I_k(s) is the trans-impedance from the unit-current k-th noise source to the output, and S_k(s) is the source’s spectral density. To turn the symbolic result into a frequency-domain PSD, substitute s = cas.I * omega.

Independent V/I sources elsewhere in the circuit are not zeroed explicitly — their small-signal contribution does not enter the transfer-function matrix A, only the right-hand side, and we rebuild b per noise source so they fall away naturally.

Returns (total_psd, per_source_psd) where per_source_psd maps each NoiseSource.name to its individual contribution.

Parameters:
  • circuit (Circuit)

  • output_node (str)

  • s (Optional[cas.Expr])

  • simplify (bool)

Return type:

tuple[cas.Expr, dict[str, cas.Expr]]

class sycan.mna.PZResult(H, poles, zeros, numerator, denominator)[source]

Bases: object

Result of a pole-zero analysis.

Parameters:
  • H (Expr)

  • poles (list[Expr])

  • zeros (list[Expr])

  • numerator (Expr)

  • denominator (Expr)

H

Transfer function H(s) = V(out) / V(in) in the Laplace domain.

Type:

sympy.core.expr.Expr

poles

List of pole locations (roots of the denominator of H(s)). Each pole is an expression in component parameters and s.

Type:

list[sympy.core.expr.Expr]

zeros

List of zero locations (roots of the numerator of H(s)).

Type:

list[sympy.core.expr.Expr]

numerator

The raw numerator polynomial / expression of H(s).

Type:

sympy.core.expr.Expr

denominator

The raw denominator polynomial / expression of H(s).

Type:

sympy.core.expr.Expr

H: Expr
poles: list[Expr]
zeros: list[Expr]
numerator: Expr
denominator: Expr
sycan.mna.solve_pz(circuit, output_node, input_source=None, s=None, simplify=False)[source]

Pole-zero analysis of a transfer function in the Laplace domain.

Builds the AC MNA system, extracts the transfer function H(s) = V(output_node) / source_value, then factors it to find poles (roots of denominator) and zeros (roots of numerator).

If input_source is None (default), the system poles are extracted from the source-to-output transfer function using the first AC source found in the circuit. If no AC source is present, a ValueError is raised.

Parameters:
  • circuit (Circuit) – The circuit under analysis.

  • output_node (str) – Name of the node whose voltage constitutes the output.

  • input_source (Optional[str]) – Name of the independent source that drives the input. If None, the first source with a non-zero ac_value is used.

  • s (Optional[cas.Expr]) – Laplace variable. If None, cas.Symbol("s") is created.

  • simplify (bool) – If True, simplify the transfer function and pole/zero expressions.

Returns:

Dataclass holding the transfer function, poles, zeros, and raw numerator / denominator.

Return type:

PZResult

sycan.mna.solve_dc_sweep(circuit, parameter, values, simplify=False)[source]

Sweep one symbolic parameter across values and return per-point DC.

Solves the symbolic DC operating point once (with simplify deferred to the caller — keep it False to amortise the symbolic cost) and substitutes parameter with each value in values. This is the closed-form analogue of SPICE’s .DC vsweep and is useful for plotting transfer characteristics, bias curves, or swept-supply gain.

Parameters:
  • circuit (Circuit) – Circuit under analysis.

  • parameter (Union[str, cas.Symbol]) – Symbol (or its string name) to sweep. Must appear in the symbolic DC solution.

  • values (list) – Iterable of values (numeric or symbolic) to substitute.

  • simplify (bool) – If True, simplify the substituted expressions per step.

Returns:

One dict per swept value, each with the same shape as solve_dc()’s return value but with parameter replaced.

Return type:

list[dict[Symbol, Expr]]

sycan.mna.solve_tf(circuit, output_node, input_source=None, s=None, simplify=False)[source]

Symbolic small-signal transfer function and summary metrics.

Returns a dict with the closed-form Laplace-domain transfer function H(s) = V(output_node) / source_value plus convenient summary metrics extracted symbolically:

  • H — full H(s) expression

  • dc_gainH(0) (i.e. H.subs(s, 0))

  • hf_gainlim s→∞ H(s) (via cas.limit)

  • numerator — numerator polynomial of H(s) (s-domain)

  • denominator — denominator polynomial of H(s) (s-domain)

Source selection follows the same rule as solve_pz(): if input_source is None, the first source with a non-zero ac_value is used.

Parameters:
  • circuit (Circuit)

  • output_node (str)

  • input_source (Optional[str])

  • s (Optional[cas.Expr])

  • simplify (bool)

Return type:

dict[str, cas.Expr]

sycan.mna.solve_sensitivity(circuit, output, parameters=None, mode='dc', s=None, normalized=False, simplify=False)[source]

Symbolic small-signal sensitivity ∂V(out)/∂p per parameter.

Solves the requested analysis ("dc" or "ac") symbolically, extracts the output expression for output (a node name or a symbol such as Symbol("V(out)")), and differentiates w.r.t. each parameter.

Parameters:
  • circuit (Circuit) – Circuit under analysis.

  • output (Union[str, cas.Symbol]) – Node name (string) or unknown symbol (e.g. Symbol("I(V1)")).

  • parameters (Optional[list]) – List of symbols (or names) to differentiate over. None means every free symbol of the output expression that is not an unknown of the MNA system.

  • mode (str) – "dc" (default) or "ac".

  • s (Optional[cas.Expr]) – Laplace variable for AC mode.

  • normalized (bool) – If True, return the relative sensitivity (p / V_out) · ∂V_out/∂p, which is dimensionless and matches the SPICE .SENS convention for “% per %”.

  • simplify (bool) – Apply cas.simplify() to each sensitivity expression.

Returns:

Mapping of each parameter symbol to its sensitivity expression.

Return type:

dict[Symbol, Expr]