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.
- spicefault-0.1.0/.gitignore +31 -0
- spicefault-0.1.0/CHANGELOG.md +34 -0
- spicefault-0.1.0/CITATION.cff +19 -0
- spicefault-0.1.0/LICENSE +21 -0
- spicefault-0.1.0/PKG-INFO +217 -0
- spicefault-0.1.0/README.md +177 -0
- spicefault-0.1.0/examples/01_quickstart.py +54 -0
- spicefault-0.1.0/examples/02_faults_and_coverage.py +75 -0
- spicefault-0.1.0/examples/03_campaign.py +50 -0
- spicefault-0.1.0/examples/04_dataset.py +38 -0
- spicefault-0.1.0/examples/05_reliability.py +47 -0
- spicefault-0.1.0/examples/06_operating_conditions.py +53 -0
- spicefault-0.1.0/examples/07_custom.py +63 -0
- spicefault-0.1.0/examples/filter/rc_lowpass.cir +7 -0
- spicefault-0.1.0/examples/filter/reliability.py +117 -0
- spicefault-0.1.0/examples/rc_study.py +58 -0
- spicefault-0.1.0/pyproject.toml +95 -0
- spicefault-0.1.0/src/spicefault/__init__.py +36 -0
- spicefault-0.1.0/src/spicefault/circuit.py +76 -0
- spicefault-0.1.0/src/spicefault/conditions/__init__.py +5 -0
- spicefault-0.1.0/src/spicefault/conditions/operating.py +42 -0
- spicefault-0.1.0/src/spicefault/dataset/__init__.py +19 -0
- spicefault-0.1.0/src/spicefault/dataset/dataset.py +289 -0
- spicefault-0.1.0/src/spicefault/dataset/manifest.py +79 -0
- spicefault-0.1.0/src/spicefault/dataset/store.py +53 -0
- spicefault-0.1.0/src/spicefault/experiments/__init__.py +21 -0
- spicefault-0.1.0/src/spicefault/experiments/campaign.py +293 -0
- spicefault-0.1.0/src/spicefault/experiments/engine.py +213 -0
- spicefault-0.1.0/src/spicefault/experiments/experiment.py +305 -0
- spicefault-0.1.0/src/spicefault/experiments/seeding.py +44 -0
- spicefault-0.1.0/src/spicefault/faults/__init__.py +35 -0
- spicefault-0.1.0/src/spicefault/faults/base.py +166 -0
- spicefault-0.1.0/src/spicefault/faults/faultset.py +177 -0
- spicefault-0.1.0/src/spicefault/faults/severity.py +61 -0
- spicefault-0.1.0/src/spicefault/faults/types.py +244 -0
- spicefault-0.1.0/src/spicefault/faults/universe.py +162 -0
- spicefault-0.1.0/src/spicefault/measurements/__init__.py +7 -0
- spicefault-0.1.0/src/spicefault/measurements/acquisition.py +12 -0
- spicefault-0.1.0/src/spicefault/measurements/measurement.py +294 -0
- spicefault-0.1.0/src/spicefault/measurements/response.py +18 -0
- spicefault-0.1.0/src/spicefault/netlist.py +263 -0
- spicefault-0.1.0/src/spicefault/reliability/__init__.py +56 -0
- spicefault-0.1.0/src/spicefault/reliability/analysis.py +522 -0
- spicefault-0.1.0/src/spicefault/reliability/detection.py +42 -0
- spicefault-0.1.0/src/spicefault/reliability/sensitivity.py +104 -0
- spicefault-0.1.0/src/spicefault/reliability/statistics.py +88 -0
- spicefault-0.1.0/src/spicefault/reliability/structure.py +90 -0
- spicefault-0.1.0/src/spicefault/simulation/__init__.py +39 -0
- spicefault-0.1.0/src/spicefault/simulation/backend.py +213 -0
- spicefault-0.1.0/src/spicefault/simulation/ngspice.py +125 -0
- spicefault-0.1.0/src/spicefault/variation/__init__.py +36 -0
- spicefault-0.1.0/src/spicefault/variation/base.py +123 -0
- spicefault-0.1.0/src/spicefault/variation/distributions.py +35 -0
- spicefault-0.1.0/src/spicefault/variation/types.py +324 -0
- spicefault-0.1.0/tests/conftest.py +17 -0
- spicefault-0.1.0/tests/unit/campaign_backend.py +36 -0
- spicefault-0.1.0/tests/unit/engine_workers.py +23 -0
- spicefault-0.1.0/tests/unit/test_backend.py +97 -0
- spicefault-0.1.0/tests/unit/test_campaign.py +286 -0
- spicefault-0.1.0/tests/unit/test_circuit.py +56 -0
- spicefault-0.1.0/tests/unit/test_dataset.py +377 -0
- spicefault-0.1.0/tests/unit/test_engine.py +121 -0
- spicefault-0.1.0/tests/unit/test_experiment.py +224 -0
- spicefault-0.1.0/tests/unit/test_faults.py +229 -0
- spicefault-0.1.0/tests/unit/test_faultset_universe.py +186 -0
- spicefault-0.1.0/tests/unit/test_measurement_api.py +134 -0
- spicefault-0.1.0/tests/unit/test_measurements.py +26 -0
- spicefault-0.1.0/tests/unit/test_netlist.py +130 -0
- spicefault-0.1.0/tests/unit/test_ngspice.py +63 -0
- spicefault-0.1.0/tests/unit/test_reliability_analysis.py +312 -0
- spicefault-0.1.0/tests/unit/test_reliability_statistics.py +145 -0
- spicefault-0.1.0/tests/unit/test_sampling.py +37 -0
- spicefault-0.1.0/tests/unit/test_variation_conditions.py +72 -0
- 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
|
spicefault-0.1.0/LICENSE
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/spicefault/)
|
|
44
|
+
[](https://pypi.org/project/spicefault/)
|
|
45
|
+
[](https://github.com/telmomm/spicefault/blob/main/LICENSE)
|
|
46
|
+
[](https://github.com/telmomm/spicefault/actions/workflows/ci.yml)
|
|
47
|
+
[](https://spicefault.readthedocs.io/en/latest/)
|
|
48
|
+
[](https://sonarcloud.io/summary/new_code?id=telmomm_spicefault)
|
|
49
|
+
[](https://sonarcloud.io/summary/new_code?id=telmomm_spicefault)
|
|
50
|
+
[](https://mybinder.org/v2/gh/telmomm/spicefault/main?labpath=examples%2Fquickstart.ipynb)
|
|
51
|
+
[](https://github.com/astral-sh/ruff)
|
|
52
|
+
<!-- DOI: after the first release archived by Zenodo, add its badge here:
|
|
53
|
+
[](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
|
+
[](https://pypi.org/project/spicefault/)
|
|
4
|
+
[](https://pypi.org/project/spicefault/)
|
|
5
|
+
[](https://github.com/telmomm/spicefault/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/telmomm/spicefault/actions/workflows/ci.yml)
|
|
7
|
+
[](https://spicefault.readthedocs.io/en/latest/)
|
|
8
|
+
[](https://sonarcloud.io/summary/new_code?id=telmomm_spicefault)
|
|
9
|
+
[](https://sonarcloud.io/summary/new_code?id=telmomm_spicefault)
|
|
10
|
+
[](https://mybinder.org/v2/gh/telmomm/spicefault/main?labpath=examples%2Fquickstart.ipynb)
|
|
11
|
+
[](https://github.com/astral-sh/ruff)
|
|
12
|
+
<!-- DOI: after the first release archived by Zenodo, add its badge here:
|
|
13
|
+
[](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))
|