rafkit 0.7.0__tar.gz → 0.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rafkit-0.7.0/src/rafkit.egg-info → rafkit-0.7.1}/PKG-INFO +100 -4
- {rafkit-0.7.0 → rafkit-0.7.1}/README.md +99 -3
- {rafkit-0.7.0 → rafkit-0.7.1}/pyproject.toml +1 -1
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/__init__.py +1 -1
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/binary_polymer.py +35 -15
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/templated_polymer.py +13 -7
- {rafkit-0.7.0 → rafkit-0.7.1/src/rafkit.egg-info}/PKG-INFO +100 -4
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_templated_polymer.py +30 -9
- {rafkit-0.7.0 → rafkit-0.7.1}/LICENSE +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/setup.cfg +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/andl.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/autocatalysis.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/catalysis.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/complementary_polymer.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/crs.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/dilution.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/firing_disk.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/gillespie.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/inhibition.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/network.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/permeation.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/pnml.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/raf.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit/thermo.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit.egg-info/SOURCES.txt +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit.egg-info/dependency_links.txt +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit.egg-info/requires.txt +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/src/rafkit.egg-info/top_level.txt +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_andl.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_autocatalysis.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_complementary_polymer.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_crs.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_dilution.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_firing_disk.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_gillespie.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_inhibition.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_permeation.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_pnml.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_published_examples.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_raf.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_seeding.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_thermo.py +0 -0
- {rafkit-0.7.0 → rafkit-0.7.1}/tests/test_thermo_kinetics.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rafkit
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Autocatalytic (RAF) sets in catalytic reaction networks: maximal RAFs, irreducible cores, and Kauffman binary polymer models.
|
|
5
5
|
Author: James P. Galasyn, Claude Théodore
|
|
6
6
|
License: MIT
|
|
@@ -39,8 +39,9 @@ Dynamic: license-file
|
|
|
39
39
|
[](https://doi.org/10.5281/zenodo.21954795)
|
|
40
40
|
|
|
41
41
|
Autocatalytic (RAF) sets in catalytic reaction networks — maximal RAFs, irreducible
|
|
42
|
-
cores,
|
|
43
|
-
[CatReNet](https://github.com/husonlab/catrenet)
|
|
42
|
+
cores, binary polymer chemistries whose catalysis is drawn, structured or templated, and
|
|
43
|
+
interoperability with [CatReNet](https://github.com/husonlab/catrenet) and the Petri net
|
|
44
|
+
tools.
|
|
44
45
|
|
|
45
46
|
Pure Python and NumPy. No Java, no GUI, no install beyond `pip`.
|
|
46
47
|
|
|
@@ -115,10 +116,17 @@ counted as **one** catalysed reaction, not two. Use `net.catalysis_level` — no
|
|
|
115
116
|
| `is_food_catalysed` | whether a core runs on food catalysis alone, and so carries no heredity |
|
|
116
117
|
| `core_raf` / `has_unique_irraf` | Huson, Xavier & Steel's polynomial test for a *unique* irreducible RAF |
|
|
117
118
|
| `catalytically_reachable` | what can be made without any spontaneous reaction |
|
|
118
|
-
| `binary_polymer` | Kauffman
|
|
119
|
+
| `binary_polymer` | Kauffman's K-BPM: catalysis by coin flip, with optional cleavage |
|
|
120
|
+
| `complementary_polymer` | Serra & Villani's C-BPM: catalysis by active-site complementarity, not by coin flip |
|
|
121
|
+
| `firing_disk_polymer` | their firing-disk construction — a chemistry *grown* from a seed, closed under its own production |
|
|
122
|
+
| `templated_polymer` / `templated_catalysts` | templated ligation: a species catalyses `a + b -> ab` iff it carries the product's complement — nothing is drawn |
|
|
123
|
+
| `degree_preserving_null` / `motif_matched_null` | same reactions, rewired catalysis — the nulls a structured chemistry is compared against |
|
|
124
|
+
| `matched_f_random` / `matched_f_cbpm` | the K-BPM and C-BPM at a structured chemistry's catalysis level |
|
|
125
|
+
| `catalysis_motifs` | what a chemistry and its nulls are compared on |
|
|
119
126
|
| `ReactionNetwork` | arbitrary catalytic reaction systems, same protocol |
|
|
120
127
|
| `read_crs` / `write_crs` | CatReNet's CRS interchange format |
|
|
121
128
|
| `to_pnml` / `write_pnml` | PNML export (ISO/IEC 15909-2) for the Petri net ecosystem |
|
|
129
|
+
| `to_andl` / `write_andl` | ANDL export with mass-action rate constants — runs unchanged in Spike |
|
|
122
130
|
| `simulate` | Gillespie direct method — watch subRAFs seed themselves into existence |
|
|
123
131
|
| `max_urafs` | uninhibited RAFs, when a molecule can prevent a reaction |
|
|
124
132
|
| `run_serial_dilution` / `run_cstr` | dilution protocols for growing–dividing compartments — **not a RAF algorithm**, see below |
|
|
@@ -133,6 +141,67 @@ counted as **one** catalysed reaction, not two. Use `net.catalysis_level` — no
|
|
|
133
141
|
Every algorithm carries hand-computed known-answer tests, because a RAF algorithm that
|
|
134
142
|
is subtly wrong produces plausible numbers rather than errors.
|
|
135
143
|
|
|
144
|
+
## Catalysis three ways: drawn, structured, and templated
|
|
145
|
+
|
|
146
|
+
`binary_polymer` is Kauffman's K-BPM: every string up to `max_len` exists, and whether a
|
|
147
|
+
species catalyses a reaction is an independent coin flip at probability `p`. Two more
|
|
148
|
+
ensembles keep that reaction set and change the one thing that matters — *which* catalyst
|
|
149
|
+
catalyses *which* reaction.
|
|
150
|
+
|
|
151
|
+
**`complementary_polymer`** is Serra & Villani's C-BPM (*Entropy* 28(2), 184, 2026),
|
|
152
|
+
reproduced rather than invented. A catalyst carries an active site — a substring of itself,
|
|
153
|
+
3–4 residues long by default — and acts on whatever is complementary to that site. A
|
|
154
|
+
K-catalyst's targets are independent draws; a C-catalyst's targets all share one template,
|
|
155
|
+
so they are structurally correlated, and Serra & Villani measure the signature of that as a
|
|
156
|
+
far higher and far more irregular reactions-per-catalyst distribution (~400 against ~20).
|
|
157
|
+
**`firing_disk_polymer`** is their other construction. Rather than enumerating every string
|
|
158
|
+
and sprinkling catalysis over the result, it grows the chemistry outward from a small seed,
|
|
159
|
+
so a species exists only if some reaction actually makes it: an enumerated chemistry is
|
|
160
|
+
full of species nothing can reach, and a grown one is closed under its own production by
|
|
161
|
+
construction. ⚠ Food is taken to be the firing disk, an assumption the paper leaves open.
|
|
162
|
+
|
|
163
|
+
**`templated_polymer`** draws nothing. A species templates the ligation `a + b -> ab`
|
|
164
|
+
exactly when it contains the (reverse) complement of the product — or, under
|
|
165
|
+
`rule="junction"`, of the `2h` residues spanning the junction — given both reactants reach
|
|
166
|
+
the per-side overlap `h`. The catalysis graph is a *function* of the sequence set, which is
|
|
167
|
+
the content of a template world and the reason this generator has no `rng` argument. The
|
|
168
|
+
cleavage carries its ligation's templates, so a template changes a reversible reaction's
|
|
169
|
+
rate and never its equilibrium. `templated_catalysts` is the same rule as a pure function
|
|
170
|
+
over whatever strands exist, for a simulator that holds explicit strands.
|
|
171
|
+
|
|
172
|
+
Sized on the complete `max_len` 7 set, the two rules behave very differently, and the
|
|
173
|
+
numbers are pinned as known answers:
|
|
174
|
+
|
|
175
|
+
| | |
|
|
176
|
+
|---|---|
|
|
177
|
+
| junction rule, `h` ≤ 2 | **saturates** — every eligible reaction templated by 194 or 46 species, f = 978 and 142 against ~5 for a random chemistry at the RAF threshold: a uniform speed-up with no specificity left |
|
|
178
|
+
| product rule, `h` 2 | f = 12.9, with the template count *falling* with product length — 46 for a 4-mer, exactly one for a 7-mer, its own complement |
|
|
179
|
+
| either rule, `h` 3 | the **maximal RAF is empty**: templated reactions need reactants of length ≥ `h`, and only untemplated reactions make those from a food set shorter than `h` |
|
|
180
|
+
|
|
181
|
+
The last row is the one to remember. `max_raf` remains the right question about closure
|
|
182
|
+
and the wrong one about whether templating acts — the chemistry is perfectly runnable on an
|
|
183
|
+
uncatalysed background.
|
|
184
|
+
|
|
185
|
+
**A structured chemistry needs something to be compared against**, and "random at the same
|
|
186
|
+
f" is a poor choice: at a templated f, a random chemistry spreads its edges over nearly
|
|
187
|
+
every reaction (reach ~0.92 against the product rule's 0.61). So each null holds something
|
|
188
|
+
different fixed, and `catalysis_motifs` reports what they are compared on — f, reach,
|
|
189
|
+
catalysts per reaction by product length, self-catalysed reactions, mutual pairs, and the
|
|
190
|
+
maximal RAF's size, all counted on the reversible pair:
|
|
191
|
+
|
|
192
|
+
- `degree_preserving_null` — a double-edge-swap chain keeping every degree;
|
|
193
|
+
`stratified=True` also keeps the *lengths* of each reaction's templates, which the plain
|
|
194
|
+
shuffle breaks along with the sequence tie.
|
|
195
|
+
- `motif_matched_null` — random catalysis with exactly the network's edge count,
|
|
196
|
+
self-catalysed reactions and mutual pairs; `match_lengths=True` plants them at the
|
|
197
|
+
network's own lengths, without which it is a second random chemistry with two counts
|
|
198
|
+
pinned.
|
|
199
|
+
- `matched_f_random` / `matched_f_cbpm` — the K-BPM and C-BPM at the network's catalysis
|
|
200
|
+
level, in expectation.
|
|
201
|
+
|
|
202
|
+
Every one of these returns the same `BinaryPolymerNetwork`, so everything in the table
|
|
203
|
+
above runs on them unchanged.
|
|
204
|
+
|
|
136
205
|
## Four modules are deliberately off-theme: `dilution`, `permeation`, `thermo` and `autocatalysis`
|
|
137
206
|
|
|
138
207
|
Everything above takes a `ReactionNetwork` and asks a RAF question of it. `rafkit.dilution`
|
|
@@ -388,6 +457,33 @@ with a warning in the file: a reader that ignores it gets a *different system*.
|
|
|
388
457
|
Reactions requiring a catalyst that nothing provides are omitted and counted, since
|
|
389
458
|
emitting them unconstrained would make them freely fireable — the opposite of the intent.
|
|
390
459
|
|
|
460
|
+
### Readable is not runnable: `to_andl`
|
|
461
|
+
|
|
462
|
+
PNML's `ptnet` grammar has no place for a rate constant, so a PNML file documents a
|
|
463
|
+
network without being able to run it. `to_andl` / `write_andl` export the same network to
|
|
464
|
+
ANDL, the PetriNuts format shared by Snoopy, Spike and Marcie, with mass-action rate
|
|
465
|
+
constants — a complete stochastic Petri net that Spike executes directly. An independently
|
|
466
|
+
developed simulator re-running this chemistry from its definition is the entire point.
|
|
467
|
+
|
|
468
|
+
The semantics are mass action and nothing else, and they were **measured** against Spike
|
|
469
|
+
1.6.0rc2 rather than read from its documentation: a catalyst is a consume-and-produce
|
|
470
|
+
self-loop, so the propensity scales with catalyst count; alternative catalyst sets are
|
|
471
|
+
separate transitions whose propensities sum; and `a + a -> aa` is a weight-2 arc counted
|
|
472
|
+
as unordered pairs, `n(n−1)/2`, with the alternatives 19–25 standard errors away.
|
|
473
|
+
⚠ Catalysis scaling with catalyst count is *not* what `simulate` does — there any catalyst
|
|
474
|
+
present buys the full rate — so this export runs a different system from the in-library
|
|
475
|
+
simulator, deliberately; the pair convention, on the other hand, is `simulate`'s exactly.
|
|
476
|
+
Rate constants are emitted as named constants, so a Spike `.spc` configuration can override
|
|
477
|
+
any single rate without regenerating the file.
|
|
478
|
+
|
|
479
|
+
Refused rather than silently altered, because an executable file that drops a feature does
|
|
480
|
+
not *document* a different system, it **runs** one: inhibition (PNML may annotate it, since
|
|
481
|
+
a reader sees the annotation; a simulator would not), and a catalyst that is also a
|
|
482
|
+
reactant of the same reaction, whose self-loop merges with the consuming arc into a
|
|
483
|
+
weight-2 pre-arc with implementation-dependent combinatorics. The latter means a
|
|
484
|
+
`templated_polymer` network, in which a template can be its own reactant, is refused until
|
|
485
|
+
someone measures what Spike does with it.
|
|
486
|
+
|
|
391
487
|
## Catalysis is a relation, not a list
|
|
392
488
|
|
|
393
489
|
`catalysts[r]` is a set of **alternative catalyst sets**, following Huson, Xavier &
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
[](https://doi.org/10.5281/zenodo.21954795)
|
|
9
9
|
|
|
10
10
|
Autocatalytic (RAF) sets in catalytic reaction networks — maximal RAFs, irreducible
|
|
11
|
-
cores,
|
|
12
|
-
[CatReNet](https://github.com/husonlab/catrenet)
|
|
11
|
+
cores, binary polymer chemistries whose catalysis is drawn, structured or templated, and
|
|
12
|
+
interoperability with [CatReNet](https://github.com/husonlab/catrenet) and the Petri net
|
|
13
|
+
tools.
|
|
13
14
|
|
|
14
15
|
Pure Python and NumPy. No Java, no GUI, no install beyond `pip`.
|
|
15
16
|
|
|
@@ -84,10 +85,17 @@ counted as **one** catalysed reaction, not two. Use `net.catalysis_level` — no
|
|
|
84
85
|
| `is_food_catalysed` | whether a core runs on food catalysis alone, and so carries no heredity |
|
|
85
86
|
| `core_raf` / `has_unique_irraf` | Huson, Xavier & Steel's polynomial test for a *unique* irreducible RAF |
|
|
86
87
|
| `catalytically_reachable` | what can be made without any spontaneous reaction |
|
|
87
|
-
| `binary_polymer` | Kauffman
|
|
88
|
+
| `binary_polymer` | Kauffman's K-BPM: catalysis by coin flip, with optional cleavage |
|
|
89
|
+
| `complementary_polymer` | Serra & Villani's C-BPM: catalysis by active-site complementarity, not by coin flip |
|
|
90
|
+
| `firing_disk_polymer` | their firing-disk construction — a chemistry *grown* from a seed, closed under its own production |
|
|
91
|
+
| `templated_polymer` / `templated_catalysts` | templated ligation: a species catalyses `a + b -> ab` iff it carries the product's complement — nothing is drawn |
|
|
92
|
+
| `degree_preserving_null` / `motif_matched_null` | same reactions, rewired catalysis — the nulls a structured chemistry is compared against |
|
|
93
|
+
| `matched_f_random` / `matched_f_cbpm` | the K-BPM and C-BPM at a structured chemistry's catalysis level |
|
|
94
|
+
| `catalysis_motifs` | what a chemistry and its nulls are compared on |
|
|
88
95
|
| `ReactionNetwork` | arbitrary catalytic reaction systems, same protocol |
|
|
89
96
|
| `read_crs` / `write_crs` | CatReNet's CRS interchange format |
|
|
90
97
|
| `to_pnml` / `write_pnml` | PNML export (ISO/IEC 15909-2) for the Petri net ecosystem |
|
|
98
|
+
| `to_andl` / `write_andl` | ANDL export with mass-action rate constants — runs unchanged in Spike |
|
|
91
99
|
| `simulate` | Gillespie direct method — watch subRAFs seed themselves into existence |
|
|
92
100
|
| `max_urafs` | uninhibited RAFs, when a molecule can prevent a reaction |
|
|
93
101
|
| `run_serial_dilution` / `run_cstr` | dilution protocols for growing–dividing compartments — **not a RAF algorithm**, see below |
|
|
@@ -102,6 +110,67 @@ counted as **one** catalysed reaction, not two. Use `net.catalysis_level` — no
|
|
|
102
110
|
Every algorithm carries hand-computed known-answer tests, because a RAF algorithm that
|
|
103
111
|
is subtly wrong produces plausible numbers rather than errors.
|
|
104
112
|
|
|
113
|
+
## Catalysis three ways: drawn, structured, and templated
|
|
114
|
+
|
|
115
|
+
`binary_polymer` is Kauffman's K-BPM: every string up to `max_len` exists, and whether a
|
|
116
|
+
species catalyses a reaction is an independent coin flip at probability `p`. Two more
|
|
117
|
+
ensembles keep that reaction set and change the one thing that matters — *which* catalyst
|
|
118
|
+
catalyses *which* reaction.
|
|
119
|
+
|
|
120
|
+
**`complementary_polymer`** is Serra & Villani's C-BPM (*Entropy* 28(2), 184, 2026),
|
|
121
|
+
reproduced rather than invented. A catalyst carries an active site — a substring of itself,
|
|
122
|
+
3–4 residues long by default — and acts on whatever is complementary to that site. A
|
|
123
|
+
K-catalyst's targets are independent draws; a C-catalyst's targets all share one template,
|
|
124
|
+
so they are structurally correlated, and Serra & Villani measure the signature of that as a
|
|
125
|
+
far higher and far more irregular reactions-per-catalyst distribution (~400 against ~20).
|
|
126
|
+
**`firing_disk_polymer`** is their other construction. Rather than enumerating every string
|
|
127
|
+
and sprinkling catalysis over the result, it grows the chemistry outward from a small seed,
|
|
128
|
+
so a species exists only if some reaction actually makes it: an enumerated chemistry is
|
|
129
|
+
full of species nothing can reach, and a grown one is closed under its own production by
|
|
130
|
+
construction. ⚠ Food is taken to be the firing disk, an assumption the paper leaves open.
|
|
131
|
+
|
|
132
|
+
**`templated_polymer`** draws nothing. A species templates the ligation `a + b -> ab`
|
|
133
|
+
exactly when it contains the (reverse) complement of the product — or, under
|
|
134
|
+
`rule="junction"`, of the `2h` residues spanning the junction — given both reactants reach
|
|
135
|
+
the per-side overlap `h`. The catalysis graph is a *function* of the sequence set, which is
|
|
136
|
+
the content of a template world and the reason this generator has no `rng` argument. The
|
|
137
|
+
cleavage carries its ligation's templates, so a template changes a reversible reaction's
|
|
138
|
+
rate and never its equilibrium. `templated_catalysts` is the same rule as a pure function
|
|
139
|
+
over whatever strands exist, for a simulator that holds explicit strands.
|
|
140
|
+
|
|
141
|
+
Sized on the complete `max_len` 7 set, the two rules behave very differently, and the
|
|
142
|
+
numbers are pinned as known answers:
|
|
143
|
+
|
|
144
|
+
| | |
|
|
145
|
+
|---|---|
|
|
146
|
+
| junction rule, `h` ≤ 2 | **saturates** — every eligible reaction templated by 194 or 46 species, f = 978 and 142 against ~5 for a random chemistry at the RAF threshold: a uniform speed-up with no specificity left |
|
|
147
|
+
| product rule, `h` 2 | f = 12.9, with the template count *falling* with product length — 46 for a 4-mer, exactly one for a 7-mer, its own complement |
|
|
148
|
+
| either rule, `h` 3 | the **maximal RAF is empty**: templated reactions need reactants of length ≥ `h`, and only untemplated reactions make those from a food set shorter than `h` |
|
|
149
|
+
|
|
150
|
+
The last row is the one to remember. `max_raf` remains the right question about closure
|
|
151
|
+
and the wrong one about whether templating acts — the chemistry is perfectly runnable on an
|
|
152
|
+
uncatalysed background.
|
|
153
|
+
|
|
154
|
+
**A structured chemistry needs something to be compared against**, and "random at the same
|
|
155
|
+
f" is a poor choice: at a templated f, a random chemistry spreads its edges over nearly
|
|
156
|
+
every reaction (reach ~0.92 against the product rule's 0.61). So each null holds something
|
|
157
|
+
different fixed, and `catalysis_motifs` reports what they are compared on — f, reach,
|
|
158
|
+
catalysts per reaction by product length, self-catalysed reactions, mutual pairs, and the
|
|
159
|
+
maximal RAF's size, all counted on the reversible pair:
|
|
160
|
+
|
|
161
|
+
- `degree_preserving_null` — a double-edge-swap chain keeping every degree;
|
|
162
|
+
`stratified=True` also keeps the *lengths* of each reaction's templates, which the plain
|
|
163
|
+
shuffle breaks along with the sequence tie.
|
|
164
|
+
- `motif_matched_null` — random catalysis with exactly the network's edge count,
|
|
165
|
+
self-catalysed reactions and mutual pairs; `match_lengths=True` plants them at the
|
|
166
|
+
network's own lengths, without which it is a second random chemistry with two counts
|
|
167
|
+
pinned.
|
|
168
|
+
- `matched_f_random` / `matched_f_cbpm` — the K-BPM and C-BPM at the network's catalysis
|
|
169
|
+
level, in expectation.
|
|
170
|
+
|
|
171
|
+
Every one of these returns the same `BinaryPolymerNetwork`, so everything in the table
|
|
172
|
+
above runs on them unchanged.
|
|
173
|
+
|
|
105
174
|
## Four modules are deliberately off-theme: `dilution`, `permeation`, `thermo` and `autocatalysis`
|
|
106
175
|
|
|
107
176
|
Everything above takes a `ReactionNetwork` and asks a RAF question of it. `rafkit.dilution`
|
|
@@ -357,6 +426,33 @@ with a warning in the file: a reader that ignores it gets a *different system*.
|
|
|
357
426
|
Reactions requiring a catalyst that nothing provides are omitted and counted, since
|
|
358
427
|
emitting them unconstrained would make them freely fireable — the opposite of the intent.
|
|
359
428
|
|
|
429
|
+
### Readable is not runnable: `to_andl`
|
|
430
|
+
|
|
431
|
+
PNML's `ptnet` grammar has no place for a rate constant, so a PNML file documents a
|
|
432
|
+
network without being able to run it. `to_andl` / `write_andl` export the same network to
|
|
433
|
+
ANDL, the PetriNuts format shared by Snoopy, Spike and Marcie, with mass-action rate
|
|
434
|
+
constants — a complete stochastic Petri net that Spike executes directly. An independently
|
|
435
|
+
developed simulator re-running this chemistry from its definition is the entire point.
|
|
436
|
+
|
|
437
|
+
The semantics are mass action and nothing else, and they were **measured** against Spike
|
|
438
|
+
1.6.0rc2 rather than read from its documentation: a catalyst is a consume-and-produce
|
|
439
|
+
self-loop, so the propensity scales with catalyst count; alternative catalyst sets are
|
|
440
|
+
separate transitions whose propensities sum; and `a + a -> aa` is a weight-2 arc counted
|
|
441
|
+
as unordered pairs, `n(n−1)/2`, with the alternatives 19–25 standard errors away.
|
|
442
|
+
⚠ Catalysis scaling with catalyst count is *not* what `simulate` does — there any catalyst
|
|
443
|
+
present buys the full rate — so this export runs a different system from the in-library
|
|
444
|
+
simulator, deliberately; the pair convention, on the other hand, is `simulate`'s exactly.
|
|
445
|
+
Rate constants are emitted as named constants, so a Spike `.spc` configuration can override
|
|
446
|
+
any single rate without regenerating the file.
|
|
447
|
+
|
|
448
|
+
Refused rather than silently altered, because an executable file that drops a feature does
|
|
449
|
+
not *document* a different system, it **runs** one: inhibition (PNML may annotate it, since
|
|
450
|
+
a reader sees the annotation; a simulator would not), and a catalyst that is also a
|
|
451
|
+
reactant of the same reaction, whose self-loop merges with the consuming arc into a
|
|
452
|
+
weight-2 pre-arc with implementation-dependent combinatorics. The latter means a
|
|
453
|
+
`templated_polymer` network, in which a template can be its own reactant, is refused until
|
|
454
|
+
someone measures what Spike does with it.
|
|
455
|
+
|
|
360
456
|
## Catalysis is a relation, not a list
|
|
361
457
|
|
|
362
458
|
`catalysts[r]` is a set of **alternative catalyst sets**, following Huson, Xavier &
|
|
@@ -102,29 +102,49 @@ class BinaryPolymerNetwork:
|
|
|
102
102
|
def n_cleavages(self) -> int:
|
|
103
103
|
return sum(1 for d in self.directions if d < 0)
|
|
104
104
|
|
|
105
|
+
def pair_index(self) -> list[tuple[int, int | None]]:
|
|
106
|
+
"""The reversible pairs as (ligation entry, cleavage entry or None), ONE source for every
|
|
107
|
+
pair-wise consumer. The standard layout -- every ligation first, then every cleavage,
|
|
108
|
+
`i` with `i + n` -- is recognised by position; any other layout (a designed chemistry
|
|
109
|
+
stores +1, -1 per reaction) is paired by the reaction TRIPLE, a cleavage with the
|
|
110
|
+
ligation of the same (a, b, ab). Refused: a triple stored twice in one direction, or a
|
|
111
|
+
cleavage with no ligation -- read with unrelated reactions unioned is the failure this
|
|
112
|
+
exists to close (review of PR 17)."""
|
|
113
|
+
n_c = self.n_cleavages
|
|
114
|
+
n = self.n_reactions - n_c
|
|
115
|
+
if n_c == 0:
|
|
116
|
+
return [(i, None) for i in range(self.n_reactions)]
|
|
117
|
+
if (n_c == n and tuple(self.directions[:n]) == (1,) * n
|
|
118
|
+
and all(self.reactions[i] == self.reactions[i + n] for i in range(n))):
|
|
119
|
+
return [(i, i + n) for i in range(n)]
|
|
120
|
+
lig = {}
|
|
121
|
+
for i, (trip, d) in enumerate(zip(self.reactions, self.directions)):
|
|
122
|
+
if d > 0:
|
|
123
|
+
if trip in lig:
|
|
124
|
+
raise ValueError(f"the ligation {trip} is stored twice; reversible pairs are ambiguous")
|
|
125
|
+
lig[trip] = i
|
|
126
|
+
out = {i: None for i in lig.values()}
|
|
127
|
+
for j, (trip, d) in enumerate(zip(self.reactions, self.directions)):
|
|
128
|
+
if d < 0:
|
|
129
|
+
if trip not in lig:
|
|
130
|
+
raise ValueError(f"the cleavage {trip} (reaction {j}) has no ligation to pair with")
|
|
131
|
+
if out[lig[trip]] is not None:
|
|
132
|
+
raise ValueError(f"the cleavage {trip} is stored twice; reversible pairs are ambiguous")
|
|
133
|
+
out[lig[trip]] = j
|
|
134
|
+
return sorted(out.items())
|
|
135
|
+
|
|
105
136
|
@property
|
|
106
137
|
def n_pairs(self) -> int:
|
|
107
|
-
"""Reversible pairs
|
|
108
|
-
|
|
109
|
-
``i + n``. Refused otherwise -- a network with the right count and interleaved
|
|
110
|
-
directions would otherwise be read with unrelated reactions unioned."""
|
|
111
|
-
n = self.n_reactions - self.n_cleavages
|
|
112
|
-
if self.n_cleavages not in (0, n):
|
|
113
|
-
raise ValueError("expected every ligation, optionally followed by every cleavage: "
|
|
114
|
-
f"{n} ligations and {self.n_cleavages} cleavages")
|
|
115
|
-
if tuple(self.directions[:n]) != (1,) * n or tuple(self.directions[n:]) != (-1,) * self.n_cleavages:
|
|
116
|
-
raise ValueError("expected every ligation first, then every cleavage, reaction i "
|
|
117
|
-
"paired with i + n; the directions are interleaved")
|
|
118
|
-
return n
|
|
138
|
+
"""Reversible pairs (a ligation with its cleavage, or a ligation alone)."""
|
|
139
|
+
return len(self.pair_index())
|
|
119
140
|
|
|
120
141
|
def pair_catalysts(self) -> list[frozenset]:
|
|
121
142
|
"""Per reversible pair, the union of the two directions' catalyst sets (the raw
|
|
122
143
|
conjunctive groups). Under `paired_catalysis` the halves are identical and this is
|
|
123
144
|
each ligation's own set; it differs only where the directions were drawn separately
|
|
124
145
|
-- as in C-BPM, where a catalyst acts on one direction only."""
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
for i in range(n)]
|
|
146
|
+
return [self.catalysts[i] if j is None else self.catalysts[i] | self.catalysts[j]
|
|
147
|
+
for i, j in self.pair_index()]
|
|
128
148
|
|
|
129
149
|
@property
|
|
130
150
|
def catalysis_level(self) -> float:
|
|
@@ -155,11 +155,10 @@ def _edges(net: BinaryPolymerNetwork) -> int:
|
|
|
155
155
|
def _simple_paired_edges(net: BinaryPolymerNetwork) -> list[tuple[int, int]]:
|
|
156
156
|
"""The (template, pair) edges of a network whose catalysis is disjunctive and paired --
|
|
157
157
|
what a rewiring null can preserve. Anything else is refused rather than flattened."""
|
|
158
|
-
|
|
159
|
-
for i in range(n):
|
|
158
|
+
for i, j in net.pair_index():
|
|
160
159
|
if any(len(group) != 1 for group in net.catalysts[i]):
|
|
161
160
|
raise ValueError("a rewiring null needs disjunctive (singleton) catalyst sets")
|
|
162
|
-
if
|
|
161
|
+
if j is not None and net.catalysts[i] != net.catalysts[j]:
|
|
163
162
|
raise ValueError("a rewiring null needs paired catalysis: a ligation and its "
|
|
164
163
|
"cleavage sharing one catalyst set")
|
|
165
164
|
return [(t, i) for i, c in enumerate(_pair_catalysts(net)) for t in sorted(c)]
|
|
@@ -167,10 +166,17 @@ def _simple_paired_edges(net: BinaryPolymerNetwork) -> list[tuple[int, int]]:
|
|
|
167
166
|
|
|
168
167
|
def _with_pair_catalysts(net: BinaryPolymerNetwork, cats: Sequence[frozenset[int]]
|
|
169
168
|
) -> BinaryPolymerNetwork:
|
|
170
|
-
|
|
169
|
+
"""`net` with each reversible pair's catalyst set replaced; the two halves of a pair
|
|
170
|
+
receive the same set whatever the layout (`pair_index`)."""
|
|
171
|
+
by_entry = [frozenset()] * net.n_reactions
|
|
172
|
+
for (i, j), c in zip(net.pair_index(), cats):
|
|
173
|
+
by_entry[i] = frozenset(c)
|
|
174
|
+
if j is not None:
|
|
175
|
+
by_entry[j] = frozenset(c)
|
|
176
|
+
drawn = tuple(by_entry)
|
|
171
177
|
return BinaryPolymerNetwork(
|
|
172
178
|
molecules=net.molecules, food=net.food, reactions=net.reactions,
|
|
173
|
-
catalysts=drawn
|
|
179
|
+
catalysts=drawn, p=net.p, max_len=net.max_len,
|
|
174
180
|
food_len=net.food_len, directions=net.directions, inhibitors=net.inhibitors)
|
|
175
181
|
|
|
176
182
|
|
|
@@ -204,7 +210,7 @@ def catalysis_motifs(net: BinaryPolymerNetwork, *, raf: bool = True) -> dict:
|
|
|
204
210
|
"""
|
|
205
211
|
cats = _pair_catalysts(net)
|
|
206
212
|
n = len(cats)
|
|
207
|
-
product = [net.reactions[i][2] for i in
|
|
213
|
+
product = [net.reactions[i][2] for i, _ in net.pair_index()]
|
|
208
214
|
edges = sum(len(c) for c in cats)
|
|
209
215
|
catalysed = [i for i in range(n) if cats[i]]
|
|
210
216
|
by_len: dict[int, list[int]] = {}
|
|
@@ -329,7 +335,7 @@ def motif_matched_null(net: BinaryPolymerNetwork, rng: np.random.Generator, *,
|
|
|
329
335
|
target = catalysis_motifs(net, raf=False)
|
|
330
336
|
n = _n_pairs(net)
|
|
331
337
|
_simple_paired_edges(net) # refuse what cannot be matched
|
|
332
|
-
product = [net.reactions[i][2] for i in
|
|
338
|
+
product = [net.reactions[i][2] for i, _ in net.pair_index()]
|
|
333
339
|
making: dict[int, list[int]] = {}
|
|
334
340
|
for i, q in enumerate(product):
|
|
335
341
|
making.setdefault(q, []).append(i)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rafkit
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Autocatalytic (RAF) sets in catalytic reaction networks: maximal RAFs, irreducible cores, and Kauffman binary polymer models.
|
|
5
5
|
Author: James P. Galasyn, Claude Théodore
|
|
6
6
|
License: MIT
|
|
@@ -39,8 +39,9 @@ Dynamic: license-file
|
|
|
39
39
|
[](https://doi.org/10.5281/zenodo.21954795)
|
|
40
40
|
|
|
41
41
|
Autocatalytic (RAF) sets in catalytic reaction networks — maximal RAFs, irreducible
|
|
42
|
-
cores,
|
|
43
|
-
[CatReNet](https://github.com/husonlab/catrenet)
|
|
42
|
+
cores, binary polymer chemistries whose catalysis is drawn, structured or templated, and
|
|
43
|
+
interoperability with [CatReNet](https://github.com/husonlab/catrenet) and the Petri net
|
|
44
|
+
tools.
|
|
44
45
|
|
|
45
46
|
Pure Python and NumPy. No Java, no GUI, no install beyond `pip`.
|
|
46
47
|
|
|
@@ -115,10 +116,17 @@ counted as **one** catalysed reaction, not two. Use `net.catalysis_level` — no
|
|
|
115
116
|
| `is_food_catalysed` | whether a core runs on food catalysis alone, and so carries no heredity |
|
|
116
117
|
| `core_raf` / `has_unique_irraf` | Huson, Xavier & Steel's polynomial test for a *unique* irreducible RAF |
|
|
117
118
|
| `catalytically_reachable` | what can be made without any spontaneous reaction |
|
|
118
|
-
| `binary_polymer` | Kauffman
|
|
119
|
+
| `binary_polymer` | Kauffman's K-BPM: catalysis by coin flip, with optional cleavage |
|
|
120
|
+
| `complementary_polymer` | Serra & Villani's C-BPM: catalysis by active-site complementarity, not by coin flip |
|
|
121
|
+
| `firing_disk_polymer` | their firing-disk construction — a chemistry *grown* from a seed, closed under its own production |
|
|
122
|
+
| `templated_polymer` / `templated_catalysts` | templated ligation: a species catalyses `a + b -> ab` iff it carries the product's complement — nothing is drawn |
|
|
123
|
+
| `degree_preserving_null` / `motif_matched_null` | same reactions, rewired catalysis — the nulls a structured chemistry is compared against |
|
|
124
|
+
| `matched_f_random` / `matched_f_cbpm` | the K-BPM and C-BPM at a structured chemistry's catalysis level |
|
|
125
|
+
| `catalysis_motifs` | what a chemistry and its nulls are compared on |
|
|
119
126
|
| `ReactionNetwork` | arbitrary catalytic reaction systems, same protocol |
|
|
120
127
|
| `read_crs` / `write_crs` | CatReNet's CRS interchange format |
|
|
121
128
|
| `to_pnml` / `write_pnml` | PNML export (ISO/IEC 15909-2) for the Petri net ecosystem |
|
|
129
|
+
| `to_andl` / `write_andl` | ANDL export with mass-action rate constants — runs unchanged in Spike |
|
|
122
130
|
| `simulate` | Gillespie direct method — watch subRAFs seed themselves into existence |
|
|
123
131
|
| `max_urafs` | uninhibited RAFs, when a molecule can prevent a reaction |
|
|
124
132
|
| `run_serial_dilution` / `run_cstr` | dilution protocols for growing–dividing compartments — **not a RAF algorithm**, see below |
|
|
@@ -133,6 +141,67 @@ counted as **one** catalysed reaction, not two. Use `net.catalysis_level` — no
|
|
|
133
141
|
Every algorithm carries hand-computed known-answer tests, because a RAF algorithm that
|
|
134
142
|
is subtly wrong produces plausible numbers rather than errors.
|
|
135
143
|
|
|
144
|
+
## Catalysis three ways: drawn, structured, and templated
|
|
145
|
+
|
|
146
|
+
`binary_polymer` is Kauffman's K-BPM: every string up to `max_len` exists, and whether a
|
|
147
|
+
species catalyses a reaction is an independent coin flip at probability `p`. Two more
|
|
148
|
+
ensembles keep that reaction set and change the one thing that matters — *which* catalyst
|
|
149
|
+
catalyses *which* reaction.
|
|
150
|
+
|
|
151
|
+
**`complementary_polymer`** is Serra & Villani's C-BPM (*Entropy* 28(2), 184, 2026),
|
|
152
|
+
reproduced rather than invented. A catalyst carries an active site — a substring of itself,
|
|
153
|
+
3–4 residues long by default — and acts on whatever is complementary to that site. A
|
|
154
|
+
K-catalyst's targets are independent draws; a C-catalyst's targets all share one template,
|
|
155
|
+
so they are structurally correlated, and Serra & Villani measure the signature of that as a
|
|
156
|
+
far higher and far more irregular reactions-per-catalyst distribution (~400 against ~20).
|
|
157
|
+
**`firing_disk_polymer`** is their other construction. Rather than enumerating every string
|
|
158
|
+
and sprinkling catalysis over the result, it grows the chemistry outward from a small seed,
|
|
159
|
+
so a species exists only if some reaction actually makes it: an enumerated chemistry is
|
|
160
|
+
full of species nothing can reach, and a grown one is closed under its own production by
|
|
161
|
+
construction. ⚠ Food is taken to be the firing disk, an assumption the paper leaves open.
|
|
162
|
+
|
|
163
|
+
**`templated_polymer`** draws nothing. A species templates the ligation `a + b -> ab`
|
|
164
|
+
exactly when it contains the (reverse) complement of the product — or, under
|
|
165
|
+
`rule="junction"`, of the `2h` residues spanning the junction — given both reactants reach
|
|
166
|
+
the per-side overlap `h`. The catalysis graph is a *function* of the sequence set, which is
|
|
167
|
+
the content of a template world and the reason this generator has no `rng` argument. The
|
|
168
|
+
cleavage carries its ligation's templates, so a template changes a reversible reaction's
|
|
169
|
+
rate and never its equilibrium. `templated_catalysts` is the same rule as a pure function
|
|
170
|
+
over whatever strands exist, for a simulator that holds explicit strands.
|
|
171
|
+
|
|
172
|
+
Sized on the complete `max_len` 7 set, the two rules behave very differently, and the
|
|
173
|
+
numbers are pinned as known answers:
|
|
174
|
+
|
|
175
|
+
| | |
|
|
176
|
+
|---|---|
|
|
177
|
+
| junction rule, `h` ≤ 2 | **saturates** — every eligible reaction templated by 194 or 46 species, f = 978 and 142 against ~5 for a random chemistry at the RAF threshold: a uniform speed-up with no specificity left |
|
|
178
|
+
| product rule, `h` 2 | f = 12.9, with the template count *falling* with product length — 46 for a 4-mer, exactly one for a 7-mer, its own complement |
|
|
179
|
+
| either rule, `h` 3 | the **maximal RAF is empty**: templated reactions need reactants of length ≥ `h`, and only untemplated reactions make those from a food set shorter than `h` |
|
|
180
|
+
|
|
181
|
+
The last row is the one to remember. `max_raf` remains the right question about closure
|
|
182
|
+
and the wrong one about whether templating acts — the chemistry is perfectly runnable on an
|
|
183
|
+
uncatalysed background.
|
|
184
|
+
|
|
185
|
+
**A structured chemistry needs something to be compared against**, and "random at the same
|
|
186
|
+
f" is a poor choice: at a templated f, a random chemistry spreads its edges over nearly
|
|
187
|
+
every reaction (reach ~0.92 against the product rule's 0.61). So each null holds something
|
|
188
|
+
different fixed, and `catalysis_motifs` reports what they are compared on — f, reach,
|
|
189
|
+
catalysts per reaction by product length, self-catalysed reactions, mutual pairs, and the
|
|
190
|
+
maximal RAF's size, all counted on the reversible pair:
|
|
191
|
+
|
|
192
|
+
- `degree_preserving_null` — a double-edge-swap chain keeping every degree;
|
|
193
|
+
`stratified=True` also keeps the *lengths* of each reaction's templates, which the plain
|
|
194
|
+
shuffle breaks along with the sequence tie.
|
|
195
|
+
- `motif_matched_null` — random catalysis with exactly the network's edge count,
|
|
196
|
+
self-catalysed reactions and mutual pairs; `match_lengths=True` plants them at the
|
|
197
|
+
network's own lengths, without which it is a second random chemistry with two counts
|
|
198
|
+
pinned.
|
|
199
|
+
- `matched_f_random` / `matched_f_cbpm` — the K-BPM and C-BPM at the network's catalysis
|
|
200
|
+
level, in expectation.
|
|
201
|
+
|
|
202
|
+
Every one of these returns the same `BinaryPolymerNetwork`, so everything in the table
|
|
203
|
+
above runs on them unchanged.
|
|
204
|
+
|
|
136
205
|
## Four modules are deliberately off-theme: `dilution`, `permeation`, `thermo` and `autocatalysis`
|
|
137
206
|
|
|
138
207
|
Everything above takes a `ReactionNetwork` and asks a RAF question of it. `rafkit.dilution`
|
|
@@ -388,6 +457,33 @@ with a warning in the file: a reader that ignores it gets a *different system*.
|
|
|
388
457
|
Reactions requiring a catalyst that nothing provides are omitted and counted, since
|
|
389
458
|
emitting them unconstrained would make them freely fireable — the opposite of the intent.
|
|
390
459
|
|
|
460
|
+
### Readable is not runnable: `to_andl`
|
|
461
|
+
|
|
462
|
+
PNML's `ptnet` grammar has no place for a rate constant, so a PNML file documents a
|
|
463
|
+
network without being able to run it. `to_andl` / `write_andl` export the same network to
|
|
464
|
+
ANDL, the PetriNuts format shared by Snoopy, Spike and Marcie, with mass-action rate
|
|
465
|
+
constants — a complete stochastic Petri net that Spike executes directly. An independently
|
|
466
|
+
developed simulator re-running this chemistry from its definition is the entire point.
|
|
467
|
+
|
|
468
|
+
The semantics are mass action and nothing else, and they were **measured** against Spike
|
|
469
|
+
1.6.0rc2 rather than read from its documentation: a catalyst is a consume-and-produce
|
|
470
|
+
self-loop, so the propensity scales with catalyst count; alternative catalyst sets are
|
|
471
|
+
separate transitions whose propensities sum; and `a + a -> aa` is a weight-2 arc counted
|
|
472
|
+
as unordered pairs, `n(n−1)/2`, with the alternatives 19–25 standard errors away.
|
|
473
|
+
⚠ Catalysis scaling with catalyst count is *not* what `simulate` does — there any catalyst
|
|
474
|
+
present buys the full rate — so this export runs a different system from the in-library
|
|
475
|
+
simulator, deliberately; the pair convention, on the other hand, is `simulate`'s exactly.
|
|
476
|
+
Rate constants are emitted as named constants, so a Spike `.spc` configuration can override
|
|
477
|
+
any single rate without regenerating the file.
|
|
478
|
+
|
|
479
|
+
Refused rather than silently altered, because an executable file that drops a feature does
|
|
480
|
+
not *document* a different system, it **runs** one: inhibition (PNML may annotate it, since
|
|
481
|
+
a reader sees the annotation; a simulator would not), and a catalyst that is also a
|
|
482
|
+
reactant of the same reaction, whose self-loop merges with the consuming arc into a
|
|
483
|
+
weight-2 pre-arc with implementation-dependent combinatorics. The latter means a
|
|
484
|
+
`templated_polymer` network, in which a template can be its own reactant, is refused until
|
|
485
|
+
someone measures what Spike does with it.
|
|
486
|
+
|
|
391
487
|
## Catalysis is a relation, not a list
|
|
392
488
|
|
|
393
489
|
`catalysts[r]` is a set of **alternative catalyst sets**, following Huson, Xavier &
|
|
@@ -322,9 +322,10 @@ class TestMotifs:
|
|
|
322
322
|
class TestGuards:
|
|
323
323
|
"""Review of 5a3c5a8 / 14e13f1 (PR 17): the layout, the loops and the edges that were unguarded."""
|
|
324
324
|
|
|
325
|
-
def
|
|
326
|
-
"""Every pair-wise consumer reads
|
|
327
|
-
|
|
325
|
+
def test_an_interleaved_layout_is_paired_by_its_triples(self):
|
|
326
|
+
"""Every pair-wise consumer reads a pair as (ligation, its cleavage). The standard layout is
|
|
327
|
+
by position; a designed chemistry storing +1, -1 per reaction is paired by the triple and
|
|
328
|
+
reads IDENTICALLY -- the same f, the same motifs, the same null degrees."""
|
|
328
329
|
from rafkit.binary_polymer import BinaryPolymerNetwork
|
|
329
330
|
net = templated_polymer(max_len=5)
|
|
330
331
|
n = net.n_reactions // 2
|
|
@@ -333,12 +334,32 @@ class TestGuards:
|
|
|
333
334
|
molecules=net.molecules, food=net.food, reactions=tuple(net.reactions[k] for k in order),
|
|
334
335
|
catalysts=tuple(net.catalysts[k] for k in order), p=net.p, max_len=net.max_len,
|
|
335
336
|
food_len=net.food_len, directions=tuple(net.directions[k] for k in order))
|
|
336
|
-
assert mixed.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
337
|
+
assert mixed.n_pairs == net.n_pairs == n
|
|
338
|
+
assert mixed.pair_index() == [(2 * i, 2 * i + 1) for i in range(n)]
|
|
339
|
+
assert mixed.catalysis_level == net.catalysis_level
|
|
340
|
+
a, b = catalysis_motifs(net), catalysis_motifs(mixed)
|
|
341
|
+
assert a == b
|
|
342
|
+
null = degree_preserving_null(mixed, np.random.default_rng(0))
|
|
343
|
+
assert null.catalysts[0::2] == null.catalysts[1::2] # the halves still share one set
|
|
344
|
+
# the same degrees as the standard layout's null from the same seed, read through the pairs
|
|
345
|
+
ref = degree_preserving_null(net, np.random.default_rng(0))
|
|
346
|
+
deg = lambda x: sorted(len(x.catalysts[i]) for i, _ in x.pair_index())
|
|
347
|
+
assert deg(null) == deg(ref) and deg(mixed) == deg(net)
|
|
348
|
+
|
|
349
|
+
def test_a_cleavage_without_its_ligation_is_refused(self):
|
|
350
|
+
from rafkit.binary_polymer import BinaryPolymerNetwork
|
|
351
|
+
net = templated_polymer(max_len=4)
|
|
352
|
+
n = net.n_reactions // 2
|
|
353
|
+
# drop one ligation, keep its cleavage: no triple to pair with
|
|
354
|
+
keep = [k for k in range(net.n_reactions) if k != 0]
|
|
355
|
+
orphan = BinaryPolymerNetwork(
|
|
356
|
+
molecules=net.molecules, food=net.food, reactions=tuple(net.reactions[k] for k in keep),
|
|
357
|
+
catalysts=tuple(net.catalysts[k] for k in keep), p=net.p, max_len=net.max_len,
|
|
358
|
+
food_len=net.food_len, directions=tuple(net.directions[k] for k in keep))
|
|
359
|
+
with pytest.raises(ValueError, match="no ligation to pair"):
|
|
360
|
+
orphan.n_pairs
|
|
361
|
+
with pytest.raises(ValueError, match="no ligation to pair"):
|
|
362
|
+
catalysis_motifs(orphan)
|
|
342
363
|
|
|
343
364
|
def test_blocked_proposals_are_the_sequential_stream(self):
|
|
344
365
|
from rafkit.templated_polymer import _proposals
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|