bosdi 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. bosdi-0.1.1/Cargo.lock +91 -0
  2. bosdi-0.1.1/Cargo.toml +15 -0
  3. bosdi-0.1.1/LICENSE +21 -0
  4. bosdi-0.1.1/MANIFEST.in +4 -0
  5. bosdi-0.1.1/PKG-INFO +167 -0
  6. bosdi-0.1.1/README.md +142 -0
  7. bosdi-0.1.1/_nanobind_src/buffer.h +166 -0
  8. bosdi-0.1.1/_nanobind_src/common.cpp +1295 -0
  9. bosdi-0.1.1/_nanobind_src/error.cpp +314 -0
  10. bosdi-0.1.1/_nanobind_src/hash.h +33 -0
  11. bosdi-0.1.1/_nanobind_src/implicit.cpp +75 -0
  12. bosdi-0.1.1/_nanobind_src/nb_abi.h +102 -0
  13. bosdi-0.1.1/_nanobind_src/nb_combined.cpp +87 -0
  14. bosdi-0.1.1/_nanobind_src/nb_enum.cpp +304 -0
  15. bosdi-0.1.1/_nanobind_src/nb_ft.cpp +56 -0
  16. bosdi-0.1.1/_nanobind_src/nb_ft.h +39 -0
  17. bosdi-0.1.1/_nanobind_src/nb_func.cpp +1586 -0
  18. bosdi-0.1.1/_nanobind_src/nb_internals.cpp +590 -0
  19. bosdi-0.1.1/_nanobind_src/nb_internals.h +577 -0
  20. bosdi-0.1.1/_nanobind_src/nb_ndarray.cpp +1051 -0
  21. bosdi-0.1.1/_nanobind_src/nb_static_property.cpp +76 -0
  22. bosdi-0.1.1/_nanobind_src/nb_type.cpp +2271 -0
  23. bosdi-0.1.1/_nanobind_src/trampoline.cpp +188 -0
  24. bosdi-0.1.1/pyproject.toml +130 -0
  25. bosdi-0.1.1/setup.cfg +4 -0
  26. bosdi-0.1.1/setup.py +130 -0
  27. bosdi-0.1.1/src/bosdi/__init__.py +22 -0
  28. bosdi-0.1.1/src/bosdi/va/__init__.py +85 -0
  29. bosdi-0.1.1/src/bosdi/va/__main__.py +107 -0
  30. bosdi-0.1.1/src/bosdi/va/binding.py +675 -0
  31. bosdi-0.1.1/src/bosdi/va/dump_parser.py +991 -0
  32. bosdi-0.1.1/src/bosdi/va/emitter.py +1157 -0
  33. bosdi-0.1.1/src/bosdi/va/interpret.py +445 -0
  34. bosdi-0.1.1/src/bosdi/va/ir_client.py +553 -0
  35. bosdi-0.1.1/src/bosdi/va/lowering.py +3283 -0
  36. bosdi-0.1.1/src/bosdi/va/mir.py +319 -0
  37. bosdi-0.1.1/src/bosdi/va/sccp.py +848 -0
  38. bosdi-0.1.1/src/bosdi/va/uniform_params.py +85 -0
  39. bosdi-0.1.1/src/bosdi/va/va_defaults.py +170 -0
  40. bosdi-0.1.1/src/bosdi.egg-info/PKG-INFO +167 -0
  41. bosdi-0.1.1/src/bosdi.egg-info/SOURCES.txt +55 -0
  42. bosdi-0.1.1/src/bosdi.egg-info/dependency_links.txt +1 -0
  43. bosdi-0.1.1/src/bosdi.egg-info/not-zip-safe +1 -0
  44. bosdi-0.1.1/src/bosdi.egg-info/requires.txt +3 -0
  45. bosdi-0.1.1/src/bosdi.egg-info/top_level.txt +5 -0
  46. bosdi-0.1.1/src/lib.rs +1824 -0
  47. bosdi-0.1.1/src/osdi_debug.py +347 -0
  48. bosdi-0.1.1/src/osdi_jax.py +468 -0
  49. bosdi-0.1.1/src/osdi_loader.py +117 -0
  50. bosdi-0.1.1/src/osdi_shim.cpp +455 -0
  51. bosdi-0.1.1/tests/test_bsim4_model_card.py +139 -0
  52. bosdi-0.1.1/tests/test_compiled_models.py +425 -0
  53. bosdi-0.1.1/tests/test_diode_behaviour.py +210 -0
  54. bosdi-0.1.1/tests/test_diode_structure.py +132 -0
  55. bosdi-0.1.1/tests/test_ir_client_json.py +207 -0
  56. bosdi-0.1.1/tests/test_osdi.py +862 -0
  57. bosdi-0.1.1/tests/test_osdi_debug.py +315 -0
bosdi-0.1.1/Cargo.lock ADDED
@@ -0,0 +1,91 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "bosdi"
7
+ version = "0.1.0"
8
+ dependencies = [
9
+ "lazy_static",
10
+ "libloading",
11
+ "rayon",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "cfg-if"
16
+ version = "1.0.4"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
19
+
20
+ [[package]]
21
+ name = "crossbeam-deque"
22
+ version = "0.8.6"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
25
+ dependencies = [
26
+ "crossbeam-epoch",
27
+ "crossbeam-utils",
28
+ ]
29
+
30
+ [[package]]
31
+ name = "crossbeam-epoch"
32
+ version = "0.9.18"
33
+ source = "registry+https://github.com/rust-lang/crates.io-index"
34
+ checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
35
+ dependencies = [
36
+ "crossbeam-utils",
37
+ ]
38
+
39
+ [[package]]
40
+ name = "crossbeam-utils"
41
+ version = "0.8.21"
42
+ source = "registry+https://github.com/rust-lang/crates.io-index"
43
+ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
44
+
45
+ [[package]]
46
+ name = "either"
47
+ version = "1.15.0"
48
+ source = "registry+https://github.com/rust-lang/crates.io-index"
49
+ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
50
+
51
+ [[package]]
52
+ name = "lazy_static"
53
+ version = "1.5.0"
54
+ source = "registry+https://github.com/rust-lang/crates.io-index"
55
+ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
56
+
57
+ [[package]]
58
+ name = "libloading"
59
+ version = "0.8.9"
60
+ source = "registry+https://github.com/rust-lang/crates.io-index"
61
+ checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
62
+ dependencies = [
63
+ "cfg-if",
64
+ "windows-link",
65
+ ]
66
+
67
+ [[package]]
68
+ name = "rayon"
69
+ version = "1.11.0"
70
+ source = "registry+https://github.com/rust-lang/crates.io-index"
71
+ checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
72
+ dependencies = [
73
+ "either",
74
+ "rayon-core",
75
+ ]
76
+
77
+ [[package]]
78
+ name = "rayon-core"
79
+ version = "1.13.0"
80
+ source = "registry+https://github.com/rust-lang/crates.io-index"
81
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
82
+ dependencies = [
83
+ "crossbeam-deque",
84
+ "crossbeam-utils",
85
+ ]
86
+
87
+ [[package]]
88
+ name = "windows-link"
89
+ version = "0.2.1"
90
+ source = "registry+https://github.com/rust-lang/crates.io-index"
91
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
bosdi-0.1.1/Cargo.toml ADDED
@@ -0,0 +1,15 @@
1
+ [package]
2
+ name = "bosdi"
3
+ version = "0.1.0" # Keep in sync with pyproject.toml [project].version
4
+ edition = "2021"
5
+ description = "Rust core for BOSDI: batched OSDI device model evaluation via libloading + Rayon"
6
+ license = "MIT"
7
+
8
+ [lib]
9
+ crate-type = ["staticlib"]
10
+ name = "bosdi"
11
+
12
+ [dependencies]
13
+ lazy_static = "1.4"
14
+ libloading = "0.8"
15
+ rayon = "1.8"
bosdi-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Chris Daunt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include Cargo.toml
2
+ include Cargo.lock
3
+ recursive-include src *.rs
4
+ recursive-include _nanobind_src *
bosdi-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: bosdi
3
+ Version: 0.1.1
4
+ Summary: Make OSDI device models (Verilog-A compiled to .osdi) differentiable via JAX
5
+ Author-email: Chris Daunt <chris@gdsfactory.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/gdsfactory/bosdi
8
+ Project-URL: Repository, https://github.com/gdsfactory/bosdi
9
+ Keywords: JAX,OSDI,Verilog-A,automatic differentiation,circuit simulation
10
+ Classifier: Operating System :: MacOS
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Rust
17
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: equinox>=0.11
22
+ Requires-Dist: jax<0.8,>=0.7.0
23
+ Requires-Dist: jaxlib<0.8,>=0.7.0
24
+ Dynamic: license-file
25
+
26
+ # bosdi — Batched OSDI
27
+
28
+ ![CI](https://github.com/gdsfactory/bosdi/actions/workflows/test.yml/badge.svg)
29
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
30
+ ![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)
31
+ ![Platform: Linux | macOS](https://img.shields.io/badge/platform-linux%20%7C%20macOS-lightgrey)
32
+ ![Status: Experimental](https://img.shields.io/badge/status-experimental-orange)
33
+
34
+ > **Experimental** — bosdi is under active development. The OSDI binary evaluation path is stable and well-tested, but
35
+ > the Verilog-A to JAX lowering compiler (`bosdi.va`) is in **alpha** and its API may change without notice. The VA
36
+ > lowering depends on a [custom fork of OpenVAF](https://github.com/cdaunt/OpenVAF) that exposes the compiler's
37
+ > intermediate representation; this fork is not yet merged upstream.
38
+
39
+ Evaluate [OSDI](https://github.com/OpenVAF/OpenVAF) device models (Verilog-A compiled to `.osdi` binaries) in batched
40
+ parallel via JAX.
41
+
42
+ ## Two evaluation paths
43
+
44
+ bosdi provides two ways to evaluate Verilog-A compact models inside JAX:
45
+
46
+ ### OSDI binary path (stable)
47
+
48
+ Loads a pre-compiled `.osdi` binary and evaluates N device instances in parallel via Rayon inside a JAX XLA custom call.
49
+ The OSDI ABI provides analytical Jacobians with respect to **node voltages only** (conductances `dI/dV`, capacitances
50
+ `dQ/dV`). A `@custom_jvp` rule makes `jax.grad()` work through node voltages — but not through model parameters or
51
+ state.
52
+
53
+ ```python
54
+ from osdi_loader import load_osdi_model
55
+ from osdi_jax import osdi_eval
56
+
57
+ model = load_osdi_model("path/to/device.osdi")
58
+ N = 1024
59
+ voltages = jnp.zeros((N, model.num_nodes), dtype=jnp.float64)
60
+ params = jnp.full((N, model.num_params), jnp.nan, dtype=jnp.float64)
61
+ old_state = jnp.zeros((N, model.num_states), dtype=jnp.float64)
62
+
63
+ cur, cond, chg, cap, new_state = osdi_eval(model.id, voltages, params, old_state)
64
+
65
+ # jax.grad works through node voltages
66
+ grad_fn = jax.grad(lambda v: osdi_eval(model.id, v, params, old_state)[0].sum())
67
+ ```
68
+
69
+ ### VA to JAX lowering (alpha)
70
+
71
+ Compiles Verilog-A source directly into pure JAX/Python, producing a function that is **fully differentiable** through
72
+ all inputs — voltages, parameters, and temperature. This enables parameter optimization, sensitivity analysis, and
73
+ end-to-end gradient-based design flows that the OSDI path cannot support.
74
+
75
+ Requires [openvaf-r](https://github.com/cdaunt/OpenVAF) (a custom OpenVAF fork).
76
+
77
+ ```bash
78
+ python -m bosdi.va device.va
79
+ ```
80
+
81
+ ### When to use which
82
+
83
+ | | OSDI binary | VA to JAX |
84
+ | ------------------------- | --------------------------------------------- | --------------------------------------------------------------------------- |
85
+ | **Use case** | Circuit simulation (Newton solve) | Parameter fitting, sensitivity analysis, inverse design |
86
+ | **Differentiable w.r.t.** | Node voltages only | Voltages, parameters, and temperature |
87
+ | **Performance** | Fast — Rayon-parallel C/Rust, batched XLA FFI | Pure Python/JAX — slower per-eval, but composable with `jax.jit`/`jax.vmap` |
88
+ | **Maturity** | Stable | Alpha |
89
+ | **Dependencies** | None beyond bosdi | [openvaf-r](https://github.com/cdaunt/OpenVAF) fork |
90
+
91
+ The OSDI path treats the compiled model as a black box and extracts only what the ABI exposes: currents, charges, and
92
+ their Jacobians w.r.t. node voltages. This is exactly what a Newton solver needs, but the parameter axis is opaque to
93
+ JAX — you cannot backpropagate through it.
94
+
95
+ The VA to JAX path exists to remove that limitation. By lowering the Verilog-A source into native JAX operations, every
96
+ computation becomes visible to JAX's autodiff, making the model fully differentiable. This is what enables
97
+ gradient-based parameter extraction, design-space exploration, and end-to-end optimization of circuits where device
98
+ parameters are the degrees of freedom.
99
+
100
+ ## Architecture
101
+
102
+ ```
103
+ OSDI path:
104
+ Python: osdi_eval() → JAX XLA custom call
105
+ → C++ (nanobind/XLA FFI): unpack buffers
106
+ → Rust (Rayon): evaluate N devices in parallel
107
+ → OSDI binary: currents, conductances, charges, capacitances
108
+
109
+ VA path:
110
+ Verilog-A source → openvaf-r (MIR dump)
111
+ → bosdi.va lowering + SCCP optimization
112
+ → Pure JAX/Python function (fully differentiable)
113
+ ```
114
+
115
+ ## Installation
116
+
117
+ ### Using Pixi (recommended)
118
+
119
+ ```bash
120
+ git clone https://github.com/gdsfactory/bosdi && cd bosdi
121
+ pixi run build
122
+ ```
123
+
124
+ ### Using pip
125
+
126
+ ```bash
127
+ pip install bosdi
128
+ ```
129
+
130
+ ## Build & test
131
+
132
+ ```bash
133
+ pixi run build # compile Rust static lib + C++ extension
134
+ pixi run test # run pytest suite
135
+
136
+ # single test
137
+ pixi run pytest tests/test_osdi.py::test_resistor_dc_evaluation -v
138
+ ```
139
+
140
+ ## OSDI outputs
141
+
142
+ The OSDI path returns per-device arrays shaped by `model.num_nodes` (terminals + internal nodes + branch-current
143
+ auxiliaries):
144
+
145
+ | Output | Shape | Description |
146
+ | ------ | ----------------- | -------------------------------------------- |
147
+ | `cur` | `[N, num_nodes]` | Resistive current residual at each unknown |
148
+ | `cond` | `[N, num_nodes²]` | `G = ∂cur/∂V` Jacobian (flattened row-major) |
149
+ | `chg` | `[N, num_nodes]` | Charge residual at each unknown |
150
+ | `cap` | `[N, num_nodes²]` | `C = ∂chg/∂V` Jacobian (flattened row-major) |
151
+
152
+ Pass `jnp.nan` for any parameter to use its Verilog-A default. Parameters can be addressed by name via
153
+ `model.param_names`. See `tests/test_bsim4_model_card.py` for a full example.
154
+
155
+ ## Further reading
156
+
157
+ - [OSDI technical reference](docs/osdi-technical-reference.md) — parameter handling, model introspection, output layout,
158
+ host-simulator integration (companion method vs MNA/DAE), and debug utilities
159
+
160
+ ## Limitations
161
+
162
+ - **Platform:** Linux and macOS; Python 3.11+; OSDI 0.4 ABI only. `.osdi` binaries are platform-specific — compile from
163
+ `.va` sources via [openvaf-r](https://github.com/cdaunt/OpenVAF) on each target
164
+ - **OSDI differentiability:** `jax.grad()` works through node voltages only, not model parameters — use the VA path for
165
+ parameter gradients
166
+ - **Stateful models** (`num_states > 0`): evaluation is skipped and outputs are zeroed
167
+ - **VA lowering (alpha):** user-defined `analog function` calls and noise contributions are not yet supported
bosdi-0.1.1/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # bosdi — Batched OSDI
2
+
3
+ ![CI](https://github.com/gdsfactory/bosdi/actions/workflows/test.yml/badge.svg)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
+ ![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)
6
+ ![Platform: Linux | macOS](https://img.shields.io/badge/platform-linux%20%7C%20macOS-lightgrey)
7
+ ![Status: Experimental](https://img.shields.io/badge/status-experimental-orange)
8
+
9
+ > **Experimental** — bosdi is under active development. The OSDI binary evaluation path is stable and well-tested, but
10
+ > the Verilog-A to JAX lowering compiler (`bosdi.va`) is in **alpha** and its API may change without notice. The VA
11
+ > lowering depends on a [custom fork of OpenVAF](https://github.com/cdaunt/OpenVAF) that exposes the compiler's
12
+ > intermediate representation; this fork is not yet merged upstream.
13
+
14
+ Evaluate [OSDI](https://github.com/OpenVAF/OpenVAF) device models (Verilog-A compiled to `.osdi` binaries) in batched
15
+ parallel via JAX.
16
+
17
+ ## Two evaluation paths
18
+
19
+ bosdi provides two ways to evaluate Verilog-A compact models inside JAX:
20
+
21
+ ### OSDI binary path (stable)
22
+
23
+ Loads a pre-compiled `.osdi` binary and evaluates N device instances in parallel via Rayon inside a JAX XLA custom call.
24
+ The OSDI ABI provides analytical Jacobians with respect to **node voltages only** (conductances `dI/dV`, capacitances
25
+ `dQ/dV`). A `@custom_jvp` rule makes `jax.grad()` work through node voltages — but not through model parameters or
26
+ state.
27
+
28
+ ```python
29
+ from osdi_loader import load_osdi_model
30
+ from osdi_jax import osdi_eval
31
+
32
+ model = load_osdi_model("path/to/device.osdi")
33
+ N = 1024
34
+ voltages = jnp.zeros((N, model.num_nodes), dtype=jnp.float64)
35
+ params = jnp.full((N, model.num_params), jnp.nan, dtype=jnp.float64)
36
+ old_state = jnp.zeros((N, model.num_states), dtype=jnp.float64)
37
+
38
+ cur, cond, chg, cap, new_state = osdi_eval(model.id, voltages, params, old_state)
39
+
40
+ # jax.grad works through node voltages
41
+ grad_fn = jax.grad(lambda v: osdi_eval(model.id, v, params, old_state)[0].sum())
42
+ ```
43
+
44
+ ### VA to JAX lowering (alpha)
45
+
46
+ Compiles Verilog-A source directly into pure JAX/Python, producing a function that is **fully differentiable** through
47
+ all inputs — voltages, parameters, and temperature. This enables parameter optimization, sensitivity analysis, and
48
+ end-to-end gradient-based design flows that the OSDI path cannot support.
49
+
50
+ Requires [openvaf-r](https://github.com/cdaunt/OpenVAF) (a custom OpenVAF fork).
51
+
52
+ ```bash
53
+ python -m bosdi.va device.va
54
+ ```
55
+
56
+ ### When to use which
57
+
58
+ | | OSDI binary | VA to JAX |
59
+ | ------------------------- | --------------------------------------------- | --------------------------------------------------------------------------- |
60
+ | **Use case** | Circuit simulation (Newton solve) | Parameter fitting, sensitivity analysis, inverse design |
61
+ | **Differentiable w.r.t.** | Node voltages only | Voltages, parameters, and temperature |
62
+ | **Performance** | Fast — Rayon-parallel C/Rust, batched XLA FFI | Pure Python/JAX — slower per-eval, but composable with `jax.jit`/`jax.vmap` |
63
+ | **Maturity** | Stable | Alpha |
64
+ | **Dependencies** | None beyond bosdi | [openvaf-r](https://github.com/cdaunt/OpenVAF) fork |
65
+
66
+ The OSDI path treats the compiled model as a black box and extracts only what the ABI exposes: currents, charges, and
67
+ their Jacobians w.r.t. node voltages. This is exactly what a Newton solver needs, but the parameter axis is opaque to
68
+ JAX — you cannot backpropagate through it.
69
+
70
+ The VA to JAX path exists to remove that limitation. By lowering the Verilog-A source into native JAX operations, every
71
+ computation becomes visible to JAX's autodiff, making the model fully differentiable. This is what enables
72
+ gradient-based parameter extraction, design-space exploration, and end-to-end optimization of circuits where device
73
+ parameters are the degrees of freedom.
74
+
75
+ ## Architecture
76
+
77
+ ```
78
+ OSDI path:
79
+ Python: osdi_eval() → JAX XLA custom call
80
+ → C++ (nanobind/XLA FFI): unpack buffers
81
+ → Rust (Rayon): evaluate N devices in parallel
82
+ → OSDI binary: currents, conductances, charges, capacitances
83
+
84
+ VA path:
85
+ Verilog-A source → openvaf-r (MIR dump)
86
+ → bosdi.va lowering + SCCP optimization
87
+ → Pure JAX/Python function (fully differentiable)
88
+ ```
89
+
90
+ ## Installation
91
+
92
+ ### Using Pixi (recommended)
93
+
94
+ ```bash
95
+ git clone https://github.com/gdsfactory/bosdi && cd bosdi
96
+ pixi run build
97
+ ```
98
+
99
+ ### Using pip
100
+
101
+ ```bash
102
+ pip install bosdi
103
+ ```
104
+
105
+ ## Build & test
106
+
107
+ ```bash
108
+ pixi run build # compile Rust static lib + C++ extension
109
+ pixi run test # run pytest suite
110
+
111
+ # single test
112
+ pixi run pytest tests/test_osdi.py::test_resistor_dc_evaluation -v
113
+ ```
114
+
115
+ ## OSDI outputs
116
+
117
+ The OSDI path returns per-device arrays shaped by `model.num_nodes` (terminals + internal nodes + branch-current
118
+ auxiliaries):
119
+
120
+ | Output | Shape | Description |
121
+ | ------ | ----------------- | -------------------------------------------- |
122
+ | `cur` | `[N, num_nodes]` | Resistive current residual at each unknown |
123
+ | `cond` | `[N, num_nodes²]` | `G = ∂cur/∂V` Jacobian (flattened row-major) |
124
+ | `chg` | `[N, num_nodes]` | Charge residual at each unknown |
125
+ | `cap` | `[N, num_nodes²]` | `C = ∂chg/∂V` Jacobian (flattened row-major) |
126
+
127
+ Pass `jnp.nan` for any parameter to use its Verilog-A default. Parameters can be addressed by name via
128
+ `model.param_names`. See `tests/test_bsim4_model_card.py` for a full example.
129
+
130
+ ## Further reading
131
+
132
+ - [OSDI technical reference](docs/osdi-technical-reference.md) — parameter handling, model introspection, output layout,
133
+ host-simulator integration (companion method vs MNA/DAE), and debug utilities
134
+
135
+ ## Limitations
136
+
137
+ - **Platform:** Linux and macOS; Python 3.11+; OSDI 0.4 ABI only. `.osdi` binaries are platform-specific — compile from
138
+ `.va` sources via [openvaf-r](https://github.com/cdaunt/OpenVAF) on each target
139
+ - **OSDI differentiability:** `jax.grad()` works through node voltages only, not model parameters — use the VA path for
140
+ parameter gradients
141
+ - **Stateful models** (`num_states > 0`): evaluation is skipped and outputs are zeroed
142
+ - **VA lowering (alpha):** user-defined `analog function` calls and noise contributions are not yet supported
@@ -0,0 +1,166 @@
1
+ #pragma once
2
+
3
+ #include <string.h>
4
+ #include <stdarg.h>
5
+ #include <stdio.h> // vsnprintf
6
+
7
+ NAMESPACE_BEGIN(NB_NAMESPACE)
8
+ NAMESPACE_BEGIN(detail)
9
+
10
+ struct Buffer {
11
+ public:
12
+ // Disable copy/move constructor and assignment
13
+ Buffer(const Buffer &) = delete;
14
+ Buffer(Buffer &&) = delete;
15
+ Buffer &operator=(const Buffer &) = delete;
16
+ Buffer &operator=(Buffer &&) = delete;
17
+
18
+ Buffer(size_t size = 0) : m_start((char *) malloc(size)) {
19
+ if (!m_start) {
20
+ fprintf(stderr, "Buffer::Buffer(): out of memory (unrecoverable error)!");
21
+ abort();
22
+ }
23
+ m_end = m_start + size;
24
+ if (size)
25
+ clear();
26
+ }
27
+
28
+ ~Buffer() {
29
+ free(m_start);
30
+ }
31
+
32
+ /// Append a string with the specified length
33
+ void put(const char *str, size_t size) {
34
+ if (m_cur + size >= m_end)
35
+ expand(size + 1 - remain());
36
+
37
+ memcpy(m_cur, str, size);
38
+ m_cur += size;
39
+ *m_cur = '\0';
40
+ }
41
+
42
+ /// Append a string
43
+ template <size_t N> void put(const char (&str)[N]) {
44
+ put(str, N - 1);
45
+ }
46
+
47
+ /// Append a dynamic string
48
+ void put_dstr(const char *str) { put(str, strlen(str)); }
49
+
50
+ /// Append a single character to the buffer
51
+ void put(char c) {
52
+ if (m_cur + 1 >= m_end)
53
+ expand();
54
+ *m_cur++ = c;
55
+ *m_cur = '\0';
56
+ }
57
+
58
+ /// Append multiple copies of a single character to the buffer
59
+ void put(char c, size_t count) {
60
+ if (m_cur + count >= m_end)
61
+ expand(count + 1 - remain());
62
+ for (size_t i = 0; i < count; ++i)
63
+ *m_cur++ = c;
64
+ *m_cur = '\0';
65
+ }
66
+
67
+ /// Append a formatted (printf-style) string to the buffer
68
+ #if defined(__GNUC__)
69
+ __attribute__((__format__ (__printf__, 2, 3)))
70
+ #endif
71
+ size_t fmt(const char *format, ...) {
72
+ size_t written;
73
+ do {
74
+ size_t size = remain();
75
+ va_list args;
76
+ va_start(args, format);
77
+ written = (size_t) vsnprintf(m_cur, size, format, args);
78
+ va_end(args);
79
+
80
+ if (written + 1 < size) {
81
+ m_cur += written;
82
+ break;
83
+ }
84
+
85
+ expand();
86
+ } while (true);
87
+
88
+ return written;
89
+ }
90
+
91
+ const char *get() { return m_start; }
92
+
93
+ void clear() {
94
+ m_cur = m_start;
95
+ if (m_start != m_end)
96
+ m_start[0] = '\0';
97
+ }
98
+
99
+ /// Remove the last 'n' characters
100
+ void rewind(size_t n) {
101
+ if (m_cur < m_start + n)
102
+ m_cur = m_start;
103
+ else
104
+ m_cur -= n;
105
+ *m_cur = '\0';
106
+ }
107
+
108
+ /// Append an unsigned 32 bit integer
109
+ void put_uint32(uint32_t value) {
110
+ const int digits = 10;
111
+ const char *num = "0123456789";
112
+ char buf[digits];
113
+ size_t i = digits;
114
+
115
+ do {
116
+ buf[--i] = num[value % 10];
117
+ value /= 10;
118
+ } while (value);
119
+
120
+ return put(buf + i, digits - i);
121
+ }
122
+
123
+ char *copy(size_t offset = 0) const {
124
+ size_t copy_size = size() + 1 - offset;
125
+ char *tmp = (char *) malloc(copy_size);
126
+ if (!tmp) {
127
+ fprintf(stderr, "Buffer::copy(): out of memory (unrecoverable error)!");
128
+ abort();
129
+ }
130
+ memcpy(tmp, m_start + offset, copy_size);
131
+ return tmp;
132
+ }
133
+
134
+ size_t size() const { return (size_t) (m_cur - m_start); }
135
+ size_t remain() const { return (size_t) (m_end - m_cur); }
136
+
137
+ private:
138
+ NB_NOINLINE void expand(size_t minval = 2) {
139
+ size_t old_alloc_size = m_end - m_start,
140
+ new_alloc_size = 2 * old_alloc_size + minval,
141
+ used_size = (size_t) (m_cur - m_start),
142
+ copy_size = used_size + 1;
143
+
144
+ if (old_alloc_size < copy_size)
145
+ copy_size = old_alloc_size;
146
+
147
+ char *tmp = (char *) malloc(new_alloc_size);
148
+ if (!tmp) {
149
+ fprintf(stderr, "Buffer::expand(): out of memory (unrecoverable error)!");
150
+ abort();
151
+ }
152
+
153
+ memcpy(tmp, m_start, copy_size);
154
+ free(m_start);
155
+
156
+ m_start = tmp;
157
+ m_end = m_start + new_alloc_size;
158
+ m_cur = m_start + used_size;
159
+ }
160
+
161
+ private:
162
+ char *m_start{nullptr}, *m_cur{nullptr}, *m_end{nullptr};
163
+ };
164
+
165
+ NAMESPACE_END(detail)
166
+ NAMESPACE_END(NB_NAMESPACE)