spicefault 0.1.0__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 (74) hide show
  1. spicefault-0.1.0/.gitignore +31 -0
  2. spicefault-0.1.0/CHANGELOG.md +34 -0
  3. spicefault-0.1.0/CITATION.cff +19 -0
  4. spicefault-0.1.0/LICENSE +21 -0
  5. spicefault-0.1.0/PKG-INFO +217 -0
  6. spicefault-0.1.0/README.md +177 -0
  7. spicefault-0.1.0/examples/01_quickstart.py +54 -0
  8. spicefault-0.1.0/examples/02_faults_and_coverage.py +75 -0
  9. spicefault-0.1.0/examples/03_campaign.py +50 -0
  10. spicefault-0.1.0/examples/04_dataset.py +38 -0
  11. spicefault-0.1.0/examples/05_reliability.py +47 -0
  12. spicefault-0.1.0/examples/06_operating_conditions.py +53 -0
  13. spicefault-0.1.0/examples/07_custom.py +63 -0
  14. spicefault-0.1.0/examples/filter/rc_lowpass.cir +7 -0
  15. spicefault-0.1.0/examples/filter/reliability.py +117 -0
  16. spicefault-0.1.0/examples/rc_study.py +58 -0
  17. spicefault-0.1.0/pyproject.toml +95 -0
  18. spicefault-0.1.0/src/spicefault/__init__.py +36 -0
  19. spicefault-0.1.0/src/spicefault/circuit.py +76 -0
  20. spicefault-0.1.0/src/spicefault/conditions/__init__.py +5 -0
  21. spicefault-0.1.0/src/spicefault/conditions/operating.py +42 -0
  22. spicefault-0.1.0/src/spicefault/dataset/__init__.py +19 -0
  23. spicefault-0.1.0/src/spicefault/dataset/dataset.py +289 -0
  24. spicefault-0.1.0/src/spicefault/dataset/manifest.py +79 -0
  25. spicefault-0.1.0/src/spicefault/dataset/store.py +53 -0
  26. spicefault-0.1.0/src/spicefault/experiments/__init__.py +21 -0
  27. spicefault-0.1.0/src/spicefault/experiments/campaign.py +293 -0
  28. spicefault-0.1.0/src/spicefault/experiments/engine.py +213 -0
  29. spicefault-0.1.0/src/spicefault/experiments/experiment.py +305 -0
  30. spicefault-0.1.0/src/spicefault/experiments/seeding.py +44 -0
  31. spicefault-0.1.0/src/spicefault/faults/__init__.py +35 -0
  32. spicefault-0.1.0/src/spicefault/faults/base.py +166 -0
  33. spicefault-0.1.0/src/spicefault/faults/faultset.py +177 -0
  34. spicefault-0.1.0/src/spicefault/faults/severity.py +61 -0
  35. spicefault-0.1.0/src/spicefault/faults/types.py +244 -0
  36. spicefault-0.1.0/src/spicefault/faults/universe.py +162 -0
  37. spicefault-0.1.0/src/spicefault/measurements/__init__.py +7 -0
  38. spicefault-0.1.0/src/spicefault/measurements/acquisition.py +12 -0
  39. spicefault-0.1.0/src/spicefault/measurements/measurement.py +294 -0
  40. spicefault-0.1.0/src/spicefault/measurements/response.py +18 -0
  41. spicefault-0.1.0/src/spicefault/netlist.py +263 -0
  42. spicefault-0.1.0/src/spicefault/reliability/__init__.py +56 -0
  43. spicefault-0.1.0/src/spicefault/reliability/analysis.py +522 -0
  44. spicefault-0.1.0/src/spicefault/reliability/detection.py +42 -0
  45. spicefault-0.1.0/src/spicefault/reliability/sensitivity.py +104 -0
  46. spicefault-0.1.0/src/spicefault/reliability/statistics.py +88 -0
  47. spicefault-0.1.0/src/spicefault/reliability/structure.py +90 -0
  48. spicefault-0.1.0/src/spicefault/simulation/__init__.py +39 -0
  49. spicefault-0.1.0/src/spicefault/simulation/backend.py +213 -0
  50. spicefault-0.1.0/src/spicefault/simulation/ngspice.py +125 -0
  51. spicefault-0.1.0/src/spicefault/variation/__init__.py +36 -0
  52. spicefault-0.1.0/src/spicefault/variation/base.py +123 -0
  53. spicefault-0.1.0/src/spicefault/variation/distributions.py +35 -0
  54. spicefault-0.1.0/src/spicefault/variation/types.py +324 -0
  55. spicefault-0.1.0/tests/conftest.py +17 -0
  56. spicefault-0.1.0/tests/unit/campaign_backend.py +36 -0
  57. spicefault-0.1.0/tests/unit/engine_workers.py +23 -0
  58. spicefault-0.1.0/tests/unit/test_backend.py +97 -0
  59. spicefault-0.1.0/tests/unit/test_campaign.py +286 -0
  60. spicefault-0.1.0/tests/unit/test_circuit.py +56 -0
  61. spicefault-0.1.0/tests/unit/test_dataset.py +377 -0
  62. spicefault-0.1.0/tests/unit/test_engine.py +121 -0
  63. spicefault-0.1.0/tests/unit/test_experiment.py +224 -0
  64. spicefault-0.1.0/tests/unit/test_faults.py +229 -0
  65. spicefault-0.1.0/tests/unit/test_faultset_universe.py +186 -0
  66. spicefault-0.1.0/tests/unit/test_measurement_api.py +134 -0
  67. spicefault-0.1.0/tests/unit/test_measurements.py +26 -0
  68. spicefault-0.1.0/tests/unit/test_netlist.py +130 -0
  69. spicefault-0.1.0/tests/unit/test_ngspice.py +63 -0
  70. spicefault-0.1.0/tests/unit/test_reliability_analysis.py +312 -0
  71. spicefault-0.1.0/tests/unit/test_reliability_statistics.py +145 -0
  72. spicefault-0.1.0/tests/unit/test_sampling.py +37 -0
  73. spicefault-0.1.0/tests/unit/test_variation_conditions.py +72 -0
  74. spicefault-0.1.0/tests/unit/test_variation_types.py +205 -0
@@ -0,0 +1,31 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ build/
9
+ dist/
10
+
11
+ # SPICE scratch files
12
+ *.raw
13
+ *.log
14
+
15
+ # Editors / OS
16
+ .DS_Store
17
+ .vscode/
18
+ .idea/
19
+
20
+ # Datasets written by the examples
21
+ examples/**/output/
22
+
23
+ # Datasets of the campaigns (large; regenerated with docs/RUNBOOK.md)
24
+ data/
25
+
26
+ # Built documentation
27
+ site/
28
+
29
+ # Coverage
30
+ .coverage
31
+ coverage.xml
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [semantic versioning](https://semver.org/spec/v2.0.0.html). Until version 1.0 the
6
+ public interface may change between minor versions.
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-10-04
11
+
12
+ First version.
13
+
14
+ ### Added
15
+
16
+ - `Circuit`: a SPICE netlist seen as components, nodes and parameters.
17
+ - Fault types `OpenCircuit`, `ShortCircuit`, `LeakageFault`, `ParametricFault` and
18
+ `CompositeFault`, each a list of primitive netlist changes with a record;
19
+ `FaultSeverity`, `FaultSet`, and `FaultUniverse` with its coverage matrix.
20
+ - Variations of the healthy population (tolerance, normal, log-normal, uniform,
21
+ log-uniform, fixed, custom, joint) and `VariationSet`.
22
+ - `OperatingCondition`, applied after the fault.
23
+ - `Simulator` with an ngspice backend, and `SimulationResult` with an explicit status.
24
+ - `Experiment` (in memory) and `FaultCampaign` (to disk, in chunks, resumable, one
25
+ process per folder), with three seeding schemes.
26
+ - `Measurement` and `Waveform`.
27
+ - `Dataset`, `Manifest` and `Provenance`: integrity, traceability and reproduction of
28
+ a campaign from its folder.
29
+ - `ReliabilityAnalysis`: detection probability, standardised shift, AUC, failure
30
+ probability, diagnostic coverage, severity response, robustness against tolerance,
31
+ dependence on operating conditions, separability and ambiguity.
32
+
33
+ [Unreleased]: https://github.com/telmomm/spicefault/compare/v0.1.0...HEAD
34
+ [0.1.0]: https://github.com/telmomm/spicefault/releases/tag/v0.1.0
@@ -0,0 +1,19 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ title: "spicefault: reproducible SPICE-based fault injection and reliability assessment of electronic circuits"
4
+ type: software
5
+ authors:
6
+ - family-names: "Miguel-Medina"
7
+ given-names: "Telmo"
8
+ repository-code: "https://github.com/telmomm/spicefault"
9
+ url: "https://spicefault.readthedocs.io"
10
+ license: MIT
11
+ version: 0.1.0
12
+ date-released: "2026-10-04"
13
+ keywords:
14
+ - SPICE
15
+ - ngspice
16
+ - fault injection
17
+ - reliability
18
+ - analog circuits
19
+ - Monte Carlo
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Telmo Miguel-Medina
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,217 @@
1
+ Metadata-Version: 2.5
2
+ Name: spicefault
3
+ Version: 0.1.0
4
+ Summary: Reproducible SPICE-based fault injection and reliability assessment of electronic circuits
5
+ Project-URL: Homepage, https://github.com/telmomm/spicefault
6
+ Project-URL: Documentation, https://spicefault.readthedocs.io
7
+ Project-URL: Repository, https://github.com/telmomm/spicefault
8
+ Project-URL: Issues, https://github.com/telmomm/spicefault/issues
9
+ Project-URL: Changelog, https://github.com/telmomm/spicefault/blob/main/CHANGELOG.md
10
+ Author: Telmo Miguel-Medina
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: Monte Carlo,SPICE,analog circuits,fault diagnosis,fault injection,ngspice,reliability,testability
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: numpy>=1.26
27
+ Requires-Dist: pandas>=2.1
28
+ Requires-Dist: pyarrow>=15
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=1.2; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
32
+ Requires-Dist: pytest>=8; extra == 'dev'
33
+ Requires-Dist: ruff>=0.5; extra == 'dev'
34
+ Requires-Dist: twine>=5; extra == 'dev'
35
+ Provides-Extra: docs
36
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
37
+ Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
38
+ Requires-Dist: mkdocstrings[python]>=0.25; extra == 'docs'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # spicefault
42
+
43
+ [![PyPI](https://img.shields.io/pypi/v/spicefault)](https://pypi.org/project/spicefault/)
44
+ [![Python](https://img.shields.io/pypi/pyversions/spicefault)](https://pypi.org/project/spicefault/)
45
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/telmomm/spicefault/blob/main/LICENSE)
46
+ [![CI](https://github.com/telmomm/spicefault/actions/workflows/ci.yml/badge.svg)](https://github.com/telmomm/spicefault/actions/workflows/ci.yml)
47
+ [![Documentation](https://readthedocs.org/projects/spicefault/badge/?version=latest)](https://spicefault.readthedocs.io/en/latest/)
48
+ [![Quality gate](https://sonarcloud.io/api/project_badges/measure?project=telmomm_spicefault&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=telmomm_spicefault)
49
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=telmomm_spicefault&metric=coverage)](https://sonarcloud.io/summary/new_code?id=telmomm_spicefault)
50
+ [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/telmomm/spicefault/main?labpath=examples%2Fquickstart.ipynb)
51
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
52
+ <!-- DOI: after the first release archived by Zenodo, add its badge here:
53
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.XXXXXXX.svg)](https://doi.org/10.5281/zenodo.XXXXXXX) -->
54
+
55
+ **Reproducible SPICE-based fault injection and reliability assessment of electronic
56
+ circuits.**
57
+
58
+ `spicefault` is a Python framework for studying how a circuit behaves when something
59
+ in it fails, under realistic component tolerances and operating conditions. You
60
+ describe a circuit, the faults it can have and how its good units vary; the framework
61
+ simulates the campaign with [ngspice](https://ngspice.sourceforge.io/), keeps a record
62
+ of every sample, and computes what the results say: which faults are detected, which
63
+ are confused with each other, and how tolerance erodes both.
64
+
65
+ It is not another Python interface to SPICE. It works one level above: the fault, the
66
+ normal variation of the components and the operating conditions are defined
67
+ explicitly, simulated as one experiment, and recorded so that every sample can be
68
+ traced and simulated again.
69
+
70
+ **[Documentation](https://spicefault.readthedocs.io)** ·
71
+ **[Examples](https://github.com/telmomm/spicefault/tree/main/examples/)** ·
72
+ **[Changelog](https://github.com/telmomm/spicefault/blob/main/CHANGELOG.md)**
73
+
74
+ ## Install
75
+
76
+ ```bash
77
+ pip install spicefault
78
+ ```
79
+
80
+ and ngspice, which does the simulations (`brew install ngspice` on macOS,
81
+ `sudo apt install ngspice` on Debian and Ubuntu). Python 3.10 or later.
82
+
83
+ ## A first experiment
84
+
85
+ A resistive divider with 1 % resistors, two faults and two operating conditions:
86
+
87
+ ```python
88
+ from spicefault import Circuit, Experiment, OperatingCondition, SimulationConfig
89
+ from spicefault.faults import OpenCircuit, ParametricFault
90
+ from spicefault.variation import ToleranceVariation
91
+
92
+ circuit = Circuit("""divider
93
+ V1 in 0 dc 1
94
+ R1 in out 10k
95
+ R2 out 0 10k
96
+ .end
97
+ """, name="divider")
98
+
99
+ experiment = Experiment(
100
+ circuit,
101
+ config=SimulationConfig(analyses=("op",), outputs=("v(out)",)),
102
+ faults=[OpenCircuit("R2"), ParametricFault("R1", deviation=0.2)],
103
+ variations=[ToleranceVariation("R1", 0.01), ToleranceVariation("R2", 0.01)],
104
+ conditions=[
105
+ OperatingCondition(),
106
+ OperatingCondition("low supply", settings={("V1", "dc"): 0.5}),
107
+ ],
108
+ samples=100,
109
+ seed=42,
110
+ )
111
+ result = experiment.run(workers=4)
112
+
113
+ print(result.status_counts()) # {'SUCCESS': 600}
114
+ table = result.to_frame() # sample definitions, status and realised values
115
+ vout = [s.result.plot("op")["v(out)"][0] for s in result]
116
+ ```
117
+
118
+ - The component values are drawn first, the fault is injected into that drawn
119
+ circuit, and then the operating condition is set.
120
+ - Each sample has its own random stream: the result is the same on one worker or on
121
+ sixteen.
122
+ - A simulation that fails stays in the results, with a status and a message.
123
+
124
+ ## What it gives you
125
+
126
+ | | |
127
+ |---|---|
128
+ | **Faults as objects** | Open, short, leakage, parametric and composite faults, each a recorded change of the netlist with a physical interpretation |
129
+ | **An auditable fault list** | A fault universe generated from rules, a coverage matrix, and a reason for every fault left out |
130
+ | **A healthy population** | Tolerance, normal, log-normal, uniform and joint distributions, kept apart from the faults |
131
+ | **Reproducible campaigns** | To disk in chunks, resumable; the same samples for any number of workers; five explicit simulation statuses |
132
+ | **Datasets that stand on their own** | Integrity check, provenance of each sample, the netlist that was simulated for it, and simulation again from the folder |
133
+ | **Reliability metrics with intervals** | Detection probability, diagnostic coverage, minimum detectable deviation, robustness against tolerance, ambiguity groups |
134
+
135
+ ```python
136
+ from spicefault import FaultCampaign, Dataset
137
+ from spicefault.reliability import ReliabilityAnalysis
138
+
139
+ campaign = FaultCampaign(circuit, faults, out_dir="data/study", samples_per_fault=200,
140
+ healthy_samples=2000, variations=population, config=config,
141
+ measurements=measurements, seed=1)
142
+ campaign.run(workers=8) # resumes if it was interrupted
143
+
144
+ dataset = Dataset("data/study")
145
+ dataset.verify() # the files match their fingerprints
146
+ dataset.provenance(1234) # everything about one sample
147
+ dataset.reproduce(n=50) # simulate again, compare with what is stored
148
+
149
+ analysis = ReliabilityAnalysis.from_dataset("data/study")
150
+ analysis.detectability(alpha=0.01).table # per fault: P(detect) and its interval
151
+ analysis.minimum_detectable() # smallest deviation that is visible
152
+ analysis.ambiguity() # faults that cannot be told apart
153
+ ```
154
+
155
+ ## Examples
156
+
157
+ Runnable scripts in [examples/](https://github.com/telmomm/spicefault/tree/main/examples/), the same ones shown in the documentation:
158
+
159
+ | Script | What it shows |
160
+ |---|---|
161
+ | `01_quickstart.py` | A circuit, a fault, a simulation, a small experiment |
162
+ | `02_faults_and_coverage.py` | Fault types, a fault universe from rules, coverage |
163
+ | `03_campaign.py` | A campaign to disk, interrupted and resumed |
164
+ | `04_dataset.py` | Integrity, provenance and reproduction of a dataset |
165
+ | `05_reliability.py` | Detection, minimum detectable deviation, ambiguity, tolerance |
166
+ | `06_operating_conditions.py` | The same circuits under several conditions |
167
+ | `07_custom.py` | Your own distributions and measurements |
168
+
169
+ ```bash
170
+ git clone https://github.com/telmomm/spicefault.git && cd spicefault
171
+ pip install -e .
172
+ python examples/01_quickstart.py
173
+ ```
174
+
175
+ Or try the quickstart in the browser, without installing anything, with the Binder
176
+ badge above.
177
+
178
+ ## Limits
179
+
180
+ - One simulator: ngspice.
181
+ - One fault per sample; faults do not change during a simulation.
182
+ - Faults act on the top level of the netlist. A component inside a subcircuit is
183
+ reached through the parameters of its instance.
184
+ - Opens and shorts are finite resistances, recorded with the fault.
185
+ - Simulated evidence does not replace testing hardware.
186
+
187
+ ## Project
188
+
189
+ The library is the instrument of a research project on reproducible fault-injection
190
+ experiments. Its definitions and plan are part of the documentation:
191
+ [fault model](https://github.com/telmomm/spicefault/blob/main/docs/FAULT_MODEL.md),
192
+ [reliability metrics](https://github.com/telmomm/spicefault/blob/main/docs/RELIABILITY_METRICS.md),
193
+ [scientific scope](https://github.com/telmomm/spicefault/blob/main/docs/SCIENTIFIC_SCOPE.md),
194
+ [experiment plan](https://github.com/telmomm/spicefault/blob/main/docs/EXPERIMENT_PLAN.md),
195
+ [related work](https://github.com/telmomm/spicefault/blob/main/docs/RELATED_WORK.md) and
196
+ [comparison with other tools](https://github.com/telmomm/spicefault/blob/main/docs/COMPARISON.md).
197
+ The circuits it is validated on are in [validation/](https://github.com/telmomm/spicefault/tree/main/validation/), the benchmarks in
198
+ [benchmarks/](https://github.com/telmomm/spicefault/blob/main/benchmarks/README.md), and the results obtained so far in `results/`.
199
+
200
+ It was extracted from the simulation code of a study on self-diagnosis of ECG analog
201
+ front-ends
202
+ ([ecg-frontend-fault-diagnosis](https://github.com/telmomm/ecg-frontend-fault-diagnosis)),
203
+ which uses it. Nothing in it is specific to that study.
204
+
205
+ ## Contributing
206
+
207
+ Bug reports and contributions are welcome: see [CONTRIBUTING.md](https://github.com/telmomm/spicefault/blob/main/CONTRIBUTING.md) and
208
+ the [code of conduct](https://github.com/telmomm/spicefault/blob/main/CODE_OF_CONDUCT.md).
209
+
210
+ ## Citing
211
+
212
+ If you use `spicefault` in your work, please cite it. The citation data are in
213
+ [CITATION.cff](https://github.com/telmomm/spicefault/blob/main/CITATION.cff); GitHub shows them under "Cite this repository".
214
+
215
+ ## Licence
216
+
217
+ [MIT](https://github.com/telmomm/spicefault/blob/main/LICENSE).
@@ -0,0 +1,177 @@
1
+ # spicefault
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/spicefault)](https://pypi.org/project/spicefault/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/spicefault)](https://pypi.org/project/spicefault/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/telmomm/spicefault/blob/main/LICENSE)
6
+ [![CI](https://github.com/telmomm/spicefault/actions/workflows/ci.yml/badge.svg)](https://github.com/telmomm/spicefault/actions/workflows/ci.yml)
7
+ [![Documentation](https://readthedocs.org/projects/spicefault/badge/?version=latest)](https://spicefault.readthedocs.io/en/latest/)
8
+ [![Quality gate](https://sonarcloud.io/api/project_badges/measure?project=telmomm_spicefault&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=telmomm_spicefault)
9
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=telmomm_spicefault&metric=coverage)](https://sonarcloud.io/summary/new_code?id=telmomm_spicefault)
10
+ [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/telmomm/spicefault/main?labpath=examples%2Fquickstart.ipynb)
11
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
12
+ <!-- DOI: after the first release archived by Zenodo, add its badge here:
13
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.XXXXXXX.svg)](https://doi.org/10.5281/zenodo.XXXXXXX) -->
14
+
15
+ **Reproducible SPICE-based fault injection and reliability assessment of electronic
16
+ circuits.**
17
+
18
+ `spicefault` is a Python framework for studying how a circuit behaves when something
19
+ in it fails, under realistic component tolerances and operating conditions. You
20
+ describe a circuit, the faults it can have and how its good units vary; the framework
21
+ simulates the campaign with [ngspice](https://ngspice.sourceforge.io/), keeps a record
22
+ of every sample, and computes what the results say: which faults are detected, which
23
+ are confused with each other, and how tolerance erodes both.
24
+
25
+ It is not another Python interface to SPICE. It works one level above: the fault, the
26
+ normal variation of the components and the operating conditions are defined
27
+ explicitly, simulated as one experiment, and recorded so that every sample can be
28
+ traced and simulated again.
29
+
30
+ **[Documentation](https://spicefault.readthedocs.io)** ·
31
+ **[Examples](https://github.com/telmomm/spicefault/tree/main/examples/)** ·
32
+ **[Changelog](https://github.com/telmomm/spicefault/blob/main/CHANGELOG.md)**
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install spicefault
38
+ ```
39
+
40
+ and ngspice, which does the simulations (`brew install ngspice` on macOS,
41
+ `sudo apt install ngspice` on Debian and Ubuntu). Python 3.10 or later.
42
+
43
+ ## A first experiment
44
+
45
+ A resistive divider with 1 % resistors, two faults and two operating conditions:
46
+
47
+ ```python
48
+ from spicefault import Circuit, Experiment, OperatingCondition, SimulationConfig
49
+ from spicefault.faults import OpenCircuit, ParametricFault
50
+ from spicefault.variation import ToleranceVariation
51
+
52
+ circuit = Circuit("""divider
53
+ V1 in 0 dc 1
54
+ R1 in out 10k
55
+ R2 out 0 10k
56
+ .end
57
+ """, name="divider")
58
+
59
+ experiment = Experiment(
60
+ circuit,
61
+ config=SimulationConfig(analyses=("op",), outputs=("v(out)",)),
62
+ faults=[OpenCircuit("R2"), ParametricFault("R1", deviation=0.2)],
63
+ variations=[ToleranceVariation("R1", 0.01), ToleranceVariation("R2", 0.01)],
64
+ conditions=[
65
+ OperatingCondition(),
66
+ OperatingCondition("low supply", settings={("V1", "dc"): 0.5}),
67
+ ],
68
+ samples=100,
69
+ seed=42,
70
+ )
71
+ result = experiment.run(workers=4)
72
+
73
+ print(result.status_counts()) # {'SUCCESS': 600}
74
+ table = result.to_frame() # sample definitions, status and realised values
75
+ vout = [s.result.plot("op")["v(out)"][0] for s in result]
76
+ ```
77
+
78
+ - The component values are drawn first, the fault is injected into that drawn
79
+ circuit, and then the operating condition is set.
80
+ - Each sample has its own random stream: the result is the same on one worker or on
81
+ sixteen.
82
+ - A simulation that fails stays in the results, with a status and a message.
83
+
84
+ ## What it gives you
85
+
86
+ | | |
87
+ |---|---|
88
+ | **Faults as objects** | Open, short, leakage, parametric and composite faults, each a recorded change of the netlist with a physical interpretation |
89
+ | **An auditable fault list** | A fault universe generated from rules, a coverage matrix, and a reason for every fault left out |
90
+ | **A healthy population** | Tolerance, normal, log-normal, uniform and joint distributions, kept apart from the faults |
91
+ | **Reproducible campaigns** | To disk in chunks, resumable; the same samples for any number of workers; five explicit simulation statuses |
92
+ | **Datasets that stand on their own** | Integrity check, provenance of each sample, the netlist that was simulated for it, and simulation again from the folder |
93
+ | **Reliability metrics with intervals** | Detection probability, diagnostic coverage, minimum detectable deviation, robustness against tolerance, ambiguity groups |
94
+
95
+ ```python
96
+ from spicefault import FaultCampaign, Dataset
97
+ from spicefault.reliability import ReliabilityAnalysis
98
+
99
+ campaign = FaultCampaign(circuit, faults, out_dir="data/study", samples_per_fault=200,
100
+ healthy_samples=2000, variations=population, config=config,
101
+ measurements=measurements, seed=1)
102
+ campaign.run(workers=8) # resumes if it was interrupted
103
+
104
+ dataset = Dataset("data/study")
105
+ dataset.verify() # the files match their fingerprints
106
+ dataset.provenance(1234) # everything about one sample
107
+ dataset.reproduce(n=50) # simulate again, compare with what is stored
108
+
109
+ analysis = ReliabilityAnalysis.from_dataset("data/study")
110
+ analysis.detectability(alpha=0.01).table # per fault: P(detect) and its interval
111
+ analysis.minimum_detectable() # smallest deviation that is visible
112
+ analysis.ambiguity() # faults that cannot be told apart
113
+ ```
114
+
115
+ ## Examples
116
+
117
+ Runnable scripts in [examples/](https://github.com/telmomm/spicefault/tree/main/examples/), the same ones shown in the documentation:
118
+
119
+ | Script | What it shows |
120
+ |---|---|
121
+ | `01_quickstart.py` | A circuit, a fault, a simulation, a small experiment |
122
+ | `02_faults_and_coverage.py` | Fault types, a fault universe from rules, coverage |
123
+ | `03_campaign.py` | A campaign to disk, interrupted and resumed |
124
+ | `04_dataset.py` | Integrity, provenance and reproduction of a dataset |
125
+ | `05_reliability.py` | Detection, minimum detectable deviation, ambiguity, tolerance |
126
+ | `06_operating_conditions.py` | The same circuits under several conditions |
127
+ | `07_custom.py` | Your own distributions and measurements |
128
+
129
+ ```bash
130
+ git clone https://github.com/telmomm/spicefault.git && cd spicefault
131
+ pip install -e .
132
+ python examples/01_quickstart.py
133
+ ```
134
+
135
+ Or try the quickstart in the browser, without installing anything, with the Binder
136
+ badge above.
137
+
138
+ ## Limits
139
+
140
+ - One simulator: ngspice.
141
+ - One fault per sample; faults do not change during a simulation.
142
+ - Faults act on the top level of the netlist. A component inside a subcircuit is
143
+ reached through the parameters of its instance.
144
+ - Opens and shorts are finite resistances, recorded with the fault.
145
+ - Simulated evidence does not replace testing hardware.
146
+
147
+ ## Project
148
+
149
+ The library is the instrument of a research project on reproducible fault-injection
150
+ experiments. Its definitions and plan are part of the documentation:
151
+ [fault model](https://github.com/telmomm/spicefault/blob/main/docs/FAULT_MODEL.md),
152
+ [reliability metrics](https://github.com/telmomm/spicefault/blob/main/docs/RELIABILITY_METRICS.md),
153
+ [scientific scope](https://github.com/telmomm/spicefault/blob/main/docs/SCIENTIFIC_SCOPE.md),
154
+ [experiment plan](https://github.com/telmomm/spicefault/blob/main/docs/EXPERIMENT_PLAN.md),
155
+ [related work](https://github.com/telmomm/spicefault/blob/main/docs/RELATED_WORK.md) and
156
+ [comparison with other tools](https://github.com/telmomm/spicefault/blob/main/docs/COMPARISON.md).
157
+ The circuits it is validated on are in [validation/](https://github.com/telmomm/spicefault/tree/main/validation/), the benchmarks in
158
+ [benchmarks/](https://github.com/telmomm/spicefault/blob/main/benchmarks/README.md), and the results obtained so far in `results/`.
159
+
160
+ It was extracted from the simulation code of a study on self-diagnosis of ECG analog
161
+ front-ends
162
+ ([ecg-frontend-fault-diagnosis](https://github.com/telmomm/ecg-frontend-fault-diagnosis)),
163
+ which uses it. Nothing in it is specific to that study.
164
+
165
+ ## Contributing
166
+
167
+ Bug reports and contributions are welcome: see [CONTRIBUTING.md](https://github.com/telmomm/spicefault/blob/main/CONTRIBUTING.md) and
168
+ the [code of conduct](https://github.com/telmomm/spicefault/blob/main/CODE_OF_CONDUCT.md).
169
+
170
+ ## Citing
171
+
172
+ If you use `spicefault` in your work, please cite it. The citation data are in
173
+ [CITATION.cff](https://github.com/telmomm/spicefault/blob/main/CITATION.cff); GitHub shows them under "Cite this repository".
174
+
175
+ ## Licence
176
+
177
+ [MIT](https://github.com/telmomm/spicefault/blob/main/LICENSE).
@@ -0,0 +1,54 @@
1
+ """Quickstart: a circuit, one fault, one simulation; then a small experiment.
2
+
3
+ python examples/01_quickstart.py
4
+ """
5
+
6
+ from spicefault import Circuit, Experiment, Measurement, SimulationConfig, Simulator
7
+ from spicefault.faults import OpenCircuit, ParametricFault
8
+ from spicefault.variation import ToleranceVariation
9
+
10
+ # --- a circuit is a SPICE netlist -----------------------------------------------------
11
+ circuit = Circuit(
12
+ """resistive divider
13
+ V1 in 0 dc 1
14
+ R1 in out 10k
15
+ R2 out 0 10k
16
+ .end
17
+ """,
18
+ name="divider",
19
+ )
20
+ print("components:", [c.name for c in circuit.components()])
21
+ print("parameters:", circuit.parameters())
22
+
23
+ # --- a fault changes a copy of the netlist --------------------------------------------
24
+ fault = OpenCircuit("R2")
25
+ netlist = circuit.netlist()
26
+ fault.apply(netlist)
27
+ print("\nnetlist with", fault.fault_id, "injected:")
28
+ print(netlist)
29
+
30
+ # --- one simulation, with an explicit status -------------------------------------------
31
+ config = SimulationConfig(analyses=("op",), outputs=("v(out)",))
32
+ output = Measurement.value("v(out)", name="vout")
33
+ simulator = Simulator("ngspice")
34
+ for label, text in (("healthy", circuit.to_netlist()), (fault.fault_id, str(netlist))):
35
+ result = simulator.run(text, config)
36
+ print(f"{label}: {result.status.value}, vout = {output(result):.4f} V")
37
+
38
+ # --- an experiment: tolerances, faults, a seed -----------------------------------------
39
+ experiment = Experiment(
40
+ circuit,
41
+ config=config,
42
+ faults=[OpenCircuit("R2"), ParametricFault("R1", deviation=0.2)],
43
+ variations=[ToleranceVariation("R1", 0.01), ToleranceVariation("R2", 0.01)],
44
+ measurements=[output],
45
+ samples=5,
46
+ seed=42,
47
+ )
48
+ table = experiment.run().to_frame()
49
+ print("\n15 simulations: 5 healthy circuits and 5 of each fault")
50
+ print(table[["fault_id", "replica", "status", "p_R1_value", "p_R2_value", "vout"]].round(4))
51
+
52
+ # the same seed gives the same circuits, whatever the number of workers
53
+ again = experiment.run().to_frame()
54
+ print("\nsame results when run again:", table["vout"].equals(again["vout"]))
@@ -0,0 +1,75 @@
1
+ """Faults as objects, a fault universe from rules, and what a campaign covers.
2
+
3
+ python examples/02_faults_and_coverage.py
4
+
5
+ Nothing is simulated here.
6
+ """
7
+
8
+ import json
9
+
10
+ from rc_study import circuit, population
11
+
12
+ from spicefault.faults import (
13
+ CompositeFault,
14
+ Fault,
15
+ FaultSet,
16
+ FaultUniverse,
17
+ LeakageFault,
18
+ OpenCircuit,
19
+ ParametricFault,
20
+ ShortCircuit,
21
+ leakage_rule,
22
+ open_rule,
23
+ parametric_rule,
24
+ short_rule,
25
+ )
26
+
27
+ # --- every fault is a list of primitive changes, with a record ------------------------
28
+ faults = FaultSet(
29
+ [
30
+ OpenCircuit("R1"),
31
+ ShortCircuit("C1"),
32
+ LeakageFault("C1", 1e6, r_min=1e4, r_max=1e8),
33
+ ParametricFault("R2", deviation=-0.2),
34
+ # an ageing capacitor: less capacitance and more series resistance, one cause
35
+ CompositeFault(
36
+ "capacitor_ageing",
37
+ [ParametricFault("C1", factor=0.7), OpenCircuit("C1", r_open=50.0)],
38
+ magnitude=0.3,
39
+ unit="capacitance loss",
40
+ ),
41
+ ]
42
+ )
43
+ for fault in faults:
44
+ severity = "-" if fault.severity is None else f"{fault.severity.value:.2f}"
45
+ print(f"{fault.fault_id:28s} type {fault.fault_type:18s} severity {severity}")
46
+
47
+ print("\nthe record of one fault:")
48
+ record = faults["R2:parametric:-0.2"].metadata()
49
+ print(json.dumps(record, indent=2))
50
+ print("rebuilt from its record, it is the same fault:", Fault.from_metadata(record) == faults[3])
51
+
52
+ # --- a universe generated from rules ---------------------------------------------------
53
+ universe = FaultUniverse(
54
+ circuit,
55
+ [
56
+ open_rule(),
57
+ short_rule(),
58
+ parametric_rule([-0.2, -0.05, 0.05, 0.2]),
59
+ leakage_rule([1e5, 1e6, 1e7], kinds="C"),
60
+ ],
61
+ )
62
+ universe.exclude("R2:short", reason="ties the output to ground: same effect as C1:short")
63
+ print(f"\n{len(universe)} fault conditions in the universe, {len(universe.selected())} selected")
64
+ print("\nconditions to simulate, by component and fault type:")
65
+ print(universe.coverage_matrix())
66
+ print("\nleft out, with the reason:")
67
+ print(universe.exclusions().to_string(index=False))
68
+ print(f"\nstructural coverage: {universe.coverage():.0%}")
69
+ print("components outside the fault model:", universe.metadata()["components_without_faults"])
70
+
71
+ # --- faults that are not faults: inside the tolerance band ----------------------------
72
+ overlap = universe.selected().tolerance_overlap(population, circuit)
73
+ inside = overlap[overlap["inside_fraction"] > 0]
74
+ print("\nparametric faults partly inside the tolerance band of their component:")
75
+ print(inside[["fault_id", "tolerance", "inside_fraction"]].to_string(index=False))