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.
- aeg_shakespeare/__init__.py +52 -0
- aeg_shakespeare/_legacy_api.py +299 -0
- aeg_shakespeare/analysis/__init__.py +12 -0
- aeg_shakespeare/analysis/abelian.py +69 -0
- aeg_shakespeare/analysis/algebraic.py +11 -0
- aeg_shakespeare/analysis/am.py +19 -0
- aeg_shakespeare/analysis/connection.py +74 -0
- aeg_shakespeare/analysis/decomposition.py +37 -0
- aeg_shakespeare/analysis/module.py +5 -0
- aeg_shakespeare/central.py +20 -0
- aeg_shakespeare/constraints.py +121 -0
- aeg_shakespeare/construction.py +303 -0
- aeg_shakespeare/core.py +59 -0
- aeg_shakespeare/cost.py +51 -0
- aeg_shakespeare/discovery/__init__.py +53 -0
- aeg_shakespeare/discovery/coefficient_extension.py +75 -0
- aeg_shakespeare/discovery/polynomial.py +342 -0
- aeg_shakespeare/discovery/selection.py +146 -0
- aeg_shakespeare/discovery/structured.py +201 -0
- aeg_shakespeare/families.py +31 -0
- aeg_shakespeare/frame.py +5 -0
- aeg_shakespeare/function_theory/__init__.py +100 -0
- aeg_shakespeare/function_theory/abel_jacobi.py +246 -0
- aeg_shakespeare/function_theory/abelian.py +129 -0
- aeg_shakespeare/function_theory/algebraic.py +105 -0
- aeg_shakespeare/function_theory/am.py +266 -0
- aeg_shakespeare/function_theory/intersection.py +321 -0
- aeg_shakespeare/function_theory/module.py +118 -0
- aeg_shakespeare/function_theory/period_matrix.py +155 -0
- aeg_shakespeare/function_theory/periods.py +216 -0
- aeg_shakespeare/function_theory/real_branch_cycles.py +286 -0
- aeg_shakespeare/function_theory/weierstrass.py +136 -0
- aeg_shakespeare/grammar.py +225 -0
- aeg_shakespeare/history_geometry.py +276 -0
- aeg_shakespeare/linear.py +70 -0
- aeg_shakespeare/presentation/__init__.py +23 -0
- aeg_shakespeare/presentation/budget.py +27 -0
- aeg_shakespeare/presentation/canonicalization.py +143 -0
- aeg_shakespeare/presentation/constraints.py +5 -0
- aeg_shakespeare/presentation/construction.py +19 -0
- aeg_shakespeare/presentation/grammar.py +15 -0
- aeg_shakespeare/presentation/history.py +45 -0
- aeg_shakespeare/presentation/morphism.py +66 -0
- aeg_shakespeare/presentation/relations.py +31 -0
- aeg_shakespeare/presentation/search.py +31 -0
- aeg_shakespeare/process/__init__.py +16 -0
- aeg_shakespeare/process/finite/__init__.py +43 -0
- aeg_shakespeare/process/finite/cocycle.py +166 -0
- aeg_shakespeare/process/finite/families.py +318 -0
- aeg_shakespeare/process/history.py +53 -0
- aeg_shakespeare/process/local/__init__.py +7 -0
- aeg_shakespeare/process/local/direction.py +88 -0
- aeg_shakespeare/process/local/frame.py +73 -0
- aeg_shakespeare/process/local/system.py +43 -0
- aeg_shakespeare/relations.py +374 -0
- aeg_shakespeare/rewrite.py +157 -0
- aeg_shakespeare/search.py +286 -0
- aeg_shakespeare/signature.py +155 -0
- process_geometry-0.0.3.dist-info/METADATA +305 -0
- process_geometry-0.0.3.dist-info/RECORD +63 -0
- process_geometry-0.0.3.dist-info/WHEEL +5 -0
- process_geometry-0.0.3.dist-info/licenses/LICENSE +24 -0
- process_geometry-0.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Costed search over reusable process-presentation candidates.
|
|
2
|
+
|
|
3
|
+
The search layer does not define one universal scalar objective. It evaluates
|
|
4
|
+
candidate presentations into the public multi-axis ``PresentationCost`` and
|
|
5
|
+
returns the Pareto frontier. The symbolic adapters implement exact
|
|
6
|
+
reconstruction tasks over ``GeneratedPresentation`` objects; downstream users
|
|
7
|
+
can reuse the generic candidate/frontier machinery for history codes, task
|
|
8
|
+
quotients, or other presentation families.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
import math
|
|
15
|
+
from typing import Callable, Generic, Sequence, TypeVar
|
|
16
|
+
|
|
17
|
+
import sympy as sp
|
|
18
|
+
|
|
19
|
+
from .construction import PrimitiveProposal
|
|
20
|
+
from .cost import PresentationCost
|
|
21
|
+
from .grammar import GeneratedPresentation, discover_generated_presentation
|
|
22
|
+
from .presentation.budget import SearchBudget
|
|
23
|
+
from .process.local import ProcessSystem
|
|
24
|
+
from .relations import decompose
|
|
25
|
+
|
|
26
|
+
PayloadT = TypeVar("PayloadT")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class PresentationCandidate(Generic[PayloadT]):
|
|
31
|
+
"""One evaluated representation candidate."""
|
|
32
|
+
|
|
33
|
+
payload: PayloadT
|
|
34
|
+
cost: PresentationCost
|
|
35
|
+
sufficient: bool = True
|
|
36
|
+
label: str | None = None
|
|
37
|
+
certificate: object | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class PresentationSearchResult(Generic[PayloadT]):
|
|
42
|
+
"""All evaluated candidates and the task-sufficient Pareto frontier."""
|
|
43
|
+
|
|
44
|
+
evaluated: tuple[PresentationCandidate[PayloadT], ...]
|
|
45
|
+
pareto: tuple[PresentationCandidate[PayloadT], ...]
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def rejected(self) -> tuple[PresentationCandidate[PayloadT], ...]:
|
|
49
|
+
return tuple(candidate for candidate in self.evaluated if not candidate.sufficient)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def pareto_frontier(
|
|
53
|
+
candidates: Sequence[PresentationCandidate[PayloadT]],
|
|
54
|
+
*,
|
|
55
|
+
require_sufficient: bool = True,
|
|
56
|
+
) -> tuple[PresentationCandidate[PayloadT], ...]:
|
|
57
|
+
"""Return candidates not Pareto-dominated by another admissible candidate."""
|
|
58
|
+
admissible = tuple(
|
|
59
|
+
candidate
|
|
60
|
+
for candidate in candidates
|
|
61
|
+
if candidate.sufficient or not require_sufficient
|
|
62
|
+
)
|
|
63
|
+
frontier: list[PresentationCandidate[PayloadT]] = []
|
|
64
|
+
for candidate in admissible:
|
|
65
|
+
if any(
|
|
66
|
+
other is not candidate and other.cost.dominates(candidate.cost)
|
|
67
|
+
for other in admissible
|
|
68
|
+
):
|
|
69
|
+
continue
|
|
70
|
+
frontier.append(candidate)
|
|
71
|
+
return tuple(frontier)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class ExactReconstructionPresentation:
|
|
76
|
+
"""Generated symbolic presentation evaluated for exact target decoding."""
|
|
77
|
+
|
|
78
|
+
proposal_seeds: tuple[sp.Expr, ...]
|
|
79
|
+
targets: tuple[sp.Expr, ...]
|
|
80
|
+
presentation: GeneratedPresentation
|
|
81
|
+
target_coordinates: tuple[tuple[sp.Expr, ...], ...]
|
|
82
|
+
failure: str | None = None
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def sufficient(self) -> bool:
|
|
86
|
+
return self.failure is None and len(self.target_coordinates) == len(self.targets)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class ConstructedPrimitivePresentation:
|
|
91
|
+
"""One construction-history-preserving primitive proposal after evaluation."""
|
|
92
|
+
|
|
93
|
+
proposal: PrimitiveProposal
|
|
94
|
+
evaluation: ExactReconstructionPresentation
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def sufficient(self) -> bool:
|
|
98
|
+
return self.evaluation.sufficient
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _expression_complexity(expr: sp.Expr) -> float:
|
|
102
|
+
"""Small structural proxy used only by the default search cost model."""
|
|
103
|
+
expr = sp.expand(sp.sympify(expr))
|
|
104
|
+
return float(int(sp.count_ops(expr, visual=False)) + 1)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def structural_exact_reconstruction_cost(
|
|
108
|
+
candidate: ExactReconstructionPresentation,
|
|
109
|
+
) -> PresentationCost:
|
|
110
|
+
"""Default transparent structural cost for exact symbolic candidates."""
|
|
111
|
+
presentation = candidate.presentation
|
|
112
|
+
grammar_cost = sum(_expression_complexity(seed) for seed in candidate.proposal_seeds)
|
|
113
|
+
grammar_cost += sum(_expression_complexity(item) for item in presentation.primitives)
|
|
114
|
+
|
|
115
|
+
relation_cost = 0.0
|
|
116
|
+
if presentation.relations is not None:
|
|
117
|
+
relation_cost += sum(
|
|
118
|
+
1.0
|
|
119
|
+
for coefficient in presentation.relations.global_relation.coefficients
|
|
120
|
+
if sp.simplify(coefficient) != 0
|
|
121
|
+
)
|
|
122
|
+
for component in presentation.relations.components:
|
|
123
|
+
relation_cost += sum(
|
|
124
|
+
1.0
|
|
125
|
+
for coefficient in component.coefficients
|
|
126
|
+
if sp.simplify(coefficient) != 0
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
history_cost = float(sum(presentation.grammar.depths))
|
|
130
|
+
decoder_cost = float(
|
|
131
|
+
sum(
|
|
132
|
+
1
|
|
133
|
+
for coordinates in candidate.target_coordinates
|
|
134
|
+
for coefficient in coordinates
|
|
135
|
+
if sp.simplify(coefficient) != 0
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return PresentationCost(
|
|
140
|
+
grammar=grammar_cost,
|
|
141
|
+
relations=relation_cost,
|
|
142
|
+
history=history_cost,
|
|
143
|
+
decoder=decoder_cost,
|
|
144
|
+
task_error=0.0 if candidate.sufficient else math.inf,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def construction_aware_exact_reconstruction_cost(
|
|
149
|
+
candidate: ConstructedPrimitivePresentation,
|
|
150
|
+
) -> PresentationCost:
|
|
151
|
+
"""Baseline exact-reconstruction cost including proposal construction depth."""
|
|
152
|
+
base = structural_exact_reconstruction_cost(candidate.evaluation)
|
|
153
|
+
return PresentationCost(
|
|
154
|
+
grammar=base.grammar + candidate.proposal.cost,
|
|
155
|
+
relations=base.relations,
|
|
156
|
+
history=base.history,
|
|
157
|
+
decoder=base.decoder,
|
|
158
|
+
task_error=base.task_error,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def evaluate_exact_reconstruction_presentation(
|
|
163
|
+
system: ProcessSystem,
|
|
164
|
+
proposal_seeds: Sequence[sp.Expr],
|
|
165
|
+
targets: Sequence[sp.Expr],
|
|
166
|
+
*,
|
|
167
|
+
budget: SearchBudget | None = None,
|
|
168
|
+
) -> ExactReconstructionPresentation:
|
|
169
|
+
"""Build one generated presentation and certify exact target reconstruction."""
|
|
170
|
+
normalized_seeds = tuple(sp.expand(sp.sympify(seed)) for seed in proposal_seeds)
|
|
171
|
+
normalized_targets = tuple(sp.expand(sp.sympify(target)) for target in targets)
|
|
172
|
+
if not normalized_targets:
|
|
173
|
+
raise ValueError("at least one reconstruction target is required")
|
|
174
|
+
|
|
175
|
+
presentation = discover_generated_presentation(system, normalized_seeds, budget=budget)
|
|
176
|
+
if not presentation.complete:
|
|
177
|
+
return ExactReconstructionPresentation(
|
|
178
|
+
proposal_seeds=normalized_seeds,
|
|
179
|
+
targets=normalized_targets,
|
|
180
|
+
presentation=presentation,
|
|
181
|
+
target_coordinates=(),
|
|
182
|
+
failure="generated presentation is incomplete",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
coordinates: list[tuple[sp.Expr, ...]] = []
|
|
186
|
+
try:
|
|
187
|
+
for target in normalized_targets:
|
|
188
|
+
coordinates.append(
|
|
189
|
+
decompose(target, presentation.primitives, system.assignments)
|
|
190
|
+
)
|
|
191
|
+
except ValueError as exc:
|
|
192
|
+
return ExactReconstructionPresentation(
|
|
193
|
+
proposal_seeds=normalized_seeds,
|
|
194
|
+
targets=normalized_targets,
|
|
195
|
+
presentation=presentation,
|
|
196
|
+
target_coordinates=tuple(coordinates),
|
|
197
|
+
failure=str(exc),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return ExactReconstructionPresentation(
|
|
201
|
+
proposal_seeds=normalized_seeds,
|
|
202
|
+
targets=normalized_targets,
|
|
203
|
+
presentation=presentation,
|
|
204
|
+
target_coordinates=tuple(coordinates),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def search_exact_reconstruction_presentations(
|
|
209
|
+
system: ProcessSystem,
|
|
210
|
+
seed_proposals: Sequence[Sequence[sp.Expr]],
|
|
211
|
+
targets: Sequence[sp.Expr],
|
|
212
|
+
*,
|
|
213
|
+
budget: SearchBudget | None = None,
|
|
214
|
+
cost_model: Callable[[ExactReconstructionPresentation], PresentationCost] | None = None,
|
|
215
|
+
) -> PresentationSearchResult[ExactReconstructionPresentation]:
|
|
216
|
+
"""Evaluate caller-proposed seed grammars and return their Pareto frontier."""
|
|
217
|
+
if not seed_proposals:
|
|
218
|
+
raise ValueError("at least one seed proposal is required")
|
|
219
|
+
score = cost_model or structural_exact_reconstruction_cost
|
|
220
|
+
|
|
221
|
+
evaluated: list[PresentationCandidate[ExactReconstructionPresentation]] = []
|
|
222
|
+
for index, proposal in enumerate(seed_proposals):
|
|
223
|
+
payload = evaluate_exact_reconstruction_presentation(
|
|
224
|
+
system,
|
|
225
|
+
proposal,
|
|
226
|
+
targets,
|
|
227
|
+
budget=budget,
|
|
228
|
+
)
|
|
229
|
+
cost = score(payload)
|
|
230
|
+
evaluated.append(
|
|
231
|
+
PresentationCandidate(
|
|
232
|
+
payload=payload,
|
|
233
|
+
cost=cost,
|
|
234
|
+
sufficient=payload.sufficient,
|
|
235
|
+
label=f"proposal-{index}",
|
|
236
|
+
certificate=payload.failure or payload.target_coordinates,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
evaluated_tuple = tuple(evaluated)
|
|
241
|
+
return PresentationSearchResult(
|
|
242
|
+
evaluated=evaluated_tuple,
|
|
243
|
+
pareto=pareto_frontier(evaluated_tuple),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def search_primitive_proposals(
|
|
248
|
+
system: ProcessSystem,
|
|
249
|
+
proposals: Sequence[PrimitiveProposal],
|
|
250
|
+
targets: Sequence[sp.Expr],
|
|
251
|
+
*,
|
|
252
|
+
budget: SearchBudget | None = None,
|
|
253
|
+
cost_model: Callable[[ConstructedPrimitivePresentation], PresentationCost] | None = None,
|
|
254
|
+
) -> PresentationSearchResult[ConstructedPrimitivePresentation]:
|
|
255
|
+
"""Evaluate construction-preserving primitive proposals as one-seed grammars."""
|
|
256
|
+
if not proposals:
|
|
257
|
+
raise ValueError("at least one primitive proposal is required")
|
|
258
|
+
score = cost_model or construction_aware_exact_reconstruction_cost
|
|
259
|
+
|
|
260
|
+
evaluated: list[PresentationCandidate[ConstructedPrimitivePresentation]] = []
|
|
261
|
+
for proposal in proposals:
|
|
262
|
+
evaluation = evaluate_exact_reconstruction_presentation(
|
|
263
|
+
system,
|
|
264
|
+
(proposal.expression,),
|
|
265
|
+
targets,
|
|
266
|
+
budget=budget,
|
|
267
|
+
)
|
|
268
|
+
payload = ConstructedPrimitivePresentation(
|
|
269
|
+
proposal=proposal,
|
|
270
|
+
evaluation=evaluation,
|
|
271
|
+
)
|
|
272
|
+
evaluated.append(
|
|
273
|
+
PresentationCandidate(
|
|
274
|
+
payload=payload,
|
|
275
|
+
cost=score(payload),
|
|
276
|
+
sufficient=payload.sufficient,
|
|
277
|
+
label=proposal.construction.recipe(),
|
|
278
|
+
certificate=evaluation.failure or evaluation.target_coordinates,
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
evaluated_tuple = tuple(evaluated)
|
|
283
|
+
return PresentationSearchResult(
|
|
284
|
+
evaluated=evaluated_tuple,
|
|
285
|
+
pareto=pareto_frontier(evaluated_tuple),
|
|
286
|
+
)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Finite future signatures for task-sufficient process quotients.
|
|
2
|
+
|
|
3
|
+
A process representation should not merge histories merely because their
|
|
4
|
+
current symbolic values happen to match. This module provides a bounded test:
|
|
5
|
+
two histories are indistinguishable for a declared task only when their task
|
|
6
|
+
observations agree under every allowed continuation up to a finite depth.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from itertools import product
|
|
13
|
+
from typing import Callable, Generic, Sequence, TypeVar
|
|
14
|
+
|
|
15
|
+
from .process.history import ProcessWord, interpret_history
|
|
16
|
+
|
|
17
|
+
StepT = TypeVar("StepT")
|
|
18
|
+
StateT = TypeVar("StateT")
|
|
19
|
+
ObservationT = TypeVar("ObservationT")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class ProcessJetSignature(Generic[StepT, ObservationT]):
|
|
24
|
+
"""Task observations under all bounded process continuations."""
|
|
25
|
+
|
|
26
|
+
depth: int
|
|
27
|
+
entries: tuple[tuple[ProcessWord[StepT], ObservationT], ...]
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def observations(self) -> tuple[ObservationT, ...]:
|
|
31
|
+
return tuple(observation for _word, observation in self.entries)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def enumerate_process_words(
|
|
35
|
+
steps: Sequence[StepT],
|
|
36
|
+
max_depth: int,
|
|
37
|
+
) -> tuple[ProcessWord[StepT], ...]:
|
|
38
|
+
"""Enumerate the free ordered continuation tree through ``max_depth``.
|
|
39
|
+
|
|
40
|
+
The empty continuation is included first. No commutativity or relation
|
|
41
|
+
quotient is assumed; callers may normalize words separately when that is
|
|
42
|
+
part of the chosen presentation.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
if max_depth < 0:
|
|
46
|
+
raise ValueError("max_depth must be non-negative")
|
|
47
|
+
alphabet = tuple(steps)
|
|
48
|
+
words: list[ProcessWord[StepT]] = [ProcessWord()]
|
|
49
|
+
for depth in range(1, max_depth + 1):
|
|
50
|
+
words.extend(ProcessWord(tuple(items)) for items in product(alphabet, repeat=depth))
|
|
51
|
+
return tuple(words)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def process_jet_signature(
|
|
55
|
+
state: StateT,
|
|
56
|
+
steps: Sequence[StepT],
|
|
57
|
+
transition: Callable[[StateT, StepT], StateT],
|
|
58
|
+
observe: Callable[[StateT], ObservationT],
|
|
59
|
+
*,
|
|
60
|
+
depth: int,
|
|
61
|
+
) -> ProcessJetSignature[StepT, ObservationT]:
|
|
62
|
+
"""Compute a finite task signature from a current state."""
|
|
63
|
+
|
|
64
|
+
continuations = enumerate_process_words(steps, depth)
|
|
65
|
+
entries = tuple(
|
|
66
|
+
(
|
|
67
|
+
continuation,
|
|
68
|
+
observe(interpret_history(continuation, state, transition)),
|
|
69
|
+
)
|
|
70
|
+
for continuation in continuations
|
|
71
|
+
)
|
|
72
|
+
return ProcessJetSignature(depth=depth, entries=entries)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def history_process_jet_signature(
|
|
76
|
+
history: ProcessWord[StepT],
|
|
77
|
+
initial: StateT,
|
|
78
|
+
steps: Sequence[StepT],
|
|
79
|
+
transition: Callable[[StateT, StepT], StateT],
|
|
80
|
+
observe: Callable[[StateT], ObservationT],
|
|
81
|
+
*,
|
|
82
|
+
depth: int,
|
|
83
|
+
) -> ProcessJetSignature[StepT, ObservationT]:
|
|
84
|
+
"""Compute the finite task signature after one literal process history."""
|
|
85
|
+
|
|
86
|
+
state = interpret_history(history, initial, transition)
|
|
87
|
+
return process_jet_signature(
|
|
88
|
+
state,
|
|
89
|
+
steps,
|
|
90
|
+
transition,
|
|
91
|
+
observe,
|
|
92
|
+
depth=depth,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def signatures_equivalent(
|
|
97
|
+
left: ProcessJetSignature[StepT, ObservationT],
|
|
98
|
+
right: ProcessJetSignature[StepT, ObservationT],
|
|
99
|
+
*,
|
|
100
|
+
equivalent: Callable[[ObservationT, ObservationT], bool] | None = None,
|
|
101
|
+
) -> bool:
|
|
102
|
+
"""Compare two finite task signatures with an optional observation metric."""
|
|
103
|
+
|
|
104
|
+
if left.depth != right.depth or len(left.entries) != len(right.entries):
|
|
105
|
+
return False
|
|
106
|
+
compare = equivalent or (lambda a, b: bool(a == b))
|
|
107
|
+
for (left_word, left_value), (right_word, right_value) in zip(
|
|
108
|
+
left.entries, right.entries
|
|
109
|
+
):
|
|
110
|
+
if left_word != right_word:
|
|
111
|
+
return False
|
|
112
|
+
if not compare(left_value, right_value):
|
|
113
|
+
return False
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def histories_task_equivalent(
|
|
118
|
+
left: ProcessWord[StepT],
|
|
119
|
+
right: ProcessWord[StepT],
|
|
120
|
+
initial: StateT,
|
|
121
|
+
steps: Sequence[StepT],
|
|
122
|
+
transition: Callable[[StateT, StepT], StateT],
|
|
123
|
+
observe: Callable[[StateT], ObservationT],
|
|
124
|
+
*,
|
|
125
|
+
depth: int,
|
|
126
|
+
equivalent: Callable[[ObservationT, ObservationT], bool] | None = None,
|
|
127
|
+
) -> bool:
|
|
128
|
+
"""Bounded task congruence test for two literal histories.
|
|
129
|
+
|
|
130
|
+
Equality of current task observations is not enough. The histories are
|
|
131
|
+
merged only when every allowed continuation up to ``depth`` remains
|
|
132
|
+
task-indistinguishable.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
left_signature = history_process_jet_signature(
|
|
136
|
+
left,
|
|
137
|
+
initial,
|
|
138
|
+
steps,
|
|
139
|
+
transition,
|
|
140
|
+
observe,
|
|
141
|
+
depth=depth,
|
|
142
|
+
)
|
|
143
|
+
right_signature = history_process_jet_signature(
|
|
144
|
+
right,
|
|
145
|
+
initial,
|
|
146
|
+
steps,
|
|
147
|
+
transition,
|
|
148
|
+
observe,
|
|
149
|
+
depth=depth,
|
|
150
|
+
)
|
|
151
|
+
return signatures_equivalent(
|
|
152
|
+
left_signature,
|
|
153
|
+
right_signature,
|
|
154
|
+
equivalent=equivalent,
|
|
155
|
+
)
|