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
93 changes: 93 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
pull_request:
branches: ["main", "development"]

env:
OPENVAF_VERSION: openvaf-reloaded-20260616-2-gc592eed6

jobs:
# lint:
# name: "lint / ruff"
Expand Down Expand Up @@ -51,7 +54,80 @@ jobs:
environments: ${{ matrix.env }}
cache: false

# ---- Linux: download pre-built binary ----

- name: Cache openvaf-r (Linux)
if: runner.os == 'Linux'
id: cache-openvaf-linux
uses: actions/cache@v4
with:
path: /usr/local/bin/openvaf-r
key: ${{ env.OPENVAF_VERSION }}-linux_x64

- name: Install openvaf-r (Linux, cache miss)
if: runner.os == 'Linux' && steps.cache-openvaf-linux.outputs.cache-hit != 'true'
run: |
curl -fsSL https://fides.fe.uni-lj.si/openvaf/download/${{ env.OPENVAF_VERSION }}-linux_x64.tar.gz | tar xzf - -C /usr/local/bin/

# ---- macOS: build from source ----
# llvm@18 must be installed even on cache hit: the cached openvaf-r binary
# links dynamically against libLLVM.dylib from Homebrew, which is absent on
# a fresh runner without an explicit brew install.

- name: Install LLVM@18 (macOS)
if: runner.os == 'macOS'
run: brew install llvm@18

- name: Cache openvaf-r (macOS)
if: runner.os == 'macOS'
id: cache-openvaf-mac
uses: actions/cache@v4
with:
path: /usr/local/bin/openvaf-r
key: ${{ env.OPENVAF_VERSION }}-macos_arm64

- name: Build openvaf-r from source (macOS, cache miss)
if: runner.os == 'macOS' && steps.cache-openvaf-mac.outputs.cache-hit != 'true'
run: |
export LLVM_PREFIX="$(brew --prefix llvm@18)"
export LLVM_SYS_181_PREFIX="$LLVM_PREFIX"
export LLVM_CONFIG="$LLVM_PREFIX/bin/llvm-config"
export PATH="$LLVM_PREFIX/bin:$PATH"
git clone --depth=1 https://github.com/arpadbuermen/OpenVAF /tmp/openvaf-src
cd /tmp/openvaf-src
cargo build --release -p openvaf-driver --features llvm18
sudo cp target/release/openvaf-r /usr/local/bin/

# ---- Windows: download pre-built binary ----

- name: Cache openvaf-r (Windows)
if: runner.os == 'Windows'
id: cache-openvaf-win
uses: actions/cache@v4
with:
path: C:\tools\openvaf-r
key: ${{ env.OPENVAF_VERSION }}-win_x64

- name: Install openvaf-r (Windows, cache miss)
if: runner.os == 'Windows' && steps.cache-openvaf-win.outputs.cache-hit != 'true'
shell: pwsh
run: |
Invoke-WebRequest -Uri "https://fides.fe.uni-lj.si/openvaf/download/${{ env.OPENVAF_VERSION }}-win_x64.zip" -OutFile openvaf.zip
Expand-Archive openvaf.zip -DestinationPath C:\tools\openvaf-r\

- name: Add openvaf-r to PATH (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: echo "C:\tools\openvaf-r" >> $env:GITHUB_PATH

# ---- Compile VA → OSDI (all platforms) ----

- name: Compile OSDI test fixtures
run: pixi run -e ${{ matrix.env }} compile_osdi

- name: Run fast tests
env:
JAX_PLATFORMS: cpu
run: pixi run -e ${{ matrix.env }} pytest_run

test_long:
Expand All @@ -71,5 +147,22 @@ jobs:
environments: py313
cache: false

- name: Cache openvaf-r
id: cache-openvaf
uses: actions/cache@v4
with:
path: /usr/local/bin/openvaf-r
key: ${{ env.OPENVAF_VERSION }}-linux_x64

- name: Install openvaf-r (cache miss)
if: steps.cache-openvaf.outputs.cache-hit != 'true'
run: |
curl -fsSL https://fides.fe.uni-lj.si/openvaf/download/${{ env.OPENVAF_VERSION }}-linux_x64.tar.gz | tar xzf - -C /usr/local/bin/

- name: Compile OSDI test fixtures
run: pixi run -e py313 compile_osdi

- name: Run all tests including @pytest.mark.long (notebooks)
env:
JAX_PLATFORMS: cpu
run: pixi run -e py313 pytest_all
2 changes: 1 addition & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
uses: taiki-e/install-action@git-cliff

- name: Generate release notes for this tag
run: git cliff --current --strip header > RELEASE_NOTES.md
run: git cliff --latest --strip header > RELEASE_NOTES.md

- name: Create or update GitHub Release
env:
Expand Down
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Versions follow [Semantic Versioning](https://semver.org/).

### Documentation

- Added annimations to start of examples([`75828a4`](https://github.com/gdsfactory/circulax/commit/75828a4c9aab88d01fdfd16797ee1c6d1fbd8633))
- Added animations to start of examples([`75828a4`](https://github.com/gdsfactory/circulax/commit/75828a4c9aab88d01fdfd16797ee1c6d1fbd8633))
## [0.1.4] - 2026-04-17

### Added
Expand All @@ -40,7 +40,7 @@ Versions follow [Semantic Versioning](https://semver.org/).
- Add osc_node multi-start to setup_harmonic_balance([`5fc15ee`](https://github.com/gdsfactory/circulax/commit/5fc15ee71fc85213564ebdce57eb2b6be3a70e29))
- **demos**: Rewrite photonic demux demo with MZI topology and length parameterisation([`aa2b119`](https://github.com/gdsfactory/circulax/commit/aa2b119f577a6958eb96fce08bb845f4190206b1))
- **demos**: Adds lattice filter demo([`6873441`](https://github.com/gdsfactory/circulax/commit/6873441e6ad55817c4ea65ee1e5fa6cee2ca3e11))
- **demos**: Added hemt_pa_optoimization([`355b954`](https://github.com/gdsfactory/circulax/commit/355b9542a4a43ec9612fb0b2dc4941a8a1fb7dcc))
- **demos**: Added hemt_pa_optimization([`355b954`](https://github.com/gdsfactory/circulax/commit/355b9542a4a43ec9612fb0b2dc4941a8a1fb7dcc))

### Changed

Expand Down Expand Up @@ -136,7 +136,7 @@ Versions follow [Semantic Versioning](https://semver.org/).

### Documentation

- Moded writing components.md([`9e1b187`](https://github.com/gdsfactory/circulax/commit/9e1b18759089733825ef0c505a2f46e2856ef893))
- Modified writing components.md([`9e1b187`](https://github.com/gdsfactory/circulax/commit/9e1b18759089733825ef0c505a2f46e2856ef893))
- Updated readme([`8e58ceb`](https://github.com/gdsfactory/circulax/commit/8e58cebd004b15270d84a102a2ff0cc7ce5a8b2c))
- Removed link to favicon in mkdocs.yaml([`da8b803`](https://github.com/gdsfactory/circulax/commit/da8b803397e1a0b7fd51e50d6e33a6d7f20de22e))
- Cleaned index([`791e10e`](https://github.com/gdsfactory/circulax/commit/791e10e4ee5abd0ad6eb123c2e84097e243c5e87))
80 changes: 80 additions & 0 deletions benchmarks/_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""Centralised path resolution for benchmark scripts.

Every external dependency (VACASK, IHP-PDK, klujax_rs, circulax-va) is
resolved from an environment variable or ``shutil.which``. No hardcoded
home-directory paths — contributors set the env vars once in their shell
profile and all benchmarks Just Work.
"""
from __future__ import annotations

import os
import shutil
from pathlib import Path


def _env_path(var: str, hint: str) -> Path:
val = os.environ.get(var)
if not val:
msg = f"${var} is not set. {hint}"
raise OSError(msg)
p = Path(val)
if not p.exists():
msg = f"${var}={val} does not exist. {hint}"
raise OSError(msg)
return p


def vacask_bin() -> str:
"""Resolve the vacask CLI binary: $VACASK_BIN > PATH lookup."""
env = os.environ.get("VACASK_BIN")
if env:
if not Path(env).exists():
msg = f"$VACASK_BIN={env} does not exist."
raise OSError(msg)
return env
found = shutil.which("vacask")
if found:
return found
msg = "vacask not found. Install it or set $VACASK_BIN to the binary path."
raise OSError(msg)


def vacask_python() -> str:
"""Resolve the VACASK Python library directory for sys.path insertion."""
return str(_env_path(
"VACASK_PYTHON",
"Set it to the VACASK Python dir (e.g. ~/code/vacask/VACASK/python).",
))


def vacask_repo() -> Path:
"""Resolve the VACASK repository root."""
return _env_path(
"VACASK_REPO",
"Set it to the VACASK repo root (e.g. ~/code/vacask/VACASK).",
)


def ihp_pdk_va() -> Path:
"""Resolve the IHP-Open-PDK verilog-a directory."""
return _env_path(
"IHP_PDK_VA",
"Set it to the IHP-Open-PDK verilog-a dir "
"(e.g. ~/code/IHP-Open-PDK/ihp-sg13g2/libs.tech/verilog-a).",
)


def klujax_rs_path() -> Path:
"""Resolve the klujax_rs static build directory."""
return _env_path(
"KLUJAX_RS_PATH",
"Set it to the klujax_rs-static build dir.",
)


def circulax_va_repo() -> Path:
"""Resolve the circulax-va companion repository."""
return _env_path(
"CIRCULAX_VA_REPO",
"Set it to the circulax-va repo root.",
)
12 changes: 6 additions & 6 deletions benchmarks/diode/bench_circulax.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ def _build_circuit(variant: str):
dump = compile_va(str(_DIODE_VA))
defaults = parse_va_defaults_expanded(_DIODE_VA)
dev = lower(dump.modules[0], va_defaults=defaults, class_name="Diode")
tmp = tempfile.mkdtemp()
out = Path(tmp) / "diode_va_bench.py"
out.write_text(emit_source([dev]))
spec = importlib.util.spec_from_file_location("diode_va_bench", out)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
with tempfile.TemporaryDirectory() as tmp:
out = Path(tmp) / "diode_va_bench.py"
out.write_text(emit_source([dev]))
spec = importlib.util.spec_from_file_location("diode_va_bench", out)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
diode_cls = mod.Diode

# Read defaults from the .va so we can pass concrete values. The
Expand Down
12 changes: 6 additions & 6 deletions benchmarks/juncap200/bench_circulax.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ def _build_va_descriptor():
static_params=int_static,
class_name="JUNCAP200",
)
tmpd = Path(tempfile.mkdtemp(prefix="juncap200_va_"))
src = tmpd / "juncap200.py"
src.write_text(emit_source([dev]))
spec = importlib.util.spec_from_file_location("juncap200_va_mod", src)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
with tempfile.TemporaryDirectory(prefix="juncap200_va_") as tmpd:
src = Path(tmpd) / "juncap200.py"
src.write_text(emit_source([dev]))
spec = importlib.util.spec_from_file_location("juncap200_va_mod", src)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.JUNCAP200, defs


Expand Down
17 changes: 13 additions & 4 deletions benchmarks/juncap200/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from __future__ import annotations

import csv
import os
import subprocess
import sys
import time
Expand All @@ -25,11 +24,21 @@
REPO = HERE.parents[1]
sys.path.insert(0, str(HERE))
sys.path.insert(0, str(REPO))
sys.path.insert(0, os.environ.get("VACASK_PYTHON", str(Path.home() / "code/vacask/VACASK/python")))
sys.path.insert(0, str(HERE.parent))
from _paths import vacask_bin as _vacask_bin # noqa: E402

try:
from _paths import vacask_repo as _vacask_repo
sys.path.insert(0, str(_vacask_repo() / "python"))
except OSError:
pass

import bench_circulax as cx # noqa: E402

VACASK_BIN = os.environ.get("VACASK_BIN", str(Path.home() / "opt/vacask/bin/vacask"))
try:
VACASK_BIN = _vacask_bin()
except OSError:
VACASK_BIN = None
VACASK_DIR = HERE / "vacask"
RAW_FILE = VACASK_DIR / "dcsweep.raw"
CSV_PATH = HERE / "results.csv"
Expand All @@ -49,7 +58,7 @@ def _ensure_vacask_osdi() -> None:

def run_vacask() -> dict[float, float] | dict:
"""Run VACASK deck, parse dcsweep.raw, return {V_AK: I_A}."""
if not Path(VACASK_BIN).exists():
if VACASK_BIN is None or not Path(VACASK_BIN).exists():
return {"status": "vacask_not_installed"}
_ensure_vacask_osdi()
t0 = time.perf_counter()
Expand Down
20 changes: 11 additions & 9 deletions benchmarks/mosvar/bench_circulax.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@
jax.config.update("jax_enable_x64", True)

_REPO = Path(__file__).resolve().parents[2]
_IHP_MOSVAR = Path(
"/home/cdaunt/code/gdsfactory/pdks/IHP-Open-PDK/ihp-sg13g2/libs.tech/verilog-a/mosvar"
)
_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE.parent))
from _paths import ihp_pdk_va # noqa: E402

_IHP_MOSVAR = ihp_pdk_va() / "mosvar"
_VA_SOURCE = _IHP_MOSVAR / "mosvar.va"
_OSDI_DIR = _REPO / "circulax" / "components" / "osdi" / "compiled"
_OSDI_PATH = _OSDI_DIR / "mosvar_ihp.osdi"
Expand Down Expand Up @@ -95,12 +97,12 @@ def _build_va_descriptor():
static_params=int_static,
class_name="MOSVAR",
)
tmpd = Path(tempfile.mkdtemp(prefix="mosvar_va_"))
src = tmpd / "mosvar.py"
src.write_text(emit_source([dev]))
spec = importlib.util.spec_from_file_location("mosvar_va_mod", src)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
with tempfile.TemporaryDirectory(prefix="mosvar_va_") as tmpd:
src = Path(tmpd) / "mosvar.py"
src.write_text(emit_source([dev]))
spec = importlib.util.spec_from_file_location("mosvar_va_mod", src)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod.MOSVAR, defs


Expand Down
21 changes: 14 additions & 7 deletions benchmarks/mosvar/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,21 @@
REPO = HERE.parents[1]
sys.path.insert(0, str(HERE))
sys.path.insert(0, str(REPO))
sys.path.insert(0, "/home/cdaunt/code/vacask/VACASK/python")
sys.path.insert(0, str(HERE.parent))
from _paths import ihp_pdk_va as _ihp_pdk_va, vacask_bin as _vacask_bin # noqa: E402

try:
from _paths import vacask_repo as _vacask_repo
sys.path.insert(0, str(_vacask_repo() / "python"))
except OSError:
pass

import bench_circulax as cx # noqa: E402

VACASK_BIN = "/home/cdaunt/opt/vacask/bin/vacask"
try:
VACASK_BIN = _vacask_bin()
except OSError:
VACASK_BIN = None
VACASK_DIR = HERE / "vacask"
RAW_FILE = VACASK_DIR / "dcsweep.raw"
CSV_PATH = HERE / "results.csv"
Expand All @@ -30,18 +40,15 @@ def _ensure_vacask_osdi() -> None:
target = VACASK_DIR / "mosvar.osdi"
if target.exists():
return
src_va = (
"/home/cdaunt/code/gdsfactory/pdks/IHP-Open-PDK/ihp-sg13g2/"
"libs.tech/verilog-a/mosvar/mosvar.va"
)
src_va = str(_ihp_pdk_va() / "mosvar" / "mosvar.va")
subprocess.run(
["openvaf-r", src_va, "-o", str(target)],
cwd=Path(src_va).parent, check=True, capture_output=True, text=True,
)


def run_vacask() -> dict[float, float] | dict:
if not Path(VACASK_BIN).exists():
if VACASK_BIN is None or not Path(VACASK_BIN).exists():
return {"status": "vacask_not_installed"}
_ensure_vacask_osdi()
t0 = time.perf_counter()
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/mul/bench_circulax.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Circulax version of the VACASK mul benchmark.

4-stage diode voltage multiplier driven by 50 V / 100 kHz sine. Same
circuit as /home/cdaunt/code/vacask/VACASK/benchmark/mul/*/runme.sim.
circuit as $VACASK_REPO/benchmark/mul/*/runme.sim.

Diode: D1N4007 (IS=76.9p, N=1.45, RS=42mΩ, CJO=26.5p, M=0.333). We
build a local @component for it that includes the junction capacitance,
Expand Down
Loading
Loading