polysolve 0.1.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.1.0/LICENSE +18 -0
- polysolve-0.1.0/PKG-INFO +147 -0
- polysolve-0.1.0/README.md +98 -0
- polysolve-0.1.0/pyproject.toml +46 -0
- polysolve-0.1.0/setup.cfg +4 -0
- polysolve-0.1.0/src/polysolve/__init__.py +451 -0
- polysolve-0.1.0/src/polysolve.egg-info/PKG-INFO +147 -0
- polysolve-0.1.0/src/polysolve.egg-info/SOURCES.txt +10 -0
- polysolve-0.1.0/src/polysolve.egg-info/dependency_links.txt +1 -0
- polysolve-0.1.0/src/polysolve.egg-info/requires.txt +7 -0
- polysolve-0.1.0/src/polysolve.egg-info/top_level.txt +1 -0
- polysolve-0.1.0/tests/test_polysolve.py +110 -0
polysolve-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 jono
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
|
+
portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
15
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
16
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
18
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
polysolve-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: polysolve
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration.
|
|
5
|
+
Author-email: Jonathan Rampersad <jonathan@jono-rams.work>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 jono
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
11
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
14
|
+
following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
17
|
+
portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
20
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
21
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
22
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
23
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
|
+
|
|
25
|
+
Project-URL: Homepage, https://gitea.jono-rams.work/jono/PolySolve
|
|
26
|
+
Project-URL: Bug Tracker, https://gitea.jono-rams.work/jono/PolySolve/issues
|
|
27
|
+
Keywords: math,polynomial,genetic algorithm,cuda,equation solver
|
|
28
|
+
Classifier: Development Status :: 4 - Beta
|
|
29
|
+
Classifier: Intended Audience :: Developers
|
|
30
|
+
Classifier: Intended Audience :: Science/Research
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
40
|
+
Requires-Python: >=3.8
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: numpy>=1.21
|
|
44
|
+
Provides-Extra: cuda12
|
|
45
|
+
Requires-Dist: cupy-cuda12x; extra == "cuda12"
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: pytest; extra == "dev"
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# polysolve
|
|
51
|
+
|
|
52
|
+
[](https://pypi.org/project/polysolve/)
|
|
53
|
+
[](https://pypi.org/project/polysolve/)
|
|
54
|
+
|
|
55
|
+
A Python library for representing, manipulating, and solving polynomial equations using a high-performance genetic algorithm, with optional CUDA/GPU acceleration.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Key Features
|
|
60
|
+
|
|
61
|
+
* **Create and Manipulate Polynomials**: Easily define polynomials of any degree and perform arithmetic operations like addition, subtraction, and scaling.
|
|
62
|
+
* **Genetic Algorithm Solver**: Find approximate real roots for complex polynomials where analytical solutions are difficult or impossible.
|
|
63
|
+
* **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
|
|
64
|
+
* **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
|
|
65
|
+
* **Simple API**: Designed to be intuitive and easy to integrate into any project.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
Install the base package from PyPI:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install polysolve
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### CUDA Acceleration
|
|
78
|
+
|
|
79
|
+
To enable GPU acceleration, install the extra that matches your installed NVIDIA CUDA Toolkit version. This provides a significant speedup for the genetic algorithm.
|
|
80
|
+
|
|
81
|
+
**For CUDA 12.x users:**
|
|
82
|
+
```bash
|
|
83
|
+
pip install polysolve[cuda12]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
Here is a simple example of how to define a quadratic function, find its properties, and solve for its roots.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from polysolve import Function, GA_Options, quadratic_solve
|
|
94
|
+
|
|
95
|
+
# 1. Define the function f(x) = 2x^2 - 3x - 5
|
|
96
|
+
f1 = Function(largest_exponent=2)
|
|
97
|
+
f1.set_constants([2, -3, -5])
|
|
98
|
+
|
|
99
|
+
print(f"Function f1: {f1}")
|
|
100
|
+
# > Function f1: 2x^2 - 3x - 5
|
|
101
|
+
|
|
102
|
+
# 2. Solve for y at a given x
|
|
103
|
+
y_val = f1.solve_y(5)
|
|
104
|
+
print(f"Value of f1 at x=5 is: {y_val}")
|
|
105
|
+
# > Value of f1 at x=5 is: 30.0
|
|
106
|
+
|
|
107
|
+
# 3. Get the derivative: 4x - 3
|
|
108
|
+
df1 = f1.differential()
|
|
109
|
+
print(f"Derivative of f1: {df1}")
|
|
110
|
+
# > Derivative of f1: 4x - 3
|
|
111
|
+
|
|
112
|
+
# 4. Find roots analytically using the quadratic formula
|
|
113
|
+
# This is exact and fast for degree-2 polynomials.
|
|
114
|
+
roots_analytic = quadratic_solve(f1)
|
|
115
|
+
print(f"Analytic roots: {sorted(roots_analytic)}")
|
|
116
|
+
# > Analytic roots: [-1.0, 2.5]
|
|
117
|
+
|
|
118
|
+
# 5. Find roots with the genetic algorithm (CPU)
|
|
119
|
+
# This can solve polynomials of any degree.
|
|
120
|
+
ga_opts = GA_Options(num_of_generations=20)
|
|
121
|
+
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
|
122
|
+
print(f"Approximate roots from GA: {roots_ga[:2]}")
|
|
123
|
+
# > Approximate roots from GA: [-1.000..., 2.500...]
|
|
124
|
+
|
|
125
|
+
# If you installed a CUDA extra, you can run it on the GPU:
|
|
126
|
+
# roots_ga_gpu = f1.get_real_roots(ga_opts, use_cuda=True)
|
|
127
|
+
# print(f"Approximate roots from GA (GPU): {roots_ga_gpu[:2]}")
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Development & Testing Environment
|
|
134
|
+
|
|
135
|
+
This project is automatically tested against a specific set of dependencies to ensure stability. Our Continuous Integration (CI) pipeline runs on an environment using **CUDA 12.5** on **Ubuntu 24.04**.
|
|
136
|
+
|
|
137
|
+
While the code may work on other configurations, all contributions must pass the automated tests in our reference environment. For detailed information on how to replicate the testing environment, please see our [**Contributing Guide**](CONTRIBUTING.md).
|
|
138
|
+
|
|
139
|
+
## Contributing
|
|
140
|
+
|
|
141
|
+
Contributions are welcome! Whether it's a bug report, a feature request, or a pull request, please feel free to get involved.
|
|
142
|
+
|
|
143
|
+
Please read our `CONTRIBUTING.md` file for details on our code of conduct and the process for submitting pull requests.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# polysolve
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/polysolve/)
|
|
4
|
+
[](https://pypi.org/project/polysolve/)
|
|
5
|
+
|
|
6
|
+
A Python library for representing, manipulating, and solving polynomial equations using a high-performance genetic algorithm, with optional CUDA/GPU acceleration.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Key Features
|
|
11
|
+
|
|
12
|
+
* **Create and Manipulate Polynomials**: Easily define polynomials of any degree and perform arithmetic operations like addition, subtraction, and scaling.
|
|
13
|
+
* **Genetic Algorithm Solver**: Find approximate real roots for complex polynomials where analytical solutions are difficult or impossible.
|
|
14
|
+
* **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
|
|
15
|
+
* **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
|
|
16
|
+
* **Simple API**: Designed to be intuitive and easy to integrate into any project.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Install the base package from PyPI:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install polysolve
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### CUDA Acceleration
|
|
29
|
+
|
|
30
|
+
To enable GPU acceleration, install the extra that matches your installed NVIDIA CUDA Toolkit version. This provides a significant speedup for the genetic algorithm.
|
|
31
|
+
|
|
32
|
+
**For CUDA 12.x users:**
|
|
33
|
+
```bash
|
|
34
|
+
pip install polysolve[cuda12]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
Here is a simple example of how to define a quadratic function, find its properties, and solve for its roots.
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from polysolve import Function, GA_Options, quadratic_solve
|
|
45
|
+
|
|
46
|
+
# 1. Define the function f(x) = 2x^2 - 3x - 5
|
|
47
|
+
f1 = Function(largest_exponent=2)
|
|
48
|
+
f1.set_constants([2, -3, -5])
|
|
49
|
+
|
|
50
|
+
print(f"Function f1: {f1}")
|
|
51
|
+
# > Function f1: 2x^2 - 3x - 5
|
|
52
|
+
|
|
53
|
+
# 2. Solve for y at a given x
|
|
54
|
+
y_val = f1.solve_y(5)
|
|
55
|
+
print(f"Value of f1 at x=5 is: {y_val}")
|
|
56
|
+
# > Value of f1 at x=5 is: 30.0
|
|
57
|
+
|
|
58
|
+
# 3. Get the derivative: 4x - 3
|
|
59
|
+
df1 = f1.differential()
|
|
60
|
+
print(f"Derivative of f1: {df1}")
|
|
61
|
+
# > Derivative of f1: 4x - 3
|
|
62
|
+
|
|
63
|
+
# 4. Find roots analytically using the quadratic formula
|
|
64
|
+
# This is exact and fast for degree-2 polynomials.
|
|
65
|
+
roots_analytic = quadratic_solve(f1)
|
|
66
|
+
print(f"Analytic roots: {sorted(roots_analytic)}")
|
|
67
|
+
# > Analytic roots: [-1.0, 2.5]
|
|
68
|
+
|
|
69
|
+
# 5. Find roots with the genetic algorithm (CPU)
|
|
70
|
+
# This can solve polynomials of any degree.
|
|
71
|
+
ga_opts = GA_Options(num_of_generations=20)
|
|
72
|
+
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
|
73
|
+
print(f"Approximate roots from GA: {roots_ga[:2]}")
|
|
74
|
+
# > Approximate roots from GA: [-1.000..., 2.500...]
|
|
75
|
+
|
|
76
|
+
# If you installed a CUDA extra, you can run it on the GPU:
|
|
77
|
+
# roots_ga_gpu = f1.get_real_roots(ga_opts, use_cuda=True)
|
|
78
|
+
# print(f"Approximate roots from GA (GPU): {roots_ga_gpu[:2]}")
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Development & Testing Environment
|
|
85
|
+
|
|
86
|
+
This project is automatically tested against a specific set of dependencies to ensure stability. Our Continuous Integration (CI) pipeline runs on an environment using **CUDA 12.5** on **Ubuntu 24.04**.
|
|
87
|
+
|
|
88
|
+
While the code may work on other configurations, all contributions must pass the automated tests in our reference environment. For detailed information on how to replicate the testing environment, please see our [**Contributing Guide**](CONTRIBUTING.md).
|
|
89
|
+
|
|
90
|
+
## Contributing
|
|
91
|
+
|
|
92
|
+
Contributions are welcome! Whether it's a bug report, a feature request, or a pull request, please feel free to get involved.
|
|
93
|
+
|
|
94
|
+
Please read our `CONTRIBUTING.md` file for details on our code of conduct and the process for submitting pull requests.
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# --- Core Metadata ---
|
|
7
|
+
name = "polysolve"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
|
|
11
|
+
]
|
|
12
|
+
description = "A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration."
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.8"
|
|
15
|
+
license = { file="LICENSE" }
|
|
16
|
+
keywords = ["math", "polynomial", "genetic algorithm", "cuda", "equation solver"]
|
|
17
|
+
|
|
18
|
+
# --- Classifiers ---
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Intended Audience :: Science/Research",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.8",
|
|
27
|
+
"Programming Language :: Python :: 3.9",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# --- Dependencies ---
|
|
35
|
+
dependencies = [
|
|
36
|
+
"numpy>=1.21"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# --- Optional Dependencies (Extras) ---
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
cuda12 = ["cupy-cuda12x"]
|
|
42
|
+
dev = ["pytest"]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://gitea.jono-rams.work/jono/PolySolve"
|
|
46
|
+
"Bug Tracker" = "https://gitea.jono-rams.work/jono/PolySolve/issues"
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import numpy as np
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
# Attempt to import CuPy for CUDA acceleration.
|
|
8
|
+
# If CuPy is not installed, the CUDA functionality will not be available.
|
|
9
|
+
try:
|
|
10
|
+
import cupy
|
|
11
|
+
_CUPY_AVAILABLE = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
_CUPY_AVAILABLE = False
|
|
14
|
+
|
|
15
|
+
# The CUDA kernel for the fitness function
|
|
16
|
+
_FITNESS_KERNEL = """
|
|
17
|
+
extern "C" __global__ void fitness_kernel(
|
|
18
|
+
const long long* coefficients,
|
|
19
|
+
int num_coefficients,
|
|
20
|
+
const double* x_vals,
|
|
21
|
+
double* ranks,
|
|
22
|
+
int size,
|
|
23
|
+
double y_val)
|
|
24
|
+
{
|
|
25
|
+
int idx = threadIdx.x + blockIdx.x * blockDim.x;
|
|
26
|
+
if (idx < size)
|
|
27
|
+
{
|
|
28
|
+
double ans = 0;
|
|
29
|
+
int lrgst_expo = num_coefficients - 1;
|
|
30
|
+
for (int i = 0; i < num_coefficients; ++i)
|
|
31
|
+
{
|
|
32
|
+
ans += coefficients[i] * pow(x_vals[idx], (double)(lrgst_expo - i));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ans -= y_val;
|
|
36
|
+
ranks[idx] = (ans == 0) ? 1.7976931348623157e+308 : fabs(1.0 / ans);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class GA_Options:
|
|
43
|
+
"""
|
|
44
|
+
Configuration options for the genetic algorithm used to find function roots.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
min_range (float): The minimum value for the initial random solutions.
|
|
48
|
+
max_range (float): The maximum value for the initial random solutions.
|
|
49
|
+
num_of_generations (int): The number of iterations the algorithm will run.
|
|
50
|
+
sample_size (int): The number of top solutions to keep and return.
|
|
51
|
+
data_size (int): The total number of solutions generated in each generation.
|
|
52
|
+
mutation_percentage (float): The amount by which top solutions are mutated each generation.
|
|
53
|
+
"""
|
|
54
|
+
min_range: float = -100.0
|
|
55
|
+
max_range: float = 100.0
|
|
56
|
+
num_of_generations: int = 10
|
|
57
|
+
sample_size: int = 1000
|
|
58
|
+
data_size: int = 100000
|
|
59
|
+
mutation_percentage: float = 0.01
|
|
60
|
+
|
|
61
|
+
class Function:
|
|
62
|
+
"""
|
|
63
|
+
Represents an exponential function (polynomial) of the form:
|
|
64
|
+
c_0*x^n + c_1*x^(n-1) + ... + c_n
|
|
65
|
+
"""
|
|
66
|
+
def __init__(self, largest_exponent: int):
|
|
67
|
+
"""
|
|
68
|
+
Initializes a function with its highest degree.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
largest_exponent (int): The largest exponent (n) in the function.
|
|
72
|
+
"""
|
|
73
|
+
if not isinstance(largest_exponent, int) or largest_exponent < 0:
|
|
74
|
+
raise ValueError("largest_exponent must be a non-negative integer.")
|
|
75
|
+
self._largest_exponent = largest_exponent
|
|
76
|
+
self.coefficients: Optional[np.ndarray] = None
|
|
77
|
+
self._initialized = False
|
|
78
|
+
|
|
79
|
+
def set_coeffs(self, coefficients: List[int]):
|
|
80
|
+
"""
|
|
81
|
+
Sets the coefficients of the polynomial.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
coefficients (List[int]): A list of integer coefficients. The list size
|
|
85
|
+
must be largest_exponent + 1.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: If the input is invalid.
|
|
89
|
+
"""
|
|
90
|
+
expected_size = self._largest_exponent + 1
|
|
91
|
+
if len(coefficients) != expected_size:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"Function with exponent {self._largest_exponent} requires {expected_size} coefficients, "
|
|
94
|
+
f"but {len(coefficients)} were given."
|
|
95
|
+
)
|
|
96
|
+
if coefficients[0] == 0 and self._largest_exponent > 0:
|
|
97
|
+
raise ValueError("The first constant (for the largest exponent) cannot be 0.")
|
|
98
|
+
|
|
99
|
+
self.coefficients = np.array(coefficients, dtype=np.int64)
|
|
100
|
+
self._initialized = True
|
|
101
|
+
|
|
102
|
+
def _check_initialized(self):
|
|
103
|
+
"""Raises a RuntimeError if the function coefficients have not been set."""
|
|
104
|
+
if not self._initialized:
|
|
105
|
+
raise RuntimeError("Function is not fully initialized. Call .set_coeffs() first.")
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def largest_exponent(self) -> int:
|
|
109
|
+
"""Returns the largest exponent of the function."""
|
|
110
|
+
return self._largest_exponent
|
|
111
|
+
|
|
112
|
+
def solve_y(self, x_val: float) -> float:
|
|
113
|
+
"""
|
|
114
|
+
Solves for y given an x value. (i.e., evaluates the polynomial at x).
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
x_val (float): The x-value to evaluate.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
float: The resulting y-value.
|
|
121
|
+
"""
|
|
122
|
+
self._check_initialized()
|
|
123
|
+
return np.polyval(self.coefficients, x_val)
|
|
124
|
+
|
|
125
|
+
def differential(self) -> 'Function':
|
|
126
|
+
"""
|
|
127
|
+
Calculates the derivative of the function.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Function: A new Function object representing the derivative.
|
|
131
|
+
"""
|
|
132
|
+
self._check_initialized()
|
|
133
|
+
if self._largest_exponent == 0:
|
|
134
|
+
raise ValueError("Cannot differentiate a constant (Function of degree 0).")
|
|
135
|
+
|
|
136
|
+
derivative_coefficients = np.polyder(self.coefficients)
|
|
137
|
+
|
|
138
|
+
diff_func = Function(self._largest_exponent - 1)
|
|
139
|
+
diff_func.set_coeffs(derivative_coefficients.tolist())
|
|
140
|
+
return diff_func
|
|
141
|
+
|
|
142
|
+
def get_real_roots(self, options: GA_Options = GA_Options(), use_cuda: bool = False) -> np.ndarray:
|
|
143
|
+
"""
|
|
144
|
+
Uses a genetic algorithm to find the approximate real roots of the function (where y=0).
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
options (GA_Options): Configuration for the genetic algorithm.
|
|
148
|
+
use_cuda (bool): If True, attempts to use CUDA for acceleration.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
np.ndarray: An array of approximate root values.
|
|
152
|
+
"""
|
|
153
|
+
self._check_initialized()
|
|
154
|
+
return self.solve_x(0.0, options, use_cuda)
|
|
155
|
+
|
|
156
|
+
def solve_x(self, y_val: float, options: GA_Options = GA_Options(), use_cuda: bool = False) -> np.ndarray:
|
|
157
|
+
"""
|
|
158
|
+
Uses a genetic algorithm to find x-values for a given y-value.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
y_val (float): The target y-value.
|
|
162
|
+
options (GA_Options): Configuration for the genetic algorithm.
|
|
163
|
+
use_cuda (bool): If True, attempts to use CUDA for acceleration.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
np.ndarray: An array of approximate x-values.
|
|
167
|
+
"""
|
|
168
|
+
self._check_initialized()
|
|
169
|
+
if use_cuda and _CUPY_AVAILABLE:
|
|
170
|
+
return self._solve_x_cuda(y_val, options)
|
|
171
|
+
else:
|
|
172
|
+
if use_cuda:
|
|
173
|
+
warnings.warn(
|
|
174
|
+
"use_cuda=True was specified, but CuPy is not installed. "
|
|
175
|
+
"Falling back to NumPy (CPU). For GPU acceleration, "
|
|
176
|
+
"install with 'pip install polysolve[cuda]'.",
|
|
177
|
+
UserWarning
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return self._solve_x_numpy(y_val, options)
|
|
181
|
+
|
|
182
|
+
def _solve_x_numpy(self, y_val: float, options: GA_Options) -> np.ndarray:
|
|
183
|
+
"""Genetic algorithm implementation using NumPy (CPU)."""
|
|
184
|
+
# Create initial random solutions
|
|
185
|
+
solutions = np.random.uniform(options.min_range, options.max_range, options.data_size)
|
|
186
|
+
|
|
187
|
+
for _ in range(options.num_of_generations):
|
|
188
|
+
# Calculate fitness for all solutions (vectorized)
|
|
189
|
+
y_calculated = np.polyval(self.coefficients, solutions)
|
|
190
|
+
error = y_calculated - y_val
|
|
191
|
+
|
|
192
|
+
ranks = np.where(error == 0, np.finfo(float).max, np.abs(1.0 / error))
|
|
193
|
+
|
|
194
|
+
# Sort solutions by fitness (descending)
|
|
195
|
+
sorted_indices = np.argsort(-ranks)
|
|
196
|
+
solutions = solutions[sorted_indices]
|
|
197
|
+
|
|
198
|
+
# Keep only the top solutions
|
|
199
|
+
top_solutions = solutions[:options.sample_size]
|
|
200
|
+
|
|
201
|
+
# For the next generation, start with the mutated top solutions
|
|
202
|
+
# and fill the rest with new random values.
|
|
203
|
+
mutation_factors = np.random.uniform(
|
|
204
|
+
1 - options.mutation_percentage,
|
|
205
|
+
1 + options.mutation_percentage,
|
|
206
|
+
options.sample_size
|
|
207
|
+
)
|
|
208
|
+
mutated_solutions = top_solutions * mutation_factors
|
|
209
|
+
|
|
210
|
+
new_random_solutions = np.random.uniform(
|
|
211
|
+
options.min_range, options.max_range, options.data_size - options.sample_size
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
solutions = np.concatenate([mutated_solutions, new_random_solutions])
|
|
215
|
+
|
|
216
|
+
# Final sort of the best solutions from the last generation
|
|
217
|
+
final_solutions = np.sort(solutions[:options.sample_size])
|
|
218
|
+
return final_solutions
|
|
219
|
+
|
|
220
|
+
def _solve_x_cuda(self, y_val: float, options: GA_Options) -> np.ndarray:
|
|
221
|
+
"""Genetic algorithm implementation using CuPy (GPU/CUDA)."""
|
|
222
|
+
# Load the raw CUDA kernel
|
|
223
|
+
fitness_gpu = cupy.RawKernel(_FITNESS_KERNEL, 'fitness_kernel')
|
|
224
|
+
|
|
225
|
+
# Move coefficients to GPU
|
|
226
|
+
d_coefficients = cupy.array(self.coefficients, dtype=cupy.int64)
|
|
227
|
+
|
|
228
|
+
# Create initial random solutions on the GPU
|
|
229
|
+
d_solutions = cupy.random.uniform(
|
|
230
|
+
options.min_range, options.max_range, options.data_size, dtype=cupy.float64
|
|
231
|
+
)
|
|
232
|
+
d_ranks = cupy.empty(options.data_size, dtype=cupy.float64)
|
|
233
|
+
|
|
234
|
+
# Configure kernel launch parameters
|
|
235
|
+
threads_per_block = 512
|
|
236
|
+
blocks_per_grid = (options.data_size + threads_per_block - 1) // threads_per_block
|
|
237
|
+
|
|
238
|
+
for i in range(options.num_of_generations):
|
|
239
|
+
# Run the fitness kernel on the GPU
|
|
240
|
+
fitness_gpu(
|
|
241
|
+
(blocks_per_grid,), (threads_per_block,),
|
|
242
|
+
(d_coefficients, d_coefficients.size, d_solutions, d_ranks, d_solutions.size, y_val)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Sort solutions by rank on the GPU
|
|
246
|
+
sorted_indices = cupy.argsort(-d_ranks)
|
|
247
|
+
d_solutions = d_solutions[sorted_indices]
|
|
248
|
+
|
|
249
|
+
if i + 1 == options.num_of_generations:
|
|
250
|
+
break
|
|
251
|
+
|
|
252
|
+
# Get top solutions
|
|
253
|
+
d_top_solutions = d_solutions[:options.sample_size]
|
|
254
|
+
|
|
255
|
+
# Mutate top solutions on the GPU
|
|
256
|
+
mutation_factors = cupy.random.uniform(
|
|
257
|
+
1 - options.mutation_percentage, 1 + options.mutation_percentage, options.sample_size
|
|
258
|
+
)
|
|
259
|
+
d_mutated = d_top_solutions * mutation_factors
|
|
260
|
+
|
|
261
|
+
# Create new random solutions for the rest
|
|
262
|
+
d_new_random = cupy.random.uniform(
|
|
263
|
+
options.min_range, options.max_range, options.data_size - options.sample_size
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
d_solutions = cupy.concatenate([d_mutated, d_new_random])
|
|
267
|
+
|
|
268
|
+
# Get the final sample, sort it, and copy back to CPU
|
|
269
|
+
final_solutions_gpu = cupy.sort(d_solutions[:options.sample_size])
|
|
270
|
+
return final_solutions_gpu.get()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def __str__(self) -> str:
|
|
274
|
+
"""Returns a human-readable string representation of the function."""
|
|
275
|
+
self._check_initialized()
|
|
276
|
+
parts = []
|
|
277
|
+
for i, c in enumerate(self.coefficients):
|
|
278
|
+
if c == 0:
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
power = self._largest_exponent - i
|
|
282
|
+
|
|
283
|
+
# Coefficient part
|
|
284
|
+
if c == 1 and power != 0:
|
|
285
|
+
coeff = ""
|
|
286
|
+
elif c == -1 and power != 0:
|
|
287
|
+
coeff = "-"
|
|
288
|
+
else:
|
|
289
|
+
coeff = str(c)
|
|
290
|
+
|
|
291
|
+
# Variable part
|
|
292
|
+
if power == 0:
|
|
293
|
+
var = ""
|
|
294
|
+
elif power == 1:
|
|
295
|
+
var = "x"
|
|
296
|
+
else:
|
|
297
|
+
var = f"x^{power}"
|
|
298
|
+
|
|
299
|
+
# Add sign for non-leading terms
|
|
300
|
+
sign = ""
|
|
301
|
+
if i > 0:
|
|
302
|
+
sign = " + " if c > 0 else " - "
|
|
303
|
+
coeff = str(abs(c))
|
|
304
|
+
if abs(c) == 1 and power != 0:
|
|
305
|
+
coeff = "" # Don't show 1 for non-constant terms
|
|
306
|
+
|
|
307
|
+
parts.append(f"{sign}{coeff}{var}")
|
|
308
|
+
|
|
309
|
+
# Join parts and clean up
|
|
310
|
+
result = "".join(parts)
|
|
311
|
+
if result.startswith(" + "):
|
|
312
|
+
result = result[3:]
|
|
313
|
+
return result if result else "0"
|
|
314
|
+
|
|
315
|
+
def __repr__(self) -> str:
|
|
316
|
+
return f"Function(str='{self}')"
|
|
317
|
+
|
|
318
|
+
def __add__(self, other: 'Function') -> 'Function':
|
|
319
|
+
"""Adds two Function objects."""
|
|
320
|
+
self._check_initialized()
|
|
321
|
+
other._check_initialized()
|
|
322
|
+
|
|
323
|
+
new_coefficients = np.polyadd(self.coefficients, other.coefficients)
|
|
324
|
+
|
|
325
|
+
result_func = Function(len(new_coefficients) - 1)
|
|
326
|
+
result_func.set_coeffs(new_coefficients.tolist())
|
|
327
|
+
return result_func
|
|
328
|
+
|
|
329
|
+
def __sub__(self, other: 'Function') -> 'Function':
|
|
330
|
+
"""Subtracts another Function object from this one."""
|
|
331
|
+
self._check_initialized()
|
|
332
|
+
other._check_initialized()
|
|
333
|
+
|
|
334
|
+
new_coefficients = np.polysub(self.coefficients, other.coefficients)
|
|
335
|
+
|
|
336
|
+
result_func = Function(len(new_coefficients) - 1)
|
|
337
|
+
result_func.set_coeffs(new_coefficients.tolist())
|
|
338
|
+
return result_func
|
|
339
|
+
|
|
340
|
+
def __mul__(self, scalar: int) -> 'Function':
|
|
341
|
+
"""Multiplies the function by a scalar constant."""
|
|
342
|
+
self._check_initialized()
|
|
343
|
+
if not isinstance(scalar, (int, float)):
|
|
344
|
+
return NotImplemented
|
|
345
|
+
if scalar == 0:
|
|
346
|
+
raise ValueError("Cannot multiply a function by 0.")
|
|
347
|
+
|
|
348
|
+
new_coefficients = self.coefficients * scalar
|
|
349
|
+
|
|
350
|
+
result_func = Function(self._largest_exponent)
|
|
351
|
+
result_func.set_coeffs(new_coefficients.tolist())
|
|
352
|
+
return result_func
|
|
353
|
+
|
|
354
|
+
def __rmul__(self, scalar: int) -> 'Function':
|
|
355
|
+
"""Handles scalar multiplication from the right (e.g., 3 * func)."""
|
|
356
|
+
return self.__mul__(scalar)
|
|
357
|
+
|
|
358
|
+
def __imul__(self, scalar: int) -> 'Function':
|
|
359
|
+
"""Performs in-place multiplication by a scalar (func *= 3)."""
|
|
360
|
+
self._check_initialized()
|
|
361
|
+
if not isinstance(scalar, (int, float)):
|
|
362
|
+
return NotImplemented
|
|
363
|
+
if scalar == 0:
|
|
364
|
+
raise ValueError("Cannot multiply a function by 0.")
|
|
365
|
+
|
|
366
|
+
self.coefficients *= scalar
|
|
367
|
+
return self
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def quadratic_solve(f: Function) -> Optional[List[float]]:
|
|
371
|
+
"""
|
|
372
|
+
Calculates the real roots of a quadratic function using the quadratic formula.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
f (Function): A Function object of degree 2.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
Optional[List[float]]: A list containing the two real roots, or None if there are no real roots.
|
|
379
|
+
"""
|
|
380
|
+
f._check_initialized()
|
|
381
|
+
if f.largest_exponent != 2:
|
|
382
|
+
raise ValueError("Input function must be quadratic (degree 2).")
|
|
383
|
+
|
|
384
|
+
a, b, c = f.coefficients
|
|
385
|
+
|
|
386
|
+
discriminant = (b**2) - (4*a*c)
|
|
387
|
+
|
|
388
|
+
if discriminant < 0:
|
|
389
|
+
return None # No real roots
|
|
390
|
+
|
|
391
|
+
sqrt_discriminant = math.sqrt(discriminant)
|
|
392
|
+
root1 = (-b + sqrt_discriminant) / (2 * a)
|
|
393
|
+
root2 = (-b - sqrt_discriminant) / (2 * a)
|
|
394
|
+
|
|
395
|
+
return [root1, root2]
|
|
396
|
+
|
|
397
|
+
# Example Usage
|
|
398
|
+
if __name__ == '__main__':
|
|
399
|
+
print("--- Demonstrating Functionality ---")
|
|
400
|
+
|
|
401
|
+
# Create a quadratic function: 2x^2 - 3x - 5
|
|
402
|
+
f1 = Function(2)
|
|
403
|
+
f1.set_coeffs([2, -3, -5])
|
|
404
|
+
print(f"Function f1: {f1}")
|
|
405
|
+
|
|
406
|
+
# Solve for y
|
|
407
|
+
y = f1.solve_y(5)
|
|
408
|
+
print(f"Value of f1 at x=5 is: {y}") # Expected: 2*(25) - 3*(5) - 5 = 50 - 15 - 5 = 30
|
|
409
|
+
|
|
410
|
+
# Find the derivative: 4x - 3
|
|
411
|
+
df1 = f1.differential()
|
|
412
|
+
print(f"Derivative of f1: {df1}")
|
|
413
|
+
|
|
414
|
+
# --- Root Finding ---
|
|
415
|
+
# 1. Analytical solution for quadratic
|
|
416
|
+
roots_analytic = quadratic_solve(f1)
|
|
417
|
+
print(f"Analytic roots of f1: {roots_analytic}") # Expected: -1, 2.5
|
|
418
|
+
|
|
419
|
+
# 2. Genetic algorithm solution
|
|
420
|
+
ga_opts = GA_Options(num_of_generations=20, data_size=50000, sample_size=10)
|
|
421
|
+
print("\nFinding roots with Genetic Algorithm (CPU)...")
|
|
422
|
+
roots_ga_cpu = f1.get_real_roots(ga_opts)
|
|
423
|
+
print(f"Approximate roots from GA (CPU): {roots_ga_cpu}")
|
|
424
|
+
print("(Note: GA provides approximations around the true roots)")
|
|
425
|
+
|
|
426
|
+
# 3. CUDA accelerated genetic algorithm
|
|
427
|
+
if _CUPY_AVAILABLE:
|
|
428
|
+
print("\nFinding roots with Genetic Algorithm (CUDA)...")
|
|
429
|
+
# Since this PC has an RTX 4060 Ti, we can use the CUDA version.
|
|
430
|
+
roots_ga_gpu = f1.get_real_roots(ga_opts, use_cuda=True)
|
|
431
|
+
print(f"Approximate roots from GA (GPU): {roots_ga_gpu}")
|
|
432
|
+
else:
|
|
433
|
+
print("\nSkipping CUDA example: CuPy library not found or no compatible GPU.")
|
|
434
|
+
|
|
435
|
+
# --- Function Arithmetic ---
|
|
436
|
+
print("\n--- Function Arithmetic ---")
|
|
437
|
+
f2 = Function(1)
|
|
438
|
+
f2.set_coeffs([1, 10]) # x + 10
|
|
439
|
+
print(f"Function f2: {f2}")
|
|
440
|
+
|
|
441
|
+
# Addition: (2x^2 - 3x - 5) + (x + 10) = 2x^2 - 2x + 5
|
|
442
|
+
f_add = f1 + f2
|
|
443
|
+
print(f"f1 + f2 = {f_add}")
|
|
444
|
+
|
|
445
|
+
# Subtraction: (2x^2 - 3x - 5) - (x + 10) = 2x^2 - 4x - 15
|
|
446
|
+
f_sub = f1 - f2
|
|
447
|
+
print(f"f1 - f2 = {f_sub}")
|
|
448
|
+
|
|
449
|
+
# Multiplication: (x + 10) * 3 = 3x + 30
|
|
450
|
+
f_mul = f2 * 3
|
|
451
|
+
print(f"f2 * 3 = {f_mul}")
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: polysolve
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration.
|
|
5
|
+
Author-email: Jonathan Rampersad <jonathan@jono-rams.work>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 jono
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
11
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
14
|
+
following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
17
|
+
portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
20
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
21
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
22
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
23
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
|
+
|
|
25
|
+
Project-URL: Homepage, https://gitea.jono-rams.work/jono/PolySolve
|
|
26
|
+
Project-URL: Bug Tracker, https://gitea.jono-rams.work/jono/PolySolve/issues
|
|
27
|
+
Keywords: math,polynomial,genetic algorithm,cuda,equation solver
|
|
28
|
+
Classifier: Development Status :: 4 - Beta
|
|
29
|
+
Classifier: Intended Audience :: Developers
|
|
30
|
+
Classifier: Intended Audience :: Science/Research
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
40
|
+
Requires-Python: >=3.8
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: numpy>=1.21
|
|
44
|
+
Provides-Extra: cuda12
|
|
45
|
+
Requires-Dist: cupy-cuda12x; extra == "cuda12"
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: pytest; extra == "dev"
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# polysolve
|
|
51
|
+
|
|
52
|
+
[](https://pypi.org/project/polysolve/)
|
|
53
|
+
[](https://pypi.org/project/polysolve/)
|
|
54
|
+
|
|
55
|
+
A Python library for representing, manipulating, and solving polynomial equations using a high-performance genetic algorithm, with optional CUDA/GPU acceleration.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Key Features
|
|
60
|
+
|
|
61
|
+
* **Create and Manipulate Polynomials**: Easily define polynomials of any degree and perform arithmetic operations like addition, subtraction, and scaling.
|
|
62
|
+
* **Genetic Algorithm Solver**: Find approximate real roots for complex polynomials where analytical solutions are difficult or impossible.
|
|
63
|
+
* **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
|
|
64
|
+
* **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
|
|
65
|
+
* **Simple API**: Designed to be intuitive and easy to integrate into any project.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
Install the base package from PyPI:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install polysolve
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### CUDA Acceleration
|
|
78
|
+
|
|
79
|
+
To enable GPU acceleration, install the extra that matches your installed NVIDIA CUDA Toolkit version. This provides a significant speedup for the genetic algorithm.
|
|
80
|
+
|
|
81
|
+
**For CUDA 12.x users:**
|
|
82
|
+
```bash
|
|
83
|
+
pip install polysolve[cuda12]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
Here is a simple example of how to define a quadratic function, find its properties, and solve for its roots.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from polysolve import Function, GA_Options, quadratic_solve
|
|
94
|
+
|
|
95
|
+
# 1. Define the function f(x) = 2x^2 - 3x - 5
|
|
96
|
+
f1 = Function(largest_exponent=2)
|
|
97
|
+
f1.set_constants([2, -3, -5])
|
|
98
|
+
|
|
99
|
+
print(f"Function f1: {f1}")
|
|
100
|
+
# > Function f1: 2x^2 - 3x - 5
|
|
101
|
+
|
|
102
|
+
# 2. Solve for y at a given x
|
|
103
|
+
y_val = f1.solve_y(5)
|
|
104
|
+
print(f"Value of f1 at x=5 is: {y_val}")
|
|
105
|
+
# > Value of f1 at x=5 is: 30.0
|
|
106
|
+
|
|
107
|
+
# 3. Get the derivative: 4x - 3
|
|
108
|
+
df1 = f1.differential()
|
|
109
|
+
print(f"Derivative of f1: {df1}")
|
|
110
|
+
# > Derivative of f1: 4x - 3
|
|
111
|
+
|
|
112
|
+
# 4. Find roots analytically using the quadratic formula
|
|
113
|
+
# This is exact and fast for degree-2 polynomials.
|
|
114
|
+
roots_analytic = quadratic_solve(f1)
|
|
115
|
+
print(f"Analytic roots: {sorted(roots_analytic)}")
|
|
116
|
+
# > Analytic roots: [-1.0, 2.5]
|
|
117
|
+
|
|
118
|
+
# 5. Find roots with the genetic algorithm (CPU)
|
|
119
|
+
# This can solve polynomials of any degree.
|
|
120
|
+
ga_opts = GA_Options(num_of_generations=20)
|
|
121
|
+
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
|
122
|
+
print(f"Approximate roots from GA: {roots_ga[:2]}")
|
|
123
|
+
# > Approximate roots from GA: [-1.000..., 2.500...]
|
|
124
|
+
|
|
125
|
+
# If you installed a CUDA extra, you can run it on the GPU:
|
|
126
|
+
# roots_ga_gpu = f1.get_real_roots(ga_opts, use_cuda=True)
|
|
127
|
+
# print(f"Approximate roots from GA (GPU): {roots_ga_gpu[:2]}")
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Development & Testing Environment
|
|
134
|
+
|
|
135
|
+
This project is automatically tested against a specific set of dependencies to ensure stability. Our Continuous Integration (CI) pipeline runs on an environment using **CUDA 12.5** on **Ubuntu 24.04**.
|
|
136
|
+
|
|
137
|
+
While the code may work on other configurations, all contributions must pass the automated tests in our reference environment. For detailed information on how to replicate the testing environment, please see our [**Contributing Guide**](CONTRIBUTING.md).
|
|
138
|
+
|
|
139
|
+
## Contributing
|
|
140
|
+
|
|
141
|
+
Contributions are welcome! Whether it's a bug report, a feature request, or a pull request, please feel free to get involved.
|
|
142
|
+
|
|
143
|
+
Please read our `CONTRIBUTING.md` file for details on our code of conduct and the process for submitting pull requests.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/polysolve/__init__.py
|
|
5
|
+
src/polysolve.egg-info/PKG-INFO
|
|
6
|
+
src/polysolve.egg-info/SOURCES.txt
|
|
7
|
+
src/polysolve.egg-info/dependency_links.txt
|
|
8
|
+
src/polysolve.egg-info/requires.txt
|
|
9
|
+
src/polysolve.egg-info/top_level.txt
|
|
10
|
+
tests/test_polysolve.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
polysolve
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
# Try to import cupy to check for CUDA availability
|
|
5
|
+
try:
|
|
6
|
+
import cupy
|
|
7
|
+
_CUPY_AVAILABLE = True
|
|
8
|
+
except ImportError:
|
|
9
|
+
_CUPY_AVAILABLE = False
|
|
10
|
+
|
|
11
|
+
from polysolve import Function, GA_Options, quadratic_solve
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def quadratic_func() -> Function:
|
|
15
|
+
"""Provides a standard quadratic function: 2x^2 - 3x - 5."""
|
|
16
|
+
f = Function(largest_exponent=2)
|
|
17
|
+
f.set_coeffs([2, -3, -5])
|
|
18
|
+
return f
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def linear_func() -> Function:
|
|
22
|
+
"""Provides a standard linear function: x + 10."""
|
|
23
|
+
f = Function(largest_exponent=1)
|
|
24
|
+
f.set_coeffs([1, 10])
|
|
25
|
+
return f
|
|
26
|
+
|
|
27
|
+
# --- Core Functionality Tests ---
|
|
28
|
+
|
|
29
|
+
def test_solve_y(quadratic_func):
|
|
30
|
+
"""Tests if the function correctly evaluates y for a given x."""
|
|
31
|
+
assert quadratic_func.solve_y(5) == 30.0
|
|
32
|
+
assert quadratic_func.solve_y(0) == -5.0
|
|
33
|
+
assert quadratic_func.solve_y(-1) == 0.0
|
|
34
|
+
|
|
35
|
+
def test_differential(quadratic_func):
|
|
36
|
+
"""Tests the calculation of the function's derivative."""
|
|
37
|
+
derivative = quadratic_func.differential()
|
|
38
|
+
assert derivative.largest_exponent == 1
|
|
39
|
+
# The derivative of 2x^2 - 3x - 5 is 4x - 3
|
|
40
|
+
assert np.array_equal(derivative.coefficients, [4, -3])
|
|
41
|
+
|
|
42
|
+
def test_quadratic_solve(quadratic_func):
|
|
43
|
+
"""Tests the analytical quadratic solver for exact roots."""
|
|
44
|
+
roots = quadratic_solve(quadratic_func)
|
|
45
|
+
# Sorting ensures consistent order for comparison
|
|
46
|
+
assert sorted(roots) == [-1.0, 2.5]
|
|
47
|
+
|
|
48
|
+
# --- Arithmetic Operation Tests ---
|
|
49
|
+
|
|
50
|
+
def test_addition(quadratic_func, linear_func):
|
|
51
|
+
"""Tests the addition of two Function objects."""
|
|
52
|
+
# (2x^2 - 3x - 5) + (x + 10) = 2x^2 - 2x + 5
|
|
53
|
+
result = quadratic_func + linear_func
|
|
54
|
+
assert result.largest_exponent == 2
|
|
55
|
+
assert np.array_equal(result.coefficients, [2, -2, 5])
|
|
56
|
+
|
|
57
|
+
def test_subtraction(quadratic_func, linear_func):
|
|
58
|
+
"""Tests the subtraction of two Function objects."""
|
|
59
|
+
# (2x^2 - 3x - 5) - (x + 10) = 2x^2 - 4x - 15
|
|
60
|
+
result = quadratic_func - linear_func
|
|
61
|
+
assert result.largest_exponent == 2
|
|
62
|
+
assert np.array_equal(result.coefficients, [2, -4, -15])
|
|
63
|
+
|
|
64
|
+
def test_multiplication(linear_func):
|
|
65
|
+
"""Tests the multiplication of a Function object by a scalar."""
|
|
66
|
+
# (x + 10) * 3 = 3x + 30
|
|
67
|
+
result = linear_func * 3
|
|
68
|
+
assert result.largest_exponent == 1
|
|
69
|
+
assert np.array_equal(result.coefficients, [3, 30])
|
|
70
|
+
|
|
71
|
+
# --- Genetic Algorithm Root-Finding Tests ---
|
|
72
|
+
|
|
73
|
+
def test_get_real_roots_numpy(quadratic_func):
|
|
74
|
+
"""
|
|
75
|
+
Tests that the NumPy-based genetic algorithm approximates the roots correctly.
|
|
76
|
+
"""
|
|
77
|
+
# Using more generations for higher accuracy in testing
|
|
78
|
+
ga_opts = GA_Options(num_of_generations=25, data_size=50000)
|
|
79
|
+
|
|
80
|
+
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=False)
|
|
81
|
+
|
|
82
|
+
# Check if the algorithm found values close to the two known roots.
|
|
83
|
+
# We don't know which order they'll be in, so we check for presence.
|
|
84
|
+
expected_roots = np.array([-1.0, 2.5])
|
|
85
|
+
|
|
86
|
+
# Check that at least one found root is close to -1.0
|
|
87
|
+
assert np.any(np.isclose(roots, expected_roots[0], atol=1e-2))
|
|
88
|
+
|
|
89
|
+
# Check that at least one found root is close to 2.5
|
|
90
|
+
assert np.any(np.isclose(roots, expected_roots[1], atol=1e-2))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.mark.skipif(not _CUPY_AVAILABLE, reason="CuPy is not installed, skipping CUDA test.")
|
|
94
|
+
def test_get_real_roots_cuda(quadratic_func):
|
|
95
|
+
"""
|
|
96
|
+
Tests that the CUDA-based genetic algorithm approximates the roots correctly.
|
|
97
|
+
This test implicitly verifies that the CUDA kernel is functioning.
|
|
98
|
+
It will be skipped automatically if CuPy is not available.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
ga_opts = GA_Options(num_of_generations=25, data_size=50000)
|
|
102
|
+
|
|
103
|
+
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=True)
|
|
104
|
+
|
|
105
|
+
expected_roots = np.array([-1.0, 2.5])
|
|
106
|
+
|
|
107
|
+
# Verify that the CUDA implementation also finds the correct roots within tolerance.
|
|
108
|
+
assert np.any(np.isclose(roots, expected_roots[0], atol=1e-2))
|
|
109
|
+
assert np.any(np.isclose(roots, expected_roots[1], atol=1e-2))
|
|
110
|
+
|