# Complete irreducible RAF enumeration with bounded suppliers

A RAF is a nonempty reaction set whose reactants can be built from food and
whose reactions each have at least one catalyst in that food closure. An
irreducible RAF contains no smaller RAF. The problem here is to find *all* of
those inclusion-minimal sets while retaining the original reaction identifiers.

This example implements the supplier catalogue and independent-component
algorithms in *Complete enumeration of irreducible autocatalytic sets with bounded
supplier choice*, 14 September 2026. It uses the six-reaction worked gate system,
the validation counterexample, the coupled and independent scaling families,
and the deterministic chain. The manuscript SHA-256 is recorded in the source.
The implementation is original code written from the paper's definitions and
algorithm, not copied or extracted from its Python or Lean sources.

## Model and editable inputs

USER INPUTS contains the food species, literal reaction tuples, deletion costs,
required/forbidden/deleted identifiers, independent availability probability,
family sizes, and computation budgets. Reaction identifiers are distinct integers;
species are nonempty strings. Each tuple is `(id, reactants, products, catalysts)`.
Reactants are a joint requirement; catalysts are alternatives. Empty product and
catalyst sets are allowed. Empty catalyst lists normally cause a reaction to be
pruned. Multiple reactions with identical chemistry retain distinct identifiers.

There are no concentrations, stoichiometric multiplicities, kinetic constants,
or units in this structural predicate. In particular catalysts do **not** gate
food closure: `f -> x` catalysed by `x` is a structural RAF. This is not a claim
that its initial catalysed firing is physically possible.

`ReactionSystem` computes synchronous food closure and maximal-RAF pruning.
`SupplierOptions` builds producer choices and effective catalyst alternatives,
collapsing food-catalyst alternatives to one canonical witness. `SupplierEnumerator`
prunes every resolution, finds sink strongly connected components in the graph
whose arrows run from consumers to suppliers, validates each candidate against
the original system, and deduplicates it. The component method includes nonfood
reactant, product, and effective-catalyst incidences when deciding independence.
It returns a `Catalogue` with exact members, frequencies, and operation counters.

The production enumeration never searches reaction subsets. The optional exact
cut and availability queries do so only below `EXHAUSTIVE_REACTION_CAP=18`.
Enumeration checks its resolution budget before starting; exceeding a budget
raises a clear error and never returns a partial catalogue as complete.

## Run and reuse

Python 3.11 or newer. All model and enumeration code uses the standard library;
Matplotlib is required only by the figure-producing entry command. The dependency
file pins that direct dependency, not its transitive dependencies.

Windows PowerShell:

```powershell
py -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe example.py --output outputs
.\.venv\Scripts\python.exe -m unittest -v
```

POSIX:

```sh
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python example.py --output outputs
.venv/bin/python -m unittest -v
```

Change `REACTIONS` and `FOOD` together with the relevant cost/query identifiers,
or provide a JSON model following the included `model.json` schema:

```sh
python example.py --input my_model.json --output my_results
```

Custom-input mode skips the preset-specific cost and deletion queries, but still
runs the named paper comparison families. For a model-only calculation, import:

```python
from example import Reaction, ReactionSystem, SupplierEnumerator

model = ReactionSystem({'food'}, [
    Reaction(10, {'food'}, {'x'}, {'x'}),
    Reaction(20, {'x'}, {'y'}, {'x'}),
])
catalogue = SupplierEnumerator().catalogue(model)
print(catalogue.members)                     # ({10},)
print(catalogue.filter(forbidden=[10]))      # no surviving irrRAF
```

The graph traversal is iterative so long chains do not require raising Python's
recursion limit. Runtime remains sensitive to supplier ambiguity, and output
can itself be exponential. Python sets deduplicate candidate masks in this
implementation; it does not reproduce the manuscript's trie storage design.

## What to expect

In the default system, reactions 0/1 supply x1, reactions 2/3 supply x2, and gates
4/5 build the shared catalyst z2. The four irrRAFs choose one producer from each
pair and contain both gates. Frequencies are `(2,2,2,2,4,4)`. Requiring 0 and
forbidding 2 selects `{0,3,4,5}`; deleting 0 leaves two alternatives.

The minimal destructive cuts are `{4}`, `{5}`, `{0,1}`, and `{2,3}`. The supplied
costs give optimal cut cost 5. Weighted greedy chooses 3, 0, then 1, for cost 6:
it is a valid approximation, not an exact optimizer. Independent half-probability
reaction availability gives RAF survival probability 9/64. These are probabilities
of static structural availability, not rates of growth or persistence.

The validation counterexample produces two candidate occurrences, rejects the
two-reaction one as originally reducible, and retains only reaction 0. Checking
whether an immediate one-reaction deletion is itself a RAF is insufficient:
the correct test asks whether *any* RAF remains, using maximal-RAF pruning.

For 64 independent binary-choice modules, global supplier combinations number
2^64, but component enumeration inspects only 128 resolutions and returns 128
irrRAFs. A 64-reaction deterministic chain needs 64 generation stages yet has
zero supplier excess and only one resolution. Coupled gates retain exponential
output growth: component splitting cannot remove genuine dependence.

Outputs include a membership matrix, a resolutions-versus-size plot, the complete
catalogue CSV, scaling counters, the input JSON, results, actual console output,
source/environment/output hashes, and PNG/SVG figures. Figures consume the same
calculated catalogue and counter records as the tables.

## Verification and scope

`python -m unittest -v` compares both enumeration paths against an independent
exhaustive oracle on bounded random systems (fixed seed 14092026). It also checks
all 64 restrictions of the worked system, paper counterexamples, the exact cuts,
greedy cost, independent availability states, canonical food catalysts, empty
products, preserved identifiers, a long chain, and budget failures. The oracle
uses a separately written asynchronous closure and direct subset minimality.
These finite tests are exact computations; they do not compile Lean or verify
the universal complexity theorem. The code buffers the complete catalogue and
does not promise polynomial delay or memory independent of supplier excess.

The probabilities and catalogue say nothing by themselves about kinetics,
thermodynamic realizability, genetic interventions, or biochemical productivity.
Deleting a whole reaction is not the same operation as removing a catalysis edge.

MIT is proposed for this new original code pending the owner's licensing choice;
no repository-wide software license was present. No manuscript, pre-existing
author code, or third-party dependency is relicensed by this package.
