adnus 0.1.9__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.
- adnus/__init__.py +317 -0
- adnus/_version.py +9 -0
- adnus/main.py +1804 -0
- adnus-0.1.9.dist-info/METADATA +958 -0
- adnus-0.1.9.dist-info/RECORD +8 -0
- adnus-0.1.9.dist-info/WHEEL +5 -0
- adnus-0.1.9.dist-info/licenses/LICENSE +661 -0
- adnus-0.1.9.dist-info/top_level.txt +1 -0
adnus/main.py
ADDED
|
@@ -0,0 +1,1804 @@
|
|
|
1
|
+
"""
|
|
2
|
+
adnus (AdNuS): A Python library for Advanced Number Systems.
|
|
3
|
+
Unified interface for hypercomplex numbers, neutrosophic numbers, and other advanced number systems.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from fractions import Fraction
|
|
10
|
+
import math
|
|
11
|
+
from math import sqrt
|
|
12
|
+
from typing import List, Union, Generator, Tuple, Any, Optional
|
|
13
|
+
import numpy as np
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
# Try to import kececinumbers, but provide fallbacks
|
|
17
|
+
try:
|
|
18
|
+
from kececinumbers import (
|
|
19
|
+
reals, Complex as KComplex, Quaternion as KQuaternion,
|
|
20
|
+
Octonion as KOctonion, Sedenion as KSedenion,
|
|
21
|
+
Pathion as KPathion, Chingon as KChingon,
|
|
22
|
+
Routon as KRouton, Voudon as KVoudon,
|
|
23
|
+
cayley_dickson_process, cayley_dickson_cebir
|
|
24
|
+
)
|
|
25
|
+
HAS_KECECI = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
HAS_KECECI = False
|
|
28
|
+
# generate dummy classes for type hints
|
|
29
|
+
class KComplex: pass
|
|
30
|
+
class KQuaternion: pass
|
|
31
|
+
class KOctonion: pass
|
|
32
|
+
class KSedenion: pass
|
|
33
|
+
class KPathion: pass
|
|
34
|
+
class KChingon: pass
|
|
35
|
+
class KRouton: pass
|
|
36
|
+
class KVoudon: pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================
|
|
40
|
+
# Abstract Base Class
|
|
41
|
+
# =============================================
|
|
42
|
+
|
|
43
|
+
class AdvancedNumber(ABC):
|
|
44
|
+
"""Abstract Base Class for all advanced number systems."""
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def __add__(self, other):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def __sub__(self, other):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def __mul__(self, other):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def __truediv__(self, other):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def __eq__(self, other) -> bool:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def __repr__(self) -> str:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def norm(self) -> float:
|
|
72
|
+
"""Return the Euclidean norm/magnitude."""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def conjugate(self):
|
|
77
|
+
"""Return the conjugate."""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def to_hypercomplex(self) -> 'HypercomplexNumber':
|
|
82
|
+
"""Convert to HypercomplexNumber."""
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
# =============================================
|
|
86
|
+
# Complex Number Implementation
|
|
87
|
+
# =============================================
|
|
88
|
+
|
|
89
|
+
class ComplexNumber(AdvancedNumber):
|
|
90
|
+
"""Complex number implementation."""
|
|
91
|
+
|
|
92
|
+
def __init__(self, real: float, imag: float = 0.0):
|
|
93
|
+
self._real = float(real)
|
|
94
|
+
self._imag = float(imag)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def real(self) -> float:
|
|
98
|
+
return self._real
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def imag(self) -> float:
|
|
102
|
+
return self._imag
|
|
103
|
+
|
|
104
|
+
def __add__(self, other):
|
|
105
|
+
if isinstance(other, ComplexNumber):
|
|
106
|
+
return ComplexNumber(self.real + other.real, self.imag + other.imag)
|
|
107
|
+
elif isinstance(other, (int, float)):
|
|
108
|
+
return ComplexNumber(self.real + float(other), self.imag)
|
|
109
|
+
elif isinstance(other, complex):
|
|
110
|
+
return ComplexNumber(self.real + other.real, self.imag + other.imag)
|
|
111
|
+
return NotImplemented
|
|
112
|
+
|
|
113
|
+
def __radd__(self, other):
|
|
114
|
+
return self.__add__(other)
|
|
115
|
+
|
|
116
|
+
def __sub__(self, other):
|
|
117
|
+
if isinstance(other, ComplexNumber):
|
|
118
|
+
return ComplexNumber(self.real - other.real, self.imag - other.imag)
|
|
119
|
+
elif isinstance(other, (int, float)):
|
|
120
|
+
return ComplexNumber(self.real - float(other), self.imag)
|
|
121
|
+
elif isinstance(other, complex):
|
|
122
|
+
return ComplexNumber(self.real - other.real, self.imag - other.imag)
|
|
123
|
+
return NotImplemented
|
|
124
|
+
|
|
125
|
+
def __rsub__(self, other):
|
|
126
|
+
if isinstance(other, (int, float)):
|
|
127
|
+
return ComplexNumber(float(other) - self.real, -self.imag)
|
|
128
|
+
elif isinstance(other, complex):
|
|
129
|
+
return ComplexNumber(other.real - self.real, other.imag - self.imag)
|
|
130
|
+
return NotImplemented
|
|
131
|
+
|
|
132
|
+
def __mul__(self, other):
|
|
133
|
+
if isinstance(other, ComplexNumber):
|
|
134
|
+
real = self.real * other.real - self.imag * other.imag
|
|
135
|
+
imag = self.real * other.imag + self.imag * other.real
|
|
136
|
+
return ComplexNumber(real, imag)
|
|
137
|
+
elif isinstance(other, (int, float)):
|
|
138
|
+
return ComplexNumber(self.real * float(other), self.imag * float(other))
|
|
139
|
+
elif isinstance(other, complex):
|
|
140
|
+
return self * ComplexNumber(other.real, other.imag)
|
|
141
|
+
return NotImplemented
|
|
142
|
+
|
|
143
|
+
def __rmul__(self, other):
|
|
144
|
+
return self.__mul__(other)
|
|
145
|
+
|
|
146
|
+
def __truediv__(self, other):
|
|
147
|
+
if isinstance(other, ComplexNumber):
|
|
148
|
+
denominator = other.norm() ** 2
|
|
149
|
+
if denominator == 0:
|
|
150
|
+
raise ZeroDivisionError("Division by zero")
|
|
151
|
+
conj = other.conjugate()
|
|
152
|
+
result = self * conj
|
|
153
|
+
return ComplexNumber(result.real / denominator, result.imag / denominator)
|
|
154
|
+
elif isinstance(other, (int, float)):
|
|
155
|
+
if float(other) == 0:
|
|
156
|
+
raise ZeroDivisionError("Division by zero")
|
|
157
|
+
return ComplexNumber(self.real / float(other), self.imag / float(other))
|
|
158
|
+
elif isinstance(other, complex):
|
|
159
|
+
return self / ComplexNumber(other.real, other.imag)
|
|
160
|
+
return NotImplemented
|
|
161
|
+
|
|
162
|
+
def __rtruediv__(self, other):
|
|
163
|
+
if isinstance(other, (int, float)):
|
|
164
|
+
return ComplexNumber(float(other), 0) / self
|
|
165
|
+
elif isinstance(other, complex):
|
|
166
|
+
return ComplexNumber(other.real, other.imag) / self
|
|
167
|
+
return NotImplemented
|
|
168
|
+
|
|
169
|
+
def __neg__(self):
|
|
170
|
+
return ComplexNumber(-self.real, -self.imag)
|
|
171
|
+
|
|
172
|
+
def __pos__(self):
|
|
173
|
+
return self
|
|
174
|
+
|
|
175
|
+
def __abs__(self):
|
|
176
|
+
return self.norm()
|
|
177
|
+
|
|
178
|
+
def __eq__(self, other):
|
|
179
|
+
if isinstance(other, ComplexNumber):
|
|
180
|
+
return math.isclose(self.real, other.real) and math.isclose(self.imag, other.imag)
|
|
181
|
+
elif isinstance(other, (int, float)):
|
|
182
|
+
return math.isclose(self.real, float(other)) and math.isclose(self.imag, 0)
|
|
183
|
+
elif isinstance(other, complex):
|
|
184
|
+
return math.isclose(self.real, other.real) and math.isclose(self.imag, other.imag)
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
def __ne__(self, other):
|
|
188
|
+
return not self.__eq__(other)
|
|
189
|
+
|
|
190
|
+
def __hash__(self):
|
|
191
|
+
return hash((round(self.real, 12), round(self.imag, 12)))
|
|
192
|
+
|
|
193
|
+
def __repr__(self):
|
|
194
|
+
return f"ComplexNumber({self.real}, {self.imag})"
|
|
195
|
+
|
|
196
|
+
def __str__(self):
|
|
197
|
+
if self.imag >= 0:
|
|
198
|
+
return f"{self.real} + {self.imag}i"
|
|
199
|
+
else:
|
|
200
|
+
return f"{self.real} - {-self.imag}i"
|
|
201
|
+
|
|
202
|
+
def norm(self) -> float:
|
|
203
|
+
return math.sqrt(self.real**2 + self.imag**2)
|
|
204
|
+
|
|
205
|
+
def conjugate(self):
|
|
206
|
+
return ComplexNumber(self.real, -self.imag)
|
|
207
|
+
|
|
208
|
+
def to_complex(self) -> complex:
|
|
209
|
+
return complex(self.real, self.imag)
|
|
210
|
+
|
|
211
|
+
# =============================================
|
|
212
|
+
# Bicomplex Number (uses HypercomplexNumber for components)
|
|
213
|
+
# =============================================
|
|
214
|
+
|
|
215
|
+
@dataclass(frozen=True)
|
|
216
|
+
class BicomplexNumber(AdvancedNumber):
|
|
217
|
+
"""
|
|
218
|
+
Bicomplex number: z = z1 + z2·j where z1, z2 ∈ ℂ and j² = -1 (independent imaginary unit).
|
|
219
|
+
"""
|
|
220
|
+
z1: HypercomplexNumber # First complex component
|
|
221
|
+
z2: HypercomplexNumber # Second complex component
|
|
222
|
+
|
|
223
|
+
def __post_init__(self):
|
|
224
|
+
# Convert to HypercomplexNumber if needed
|
|
225
|
+
# DÜZELTME: Eğer z1 veya z2 HypercomplexNumber değilse, dönüştür
|
|
226
|
+
if not isinstance(self.z1, HypercomplexNumber):
|
|
227
|
+
if isinstance(self.z1, complex):
|
|
228
|
+
object.__setattr__(self, 'z1', HypercomplexNumber(self.z1.real, self.z1.imag, dimension=2))
|
|
229
|
+
elif isinstance(self.z1, (int, float)):
|
|
230
|
+
object.__setattr__(self, 'z1', HypercomplexNumber(float(self.z1), 0.0, dimension=2))
|
|
231
|
+
elif isinstance(self.z1, ComplexNumber): # Eğer ComplexNumber sınıfı varsa
|
|
232
|
+
object.__setattr__(self, 'z1', HypercomplexNumber(self.z1.real, self.z1.imag, dimension=2))
|
|
233
|
+
else:
|
|
234
|
+
# Diğer türler için from_any kullan
|
|
235
|
+
object.__setattr__(self, 'z1', HypercomplexNumber.from_any(self.z1).pad_to_dimension(2))
|
|
236
|
+
|
|
237
|
+
if not isinstance(self.z2, HypercomplexNumber):
|
|
238
|
+
if isinstance(self.z2, complex):
|
|
239
|
+
object.__setattr__(self, 'z2', HypercomplexNumber(self.z2.real, self.z2.imag, dimension=2))
|
|
240
|
+
elif isinstance(self.z2, (int, float)):
|
|
241
|
+
object.__setattr__(self, 'z2', HypercomplexNumber(float(self.z2), 0.0, dimension=2))
|
|
242
|
+
elif isinstance(self.z2, ComplexNumber): # Eğer ComplexNumber sınıfı varsa
|
|
243
|
+
object.__setattr__(self, 'z2', HypercomplexNumber(self.z2.real, self.z2.imag, dimension=2))
|
|
244
|
+
else:
|
|
245
|
+
# Diğer türler için from_any kullan
|
|
246
|
+
object.__setattr__(self, 'z2', HypercomplexNumber.from_any(self.z2).pad_to_dimension(2))
|
|
247
|
+
|
|
248
|
+
# Ensure both are complex numbers (dimension 2)
|
|
249
|
+
# DÜZELTME: dimension attribute kontrolü
|
|
250
|
+
if hasattr(self.z1, 'dimension') and self.z1.dimension != 2:
|
|
251
|
+
object.__setattr__(self, 'z1', self.z1.pad_to_dimension(2))
|
|
252
|
+
|
|
253
|
+
if hasattr(self.z2, 'dimension') and self.z2.dimension != 2:
|
|
254
|
+
object.__setattr__(self, 'z2', self.z2.pad_to_dimension(2))
|
|
255
|
+
|
|
256
|
+
def __add__(self, other):
|
|
257
|
+
if isinstance(other, BicomplexNumber):
|
|
258
|
+
return BicomplexNumber(self.z1 + other.z1, self.z2 + other.z2)
|
|
259
|
+
else:
|
|
260
|
+
# Try to convert to HypercomplexNumber first
|
|
261
|
+
try:
|
|
262
|
+
other_h = HypercomplexNumber.from_any(other)
|
|
263
|
+
if other_h.dimension == 2:
|
|
264
|
+
return BicomplexNumber(self.z1 + other_h, self.z2)
|
|
265
|
+
else:
|
|
266
|
+
# If higher dimension, convert to HypercomplexNumber
|
|
267
|
+
return self.to_hypercomplex() + other_h
|
|
268
|
+
except:
|
|
269
|
+
return NotImplemented
|
|
270
|
+
|
|
271
|
+
def __radd__(self, other):
|
|
272
|
+
return self.__add__(other)
|
|
273
|
+
|
|
274
|
+
def __sub__(self, other):
|
|
275
|
+
if isinstance(other, BicomplexNumber):
|
|
276
|
+
return BicomplexNumber(self.z1 - other.z1, self.z2 - other.z2)
|
|
277
|
+
else:
|
|
278
|
+
try:
|
|
279
|
+
other_h = HypercomplexNumber.from_any(other)
|
|
280
|
+
if other_h.dimension == 2:
|
|
281
|
+
return BicomplexNumber(self.z1 - other_h, self.z2)
|
|
282
|
+
else:
|
|
283
|
+
return self.to_hypercomplex() - other_h
|
|
284
|
+
except:
|
|
285
|
+
return NotImplemented
|
|
286
|
+
|
|
287
|
+
def __rsub__(self, other):
|
|
288
|
+
try:
|
|
289
|
+
other_h = HypercomplexNumber.from_any(other)
|
|
290
|
+
if other_h.dimension == 2:
|
|
291
|
+
return BicomplexNumber(other_h - self.z1, -self.z2)
|
|
292
|
+
else:
|
|
293
|
+
return other_h - self.to_hypercomplex()
|
|
294
|
+
except:
|
|
295
|
+
return NotImplemented
|
|
296
|
+
|
|
297
|
+
def __mul__(self, other):
|
|
298
|
+
if isinstance(other, BicomplexNumber):
|
|
299
|
+
# (z1 + z2j)(w1 + w2j) = (z1w1 - z2w2) + (z1w2 + z2w1)j
|
|
300
|
+
z1w1 = self.z1 * other.z1
|
|
301
|
+
z2w2 = self.z2 * other.z2
|
|
302
|
+
z1w2 = self.z1 * other.z2
|
|
303
|
+
z2w1 = self.z2 * other.z1
|
|
304
|
+
|
|
305
|
+
return BicomplexNumber(z1w1 - z2w2, z1w2 + z2w1)
|
|
306
|
+
else:
|
|
307
|
+
try:
|
|
308
|
+
other_h = HypercomplexNumber.from_any(other)
|
|
309
|
+
if other_h.dimension == 2:
|
|
310
|
+
return BicomplexNumber(self.z1 * other_h, self.z2 * other_h)
|
|
311
|
+
else:
|
|
312
|
+
return self.to_hypercomplex() * other_h
|
|
313
|
+
except:
|
|
314
|
+
return NotImplemented
|
|
315
|
+
|
|
316
|
+
def __rmul__(self, other):
|
|
317
|
+
return self.__mul__(other)
|
|
318
|
+
|
|
319
|
+
def __truediv__(self, other):
|
|
320
|
+
if isinstance(other, BicomplexNumber):
|
|
321
|
+
# Use conjugate method: a/b = a * conj(b) / |b|²
|
|
322
|
+
conj = other.conjugate()
|
|
323
|
+
numerator = self * conj
|
|
324
|
+
denominator = other.norm() ** 2
|
|
325
|
+
if denominator == 0:
|
|
326
|
+
raise ZeroDivisionError("Division by zero bicomplex")
|
|
327
|
+
return BicomplexNumber(numerator.z1 / denominator, numerator.z2 / denominator)
|
|
328
|
+
else:
|
|
329
|
+
try:
|
|
330
|
+
other_h = HypercomplexNumber.from_any(other)
|
|
331
|
+
if other == 0:
|
|
332
|
+
raise ZeroDivisionError("Division by zero")
|
|
333
|
+
return BicomplexNumber(self.z1 / other_h, self.z2 / other_h)
|
|
334
|
+
except:
|
|
335
|
+
return NotImplemented
|
|
336
|
+
|
|
337
|
+
def __rtruediv__(self, other):
|
|
338
|
+
try:
|
|
339
|
+
other_h = HypercomplexNumber.from_any(other)
|
|
340
|
+
if other_h.dimension == 2:
|
|
341
|
+
return BicomplexNumber(other_h, HypercomplexNumber(0, 0, dimension=2)) / self
|
|
342
|
+
else:
|
|
343
|
+
return other_h / self.to_hypercomplex()
|
|
344
|
+
except:
|
|
345
|
+
return NotImplemented
|
|
346
|
+
|
|
347
|
+
def __neg__(self):
|
|
348
|
+
return BicomplexNumber(-self.z1, -self.z2)
|
|
349
|
+
|
|
350
|
+
def __pos__(self):
|
|
351
|
+
return self
|
|
352
|
+
|
|
353
|
+
def __abs__(self):
|
|
354
|
+
return self.norm()
|
|
355
|
+
|
|
356
|
+
def __eq__(self, other):
|
|
357
|
+
if isinstance(other, BicomplexNumber):
|
|
358
|
+
return self.z1 == other.z1 and self.z2 == other.z2
|
|
359
|
+
else:
|
|
360
|
+
try:
|
|
361
|
+
other_h = HypercomplexNumber.from_any(other)
|
|
362
|
+
if other_h.dimension == 2:
|
|
363
|
+
return self.z1 == other_h and self.z2 == HypercomplexNumber(0, 0, dimension=2)
|
|
364
|
+
else:
|
|
365
|
+
return self.to_hypercomplex() == other_h
|
|
366
|
+
except:
|
|
367
|
+
return False
|
|
368
|
+
|
|
369
|
+
def __ne__(self, other):
|
|
370
|
+
return not self.__eq__(other)
|
|
371
|
+
|
|
372
|
+
def __hash__(self):
|
|
373
|
+
return hash((self.z1, self.z2))
|
|
374
|
+
|
|
375
|
+
def __repr__(self):
|
|
376
|
+
return f"BicomplexNumber(z1={self.z1}, z2={self.z2})"
|
|
377
|
+
|
|
378
|
+
def __str__(self):
|
|
379
|
+
return f"({self.z1}) + ({self.z2})·j"
|
|
380
|
+
|
|
381
|
+
def norm(self) -> float:
|
|
382
|
+
return math.sqrt(self.z1.norm()**2 + self.z2.norm()**2)
|
|
383
|
+
|
|
384
|
+
def conjugate(self):
|
|
385
|
+
return BicomplexNumber(self.z1.conjugate(), -self.z2)
|
|
386
|
+
|
|
387
|
+
def components(self) -> Tuple[float, float, float, float]:
|
|
388
|
+
"""Return (Re(z1), Im(z1), Re(z2), Im(z2))."""
|
|
389
|
+
return (self.z1[0], self.z1[1], self.z2[0], self.z2[1])
|
|
390
|
+
|
|
391
|
+
def to_hypercomplex(self) -> HypercomplexNumber:
|
|
392
|
+
"""Convert to 4D HypercomplexNumber."""
|
|
393
|
+
return HypercomplexNumber(
|
|
394
|
+
self.z1[0], self.z1[1],
|
|
395
|
+
self.z2[0], self.z2[1],
|
|
396
|
+
dimension=4
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# =============================================
|
|
402
|
+
# Neutrosophic Number
|
|
403
|
+
# =============================================
|
|
404
|
+
|
|
405
|
+
@dataclass(frozen=True)
|
|
406
|
+
class NeutrosophicNumber(AdvancedNumber):
|
|
407
|
+
"""
|
|
408
|
+
Neutrosophic number: a + bI where I² = I (indeterminacy).
|
|
409
|
+
"""
|
|
410
|
+
determinate: float
|
|
411
|
+
indeterminate: float
|
|
412
|
+
|
|
413
|
+
def __add__(self, other):
|
|
414
|
+
if isinstance(other, NeutrosophicNumber):
|
|
415
|
+
return NeutrosophicNumber(
|
|
416
|
+
self.determinate + other.determinate,
|
|
417
|
+
self.indeterminate + other.indeterminate
|
|
418
|
+
)
|
|
419
|
+
elif isinstance(other, (int, float)):
|
|
420
|
+
return NeutrosophicNumber(self.determinate + other, self.indeterminate)
|
|
421
|
+
return NotImplemented
|
|
422
|
+
|
|
423
|
+
def __radd__(self, other):
|
|
424
|
+
return self.__add__(other)
|
|
425
|
+
|
|
426
|
+
def __sub__(self, other):
|
|
427
|
+
if isinstance(other, NeutrosophicNumber):
|
|
428
|
+
return NeutrosophicNumber(
|
|
429
|
+
self.determinate - other.determinate,
|
|
430
|
+
self.indeterminate - other.indeterminate
|
|
431
|
+
)
|
|
432
|
+
elif isinstance(other, (int, float)):
|
|
433
|
+
return NeutrosophicNumber(self.determinate - other, self.indeterminate)
|
|
434
|
+
return NotImplemented
|
|
435
|
+
|
|
436
|
+
def __rsub__(self, other):
|
|
437
|
+
if isinstance(other, (int, float)):
|
|
438
|
+
return NeutrosophicNumber(other - self.determinate, -self.indeterminate)
|
|
439
|
+
return NotImplemented
|
|
440
|
+
|
|
441
|
+
def __mul__(self, other):
|
|
442
|
+
if isinstance(other, NeutrosophicNumber):
|
|
443
|
+
# (a + bI)(c + dI) = ac + (ad + bc + bd)I
|
|
444
|
+
determinate = self.determinate * other.determinate
|
|
445
|
+
indeterminate = (self.determinate * other.indeterminate +
|
|
446
|
+
self.indeterminate * other.determinate +
|
|
447
|
+
self.indeterminate * other.indeterminate)
|
|
448
|
+
return NeutrosophicNumber(determinate, indeterminate)
|
|
449
|
+
elif isinstance(other, (int, float)):
|
|
450
|
+
return NeutrosophicNumber(
|
|
451
|
+
self.determinate * other,
|
|
452
|
+
self.indeterminate * other
|
|
453
|
+
)
|
|
454
|
+
return NotImplemented
|
|
455
|
+
|
|
456
|
+
def __rmul__(self, other):
|
|
457
|
+
return self.__mul__(other)
|
|
458
|
+
|
|
459
|
+
def __truediv__(self, other):
|
|
460
|
+
if isinstance(other, (int, float)):
|
|
461
|
+
if other == 0:
|
|
462
|
+
raise ZeroDivisionError("Division by zero")
|
|
463
|
+
return NeutrosophicNumber(
|
|
464
|
+
self.determinate / other,
|
|
465
|
+
self.indeterminate / other
|
|
466
|
+
)
|
|
467
|
+
elif isinstance(other, NeutrosophicNumber):
|
|
468
|
+
# Use conjugate: (a + bI)/(c + dI) = (a + bI)(c - dI) / (c² + (c-d)d)
|
|
469
|
+
conj = other.conjugate()
|
|
470
|
+
numerator = self * conj
|
|
471
|
+
denominator = other.determinate**2 + (other.determinate - other.indeterminate) * other.indeterminate
|
|
472
|
+
if denominator == 0:
|
|
473
|
+
raise ZeroDivisionError("Division by zero neutrosophic")
|
|
474
|
+
return NeutrosophicNumber(
|
|
475
|
+
numerator.determinate / denominator,
|
|
476
|
+
numerator.indeterminate / denominator
|
|
477
|
+
)
|
|
478
|
+
return NotImplemented
|
|
479
|
+
|
|
480
|
+
def __rtruediv__(self, other):
|
|
481
|
+
if isinstance(other, (int, float)):
|
|
482
|
+
return NeutrosophicNumber(other, 0) / self
|
|
483
|
+
return NotImplemented
|
|
484
|
+
|
|
485
|
+
def __neg__(self):
|
|
486
|
+
return NeutrosophicNumber(-self.determinate, -self.indeterminate)
|
|
487
|
+
|
|
488
|
+
def __pos__(self):
|
|
489
|
+
return self
|
|
490
|
+
|
|
491
|
+
def __abs__(self):
|
|
492
|
+
return self.norm()
|
|
493
|
+
|
|
494
|
+
def __eq__(self, other):
|
|
495
|
+
if isinstance(other, NeutrosophicNumber):
|
|
496
|
+
return (math.isclose(self.determinate, other.determinate) and
|
|
497
|
+
math.isclose(self.indeterminate, other.indeterminate))
|
|
498
|
+
elif isinstance(other, (int, float)):
|
|
499
|
+
return math.isclose(self.determinate, other) and math.isclose(self.indeterminate, 0)
|
|
500
|
+
return False
|
|
501
|
+
|
|
502
|
+
def __ne__(self, other):
|
|
503
|
+
return not self.__eq__(other)
|
|
504
|
+
|
|
505
|
+
def __hash__(self):
|
|
506
|
+
return hash((round(self.determinate, 12), round(self.indeterminate, 12)))
|
|
507
|
+
|
|
508
|
+
def __repr__(self):
|
|
509
|
+
return f"NeutrosophicNumber({self.determinate}, {self.indeterminate})"
|
|
510
|
+
|
|
511
|
+
def __str__(self):
|
|
512
|
+
if self.indeterminate >= 0:
|
|
513
|
+
return f"{self.determinate} + {self.indeterminate}I"
|
|
514
|
+
else:
|
|
515
|
+
return f"{self.determinate} - {-self.indeterminate}I"
|
|
516
|
+
|
|
517
|
+
def norm(self) -> float:
|
|
518
|
+
return math.sqrt(self.determinate**2 + self.indeterminate**2)
|
|
519
|
+
|
|
520
|
+
def conjugate(self):
|
|
521
|
+
return NeutrosophicNumber(self.determinate, -self.indeterminate)
|
|
522
|
+
|
|
523
|
+
# =============================================
|
|
524
|
+
# Unified Number System: HypercomplexNumber
|
|
525
|
+
# =============================================
|
|
526
|
+
|
|
527
|
+
class HypercomplexNumber(AdvancedNumber):
|
|
528
|
+
"""
|
|
529
|
+
Unified hypercomplex number implementation for all dimensions.
|
|
530
|
+
Supports: Real (1), Complex (2), Quaternion (4), Octonion (8), etc.
|
|
531
|
+
"""
|
|
532
|
+
|
|
533
|
+
# Dimension names
|
|
534
|
+
DIM_NAMES = {
|
|
535
|
+
1: "Real",
|
|
536
|
+
2: "Complex",
|
|
537
|
+
4: "Quaternion",
|
|
538
|
+
8: "Octonion",
|
|
539
|
+
16: "Sedenion",
|
|
540
|
+
32: "Pathion",
|
|
541
|
+
64: "Chingon",
|
|
542
|
+
128: "Routon",
|
|
543
|
+
256: "Voudon"
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
def __init__(self, *components: float, dimension: Optional[int] = None):
|
|
547
|
+
"""
|
|
548
|
+
Initialize hypercomplex number.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
*components: Number components
|
|
552
|
+
dimension: Dimension (power of 2). If None, inferred from components.
|
|
553
|
+
"""
|
|
554
|
+
if dimension is None:
|
|
555
|
+
# Find smallest power of 2 >= len(components)
|
|
556
|
+
n = len(components)
|
|
557
|
+
dimension = 1
|
|
558
|
+
while dimension < n and dimension < 256:
|
|
559
|
+
dimension <<= 1
|
|
560
|
+
|
|
561
|
+
if dimension & (dimension - 1) != 0 or dimension < 1:
|
|
562
|
+
raise ValueError(f"Dimension must be power of 2 (1-256), got {dimension}")
|
|
563
|
+
|
|
564
|
+
self.dimension = dimension
|
|
565
|
+
|
|
566
|
+
# Pad or truncate components
|
|
567
|
+
if len(components) < dimension:
|
|
568
|
+
self.coeffs = list(components) + [0.0] * (dimension - len(components))
|
|
569
|
+
elif len(components) > dimension:
|
|
570
|
+
self.coeffs = components[:dimension]
|
|
571
|
+
else:
|
|
572
|
+
self.coeffs = list(components)
|
|
573
|
+
|
|
574
|
+
# Type name - DÜZELTİLDİ: 'dim' yerine 'dimension' kullan
|
|
575
|
+
self.type_name = self.DIM_NAMES.get(dimension, f"Hypercomplex{dimension}")
|
|
576
|
+
|
|
577
|
+
@classmethod
|
|
578
|
+
def from_real(cls, value: float) -> 'HypercomplexNumber':
|
|
579
|
+
"""Create from a real number."""
|
|
580
|
+
return cls(value, dimension=1)
|
|
581
|
+
|
|
582
|
+
@classmethod
|
|
583
|
+
def from_complex(cls, real: float, imag: float) -> 'HypercomplexNumber':
|
|
584
|
+
"""Create from complex components."""
|
|
585
|
+
return cls(real, imag, dimension=2)
|
|
586
|
+
|
|
587
|
+
@classmethod
|
|
588
|
+
def from_quaternion(cls, w: float, x: float, y: float, z: float) -> 'HypercomplexNumber':
|
|
589
|
+
"""Create from quaternion components."""
|
|
590
|
+
return cls(w, x, y, z, dimension=4)
|
|
591
|
+
|
|
592
|
+
@classmethod
|
|
593
|
+
def from_octonion(cls, *coeffs: float) -> 'HypercomplexNumber':
|
|
594
|
+
"""Create from octonion components."""
|
|
595
|
+
if len(coeffs) != 8:
|
|
596
|
+
coeffs = list(coeffs) + [0.0] * (8 - len(coeffs))
|
|
597
|
+
return cls(*coeffs, dimension=8)
|
|
598
|
+
|
|
599
|
+
@classmethod
|
|
600
|
+
def from_any(cls, value: Any) -> 'HypercomplexNumber':
|
|
601
|
+
"""Create from any numeric type."""
|
|
602
|
+
if isinstance(value, HypercomplexNumber):
|
|
603
|
+
return value
|
|
604
|
+
elif isinstance(value, (int, float)):
|
|
605
|
+
return cls.from_real(float(value))
|
|
606
|
+
elif isinstance(value, complex):
|
|
607
|
+
return cls.from_complex(value.real, value.imag)
|
|
608
|
+
elif isinstance(value, (list, tuple)):
|
|
609
|
+
return cls(*value)
|
|
610
|
+
else:
|
|
611
|
+
try:
|
|
612
|
+
return cls.from_real(float(value))
|
|
613
|
+
except:
|
|
614
|
+
raise ValueError(f"Cannot convert {type(value)} to HypercomplexNumber")
|
|
615
|
+
|
|
616
|
+
@property
|
|
617
|
+
def real(self) -> float:
|
|
618
|
+
"""Real part (first component)."""
|
|
619
|
+
return self.coeffs[0]
|
|
620
|
+
|
|
621
|
+
@property
|
|
622
|
+
def imag(self) -> float:
|
|
623
|
+
"""Imaginary part (for complex numbers)."""
|
|
624
|
+
if self.dimension >= 2:
|
|
625
|
+
return self.coeffs[1]
|
|
626
|
+
return 0.0
|
|
627
|
+
|
|
628
|
+
def __len__(self):
|
|
629
|
+
return self.dimension
|
|
630
|
+
|
|
631
|
+
def __getitem__(self, idx):
|
|
632
|
+
return self.coeffs[idx]
|
|
633
|
+
|
|
634
|
+
def __iter__(self):
|
|
635
|
+
return iter(self.coeffs)
|
|
636
|
+
|
|
637
|
+
def __add__(self, other):
|
|
638
|
+
# Convert other to HypercomplexNumber if needed
|
|
639
|
+
if not isinstance(other, HypercomplexNumber):
|
|
640
|
+
try:
|
|
641
|
+
other = self.from_any(other)
|
|
642
|
+
except:
|
|
643
|
+
return NotImplemented
|
|
644
|
+
|
|
645
|
+
# Handle different dimensions
|
|
646
|
+
if self.dimension != other.dimension:
|
|
647
|
+
common_dim = max(self.dimension, other.dimension)
|
|
648
|
+
self_padded = self.pad_to_dimension(common_dim)
|
|
649
|
+
other_padded = other.pad_to_dimension(common_dim)
|
|
650
|
+
return self_padded + other_padded
|
|
651
|
+
|
|
652
|
+
new_coeffs = [a + b for a, b in zip(self.coeffs, other.coeffs)]
|
|
653
|
+
return HypercomplexNumber(*new_coeffs, dimension=self.dimension)
|
|
654
|
+
|
|
655
|
+
def __radd__(self, other):
|
|
656
|
+
return self.__add__(other)
|
|
657
|
+
|
|
658
|
+
def __sub__(self, other):
|
|
659
|
+
# Convert other to HypercomplexNumber if needed
|
|
660
|
+
if not isinstance(other, HypercomplexNumber):
|
|
661
|
+
try:
|
|
662
|
+
other = self.from_any(other)
|
|
663
|
+
except:
|
|
664
|
+
return NotImplemented
|
|
665
|
+
|
|
666
|
+
# Handle different dimensions
|
|
667
|
+
if self.dimension != other.dimension:
|
|
668
|
+
common_dim = max(self.dimension, other.dimension)
|
|
669
|
+
self_padded = self.pad_to_dimension(common_dim)
|
|
670
|
+
other_padded = other.pad_to_dimension(common_dim)
|
|
671
|
+
return self_padded - other_padded
|
|
672
|
+
|
|
673
|
+
new_coeffs = [a - b for a, b in zip(self.coeffs, other.coeffs)]
|
|
674
|
+
return HypercomplexNumber(*new_coeffs, dimension=self.dimension)
|
|
675
|
+
|
|
676
|
+
def __rsub__(self, other):
|
|
677
|
+
# Convert other to HypercomplexNumber
|
|
678
|
+
try:
|
|
679
|
+
other_num = self.from_any(other)
|
|
680
|
+
return other_num - self
|
|
681
|
+
except:
|
|
682
|
+
return NotImplemented
|
|
683
|
+
|
|
684
|
+
def _multiply_complex(self, other: 'HypercomplexNumber') -> 'HypercomplexNumber':
|
|
685
|
+
"""Complex multiplication (dimension 2)."""
|
|
686
|
+
a, b = self.coeffs
|
|
687
|
+
c, d = other.coeffs
|
|
688
|
+
real = a*c - b*d
|
|
689
|
+
imag = a*d + b*c
|
|
690
|
+
return HypercomplexNumber(real, imag, dimension=2)
|
|
691
|
+
|
|
692
|
+
def _multiply_quaternion(self, other: 'HypercomplexNumber') -> 'HypercomplexNumber':
|
|
693
|
+
"""Quaternion multiplication (dimension 4)."""
|
|
694
|
+
w1, x1, y1, z1 = self.coeffs
|
|
695
|
+
w2, x2, y2, z2 = other.coeffs
|
|
696
|
+
|
|
697
|
+
w = w1*w2 - x1*x2 - y1*y2 - z1*z2
|
|
698
|
+
x = w1*x2 + x1*w2 + y1*z2 - z1*y2
|
|
699
|
+
y = w1*y2 - x1*z2 + y1*w2 + z1*x2
|
|
700
|
+
z = w1*z2 + x1*y2 - y1*x2 + z1*w2
|
|
701
|
+
|
|
702
|
+
return HypercomplexNumber(w, x, y, z, dimension=4)
|
|
703
|
+
|
|
704
|
+
def __mul__(self, other):
|
|
705
|
+
# Convert other to HypercomplexNumber if needed
|
|
706
|
+
if not isinstance(other, HypercomplexNumber):
|
|
707
|
+
try:
|
|
708
|
+
other = self.from_any(other)
|
|
709
|
+
except:
|
|
710
|
+
return NotImplemented
|
|
711
|
+
|
|
712
|
+
# Handle different dimensions
|
|
713
|
+
if self.dimension != other.dimension:
|
|
714
|
+
common_dim = max(self.dimension, other.dimension)
|
|
715
|
+
self_padded = self.pad_to_dimension(common_dim)
|
|
716
|
+
other_padded = other.pad_to_dimension(common_dim)
|
|
717
|
+
return self_padded * other_padded
|
|
718
|
+
|
|
719
|
+
# Different multiplication rules based on dimension
|
|
720
|
+
if self.dimension == 1:
|
|
721
|
+
# Real multiplication
|
|
722
|
+
return HypercomplexNumber(self.coeffs[0] * other.coeffs[0], dimension=1)
|
|
723
|
+
|
|
724
|
+
elif self.dimension == 2:
|
|
725
|
+
# Complex multiplication
|
|
726
|
+
return self._multiply_complex(other)
|
|
727
|
+
|
|
728
|
+
elif self.dimension == 4:
|
|
729
|
+
# Quaternion multiplication
|
|
730
|
+
return self._multiply_quaternion(other)
|
|
731
|
+
|
|
732
|
+
else:
|
|
733
|
+
# For higher dimensions, use component-wise as fallback
|
|
734
|
+
warnings.warn(f"Using component-wise multiplication for {self.type_name}", RuntimeWarning)
|
|
735
|
+
new_coeffs = [a * b for a, b in zip(self.coeffs, other.coeffs)]
|
|
736
|
+
return HypercomplexNumber(*new_coeffs, dimension=self.dimension)
|
|
737
|
+
|
|
738
|
+
def __rmul__(self, other):
|
|
739
|
+
return self.__mul__(other)
|
|
740
|
+
|
|
741
|
+
def __truediv__(self, other):
|
|
742
|
+
# Convert other to HypercomplexNumber if needed
|
|
743
|
+
if not isinstance(other, HypercomplexNumber):
|
|
744
|
+
try:
|
|
745
|
+
other = self.from_any(other)
|
|
746
|
+
except:
|
|
747
|
+
return NotImplemented
|
|
748
|
+
|
|
749
|
+
# Use inverse for division
|
|
750
|
+
return self * other.inverse()
|
|
751
|
+
|
|
752
|
+
def __rtruediv__(self, other):
|
|
753
|
+
# Convert other to HypercomplexNumber
|
|
754
|
+
try:
|
|
755
|
+
other_num = self.from_any(other)
|
|
756
|
+
return other_num / self
|
|
757
|
+
except:
|
|
758
|
+
return NotImplemented
|
|
759
|
+
|
|
760
|
+
def __neg__(self):
|
|
761
|
+
new_coeffs = [-c for c in self.coeffs]
|
|
762
|
+
return HypercomplexNumber(*new_coeffs, dimension=self.dimension)
|
|
763
|
+
|
|
764
|
+
def __pos__(self):
|
|
765
|
+
return self
|
|
766
|
+
|
|
767
|
+
def __abs__(self):
|
|
768
|
+
return self.norm()
|
|
769
|
+
|
|
770
|
+
def __eq__(self, other):
|
|
771
|
+
if not isinstance(other, HypercomplexNumber):
|
|
772
|
+
try:
|
|
773
|
+
other = self.from_any(other)
|
|
774
|
+
except:
|
|
775
|
+
return False
|
|
776
|
+
|
|
777
|
+
if self.dimension != other.dimension:
|
|
778
|
+
return False
|
|
779
|
+
|
|
780
|
+
return all(math.isclose(a, b, abs_tol=1e-12) for a, b in zip(self.coeffs, other.coeffs))
|
|
781
|
+
|
|
782
|
+
def __ne__(self, other):
|
|
783
|
+
return not self.__eq__(other)
|
|
784
|
+
|
|
785
|
+
def __hash__(self):
|
|
786
|
+
return hash(tuple(round(c, 12) for c in self.coeffs))
|
|
787
|
+
|
|
788
|
+
def __repr__(self):
|
|
789
|
+
return f"HypercomplexNumber({', '.join(map(str, self.coeffs))}, dimension={self.dimension})"
|
|
790
|
+
|
|
791
|
+
def __str__(self):
|
|
792
|
+
if self.dimension == 1:
|
|
793
|
+
return f"{self.type_name}({self.coeffs[0]})"
|
|
794
|
+
elif self.dimension == 2:
|
|
795
|
+
a, b = self.coeffs
|
|
796
|
+
if b >= 0:
|
|
797
|
+
return f"{self.type_name}({a} + {b}i)"
|
|
798
|
+
else:
|
|
799
|
+
return f"{self.type_name}({a} - {-b}i)"
|
|
800
|
+
elif self.dimension <= 8:
|
|
801
|
+
non_zero = [(i, c) for i, c in enumerate(self.coeffs) if abs(c) > 1e-10]
|
|
802
|
+
if not non_zero:
|
|
803
|
+
return f"{self.type_name}(0)"
|
|
804
|
+
|
|
805
|
+
parts = []
|
|
806
|
+
for i, c in non_zero:
|
|
807
|
+
if i == 0:
|
|
808
|
+
parts.append(f"{c:.4f}")
|
|
809
|
+
else:
|
|
810
|
+
sign = "+" if c >= 0 else "-"
|
|
811
|
+
parts.append(f"{sign} {abs(c):.4f}e{i}")
|
|
812
|
+
return f"{self.type_name}({' '.join(parts)})"
|
|
813
|
+
else:
|
|
814
|
+
return f"{self.type_name}[real={self.real:.4f}, norm={self.norm():.4f}, dim={self.dimension}]"
|
|
815
|
+
|
|
816
|
+
def norm(self) -> float:
|
|
817
|
+
"""Euclidean norm."""
|
|
818
|
+
return math.sqrt(sum(c**2 for c in self.coeffs))
|
|
819
|
+
|
|
820
|
+
def conjugate(self):
|
|
821
|
+
"""Conjugate (negate all imaginary parts)."""
|
|
822
|
+
if self.dimension == 1:
|
|
823
|
+
return self
|
|
824
|
+
|
|
825
|
+
new_coeffs = self.coeffs.copy()
|
|
826
|
+
for i in range(1, self.dimension):
|
|
827
|
+
new_coeffs[i] = -new_coeffs[i]
|
|
828
|
+
|
|
829
|
+
return HypercomplexNumber(*new_coeffs, dimension=self.dimension)
|
|
830
|
+
|
|
831
|
+
def inverse(self):
|
|
832
|
+
"""Multiplicative inverse."""
|
|
833
|
+
norm_sq = self.norm() ** 2
|
|
834
|
+
if norm_sq == 0:
|
|
835
|
+
raise ZeroDivisionError("Cannot invert zero element")
|
|
836
|
+
|
|
837
|
+
conj = self.conjugate()
|
|
838
|
+
new_coeffs = [c / norm_sq for c in conj.coeffs]
|
|
839
|
+
return HypercomplexNumber(*new_coeffs, dimension=self.dimension)
|
|
840
|
+
|
|
841
|
+
def pad_to_dimension(self, new_dim: int) -> 'HypercomplexNumber':
|
|
842
|
+
"""Pad to higher dimension with zeros."""
|
|
843
|
+
if new_dim < self.dimension:
|
|
844
|
+
raise ValueError(f"Cannot pad to smaller dimension: {new_dim} < {self.dimension}")
|
|
845
|
+
|
|
846
|
+
if new_dim == self.dimension:
|
|
847
|
+
return self
|
|
848
|
+
|
|
849
|
+
new_coeffs = self.coeffs + [0.0] * (new_dim - self.dimension)
|
|
850
|
+
return HypercomplexNumber(*new_coeffs, dimension=new_dim)
|
|
851
|
+
|
|
852
|
+
def truncate_to_dimension(self, new_dim: int) -> 'HypercomplexNumber':
|
|
853
|
+
"""Truncate to smaller dimension."""
|
|
854
|
+
if new_dim > self.dimension:
|
|
855
|
+
raise ValueError(f"Cannot truncate to larger dimension: {new_dim} > {self.dimension}")
|
|
856
|
+
|
|
857
|
+
if new_dim == self.dimension:
|
|
858
|
+
return self
|
|
859
|
+
|
|
860
|
+
new_coeffs = self.coeffs[:new_dim]
|
|
861
|
+
return HypercomplexNumber(*new_coeffs, dimension=new_dim)
|
|
862
|
+
|
|
863
|
+
def to_list(self) -> List[float]:
|
|
864
|
+
"""Convert to Python list."""
|
|
865
|
+
return self.coeffs.copy()
|
|
866
|
+
|
|
867
|
+
def to_numpy(self) -> np.ndarray:
|
|
868
|
+
"""Convert to numpy array."""
|
|
869
|
+
return np.array(self.coeffs, dtype=np.float64)
|
|
870
|
+
|
|
871
|
+
def copy(self) -> 'HypercomplexNumber':
|
|
872
|
+
"""Generate a copy."""
|
|
873
|
+
return HypercomplexNumber(*self.coeffs, dimension=self.dimension)
|
|
874
|
+
|
|
875
|
+
def __float__(self):
|
|
876
|
+
"""Convert to float (returns real part)."""
|
|
877
|
+
return float(self.real)
|
|
878
|
+
|
|
879
|
+
def to_complex(self) -> complex:
|
|
880
|
+
"""Convert to Python complex if possible."""
|
|
881
|
+
if self.dimension >= 2:
|
|
882
|
+
return complex(self.coeffs[0], self.coeffs[1])
|
|
883
|
+
return complex(self.coeffs[0], 0.0)
|
|
884
|
+
|
|
885
|
+
def to_hypercomplex(self) -> 'HypercomplexNumber':
|
|
886
|
+
"""Convert to HypercomplexNumber (identity for this class)."""
|
|
887
|
+
return self
|
|
888
|
+
|
|
889
|
+
# =============================================
|
|
890
|
+
# Neutrosophic Number
|
|
891
|
+
# =============================================
|
|
892
|
+
|
|
893
|
+
@dataclass(frozen=True)
|
|
894
|
+
class NeutrosophicNumber(AdvancedNumber):
|
|
895
|
+
"""
|
|
896
|
+
Neutrosophic number: a + bI where I² = I (indeterminacy).
|
|
897
|
+
"""
|
|
898
|
+
determinate: float
|
|
899
|
+
indeterminate: float
|
|
900
|
+
|
|
901
|
+
def __add__(self, other):
|
|
902
|
+
if isinstance(other, NeutrosophicNumber):
|
|
903
|
+
return NeutrosophicNumber(
|
|
904
|
+
self.determinate + other.determinate,
|
|
905
|
+
self.indeterminate + other.indeterminate
|
|
906
|
+
)
|
|
907
|
+
elif isinstance(other, (int, float)):
|
|
908
|
+
return NeutrosophicNumber(self.determinate + other, self.indeterminate)
|
|
909
|
+
return NotImplemented
|
|
910
|
+
|
|
911
|
+
def __radd__(self, other):
|
|
912
|
+
return self.__add__(other)
|
|
913
|
+
|
|
914
|
+
def __sub__(self, other):
|
|
915
|
+
if isinstance(other, NeutrosophicNumber):
|
|
916
|
+
return NeutrosophicNumber(
|
|
917
|
+
self.determinate - other.determinate,
|
|
918
|
+
self.indeterminate - other.indeterminate
|
|
919
|
+
)
|
|
920
|
+
elif isinstance(other, (int, float)):
|
|
921
|
+
return NeutrosophicNumber(self.determinate - other, self.indeterminate)
|
|
922
|
+
return NotImplemented
|
|
923
|
+
|
|
924
|
+
def __rsub__(self, other):
|
|
925
|
+
if isinstance(other, (int, float)):
|
|
926
|
+
return NeutrosophicNumber(other - self.determinate, -self.indeterminate)
|
|
927
|
+
return NotImplemented
|
|
928
|
+
|
|
929
|
+
def __mul__(self, other):
|
|
930
|
+
if isinstance(other, NeutrosophicNumber):
|
|
931
|
+
# (a + bI)(c + dI) = ac + (ad + bc + bd)I
|
|
932
|
+
determinate = self.determinate * other.determinate
|
|
933
|
+
indeterminate = (self.determinate * other.indeterminate +
|
|
934
|
+
self.indeterminate * other.determinate +
|
|
935
|
+
self.indeterminate * other.indeterminate)
|
|
936
|
+
return NeutrosophicNumber(determinate, indeterminate)
|
|
937
|
+
elif isinstance(other, (int, float)):
|
|
938
|
+
return NeutrosophicNumber(
|
|
939
|
+
self.determinate * other,
|
|
940
|
+
self.indeterminate * other
|
|
941
|
+
)
|
|
942
|
+
return NotImplemented
|
|
943
|
+
|
|
944
|
+
def __rmul__(self, other):
|
|
945
|
+
return self.__mul__(other)
|
|
946
|
+
|
|
947
|
+
def __truediv__(self, other):
|
|
948
|
+
if isinstance(other, (int, float)):
|
|
949
|
+
if other == 0:
|
|
950
|
+
raise ZeroDivisionError("Division by zero")
|
|
951
|
+
return NeutrosophicNumber(
|
|
952
|
+
self.determinate / other,
|
|
953
|
+
self.indeterminate / other
|
|
954
|
+
)
|
|
955
|
+
elif isinstance(other, NeutrosophicNumber):
|
|
956
|
+
# Use conjugate: (a + bI)/(c + dI) = (a + bI)(c - dI) / (c² + (c-d)d)
|
|
957
|
+
conj = other.conjugate()
|
|
958
|
+
numerator = self * conj
|
|
959
|
+
denominator = other.determinate**2 + (other.determinate - other.indeterminate) * other.indeterminate
|
|
960
|
+
if denominator == 0:
|
|
961
|
+
raise ZeroDivisionError("Division by zero neutrosophic")
|
|
962
|
+
return NeutrosophicNumber(
|
|
963
|
+
numerator.determinate / denominator,
|
|
964
|
+
numerator.indeterminate / denominator
|
|
965
|
+
)
|
|
966
|
+
return NotImplemented
|
|
967
|
+
|
|
968
|
+
def __rtruediv__(self, other):
|
|
969
|
+
if isinstance(other, (int, float)):
|
|
970
|
+
return NeutrosophicNumber(other, 0) / self
|
|
971
|
+
return NotImplemented
|
|
972
|
+
|
|
973
|
+
def __neg__(self):
|
|
974
|
+
return NeutrosophicNumber(-self.determinate, -self.indeterminate)
|
|
975
|
+
|
|
976
|
+
def __pos__(self):
|
|
977
|
+
return self
|
|
978
|
+
|
|
979
|
+
def __abs__(self):
|
|
980
|
+
return self.norm()
|
|
981
|
+
|
|
982
|
+
def __eq__(self, other):
|
|
983
|
+
if isinstance(other, NeutrosophicNumber):
|
|
984
|
+
return (math.isclose(self.determinate, other.determinate) and
|
|
985
|
+
math.isclose(self.indeterminate, other.indeterminate))
|
|
986
|
+
elif isinstance(other, (int, float)):
|
|
987
|
+
return math.isclose(self.determinate, other) and math.isclose(self.indeterminate, 0)
|
|
988
|
+
return False
|
|
989
|
+
|
|
990
|
+
def __ne__(self, other):
|
|
991
|
+
return not self.__eq__(other)
|
|
992
|
+
|
|
993
|
+
def __hash__(self):
|
|
994
|
+
return hash((round(self.determinate, 12), round(self.indeterminate, 12)))
|
|
995
|
+
|
|
996
|
+
def __repr__(self):
|
|
997
|
+
return f"NeutrosophicNumber({self.determinate}, {self.indeterminate})"
|
|
998
|
+
|
|
999
|
+
def __str__(self):
|
|
1000
|
+
if self.indeterminate >= 0:
|
|
1001
|
+
return f"{self.determinate} + {self.indeterminate}I"
|
|
1002
|
+
else:
|
|
1003
|
+
return f"{self.determinate} - {-self.indeterminate}I"
|
|
1004
|
+
|
|
1005
|
+
def norm(self) -> float:
|
|
1006
|
+
return math.sqrt(self.determinate**2 + self.indeterminate**2)
|
|
1007
|
+
|
|
1008
|
+
def conjugate(self):
|
|
1009
|
+
return NeutrosophicNumber(self.determinate, -self.indeterminate)
|
|
1010
|
+
|
|
1011
|
+
def to_hypercomplex(self) -> HypercomplexNumber:
|
|
1012
|
+
"""Convert to 2D HypercomplexNumber (treats I as imaginary unit)."""
|
|
1013
|
+
return HypercomplexNumber(self.determinate, self.indeterminate, dimension=2)
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
# =============================================
|
|
1017
|
+
# Factory Functions (Simplified)
|
|
1018
|
+
# =============================================
|
|
1019
|
+
|
|
1020
|
+
def Real(x: float) -> HypercomplexNumber:
|
|
1021
|
+
"""Generate a real number (1D hypercomplex)."""
|
|
1022
|
+
return HypercomplexNumber.from_real(x)
|
|
1023
|
+
|
|
1024
|
+
def Complex(real: float, imag: float) -> HypercomplexNumber:
|
|
1025
|
+
"""Generate a complex number (2D hypercomplex)."""
|
|
1026
|
+
return HypercomplexNumber.from_complex(real, imag)
|
|
1027
|
+
|
|
1028
|
+
def Quaternion(w: float, x: float, y: float, z: float) -> HypercomplexNumber:
|
|
1029
|
+
"""Generate a quaternion (4D hypercomplex)."""
|
|
1030
|
+
return HypercomplexNumber.from_quaternion(w, x, y, z)
|
|
1031
|
+
|
|
1032
|
+
def Octonion(*coeffs: float) -> HypercomplexNumber:
|
|
1033
|
+
"""Generate an octonion (8D hypercomplex)."""
|
|
1034
|
+
return HypercomplexNumber.from_octonion(*coeffs)
|
|
1035
|
+
|
|
1036
|
+
def Bicomplex(z1_real: float, z1_imag: float, z2_real: float, z2_imag: float) -> BicomplexNumber:
|
|
1037
|
+
"""Generate a bicomplex number."""
|
|
1038
|
+
z1 = HypercomplexNumber(z1_real, z1_imag, dimension=2)
|
|
1039
|
+
z2 = HypercomplexNumber(z2_real, z2_imag, dimension=2)
|
|
1040
|
+
return BicomplexNumber(z1, z2)
|
|
1041
|
+
|
|
1042
|
+
def Neutrosophic(determinate: float, indeterminate: float) -> NeutrosophicNumber:
|
|
1043
|
+
"""Generate a neutrosophic number."""
|
|
1044
|
+
return NeutrosophicNumber(determinate, indeterminate)
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
def Sedenion(*coeffs) -> HypercomplexNumber:
|
|
1048
|
+
"""Generate a sedenion."""
|
|
1049
|
+
if len(coeffs) != 16:
|
|
1050
|
+
coeffs = list(coeffs) + [0.0] * (16 - len(coeffs))
|
|
1051
|
+
return HypercomplexNumber(*coeffs, dimension=16)
|
|
1052
|
+
|
|
1053
|
+
def Pathion(*coeffs) -> HypercomplexNumber:
|
|
1054
|
+
"""Generate a pathion."""
|
|
1055
|
+
if len(coeffs) != 32:
|
|
1056
|
+
coeffs = list(coeffs) + [0.0] * (32 - len(coeffs))
|
|
1057
|
+
return HypercomplexNumber(*coeffs, dimension=32)
|
|
1058
|
+
|
|
1059
|
+
def Chingon(*coeffs) -> HypercomplexNumber:
|
|
1060
|
+
"""Generate a chingon."""
|
|
1061
|
+
if len(coeffs) != 64:
|
|
1062
|
+
coeffs = list(coeffs) + [0.0] * (64 - len(coeffs))
|
|
1063
|
+
return HypercomplexNumber(*coeffs, dimension=64)
|
|
1064
|
+
|
|
1065
|
+
def Routon(*coeffs) -> HypercomplexNumber:
|
|
1066
|
+
"""Generate a routon."""
|
|
1067
|
+
if len(coeffs) != 128:
|
|
1068
|
+
coeffs = list(coeffs) + [0.0] * (128 - len(coeffs))
|
|
1069
|
+
return HypercomplexNumber(*coeffs, dimension=128)
|
|
1070
|
+
|
|
1071
|
+
def Voudon(*coeffs) -> HypercomplexNumber:
|
|
1072
|
+
"""Generate a voudon."""
|
|
1073
|
+
if len(coeffs) != 256:
|
|
1074
|
+
coeffs = list(coeffs) + [0.0] * (256 - len(coeffs))
|
|
1075
|
+
return HypercomplexNumber(*coeffs, dimension=256)
|
|
1076
|
+
|
|
1077
|
+
# =============================================
|
|
1078
|
+
# Cayley-Dickson Implementation
|
|
1079
|
+
# =============================================
|
|
1080
|
+
|
|
1081
|
+
def cayley_dickson_process(cebr: type, base_type=float) -> type:
|
|
1082
|
+
"""
|
|
1083
|
+
Apply the Cayley-Dickson construction to generate an algebra of twice the dimension.
|
|
1084
|
+
"""
|
|
1085
|
+
|
|
1086
|
+
class CayleyDicksonCebr:
|
|
1087
|
+
"""Hypercomplex algebra generated via Cayley-Dickson construction."""
|
|
1088
|
+
|
|
1089
|
+
dimensions = None
|
|
1090
|
+
base = base_type
|
|
1091
|
+
|
|
1092
|
+
def __init__(self, *args, pair=False):
|
|
1093
|
+
if pair and len(args) == 2:
|
|
1094
|
+
# (a, b) pair format
|
|
1095
|
+
self.a = self._ensure_cebr(args[0], cebr)
|
|
1096
|
+
self.b = self._ensure_cebr(args[1], cebr)
|
|
1097
|
+
else:
|
|
1098
|
+
# Handle various input formats
|
|
1099
|
+
if len(args) == 1:
|
|
1100
|
+
arg = args[0]
|
|
1101
|
+
# Handle complex numbers
|
|
1102
|
+
if isinstance(arg, complex):
|
|
1103
|
+
# Convert complex to pair (real, imag)
|
|
1104
|
+
self.a = cebr(arg.real)
|
|
1105
|
+
self.b = cebr(arg.imag)
|
|
1106
|
+
return
|
|
1107
|
+
# Handle strings
|
|
1108
|
+
elif isinstance(arg, str):
|
|
1109
|
+
# Try to parse as complex
|
|
1110
|
+
try:
|
|
1111
|
+
c = complex(arg)
|
|
1112
|
+
self.a = cebr(c.real)
|
|
1113
|
+
self.b = cebr(c.imag)
|
|
1114
|
+
return
|
|
1115
|
+
except ValueError:
|
|
1116
|
+
pass
|
|
1117
|
+
# Handle iterables
|
|
1118
|
+
elif hasattr(arg, '__iter__'):
|
|
1119
|
+
components = list(arg)
|
|
1120
|
+
else:
|
|
1121
|
+
components = [arg]
|
|
1122
|
+
else:
|
|
1123
|
+
components = list(args)
|
|
1124
|
+
|
|
1125
|
+
# Ensure even number of components
|
|
1126
|
+
if len(components) % 2 != 0:
|
|
1127
|
+
components.append(base_type(0))
|
|
1128
|
+
|
|
1129
|
+
half = len(components) // 2
|
|
1130
|
+
self.a = cebr(*components[:half])
|
|
1131
|
+
self.b = cebr(*components[half:])
|
|
1132
|
+
|
|
1133
|
+
@staticmethod
|
|
1134
|
+
def _ensure_cebr(value, cebr_class):
|
|
1135
|
+
"""Convert value to cebr instance if needed."""
|
|
1136
|
+
if isinstance(value, cebr_class):
|
|
1137
|
+
return value
|
|
1138
|
+
# Handle complex numbers
|
|
1139
|
+
elif isinstance(value, complex):
|
|
1140
|
+
return cebr_class(value.real, value.imag)
|
|
1141
|
+
# Handle single values
|
|
1142
|
+
else:
|
|
1143
|
+
return cebr_class(value)
|
|
1144
|
+
|
|
1145
|
+
@classmethod
|
|
1146
|
+
def from_complex(cls, c: complex):
|
|
1147
|
+
"""generate from a complex number."""
|
|
1148
|
+
return cls(c.real, c.imag)
|
|
1149
|
+
|
|
1150
|
+
@classmethod
|
|
1151
|
+
def from_pair(cls, a, b):
|
|
1152
|
+
"""generate from a pair (a, b)."""
|
|
1153
|
+
return cls(a, b, pair=True)
|
|
1154
|
+
|
|
1155
|
+
@property
|
|
1156
|
+
def real(self) -> float:
|
|
1157
|
+
"""Real part."""
|
|
1158
|
+
if hasattr(self.a, 'real'):
|
|
1159
|
+
return float(self.a.real)
|
|
1160
|
+
else:
|
|
1161
|
+
return float(self.a)
|
|
1162
|
+
|
|
1163
|
+
def coefficients(self):
|
|
1164
|
+
"""Get all coefficients as a tuple."""
|
|
1165
|
+
if hasattr(self.a, 'coefficients'):
|
|
1166
|
+
a_coeffs = self.a.coefficients()
|
|
1167
|
+
else:
|
|
1168
|
+
a_coeffs = (float(self.a),)
|
|
1169
|
+
|
|
1170
|
+
if hasattr(self.b, 'coefficients'):
|
|
1171
|
+
b_coeffs = self.b.coefficients()
|
|
1172
|
+
else:
|
|
1173
|
+
b_coeffs = (float(self.b),)
|
|
1174
|
+
|
|
1175
|
+
return a_coeffs + b_coeffs
|
|
1176
|
+
|
|
1177
|
+
def __add__(self, other):
|
|
1178
|
+
if isinstance(other, CayleyDicksonCebr):
|
|
1179
|
+
return CayleyDicksonCebr(
|
|
1180
|
+
self.a + other.a,
|
|
1181
|
+
self.b + other.b,
|
|
1182
|
+
pair=True
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1185
|
+
# Try to convert to this cebr
|
|
1186
|
+
try:
|
|
1187
|
+
other_cd = CayleyDicksonCebr(other)
|
|
1188
|
+
return self + other_cd
|
|
1189
|
+
except:
|
|
1190
|
+
return NotImplemented
|
|
1191
|
+
|
|
1192
|
+
def __radd__(self, other):
|
|
1193
|
+
return self.__add__(other)
|
|
1194
|
+
|
|
1195
|
+
def __sub__(self, other):
|
|
1196
|
+
if isinstance(other, CayleyDicksonCebr):
|
|
1197
|
+
return CayleyDicksonCebr(
|
|
1198
|
+
self.a - other.a,
|
|
1199
|
+
self.b - other.b,
|
|
1200
|
+
pair=True
|
|
1201
|
+
)
|
|
1202
|
+
|
|
1203
|
+
try:
|
|
1204
|
+
other_cd = CayleyDicksonCebr(other)
|
|
1205
|
+
return self - other_cd
|
|
1206
|
+
except:
|
|
1207
|
+
return NotImplemented
|
|
1208
|
+
|
|
1209
|
+
def __rsub__(self, other):
|
|
1210
|
+
try:
|
|
1211
|
+
other_cd = CayleyDicksonCebr(other)
|
|
1212
|
+
return other_cd - self
|
|
1213
|
+
except:
|
|
1214
|
+
return NotImplemented
|
|
1215
|
+
|
|
1216
|
+
def __mul__(self, other):
|
|
1217
|
+
if isinstance(other, CayleyDicksonCebr):
|
|
1218
|
+
# Cayley-Dickson multiplication
|
|
1219
|
+
a = self.a * other.a - other.b * self._conj_b()
|
|
1220
|
+
b = self._conj_a() * other.b + other.a * self.b
|
|
1221
|
+
return CayleyDicksonCebr(a, b, pair=True)
|
|
1222
|
+
|
|
1223
|
+
# Scalar multiplication
|
|
1224
|
+
try:
|
|
1225
|
+
other_cd = CayleyDicksonCebr(other)
|
|
1226
|
+
return self * other_cd
|
|
1227
|
+
except:
|
|
1228
|
+
return NotImplemented
|
|
1229
|
+
|
|
1230
|
+
def __rmul__(self, other):
|
|
1231
|
+
return self.__mul__(other)
|
|
1232
|
+
|
|
1233
|
+
def __truediv__(self, other):
|
|
1234
|
+
if isinstance(other, CayleyDicksonCebr):
|
|
1235
|
+
return self * other.inverse()
|
|
1236
|
+
|
|
1237
|
+
try:
|
|
1238
|
+
other_cd = CayleyDicksonCebr(other)
|
|
1239
|
+
return self / other_cd
|
|
1240
|
+
except:
|
|
1241
|
+
return NotImplemented
|
|
1242
|
+
|
|
1243
|
+
def __rtruediv__(self, other):
|
|
1244
|
+
try:
|
|
1245
|
+
other_cd = CayleyDicksonCebr(other)
|
|
1246
|
+
return other_cd / self
|
|
1247
|
+
except:
|
|
1248
|
+
return NotImplemented
|
|
1249
|
+
|
|
1250
|
+
def __neg__(self):
|
|
1251
|
+
return CayleyDicksonCebr(-self.a, -self.b, pair=True)
|
|
1252
|
+
|
|
1253
|
+
def __pos__(self):
|
|
1254
|
+
return self
|
|
1255
|
+
|
|
1256
|
+
def __abs__(self):
|
|
1257
|
+
return self.norm()
|
|
1258
|
+
|
|
1259
|
+
def __eq__(self, other):
|
|
1260
|
+
if isinstance(other, CayleyDicksonCebr):
|
|
1261
|
+
return self.a == other.a and self.b == other.b
|
|
1262
|
+
|
|
1263
|
+
try:
|
|
1264
|
+
other_cd = CayleyDicksonCebr(other)
|
|
1265
|
+
return self == other_cd
|
|
1266
|
+
except:
|
|
1267
|
+
return False
|
|
1268
|
+
|
|
1269
|
+
def __ne__(self, other):
|
|
1270
|
+
return not self.__eq__(other)
|
|
1271
|
+
|
|
1272
|
+
def __hash__(self):
|
|
1273
|
+
return hash((self.a, self.b))
|
|
1274
|
+
|
|
1275
|
+
def __str__(self):
|
|
1276
|
+
coeffs = self.coefficients()
|
|
1277
|
+
if len(coeffs) <= 8:
|
|
1278
|
+
return f"({', '.join(f'{c:.4f}' for c in coeffs)})"
|
|
1279
|
+
else:
|
|
1280
|
+
return f"CD[{len(coeffs)}]({coeffs[0]:.4f}, ..., {coeffs[-1]:.4f})"
|
|
1281
|
+
|
|
1282
|
+
def __repr__(self):
|
|
1283
|
+
return f"{self.__class__.__name__}({', '.join(map(str, self.coefficients()))})"
|
|
1284
|
+
|
|
1285
|
+
def _conj_a(self):
|
|
1286
|
+
"""Conjugate of a."""
|
|
1287
|
+
if hasattr(self.a, 'conjugate'):
|
|
1288
|
+
return self.a.conjugate()
|
|
1289
|
+
return self.a
|
|
1290
|
+
|
|
1291
|
+
def _conj_b(self):
|
|
1292
|
+
"""Conjugate of b."""
|
|
1293
|
+
if hasattr(self.b, 'conjugate'):
|
|
1294
|
+
return self.b.conjugate()
|
|
1295
|
+
return self.b
|
|
1296
|
+
|
|
1297
|
+
def conjugate(self):
|
|
1298
|
+
"""Conjugate: conj(a, b) = (conj(a), -b)."""
|
|
1299
|
+
return CayleyDicksonCebr(
|
|
1300
|
+
self._conj_a(),
|
|
1301
|
+
-self.b,
|
|
1302
|
+
pair=True
|
|
1303
|
+
)
|
|
1304
|
+
|
|
1305
|
+
def norm(self) -> float:
|
|
1306
|
+
"""Euclidean norm."""
|
|
1307
|
+
|
|
1308
|
+
def get_norm_squared(x):
|
|
1309
|
+
if hasattr(x, 'norm_squared'):
|
|
1310
|
+
return float(x.norm_squared())
|
|
1311
|
+
elif hasattr(x, 'norm'):
|
|
1312
|
+
n = float(x.norm())
|
|
1313
|
+
return n * n
|
|
1314
|
+
else:
|
|
1315
|
+
val = float(x)
|
|
1316
|
+
return val * val
|
|
1317
|
+
|
|
1318
|
+
norm_sq = get_norm_squared(self.a) + get_norm_squared(self.b)
|
|
1319
|
+
return math.sqrt(norm_sq)
|
|
1320
|
+
|
|
1321
|
+
def norm_squared(self):
|
|
1322
|
+
"""Square of the norm."""
|
|
1323
|
+
def get_norm_squared(x):
|
|
1324
|
+
if hasattr(x, 'norm_squared'):
|
|
1325
|
+
return x.norm_squared()
|
|
1326
|
+
elif hasattr(x, 'norm'):
|
|
1327
|
+
n = x.norm()
|
|
1328
|
+
return n * n
|
|
1329
|
+
else:
|
|
1330
|
+
return x * x
|
|
1331
|
+
|
|
1332
|
+
return get_num_squared(self.a) + get_norm_squared(self.b)
|
|
1333
|
+
|
|
1334
|
+
def inverse(self):
|
|
1335
|
+
"""Multiplicative inverse."""
|
|
1336
|
+
norm_sq = self.norm_squared()
|
|
1337
|
+
if float(norm_sq) == 0:
|
|
1338
|
+
raise ZeroDivisionError("Cannot invert zero element")
|
|
1339
|
+
|
|
1340
|
+
conj = self.conjugate()
|
|
1341
|
+
return CayleyDicksonCebr(
|
|
1342
|
+
conj.a / norm_sq,
|
|
1343
|
+
conj.b / norm_sq,
|
|
1344
|
+
pair=True
|
|
1345
|
+
)
|
|
1346
|
+
|
|
1347
|
+
# Set class attributes
|
|
1348
|
+
if hasattr(cebr, 'dimensions'):
|
|
1349
|
+
CayleyDicksonCebr.dimensions = cebr.dimensions * 2
|
|
1350
|
+
else:
|
|
1351
|
+
CayleyDicksonCebr.dimensions = 2
|
|
1352
|
+
|
|
1353
|
+
# DÜZELTME: 'algebra_class' yerine 'cebr' kullan
|
|
1354
|
+
CayleyDicksonCebr.__name__ = f"CD{cebr.__name__}"
|
|
1355
|
+
|
|
1356
|
+
return CayleyDicksonCebr
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
def cayley_dickson_cebr(level: int, base_type=float) -> type:
|
|
1360
|
+
"""generate Cayley-Dickson cebr of given level."""
|
|
1361
|
+
if not isinstance(level, int) or level < 0:
|
|
1362
|
+
raise ValueError(f"Level must be non-negative integer, got {level}")
|
|
1363
|
+
|
|
1364
|
+
# Start with real numbers
|
|
1365
|
+
if level == 0:
|
|
1366
|
+
class RealAlgebra:
|
|
1367
|
+
dimensions = 1
|
|
1368
|
+
base = base_type
|
|
1369
|
+
|
|
1370
|
+
def __init__(self, value):
|
|
1371
|
+
self.value = base_type(value)
|
|
1372
|
+
|
|
1373
|
+
def __add__(self, other):
|
|
1374
|
+
if isinstance(other, RealAlgebra):
|
|
1375
|
+
return RealAlgebra(self.value + other.value)
|
|
1376
|
+
return RealAlgebra(self.value + base_type(other))
|
|
1377
|
+
|
|
1378
|
+
def __mul__(self, other):
|
|
1379
|
+
if isinstance(other, RealAlgebra):
|
|
1380
|
+
return RealAlgebra(self.value * other.value)
|
|
1381
|
+
return RealAlgebra(self.value * base_type(other))
|
|
1382
|
+
|
|
1383
|
+
def __repr__(self):
|
|
1384
|
+
return f"RealAlgebra({self.value})"
|
|
1385
|
+
|
|
1386
|
+
@property
|
|
1387
|
+
def real(self):
|
|
1388
|
+
return float(self.value)
|
|
1389
|
+
|
|
1390
|
+
def conjugate(self):
|
|
1391
|
+
return self
|
|
1392
|
+
|
|
1393
|
+
def norm(self):
|
|
1394
|
+
return abs(float(self.value))
|
|
1395
|
+
|
|
1396
|
+
return RealAlgebra
|
|
1397
|
+
|
|
1398
|
+
# Apply construction level times
|
|
1399
|
+
current_cebr = cayley_dickson_cebr(0, base_type)
|
|
1400
|
+
for i in range(level):
|
|
1401
|
+
current_cebr = cayley_dickson_process(current_cebr, base_type)
|
|
1402
|
+
|
|
1403
|
+
# Set name
|
|
1404
|
+
if level == 0:
|
|
1405
|
+
current_cebr.__name__ = "Real"
|
|
1406
|
+
elif level == 1:
|
|
1407
|
+
current_cebr.__name__ = "Complex"
|
|
1408
|
+
elif level == 2:
|
|
1409
|
+
current_cebr.__name__ = "Quaternion"
|
|
1410
|
+
elif level == 3:
|
|
1411
|
+
current_cebr.__name__ = "Octonion"
|
|
1412
|
+
elif level == 4:
|
|
1413
|
+
current_cebr.__name__ = "Sedenion"
|
|
1414
|
+
elif level == 5:
|
|
1415
|
+
current_cebr.__name__ = "Pathion"
|
|
1416
|
+
elif level == 6:
|
|
1417
|
+
current_cebr.__name__ = "Chingon"
|
|
1418
|
+
elif level == 7:
|
|
1419
|
+
current_cebr.__name__ = "Routon"
|
|
1420
|
+
elif level == 8:
|
|
1421
|
+
current_cebr.__name__ = "Voudon"
|
|
1422
|
+
else:
|
|
1423
|
+
current_cebr.__name__ = f"CD{level}"
|
|
1424
|
+
|
|
1425
|
+
return current_cebr
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
# =============================================
|
|
1429
|
+
# Utility Functions
|
|
1430
|
+
# =============================================
|
|
1431
|
+
|
|
1432
|
+
def generate_cd_chain(max_level: int = 8) -> list:
|
|
1433
|
+
"""
|
|
1434
|
+
Generate chain of Cayley-Dickson algebras.
|
|
1435
|
+
|
|
1436
|
+
Args:
|
|
1437
|
+
max_level: Maximum level to generate
|
|
1438
|
+
|
|
1439
|
+
Returns:
|
|
1440
|
+
List of cebr classes
|
|
1441
|
+
"""
|
|
1442
|
+
return [cayley_dickson_cebr(i) for i in range(max_level + 1)]
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
def cd_number_from_components(level: int, *components) -> object:
|
|
1446
|
+
"""
|
|
1447
|
+
generate a Cayley-Dickson number from components.
|
|
1448
|
+
|
|
1449
|
+
Args:
|
|
1450
|
+
level: cebr level
|
|
1451
|
+
*components: Number components
|
|
1452
|
+
|
|
1453
|
+
Returns:
|
|
1454
|
+
Cayley-Dickson number instance
|
|
1455
|
+
"""
|
|
1456
|
+
cebr = cayley_dickson_cebr(level)
|
|
1457
|
+
return cebr(*components)
|
|
1458
|
+
|
|
1459
|
+
def _parse_complex(s: Any) -> ComplexNumber:
|
|
1460
|
+
"""
|
|
1461
|
+
Parse input as complex number.
|
|
1462
|
+
Supports: "1,2", "1+2j", "3j", 5, 3.14, etc.
|
|
1463
|
+
"""
|
|
1464
|
+
# If already ComplexNumber
|
|
1465
|
+
if isinstance(s, ComplexNumber):
|
|
1466
|
+
return s
|
|
1467
|
+
|
|
1468
|
+
# If complex
|
|
1469
|
+
if isinstance(s, complex):
|
|
1470
|
+
return ComplexNumber(s.real, s.imag)
|
|
1471
|
+
|
|
1472
|
+
# If numeric
|
|
1473
|
+
if isinstance(s, (int, float)):
|
|
1474
|
+
return ComplexNumber(float(s), 0.0)
|
|
1475
|
+
|
|
1476
|
+
# Convert to string
|
|
1477
|
+
if not isinstance(s, str):
|
|
1478
|
+
s = str(s)
|
|
1479
|
+
|
|
1480
|
+
s = s.strip().replace(' ', '').replace('J', 'j').replace('i', 'j')
|
|
1481
|
+
|
|
1482
|
+
# Try comma-separated
|
|
1483
|
+
if ',' in s:
|
|
1484
|
+
parts = s.split(',')
|
|
1485
|
+
if len(parts) == 2:
|
|
1486
|
+
try:
|
|
1487
|
+
return ComplexNumber(float(parts[0]), float(parts[1]))
|
|
1488
|
+
except ValueError:
|
|
1489
|
+
pass
|
|
1490
|
+
|
|
1491
|
+
# Try Python's complex parser
|
|
1492
|
+
try:
|
|
1493
|
+
c = complex(s)
|
|
1494
|
+
return ComplexNumber(c.real, c.imag)
|
|
1495
|
+
except ValueError:
|
|
1496
|
+
pass
|
|
1497
|
+
|
|
1498
|
+
# Try as real number
|
|
1499
|
+
try:
|
|
1500
|
+
return ComplexNumber(float(s), 0.0)
|
|
1501
|
+
except ValueError:
|
|
1502
|
+
pass
|
|
1503
|
+
|
|
1504
|
+
# Try as pure imaginary
|
|
1505
|
+
if s.endswith('j'):
|
|
1506
|
+
try:
|
|
1507
|
+
imag = float(s[:-1]) if s[:-1] not in ['', '+', '-'] else 1.0
|
|
1508
|
+
if s.startswith('-'):
|
|
1509
|
+
imag = -imag
|
|
1510
|
+
return ComplexNumber(0.0, imag)
|
|
1511
|
+
except ValueError:
|
|
1512
|
+
pass
|
|
1513
|
+
|
|
1514
|
+
# Fallback
|
|
1515
|
+
warnings.warn(f"Could not parse as complex: {repr(s)}", RuntimeWarning)
|
|
1516
|
+
return ComplexNumber(0.0, 0.0)
|
|
1517
|
+
|
|
1518
|
+
|
|
1519
|
+
def _parse_hypercomplex(s: Any, dimension: int) -> HypercomplexNumber:
|
|
1520
|
+
"""Parse input as hypercomplex number of given dimension."""
|
|
1521
|
+
try:
|
|
1522
|
+
# If already HypercomplexNumber
|
|
1523
|
+
if isinstance(s, HypercomplexNumber):
|
|
1524
|
+
if s.dimension == dimension:
|
|
1525
|
+
return s
|
|
1526
|
+
elif s.dimension < dimension:
|
|
1527
|
+
return s.pad_to_dimension(dimension)
|
|
1528
|
+
else:
|
|
1529
|
+
return s.truncate_to_dimension(dimension)
|
|
1530
|
+
|
|
1531
|
+
# If numeric
|
|
1532
|
+
if isinstance(s, (int, float)):
|
|
1533
|
+
coeffs = [float(s)] + [0.0] * (dimension - 1)
|
|
1534
|
+
return HypercomplexNumber(*coeffs, dimension=dimension)
|
|
1535
|
+
|
|
1536
|
+
# If complex
|
|
1537
|
+
if isinstance(s, (complex, ComplexNumber)):
|
|
1538
|
+
if isinstance(s, complex):
|
|
1539
|
+
c = s
|
|
1540
|
+
else:
|
|
1541
|
+
c = s.to_complex()
|
|
1542
|
+
coeffs = [c.real, c.imag] + [0.0] * (dimension - 2)
|
|
1543
|
+
return HypercomplexNumber(*coeffs, dimension=dimension)
|
|
1544
|
+
|
|
1545
|
+
# If iterable (list, tuple, etc.)
|
|
1546
|
+
if hasattr(s, '__iter__') and not isinstance(s, str):
|
|
1547
|
+
coeffs = list(s)
|
|
1548
|
+
if len(coeffs) < dimension:
|
|
1549
|
+
coeffs = coeffs + [0.0] * (dimension - len(coeffs))
|
|
1550
|
+
elif len(coeffs) > dimension:
|
|
1551
|
+
coeffs = coeffs[:dimension]
|
|
1552
|
+
return HypercomplexNumber(*coeffs, dimension=dimension)
|
|
1553
|
+
|
|
1554
|
+
# String parsing
|
|
1555
|
+
if not isinstance(s, str):
|
|
1556
|
+
s = str(s)
|
|
1557
|
+
|
|
1558
|
+
s = s.strip()
|
|
1559
|
+
s = s.strip('[]{}()')
|
|
1560
|
+
|
|
1561
|
+
if not s:
|
|
1562
|
+
return HypercomplexNumber(*([0.0] * dimension), dimension=dimension)
|
|
1563
|
+
|
|
1564
|
+
# Comma-separated list
|
|
1565
|
+
if ',' in s:
|
|
1566
|
+
parts = [p.strip() for p in s.split(',') if p.strip()]
|
|
1567
|
+
if parts:
|
|
1568
|
+
try:
|
|
1569
|
+
coeffs = [float(p) for p in parts]
|
|
1570
|
+
if len(coeffs) < dimension:
|
|
1571
|
+
coeffs = coeffs + [0.0] * (dimension - len(coeffs))
|
|
1572
|
+
elif len(coeffs) > dimension:
|
|
1573
|
+
coeffs = coeffs[:dimension]
|
|
1574
|
+
return HypercomplexNumber(*coeffs, dimension=dimension)
|
|
1575
|
+
except ValueError:
|
|
1576
|
+
pass
|
|
1577
|
+
|
|
1578
|
+
# Single number
|
|
1579
|
+
try:
|
|
1580
|
+
coeffs = [float(s)] + [0.0] * (dimension - 1)
|
|
1581
|
+
return HypercomplexNumber(*coeffs, dimension=dimension)
|
|
1582
|
+
except ValueError:
|
|
1583
|
+
pass
|
|
1584
|
+
|
|
1585
|
+
# Complex string
|
|
1586
|
+
try:
|
|
1587
|
+
c = complex(s)
|
|
1588
|
+
coeffs = [c.real, c.imag] + [0.0] * (dimension - 2)
|
|
1589
|
+
return HypercomplexNumber(*coeffs, dimension=dimension)
|
|
1590
|
+
except ValueError:
|
|
1591
|
+
pass
|
|
1592
|
+
|
|
1593
|
+
except Exception as e:
|
|
1594
|
+
warnings.warn(f"Parse error for hypercomplex (dim={dimension}): {e}", RuntimeWarning)
|
|
1595
|
+
|
|
1596
|
+
# Fallback: zero
|
|
1597
|
+
return HypercomplexNumber(*([0.0] * dimension), dimension=dimension)
|
|
1598
|
+
|
|
1599
|
+
|
|
1600
|
+
def _parse_universal(s: Any, target_type: str) -> Any:
|
|
1601
|
+
"""
|
|
1602
|
+
Universal parser for all number types.
|
|
1603
|
+
|
|
1604
|
+
Args:
|
|
1605
|
+
s: Input to parse
|
|
1606
|
+
target_type: "real", "complex", "quaternion", "octonion",
|
|
1607
|
+
"sedenion", "pathion", "chingon", "routon", "voudon",
|
|
1608
|
+
"bicomplex", "neutrosophic"
|
|
1609
|
+
|
|
1610
|
+
Returns:
|
|
1611
|
+
Parsed number
|
|
1612
|
+
"""
|
|
1613
|
+
# Type mapping
|
|
1614
|
+
type_map = {
|
|
1615
|
+
"real": 1,
|
|
1616
|
+
"complex": 2,
|
|
1617
|
+
"quaternion": 4,
|
|
1618
|
+
"octonion": 8,
|
|
1619
|
+
"sedenion": 16,
|
|
1620
|
+
"pathion": 32,
|
|
1621
|
+
"chingon": 64,
|
|
1622
|
+
"routon": 128,
|
|
1623
|
+
"voudon": 256
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
try:
|
|
1627
|
+
# Special cases
|
|
1628
|
+
if target_type == "bicomplex":
|
|
1629
|
+
# Parse bicomplex (4 components: re1, im1, re2, im2)
|
|
1630
|
+
if isinstance(s, BicomplexNumber):
|
|
1631
|
+
return s
|
|
1632
|
+
|
|
1633
|
+
if isinstance(s, str):
|
|
1634
|
+
s = s.strip().strip('[]{}()')
|
|
1635
|
+
|
|
1636
|
+
# Try to parse as 4 numbers
|
|
1637
|
+
if hasattr(s, '__iter__') and not isinstance(s, str):
|
|
1638
|
+
coeffs = list(s)
|
|
1639
|
+
elif isinstance(s, str) and ',' in s:
|
|
1640
|
+
coeffs = [float(p.strip()) for p in s.split(',') if p.strip()]
|
|
1641
|
+
else:
|
|
1642
|
+
coeffs = [float(s), 0.0, 0.0, 0.0]
|
|
1643
|
+
|
|
1644
|
+
if len(coeffs) < 4:
|
|
1645
|
+
coeffs = coeffs + [0.0] * (4 - len(coeffs))
|
|
1646
|
+
|
|
1647
|
+
z1 = ComplexNumber(coeffs[0], coeffs[1])
|
|
1648
|
+
z2 = ComplexNumber(coeffs[2], coeffs[3])
|
|
1649
|
+
return BicomplexNumber(z1, z2)
|
|
1650
|
+
|
|
1651
|
+
elif target_type == "neutrosophic":
|
|
1652
|
+
# Parse neutrosophic (2 components: a, b)
|
|
1653
|
+
if isinstance(s, NeutrosophicNumber):
|
|
1654
|
+
return s
|
|
1655
|
+
|
|
1656
|
+
if isinstance(s, str):
|
|
1657
|
+
s = s.strip().strip('[]{}()')
|
|
1658
|
+
|
|
1659
|
+
if hasattr(s, '__iter__') and not isinstance(s, str):
|
|
1660
|
+
coeffs = list(s)
|
|
1661
|
+
elif isinstance(s, str) and ',' in s:
|
|
1662
|
+
coeffs = [float(p.strip()) for p in s.split(',') if p.strip()]
|
|
1663
|
+
else:
|
|
1664
|
+
coeffs = [float(s), 0.0]
|
|
1665
|
+
|
|
1666
|
+
if len(coeffs) < 2:
|
|
1667
|
+
coeffs = coeffs + [0.0] * (2 - len(coeffs))
|
|
1668
|
+
|
|
1669
|
+
return NeutrosophicNumber(coeffs[0], coeffs[1])
|
|
1670
|
+
|
|
1671
|
+
# Standard hypercomplex types
|
|
1672
|
+
elif target_type in type_map:
|
|
1673
|
+
dimension = type_map[target_type]
|
|
1674
|
+
return _parse_hypercomplex(s, dimension)
|
|
1675
|
+
|
|
1676
|
+
else:
|
|
1677
|
+
raise ValueError(f"Unknown target type: {target_type}")
|
|
1678
|
+
|
|
1679
|
+
except Exception as e:
|
|
1680
|
+
warnings.warn(f"Parse error for {target_type}: {e}", RuntimeWarning)
|
|
1681
|
+
|
|
1682
|
+
# Return default value
|
|
1683
|
+
defaults = {
|
|
1684
|
+
"real": 0.0,
|
|
1685
|
+
"complex": ComplexNumber(0, 0),
|
|
1686
|
+
"quaternion": HypercomplexNumber(0, 0, 0, 0, dimension=4),
|
|
1687
|
+
"octonion": HypercomplexNumber(*([0.0] * 8), dimension=8),
|
|
1688
|
+
"sedenion": HypercomplexNumber(*([0.0] * 16), dimension=16),
|
|
1689
|
+
"pathion": HypercomplexNumber(*([0.0] * 32), dimension=32),
|
|
1690
|
+
"chingon": HypercomplexNumber(*([0.0] * 64), dimension=64),
|
|
1691
|
+
"routon": HypercomplexNumber(*([0.0] * 128), dimension=128),
|
|
1692
|
+
"voudon": HypercomplexNumber(*([0.0] * 256), dimension=256),
|
|
1693
|
+
"bicomplex": BicomplexNumber(ComplexNumber(0, 0), ComplexNumber(0, 0)),
|
|
1694
|
+
"neutrosophic": NeutrosophicNumber(0.0, 0.0)
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
return defaults.get(target_type, None)
|
|
1698
|
+
|
|
1699
|
+
# =============================================
|
|
1700
|
+
# Mathematical Sequences and Functions
|
|
1701
|
+
# =============================================
|
|
1702
|
+
|
|
1703
|
+
def oresme_sequence(n_terms: int) -> List[float]:
|
|
1704
|
+
"""Generate Oresme sequence: n / 2^n."""
|
|
1705
|
+
return [n / (2 ** n) for n in range(1, n_terms + 1)]
|
|
1706
|
+
|
|
1707
|
+
|
|
1708
|
+
def harmonic_numbers(n_terms: int) -> Generator[Fraction, None, None]:
|
|
1709
|
+
"""Generate harmonic numbers: H_n = 1 + 1/2 + ... + 1/n."""
|
|
1710
|
+
current = Fraction(0)
|
|
1711
|
+
for n in range(1, n_terms + 1):
|
|
1712
|
+
current += Fraction(1, n)
|
|
1713
|
+
yield current
|
|
1714
|
+
|
|
1715
|
+
|
|
1716
|
+
def binet_formula(n: int) -> float:
|
|
1717
|
+
"""Calculate nth Fibonacci number using Binet's formula."""
|
|
1718
|
+
sqrt5 = math.sqrt(5)
|
|
1719
|
+
phi = (1 + sqrt5) / 2
|
|
1720
|
+
psi = (1 - sqrt5) / 2
|
|
1721
|
+
return (phi**n - psi**n) / sqrt5
|
|
1722
|
+
|
|
1723
|
+
|
|
1724
|
+
def generate_cd_chain_names(max_level: int = 8) -> List[str]:
|
|
1725
|
+
"""Generate names of Cayley-Dickson algebras up to given level."""
|
|
1726
|
+
names = ["Real", "Complex", "Quaternion", "Octonion", "Sedenion",
|
|
1727
|
+
"Pathion", "Chingon", "Routon", "Voudon"]
|
|
1728
|
+
return names[:max_level + 1]
|
|
1729
|
+
# =============================================
|
|
1730
|
+
# Example Usage and Tests
|
|
1731
|
+
# =============================================
|
|
1732
|
+
|
|
1733
|
+
if __name__ == "__main__":
|
|
1734
|
+
print("Advanced Number Systems Library")
|
|
1735
|
+
print("=" * 60)
|
|
1736
|
+
|
|
1737
|
+
# Test HypercomplexNumber (Real)
|
|
1738
|
+
print("\n1. Testing HypercomplexNumber (Real):")
|
|
1739
|
+
r1 = Real(3.14)
|
|
1740
|
+
r2 = Real(2.71)
|
|
1741
|
+
print(f" Real(3.14) = {r1}")
|
|
1742
|
+
print(f" Real(2.71) = {r2}")
|
|
1743
|
+
print(f" r1 + r2 = {r1 + r2}")
|
|
1744
|
+
print(f" r1 * r2 = {r1 * r2}")
|
|
1745
|
+
|
|
1746
|
+
# Test HypercomplexNumber (Complex)
|
|
1747
|
+
print("\n2. Testing HypercomplexNumber (Complex):")
|
|
1748
|
+
c1 = Complex(3, 4)
|
|
1749
|
+
c2 = Complex(1, 2)
|
|
1750
|
+
print(f" Complex(3, 4) = {c1}")
|
|
1751
|
+
print(f" Complex(1, 2) = {c2}")
|
|
1752
|
+
print(f" c1 + c2 = {c1 + c2}")
|
|
1753
|
+
print(f" c1 * c2 = {c1 * c2}")
|
|
1754
|
+
print(f" |c1| = {c1.norm():.2f}")
|
|
1755
|
+
|
|
1756
|
+
# Test HypercomplexNumber (Quaternion)
|
|
1757
|
+
print("\n3. Testing HypercomplexNumber (Quaternion):")
|
|
1758
|
+
q1 = Quaternion(1, 2, 3, 4)
|
|
1759
|
+
q2 = Quaternion(5, 6, 7, 8)
|
|
1760
|
+
print(f" Quaternion(1,2,3,4) = {q1}")
|
|
1761
|
+
print(f" Quaternion(5,6,7,8) = {q2}")
|
|
1762
|
+
print(f" q1 + q2 = {q1 + q2}")
|
|
1763
|
+
print(f" q1 * q2 = {q1 * q2}")
|
|
1764
|
+
print(f" Note: Quaternion multiplication is non-commutative")
|
|
1765
|
+
print(f" q2 * q1 = {q2 * q1}")
|
|
1766
|
+
print(f" q1 * q2 == q2 * q1? {q1 * q2 == q2 * q1}")
|
|
1767
|
+
|
|
1768
|
+
# Test BicomplexNumber
|
|
1769
|
+
print("\n4. Testing BicomplexNumber:")
|
|
1770
|
+
bc1 = Bicomplex(1, 2, 3, 4)
|
|
1771
|
+
bc2 = Bicomplex(5, 6, 7, 8)
|
|
1772
|
+
print(f" Bicomplex(1,2,3,4) = {bc1}")
|
|
1773
|
+
print(f" Bicomplex(5,6,7,8) = {bc2}")
|
|
1774
|
+
print(f" bc1 + bc2 = {bc1 + bc2}")
|
|
1775
|
+
print(f" bc1 * bc2 = {bc1 * bc2}")
|
|
1776
|
+
|
|
1777
|
+
# Test NeutrosophicNumber
|
|
1778
|
+
print("\n5. Testing NeutrosophicNumber:")
|
|
1779
|
+
n1 = Neutrosophic(3, 2)
|
|
1780
|
+
n2 = Neutrosophic(1, 4)
|
|
1781
|
+
print(f" Neutrosophic(3, 2) = {n1}")
|
|
1782
|
+
print(f" n1 + n2 = {n1 + n2}")
|
|
1783
|
+
print(f" n1 * n2 = {n1 * n2}")
|
|
1784
|
+
|
|
1785
|
+
# Test mixed operations
|
|
1786
|
+
print("\n6. Testing mixed operations:")
|
|
1787
|
+
real_num = Real(5.0)
|
|
1788
|
+
complex_num = Complex(3, 4)
|
|
1789
|
+
print(f" Real(5) * Complex(3,4) = {real_num * complex_num}")
|
|
1790
|
+
print(f" Complex(3,4) * Real(5) = {complex_num * real_num}")
|
|
1791
|
+
|
|
1792
|
+
# Test conversion
|
|
1793
|
+
print("\n7. Testing conversions:")
|
|
1794
|
+
print(f" Real(5).to_complex() = {Real(5).to_complex()}")
|
|
1795
|
+
print(f" Complex(3,4).to_complex() = {Complex(3,4).to_complex()}")
|
|
1796
|
+
|
|
1797
|
+
# Test with Python built-in types
|
|
1798
|
+
print("\n8. Testing with Python built-in types:")
|
|
1799
|
+
print(f" Complex(3,4) + 5 = {Complex(3,4) + 5}")
|
|
1800
|
+
print(f" 5 + Complex(3,4) = {5 + Complex(3,4)}")
|
|
1801
|
+
print(f" Complex(3,4) * 2 = {Complex(3,4) * 2}")
|
|
1802
|
+
print(f" 2 * Complex(3,4) = {2 * Complex(3,4)}")
|
|
1803
|
+
|
|
1804
|
+
print("\n✓ All tests completed successfully!")
|