setreplace 0.1.0__tar.gz → 0.2.0__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.
- {setreplace-0.1.0 → setreplace-0.2.0}/.gitignore +1 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/Cargo.lock +1 -1
- {setreplace-0.1.0 → setreplace-0.2.0}/PKG-INFO +1 -1
- {setreplace-0.1.0 → setreplace-0.2.0}/README.md +1 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/python-api.md +20 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/pyproject.toml +1 -1
- {setreplace-0.1.0 → setreplace-0.2.0}/python/Cargo.toml +1 -1
- {setreplace-0.1.0 → setreplace-0.2.0}/python/setreplace.pyi +11 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/python/src/lib.rs +52 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/python/tests/test_api.py +17 -0
- setreplace-0.2.0/scripts/scan_growth_rates.py +143 -0
- setreplace-0.2.0/src/enumerate.rs +419 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/src/lib.rs +4 -0
- setreplace-0.2.0/tests/enumeration_vectors.rs +150 -0
- setreplace-0.2.0/tests/fixtures/enumerate_rules_ground_truth.txt +10 -0
- setreplace-0.2.0/tests/fixtures/wl_2_2_to_2_2.txt +562 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/.github/workflows/ci.yml +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/.github/workflows/release.yml +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/Cargo.toml +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/LICENSE +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/engine.md +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/basic_hypergraph_plot.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/causal10_wolfram_vs_rust.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/evolution_result_100_steps.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/evolution_result_10_steps.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/evolution_result_1_step.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/layered_causal_graph.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/showcase/announcement_web.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/showcase/crumpled_ball.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/showcase/lens_mesh.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/showcase/sierpinski_fractal.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/showcase/triangular_net.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/images/step10_wolfram_vs_rust.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/release.md +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/docs/rust.md +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/examples/bench.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/examples/growth.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/examples/orderings.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/examples/print_states.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/basic_hypergraph_plot.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/basic_hypergraph_plot.svg +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/comparison/basic_wolfram_vs_rust.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/comparison/causal10_wolfram_vs_rust.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/comparison/step100_wolfram_vs_rust.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/comparison/step10_wolfram_vs_rust.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/comparison/step1_wolfram_vs_rust.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/evolution_result_100_steps.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/evolution_result_100_steps.svg +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/evolution_result_10_steps.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/evolution_result_10_steps.svg +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/evolution_result_1_step.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/evolution_result_1_step.svg +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/layered_causal_graph.png +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/out/layered_causal_graph.svg +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/python/README.md +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/src/atoms_index.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/src/error.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/src/matcher.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/src/pcg.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/src/rule.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/src/system.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/tests/wolfram_vectors.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/Cargo.toml +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/README.md +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/examples/compose_comparison.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/examples/inline_editions.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/examples/readme_figures.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/examples/showcase.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/causal.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/geometry.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/layout.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/lib.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/pcg.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/plot.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/png.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/style.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/svg.rs +0 -0
- {setreplace-0.1.0 → setreplace-0.2.0}/viz/src/vec2.rs +0 -0
|
@@ -123,6 +123,7 @@ what lets meshes and fractals unfold instead of freezing into hairballs.
|
|
|
123
123
|
| `"AllEventsList"` / `"AllExpressions"` | `system.events()` / `system.tokens()` |
|
|
124
124
|
| `"CausalGraph"` / `"LayeredCausalGraph"` | `system.causal_graph_edges()` / `.causal_graph_plot()` |
|
|
125
125
|
| `HypergraphPlot[state, VertexLabels -> Automatic]` | `sr.plot(state, labels=True)` |
|
|
126
|
+
| `EnumerateWolframModelRules[{{2, 2}} -> {{3, 2}}]` | `sr.enumerate_rules([(2, 2)], [(3, 2)])` |
|
|
126
127
|
|
|
127
128
|
Rules can be strings (as above), structured
|
|
128
129
|
(`sr.Rule([["x", "y"]], [["x", "y"], ["y", "z"]])` — strings are variables,
|
|
@@ -237,3 +237,23 @@ pos = sr.layout(system.final_state, seed=1)
|
|
|
237
237
|
abi3 keeps it to one wheel per platform.
|
|
238
238
|
3. Later, when multiway lands in the engine, `evolve()` gains kwargs rather
|
|
239
239
|
than new entry points — worth keeping in mind when naming things now.
|
|
240
|
+
|
|
241
|
+
## Added after v0.1.0: rule enumeration
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
def enumerate_rules(inputs, outputs, *, connectivity="Automatic",
|
|
245
|
+
max_elements=None) -> list[Rule]: ...
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Replicates the Wolfram Physics Project's `EnumerateWolframModelRules`
|
|
249
|
+
exactly — same canonical forms, same order — so indices interoperate with
|
|
250
|
+
the registry and the literature. Signature sides are `(count, arity)` pairs;
|
|
251
|
+
`connectivity` is `"Automatic"` (left side connected and rule connected),
|
|
252
|
+
`"All"`, or `"None"`. Verified against captured ground truth: the complete
|
|
253
|
+
11-rule (1_2 -> 1_2) and 562-rule (2_2 -> 2_2) lists element-by-element, and
|
|
254
|
+
the published 40,405 count for 2_2 -> 4_2.
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
rules = sr.enumerate_rules([(2, 2)], [(2, 2)]) # 562 rules, registry order
|
|
258
|
+
sr.evolve(rules[101], [[1, 1], [1, 1]], events=500).plot()
|
|
259
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "setreplace"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Wolfram models in Python: SetReplace-exact hypergraph substitution and HypergraphPlot-style rendering, powered by Rust"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -113,6 +113,17 @@ def evolve(
|
|
|
113
113
|
event_ordering: list[str] | None = None,
|
|
114
114
|
random_seed: int = 0,
|
|
115
115
|
) -> HypergraphSystem: ...
|
|
116
|
+
def enumerate_rules(
|
|
117
|
+
inputs: list[tuple[int, int]] | list[list[int]],
|
|
118
|
+
outputs: list[tuple[int, int]] | list[list[int]],
|
|
119
|
+
*,
|
|
120
|
+
connectivity: str = "Automatic",
|
|
121
|
+
max_elements: int | None = None,
|
|
122
|
+
) -> list[Rule]:
|
|
123
|
+
"""All inequivalent rules of a signature ((count, arity) pairs per side),
|
|
124
|
+
replicating EnumerateWolframModelRules: same canonical forms, same order.
|
|
125
|
+
connectivity: "Automatic" (LHS connected, rule connected), "All", "None"."""
|
|
126
|
+
|
|
116
127
|
def set_replace(state: list[list[int]], rules: Rules, events: int = 1) -> list[list[int]]: ...
|
|
117
128
|
def set_replace_list(
|
|
118
129
|
state: list[list[int]], rules: Rules, events: int
|
|
@@ -538,6 +538,57 @@ fn evolve(
|
|
|
538
538
|
Ok(system)
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
+
/// All inequivalent rules of a signature, replicating the Wolfram Physics
|
|
542
|
+
/// Project's EnumerateWolframModelRules (canonical forms, same order).
|
|
543
|
+
/// Signature sides are (count, arity) pairs: 2 binary edges -> 3 binary
|
|
544
|
+
/// edges is `enumerate_rules([(2, 2)], [(3, 2)])`.
|
|
545
|
+
#[pyfunction]
|
|
546
|
+
#[pyo3(signature = (inputs, outputs, *, connectivity="Automatic", max_elements=None))]
|
|
547
|
+
fn enumerate_rules(
|
|
548
|
+
py: Python<'_>,
|
|
549
|
+
inputs: Vec<Vec<usize>>,
|
|
550
|
+
outputs: Vec<Vec<usize>>,
|
|
551
|
+
connectivity: &str,
|
|
552
|
+
max_elements: Option<usize>,
|
|
553
|
+
) -> PyResult<Vec<Rule>> {
|
|
554
|
+
fn groups(side: Vec<Vec<usize>>) -> PyResult<Vec<(usize, usize)>> {
|
|
555
|
+
side.into_iter()
|
|
556
|
+
.map(|g| {
|
|
557
|
+
if g.len() == 2 {
|
|
558
|
+
Ok((g[0], g[1]))
|
|
559
|
+
} else {
|
|
560
|
+
Err(PyValueError::new_err(
|
|
561
|
+
"signature sides must be lists of (count, arity) pairs, \
|
|
562
|
+
e.g. enumerate_rules([(2, 2)], [(3, 2)])",
|
|
563
|
+
))
|
|
564
|
+
}
|
|
565
|
+
})
|
|
566
|
+
.collect()
|
|
567
|
+
}
|
|
568
|
+
let connectivity = match connectivity {
|
|
569
|
+
"Automatic" => engine::Connectivity::LeftConnected,
|
|
570
|
+
"None" => engine::Connectivity::None,
|
|
571
|
+
"All" => engine::Connectivity::All,
|
|
572
|
+
other => {
|
|
573
|
+
return Err(PyValueError::new_err(format!(
|
|
574
|
+
"unknown connectivity `{other}`; valid: Automatic, None, All"
|
|
575
|
+
)))
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
let signature = engine::RuleSignature {
|
|
579
|
+
inputs: groups(inputs)?,
|
|
580
|
+
outputs: groups(outputs)?,
|
|
581
|
+
};
|
|
582
|
+
let options = engine::EnumerationOptions {
|
|
583
|
+
connectivity,
|
|
584
|
+
max_elements,
|
|
585
|
+
};
|
|
586
|
+
let rules = py
|
|
587
|
+
.allow_threads(|| engine::enumerate_rules(&signature, &options))
|
|
588
|
+
.map_err(engine_err)?;
|
|
589
|
+
Ok(rules.into_iter().map(|inner| Rule { inner }).collect())
|
|
590
|
+
}
|
|
591
|
+
|
|
541
592
|
/// SetReplace[state, rules, events]: applies up to `events` substitution
|
|
542
593
|
/// events and returns the resulting state.
|
|
543
594
|
#[pyfunction]
|
|
@@ -604,6 +655,7 @@ fn setreplace(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
604
655
|
m.add_function(wrap_pyfunction!(plot, m)?)?;
|
|
605
656
|
m.add_function(wrap_pyfunction!(layout, m)?)?;
|
|
606
657
|
m.add_function(wrap_pyfunction!(evolve, m)?)?;
|
|
658
|
+
m.add_function(wrap_pyfunction!(enumerate_rules, m)?)?;
|
|
607
659
|
m.add_function(wrap_pyfunction!(set_replace, m)?)?;
|
|
608
660
|
m.add_function(wrap_pyfunction!(set_replace_list, m)?)?;
|
|
609
661
|
m.add_function(wrap_pyfunction!(set_replace_all, m)?)?;
|
|
@@ -173,6 +173,23 @@ def test_determinism_and_seeds():
|
|
|
173
173
|
assert [e.inputs for e in a.events()] == [e.inputs for e in b.events()]
|
|
174
174
|
|
|
175
175
|
|
|
176
|
+
def test_enumerate_rules():
|
|
177
|
+
rules = sr.enumerate_rules([(1, 2)], [(1, 2)])
|
|
178
|
+
assert len(rules) == 11
|
|
179
|
+
assert rules[0].inputs == [[-1, -1]] and rules[0].outputs == [[-1, -1]]
|
|
180
|
+
assert len(sr.enumerate_rules([(1, 2)], [(1, 2)], max_elements=2)) == 7
|
|
181
|
+
assert len(sr.enumerate_rules([(2, 2)], [(2, 2)])) == 562
|
|
182
|
+
assert len(sr.enumerate_rules([(1, 2)], [(2, 2)], connectivity="None")) > 73
|
|
183
|
+
# Enumerated rules plug straight into evolution.
|
|
184
|
+
system = sr.evolve(rules[10], [[1, 2]], events=5)
|
|
185
|
+
assert system.events_count == 5
|
|
186
|
+
try:
|
|
187
|
+
sr.enumerate_rules([(1, 2)], [(1, 2)], connectivity="Bogus")
|
|
188
|
+
raise AssertionError("expected ValueError")
|
|
189
|
+
except ValueError as e:
|
|
190
|
+
assert "Bogus" in str(e)
|
|
191
|
+
|
|
192
|
+
|
|
176
193
|
def main():
|
|
177
194
|
tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
|
178
195
|
for t in tests:
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Growth-rate scan over an enumerated rule signature.
|
|
2
|
+
|
|
3
|
+
Replicates the Wolfram Physics Project's triage methodology: every
|
|
4
|
+
inequivalent rule of a signature is evolved from the standard self-loop
|
|
5
|
+
initial condition under the standard event ordering; per-generation state
|
|
6
|
+
sizes are recorded and classified. Results go to scans/ (gitignored).
|
|
7
|
+
|
|
8
|
+
Usage: python scripts/scan_growth_rates.py [--max-gens 9] [--max-events 10000]
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
import time
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import setreplace as sr
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def classify(sizes, reason):
|
|
21
|
+
"""Coarse growth classification from per-generation edge counts."""
|
|
22
|
+
if reason == "FixedPoint":
|
|
23
|
+
return "terminated", 0.0
|
|
24
|
+
# The last generation is incomplete if we stopped on the event cap.
|
|
25
|
+
if reason == "MaxEvents" and len(sizes) > 2:
|
|
26
|
+
sizes = sizes[:-1]
|
|
27
|
+
if len(sizes) < 4:
|
|
28
|
+
return "indeterminate", 0.0
|
|
29
|
+
tail = sizes[-5:]
|
|
30
|
+
ratios = [b / a for a, b in zip(tail, tail[1:]) if a > 0]
|
|
31
|
+
diffs = [b - a for a, b in zip(tail, tail[1:])]
|
|
32
|
+
if all(d == 0 for d in diffs):
|
|
33
|
+
return "static", 0.0
|
|
34
|
+
mean_r = sum(ratios) / len(ratios)
|
|
35
|
+
var_r = sum((r - mean_r) ** 2 for r in ratios) / len(ratios)
|
|
36
|
+
mean_d = sum(diffs) / len(diffs)
|
|
37
|
+
var_d = sum((d - mean_d) ** 2 for d in diffs) / len(diffs)
|
|
38
|
+
rel_r = math.sqrt(var_r) / mean_r if mean_r else 1.0
|
|
39
|
+
rel_d = math.sqrt(var_d) / abs(mean_d) if mean_d else 1.0
|
|
40
|
+
if mean_r > 1.25 and rel_r < 0.08:
|
|
41
|
+
return "exponential", round(mean_r, 3)
|
|
42
|
+
if rel_d < 0.25:
|
|
43
|
+
return "linear", round(mean_d, 3)
|
|
44
|
+
# Power-law fit s ~ t^p over the recorded generations.
|
|
45
|
+
points = [(g, s) for g, s in enumerate(sizes) if g >= 1 and s > 0]
|
|
46
|
+
if len(points) >= 3:
|
|
47
|
+
xs = [math.log(g) for g, _ in points]
|
|
48
|
+
ys = [math.log(s) for _, s in points]
|
|
49
|
+
n = len(xs)
|
|
50
|
+
mx, my = sum(xs) / n, sum(ys) / n
|
|
51
|
+
denom = sum((x - mx) ** 2 for x in xs)
|
|
52
|
+
if denom > 0:
|
|
53
|
+
p = sum((x - mx) * (y - my) for x, y in zip(xs, ys)) / denom
|
|
54
|
+
return "polynomial", round(p, 2)
|
|
55
|
+
return "irregular", 0.0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def connected(state):
|
|
59
|
+
"""Whether the hypergraph's edges form one component."""
|
|
60
|
+
if len(state) <= 1:
|
|
61
|
+
return True
|
|
62
|
+
parent = {}
|
|
63
|
+
|
|
64
|
+
def find(x):
|
|
65
|
+
while parent.setdefault(x, x) != x:
|
|
66
|
+
parent[x] = parent[parent[x]]
|
|
67
|
+
x = parent[x]
|
|
68
|
+
return x
|
|
69
|
+
|
|
70
|
+
for i, edge in enumerate(state):
|
|
71
|
+
for v in edge:
|
|
72
|
+
parent[find(("v", v))] = find(("e", i))
|
|
73
|
+
roots = {find(("e", i)) for i in range(len(state))}
|
|
74
|
+
return len(roots) == 1
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def hub_fraction(state):
|
|
78
|
+
"""Largest share of edges any single vertex appears in."""
|
|
79
|
+
if not state:
|
|
80
|
+
return 0.0
|
|
81
|
+
counts = {}
|
|
82
|
+
for edge in state:
|
|
83
|
+
for v in set(edge):
|
|
84
|
+
counts[v] = counts.get(v, 0) + 1
|
|
85
|
+
return round(max(counts.values()) / len(state), 3)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main():
|
|
89
|
+
ap = argparse.ArgumentParser()
|
|
90
|
+
ap.add_argument("--max-gens", type=int, default=9)
|
|
91
|
+
ap.add_argument("--max-events", type=int, default=10_000)
|
|
92
|
+
args = ap.parse_args()
|
|
93
|
+
|
|
94
|
+
signature_inputs, signature_outputs = [(2, 2)], [(3, 2)]
|
|
95
|
+
init = [[1, 1], [1, 1]] # standard self-loop initial condition
|
|
96
|
+
|
|
97
|
+
t0 = time.perf_counter()
|
|
98
|
+
rules = sr.enumerate_rules(signature_inputs, signature_outputs)
|
|
99
|
+
t_enum = time.perf_counter() - t0
|
|
100
|
+
print(f"enumerated {len(rules)} rules in {t_enum:.2f}s")
|
|
101
|
+
|
|
102
|
+
out_path = Path("scans/2_2-3_2.jsonl")
|
|
103
|
+
out_path.parent.mkdir(exist_ok=True)
|
|
104
|
+
counts = {}
|
|
105
|
+
total_events = 0
|
|
106
|
+
t1 = time.perf_counter()
|
|
107
|
+
with out_path.open("w") as out:
|
|
108
|
+
for i, rule in enumerate(rules):
|
|
109
|
+
system = sr.evolve(
|
|
110
|
+
rule, init, generations=args.max_gens, events=args.max_events
|
|
111
|
+
)
|
|
112
|
+
gens = system.generations_count
|
|
113
|
+
sizes = [len(system.state_at_generation(g)) for g in range(gens + 1)]
|
|
114
|
+
reason = system.termination_reason
|
|
115
|
+
cls, rate = classify(sizes, reason)
|
|
116
|
+
final = system.final_state
|
|
117
|
+
record = {
|
|
118
|
+
"i": i,
|
|
119
|
+
"rule": str(rule),
|
|
120
|
+
"reason": reason,
|
|
121
|
+
"events": system.events_count,
|
|
122
|
+
"gens": gens,
|
|
123
|
+
"sizes": sizes,
|
|
124
|
+
"atoms": system.final_atom_count,
|
|
125
|
+
"connected": connected(final),
|
|
126
|
+
"hub": hub_fraction(final),
|
|
127
|
+
"class": cls,
|
|
128
|
+
"rate": rate,
|
|
129
|
+
}
|
|
130
|
+
out.write(json.dumps(record) + "\n")
|
|
131
|
+
counts[cls] = counts.get(cls, 0) + 1
|
|
132
|
+
total_events += system.events_count
|
|
133
|
+
t_scan = time.perf_counter() - t1
|
|
134
|
+
|
|
135
|
+
print(f"scanned {len(rules)} rules in {t_scan:.2f}s "
|
|
136
|
+
f"({total_events} events, {total_events / t_scan:,.0f} events/s)")
|
|
137
|
+
for cls in sorted(counts, key=counts.get, reverse=True):
|
|
138
|
+
print(f" {cls:14} {counts[cls]:5} ({100 * counts[cls] / len(rules):.1f}%)")
|
|
139
|
+
print(f"results: {out_path}")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
main()
|