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
|
@@ -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
|
+
)
|