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.
@@ -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,9 @@
1
+ from .standard import StandardHolographic
2
+ from .split import SplitHolographic
3
+ from .dual import DualHolographic
4
+
5
+ __all__ = [
6
+ "StandardHolographic",
7
+ "SplitHolographic",
8
+ "DualHolographic",
9
+ ]
@@ -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,9 @@
1
+ from .standard import StandardTableBuilder
2
+ from .split import SplitTableBuilder
3
+ from .dual import DualTableBuilder
4
+
5
+ __all__ = [
6
+ "StandardTableBuilder",
7
+ "SplitTableBuilder",
8
+ "DualTableBuilder",
9
+ ]
@@ -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