Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ archive/*
.vscode/*
.env
app/*
circulus_workspace.code-workspace
circulax_workspace.code-workspace
docs/examples/
docs/references/
*code-workspace
site/*
circulus/_version.py
circulax/_version.py
26 changes: 13 additions & 13 deletions ReadMe.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# **Circulus**
# **circulax**

<img src="docs/images/logo_white.svg" alt="logo" width="500">

## **A Differentiable, Functional Circuit Simulator based on JAX**
Circulus is a differentiable circuit simulation framework built on [JAX](https://docs.jax.dev/en/latest/notebooks/thinking_in_jax.html), [Optimistix](https://github.com/patrick-kidger/optimistix) and [Diffrax](https://docs.kidger.site/diffrax/). It treats circuit netlists as systems of Ordinary Differential Equations (ODEs), leveraging Diffrax's suite of numerical solvers for transient analysis.
circulax is a differentiable circuit simulation framework built on [JAX](https://docs.jax.dev/en/latest/notebooks/thinking_in_jax.html), [Optimistix](https://github.com/patrick-kidger/optimistix) and [Diffrax](https://docs.kidger.site/diffrax/). It treats circuit netlists as systems of Ordinary Differential Equations (ODEs), leveraging Diffrax's suite of numerical solvers for transient analysis.

By using JAX as its backend, Circulus provides:
By using JAX as its backend, circulax provides:

**Native Differentiation**: Full support for forward and reverse-mode automatic differentiation through the solver, enabling gradient-based parameter optimization and inverse design.

Expand All @@ -15,9 +15,9 @@ By using JAX as its backend, Circulus provides:

**Modular Architecture**: A functional approach to simulation that integrates directly into machine learning and scientific computing workflows.

Standard tools (SPICE, Spectre, Ngspice) rely on established matrix stamping methods and CPU-bound sparse solvers. Circulus leverages the JAX ecosystem to offer specific advantages in optimization and hardware utilization:
Standard tools (SPICE, Spectre, Ngspice) rely on established matrix stamping methods and CPU-bound sparse solvers. circulax leverages the JAX ecosystem to offer specific advantages in optimization and hardware utilization:

| Feature | Legacy(SPICE) | Circulus |
| Feature | Legacy(SPICE) | circulax |
| ----------- | ----------- | ----------- |
| Model Definition | Hardcoded C++ / Verilog-A | Simple python functions |
| Derivatives | Hardcoded (C) or Compiler-Generated (Verilog-A) | Automatic Differentiation (AD)|
Expand All @@ -27,13 +27,13 @@ Standard tools (SPICE, Spectre, Ngspice) rely on established matrix stamping met


## **Simulator setup**
Circulus strictly separates Physics, Topology, and Analysis, enabling the interchange of solvers or models without netlist modification.
circulax strictly separates Physics, Topology, and Analysis, enabling the interchange of solvers or models without netlist modification.

### **Physics Layer**
Components are defined as simple Python functions wrapped with the ```@component``` decorator. This functional interface abstracts away the boilerplate, allowing users to define physics using simple voltage/current/field/flux relationships.

```python
from circulus.base_component import component, Signals, States
from circulax.base_component import component, Signals, States
import jax.numpy as jnp

@component(ports=("p1", "p2"))
Expand Down Expand Up @@ -84,7 +84,7 @@ The solver is a generic DAE engine linking Diffrax (Time-stepping) and Optimisti
## **Installation**

```sh
pip install circulus
pip install circulax
```

## **Simulation Example**
Expand All @@ -93,10 +93,10 @@ pip install circulus
import jax
import diffrax

from circulus.components import Resistor, Capacitor, Inductor, VoltageSource
from circulus.compiler import compile_netlist
from circulus.solvers.transient import VectorizedTransientSolver
from circulus.solvers.strategies import DenseSolver
from circulax.components import Resistor, Capacitor, Inductor, VoltageSource
from circulax.compiler import compile_netlist
from circulax.solvers.transient import VectorizedTransientSolver
from circulax.solvers.strategies import DenseSolver
import jax.numpy as jnp

import matplotlib.pyplot as plt
Expand Down Expand Up @@ -195,4 +195,4 @@ plt.show()

## **License**

Copyright © 2026, Chris Daunt, [Apache-2.0 License](https://github.com/cdaunt/circulus/blob/master/LICENSE)
Copyright © 2026, Chris Daunt, [Apache-2.0 License](https://github.com/cdaunt/circulax/blob/master/LICENSE)
5 changes: 5 additions & 0 deletions circulax/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""circulax: A differentiable, JAX based circuit simulator."""
from circulax.compiler import compile_netlist
from circulax.netlist import circulaxNetlist as Netlist
from circulax.netlist import build_net_map, netlist
from circulax.solvers import analyze_circuit, setup_transient
6 changes: 3 additions & 3 deletions circulus/compiler.py → circulax/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import jax
import jax.numpy as jnp

from circulus.components.base_component import PhysicsReturn, Signals
from circulus.netlist import build_net_map
from circulax.components.base_component import PhysicsReturn, Signals
from circulax.netlist import build_net_map


def ensure_time_signature(model_func: callable) -> callable:
Expand Down Expand Up @@ -194,7 +194,7 @@ def compile_netlist(netlist: dict, models_map: dict) -> tuple[dict, int, dict]:
constructor). A ``"GND"`` instance with ``component="ground"`` is
recognised and skipped.
models_map: Mapping from model name strings to
:class:`~circulus.components.base_component.CircuitComponent`
:class:`~circulax.components.base_component.CircuitComponent`
subclasses, e.g. ``{"Resistor": Resistor, "Capacitor": Capacitor}``.

Returns:
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import jax.nn as jnn
import jax.numpy as jnp

from circulus.components.base_component import (
from circulax.components.base_component import (
PhysicsReturn,
Signals,
States,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import jax.nn as jnn
import jax.numpy as jnp

from circulus.components.base_component import PhysicsReturn, Signals, States, component, source
from circulus.s_transforms import s_to_y
from circulax.components.base_component import PhysicsReturn, Signals, States, component, source
from circulax.s_transforms import s_to_y

# ===========================================================================
# Passive Optical Components (S-Matrix based)
Expand Down
12 changes: 6 additions & 6 deletions circulus/netlist.py → circulax/netlist.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Circulus netlists.
"""circulax netlists.

SAX netlists will be used as much as possible in circulus;
SAX netlists will be used as much as possible in circulax;
however, connections for node based simulators need to be handled slightly differently.
"""

Expand All @@ -26,7 +26,7 @@
InstancePort, InstancePort | tuple[InstancePort, ...]
]

CirculusNetlist = Annotated[
circulaxNetlist = Annotated[
TypedDict(
"Netlist",
{
Expand All @@ -52,11 +52,11 @@
settings: Global circuit settings.
"""

Netlist = CirculusNetlist
Netlist = circulaxNetlist

# Monkeypatch sax.Netlist to be CirculusNetlist so that all functions using sax.Netlist
# Monkeypatch sax.Netlist to be circulaxNetlist so that all functions using sax.Netlist
# May need to make this explicit in the future
sax_netlist.Netlist = CirculusNetlist # type: ignore[assignment]
sax_netlist.Netlist = circulaxNetlist # type: ignore[assignment]


def build_net_map(netlist: dict) -> tuple[dict[str, int], int]:
Expand Down
14 changes: 7 additions & 7 deletions circulus/s_transforms.py → circulax/s_transforms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Utilities for converting between S-parameters.

Utilities for converting between S-parameter and admittance representations,
and for wrapping SAX model functions as Circulus components.
and for wrapping SAX model functions as circulax components.
"""

import inspect
Expand All @@ -10,7 +10,7 @@
import jax.numpy as jnp
from sax import get_ports, sdense

from circulus.components.base_component import Signals, States, component
from circulax.components.base_component import Signals, States, component


@jax.jit
Expand All @@ -35,11 +35,11 @@ def s_to_y(S: jax.Array, z0: float = 1.0) -> jax.Array:


def sax_component(fn: callable) -> callable:
"""Decorator to convert a SAX model function into a Circulus component.
"""Decorator to convert a SAX model function into a circulax component.

Inspects ``fn`` at decoration time to discover its port interface via a
dry run, then wraps its S-matrix output in an admittance-based physics
function compatible with the Circulus nodal solver.
function compatible with the circulax nodal solver.

The conversion proceeds in three stages:

Expand All @@ -51,8 +51,8 @@ def sax_component(fn: callable) -> callable:
it to an admittance matrix via :func:`s_to_y`, and returns
``I = Y @ V`` as a port current dict.
3. **Component registration** — the wrapper is passed to
:func:`~circulus.components.base_component.component` with the
discovered ports, producing a :class:`~circulus.components.base_component.CircuitComponent`
:func:`~circulax.components.base_component.component` with the
discovered ports, producing a :class:`~circulax.components.base_component.CircuitComponent`
subclass.

Args:
Expand All @@ -62,7 +62,7 @@ def sax_component(fn: callable) -> callable:
``1.0`` during the dry run.

Returns:
A :class:`~circulus.components.base_component.CircuitComponent`
A :class:`~circulax.components.base_component.CircuitComponent`
subclass named after ``fn``.

Raises:
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- **Full assembly** (:func:`assemble_system_real`, :func:`assemble_system_complex`)
— evaluates both the residual and the forward-mode Jacobian via
``jax.jacfwd``. Used once per timestep to assemble and factor the frozen
Jacobian in :class:`~circulus.solver.FactorizedTransientSolver`.
Jacobian in :class:`~circulax.solver.FactorizedTransientSolver`.

- **Residual only** (:func:`assemble_residual_only_real`,
:func:`assemble_residual_only_complex`) — evaluates only the primal
Expand Down
2 changes: 1 addition & 1 deletion circulus/solvers/linear.py → circulax/solvers/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import numpy as np
import optimistix as optx

from circulus.solvers.assembly import assemble_system_complex, assemble_system_real
from circulax.solvers.assembly import assemble_system_complex, assemble_system_real

# Check if split solver available — KLUHandleManager was added in a later version of klujax.
split_solver_available = True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
from jax.typing import ArrayLike
#from klujax import free_numeric

from circulus.solvers.assembly import (
from circulax.solvers.assembly import (
assemble_residual_only_complex,
assemble_residual_only_real,
assemble_system_complex,
assemble_system_real,
)
from circulus.solvers.linear import CircuitLinearSolver
from circulax.solvers.linear import CircuitLinearSolver


def _compute_history(component_groups, y_c, t, num_vars) -> ArrayLike:
Expand Down
4 changes: 2 additions & 2 deletions circulus/utils.py → circulax/utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Circulus utilities."""
"""circulax utilities."""

from typing import TYPE_CHECKING

import equinox as eqx
import jax.numpy as jnp

if TYPE_CHECKING:
from circulus.compiler import ComponentGroup
from circulax.compiler import ComponentGroup


def update_params_dict(
Expand Down
5 changes: 0 additions & 5 deletions circulus/__init__.py

This file was deleted.

4 changes: 2 additions & 2 deletions docs/gen_doc_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

this_dir = Path(__file__).parent

src_root = (this_dir / "../circulus").resolve()
src_root = (this_dir / "../circulax").resolve()

for path in src_root.rglob("*.py"):
rel_path = path.relative_to(src_root)
Expand All @@ -20,7 +20,7 @@
else:
doc_path = rel_path.with_suffix(".md")

full_parts = ("circulus",) + parts
full_parts = ("circulax",) + parts
identifier = ".".join(full_parts)

output_filename = Path("references") / doc_path
Expand Down
110 changes: 0 additions & 110 deletions docs/images/circulus_logo.svg

This file was deleted.

Loading