Centaur is a C++ prototype for symbolic analog-circuit analysis and topology
rewriting. The goal is Herbie-like exploration for circuit structure: keep the
input close to textbook circuits, infer useful equivalent forms, and produce
readable expressions such as vdiv, par, and simplified Thevenin results.
Centaur currently focuses on linear DC/resistive circuits with independent and linear dependent sources. It combines three pieces:
- a SPICE-like netlist parser
- symbolic modified nodal analysis (MNA)
- equality-saturation and topology rewrite passes
Implemented today:
- symbolic expressions with local simplification
- egg-style e-graph rewrites for common analog forms
- Thevenin equivalent calculation
- operating-point node voltages, branch currents, powers, and source seen resistance
- constraint solving for backward design queries, including inequality bounds, multiple equality roots, and coupled linear equality systems
- topology rewrites for series/parallel resistors and ideal-source patterns
- independent voltage/current sources
- voltage-controlled voltage/current sources
- current-controlled current sources using component current references
- regression examples from Schaum Basic Circuit Analysis exercises 3.20-3.50, 3.52-3.90, and 4.1-4.67
This is still a prototype. The current solver is linear and DC-oriented; AC impedance forms are present in the expression rewrite layer, but not yet a full frequency-domain circuit frontend.
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failure
ctest --test-dir build --output-on-failure -R exercise_4_The main executable is:
./build/centaurOptimize a symbolic expression:
./build/centaur '(mul Vin (div R2 (add R1 R2)))'Output:
(vdiv Vin R1 R2)
Compute a Thevenin equivalent:
./build/centaur --thevenin examples/voltage_divider.cir out 0Output:
Vth out 0: (vdiv Vin R1 R2)
Rth out 0: (par R1 R2)
Solve selected textbook quantities:
./build/centaur --solve examples/exercise_3_28.cir \
--voltage a 0 \
--current R9 \
--current R13Output:
V a 0: -35
I R9: 0
I R13: 0
Use --explain to see topology trace lines and expression rewrite activity:
./build/centaur --explain --solve examples/exercise_3_28.cir --voltage a 0Infer an unknown resistance from a target equivalent resistance:
./build/centaur --solve-rth-for examples/exercise_3_35.cir a 0 R \
'(eq Rth 12000)'Output:
R: -15000
The parser accepts a compact SPICE-like format:
Rname node+ node- value
Vname node+ node- value
Iname node+ node- value
Ename node+ node- ctrl+ ctrl- gain
Gname node+ node- ctrl+ ctrl- gain
Fname node+ node- control-component gain
Component meanings:
R: resistorV: independent voltage sourceI: independent current sourceE: voltage-controlled voltage sourceG: voltage-controlled current sourceF: current-controlled current source
For F, the control is another component name. For example:
Fdep v1 0 R16 0.5
means:
I(Fdep from v1 to 0) = 0.5 * I(R16)
Values may be numeric atoms such as 10, or symbolic atoms such as R1,
Vin, and gm.
Ground may be written as 0, gnd, or GND.
Solve an operating point:
./build/centaur --solve file.cirWith no explicit queries, Centaur prints node voltages relative to ground and resistor currents. With explicit queries, it prints only the requested values:
./build/centaur --solve file.cir \
--voltage node+ node- \
--current component \
--power component \
--seen-resistance VsourceCompute a Thevenin equivalent:
./build/centaur --thevenin file.cir node+ node-Solve a one-variable symbolic constraint:
./build/centaur --solve-for R '(eq (par 10000 20000 R) 12000)'Inequality constraints are supported with lt/<, le/<=, gt/>, and
ge/>=:
./build/centaur --solve-for R '(le (div (sub 15 12) (add R 0.3)) 2)'Output:
R >= 1.2
Equalities may produce more than one discrete solution:
./build/centaur --solve-for R \
'(eq (mul R (div 240 (add R 100)) (div 240 (add R 100))) 80)'Output:
R: 20 or 500
Use circuit-derived values directly inside a constraint:
./build/centaur --solve-constraint examples/exercise_3_57.cir ROutput:
R >= 1.2
The netlist carries the observable inside the constraint:
.constraint (le (current Rlimit) 2)
The observable forms are (current component), (voltage node+ node-),
(power component), and (rth node+ node-).
Multiple .constraint lines are treated as a conjunction when a solve command
asks for a variable; equality-only linear systems may contain additional
intermediate variables.
Infer a value from a target Thevenin resistance:
./build/centaur --solve-rth-for file.cir node+ node- R '(eq Rth 12000)'The Rth atom may appear anywhere in the constraint expression:
./build/centaur --solve-rth-for file.cir node+ node- R '(eq (inv Rth) 1.75)'Run only the topology prepass:
./build/centaur --rewrite-topology file.cir protected-node ...Protected nodes are observed terminals; the topology pass avoids removing or contracting them.
Topology rewrites operate on the circuit graph before symbolic MNA. They are
symbolic: resistor values such as R1 and R2 are preserved and combined into
expressions such as (par R1 R2) and (add R1 R2).
Current rules:
- parallel resistors with the same two terminals become one resistor with
(par ...) - series resistors through an unprotected degree-2 node become one resistor
with
(add ...) - shorted resistors are removed
- unprotected independent
0 Vsources are removed by merging their two nodes - dangling resistor branches ending at unprotected nodes are removed
- a resistor in series with an ideal current source is removed and the source is reconnected across the branch
- a resistor in parallel with an ideal voltage source is removed
The solve path is query-aware. Explicit --voltage queries protect their nodes;
explicit --current, --power, and --seen-resistance queries protect their
components. This lets Centaur simplify the circuit for a Vab query while still
keeping a resistor if the user asks for that resistor current.
Example:
./build/centaur --explain --rewrite-topology examples/exercise_3_28.cir a 0The topology summary reports two current-source series resistor removals and
one voltage-source parallel resistor removal. For ladder-style reductions,
--explain also prints each topology rewrite step:
./build/centaur --explain --rewrite-topology examples/exercise_3_41.cir a 0Example trace line:
series-resistors: R4 inner tail 4 + R8 tail right 8 through tail -> Rser2 inner right (add 4 8)
Expressions use s-expressions:
(add a b)
(sub a b)
(mul a b)
(div a b)
(neg a)
(inv a)
(par R1 R2 ...)
(vdiv Vin Rtop Rbot)
(zc C1)
(zl L1)
The rewrite rules include algebraic identities, parallel impedance forms, voltage-divider recognition, capacitor/inductor impedance forms, RC low-pass forms, and a common-source gain idiom.
Constraint queries are ordinary expressions:
(eq (par 10000 20000 R) 12000)
(eq Rth 12000)
(eq (inv Rth) 1.75)
(le (div (sub 15 12) (add R 0.3)) 2)
(lt (current Rlimit) 2)
The examples/ directory includes checked examples from Schaum Basic Circuit
Analysis:
./build/centaur --solve examples/exercise_3_20.cir \
--current R10 --current R25 --voltage top n15
./build/centaur --solve examples/exercise_3_26.cir \
--voltage v1 0 --current R16 --current Fdep
./build/centaur --solve examples/exercise_3_28.cir \
--voltage a 0 --current R9 --current R13
./build/centaur --solve-rth-for examples/exercise_3_35.cir \
a 0 R '(eq Rth 12000)'
./build/centaur --solve-rth-for examples/exercise_3_36.cir \
a 0 R '(eq (inv Rth) 1.75)'
./build/centaur --solve-constraint examples/exercise_3_57.cir R
./build/centaur --solve-constraint examples/exercise_3_58.cir R
./build/centaur --solve examples/exercise_3_59.cir \
--power R2 --power R4 --power R6
./build/centaur --solve examples/exercise_3_60.cir --voltage a 0
./build/centaur --solve examples/exercise_3_60.cir \
--voltage bottom_left bottom_mid
./build/centaur --solve examples/exercise_3_62.cir --voltage n5 n6
./build/centaur --solve examples/exercise_3_63.cir --current R2
./build/centaur --solve examples/exercise_3_64.cir \
--voltage out_plus out_minus
./build/centaur --solve-constraint examples/exercise_3_65.cir I1
./build/centaur --solve-constraint examples/exercise_3_65.cir I2
./build/centaur --solve-constraint examples/exercise_3_65.cir I3
./build/centaur --solve-constraint examples/exercise_3_65.cir I4
./build/centaur --solve examples/exercise_3_66.cir --current V_I
./build/centaur --solve examples/exercise_3_67.cir --voltage out_top 0
./build/centaur '(par (add 4 4) 4)'
./build/centaur --solve-constraint examples/exercise_3_69.cir R
./build/centaur --thevenin examples/exercise_3_70.cir a 0
./build/centaur --solve-constraint examples/exercise_3_71.cir R
./build/centaur --solve-constraint examples/exercise_3_72.cir R
./build/centaur '(par (add (par 24 48) 24) 10)'
./build/centaur '(par (add (par 6 12) (par 10 40)) (add 6 2))'
./build/centaur --explain --rewrite-topology examples/exercise_3_75.cir a 0
./build/centaur --thevenin examples/exercise_3_75.cir a 0
./build/centaur --thevenin examples/exercise_3_76.cir a 0
./build/centaur --thevenin examples/exercise_3_77a.cir in 0
./build/centaur --thevenin examples/exercise_3_77b.cir in 0
./build/centaur --solve examples/exercise_3_78.cir --current R4 --current R6 --current R8 --current R12
./build/centaur --solve examples/exercise_3_79.cir --current R8 --current R12 --current R16 --current R24
./build/centaur --solve examples/exercise_3_80.cir --current R24_i1 --current R24_i2 --current R8_i3 --current R24_i4 --current R20_i5
./build/centaur --solve-constraint examples/exercise_3_81.cir R1
./build/centaur --solve-constraint examples/exercise_3_81.cir R2
./build/centaur --solve examples/exercise_3_82.cir --current R1
./build/centaur --solve examples/exercise_3_83.cir --current R10
./build/centaur --solve-constraint examples/exercise_3_84.cir R
./build/centaur --solve examples/exercise_3_85.cir --current R40
./build/centaur --solve-constraint examples/exercise_3_86.cir Vs
./build/centaur --solve examples/exercise_3_87.cir --current R70 --power Fdep
./build/centaur --solve examples/exercise_3_88.cir --voltage out 0
./build/centaur --solve examples/exercise_3_89a.cir --current Vload
./build/centaur --solve examples/exercise_3_89b.cir --current Rload
./build/centaur --solve examples/exercise_3_89c.cir --current Rload
./build/centaur --solve examples/exercise_3_90.cir --current R30These are also covered by CTest.
Near-term work:
- add more source-control forms, including current-controlled voltage sources
- generalize constraints to multiple variables with a solver backend and verified observable lowering
- improve symbolic linear solving to avoid floating-point artifacts
- add typed symbols and dimensions
- extend the frontend toward impedance-domain
Zcircuits - integrate
c-spiceas an optional parser/simulation frontend - grow the textbook regression suite into a rule-discovery harness