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.
@@ -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