se-interpretive-kernel 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.
- se_interpretive_kernel/__init__.py +7 -0
- se_interpretive_kernel/_version.py +24 -0
- se_interpretive_kernel/artifact.py +76 -0
- se_interpretive_kernel/cases.py +120 -0
- se_interpretive_kernel/check.py +293 -0
- se_interpretive_kernel/cli.py +5 -0
- se_interpretive_kernel/demand.py +66 -0
- se_interpretive_kernel/differences.py +69 -0
- se_interpretive_kernel/explain.py +224 -0
- se_interpretive_kernel/generated.py +100 -0
- se_interpretive_kernel/kernel.py +52 -0
- se_interpretive_kernel/lifting.py +374 -0
- se_interpretive_kernel/literal.py +87 -0
- se_interpretive_kernel/loader.py +238 -0
- se_interpretive_kernel/partition.py +363 -0
- se_interpretive_kernel/probes.py +124 -0
- se_interpretive_kernel/sampling.py +36 -0
- se_interpretive_kernel/witness.py +69 -0
- se_interpretive_kernel-0.1.0.dist-info/METADATA +261 -0
- se_interpretive_kernel-0.1.0.dist-info/RECORD +23 -0
- se_interpretive_kernel-0.1.0.dist-info/WHEEL +4 -0
- se_interpretive_kernel-0.1.0.dist-info/entry_points.txt +2 -0
- se_interpretive_kernel-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""SE Interpretive Kernel (SE-220).
|
|
2
|
+
|
|
3
|
+
A companion to the paper, providing executable finite constructions and checks
|
|
4
|
+
for the interpretation-induced kernel, the generated occurrence regime, their
|
|
5
|
+
adjunction, interpretive thresholds, pairwise demand, and incompatibility
|
|
6
|
+
witnesses.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""artifact.py - Finite comparison artifact for the Interpretive Kernel.
|
|
2
|
+
|
|
3
|
+
An :class:`Artifact` holds the data the theory starts from:
|
|
4
|
+
|
|
5
|
+
- a finite carrier of frames (the paper's E),
|
|
6
|
+
- a finite carrier of outputs (the paper's A),
|
|
7
|
+
- a finite set of probes (the paper's P), and
|
|
8
|
+
- a total interpretation table I : P x E -> A.
|
|
9
|
+
|
|
10
|
+
It holds no equivalence relations. Frame regimes and output regimes are
|
|
11
|
+
:class:`~se_interpretive_kernel.partition.Partition` values supplied
|
|
12
|
+
separately, over ``frames`` and ``outputs`` in the order stored here.
|
|
13
|
+
|
|
14
|
+
Nothing here decides which probes, frames, or outputs a domain ought to
|
|
15
|
+
declare, and nothing here shows that an interpretation is correct.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from collections.abc import Mapping, Sequence
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
|
|
23
|
+
from se_interpretive_kernel.partition import Item
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class Artifact:
|
|
28
|
+
"""Contain the finite data of one comparison.
|
|
29
|
+
|
|
30
|
+
``table`` maps ``(probe, frame)`` to an output. It must be defined for
|
|
31
|
+
every probe and frame, and every value must be an output. Totality is
|
|
32
|
+
required by the theory: a missing or inapplicable result must be
|
|
33
|
+
represented by a declared output value.
|
|
34
|
+
|
|
35
|
+
``label`` is a human-readable name for diagnostics.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
frames: tuple[Item, ...]
|
|
39
|
+
outputs: tuple[Item, ...]
|
|
40
|
+
probes: tuple[Item, ...]
|
|
41
|
+
table: Mapping[tuple[Item, Item], Item]
|
|
42
|
+
label: str = ""
|
|
43
|
+
|
|
44
|
+
def __post_init__(self) -> None:
|
|
45
|
+
"""Reject malformed data: duplicates, missing entries, unknown outputs."""
|
|
46
|
+
if len(set(self.frames)) != len(self.frames):
|
|
47
|
+
msg = "frames must be distinct"
|
|
48
|
+
raise ValueError(msg)
|
|
49
|
+
if len(set(self.outputs)) != len(self.outputs):
|
|
50
|
+
msg = "outputs must be distinct"
|
|
51
|
+
raise ValueError(msg)
|
|
52
|
+
if len(set(self.probes)) != len(self.probes):
|
|
53
|
+
msg = "probes must be distinct"
|
|
54
|
+
raise ValueError(msg)
|
|
55
|
+
output_set = set(self.outputs)
|
|
56
|
+
for probe in self.probes:
|
|
57
|
+
for frame in self.frames:
|
|
58
|
+
key = (probe, frame)
|
|
59
|
+
if key not in self.table:
|
|
60
|
+
msg = f"interpretation is not total: missing {key!r}"
|
|
61
|
+
raise ValueError(msg)
|
|
62
|
+
if self.table[key] not in output_set:
|
|
63
|
+
msg = f"interpretation value {self.table[key]!r} is not an output"
|
|
64
|
+
raise ValueError(msg)
|
|
65
|
+
|
|
66
|
+
def interpret(self, probe: Item, frame: Item) -> Item:
|
|
67
|
+
"""Return I(probe, frame)."""
|
|
68
|
+
return self.table[(probe, frame)]
|
|
69
|
+
|
|
70
|
+
def require_selected(self, selected: Sequence[Item]) -> None:
|
|
71
|
+
"""Reject a selection that names a probe the artifact does not have."""
|
|
72
|
+
probe_set = set(self.probes)
|
|
73
|
+
for probe in selected:
|
|
74
|
+
if probe not in probe_set:
|
|
75
|
+
msg = f"selected probe {probe!r} is not a probe of this artifact"
|
|
76
|
+
raise ValueError(msg)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""cases.py - Finite cases mirroring the Lean examples.
|
|
2
|
+
|
|
3
|
+
Each function returns an :class:`Artifact`, using the same carriers and
|
|
4
|
+
interpretation as the corresponding Lean example in
|
|
5
|
+
``SETest/InterpretiveKernel``. The assertions about these cases live in
|
|
6
|
+
``check.py``.
|
|
7
|
+
|
|
8
|
+
Lean names are given in each docstring. Carriers of size 2 or 3 use integers;
|
|
9
|
+
Booleans are written 0 and 1.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
|
|
16
|
+
from .artifact import Artifact
|
|
17
|
+
from .partition import Item, Partition
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _table(frames, probes, rule):
|
|
21
|
+
"""Return the table {(p, f): rule(p, f)}."""
|
|
22
|
+
return {(p, f): rule(p, f) for p in probes for f in frames}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def example_a() -> Artifact:
|
|
26
|
+
"""Return Example A: four frames, parity regime, XOR interpretation."""
|
|
27
|
+
frames, probes, outputs = (0, 1, 2, 3), (0, 1), (0, 1)
|
|
28
|
+
return Artifact(
|
|
29
|
+
frames, outputs, probes, _table(frames, probes, lambda p, f: p ^ (f % 2)), "A"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def example_b() -> Artifact:
|
|
34
|
+
"""Return Example B: identity interpretation with a single probe."""
|
|
35
|
+
frames, probes, outputs = (0, 1), ("p",), (0, 1)
|
|
36
|
+
return Artifact(
|
|
37
|
+
frames, outputs, probes, _table(frames, probes, lambda _p, f: f), "B"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def example_c() -> Artifact:
|
|
42
|
+
"""Return Example C: the interpretation only records whether the frame is 0."""
|
|
43
|
+
frames, probes, outputs = (0, 1, 2), (0, 1), (0, 1)
|
|
44
|
+
return Artifact(
|
|
45
|
+
frames, outputs, probes, _table(frames, probes, lambda _p, f: int(f == 0)), "C"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def example_d() -> Artifact:
|
|
50
|
+
"""Return Example D: I(p, f) = p and f, so probe 0 erases the frame."""
|
|
51
|
+
frames, probes, outputs = (0, 1), (0, 1), (0, 1)
|
|
52
|
+
return Artifact(
|
|
53
|
+
frames, outputs, probes, _table(frames, probes, lambda p, f: p & f), "D"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def example_e() -> Artifact:
|
|
58
|
+
"""Return Example E: two probes whose images chain 0~1 and 1~2."""
|
|
59
|
+
frames, probes, outputs = (0, 1), (0, 1), (0, 1, 2)
|
|
60
|
+
|
|
61
|
+
def rule(p: int, f: int) -> int:
|
|
62
|
+
return (2 if f else 1) if p else (1 if f else 0)
|
|
63
|
+
|
|
64
|
+
return Artifact(frames, outputs, probes, _table(frames, probes, rule), "E")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def example_f() -> Artifact:
|
|
68
|
+
"""Return Example F: one frame, constant output, so the counit is strict."""
|
|
69
|
+
frames, probes, outputs = (0,), ("p",), (0, 1)
|
|
70
|
+
return Artifact(
|
|
71
|
+
frames, outputs, probes, _table(frames, probes, lambda _p, _f: 0), "F"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def example_g() -> Artifact:
|
|
76
|
+
"""Return Example G: K need not preserve joins."""
|
|
77
|
+
frames, probes, outputs = (0, 1), ("p",), (0, 1, 2)
|
|
78
|
+
return Artifact(
|
|
79
|
+
frames,
|
|
80
|
+
outputs,
|
|
81
|
+
probes,
|
|
82
|
+
_table(frames, probes, lambda _p, f: 0 if f == 0 else 2),
|
|
83
|
+
"G",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def example_h() -> Artifact:
|
|
88
|
+
"""Return Example H: L need not preserve meets."""
|
|
89
|
+
frames, probes, outputs = (0, 1, 2), ("p",), (0, 1)
|
|
90
|
+
return Artifact(
|
|
91
|
+
frames, outputs, probes, _table(frames, probes, lambda _p, f: int(f != 0)), "H"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def example_i() -> Artifact:
|
|
96
|
+
"""Return Example I: identity interpretation, incomparable thresholds."""
|
|
97
|
+
frames, probes, outputs = (0, 1, 2), ("p",), (0, 1, 2)
|
|
98
|
+
return Artifact(
|
|
99
|
+
frames, outputs, probes, _table(frames, probes, lambda _p, f: f), "I"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def merge(carrier: Sequence[Item], a: Item, b: Item) -> Partition:
|
|
104
|
+
"""Return the partition of ``carrier`` that merges only ``a`` and ``b``."""
|
|
105
|
+
return Partition.from_pairs(carrier, [(a, b)])
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def all_cases() -> list[Artifact]:
|
|
109
|
+
"""Return every example artifact."""
|
|
110
|
+
return [
|
|
111
|
+
example_a(),
|
|
112
|
+
example_b(),
|
|
113
|
+
example_c(),
|
|
114
|
+
example_d(),
|
|
115
|
+
example_e(),
|
|
116
|
+
example_f(),
|
|
117
|
+
example_g(),
|
|
118
|
+
example_h(),
|
|
119
|
+
example_i(),
|
|
120
|
+
]
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""check.py - Exhaustive and randomized checks of the Interpretive Kernel laws.
|
|
2
|
+
|
|
3
|
+
Replaces the SE-210 check. Verifies, on finite artifacts, the statements that
|
|
4
|
+
the Lean development proves in general:
|
|
5
|
+
|
|
6
|
+
- the Galois adjunction L_Q(phi) <= rho iff phi <= K_Q(rho),
|
|
7
|
+
- unit, counit, and the identities L K L = L and K L K = K,
|
|
8
|
+
- monotonicity in the regimes and in the probe family,
|
|
9
|
+
- K preserves meets and L preserves joins,
|
|
10
|
+
- probe-union duality and the empty family,
|
|
11
|
+
- generating-set reduction,
|
|
12
|
+
- agreement of the partition-based code with the literal pair-set oracle,
|
|
13
|
+
- witnesses, thresholds, and pairwise demand,
|
|
14
|
+
- the specific phenomena of Examples A to I.
|
|
15
|
+
|
|
16
|
+
For each artifact, every selected family Q, every phi on the frames, and every
|
|
17
|
+
rho on the outputs is enumerated. This is evidence for small carriers, not a
|
|
18
|
+
proof. The proofs are in the Lean library.
|
|
19
|
+
|
|
20
|
+
Usage::
|
|
21
|
+
|
|
22
|
+
python -m se_interpretive_kernel.check [--random N] [--seed S]
|
|
23
|
+
|
|
24
|
+
Exits nonzero if any check fails.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
from collections.abc import Sequence
|
|
31
|
+
from itertools import combinations
|
|
32
|
+
import sys
|
|
33
|
+
|
|
34
|
+
from .artifact import Artifact
|
|
35
|
+
from .cases import (
|
|
36
|
+
all_cases,
|
|
37
|
+
example_a,
|
|
38
|
+
example_b,
|
|
39
|
+
example_c,
|
|
40
|
+
example_d,
|
|
41
|
+
example_e,
|
|
42
|
+
example_f,
|
|
43
|
+
example_g,
|
|
44
|
+
example_h,
|
|
45
|
+
example_i,
|
|
46
|
+
merge,
|
|
47
|
+
)
|
|
48
|
+
from .demand import demand, kernel_from_demand, ultrametric_violations
|
|
49
|
+
from .generated import generated, generated_all_pairs, threshold
|
|
50
|
+
from .kernel import coherent, kernel
|
|
51
|
+
from .literal import generated_pairs, generator_pairs, kernel_pairs, pairs_of
|
|
52
|
+
from .partition import Item, Partition, all_partitions
|
|
53
|
+
from .sampling import random_artifacts
|
|
54
|
+
from .witness import find_witness, witness_is_valid
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def probe_families(artifact: Artifact) -> list[tuple[Item, ...]]:
|
|
58
|
+
"""Return every subset of the probes, smallest first, in probe order."""
|
|
59
|
+
subsets: list[tuple[Item, ...]] = []
|
|
60
|
+
for size in range(len(artifact.probes) + 1):
|
|
61
|
+
subsets.extend(combinations(artifact.probes, size))
|
|
62
|
+
return subsets
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def check_artifact(artifact: Artifact) -> list[str]:
|
|
66
|
+
"""Return a description of every law violated on this artifact."""
|
|
67
|
+
failures: list[str] = []
|
|
68
|
+
name = artifact.label or "artifact"
|
|
69
|
+
|
|
70
|
+
def fail(law: str, detail: str = "") -> None:
|
|
71
|
+
failures.append(f"{name}: {law} {detail}".rstrip())
|
|
72
|
+
|
|
73
|
+
frame_regimes = list(all_partitions(artifact.frames))
|
|
74
|
+
output_regimes = list(all_partitions(artifact.outputs))
|
|
75
|
+
frame_top = Partition.indiscrete(artifact.frames)
|
|
76
|
+
output_bottom = Partition.discrete(artifact.outputs)
|
|
77
|
+
output_top = Partition.indiscrete(artifact.outputs)
|
|
78
|
+
frame_bottom = Partition.discrete(artifact.frames)
|
|
79
|
+
families = probe_families(artifact)
|
|
80
|
+
|
|
81
|
+
for q in families:
|
|
82
|
+
# Totals and bottoms.
|
|
83
|
+
if kernel(artifact, q, output_top) != frame_top:
|
|
84
|
+
fail("K(total) = total", f"Q={q}")
|
|
85
|
+
if generated(artifact, q, frame_bottom) != output_bottom:
|
|
86
|
+
fail("L(discrete) = discrete", f"Q={q}")
|
|
87
|
+
|
|
88
|
+
for phi in frame_regimes:
|
|
89
|
+
lift = generated(artifact, q, phi)
|
|
90
|
+
if lift != generated_all_pairs(artifact, q, phi):
|
|
91
|
+
fail("generating-set reduction", f"Q={q} phi={phi.labels}")
|
|
92
|
+
if pairs_of(lift) != generated_pairs(artifact, q, pairs_of(phi)):
|
|
93
|
+
fail("L matches literal oracle", f"Q={q} phi={phi.labels}")
|
|
94
|
+
if not phi.refines(kernel(artifact, q, lift)):
|
|
95
|
+
fail("unit", f"Q={q} phi={phi.labels}")
|
|
96
|
+
if generated(artifact, q, kernel(artifact, q, lift)) != lift:
|
|
97
|
+
fail("L K L = L", f"Q={q} phi={phi.labels}")
|
|
98
|
+
if threshold(artifact, q, phi) != lift:
|
|
99
|
+
fail("threshold = L", f"Q={q} phi={phi.labels}")
|
|
100
|
+
|
|
101
|
+
for rho in output_regimes:
|
|
102
|
+
ker = kernel(artifact, q, rho)
|
|
103
|
+
if pairs_of(ker) != kernel_pairs(artifact, q, pairs_of(rho)):
|
|
104
|
+
fail("K matches literal oracle", f"Q={q} rho={rho.labels}")
|
|
105
|
+
if not generated(artifact, q, ker).refines(rho):
|
|
106
|
+
fail("counit", f"Q={q} rho={rho.labels}")
|
|
107
|
+
if kernel(artifact, q, generated(artifact, q, ker)) != ker:
|
|
108
|
+
fail("K L K = K", f"Q={q} rho={rho.labels}")
|
|
109
|
+
if kernel_from_demand(artifact, q, rho) != ker:
|
|
110
|
+
fail("kernel via demand", f"Q={q} rho={rho.labels}")
|
|
111
|
+
|
|
112
|
+
for x in artifact.frames:
|
|
113
|
+
for y in artifact.frames:
|
|
114
|
+
if ker.same(x, y) != demand(artifact, q, x, y).refines(rho):
|
|
115
|
+
fail("K iff demand refines rho", f"Q={q} rho={rho.labels}")
|
|
116
|
+
|
|
117
|
+
for phi in frame_regimes:
|
|
118
|
+
adjoint_left = generated(artifact, q, phi).refines(rho)
|
|
119
|
+
adjoint_right = phi.refines(ker)
|
|
120
|
+
if adjoint_left != adjoint_right:
|
|
121
|
+
fail("adjunction", f"Q={q} phi={phi.labels} rho={rho.labels}")
|
|
122
|
+
witness = find_witness(artifact, q, phi, rho)
|
|
123
|
+
if adjoint_right != (witness is None):
|
|
124
|
+
fail(
|
|
125
|
+
"witness iff incoherent",
|
|
126
|
+
f"Q={q} phi={phi.labels} rho={rho.labels}",
|
|
127
|
+
)
|
|
128
|
+
if witness is not None and not witness_is_valid(
|
|
129
|
+
artifact, q, phi, rho, witness
|
|
130
|
+
):
|
|
131
|
+
fail("witness is valid", f"Q={q} phi={phi.labels} rho={rho.labels}")
|
|
132
|
+
if coherent(artifact, q, phi, rho) != adjoint_right:
|
|
133
|
+
fail("coherent agrees with refinement")
|
|
134
|
+
|
|
135
|
+
# Monotonicity and preservation.
|
|
136
|
+
for r1 in output_regimes:
|
|
137
|
+
for r2 in output_regimes:
|
|
138
|
+
k1, k2 = kernel(artifact, q, r1), kernel(artifact, q, r2)
|
|
139
|
+
if r1.refines(r2) and not k1.refines(k2):
|
|
140
|
+
fail("K monotone")
|
|
141
|
+
if kernel(artifact, q, r1.meet(r2)) != k1.meet(k2):
|
|
142
|
+
fail("K preserves meets", f"Q={q}")
|
|
143
|
+
for p1 in frame_regimes:
|
|
144
|
+
for p2 in frame_regimes:
|
|
145
|
+
l1, l2 = generated(artifact, q, p1), generated(artifact, q, p2)
|
|
146
|
+
if p1.refines(p2) and not l1.refines(l2):
|
|
147
|
+
fail("L monotone")
|
|
148
|
+
if generated(artifact, q, p1.join(p2)) != l1.join(l2):
|
|
149
|
+
fail("L preserves joins", f"Q={q}")
|
|
150
|
+
|
|
151
|
+
# Probe-family laws.
|
|
152
|
+
for q1 in families:
|
|
153
|
+
for q2 in families:
|
|
154
|
+
union = tuple(p for p in artifact.probes if p in q1 or p in q2)
|
|
155
|
+
if set(q1) <= set(q2):
|
|
156
|
+
for rho in output_regimes:
|
|
157
|
+
if not kernel(artifact, q2, rho).refines(kernel(artifact, q1, rho)):
|
|
158
|
+
fail("more probes refine K", f"{q1} <= {q2}")
|
|
159
|
+
for phi in frame_regimes:
|
|
160
|
+
if not generated(artifact, q1, phi).refines(
|
|
161
|
+
generated(artifact, q2, phi)
|
|
162
|
+
):
|
|
163
|
+
fail("more probes enlarge L", f"{q1} <= {q2}")
|
|
164
|
+
for rho in output_regimes:
|
|
165
|
+
if kernel(artifact, union, rho) != kernel(artifact, q1, rho).meet(
|
|
166
|
+
kernel(artifact, q2, rho)
|
|
167
|
+
):
|
|
168
|
+
fail("K probe-union duality", f"{q1} {q2}")
|
|
169
|
+
for phi in frame_regimes:
|
|
170
|
+
if generated(artifact, union, phi) != generated(artifact, q1, phi).join(
|
|
171
|
+
generated(artifact, q2, phi)
|
|
172
|
+
):
|
|
173
|
+
fail("L probe-union duality", f"{q1} {q2}")
|
|
174
|
+
|
|
175
|
+
for q in families:
|
|
176
|
+
for violation in ultrametric_violations(artifact, q):
|
|
177
|
+
fail("ultrametric", f"Q={q} {violation}")
|
|
178
|
+
return failures
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def check_cases() -> list[str]:
|
|
182
|
+
"""Return failures of the specific phenomena of Examples A to I."""
|
|
183
|
+
failures: list[str] = []
|
|
184
|
+
|
|
185
|
+
def expect(condition: bool, message: str) -> None:
|
|
186
|
+
if not condition:
|
|
187
|
+
failures.append(f"case: {message}")
|
|
188
|
+
|
|
189
|
+
# A: parity regime is coherent with equality on the outputs.
|
|
190
|
+
a = example_a()
|
|
191
|
+
parity = Partition.from_labels(a.frames, {0: 0, 1: 1, 2: 0, 3: 1})
|
|
192
|
+
eq_out = Partition.discrete(a.outputs)
|
|
193
|
+
expect(coherent(a, a.probes, parity, eq_out), "A coherent")
|
|
194
|
+
expect(generated(a, a.probes, parity).refines(eq_out), "A lift finer than equality")
|
|
195
|
+
|
|
196
|
+
# B: the total frame regime is incoherent with equality; the witness is valid.
|
|
197
|
+
b = example_b()
|
|
198
|
+
b_total, b_eq = Partition.indiscrete(b.frames), Partition.discrete(b.outputs)
|
|
199
|
+
expect(not coherent(b, b.probes, b_total, b_eq), "B incoherent")
|
|
200
|
+
w = find_witness(b, b.probes, b_total, b_eq)
|
|
201
|
+
expect(
|
|
202
|
+
w is not None and witness_is_valid(b, b.probes, b_total, b_eq, w), "B witness"
|
|
203
|
+
)
|
|
204
|
+
expect(generated(b, b.probes, b_total).same(0, 1), "B lift relates 0 and 1")
|
|
205
|
+
|
|
206
|
+
# C: the kernel separates 0 from 1 and relates 1 and 2; the unit is strict.
|
|
207
|
+
c = example_c()
|
|
208
|
+
c_eq = Partition.discrete(c.outputs)
|
|
209
|
+
ker_c = kernel(c, c.probes, c_eq)
|
|
210
|
+
expect(ker_c.same(1, 2) and not ker_c.same(0, 1), "C kernel collapses 1 and 2")
|
|
211
|
+
c_frames_eq = Partition.discrete(c.frames)
|
|
212
|
+
expect(
|
|
213
|
+
c_frames_eq != kernel(c, c.probes, generated(c, c.probes, c_frames_eq)),
|
|
214
|
+
"C unit strict",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# D: more probes refine the kernel and enlarge the lift, both strictly.
|
|
218
|
+
d = example_d()
|
|
219
|
+
d_eq, d_total = Partition.discrete(d.outputs), Partition.indiscrete(d.frames)
|
|
220
|
+
expect(kernel(d, (0,), d_eq).same(0, 1), "D Q0 does not separate")
|
|
221
|
+
expect(not kernel(d, (0, 1), d_eq).same(0, 1), "D Q1 separates")
|
|
222
|
+
expect(coherent(d, (0,), d_total, d_eq), "D coherent at Q0")
|
|
223
|
+
expect(not coherent(d, (0, 1), d_total, d_eq), "D incoherent at Q1")
|
|
224
|
+
expect(not generated(d, (0,), d_total).same(0, 1), "D L_Q0 stays discrete")
|
|
225
|
+
expect(generated(d, (0, 1), d_total).same(0, 1), "D L_Q1 relates 0 and 1")
|
|
226
|
+
|
|
227
|
+
# E: the generating pairs are not transitive; the closure relates 0 and 2.
|
|
228
|
+
e = example_e()
|
|
229
|
+
e_total = Partition.indiscrete(e.frames)
|
|
230
|
+
expect(generated(e, e.probes, e_total).same(0, 2), "E closure relates 0 and 2")
|
|
231
|
+
expect(
|
|
232
|
+
(0, 2) not in generator_pairs(e, e.probes, pairs_of(e_total)),
|
|
233
|
+
"E generators skip (0, 2)",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# F: the counit is strict.
|
|
237
|
+
f = example_f()
|
|
238
|
+
f_total = Partition.indiscrete(f.outputs)
|
|
239
|
+
f_lk = generated(f, f.probes, kernel(f, f.probes, f_total))
|
|
240
|
+
expect(f_lk != f_total and f_lk.refines(f_total), "F counit strict")
|
|
241
|
+
|
|
242
|
+
# G: K does not preserve joins.
|
|
243
|
+
g = example_g()
|
|
244
|
+
rho, sigma = merge(g.outputs, 0, 1), merge(g.outputs, 1, 2)
|
|
245
|
+
kg = lambda r: kernel(g, g.probes, r)
|
|
246
|
+
expect(
|
|
247
|
+
kg(rho.join(sigma)) != kg(rho).join(kg(sigma)), "G K does not preserve joins"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# H: L does not preserve meets.
|
|
251
|
+
h = example_h()
|
|
252
|
+
phi1, phi2 = merge(h.frames, 0, 1), merge(h.frames, 0, 2)
|
|
253
|
+
lh = lambda p: generated(h, h.probes, p)
|
|
254
|
+
expect(
|
|
255
|
+
lh(phi1.meet(phi2)) != lh(phi1).meet(lh(phi2)), "H L does not preserve meets"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# I: thresholds are incomparable.
|
|
259
|
+
i = example_i()
|
|
260
|
+
t01 = threshold(i, i.probes, merge(i.frames, 0, 1))
|
|
261
|
+
t12 = threshold(i, i.probes, merge(i.frames, 1, 2))
|
|
262
|
+
expect(not t01.refines(t12) and not t12.refines(t01), "I thresholds incomparable")
|
|
263
|
+
|
|
264
|
+
# J: without equivalence the adjunction fails (empty family, empty relation).
|
|
265
|
+
# Partition values are always equivalences, so this is stated, not run.
|
|
266
|
+
return failures
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
270
|
+
"""Run all checks and return a process exit status."""
|
|
271
|
+
parser = argparse.ArgumentParser(description="Check Interpretive Kernel laws.")
|
|
272
|
+
parser.add_argument("--random", type=int, default=50, help="random artifacts")
|
|
273
|
+
parser.add_argument("--seed", type=int, default=0, help="random seed")
|
|
274
|
+
args = parser.parse_args(argv)
|
|
275
|
+
|
|
276
|
+
failures: list[str] = []
|
|
277
|
+
for artifact in all_cases():
|
|
278
|
+
failures.extend(check_artifact(artifact))
|
|
279
|
+
failures.extend(check_cases())
|
|
280
|
+
checked = len(all_cases())
|
|
281
|
+
for artifact in random_artifacts(args.random, args.seed):
|
|
282
|
+
failures.extend(check_artifact(artifact))
|
|
283
|
+
checked += 1
|
|
284
|
+
|
|
285
|
+
for line in failures[:50]:
|
|
286
|
+
print(line)
|
|
287
|
+
status = "FAILED" if failures else "OK"
|
|
288
|
+
print(f"{status}: {checked} artifacts, {len(failures)} failures")
|
|
289
|
+
return 1 if failures else 0
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
sys.exit(main())
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""demand.py - Pairwise interpretive demand.
|
|
2
|
+
|
|
3
|
+
Definition (Lean: ``demand``). The demand of a pair of frames (x, y) is the
|
|
4
|
+
least occurrence regime under which every selected probe treats x and y alike:
|
|
5
|
+
|
|
6
|
+
delta(x, y) = equivalence closure of {(I(p,x), I(p,y)) | p selected}.
|
|
7
|
+
|
|
8
|
+
Theorem (Lean: ``kernelRel_iff_demand_finer``). For every occurrence regime
|
|
9
|
+
rho, F K_Q(rho) F' exactly when delta(F, F') refines rho.
|
|
10
|
+
|
|
11
|
+
delta is a generalized pseudo-ultrametric: it is the bottom partition on the
|
|
12
|
+
diagonal, symmetric, and satisfies the strong triangle inequality
|
|
13
|
+
delta(x,z) <= delta(x,y) join delta(y,z). Distinct frames may have zero
|
|
14
|
+
demand, so delta does not recover identity of frames.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
|
|
21
|
+
from .artifact import Artifact
|
|
22
|
+
from .partition import Item, Pair, Partition
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def demand(artifact: Artifact, selected: Sequence[Item], x: Item, y: Item) -> Partition:
|
|
26
|
+
"""Return the pairwise interpretive demand delta(x, y)."""
|
|
27
|
+
artifact.require_selected(selected)
|
|
28
|
+
pairs: list[Pair] = []
|
|
29
|
+
for probe in selected:
|
|
30
|
+
pairs.append((artifact.interpret(probe, x), artifact.interpret(probe, y)))
|
|
31
|
+
return Partition.from_pairs(artifact.outputs, pairs)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def kernel_from_demand(
|
|
35
|
+
artifact: Artifact, selected: Sequence[Item], rho: Partition
|
|
36
|
+
) -> Partition:
|
|
37
|
+
"""Return K_Q(rho) computed as {(x, y) | delta(x, y) refines rho}."""
|
|
38
|
+
pairs: list[Pair] = []
|
|
39
|
+
for x in artifact.frames:
|
|
40
|
+
for y in artifact.frames:
|
|
41
|
+
if demand(artifact, selected, x, y).refines(rho):
|
|
42
|
+
pairs.append((x, y))
|
|
43
|
+
return Partition.from_pairs(artifact.frames, pairs)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ultrametric_violations(artifact: Artifact, selected: Sequence[Item]) -> list[str]:
|
|
47
|
+
"""Return a description of every violated ultrametric law."""
|
|
48
|
+
bottom = Partition.discrete(artifact.outputs)
|
|
49
|
+
found: list[str] = []
|
|
50
|
+
for x in artifact.frames:
|
|
51
|
+
if demand(artifact, selected, x, x) != bottom:
|
|
52
|
+
found.append(f"zero: demand({x!r}, {x!r}) is not discrete")
|
|
53
|
+
for x in artifact.frames:
|
|
54
|
+
for y in artifact.frames:
|
|
55
|
+
if demand(artifact, selected, x, y) != demand(artifact, selected, y, x):
|
|
56
|
+
found.append(f"symmetry: demand({x!r}, {y!r}) differs from reverse")
|
|
57
|
+
for x in artifact.frames:
|
|
58
|
+
for y in artifact.frames:
|
|
59
|
+
for z in artifact.frames:
|
|
60
|
+
direct = demand(artifact, selected, x, z)
|
|
61
|
+
detour = demand(artifact, selected, x, y).join(
|
|
62
|
+
demand(artifact, selected, y, z)
|
|
63
|
+
)
|
|
64
|
+
if not direct.refines(detour):
|
|
65
|
+
found.append(f"triangle: ({x!r}, {y!r}, {z!r})")
|
|
66
|
+
return found
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""differences.py - Report where two frame regimes disagree.
|
|
2
|
+
|
|
3
|
+
Given two declared groupings of the same frames, this module lists the groups
|
|
4
|
+
one regime makes that the other regime splits.
|
|
5
|
+
|
|
6
|
+
It reports differences. It does not say which regime is correct, which is
|
|
7
|
+
closer to some truth, or which should be preferred. Deciding that needs
|
|
8
|
+
evidence outside this package.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
from .partition import Item, Partition
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class Difference:
|
|
20
|
+
"""Contain one group made by one regime and split by the other.
|
|
21
|
+
|
|
22
|
+
``grouped_by`` is ``"first"`` or ``"second"``: the regime that puts
|
|
23
|
+
``frames`` in one block. ``split_into`` is how the other regime separates
|
|
24
|
+
those frames, as parts that each stay together under both regimes.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
grouped_by: str
|
|
28
|
+
frames: tuple[Item, ...]
|
|
29
|
+
split_into: tuple[tuple[Item, ...], ...]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _splits(block: tuple[Item, ...], common: Partition) -> tuple[tuple[Item, ...], ...]:
|
|
33
|
+
"""Return the parts of ``block`` under a partition, in first-seen order."""
|
|
34
|
+
parts: dict[int, list[Item]] = {}
|
|
35
|
+
for frame in block:
|
|
36
|
+
parts.setdefault(common.label_of(frame), []).append(frame)
|
|
37
|
+
return tuple(tuple(part) for part in parts.values())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def regime_differences(first: Partition, second: Partition) -> list[Difference]:
|
|
41
|
+
"""Return every group that one regime makes and the other splits.
|
|
42
|
+
|
|
43
|
+
An empty list means the two regimes are the same partition.
|
|
44
|
+
"""
|
|
45
|
+
if first.carrier != second.carrier:
|
|
46
|
+
msg = "both regimes must be partitions of the same frames, in order"
|
|
47
|
+
raise ValueError(msg)
|
|
48
|
+
common = first.meet(second)
|
|
49
|
+
found: list[Difference] = []
|
|
50
|
+
for block in first.blocks():
|
|
51
|
+
parts = _splits(block, common)
|
|
52
|
+
if len(parts) > 1:
|
|
53
|
+
found.append(Difference("first", block, parts))
|
|
54
|
+
for block in second.blocks():
|
|
55
|
+
parts = _splits(block, common)
|
|
56
|
+
if len(parts) > 1:
|
|
57
|
+
found.append(Difference("second", block, parts))
|
|
58
|
+
return found
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def format_differences(differences: list[Difference]) -> str:
|
|
62
|
+
"""Return the differences as plain text, one line each."""
|
|
63
|
+
lines: list[str] = []
|
|
64
|
+
for item in differences:
|
|
65
|
+
parts = " | ".join(", ".join(repr(f) for f in part) for part in item.split_into)
|
|
66
|
+
lines.append(
|
|
67
|
+
f"{item.grouped_by} groups {len(item.frames)} frames; other splits: {parts}"
|
|
68
|
+
)
|
|
69
|
+
return "\n".join(lines)
|