hypercomplex-engine 1.0.1__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.
- hypercomplex/__init__.py +63 -0
- hypercomplex/core/__init__.py +39 -0
- hypercomplex/core/basis_element.py +58 -0
- hypercomplex/core/basis_notation.py +134 -0
- hypercomplex/core/fast/__init__.py +9 -0
- hypercomplex/core/fast/bit_utils.py +20 -0
- hypercomplex/core/fast/fast_dual.py +182 -0
- hypercomplex/core/fast/fast_split.py +113 -0
- hypercomplex/core/fast/fast_standard.py +124 -0
- hypercomplex/core/holographic/__init__.py +9 -0
- hypercomplex/core/holographic/dual.py +88 -0
- hypercomplex/core/holographic/split.py +100 -0
- hypercomplex/core/holographic/standard.py +141 -0
- hypercomplex/core/table_builder/__init__.py +9 -0
- hypercomplex/core/table_builder/common.py +23 -0
- hypercomplex/core/table_builder/dual.py +97 -0
- hypercomplex/core/table_builder/split.py +101 -0
- hypercomplex/core/table_builder/standard.py +90 -0
- hypercomplex/core/validation.py +112 -0
- hypercomplex/facade.py +310 -0
- hypercomplex/printer/__init__.py +7 -0
- hypercomplex/printer/cd_format.py +209 -0
- hypercomplex/printer/cd_table_printer.py +204 -0
- hypercomplex_engine-1.0.1.dist-info/METADATA +1059 -0
- hypercomplex_engine-1.0.1.dist-info/RECORD +28 -0
- hypercomplex_engine-1.0.1.dist-info/WHEEL +5 -0
- hypercomplex_engine-1.0.1.dist-info/licenses/LICENSE +21 -0
- hypercomplex_engine-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from ..validation import Validation
|
|
2
|
+
from .bit_utils import nu2, popcount
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FastStandard:
|
|
6
|
+
"""
|
|
7
|
+
Standard Cayley-Dickson O(1) multiplier.
|
|
8
|
+
|
|
9
|
+
Input / output convention:
|
|
10
|
+
(sign, index)
|
|
11
|
+
|
|
12
|
+
The sign is computed in O(1) Word-RAM time using:
|
|
13
|
+
t = nu2(i XOR j)
|
|
14
|
+
ti = nu2(i)
|
|
15
|
+
tj = nu2(j)
|
|
16
|
+
|
|
17
|
+
The basis index is always:
|
|
18
|
+
i XOR j
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def multiply(self, t1: tuple, t2: tuple) -> tuple:
|
|
22
|
+
"""
|
|
23
|
+
Multiply two standard basis element tuples.
|
|
24
|
+
|
|
25
|
+
t1 = (sign1, index1)
|
|
26
|
+
t2 = (sign2, index2)
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
(final_sign, index1 XOR index2)
|
|
30
|
+
"""
|
|
31
|
+
Validation.basis_tuple(t1, allow_zero=True, allow_eps=False)
|
|
32
|
+
Validation.basis_tuple(t2, allow_zero=True, allow_eps=False)
|
|
33
|
+
|
|
34
|
+
s1, i = int(t1[0]), int(t1[1])
|
|
35
|
+
s2, j = int(t2[0]), int(t2[1])
|
|
36
|
+
|
|
37
|
+
if s1 == 0 or s2 == 0:
|
|
38
|
+
return (0, 0)
|
|
39
|
+
|
|
40
|
+
sign, idx = self.multiply_indices(i, j)
|
|
41
|
+
|
|
42
|
+
return (s1 * s2 * sign, idx)
|
|
43
|
+
|
|
44
|
+
def multiply_indices(self, i: int, j: int) -> tuple:
|
|
45
|
+
"""
|
|
46
|
+
Core O(1) multiplication for non-negative integer indices.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
(sign, i XOR j)
|
|
50
|
+
"""
|
|
51
|
+
i = int(i)
|
|
52
|
+
j = int(j)
|
|
53
|
+
|
|
54
|
+
if i < 0 or j < 0:
|
|
55
|
+
raise ValueError("indices must be >= 0")
|
|
56
|
+
|
|
57
|
+
# Identity
|
|
58
|
+
if i == 0 or j == 0:
|
|
59
|
+
return (1, i ^ j)
|
|
60
|
+
|
|
61
|
+
# Main diagonal
|
|
62
|
+
if i == j:
|
|
63
|
+
return (-1, 0)
|
|
64
|
+
|
|
65
|
+
# Structural break levels
|
|
66
|
+
t = nu2(i ^ j)
|
|
67
|
+
ti = nu2(i)
|
|
68
|
+
tj = nu2(j)
|
|
69
|
+
|
|
70
|
+
# --------------------------------------------------------------
|
|
71
|
+
# Diagonal break:
|
|
72
|
+
# t >= ti and t >= tj
|
|
73
|
+
# --------------------------------------------------------------
|
|
74
|
+
if t >= ti and t >= tj:
|
|
75
|
+
k = t + 1
|
|
76
|
+
s = 2 * ((i >> t) & 1) - 1
|
|
77
|
+
|
|
78
|
+
# --------------------------------------------------------------
|
|
79
|
+
# i-axis break:
|
|
80
|
+
# ti > t and ti >= tj
|
|
81
|
+
# --------------------------------------------------------------
|
|
82
|
+
elif ti >= tj:
|
|
83
|
+
k = ti + 1
|
|
84
|
+
|
|
85
|
+
# Quadrant bit:
|
|
86
|
+
# 0 -> quadrant c
|
|
87
|
+
# 1 -> quadrant d
|
|
88
|
+
b = (j >> ti) & 1
|
|
89
|
+
|
|
90
|
+
# Local j below the break level
|
|
91
|
+
j_loc = j & ((1 << ti) - 1)
|
|
92
|
+
|
|
93
|
+
# delta(j_loc):
|
|
94
|
+
# +1 if j_loc == 0
|
|
95
|
+
# -1 otherwise
|
|
96
|
+
delta = 1 if j_loc == 0 else -1
|
|
97
|
+
|
|
98
|
+
if b == 0:
|
|
99
|
+
# quadrant c
|
|
100
|
+
s = delta
|
|
101
|
+
else:
|
|
102
|
+
# quadrant d
|
|
103
|
+
s = -delta
|
|
104
|
+
|
|
105
|
+
# --------------------------------------------------------------
|
|
106
|
+
# j-axis break:
|
|
107
|
+
# tj > t and tj > ti
|
|
108
|
+
# --------------------------------------------------------------
|
|
109
|
+
else:
|
|
110
|
+
k = tj + 1
|
|
111
|
+
|
|
112
|
+
# Quadrant bit:
|
|
113
|
+
# 0 -> quadrant b, sign +1
|
|
114
|
+
# 1 -> quadrant d, sign -1
|
|
115
|
+
s = 1 - 2 * ((i >> tj) & 1)
|
|
116
|
+
|
|
117
|
+
# --------------------------------------------------------------
|
|
118
|
+
# Higher-bit parity correction:
|
|
119
|
+
# standard flips in quadrants b, c, d
|
|
120
|
+
# --------------------------------------------------------------
|
|
121
|
+
if popcount((i | j) >> k) & 1:
|
|
122
|
+
s = -s
|
|
123
|
+
|
|
124
|
+
return (s, i ^ j)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from ..validation import Validation
|
|
2
|
+
from .standard import StandardHolographic
|
|
3
|
+
from .split import SplitHolographic
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DualHolographic:
|
|
7
|
+
"""
|
|
8
|
+
Dual Cayley-Dickson holographic multiplier.
|
|
9
|
+
|
|
10
|
+
Input index convention:
|
|
11
|
+
The total dual dimension is 2^(dim + 1).
|
|
12
|
+
Bit `dim` marks the epsilon component.
|
|
13
|
+
|
|
14
|
+
lower half: 0 ... 2^dim - 1
|
|
15
|
+
upper half: epsilon times lower half
|
|
16
|
+
|
|
17
|
+
Output:
|
|
18
|
+
(sign, local_index, eps_flag)
|
|
19
|
+
|
|
20
|
+
Nilpotency:
|
|
21
|
+
(epsilon e_i) * (epsilon e_j) = 0
|
|
22
|
+
returned as (0, 0, 1)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, split: bool = False):
|
|
26
|
+
self._split_mode = bool(split)
|
|
27
|
+
self._standard = StandardHolographic()
|
|
28
|
+
self._split = SplitHolographic() if self._split_mode else None
|
|
29
|
+
|
|
30
|
+
def multiply(self, t1: tuple, t2: tuple, dim: int) -> tuple:
|
|
31
|
+
"""
|
|
32
|
+
Multiply two dual basis elements.
|
|
33
|
+
|
|
34
|
+
t1, t2 may be:
|
|
35
|
+
(sign, global_index)
|
|
36
|
+
(sign, global_index, eps_flag)
|
|
37
|
+
|
|
38
|
+
The epsilon component is determined by bit `dim` of global_index.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
(final_sign, local_index, eps_flag)
|
|
42
|
+
"""
|
|
43
|
+
Validation.basis_tuple(t1, allow_zero=True, allow_eps=True)
|
|
44
|
+
Validation.basis_tuple(t2, allow_zero=True, allow_eps=True)
|
|
45
|
+
|
|
46
|
+
dim = Validation.dimension(dim)
|
|
47
|
+
|
|
48
|
+
s1, i = int(t1[0]), int(t1[1])
|
|
49
|
+
s2, j = int(t2[0]), int(t2[1])
|
|
50
|
+
|
|
51
|
+
# Zero propagates.
|
|
52
|
+
if s1 == 0 or s2 == 0:
|
|
53
|
+
return (0, 0, 0)
|
|
54
|
+
|
|
55
|
+
total_size = 1 << (dim + 1)
|
|
56
|
+
|
|
57
|
+
if i < 0 or i >= total_size:
|
|
58
|
+
raise ValueError(f"index must be in [0, {total_size - 1}] for dual dim={dim}, got {i}")
|
|
59
|
+
|
|
60
|
+
if j < 0 or j >= total_size:
|
|
61
|
+
raise ValueError(f"index must be in [0, {total_size - 1}] for dual dim={dim}, got {j}")
|
|
62
|
+
|
|
63
|
+
half = 1 << dim
|
|
64
|
+
|
|
65
|
+
i_eps = i >= half
|
|
66
|
+
j_eps = j >= half
|
|
67
|
+
|
|
68
|
+
i_loc = i & (half - 1)
|
|
69
|
+
j_loc = j & (half - 1)
|
|
70
|
+
|
|
71
|
+
# --------------------------------------------------------------
|
|
72
|
+
# Epsilon nilpotency:
|
|
73
|
+
# (epsilon e_i) * (epsilon e_j) = 0
|
|
74
|
+
# --------------------------------------------------------------
|
|
75
|
+
if i_eps and j_eps:
|
|
76
|
+
return (0, 0, 1)
|
|
77
|
+
|
|
78
|
+
# --------------------------------------------------------------
|
|
79
|
+
# Base multiplication.
|
|
80
|
+
# --------------------------------------------------------------
|
|
81
|
+
if self._split_mode:
|
|
82
|
+
base_sign, local_idx = self._split.multiply_indices(i_loc, j_loc, dim)
|
|
83
|
+
else:
|
|
84
|
+
base_sign, local_idx = self._standard.multiply_indices(i_loc, j_loc)
|
|
85
|
+
|
|
86
|
+
eps_flag = 1 if (i_eps or j_eps) else 0
|
|
87
|
+
|
|
88
|
+
return (s1 * s2 * base_sign, local_idx, eps_flag)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from ..validation import Validation
|
|
2
|
+
from .standard import StandardHolographic
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SplitHolographic:
|
|
6
|
+
"""
|
|
7
|
+
Split Cayley-Dickson holographic O(n) multiplier.
|
|
8
|
+
|
|
9
|
+
Architecture:
|
|
10
|
+
Standard parent A_{dim-1} + one split doubling at the top.
|
|
11
|
+
|
|
12
|
+
Blocks A, B, C:
|
|
13
|
+
delegate to StandardHolographic.
|
|
14
|
+
|
|
15
|
+
Block D:
|
|
16
|
+
handled by split rules.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self._standard = StandardHolographic()
|
|
21
|
+
|
|
22
|
+
def multiply(self, t1: tuple, t2: tuple, dim: int) -> tuple:
|
|
23
|
+
"""
|
|
24
|
+
Multiply two basis elements in split A_dim.
|
|
25
|
+
|
|
26
|
+
t1 = (sign1, index1)
|
|
27
|
+
t2 = (sign2, index2)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
(final_sign, index1 XOR index2)
|
|
31
|
+
"""
|
|
32
|
+
Validation.basis_tuple(t1, allow_zero=False, allow_eps=False)
|
|
33
|
+
Validation.basis_tuple(t2, allow_zero=False, allow_eps=False)
|
|
34
|
+
|
|
35
|
+
s1, i = int(t1[0]), int(t1[1])
|
|
36
|
+
s2, j = int(t2[0]), int(t2[1])
|
|
37
|
+
|
|
38
|
+
dim = Validation.dimension(dim)
|
|
39
|
+
Validation.index_in_range(i, dim)
|
|
40
|
+
Validation.index_in_range(j, dim)
|
|
41
|
+
|
|
42
|
+
sign, idx = self.multiply_indices(i, j, dim)
|
|
43
|
+
|
|
44
|
+
return (s1 * s2 * sign, idx)
|
|
45
|
+
|
|
46
|
+
def multiply_indices(self, i: int, j: int, dim: int) -> tuple:
|
|
47
|
+
"""
|
|
48
|
+
Core split multiplication for non-negative indices.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
(sign, i XOR j)
|
|
52
|
+
"""
|
|
53
|
+
i = int(i)
|
|
54
|
+
j = int(j)
|
|
55
|
+
dim = Validation.dimension(dim)
|
|
56
|
+
|
|
57
|
+
if i < 0 or j < 0:
|
|
58
|
+
raise ValueError("indices must be >= 0")
|
|
59
|
+
|
|
60
|
+
Validation.index_in_range(i, dim)
|
|
61
|
+
Validation.index_in_range(j, dim)
|
|
62
|
+
|
|
63
|
+
if dim == 0:
|
|
64
|
+
return (1, 0)
|
|
65
|
+
|
|
66
|
+
half = 1 << (dim - 1)
|
|
67
|
+
|
|
68
|
+
# --------------------------------------------------------------
|
|
69
|
+
# Top-level Block D:
|
|
70
|
+
# both indices are in the upper half.
|
|
71
|
+
# --------------------------------------------------------------
|
|
72
|
+
if i >= half and j >= half:
|
|
73
|
+
i_loc = i - half
|
|
74
|
+
j_loc = j - half
|
|
75
|
+
|
|
76
|
+
# Split Block D diagonal:
|
|
77
|
+
# (e_i ℓ)^2 = +e_0 for all i.
|
|
78
|
+
if i_loc == j_loc:
|
|
79
|
+
return (1, 0)
|
|
80
|
+
|
|
81
|
+
# Split Block D first row:
|
|
82
|
+
# i_loc == 0, j_loc > 0 -> sign -1.
|
|
83
|
+
if i_loc == 0:
|
|
84
|
+
return (-1, i ^ j)
|
|
85
|
+
|
|
86
|
+
# Split Block D first column:
|
|
87
|
+
# j_loc == 0, i_loc > 0 -> sign +1.
|
|
88
|
+
if j_loc == 0:
|
|
89
|
+
return (1, i ^ j)
|
|
90
|
+
|
|
91
|
+
# Split Block D interior:
|
|
92
|
+
# sign = sigma_a(i_loc, j_loc).
|
|
93
|
+
sign, _ = self._standard.multiply_indices(i_loc, j_loc)
|
|
94
|
+
return (sign, i ^ j)
|
|
95
|
+
|
|
96
|
+
# --------------------------------------------------------------
|
|
97
|
+
# Blocks A, B, C:
|
|
98
|
+
# identical to standard.
|
|
99
|
+
# --------------------------------------------------------------
|
|
100
|
+
return self._standard.multiply_indices(i, j)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from ..validation import Validation
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class StandardHolographic:
|
|
5
|
+
"""
|
|
6
|
+
Standard Cayley-Dickson holographic O(n) multiplier.
|
|
7
|
+
|
|
8
|
+
Input / output convention:
|
|
9
|
+
(sign, index)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def multiply(self, t1: tuple, t2: tuple) -> tuple:
|
|
13
|
+
"""
|
|
14
|
+
Multiply two basis elements.
|
|
15
|
+
|
|
16
|
+
t1 = (sign1, index1)
|
|
17
|
+
t2 = (sign2, index2)
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
(final_sign, index1 XOR index2)
|
|
21
|
+
"""
|
|
22
|
+
Validation.basis_tuple(t1, allow_zero=False, allow_eps=False)
|
|
23
|
+
Validation.basis_tuple(t2, allow_zero=False, allow_eps=False)
|
|
24
|
+
|
|
25
|
+
s1, i = int(t1[0]), int(t1[1])
|
|
26
|
+
s2, j = int(t2[0]), int(t2[1])
|
|
27
|
+
|
|
28
|
+
sign, idx = self.multiply_indices(i, j)
|
|
29
|
+
|
|
30
|
+
return (s1 * s2 * sign, idx)
|
|
31
|
+
|
|
32
|
+
def multiply_indices(self, i: int, j: int) -> tuple:
|
|
33
|
+
"""
|
|
34
|
+
Core O(n) descent for non-negative integer indices.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
(sign, i XOR j)
|
|
38
|
+
"""
|
|
39
|
+
i = int(i)
|
|
40
|
+
j = int(j)
|
|
41
|
+
|
|
42
|
+
if i < 0 or j < 0:
|
|
43
|
+
raise ValueError("indices must be >= 0")
|
|
44
|
+
|
|
45
|
+
if i == 0 and j == 0:
|
|
46
|
+
return (1, 0)
|
|
47
|
+
|
|
48
|
+
sign = 1
|
|
49
|
+
ic = i
|
|
50
|
+
jc = j
|
|
51
|
+
|
|
52
|
+
n = max(i, j).bit_length()
|
|
53
|
+
|
|
54
|
+
for level in range(n, 0, -1):
|
|
55
|
+
half = 1 << (level - 1)
|
|
56
|
+
|
|
57
|
+
if ic < half and jc < half:
|
|
58
|
+
q = "a"
|
|
59
|
+
elif ic < half and jc >= half:
|
|
60
|
+
q = "b"
|
|
61
|
+
jc -= half
|
|
62
|
+
elif ic >= half and jc < half:
|
|
63
|
+
q = "c"
|
|
64
|
+
ic -= half
|
|
65
|
+
else:
|
|
66
|
+
q = "d"
|
|
67
|
+
ic -= half
|
|
68
|
+
jc -= half
|
|
69
|
+
|
|
70
|
+
# Structural position inside the current quadrant.
|
|
71
|
+
if ic == 0 or jc == 0 or ic == jc:
|
|
72
|
+
structural_sign = self._structural_sign(q, ic, jc, split=False)
|
|
73
|
+
return (sign * structural_sign, i ^ j)
|
|
74
|
+
|
|
75
|
+
# Non-structural sign flip.
|
|
76
|
+
if self._flips(q, split=False):
|
|
77
|
+
sign = -sign
|
|
78
|
+
|
|
79
|
+
# Should normally terminate via structural detection.
|
|
80
|
+
return (sign, i ^ j)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _flips(q: str, split: bool) -> bool:
|
|
84
|
+
"""
|
|
85
|
+
Non-structural sign flip rule.
|
|
86
|
+
|
|
87
|
+
Standard:
|
|
88
|
+
flips in b, c, d
|
|
89
|
+
|
|
90
|
+
Split:
|
|
91
|
+
flips in b, c only
|
|
92
|
+
"""
|
|
93
|
+
if split:
|
|
94
|
+
return q in ("b", "c")
|
|
95
|
+
return q in ("b", "c", "d")
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _structural_sign(q: str, i_loc: int, j_loc: int, split: bool) -> int:
|
|
99
|
+
"""
|
|
100
|
+
Structural sign tables from OPMT Theorem 1.2 and Theorem 2.1.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
# --------------------------------------------------------------
|
|
104
|
+
# Diagonal
|
|
105
|
+
# --------------------------------------------------------------
|
|
106
|
+
if i_loc == j_loc:
|
|
107
|
+
if i_loc == 0:
|
|
108
|
+
if q in ("a", "b", "c"):
|
|
109
|
+
return 1
|
|
110
|
+
# Block d, (0,0)
|
|
111
|
+
return 1 if split else -1
|
|
112
|
+
|
|
113
|
+
# i_loc == j_loc > 0
|
|
114
|
+
if q in ("a", "b"):
|
|
115
|
+
return -1
|
|
116
|
+
if q == "c":
|
|
117
|
+
return 1
|
|
118
|
+
# Block d diagonal
|
|
119
|
+
return 1 if split else -1
|
|
120
|
+
|
|
121
|
+
# --------------------------------------------------------------
|
|
122
|
+
# First row: i_loc == 0, j_loc > 0
|
|
123
|
+
# --------------------------------------------------------------
|
|
124
|
+
if i_loc == 0:
|
|
125
|
+
if q in ("a", "b"):
|
|
126
|
+
return 1
|
|
127
|
+
if q == "c":
|
|
128
|
+
return -1
|
|
129
|
+
# Block d first row
|
|
130
|
+
return -1 if split else 1
|
|
131
|
+
|
|
132
|
+
# --------------------------------------------------------------
|
|
133
|
+
# First column: j_loc == 0, i_loc > 0
|
|
134
|
+
# --------------------------------------------------------------
|
|
135
|
+
if j_loc == 0:
|
|
136
|
+
if q in ("a", "b", "c"):
|
|
137
|
+
return 1
|
|
138
|
+
# Block d first column
|
|
139
|
+
return 1 if split else -1
|
|
140
|
+
|
|
141
|
+
raise ValueError("Not a structural position.")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def index_dtype(dim: int):
|
|
5
|
+
"""
|
|
6
|
+
Choose the smallest unsigned dtype capable of storing indices 0 ... dim-1.
|
|
7
|
+
"""
|
|
8
|
+
if dim <= 1 << 8:
|
|
9
|
+
return np.uint8
|
|
10
|
+
if dim <= 1 << 16:
|
|
11
|
+
return np.uint16
|
|
12
|
+
if dim <= 1 << 32:
|
|
13
|
+
return np.uint32
|
|
14
|
+
return np.uint64
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def real_table():
|
|
18
|
+
"""
|
|
19
|
+
The real numbers A_0.
|
|
20
|
+
"""
|
|
21
|
+
signs = np.array([[1]], dtype=np.int8)
|
|
22
|
+
indices = np.array([[0]], dtype=np.uint8)
|
|
23
|
+
return signs, indices
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..validation import Validation
|
|
4
|
+
from .common import index_dtype
|
|
5
|
+
from .standard import StandardTableBuilder
|
|
6
|
+
from .split import SplitTableBuilder
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DualTableBuilder:
|
|
10
|
+
"""
|
|
11
|
+
Dual Cayley-Dickson table builder.
|
|
12
|
+
|
|
13
|
+
Adds epsilon with epsilon^2 = 0.
|
|
14
|
+
|
|
15
|
+
The returned table uses:
|
|
16
|
+
signs[i, j]
|
|
17
|
+
indices[i, j] local parent index
|
|
18
|
+
eps[i, j] epsilon flag
|
|
19
|
+
|
|
20
|
+
For dual tables, index is the local parent basis index, and eps_flag
|
|
21
|
+
tells whether the product carries epsilon.
|
|
22
|
+
|
|
23
|
+
This matches the tuple convention:
|
|
24
|
+
(sign, index, eps_flag)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self._standard = StandardTableBuilder()
|
|
29
|
+
self._split = SplitTableBuilder()
|
|
30
|
+
|
|
31
|
+
def build(self, n: int, split: bool = False):
|
|
32
|
+
"""
|
|
33
|
+
Build the dual extension of A_n.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
n:
|
|
38
|
+
Parent algebra exponent.
|
|
39
|
+
Parent dimension is 2^n.
|
|
40
|
+
Dual table dimension is 2^(n+1).
|
|
41
|
+
|
|
42
|
+
split:
|
|
43
|
+
False -> dual over standard A_n
|
|
44
|
+
True -> dual over split A_n
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
signs, indices, eps
|
|
49
|
+
"""
|
|
50
|
+
n = Validation.dimension(n)
|
|
51
|
+
|
|
52
|
+
if split:
|
|
53
|
+
base_signs, base_indices = self._split.build(n)
|
|
54
|
+
else:
|
|
55
|
+
base_signs, base_indices = self._standard.build(n)
|
|
56
|
+
|
|
57
|
+
N = base_signs.shape[0]
|
|
58
|
+
full = 2 * N
|
|
59
|
+
dtype = index_dtype(full)
|
|
60
|
+
|
|
61
|
+
signs = np.zeros((full, full), dtype=np.int8)
|
|
62
|
+
indices = np.zeros((full, full), dtype=dtype)
|
|
63
|
+
eps = np.zeros((full, full), dtype=np.uint8)
|
|
64
|
+
|
|
65
|
+
# --------------------------------------------------------------
|
|
66
|
+
# Block a:
|
|
67
|
+
# e_i * e_j = parent product
|
|
68
|
+
# --------------------------------------------------------------
|
|
69
|
+
signs[:N, :N] = base_signs
|
|
70
|
+
indices[:N, :N] = base_indices
|
|
71
|
+
eps[:N, :N] = 0
|
|
72
|
+
|
|
73
|
+
# --------------------------------------------------------------
|
|
74
|
+
# Block b:
|
|
75
|
+
# e_i * (epsilon e_j) = epsilon (e_i e_j)
|
|
76
|
+
# --------------------------------------------------------------
|
|
77
|
+
signs[:N, N:] = base_signs
|
|
78
|
+
indices[:N, N:] = base_indices
|
|
79
|
+
eps[:N, N:] = 1
|
|
80
|
+
|
|
81
|
+
# --------------------------------------------------------------
|
|
82
|
+
# Block c:
|
|
83
|
+
# (epsilon e_i) * e_j = epsilon (e_i e_j)
|
|
84
|
+
# --------------------------------------------------------------
|
|
85
|
+
signs[N:, :N] = base_signs
|
|
86
|
+
indices[N:, :N] = base_indices
|
|
87
|
+
eps[N:, :N] = 1
|
|
88
|
+
|
|
89
|
+
# --------------------------------------------------------------
|
|
90
|
+
# Block d:
|
|
91
|
+
# (epsilon e_i) * (epsilon e_j) = epsilon^2 (e_i e_j) = 0
|
|
92
|
+
# --------------------------------------------------------------
|
|
93
|
+
signs[N:, N:] = 0
|
|
94
|
+
indices[N:, N:] = 0
|
|
95
|
+
eps[N:, N:] = 0
|
|
96
|
+
|
|
97
|
+
return signs, indices, eps
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..validation import Validation
|
|
4
|
+
from .common import index_dtype, real_table
|
|
5
|
+
from .standard import StandardTableBuilder
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SplitTableBuilder:
|
|
9
|
+
"""
|
|
10
|
+
Split Cayley-Dickson table builder.
|
|
11
|
+
|
|
12
|
+
Architecture:
|
|
13
|
+
Standard parent A_{n-1} + one split doubling at the top.
|
|
14
|
+
|
|
15
|
+
OPMT Theorem 2.1:
|
|
16
|
+
Blocks a, b, c are identical to standard.
|
|
17
|
+
Block d is the split Block d:
|
|
18
|
+
diagonal = +1
|
|
19
|
+
first column = +1
|
|
20
|
+
first row j > 0 = -1
|
|
21
|
+
interior = +sigma_a
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self._standard = StandardTableBuilder()
|
|
26
|
+
|
|
27
|
+
def build(self, n: int):
|
|
28
|
+
"""
|
|
29
|
+
Build the split Cayley-Dickson table A_n.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
signs:
|
|
34
|
+
signs[i, j] = ±1
|
|
35
|
+
indices:
|
|
36
|
+
indices[i, j] = k
|
|
37
|
+
"""
|
|
38
|
+
n = Validation.dimension(n)
|
|
39
|
+
|
|
40
|
+
if n == 0:
|
|
41
|
+
return real_table()
|
|
42
|
+
|
|
43
|
+
# Standard parent A_{n-1}
|
|
44
|
+
parent_signs, parent_indices = self._standard.build(n - 1)
|
|
45
|
+
|
|
46
|
+
half = parent_signs.shape[0]
|
|
47
|
+
full = 2 * half
|
|
48
|
+
dtype = index_dtype(full)
|
|
49
|
+
|
|
50
|
+
signs = np.zeros((full, full), dtype=np.int8)
|
|
51
|
+
indices = np.zeros((full, full), dtype=dtype)
|
|
52
|
+
|
|
53
|
+
# --------------------------------------------------------------
|
|
54
|
+
# Block a:
|
|
55
|
+
# Inherits the standard parent.
|
|
56
|
+
# --------------------------------------------------------------
|
|
57
|
+
signs[:half, :half] = parent_signs
|
|
58
|
+
indices[:half, :half] = parent_indices
|
|
59
|
+
|
|
60
|
+
# --------------------------------------------------------------
|
|
61
|
+
# Block b:
|
|
62
|
+
# Same as standard.
|
|
63
|
+
# --------------------------------------------------------------
|
|
64
|
+
signs[:half, half:] = parent_signs.T
|
|
65
|
+
np.add(
|
|
66
|
+
parent_indices.T,
|
|
67
|
+
dtype(half),
|
|
68
|
+
out=indices[:half, half:],
|
|
69
|
+
casting="unsafe",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# --------------------------------------------------------------
|
|
73
|
+
# Block c:
|
|
74
|
+
# Same as standard.
|
|
75
|
+
# --------------------------------------------------------------
|
|
76
|
+
block_c = parent_signs.copy()
|
|
77
|
+
block_c[:, 1:] = -block_c[:, 1:]
|
|
78
|
+
|
|
79
|
+
signs[half:, :half] = block_c
|
|
80
|
+
np.add(
|
|
81
|
+
parent_indices,
|
|
82
|
+
dtype(half),
|
|
83
|
+
out=indices[half:, :half],
|
|
84
|
+
casting="unsafe",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# --------------------------------------------------------------
|
|
88
|
+
# Block d:
|
|
89
|
+
# Split Block d.
|
|
90
|
+
#
|
|
91
|
+
# (0, e_i)(0, e_j) = (+e_j* e_i, 0)
|
|
92
|
+
#
|
|
93
|
+
# No leading minus sign, but conjugation still flips columns j > 0.
|
|
94
|
+
# --------------------------------------------------------------
|
|
95
|
+
block_d = parent_signs.T.copy()
|
|
96
|
+
block_d[:, 1:] = -block_d[:, 1:]
|
|
97
|
+
|
|
98
|
+
signs[half:, half:] = block_d
|
|
99
|
+
indices[half:, half:] = parent_indices.T
|
|
100
|
+
|
|
101
|
+
return signs, indices
|