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
hypercomplex/__init__.py
ADDED
|
@@ -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,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)
|