-
Notifications
You must be signed in to change notification settings - Fork 0
Restore MPL baseline and add TC axis consistency layer #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pt2710
wants to merge
2
commits into
master
Choose a base branch
from
repair/restore-mpl-tc-consistency
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| from triadic_domains import TriadicDomains | ||
|
|
||
|
|
||
| def test_unity_even_odd_placements(): | ||
| domains = TriadicDomains([2, 3, 5, 7]) | ||
|
|
||
| assert domains.place(1).domain == "U" | ||
| assert domains.place(1).axis == "U1" | ||
|
|
||
| placed_even = domains.place(40) | ||
| assert placed_even.domain == "E" | ||
| assert placed_even.axis == "E3" | ||
| assert placed_even.cofactor == 5 | ||
|
|
||
| placed_prime = domains.place(31) | ||
| assert placed_prime.domain == "O" | ||
| assert placed_prime.axis == "O1" | ||
| assert placed_prime.kind == "odd-prime" | ||
|
|
||
|
|
||
| def test_odd_composites_are_lpf_axis_placed(): | ||
| domains = TriadicDomains([2, 3, 5, 7]) | ||
|
|
||
| expected = { | ||
| 9: ("O2", 3, 3), | ||
| 15: ("O2", 3, 5), | ||
| 21: ("O2", 3, 7), | ||
| 25: ("O3", 5, 5), | ||
| 27: ("O2", 3, 9), | ||
| 35: ("O3", 5, 7), | ||
| 49: ("O4", 7, 7), | ||
| } | ||
|
|
||
| for value, (axis, lpf, cofactor) in expected.items(): | ||
| placement = domains.place(value) | ||
| assert placement.domain == "O" | ||
| assert placement.kind == "odd-composite" | ||
| assert placement.axis == axis | ||
| assert placement.least_prime_factor == lpf | ||
| assert placement.cofactor == cofactor | ||
|
|
||
|
|
||
| def test_axis_law_first_hole_examples(): | ||
| domains_after_3 = TriadicDomains([2, 3]) | ||
| assert domains_after_3.axis_law_successor_gap(3, limit=16) == (5, 2) | ||
| assert domains_after_3.axis_law_successor_gap(5, limit=16) == (7, 2) | ||
| assert domains_after_3.axis_law_successor_gap(7, limit=16) == (11, 4) | ||
|
|
||
| domains_after_7 = TriadicDomains([2, 3, 5, 7]) | ||
| assert domains_after_7.axis_law_successor_gap(29, limit=64) == (31, 2) | ||
| assert domains_after_7.place(31).axis == "O1" | ||
|
|
||
|
|
||
| def test_composite_stream_has_canonical_lpf_entries_without_duplicate_axes(): | ||
| domains = TriadicDomains([2, 3, 5, 7]) | ||
| placements = {p.value: p for p in domains.odd_composite_stream(50)} | ||
|
|
||
| assert placements[15].axis == "O2" | ||
| assert placements[15].least_prime_factor == 3 | ||
| assert placements[25].axis == "O3" | ||
| assert placements[35].axis == "O3" | ||
| assert placements[49].axis == "O4" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| """ | ||
| Triadic Completeness domain model for McCrackn's Prime Law. | ||
|
|
||
| This module keeps the TC / UEO layer separate from the gap-motif scheduler. | ||
| It models the paper-level bridge: | ||
|
|
||
| U -> E -> O | ||
| M_j = <{2} union P_j> | ||
| p_{j+1} = min(N>=1 \ M_j) | ||
|
|
||
| The implementation is deliberately finite-prefix and audit oriented. It does | ||
| not perform trial division or primality testing. Composite placement is derived | ||
| from already realized axes. | ||
| """ | ||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from heapq import heappop, heappush | ||
| from typing import Iterable, Iterator, Literal | ||
|
|
||
|
|
||
| AxisKind = Literal["unity", "even", "odd-prime", "odd-composite"] | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class AxisPlacement: | ||
| """Canonical U/E/O placement for a positive integer in a realized prefix.""" | ||
|
|
||
| value: int | ||
| domain: str | ||
| axis: str | ||
| kind: AxisKind | ||
| least_prime_factor: int | None = None | ||
| cofactor: int | None = None | ||
|
|
||
|
|
||
| class TriadicDomains: | ||
| """ | ||
| Finite-prefix TC axis machine. | ||
|
|
||
| The odd domain is split into: | ||
| - O1: the free / prime odd axis in the user's notation. | ||
| - O{k}: least-prime-factor composite strata, where O2 is lpf=3, | ||
| O3 is lpf=5, O4 is lpf=7, and so on. | ||
|
|
||
| The paper notation uses O_0 for the prime/free layer and O_i^(min) | ||
| for least-prime-factor composite strata. This class exposes both via | ||
| explicit placement metadata. | ||
| """ | ||
|
|
||
| UNITY = 1 | ||
|
|
||
| def __init__(self, realized_primes: Iterable[int]): | ||
| primes = tuple(int(p) for p in realized_primes) | ||
| if not primes or primes[0] != 2: | ||
| raise ValueError("realized_primes must start with 2") | ||
| if any(p <= 0 for p in primes): | ||
| raise ValueError("realized_primes must be positive") | ||
| self.realized_primes = primes | ||
| self.odd_primes = tuple(p for p in primes if p != 2) | ||
| self._odd_axis_index = {p: i + 2 for i, p in enumerate(self.odd_primes)} | ||
|
|
||
| @staticmethod | ||
| def odd_axis_value(position: int) -> int: | ||
| """Return the primitive odd-axis value at zero-based position.""" | ||
| if position < 0: | ||
| raise ValueError("position must be non-negative") | ||
| return 2 * position + 1 | ||
|
|
||
| def even_face(self, n: int) -> tuple[int, int]: | ||
| """Return (v2(n), odd_face) for a positive integer.""" | ||
| if n <= 0: | ||
| raise ValueError("n must be positive") | ||
| depth = 0 | ||
| value = n | ||
| while value % 2 == 0: | ||
| depth += 1 | ||
| value //= 2 | ||
| return depth, value | ||
|
|
||
| def emitted_smooth_numbers(self, limit: int) -> list[int]: | ||
| """ | ||
| Emit the finite prefix of <{2} union P_j> up to limit. | ||
|
|
||
| This uses merge/multiply stream operations over already realized axes. | ||
| It is a finite-prefix witness for Axis Law coverage, not a primality | ||
| test over arbitrary candidates. | ||
| """ | ||
| if limit < 1: | ||
| return [] | ||
| seen = {1} | ||
| heap = [1] | ||
| out: list[int] = [] | ||
| while heap: | ||
| value = heappop(heap) | ||
| if value > limit: | ||
| continue | ||
| out.append(value) | ||
| for axis in self.realized_primes: | ||
| nxt = value * axis | ||
| if nxt <= limit and nxt not in seen: | ||
| seen.add(nxt) | ||
| heappush(heap, nxt) | ||
| return out | ||
|
|
||
| def first_hole_after(self, current_prime: int, limit: int | None = None) -> int: | ||
| """ | ||
| Return the first positive integer after current_prime not emitted by | ||
| the realized-axis monoid within a finite bound. | ||
| """ | ||
| if current_prime < 1: | ||
| raise ValueError("current_prime must be positive") | ||
| bound = limit if limit is not None else max(current_prime * current_prime, current_prime + 32) | ||
| covered = set(self.emitted_smooth_numbers(bound)) | ||
| for n in range(current_prime + 1, bound + 1): | ||
| if n not in covered: | ||
| return n | ||
| raise ValueError("finite bound did not expose a first hole") | ||
|
|
||
| def axis_law_successor_gap(self, current_prime: int, limit: int | None = None) -> tuple[int, int]: | ||
| """Return (next_prime, gap) from the finite first-hole law.""" | ||
| nxt = self.first_hole_after(current_prime, limit=limit) | ||
| return nxt, nxt - current_prime | ||
|
|
||
| def odd_composite_stream(self, limit: int) -> Iterator[AxisPlacement]: | ||
| """Yield canonical odd composite placements up to limit by lpf strata.""" | ||
| if limit < 3: | ||
| return | ||
| for p in self.odd_primes: | ||
| for odd in range(p, limit // p + 1, 2): | ||
| value = p * odd | ||
| if value > limit: | ||
| break | ||
| if self._least_realized_odd_prime_factor(value) == p and value != p: | ||
| yield AxisPlacement( | ||
| value=value, | ||
| domain="O", | ||
| axis=f"O{self._odd_axis_index[p]}", | ||
| kind="odd-composite", | ||
| least_prime_factor=p, | ||
| cofactor=odd, | ||
| ) | ||
|
|
||
| def place(self, n: int) -> AxisPlacement: | ||
| """Return the canonical finite-prefix U/E/O placement for n.""" | ||
| if n <= 0: | ||
| raise ValueError("n must be positive") | ||
| if n == 1: | ||
| return AxisPlacement(n, "U", "U1", "unity") | ||
|
|
||
| depth, odd_face = self.even_face(n) | ||
| if depth > 0: | ||
| return AxisPlacement(n, "E", f"E{depth}", "even", cofactor=odd_face) | ||
|
|
||
| if n in self.odd_primes: | ||
| return AxisPlacement(n, "O", "O1", "odd-prime") | ||
|
|
||
| lpf = self._least_realized_odd_prime_factor(n) | ||
| if lpf is None: | ||
| return AxisPlacement(n, "O", "O1", "odd-prime") | ||
|
|
||
| return AxisPlacement( | ||
| value=n, | ||
| domain="O", | ||
| axis=f"O{self._odd_axis_index[lpf]}", | ||
| kind="odd-composite", | ||
| least_prime_factor=lpf, | ||
| cofactor=n // lpf, | ||
| ) | ||
|
|
||
| def _least_realized_odd_prime_factor(self, n: int) -> int | None: | ||
| for p in self.odd_primes: | ||
| if p * p > n: | ||
| break | ||
| if n % p == 0: | ||
| return p | ||
| return None |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test module imports
triadic_domainsdirectly without first adding the repository root tosys.path, unliketests/test_basic.py, so invoking tests via thepytestentrypoint can fail during collection withModuleNotFoundError: No module named 'triadic_domains'. In this environment,pytest -q tests/test_triadic_domains.pyreproduces the error immediately, which blocks the new test suite from running in common CI/test invocations.Useful? React with 👍 / 👎.