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,90 @@
1
+ import numpy as np
2
+
3
+ from ..validation import Validation
4
+ from .common import index_dtype, real_table
5
+
6
+
7
+ class StandardTableBuilder:
8
+ """
9
+ Standard Cayley-Dickson table builder.
10
+
11
+ OPMT Theorem 1.2:
12
+ Block a: inherits parent
13
+ Block b: transpose, index shifted
14
+ Block c: conjugation on column j
15
+ Block d: standard sign rules
16
+ """
17
+
18
+ def build(self, n: int):
19
+ """
20
+ Build the standard Cayley-Dickson table A_n.
21
+
22
+ Returns
23
+ -------
24
+ signs:
25
+ signs[i, j] = ±1
26
+ indices:
27
+ indices[i, j] = k, where e_i e_j = signs[i, j] * e_k
28
+ """
29
+ n = Validation.dimension(n)
30
+
31
+ signs, indices = real_table()
32
+
33
+ for _ in range(n):
34
+ half = signs.shape[0]
35
+ full = 2 * half
36
+ dtype = index_dtype(full)
37
+
38
+ new_signs = np.zeros((full, full), dtype=np.int8)
39
+ new_indices = np.zeros((full, full), dtype=dtype)
40
+
41
+ # ----------------------------------------------------------
42
+ # Block a:
43
+ # (e_i, 0)(e_j, 0) = (e_i e_j, 0)
44
+ # ----------------------------------------------------------
45
+ new_signs[:half, :half] = signs
46
+ new_indices[:half, :half] = indices
47
+
48
+ # ----------------------------------------------------------
49
+ # Block b:
50
+ # (e_i, 0)(0, e_j) = (0, e_j e_i)
51
+ # ----------------------------------------------------------
52
+ new_signs[:half, half:] = signs.T
53
+ np.add(
54
+ indices.T,
55
+ dtype(half),
56
+ out=new_indices[:half, half:],
57
+ casting="unsafe",
58
+ )
59
+
60
+ # ----------------------------------------------------------
61
+ # Block c:
62
+ # (0, e_i)(e_j, 0) = (0, e_i e_j*)
63
+ # Conjugation flips columns j > 0.
64
+ # ----------------------------------------------------------
65
+ block_c = signs.copy()
66
+ block_c[:, 1:] = -block_c[:, 1:]
67
+
68
+ new_signs[half:, :half] = block_c
69
+ np.add(
70
+ indices,
71
+ dtype(half),
72
+ out=new_indices[half:, :half],
73
+ casting="unsafe",
74
+ )
75
+
76
+ # ----------------------------------------------------------
77
+ # Block d:
78
+ # (0, e_i)(0, e_j) = (-e_j* e_i, 0)
79
+ # Start from -signs.T, then conjugation flips columns j > 0.
80
+ # ----------------------------------------------------------
81
+ block_d = -signs.T
82
+ block_d[:, 1:] = -block_d[:, 1:]
83
+
84
+ new_signs[half:, half:] = block_d
85
+ new_indices[half:, half:] = indices.T
86
+
87
+ signs = new_signs
88
+ indices = new_indices
89
+
90
+ return signs, indices
@@ -0,0 +1,112 @@
1
+ from numbers import Integral
2
+
3
+
4
+ class Validation:
5
+ """
6
+ Input validation for basis elements, indices, and dimensions.
7
+ """
8
+
9
+ @staticmethod
10
+ def dimension(dim) -> int:
11
+ """
12
+ Validates an algebra dimension exponent.
13
+
14
+ dim must be a non-negative integer.
15
+ dim = n means algebra dimension 2^n.
16
+ """
17
+ if isinstance(dim, bool) or not isinstance(dim, Integral):
18
+ raise TypeError(f"dim must be an integer, got {type(dim).__name__}")
19
+
20
+ dim = int(dim)
21
+
22
+ if dim < 0:
23
+ raise ValueError(f"dim must be >= 0, got {dim}")
24
+
25
+ return dim
26
+
27
+ @staticmethod
28
+ def basis_tuple(
29
+ data,
30
+ allow_zero: bool = False,
31
+ allow_eps: bool = False,
32
+ ) -> bool:
33
+ """
34
+ Validates a basis-element tuple.
35
+
36
+ Standard / Split:
37
+ (sign, index)
38
+
39
+ Dual:
40
+ (sign, index, eps_flag)
41
+
42
+ Parameters
43
+ ----------
44
+ data:
45
+ The tuple to validate.
46
+
47
+ allow_zero:
48
+ If True, sign == 0 is allowed.
49
+ This is needed for dual nilpotent zero products.
50
+
51
+ allow_eps:
52
+ If True, 3-tuples with an epsilon flag are allowed.
53
+ """
54
+ if not isinstance(data, tuple):
55
+ raise TypeError(f"Expected tuple, got {type(data).__name__}")
56
+
57
+ if len(data) not in (2, 3):
58
+ raise ValueError(f"Basis tuple must have length 2 or 3, got {len(data)}")
59
+
60
+ if len(data) == 3 and not allow_eps:
61
+ raise ValueError("3-tuple epsilon form is not allowed here")
62
+
63
+ sign = data[0]
64
+ index = data[1]
65
+
66
+ if isinstance(sign, bool) or not isinstance(sign, Integral):
67
+ raise TypeError(f"sign must be an integer, got {type(sign).__name__}")
68
+
69
+ if isinstance(index, bool) or not isinstance(index, Integral):
70
+ raise TypeError(f"index must be an integer, got {type(index).__name__}")
71
+
72
+ sign = int(sign)
73
+ index = int(index)
74
+
75
+ if index < 0:
76
+ raise ValueError(f"index must be >= 0, got {index}")
77
+
78
+ allowed_signs = (-1, 0, 1) if allow_zero else (-1, 1)
79
+
80
+ if sign not in allowed_signs:
81
+ raise ValueError(f"sign must be in {allowed_signs}, got {sign}")
82
+
83
+ if len(data) == 3:
84
+ eps = data[2]
85
+
86
+ if isinstance(eps, bool) or not isinstance(eps, Integral):
87
+ raise TypeError(f"eps must be an integer, got {type(eps).__name__}")
88
+
89
+ eps = int(eps)
90
+
91
+ if eps not in (0, 1):
92
+ raise ValueError(f"eps must be 0 or 1, got {eps}")
93
+
94
+ return True
95
+
96
+ @staticmethod
97
+ def index_in_range(index, dim: int) -> int:
98
+ """
99
+ Validates that index is inside [0, 2^dim - 1].
100
+ """
101
+ if isinstance(index, bool) or not isinstance(index, Integral):
102
+ raise TypeError(f"index must be an integer, got {type(index).__name__}")
103
+
104
+ index = int(index)
105
+ dim = Validation.dimension(dim)
106
+
107
+ size = 1 << dim
108
+
109
+ if index < 0 or index >= size:
110
+ raise ValueError(f"index must be in [0, {size - 1}] for dim={dim}, got {index}")
111
+
112
+ return index
hypercomplex/facade.py ADDED
@@ -0,0 +1,310 @@
1
+ from .core.table_builder import (
2
+ StandardTableBuilder,
3
+ SplitTableBuilder,
4
+ DualTableBuilder,
5
+ )
6
+
7
+ from .core.holographic import (
8
+ StandardHolographic,
9
+ SplitHolographic,
10
+ DualHolographic,
11
+ )
12
+
13
+ from .core.fast import (
14
+ FastStandard,
15
+ FastSplit,
16
+ FastDual,
17
+ )
18
+
19
+ from .core.validation import Validation
20
+
21
+ from .printer import CDFormat, CDTablePrinter
22
+
23
+
24
+ # ----------------------------------------------------------------------
25
+ # Internal singletons
26
+ # ----------------------------------------------------------------------
27
+
28
+ _standard_table = StandardTableBuilder()
29
+ _split_table = SplitTableBuilder()
30
+ _dual_table = DualTableBuilder()
31
+
32
+ _standard_holo = StandardHolographic()
33
+ _split_holo = SplitHolographic()
34
+ _dual_holo = DualHolographic(split=False)
35
+ _dual_split_holo = DualHolographic(split=True)
36
+
37
+ _standard_fast = FastStandard()
38
+ _split_fast = FastSplit()
39
+ _dual_fast = FastDual(split=False)
40
+ _dual_split_fast = FastDual(split=True)
41
+
42
+
43
+ # ----------------------------------------------------------------------
44
+ # Helpers
45
+ # ----------------------------------------------------------------------
46
+
47
+ def _normalize_kind(kind: str) -> str:
48
+ if not isinstance(kind, str):
49
+ raise TypeError("kind must be a string")
50
+
51
+ kind = kind.strip().lower()
52
+
53
+ aliases = {
54
+ "standard": "standard",
55
+ "std": "standard",
56
+ "ordinary": "standard",
57
+ "o": "standard",
58
+
59
+ "split": "split",
60
+ "s": "split",
61
+
62
+ "dual": "dual",
63
+ "dual_standard": "dual",
64
+ "d": "dual",
65
+
66
+ "dual_split": "dual_split",
67
+ "split_dual": "dual_split",
68
+ "ds": "dual_split",
69
+ }
70
+
71
+ if kind not in aliases:
72
+ raise ValueError(
73
+ f"Unknown algebra kind '{kind}'. "
74
+ "Valid kinds: standard, split, dual, dual_split"
75
+ )
76
+
77
+ return aliases[kind]
78
+
79
+
80
+ def _normalize_engine(engine: str) -> str:
81
+ if not isinstance(engine, str):
82
+ raise TypeError("engine must be a string")
83
+
84
+ engine = engine.strip().lower()
85
+
86
+ aliases = {
87
+ "fast": "fast",
88
+ "constant": "fast",
89
+ "fast": "fast",
90
+ "bitwise": "fast",
91
+
92
+ "holographic": "holographic",
93
+ "on": "holographic",
94
+ "o(n)": "holographic",
95
+ "descent": "holographic",
96
+ }
97
+
98
+ if engine not in aliases:
99
+ raise ValueError(
100
+ f"Unknown engine '{engine}'. "
101
+ "Valid engines: fast, holographic"
102
+ )
103
+
104
+ return aliases[engine]
105
+
106
+
107
+ def _unpack_table(table):
108
+ """
109
+ Accepts:
110
+ (signs, indices)
111
+ (signs, indices, eps)
112
+ """
113
+ if len(table) == 2:
114
+ signs, indices = table
115
+ return signs, indices, None
116
+
117
+ if len(table) == 3:
118
+ signs, indices, eps = table
119
+ return signs, indices, eps
120
+
121
+ raise ValueError(
122
+ "table must be (signs, indices) or (signs, indices, eps)"
123
+ )
124
+
125
+
126
+ def _as_dual_global(t: tuple, dim: int) -> tuple:
127
+ """
128
+ Converts a dual input tuple into a global 2-tuple.
129
+
130
+ Accepts:
131
+ (sign, global_index)
132
+ (sign, local_index, eps_flag)
133
+
134
+ Returns:
135
+ (sign, global_index)
136
+ """
137
+ sign = int(t[0])
138
+ idx = int(t[1])
139
+
140
+ if sign == 0:
141
+ return (0, 0)
142
+
143
+ half = 1 << Validation.dimension(dim)
144
+
145
+ if len(t) >= 3:
146
+ eps = int(t[2])
147
+
148
+ if eps == 1 and idx < half:
149
+ idx += half
150
+
151
+ return (sign, idx)
152
+
153
+
154
+ # ----------------------------------------------------------------------
155
+ # Public simple API
156
+ # ----------------------------------------------------------------------
157
+
158
+ def build_table(kind: str, n: int):
159
+ """
160
+ Build a multiplication table.
161
+
162
+ kind:
163
+ "standard"
164
+ "split"
165
+ "dual"
166
+ "dual_split"
167
+ """
168
+ kind = _normalize_kind(kind)
169
+ n = int(n)
170
+
171
+ if kind == "standard":
172
+ return _standard_table.build(n)
173
+
174
+ if kind == "split":
175
+ return _split_table.build(n)
176
+
177
+ if kind == "dual":
178
+ return _dual_table.build(n, split=False)
179
+
180
+ if kind == "dual_split":
181
+ return _dual_table.build(n, split=True)
182
+
183
+ raise ValueError(f"Unknown algebra kind '{kind}'")
184
+
185
+
186
+ def multiply(
187
+ kind: str,
188
+ a: tuple,
189
+ b: tuple,
190
+ dim: int | None = None,
191
+ engine: str = "fast",
192
+ ) -> tuple:
193
+ """
194
+ Multiply two basis elements.
195
+
196
+ kind:
197
+ "standard"
198
+ "split"
199
+ "dual"
200
+ "dual_split"
201
+
202
+ engine:
203
+ "fast" default, fastest
204
+ "holographic" O(n) descent, useful for verification
205
+
206
+ dim:
207
+ Required for dual and dual_split.
208
+ Optional for split.
209
+ """
210
+ kind = _normalize_kind(kind)
211
+ engine = _normalize_engine(engine)
212
+
213
+ # Zero propagation
214
+ if int(a[0]) == 0 or int(b[0]) == 0:
215
+ if kind in ("dual", "dual_split"):
216
+ return (0, 0, 0)
217
+ return (0, 0)
218
+
219
+ # Dual inputs may be local 3-tuples: (sign, local_index, eps)
220
+ if kind in ("dual", "dual_split"):
221
+ if dim is None:
222
+ raise ValueError("dim is required for dual multiplication")
223
+
224
+ a = _as_dual_global(a, dim)
225
+ b = _as_dual_global(b, dim)
226
+
227
+ # ------------------------------------------------------------------
228
+ # O(1) engine
229
+ # ------------------------------------------------------------------
230
+ if engine == "fast":
231
+ if kind == "standard":
232
+ return _standard_fast.multiply(a, b)
233
+
234
+ if kind == "split":
235
+ return _split_fast.multiply(a, b, dim)
236
+
237
+ if kind == "dual":
238
+ return _dual_fast.multiply(a, b, dim)
239
+
240
+ if kind == "dual_split":
241
+ return _dual_split_fast.multiply(a, b, dim)
242
+
243
+ # ------------------------------------------------------------------
244
+ # Holographic O(n) engine
245
+ # ------------------------------------------------------------------
246
+ if engine == "holographic":
247
+ if kind == "standard":
248
+ return _standard_holo.multiply(a, b)
249
+
250
+ if kind == "split":
251
+ if dim is None:
252
+ dim = max(int(a[1]), int(b[1])).bit_length()
253
+ return _split_holo.multiply(a, b, dim)
254
+
255
+ if kind == "dual":
256
+ return _dual_holo.multiply(a, b, dim)
257
+
258
+ if kind == "dual_split":
259
+ return _dual_split_holo.multiply(a, b, dim)
260
+
261
+ raise ValueError(f"Unknown engine '{engine}'")
262
+
263
+
264
+ def format_element(element: tuple, mode: str = "integer") -> str:
265
+ """
266
+ Format a basis element tuple.
267
+ """
268
+ return CDFormat.format_element(element, mode=mode)
269
+
270
+
271
+ def print_table(
272
+ table,
273
+ title: str | None = None,
274
+ limit: int | None = None,
275
+ mode: str = "integer",
276
+ ):
277
+ """
278
+ Print a table built by build_table().
279
+ """
280
+ signs, indices, eps = _unpack_table(table)
281
+
282
+ return CDTablePrinter.print_table(
283
+ signs,
284
+ indices,
285
+ eps=eps,
286
+ title=title,
287
+ limit=limit,
288
+ mode=mode,
289
+ )
290
+
291
+
292
+ def export_csv(
293
+ path: str,
294
+ table,
295
+ mode: str = "integer",
296
+ csv_mode: str = "matrix",
297
+ ) -> str:
298
+ """
299
+ Export a table built by build_table().
300
+ """
301
+ signs, indices, eps = _unpack_table(table)
302
+
303
+ return CDTablePrinter.export_csv(
304
+ path,
305
+ signs,
306
+ indices,
307
+ eps=eps,
308
+ mode=mode,
309
+ csv_mode=csv_mode,
310
+ )
@@ -0,0 +1,7 @@
1
+ from .cd_format import CDFormat
2
+ from .cd_table_printer import CDTablePrinter
3
+
4
+ __all__ = [
5
+ "CDFormat",
6
+ "CDTablePrinter",
7
+ ]