process-geometry 0.0.3__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.
Files changed (63) hide show
  1. aeg_shakespeare/__init__.py +52 -0
  2. aeg_shakespeare/_legacy_api.py +299 -0
  3. aeg_shakespeare/analysis/__init__.py +12 -0
  4. aeg_shakespeare/analysis/abelian.py +69 -0
  5. aeg_shakespeare/analysis/algebraic.py +11 -0
  6. aeg_shakespeare/analysis/am.py +19 -0
  7. aeg_shakespeare/analysis/connection.py +74 -0
  8. aeg_shakespeare/analysis/decomposition.py +37 -0
  9. aeg_shakespeare/analysis/module.py +5 -0
  10. aeg_shakespeare/central.py +20 -0
  11. aeg_shakespeare/constraints.py +121 -0
  12. aeg_shakespeare/construction.py +303 -0
  13. aeg_shakespeare/core.py +59 -0
  14. aeg_shakespeare/cost.py +51 -0
  15. aeg_shakespeare/discovery/__init__.py +53 -0
  16. aeg_shakespeare/discovery/coefficient_extension.py +75 -0
  17. aeg_shakespeare/discovery/polynomial.py +342 -0
  18. aeg_shakespeare/discovery/selection.py +146 -0
  19. aeg_shakespeare/discovery/structured.py +201 -0
  20. aeg_shakespeare/families.py +31 -0
  21. aeg_shakespeare/frame.py +5 -0
  22. aeg_shakespeare/function_theory/__init__.py +100 -0
  23. aeg_shakespeare/function_theory/abel_jacobi.py +246 -0
  24. aeg_shakespeare/function_theory/abelian.py +129 -0
  25. aeg_shakespeare/function_theory/algebraic.py +105 -0
  26. aeg_shakespeare/function_theory/am.py +266 -0
  27. aeg_shakespeare/function_theory/intersection.py +321 -0
  28. aeg_shakespeare/function_theory/module.py +118 -0
  29. aeg_shakespeare/function_theory/period_matrix.py +155 -0
  30. aeg_shakespeare/function_theory/periods.py +216 -0
  31. aeg_shakespeare/function_theory/real_branch_cycles.py +286 -0
  32. aeg_shakespeare/function_theory/weierstrass.py +136 -0
  33. aeg_shakespeare/grammar.py +225 -0
  34. aeg_shakespeare/history_geometry.py +276 -0
  35. aeg_shakespeare/linear.py +70 -0
  36. aeg_shakespeare/presentation/__init__.py +23 -0
  37. aeg_shakespeare/presentation/budget.py +27 -0
  38. aeg_shakespeare/presentation/canonicalization.py +143 -0
  39. aeg_shakespeare/presentation/constraints.py +5 -0
  40. aeg_shakespeare/presentation/construction.py +19 -0
  41. aeg_shakespeare/presentation/grammar.py +15 -0
  42. aeg_shakespeare/presentation/history.py +45 -0
  43. aeg_shakespeare/presentation/morphism.py +66 -0
  44. aeg_shakespeare/presentation/relations.py +31 -0
  45. aeg_shakespeare/presentation/search.py +31 -0
  46. aeg_shakespeare/process/__init__.py +16 -0
  47. aeg_shakespeare/process/finite/__init__.py +43 -0
  48. aeg_shakespeare/process/finite/cocycle.py +166 -0
  49. aeg_shakespeare/process/finite/families.py +318 -0
  50. aeg_shakespeare/process/history.py +53 -0
  51. aeg_shakespeare/process/local/__init__.py +7 -0
  52. aeg_shakespeare/process/local/direction.py +88 -0
  53. aeg_shakespeare/process/local/frame.py +73 -0
  54. aeg_shakespeare/process/local/system.py +43 -0
  55. aeg_shakespeare/relations.py +374 -0
  56. aeg_shakespeare/rewrite.py +157 -0
  57. aeg_shakespeare/search.py +286 -0
  58. aeg_shakespeare/signature.py +155 -0
  59. process_geometry-0.0.3.dist-info/METADATA +305 -0
  60. process_geometry-0.0.3.dist-info/RECORD +63 -0
  61. process_geometry-0.0.3.dist-info/WHEEL +5 -0
  62. process_geometry-0.0.3.dist-info/licenses/LICENSE +24 -0
  63. process_geometry-0.0.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,374 @@
1
+ """Generic relation discovery for bounded process presentations.
2
+
3
+ Linear algebra is used only as a computational backend for discovering exact
4
+ relations among process-generated expressions. The public results are process
5
+ relations, relation kernels, and decompositions of finite process grammars; no
6
+ spectral interpretation is required by the API.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from functools import reduce
13
+ from math import gcd
14
+ from typing import Sequence
15
+
16
+ import sympy as sp
17
+
18
+ from .process.local import ProcessSystem
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ProcessPolynomialRelation:
23
+ """A constant-coefficient process relation ``sum c_i D^i = 0``.
24
+
25
+ Coefficients are stored in ascending process order: ``(c_0, ..., c_n)``.
26
+ The relation is an algebraic certificate; callers may interpret it further,
27
+ but Shakespeare does not attach eigenvalue/frequency semantics to it.
28
+ """
29
+
30
+ coefficients: tuple[sp.Expr, ...]
31
+
32
+ @property
33
+ def order(self) -> int:
34
+ return len(self.coefficients) - 1
35
+
36
+ def as_expr(self, symbol: sp.Symbol | None = None) -> sp.Expr:
37
+ z = symbol or sp.Symbol("D")
38
+ return sp.expand(sum(c * z**i for i, c in enumerate(self.coefficients)))
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class ReturnRelation:
43
+ """A process polynomial relation attached to one expression."""
44
+
45
+ expression: sp.Expr
46
+ coefficients: tuple[sp.Expr, ...]
47
+
48
+ @property
49
+ def order(self) -> int:
50
+ return len(self.coefficients) - 1
51
+
52
+ @property
53
+ def relation(self) -> ProcessPolynomialRelation:
54
+ return ProcessPolynomialRelation(self.coefficients)
55
+
56
+ def as_expr(self, symbol: sp.Symbol | None = None) -> sp.Expr:
57
+ return self.relation.as_expr(symbol)
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class RelationKernel:
62
+ """Primitive expressions satisfying one discovered or declared relation."""
63
+
64
+ coefficients: tuple[sp.Expr, ...]
65
+ primitives: tuple[sp.Expr, ...]
66
+
67
+ @property
68
+ def order(self) -> int:
69
+ return len(self.coefficients) - 1
70
+
71
+ @property
72
+ def relation(self) -> ProcessPolynomialRelation:
73
+ return ProcessPolynomialRelation(self.coefficients)
74
+
75
+ def as_expr(self, symbol: sp.Symbol | None = None) -> sp.Expr:
76
+ return self.relation.as_expr(symbol)
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class RelationDecomposition:
81
+ """Template-free relation decomposition of a finite closed grammar."""
82
+
83
+ global_relation: ProcessPolynomialRelation
84
+ components: tuple[RelationKernel, ...]
85
+ complete: bool
86
+
87
+ @property
88
+ def primitives(self) -> tuple[sp.Expr, ...]:
89
+ return tuple(
90
+ primitive
91
+ for component in self.components
92
+ for primitive in component.primitives
93
+ )
94
+
95
+
96
+ def _normalize_coordinate_vector(v: Sequence[sp.Expr] | sp.Matrix) -> sp.Matrix:
97
+ """Normalize a coordinate vector without changing its length."""
98
+ values = [sp.simplify(sp.sympify(value)) for value in list(v)]
99
+ if not values or all(value == 0 for value in values):
100
+ return sp.Matrix(values)
101
+
102
+ if all(value.is_Rational is True for value in values):
103
+ rationals = [sp.Rational(value) for value in values]
104
+ denominators = [q.q for q in rationals]
105
+ lcm = sp.ilcm(*denominators) if denominators else 1
106
+ ints = [int(q * lcm) for q in rationals]
107
+ nonzero = [abs(value) for value in ints if value]
108
+ common = reduce(gcd, nonzero) if nonzero else 1
109
+ ints = [value // common for value in ints]
110
+ first = next((value for value in ints if value), 1)
111
+ if first < 0:
112
+ ints = [-value for value in ints]
113
+ return sp.Matrix([sp.Integer(value) for value in ints])
114
+
115
+ pivot = next(value for value in values if value != 0)
116
+ return sp.Matrix([sp.simplify(value / pivot) for value in values])
117
+
118
+
119
+ def _normalize_relation_coefficients(
120
+ coefficients: Sequence[sp.Expr] | sp.Matrix,
121
+ ) -> tuple[sp.Expr, ...]:
122
+ """Normalize a relation up to nonzero scalar multiplication."""
123
+ values = [sp.simplify(sp.sympify(value)) for value in list(coefficients)]
124
+ while len(values) > 1 and values[-1] == 0:
125
+ values.pop()
126
+ if not values or all(value == 0 for value in values):
127
+ raise ValueError("the zero relation is not informative")
128
+
129
+ if all(value.is_Rational is True for value in values):
130
+ return tuple(_normalize_coordinate_vector(values))
131
+
132
+ leading = values[-1]
133
+ return tuple(sp.simplify(value / leading) for value in values)
134
+
135
+
136
+ def _ambient_monomials(
137
+ expressions: Sequence[sp.Expr],
138
+ variables: Sequence[sp.Symbol],
139
+ ) -> list[tuple[int, ...]]:
140
+ monomials: set[tuple[int, ...]] = set()
141
+ for expression in expressions:
142
+ poly = sp.Poly(sp.expand(expression), *variables)
143
+ monomials.update(poly.monoms())
144
+ return sorted(monomials, reverse=True)
145
+
146
+
147
+ def _ambient_column(
148
+ expr: sp.Expr,
149
+ monomials: Sequence[tuple[int, ...]],
150
+ variables: Sequence[sp.Symbol],
151
+ ) -> sp.Matrix:
152
+ poly = sp.Poly(sp.expand(expr), *variables)
153
+ return sp.Matrix([poly.coeff_monomial(monomial) for monomial in monomials])
154
+
155
+
156
+ def coefficient_vector(
157
+ expr: sp.Expr,
158
+ basis: Sequence[sp.Expr],
159
+ variables: Sequence[sp.Symbol],
160
+ ) -> sp.Matrix:
161
+ """Return exact coordinates of ``expr`` in an arbitrary polynomial basis."""
162
+ basis = tuple(sp.expand(sp.sympify(item)) for item in basis)
163
+ if not basis:
164
+ raise ValueError("basis must be non-empty")
165
+
166
+ monomials = _ambient_monomials((*basis, sp.expand(sp.sympify(expr))), variables)
167
+ matrix = sp.Matrix.hstack(
168
+ *[_ambient_column(item, monomials, variables) for item in basis]
169
+ )
170
+ if matrix.rank() < len(basis):
171
+ raise ValueError("basis expressions are linearly dependent")
172
+
173
+ target = _ambient_column(expr, monomials, variables)
174
+ solution = sp.linsolve((matrix, target))
175
+ if solution is sp.EmptySet or not solution:
176
+ raise ValueError("expression is not in the supplied basis span")
177
+ row = next(iter(solution))
178
+ return sp.Matrix([sp.simplify(value) for value in row])
179
+
180
+
181
+ def action_matrix(system: ProcessSystem, basis: Sequence[sp.Expr]) -> sp.Matrix:
182
+ """Matrix backend for process action on a declared finite grammar."""
183
+ basis = tuple(basis)
184
+ columns: list[sp.Matrix] = []
185
+ for item in basis:
186
+ derived = sp.expand(system.derive(item))
187
+ try:
188
+ vector = coefficient_vector(derived, basis, system.assignments)
189
+ except ValueError as exc:
190
+ raise ValueError(
191
+ "declared basis is not closed under the process action"
192
+ ) from exc
193
+ columns.append(vector)
194
+ return sp.Matrix.hstack(*columns)
195
+
196
+
197
+ def _apply_relation(
198
+ system: ProcessSystem,
199
+ expr: sp.Expr,
200
+ coefficients: Sequence[sp.Expr],
201
+ ) -> sp.Expr:
202
+ orbit = system.iterate(expr, len(coefficients) - 1)
203
+ return sp.expand(sum(c * orbit[i] for i, c in enumerate(coefficients)))
204
+
205
+
206
+ def discover_return_relation(
207
+ system: ProcessSystem,
208
+ expr: sp.Expr,
209
+ max_order: int = 8,
210
+ ) -> ReturnRelation | None:
211
+ """Find the first exact constant-coefficient relation among ``D^i(expr)``."""
212
+ orbit = system.iterate(expr, max_order)
213
+ monomials: set[tuple[int, ...]] = set()
214
+ polys: list[sp.Poly] = []
215
+ for item in orbit:
216
+ poly = sp.Poly(item, *system.assignments)
217
+ polys.append(poly)
218
+ monomials.update(poly.monoms())
219
+ monomial_list = sorted(monomials, reverse=True)
220
+
221
+ for order in range(1, max_order + 1):
222
+ columns = [
223
+ sp.Matrix([polys[i].coeff_monomial(m) for m in monomial_list])
224
+ for i in range(order + 1)
225
+ ]
226
+ matrix = sp.Matrix.hstack(*columns)
227
+ candidates = [
228
+ vector
229
+ for vector in matrix.nullspace()
230
+ if sp.simplify(vector[-1]) != 0
231
+ ]
232
+ if not candidates:
233
+ continue
234
+ coefficients = _normalize_relation_coefficients(candidates[0])
235
+ if sp.simplify(_apply_relation(system, expr, coefficients)) != 0:
236
+ raise AssertionError("internal relation verification failed")
237
+ return ReturnRelation(sp.expand(expr), coefficients)
238
+ return None
239
+
240
+
241
+ def discover_operator_relation(
242
+ operator: sp.Matrix,
243
+ max_order: int | None = None,
244
+ ) -> ProcessPolynomialRelation | None:
245
+ """Discover the shortest polynomial relation satisfied by a finite action."""
246
+ operator = sp.Matrix(operator)
247
+ if operator.rows != operator.cols:
248
+ raise ValueError("operator must be square")
249
+ if operator.rows == 0:
250
+ raise ValueError("operator must be non-empty")
251
+
252
+ size = operator.rows
253
+ bound = size if max_order is None else min(max_order, size)
254
+ if bound < 1:
255
+ return None
256
+
257
+ powers = [sp.eye(size)]
258
+ columns = [sp.Matrix(list(powers[0]))]
259
+ for _order in range(1, bound + 1):
260
+ powers.append(sp.expand(powers[-1] * operator))
261
+ columns.append(sp.Matrix(list(powers[-1])))
262
+ matrix = sp.Matrix.hstack(*columns)
263
+ candidates = [
264
+ vector
265
+ for vector in matrix.nullspace()
266
+ if sp.simplify(vector[-1]) != 0
267
+ ]
268
+ if not candidates:
269
+ continue
270
+
271
+ coefficients = _normalize_relation_coefficients(candidates[0])
272
+ certificate = sp.zeros(size)
273
+ for index, coefficient in enumerate(coefficients):
274
+ certificate += coefficient * powers[index]
275
+ certificate = certificate.applyfunc(sp.simplify)
276
+ if certificate != sp.zeros(size):
277
+ raise AssertionError("operator-relation verification failed")
278
+ return ProcessPolynomialRelation(coefficients)
279
+ return None
280
+
281
+
282
+ def factor_process_relation(
283
+ relation: ProcessPolynomialRelation,
284
+ ) -> tuple[ProcessPolynomialRelation, ...]:
285
+ """Factor a process relation into pairwise-coprime primary factors."""
286
+ symbol = sp.Symbol("_D")
287
+ expression = relation.as_expr(symbol)
288
+ _unit, factors = sp.factor_list(expression, symbol)
289
+ if not factors:
290
+ return (relation,)
291
+
292
+ result: list[ProcessPolynomialRelation] = []
293
+ for factor, multiplicity in factors:
294
+ primary = sp.Poly(sp.expand(factor**multiplicity), symbol)
295
+ coefficients = tuple(reversed(primary.all_coeffs()))
296
+ result.append(
297
+ ProcessPolynomialRelation(_normalize_relation_coefficients(coefficients))
298
+ )
299
+ return tuple(result)
300
+
301
+
302
+ def discover_relation_kernel(
303
+ system: ProcessSystem,
304
+ basis: Sequence[sp.Expr],
305
+ coefficients: Sequence[sp.Expr],
306
+ ) -> RelationKernel:
307
+ """Discover all primitives in ``span(basis)`` satisfying a process relation."""
308
+ coefficients = _normalize_relation_coefficients(coefficients)
309
+ action = action_matrix(system, basis)
310
+ operator = sp.zeros(len(basis))
311
+ power = sp.eye(len(basis))
312
+ for coefficient in coefficients:
313
+ operator += coefficient * power
314
+ power = power * action
315
+
316
+ primitives: list[sp.Expr] = []
317
+ for raw in operator.nullspace():
318
+ vector = _normalize_coordinate_vector(raw)
319
+ expr = sp.expand(sum(vector[i] * basis[i] for i in range(len(basis))))
320
+ if sp.simplify(_apply_relation(system, expr, coefficients)) != 0:
321
+ raise AssertionError("relation-kernel verification failed")
322
+ if expr not in primitives:
323
+ primitives.append(expr)
324
+ return RelationKernel(coefficients=coefficients, primitives=tuple(primitives))
325
+
326
+
327
+ def discover_relation_decomposition(
328
+ system: ProcessSystem,
329
+ basis: Sequence[sp.Expr],
330
+ max_order: int | None = None,
331
+ ) -> RelationDecomposition | None:
332
+ """Discover relation factors and their primitive subgrammars jointly."""
333
+ basis = tuple(basis)
334
+ if not basis:
335
+ raise ValueError("basis must be non-empty")
336
+
337
+ action = action_matrix(system, basis)
338
+ global_relation = discover_operator_relation(action, max_order=max_order)
339
+ if global_relation is None:
340
+ return None
341
+
342
+ components = tuple(
343
+ discover_relation_kernel(system, basis, factor.coefficients)
344
+ for factor in factor_process_relation(global_relation)
345
+ )
346
+ primitives = tuple(
347
+ primitive
348
+ for component in components
349
+ for primitive in component.primitives
350
+ )
351
+
352
+ complete = False
353
+ if primitives:
354
+ coordinate_columns = [
355
+ coefficient_vector(primitive, basis, system.assignments)
356
+ for primitive in primitives
357
+ ]
358
+ complete = sp.Matrix.hstack(*coordinate_columns).rank() == len(basis)
359
+
360
+ return RelationDecomposition(
361
+ global_relation=global_relation,
362
+ components=components,
363
+ complete=complete,
364
+ )
365
+
366
+
367
+ def decompose(
368
+ target: sp.Expr,
369
+ primitives: Sequence[sp.Expr],
370
+ variables: Sequence[sp.Symbol],
371
+ ) -> tuple[sp.Expr, ...]:
372
+ """Express ``target`` uniquely in a supplied primitive grammar."""
373
+ vector = coefficient_vector(target, primitives, variables)
374
+ return tuple(sp.simplify(value) for value in vector)
@@ -0,0 +1,157 @@
1
+ """Exact rewriting for ordered process histories.
2
+
3
+ This module is deliberately independent of SymPy and derivation-style process
4
+ systems. It provides a first executable layer for noncommutative finite process
5
+ presentations: literal histories are preserved, oriented process relations are
6
+ applied explicitly, and every normalization carries a rewrite trace.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from typing import Generic, Literal, Sequence, TypeVar
13
+
14
+ from .process.history import ProcessWord
15
+
16
+ StepT = TypeVar("StepT")
17
+ RewriteReason = Literal["normal_form", "cycle", "max_steps"]
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class WordRewriteRule(Generic[StepT]):
22
+ """An oriented exact relation between two finite process words.
23
+
24
+ The rule rewrites a contiguous occurrence of ``lhs`` to ``rhs``. An empty
25
+ left-hand side is forbidden because it would permit insertion at every
26
+ history position and destroy the bounded-normalization semantics.
27
+ """
28
+
29
+ lhs: ProcessWord[StepT]
30
+ rhs: ProcessWord[StepT]
31
+ name: str = ""
32
+
33
+ def __post_init__(self) -> None:
34
+ if not self.lhs.steps:
35
+ raise ValueError("rewrite-rule lhs must be non-empty")
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class RewriteStep(Generic[StepT]):
40
+ """One certified history rewrite."""
41
+
42
+ rule: WordRewriteRule[StepT]
43
+ position: int
44
+ before: ProcessWord[StepT]
45
+ after: ProcessWord[StepT]
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class RewriteResult(Generic[StepT]):
50
+ """Result and certificate of a bounded normalization attempt."""
51
+
52
+ original: ProcessWord[StepT]
53
+ normal_form: ProcessWord[StepT]
54
+ trace: tuple[RewriteStep[StepT], ...]
55
+ terminated: bool
56
+ reason: RewriteReason
57
+
58
+ @property
59
+ def rewrite_steps(self) -> int:
60
+ return len(self.trace)
61
+
62
+ @property
63
+ def depth_delta(self) -> int:
64
+ """Final history depth minus original history depth."""
65
+
66
+ return self.normal_form.depth - self.original.depth
67
+
68
+
69
+ def rewrite_once(
70
+ history: ProcessWord[StepT],
71
+ rules: Sequence[WordRewriteRule[StepT]],
72
+ ) -> RewriteStep[StepT] | None:
73
+ """Apply one deterministic leftmost rewrite.
74
+
75
+ Positions are searched from left to right. If several rules match at the
76
+ same position, their order in ``rules`` is the tie breaker.
77
+ """
78
+
79
+ steps = history.steps
80
+ for position in range(len(steps)):
81
+ for rule in rules:
82
+ width = len(rule.lhs.steps)
83
+ if steps[position : position + width] != rule.lhs.steps:
84
+ continue
85
+ after = ProcessWord(
86
+ steps[:position] + rule.rhs.steps + steps[position + width :]
87
+ )
88
+ return RewriteStep(
89
+ rule=rule,
90
+ position=position,
91
+ before=history,
92
+ after=after,
93
+ )
94
+ return None
95
+
96
+
97
+ def normalize_word(
98
+ history: ProcessWord[StepT],
99
+ rules: Sequence[WordRewriteRule[StepT]],
100
+ *,
101
+ max_steps: int = 1_000,
102
+ detect_cycles: bool = True,
103
+ ) -> RewriteResult[StepT]:
104
+ """Normalize a process word under explicit oriented relations.
105
+
106
+ The function never assumes that the supplied rewrite system terminates or
107
+ is confluent. It returns ``terminated=False`` with an explicit reason when a
108
+ cycle or step bound is encountered. Literal input history and the complete
109
+ sequence of process-relation applications remain available in ``trace``.
110
+ """
111
+
112
+ if max_steps < 0:
113
+ raise ValueError("max_steps must be non-negative")
114
+
115
+ current = history
116
+ trace: list[RewriteStep[StepT]] = []
117
+ seen: list[ProcessWord[StepT]] = [history]
118
+
119
+ for _ in range(max_steps):
120
+ step = rewrite_once(current, rules)
121
+ if step is None:
122
+ return RewriteResult(
123
+ original=history,
124
+ normal_form=current,
125
+ trace=tuple(trace),
126
+ terminated=True,
127
+ reason="normal_form",
128
+ )
129
+
130
+ current = step.after
131
+ trace.append(step)
132
+
133
+ if detect_cycles and any(current == previous for previous in seen):
134
+ return RewriteResult(
135
+ original=history,
136
+ normal_form=current,
137
+ trace=tuple(trace),
138
+ terminated=False,
139
+ reason="cycle",
140
+ )
141
+ seen.append(current)
142
+
143
+ if rewrite_once(current, rules) is None:
144
+ return RewriteResult(
145
+ original=history,
146
+ normal_form=current,
147
+ trace=tuple(trace),
148
+ terminated=True,
149
+ reason="normal_form",
150
+ )
151
+ return RewriteResult(
152
+ original=history,
153
+ normal_form=current,
154
+ trace=tuple(trace),
155
+ terminated=False,
156
+ reason="max_steps",
157
+ )