# User Guide

## Element Creation

All circuit elements are created by calling corresponding functions; see the Element Reference for details.

### Unitful elements

ACME provides a package extension for Unitful to support quantities with units when constructing elements. E.g. `resistor(4.7e3)`

and `resistor(4.7u"kΩ")`

are equivalent after `Unitful`

has been loaded. This can increase readability and help catch bugs (e.g. `resistor(5u"V")`

will throw an error). The input and output signals of the curcuit models will still be unitless, however.

Package extensions require Julia 1.9 or later. Consequently, unitful quantities are not supported on earlier Julia versions.

## Circuit Description

Circuits are described using `Circuit`

instances, which are most easily created using the `@circuit`

macro:

`ACME.@circuit`

— Macro`@circuit begin #= ... =# end`

Provides a simple domain-specific language to decribe circuits. The `begin`

/`end`

block can hold element definitions of the form `refdes = elementfunc(params)`

and connection specifications of the form `refdes1[pin1] ⟷ refdes2[pin2]`

.

**Example**

To create a circuit with a voltage source connected to a resistor:

```
@circuit begin
src = voltagesource(5)
r = resistor(1000)
src[+] ⟷ r[1]
src[-] ⟷ r[2]
end
```

Alternatively, connection specifications can be given after an element specification, separated by commas. In that case, the `refdes`

may be omitted, defaulting to the current element.

**Example**

```
@circuit begin
src = voltagesource(5)
r = resistor(1000), src[+] ⟷ [1], src[-] ⟷ [2]
end
```

Finally, a connection endpoint may simply be of the form `netname`

, to connect to a named net. (Such named nets are created as needed.)

**Example**

```
@circuit begin
src = voltagesource(5), [-] ⟷ gnd
r = resistor(1000), [1] ⟷ src[+], [2] ⟷ gnd
end
```

If a net or pin specification is not just a single symbol or number, and has to be put in quotes (e.g. `"in+"`

, `"9V"`

)

Instead of `⟷`

(`\longleftrightarrow`

), one can also use `==`

.

The pins provided by each type of element are described in the Element Reference.

Instead of or in addition to using the `@circuit`

macro, `Circuit`

instances can also be populated and modified programmatically using the following functions:

`ACME.add!`

— Function`add!(c::Circuit, elem::Element)`

Adds the element `elem`

to the circuit `c`

, creating and returning a new, unique reference designator, leaving its pins unconnected.

`add!(c::Circuit, designator::Symbol, elem::Element)`

Adds the element `elem`

to the circuit `c`

with the reference designator `designator`

, leaving its pins unconnected. If the circuit already contained an element named `designator`

, it is removed first.

`Base.delete!`

— Function`delete!(c::Circuit, designator::Symbol)`

Deletes the element named `designator`

from the circuit `c`

(disconnecting all its pins).

`ACME.connect!`

— Function`connect!(c::Circuit, pins::Union{Symbol,Tuple{Symbol,Any}}...)`

Connects the given pins (or named nets) to each other in the circuit `c`

. Named nets are given as `Symbol`

s, pins are given as `Tuple{Symbols,Any}`

s, where the first entry is the reference designator of an element in `c`

, and the second entry is the pin name. For convenience, the latter is automatically converted to a `Symbol`

as needed.

**Example**

```
circ = Circuit()
add!(circ, :r, resistor(1e3))
add!(circ, :src, voltagesource(5))
connect!(circ, (:src, -), (:r, 2), :gnd) # connect to gnd net
```

`ACME.disconnect!`

— Function`disconnect!(c::Circuit, p::Tuple{Symbol,Any})`

Disconnects the given pin `p`

from anything else in the circuit `c`

. The pin is given as a`Tuple{Symbols,Any}`

, where the first entry is the reference designator of an element in `c`

, and the second entry is the pin name. For convenience, the latter is automatically converted to a `Symbol`

as needed. Note that if e.g. three pin `p1`

, `p2`

, and `p3`

are connected then `disconnect!(c, p1)`

will disconnect `p1`

from `p2`

and `p3`

, but leave `p2`

and `p3`

connected to each other.

For example, a cascade of 20 RC-lowpasses could be generated by:

```
circ = @circuit begin
src = voltagesource(), [-] ⟷ gnd
output = voltageprobe(), [-] ⟷ gnd
end
pin = (:src, +)
for i in 1:20
resrefdes = add!(circ, resistor(1000))
caprefdes = add!(circ, capacitor(10e-9))
connect!(circ, (resrefdes, 1), pin)
connect!(circ, (resrefdes, 2), (caprefdes, 1))
connect!(circ, (caprefdes, 2), :gnd)
global pin = (resrefdes, 2)
end
connect!(circ, pin, (:output, +))
```

## Model Creation and Use

A `Circuit`

only stores elements and information about their connections. To simulate a circuit, a model has to be derived from it. This can be as simple as:

`model = DiscreteModel(circ, 1/44100)`

Here, `1/44100`

denotes the sampling interval, i.e. the reciprocal of the sampling rate at which the model should run. Optionally, one can specify the solver to use for solving the model's non-linear equation:

`model = DiscreteModel(circ, 1/44100, HomotopySolver{SimpleSolver})`

See Solvers for more information about the available solvers.

Once a model is created, it can be run:

`y = run!(model, u)`

The input `u`

is matrix with one row for each of the circuit's inputs and one column for each time step to simulate. Likewise, the output `y`

will be a matrix with one row for each of the circuit's outputs and one column for each simulated time step. The order of the rows will correspond to the order in which the respective input and output elements were added to the `Circuit`

. So for above circuit, we may obtain the first 100 samples of the impulse response with

```
run!(model, [1 zeros(1,99)])
# output
1×100 Matrix{Float64}:
1.83357e-8 3.1622e-7 2.59861e-6 … 0.00465423 0.00459275 0.00453208
```

To simulate a circuit without inputs, a matrix with zero rows may be passed:

`y = run!(model, zeros(0, 100))`

The internal state of the model (e.g. capacitor charges) is preserved accross calls to `run!`

.

Each invocation of `run!`

in this way has to allocate some memory as temporary storage. To avoid this overhead when running the same model for many small input blocks, a `ModelRunner`

instance can be created explicitly:

```
runner = ModelRunner(model, false)
run!(runner, y, u)
```

By using a pre-allocated output `y`

as in the example, allocations in `run!`

are reduced to a minimum.

Upon creation of a `DiscreteModel`

, its internal states (e.g. capacitor charges) are set to zero. It is also possible to set the states to a steady state (if one can be found) with:

`steadystate!(model)`

This is often desirable for circuits where bias voltages are only slowly obtained after turning them on.

## Solvers

`ACME.SimpleSolver`

— Type`SimpleSolver`

The `SimpleSolver`

is the simplest available solver. It uses Newton iteration which features fast local convergence, but makes no guarantees about global convergence. The initial solution of the iteration is obtained by extrapolating the last solution found (or another solution provided externally) using the available Jacobians. Due to the missing global convergence, the `SimpleSolver`

is rarely useful as such.

`ACME.HomotopySolver`

— Type`HomotopySolver{BaseSolver}`

The `HomotopySolver`

extends an existing solver (provided as the type parameter) by applying homotopy to (at least theoretically) ensure global convergence. It can be combined with the `SimpleSolver`

as `HomotopySolver{SimpleSolver}`

to obtain a useful Newton homtopy solver with generally good convergence properties.

`ACME.CachingSolver`

— Type`CachingSolver{BaseSolver}`

The `CachingSolver`

extends an existing solver (provided as the type parameter) by storing found solutions in a k-d tree to use as initial solutions in the future. Whenever the underlying solver needs more than a preset number of iterations (defaults to five), the solution will be stored. Storing new solutions is a relatively expensive operation, so until the stored solutions suffice to ensure convergence in few iterations throughout, use of a `CachingSolver`

may actually slow things down.

See M. Holters, U. Zölzer, "A k-d Tree Based Solution Cache for the Non-linear Equation of Circuit Simulations" for a more detailed discussion.

The default solver used is a `HomotopySolver{CachingSolver{SimpleSolver}}`

.