Skip to content

Commit d288c71

Browse files
committed
feat : Add Readme
1 parent fad67d7 commit d288c71

1 file changed

Lines changed: 380 additions & 0 deletions

File tree

README.md

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
<div align="center">
2+
3+
<img src="docs/assets/hero.png" alt="CNSD — Causal Neuro-Symbolic Diagnosis" width="100%">
4+
5+
<br>
6+
7+
[![License: MIT](https://img.shields.io/badge/License-MIT-2563eb.svg)](LICENSE)
8+
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
9+
[![Version](https://img.shields.io/badge/version-1.0.0-informational.svg)](CHANGELOG.md)
10+
[![CI](https://img.shields.io/badge/CI-ruff%20%2B%20pytest-16a34a.svg)](.github/workflows)
11+
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
12+
[![DOI](https://img.shields.io/badge/DOI-pending-lightgrey.svg)](#citation)
13+
14+
**A five-layer framework that _verifies_ bearing-fault diagnoses against physics and causal structure — instead of trusting a neural network's confidence alone.**
15+
16+
[Quickstart](#-quickstart) · [How it works](#-how-it-works) · [Results](#-results) · [API](#-usage) · [Reproduce](#-reproducing-the-experiments) · [Citation](#-citation)
17+
18+
</div>
19+
20+
---
21+
22+
## What is CNSD?
23+
24+
**CNSD (Causal Neuro-Symbolic Diagnosis)** is a Python framework for diagnosing rolling-element **bearing faults** from vibration signals. A neural network proposes a diagnosis; then three independent layers check that proposal against the **physics of the signal** and the **causal structure of the machine**, and a consensus layer turns the result into an actionable maintenance decision.
25+
26+
The problem it solves is **operating-condition shift**: a model trained at one load or speed silently loses accuracy at another, and its confidence — the usual signal for "can I trust this prediction?" — is the first thing to break. CNSD answers a question a plain classifier cannot: *is this predicted fault physically present in the signal, and would it survive a change of operating condition?*
27+
28+
```python
29+
from cnsd import CNSD, Dataset
30+
31+
data = Dataset.from_arrays(signals, labels, condition, fs=12000)
32+
report = CNSD().fit(data).diagnose(data)
33+
34+
print(report.summary())
35+
# HIGH_CONFIDENCE: 61% RELIABLE: 22% UNCERTAIN: 9% MANUAL_REVIEW: 8%
36+
# physics verification rate: CONFIRMED 71% · CONFLICT 18% · INCONCLUSIVE 11%
37+
```
38+
39+
> **Why it matters.** On two cross-domain benchmarks the physics layer is a **significantly better reliability signal than deep ensembles** (p < 0.005), and under heavy noise it catches **up to 100%** of the ensemble's confident mistakes. Where the task is easy, it honestly reports **no advantage** — see [Results](#-results).
40+
41+
---
42+
43+
## Table of Contents
44+
45+
- [Quickstart](#-quickstart)
46+
- [How it works](#-how-it-works)
47+
- [Results](#-results)
48+
- [Features](#-features)
49+
- [Installation](#-installation)
50+
- [Usage](#-usage)
51+
- [Configuration](#-configuration)
52+
- [Repository structure](#-repository-structure)
53+
- [Reproducing the experiments](#-reproducing-the-experiments)
54+
- [Extending CNSD](#-extending-cnsd-to-new-machinery)
55+
- [Project status & roadmap](#-project-status--roadmap)
56+
- [Contributing](#-contributing)
57+
- [Citation](#-citation)
58+
- [License](#-license)
59+
60+
---
61+
62+
## 🚀 Quickstart
63+
64+
**Install** (core is dependency-light — no TensorFlow required to use the physics and causal tools):
65+
66+
```bash
67+
pip install cnsd # core
68+
pip install "cnsd[all]" # + perception (TensorFlow) and counterfactual (DoWhy)
69+
```
70+
71+
**Diagnose a dataset in five lines.** Bring your own arrays — signals, labels, the operating condition per window, and the sampling rate:
72+
73+
```python
74+
import numpy as np
75+
from cnsd import CNSD, Dataset
76+
77+
X = np.random.randn(200, 1024) # 200 windows of 1024 samples
78+
y = np.random.randint(0, 10, 200) # fault labels
79+
cond = np.random.choice([0, 1, 2, 3], 200) # operating condition per window
80+
data = Dataset.from_arrays(X, y, cond, fs=12000)
81+
82+
report = CNSD().fit(data).diagnose(data)
83+
84+
print(report.summary())
85+
for statement in report.root_causes()[:5]:
86+
print(statement) # e.g. "outer-race comb at 107 Hz (2 harmonics) supports predicted Outer fault"
87+
88+
print(f"{len(report.conflicts())} units flagged for review (physics disagrees with the network).")
89+
```
90+
91+
Runnable versions live in [`examples/`](examples/): [`quickstart.py`](examples/quickstart.py), [`public_api_demo.py`](examples/public_api_demo.py), and [`unseen_data.py`](examples/unseen_data.py).
92+
93+
---
94+
95+
## 🧠 How it works
96+
97+
CNSD is a **propose → verify → decide** pipeline. The network only *proposes*; the diagnosis is not trusted until physics and causal reasoning have checked it.
98+
99+
<div align="center">
100+
<img src="docs/assets/architecture.png" alt="CNSD five-layer architecture" width="95%">
101+
</div>
102+
103+
| Layer | Role | What it produces |
104+
|------|------|------------------|
105+
| **1 · Perception** | A 1-D CNN (or self-supervised **S-JEPA** backbone) classifies the vibration window. | predicted fault `ŷ`, confidence `c` |
106+
| **2 · Symbolic** | Checks the prediction against the bearing's **characteristic frequencies** in the envelope spectrum. Independently names the fault family the *signal* supports. | verdict `CONFIRMED` / `CONFLICT` / `INCONCLUSIVE`, root cause, maintenance action |
107+
| **3 · Causal (Rung 2)** | Estimates the interventional effect of the **operating condition** `do(Z)` on a corrected structural causal model (vibration does **not** cause the fault), with a refutation suite. | interventional warrant, CATE, invariance test |
108+
| **3B · Counterfactual (Rung 3)** | On an invertible SCM, asks how the diagnosis would move under a different condition, using a continuous, model-independent severity outcome (RMS). | per-unit counterfactual stability |
109+
| **4 · Consensus** | Fuses the network confidence with the physics verdict. **A physical conflict can veto a confident network.** | `HIGH_CONFIDENCE` / `RELIABLE` / `UNCERTAIN` / `MANUAL_REVIEW` |
110+
111+
**The core idea in one sentence:** a bearing defect strikes at a *characteristic frequency* fixed by geometry and shaft speed — a signature that is invariant to load by mechanical law, computable at any operating condition, and never consulted by a plain classifier. CNSD reads it directly and uses it to verify, and if necessary overrule, the network. See [`docs/architecture.md`](docs/architecture.md) for the full design.
112+
113+
---
114+
115+
## 📊 Results
116+
117+
Across three public bearing datasets, we compare reliability estimators at **matched coverage** (every estimator is allowed to trust the same number of predictions; the reported **gap Δ** is `accuracy(trusted) − accuracy(rest)`). Full log: [`EXPERIMENTS.md`](EXPERIMENTS.md).
118+
119+
<div align="center">
120+
<img src="docs/assets/results.png" alt="Physics verification vs. deep ensemble across datasets" width="72%">
121+
</div>
122+
123+
| Dataset | Regime | Physics Δ | Ensemble Δ | Physics vs. Ensemble | Verdict |
124+
|---------|--------|-----------|------------|----------------------|---------|
125+
| **PU** (900→1500 rpm) | shifted | **+0.538** | +0.386 | **p = 0.0032** | ✅ significant win |
126+
| **XJTU-SY** (cross-condition) | shifted | **+0.460** | +0.381 | **p = 0.0028** | ✅ significant win |
127+
| **CWRU** (cross-load) | saturated | +0.189 | +0.074 | p = 0.072 | ⚪ null (reported as control) |
128+
129+
**Noise robustness.** As additive noise increases, the physics layer catches the ensemble's *confident* errors: **100% at 0 dB on XJTU-SY**, ~40% on the saturated CWRU regime.
130+
131+
> **On the CWRU null.** We report it at equal prominence because it is *evidence*, not a weakness. CNSD's thesis is that mechanistic verification helps where the signal is degraded and the network is unsure — and adds nothing where predictions are already reliable. CWRU's cross-load task is comparatively saturated, so the absence of an advantage there is exactly what the mechanism predicts.
132+
133+
---
134+
135+
## ✨ Features
136+
137+
| | Feature | Detail |
138+
|---|---------|--------|
139+
| 🔬 | **Physics verification** | Envelope-spectrum check against BPFO/BPFI/BSF/FTF with harmonic aggregation and FFT-resolution-adaptive tolerance. |
140+
| ⚖️ | **Three-valued verdict** | `CONFIRMED` / `CONFLICT` / `INCONCLUSIVE` — the engine *abstains* when the physics can't adjudicate, instead of guessing. |
141+
| 🧾 | **Explainable output** | Every diagnosis carries a named component, characteristic frequencies, a root-cause statement, and a maintenance action. |
142+
| 🎯 | **Runtime causal reasoning** | Pearl Rung-2 `do(Z)` intervention on a corrected SCM, with CATE, cross-load invariance, and a DoWhy refutation suite. |
143+
| 🔮 | **Counterfactuals** | Pearl Rung-3 on an invertible SCM with a model-independent severity outcome; graceful sensitivity-analysis fallback. |
144+
| 🛡️ | **Physics veto** | A confident-but-unsupported prediction is escalated to human review — the failure a confidence-only pipeline can't catch. |
145+
| 🔌 | **Pluggable physics** | `bearing`, `gear`, and a zero-knowledge `spectral` provider behind one registry; add a mechanism with a single class. |
146+
| 📦 | **Deployable** | Dependency-light core (no TensorFlow needed), one-call API, universal dataset contract, tested and packaged. |
147+
148+
---
149+
150+
## 📥 Installation
151+
152+
**Requirements:** Python ≥ 3.11.
153+
154+
```bash
155+
# from PyPI (core only — numpy / scipy / scikit-learn / pyyaml)
156+
pip install cnsd
157+
158+
# with optional extras
159+
pip install "cnsd[perception]" # 1-D CNN / S-JEPA backbone (TensorFlow, Keras)
160+
pip install "cnsd[counterfactual]" # Rung-3 counterfactuals (DoWhy, pandas, networkx)
161+
pip install "cnsd[all]" # everything
162+
```
163+
164+
**From source** (for development or exact reproduction):
165+
166+
```bash
167+
git clone https://github.com/abhiprd2000/CNSD.git
168+
cd CNSD
169+
pip install -e ".[all]"
170+
# or pin the exact validated environment:
171+
pip install -r requirements.txt
172+
```
173+
174+
> The core install deliberately avoids heavy dependencies: `from cnsd.causal import intervention_effect_of_condition` and the physics engine work **without TensorFlow**. Only training the CNN perception layer needs the `perception` extra.
175+
176+
---
177+
178+
## 🛠️ Usage
179+
180+
CNSD exposes one clean object with four verbs.
181+
182+
### Diagnose
183+
184+
```python
185+
from cnsd import CNSD, Dataset
186+
187+
model = CNSD(config="cnsd_config.yaml") # or CNSD() for defaults
188+
report = model.fit(data).diagnose(data)
189+
190+
report.summary() # decision + verification-rate breakdown
191+
report.root_causes() # human-readable root-cause statements
192+
report.conflicts() # units where physics disagrees with the network
193+
report.verification_rate() # CONFIRMED / CONFLICT / INCONCLUSIVE fractions
194+
report.accuracy_by_verdict() # accuracy within each verdict bucket
195+
```
196+
197+
### Explain — the causal warrant (Rung 2)
198+
199+
```python
200+
effect = model.explain(data) # interventional effect of the operating condition do(Z)
201+
```
202+
203+
### What-if — a counterfactual (Rung 3)
204+
205+
```python
206+
cf = model.what_if(data, intervention={"load": 0.8}, unit_index=0)
207+
# "what severity would this unit have shown at load 0.8?"
208+
```
209+
210+
### Inspect the structural causal model
211+
212+
```python
213+
scm = model.scm_analysis(data) # the fitted SCM behind the causal layers
214+
```
215+
216+
---
217+
218+
## ⚙️ Configuration
219+
220+
CNSD is driven by a single YAML file. The taxonomy, sampling rate, and bearing geometry live in one place, so a new machine is onboarded by editing config — not code.
221+
222+
```yaml
223+
# cnsd_config.yaml
224+
dataset:
225+
name: "cwru"
226+
sampling_rate_hz: 12000
227+
domain:
228+
type: "bearing" # -> selects the bearing physics provider
229+
physics:
230+
parameters:
231+
bearing_type: "6205-2RS"
232+
motor_load_rpm: { 0: 1797, 1: 1772, 2: 1750, 3: 1730 }
233+
taxonomy:
234+
classes:
235+
0: ["Normal", null]
236+
7: ["Outer", 0.007]
237+
# ... (fault family, defect size in inches)
238+
```
239+
240+
---
241+
242+
## 🗂️ Repository structure
243+
244+
```
245+
CNSD/
246+
├── README.md # you are here
247+
├── LICENSE # MIT
248+
├── cnsd_config.yaml # example configuration
249+
├── requirements.txt # exact validated environment
250+
├── setup.py / pyproject.toml # packaging
251+
├── cnsd/ # the framework (installable package)
252+
│ ├── api.py # public CNSD class: diagnose / explain / what_if / scm_analysis
253+
│ ├── builder.py # assembles the five-layer pipeline from config
254+
│ ├── config.py # YAML config loader
255+
│ ├── perception/ # Layer 1 — 1-D CNN + S-JEPA backbone
256+
│ │ └── cnn.py
257+
│ ├── symbolic/ # Layer 2 — physics verification engine
258+
│ │ └── engine.py
259+
│ ├── physics/ # characteristic-frequency kinematics
260+
│ │ ├── bearing.py # envelope analysis, harmonic prominence, adaptive tolerance
261+
│ │ ├── gear.py
262+
│ │ └── providers/ # pluggable domain registry: bearing / gear / spectral
263+
│ ├── scm/ # the corrected structural causal model
264+
│ │ └── graph.py
265+
│ ├── causal/ # Layer 3 — Rung-2 intervention, CATE, invariance
266+
│ │ ├── estimators.py
267+
│ │ └── refutation.py # DoWhy refutation suite (+ permutation fallback)
268+
│ ├── counterfactual/ # Layer 3B — Rung-3 counterfactual
269+
│ │ ├── rung3.py
270+
│ │ └── sensitivity.py # sensitivity-analysis fallback
271+
│ ├── consensus/ # Layer 4 — verdict + confidence -> decision
272+
│ │ └── fusion.py
273+
│ ├── datasets/ # universal dataset contract + loaders
274+
│ │ └── contract.py
275+
│ └── diagnosis/ # orchestration + the DiagnosisReport object
276+
│ ├── system.py
277+
│ └── report.py
278+
├── validation/ # reproducible cross-domain benchmarks
279+
│ ├── multi_seed_benchmark.py # 20-seed physics-vs-ensemble benchmark
280+
│ ├── validate_cwru.py
281+
│ ├── validate_pu.py
282+
│ └── validate_xjtusy.py
283+
├── examples/ # runnable usage demos
284+
├── test/ # test suite (runs without heavy deps)
285+
├── docs/ # documentation + README assets
286+
│ ├── architecture.md # full design document
287+
│ └── assets/ # README images (hero, architecture, results)
288+
├── EXPERIMENTS.md # the validation log (every run, incl. nulls)
289+
├── CONTRIBUTING.md · CODE_OF_CONDUCT.md · SECURITY.md · MAINTAINERS.md
290+
└── CHANGELOG.md
291+
```
292+
293+
---
294+
295+
## 🔁 Reproducing the experiments
296+
297+
Every headline number in [`EXPERIMENTS.md`](EXPERIMENTS.md) comes from the `validation/` scripts. With a dataset prepared under `data/` (see per-script docstrings) and the pinned environment installed:
298+
299+
```bash
300+
# 20-seed, matched-coverage physics-vs-ensemble benchmark
301+
python -m validation.multi_seed_benchmark --dataset pu
302+
python -m validation.multi_seed_benchmark --dataset xjtusy
303+
python -m validation.multi_seed_benchmark --dataset cwru
304+
```
305+
306+
Seeds are fixed (42–61); numbers should match within run-to-run noise. Data paths are overridable via the `CNSD_DATA_CWRU`, `CNSD_DATA_PU`, and `CNSD_DATA_SEU` environment variables.
307+
308+
---
309+
310+
## 🔧 Extending CNSD to new machinery
311+
312+
The physics layer is a registry of **providers**. `bearing` is validated here; `gear` and a zero-knowledge `spectral` fallback ship alongside it. A new mechanism is one class:
313+
314+
```python
315+
from cnsd.physics.providers import register_provider, BaseProvider
316+
317+
class MyProvider(BaseProvider):
318+
def characteristic_frequencies(self, rpm, geometry): ...
319+
def dominant_family(self, envelope_spectrum): ...
320+
321+
register_provider("my_machine", MyProvider)
322+
# now: domain.type = "my_machine" in cnsd_config.yaml
323+
```
324+
325+
> **Scope note.** CNSD is validated on **bearings**, where the characteristic-frequency physics is exact. See [`EXPERIMENTS.md`](EXPERIMENTS.md) for all logs.
326+
327+
---
328+
329+
## 📌 Project status & roadmap
330+
331+
CNSD is **v1.0.0** and one step from public release. The framework, physics/causal/counterfactual layers, and cross-domain benchmarks are complete and tested.
332+
333+
- [x] Five-layer pipeline with physics veto
334+
- [x] Rung-2 intervention + refutation suite; Rung-3 counterfactual
335+
- [x] Three-dataset cross-domain validation (PU, XJTU-SY, CWRU)
336+
- [x] Pluggable provider registry; dependency-light core
337+
- [ ] Gear-mesh provider (mechanism-agnostic verification)
338+
......and continued
339+
See [`CHANGELOG.md`](CHANGELOG.md) for version history.
340+
341+
---
342+
343+
## 🤝 Contributing
344+
345+
Contributions are welcome. The workflow, integrity norms (honest nulls, no fabricated results, auto-generated artifacts), and review process are in [`CONTRIBUTING.md`](CONTRIBUTING.md). In short:
346+
347+
1. Fork → branch → make your change.
348+
2. Run `ruff check . && ruff format .` and `pytest test/` before submitting.
349+
3. Open a PR describing the change; CI must be green.
350+
351+
Please also read the [Code of Conduct](CODE_OF_CONDUCT.md) and, for vulnerabilities, the [Security Policy](SECURITY.md).
352+
353+
---
354+
355+
## 📝 Citation
356+
357+
If you use CNSD, please cite:
358+
359+
```bibtex
360+
@software{cnsd2026,
361+
title = {CNSD: Causal Neuro-Symbolic Diagnosis for Bearing Fault Diagnosis},
362+
author = {Prasad, Abhimanyu and Mahmud, Kazi Tasfin},
363+
year = {2026},
364+
url = {https://github.com/abhiprd2000/CNSD},
365+
version = {1.0.0},
366+
}
367+
```
368+
369+
370+
---
371+
372+
## 📄 License
373+
374+
Released under the [MIT License](LICENSE). © 2026 the CNSD authors.
375+
376+
---
377+
378+
<div align="center">
379+
<sub>Built for machines that shouldn't fail silently.</sub>
380+
</div>

0 commit comments

Comments
 (0)