Metadata-Version: 2.4
Name: actualcauses
Version: 2.0.0
Summary: A package for Actual Causes Identification
Author: Samuel Reyd
License-Expression: MIT
Project-URL: Homepage, https://github.com/SamuelReyd/ActualCausesIdentification
Project-URL: Repository, https://github.com/SamuelReyd/ActualCausesIdentification
Project-URL: Issues, https://github.com/SamuelReyd/ActualCausesIdentification/issues
Keywords: causality,actual causation,explainability,SCM,Halpern-Pearl causes,causes identification,counterfactual reasoning
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: tqdm>=4.62.3
Requires-Dist: numpy>=1.21.0
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Dynamic: license-file

# Actual Causes Identification — actualcauses

## Description

**actualcauses** is a Python package to identify *actual causes* (Halpern–Pearl style) in deterministic or stochastic systems.  
It implements approximate algorithms with adjustable precision introduced in the paper:

> *Searching for actual causes: approximate algorithms with adjustable precision* (Reyd, Diaconescu, Dessalles).  
> Accepted at Transactions on Machine Learning Research (TMLR), 2026 — [OpenReview](https://openreview.net/forum?id=3NlYZPCH9v&).

The package is designed for explainability and causal analysis of autonomous / AI-based systems: given a system model, a context, and a target consequence, it searches for exact or approximate actual causes.

---

## Installation

```bash
pip install actualcauses
```

For local development:
```bash
git clone https://github.com/SamuelReyd/ActualCausesIdentification
cd ActualCausesIdentification
python -m pip install -e ".[dev]"
```

---

## Core concepts
### `SCM`

An SCM object represents a Structural Causal Model:

- V: endogenous variables (names)
- U: exogenous variables (names)
- D: variable domains
- u: a context (values for exogenous variables)
- dag: optional causal graph (for algorithms that exploit structure)
- model: a system model (see [`SystemModel`](src/actualcauses/system_model.py) class) used to evaluate interventions

Once created, you call:

```python
scm.find_causes(...)
scm.show_identification_result()
```

The result is stored on the SCM (`scm.causes`, `scm.witnesses`, `scm.interventions`, `scm.n_calls`, ...) and can be saved with `scm.save(path)` / `SCM.load(path, model)`.

### System models (`SystemModel`, `BaseNumpyModel`, …)

A system model defines how the system responds to interventions.

For simple Python models, subclass `SystemModel` and implement __call__(u, e).

For accelerated / vectorized evaluation, subclass `BaseNumpyModel` (and optionally its stochastic variants).

---
## Quickstart (Suzzy rock-throwing example)
The package provides a ready-to-run example SCM:
```python
from actualcauses import suzzy_example_scm

# Identify causes with two algorithms (Beam Search / ISI)
suzzy_example_scm.find_causes(ISI=False, max_steps=5, beam_size=20, epsilon=0.05, early_stop=False, verbose=2)
suzzy_example_scm.show_identification_result()

suzzy_example_scm.find_causes(ISI=True,  max_steps=5, beam_size=20, epsilon=0.05, early_stop=False, verbose=2)
suzzy_example_scm.show_identification_result()
```

A complete runnable version is in [examples/quickstart.py](examples/quickstart.py).

See the [examples/](examples/) folder for additional scenarios and advanced usage.

---
## Minimal custom SCM example (deterministic)

This mirrors [examples/custom_scm.py](examples/custom_scm.py) (forest fire scenario). The important part is implementing a `SystemModel` and passing it to `SCM`.

```python
from actualcauses import SCM, SystemModel

class ForestFireModel(SystemModel):
    def __init__(self, disjunctive=True):
        super().__init__(
            phi=lambda s: s[-1],  # consequence predicate (example: last variable)
            psi=lambda s: sum(s), # heuristic used by search
        )
        self.disjunctive = disjunctive

    def __call__(self, u, e):
        md, l = u
        e = dict(e)
        MD = e.get("MD", md)
        L  = e.get("L",  l)
        if self.disjunctive:
            FF = e.get("FF", int(L or MD))
        else:
            FF = e.get("FF", int(L and MD))
        self.n_calls += 1
        return [MD, L, FF]

scm = SCM(
    V=("MD", "L", "FF"),
    U=("md", "l"),
    D=(0, 1),
    u=(1, 1),
    model=ForestFireModel(disjunctive=True),
    dag={"MD": [], "L": [], "FF": ["MD", "L"]},
)

scm.find_causes()
scm.show_identification_result()
```

---
## Algorithms

All parameters are passed through `SCM.find_causes(ISI=..., **kwargs)`. Every algorithm has 3 levels of verbosity (`verbose=1`, `2` or `3`).

### MBS — Minimal Beam Search (`ISI=False`, the default)

A beam search over interventions of increasing size, which only needs the oracle `phi` and the heuristic `psi`.

| Parameter | Default | Meaning |
|---|---|---|
| `beam_size` | `10` | Interventions kept from one step to the next (`-1`: all). |
| `max_steps` | `5` | Largest intervention size explored (`-1`: unlimited). |
| `epsilon` | `0.05` | The target is canceled when `phi` is below this value. |
| `early_stop` | `False` | Stop at the first cause (approximate smallest cause). |
| `max_time` | `None` | Time limit in seconds. |
| `prune_supersets`, `expand_causes`, `include_actual_initial` | `True`, `False`, `False` | Ablations of the minimality mechanisms (paper, Section 5.5). |

With `beam_size=-1` and `max_steps=-1`, MBS is exhaustive.

### ISI — Iterative Sub-Instance identification (`ISI=True`)

Uses the causal graph (`dag`) to split the search into subproblems, starting from the parents of the target and “causally backtracking” from every canceling intervention (paper, Section 4.2).

| Parameter | Default | Meaning |
|---|---|---|
| `exhaustive` | auto | Solve each subproblem by enumeration (exact ISI). Automatically on unless a `beam_size` other than `-1` or a `max_steps` limit is given, so **ISI is exact by default**. |
| `beam_size`, `max_steps`, ... | | When not exhaustive, subproblems are solved with MBS using these parameters. |
| `minimal_only` | `False` | Inner MBS keeps only minimal causes, and only those are expanded (faster, not complete). |
| `assign` | `"greedy"` | How the inner MBS completes a partial intervention: `"greedy"` (keeps the cancellation) or `"naive"` (actual values, no extra call, may return non-canceling pairs). |
| `do_decomposition` | `True` | Assemble expansions from independent singletons when the graph allows it (exact optimisation). |
| `backtrack` | `"subsets"` | `"subsets"` or `"singleton"` expansion of each cause. |
| `max_backtrack` | `-1` | Maximum number of expansion levels (`-1`: unlimited). |
| `sample_backtrack` | `-1` | Keep only this many random subproblems per level (`-1`: all). |
| `mbs_verbose`, `mbs_early_stop` | | Override `verbose` / `early_stop` for the inner MBS only. |

Exact ISI returns exactly the HP-causes (paper, Appendix G). Its cost follows the structure of the graph rather than the size of the model; for models whose causes are all singletons, MBS can be cheaper.

### LUCB — adaptive sampling for stochastic models

`LUCBNumpyModel(V, t, lucb_params)` estimates `phi` and `psi` with confidence bounds and only resamples the interventions whose status is still ambiguous (paper, Section 4.3). `lucb_params` contains `beam_size`, `a` (canceling threshold), `cause_eps`, `non_cause_eps`, `beam_eps` (slacks), `delta` (confidence level), `max_iter` (average budget per intervention), `init_batch_size`, `batch_size`, and optionally `seed` and `verbose`. `AverageNumpyModel` is the fixed-budget baseline.

--- 

## Examples

The [examples/](examples/) folder contains scripts intended to be read and modified:

- [quickstart.py](examples/quickstart.py) — Minimal end-to-end run using suzzy_example_scm.
- [custom_scm.py](examples/custom_scm.py) — Implement a custom basic SCM and identify actual causes.
- [custom_heuristic.py](examples/custom_heuristic.py) — Use different heuristics (psi) with a fixed system model and SCM.
- [vectorized_system_model.py](examples/vectorized_system_model.py) — Use BaseNumpyModel to accelerate identification by vectorizing intervention evaluation.
- [stochastic_system_model.py](examples/stochastic_system_model.py) — Stochastic evaluation with a naive average estimator and the LUCB estimator.

Run any example from the repository root, e.g.:
```bash
python examples/quickstart.py
```

The code of the paper's experiments is available at [SearchingForCauses](https://github.com/SamuelReyd/SearchingForCauses).

--- 
## License

MIT License. See [LICENSE](LICENSE).

---
## Citation

If you use this software in academic work, please cite:

> Reyd, S., Diaconescu, A., & Dessalles, J.-L. (2026). Searching for actual causes: Approximate algorithms with adjustable precision. *Transactions on Machine Learning Research* (accepted).

````bibtex
@article{reyd2026searchingactualcauses,
  title        = {Searching for actual causes: Approximate algorithms with adjustable precision},
  author       = {Samuel Reyd and Ada Diaconescu and Jean-Louis Dessalles},
  journal      = {Transactions on Machine Learning Research},
  year         = {2026},
  url          = {https://openreview.net/forum?id=3NlYZPCH9v&}
}
```
