hypercomplex-engine 0.4.0__tar.gz → 0.4.2__tar.gz

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.
Files changed (38) hide show
  1. {hypercomplex_engine-0.4.0/hypercomplex_engine.egg-info → hypercomplex_engine-0.4.2}/PKG-INFO +87 -2
  2. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/README.md +86 -1
  3. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/fast/fast_dual.py +12 -6
  4. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/fast/fast_standard.py +2 -2
  5. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/holographic/dual.py +4 -3
  6. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/validation.py +1 -0
  7. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/facade.py +5 -0
  8. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2/hypercomplex_engine.egg-info}/PKG-INFO +87 -2
  9. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex_engine.egg-info/SOURCES.txt +1 -0
  10. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/pyproject.toml +1 -1
  11. hypercomplex_engine-0.4.2/tests/test_dual_tuple_regression.py +90 -0
  12. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/LICENSE +0 -0
  13. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/__init__.py +0 -0
  14. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/__init__.py +0 -0
  15. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/basis_element.py +0 -0
  16. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/basis_notation.py +0 -0
  17. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/fast/__init__.py +0 -0
  18. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/fast/bit_utils.py +0 -0
  19. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/fast/fast_split.py +0 -0
  20. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/holographic/__init__.py +0 -0
  21. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/holographic/split.py +0 -0
  22. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/holographic/standard.py +0 -0
  23. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/table_builder/__init__.py +0 -0
  24. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/table_builder/common.py +0 -0
  25. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/table_builder/dual.py +0 -0
  26. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/table_builder/split.py +0 -0
  27. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/core/table_builder/standard.py +0 -0
  28. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/printer/__init__.py +0 -0
  29. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/printer/cd_format.py +0 -0
  30. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex/printer/cd_table_printer.py +0 -0
  31. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex_engine.egg-info/dependency_links.txt +0 -0
  32. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex_engine.egg-info/requires.txt +0 -0
  33. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/hypercomplex_engine.egg-info/top_level.txt +0 -0
  34. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/setup.cfg +0 -0
  35. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/tests/test_fast_mode.py +0 -0
  36. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/tests/test_fixes_0_4.py +0 -0
  37. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/tests/test_mega_mother.py +0 -0
  38. {hypercomplex_engine-0.4.0 → hypercomplex_engine-0.4.2}/tests/test_split_dim.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypercomplex-engine
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Fast O(1)/O(n) multiplication engine and table generator for ordinary, split, and dual Cayley-Dickson algebras.
5
5
  Author-email: Maher Ben Abdessalem <ba.maher94@gmail.com>
6
6
  License: MIT License
@@ -50,7 +50,6 @@ Dynamic: license-file
50
50
 
51
51
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
52
52
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
53
- [![Tests](https://img.shields.io/badge/tests-62%20passed-success)](#)
54
53
  [![GitHub Repo](https://img.shields.io/badge/GitHub-maher1719%2Fhypercomplex--engine-black?logo=github)](https://github.com/maher1719/hypercomplex-engine)
55
54
  [![PyPI version](https://badge.fury.io/py/hypercomplex-engine.svg)](https://pypi.org/project/hypercomplex-engine/)
56
55
  [![Downloads](https://static.pepy.tech/badge/hypercomplex-engine)](https://pepy.tech/project/hypercomplex-engine)
@@ -69,6 +68,11 @@ This library provides the computational substrate for high-dimensional hypercomp
69
68
 
70
69
  ---
71
70
 
71
+ ## Scope and limitations
72
+
73
+ If you build upon this package, please read fully [Go to Scope, Strengths, and Limitations](#scope-strengths-and-limitations) below for more information or check [Scope, Strengths, and Limitations file](README_scope_and_limitations.md#scope-strengths-and-limitations)
74
+
75
+
72
76
  ## 📄 Publications & Preprints
73
77
 
74
78
  This library serves as the formal verification substrate and computational engine for the following mathematical preprints:
@@ -1037,6 +1041,87 @@ hypercomplex-engine/
1037
1041
  └── pyproject.toml
1038
1042
  ```
1039
1043
 
1044
+ # Scope, strengths and limitations
1045
+
1046
+ > **Status: 0.4.x, pre-1.0.** The API can still change. Read this section before building on the package.
1047
+
1048
+ ## What this package is
1049
+
1050
+ It computes the product of two **signed basis elements** of a Cayley–Dickson algebra: `±e_i × ±e_j → ±e_k` (or zero in dual algebras). It is a low-level component, a sign and index calculator, not a number type. There are no coefficients, no sums, and no vectors.
1051
+
1052
+ ## Advantages
1053
+
1054
+ - **One job, small surface.** About 2,000 lines, one dependency (NumPy), and multipliers that keep no per-call state.
1055
+ - **Two interchangeable engines.**
1056
+ - `fast` is a closed-form bitwise evaluator. It does constant work per call for word-sized indices, and cost grows only with bit length for arbitrary-precision indices.
1057
+ - `holographic` is an O(n) recursive descent, useful as a cross-check.
1058
+ - **Works where tables cannot.** Only the two indices are needed, so indices hundreds of bits long are fine. Full tables need 4^n entries.
1059
+ - **Cross-validated.** The test suite checks the table builders, the `fast` engine and the `holographic` engine against each other.
1060
+ - **Strict about types.** `bool`, `float`, `str` and `list` inputs are rejected. Results are plain Python `int`s.
1061
+ - **Tested on Python 3.10–3.14** and with NumPy 1.22 and newer.
1062
+
1063
+ ## Input rules (0.4.x behavior)
1064
+
1065
+ | Kind | Element form | `dim` | Index range |
1066
+ |---|---|---|---|
1067
+ | `standard` | `(sign, index)` | not used | any integer ≥ 0 (no upper bound, since there is no `dim` to check against) |
1068
+ | `split` | `(sign, index)` | **required** | `0 ≤ index < 2**dim` |
1069
+ | `dual`, `dual_split` | `(sign, global_index)` or `(sign, local_index, eps)` | **required** | global: `0 ≤ i < 2**(dim+1)`; local: `0 ≤ i < 2**dim` |
1070
+
1071
+ - Elements must be **tuples**. Integers only, and NumPy integers are accepted.
1072
+ - `sign` should be `-1` or `+1`. Note: `multiply` currently also accepts `0` as a formal zero and returns zero. **Do not rely on this**; it may be removed.
1073
+ - Dual elements have two spellings of the same thing. At `dim=3`, `(1, 9)` and `(1, 1, 1)` both mean `+ε·e1` (global index `8 + 1`).
1074
+ - **Known looseness:** at `dim=3`, `(1, 9, 1)` and even `(1, 9, 0)` are also accepted and read as `+ε·e1`. The second one contradicts itself, so don't rely on it.
1075
+ - The dual result is always `(sign, local_index, eps)`, and `ε·ε` gives `(0, 0, 0)`.
1076
+
1077
+ ## What the package cannot detect (your responsibility)
1078
+
1079
+ Elements are plain tuples, so they do not remember which algebra produced them.
1080
+
1081
+ - Feeding a result from one `dim` into an algebra of a different `dim` is not detected if the index happens to be in range.
1082
+ - Mixing `standard` and `split` elements is not detected. The same tuple means different things: `e2·e2` is `-1` in the standard algebra, `+1` in `split` with `dim=2`, and `-1` in `split` with `dim>=3`.
1083
+ - For `standard`, nothing tells you an index is "too big for the octonions".
1084
+
1085
+ ## Not supported
1086
+
1087
+ - **No chain multiplication and no expressions.** `multiply` is strictly binary. You can pass a result into the next call yourself, but you choose the bracketing. From `n=3` (octonions) upward multiplication is **not associative**, so `(ab)c` and `a(bc)` can differ in sign.
1088
+ - **No coefficients, sums, vectors, arrays, or parsing** of expressions like `e1+e2+e12`. This is planned for a separate package built on top of this one.
1089
+ - **No addition, conjugation, norm, inverse, or division.**
1090
+ - **`split` means one split doubling on top of a standard parent.** Other sign patterns are not offered.
1091
+ - **`dual` and `dual_split`** extend a standard or split parent with a central `ε`, where `ε² = 0`.
1092
+
1093
+ ## Algebra facts to keep in mind
1094
+
1095
+ | n | Standard | Split |
1096
+ |---|---|---|
1097
+ | 1 | complex, commutative | split-complex, commutative, has zero divisors |
1098
+ | 2 | quaternions, associative, **not commutative** | split-quaternions, associative, not commutative |
1099
+ | 3 | octonions, alternative, **not associative** | split-octonions, alternative, not associative, has zero divisors |
1100
+ | ≥ 4 | sedenions and beyond: **zero divisors**, no longer alternative | no longer alternative |
1101
+
1102
+ ## Convention
1103
+
1104
+ Products follow the standard doubling `(a, b)(c, d) = (ac − d*b, da + bc*)` (split algebras flip the sign of the `d*b` term at the top level). Other conventions give isomorphic algebras with **different tables**, so comparing against another source may show sign differences after relabeling. For example, here `e1·e2 = e3` and `e1·e6 = −e7`.
1105
+
1106
+ ## Direct low-level classes
1107
+
1108
+ `FastStandard`, `FastSplit`, `FastDual`, `StandardHolographic`, `SplitHolographic` and `DualHolographic` are exported, but they validate **differently** from each other and from `multiply`. For example, `FastSplit` accepts a zero element while the other three reject it. Their `multiply_indices` methods do only minimal checks. **Prefer `multiply`.** These classes are not a stable API and are expected to become internal.
1109
+
1110
+ ## Tables and memory
1111
+
1112
+ Full tables have `4**n` entries. `build_table` refuses requests above a default memory budget (`max_bytes`, 256 MiB):
1113
+
1114
+ - `standard` and `split` are allowed up to `n = 13`.
1115
+ - `dual` and `dual_split` are allowed up to `n = 12`, since they are one doubling larger.
1116
+
1117
+ Use `estimate_table_bytes(kind, n)` to check a size, and pass a larger `max_bytes` (or `None`) if you really mean it. In dual tables, `sign == 0` marks `ε·ε`.
1118
+
1119
+ ## Stability and planned changes
1120
+
1121
+ - Pin your dependency, for example `hypercomplex-engine>=0.4.1,<0.5`.
1122
+ - *Planned for 0.5.0, subject to change:* stricter inputs (no zero elements, one dual form), and low-level classes made internal. Breaking changes will be listed in the release notes.
1123
+ - Do not treat the package as 1.0-stable until it has been used by futur a palnned package the vector-and-expression package.
1124
+
1040
1125
  ---
1041
1126
 
1042
1127
  # Citation
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
5
- [![Tests](https://img.shields.io/badge/tests-62%20passed-success)](#)
6
5
  [![GitHub Repo](https://img.shields.io/badge/GitHub-maher1719%2Fhypercomplex--engine-black?logo=github)](https://github.com/maher1719/hypercomplex-engine)
7
6
  [![PyPI version](https://badge.fury.io/py/hypercomplex-engine.svg)](https://pypi.org/project/hypercomplex-engine/)
8
7
  [![Downloads](https://static.pepy.tech/badge/hypercomplex-engine)](https://pepy.tech/project/hypercomplex-engine)
@@ -21,6 +20,11 @@ This library provides the computational substrate for high-dimensional hypercomp
21
20
 
22
21
  ---
23
22
 
23
+ ## Scope and limitations
24
+
25
+ If you build upon this package, please read fully [Go to Scope, Strengths, and Limitations](#scope-strengths-and-limitations) below for more information or check [Scope, Strengths, and Limitations file](README_scope_and_limitations.md#scope-strengths-and-limitations)
26
+
27
+
24
28
  ## 📄 Publications & Preprints
25
29
 
26
30
  This library serves as the formal verification substrate and computational engine for the following mathematical preprints:
@@ -989,6 +993,87 @@ hypercomplex-engine/
989
993
  └── pyproject.toml
990
994
  ```
991
995
 
996
+ # Scope, strengths and limitations
997
+
998
+ > **Status: 0.4.x, pre-1.0.** The API can still change. Read this section before building on the package.
999
+
1000
+ ## What this package is
1001
+
1002
+ It computes the product of two **signed basis elements** of a Cayley–Dickson algebra: `±e_i × ±e_j → ±e_k` (or zero in dual algebras). It is a low-level component, a sign and index calculator, not a number type. There are no coefficients, no sums, and no vectors.
1003
+
1004
+ ## Advantages
1005
+
1006
+ - **One job, small surface.** About 2,000 lines, one dependency (NumPy), and multipliers that keep no per-call state.
1007
+ - **Two interchangeable engines.**
1008
+ - `fast` is a closed-form bitwise evaluator. It does constant work per call for word-sized indices, and cost grows only with bit length for arbitrary-precision indices.
1009
+ - `holographic` is an O(n) recursive descent, useful as a cross-check.
1010
+ - **Works where tables cannot.** Only the two indices are needed, so indices hundreds of bits long are fine. Full tables need 4^n entries.
1011
+ - **Cross-validated.** The test suite checks the table builders, the `fast` engine and the `holographic` engine against each other.
1012
+ - **Strict about types.** `bool`, `float`, `str` and `list` inputs are rejected. Results are plain Python `int`s.
1013
+ - **Tested on Python 3.10–3.14** and with NumPy 1.22 and newer.
1014
+
1015
+ ## Input rules (0.4.x behavior)
1016
+
1017
+ | Kind | Element form | `dim` | Index range |
1018
+ |---|---|---|---|
1019
+ | `standard` | `(sign, index)` | not used | any integer ≥ 0 (no upper bound, since there is no `dim` to check against) |
1020
+ | `split` | `(sign, index)` | **required** | `0 ≤ index < 2**dim` |
1021
+ | `dual`, `dual_split` | `(sign, global_index)` or `(sign, local_index, eps)` | **required** | global: `0 ≤ i < 2**(dim+1)`; local: `0 ≤ i < 2**dim` |
1022
+
1023
+ - Elements must be **tuples**. Integers only, and NumPy integers are accepted.
1024
+ - `sign` should be `-1` or `+1`. Note: `multiply` currently also accepts `0` as a formal zero and returns zero. **Do not rely on this**; it may be removed.
1025
+ - Dual elements have two spellings of the same thing. At `dim=3`, `(1, 9)` and `(1, 1, 1)` both mean `+ε·e1` (global index `8 + 1`).
1026
+ - **Known looseness:** at `dim=3`, `(1, 9, 1)` and even `(1, 9, 0)` are also accepted and read as `+ε·e1`. The second one contradicts itself, so don't rely on it.
1027
+ - The dual result is always `(sign, local_index, eps)`, and `ε·ε` gives `(0, 0, 0)`.
1028
+
1029
+ ## What the package cannot detect (your responsibility)
1030
+
1031
+ Elements are plain tuples, so they do not remember which algebra produced them.
1032
+
1033
+ - Feeding a result from one `dim` into an algebra of a different `dim` is not detected if the index happens to be in range.
1034
+ - Mixing `standard` and `split` elements is not detected. The same tuple means different things: `e2·e2` is `-1` in the standard algebra, `+1` in `split` with `dim=2`, and `-1` in `split` with `dim>=3`.
1035
+ - For `standard`, nothing tells you an index is "too big for the octonions".
1036
+
1037
+ ## Not supported
1038
+
1039
+ - **No chain multiplication and no expressions.** `multiply` is strictly binary. You can pass a result into the next call yourself, but you choose the bracketing. From `n=3` (octonions) upward multiplication is **not associative**, so `(ab)c` and `a(bc)` can differ in sign.
1040
+ - **No coefficients, sums, vectors, arrays, or parsing** of expressions like `e1+e2+e12`. This is planned for a separate package built on top of this one.
1041
+ - **No addition, conjugation, norm, inverse, or division.**
1042
+ - **`split` means one split doubling on top of a standard parent.** Other sign patterns are not offered.
1043
+ - **`dual` and `dual_split`** extend a standard or split parent with a central `ε`, where `ε² = 0`.
1044
+
1045
+ ## Algebra facts to keep in mind
1046
+
1047
+ | n | Standard | Split |
1048
+ |---|---|---|
1049
+ | 1 | complex, commutative | split-complex, commutative, has zero divisors |
1050
+ | 2 | quaternions, associative, **not commutative** | split-quaternions, associative, not commutative |
1051
+ | 3 | octonions, alternative, **not associative** | split-octonions, alternative, not associative, has zero divisors |
1052
+ | ≥ 4 | sedenions and beyond: **zero divisors**, no longer alternative | no longer alternative |
1053
+
1054
+ ## Convention
1055
+
1056
+ Products follow the standard doubling `(a, b)(c, d) = (ac − d*b, da + bc*)` (split algebras flip the sign of the `d*b` term at the top level). Other conventions give isomorphic algebras with **different tables**, so comparing against another source may show sign differences after relabeling. For example, here `e1·e2 = e3` and `e1·e6 = −e7`.
1057
+
1058
+ ## Direct low-level classes
1059
+
1060
+ `FastStandard`, `FastSplit`, `FastDual`, `StandardHolographic`, `SplitHolographic` and `DualHolographic` are exported, but they validate **differently** from each other and from `multiply`. For example, `FastSplit` accepts a zero element while the other three reject it. Their `multiply_indices` methods do only minimal checks. **Prefer `multiply`.** These classes are not a stable API and are expected to become internal.
1061
+
1062
+ ## Tables and memory
1063
+
1064
+ Full tables have `4**n` entries. `build_table` refuses requests above a default memory budget (`max_bytes`, 256 MiB):
1065
+
1066
+ - `standard` and `split` are allowed up to `n = 13`.
1067
+ - `dual` and `dual_split` are allowed up to `n = 12`, since they are one doubling larger.
1068
+
1069
+ Use `estimate_table_bytes(kind, n)` to check a size, and pass a larger `max_bytes` (or `None`) if you really mean it. In dual tables, `sign == 0` marks `ε·ε`.
1070
+
1071
+ ## Stability and planned changes
1072
+
1073
+ - Pin your dependency, for example `hypercomplex-engine>=0.4.1,<0.5`.
1074
+ - *Planned for 0.5.0, subject to change:* stricter inputs (no zero elements, one dual form), and low-level classes made internal. Breaking changes will be listed in the release notes.
1075
+ - Do not treat the package as 1.0-stable until it has been used by futur a palnned package the vector-and-expression package.
1076
+
992
1077
  ---
993
1078
 
994
1079
  # Citation
@@ -125,18 +125,24 @@ class FastDual:
125
125
  (final_sign, local_index, eps_flag)
126
126
  """
127
127
  dim = Validation.dimension(dim)
128
+ Validation.basis_tuple(t1, allow_zero=True, allow_eps=True)
129
+ Validation.basis_tuple(t2, allow_zero=True, allow_eps=True)
128
130
 
129
- s1, i = self._extract_global(t1, dim)
130
- s2, j = self._extract_global(t2, dim)
131
+
132
+
133
+ s1, i = int(t1[0]), int(t1[1])
134
+ s2, j = int(t2[0]), int(t2[1])
135
+ i_eps_flag = int(t1[2]) if len(t1) == 3 else 0
136
+ j_eps_flag = int(t2[2]) if len(t2) == 3 else 0
131
137
 
132
138
  if s1 == 0 or s2 == 0:
133
139
  return (0, 0, 0)
134
140
 
135
- sign, idx, eps = self.multiply_indices(i, j, dim)
141
+ sign, idx, eps = self.multiply_indices(i, j,dim,i_eps_flag,j_eps_flag)
136
142
 
137
143
  return (s1 * s2 * sign, idx, eps)
138
144
 
139
- def multiply_indices(self, i: int, j: int, dim: int) -> tuple:
145
+ def multiply_indices(self, i: int, j: int,dim:int, i_eps_flag=0 ,j_eps_flag=0) -> tuple:
140
146
  """
141
147
  Core dual O(1) multiplication using global indices.
142
148
 
@@ -156,8 +162,8 @@ class FastDual:
156
162
  if j < 0 or j >= total:
157
163
  raise ValueError(f"index must be in [0, {total - 1}] for dual dim={dim}, got {j}")
158
164
 
159
- i_eps = i >= half
160
- j_eps = j >= half
165
+ i_eps = i >= half or i_eps_flag
166
+ j_eps = j >= half or j_eps_flag
161
167
 
162
168
  i_loc = i & (half - 1)
163
169
  j_loc = j & (half - 1)
@@ -28,8 +28,8 @@ class FastStandard:
28
28
  Returns:
29
29
  (final_sign, index1 XOR index2)
30
30
  """
31
- Validation.basis_tuple(t1, allow_zero=True, allow_eps=False)
32
- Validation.basis_tuple(t2, allow_zero=True, allow_eps=False)
31
+ Validation.basis_tuple(t1, allow_zero=False, allow_eps=False)
32
+ Validation.basis_tuple(t2, allow_zero=False, allow_eps=False)
33
33
 
34
34
  s1, i = int(t1[0]), int(t1[1])
35
35
  s2, j = int(t2[0]), int(t2[1])
@@ -48,7 +48,8 @@ class DualHolographic:
48
48
 
49
49
  s1, i = int(t1[0]), int(t1[1])
50
50
  s2, j = int(t2[0]), int(t2[1])
51
-
51
+ i_eps_flag = int(t1[2]) if len(t1) == 3 else 0
52
+ j_eps_flag = int(t2[2]) if len(t2) == 3 else 0
52
53
  # Zero propagates.
53
54
  if s1 == 0 or s2 == 0:
54
55
  return (0, 0, 0)
@@ -63,8 +64,8 @@ class DualHolographic:
63
64
 
64
65
  half = 1 << dim
65
66
 
66
- i_eps = i >= half
67
- j_eps = j >= half
67
+ i_eps = i >= half or i_eps_flag
68
+ j_eps = j >= half or j_eps_flag
68
69
 
69
70
  i_loc = i & (half - 1)
70
71
  j_loc = j & (half - 1)
@@ -59,6 +59,7 @@ class Validation:
59
59
 
60
60
  if len(data) == 3 and not allow_eps:
61
61
  raise ValueError("3-tuple epsilon form is not allowed here")
62
+
62
63
 
63
64
  sign = data[0]
64
65
  index = data[1]
@@ -264,6 +264,11 @@ def multiply(
264
264
  # required, never inferred. Validate before anything else.
265
265
  if kind in ("split", "dual", "dual_split") and dim is None:
266
266
  raise ValueError(f"dim is required for {kind} multiplication")
267
+ # Validate inputs before any early return, so malformed data always
268
+ # raises ValidationError instead of leaking TypeError/IndexError.
269
+ allow_eps = kind in ("dual", "dual_split")
270
+ Validation.basis_tuple(a, allow_zero=True, allow_eps=allow_eps)
271
+ Validation.basis_tuple(b, allow_zero=True, allow_eps=allow_eps)
267
272
 
268
273
  # Zero propagation
269
274
  if int(a[0]) == 0 or int(b[0]) == 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypercomplex-engine
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Fast O(1)/O(n) multiplication engine and table generator for ordinary, split, and dual Cayley-Dickson algebras.
5
5
  Author-email: Maher Ben Abdessalem <ba.maher94@gmail.com>
6
6
  License: MIT License
@@ -50,7 +50,6 @@ Dynamic: license-file
50
50
 
51
51
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
52
52
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
53
- [![Tests](https://img.shields.io/badge/tests-62%20passed-success)](#)
54
53
  [![GitHub Repo](https://img.shields.io/badge/GitHub-maher1719%2Fhypercomplex--engine-black?logo=github)](https://github.com/maher1719/hypercomplex-engine)
55
54
  [![PyPI version](https://badge.fury.io/py/hypercomplex-engine.svg)](https://pypi.org/project/hypercomplex-engine/)
56
55
  [![Downloads](https://static.pepy.tech/badge/hypercomplex-engine)](https://pepy.tech/project/hypercomplex-engine)
@@ -69,6 +68,11 @@ This library provides the computational substrate for high-dimensional hypercomp
69
68
 
70
69
  ---
71
70
 
71
+ ## Scope and limitations
72
+
73
+ If you build upon this package, please read fully [Go to Scope, Strengths, and Limitations](#scope-strengths-and-limitations) below for more information or check [Scope, Strengths, and Limitations file](README_scope_and_limitations.md#scope-strengths-and-limitations)
74
+
75
+
72
76
  ## 📄 Publications & Preprints
73
77
 
74
78
  This library serves as the formal verification substrate and computational engine for the following mathematical preprints:
@@ -1037,6 +1041,87 @@ hypercomplex-engine/
1037
1041
  └── pyproject.toml
1038
1042
  ```
1039
1043
 
1044
+ # Scope, strengths and limitations
1045
+
1046
+ > **Status: 0.4.x, pre-1.0.** The API can still change. Read this section before building on the package.
1047
+
1048
+ ## What this package is
1049
+
1050
+ It computes the product of two **signed basis elements** of a Cayley–Dickson algebra: `±e_i × ±e_j → ±e_k` (or zero in dual algebras). It is a low-level component, a sign and index calculator, not a number type. There are no coefficients, no sums, and no vectors.
1051
+
1052
+ ## Advantages
1053
+
1054
+ - **One job, small surface.** About 2,000 lines, one dependency (NumPy), and multipliers that keep no per-call state.
1055
+ - **Two interchangeable engines.**
1056
+ - `fast` is a closed-form bitwise evaluator. It does constant work per call for word-sized indices, and cost grows only with bit length for arbitrary-precision indices.
1057
+ - `holographic` is an O(n) recursive descent, useful as a cross-check.
1058
+ - **Works where tables cannot.** Only the two indices are needed, so indices hundreds of bits long are fine. Full tables need 4^n entries.
1059
+ - **Cross-validated.** The test suite checks the table builders, the `fast` engine and the `holographic` engine against each other.
1060
+ - **Strict about types.** `bool`, `float`, `str` and `list` inputs are rejected. Results are plain Python `int`s.
1061
+ - **Tested on Python 3.10–3.14** and with NumPy 1.22 and newer.
1062
+
1063
+ ## Input rules (0.4.x behavior)
1064
+
1065
+ | Kind | Element form | `dim` | Index range |
1066
+ |---|---|---|---|
1067
+ | `standard` | `(sign, index)` | not used | any integer ≥ 0 (no upper bound, since there is no `dim` to check against) |
1068
+ | `split` | `(sign, index)` | **required** | `0 ≤ index < 2**dim` |
1069
+ | `dual`, `dual_split` | `(sign, global_index)` or `(sign, local_index, eps)` | **required** | global: `0 ≤ i < 2**(dim+1)`; local: `0 ≤ i < 2**dim` |
1070
+
1071
+ - Elements must be **tuples**. Integers only, and NumPy integers are accepted.
1072
+ - `sign` should be `-1` or `+1`. Note: `multiply` currently also accepts `0` as a formal zero and returns zero. **Do not rely on this**; it may be removed.
1073
+ - Dual elements have two spellings of the same thing. At `dim=3`, `(1, 9)` and `(1, 1, 1)` both mean `+ε·e1` (global index `8 + 1`).
1074
+ - **Known looseness:** at `dim=3`, `(1, 9, 1)` and even `(1, 9, 0)` are also accepted and read as `+ε·e1`. The second one contradicts itself, so don't rely on it.
1075
+ - The dual result is always `(sign, local_index, eps)`, and `ε·ε` gives `(0, 0, 0)`.
1076
+
1077
+ ## What the package cannot detect (your responsibility)
1078
+
1079
+ Elements are plain tuples, so they do not remember which algebra produced them.
1080
+
1081
+ - Feeding a result from one `dim` into an algebra of a different `dim` is not detected if the index happens to be in range.
1082
+ - Mixing `standard` and `split` elements is not detected. The same tuple means different things: `e2·e2` is `-1` in the standard algebra, `+1` in `split` with `dim=2`, and `-1` in `split` with `dim>=3`.
1083
+ - For `standard`, nothing tells you an index is "too big for the octonions".
1084
+
1085
+ ## Not supported
1086
+
1087
+ - **No chain multiplication and no expressions.** `multiply` is strictly binary. You can pass a result into the next call yourself, but you choose the bracketing. From `n=3` (octonions) upward multiplication is **not associative**, so `(ab)c` and `a(bc)` can differ in sign.
1088
+ - **No coefficients, sums, vectors, arrays, or parsing** of expressions like `e1+e2+e12`. This is planned for a separate package built on top of this one.
1089
+ - **No addition, conjugation, norm, inverse, or division.**
1090
+ - **`split` means one split doubling on top of a standard parent.** Other sign patterns are not offered.
1091
+ - **`dual` and `dual_split`** extend a standard or split parent with a central `ε`, where `ε² = 0`.
1092
+
1093
+ ## Algebra facts to keep in mind
1094
+
1095
+ | n | Standard | Split |
1096
+ |---|---|---|
1097
+ | 1 | complex, commutative | split-complex, commutative, has zero divisors |
1098
+ | 2 | quaternions, associative, **not commutative** | split-quaternions, associative, not commutative |
1099
+ | 3 | octonions, alternative, **not associative** | split-octonions, alternative, not associative, has zero divisors |
1100
+ | ≥ 4 | sedenions and beyond: **zero divisors**, no longer alternative | no longer alternative |
1101
+
1102
+ ## Convention
1103
+
1104
+ Products follow the standard doubling `(a, b)(c, d) = (ac − d*b, da + bc*)` (split algebras flip the sign of the `d*b` term at the top level). Other conventions give isomorphic algebras with **different tables**, so comparing against another source may show sign differences after relabeling. For example, here `e1·e2 = e3` and `e1·e6 = −e7`.
1105
+
1106
+ ## Direct low-level classes
1107
+
1108
+ `FastStandard`, `FastSplit`, `FastDual`, `StandardHolographic`, `SplitHolographic` and `DualHolographic` are exported, but they validate **differently** from each other and from `multiply`. For example, `FastSplit` accepts a zero element while the other three reject it. Their `multiply_indices` methods do only minimal checks. **Prefer `multiply`.** These classes are not a stable API and are expected to become internal.
1109
+
1110
+ ## Tables and memory
1111
+
1112
+ Full tables have `4**n` entries. `build_table` refuses requests above a default memory budget (`max_bytes`, 256 MiB):
1113
+
1114
+ - `standard` and `split` are allowed up to `n = 13`.
1115
+ - `dual` and `dual_split` are allowed up to `n = 12`, since they are one doubling larger.
1116
+
1117
+ Use `estimate_table_bytes(kind, n)` to check a size, and pass a larger `max_bytes` (or `None`) if you really mean it. In dual tables, `sign == 0` marks `ε·ε`.
1118
+
1119
+ ## Stability and planned changes
1120
+
1121
+ - Pin your dependency, for example `hypercomplex-engine>=0.4.1,<0.5`.
1122
+ - *Planned for 0.5.0, subject to change:* stricter inputs (no zero elements, one dual form), and low-level classes made internal. Breaking changes will be listed in the release notes.
1123
+ - Do not treat the package as 1.0-stable until it has been used by futur a palnned package the vector-and-expression package.
1124
+
1040
1125
  ---
1041
1126
 
1042
1127
  # Citation
@@ -29,6 +29,7 @@ hypercomplex_engine.egg-info/SOURCES.txt
29
29
  hypercomplex_engine.egg-info/dependency_links.txt
30
30
  hypercomplex_engine.egg-info/requires.txt
31
31
  hypercomplex_engine.egg-info/top_level.txt
32
+ tests/test_dual_tuple_regression.py
32
33
  tests/test_fast_mode.py
33
34
  tests/test_fixes_0_4.py
34
35
  tests/test_mega_mother.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hypercomplex-engine"
7
- version = "0.4.0"
7
+ version = "0.4.2"
8
8
  authors = [
9
9
  { name="Maher Ben Abdessalem", email="ba.maher94@gmail.com" },
10
10
  ]
@@ -0,0 +1,90 @@
1
+ """Regression tests for dual-engine tuple normalization and facade zero handling.
2
+
3
+ History:
4
+ - 0.4.0: DualHolographic ignored the eps flag while FastDual promoted it
5
+ (engines diverged on identical 3-tuple input).
6
+ - 0.4.0 hotfix attempt: DualHolographic read t[2] unconditionally, crashing
7
+ with IndexError on documented 2-tuple input and breaking the facade's
8
+ engine="holographic" dual path (which normalizes to 2-tuples).
9
+ - 0.4.1: both fixed; facade validates before zero-propagation.
10
+ """
11
+
12
+ import pytest
13
+
14
+ # Adjust these imports to whatever hypercomplex/__init__.py exports.
15
+ from hypercomplex import multiply, FastDual, DualHolographic
16
+ from hypercomplex.core.validation import Validation
17
+
18
+
19
+ # ---------- tuple normalization: engines must agree on identical input ----------
20
+
21
+ def _all_global_pairs(dim):
22
+ half = 1 << dim
23
+ for i in range(half << 1):
24
+ for j in range(half << 1):
25
+ yield (1, i, 1 if i >= half else 0), (1, j, 1 if j >= half else 0)
26
+
27
+
28
+ @pytest.mark.parametrize("dim", [0, 1, 2, 3])
29
+ def test_engines_agree_global_3tuples(dim):
30
+ fast, holo = FastDual(), DualHolographic()
31
+ for a, b in _all_global_pairs(dim):
32
+ assert fast.multiply(a, b, dim) == holo.multiply(a, b, dim)
33
+
34
+
35
+ @pytest.mark.parametrize("dim", [1, 2, 3])
36
+ def test_engines_agree_on_2tuples(dim):
37
+ """2-tuples are documented input; must not raise (the 0.4.0 regression)."""
38
+ fast, holo = FastDual(), DualHolographic()
39
+ for a, b in _all_global_pairs(dim):
40
+ a2, b2 = a[:2], b[:2] # strip the eps flag
41
+ assert fast.multiply(a2, b2, dim) == holo.multiply(a2, b2, dim)
42
+
43
+
44
+ @pytest.mark.parametrize("dim", [1, 2, 3])
45
+ def test_local_3tuple_matches_promoted_global(dim):
46
+ """(sign, local_index, eps=1) must equal (sign, local_index + half)."""
47
+ fast, holo = FastDual(), DualHolographic()
48
+ half = 1 << dim
49
+ for loc in range(half):
50
+ for j in range(half << 1):
51
+ for engine in (fast, holo):
52
+ assert engine.multiply((1, loc, 1), (1, j), dim) == \
53
+ engine.multiply((1, loc + half), (1, j), dim)
54
+
55
+
56
+ # ---------- facade routes, including the path that crashed ----------
57
+
58
+ @pytest.mark.parametrize("engine", ["fast", "holographic"])
59
+ def test_facade_dual_both_engines(engine):
60
+ assert multiply("dual", (1, 3), (1, 5), dim=3, engine=engine) == \
61
+ multiply("dual", (1, 3), (1, 5), dim=3, engine="fast")
62
+
63
+
64
+ def test_facade_dual_3tuple_promotion():
65
+ half = 1 << 3
66
+ for engine in ("fast", "holographic"):
67
+ assert multiply("dual", (1, 2, 1), (1, 1), dim=3, engine=engine) == \
68
+ multiply("dual", (1, 2 + half), (1, 1), dim=3, engine=engine)
69
+
70
+
71
+ # ---------- zero handling: validated, shape-correct, convention documented ----------
72
+
73
+ def test_facade_zero_shapes():
74
+ assert multiply("dual", (0, 2), (1, 3), dim=2) == (0, 0, 0)
75
+ assert multiply("dual_split", (1, 2), (0, 3), dim=2) == (0, 0, 0)
76
+ assert multiply("standard", (0, 2), (1, 3), dim=2) == (0, 0) # formal zero
77
+ assert multiply("split", (0, 2), (1, 3), dim=2) == (0, 0)
78
+
79
+
80
+ def test_facade_malformed_raises_validation_error():
81
+ with pytest.raises((TypeError, ValueError)):
82
+ multiply("dual", 5, (1, 1), dim=2) # not a tuple
83
+ with pytest.raises((TypeError, ValueError)):
84
+ multiply("standard", (1, 2, 3, 4), (1, 1)) # wrong arity
85
+
86
+ # ---------- nilpotency still intact ----------
87
+
88
+ @pytest.mark.parametrize("engine", ["fast", "holographic"])
89
+ def test_nilpotency(engine):
90
+ assert multiply("dual", (1, 1), (1, 1), dim=0, engine=engine) == (0, 0, 0)