Skip to content

Commit c94eb94

Browse files
authored
fix: rung-3 continuous degradation outcome (RMS) for unit-level counterfactuals (#20)
1 parent ea06e38 commit c94eb94

2 files changed

Lines changed: 14 additions & 11 deletions

File tree

cnsd/counterfactual/rung3.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ def dowhy_gcm_available():
2323
return False
2424

2525

26-
def build_scm(condition, signal_feature, fault_outcome):
27-
"""Fit an InvertibleStructuralCausalModel over the corrected CNSD graph:
28-
Z (condition) -> X (signal feature) -> Y (fault outcome)
26+
def build_scm(condition, signal_feature, degradation_outcome):
27+
""" Z (condition) -> X (signal feature) -> Y (degradation outcome)
2928
Z -> Y
30-
Returns a fitted SCM ready for counterfactual queries, or None if DoWhy is
31-
unavailable.
29+
Y must be a CONTINUOUS degradation quantity (e.g. vibration RMS), not a
30+
binary fault label - a binary Y collapses unit-level counterfactuals to
31+
flips and cannot express gradual, direction-sensible deltas.
3232
"""
3333
if not dowhy_gcm_available():
3434
return None
@@ -40,7 +40,7 @@ def build_scm(condition, signal_feature, fault_outcome):
4040
{
4141
'Z': np.asarray(condition, float),
4242
'X': np.asarray(signal_feature, float),
43-
'Y': (np.asarray(fault_outcome) > 0).astype(float),
43+
'Y': np.asarray(degradation_outcome, float),
4444
}
4545
)
4646
# operational graph over the measured descriptor X (condition Z -> descriptor
@@ -57,7 +57,8 @@ def counterfactual_for_unit(scm, observed_row, condition_cf):
5757
5858
observed_row : dict with the unit's factual {'Z','X','Y'}
5959
condition_cf : the counterfactual operating condition (do(Z := condition_cf))
60-
Returns the factual vs counterfactual fault indication for this unit.
60+
Returns the factual vs counterfactual degradation severity (vibration RMS)
61+
for this unit - a gradual, direction-sensible delta, not a fault flip.
6162
"""
6263
import pandas as pd
6364
from dowhy import gcm

cnsd/diagnosis/system.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
Rung-2 (3) -> counterfactual Rung-3 (3B) -> consensus (4).
1414
"""
1515

16-
from cnsd.causal import intervention_effect_of_condition, signal_kurtosis
16+
from cnsd.causal import compute_vibration_rms, intervention_effect_of_condition, signal_kurtosis
1717
from cnsd.consensus import fuse
1818
from cnsd.counterfactual import build_scm, what_if
1919
from cnsd.datasets import Dataset
@@ -41,7 +41,8 @@ def fit(self, data: Dataset, epochs=30):
4141
self.symbolic = self._build_symbolic(data)
4242
# fit the Rung-3 SCM (graceful None if DoWhy absent)
4343
feat = signal_kurtosis(data.X)
44-
self.scm = build_scm(data.cond, feat, data.y)
44+
rms = compute_vibration_rms(data.X)
45+
self.scm = build_scm(data.cond, feat, rms)
4546
self._fitted = True
4647
return self
4748

@@ -100,7 +101,8 @@ def condition_effect(self, data: Dataset):
100101
return intervention_effect_of_condition(data.y, data.cond)
101102

102103
def what_if(self, data: Dataset, unit_index, condition_cf):
103-
"""Rung-3 counterfactual for one unit (sensitivity fallback w/o DoWhy)."""
104+
"""Rung-3 counterfactual degradation severity (RMS) under do(Z) for one
105+
unit (sensitivity fallback w/o DoWhy)."""
104106
# Future refactor: process the full condition_cf dictionary for multiple interventions.
105107
# Currently, the core SCM supports a single Z variable, so we extract the first value.
106108
if isinstance(condition_cf, dict):
@@ -115,5 +117,5 @@ def what_if(self, data: Dataset, unit_index, condition_cf):
115117
cf_val,
116118
scm=self.scm,
117119
X_sample=data.X[unit_index].flatten(),
118-
factual_y=(data.y[unit_index] > 0),
120+
factual_y=compute_vibration_rms(data.X[unit_index : unit_index + 1])[0],
119121
)

0 commit comments

Comments
 (0)