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,23 @@
1
+ """Finite process presentations: history quotients, grammars, relations, and search."""
2
+
3
+ from . import (
4
+ canonicalization,
5
+ constraints,
6
+ construction,
7
+ grammar,
8
+ history,
9
+ morphism,
10
+ relations,
11
+ search,
12
+ )
13
+
14
+ __all__ = [
15
+ "history",
16
+ "construction",
17
+ "constraints",
18
+ "canonicalization",
19
+ "grammar",
20
+ "relations",
21
+ "search",
22
+ "morphism",
23
+ ]
@@ -0,0 +1,27 @@
1
+ """Explicit finite search budgets for process-presentation discovery."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class SearchBudget:
8
+ """Finite search limits for local representation discovery.
9
+
10
+ Bounded search is part of the presentation contract: general word/relation
11
+ problems need not be decidable, so search limits remain explicit data.
12
+ """
13
+
14
+ max_history_depth: int = 8
15
+ max_expression_degree: int = 4
16
+ max_relation_order: int = 8
17
+ max_new_primitives: int = 8
18
+
19
+ def __post_init__(self) -> None:
20
+ values = (
21
+ self.max_history_depth,
22
+ self.max_expression_degree,
23
+ self.max_relation_order,
24
+ self.max_new_primitives,
25
+ )
26
+ if any(value < 0 for value in values):
27
+ raise ValueError("search-budget values must be non-negative")
@@ -0,0 +1,143 @@
1
+ """Experimental exact constraint canonicalization.
2
+
3
+ The generic research concept is *canonicalization*: a local rule selecting a
4
+ low-complexity observer representation. This module implements only the first
5
+ backend forced by independent calibrations, namely exact finite equations
6
+
7
+ Phi(local data, observer parameters) = 0.
8
+
9
+ ``ConstraintCanonicalization`` obtains observer motion by differentiating those
10
+ equations along declared base rates and solving for observer-parameter rates.
11
+ This realizes
12
+
13
+ local canonicalization -> connection -> observer ODE
14
+
15
+ without claiming that every useful canonicalization is algebraic. Kepler
16
+ orthogonality/osculation already provides a red team against such a claim;
17
+ stationary, projection, or cost-based backends should be promoted separately
18
+ only when their retained semantics are calibrated.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from dataclasses import dataclass
24
+ from typing import Mapping
25
+
26
+ import sympy as sp
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class ConstraintCanonicalization:
31
+ """Exact local constraints selecting observer parameters."""
32
+
33
+ observer_parameters: tuple[sp.Symbol, ...]
34
+ constraints: tuple[sp.Expr, ...]
35
+ label: str = ""
36
+
37
+ def __post_init__(self) -> None:
38
+ parameters = tuple(self.observer_parameters)
39
+ if not parameters:
40
+ raise ValueError("canonicalization requires observer parameters")
41
+ if len(set(parameters)) != len(parameters):
42
+ raise ValueError("observer parameters must be distinct")
43
+ constraints = tuple(
44
+ sp.expand(sp.sympify(constraint))
45
+ for constraint in self.constraints
46
+ if sp.expand(sp.sympify(constraint)) != 0
47
+ )
48
+ if not constraints:
49
+ raise ValueError("canonicalization requires at least one nonzero constraint")
50
+ object.__setattr__(self, "observer_parameters", parameters)
51
+ object.__setattr__(self, "constraints", constraints)
52
+
53
+ def differentiated_constraints(
54
+ self,
55
+ base_rates: Mapping[sp.Symbol, sp.Expr],
56
+ observer_rates: Mapping[sp.Symbol, sp.Expr],
57
+ ) -> tuple[sp.Expr, ...]:
58
+ """Differentiate ``Phi=0`` along base and observer parameter rates.
59
+
60
+ Symbols not named in either mapping are treated as locally frozen
61
+ parameters. This keeps the operation explicitly local and prevents the
62
+ canonicalization object from reaching into a trajectory or propagator.
63
+ """
64
+
65
+ missing = set(self.observer_parameters) - set(observer_rates)
66
+ if missing:
67
+ raise ValueError(
68
+ "missing rates for observer parameters: "
69
+ f"{sorted(map(str, missing))}"
70
+ )
71
+
72
+ rates = {
73
+ **{
74
+ symbol: sp.sympify(value)
75
+ for symbol, value in base_rates.items()
76
+ },
77
+ **{
78
+ symbol: sp.sympify(value)
79
+ for symbol, value in observer_rates.items()
80
+ },
81
+ }
82
+ return tuple(
83
+ sp.expand(
84
+ sum(
85
+ sp.diff(constraint, symbol) * rate
86
+ for symbol, rate in rates.items()
87
+ )
88
+ )
89
+ for constraint in self.constraints
90
+ )
91
+
92
+ def induced_connection(
93
+ self,
94
+ base_rates: Mapping[sp.Symbol, sp.Expr],
95
+ *,
96
+ label: str = "",
97
+ ):
98
+ """Solve the differentiated constraints for local observer rates.
99
+
100
+ The current implementation requires a unique symbolic local solution.
101
+ Residual gauge freedom, singular strata, least-squares stationarity, and
102
+ branch selection remain explicit future API questions rather than being
103
+ silently resolved here.
104
+ """
105
+
106
+ from ..analysis.connection import ObserverConnection
107
+
108
+ rate_symbols = {
109
+ parameter: sp.Dummy(f"d_{parameter}")
110
+ for parameter in self.observer_parameters
111
+ }
112
+ equations = self.differentiated_constraints(base_rates, rate_symbols)
113
+ unknowns = tuple(rate_symbols[parameter] for parameter in self.observer_parameters)
114
+ solutions = sp.solve(
115
+ tuple(sp.Eq(equation, 0) for equation in equations),
116
+ unknowns,
117
+ dict=True,
118
+ simplify=True,
119
+ )
120
+ if len(solutions) != 1 or any(
121
+ unknown not in solutions[0]
122
+ for unknown in unknowns
123
+ ):
124
+ raise ValueError(
125
+ "canonicalization does not induce a unique local observer connection"
126
+ )
127
+
128
+ solution = solutions[0]
129
+ observer_rates = {
130
+ parameter: sp.simplify(solution[rate_symbols[parameter]])
131
+ for parameter in self.observer_parameters
132
+ }
133
+ residuals = self.differentiated_constraints(base_rates, observer_rates)
134
+ return ObserverConnection(
135
+ canonicalization=self,
136
+ base_rates=base_rates,
137
+ observer_rates=observer_rates,
138
+ residuals=residuals,
139
+ label=label or self.label,
140
+ )
141
+
142
+
143
+ __all__ = ["ConstraintCanonicalization"]
@@ -0,0 +1,5 @@
1
+ """Exact algebraic constraints used by finite process presentations."""
2
+
3
+ from ..constraints import AlgebraicConstraintSet, constraint_prolongation
4
+
5
+ __all__ = ["AlgebraicConstraintSet", "constraint_prolongation"]
@@ -0,0 +1,19 @@
1
+ """Construction-history-preserving primitive proposals."""
2
+
3
+ from ..construction import (
4
+ PrimitiveConstruction,
5
+ PrimitiveProposal,
6
+ PrimitiveProposalResult,
7
+ RejectedPrimitiveProposal,
8
+ SymbolicOperation,
9
+ generate_primitive_proposals,
10
+ )
11
+
12
+ __all__ = [
13
+ "PrimitiveConstruction",
14
+ "PrimitiveProposal",
15
+ "PrimitiveProposalResult",
16
+ "RejectedPrimitiveProposal",
17
+ "SymbolicOperation",
18
+ "generate_primitive_proposals",
19
+ ]
@@ -0,0 +1,15 @@
1
+ """Generated finite process grammars and presentations."""
2
+
3
+ from ..grammar import (
4
+ GeneratedGrammar,
5
+ GeneratedPresentation,
6
+ discover_generated_grammar,
7
+ discover_generated_presentation,
8
+ )
9
+
10
+ __all__ = [
11
+ "GeneratedGrammar",
12
+ "GeneratedPresentation",
13
+ "discover_generated_grammar",
14
+ "discover_generated_presentation",
15
+ ]
@@ -0,0 +1,45 @@
1
+ """History rewriting, task signatures, and finite history geometry."""
2
+
3
+ from ..history_geometry import (
4
+ BoundaryProfile,
5
+ PrefixCode,
6
+ PrefixCodeMetrics,
7
+ boundary_profile,
8
+ history_depth,
9
+ huffman_prefix_code,
10
+ )
11
+ from ..rewrite import (
12
+ RewriteResult,
13
+ RewriteStep,
14
+ WordRewriteRule,
15
+ normalize_word,
16
+ rewrite_once,
17
+ )
18
+ from ..signature import (
19
+ ProcessJetSignature,
20
+ enumerate_process_words,
21
+ histories_task_equivalent,
22
+ history_process_jet_signature,
23
+ process_jet_signature,
24
+ signatures_equivalent,
25
+ )
26
+
27
+ __all__ = [
28
+ "RewriteResult",
29
+ "RewriteStep",
30
+ "WordRewriteRule",
31
+ "normalize_word",
32
+ "rewrite_once",
33
+ "ProcessJetSignature",
34
+ "enumerate_process_words",
35
+ "histories_task_equivalent",
36
+ "history_process_jet_signature",
37
+ "process_jet_signature",
38
+ "signatures_equivalent",
39
+ "BoundaryProfile",
40
+ "PrefixCode",
41
+ "PrefixCodeMetrics",
42
+ "boundary_profile",
43
+ "history_depth",
44
+ "huffman_prefix_code",
45
+ ]
@@ -0,0 +1,66 @@
1
+ """Evidence-bearing transformations between process presentations.
2
+
3
+ Mathematical pressure
4
+ ---------------------
5
+ Three independent research calibrations now force the same minimal shape.
6
+
7
+ * KdV: a tau presentation and a parametric scattering-history presentation can
8
+ encode the same pair/three-body interaction data, while a locally confluent
9
+ rewrite can still be incomplete for the tau task.
10
+ * Resistor networks: Schur/Y-Delta transformations can produce syntactically
11
+ different graphs with exactly the same declared boundary response.
12
+ * Braids: Markov stabilization can preserve closure semantics while changing
13
+ braid index and therefore the dimension/type of the chosen representation.
14
+
15
+ The shared object is not a universal rewrite rule. It is an evidence-bearing
16
+ arrow between a source presentation and a target presentation, whose validity is
17
+ always relative to an explicitly declared task semantics.
18
+
19
+ Boundary
20
+ --------
21
+ ``PresentationMorphism`` deliberately does *not* define:
22
+
23
+ * a universal equality or verification method;
24
+ * composition, inverses, identities, or a category/groupoid structure;
25
+ * a normal-form relation;
26
+ * a requirement that source and target have the same Python type;
27
+ * a requirement that certificates be exact rather than numerical/bounded.
28
+
29
+ Those structures should be promoted only when further independent calibrations
30
+ show what information composition and verification must retain.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from dataclasses import dataclass
36
+ from typing import Generic, TypeVar
37
+
38
+ SourceT = TypeVar("SourceT")
39
+ TargetT = TypeVar("TargetT")
40
+ TaskSemanticsT = TypeVar("TaskSemanticsT")
41
+ CertificateT = TypeVar("CertificateT")
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class PresentationMorphism(
46
+ Generic[SourceT, TargetT, TaskSemanticsT, CertificateT]
47
+ ):
48
+ """A task-relative, certificate-carrying presentation transformation.
49
+
50
+ ``source`` and ``target`` may be entirely different representation types.
51
+ ``task_semantics`` records what meaning is claimed to be preserved.
52
+ ``certificate`` is caller-defined evidence for that claim; Shakespeare does
53
+ not interpret it generically. ``witness`` may record a rewrite trace,
54
+ construction history, parameter map, or other provenance for how the target
55
+ was obtained.
56
+ """
57
+
58
+ source: SourceT
59
+ target: TargetT
60
+ task_semantics: TaskSemanticsT
61
+ certificate: CertificateT
62
+ witness: object | None = None
63
+ label: str = ""
64
+
65
+
66
+ __all__ = ["PresentationMorphism"]
@@ -0,0 +1,31 @@
1
+ """Exact process relations, factorization, kernels, and decomposition."""
2
+
3
+ from ..relations import (
4
+ ProcessPolynomialRelation,
5
+ RelationDecomposition,
6
+ RelationKernel,
7
+ ReturnRelation,
8
+ action_matrix,
9
+ coefficient_vector,
10
+ decompose,
11
+ discover_operator_relation,
12
+ discover_relation_decomposition,
13
+ discover_relation_kernel,
14
+ discover_return_relation,
15
+ factor_process_relation,
16
+ )
17
+
18
+ __all__ = [
19
+ "ProcessPolynomialRelation",
20
+ "RelationDecomposition",
21
+ "RelationKernel",
22
+ "ReturnRelation",
23
+ "action_matrix",
24
+ "coefficient_vector",
25
+ "decompose",
26
+ "discover_operator_relation",
27
+ "discover_relation_decomposition",
28
+ "discover_relation_kernel",
29
+ "discover_return_relation",
30
+ "factor_process_relation",
31
+ ]
@@ -0,0 +1,31 @@
1
+ """Budgets, costs, Pareto filtering, and presentation search."""
2
+
3
+ from .budget import SearchBudget
4
+ from ..cost import PresentationCost
5
+ from ..search import (
6
+ ConstructedPrimitivePresentation,
7
+ ExactReconstructionPresentation,
8
+ PresentationCandidate,
9
+ PresentationSearchResult,
10
+ construction_aware_exact_reconstruction_cost,
11
+ evaluate_exact_reconstruction_presentation,
12
+ pareto_frontier,
13
+ search_exact_reconstruction_presentations,
14
+ search_primitive_proposals,
15
+ structural_exact_reconstruction_cost,
16
+ )
17
+
18
+ __all__ = [
19
+ "SearchBudget",
20
+ "PresentationCost",
21
+ "ConstructedPrimitivePresentation",
22
+ "ExactReconstructionPresentation",
23
+ "PresentationCandidate",
24
+ "PresentationSearchResult",
25
+ "construction_aware_exact_reconstruction_cost",
26
+ "evaluate_exact_reconstruction_presentation",
27
+ "pareto_frontier",
28
+ "search_exact_reconstruction_presentations",
29
+ "search_primitive_proposals",
30
+ "structural_exact_reconstruction_cost",
31
+ ]
@@ -0,0 +1,16 @@
1
+ """Process ontology: histories, finite process families, and local generators."""
2
+
3
+ from . import finite, history, local
4
+ from .finite import ProcessFamily
5
+ from .history import ProcessWord
6
+ from .local import ProcessFrame, ProcessSystem
7
+
8
+ __all__ = [
9
+ "history",
10
+ "finite",
11
+ "local",
12
+ "ProcessWord",
13
+ "ProcessFamily",
14
+ "ProcessSystem",
15
+ "ProcessFrame",
16
+ ]
@@ -0,0 +1,43 @@
1
+ """Finite parameterized process structure.
2
+
3
+ Families, scalar responses, actions, and additive central composition residuals
4
+ live together here because they are structures on finite process composition.
5
+ The package deliberately stops before universal group, representation,
6
+ cohomology, topology, or measure hierarchies.
7
+ """
8
+
9
+ from .cocycle import (
10
+ CocycleVerification,
11
+ ProcessCocycle,
12
+ central_commutator_residual,
13
+ verify_process_cocycle,
14
+ )
15
+ from .families import (
16
+ CharacterVerification,
17
+ FamilyAction,
18
+ FamilyActionVerification,
19
+ FamilyStep,
20
+ ProcessCharacter,
21
+ ProcessFamily,
22
+ character_invariance_residual,
23
+ transport_process_character,
24
+ verify_family_action,
25
+ verify_process_character,
26
+ )
27
+
28
+ __all__ = [
29
+ "ProcessFamily",
30
+ "FamilyStep",
31
+ "ProcessCharacter",
32
+ "CharacterVerification",
33
+ "verify_process_character",
34
+ "FamilyAction",
35
+ "FamilyActionVerification",
36
+ "verify_family_action",
37
+ "transport_process_character",
38
+ "character_invariance_residual",
39
+ "ProcessCocycle",
40
+ "CocycleVerification",
41
+ "verify_process_cocycle",
42
+ "central_commutator_residual",
43
+ ]
@@ -0,0 +1,166 @@
1
+ """Additive central residuals for finite process-family composition.
2
+
3
+ This module is intentionally narrower than a central-extension or cohomology
4
+ framework. It records one structure now forced independently by Galilean
5
+ mechanics and magnetic translations:
6
+
7
+ * a visible ``ProcessFamily`` has its ordinary parameter-composition law;
8
+ * a lifted realization may compose with an additional additive scalar residual
9
+ ``omega(g, h)``; and
10
+ * associativity of the lifted composition is exactly the 2-cocycle identity.
11
+
12
+ The central value is represented by a SymPy expression and combined additively.
13
+ It may be an action-like quantity, a phase exponent, or another caller-defined
14
+ central coordinate. Shakespeare does not identify it with U(1), choose a
15
+ quantization convention, quotient cocycles by coboundaries, or construct a full
16
+ central-extension group.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from dataclasses import dataclass, field
22
+ from typing import Callable, Generic, Sequence, TypeVar
23
+
24
+ import sympy as sp
25
+
26
+ from .families import ProcessFamily
27
+
28
+ ParamT = TypeVar("ParamT")
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class ProcessCocycle(Generic[ParamT]):
33
+ """An additive central composition residual over one ``ProcessFamily``.
34
+
35
+ For a visible composition ``g*h``, a lifted realization is represented by
36
+
37
+ ``(g, z) * (h, w) = (g*h, z + w + omega(g, h))``.
38
+
39
+ The object stores only ``omega`` and the visible family. Whether a given
40
+ residual is physically a phase, action, charge, mass contribution, flux, or
41
+ something else belongs to the caller's realization.
42
+ """
43
+
44
+ family: ProcessFamily[ParamT]
45
+ residual: Callable[[ParamT, ParamT], sp.Expr] = field(repr=False, compare=False)
46
+ label: object | None = None
47
+ simplify: Callable[[sp.Expr], sp.Expr] = field(
48
+ default=sp.simplify,
49
+ repr=False,
50
+ compare=False,
51
+ )
52
+
53
+ def value(self, left: ParamT, right: ParamT) -> sp.Expr:
54
+ return sp.sympify(self.simplify(sp.sympify(self.residual(left, right))))
55
+
56
+ def cocycle_residual(self, first: ParamT, second: ParamT, third: ParamT) -> sp.Expr:
57
+ """Residual of the additive 2-cocycle identity.
58
+
59
+ Exact associativity of lifted composition requires
60
+
61
+ ``omega(g,h) + omega(g*h,k) = omega(h,k) + omega(g,h*k)``.
62
+ """
63
+
64
+ gh = self.family.compose_parameters(first, second)
65
+ hk = self.family.compose_parameters(second, third)
66
+ residual = (
67
+ self.value(first, second)
68
+ + self.value(gh, third)
69
+ - self.value(second, third)
70
+ - self.value(first, hk)
71
+ )
72
+ return sp.sympify(self.simplify(sp.expand(residual)))
73
+
74
+ def compose_lifted(
75
+ self,
76
+ left: tuple[ParamT, sp.Expr],
77
+ right: tuple[ParamT, sp.Expr],
78
+ ) -> tuple[ParamT, sp.Expr]:
79
+ """Compose two lifted elements using the declared central residual."""
80
+
81
+ left_parameter, left_central = left
82
+ right_parameter, right_central = right
83
+ parameter = self.family.compose_parameters(left_parameter, right_parameter)
84
+ central = self.simplify(
85
+ sp.sympify(left_central)
86
+ + sp.sympify(right_central)
87
+ + self.value(left_parameter, right_parameter)
88
+ )
89
+ return parameter, sp.sympify(central)
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class CocycleVerification:
94
+ """Bounded exact certificate for a ``ProcessCocycle``."""
95
+
96
+ cocycle_residuals: tuple[sp.Expr, ...]
97
+ normalization_residuals: tuple[sp.Expr, ...] = ()
98
+
99
+ @property
100
+ def exact(self) -> bool:
101
+ return all(
102
+ residual == 0
103
+ for residual in (*self.cocycle_residuals, *self.normalization_residuals)
104
+ )
105
+
106
+
107
+ def verify_process_cocycle(
108
+ cocycle: ProcessCocycle[ParamT],
109
+ triples: Sequence[tuple[ParamT, ParamT, ParamT]],
110
+ *,
111
+ normalization_parameters: Sequence[ParamT] = (),
112
+ ) -> CocycleVerification:
113
+ """Verify the cocycle law and optional identity normalization exactly.
114
+
115
+ Like the family/character verification layer, this is a bounded certificate.
116
+ Symbolic triples may establish identities over an unbounded parameter domain
117
+ when the backend simplifies the resulting residuals exactly.
118
+ """
119
+
120
+ cocycle_residuals = tuple(
121
+ cocycle.cocycle_residual(first, second, third)
122
+ for first, second, third in triples
123
+ )
124
+
125
+ normalization: list[sp.Expr] = []
126
+ if normalization_parameters:
127
+ if cocycle.family.identity is None:
128
+ raise ValueError("cocycle normalization requires a declared family identity")
129
+ identity = cocycle.family.identity
130
+ for parameter in normalization_parameters:
131
+ normalization.append(cocycle.value(identity, parameter))
132
+ normalization.append(cocycle.value(parameter, identity))
133
+
134
+ return CocycleVerification(
135
+ cocycle_residuals=cocycle_residuals,
136
+ normalization_residuals=tuple(normalization),
137
+ )
138
+
139
+
140
+ def central_commutator_residual(
141
+ cocycle: ProcessCocycle[ParamT],
142
+ left: ParamT,
143
+ right: ParamT,
144
+ *,
145
+ require_visible_commutation: bool = True,
146
+ ) -> sp.Expr:
147
+ """Return the ordering residual for visibly commuting family elements.
148
+
149
+ If the base compositions ``left*right`` and ``right*left`` agree, their
150
+ lifted compositions can still differ centrally by
151
+
152
+ ``omega(left,right) - omega(right,left)``.
153
+
154
+ By default the function refuses to call this a *central commutator* unless
155
+ the visible family elements commute. Callers studying noncommuting base
156
+ elements should inspect the two cocycle values directly.
157
+ """
158
+
159
+ if require_visible_commutation:
160
+ left_right = cocycle.family.compose_parameters(left, right)
161
+ right_left = cocycle.family.compose_parameters(right, left)
162
+ if not cocycle.family.parameters_equivalent(left_right, right_left):
163
+ raise ValueError("central commutator residual requires visibly commuting parameters")
164
+
165
+ residual = cocycle.value(left, right) - cocycle.value(right, left)
166
+ return sp.sympify(cocycle.simplify(sp.expand(residual)))