sum-engine 0.1.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.
- internal/__init__.py +8 -0
- internal/algorithms/__init__.py +1 -0
- internal/algorithms/causal_discovery.py +96 -0
- internal/algorithms/predicate_canon.py +137 -0
- internal/algorithms/semantic_arithmetic.py +890 -0
- internal/algorithms/syntactic_sieve.py +452 -0
- internal/algorithms/zk_semantics.py +90 -0
- internal/ensemble/__init__.py +1 -0
- internal/ensemble/automated_scientist.py +138 -0
- internal/ensemble/autonomous_agent.py +157 -0
- internal/ensemble/causal_triggers.py +121 -0
- internal/ensemble/confidence_calibrator.py +284 -0
- internal/ensemble/epistemic_arbiter.py +159 -0
- internal/ensemble/epistemic_loop.py +136 -0
- internal/ensemble/extraction_validator.py +172 -0
- internal/ensemble/gauge_orchestrator.py +150 -0
- internal/ensemble/live_llm_adapter.py +183 -0
- internal/ensemble/llm_entailment.py +117 -0
- internal/ensemble/mass_semantic_engine.py +138 -0
- internal/ensemble/ouroboros.py +281 -0
- internal/ensemble/semantic_dedup.py +261 -0
- internal/ensemble/tome_generator.py +286 -0
- internal/ensemble/tome_sliders.py +104 -0
- internal/ensemble/vector_bridge.py +195 -0
- internal/ensemble/venn_abers.py +211 -0
- internal/infrastructure/__init__.py +1 -0
- internal/infrastructure/akashic_ledger.py +812 -0
- internal/infrastructure/canonical_codec.py +452 -0
- internal/infrastructure/jcs.py +115 -0
- internal/infrastructure/key_manager.py +239 -0
- internal/infrastructure/p2p_mesh.py +168 -0
- internal/infrastructure/prov_o.py +159 -0
- internal/infrastructure/provenance.py +181 -0
- internal/infrastructure/rate_limiter.py +81 -0
- internal/infrastructure/resource_guards.py +117 -0
- internal/infrastructure/scheme_registry.py +136 -0
- internal/infrastructure/state_encoding.py +94 -0
- internal/infrastructure/telemetry.py +91 -0
- internal/infrastructure/tome_parser.py +55 -0
- internal/infrastructure/verifiable_credential.py +412 -0
- internal/infrastructure/zig_bridge.py +256 -0
- sum_cli/__init__.py +18 -0
- sum_cli/main.py +688 -0
- sum_engine-0.1.0.dist-info/METADATA +590 -0
- sum_engine-0.1.0.dist-info/RECORD +49 -0
- sum_engine-0.1.0.dist-info/WHEEL +5 -0
- sum_engine-0.1.0.dist-info/entry_points.txt +2 -0
- sum_engine-0.1.0.dist-info/licenses/LICENSE +201 -0
- sum_engine-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
sys.set_int_max_str_digits(0)
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Semantic Prime Number Theorem (SPNT) & Gödel-State Algebra
|
|
6
|
+
|
|
7
|
+
Maps irreducible semantic axioms to Prime Numbers. Document states become
|
|
8
|
+
massive integers. Merging parallel extractions is an LCM operation, and
|
|
9
|
+
entailment checking is Modulo arithmetic.
|
|
10
|
+
|
|
11
|
+
Mathematical foundations:
|
|
12
|
+
- SPNT Bound: π_sem(N) ~ N / ln(N)
|
|
13
|
+
- Gödel Encoding: state = lcm(prime_1, prime_2, ...) for each distinct
|
|
14
|
+
axiom. Primes are pairwise coprime so this equals
|
|
15
|
+
the product for a set, but is idempotent under
|
|
16
|
+
duplicates (a *set* of axioms, not a multiset).
|
|
17
|
+
- Lock-Free Merge: LCM(state_A, state_B) deduplicates automatically
|
|
18
|
+
- Entailment: global_state % hypothesis_state == 0
|
|
19
|
+
|
|
20
|
+
Author: ototao
|
|
21
|
+
License: Apache License 2.0
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import math
|
|
25
|
+
import hashlib
|
|
26
|
+
import logging
|
|
27
|
+
from typing import Dict, List, Tuple, Set
|
|
28
|
+
|
|
29
|
+
import sympy
|
|
30
|
+
|
|
31
|
+
from internal.infrastructure.scheme_registry import CURRENT_SCHEME
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PrimeCollisionError(Exception):
|
|
37
|
+
"""Hard failure when two distinct axiom keys hash to the same prime.
|
|
38
|
+
|
|
39
|
+
In sha256_64_v1, collisions are resolved by advancing to the next
|
|
40
|
+
prime (order-dependent, consensus-unsafe for distributed systems).
|
|
41
|
+
|
|
42
|
+
In sha256_128_v2, collisions are fatal. The 128-bit seed space
|
|
43
|
+
makes birthday-bound collisions require ~2^64 distinct axioms.
|
|
44
|
+
If one ever occurs, something is deeply wrong.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get_zig_engine():
|
|
49
|
+
"""Lazy import of the Zig FFI bridge singleton."""
|
|
50
|
+
try:
|
|
51
|
+
from internal.infrastructure.zig_bridge import zig_engine
|
|
52
|
+
return zig_engine
|
|
53
|
+
except ImportError:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SemanticPrimeNumberTheorem:
|
|
58
|
+
"""
|
|
59
|
+
The classical PNT states that the density of primes up to N is
|
|
60
|
+
asymptotically N / ln(N). Here we apply the same bound to
|
|
61
|
+
*semantic primes* — the irreducible, highly-surprising axiomatic
|
|
62
|
+
facts inside a document corpus.
|
|
63
|
+
|
|
64
|
+
If an LLM extraction yields more axioms than the SPNT bound,
|
|
65
|
+
it has failed to compress (hallucinated verbosity) and the
|
|
66
|
+
extraction should be rejected.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def asymptotic_bound(total_raw_claims: int) -> int:
|
|
71
|
+
"""
|
|
72
|
+
π_sem(N) ~ N / ln(N)
|
|
73
|
+
|
|
74
|
+
Calculates the theoretical maximum number of irreducible semantic
|
|
75
|
+
primes for a corpus of N raw claims.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
total_raw_claims: Total number of raw claims extracted.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The SPNT upper bound on truly novel axioms.
|
|
82
|
+
"""
|
|
83
|
+
if total_raw_claims <= 3:
|
|
84
|
+
return total_raw_claims
|
|
85
|
+
return int(total_raw_claims / math.log(total_raw_claims))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class GodelStateAlgebra:
|
|
89
|
+
"""
|
|
90
|
+
Reduces Mass-Parallel Graph Merging and SMT Theorem Proving
|
|
91
|
+
to Arbitrary-Precision Integer Arithmetic.
|
|
92
|
+
|
|
93
|
+
Every unique (subject, predicate, object) triple is assigned a unique
|
|
94
|
+
prime number. A document chunk's semantic state is the *product* of
|
|
95
|
+
its primes. Merging states is an LCM. Entailment is a modulo check.
|
|
96
|
+
Paradox detection looks for mutually exclusive primes that both
|
|
97
|
+
divide the global state.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self):
|
|
101
|
+
# Deterministic primes — no sequential watermark needed
|
|
102
|
+
self.axiom_to_prime: Dict[str, int] = {}
|
|
103
|
+
self.prime_to_axiom: Dict[int, str] = {}
|
|
104
|
+
|
|
105
|
+
# Tracks mutually exclusive primes (Level 3 Curvature / Symmetry Breaking).
|
|
106
|
+
# Maps a context key (subject||predicate) to its set of competing object primes.
|
|
107
|
+
self.exclusion_zones: Dict[str, Set[int]] = {}
|
|
108
|
+
|
|
109
|
+
# Fractal Crystallization provenance.
|
|
110
|
+
# Maps macro-prime → product of micro-primes for lossless decompression.
|
|
111
|
+
self.macro_provenance: Dict[int, int] = {}
|
|
112
|
+
|
|
113
|
+
# Quantum GraphRAG node registry.
|
|
114
|
+
# Maps each node (subject/object) to the product of all primes it participates in.
|
|
115
|
+
self.node_registry: Dict[str, int] = {}
|
|
116
|
+
|
|
117
|
+
# ------------------------------------------------------------------
|
|
118
|
+
# Predicate canonicalization
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _canonicalize_predicate(predicate: str) -> str:
|
|
123
|
+
"""Normalize a free-form predicate to the canonical vocabulary."""
|
|
124
|
+
from internal.algorithms.predicate_canon import canonicalize
|
|
125
|
+
return canonicalize(predicate)
|
|
126
|
+
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
# Prime minting
|
|
129
|
+
# ------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def _deterministic_prime(self, axiom_key: str) -> int:
|
|
132
|
+
"""
|
|
133
|
+
Generates a globally unique, deterministic prime for a given axiom.
|
|
134
|
+
|
|
135
|
+
Dispatches based on the active scheme:
|
|
136
|
+
sha256_64_v1: SHA-256 → first 8 bytes → nextprime(seed)
|
|
137
|
+
sha256_128_v2: SHA-256 → first 16 bytes → nextprime(seed)
|
|
138
|
+
|
|
139
|
+
HORIZON III — Strangler Fig:
|
|
140
|
+
Routes to the bare-metal Zig core (nanosecond-speed C-ABI)
|
|
141
|
+
when ``libsum_core`` is compiled and available. Falls back
|
|
142
|
+
to Python ``sympy.nextprime`` seamlessly otherwise.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
axiom_key: The normalised axiom string.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
A deterministic prime number unique to this axiom.
|
|
149
|
+
"""
|
|
150
|
+
if CURRENT_SCHEME == "sha256_128_v2":
|
|
151
|
+
return self._deterministic_prime_v2(axiom_key)
|
|
152
|
+
return self._deterministic_prime_v1(axiom_key)
|
|
153
|
+
|
|
154
|
+
def _deterministic_prime_v1(self, axiom_key: str) -> int:
|
|
155
|
+
"""v1: SHA-256 → first 8 bytes big-endian → nextprime(seed)."""
|
|
156
|
+
# ── Bare-Metal Fast Path (Zig C-ABI) ──
|
|
157
|
+
zig = _get_zig_engine()
|
|
158
|
+
if zig is not None:
|
|
159
|
+
result = zig.get_deterministic_prime(axiom_key)
|
|
160
|
+
if result is not None:
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
# ── Python Fallback ──
|
|
164
|
+
h = hashlib.sha256(axiom_key.encode('utf-8')).digest()
|
|
165
|
+
seed = int.from_bytes(h[:8], byteorder='big')
|
|
166
|
+
return sympy.nextprime(seed)
|
|
167
|
+
|
|
168
|
+
def _deterministic_prime_v2(self, axiom_key: str) -> int:
|
|
169
|
+
"""v2: SHA-256 → first 16 bytes big-endian → nextprime(seed).
|
|
170
|
+
|
|
171
|
+
Uses sympy.nextprime which internally uses BPSW for large inputs.
|
|
172
|
+
"""
|
|
173
|
+
# ── Bare-Metal Fast Path (Zig C-ABI v2) ──
|
|
174
|
+
zig = _get_zig_engine()
|
|
175
|
+
if zig is not None and hasattr(zig, 'get_deterministic_prime_v2'):
|
|
176
|
+
result = zig.get_deterministic_prime_v2(axiom_key)
|
|
177
|
+
if result is not None:
|
|
178
|
+
return result
|
|
179
|
+
|
|
180
|
+
# ── Python path ──
|
|
181
|
+
h = hashlib.sha256(axiom_key.encode('utf-8')).digest()
|
|
182
|
+
seed = int.from_bytes(h[:16], byteorder='big')
|
|
183
|
+
return sympy.nextprime(seed)
|
|
184
|
+
|
|
185
|
+
def get_or_mint_prime(self, subject: str, predicate: str, object_: str) -> int:
|
|
186
|
+
"""
|
|
187
|
+
Mint a new Universal Semantic Prime for an irreducible axiom,
|
|
188
|
+
or return the existing one if we have seen this axiom before.
|
|
189
|
+
|
|
190
|
+
Uses cryptographic hashing for deterministic, globally consistent
|
|
191
|
+
prime assignment. Two isolated instances will always generate
|
|
192
|
+
the exact same prime for the same axiom.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
subject: The entity.
|
|
196
|
+
predicate: The relation / property.
|
|
197
|
+
object_: The value.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
The prime number assigned to this axiom.
|
|
201
|
+
"""
|
|
202
|
+
axiom_key = (
|
|
203
|
+
f"{subject.strip().lower()}||"
|
|
204
|
+
f"{self._canonicalize_predicate(predicate)}||"
|
|
205
|
+
f"{object_.strip().lower()}"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if axiom_key not in self.axiom_to_prime:
|
|
209
|
+
p = self._deterministic_prime(axiom_key)
|
|
210
|
+
|
|
211
|
+
# Collision handling is scheme-dependent
|
|
212
|
+
if p in self.prime_to_axiom and self.prime_to_axiom[p] != axiom_key:
|
|
213
|
+
if CURRENT_SCHEME == "sha256_128_v2":
|
|
214
|
+
# v2: hard fail — collision loop is hidden nondeterminism
|
|
215
|
+
raise PrimeCollisionError(
|
|
216
|
+
f"128-bit prime collision detected: prime {p} already "
|
|
217
|
+
f"assigned to {self.prime_to_axiom[p]!r}, cannot assign "
|
|
218
|
+
f"to {axiom_key!r}. This should be astronomically rare. "
|
|
219
|
+
f"Investigate immediately."
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
# v1: advance to next prime (order-dependent, legacy behavior)
|
|
223
|
+
while p in self.prime_to_axiom and self.prime_to_axiom[p] != axiom_key:
|
|
224
|
+
p = sympy.nextprime(p)
|
|
225
|
+
|
|
226
|
+
self.axiom_to_prime[axiom_key] = p
|
|
227
|
+
self.prime_to_axiom[p] = axiom_key
|
|
228
|
+
|
|
229
|
+
# Register exclusion zone (subject + predicate).
|
|
230
|
+
# Any two primes in the same zone are mutually exclusive
|
|
231
|
+
# (e.g. "alice||age||30" vs "alice||age||31").
|
|
232
|
+
zone_key = (
|
|
233
|
+
f"{subject.strip().lower()}||"
|
|
234
|
+
f"{predicate.strip().lower()}"
|
|
235
|
+
)
|
|
236
|
+
if zone_key not in self.exclusion_zones:
|
|
237
|
+
self.exclusion_zones[zone_key] = set()
|
|
238
|
+
self.exclusion_zones[zone_key].add(p)
|
|
239
|
+
|
|
240
|
+
# Update node registry for GraphRAG traversal
|
|
241
|
+
self._update_node_registry(
|
|
242
|
+
subject.strip().lower(), object_.strip().lower(), p
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return self.axiom_to_prime[axiom_key]
|
|
246
|
+
|
|
247
|
+
# ------------------------------------------------------------------
|
|
248
|
+
# State Factorisation
|
|
249
|
+
# ------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
def get_active_axioms(self, state: int) -> list:
|
|
252
|
+
"""
|
|
253
|
+
Factorise a Gödel state to extract all active axiom keys.
|
|
254
|
+
|
|
255
|
+
Iterates over all known primes and checks divisibility.
|
|
256
|
+
Returns the list of axiom key strings (e.g. "alice||likes||cats")
|
|
257
|
+
whose primes divide the state.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
state: The Gödel integer to factorise.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
List of axiom key strings.
|
|
264
|
+
"""
|
|
265
|
+
if state <= 1:
|
|
266
|
+
return []
|
|
267
|
+
active = []
|
|
268
|
+
for prime, axiom in self.prime_to_axiom.items():
|
|
269
|
+
if state % prime == 0:
|
|
270
|
+
active.append(axiom)
|
|
271
|
+
return active
|
|
272
|
+
|
|
273
|
+
# ------------------------------------------------------------------
|
|
274
|
+
# Quantum GraphRAG (Node Registry)
|
|
275
|
+
# ------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
def _update_node_registry(
|
|
278
|
+
self, subject: str, object_: str, prime: int
|
|
279
|
+
):
|
|
280
|
+
"""
|
|
281
|
+
Maintains the Context Integer for each node.
|
|
282
|
+
|
|
283
|
+
Every time a prime is minted, both its subject and object nodes
|
|
284
|
+
accumulate the prime into their integer via LCM.
|
|
285
|
+
"""
|
|
286
|
+
if subject not in self.node_registry:
|
|
287
|
+
self.node_registry[subject] = 1
|
|
288
|
+
if object_ not in self.node_registry:
|
|
289
|
+
self.node_registry[object_] = 1
|
|
290
|
+
self.node_registry[subject] = math.lcm(
|
|
291
|
+
self.node_registry[subject], prime
|
|
292
|
+
)
|
|
293
|
+
self.node_registry[object_] = math.lcm(
|
|
294
|
+
self.node_registry[object_], prime
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def get_quantum_neighborhood(
|
|
298
|
+
self, global_state: int, nodes: List[str], hops: int = 1
|
|
299
|
+
) -> int:
|
|
300
|
+
"""
|
|
301
|
+
GraphRAG Traversal with N-Hop Support.
|
|
302
|
+
|
|
303
|
+
Returns the exact Gödel Integer representing the combined
|
|
304
|
+
topological neighborhood of the requested nodes, filtered to
|
|
305
|
+
only include axioms alive in the current global state.
|
|
306
|
+
|
|
307
|
+
The traversal iteratively expands the frontier: at each hop,
|
|
308
|
+
new neighbor nodes are discovered and their edges are collected.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
global_state: Current Gödel integer.
|
|
312
|
+
nodes: Entity names to query.
|
|
313
|
+
hops: Number of traversal hops (1-hop, 2-hop, etc.).
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Gödel integer representing the neighbourhood context.
|
|
317
|
+
"""
|
|
318
|
+
visited: set = set()
|
|
319
|
+
frontier: set = set(n.strip().lower() for n in nodes)
|
|
320
|
+
context_state = 1
|
|
321
|
+
|
|
322
|
+
for _hop in range(hops):
|
|
323
|
+
next_frontier: set = set()
|
|
324
|
+
for node in frontier:
|
|
325
|
+
if node in visited:
|
|
326
|
+
continue
|
|
327
|
+
visited.add(node)
|
|
328
|
+
if node in self.node_registry:
|
|
329
|
+
node_integer = self.node_registry[node]
|
|
330
|
+
# Filter for only edges alive in the global state
|
|
331
|
+
alive_node_integer = math.gcd(
|
|
332
|
+
global_state, node_integer
|
|
333
|
+
)
|
|
334
|
+
context_state = math.lcm(
|
|
335
|
+
context_state, alive_node_integer
|
|
336
|
+
)
|
|
337
|
+
# Discover neighbor nodes for next hop
|
|
338
|
+
for prime, axiom in self.prime_to_axiom.items():
|
|
339
|
+
if alive_node_integer % prime == 0:
|
|
340
|
+
parts = axiom.split("||")
|
|
341
|
+
if len(parts) == 3:
|
|
342
|
+
next_frontier.add(parts[0])
|
|
343
|
+
next_frontier.add(parts[2])
|
|
344
|
+
frontier = next_frontier - visited
|
|
345
|
+
|
|
346
|
+
return context_state
|
|
347
|
+
|
|
348
|
+
# ------------------------------------------------------------------
|
|
349
|
+
# Encoding
|
|
350
|
+
# ------------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
def encode_chunk_state(self, axioms: List[Tuple[str, str, str]]) -> int:
|
|
353
|
+
"""
|
|
354
|
+
Convert a list of axioms into a single Gödel State Integer via
|
|
355
|
+
iterative LCM over each axiom's derived prime.
|
|
356
|
+
|
|
357
|
+
Idempotent under duplicates: encoding the same triple twice
|
|
358
|
+
produces the same state as encoding it once, because
|
|
359
|
+
``math.lcm(prime, prime) == prime``. This matches the semantic
|
|
360
|
+
model of a *set* of axioms and aligns with every other merge
|
|
361
|
+
operation in the codebase (``math.lcm`` on lines 287, 331, 391,
|
|
362
|
+
399, 592, 617, 706, 742).
|
|
363
|
+
|
|
364
|
+
Historical note (pre-M1c cross-runtime harness): this function
|
|
365
|
+
used raw multiplicative accumulation (``state *= prime``), which
|
|
366
|
+
produced ``prime^n`` for duplicated triples. All production
|
|
367
|
+
callers pre-deduplicated their input (the sieve returns
|
|
368
|
+
``list(set(triples))``), so the difference was invisible in
|
|
369
|
+
running code. The JS port at ``single_file_demo/godel.js`` used
|
|
370
|
+
LCM from day one, and the cross-runtime byte-identity harness
|
|
371
|
+
at ``scripts/verify_godel_cross_runtime.py`` caught the drift
|
|
372
|
+
on the "repeated triple" fixture. The fix aligns the two
|
|
373
|
+
implementations and removes a latent source of non-determinism
|
|
374
|
+
(re-encoding a non-deduplicated list no longer perturbs the
|
|
375
|
+
state integer).
|
|
376
|
+
|
|
377
|
+
For input with only distinct triples (the sieve's output shape),
|
|
378
|
+
``math.lcm(*primes) == product(primes)`` because the primes are
|
|
379
|
+
pairwise coprime, so every existing test observes identical
|
|
380
|
+
state integers before and after this change.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
axioms: List of (subject, predicate, object) triples.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
A single integer encoding the full semantic state.
|
|
387
|
+
"""
|
|
388
|
+
state = 1
|
|
389
|
+
for subj, pred, obj in axioms:
|
|
390
|
+
state = math.lcm(state, self.get_or_mint_prime(subj, pred, obj))
|
|
391
|
+
return state
|
|
392
|
+
|
|
393
|
+
# ------------------------------------------------------------------
|
|
394
|
+
# Lock-free merge
|
|
395
|
+
# ------------------------------------------------------------------
|
|
396
|
+
|
|
397
|
+
def merge_parallel_states(self, state_integers: List[int]) -> int:
|
|
398
|
+
"""
|
|
399
|
+
Lock-Free Mass Parallel Merge.
|
|
400
|
+
|
|
401
|
+
The global semantic state is the Least Common Multiple (LCM) of
|
|
402
|
+
all chunk states. This automatically deduplicates overlapping
|
|
403
|
+
knowledge and is both commutative and associative.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
state_integers: Gödel integers from parallel chunk extractions.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
The merged global state.
|
|
410
|
+
"""
|
|
411
|
+
if not state_integers:
|
|
412
|
+
return 1
|
|
413
|
+
|
|
414
|
+
# ── Bare-Metal Fast Path (Zig BigInt LCM) ──
|
|
415
|
+
zig = _get_zig_engine()
|
|
416
|
+
if zig is not None:
|
|
417
|
+
result = state_integers[0]
|
|
418
|
+
for s in state_integers[1:]:
|
|
419
|
+
r = zig.bigint_lcm(result, s)
|
|
420
|
+
if r is None:
|
|
421
|
+
break
|
|
422
|
+
result = r
|
|
423
|
+
else:
|
|
424
|
+
return result
|
|
425
|
+
|
|
426
|
+
# ── Legacy Python Fallback ──
|
|
427
|
+
return math.lcm(*state_integers)
|
|
428
|
+
|
|
429
|
+
# ------------------------------------------------------------------
|
|
430
|
+
# Entailment
|
|
431
|
+
# ------------------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
def verify_entailment(self, global_state: int, hypothesis_state: int) -> bool:
|
|
434
|
+
"""
|
|
435
|
+
Epistemic Fidelity Gate (Tags-to-Tomes).
|
|
436
|
+
|
|
437
|
+
Does the Global State entail the Hypothesis? If the modulo is 0,
|
|
438
|
+
the hypothesis is *mathematically proven* to be a subset of the
|
|
439
|
+
global knowledge.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
global_state: The merged global Gödel integer.
|
|
443
|
+
hypothesis_state: The Gödel integer of the claim to verify.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
True if the hypothesis is entailed, False otherwise.
|
|
447
|
+
"""
|
|
448
|
+
if hypothesis_state == 0:
|
|
449
|
+
return False
|
|
450
|
+
|
|
451
|
+
# ── Bare-Metal Fast Path (Zig) ──
|
|
452
|
+
zig = _get_zig_engine()
|
|
453
|
+
if zig is not None:
|
|
454
|
+
if hypothesis_state.bit_length() <= 64:
|
|
455
|
+
# Single prime — optimized streaming mod (no BigInt needed)
|
|
456
|
+
result = zig.is_divisible_by(global_state, hypothesis_state)
|
|
457
|
+
if result is not None:
|
|
458
|
+
return result
|
|
459
|
+
else:
|
|
460
|
+
# Composite hypothesis — full BigInt modulo
|
|
461
|
+
result = zig.bigint_mod(global_state, hypothesis_state)
|
|
462
|
+
if result is not None:
|
|
463
|
+
return result == 0
|
|
464
|
+
|
|
465
|
+
# ── Legacy Python Fallback ──
|
|
466
|
+
return global_state % hypothesis_state == 0
|
|
467
|
+
|
|
468
|
+
# ------------------------------------------------------------------
|
|
469
|
+
# Curvature / Paradox detection
|
|
470
|
+
# ------------------------------------------------------------------
|
|
471
|
+
|
|
472
|
+
def detect_curvature_paradoxes(self, global_state: int) -> List[str]:
|
|
473
|
+
"""
|
|
474
|
+
Scan the global integer for Level 3 Curvature (Semantic Contradictions).
|
|
475
|
+
|
|
476
|
+
If mutually exclusive claims (e.g. "Alice is 30" AND "Alice is 31")
|
|
477
|
+
both divide the global state evenly, a curvature paradox is
|
|
478
|
+
detected and the SMT solver should be triggered to arbitrate.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
global_state: The merged global Gödel integer.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
List of human-readable paradox descriptions (empty = clean).
|
|
485
|
+
"""
|
|
486
|
+
paradoxes: List[str] = []
|
|
487
|
+
|
|
488
|
+
for zone_key, primes in self.exclusion_zones.items():
|
|
489
|
+
if len(primes) < 2:
|
|
490
|
+
continue
|
|
491
|
+
|
|
492
|
+
active_primes = [p for p in primes if global_state % p == 0]
|
|
493
|
+
|
|
494
|
+
if len(active_primes) > 1:
|
|
495
|
+
conflicting = [self.prime_to_axiom[p] for p in active_primes]
|
|
496
|
+
paradoxes.append(
|
|
497
|
+
f"Curvature Contradiction in [{zone_key}]: {conflicting}"
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
return paradoxes
|
|
501
|
+
|
|
502
|
+
# ------------------------------------------------------------------
|
|
503
|
+
# Hallucination isolation (Sieve of Hallucinations)
|
|
504
|
+
# ------------------------------------------------------------------
|
|
505
|
+
|
|
506
|
+
def isolate_hallucinations(
|
|
507
|
+
self, global_state: int, generated_state: int
|
|
508
|
+
) -> List[str]:
|
|
509
|
+
"""
|
|
510
|
+
The Sieve of Hallucinations.
|
|
511
|
+
|
|
512
|
+
If ``generated_state`` does not divide ``global_state`` evenly, the
|
|
513
|
+
LLM hallucinated. This method uses ``math.gcd`` to find the exact
|
|
514
|
+
prime numbers (axioms) the LLM fabricated — in microseconds.
|
|
515
|
+
|
|
516
|
+
Algorithm:
|
|
517
|
+
1. ``shared_truth = gcd(global_state, generated_state)``
|
|
518
|
+
— the primes that *are* in the global state.
|
|
519
|
+
2. ``hallucinated_product = generated_state // shared_truth``
|
|
520
|
+
— the product of primes *not* in the global state.
|
|
521
|
+
3. Factor ``hallucinated_product`` against known primes to
|
|
522
|
+
recover the specific axiom strings.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
global_state: The verified global Gödel integer.
|
|
526
|
+
generated_state: The Gödel integer extracted from LLM output.
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
List of axiom key strings that were hallucinated (empty = clean).
|
|
530
|
+
"""
|
|
531
|
+
if global_state % generated_state == 0:
|
|
532
|
+
return [] # Zero hallucinations
|
|
533
|
+
|
|
534
|
+
# ── Bare-Metal Fast Path (Zig BigInt GCD) ──
|
|
535
|
+
zig = _get_zig_engine()
|
|
536
|
+
if zig is not None:
|
|
537
|
+
zig_gcd = zig.bigint_gcd(global_state, generated_state)
|
|
538
|
+
if zig_gcd is not None:
|
|
539
|
+
shared_truth = zig_gcd
|
|
540
|
+
else:
|
|
541
|
+
shared_truth = math.gcd(global_state, generated_state)
|
|
542
|
+
else:
|
|
543
|
+
shared_truth = math.gcd(global_state, generated_state)
|
|
544
|
+
|
|
545
|
+
hallucinated_product = generated_state // shared_truth
|
|
546
|
+
|
|
547
|
+
hallucinated_axioms: List[str] = []
|
|
548
|
+
|
|
549
|
+
for prime, axiom in self.prime_to_axiom.items():
|
|
550
|
+
if hallucinated_product % prime == 0:
|
|
551
|
+
hallucinated_axioms.append(axiom)
|
|
552
|
+
# Divide out the prime to break early if product reaches 1
|
|
553
|
+
while hallucinated_product % prime == 0:
|
|
554
|
+
hallucinated_product //= prime
|
|
555
|
+
if hallucinated_product == 1:
|
|
556
|
+
break
|
|
557
|
+
|
|
558
|
+
return hallucinated_axioms
|
|
559
|
+
|
|
560
|
+
# ------------------------------------------------------------------
|
|
561
|
+
# Temporal evolution (Delete / Update)
|
|
562
|
+
# ------------------------------------------------------------------
|
|
563
|
+
|
|
564
|
+
def delete_axiom(self, global_state: int, axiom_key: str) -> int:
|
|
565
|
+
"""
|
|
566
|
+
Semantic Deletion.
|
|
567
|
+
|
|
568
|
+
Removes a fact from the global state by dividing out its prime
|
|
569
|
+
factor. Handles multiple occurrences if merged carelessly.
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
global_state: The current global Gödel integer.
|
|
573
|
+
axiom_key: Normalised axiom key (``subject||predicate||object``).
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
The updated global state with the axiom removed.
|
|
577
|
+
"""
|
|
578
|
+
axiom_key = axiom_key.strip().lower()
|
|
579
|
+
|
|
580
|
+
if axiom_key not in self.axiom_to_prime:
|
|
581
|
+
return global_state # Axiom was never minted
|
|
582
|
+
|
|
583
|
+
prime = self.axiom_to_prime[axiom_key]
|
|
584
|
+
|
|
585
|
+
# Completely factor out the prime
|
|
586
|
+
while global_state % prime == 0:
|
|
587
|
+
global_state //= prime
|
|
588
|
+
|
|
589
|
+
return global_state
|
|
590
|
+
|
|
591
|
+
def update_axiom(
|
|
592
|
+
self, global_state: int, old_axiom_key: str, new_axiom_key: str
|
|
593
|
+
) -> int:
|
|
594
|
+
"""
|
|
595
|
+
Semantic Update (Curvature Resolution).
|
|
596
|
+
|
|
597
|
+
Divides out the obsolete fact and multiplies in the new fact —
|
|
598
|
+
a single atomic gauge transformation on the state integer.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
global_state: The current global Gödel integer.
|
|
602
|
+
old_axiom_key: Key of the axiom to replace (``subj||pred||obj``).
|
|
603
|
+
new_axiom_key: Key of the replacement axiom (``subj||pred||obj``).
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
The updated global state.
|
|
607
|
+
|
|
608
|
+
Raises:
|
|
609
|
+
ValueError: If the new axiom key is malformed.
|
|
610
|
+
"""
|
|
611
|
+
parts = new_axiom_key.split("||")
|
|
612
|
+
if len(parts) != 3:
|
|
613
|
+
raise ValueError(
|
|
614
|
+
"Invalid axiom format. Must be subject||predicate||object"
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
new_prime = self.get_or_mint_prime(parts[0], parts[1], parts[2])
|
|
618
|
+
state_without_old = self.delete_axiom(global_state, old_axiom_key)
|
|
619
|
+
|
|
620
|
+
return math.lcm(state_without_old, new_prime)
|
|
621
|
+
|
|
622
|
+
# ------------------------------------------------------------------
|
|
623
|
+
# Gödel Sync Protocol (Distributed Delta)
|
|
624
|
+
# ------------------------------------------------------------------
|
|
625
|
+
|
|
626
|
+
def calculate_network_delta(
|
|
627
|
+
self, global_state: int, client_state: int
|
|
628
|
+
) -> dict:
|
|
629
|
+
"""
|
|
630
|
+
O(1) Distributed State Synchronization.
|
|
631
|
+
|
|
632
|
+
Compares the server's global integer with the client's integer to
|
|
633
|
+
compute the exact semantic delta:
|
|
634
|
+
shared_truth = gcd(global, client)
|
|
635
|
+
missing_product = global // shared_truth → axioms to ADD
|
|
636
|
+
obsolete_product = client // shared_truth → axioms to DELETE
|
|
637
|
+
|
|
638
|
+
Args:
|
|
639
|
+
global_state: The server's current Gödel integer.
|
|
640
|
+
client_state: The client's cached Gödel integer.
|
|
641
|
+
|
|
642
|
+
Returns:
|
|
643
|
+
Dict with ``"add"`` and ``"delete"`` lists of axiom key strings.
|
|
644
|
+
"""
|
|
645
|
+
if global_state == client_state:
|
|
646
|
+
return {"add": [], "delete": []}
|
|
647
|
+
|
|
648
|
+
# ── Bare-Metal Fast Path (Zig BigInt GCD) ──
|
|
649
|
+
zig = _get_zig_engine()
|
|
650
|
+
if zig is not None:
|
|
651
|
+
zig_gcd = zig.bigint_gcd(global_state, client_state)
|
|
652
|
+
if zig_gcd is not None:
|
|
653
|
+
shared_truth = zig_gcd
|
|
654
|
+
else:
|
|
655
|
+
shared_truth = math.gcd(global_state, client_state)
|
|
656
|
+
else:
|
|
657
|
+
shared_truth = math.gcd(global_state, client_state)
|
|
658
|
+
|
|
659
|
+
# Primes the client is missing (server has, client doesn't)
|
|
660
|
+
missing_product = global_state // shared_truth
|
|
661
|
+
|
|
662
|
+
# Primes the client has that are obsolete
|
|
663
|
+
obsolete_product = client_state // shared_truth
|
|
664
|
+
|
|
665
|
+
def _extract_axioms(product: int) -> List[str]:
|
|
666
|
+
axioms: List[str] = []
|
|
667
|
+
for prime, axiom in self.prime_to_axiom.items():
|
|
668
|
+
if product % prime == 0:
|
|
669
|
+
axioms.append(axiom)
|
|
670
|
+
while product % prime == 0:
|
|
671
|
+
product //= prime
|
|
672
|
+
if product == 1:
|
|
673
|
+
break
|
|
674
|
+
return axioms
|
|
675
|
+
|
|
676
|
+
return {
|
|
677
|
+
"add": _extract_axioms(missing_product),
|
|
678
|
+
"delete": _extract_axioms(obsolete_product),
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
# ------------------------------------------------------------------
|
|
682
|
+
# Fractal Crystallization (Semantic Zooming)
|
|
683
|
+
# ------------------------------------------------------------------
|
|
684
|
+
|
|
685
|
+
def crystallize_axioms(
|
|
686
|
+
self,
|
|
687
|
+
global_state: int,
|
|
688
|
+
micro_axiom_keys: List[str],
|
|
689
|
+
macro_axiom_key: str,
|
|
690
|
+
) -> int:
|
|
691
|
+
"""
|
|
692
|
+
O(1) Fractal Compression (Zoom Out).
|
|
693
|
+
|
|
694
|
+
Replaces a cluster of micro-primes with a single Macro-Prime.
|
|
695
|
+
Stores the cluster product as provenance for lossless
|
|
696
|
+
decompression via ``decrystallize_axiom``.
|
|
697
|
+
|
|
698
|
+
Args:
|
|
699
|
+
global_state: Current global Gödel integer.
|
|
700
|
+
micro_axiom_keys: Keys of micro-axioms to compress.
|
|
701
|
+
macro_axiom_key: Key for the new macro-axiom.
|
|
702
|
+
|
|
703
|
+
Returns:
|
|
704
|
+
Updated global state with micro-primes replaced by macro-prime.
|
|
705
|
+
"""
|
|
706
|
+
cluster_product = 1
|
|
707
|
+
|
|
708
|
+
for key in micro_axiom_keys:
|
|
709
|
+
key = key.strip().lower()
|
|
710
|
+
if (
|
|
711
|
+
key in self.axiom_to_prime
|
|
712
|
+
and global_state % self.axiom_to_prime[key] == 0
|
|
713
|
+
):
|
|
714
|
+
prime = self.axiom_to_prime[key]
|
|
715
|
+
cluster_product *= prime
|
|
716
|
+
# Completely factor out the micro-prime
|
|
717
|
+
while global_state % prime == 0:
|
|
718
|
+
global_state //= prime
|
|
719
|
+
|
|
720
|
+
if cluster_product == 1:
|
|
721
|
+
return global_state # No active micro-primes found
|
|
722
|
+
|
|
723
|
+
parts = macro_axiom_key.split("||")
|
|
724
|
+
if len(parts) != 3:
|
|
725
|
+
raise ValueError(
|
|
726
|
+
"Invalid macro axiom format. Must be subject||predicate||object"
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
macro_prime = self.get_or_mint_prime(parts[0], parts[1], parts[2])
|
|
730
|
+
|
|
731
|
+
# Store provenance for lossless decompression
|
|
732
|
+
self.macro_provenance[macro_prime] = cluster_product
|
|
733
|
+
|
|
734
|
+
return math.lcm(global_state, macro_prime)
|
|
735
|
+
|
|
736
|
+
def decrystallize_axiom(
|
|
737
|
+
self, global_state: int, macro_axiom_key: str
|
|
738
|
+
) -> int:
|
|
739
|
+
"""
|
|
740
|
+
O(1) Semantic Decompression (Zoom In).
|
|
741
|
+
|
|
742
|
+
Divides out the Macro-Prime and restores the full cluster of
|
|
743
|
+
micro-primes from stored provenance.
|
|
744
|
+
|
|
745
|
+
Args:
|
|
746
|
+
global_state: Current global Gödel integer.
|
|
747
|
+
macro_axiom_key: Key of the macro-axiom to decompress.
|
|
748
|
+
|
|
749
|
+
Returns:
|
|
750
|
+
Updated global state with micro-primes restored.
|
|
751
|
+
"""
|
|
752
|
+
macro_axiom_key = macro_axiom_key.strip().lower()
|
|
753
|
+
|
|
754
|
+
if macro_axiom_key not in self.axiom_to_prime:
|
|
755
|
+
return global_state
|
|
756
|
+
|
|
757
|
+
macro_prime = self.axiom_to_prime[macro_axiom_key]
|
|
758
|
+
|
|
759
|
+
if (
|
|
760
|
+
global_state % macro_prime != 0
|
|
761
|
+
or macro_prime not in self.macro_provenance
|
|
762
|
+
):
|
|
763
|
+
return global_state
|
|
764
|
+
|
|
765
|
+
# Factor out the macro-prime
|
|
766
|
+
while global_state % macro_prime == 0:
|
|
767
|
+
global_state //= macro_prime
|
|
768
|
+
|
|
769
|
+
# Restore the micro-prime cluster
|
|
770
|
+
return math.lcm(global_state, self.macro_provenance[macro_prime])
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
# ======================================================================
|
|
774
|
+
# Phase 19D: Active Prime Set Index
|
|
775
|
+
# ======================================================================
|
|
776
|
+
|
|
777
|
+
class ActivePrimeIndex:
|
|
778
|
+
"""O(1) active-prime lookup per branch.
|
|
779
|
+
|
|
780
|
+
Maintains a ``Set[int]`` of primes known to divide each branch's
|
|
781
|
+
state integer. This eliminates the O(n) scan over
|
|
782
|
+
``prime_to_axiom`` that dominated profiling at scale:
|
|
783
|
+
|
|
784
|
+
get_active_axioms : 700 ms → O(k) where k = active primes
|
|
785
|
+
calculate_network_delta: 3.7 s → O(k)
|
|
786
|
+
|
|
787
|
+
Lifecycle:
|
|
788
|
+
rebuild() — cold-start from a state integer (boot / merge)
|
|
789
|
+
add() — O(1) on ingest
|
|
790
|
+
remove() — O(1) on delete
|
|
791
|
+
fork() — O(k) on branch creation
|
|
792
|
+
"""
|
|
793
|
+
|
|
794
|
+
def __init__(self):
|
|
795
|
+
self._branches: Dict[str, Set[int]] = {}
|
|
796
|
+
|
|
797
|
+
def rebuild(
|
|
798
|
+
self, branch: str, state: int, algebra: GodelStateAlgebra
|
|
799
|
+
) -> None:
|
|
800
|
+
"""Cold-start: scan once to populate the index from a state integer."""
|
|
801
|
+
if state <= 1:
|
|
802
|
+
self._branches[branch] = set()
|
|
803
|
+
return
|
|
804
|
+
self._branches[branch] = {
|
|
805
|
+
p for p in algebra.prime_to_axiom if state % p == 0
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
def add(self, branch: str, prime: int) -> None:
|
|
809
|
+
"""Register a newly-minted prime as active in a branch."""
|
|
810
|
+
self._branches.setdefault(branch, set()).add(prime)
|
|
811
|
+
|
|
812
|
+
def remove(self, branch: str, prime: int) -> None:
|
|
813
|
+
"""Remove a prime from a branch (deletion / DIV)."""
|
|
814
|
+
if branch in self._branches:
|
|
815
|
+
self._branches[branch].discard(prime)
|
|
816
|
+
|
|
817
|
+
def fork(self, source: str, target: str) -> None:
|
|
818
|
+
"""Copy the active set from one branch to another."""
|
|
819
|
+
self._branches[target] = set(self._branches.get(source, set()))
|
|
820
|
+
|
|
821
|
+
def merge(self, target: str, *sources: str) -> None:
|
|
822
|
+
"""Union active sets from multiple branches into target."""
|
|
823
|
+
merged: Set[int] = set()
|
|
824
|
+
for src in sources:
|
|
825
|
+
merged |= self._branches.get(src, set())
|
|
826
|
+
self._branches[target] = merged
|
|
827
|
+
|
|
828
|
+
def get_active_primes(self, branch: str) -> Set[int]:
|
|
829
|
+
"""Return the set of active primes for a branch."""
|
|
830
|
+
return self._branches.get(branch, set())
|
|
831
|
+
|
|
832
|
+
def get_active_axioms(
|
|
833
|
+
self, branch: str, algebra: GodelStateAlgebra
|
|
834
|
+
) -> list:
|
|
835
|
+
"""Return axiom key strings for all active primes in a branch."""
|
|
836
|
+
return [
|
|
837
|
+
algebra.prime_to_axiom[p]
|
|
838
|
+
for p in self._branches.get(branch, set())
|
|
839
|
+
if p in algebra.prime_to_axiom
|
|
840
|
+
]
|
|
841
|
+
|
|
842
|
+
def assert_coherent(
|
|
843
|
+
self,
|
|
844
|
+
branch: str,
|
|
845
|
+
state: int,
|
|
846
|
+
algebra: "GodelStateAlgebra",
|
|
847
|
+
context: str = "",
|
|
848
|
+
) -> None:
|
|
849
|
+
"""Debug assertion: verify index matches brute-force scan."""
|
|
850
|
+
import os
|
|
851
|
+
if not os.getenv("SUM_DEBUG_INDEX"):
|
|
852
|
+
return
|
|
853
|
+
|
|
854
|
+
indexed = sorted(self.get_active_axioms(branch, algebra))
|
|
855
|
+
brute = sorted(algebra.get_active_axioms(state))
|
|
856
|
+
if indexed != brute:
|
|
857
|
+
indexed_set = set(indexed)
|
|
858
|
+
brute_set = set(brute)
|
|
859
|
+
missing = brute_set - indexed_set
|
|
860
|
+
extra = indexed_set - brute_set
|
|
861
|
+
raise AssertionError(
|
|
862
|
+
f"INDEX COHERENCE VIOLATION [{context}] "
|
|
863
|
+
f"branch={branch}: "
|
|
864
|
+
f"index has {len(indexed)} axioms, "
|
|
865
|
+
f"brute-force has {len(brute)} axioms. "
|
|
866
|
+
f"Missing from index: {missing}. "
|
|
867
|
+
f"Extra in index: {extra}."
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
def extract_axioms_from_product(
|
|
871
|
+
self, product: int, algebra: GodelStateAlgebra,
|
|
872
|
+
candidate_primes: Set[int] = None,
|
|
873
|
+
) -> List[str]:
|
|
874
|
+
"""Extract axiom keys from a quotient product, optionally narrowed.
|
|
875
|
+
|
|
876
|
+
When ``candidate_primes`` is provided, only those primes are
|
|
877
|
+
checked — O(k) instead of O(n). Falls back to full scan if
|
|
878
|
+
no candidates are given.
|
|
879
|
+
"""
|
|
880
|
+
axioms: List[str] = []
|
|
881
|
+
primes_to_check = candidate_primes or set(algebra.prime_to_axiom.keys())
|
|
882
|
+
for prime in primes_to_check:
|
|
883
|
+
if product % prime == 0:
|
|
884
|
+
if prime in algebra.prime_to_axiom:
|
|
885
|
+
axioms.append(algebra.prime_to_axiom[prime])
|
|
886
|
+
while product % prime == 0:
|
|
887
|
+
product //= prime
|
|
888
|
+
if product == 1:
|
|
889
|
+
break
|
|
890
|
+
return axioms
|