polysolve 0.7.0__tar.gz → 0.8.0__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.
- {polysolve-0.7.0/src/polysolve.egg-info → polysolve-0.8.0}/PKG-INFO +21 -9
- {polysolve-0.7.0 → polysolve-0.8.0}/README.md +15 -3
- {polysolve-0.7.0 → polysolve-0.8.0}/pyproject.toml +8 -8
- {polysolve-0.7.0 → polysolve-0.8.0}/src/polysolve/__init__.py +60 -82
- {polysolve-0.7.0 → polysolve-0.8.0/src/polysolve.egg-info}/PKG-INFO +21 -9
- {polysolve-0.7.0 → polysolve-0.8.0}/src/polysolve.egg-info/requires.txt +2 -2
- {polysolve-0.7.0 → polysolve-0.8.0}/tests/test_polysolve.py +25 -4
- {polysolve-0.7.0 → polysolve-0.8.0}/LICENSE +0 -0
- {polysolve-0.7.0 → polysolve-0.8.0}/setup.cfg +0 -0
- {polysolve-0.7.0 → polysolve-0.8.0}/src/polysolve.egg-info/SOURCES.txt +0 -0
- {polysolve-0.7.0 → polysolve-0.8.0}/src/polysolve.egg-info/dependency_links.txt +0 -0
- {polysolve-0.7.0 → polysolve-0.8.0}/src/polysolve.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polysolve
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration.
|
|
5
5
|
Author-email: Jonathan Rampersad <jonathan@jono-rams.work>
|
|
6
6
|
License: MIT License
|
|
@@ -33,17 +33,17 @@ Classifier: Intended Audience :: Science/Research
|
|
|
33
33
|
Classifier: License :: OSI Approved :: MIT License
|
|
34
34
|
Classifier: Operating System :: OS Independent
|
|
35
35
|
Classifier: Programming Language :: Python :: 3
|
|
36
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
37
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
38
36
|
Classifier: Programming Language :: Python :: 3.10
|
|
39
37
|
Classifier: Programming Language :: Python :: 3.11
|
|
40
38
|
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
41
41
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
42
|
-
Requires-Python: >=3.
|
|
42
|
+
Requires-Python: >=3.10
|
|
43
43
|
Description-Content-Type: text/markdown
|
|
44
44
|
License-File: LICENSE
|
|
45
|
-
Requires-Dist: numpy>=
|
|
46
|
-
Requires-Dist: numba
|
|
45
|
+
Requires-Dist: numpy>=2.0
|
|
46
|
+
Requires-Dist: numba>=0.60
|
|
47
47
|
Provides-Extra: cuda12
|
|
48
48
|
Requires-Dist: cupy-cuda12x; extra == "cuda12"
|
|
49
49
|
Provides-Extra: dev
|
|
@@ -103,6 +103,8 @@ from polysolve import Function, GA_Options
|
|
|
103
103
|
# Coefficients can be integers or floats.
|
|
104
104
|
f1 = Function(largest_exponent=2)
|
|
105
105
|
f1.set_coeffs([2, -3, -5])
|
|
106
|
+
f2 = Function(largest_exponent=2)
|
|
107
|
+
f2.set_coeffs([3, -4, -5])
|
|
106
108
|
|
|
107
109
|
print(f"Function f1: {f1}")
|
|
108
110
|
# > Function f1: 2x^2 - 3x - 5
|
|
@@ -122,20 +124,30 @@ ddf1 = f1.nth_derivative(2)
|
|
|
122
124
|
print(f"2nd Derivative of f1: {ddf1}")
|
|
123
125
|
# > Derivative of f1: 4
|
|
124
126
|
|
|
125
|
-
# 5. Find
|
|
127
|
+
# 5. Find the integral: x^3 - 2x^2 - 5x + 0
|
|
128
|
+
igl = f2.integral()
|
|
129
|
+
print(f"Integral of f2: {igl}")
|
|
130
|
+
# > integral of f2: x^3 - 2x^2 - 5x + 0
|
|
131
|
+
|
|
132
|
+
# 6. Find the integral with custom constant of integration: x^3 - 2x^2 - 5x + 2
|
|
133
|
+
igl = f2.integral(constant_of_integration=2)
|
|
134
|
+
print(f"Integral of f2: {igl}")
|
|
135
|
+
# > integral of f2: x^3 - 2x^2 - 5x + 2
|
|
136
|
+
|
|
137
|
+
# 7. Find roots analytically using the quadratic formula
|
|
126
138
|
# This is exact and fast for degree-2 polynomials.
|
|
127
139
|
roots_analytic = f1.quadratic_solve()
|
|
128
140
|
print(f"Analytic roots: {sorted(roots_analytic)}")
|
|
129
141
|
# > Analytic roots: [-1.0, 2.5]
|
|
130
142
|
|
|
131
|
-
#
|
|
143
|
+
# 8. Find REAL roots with the genetic algorithm (Numba CPU)
|
|
132
144
|
# This is the default, JIT-compiled CPU solver.
|
|
133
145
|
ga_opts = GA_Options(num_of_generations=20)
|
|
134
146
|
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
|
135
147
|
print(f"Approximate real roots: {roots_ga[:2]}")
|
|
136
148
|
# > Approximate real roots: [-1.000..., 2.500...]
|
|
137
149
|
|
|
138
|
-
#
|
|
150
|
+
# 9. Find ALL roots (Real + Complex)
|
|
139
151
|
# Use get_roots() to search the complex plane.
|
|
140
152
|
f_complex = Function(2, [1, 0, 1]) # x^2 + 1
|
|
141
153
|
roots_all = f_complex.get_roots(ga_opts)
|
|
@@ -51,6 +51,8 @@ from polysolve import Function, GA_Options
|
|
|
51
51
|
# Coefficients can be integers or floats.
|
|
52
52
|
f1 = Function(largest_exponent=2)
|
|
53
53
|
f1.set_coeffs([2, -3, -5])
|
|
54
|
+
f2 = Function(largest_exponent=2)
|
|
55
|
+
f2.set_coeffs([3, -4, -5])
|
|
54
56
|
|
|
55
57
|
print(f"Function f1: {f1}")
|
|
56
58
|
# > Function f1: 2x^2 - 3x - 5
|
|
@@ -70,20 +72,30 @@ ddf1 = f1.nth_derivative(2)
|
|
|
70
72
|
print(f"2nd Derivative of f1: {ddf1}")
|
|
71
73
|
# > Derivative of f1: 4
|
|
72
74
|
|
|
73
|
-
# 5. Find
|
|
75
|
+
# 5. Find the integral: x^3 - 2x^2 - 5x + 0
|
|
76
|
+
igl = f2.integral()
|
|
77
|
+
print(f"Integral of f2: {igl}")
|
|
78
|
+
# > integral of f2: x^3 - 2x^2 - 5x + 0
|
|
79
|
+
|
|
80
|
+
# 6. Find the integral with custom constant of integration: x^3 - 2x^2 - 5x + 2
|
|
81
|
+
igl = f2.integral(constant_of_integration=2)
|
|
82
|
+
print(f"Integral of f2: {igl}")
|
|
83
|
+
# > integral of f2: x^3 - 2x^2 - 5x + 2
|
|
84
|
+
|
|
85
|
+
# 7. Find roots analytically using the quadratic formula
|
|
74
86
|
# This is exact and fast for degree-2 polynomials.
|
|
75
87
|
roots_analytic = f1.quadratic_solve()
|
|
76
88
|
print(f"Analytic roots: {sorted(roots_analytic)}")
|
|
77
89
|
# > Analytic roots: [-1.0, 2.5]
|
|
78
90
|
|
|
79
|
-
#
|
|
91
|
+
# 8. Find REAL roots with the genetic algorithm (Numba CPU)
|
|
80
92
|
# This is the default, JIT-compiled CPU solver.
|
|
81
93
|
ga_opts = GA_Options(num_of_generations=20)
|
|
82
94
|
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
|
83
95
|
print(f"Approximate real roots: {roots_ga[:2]}")
|
|
84
96
|
# > Approximate real roots: [-1.000..., 2.500...]
|
|
85
97
|
|
|
86
|
-
#
|
|
98
|
+
# 9. Find ALL roots (Real + Complex)
|
|
87
99
|
# Use get_roots() to search the complex plane.
|
|
88
100
|
f_complex = Function(2, [1, 0, 1]) # x^2 + 1
|
|
89
101
|
roots_all = f_complex.get_roots(ga_opts)
|
|
@@ -5,14 +5,14 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
# --- Core Metadata ---
|
|
7
7
|
name = "polysolve"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.8.0"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
|
|
11
11
|
]
|
|
12
12
|
description = "A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration."
|
|
13
13
|
readme = "README.md"
|
|
14
|
-
requires-python = ">=3.
|
|
15
|
-
license = { file="LICENSE" }
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
license = { file = "LICENSE" }
|
|
16
16
|
keywords = ["math", "polynomial", "genetic algorithm", "cuda", "equation solver"]
|
|
17
17
|
|
|
18
18
|
# --- Classifiers ---
|
|
@@ -23,18 +23,18 @@ classifiers = [
|
|
|
23
23
|
"License :: OSI Approved :: MIT License",
|
|
24
24
|
"Operating System :: OS Independent",
|
|
25
25
|
"Programming Language :: Python :: 3",
|
|
26
|
-
"Programming Language :: Python :: 3.8",
|
|
27
|
-
"Programming Language :: Python :: 3.9",
|
|
28
26
|
"Programming Language :: Python :: 3.10",
|
|
29
27
|
"Programming Language :: Python :: 3.11",
|
|
30
28
|
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Programming Language :: Python :: 3.14",
|
|
31
31
|
"Topic :: Scientific/Engineering :: Mathematics",
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
# --- Dependencies ---
|
|
35
35
|
dependencies = [
|
|
36
|
-
"numpy>=
|
|
37
|
-
"numba"
|
|
36
|
+
"numpy>=2.0",
|
|
37
|
+
"numba>=0.60"
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
# --- Optional Dependencies (Extras) ---
|
|
@@ -45,5 +45,5 @@ dev = ["pytest"]
|
|
|
45
45
|
[project.urls]
|
|
46
46
|
Homepage = "https://polysolve.jono-rams.work"
|
|
47
47
|
Documentation = "https://polysolve.jono-rams.work/docs"
|
|
48
|
-
Repository
|
|
48
|
+
Repository = "https://github.com/jono-rams/PolySolve"
|
|
49
49
|
"Bug Tracker" = "https://github.com/jono-rams/PolySolve/issues"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import cmath
|
|
3
3
|
import numpy as np
|
|
4
|
+
from numpy.polynomial import Polynomial
|
|
4
5
|
import numba
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from typing import List, Optional, Union
|
|
@@ -383,6 +384,9 @@ class Function:
|
|
|
383
384
|
largest_exponent (int): The largest exponent (n) in the function.
|
|
384
385
|
"""
|
|
385
386
|
self._largest_exponent = largest_exponent
|
|
387
|
+
self.poly_obj = None
|
|
388
|
+
self._initialized = False
|
|
389
|
+
|
|
386
390
|
if coefficients is not None:
|
|
387
391
|
self.set_coeffs(coefficients)
|
|
388
392
|
# Verify user provided exponent matches if they provided both
|
|
@@ -390,7 +394,6 @@ class Function:
|
|
|
390
394
|
raise ValueError("Provided largest_exponent does not match coefficient list length.")
|
|
391
395
|
elif largest_exponent is not None:
|
|
392
396
|
self.coefficients = None
|
|
393
|
-
self._initialized = False
|
|
394
397
|
else:
|
|
395
398
|
raise ValueError("Must provide either coefficients or largest_exponent.")
|
|
396
399
|
|
|
@@ -425,6 +428,9 @@ class Function:
|
|
|
425
428
|
target_dtype = np.float64
|
|
426
429
|
|
|
427
430
|
self.coefficients = np.array(coefficients, dtype=target_dtype)
|
|
431
|
+
reversed_coeffs = coefficients[::-1]
|
|
432
|
+
self.poly_obj = Polynomial(reversed_coeffs)
|
|
433
|
+
|
|
428
434
|
self._largest_exponent = len(coefficients) - 1
|
|
429
435
|
self._initialized = True
|
|
430
436
|
|
|
@@ -454,7 +460,7 @@ class Function:
|
|
|
454
460
|
float: The resulting y-value.
|
|
455
461
|
"""
|
|
456
462
|
self._check_initialized()
|
|
457
|
-
return
|
|
463
|
+
return self.poly_obj(x_val)
|
|
458
464
|
|
|
459
465
|
def differential(self) -> 'Function':
|
|
460
466
|
"""
|
|
@@ -489,10 +495,11 @@ class Function:
|
|
|
489
495
|
diff_func.set_coeffs([0])
|
|
490
496
|
return diff_func
|
|
491
497
|
|
|
492
|
-
|
|
498
|
+
deriv_poly = self.poly_obj.deriv(1)
|
|
499
|
+
new_coeffs = deriv_poly.coef[::-1].tolist()
|
|
493
500
|
|
|
494
501
|
diff_func = Function(self._largest_exponent - 1)
|
|
495
|
-
diff_func.set_coeffs(
|
|
502
|
+
diff_func.set_coeffs(new_coeffs)
|
|
496
503
|
return diff_func
|
|
497
504
|
|
|
498
505
|
|
|
@@ -512,18 +519,30 @@ class Function:
|
|
|
512
519
|
raise ValueError("Derivative order 'n' must be a positive integer.")
|
|
513
520
|
|
|
514
521
|
if n > self.largest_exponent:
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
return
|
|
522
|
+
diff_func = Function(0)
|
|
523
|
+
diff_func.set_coeffs([0])
|
|
524
|
+
return diff_func
|
|
518
525
|
|
|
519
|
-
|
|
520
|
-
|
|
526
|
+
deriv_poly = self.poly_obj.deriv(n)
|
|
527
|
+
new_coeffs = deriv_poly.coef[::-1].tolist()
|
|
521
528
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
529
|
+
new_degree = self._largest_exponent - n
|
|
530
|
+
diff_func = Function(new_degree)
|
|
531
|
+
diff_func.set_coeffs(new_coeffs)
|
|
532
|
+
return diff_func
|
|
533
|
+
|
|
525
534
|
|
|
526
|
-
|
|
535
|
+
def integral(self, constant_of_integration: float = 0.0) -> 'Function':
|
|
536
|
+
"""Calculates the indefinite integral (antiderivative)."""
|
|
537
|
+
self._check_initialized()
|
|
538
|
+
|
|
539
|
+
# Integration naturally increases the degree by 1
|
|
540
|
+
integ_poly = self.poly_obj.integ(m=1, k=[constant_of_integration])
|
|
541
|
+
new_coeffs = integ_poly.coef[::-1].tolist()
|
|
542
|
+
|
|
543
|
+
integral_func = Function(self._largest_exponent + 1)
|
|
544
|
+
integral_func.set_coeffs(new_coeffs)
|
|
545
|
+
return integral_func
|
|
527
546
|
|
|
528
547
|
|
|
529
548
|
def get_real_roots(self, options: Optional[GA_Options] = None, use_cuda: bool = False) -> np.ndarray:
|
|
@@ -1139,49 +1158,10 @@ class Function:
|
|
|
1139
1158
|
|
|
1140
1159
|
def __str__(self) -> str:
|
|
1141
1160
|
"""Returns a human-readable string representation of the function."""
|
|
1142
|
-
self.
|
|
1143
|
-
|
|
1144
|
-
for i, c in enumerate(self.coefficients):
|
|
1145
|
-
if c == 0:
|
|
1146
|
-
continue
|
|
1147
|
-
|
|
1148
|
-
power = self._largest_exponent - i
|
|
1149
|
-
|
|
1150
|
-
# Coefficient part
|
|
1151
|
-
coeff_val = c
|
|
1152
|
-
if c == int(c):
|
|
1153
|
-
coeff_val = int(c)
|
|
1154
|
-
|
|
1155
|
-
if coeff_val == 1 and power != 0:
|
|
1156
|
-
coeff = ""
|
|
1157
|
-
elif coeff_val == -1 and power != 0:
|
|
1158
|
-
coeff = "-"
|
|
1159
|
-
else:
|
|
1160
|
-
coeff = str(coeff_val)
|
|
1161
|
-
|
|
1162
|
-
# Variable part
|
|
1163
|
-
if power == 0:
|
|
1164
|
-
var = ""
|
|
1165
|
-
elif power == 1:
|
|
1166
|
-
var = "x"
|
|
1167
|
-
else:
|
|
1168
|
-
var = f"x^{power}"
|
|
1161
|
+
if not self._initialized:
|
|
1162
|
+
return "Uninitialized Function"
|
|
1169
1163
|
|
|
1170
|
-
|
|
1171
|
-
sign = ""
|
|
1172
|
-
if i > 0:
|
|
1173
|
-
sign = " + " if c > 0 else " - "
|
|
1174
|
-
coeff = str(abs(coeff_val))
|
|
1175
|
-
if abs(c) == 1 and power != 0:
|
|
1176
|
-
coeff = "" # Don't show 1 for non-constant terms
|
|
1177
|
-
|
|
1178
|
-
parts.append(f"{sign}{coeff}{var}")
|
|
1179
|
-
|
|
1180
|
-
# Join parts and clean up
|
|
1181
|
-
result = "".join(parts)
|
|
1182
|
-
if result.startswith(" + "):
|
|
1183
|
-
result = result[3:]
|
|
1184
|
-
return result if result else "0"
|
|
1164
|
+
return str(self.poly_obj)
|
|
1185
1165
|
|
|
1186
1166
|
def __repr__(self) -> str:
|
|
1187
1167
|
return f"Function(str='{self}')"
|
|
@@ -1191,11 +1171,12 @@ class Function:
|
|
|
1191
1171
|
self._check_initialized()
|
|
1192
1172
|
other._check_initialized()
|
|
1193
1173
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1174
|
+
# The Polynomial objects handle degree alignment and zero-trimming natively
|
|
1175
|
+
new_poly = self.poly_obj + other.poly_obj
|
|
1176
|
+
new_coeffs = new_poly.coef[::-1].tolist()
|
|
1196
1177
|
|
|
1197
|
-
result_func = Function(len(
|
|
1198
|
-
result_func.set_coeffs(
|
|
1178
|
+
result_func = Function(len(new_coeffs) - 1)
|
|
1179
|
+
result_func.set_coeffs(new_coeffs)
|
|
1199
1180
|
return result_func
|
|
1200
1181
|
|
|
1201
1182
|
def _strip_leading_zeros(self, coeffs: np.ndarray) -> np.ndarray:
|
|
@@ -1209,11 +1190,11 @@ class Function:
|
|
|
1209
1190
|
self._check_initialized()
|
|
1210
1191
|
other._check_initialized()
|
|
1211
1192
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1193
|
+
new_poly = self.poly_obj - other.poly_obj
|
|
1194
|
+
new_coeffs = new_poly.coef[::-1].tolist()
|
|
1214
1195
|
|
|
1215
|
-
result_func = Function(len(
|
|
1216
|
-
result_func.set_coeffs(
|
|
1196
|
+
result_func = Function(len(new_coeffs) - 1)
|
|
1197
|
+
result_func.set_coeffs(new_coeffs)
|
|
1217
1198
|
return result_func
|
|
1218
1199
|
|
|
1219
1200
|
def _multiply_by_scalar(self, scalar: Union[int, float, complex]) -> 'Function':
|
|
@@ -1237,15 +1218,11 @@ class Function:
|
|
|
1237
1218
|
self._check_initialized()
|
|
1238
1219
|
other._check_initialized()
|
|
1239
1220
|
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
new_coefficients = self._strip_leading_zeros(new_coefficients)
|
|
1243
|
-
|
|
1244
|
-
# The degree of the resulting polynomial is derived from the new coefficients
|
|
1245
|
-
new_degree = len(new_coefficients) - 1
|
|
1221
|
+
new_poly = self.poly_obj * other.poly_obj
|
|
1222
|
+
new_coeffs = new_poly.coef[::-1].tolist()
|
|
1246
1223
|
|
|
1247
|
-
result_func = Function(
|
|
1248
|
-
result_func.set_coeffs(
|
|
1224
|
+
result_func = Function(len(new_coeffs) - 1)
|
|
1225
|
+
result_func.set_coeffs(new_coeffs)
|
|
1249
1226
|
return result_func
|
|
1250
1227
|
|
|
1251
1228
|
def __mul__(self, other: Union['Function', int, float, complex]) -> 'Function':
|
|
@@ -1269,16 +1246,18 @@ class Function:
|
|
|
1269
1246
|
|
|
1270
1247
|
if isinstance(other, (int, float, complex)):
|
|
1271
1248
|
if other == 0:
|
|
1272
|
-
self.
|
|
1273
|
-
self._largest_exponent = 0
|
|
1249
|
+
self.set_coeffs([0])
|
|
1274
1250
|
else:
|
|
1275
|
-
|
|
1251
|
+
# Update the underlying poly_obj, then sync the legacy array
|
|
1252
|
+
self.poly_obj *= other
|
|
1253
|
+
self.coefficients = self.poly_obj.coef[::-1]
|
|
1254
|
+
self._largest_exponent = len(self.coefficients) - 1
|
|
1276
1255
|
|
|
1277
1256
|
elif isinstance(other, self.__class__):
|
|
1278
1257
|
other._check_initialized()
|
|
1279
|
-
self.
|
|
1258
|
+
self.poly_obj *= other.poly_obj
|
|
1259
|
+
self.coefficients = self.poly_obj.coef[::-1]
|
|
1280
1260
|
self._largest_exponent = len(self.coefficients) - 1
|
|
1281
|
-
|
|
1282
1261
|
else:
|
|
1283
1262
|
return NotImplemented
|
|
1284
1263
|
|
|
@@ -1289,20 +1268,20 @@ class Function:
|
|
|
1289
1268
|
Checks if two Function objects are equal by comparing
|
|
1290
1269
|
their coefficients.
|
|
1291
1270
|
"""
|
|
1292
|
-
# Check if the 'other' object is even a Function
|
|
1293
1271
|
if not isinstance(other, Function):
|
|
1294
1272
|
return NotImplemented
|
|
1295
1273
|
|
|
1296
|
-
# Ensure both are initialized before trying to access .coefficients
|
|
1297
1274
|
if not self._initialized or not other._initialized:
|
|
1298
1275
|
return False
|
|
1299
1276
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1277
|
+
# Extract the auto-trimmed arrays from the modern API
|
|
1278
|
+
c1 = self.poly_obj.coef
|
|
1279
|
+
c2 = other.poly_obj.coef
|
|
1302
1280
|
|
|
1303
1281
|
if c1.shape != c2.shape:
|
|
1304
1282
|
return False
|
|
1305
1283
|
|
|
1284
|
+
# Retain np.allclose to protect against floating-point inaccuracies
|
|
1306
1285
|
return np.allclose(c1, c2)
|
|
1307
1286
|
|
|
1308
1287
|
|
|
@@ -1320,10 +1299,9 @@ class Function:
|
|
|
1320
1299
|
a, b, c = self.coefficients
|
|
1321
1300
|
|
|
1322
1301
|
discriminant = (b**2) - (4*a*c)
|
|
1323
|
-
|
|
1324
1302
|
sqrt_discriminant = cmath.sqrt(discriminant)
|
|
1325
1303
|
|
|
1326
|
-
if b >= 0:
|
|
1304
|
+
if b.real >= 0:
|
|
1327
1305
|
sign_b = 1
|
|
1328
1306
|
else:
|
|
1329
1307
|
sign_b = -1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polysolve
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration.
|
|
5
5
|
Author-email: Jonathan Rampersad <jonathan@jono-rams.work>
|
|
6
6
|
License: MIT License
|
|
@@ -33,17 +33,17 @@ Classifier: Intended Audience :: Science/Research
|
|
|
33
33
|
Classifier: License :: OSI Approved :: MIT License
|
|
34
34
|
Classifier: Operating System :: OS Independent
|
|
35
35
|
Classifier: Programming Language :: Python :: 3
|
|
36
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
37
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
38
36
|
Classifier: Programming Language :: Python :: 3.10
|
|
39
37
|
Classifier: Programming Language :: Python :: 3.11
|
|
40
38
|
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
41
41
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
42
|
-
Requires-Python: >=3.
|
|
42
|
+
Requires-Python: >=3.10
|
|
43
43
|
Description-Content-Type: text/markdown
|
|
44
44
|
License-File: LICENSE
|
|
45
|
-
Requires-Dist: numpy>=
|
|
46
|
-
Requires-Dist: numba
|
|
45
|
+
Requires-Dist: numpy>=2.0
|
|
46
|
+
Requires-Dist: numba>=0.60
|
|
47
47
|
Provides-Extra: cuda12
|
|
48
48
|
Requires-Dist: cupy-cuda12x; extra == "cuda12"
|
|
49
49
|
Provides-Extra: dev
|
|
@@ -103,6 +103,8 @@ from polysolve import Function, GA_Options
|
|
|
103
103
|
# Coefficients can be integers or floats.
|
|
104
104
|
f1 = Function(largest_exponent=2)
|
|
105
105
|
f1.set_coeffs([2, -3, -5])
|
|
106
|
+
f2 = Function(largest_exponent=2)
|
|
107
|
+
f2.set_coeffs([3, -4, -5])
|
|
106
108
|
|
|
107
109
|
print(f"Function f1: {f1}")
|
|
108
110
|
# > Function f1: 2x^2 - 3x - 5
|
|
@@ -122,20 +124,30 @@ ddf1 = f1.nth_derivative(2)
|
|
|
122
124
|
print(f"2nd Derivative of f1: {ddf1}")
|
|
123
125
|
# > Derivative of f1: 4
|
|
124
126
|
|
|
125
|
-
# 5. Find
|
|
127
|
+
# 5. Find the integral: x^3 - 2x^2 - 5x + 0
|
|
128
|
+
igl = f2.integral()
|
|
129
|
+
print(f"Integral of f2: {igl}")
|
|
130
|
+
# > integral of f2: x^3 - 2x^2 - 5x + 0
|
|
131
|
+
|
|
132
|
+
# 6. Find the integral with custom constant of integration: x^3 - 2x^2 - 5x + 2
|
|
133
|
+
igl = f2.integral(constant_of_integration=2)
|
|
134
|
+
print(f"Integral of f2: {igl}")
|
|
135
|
+
# > integral of f2: x^3 - 2x^2 - 5x + 2
|
|
136
|
+
|
|
137
|
+
# 7. Find roots analytically using the quadratic formula
|
|
126
138
|
# This is exact and fast for degree-2 polynomials.
|
|
127
139
|
roots_analytic = f1.quadratic_solve()
|
|
128
140
|
print(f"Analytic roots: {sorted(roots_analytic)}")
|
|
129
141
|
# > Analytic roots: [-1.0, 2.5]
|
|
130
142
|
|
|
131
|
-
#
|
|
143
|
+
# 8. Find REAL roots with the genetic algorithm (Numba CPU)
|
|
132
144
|
# This is the default, JIT-compiled CPU solver.
|
|
133
145
|
ga_opts = GA_Options(num_of_generations=20)
|
|
134
146
|
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
|
135
147
|
print(f"Approximate real roots: {roots_ga[:2]}")
|
|
136
148
|
# > Approximate real roots: [-1.000..., 2.500...]
|
|
137
149
|
|
|
138
|
-
#
|
|
150
|
+
# 9. Find ALL roots (Real + Complex)
|
|
139
151
|
# Use get_roots() to search the complex plane.
|
|
140
152
|
f_complex = Function(2, [1, 0, 1]) # x^2 + 1
|
|
141
153
|
roots_all = f_complex.get_roots(ga_opts)
|
|
@@ -18,6 +18,13 @@ def quadratic_func() -> Function:
|
|
|
18
18
|
f.set_coeffs([2, -3, -5])
|
|
19
19
|
return f
|
|
20
20
|
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def int_func() -> Function:
|
|
23
|
+
"""Provides a function for easy integral calculation."""
|
|
24
|
+
f = Function(largest_exponent=2)
|
|
25
|
+
f.set_coeffs([3, -4, -5])
|
|
26
|
+
return f
|
|
27
|
+
|
|
21
28
|
@pytest.fixture
|
|
22
29
|
def linear_func() -> Function:
|
|
23
30
|
"""Provides a standard linear function: x + 10."""
|
|
@@ -70,6 +77,20 @@ def test_nth_derivative(quadratic_func):
|
|
|
70
77
|
# The derivative of 2x^2 - 3x - 5 is 4x - 3
|
|
71
78
|
assert np.array_equal(derivative.coefficients, [4])
|
|
72
79
|
|
|
80
|
+
def test_integral(int_func):
|
|
81
|
+
"""Tests the calculation of the function's integral."""
|
|
82
|
+
integral = int_func.integral()
|
|
83
|
+
assert integral.largest_exponent == 3
|
|
84
|
+
# The integral of 2x^2 -3x - 5 is x^3 - 2x^2 - 5x
|
|
85
|
+
assert np.array_equal(integral.coefficients, [1, -2, -5, 0])
|
|
86
|
+
|
|
87
|
+
def test_integral_custom_constant(int_func):
|
|
88
|
+
"""Tests the calculation of the function's integral with custom constant of integration."""
|
|
89
|
+
integral = int_func.integral(constant_of_integration=42)
|
|
90
|
+
assert integral.largest_exponent == 3
|
|
91
|
+
# The integral of 2x^2 -3x - 5 is x^3 - 2x^2 - 5x
|
|
92
|
+
assert np.array_equal(integral.coefficients, [1, -2, -5, 42])
|
|
93
|
+
|
|
73
94
|
def test_quadratic_solve(quadratic_func):
|
|
74
95
|
"""Tests the analytical quadratic solver for exact roots."""
|
|
75
96
|
roots = quadratic_func.quadratic_solve()
|
|
@@ -139,7 +160,7 @@ def test_get_real_roots_numpy(quadratic_func):
|
|
|
139
160
|
Tests that the NumPy-based genetic algorithm approximates the roots correctly.
|
|
140
161
|
"""
|
|
141
162
|
# Using more generations for higher accuracy in testing
|
|
142
|
-
ga_opts = GA_Options(num_of_generations=
|
|
163
|
+
ga_opts = GA_Options(num_of_generations=100, data_size=500000, selection_percentile=0.66, root_precision=2)
|
|
143
164
|
|
|
144
165
|
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=False)
|
|
145
166
|
|
|
@@ -158,7 +179,7 @@ def test_get_real_roots_cuda(quadratic_func):
|
|
|
158
179
|
It will be skipped automatically if CuPy is not available.
|
|
159
180
|
"""
|
|
160
181
|
|
|
161
|
-
ga_opts = GA_Options(num_of_generations=
|
|
182
|
+
ga_opts = GA_Options(num_of_generations=100, data_size=500000, selection_percentile=0.66, root_precision=2)
|
|
162
183
|
|
|
163
184
|
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=True)
|
|
164
185
|
|
|
@@ -172,7 +193,7 @@ def test_get_roots_numpy(complex_func):
|
|
|
172
193
|
Tests that the NumPy-based genetic algorithm approximates the roots correctly.
|
|
173
194
|
"""
|
|
174
195
|
# Using more generations for higher accuracy in testing
|
|
175
|
-
ga_opts = GA_Options(num_of_generations=
|
|
196
|
+
ga_opts = GA_Options(num_of_generations=100, data_size=500000, selection_percentile=0.66, root_precision=2)
|
|
176
197
|
|
|
177
198
|
roots = complex_func.get_roots(ga_opts, use_cuda=False)
|
|
178
199
|
|
|
@@ -191,7 +212,7 @@ def test_get_roots_cuda(complex_func):
|
|
|
191
212
|
It will be skipped automatically if CuPy is not available.
|
|
192
213
|
"""
|
|
193
214
|
|
|
194
|
-
ga_opts = GA_Options(num_of_generations=
|
|
215
|
+
ga_opts = GA_Options(num_of_generations=100, data_size=500000, selection_percentile=0.66, root_precision=2)
|
|
195
216
|
|
|
196
217
|
roots = complex_func.get_roots(ga_opts, use_cuda=True)
|
|
197
218
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|