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,209 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from ..core import BasisNotation
|
|
3
|
+
except ImportError:
|
|
4
|
+
try:
|
|
5
|
+
from ..core.Basis_notation import BasisNotation
|
|
6
|
+
except ImportError:
|
|
7
|
+
from ..core.basis_notation import BasisNotation
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CDFormat:
|
|
11
|
+
"""
|
|
12
|
+
Formatting engine for hypercomplex elements.
|
|
13
|
+
|
|
14
|
+
This class handles all string formatting for:
|
|
15
|
+
- Single basis elements (from holo / O1 multipliers)
|
|
16
|
+
- Table cells (from table builders)
|
|
17
|
+
- Row/column labels
|
|
18
|
+
|
|
19
|
+
It does NOT handle printing or file IO.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
VALID_MODES = {
|
|
23
|
+
"integer",
|
|
24
|
+
"graded",
|
|
25
|
+
"latex",
|
|
26
|
+
"latex_integer",
|
|
27
|
+
"latex_graded",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# ==================================================================
|
|
31
|
+
# MODE HELPERS
|
|
32
|
+
# ==================================================================
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def validate_mode(cls, mode: str) -> None:
|
|
36
|
+
if mode not in cls.VALID_MODES:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"Invalid mode '{mode}'. "
|
|
39
|
+
f"Valid modes: {sorted(cls.VALID_MODES)}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def is_latex(mode: str) -> bool:
|
|
44
|
+
return mode.startswith("latex")
|
|
45
|
+
|
|
46
|
+
# ==================================================================
|
|
47
|
+
# CORE FORMATTING
|
|
48
|
+
# ==================================================================
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def format_basis(cls, index: int, mode: str) -> str:
|
|
52
|
+
"""
|
|
53
|
+
Format only the basis element.
|
|
54
|
+
|
|
55
|
+
integer: e0, e1, e2, ...
|
|
56
|
+
graded: 1, o1, o2, o12, ...
|
|
57
|
+
latex_integer: e_{0}, e_{1}, ...
|
|
58
|
+
latex_graded: 1, o_{1}, o_{2}, o_{12}, ...
|
|
59
|
+
"""
|
|
60
|
+
cls.validate_mode(mode)
|
|
61
|
+
index = int(index)
|
|
62
|
+
|
|
63
|
+
if mode == "integer":
|
|
64
|
+
return f"e{index}"
|
|
65
|
+
|
|
66
|
+
if mode == "graded":
|
|
67
|
+
return BasisNotation.to_graded_str(index)
|
|
68
|
+
|
|
69
|
+
if mode == "latex":
|
|
70
|
+
return BasisNotation.to_latex(index, mode="integer")
|
|
71
|
+
|
|
72
|
+
if mode == "latex_integer":
|
|
73
|
+
return BasisNotation.to_latex(index, mode="integer")
|
|
74
|
+
|
|
75
|
+
if mode == "latex_graded":
|
|
76
|
+
return BasisNotation.to_latex(index, mode="graded")
|
|
77
|
+
|
|
78
|
+
raise ValueError(f"Invalid mode '{mode}'")
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def format_entry(
|
|
82
|
+
cls,
|
|
83
|
+
sign: int,
|
|
84
|
+
index: int,
|
|
85
|
+
eps: int = 0,
|
|
86
|
+
mode: str = "integer",
|
|
87
|
+
) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Format a single element from its components.
|
|
90
|
+
|
|
91
|
+
This is the low-level formatter used by both
|
|
92
|
+
format_element and the table printer.
|
|
93
|
+
"""
|
|
94
|
+
cls.validate_mode(mode)
|
|
95
|
+
|
|
96
|
+
sign = int(sign)
|
|
97
|
+
index = int(index)
|
|
98
|
+
eps = int(eps)
|
|
99
|
+
|
|
100
|
+
if sign == 0:
|
|
101
|
+
return "0"
|
|
102
|
+
|
|
103
|
+
prefix = "+" if sign > 0 else "-"
|
|
104
|
+
base = cls.format_basis(index, mode)
|
|
105
|
+
|
|
106
|
+
if eps == 0:
|
|
107
|
+
return prefix + base
|
|
108
|
+
|
|
109
|
+
latex = cls.is_latex(mode)
|
|
110
|
+
eps_label = "\\epsilon" if latex else "eps"
|
|
111
|
+
|
|
112
|
+
# epsilon alone: +eps or +\epsilon
|
|
113
|
+
if index == 0:
|
|
114
|
+
return prefix + eps_label
|
|
115
|
+
|
|
116
|
+
if latex:
|
|
117
|
+
return f"{prefix}{base}{eps_label}"
|
|
118
|
+
|
|
119
|
+
return f"{prefix}{base}*{eps_label}"
|
|
120
|
+
|
|
121
|
+
# ==================================================================
|
|
122
|
+
# SINGLE ELEMENT FORMATTING (for holo / O1 output)
|
|
123
|
+
# ==================================================================
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def format_element(cls, element: tuple, mode: str = "integer") -> str:
|
|
127
|
+
"""
|
|
128
|
+
Format a basis element tuple returned by holo or O1 multipliers.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
element : tuple
|
|
133
|
+
(sign, index) for standard / split
|
|
134
|
+
(sign, index, eps) for dual
|
|
135
|
+
|
|
136
|
+
mode : str
|
|
137
|
+
integer, graded, latex, latex_integer, latex_graded
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
str
|
|
142
|
+
Formatted string.
|
|
143
|
+
|
|
144
|
+
Examples
|
|
145
|
+
--------
|
|
146
|
+
>>> CDFormat.format_element((1, 3), mode="integer")
|
|
147
|
+
'+e3'
|
|
148
|
+
|
|
149
|
+
>>> CDFormat.format_element((-1, 5), mode="graded")
|
|
150
|
+
'-o13'
|
|
151
|
+
|
|
152
|
+
>>> CDFormat.format_element((1, 2, 1), mode="integer")
|
|
153
|
+
'+e2*eps'
|
|
154
|
+
|
|
155
|
+
>>> CDFormat.format_element((0, 0, 1), mode="integer")
|
|
156
|
+
'0'
|
|
157
|
+
"""
|
|
158
|
+
if not isinstance(element, tuple):
|
|
159
|
+
raise TypeError(f"element must be a tuple, got {type(element).__name__}")
|
|
160
|
+
|
|
161
|
+
if len(element) == 2:
|
|
162
|
+
sign, index = element
|
|
163
|
+
return cls.format_entry(sign, index, eps=0, mode=mode)
|
|
164
|
+
|
|
165
|
+
if len(element) == 3:
|
|
166
|
+
sign, index, eps = element
|
|
167
|
+
return cls.format_entry(sign, index, eps=eps, mode=mode)
|
|
168
|
+
|
|
169
|
+
raise ValueError(
|
|
170
|
+
f"element must be a 2-tuple or 3-tuple, got {len(element)}-tuple"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# ==================================================================
|
|
174
|
+
# TABLE LABELS
|
|
175
|
+
# ==================================================================
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def basis_labels(cls, dim: int, dual: bool, mode: str) -> list:
|
|
179
|
+
"""
|
|
180
|
+
Build row/column labels for a multiplication table.
|
|
181
|
+
"""
|
|
182
|
+
cls.validate_mode(mode)
|
|
183
|
+
|
|
184
|
+
base_dim = dim // 2 if dual else dim
|
|
185
|
+
|
|
186
|
+
labels = [
|
|
187
|
+
cls.format_basis(i, mode)
|
|
188
|
+
for i in range(base_dim)
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
if not dual:
|
|
192
|
+
return labels
|
|
193
|
+
|
|
194
|
+
latex = cls.is_latex(mode)
|
|
195
|
+
eps_label = "\\epsilon" if latex else "eps"
|
|
196
|
+
|
|
197
|
+
# epsilon itself: eps*e0
|
|
198
|
+
labels.append(eps_label)
|
|
199
|
+
|
|
200
|
+
# epsilon times non-scalar basis elements
|
|
201
|
+
for i in range(1, base_dim):
|
|
202
|
+
base = cls.format_basis(i, mode)
|
|
203
|
+
|
|
204
|
+
if latex:
|
|
205
|
+
labels.append(f"{base}{eps_label}")
|
|
206
|
+
else:
|
|
207
|
+
labels.append(f"{base}*{eps_label}")
|
|
208
|
+
|
|
209
|
+
return labels
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from .cd_format import CDFormat
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CDTablePrinter:
|
|
5
|
+
"""
|
|
6
|
+
Printer / CSV exporter for hypercomplex multiplication tables.
|
|
7
|
+
|
|
8
|
+
This class handles only:
|
|
9
|
+
- terminal printing
|
|
10
|
+
- CSV export
|
|
11
|
+
|
|
12
|
+
All formatting is delegated to CDFormat.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def print_table(
|
|
17
|
+
signs,
|
|
18
|
+
indices,
|
|
19
|
+
eps=None,
|
|
20
|
+
title: str | None = None,
|
|
21
|
+
limit: int | None = None,
|
|
22
|
+
mode: str = "integer",
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Print a multiplication table to the terminal.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
signs:
|
|
30
|
+
2D array of signs.
|
|
31
|
+
|
|
32
|
+
indices:
|
|
33
|
+
2D array of basis indices.
|
|
34
|
+
|
|
35
|
+
eps:
|
|
36
|
+
Optional 2D array for dual tables.
|
|
37
|
+
If provided, the table is treated as dual.
|
|
38
|
+
|
|
39
|
+
title:
|
|
40
|
+
Optional title.
|
|
41
|
+
|
|
42
|
+
limit:
|
|
43
|
+
Optional maximum number of rows/columns to display.
|
|
44
|
+
|
|
45
|
+
mode:
|
|
46
|
+
integer, graded, latex, latex_integer, latex_graded
|
|
47
|
+
"""
|
|
48
|
+
CDFormat.validate_mode(mode)
|
|
49
|
+
|
|
50
|
+
dim = signs.shape[0]
|
|
51
|
+
lim = dim if limit is None else min(limit, dim)
|
|
52
|
+
|
|
53
|
+
if lim == 0:
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
dual = eps is not None
|
|
57
|
+
|
|
58
|
+
labels = CDFormat.basis_labels(
|
|
59
|
+
dim,
|
|
60
|
+
dual=dual,
|
|
61
|
+
mode=mode,
|
|
62
|
+
)[:lim]
|
|
63
|
+
|
|
64
|
+
rows = []
|
|
65
|
+
|
|
66
|
+
for i in range(lim):
|
|
67
|
+
row = []
|
|
68
|
+
|
|
69
|
+
for j in range(lim):
|
|
70
|
+
e = 0 if eps is None else int(eps[i, j])
|
|
71
|
+
|
|
72
|
+
row.append(
|
|
73
|
+
CDFormat.format_entry(
|
|
74
|
+
int(signs[i, j]),
|
|
75
|
+
int(indices[i, j]),
|
|
76
|
+
e,
|
|
77
|
+
mode,
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
rows.append(row)
|
|
82
|
+
|
|
83
|
+
if title is None:
|
|
84
|
+
title = "Dual Table" if dual else "Table"
|
|
85
|
+
|
|
86
|
+
print(title)
|
|
87
|
+
|
|
88
|
+
cell_width = max(len(cell) for row in rows for cell in row)
|
|
89
|
+
label_width = max(len(label) for label in labels)
|
|
90
|
+
|
|
91
|
+
# Header
|
|
92
|
+
print(
|
|
93
|
+
" " * (label_width + 3)
|
|
94
|
+
+ " ".join(f"{label:>{cell_width}}" for label in labels)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Rows
|
|
98
|
+
for label, row in zip(labels, rows):
|
|
99
|
+
print(
|
|
100
|
+
f"{label:>{label_width}} | "
|
|
101
|
+
+ " ".join(f"{cell:>{cell_width}}" for cell in row)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
print()
|
|
105
|
+
|
|
106
|
+
return rows
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def export_csv(
|
|
110
|
+
path: str,
|
|
111
|
+
signs,
|
|
112
|
+
indices,
|
|
113
|
+
eps=None,
|
|
114
|
+
mode: str = "integer",
|
|
115
|
+
csv_mode: str = "matrix",
|
|
116
|
+
) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Export table to CSV.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
path:
|
|
123
|
+
Output file path.
|
|
124
|
+
|
|
125
|
+
signs:
|
|
126
|
+
2D sign array.
|
|
127
|
+
|
|
128
|
+
indices:
|
|
129
|
+
2D index array.
|
|
130
|
+
|
|
131
|
+
eps:
|
|
132
|
+
Optional epsilon array for dual tables.
|
|
133
|
+
|
|
134
|
+
mode:
|
|
135
|
+
integer, graded, latex, latex_integer, latex_graded
|
|
136
|
+
|
|
137
|
+
csv_mode:
|
|
138
|
+
"matrix":
|
|
139
|
+
Spreadsheet-style grid.
|
|
140
|
+
|
|
141
|
+
"long":
|
|
142
|
+
One row per product:
|
|
143
|
+
i,j,sign,index[,eps]
|
|
144
|
+
"""
|
|
145
|
+
CDFormat.validate_mode(mode)
|
|
146
|
+
|
|
147
|
+
if csv_mode not in ("matrix", "long"):
|
|
148
|
+
raise ValueError("csv_mode must be 'matrix' or 'long'")
|
|
149
|
+
|
|
150
|
+
dim = signs.shape[0]
|
|
151
|
+
dual = eps is not None
|
|
152
|
+
|
|
153
|
+
with open(path, "w", newline="", encoding="utf-8") as f:
|
|
154
|
+
|
|
155
|
+
if csv_mode == "matrix":
|
|
156
|
+
labels = CDFormat.basis_labels(
|
|
157
|
+
dim,
|
|
158
|
+
dual=dual,
|
|
159
|
+
mode=mode,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Header row
|
|
163
|
+
f.write("," + ",".join(labels) + "\n")
|
|
164
|
+
|
|
165
|
+
for i in range(dim):
|
|
166
|
+
cells = []
|
|
167
|
+
|
|
168
|
+
for j in range(dim):
|
|
169
|
+
e = 0 if eps is None else int(eps[i, j])
|
|
170
|
+
|
|
171
|
+
cells.append(
|
|
172
|
+
CDFormat.format_entry(
|
|
173
|
+
int(signs[i, j]),
|
|
174
|
+
int(indices[i, j]),
|
|
175
|
+
e,
|
|
176
|
+
mode,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
f.write(labels[i] + "," + ",".join(cells) + "\n")
|
|
181
|
+
|
|
182
|
+
else:
|
|
183
|
+
header = "i,j,sign,index"
|
|
184
|
+
|
|
185
|
+
if dual:
|
|
186
|
+
header += ",eps"
|
|
187
|
+
|
|
188
|
+
f.write(header + "\n")
|
|
189
|
+
|
|
190
|
+
for i in range(dim):
|
|
191
|
+
for j in range(dim):
|
|
192
|
+
line = (
|
|
193
|
+
f"{i},"
|
|
194
|
+
f"{j},"
|
|
195
|
+
f"{int(signs[i, j])},"
|
|
196
|
+
f"{int(indices[i, j])}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if dual:
|
|
200
|
+
line += f",{int(eps[i, j])}"
|
|
201
|
+
|
|
202
|
+
f.write(line + "\n")
|
|
203
|
+
|
|
204
|
+
return path
|