polysolve 0.5.0__tar.gz → 0.6.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.5.0/src/polysolve.egg-info → polysolve-0.6.0}/PKG-INFO +22 -11
- {polysolve-0.5.0 → polysolve-0.6.0}/README.md +20 -10
- {polysolve-0.5.0 → polysolve-0.6.0}/pyproject.toml +3 -2
- {polysolve-0.5.0 → polysolve-0.6.0}/src/polysolve/__init__.py +107 -33
- {polysolve-0.5.0 → polysolve-0.6.0/src/polysolve.egg-info}/PKG-INFO +22 -11
- {polysolve-0.5.0 → polysolve-0.6.0}/src/polysolve.egg-info/requires.txt +1 -0
- {polysolve-0.5.0 → polysolve-0.6.0}/tests/test_polysolve.py +5 -9
- {polysolve-0.5.0 → polysolve-0.6.0}/LICENSE +0 -0
- {polysolve-0.5.0 → polysolve-0.6.0}/setup.cfg +0 -0
- {polysolve-0.5.0 → polysolve-0.6.0}/src/polysolve.egg-info/SOURCES.txt +0 -0
- {polysolve-0.5.0 → polysolve-0.6.0}/src/polysolve.egg-info/dependency_links.txt +0 -0
- {polysolve-0.5.0 → polysolve-0.6.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.6.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
|
|
@@ -43,6 +43,7 @@ Requires-Python: >=3.8
|
|
|
43
43
|
Description-Content-Type: text/markdown
|
|
44
44
|
License-File: LICENSE
|
|
45
45
|
Requires-Dist: numpy>=1.21
|
|
46
|
+
Requires-Dist: numba
|
|
46
47
|
Provides-Extra: cuda12
|
|
47
48
|
Requires-Dist: cupy-cuda12x; extra == "cuda12"
|
|
48
49
|
Provides-Extra: dev
|
|
@@ -56,15 +57,16 @@ Dynamic: license-file
|
|
|
56
57
|
[](https://pypi.org/project/polysolve/)
|
|
57
58
|
[](https://pypi.org/project/polysolve/)
|
|
58
59
|
|
|
59
|
-
A Python library for representing, manipulating, and solving polynomial equations
|
|
60
|
+
A Python library for representing, manipulating, and solving polynomial equations. Features a high-performance, Numba-accelerated genetic algorithm for CPU, with an optional CUDA/GPU backend for massive-scale parallel solving.
|
|
60
61
|
|
|
61
62
|
---
|
|
62
63
|
|
|
63
64
|
## Key Features
|
|
64
65
|
|
|
65
|
-
* **
|
|
66
|
-
* **
|
|
66
|
+
* **Numerically Stable Solver**: Makes complex calculations **practical**. Leverage your GPU to power the robust genetic algorithm, solving high-degree polynomials accurately in a reasonable timeframe.
|
|
67
|
+
* **Numba Accelerated CPU Solver**: The default genetic algorithm is JIT-compiled with Numba for high-speed CPU performance, right out of the box.
|
|
67
68
|
* **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
|
|
69
|
+
* **Create and Manipulate Polynomials**: Easily define polynomials of any degree using integer or float coefficients, and perform arithmetic operations like addition, subtraction, multiplication, and scaling.
|
|
68
70
|
* **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
|
|
69
71
|
* **Simple API**: Designed to be intuitive and easy to integrate into any project.
|
|
70
72
|
|
|
@@ -125,8 +127,8 @@ roots_analytic = f1.quadratic_solve()
|
|
|
125
127
|
print(f"Analytic roots: {sorted(roots_analytic)}")
|
|
126
128
|
# > Analytic roots: [-1.0, 2.5]
|
|
127
129
|
|
|
128
|
-
# 6. Find roots with the genetic algorithm (CPU)
|
|
129
|
-
#
|
|
130
|
+
# 6. Find roots with the genetic algorithm (Numba CPU)
|
|
131
|
+
# This is the default, JIT-compiled CPU solver.
|
|
130
132
|
ga_opts = GA_Options(num_of_generations=20)
|
|
131
133
|
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
|
132
134
|
print(f"Approximate roots from GA: {roots_ga[:2]}")
|
|
@@ -149,13 +151,22 @@ The default options are balanced, but for very complex polynomials, you may want
|
|
|
149
151
|
```python
|
|
150
152
|
from polysolve import GA_Options
|
|
151
153
|
|
|
152
|
-
# Create a config for a
|
|
153
|
-
# (
|
|
154
|
-
|
|
154
|
+
# Create a config for a deep search, optimized for finding
|
|
155
|
+
# *all* real roots (even if they are far apart).
|
|
156
|
+
ga_robust_search = GA_Options(
|
|
155
157
|
num_of_generations=50, # Run for more generations
|
|
156
158
|
data_size=500000, # Use a larger population
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
|
|
160
|
+
# --- Key Tuning Parameters for Multi-Root Finding ---
|
|
161
|
+
|
|
162
|
+
# Widen the parent pool to 75% to keep more "niches"
|
|
163
|
+
# (solution-clouds around different roots) alive.
|
|
164
|
+
selection_percentile=0.75,
|
|
165
|
+
|
|
166
|
+
# Increase the crossover blend factor to 0.75.
|
|
167
|
+
# This allows new solutions to be created further
|
|
168
|
+
# away from their parents, increasing exploration.
|
|
169
|
+
blend_alpha=0.75
|
|
159
170
|
)
|
|
160
171
|
|
|
161
172
|
# Pass the custom options to the solver
|
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
[](https://pypi.org/project/polysolve/)
|
|
6
6
|
[](https://pypi.org/project/polysolve/)
|
|
7
7
|
|
|
8
|
-
A Python library for representing, manipulating, and solving polynomial equations
|
|
8
|
+
A Python library for representing, manipulating, and solving polynomial equations. Features a high-performance, Numba-accelerated genetic algorithm for CPU, with an optional CUDA/GPU backend for massive-scale parallel solving.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## Key Features
|
|
13
13
|
|
|
14
|
-
* **
|
|
15
|
-
* **
|
|
14
|
+
* **Numerically Stable Solver**: Makes complex calculations **practical**. Leverage your GPU to power the robust genetic algorithm, solving high-degree polynomials accurately in a reasonable timeframe.
|
|
15
|
+
* **Numba Accelerated CPU Solver**: The default genetic algorithm is JIT-compiled with Numba for high-speed CPU performance, right out of the box.
|
|
16
16
|
* **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
|
|
17
|
+
* **Create and Manipulate Polynomials**: Easily define polynomials of any degree using integer or float coefficients, and perform arithmetic operations like addition, subtraction, multiplication, and scaling.
|
|
17
18
|
* **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
|
|
18
19
|
* **Simple API**: Designed to be intuitive and easy to integrate into any project.
|
|
19
20
|
|
|
@@ -74,8 +75,8 @@ roots_analytic = f1.quadratic_solve()
|
|
|
74
75
|
print(f"Analytic roots: {sorted(roots_analytic)}")
|
|
75
76
|
# > Analytic roots: [-1.0, 2.5]
|
|
76
77
|
|
|
77
|
-
# 6. Find roots with the genetic algorithm (CPU)
|
|
78
|
-
#
|
|
78
|
+
# 6. Find roots with the genetic algorithm (Numba CPU)
|
|
79
|
+
# This is the default, JIT-compiled CPU solver.
|
|
79
80
|
ga_opts = GA_Options(num_of_generations=20)
|
|
80
81
|
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
|
81
82
|
print(f"Approximate roots from GA: {roots_ga[:2]}")
|
|
@@ -98,13 +99,22 @@ The default options are balanced, but for very complex polynomials, you may want
|
|
|
98
99
|
```python
|
|
99
100
|
from polysolve import GA_Options
|
|
100
101
|
|
|
101
|
-
# Create a config for a
|
|
102
|
-
# (
|
|
103
|
-
|
|
102
|
+
# Create a config for a deep search, optimized for finding
|
|
103
|
+
# *all* real roots (even if they are far apart).
|
|
104
|
+
ga_robust_search = GA_Options(
|
|
104
105
|
num_of_generations=50, # Run for more generations
|
|
105
106
|
data_size=500000, # Use a larger population
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
|
|
108
|
+
# --- Key Tuning Parameters for Multi-Root Finding ---
|
|
109
|
+
|
|
110
|
+
# Widen the parent pool to 75% to keep more "niches"
|
|
111
|
+
# (solution-clouds around different roots) alive.
|
|
112
|
+
selection_percentile=0.75,
|
|
113
|
+
|
|
114
|
+
# Increase the crossover blend factor to 0.75.
|
|
115
|
+
# This allows new solutions to be created further
|
|
116
|
+
# away from their parents, increasing exploration.
|
|
117
|
+
blend_alpha=0.75
|
|
108
118
|
)
|
|
109
119
|
|
|
110
120
|
# Pass the custom options to the solver
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
# --- Core Metadata ---
|
|
7
7
|
name = "polysolve"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.6.0"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
|
|
11
11
|
]
|
|
@@ -33,7 +33,8 @@ classifiers = [
|
|
|
33
33
|
|
|
34
34
|
# --- Dependencies ---
|
|
35
35
|
dependencies = [
|
|
36
|
-
"numpy>=1.21"
|
|
36
|
+
"numpy>=1.21",
|
|
37
|
+
"numba"
|
|
37
38
|
]
|
|
38
39
|
|
|
39
40
|
# --- Optional Dependencies (Extras) ---
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import numpy as np
|
|
3
|
+
import numba
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from typing import List, Optional, Union
|
|
5
6
|
import warnings
|
|
@@ -37,6 +38,31 @@ extern "C" __global__ void fitness_kernel(
|
|
|
37
38
|
}
|
|
38
39
|
"""
|
|
39
40
|
|
|
41
|
+
@numba.jit(nopython=True, fastmath=True, parallel=True)
|
|
42
|
+
def _calculate_ranks_numba(solutions, coefficients, y_val, ranks):
|
|
43
|
+
"""
|
|
44
|
+
A Numba-jitted, parallel function to calculate fitness.
|
|
45
|
+
This replaces np.polyval and the rank calculation.
|
|
46
|
+
"""
|
|
47
|
+
num_coefficients = coefficients.shape[0]
|
|
48
|
+
data_size = solutions.shape[0]
|
|
49
|
+
|
|
50
|
+
# This prange will be run in parallel on all your CPU cores
|
|
51
|
+
for idx in numba.prange(data_size):
|
|
52
|
+
x_val = solutions[idx]
|
|
53
|
+
|
|
54
|
+
# Horner's method (same as np.polyval)
|
|
55
|
+
ans = coefficients[0]
|
|
56
|
+
for i in range(1, num_coefficients):
|
|
57
|
+
ans = ans * x_val + coefficients[i]
|
|
58
|
+
|
|
59
|
+
ans -= y_val
|
|
60
|
+
|
|
61
|
+
if ans == 0.0:
|
|
62
|
+
ranks[idx] = 1.7976931348623157e+308 # np.finfo(float).max
|
|
63
|
+
else:
|
|
64
|
+
ranks[idx] = abs(1.0 / ans)
|
|
65
|
+
|
|
40
66
|
@dataclass
|
|
41
67
|
class GA_Options:
|
|
42
68
|
"""
|
|
@@ -62,6 +88,16 @@ class GA_Options:
|
|
|
62
88
|
mutation_ratio (float): The percentage (e.g., 0.40 for 40%) of the next
|
|
63
89
|
generation to be created by mutating solutions
|
|
64
90
|
from the parent pool. Default: 0.40
|
|
91
|
+
selection_percentile (float): The top percentage (e.g., 0.66 for 66%)
|
|
92
|
+
of solutions to use as the parent pool
|
|
93
|
+
for crossover. A smaller value speeds
|
|
94
|
+
up single-root convergence; a larger
|
|
95
|
+
value helps find multiple roots.
|
|
96
|
+
Default: 0.66
|
|
97
|
+
blend_alpha (float): The expansion factor for Blend Crossover (BLX-alpha).
|
|
98
|
+
0.0 = average crossover (no expansion).
|
|
99
|
+
0.5 = 50% expansion beyond the parent range.
|
|
100
|
+
Default: 0.5
|
|
65
101
|
root_precision (int): The number of decimal places to round roots to
|
|
66
102
|
when clustering. A smaller number (e.g., 3)
|
|
67
103
|
groups roots more aggressively. A larger number
|
|
@@ -76,6 +112,8 @@ class GA_Options:
|
|
|
76
112
|
elite_ratio: float = 0.05
|
|
77
113
|
crossover_ratio: float = 0.45
|
|
78
114
|
mutation_ratio: float = 0.40
|
|
115
|
+
selection_percentile: float = 0.66
|
|
116
|
+
blend_alpha: float = 0.5
|
|
79
117
|
root_precision: int = 5
|
|
80
118
|
|
|
81
119
|
def __post_init__(self):
|
|
@@ -87,6 +125,14 @@ class GA_Options:
|
|
|
87
125
|
)
|
|
88
126
|
if any(r < 0 for r in [self.elite_ratio, self.crossover_ratio, self.mutation_ratio]):
|
|
89
127
|
raise ValueError("GA ratios cannot be negative.")
|
|
128
|
+
if not (0 < self.selection_percentile <= 1.0):
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"selection_percentile must be between 0 (exclusive) and 1.0 (inclusive), but got {self.selection_percentile}"
|
|
131
|
+
)
|
|
132
|
+
if self.blend_alpha < 0:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"blend_alpha cannot be negative, but got {self.blend_alpha}"
|
|
135
|
+
)
|
|
90
136
|
|
|
91
137
|
def _get_cauchy_bound(coeffs: np.ndarray) -> float:
|
|
92
138
|
"""
|
|
@@ -320,40 +366,57 @@ class Function:
|
|
|
320
366
|
# Create initial random solutions
|
|
321
367
|
solutions = np.random.uniform(min_r, max_r, data_size)
|
|
322
368
|
|
|
369
|
+
# Pre-allocate ranks array
|
|
370
|
+
ranks = np.empty(data_size, dtype=np.float64)
|
|
371
|
+
|
|
323
372
|
for _ in range(options.num_of_generations):
|
|
324
373
|
# Calculate fitness for all solutions (vectorized)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
374
|
+
_calculate_ranks_numba(solutions, self.coefficients, y_val, ranks)
|
|
375
|
+
|
|
376
|
+
parent_pool_size = int(data_size * options.selection_percentile)
|
|
377
|
+
|
|
378
|
+
# 1. Get indices for the elite solutions (O(N) operation)
|
|
379
|
+
# We find the 'elite_size'-th largest element.
|
|
380
|
+
elite_indices = np.argpartition(-ranks, elite_size)[:elite_size]
|
|
330
381
|
|
|
331
|
-
#
|
|
332
|
-
|
|
333
|
-
|
|
382
|
+
# 2. Get indices for the parent pool (O(N) operation)
|
|
383
|
+
# We find the 'parent_pool_size'-th largest element.
|
|
384
|
+
parent_pool_indices = np.argpartition(-ranks, parent_pool_size)[:parent_pool_size]
|
|
334
385
|
|
|
335
386
|
# --- Create the next generation ---
|
|
336
387
|
|
|
337
388
|
# 1. Elitism: Keep the best solutions as-is
|
|
338
|
-
elite_solutions = solutions[
|
|
339
|
-
|
|
340
|
-
# Define a "parent pool" of the top 50% of solutions to breed from
|
|
341
|
-
parent_pool = solutions[:data_size // 2]
|
|
389
|
+
elite_solutions = solutions[elite_indices]
|
|
342
390
|
|
|
343
391
|
# 2. Crossover: Breed two parents to create a child
|
|
344
|
-
# Select from the
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
392
|
+
# Select from the fitter PARENT POOL
|
|
393
|
+
parents1_idx = np.random.choice(parent_pool_indices, crossover_size)
|
|
394
|
+
parents2_idx = np.random.choice(parent_pool_indices, crossover_size)
|
|
395
|
+
|
|
396
|
+
parents1 = solutions[parents1_idx]
|
|
397
|
+
parents2 = solutions[parents2_idx]
|
|
398
|
+
# Blend Crossover (BLX-alpha)
|
|
399
|
+
alpha = options.blend_alpha
|
|
400
|
+
|
|
401
|
+
# Find min/max for all parent pairs
|
|
402
|
+
p_min = np.minimum(parents1, parents2)
|
|
403
|
+
p_max = np.maximum(parents1, parents2)
|
|
404
|
+
|
|
405
|
+
# Calculate range (I)
|
|
406
|
+
parent_range = p_max - p_min
|
|
407
|
+
|
|
408
|
+
# Calculate new min/max for the expanded range
|
|
409
|
+
new_min = p_min - (alpha * parent_range)
|
|
410
|
+
new_max = p_max + (alpha * parent_range)
|
|
411
|
+
|
|
412
|
+
# Create a new random child within the expanded range
|
|
413
|
+
crossover_solutions = np.random.uniform(new_min, new_max, crossover_size)
|
|
351
414
|
|
|
352
415
|
# 3. Mutation:
|
|
353
416
|
# Select from the full list (indices 0 to data_size-1)
|
|
354
417
|
mutation_candidates = solutions[np.random.randint(0, data_size, mutation_size)]
|
|
355
418
|
|
|
356
|
-
# Use mutation_strength
|
|
419
|
+
# Use mutation_strength
|
|
357
420
|
mutation_factors = np.random.uniform(
|
|
358
421
|
1 - options.mutation_strength,
|
|
359
422
|
1 + options.mutation_strength,
|
|
@@ -374,10 +437,7 @@ class Function:
|
|
|
374
437
|
|
|
375
438
|
# --- Final Step: Return the best results ---
|
|
376
439
|
# After all generations, do one last ranking to find the best solutions
|
|
377
|
-
|
|
378
|
-
error = y_calculated - y_val
|
|
379
|
-
with np.errstate(divide='ignore'):
|
|
380
|
-
ranks = np.where(error == 0, np.finfo(float).max, np.abs(1.0 / error))
|
|
440
|
+
_calculate_ranks_numba(solutions, self.coefficients, y_val, ranks)
|
|
381
441
|
|
|
382
442
|
# 1. Define quality based on the user's desired precision
|
|
383
443
|
# (e.g., precision=5 -> rank > 1e6, precision=8 -> rank > 1e9)
|
|
@@ -458,17 +518,31 @@ class Function:
|
|
|
458
518
|
# 1. Elitism
|
|
459
519
|
d_elite_solutions = d_solutions[:elite_size]
|
|
460
520
|
|
|
461
|
-
# Define a "parent pool" of the top 50% of solutions to breed from
|
|
462
|
-
d_parent_pool = d_solutions[:data_size // 2]
|
|
463
|
-
parent_pool_size = d_parent_pool.size
|
|
464
|
-
|
|
465
521
|
# 2. Crossover
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
522
|
+
parent_pool_size = int(data_size * options.selection_percentile)
|
|
523
|
+
# Select from the fitter PARENT POOL
|
|
524
|
+
parent1_indices = cupy.random.randint(0, parent_pool_size, crossover_size)
|
|
525
|
+
parent2_indices = cupy.random.randint(0, parent_pool_size, crossover_size)
|
|
526
|
+
# Get parents directly from the sorted solutions array using the pool-sized indices
|
|
469
527
|
d_parents1 = d_solutions[parent1_indices]
|
|
470
528
|
d_parents2 = d_solutions[parent2_indices]
|
|
471
|
-
|
|
529
|
+
|
|
530
|
+
# Blend Crossover (BLX-alpha)
|
|
531
|
+
alpha = options.blend_alpha
|
|
532
|
+
|
|
533
|
+
# Find min/max for all parent pairs
|
|
534
|
+
d_p_min = cupy.minimum(d_parents1, d_parents2)
|
|
535
|
+
d_p_max = cupy.maximum(d_parents1, d_parents2)
|
|
536
|
+
|
|
537
|
+
# Calculate range (I)
|
|
538
|
+
d_parent_range = d_p_max - d_p_min
|
|
539
|
+
|
|
540
|
+
# Calculate new min/max for the expanded range
|
|
541
|
+
d_new_min = d_p_min - (alpha * d_parent_range)
|
|
542
|
+
d_new_max = d_p_max + (alpha * d_parent_range)
|
|
543
|
+
|
|
544
|
+
# Create a new random child within the expanded range
|
|
545
|
+
d_crossover_solutions = cupy.random.uniform(d_new_min, d_new_max, crossover_size)
|
|
472
546
|
|
|
473
547
|
# 3. Mutation
|
|
474
548
|
# Select from the full list (indices 0 to data_size-1)
|
|
@@ -713,7 +787,7 @@ if __name__ == '__main__':
|
|
|
713
787
|
|
|
714
788
|
# --- Root Finding ---
|
|
715
789
|
# 1. Analytical solution for quadratic
|
|
716
|
-
roots_analytic = quadratic_solve(
|
|
790
|
+
roots_analytic = f1.quadratic_solve()
|
|
717
791
|
print(f"Analytic roots of f1: {roots_analytic}") # Expected: -1, 2.5
|
|
718
792
|
|
|
719
793
|
# 2. Genetic algorithm solution
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polysolve
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -43,6 +43,7 @@ Requires-Python: >=3.8
|
|
|
43
43
|
Description-Content-Type: text/markdown
|
|
44
44
|
License-File: LICENSE
|
|
45
45
|
Requires-Dist: numpy>=1.21
|
|
46
|
+
Requires-Dist: numba
|
|
46
47
|
Provides-Extra: cuda12
|
|
47
48
|
Requires-Dist: cupy-cuda12x; extra == "cuda12"
|
|
48
49
|
Provides-Extra: dev
|
|
@@ -56,15 +57,16 @@ Dynamic: license-file
|
|
|
56
57
|
[](https://pypi.org/project/polysolve/)
|
|
57
58
|
[](https://pypi.org/project/polysolve/)
|
|
58
59
|
|
|
59
|
-
A Python library for representing, manipulating, and solving polynomial equations
|
|
60
|
+
A Python library for representing, manipulating, and solving polynomial equations. Features a high-performance, Numba-accelerated genetic algorithm for CPU, with an optional CUDA/GPU backend for massive-scale parallel solving.
|
|
60
61
|
|
|
61
62
|
---
|
|
62
63
|
|
|
63
64
|
## Key Features
|
|
64
65
|
|
|
65
|
-
* **
|
|
66
|
-
* **
|
|
66
|
+
* **Numerically Stable Solver**: Makes complex calculations **practical**. Leverage your GPU to power the robust genetic algorithm, solving high-degree polynomials accurately in a reasonable timeframe.
|
|
67
|
+
* **Numba Accelerated CPU Solver**: The default genetic algorithm is JIT-compiled with Numba for high-speed CPU performance, right out of the box.
|
|
67
68
|
* **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
|
|
69
|
+
* **Create and Manipulate Polynomials**: Easily define polynomials of any degree using integer or float coefficients, and perform arithmetic operations like addition, subtraction, multiplication, and scaling.
|
|
68
70
|
* **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
|
|
69
71
|
* **Simple API**: Designed to be intuitive and easy to integrate into any project.
|
|
70
72
|
|
|
@@ -125,8 +127,8 @@ roots_analytic = f1.quadratic_solve()
|
|
|
125
127
|
print(f"Analytic roots: {sorted(roots_analytic)}")
|
|
126
128
|
# > Analytic roots: [-1.0, 2.5]
|
|
127
129
|
|
|
128
|
-
# 6. Find roots with the genetic algorithm (CPU)
|
|
129
|
-
#
|
|
130
|
+
# 6. Find roots with the genetic algorithm (Numba CPU)
|
|
131
|
+
# This is the default, JIT-compiled CPU solver.
|
|
130
132
|
ga_opts = GA_Options(num_of_generations=20)
|
|
131
133
|
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
|
132
134
|
print(f"Approximate roots from GA: {roots_ga[:2]}")
|
|
@@ -149,13 +151,22 @@ The default options are balanced, but for very complex polynomials, you may want
|
|
|
149
151
|
```python
|
|
150
152
|
from polysolve import GA_Options
|
|
151
153
|
|
|
152
|
-
# Create a config for a
|
|
153
|
-
# (
|
|
154
|
-
|
|
154
|
+
# Create a config for a deep search, optimized for finding
|
|
155
|
+
# *all* real roots (even if they are far apart).
|
|
156
|
+
ga_robust_search = GA_Options(
|
|
155
157
|
num_of_generations=50, # Run for more generations
|
|
156
158
|
data_size=500000, # Use a larger population
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
|
|
160
|
+
# --- Key Tuning Parameters for Multi-Root Finding ---
|
|
161
|
+
|
|
162
|
+
# Widen the parent pool to 75% to keep more "niches"
|
|
163
|
+
# (solution-clouds around different roots) alive.
|
|
164
|
+
selection_percentile=0.75,
|
|
165
|
+
|
|
166
|
+
# Increase the crossover blend factor to 0.75.
|
|
167
|
+
# This allows new solutions to be created further
|
|
168
|
+
# away from their parents, increasing exploration.
|
|
169
|
+
blend_alpha=0.75
|
|
159
170
|
)
|
|
160
171
|
|
|
161
172
|
# Pass the custom options to the solver
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
import numpy as np
|
|
3
|
+
import numpy.testing as npt
|
|
3
4
|
|
|
4
5
|
# Try to import cupy to check for CUDA availability
|
|
5
6
|
try:
|
|
@@ -101,7 +102,7 @@ def test_get_real_roots_numpy(quadratic_func):
|
|
|
101
102
|
Tests that the NumPy-based genetic algorithm approximates the roots correctly.
|
|
102
103
|
"""
|
|
103
104
|
# Using more generations for higher accuracy in testing
|
|
104
|
-
ga_opts = GA_Options(num_of_generations=50, data_size=200000, root_precision=3)
|
|
105
|
+
ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
|
|
105
106
|
|
|
106
107
|
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=False)
|
|
107
108
|
|
|
@@ -109,11 +110,7 @@ def test_get_real_roots_numpy(quadratic_func):
|
|
|
109
110
|
# We don't know which order they'll be in, so we check for presence.
|
|
110
111
|
expected_roots = np.array([-1.0, 2.5])
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
assert np.any(np.isclose(roots, expected_roots[0], atol=1e-2))
|
|
114
|
-
|
|
115
|
-
# Check that at least one found root is close to 2.5
|
|
116
|
-
assert np.any(np.isclose(roots, expected_roots[1], atol=1e-2))
|
|
113
|
+
npt.assert_allclose(np.sort(roots), np.sort(expected_roots), atol=1e-2)
|
|
117
114
|
|
|
118
115
|
|
|
119
116
|
@pytest.mark.skipif(not _CUPY_AVAILABLE, reason="CuPy is not installed, skipping CUDA test.")
|
|
@@ -124,13 +121,12 @@ def test_get_real_roots_cuda(quadratic_func):
|
|
|
124
121
|
It will be skipped automatically if CuPy is not available.
|
|
125
122
|
"""
|
|
126
123
|
|
|
127
|
-
ga_opts = GA_Options(num_of_generations=50, data_size=200000, root_precision=3)
|
|
124
|
+
ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
|
|
128
125
|
|
|
129
126
|
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=True)
|
|
130
127
|
|
|
131
128
|
expected_roots = np.array([-1.0, 2.5])
|
|
132
129
|
|
|
133
130
|
# Verify that the CUDA implementation also finds the correct roots within tolerance.
|
|
134
|
-
|
|
135
|
-
assert np.any(np.isclose(roots, expected_roots[1], atol=1e-2))
|
|
131
|
+
npt.assert_allclose(np.sort(roots), np.sort(expected_roots), atol=1e-2)
|
|
136
132
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|