ampzip 1.0.0__py3-none-any.whl
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.
- ampzip/__init__.py +7 -0
- ampzip/__main__.py +7 -0
- ampzip/_version.py +26 -0
- ampzip/atoms.py +74 -0
- ampzip/search/__init__.py +5 -0
- ampzip/search/action_space.py +101 -0
- ampzip/search/actions.py +135 -0
- ampzip/search/finiteflow.py +367 -0
- ampzip/search/gbfs.py +707 -0
- ampzip/search/gbfs_checkpoint.py +260 -0
- ampzip/search/objective_atoms.py +111 -0
- ampzip/search/observation.py +64 -0
- ampzip/search/spurious.py +309 -0
- ampzip/search/value_den.py +96 -0
- ampzip/simplifier/__init__.py +41 -0
- ampzip/simplifier/__main__.py +5 -0
- ampzip/simplifier/_paths.py +97 -0
- ampzip/simplifier/_source.py +84 -0
- ampzip/simplifier/algorithms/__init__.py +5 -0
- ampzip/simplifier/algorithms/_post.py +353 -0
- ampzip/simplifier/algorithms/_post_worker.py +45 -0
- ampzip/simplifier/algorithms/base.py +27 -0
- ampzip/simplifier/algorithms/cds.py +463 -0
- ampzip/simplifier/algorithms/gbfs.py +302 -0
- ampzip/simplifier/algorithms/gbfs_c.py +366 -0
- ampzip/simplifier/algorithms/gbfs_d.py +33 -0
- ampzip/simplifier/algorithms/gbfs_e.py +356 -0
- ampzip/simplifier/algorithms/gbfs_f.py +92 -0
- ampzip/simplifier/algorithms/sft_beam.py +172 -0
- ampzip/simplifier/algorithms/sft_greedy.py +264 -0
- ampzip/simplifier/atomize.py +237 -0
- ampzip/simplifier/cli.py +452 -0
- ampzip/simplifier/fetch_backends.py +280 -0
- ampzip/simplifier/input_limits.py +293 -0
- ampzip/simplifier/limits.py +139 -0
- ampzip/simplifier/log.py +53 -0
- ampzip/simplifier/parse.py +246 -0
- ampzip/simplifier/patches/sft_eval_extensions.patch +126 -0
- ampzip/simplifier/reconstruct.py +426 -0
- ampzip/simplifier/result.py +74 -0
- ampzip/simplifier/runner.py +606 -0
- ampzip/simplifier/validate.py +358 -0
- ampzip/verify/__init__.py +18 -0
- ampzip/verify/field.py +72 -0
- ampzip/verify/kinematics.py +83 -0
- ampzip/verify/twistor.py +166 -0
- ampzip/verify/verifier.py +243 -0
- ampzip/webservice/README.md +95 -0
- ampzip/webservice/__init__.py +24 -0
- ampzip/webservice/__main__.py +30 -0
- ampzip/webservice/app.py +561 -0
- ampzip/webservice/audit.py +106 -0
- ampzip/webservice/auth.py +161 -0
- ampzip/webservice/config.py +221 -0
- ampzip/webservice/deploy/cloudflared-config.example.yml +17 -0
- ampzip/webservice/deploy/cloudflared.service +20 -0
- ampzip/webservice/deploy/webservice.env.example +65 -0
- ampzip/webservice/deploy/webservice.service +33 -0
- ampzip/webservice/hashpw.py +51 -0
- ampzip/webservice/jobs.py +281 -0
- ampzip/webservice/pwhash.py +34 -0
- ampzip/webservice/ratelimit.py +37 -0
- ampzip/webservice/safety.py +19 -0
- ampzip/webservice/static/index.html +1573 -0
- ampzip/webservice/static/mathjax/LICENSE +202 -0
- ampzip/webservice/static/mathjax/tex-svg.js +1 -0
- ampzip-1.0.0.dist-info/METADATA +171 -0
- ampzip-1.0.0.dist-info/RECORD +71 -0
- ampzip-1.0.0.dist-info/WHEEL +4 -0
- ampzip-1.0.0.dist-info/entry_points.txt +3 -0
- ampzip-1.0.0.dist-info/licenses/LICENSE +21 -0
ampzip/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""ampzip: training-free simplification of spinor-helicity amplitudes.
|
|
2
|
+
|
|
3
|
+
``simplifier`` holds the CLI, the input check and the backends, ``search`` the best-first
|
|
4
|
+
search, ``verify`` the finite-field test and ``webservice`` the HTTP service. The moves are
|
|
5
|
+
executed by the extension module ``ampzip_engine`` built from ``ampzip/engine``.
|
|
6
|
+
"""
|
|
7
|
+
from ._version import __version__ # noqa: F401
|
ampzip/__main__.py
ADDED
ampzip/_version.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""The package version, from one source.
|
|
2
|
+
|
|
3
|
+
An installed distribution reports the version in its metadata, taken from `pyproject.toml`
|
|
4
|
+
at build time. A source checkout run without installing reads the same field from the
|
|
5
|
+
`pyproject.toml` next to the package.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version as _dist_version
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _read() -> str:
|
|
14
|
+
try:
|
|
15
|
+
return _dist_version("ampzip")
|
|
16
|
+
except PackageNotFoundError:
|
|
17
|
+
pass
|
|
18
|
+
try:
|
|
19
|
+
import tomllib
|
|
20
|
+
data = tomllib.loads((Path(__file__).resolve().parents[1] / "pyproject.toml").read_text())
|
|
21
|
+
return str(data["project"]["version"]) + "+source"
|
|
22
|
+
except Exception: # noqa: BLE001 no pyproject next to the package
|
|
23
|
+
return "0+unknown"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__version__ = _read()
|
ampzip/atoms.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""The bracket atoms and their canonical ordering.
|
|
2
|
+
|
|
3
|
+
`ab(i, j)` is the angle bracket ⟨i j⟩ and `sb(i, j)` the square bracket [i j], both as
|
|
4
|
+
uninterpreted sympy functions of two leg labels. Every expression the package handles is
|
|
5
|
+
built from these two atoms and `opaque_den`, the engine's marker for a polynomial
|
|
6
|
+
denominator factor, which is created where it is needed.
|
|
7
|
+
|
|
8
|
+
`canonical_order(expr)` rewrites every bracket to `i < j`, pushing the antisymmetry sign into
|
|
9
|
+
the coefficient: `ab(2, 1) -> -ab(1, 2)`. The engine, the atomizer and the display layer all
|
|
10
|
+
rely on this spelling, so a Mandelstam `s_ij = ⟨ij⟩[ji]` is stored as `-ab(i,j)*sb(i,j)`.
|
|
11
|
+
|
|
12
|
+
`ATOMS` is the registry of atom classes by name that the input check consults. A new atom
|
|
13
|
+
kind is added by registering it here together with its canonicalisation rule.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Dict, Type
|
|
18
|
+
|
|
19
|
+
import sympy as sp
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _leg_labels(printer, args):
|
|
23
|
+
"""The leg labels for display. An integer prints as itself, and a symbol such as `p_{3}`
|
|
24
|
+
prints as its bare label."""
|
|
25
|
+
return [printer._print(a).replace("p_{", "").replace("}", "") for a in args]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _Bracket(sp.Function):
|
|
29
|
+
"""A two-leg bracket. Subclasses only choose the delimiters."""
|
|
30
|
+
|
|
31
|
+
_delims = ("", "")
|
|
32
|
+
|
|
33
|
+
# sympy would infer is_number from the integer leg labels and then evaluate bracket
|
|
34
|
+
# products numerically while printing, which takes seconds on a power of a sum.
|
|
35
|
+
is_number = False
|
|
36
|
+
|
|
37
|
+
def _latex(self, printer, exp=1):
|
|
38
|
+
i, j = _leg_labels(printer, self.args)
|
|
39
|
+
left, right = self._delims
|
|
40
|
+
body = f"{left}{i} {j}{right}"
|
|
41
|
+
return body if exp == 1 else f"{body}^{{{exp}}}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ab(_Bracket):
|
|
45
|
+
"""Angle bracket ⟨i j⟩ of legs i and j."""
|
|
46
|
+
|
|
47
|
+
_delims = (r"\langle ", r" \rangle")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class sb(_Bracket):
|
|
51
|
+
"""Square bracket [i j] of legs i and j."""
|
|
52
|
+
|
|
53
|
+
_delims = (r"\left[ ", r" \right]")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
ATOMS: Dict[str, Type[sp.Function]] = {"ab": ab, "sb": sb}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def bracket_classes():
|
|
60
|
+
"""The two bracket classes in the order every caller unpacks them, `ab, sb`."""
|
|
61
|
+
return ab, sb
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def canonical_order(expr: sp.Expr) -> sp.Expr:
|
|
65
|
+
"""Rewrite each two-leg bracket with its legs ascending, times -1 when they were swapped.
|
|
66
|
+
|
|
67
|
+
It applies to every two-argument function atom in `expr`, which is what the bracket
|
|
68
|
+
antisymmetry needs, and leaves one-argument atoms such as `opaque_den` alone.
|
|
69
|
+
"""
|
|
70
|
+
replace = {}
|
|
71
|
+
for fun in expr.atoms(sp.Function):
|
|
72
|
+
if len(fun.args) == 2 and fun.args[0] > fun.args[1]:
|
|
73
|
+
replace[fun] = fun.func(fun.args[1], fun.args[0]) * (-1)
|
|
74
|
+
return expr.subs(replace)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""A fixed-size action encoding and legality mask for a learned policy.
|
|
2
|
+
|
|
3
|
+
It follows the SFT action spaces of `RL_amplitudes_claude/src/action_space_{4,5,6}pt.py`.
|
|
4
|
+
|
|
5
|
+
action = bracket_idx * (n_term_slots * identities_per_bracket)
|
|
6
|
+
+ term_slot * identities_per_bracket
|
|
7
|
+
+ identity_idx
|
|
8
|
+
|
|
9
|
+
The sizes n_brackets x n_term_slots x identities_per_bracket are 12x11x11 = 1452 at 4
|
|
10
|
+
points, 20x26x19 = 9880 at 5 and 30x31x32 = 29760 at 6. The per-n `max_terms` defaults
|
|
11
|
+
below set n_term_slots. `term_slot=0` is the whole expression and `term_slot=k>0` the
|
|
12
|
+
engine's k-1'th numerator term. The identity order is that of `actions.identity_menu`,
|
|
13
|
+
Schouten, then momentum, then momentum_sq.
|
|
14
|
+
|
|
15
|
+
The mask marks the legal action indices, so a policy can exclude illegal actions when
|
|
16
|
+
selecting from its logits. `decode_to_move` maps a selected index to an engine move for
|
|
17
|
+
`Expr.apply`. The sequential search enumerates moves through `actions.legal_moves` and the
|
|
18
|
+
parallel expansion through `Expr.legal_move_list`. Neither uses this fixed encoding.
|
|
19
|
+
|
|
20
|
+
The default term-slot limits are 10, 25 and 30 for 4, 5 and 6-point inputs. Single-term
|
|
21
|
+
actions exist only for the first `max_terms` numerator terms.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
from .actions import identity_menu, _term_brackets
|
|
28
|
+
|
|
29
|
+
# The per-n term-slot cap, with n_term_slots = 1 + max_terms, matching the action-space sizes
|
|
30
|
+
# above. It bounds how many per-term slots the fixed encoding can represent and is the
|
|
31
|
+
# search's own choice, like the term_slot ordering.
|
|
32
|
+
_DEFAULT_MAX_TERMS = {4: 10, 5: 25, 6: 30}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def default_max_terms(n: int) -> int:
|
|
36
|
+
return _DEFAULT_MAX_TERMS.get(n, 10)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ActionSpace:
|
|
40
|
+
def __init__(self, n: int = 4, max_terms: int | None = None):
|
|
41
|
+
self.n = n
|
|
42
|
+
if max_terms is None:
|
|
43
|
+
max_terms = default_max_terms(n)
|
|
44
|
+
self.max_terms = max_terms
|
|
45
|
+
self.n_term_slots = 1 + max_terms
|
|
46
|
+
self.brackets = self._canonical_brackets(n) # [(square,i,j)], ab before sb
|
|
47
|
+
self._bidx = {b: i for i, b in enumerate(self.brackets)}
|
|
48
|
+
# The menu has the same size for every bracket.
|
|
49
|
+
self.identities_per_bracket = len(identity_menu(False, 1, 2, n))
|
|
50
|
+
self.n_actions = len(self.brackets) * self.n_term_slots * self.identities_per_bracket
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def _canonical_brackets(n: int):
|
|
54
|
+
out = []
|
|
55
|
+
for square in (False, True): # ab, then sb
|
|
56
|
+
for i in range(1, n + 1):
|
|
57
|
+
for j in range(i + 1, n + 1):
|
|
58
|
+
out.append((square, i, j))
|
|
59
|
+
return out
|
|
60
|
+
|
|
61
|
+
def encode(self, bracket_idx: int, term_slot: int, identity_idx: int) -> int:
|
|
62
|
+
return (bracket_idx * (self.n_term_slots * self.identities_per_bracket)
|
|
63
|
+
+ term_slot * self.identities_per_bracket + identity_idx)
|
|
64
|
+
|
|
65
|
+
def decode(self, action: int):
|
|
66
|
+
idy = action % self.identities_per_bracket
|
|
67
|
+
rest = action // self.identities_per_bracket
|
|
68
|
+
term_slot = rest % self.n_term_slots
|
|
69
|
+
bracket_idx = rest // self.n_term_slots
|
|
70
|
+
return bracket_idx, term_slot, idy
|
|
71
|
+
|
|
72
|
+
def decode_to_move(self, action: int):
|
|
73
|
+
"""The move (square,i,j,term_slot,identity,params) of an action index, for Expr.apply."""
|
|
74
|
+
bracket_idx, term_slot, idy = self.decode(action)
|
|
75
|
+
square, i, j = self.brackets[bracket_idx]
|
|
76
|
+
ident, params = identity_menu(square, i, j, self.n)[idy]
|
|
77
|
+
return (square, i, j, term_slot, ident, params)
|
|
78
|
+
|
|
79
|
+
def mask(self, handle) -> np.ndarray:
|
|
80
|
+
"""The 0/1 legality vector of length n_actions for the current expression.
|
|
81
|
+
|
|
82
|
+
Every identity of a bracket is legal once the bracket is present, as in
|
|
83
|
+
`build_action_mask_v3`.
|
|
84
|
+
"""
|
|
85
|
+
m = np.zeros(self.n_actions, dtype=np.float32)
|
|
86
|
+
n_id = self.identities_per_bracket
|
|
87
|
+
present = handle.distinct_brackets()
|
|
88
|
+
per_term = [_term_brackets(s) for s in handle.term_monos()]
|
|
89
|
+
for b in present:
|
|
90
|
+
b_idx = self._bidx.get(tuple(b))
|
|
91
|
+
if b_idx is None:
|
|
92
|
+
continue
|
|
93
|
+
base = self.encode(b_idx, 0, 0) # the whole-expression block
|
|
94
|
+
m[base:base + n_id] = 1.0
|
|
95
|
+
for k, bset in enumerate(per_term):
|
|
96
|
+
if k >= self.max_terms:
|
|
97
|
+
break
|
|
98
|
+
if tuple(b) in bset:
|
|
99
|
+
base = self.encode(b_idx, k + 1, 0)
|
|
100
|
+
m[base:base + n_id] = 1.0
|
|
101
|
+
return m
|
ampzip/search/actions.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Legal-move enumeration on an `ampzip_engine.Expr` handle.
|
|
2
|
+
|
|
3
|
+
A move is `(square, i, j, term_slot, identity, params)`. It applies the identity to the
|
|
4
|
+
bracket `bk(i,j)`, square False being ab and True sb, either to the whole expression with
|
|
5
|
+
`term_slot=0` or to one numerator term with `term_slot=k>0`, the engine's k-1'th term.
|
|
6
|
+
|
|
7
|
+
The per-bracket identity menu reproduces the SFT action spaces of
|
|
8
|
+
`RL_amplitudes_claude/src/action_space_{4,5,6}pt.py::get_identity_types_for_bracket`. Per
|
|
9
|
+
bracket there are C(n-2,2) Schouten, 2(n-1) momentum and 2^(n-2) momentum_sq identities,
|
|
10
|
+
11 at 4 points, 19 at 5 and 32 at 6.
|
|
11
|
+
|
|
12
|
+
Legality follows `eval_sft_4pt.py::build_action_mask_v3`. A bracket move is legal at
|
|
13
|
+
`term_slot=0` when the bracket is in the numerator and at `term_slot=k>0` when the bracket
|
|
14
|
+
is in that term. There is no identity-specific masking. The full identity menu is offered
|
|
15
|
+
once the bracket is present, and degenerate identities are filtered when the engine applies
|
|
16
|
+
them.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import ast
|
|
21
|
+
import re
|
|
22
|
+
from itertools import combinations
|
|
23
|
+
|
|
24
|
+
_BRACKET_RE = re.compile(r"\b(ab|sb)\((\d+),\s*(\d+)\)")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def identity_menu(square: bool, i: int, j: int, n: int):
|
|
28
|
+
"""The (identity, params) menu for bracket `bk(i,j)`, 11, 19 or 32 entries at n = 4, 5 or 6.
|
|
29
|
+
|
|
30
|
+
The order matches the SFT action spaces, Schouten, then momentum, then momentum_sq. The
|
|
31
|
+
params encoding matches `ampzip_engine`, [pk, pl] for schouten, [pout1, pout2] for
|
|
32
|
+
momentum and the mom_add list for momentum_sq.
|
|
33
|
+
"""
|
|
34
|
+
other = [m for m in range(1, n + 1) if m != i and m != j]
|
|
35
|
+
menu = []
|
|
36
|
+
# Schouten, every one of the C(n-2, 2) pairs of the other legs in combinations order.
|
|
37
|
+
for pk, pl in combinations(other, 2):
|
|
38
|
+
menu.append(("schouten", [pk, pl]))
|
|
39
|
+
# Momentum, pout1 among the bracket's own legs and pout2 among pout1 and the other legs, 2(n-1) in all.
|
|
40
|
+
for pout1 in (i, j):
|
|
41
|
+
for pout2 in [pout1] + other:
|
|
42
|
+
menu.append(("momentum", [pout1, pout2]))
|
|
43
|
+
# Momentum_sq, every subset of the other legs as mom_add, 2^(n-2) in all.
|
|
44
|
+
for size in range(len(other) + 1):
|
|
45
|
+
for mom_add in combinations(other, size):
|
|
46
|
+
menu.append(("momentum_sq", list(mom_add)))
|
|
47
|
+
return menu
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _term_brackets(mono_str: str):
|
|
51
|
+
"""The set of (square, i, j) brackets present in a canonical monomial string."""
|
|
52
|
+
return {
|
|
53
|
+
(typ == "sb", int(a), int(b))
|
|
54
|
+
for typ, a, b in _BRACKET_RE.findall(mono_str)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def legal_moves(handle, n: int):
|
|
59
|
+
"""All legal `(square, i, j, term_slot, identity, params)` moves for `handle`.
|
|
60
|
+
|
|
61
|
+
It yields `term_slot=0`, the whole expression, for every present numerator bracket, plus
|
|
62
|
+
`term_slot=k+1` for each numerator term that contains the bracket.
|
|
63
|
+
|
|
64
|
+
The engine also enumerates moves through `Expr.legal_moves`, but returning a
|
|
65
|
+
Python tuple, string and parameter list for every move can make it slower than
|
|
66
|
+
this function, which reuses each identity's `params` list across term slots.
|
|
67
|
+
The threaded search avoids these allocations by keeping the move list in Rust
|
|
68
|
+
with `Expr.legal_move_list` and applying slices through `Expr.apply_slice_coded`,
|
|
69
|
+
falling back to `Expr.apply_slice` on older engines.
|
|
70
|
+
"""
|
|
71
|
+
present = handle.distinct_brackets() # the [(square, i, j)] of the numerator
|
|
72
|
+
if not present:
|
|
73
|
+
return []
|
|
74
|
+
per_term = [_term_brackets(m) for m in handle.term_monos()]
|
|
75
|
+
moves = []
|
|
76
|
+
for square, i, j in present:
|
|
77
|
+
menu = identity_menu(square, i, j, n)
|
|
78
|
+
# the whole expression
|
|
79
|
+
for ident, params in menu:
|
|
80
|
+
moves.append((square, i, j, 0, ident, params))
|
|
81
|
+
# a single term
|
|
82
|
+
for k, bset in enumerate(per_term):
|
|
83
|
+
if (square, i, j) in bset:
|
|
84
|
+
for ident, params in menu:
|
|
85
|
+
moves.append((square, i, j, k + 1, ident, params))
|
|
86
|
+
return moves
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# The explicit name of the Python move enumerator, kept for compatibility and for comparison
|
|
90
|
+
# with the Rust implementation.
|
|
91
|
+
legal_moves_py = legal_moves
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def move_rule(square, i, j, identity, params, n):
|
|
95
|
+
"""The local rewrite a move performs on `bk(i,j)`, as `(lhs_str, rhs_str)` in ab/sb
|
|
96
|
+
notation. The right-hand side comes from applying the identity to the isolated bracket,
|
|
97
|
+
so it is the literal substitution the engine makes, signs and all, with no re-derivation
|
|
98
|
+
of the algebra here. Returns `(lhs, None)` when the engine cannot build it, as with
|
|
99
|
+
degenerate params.
|
|
100
|
+
"""
|
|
101
|
+
import ampzip_engine as _E
|
|
102
|
+
lhs = f"{'sb' if square else 'ab'}({i},{j})"
|
|
103
|
+
try:
|
|
104
|
+
rhs = _E.Expr(lhs).apply(square, i, j, identity, list(params), n, term_slot=0).to_str()
|
|
105
|
+
except Exception:
|
|
106
|
+
rhs = None
|
|
107
|
+
return lhs, rhs
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def annotate_trajectory_rules(traj, n):
|
|
111
|
+
"""Add the ab/sb strings `rule_lhs` and `rule_rhs` to each trajectory step in place,
|
|
112
|
+
reconstructed from its `bracket`, `identity` and `params`. A step missing fields or whose
|
|
113
|
+
rule cannot be built is left as it is. It accepts the raw rollout trajectory, where
|
|
114
|
+
params is a list, and a serialised one, where params is a string.
|
|
115
|
+
"""
|
|
116
|
+
if not traj:
|
|
117
|
+
return traj
|
|
118
|
+
for step in traj:
|
|
119
|
+
if not isinstance(step, dict) or step.get("rule_lhs"):
|
|
120
|
+
continue
|
|
121
|
+
m = _BRACKET_RE.search(str(step.get("bracket", "")))
|
|
122
|
+
ident, params = step.get("identity"), step.get("params")
|
|
123
|
+
if not m or ident is None or params is None:
|
|
124
|
+
continue
|
|
125
|
+
if not isinstance(params, (list, tuple)):
|
|
126
|
+
try:
|
|
127
|
+
params = ast.literal_eval(str(params))
|
|
128
|
+
except Exception:
|
|
129
|
+
continue
|
|
130
|
+
square, ii, jj = (m.group(1) == "sb"), int(m.group(2)), int(m.group(3))
|
|
131
|
+
lhs, rhs = move_rule(square, ii, jj, ident, list(params), n)
|
|
132
|
+
step["rule_lhs"] = lhs
|
|
133
|
+
if rhs is not None:
|
|
134
|
+
step["rule_rhs"] = rhs
|
|
135
|
+
return traj
|