Verifying with Python
Intent
Section titled “Intent”- Follow one equation from its SymPy derivation to the page and to the tests.
- Compare two computations by their relative difference, and choose a tolerance that rounding cannot fail and a real mistake cannot pass.
- Run the site’s checks on your own machine.
Theory
Section titled “Theory”One source, two implementations
Section titled “One source, two implementations”Every equation on the site lives once, in
packages/pe-core/equations/equations.yaml, as a SymPy expression with its
symbols, its tests and its citation. The Python package python/pe_core
works from it:
- Where it can, a script in
python/pe_core/derive/derives the equation from first principles, solving the circuit’s balance equations with SymPy step by step. The derivations page shows those steps as the code computes them, and pytest checks that each result equals the catalogue’s expression. python scripts/gen_equations.pywrites the LaTeX the pages show and the test vectors: each equation’s own tests, and random inputs evaluated by SymPy. CI runs it again and fails on any difference.
The TypeScript engine behind the pages and the tools has its own evaluators, written by hand rather than generated from the same file, so that a mistake in one is not copied into the other. vitest checks every evaluator against the vectors.
Comparing two numbers
Section titled “Comparing two numbers”Two computations are compared by their relative difference:
- relative difference
- reference value
- value under test
Assumptions, convention and source
- Assumptions
- definition
- Notes
- it does not change when the quantity is scaled, as by a change of unit from volts to millivolts, so one tolerance serves every quantity (a unit whose zero moves, such as degrees Celsius against kelvins, is the exception); at a zero reference it is undefined, and near one a small absolute difference becomes a large relative one. The site asks 1e-9 of the TypeScript and Python evaluations of the same equation, and 1 % or 2 % of a simulation against its equation
- Source
- Higham 2002, Sec. 1.2 (Relative Error and Significant Digits)
- Equation id
num.rel_diff· equations.yaml entry
It does not change when the quantity is scaled, as by a change of unit from volts to millivolts, so one tolerance serves every quantity. At a zero reference it is undefined [Higham 2002, Sec. 1.2], and near zero it exaggerates a small difference, so compare against a scale there. Two correct programs need not produce the same bits: each operation rounds its result, and the result depends on the order of the operations. One rounding changes a number by at most the unit roundoff of the format, relative to its size:
- unit roundoff
- base (2 for binary)
- precision (significand digits)
Assumptions, convention and source
- Assumptions
- rounding to the nearest number of the format, IEEE 754's default
- Notes
- rounding a real number to the nearest number of the format changes it by at most this fraction of itself (in the normal range). For binary64, the IEEE 754 double precision of JavaScript's numbers and Python's floats, the base is 2 and the precision 53 digits, so . Higham writes the precision as
- Source
- Higham 2002, Ch. 2 (Floating Point Arithmetic: the unit roundoff, Sec. 2.1, and its value in IEEE double precision)
- Equation id
num.unit_roundoff· equations.yaml entry
The site requires the TypeScript and the Python evaluations of an equation to agree within one part in a billion. That is millions of times the unit roundoff, so rounding passes as long as the evaluation does not magnify its errors, as subtracting nearly equal numbers does (cancellation) [Higham 2002, Sec. 1.7]; yet it lies far below any real mistake, such as a wrong constant or a missing factor. A simulation compared with its equation gets a looser tolerance, because the equation idealizes the circuit: it takes the ripple as small [Erickson & Maksimović 2020, Ch. 2], and the SPICE library’s switch and diodes are only nearly ideal. The SPICE library’s values must lie within one percent of the ideal equations, measured against each quantity’s largest magnitude over the period, and the in-browser simulator’s conversion ratio and ripple within two percent.
Checks against another solver
Section titled “Checks against another solver”scripts/spice_crosscheck.py writes the in-browser simulator’s circuits
again as netlists, from their schematics rather than from the simulator’s
equations, and runs them in ngspice [ngspice – open source SPICE simulator]. CI compares the
simulator with ngspice’s results: each value within half a percent of the
quantity’s largest magnitude over the period, with a small allowance for what
the SPICE parts add, and each peak-to-peak swing within two percent.
Worked example
Section titled “Worked example”A simulated conversion ratio against the ideal one, and the unit roundoff of binary64, the IEEE 754 double precision of JavaScript’s numbers and Python’s floats:
| Symbol | Value | How it is obtained |
|---|---|---|
| value under test | 0.496 | given |
| reference value | 0.5 | given |
| base (2 for binary) | 2 | given |
| precision (significand digits) | 53 | given |
| relative difference | 0.008 | Relative difference of a value from its reference |
| unit roundoff | 1.11e-16 | Unit roundoff of a floating-point format |
The ratio differs from the ideal one by 0.008, within the library’s one percent. The unit roundoff is 1.11e-16: a tolerance of one part in a billion leaves room for millions of roundings.
Try it
Section titled “Try it”▶ Open in the equation explorer: Relative difference of a value from its reference
Preset from the example num-parity
Sweep the value under test: the relative difference grows linearly on each side of the reference and reaches zero only at the reference itself.
Bench exercise
Section titled “Bench exercise”Measure a converter’s input and output voltages on the bench with a multimeter, compute the relative difference of their ratio from the ideal one in Python, and list what can account for it: the diode’s drop, the switch’s and the winding’s resistances, the ripple. Then change the load and see which of them grows with the current.
Gotchas
Section titled “Gotchas”- A reference near zero makes the relative difference meaningless; compare against a scale, such as the quantity’s largest value over the period, as the library’s checks do.
- Two implementations that share code share its mistakes. Generated evaluators would agree with the file they come from even where it is wrong.
- Floating-point numbers are not compared for equality. In binary64, 0.1 + 0.1 + 0.1 is not 0.3: compare within a tolerance (unit roundoff).
Go deeper
Section titled “Go deeper”- Tool SymPy: Solve Equations
How SymPy solves an equation or a system for a symbol, as the site's derivation scripts solve the balance equations. - Technical article Floating-Point Arithmetic: Issues and Limitations
Why 0.1 + 0.1 + 0.1 is not 0.3 in a computer, why each operation can add a rounding error of up to one part in 2**53, and why results are compared within a tolerance, not for equality.
Where does an equation shown on this site come from?
Answer and explanation
Correct answer: B The generator writes the LaTeX and the test vectors from that entry, and CI fails if the committed files differ from what it writes.
Why are the TypeScript evaluators written by hand instead of generated from equations.yaml?
Answer and explanation
Correct answer: A Generated code would agree with its source even where the source is wrong; an independent implementation has to agree with the vectors on its own.
A simulated conversion ratio of 0.495 is compared with the ideal 0.5. What is the relative difference?
Answer and explanation
Correct answer: D The difference, 0.005, over the reference, 0.5, is 0.01: one percent.
The unit roundoff of binary64 is about 1.1e-16. Why is the parity tolerance one part in a billion and not the unit roundoff?
Answer and explanation
Correct answer: B A billionth is millions of times the unit roundoff, so rounding passes in an evaluation that does not magnify it, while a wrong constant or factor is far larger.
Why does the relative difference fail near a zero reference?
Answer and explanation
Correct answer: A Compare against a scale there, such as the quantity's largest value over the period, as the library's checks do.
In Python, 0.1 + 0.1 + 0.1 == 0.3 evaluates to:
Answer and explanation
Correct answer: C 0.1 is not exact in binary, and the rounded sum of three of them, 0.30000000000000004, is the double next to 0.3; compare within a tolerance.
The numbers in the examples, presets and exercises on this site are synthetic: round teaching values, not data from any real design.