# Exact interfaces for modular autocatalytic networks

This example accompanies paper 11, **Exact Interfaces for Modular Autocatalytic
Networks**. A module may be substituted safely when its interface retains every
boundary state that its neighbours can observe. A standalone score can lose
that information. Four independently reusable components demonstrate the
difference with literal stoichiometric, linear kinetic and finite relational
models. They are not parts of a proposed universal reaction-network framework.

Source manuscript SHA-256:
`62106305bc8d8f4652be7989329b13ed300007da7596d596e7e0cdc1ea52ff61`.
The manuscript is unchanged. This is original explanatory Python, not a copy
or re-execution of the manuscript's Lean artifacts.

## Run and edit

Python 3.11 or newer:

```sh
python -m venv .venv
# Windows PowerShell: .venv\Scripts\Activate.ps1
# macOS/Linux: source .venv/bin/activate
python -m pip install -r requirements.txt
python example.py --output outputs
python -m unittest -v
```

All run settings are immediately below the module docstring. Supply rational
or decimal strings, such as `"1/5"` or `"0.2"`. Arithmetic uses `Fraction`;
only plotting converts to floating point. No external data or random simulation
is required. The tests use a fixed seed to select additional rational cases.

Tail rates and degradation have units inverse time; concentrations use any
consistent unit. For example, choosing hours and millimolar makes currents
millimolar per hour. The four-edge tail inputs are illustrative positive values,
not fitted experimental constants. The amplification and star counterexamples
are the paper's exact values. Change chain lengths by supplying one forward and
backward rate per edge and one degradation rate per internal node.

## 1. Keep the price profile, not only the amplification factor

`PriceProfile` holds species-by-reaction nonnegative input and output matrices
A and B. A threshold q is feasible when a nonzero nonnegative flux x satisfies
(B-qA)x >= 0. Normalizing sum(x)=1 does not change existence. When it is
infeasible, the alternative is a nonnegative price vector p with
(B-qA)^T p **strictly negative in every reaction coordinate**.

`decide(q)` enumerates exact vertices and returns either a feasible flux with
its residual or a strict price witness with its positive margin. In particular,
the feasible threshold endpoint stays included. A weak price inequality is
not enough to refute feasibility. `contains(q, p)` checks a proposed price.
`compose(other)` concatenates reaction columns, so the composite price profile
is precisely the intersection of the two profiles. Species row orders must
match; give distinct species a shared union row order with zero padding first.

The paper's N1={A->B, B->4A} and N2={A->4B, B->A} both have maximum amplification
2. N1+N1 still has amplification 2, but N2+N1 reaches 4. At q=3 the strict price
ratios pB/pA are (4/3,3) for N1 and (1/3,3/4) for N2. Their disjointness explains
why no composite impossibility certificate exists. Open endpoints matter.

```python
from example import PriceProfile
n = PriceProfile(((1, 0), (0, 1)), ((0, 4), (1, 0)))
certificate = n.decide("3")
print(certificate["prices"], certificate["strict_margin"])
```

This is an exact small-network reference solver, not a scalable LP package or a
general exact MAF optimizer. Vertex enumeration has combinatorial cost. For
large problems, replace the vertex-search backend and retain independent
certificate checks. A missing numerical witness must never mean infeasibility.

## 2. Replace a passive chain and recover its hidden state

`PassiveTail` represents X0 <-> ... <-> Xn with positive reversible unary rates
and internal degradation. It exposes a `TwoPort` with four coefficients:

```
j_left  = (forward + left_leak) X - backward Y
j_right = forward X - (backward + right_leak) Y
```

The left current leaves the left endpoint; the right current enters the right
endpoint. Negative values reverse that direction. `port()` eliminates internal
steady balances exactly. `reconstruct(X,Y)` recovers the unique internal
concentrations, and `audit(X,Y)` checks every original balance and both boundary
currents. Their difference equals the summed internal degradation loss.

For the default chain, the four coefficients are 800/1489, 400/4467, 888/1489
and 587/4467. All three internal balance residuals are exactly zero. The sweep
varies internal degradation from zero to four times the default. Zero is a
validated limiting extension of the paper's strictly positive degradation
assumption; the elimination denominator stays positive because reversible
rates remain positive.

```python
from example import PassiveTail
tail = PassiveTail((2, 3), (1, 2), ("1/5",))
port = tail.port()
left_current, right_current = port.currents("1", "1/5")
full_steady_state = tail.reconstruct("1", "1/5")
```

Use the two-port currents as terms in endpoint **steady-state** balances. The
tests add external forcing and endpoint degradation, then independently solve
both the full chain and the compressed two-species system. Every endpoint and
reconstructed internal state agrees exactly. This is a steady-state interface;
it does not preserve the chain's time-dependent response or relaxation modes.

## 3. Add degradation branch loads at a shared root

`Branch` holds a Metzler internal block Ai, a return coupling bi and a strictly
positive source coupling ci. It checks the positive solve witness Ai ui=-ci.
`DegradationStar` combines arbitrary-sized branches around one root diagonal m.
Each branch contributes bi*ui to kappa=m+sum(bi*ui). Its certificate returns a
positive vector with Mv strictly positive, zero, or strictly negative according
to the sign of kappa. The root perturbation makes the strict cases strict in
every coordinate; simply returning (1,u) would leave the internal rows zero.

The paper's m=-1, Ai=-15, bi=ci=3 gives load 3/5 per branch. One branch has
kappa=-2/5 and extinguishes; two branches have kappa=1/5 and grow. Keeping the
same root diagonal is part of this assembly operation. It is not equivalent
to summing two complete reaction systems while silently duplicating the root.

This component accepts an onset matrix, not a nonlinear reactor chemistry.
Rates, couplings and the root diagonal must be derived consistently from the
user's source. The example does not implement species-specific control design
or use a determinant sign as a general spectral classifier.

## 4. Expose finite traces and validate join-tree gluing

`FiniteRelation` stores all allowed tuples on an ordered variable scope. It can
project out internal coordinates, test compatibility with an outside relation,
and produce a singleton separating context for two different traces. A scope
is part of the data, so local predicates cannot secretly read other variables.

`JoinNode` computes finite separator messages bottom-up. It first checks that
every variable shared between a child's subtree and the rest of the tree
passes through that child's separator. Child separators must also belong to
the parent bag, a deliberate restriction of this implementation. `global_trace`
is a separate exhaustive oracle using one assignment for the entire tree.

The positive example joins a=x with b=1-x and exposes the exact binary x trace.
The negative example has independent children forcing x=0 and x=1 while hiding
x from the separators. `message` rejects it. Routing x correctly yields the
empty relation, matching the globally inconsistent source. A cardinality
summary also fails: traces {0} and {1} each have size one, but context {0}
accepts only the first.

This is a finite-domain engine, not a grid approximation to a continuous
chemical trace. Its message width bounds the number of exposed coordinates;
it does not imply small tables or polynomial runtime. Use finite relations
directly or implement another exact relation language for continuous models.

## Outputs and checks

`results.json` contains exact rational price/flux witnesses, reconstructed
states, internal balances, star certificates and finite trace decisions.
`tail_sweep.csv` retains rational values. Two figure pairs (PNG/SVG), console
output and metadata with source/manuscript/output hashes accompany them.
Scientific JSON/CSV is deterministic; SVG creation metadata may differ.

Seven test groups check 275 two-reaction thresholds against their analytic
square-root condition, price-profile intersections, 96 chains against direct
linear solves, replacement in 16 external environments, multi-coordinate
star certificates including criticality, all 256 pairs of binary child
relations, and all eight traces against all eight contexts on a three-element
boundary. Strict/weak confusion, invalid rates and failed gluing are regressions.

These examples implement the paper's central composition and information-loss
mechanisms. They do not reproduce every theorem: catalyst-aware core extraction,
ownership forests, actuator placement, the Type-II branch trap and thermodynamic
toric obstructions are outside this package. The companion thermodynamic paper
has its own runnable example. Tests are finite checks, not substitutes for the
paper's formal proofs or an independent validation of its Lean build.

## License

MIT is proposed for this original example code, pending the owner's license
choice. No license grant is asserted here. This does not change the license of
the manuscript or any third-party dependency.
