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,63 @@
1
+ from .facade import (
2
+ build_table,
3
+ multiply,
4
+ format_element,
5
+ print_table,
6
+ export_csv,
7
+ )
8
+
9
+ from .core import (
10
+ BasisElement,
11
+ BasisNotation,
12
+ Validation,
13
+
14
+ StandardTableBuilder,
15
+ SplitTableBuilder,
16
+ DualTableBuilder,
17
+
18
+ StandardHolographic,
19
+ SplitHolographic,
20
+ DualHolographic,
21
+
22
+ FastStandard,
23
+ FastSplit,
24
+ FastDual,
25
+ )
26
+
27
+ from .printer import (
28
+ CDFormat,
29
+ CDTablePrinter,
30
+ )
31
+
32
+ __all__ = [
33
+ # Simple API
34
+ "build_table",
35
+ "multiply",
36
+ "format_element",
37
+ "print_table",
38
+ "export_csv",
39
+
40
+ # Core
41
+ "BasisElement",
42
+ "BasisNotation",
43
+ "Validation",
44
+
45
+ # Table builders
46
+ "StandardTableBuilder",
47
+ "SplitTableBuilder",
48
+ "DualTableBuilder",
49
+
50
+ # Holographic O(n)
51
+ "StandardHolographic",
52
+ "SplitHolographic",
53
+ "DualHolographic",
54
+
55
+ # O(1)
56
+ "FastStandard",
57
+ "FastSplit",
58
+ "FastDual",
59
+
60
+ # Printer / formatter
61
+ "CDFormat",
62
+ "CDTablePrinter",
63
+ ]
@@ -0,0 +1,39 @@
1
+ from .basis_element import BasisElement
2
+ from .basis_notation import BasisNotation
3
+ from .validation import Validation
4
+
5
+ from .table_builder import (
6
+ StandardTableBuilder,
7
+ SplitTableBuilder,
8
+ DualTableBuilder,
9
+ )
10
+
11
+ from .holographic import (
12
+ StandardHolographic,
13
+ SplitHolographic,
14
+ DualHolographic,
15
+ )
16
+
17
+ from .fast import (
18
+ FastStandard,
19
+ FastSplit,
20
+ FastDual,
21
+ )
22
+
23
+ __all__ = [
24
+ "BasisElement",
25
+ "BasisNotation",
26
+ "Validation",
27
+
28
+ "StandardTableBuilder",
29
+ "SplitTableBuilder",
30
+ "DualTableBuilder",
31
+
32
+ "StandardHolographic",
33
+ "SplitHolographic",
34
+ "DualHolographic",
35
+
36
+ "FastStandard",
37
+ "FastSplit",
38
+ "FastDual",
39
+ ]
@@ -0,0 +1,58 @@
1
+ class BasisElement:
2
+ """
3
+ Basis elements are represented as plain tuples.
4
+
5
+ Standard / Split:
6
+ (sign, index)
7
+
8
+ Dual:
9
+ (sign, index, eps_flag)
10
+
11
+ sign:
12
+ -1, 0, or +1
13
+ sign == 0 means the zero element.
14
+
15
+ index:
16
+ basis index k, meaning e_k.
17
+
18
+ eps_flag:
19
+ 0 or 1, used only for dual numbers.
20
+ """
21
+
22
+ @staticmethod
23
+ def make(sign: int, index: int, eps: int | None = None) -> tuple:
24
+ if eps is None:
25
+ return (int(sign), int(index))
26
+ return (int(sign), int(index), int(eps))
27
+
28
+ @staticmethod
29
+ def zero(eps: int = 0) -> tuple:
30
+ return (0, 0, int(eps))
31
+
32
+ @staticmethod
33
+ def sign(element: tuple) -> int:
34
+ return int(element[0])
35
+
36
+ @staticmethod
37
+ def index(element: tuple) -> int:
38
+ return int(element[1])
39
+
40
+ @staticmethod
41
+ def eps(element: tuple) -> int:
42
+ if len(element) >= 3:
43
+ return int(element[2])
44
+ return 0
45
+
46
+ @staticmethod
47
+ def is_zero(element: tuple) -> bool:
48
+ return int(element[0]) == 0
49
+
50
+ @staticmethod
51
+ def negate(element: tuple) -> tuple:
52
+ if len(element) == 2:
53
+ return (-int(element[0]), int(element[1]))
54
+ return (-int(element[0]), int(element[1]), int(element[2]))
55
+
56
+ @staticmethod
57
+ def with_eps(element: tuple, eps: int) -> tuple:
58
+ return (int(element[0]), int(element[1]), int(eps))
@@ -0,0 +1,134 @@
1
+ # basis_notation.py
2
+
3
+ class BasisNotation:
4
+ """
5
+ Handles conversions between integer indices, graded (bitmask) notation,
6
+ and LaTeX strings for Cayley-Dickson basis elements.
7
+ """
8
+
9
+ @staticmethod
10
+ def int_to_generators(k: int) -> tuple:
11
+ """Converts integer index k to a tuple of 1-based generator indices."""
12
+ if k == 0: return ()
13
+ gens = []
14
+ i = 1
15
+ temp_k = k
16
+ while temp_k > 0:
17
+ if temp_k & 1: gens.append(i)
18
+ temp_k >>= 1
19
+ i += 1
20
+ return tuple(gens)
21
+
22
+ @staticmethod
23
+ def generators_to_int(gens) -> int:
24
+ """Converts generators (tuple, list, or string like '13') to integer index."""
25
+ if isinstance(gens, str):
26
+ # Extract digits from strings like "o13" or "o_{145}"
27
+ # NOTE: This assumes single-digit generators (valid up to 512-dim / 9 generators).
28
+ gens = [int(char) for char in gens if char.isdigit()]
29
+ k = 0
30
+ for g in gens:
31
+ k |= (1 << (g - 1))
32
+ return k
33
+
34
+ @staticmethod
35
+ def to_graded_str(k: int) -> str:
36
+ """Integer to graded string (e.g., 5 -> 'o13', 0 -> '1')."""
37
+ if k == 0: return "1"
38
+ gens = BasisNotation.int_to_generators(k)
39
+ return "o" + "".join(str(g) for g in gens)
40
+
41
+ @staticmethod
42
+ def to_latex(k: int, mode: str = "integer") -> str:
43
+ """Integer to LaTeX string.
44
+ mode="integer": e_{5}, e_{13} (index as number)
45
+ mode="graded": o_{13}, o_{123} (generators)
46
+ """
47
+ if k == 0:
48
+ return "1"
49
+
50
+ if mode == "integer":
51
+ return f"e_{{{k}}}"
52
+ elif mode == "graded":
53
+ gens = BasisNotation.int_to_generators(k)
54
+ return "o_{" + "".join(str(g) for g in gens) + "}"
55
+ else:
56
+ raise ValueError("mode must be 'integer' or 'graded'")
57
+
58
+ @staticmethod
59
+ def from_graded_str(s: str) -> int:
60
+ """Parses a graded string back to an integer index.
61
+ Accepts 'o13', 'o_{13}', '1', and also legacy 'e13'.
62
+ """
63
+ s = s.strip()
64
+
65
+ # Zero / scalar cases
66
+ if s in ("1", "o", "o0", "e", "e0", "o_\\emptyset"):
67
+ return 0
68
+
69
+ # Strip prefixes and braces; digit extraction does the real work
70
+ s = (s.replace("o_", "").replace("o", "")
71
+ .replace("e_", "").replace("e", "")
72
+ .replace("{", "").replace("}", ""))
73
+
74
+ gens = [int(char) for char in s if char.isdigit()]
75
+ return BasisNotation.generators_to_int(gens)
76
+
77
+ @staticmethod
78
+ def format_entry(sign: int, k: int, eps: int = 0, mode: str = "integer") -> str:
79
+ """Formats a single table cell or O(1) product."""
80
+ if sign == 0:
81
+ return "0"
82
+
83
+ prefix = "+" if sign > 0 else "-"
84
+
85
+ if mode == "integer":
86
+ base = f"e{k}"
87
+ elif mode == "graded":
88
+ base = BasisNotation.to_graded_str(k)
89
+ elif mode == "latex": # alias for graded-latex (backward compat)
90
+ base = BasisNotation.to_latex(k, mode="graded")
91
+ elif mode == "latex_integer":
92
+ base = BasisNotation.to_latex(k, mode="integer")
93
+ elif mode == "latex_graded":
94
+ base = BasisNotation.to_latex(k, mode="graded")
95
+ else:
96
+ raise ValueError("mode must be 'integer', 'graded', 'latex', 'latex_integer', or 'latex_graded'")
97
+
98
+ if eps:
99
+ if mode.startswith("latex"):
100
+ eps_str = "\\epsilon"
101
+ if k == 0: return f"{prefix}{eps_str}"
102
+ return f"{prefix}{base}{eps_str}"
103
+ else:
104
+ eps_str = "eps"
105
+ if k == 0: return f"{prefix}{eps_str}"
106
+ return f"{prefix}{base}*{eps_str}"
107
+
108
+ return prefix + base
109
+
110
+ @staticmethod
111
+ def basis_labels(dim: int, dual: bool = False, mode: str = "integer") -> list:
112
+ """Generates the headers/row labels for the table."""
113
+ base_dim = dim // 2 if dual else dim
114
+
115
+ def _base_label(i):
116
+ if mode == "integer": return f"e{i}"
117
+ elif mode == "graded": return BasisNotation.to_graded_str(i)
118
+ elif mode == "latex": return BasisNotation.to_latex(i, mode="graded")
119
+ elif mode == "latex_integer": return BasisNotation.to_latex(i, mode="integer")
120
+ elif mode == "latex_graded": return BasisNotation.to_latex(i, mode="graded")
121
+ else: raise ValueError("Invalid mode")
122
+
123
+ labels = [_base_label(i) for i in range(base_dim)]
124
+
125
+ if dual:
126
+ is_latex = mode.startswith("latex")
127
+ eps_str = "\\epsilon" if is_latex else "eps"
128
+ labels.append(eps_str)
129
+
130
+ for k in range(1, base_dim):
131
+ base = _base_label(k)
132
+ labels.append(f"{base}{eps_str}" if is_latex else f"{base}*{eps_str}")
133
+
134
+ return labels
@@ -0,0 +1,9 @@
1
+ from .fast_standard import FastStandard
2
+ from .fast_split import FastSplit
3
+ from .fast_dual import FastDual
4
+
5
+ __all__ = [
6
+ "FastStandard",
7
+ "FastSplit",
8
+ "FastDual",
9
+ ]
@@ -0,0 +1,20 @@
1
+ def nu2(x: int) -> int:
2
+ """
3
+ 2-adic valuation: index of the lowest set bit.
4
+
5
+ nu2(1) = 0
6
+ nu2(2) = 1
7
+ nu2(4) = 2
8
+ nu2(12) = 2
9
+ """
10
+ if x == 0:
11
+ raise ValueError("nu2(0) is undefined in the O(1) structural descent")
12
+
13
+ return (x & -x).bit_length() - 1
14
+
15
+
16
+ def popcount(x: int) -> int:
17
+ """
18
+ Number of set bits.
19
+ """
20
+ return int(x).bit_count()
@@ -0,0 +1,182 @@
1
+ from ..validation import Validation
2
+ from .fast_standard import FastStandard
3
+ from .fast_split import FastSplit
4
+
5
+
6
+ class FastDual:
7
+ """
8
+ Dual Cayley-Dickson O(1) multiplier.
9
+
10
+ Input index convention:
11
+ Total dual dimension is 2^(dim + 1).
12
+ Bit `dim` marks the epsilon component.
13
+
14
+ lower half: base elements
15
+ upper half: epsilon * base elements
16
+
17
+ Output:
18
+ (sign, local_index, eps_flag)
19
+
20
+ The output index is the local base index, so it can be formatted
21
+ directly by CDFormat.
22
+
23
+ Zero products are normalized to:
24
+ (0, 0, 0)
25
+ """
26
+
27
+ def __init__(self, split: bool = False):
28
+ self._split_mode = bool(split)
29
+ self._standard = FastStandard()
30
+ self._split = FastSplit() if self._split_mode else None
31
+
32
+ # ==================================================================
33
+ # Index helpers
34
+ # ==================================================================
35
+
36
+ @staticmethod
37
+ def local_to_global(index: int, eps: int, dim: int) -> int:
38
+ """
39
+ Convert local dual representation to global index.
40
+
41
+ local:
42
+ index in [0, 2^dim - 1]
43
+ eps = 0 or 1
44
+
45
+ global:
46
+ index in [0, 2^(dim+1) - 1]
47
+ """
48
+ dim = Validation.dimension(dim)
49
+ index = int(index)
50
+ eps = int(eps)
51
+
52
+ half = 1 << dim
53
+
54
+ if index < 0 or index >= half:
55
+ raise ValueError(f"local index must be in [0, {half - 1}], got {index}")
56
+
57
+ if eps:
58
+ return index + half
59
+
60
+ return index
61
+
62
+ @staticmethod
63
+ def global_to_local(global_index: int, dim: int) -> tuple:
64
+ """
65
+ Convert global dual index to:
66
+ (local_index, eps_flag)
67
+ """
68
+ dim = Validation.dimension(dim)
69
+ global_index = int(global_index)
70
+
71
+ half = 1 << dim
72
+ total = half << 1
73
+
74
+ if global_index < 0 or global_index >= total:
75
+ raise ValueError(f"global index must be in [0, {total - 1}], got {global_index}")
76
+
77
+ return (global_index & (half - 1), 1 if global_index >= half else 0)
78
+
79
+ # ==================================================================
80
+ # Tuple input helper
81
+ # ==================================================================
82
+
83
+ def _extract_global(self, t: tuple, dim: int) -> tuple:
84
+ """
85
+ Accepts:
86
+ (sign, global_index)
87
+ (sign, local_index, eps_flag)
88
+
89
+ Returns:
90
+ (sign, global_index)
91
+ """
92
+ Validation.basis_tuple(t, allow_zero=True, allow_eps=True)
93
+
94
+ sign = int(t[0])
95
+ idx = int(t[1])
96
+
97
+ if sign == 0:
98
+ return (0, 0)
99
+
100
+ half = 1 << Validation.dimension(dim)
101
+
102
+ if len(t) >= 3:
103
+ eps = int(t[2])
104
+
105
+ # If the tuple carries an epsilon flag and the index is still
106
+ # local, promote it to the global upper half.
107
+ if eps == 1 and idx < half:
108
+ idx += half
109
+
110
+ return (sign, idx)
111
+
112
+ # ==================================================================
113
+ # Public API
114
+ # ==================================================================
115
+
116
+ def multiply(self, t1: tuple, t2: tuple, dim: int) -> tuple:
117
+ """
118
+ Multiply two dual basis element tuples.
119
+
120
+ Accepted inputs:
121
+ (sign, global_index)
122
+ (sign, local_index, eps_flag)
123
+
124
+ Returns:
125
+ (final_sign, local_index, eps_flag)
126
+ """
127
+ dim = Validation.dimension(dim)
128
+
129
+ s1, i = self._extract_global(t1, dim)
130
+ s2, j = self._extract_global(t2, dim)
131
+
132
+ if s1 == 0 or s2 == 0:
133
+ return (0, 0, 0)
134
+
135
+ sign, idx, eps = self.multiply_indices(i, j, dim)
136
+
137
+ return (s1 * s2 * sign, idx, eps)
138
+
139
+ def multiply_indices(self, i: int, j: int, dim: int) -> tuple:
140
+ """
141
+ Core dual O(1) multiplication using global indices.
142
+
143
+ Returns:
144
+ (sign, local_index, eps_flag)
145
+ """
146
+ i = int(i)
147
+ j = int(j)
148
+ dim = Validation.dimension(dim)
149
+
150
+ half = 1 << dim
151
+ total = half << 1
152
+
153
+ if i < 0 or i >= total:
154
+ raise ValueError(f"index must be in [0, {total - 1}] for dual dim={dim}, got {i}")
155
+
156
+ if j < 0 or j >= total:
157
+ raise ValueError(f"index must be in [0, {total - 1}] for dual dim={dim}, got {j}")
158
+
159
+ i_eps = i >= half
160
+ j_eps = j >= half
161
+
162
+ i_loc = i & (half - 1)
163
+ j_loc = j & (half - 1)
164
+
165
+ # --------------------------------------------------------------
166
+ # Epsilon nilpotency:
167
+ # (epsilon e_i)(epsilon e_j) = 0
168
+ # --------------------------------------------------------------
169
+ if i_eps and j_eps:
170
+ return (0, 0, 0)
171
+
172
+ # --------------------------------------------------------------
173
+ # Base multiplication
174
+ # --------------------------------------------------------------
175
+ if self._split_mode:
176
+ base_sign, local_idx = self._split.multiply_indices(i_loc, j_loc, dim)
177
+ else:
178
+ base_sign, local_idx = self._standard.multiply_indices(i_loc, j_loc)
179
+
180
+ eps_flag = 1 if (i_eps or j_eps) else 0
181
+
182
+ return (base_sign, local_idx, eps_flag)
@@ -0,0 +1,113 @@
1
+ from ..validation import Validation
2
+ from .fast_standard import FastStandard
3
+
4
+
5
+ class FastSplit:
6
+ """
7
+ Split Cayley-Dickson O(1) 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 FastStandard.
14
+
15
+ Block D:
16
+ split rules:
17
+ diagonal = +1
18
+ first row = -1
19
+ first column = +1
20
+ interior = +sigma_a
21
+ """
22
+
23
+ def __init__(self):
24
+ self._standard = FastStandard()
25
+
26
+ def multiply(self, t1: tuple, t2: tuple, dim: int | None = None) -> tuple:
27
+ """
28
+ Multiply two split basis element tuples.
29
+
30
+ t1 = (sign1, index1)
31
+ t2 = (sign2, index2)
32
+
33
+ dim:
34
+ Algebra exponent.
35
+ If None, it is inferred from the indices.
36
+
37
+ Returns:
38
+ (final_sign, index1 XOR index2)
39
+ """
40
+ Validation.basis_tuple(t1, allow_zero=True, allow_eps=False)
41
+ Validation.basis_tuple(t2, allow_zero=True, allow_eps=False)
42
+
43
+ s1, i = int(t1[0]), int(t1[1])
44
+ s2, j = int(t2[0]), int(t2[1])
45
+
46
+ if s1 == 0 or s2 == 0:
47
+ return (0, 0)
48
+
49
+ if dim is None:
50
+ dim = max(i, j).bit_length()
51
+ else:
52
+ dim = Validation.dimension(dim)
53
+
54
+ Validation.index_in_range(i, dim)
55
+ Validation.index_in_range(j, dim)
56
+
57
+ sign, idx = self.multiply_indices(i, j, dim)
58
+
59
+ return (s1 * s2 * sign, idx)
60
+
61
+ def multiply_indices(self, i: int, j: int, dim: int) -> tuple:
62
+ """
63
+ Core split O(1) multiplication.
64
+
65
+ Returns:
66
+ (sign, i XOR j)
67
+ """
68
+ i = int(i)
69
+ j = int(j)
70
+ dim = Validation.dimension(dim)
71
+
72
+ Validation.index_in_range(i, dim)
73
+ Validation.index_in_range(j, dim)
74
+
75
+ if dim == 0:
76
+ return (1, 0)
77
+
78
+ half = 1 << (dim - 1)
79
+
80
+ # --------------------------------------------------------------
81
+ # Top-level Block D:
82
+ # both indices are in the upper half.
83
+ # --------------------------------------------------------------
84
+ if i >= half and j >= half:
85
+ i_loc = i - half
86
+ j_loc = j - half
87
+
88
+ # Split Block D diagonal:
89
+ # (e_i ℓ)^2 = +e0
90
+ if i_loc == j_loc:
91
+ return (1, 0)
92
+
93
+ # Split Block D first row:
94
+ # i_loc == 0, j_loc > 0 -> sign -1
95
+ if i_loc == 0:
96
+ return (-1, i ^ j)
97
+
98
+ # Split Block D first column:
99
+ # j_loc == 0, i_loc > 0 -> sign +1
100
+ if j_loc == 0:
101
+ return (1, i ^ j)
102
+
103
+ # Split Block D interior:
104
+ # sign = sigma_a(i_loc, j_loc)
105
+ sign, _ = self._standard.multiply_indices(i_loc, j_loc)
106
+
107
+ return (sign, i ^ j)
108
+
109
+ # --------------------------------------------------------------
110
+ # Blocks A, B, C:
111
+ # identical to standard.
112
+ # --------------------------------------------------------------
113
+ return self._standard.multiply_indices(i, j)