hypercomplex-engine 0.0.4__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 +65 -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 +111 -0
- hypercomplex/core/fast/fast_standard.py +124 -0
- hypercomplex/core/holographic/__init__.py +9 -0
- hypercomplex/core/holographic/dual.py +89 -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 +102 -0
- hypercomplex/core/table_builder/standard.py +90 -0
- hypercomplex/core/validation.py +112 -0
- hypercomplex/facade.py +360 -0
- hypercomplex/printer/__init__.py +7 -0
- hypercomplex/printer/cd_format.py +209 -0
- hypercomplex/printer/cd_table_printer.py +204 -0
- hypercomplex_engine-0.0.4.dist-info/METADATA +1066 -0
- hypercomplex_engine-0.0.4.dist-info/RECORD +28 -0
- hypercomplex_engine-0.0.4.dist-info/WHEEL +5 -0
- hypercomplex_engine-0.0.4.dist-info/licenses/LICENSE +21 -0
- hypercomplex_engine-0.0.4.dist-info/top_level.txt +1 -0
|
@@ -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,360 @@
|
|
|
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
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from .core.table_builder.common import index_dtype
|
|
22
|
+
from .core.validation import Validation
|
|
23
|
+
|
|
24
|
+
from .printer import CDFormat, CDTablePrinter
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ----------------------------------------------------------------------
|
|
28
|
+
# Internal singletons
|
|
29
|
+
# ----------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
_standard_table = StandardTableBuilder()
|
|
32
|
+
_split_table = SplitTableBuilder()
|
|
33
|
+
_dual_table = DualTableBuilder()
|
|
34
|
+
|
|
35
|
+
_standard_holo = StandardHolographic()
|
|
36
|
+
_split_holo = SplitHolographic()
|
|
37
|
+
_dual_holo = DualHolographic(split=False)
|
|
38
|
+
_dual_split_holo = DualHolographic(split=True)
|
|
39
|
+
|
|
40
|
+
_standard_fast = FastStandard()
|
|
41
|
+
_split_fast = FastSplit()
|
|
42
|
+
_dual_fast = FastDual(split=False)
|
|
43
|
+
_dual_split_fast = FastDual(split=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ----------------------------------------------------------------------
|
|
47
|
+
# Helpers
|
|
48
|
+
# ----------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
def _normalize_kind(kind: str) -> str:
|
|
51
|
+
if not isinstance(kind, str):
|
|
52
|
+
raise TypeError("kind must be a string")
|
|
53
|
+
|
|
54
|
+
kind = kind.strip().lower()
|
|
55
|
+
|
|
56
|
+
aliases = {
|
|
57
|
+
"standard": "standard",
|
|
58
|
+
"std": "standard",
|
|
59
|
+
"ordinary": "standard",
|
|
60
|
+
"o": "standard",
|
|
61
|
+
|
|
62
|
+
"split": "split",
|
|
63
|
+
"s": "split",
|
|
64
|
+
|
|
65
|
+
"dual": "dual",
|
|
66
|
+
"dual_standard": "dual",
|
|
67
|
+
"d": "dual",
|
|
68
|
+
|
|
69
|
+
"dual_split": "dual_split",
|
|
70
|
+
"split_dual": "dual_split",
|
|
71
|
+
"ds": "dual_split",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if kind not in aliases:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Unknown algebra kind '{kind}'. "
|
|
77
|
+
"Valid kinds: standard, split, dual, dual_split"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return aliases[kind]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _normalize_engine(engine: str) -> str:
|
|
84
|
+
if not isinstance(engine, str):
|
|
85
|
+
raise TypeError("engine must be a string")
|
|
86
|
+
|
|
87
|
+
engine = engine.strip().lower()
|
|
88
|
+
|
|
89
|
+
aliases = {
|
|
90
|
+
"fast": "fast",
|
|
91
|
+
"constant": "fast",
|
|
92
|
+
"fast": "fast",
|
|
93
|
+
"bitwise": "fast",
|
|
94
|
+
"o1":"fast",
|
|
95
|
+
|
|
96
|
+
"holographic": "holographic",
|
|
97
|
+
"on": "holographic",
|
|
98
|
+
"o(n)": "holographic",
|
|
99
|
+
"descent": "holographic",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if engine not in aliases:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"Unknown engine '{engine}'. "
|
|
105
|
+
"Valid engines: fast, holographic"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return aliases[engine]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _unpack_table(table):
|
|
112
|
+
"""
|
|
113
|
+
Accepts:
|
|
114
|
+
(signs, indices)
|
|
115
|
+
(signs, indices, eps)
|
|
116
|
+
"""
|
|
117
|
+
if len(table) == 2:
|
|
118
|
+
signs, indices = table
|
|
119
|
+
return signs, indices, None
|
|
120
|
+
|
|
121
|
+
if len(table) == 3:
|
|
122
|
+
signs, indices, eps = table
|
|
123
|
+
return signs, indices, eps
|
|
124
|
+
|
|
125
|
+
raise ValueError(
|
|
126
|
+
"table must be (signs, indices) or (signs, indices, eps)"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _as_dual_global(t: tuple, dim: int) -> tuple:
|
|
131
|
+
"""
|
|
132
|
+
Converts a dual input tuple into a global 2-tuple.
|
|
133
|
+
|
|
134
|
+
Accepts:
|
|
135
|
+
(sign, global_index)
|
|
136
|
+
(sign, local_index, eps_flag)
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
(sign, global_index)
|
|
140
|
+
"""
|
|
141
|
+
sign = int(t[0])
|
|
142
|
+
idx = int(t[1])
|
|
143
|
+
|
|
144
|
+
if sign == 0:
|
|
145
|
+
return (0, 0)
|
|
146
|
+
|
|
147
|
+
half = 1 << Validation.dimension(dim)
|
|
148
|
+
|
|
149
|
+
if len(t) >= 3:
|
|
150
|
+
eps = int(t[2])
|
|
151
|
+
|
|
152
|
+
if eps == 1 and idx < half:
|
|
153
|
+
idx += half
|
|
154
|
+
|
|
155
|
+
return (sign, idx)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ----------------------------------------------------------------------
|
|
159
|
+
# Public simple API
|
|
160
|
+
# ----------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
# Default memory budget for build_table (bytes of final table data).
|
|
163
|
+
# 256 MiB allows standard/split up to n=13 and dual/dual_split up to n=12.
|
|
164
|
+
DEFAULT_MAX_TABLE_BYTES = 1 << 28
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def estimate_table_bytes(kind: str, n: int) -> int:
|
|
168
|
+
"""
|
|
169
|
+
Estimate the bytes of the final table arrays that build_table(kind, n)
|
|
170
|
+
would allocate. Peak usage while building is roughly 1.5x this value.
|
|
171
|
+
|
|
172
|
+
Dual kinds are one doubling larger than the same n for standard/split,
|
|
173
|
+
and carry an extra uint8 epsilon array.
|
|
174
|
+
"""
|
|
175
|
+
kind = _normalize_kind(kind)
|
|
176
|
+
n = Validation.dimension(n)
|
|
177
|
+
|
|
178
|
+
is_dual = kind in ("dual", "dual_split")
|
|
179
|
+
full = 1 << (n + 1 if is_dual else n)
|
|
180
|
+
|
|
181
|
+
per_entry = 1 + np.dtype(index_dtype(full)).itemsize # signs + indices
|
|
182
|
+
if is_dual:
|
|
183
|
+
per_entry += 1 # eps array
|
|
184
|
+
|
|
185
|
+
return full * full * per_entry
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def build_table(kind: str, n: int, max_bytes: int | None = DEFAULT_MAX_TABLE_BYTES):
|
|
189
|
+
"""
|
|
190
|
+
Build a multiplication table.
|
|
191
|
+
|
|
192
|
+
kind:
|
|
193
|
+
"standard"
|
|
194
|
+
"split"
|
|
195
|
+
"dual"
|
|
196
|
+
"dual_split"
|
|
197
|
+
|
|
198
|
+
n:
|
|
199
|
+
Dimension exponent (algebra dimension 2**n). Must be an integer.
|
|
200
|
+
|
|
201
|
+
max_bytes:
|
|
202
|
+
Memory budget for the final table arrays. If the estimated size
|
|
203
|
+
exceeds it, ValueError is raised instead of attempting the build.
|
|
204
|
+
Pass a larger int on a strong machine, or None to disable the check.
|
|
205
|
+
Use estimate_table_bytes(kind, n) to check a size in advance, or
|
|
206
|
+
multiply(..., engine="fast") to avoid tables entirely.
|
|
207
|
+
"""
|
|
208
|
+
kind = _normalize_kind(kind)
|
|
209
|
+
n = Validation.dimension(n)
|
|
210
|
+
|
|
211
|
+
if max_bytes is not None:
|
|
212
|
+
needed = estimate_table_bytes(kind, n)
|
|
213
|
+
if needed > max_bytes:
|
|
214
|
+
raise ValueError(
|
|
215
|
+
f"build_table({kind!r}, {n}) needs about {needed / 2**20:,.1f} MiB "
|
|
216
|
+
f"(limit {max_bytes / 2**20:,.1f} MiB). Pass a larger max_bytes, "
|
|
217
|
+
"max_bytes=None to disable the check, or use "
|
|
218
|
+
'multiply(..., engine="fast") which needs no table.'
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if kind == "standard":
|
|
222
|
+
return _standard_table.build(n)
|
|
223
|
+
|
|
224
|
+
if kind == "split":
|
|
225
|
+
return _split_table.build(n)
|
|
226
|
+
|
|
227
|
+
if kind == "dual":
|
|
228
|
+
return _dual_table.build(n, split=False)
|
|
229
|
+
|
|
230
|
+
if kind == "dual_split":
|
|
231
|
+
return _dual_table.build(n, split=True)
|
|
232
|
+
|
|
233
|
+
raise ValueError(f"Unknown algebra kind '{kind}'")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def multiply(
|
|
237
|
+
kind: str,
|
|
238
|
+
a: tuple,
|
|
239
|
+
b: tuple,
|
|
240
|
+
dim: int | None = None,
|
|
241
|
+
engine: str = "fast",
|
|
242
|
+
) -> tuple:
|
|
243
|
+
"""
|
|
244
|
+
Multiply two basis elements.
|
|
245
|
+
|
|
246
|
+
kind:
|
|
247
|
+
"standard"
|
|
248
|
+
"split"
|
|
249
|
+
"dual"
|
|
250
|
+
"dual_split"
|
|
251
|
+
|
|
252
|
+
engine:
|
|
253
|
+
"fast" default, fastest
|
|
254
|
+
"holographic" O(n) descent, useful for verification
|
|
255
|
+
|
|
256
|
+
dim:
|
|
257
|
+
Required for dual and dual_split.
|
|
258
|
+
Optional for split.
|
|
259
|
+
"""
|
|
260
|
+
kind = _normalize_kind(kind)
|
|
261
|
+
engine = _normalize_engine(engine)
|
|
262
|
+
|
|
263
|
+
# Zero propagation
|
|
264
|
+
if int(a[0]) == 0 or int(b[0]) == 0:
|
|
265
|
+
if kind in ("dual", "dual_split"):
|
|
266
|
+
return (0, 0, 0)
|
|
267
|
+
return (0, 0)
|
|
268
|
+
|
|
269
|
+
# Dual inputs may be local 3-tuples: (sign, local_index, eps)
|
|
270
|
+
if kind in ("dual", "dual_split"):
|
|
271
|
+
if dim is None:
|
|
272
|
+
raise ValueError("dim is required for dual multiplication")
|
|
273
|
+
|
|
274
|
+
a = _as_dual_global(a, dim)
|
|
275
|
+
b = _as_dual_global(b, dim)
|
|
276
|
+
|
|
277
|
+
# ------------------------------------------------------------------
|
|
278
|
+
# O(1) engine
|
|
279
|
+
# ------------------------------------------------------------------
|
|
280
|
+
if engine == "fast":
|
|
281
|
+
if kind == "standard":
|
|
282
|
+
return _standard_fast.multiply(a, b)
|
|
283
|
+
|
|
284
|
+
if kind == "split":
|
|
285
|
+
return _split_fast.multiply(a, b, dim)
|
|
286
|
+
|
|
287
|
+
if kind == "dual":
|
|
288
|
+
return _dual_fast.multiply(a, b, dim)
|
|
289
|
+
|
|
290
|
+
if kind == "dual_split":
|
|
291
|
+
return _dual_split_fast.multiply(a, b, dim)
|
|
292
|
+
|
|
293
|
+
# ------------------------------------------------------------------
|
|
294
|
+
# Holographic O(n) engine
|
|
295
|
+
# ------------------------------------------------------------------
|
|
296
|
+
if engine == "holographic":
|
|
297
|
+
if kind == "standard":
|
|
298
|
+
return _standard_holo.multiply(a, b)
|
|
299
|
+
|
|
300
|
+
if kind == "split":
|
|
301
|
+
if dim is None:
|
|
302
|
+
dim = max(int(a[1]), int(b[1])).bit_length()
|
|
303
|
+
return _split_holo.multiply(a, b, dim)
|
|
304
|
+
|
|
305
|
+
if kind == "dual":
|
|
306
|
+
return _dual_holo.multiply(a, b, dim)
|
|
307
|
+
|
|
308
|
+
if kind == "dual_split":
|
|
309
|
+
return _dual_split_holo.multiply(a, b, dim)
|
|
310
|
+
|
|
311
|
+
raise ValueError(f"Unknown engine '{engine}'")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def format_element(element: tuple, mode: str = "integer") -> str:
|
|
315
|
+
"""
|
|
316
|
+
Format a basis element tuple.
|
|
317
|
+
"""
|
|
318
|
+
return CDFormat.format_element(element, mode=mode)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def print_table(
|
|
322
|
+
table,
|
|
323
|
+
title: str | None = None,
|
|
324
|
+
limit: int | None = None,
|
|
325
|
+
mode: str = "integer",
|
|
326
|
+
):
|
|
327
|
+
"""
|
|
328
|
+
Print a table built by build_table().
|
|
329
|
+
"""
|
|
330
|
+
signs, indices, eps = _unpack_table(table)
|
|
331
|
+
|
|
332
|
+
return CDTablePrinter.print_table(
|
|
333
|
+
signs,
|
|
334
|
+
indices,
|
|
335
|
+
eps=eps,
|
|
336
|
+
title=title,
|
|
337
|
+
limit=limit,
|
|
338
|
+
mode=mode,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def export_csv(
|
|
343
|
+
path: str,
|
|
344
|
+
table,
|
|
345
|
+
mode: str = "integer",
|
|
346
|
+
csv_mode: str = "matrix",
|
|
347
|
+
) -> str:
|
|
348
|
+
"""
|
|
349
|
+
Export a table built by build_table().
|
|
350
|
+
"""
|
|
351
|
+
signs, indices, eps = _unpack_table(table)
|
|
352
|
+
|
|
353
|
+
return CDTablePrinter.export_csv(
|
|
354
|
+
path,
|
|
355
|
+
signs,
|
|
356
|
+
indices,
|
|
357
|
+
eps=eps,
|
|
358
|
+
mode=mode,
|
|
359
|
+
csv_mode=csv_mode,
|
|
360
|
+
)
|