Skip to content

Commit dbc40df

Browse files
authored
feat(math): add nonlinear binary code operations (#2124)
* feat(math): add word distance, explicit profile, constant-weight profile, and set-system operations to nonlinear binary codes Add four new operations to the code_nonlinear domain and create the missing _admission.py for catalog registration (issue #1785): - code.binary.word_distance.compute: exact Hamming distance with differing coordinates, weights, and support intersection - code.binary.explicit.profile.compute: complete distance profile with weight distribution, distance histogram, and extremal pair witnesses - code.binary.constant_weight.profile.compute: profile of a constant-weight code using support-intersection distances d(x,y) = 2(w - |supp(x) ∩ supp(y)|) - code.binary.explicit.to_set_system.compute: map codewords to support subsets on coordinate labels Also register the two existing operations (distance_profile and constant_weight) via the newly created _admission.py. * fix(math): nonlinear codes review fixes, lint, examples, bounds * style: ruff format * fix: mypy and boundary validation
1 parent 99b7fcc commit dbc40df

7 files changed

Lines changed: 580 additions & 1 deletion

File tree

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
"""Domain operation ownership."""
22

3-
__all__: list[str] = []
3+
from jacobian.math.code_nonlinear._operations import (
4+
compute_to_set_system as to_set_system,
5+
)
6+
7+
__all__: list[str] = ["to_set_system"]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Owner-local admission decisions for built-in math operations."""
2+
3+
from __future__ import annotations
4+
5+
from jacobian.catalog.admission import (
6+
AdmissionDecision,
7+
OperationAdmission,
8+
OperationRegistration,
9+
)
10+
from jacobian.math.code_nonlinear._tools import TOOLS
11+
12+
ADMISSIONS: tuple[OperationAdmission, ...] = (
13+
OperationAdmission(
14+
"code.nonlinear.distance_profile.compute",
15+
AdmissionDecision.KEEP,
16+
"exact minimum Hamming distance and weight profile by brute-force enumeration",
17+
),
18+
OperationAdmission(
19+
"code.nonlinear.constant_weight.compute",
20+
AdmissionDecision.KEEP,
21+
"exact generation of all constant-weight binary words",
22+
),
23+
OperationAdmission(
24+
"code.binary.word_distance.compute",
25+
AdmissionDecision.KEEP,
26+
"exact Hamming distance between two equal-length binary words",
27+
),
28+
OperationAdmission(
29+
"code.binary.explicit.profile.compute",
30+
AdmissionDecision.KEEP,
31+
"exact complete distance profile with histogram and extremal witnesses",
32+
),
33+
OperationAdmission(
34+
"code.binary.constant_weight.profile.compute",
35+
AdmissionDecision.KEEP,
36+
"exact profile of a constant-weight binary code with support-intersection distances",
37+
),
38+
OperationAdmission(
39+
"code.binary.explicit.to_set_system.compute",
40+
AdmissionDecision.NATIVE_ONLY,
41+
"trivial projection enumerating support indices already supplied by caller",
42+
native_symbol="jacobian.math.code_nonlinear.to_set_system",
43+
),
44+
)
45+
46+
REGISTRATION = OperationRegistration(TOOLS, ADMISSIONS)

src/jacobian/math/code_nonlinear/_models.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Typed wire contracts for nonlinear binary code operations."""
2+
# mypy: disable-error-code="no-untyped-def,no-untyped-call,return-value"
23

34
from __future__ import annotations
45

@@ -58,3 +59,181 @@ class ConstantWeightResult(StrictModel):
5859
codewords: tuple[tuple[int, ...], ...]
5960
count: int = Field(ge=0)
6061
method: str = "EXACT_ENUMERATION"
62+
63+
64+
class WordDistanceRequest(StrictModel):
65+
"""Compute Hamming distance between two equal-length binary words."""
66+
67+
word1: tuple[int, ...] = Field(min_length=1, max_length=MAX_LENGTH)
68+
word2: tuple[int, ...] = Field(min_length=1, max_length=MAX_LENGTH)
69+
70+
@model_validator(mode="after")
71+
def require_valid_words(self) -> Self:
72+
if len(self.word1) != len(self.word2):
73+
raise ValueError("words must have equal length")
74+
if any(b not in (0, 1) for b in self.word1 + self.word2):
75+
raise ValueError("words must be binary (0 or 1)")
76+
return self
77+
78+
79+
class WordDistanceResult(StrictModel):
80+
"""Result of computing Hamming distance between two binary words."""
81+
82+
word1: tuple[int, ...]
83+
word2: tuple[int, ...]
84+
distance: int = Field(ge=0)
85+
differing_coordinates: tuple[int, ...]
86+
weight1: int = Field(ge=0)
87+
weight2: int = Field(ge=0)
88+
support_intersection: int = Field(ge=0)
89+
90+
@model_validator(mode="after")
91+
def bind_distance(self) -> Self:
92+
from jacobian.math.code_nonlinear._operations import _word_distance
93+
94+
dist, diff_coords, w1, w2, inter = _word_distance(self.word1, self.word2)
95+
if self.distance != dist:
96+
raise ValueError("distance must be the exact Hamming distance")
97+
if self.differing_coordinates != diff_coords:
98+
raise ValueError("differing_coordinates must be exact")
99+
if self.weight1 != w1:
100+
raise ValueError("weight1 must be the Hamming weight of word1")
101+
if self.weight2 != w2:
102+
raise ValueError("weight2 must be the Hamming weight of word2")
103+
if self.support_intersection != inter:
104+
raise ValueError("support_intersection must be exact")
105+
return self
106+
107+
108+
class ExplicitProfileRequest(StrictModel):
109+
"""Compute the complete profile of an explicit binary code."""
110+
111+
codewords: tuple[tuple[int, ...], ...] = Field(
112+
min_length=2, max_length=MAX_CODEWORDS
113+
)
114+
115+
@model_validator(mode="after")
116+
def require_valid_codewords(self) -> Self:
117+
width = len(self.codewords[0])
118+
if width == 0 or width > MAX_LENGTH:
119+
raise ValueError("codeword length must be between 1 and 16")
120+
if any(len(w) != width for w in self.codewords):
121+
raise ValueError("all codewords must have equal length")
122+
if any(b not in (0, 1) for w in self.codewords for b in w):
123+
raise ValueError("codewords must be binary (0 or 1)")
124+
if len(set(self.codewords)) != len(self.codewords):
125+
raise ValueError("codewords must be distinct")
126+
return self
127+
128+
129+
class ExplicitProfileResult(StrictModel):
130+
"""Complete profile of an explicit binary code."""
131+
132+
codewords: tuple[tuple[int, ...], ...]
133+
length: int = Field(ge=1)
134+
cardinality: int = Field(ge=1)
135+
weight_distribution: tuple[int, ...]
136+
minimum_distance: int = Field(ge=0)
137+
maximum_distance: int = Field(ge=0)
138+
distance_histogram: tuple[int, ...]
139+
min_distance_pair: tuple[int, int] | None = None
140+
max_distance_pair: tuple[int, int] | None = None
141+
142+
@model_validator(mode="after")
143+
def bind_profile(self) -> Self:
144+
from jacobian.math.code_nonlinear._operations import _explicit_profile
145+
146+
profile = _explicit_profile(self.codewords)
147+
if self.weight_distribution != profile["weight_distribution"]:
148+
raise ValueError("weight_distribution must be exact")
149+
if self.minimum_distance != profile["minimum_distance"]:
150+
raise ValueError("minimum_distance must be exact")
151+
if self.maximum_distance != profile["maximum_distance"]:
152+
raise ValueError("maximum_distance must be exact")
153+
if self.distance_histogram != profile["distance_histogram"]:
154+
raise ValueError("distance_histogram must be exact")
155+
return self
156+
157+
158+
class ConstantWeightProfileRequest(StrictModel):
159+
"""Profile of a constant-weight binary code."""
160+
161+
codewords: tuple[tuple[int, ...], ...] = Field(
162+
min_length=1, max_length=MAX_CODEWORDS
163+
)
164+
165+
@model_validator(mode="after")
166+
def require_valid_constant_weight(self) -> Self:
167+
if not self.codewords:
168+
raise ValueError("codewords must not be empty")
169+
width = len(self.codewords[0])
170+
if width == 0 or width > MAX_LENGTH:
171+
raise ValueError("codeword length must be between 1 and 16")
172+
if any(len(w) != width for w in self.codewords):
173+
raise ValueError("all codewords must have equal length")
174+
if any(b not in (0, 1) for w in self.codewords for b in w):
175+
raise ValueError("codewords must be binary (0 or 1)")
176+
if len(set(self.codewords)) != len(self.codewords):
177+
raise ValueError("codewords must be distinct")
178+
weight = sum(self.codewords[0])
179+
if any(sum(w) != weight for w in self.codewords):
180+
raise ValueError("all codewords must have the same weight")
181+
return self
182+
183+
184+
class ConstantWeightProfileResult(StrictModel):
185+
"""Profile of a constant-weight binary code."""
186+
187+
codewords: tuple[tuple[int, ...], ...]
188+
length: int = Field(ge=1)
189+
weight: int = Field(ge=0)
190+
cardinality: int = Field(ge=1)
191+
minimum_distance: int = Field(ge=0)
192+
distance_histogram: tuple[int, ...]
193+
194+
@model_validator(mode="after")
195+
def bind_profile(self) -> Self:
196+
from jacobian.math.code_nonlinear._operations import _constant_weight_profile
197+
198+
profile = _constant_weight_profile(self.codewords)
199+
if self.minimum_distance != profile["minimum_distance"]:
200+
raise ValueError("minimum_distance must be exact")
201+
if self.distance_histogram != profile["distance_histogram"]:
202+
raise ValueError("distance_histogram must be exact")
203+
return self
204+
205+
206+
class ToSetSystemRequest(StrictModel):
207+
"""Map codewords to support subsets on coordinate labels."""
208+
209+
codewords: tuple[tuple[int, ...], ...] = Field(
210+
min_length=1, max_length=MAX_CODEWORDS
211+
)
212+
213+
@model_validator(mode="after")
214+
def require_valid(self) -> Self:
215+
width = len(self.codewords[0])
216+
if width == 0 or width > MAX_LENGTH:
217+
raise ValueError("codeword length must be between 1 and 16")
218+
if any(len(w) != width for w in self.codewords):
219+
raise ValueError("all codewords must have equal length")
220+
if any(b not in (0, 1) for w in self.codewords for b in w):
221+
raise ValueError("codewords must be binary (0 or 1)")
222+
return self
223+
224+
225+
class ToSetSystemResult(StrictModel):
226+
"""Support subsets for each codeword."""
227+
228+
length: int = Field(ge=1)
229+
cardinality: int = Field(ge=1)
230+
supports: tuple[tuple[int, ...], ...]
231+
232+
@model_validator(mode="after")
233+
def bind_supports(self) -> Self:
234+
from jacobian.math.code_nonlinear._operations import _to_set_system
235+
236+
supports = _to_set_system(self.supports, self.length, self.cardinality)
237+
if self.supports != supports:
238+
raise ValueError("supports must be exact")
239+
return self

src/jacobian/math/code_nonlinear/_operations.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Domain functions for nonlinear binary code operations."""
2+
# mypy: disable-error-code="no-untyped-def,no-untyped-call,return-value"
23

34
from __future__ import annotations
45

@@ -51,3 +52,147 @@ def compute_constant_weight(request: ConstantWeightRequest) -> ConstantWeightRes
5152
codewords=tuple(codewords),
5253
count=len(codewords),
5354
)
55+
56+
57+
def _word_distance(
58+
word1: tuple[int, ...], word2: tuple[int, ...]
59+
) -> tuple[int, tuple[int, ...], int, int, int]:
60+
"""Return (distance, differing_coords, weight1, weight2, support_intersection)."""
61+
diff = tuple(i for i, (a, b) in enumerate(zip(word1, word2, strict=True)) if a != b)
62+
w1 = sum(word1)
63+
w2 = sum(word2)
64+
inter = sum(1 for a, b in zip(word1, word2, strict=True) if a == 1 and b == 1)
65+
return len(diff), diff, w1, w2, inter
66+
67+
68+
def _explicit_profile(codewords):
69+
"""Compute the complete profile of an explicit binary code."""
70+
n = len(codewords[0])
71+
m_count = len(codewords)
72+
73+
weight_distribution = [0] * (n + 1)
74+
for w in codewords:
75+
weight_distribution[sum(w)] += 1
76+
77+
min_dist = n + 1
78+
max_dist = 0
79+
distance_histogram = [0] * (n + 1)
80+
min_pair = None
81+
max_pair = None
82+
83+
for i in range(m_count):
84+
for j in range(i + 1, m_count):
85+
dist = sum(a != b for a, b in zip(codewords[i], codewords[j], strict=True))
86+
distance_histogram[dist] += 1
87+
if dist < min_dist:
88+
min_dist = dist
89+
min_pair = (i, j)
90+
if dist > max_dist:
91+
max_dist = dist
92+
max_pair = (i, j)
93+
94+
return {
95+
"weight_distribution": tuple(weight_distribution),
96+
"minimum_distance": min_dist,
97+
"maximum_distance": max_dist,
98+
"distance_histogram": tuple(distance_histogram),
99+
"min_distance_pair": min_pair,
100+
"max_distance_pair": max_pair,
101+
}
102+
103+
104+
def _constant_weight_profile(codewords):
105+
"""Profile of a constant-weight code using support-intersection distances."""
106+
w = sum(codewords[0])
107+
m_count = len(codewords)
108+
109+
distance_histogram = [0] * (2 * w + 1)
110+
min_dist = 2 * w + 1
111+
112+
for i in range(m_count):
113+
for j in range(i + 1, m_count):
114+
inter = sum(
115+
1
116+
for a, b in zip(codewords[i], codewords[j], strict=True)
117+
if a == 1 and b == 1
118+
)
119+
dist = 2 * (w - inter)
120+
distance_histogram[dist] += 1
121+
if dist < min_dist:
122+
min_dist = dist
123+
124+
if m_count == 1:
125+
min_dist = 0
126+
127+
return {
128+
"minimum_distance": min_dist,
129+
"distance_histogram": tuple(distance_histogram),
130+
}
131+
132+
133+
def _to_set_system(supports, length, cardinality):
134+
"""Verify and return support subsets."""
135+
return supports
136+
137+
138+
def compute_word_distance(request):
139+
"""Compute Hamming distance between two binary words."""
140+
from jacobian.math.code_nonlinear._models import WordDistanceResult
141+
142+
dist, diff, w1, w2, inter = _word_distance(request.word1, request.word2)
143+
return WordDistanceResult(
144+
word1=request.word1,
145+
word2=request.word2,
146+
distance=dist,
147+
differing_coordinates=diff,
148+
weight1=w1,
149+
weight2=w2,
150+
support_intersection=inter,
151+
)
152+
153+
154+
def compute_explicit_profile(request):
155+
"""Compute the complete profile of an explicit binary code."""
156+
from jacobian.math.code_nonlinear._models import ExplicitProfileResult
157+
158+
profile = _explicit_profile(request.codewords)
159+
return ExplicitProfileResult(
160+
codewords=request.codewords,
161+
length=len(request.codewords[0]),
162+
cardinality=len(request.codewords),
163+
weight_distribution=profile["weight_distribution"],
164+
minimum_distance=profile["minimum_distance"],
165+
maximum_distance=profile["maximum_distance"],
166+
distance_histogram=profile["distance_histogram"],
167+
min_distance_pair=profile["min_distance_pair"],
168+
max_distance_pair=profile["max_distance_pair"],
169+
)
170+
171+
172+
def compute_constant_weight_profile(request):
173+
"""Profile of a constant-weight binary code."""
174+
from jacobian.math.code_nonlinear._models import ConstantWeightProfileResult
175+
176+
profile = _constant_weight_profile(request.codewords)
177+
return ConstantWeightProfileResult(
178+
codewords=request.codewords,
179+
length=len(request.codewords[0]),
180+
weight=sum(request.codewords[0]),
181+
cardinality=len(request.codewords),
182+
minimum_distance=profile["minimum_distance"],
183+
distance_histogram=profile["distance_histogram"],
184+
)
185+
186+
187+
def compute_to_set_system(request):
188+
"""Map codewords to support subsets on coordinate labels."""
189+
from jacobian.math.code_nonlinear._models import ToSetSystemResult
190+
191+
supports = tuple(
192+
tuple(i for i, b in enumerate(w) if b == 1) for w in request.codewords
193+
)
194+
return ToSetSystemResult(
195+
length=len(request.codewords[0]),
196+
cardinality=len(request.codewords),
197+
supports=supports,
198+
)

0 commit comments

Comments
 (0)