Whilst I might succumb in my crazier evangelical moments to the idea that academic authors (other than those who speak `LateX`

natively) and media developers might engage in the raw `circuitikz`

authoring described Reproducible Diagram Generators – First Thoughts on Electrical Circuit Diagrams, the reality is that it’s probably just way too clunky, and a little bit too far removed from the everyday diagrams educators are likely to want to create, to get much, if any, take up.

However, understanding something of the capabilities of quite low level drawing packages, and reflecting (as in the last post) on some of the strategies we might adopt for creating reusable, maintainable, revisable with modification and extensible diagram scripts puts us in good stead for looking out for more usable approaches.

One such example is the Python `lcapy`

package, a linear circuit analysis package that supports:

- the description of simple electrical circuits at a sloghtly hoger level than the raw
`circuitikz`

circuit creation model; - the rendering of the circuits, with a few layout cues, using
`circuitikz`

; - numerical analysis of the circuits in terms of response in time and frequency domains, and the charting of the results of the analysis; and
- various forms of symbolic analysis of circuit descriptions in various domains.

Here are some quick examples to give a taste of what’s possible.

*You can run the notebook (albeit subject to significant changes) that contains the original working for examples used in this post on Binderhub: *

Here’s a simple circuit:

And here’s how we can create it in `lcapy`

from a netlist, annotated with cues for the underlying `circuitikz`

generator about how to lay out the diagram.

from lcapy import Circuit cct = Circuit() cct.add(""" Vi 1 0_1 step 20; down C 1 2; right, size=1.5 R 2 0; down W 0_1 0; right W 0 0_2; right, size=0.5 P1 2_2 0_2; down W 2 2_2;right, size=0.5""") cct.draw(style='american')

The things to the right of the semicolon on each line are the optional layout elements – they’re not required when defining the actual circuit itself.

The display of nodes and numbered nodes are all controllable, and the symbol styles are selectable between `american`

, `british`

and `european`

stylings.

The `lcapy/schematic.py`

package describes the various stylings as composites of `circuitikz`

regionalisations, and could be easily extended to support a named house style, or perhaps accommodate a regionalisation passed in as an explicit argument value.

if style == 'american': style_args = 'american currents, american voltages' elif style == 'british': style_args = 'american currents, european voltages' elif style == 'european': style_args = ('european currents, european voltages, european inductors, european resistors')

As well as constructing circuits from netlist descriptions, we can also create them from network style descriptions:

from lcapy import R, C, L cct2= (R(1e6) + L(2e-3)) | C(3e-6) #For some reason, the style= argument is not respected cct2.draw()

The diagrams generated from networks are open linear circuits rather than loops, which may not be quite what we want. But these circuits are quicker to write, so we can use them to draft netlists for us that we may then want to tidy up a bit further.

print(cct2.netlist()) ''' W 1 2; right=0.5 W 2 4; up=0.4 W 3 5; up=0.4 R 4 6 1000000.0; right W 6 7; right=0.5 L 7 5 0.002; right W 2 8; down=0.4 W 3 9; down=0.4 C 8 9 3e-06; right W 3 0; right=0.5 '''

Circuit descriptions can also be loaded in from a named text file, which is handy for course material maintenance as well as reuse of circuits across materials: it’s easy enough to imagine a library of circuit descriptions.

#Create a file containing a circuit netlist sch=''' Vi 1 0_1 {sin(t)}; down R1 1 2 22e3; right, size=1.5 R2 2 0 1e3; down P1 2_2 0_2; down, v=V_{o} W 2 2_2; right, size=1.5 W 0_1 0; right W 0 0_2; right ''' fn="voltageDivider.sch" with open(fn, "w") as text_file: text_file.write(sch) #Create a circuit from a netlist file cct = Circuit(fn)

The ability to create – and share – circuit diagrams in a Python context that plays nicely with Jupyter notebooks is handy, but the `lcapy`

approach becomes really useful if we want to produce other assets around the circuit we’ve just created.

For example, in the case of the above circuit, how do the various voltage levels across the resistors respond when we switch on the sinusoidal source?

import numpy as np t = np.linspace(0, 5, 1000) vr = cct.R2.v.evaluate(t) from matplotlib.pyplot import figure, savefig fig = figure() ax = fig.add_subplot(111, title='Resistor R2 voltage') ax.plot(t, vr, linewidth=2) ax.plot(t, cct.Vi.v.evaluate(t), linewidth=2, color='red') ax.plot(t, cct.R1.v.evaluate(t), linewidth=2, color='green') ax.set_xlabel('Time (s)') ax.set_ylabel('Resistor voltage (V)');

Not the best example, admittedly, but you get the idea!

Here’s another example, where I’ve created a simple interactive to let me see the effect of changing one of the component values on the response of a circuit to a step input:

(The nice plotting of the diagram gets messed up unfortunately, at least in the way I’ve set things up for this example…)

As the code below shows, the `@interact`

decorator from `ipywidgets`

makes it trivial to create a set of interactive controls based around the arguments passed into a function:

import numpy as np from matplotlib.pyplot import figure, savefig @interact(R=(1,10,1)) def response(R=1): cct = Circuit() cct.add('V 0_1 0 step 10;down') cct.add('L 0_1 0_2 1e-3;right') cct.add('C 0_2 1 1e-4;right') cct.add('R 1 0_4 {R};down'.format(R=R)) cct.add('W 0_4 0; left') t = np.linspace(0, 0.01, 1000) vr = cct.R.v.evaluate(t) fig = figure() #Note that we can add Greek symbols from LaTex into the figure text ax = fig.add_subplot(111, title='Resistor voltage (R={}$\Omega$)'.format(R)) ax.plot(t, vr, linewidth=2) ax.set_xlabel('Time (s)') ax.set_ylabel('Resistor voltage (V)') ax.grid(True) cct.draw()

Using the network description of a circuit, it only takes a couple of lines to define a circuit and then get the transient response to step function for it:

Again, it doesn’t take much more effort to create an interactive that lets us select component values and explore the effect they have on the damping:

As well as the numerical analysis, `lcapy`

also supports a range of symbolic analysis functions. For example, given a parallel resistor circuit, defined using a network description, we can find the overall resistance in simplest terms:

Or for parallel capacitors:

Some other elementary transformations we can apply – providing expressions for the an input voltage in the time or Laplace/s domain:

We can also create pole-zero plots quite straightforwardly, directly from an expression in the s-domain:

This is just a quick skim through of some of what’s possible with `lcapy`

. So how and why might it be useful as part of a reproducible educational resource production process?

One reason is that several of the functions can reduce the “production distance” between different likely components of a set of educational materials.

For example, given a particular circuit description as a netlist, we can annotate it with direction cribs in order to generate a visual circuit diagram, and we can use a circuit created from it directly (or from the direction annotated script) to generate time or frequency response charts. (We can also obtain symbolic transfer functions.)

When trying to plot things like pole zero charts, where it is important that the chart matches a particular s-domain expression, we can *guarantee* that the chart is correct by deriving it directly from the s-domain expression, and then rendering that expression in pretty *LaTeX* equation form in the materials.

The ability to simplify expressions – as in the example of the simplified expressions for overall capacitance or resistance in the parallel circuit examples above – *directly from a circuit description* whilst at the same time using that circuit description to render the circuit diagram, also reduces the amount of separation between those two outputs to zero – they are both generated from the self-same source item.

*You can run the notebook (albeit subject to significant changes) that contains the original working for examples used in this post on Binderhub: *