# Collective thermodynamic incompatibility: exact activity models

Companion to paper 15, *Collective thermodynamic incompatibility of
autocatalytic cores*. Manuscript SHA-256:
`6b1d307cee4aa5f7cb65edbbc36b3071e2a62851bedd27741451321209cba972`.

The example teaches a nonlinear consistency failure. Several cores may each
produce all their internal species, and even pass global direction and
independent-complex tests, while no single physical activity vector makes
them productive together. It implements the path-with-shortcut construction,
constructive graded assemblies, bounded triangle-fan elimination, and the
paper's robust three-core example. The original PDF is unchanged.

## Run

Python 3.11 or newer:

```sh
python -m venv .venv
# 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
```

Exact polynomial root isolation can take several minutes for the complete
three-branch family. Tests deliberately repeat that decision independently.
SymPy supplies exact rational polynomial arithmetic and real-root isolation;
Matplotlib supplies illustrations. Neither plots nor floating-point grids
decide compatibility. Fractions in JSON are strings to preserve exact values.

## Model and inputs

Editable inputs are at the very top of `example.py`. Activities and factors
are dimensionless, with the manuscript's standard-state conventions. Use
`Fraction` values, integers, or exact decimal strings such as `Q('0.65')`.
The defaults reproduce the manuscript, rather than measured chemistry.

`PairCore(s,t)` represents Xs <-> Xt and Xt <-> 2Xs, with currents
p = xs - xt and q = xt - xs^2. Its production residuals are (2q-p, p-q).
Productivity is exactly g(xs) < xt < f(xs), where f(x)=(x+x^2)/2 and
g(x)=(x+2x^2)/3. Two steps have upper bound f(f(x)), strictly below g(x)
on (0,1). A path with a shortcut therefore conflicts on the entire positive
orthant, even though every proper subfamily has a witness in [0.9,1].

`PairAssembly.graded_state()` propagates integer ranks, increasing by one
along every edge, and constructs rational activities. A return value of
`None` means only that this sufficient grading certificate is unavailable.
It does not diagnose general incompatibility. `ShortcutFamily` has a separate
analytic negative certificate and explicit witnesses for both relaxations.
The independent-complex assignment uses one value for each doubled complex
shared across edges; it deliberately drops the physical square relationship.

`Branch` represents B <-> C (factor x) and C <-> mA (factor y).
`Fan` supplies the shared A <-> B reaction (factor b0), its single shared
current j=b0(a-b), and the shared activity boxes. Private currents are
p=x(b-c) and q=y(c-a^m); production is (mq-j, j-p, p-q).
Compatibility requires all three entries to be strictly positive on every
selected branch. This also implies forward currents.

Private elimination gives three lower and two upper responses for B as
polynomials in A. `witness_at(a)` intersects these strict bounds with the
closed B box, reconstructs each private C, and independently audits all
physical residuals. Point boxes are supported. `decide()` checks A endpoints
and one rational sample in every remaining exact sign cell, using a
square-free polynomial containing every inequality root and both endpoints.
An internal root cannot satisfy all strict inequalities. Consequently a
negative answer is exhaustive, not a failed numerical search.

The root product degree guard returns `not_computed`; it must not be treated
as incompatibility. It is a degree guard, not a wall-clock timeout. SymPy's
root isolation is trusted software, not extracted or verified Lean code.

## Reuse

```python
from fractions import Fraction as Q
from example import Box, Branch, Fan, PairAssembly, PairCore

model = Fan(Q(1), Box(Q('.7'), Q('.9')), Box(Q('.5'), Q('.85')), (
    Branch(3, Q(4), Q(2), Box(Q('.65'), Q('.71'))),
    Branch(4, Q('.5'), Q(1), Box(Q('.65'), Q('.76'))),
))
answer = model.decide()
if answer['status'] == 'compatible':
    assert model.audit(answer['state'])

# Sweep a kinetic factor by replacing one immutable component.
from dataclasses import replace
for factor in (Q(1), Q(2), Q(4)):
    candidate = replace(model, branches=(
        replace(model.branches[0], forward_factor=factor), model.branches[1]))
    print(factor, candidate.decide()['status'])

diamond = PairAssembly(4, tuple(PairCore(s,t) for s,t in
    ((0,1), (0,2), (1,3), (2,3))))
print(diamond.graded_state())
```

Selecting a subfamily removes only its omitted production requirements.
All species, boxes, factors, and the shared reaction remain. Omitted C
coordinates remain inside their boxes; their currents need not vanish.
The empty selected family is vacuously compatible in the nonempty boxes.

`robustness_certificate()` is intentionally tied to the fixed manuscript
reference fan, independently of editable defaults. It checks exact deletion
and relaxation margins, the two analytic negative-margin bounds, and the
uniform 5*rho residual perturbation estimate for rho <= 10^-6. It certifies
a whole cube of seven kinetic factors and ten box endpoints, keeping integer
gains fixed. Larger radii return `not_certified`, not a counterexample.

## Outputs and evidence

`results.json` records every fan subfamily, reconstructed compatible states,
shortcut witnesses, and the robust reference certificate. `console.txt`
summarizes the decisions. `compatibility.png` and `.svg` illustrate the exact
response obstruction; the fan plot shows A from 0.8 to 0.9, whereas the exact
decision covers the entire configured box. `run_metadata.json` records the
manuscript, source and output hashes.

Seven test groups check the symbolic response identity, 900 direct pair
states, arbitrary-order deletion witnesses, graded and cyclic assemblies,
108 private-elimination cases, all eight reference fan subfamilies, strict
boundaries/point boxes, resource-limit semantics, and exact robustness
margins. Full-family absence uses exact sign cells and a separate universal
negative-margin argument; it is not inferred from tests alone.

These are instantaneous compatibility models, not ODE reactors, equilibrium
solvers, persistence claims, or predictions of sustained growth. An open
food interpretation can balance Xt+F <-> 2Xs with food activity one, but
this example does not claim closed-system growth. Unbounded conflict order
does not itself imply computational hardness or violate fixed-dimensional
Helly bounds. No empirical frequency or measured rate constants are assumed.

License: MIT is proposed, pending the owner's choice. No license grant is
asserted by this package.
