Skip to content

Commit 355c098

Browse files
author
Grace Lee Rui Yue
committed
add verified finite group factorizations
1 parent 614fdc4 commit 355c098

8 files changed

Lines changed: 543 additions & 0 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Finite abelian group exact factorization
2+
3+
`finite_abelian_group.exact_factorization.compute` works in a bounded product
4+
of cyclic groups `Z/n1 x ... x Z/nr`. It normalizes two supplied lists of
5+
integer vectors and exhaustively counts representations of every group element
6+
as `a+b` with `a` from the left factor and `b` from the right factor.
7+
8+
The result contains the normalized factors, the complete representation-count
9+
histogram, an exact-factorization decision, and the first missing and duplicate
10+
representation witnesses when the decision is false. A complete coset
11+
transversal is the special case where the right factor contains only zero.
12+
13+
The current contract supports rank at most six, group order at most 4,096,
14+
factor sizes at most 256, and a factor Cartesian product no larger than the
15+
group order. Coordinates may be noncanonical integers and are reduced by their
16+
corresponding cyclic moduli.
17+
18+
The producer returns `COMPUTED` evidence. The operator-authorized companion
19+
`finite_abelian_group.exact_factorization.verify` independently normalizes the
20+
payload and replays every group sum, binding the accepted record to the exact
21+
group presentation, factors, histogram, decision, and witnesses.
22+
23+
This capability verifies only the supplied finite factorization. It does not
24+
decide infinite lattice tiling, periodicity, or orbit-closure properties.

docs/reference/capabilities/number-theory/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
- [Powerful-number decision](integer-powerful-number-decision.md)
66
- [Integer prime-factorization verification](integer-prime-factorization-verification.md)
7+
- [Finite abelian group exact factorization](finite-abelian-group-factorization.md)

src/jacobian/contracts/number_theory.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
_MAX_RESIDUE_EXPONENT = 32
4040
_MAX_RESIDUE_ASSIGNMENTS = 4_096
4141
_MAX_POLYNOMIAL_RESIDUE_MODULUS = 1_000_000
42+
_MAX_FINITE_GROUP_ORDER = 4_096
43+
_MAX_FINITE_GROUP_RANK = 6
44+
_MAX_FINITE_GROUP_FACTOR_SIZE = 256
4245

4346
BoundedInteger = Annotated[
4447
str,
@@ -294,6 +297,43 @@ def require_canonical_bounded_polynomial(self) -> Self:
294297
return self
295298

296299

300+
class FiniteAbelianGroupFactorizationRequest(ContractModel):
301+
"""Two bounded integer-vector factors in a product of cyclic groups."""
302+
303+
moduli: tuple[StrictInt, ...] = Field(
304+
min_length=1, max_length=_MAX_FINITE_GROUP_RANK
305+
)
306+
left: tuple[tuple[StrictInt, ...], ...] = Field(
307+
min_length=1, max_length=_MAX_FINITE_GROUP_FACTOR_SIZE
308+
)
309+
right: tuple[tuple[StrictInt, ...], ...] = Field(
310+
min_length=1, max_length=_MAX_FINITE_GROUP_FACTOR_SIZE
311+
)
312+
313+
@model_validator(mode="after")
314+
def require_bounded_product_group(self) -> Self:
315+
if any(modulus < 2 or modulus > 1_000_000 for modulus in self.moduli):
316+
raise ValueError("cyclic moduli must be between 2 and 1,000,000")
317+
if math.prod(self.moduli) > _MAX_FINITE_GROUP_ORDER:
318+
raise ValueError("finite abelian group exceeds the 4,096-element bound")
319+
if len(self.left) * len(self.right) > _MAX_FINITE_GROUP_ORDER:
320+
raise ValueError("factor Cartesian product exceeds the group-order bound")
321+
if any(
322+
len(element) != len(self.moduli)
323+
for factor in (self.left, self.right)
324+
for element in factor
325+
):
326+
raise ValueError("every factor element must match the group rank")
327+
if any(
328+
abs(coordinate) > 1_000_000
329+
for factor in (self.left, self.right)
330+
for element in factor
331+
for coordinate in element
332+
):
333+
raise ValueError("factor coordinates exceed the input bound")
334+
return self
335+
336+
297337
class ChineseRemainderRequest(ContractModel):
298338
"""A finite system of integer congruences with parallel residues and moduli."""
299339

@@ -541,6 +581,90 @@ def bind_complete_residue_image(self) -> Self:
541581
return self
542582

543583

584+
class FiniteAbelianRepresentationCount(ContractModel):
585+
representation_count: StrictInt = Field(ge=0, le=_MAX_FINITE_GROUP_ORDER)
586+
element_count: StrictInt = Field(ge=1, le=_MAX_FINITE_GROUP_ORDER)
587+
588+
589+
class FiniteAbelianRepresentationWitness(ContractModel):
590+
element: tuple[StrictInt, ...] = Field(
591+
min_length=1, max_length=_MAX_FINITE_GROUP_RANK
592+
)
593+
left: tuple[StrictInt, ...] = Field(min_length=1, max_length=_MAX_FINITE_GROUP_RANK)
594+
right: tuple[StrictInt, ...] = Field(
595+
min_length=1, max_length=_MAX_FINITE_GROUP_RANK
596+
)
597+
other_left: tuple[StrictInt, ...] | None = Field(
598+
default=None, min_length=1, max_length=_MAX_FINITE_GROUP_RANK
599+
)
600+
other_right: tuple[StrictInt, ...] | None = Field(
601+
default=None, min_length=1, max_length=_MAX_FINITE_GROUP_RANK
602+
)
603+
604+
605+
class FiniteAbelianGroupFactorizationResult(ContractModel):
606+
"""Complete unique-representation summary for ``G = left + right``."""
607+
608+
semantics_version: Literal["finite-abelian-group-factorization.v1"]
609+
moduli: tuple[StrictInt, ...] = Field(
610+
min_length=1, max_length=_MAX_FINITE_GROUP_RANK
611+
)
612+
normalized_left: tuple[tuple[StrictInt, ...], ...] = Field(
613+
min_length=1, max_length=_MAX_FINITE_GROUP_FACTOR_SIZE
614+
)
615+
normalized_right: tuple[tuple[StrictInt, ...], ...] = Field(
616+
min_length=1, max_length=_MAX_FINITE_GROUP_FACTOR_SIZE
617+
)
618+
group_order: StrictInt = Field(ge=2, le=_MAX_FINITE_GROUP_ORDER)
619+
pair_count: StrictInt = Field(ge=1, le=_MAX_FINITE_GROUP_ORDER)
620+
distinct_sum_count: StrictInt = Field(ge=1, le=_MAX_FINITE_GROUP_ORDER)
621+
representation_histogram: tuple[FiniteAbelianRepresentationCount, ...] = Field(
622+
min_length=1
623+
)
624+
is_exact_factorization: StrictBool
625+
first_missing: tuple[StrictInt, ...] | None = Field(
626+
default=None, min_length=1, max_length=_MAX_FINITE_GROUP_RANK
627+
)
628+
first_duplicate: FiniteAbelianRepresentationWitness | None = None
629+
630+
@model_validator(mode="after")
631+
def bind_factorization_summary(self) -> Self:
632+
if self.group_order != math.prod(self.moduli):
633+
raise ValueError("group order must equal the product of cyclic moduli")
634+
if self.pair_count != len(self.normalized_left) * len(self.normalized_right):
635+
raise ValueError("pair count must equal the factor Cartesian-product size")
636+
counts = tuple(
637+
item.representation_count for item in self.representation_histogram
638+
)
639+
if counts != tuple(sorted(set(counts))):
640+
raise ValueError(
641+
"representation histogram counts must be unique and increasing"
642+
)
643+
if (
644+
sum(item.element_count for item in self.representation_histogram)
645+
!= self.group_order
646+
):
647+
raise ValueError("representation histogram must cover the complete group")
648+
if (
649+
sum(
650+
item.representation_count * item.element_count
651+
for item in self.representation_histogram
652+
)
653+
!= self.pair_count
654+
):
655+
raise ValueError("representation histogram must cover every factor pair")
656+
expected = self.pair_count == self.group_order and all(
657+
item.representation_count == 1 for item in self.representation_histogram
658+
)
659+
if self.is_exact_factorization != expected:
660+
raise ValueError(
661+
"factorization decision does not match the complete histogram"
662+
)
663+
if self.is_exact_factorization and (self.first_missing or self.first_duplicate):
664+
raise ValueError("exact factorizations cannot carry failure witnesses")
665+
return self
666+
667+
544668
def _evaluate_normalized_modular_polynomial(
545669
terms: tuple[NormalizedModularPolynomialTerm, ...],
546670
assignment: tuple[int, ...],

src/jacobian/domains/number_theory/checkers.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,40 @@
33
from jacobian.checker_operations import ExactReplayCheckerDeclaration
44
from jacobian.contracts.number_theory import (
55
FactorizationRequest,
6+
FiniteAbelianGroupFactorizationRequest,
67
ModularPolynomialResidueImageRequest,
78
PowerfulNumberRequest,
89
)
910

1011
_EXACT_DOMAIN_ENTRYPOINT = "jacobian_checkers.exact_domain_operations"
1112

1213
NUMBER_THEORY_EXACT_REPLAY_CHECKERS = (
14+
ExactReplayCheckerDeclaration(
15+
"finite_abelian_group.exact_factorization.compute",
16+
FiniteAbelianGroupFactorizationRequest,
17+
"check_finite_abelian_group_exact_factorization",
18+
"finite-abelian-group.exact-factorization.stdlib-replay",
19+
entrypoint_module=_EXACT_DOMAIN_ENTRYPOINT,
20+
replay_method="Python standard-library exhaustive group replay",
21+
reason=(
22+
"operator-authorized checker independently normalizes both factors "
23+
"and replays every sum in the complete finite group"
24+
),
25+
verification_capability_id="finite_abelian_group.exact_factorization.verify",
26+
verification_title="Verify a finite abelian group exact factorization",
27+
verification_description=(
28+
"Independently verify the complete representation histogram, exact "
29+
"factorization decision, and first failure witnesses."
30+
),
31+
verification_tags=(
32+
"verification",
33+
"exact",
34+
"number-theory",
35+
"finite-abelian-group",
36+
"factorization",
37+
"coset-transversal",
38+
),
39+
),
1340
ExactReplayCheckerDeclaration(
1441
"integer.compute.prime_factorization",
1542
FactorizationRequest,

src/jacobian/domains/number_theory/modular.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from jacobian.contracts.number_theory import (
44
ChineseRemainderRequest,
55
ChineseRemainderResult,
6+
FiniteAbelianGroupFactorizationRequest,
7+
FiniteAbelianGroupFactorizationResult,
68
IntegerValueResult,
79
JacobiSymbolRequest,
810
JacobiSymbolResult,
@@ -21,6 +23,7 @@
2123
DISCRETE_LOGARITHM_CAPABILITY,
2224
)
2325
from jacobian.domains.number_theory.operations import (
26+
compute_finite_abelian_group_factorization,
2427
compute_jacobi_symbol,
2528
compute_modular_inverse,
2629
compute_modular_polynomial_residue_image,
@@ -31,6 +34,45 @@
3134
)
3235

3336
MODULAR_CAPABILITIES = (
37+
number_theory_operation(
38+
"finite_abelian_group.exact_factorization.compute",
39+
"Compute a finite abelian group exact factorization",
40+
(
41+
"Normalize two supplied integer-vector factors in a bounded product "
42+
"of cyclic groups and exhaustively decide whether every group element "
43+
"has exactly one representation as a left-plus-right sum."
44+
),
45+
FiniteAbelianGroupFactorizationRequest,
46+
FiniteAbelianGroupFactorizationResult,
47+
compute_finite_abelian_group_factorization,
48+
"number-theory",
49+
"finite-abelian-group",
50+
"exact-factorization",
51+
"coset-transversal",
52+
"unique-representation",
53+
"enumeration",
54+
relation_id="finite_abelian_group.exact_factorization.relation",
55+
invocation_examples=(
56+
example(
57+
"transversal_mod_2_4",
58+
"Verify eight representatives of Z/2 x Z/4.",
59+
{
60+
"moduli": [2, 4],
61+
"left": [
62+
[0, 0],
63+
[3, 0],
64+
[0, 2],
65+
[3, 2],
66+
[1, 1],
67+
[4, 1],
68+
[1, 3],
69+
[4, 3],
70+
],
71+
"right": [[0, 0]],
72+
},
73+
),
74+
),
75+
),
3476
number_theory_operation(
3577
"number_theory.compute.jacobi_symbol",
3678
"Compute Jacobi symbol",

src/jacobian/domains/number_theory/operations.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
FactorialValuationRequest,
2929
FactorialValuationResult,
3030
FactorizationRequest,
31+
FiniteAbelianGroupFactorizationRequest,
32+
FiniteAbelianGroupFactorizationResult,
33+
FiniteAbelianRepresentationCount,
34+
FiniteAbelianRepresentationWitness,
3135
FloorSquareRootRequest,
3236
FloorSquareRootResult,
3337
IntegerPairRequest,
@@ -62,6 +66,7 @@
6266
"compute_euler_totient",
6367
"compute_extended_gcd",
6468
"compute_factorial_valuation",
69+
"compute_finite_abelian_group_factorization",
6570
"compute_floor_square_root",
6671
"compute_gcd",
6772
"compute_jacobi_symbol",
@@ -442,6 +447,77 @@ def compute_modular_polynomial_residue_image(
442447
return _compute_modular_polynomial_residue_image(request, include_table=False)
443448

444449

450+
def compute_finite_abelian_group_factorization(
451+
request: FiniteAbelianGroupFactorizationRequest,
452+
) -> FiniteAbelianGroupFactorizationResult:
453+
"""Exhaustively test unique representation in a product of cyclic groups."""
454+
from collections import Counter
455+
from itertools import product
456+
457+
moduli = request.moduli
458+
459+
def normalize(element: tuple[int, ...]) -> tuple[int, ...]:
460+
return tuple(
461+
coordinate % modulus
462+
for coordinate, modulus in zip(element, moduli, strict=True)
463+
)
464+
465+
left = tuple(normalize(element) for element in request.left)
466+
right = tuple(normalize(element) for element in request.right)
467+
representations: dict[
468+
tuple[int, ...], list[tuple[tuple[int, ...], tuple[int, ...]]]
469+
] = {}
470+
for left_element in left:
471+
for right_element in right:
472+
total = tuple(
473+
(left_coordinate + right_coordinate) % modulus
474+
for left_coordinate, right_coordinate, modulus in zip(
475+
left_element, right_element, moduli, strict=True
476+
)
477+
)
478+
representations.setdefault(total, []).append((left_element, right_element))
479+
group = tuple(product(*(range(modulus) for modulus in moduli)))
480+
histogram = Counter(len(representations.get(element, ())) for element in group)
481+
first_missing = next(
482+
(element for element in group if element not in representations), None
483+
)
484+
duplicate_element = next(
485+
(element for element in group if len(representations.get(element, ())) > 1),
486+
None,
487+
)
488+
duplicate = None
489+
if duplicate_element is not None:
490+
first, second = representations[duplicate_element][:2]
491+
duplicate = FiniteAbelianRepresentationWitness(
492+
element=duplicate_element,
493+
left=first[0],
494+
right=first[1],
495+
other_left=second[0],
496+
other_right=second[1],
497+
)
498+
group_order = math.prod(moduli)
499+
exact = len(left) * len(right) == group_order and histogram == {1: group_order}
500+
return FiniteAbelianGroupFactorizationResult(
501+
semantics_version="finite-abelian-group-factorization.v1",
502+
moduli=moduli,
503+
normalized_left=left,
504+
normalized_right=right,
505+
group_order=group_order,
506+
pair_count=len(left) * len(right),
507+
distinct_sum_count=len(representations),
508+
representation_histogram=tuple(
509+
FiniteAbelianRepresentationCount(
510+
representation_count=count,
511+
element_count=histogram[count],
512+
)
513+
for count in sorted(histogram)
514+
),
515+
is_exact_factorization=exact,
516+
first_missing=None if exact else first_missing,
517+
first_duplicate=None if exact else duplicate,
518+
)
519+
520+
445521
def materialize_modular_polynomial_residue_assignments(
446522
request: ModularPolynomialResidueImageRequest,
447523
) -> ModularPolynomialResidueImageResult:

0 commit comments

Comments
 (0)