https://github.com/gdsfactory/circulax
Circulax is a differentiable circuit simulation framework built on JAX, Optimistix and Diffrax
https://github.com/gdsfactory/circulax
circuit jax photonics-circuits
Last synced: 3 months ago
JSON representation
Circulax is a differentiable circuit simulation framework built on JAX, Optimistix and Diffrax
- Host: GitHub
- URL: https://github.com/gdsfactory/circulax
- Owner: gdsfactory
- License: apache-2.0
- Created: 2026-02-18T10:40:27.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-04-27T12:24:34.000Z (3 months ago)
- Last Synced: 2026-04-27T13:21:24.324Z (3 months ago)
- Topics: circuit, jax, photonics-circuits
- Language: Python
- Homepage: https://gdsfactory.github.io/circulax/
- Size: 29.5 MB
- Stars: 14
- Watchers: 1
- Forks: 3
- Open Issues: 2
-
Metadata Files:
- Readme: ReadMe.md
- License: LICENSE
Awesome Lists containing this project
README
# Circulax

**A differentiable circuit simulator built on JAX.**
Define netlists, run transient / DC / AC / harmonic-balance analysis, and differentiate through the solver for gradient-based optimization and inverse design. Circulax aims to be flexible multi-diciplined circuit simulator offering a similar interface to the linear s-parameter solver [SAX](https://github.com/flaport/sax).
**[Read the Documentation here](https://gdsfactory.github.io/circulax/)**
## Installation
```sh
pip install circulax
```
## Quickstart
Simulate an underdamped LCR circuit in the time domain:

```python
import diffrax, jax, jax.numpy as jnp
from circulax import compile_circuit
from circulax.components.electronic import Capacitor, Inductor, Resistor, VoltageSource
from circulax.solvers import setup_transient
jax.config.update("jax_enable_x64", True)
net_dict = {
"instances": {
"GND": {"component": "ground"},
"V1": {"component": "source_voltage", "settings": {"V": 1.0, "delay": 0.25e-9}},
"R1": {"component": "resistor", "settings": {"R": 10.0}},
"C1": {"component": "capacitor", "settings": {"C": 1e-11}},
"L1": {"component": "inductor", "settings": {"L": 5e-9}},
},
"connections": {
"GND,p1": ("V1,p2", "C1,p2"),
"V1,p1": "R1,p1", "R1,p2": "L1,p1", "L1,p2": "C1,p1",
},
}
models = {
"resistor": Resistor, "capacitor": Capacitor,
"inductor": Inductor, "source_voltage": VoltageSource, "ground": lambda: 0,
}
circuit = compile_circuit(net_dict, models)
y_op = circuit()
sim = setup_transient(groups=circuit.groups, linear_strategy=circuit.solver)
sol = sim(
t0=0.0, t1=3e-9, dt0=3e-12, y0=y_op,
saveat=diffrax.SaveAt(ts=jnp.linspace(0, 3e-9, 500)),
max_steps=100_000,
)
v_cap = circuit.get_port_field(sol.ys, "C1,p1") # capacitor voltage over time
```
## Defining Components
Components are plain Python functions — no boilerplate, no subclassing:
```python
from circulax.components.base_component import component, Signals, States
@component(ports=("p1", "p2"))
def Resistor(signals: Signals, s: States, R: float = 1e3):
i = (signals.p1 - signals.p2) / R
return {"p1": i, "p2": -i}, {} # (currents, charges)
@component(ports=("p1", "p2"))
def Capacitor(signals: Signals, s: States, C: float = 1e-12):
q = C * (signals.p1 - signals.p2)
return {}, {"p1": q, "p2": -q} # dq/dt becomes current automatically
```
Non-linear opto-electronic components are just as simple — the Jacobian is computed automatically via [Automatic Differentiation](https://docs.jax.dev/en/latest/automatic-differentiation.html):
```python
@component(ports=("optical_in", "anode", "cathode"))
def Photodetector(signals: Signals, s: States,
responsivity: float = 0.8, dark_current: float = 1e-9):
optical_power = jnp.abs(signals.optical_in) ** 2 # non-linear
i_photo = responsivity * optical_power + dark_current
i_reflect = -0.01 * signals.optical_in # small back-reflection
return {"optical_in": i_reflect, "anode": i_photo, "cathode": -i_photo}, {}
```
Existing [SAX](https://flaport.github.io/sax/) models plug in directly — reuse your photonic PDK as-is:
```python
import sax
from circulax.s_transforms import sax_component
Straight = sax_component(sax.models.straight) # that's it — ready to simulate
```
## Features
- **Transient** — implicit ODE stepping via [Diffrax](https://docs.kidger.site/diffrax/); handles stiff circuits.
- **DC operating point** — Newton-Raphson root-finding via [Optimistix](https://github.com/patrick-kidger/optimistix).
- **Harmonic Balance** — periodic steady state directly in the frequency domain.
- **AC sweep** — linearise at DC op-point, sweep frequency, return S-parameters.
- **Automatic differentiation** — differentiate through the solver for gradient-based inverse design.
- **Hardware-agnostic** — CPU, GPU, or TPU with no code changes.
- **Mixed-domain** — electronic and photonic circuits in a single netlist.
## Comparison to SPICE
Circulax is a SPICE-like simulator but built with modern tooling so users can easily create their own models in a language they know.
| | SPICE | circulax |
|---|---|---|
| Model definition | Verilog-A / hardcoded C++ | Python functions |
| Derivatives | Hardcoded or compiler-generated | Automatic differentiation |
| Solver | Fixed/heuristic stepping | Adaptive ODE (Diffrax) |
| Hardware | CPU-only | CPU / GPU / TPU |
## Inverse Design via Back-propagation
Because the entire solver is written in JAX, gradients flow end-to-end from a loss function back through the simulation and into component parameters. Use `jax.grad` and standard optimizers to automatically tune circuit designs — the cost is one forward + one backward pass regardless of parameter count.
See the [Inverse Design guide](docs/inverse_design.md) for a comparison with finite differences and worked examples.
---
Copyright © 2026 Chris Daunt — [Apache-2.0](https://github.com/gdsfactory/circulax/blob/HEAD/LICENSE)