// counterfactual_reasoning.mbt — Counterfactual reasoning via Pearl's
// three-step procedure (v0.99.0).
//
// Pearl's three-step procedure for counterfactual queries (Pearl 2009
// Chapter 9):
// 1. Abduction: Given observed evidence x_obs, infer the noise
// values ε = (I - B) · x_obs.
// 2. Action: Modify the SCM (do-operator) to apply the
// counterfactual intervention do(X_i = c).
// 3. Prediction: Use the modified SCM with the SAME noise ε to
// compute the counterfactual outcome:
// X_i = c
// X_k = intercept[k] + Σ_j b_mutilated[k][j] · X_j + ε_k
//
// Scope of v0.99.0:
// - scm_abduct_noise: extract ε from observed x_obs (step 1)
// - scm_counterfactual: full three-step procedure → counterfactual
// x vector
// - scm_counterfactual_query: shortcut for "what would X_j be if
// we had set X_i = c?" given observed x_obs.
//
// Reference: Pearl 2009 "Causality" Chapter 9 (counterfactuals).
///|
/// Abduction step: extract noise values ε = (I - B) · x_obs from the
/// observed x_obs. ε represents the exogenous randomness that
/// generated the observation.
pub fn scm_abduct_noise(
scm : LinearGaussianSCM,
x_obs : Array[Float],
) -> Array[Float] {
let n = scm.n
let eps : Array[Float] = Array::make(n, 0.0F)
for k in 0.. Array[Float] {
let n = mscm.n
let x : Array[Float] = Array::make(n, 0.0F)
for k in 0.. Array[Float] {
// 1. Abduction.
let eps = scm_abduct_noise(scm, x_obs)
// 2. Action.
let mscm = scm_intervene(scm, intervention_idx, intervention_value)
// 3. Prediction.
scm_predict_counterfactual(mscm, eps)
}
///|
/// Counterfactual query for a single target: "what would X_j be if
/// we had set X_i = c, given observed x_obs?" Returns the
/// counterfactual value of X_j (length 1).
pub fn scm_counterfactual_query(
scm : LinearGaussianSCM,
x_obs : Array[Float],
intervention_idx : Int,
intervention_value : Float,
target_idx : Int,
) -> Float {
let x_cf = scm_counterfactual(
scm, x_obs, intervention_idx, intervention_value,
)
x_cf[target_idx]
}