Skip to content

Defining a circuit ​

A Circuit contains named component instances and their connections. Use a netlist for a circuit with named nodes, or connection groups to connect terminals directly. Both forms produce the same circuit model and can be mixed across a hierarchy.

The netlist form ​

Each entry is (name, nodes..., component). The node arguments follow the component's terminal order. Instance names can be symbols or strings; node names can also be integers. Node 0, "0", or Ground is ground. Entries naming the same node are connected.

julia
using JosephsonCircuits
R, Cc, Lj, Cj = 50.0, 100e-15, 1000e-12, 1000e-15
circuit = Circuit([
    (:p1, 1, 0, Port(1; Z0 = R)),
    (:cc, 1, 2, Capacitor(Cc)),
    (:jj, 2, 0, JosephsonJunction(Lj)),
    (:cj, 2, 0, Capacitor(Cj)),
])

The example is a JPA: a port couples through cc to a junction shunted by cj. Component values use SI units. JosephsonJunction(Lj) takes its small-signal inductance; IctoLj converts a critical current to that inductance.

Ports and sources ​

Port(1; Z0=50.0) identifies port 1 and supplies a matched 50 Ω external termination across its two terminals. It does not connect either terminal to ground unless the circuit does so. An extra resistor across the port is an additional load, with its own internal noise.

Use termination=nothing when the port should add no physical loading. This is a current-source and impedance-probe boundary, with a different reflection interpretation; see Port. Transient noise baths require matched port terminations.

Drive amplitudes belong to the analysis. HB uses Fourier coefficients; TransientSource uses instantaneous current. A port source injects its current into the port's first (positive) terminal. A CurrentSource component drives its constant current through itself from its first terminal to its second: it draws the current from the node at its first terminal and delivers it to the node at its second, the opposite sense of a port source. A port and a CurrentSource written on the same nodes in the same order therefore drive in opposite senses. See current conventions.

The connection-group form ​

A connection group lists terminals that share a net. (instance, number) selects a terminal; Ground can occur directly in a group or as a named component. Continuing the setup above:

julia
connected = Circuit(
    [:p1 => Port(1; Z0 = R), :cc => Capacitor(Cc),
     :jj => JosephsonJunction(Lj), :cj => Capacitor(Cj), :gnd => Ground()],
    [[(:p1, 1), (:cc, 1)],
     [(:cc, 2), (:jj, 1), (:cj, 1)],
     [(:p1, 2), (:jj, 2), (:cj, 2), (:gnd, 1)]])
# Both descriptions give the same reflection coefficient.
a = hblinsolve([2pi*4e9], circuit; keyedarrays = false)
b = hblinsolve([2pi*4e9], connected; keyedarrays = false)
@assert isapprox(a.S, b.S; rtol = 1e-12)

Use this form when connections are easier to express through component interfaces than through node names. Net names a connection explicitly; PortRef and PinRef address interfaces.

Mutual inductors ​

A mutual-inductor entry names two inductor instances rather than nodes:

julia
using JosephsonCircuits
coupled = Circuit([
    (:p1, 1, 0, Port(1)),
    (:l1, 1, 0, Inductor(1e-9)),
    (:l2, 2, 0, Inductor(2e-9)),
    (:p2, 2, 0, Port(2)),
    (:k1, :l1, :l2, MutualInductor(0.9)),
])

For positive coupling, currents entering the first terminal of each inductor add flux to both. Reversing one inductor's terminals, or changing the sign of the coefficient, reverses the coupling orientation.

Subcircuits ​

Give a circuit a pins interface to use it as a component. Each interface pin maps to a terminal of an internal instance. A netlist instance lists the external nodes in the declared pin order.

julia
using JosephsonCircuits
cell(Lj, Cj, Cg) = Circuit([
    (:jj, 1, 2, JosephsonJunction(Lj)),
    (:cj, 1, 2, Capacitor(Cj)),
    (:cg, 1, 0, Capacitor(Cg)),
]; pins = [1 => (:jj, 1), 2 => (:jj, 2)])

line = Circuit([
    (:p1, 1, 0, Port(1)),
    (:cell1, 1, 2, cell(1e-9, 50e-15, 40e-15)),
    (:cell2, 2, 3, cell(1e-9, 50e-15, 40e-15)),
    (:cell3, 3, 4, cell(1e-9, 50e-15, 40e-15)),
    (:p2, 4, 0, Port(2)),
])
compiled = compile(line)

Repeated instances can share one subcircuit definition. Elaboration flattens the hierarchy and qualifies internal names by their instance paths. The JTWPA and snake amplifier examples use this pattern at larger scales.

Component values and design parameters ​

A component value can be a number or a name such as :Lj. Supply a dictionary of the names' values when solving. A value derived from others is computed in Julia, by a function of the design parameters which returns the values, and the design sensitivities differentiate that function. With Symbolics loaded, symbolicmatrices gives the circuit's matrices with the names left as variables:

julia
julia> using JosephsonCircuits, Symbolics

julia> jpa = Circuit([
           (:p1, 1, 0, Port(1; Z0 = :R)),
           (:cc, 1, 2, Capacitor(:Cc)),
           (:jj, 2, 0, JosephsonJunction(:Lj)),
           (:cj, 2, 0, Capacitor(:Cj)),
       ]);

julia> m = symbolicmatrices(jpa);

julia> Matrix(m.Cnm)
2×2 Matrix{Num}:
  Cc      -Cc
 -Cc  Cc + Cj

julia> m.Gnm[1, 1], m.Ljb[2]
(1 / R, Lj)

Frequency-domain analyses also accept complex values and FrequencyDependent closures. The callable receives nonnegative angular frequency in rad/s; negative-frequency modes use conjugate symmetry. For example, a simple frequency-dependent impedance is:

julia
using JosephsonCircuits
wc = 2pi*10e9
load = Resistor(FrequencyDependent(w -> 50.0*(1 + im*w/wc)))

Such a value is not automatically a causal time-domain realization. Use explicit circuit elements or a rational scattering model for transient simulation.

Nonlinear elements and their current-phase relations ​

A JosephsonJunction uses I(φ) = (phi0/Lj)*sin(φ), where φ is reduced branch flux and phi0 is the reduced flux quantum. NonlinearInductor accepts a different current-phase relation. The inductance sets the current scale; the relation has unit slope at zero.

julia
using JosephsonCircuits
# An effective asymmetric relation with quadratic and cubic terms.
element = NonlinearInductor(1e-9, PolynomialCPR([1.0, 0.3, -1/6]))

PolynomialCPR represents f(φ) = c[1]*φ + c[2]*φ^2 + ..., with c[1]=1. It can approximate a biased SNAIL, a SQUID, a kinetic inductor, or a junction array over a specified phase range. For example, N identical series junctions have normalized relation N*sin(φ/N); expand that relation to obtain a local polynomial approximation.

For a kinetic inductor with differential inductance L(I)=L0*(1+I^2/Istar^2), the expansion through fifth order is [1, 0, -(IL/Istar)^2/3, 0, (IL/Istar)^4/3], where IL=phi0/L0.

Check two approximations independently:

  1. Keep the simulated phase within the range where the fitted relation is physically meaningful. Polynomial extrapolation can predict unphysical currents.

  2. Refine the Fourier grids or time step. A degree-d polynomial can expand the Fourier support of its input phase by a factor of d; a sine also generates higher harmonics. The drive frequency alone does not determine the required grid.

Both solvers use the chosen relation and its derivatives for the nonlinear response, linearization, and sensitivities.

Supported analyses ​

RepresentationHarmonic balanceTransient simulation
Real constant R, C, L, mutual inductors, junctions, polynomial CPRsSupportedSupported; initial algebraic constraints must hold
Complex or frequency-dependent lumped valuesSupportedUse a causal circuit or block realization instead
Constant real scattering matrixSupportedGaussLegendre()
Tabulated, Touchstone, or callable scattering dataSupported within the provider's domainFit a rational model first
Rational scattering realizationSupportedGaussLegendre()
Pumped LinearizedScatteringSupported subject to its excitation restrictionsFit with RationalScattering; GaussLegendre()
Ideal TransmissionLineSupportedGaussLegendre(); delay at least one step

Noise calculations have additional restrictions. In particular, noise outputs for lossy mutually coupled inductors are rejected; an S-only frequency-domain calculation does not supply a noise model for that loss. Transient noise requires a passive equilibrium prehistory and supported baths. See quantum noise in time.

Storage and compilation ​

For a generated circuit, use vectors of entries or connection groups. Component types may differ, so a heterogeneous description is normal. Compilation gathers values into concrete numerical arrays for the solver; the entire topology need not be encoded in a Julia tuple type.

The connection-group constructor retains the supplied collections. Subsequent elaboration observes edits to them. A compiled circuit or solver cache represents a compiled structure: rebuild it after changing topology. For repeated value changes, use the cache interface.

The implementation notes describe parsing, hierarchy reuse, and storage. Custom interface keys must obey Julia's isequal/hash contract.

Interface changes ​

See migration for code written for v0.5.4: tuple netlists, symbolic values, node order, and frequency-dependent expressions.

Scattering blocks, transmission lines and fitted data ​

The scattering-block guide covers construction, fitting, delays, pumped devices, and noise contracts with separate examples.

Noise models and temperatures ​

Ports, internal losses and scattering blocks take the temperatures and noise models of the temperature table; noise at the ports works through warm ports and a readout chain.