Compare commits
48 Commits
78970c8288
...
v0.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
| b87ea5011c | |||
| 117e43a984 | |||
| b415df2983 | |||
| 602269889b | |||
| dca1d66346 | |||
| 1aa2e8875a | |||
| 94723dcb88 | |||
| f4c5d245e4 | |||
| b7ea6c2e23 | |||
| 9d967210fa | |||
| 1318006959 | |||
| 2d8c8a09e3 | |||
| 4e46c11f83 | |||
| 962eab5af7 | |||
| 7c75000637 | |||
| c3b3513e79 | |||
| 0536003dce | |||
| bb89149930 | |||
| 6596c2df99 | |||
| 24337cea48 | |||
| ee18cc9e59 | |||
| ce464cffd4 | |||
| c94d08498d | |||
| 3aad9efb61 | |||
| 32d6cfeeea | |||
| 8d6fe7aca0 | |||
| 7927845f17 | |||
| ac591f49ec | |||
| ec97aefee1 | |||
| d27497488f | |||
| 41daf4f7e0 | |||
| 36f51ca67e | |||
| 25f20a4db2 | |||
| ee414ea0dc | |||
| 8656b558b4 | |||
| 30a5189928 | |||
| 3d2c724ad4 | |||
| a761efe28e | |||
|
|
1165c03955 | ||
|
|
0a36e955a1 | ||
| c896ecaff8 | |||
| b7073287e5 | |||
| 9c47db4e6a | |||
| 97e4259bfa | |||
|
|
a4b947ee07 | ||
| 8e8462d9d8 | |||
| a4d9f657fe | |||
|
|
83f565c08c |
30
.all-contributorsrc
Normal file
30
.all-contributorsrc
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"projectName": "PolySolve",
|
||||||
|
"projectOwner": "jono-rams",
|
||||||
|
"repoType": "github",
|
||||||
|
"repoHost": "https://github.com",
|
||||||
|
"files": [
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"imageSize": 100,
|
||||||
|
"commit": false,
|
||||||
|
"commitConvention": "angular",
|
||||||
|
"contributorsPerLine": 7,
|
||||||
|
"linkToUsage": true,
|
||||||
|
"skipCi": true,
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"login": "jono-rams",
|
||||||
|
"name": "Jonathan Rampersad",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/29872001?v=4",
|
||||||
|
"profile": "https://jono-rams.work",
|
||||||
|
"contributions": [
|
||||||
|
"maintenance",
|
||||||
|
"code",
|
||||||
|
"doc",
|
||||||
|
"infra"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commitType": "docs"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Contributing to polysolve
|
# Contributing to polysolve
|
||||||
|
|
||||||
First off, thank you for considering contributing! We welcome any contributions, from fixing a typo to implementing a new feature.
|
First off, thank you for considering contributing! We welcome any contributions, from fixing a typo to implementing a new feature. You can see a list of everyone who has contributed in our [Contributors Hall of Fame](/.github/CONTRIBUTORS.md).
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@@ -8,23 +8,26 @@ First off, thank you for considering contributing! We welcome any contributions,
|
|||||||
* [Suggesting Enhancements](#suggesting-enhancements)
|
* [Suggesting Enhancements](#suggesting-enhancements)
|
||||||
* [Setting Up the Development Environment](#setting-up-the-development-environment)
|
* [Setting Up the Development Environment](#setting-up-the-development-environment)
|
||||||
* [Running Tests](#running-tests)
|
* [Running Tests](#running-tests)
|
||||||
|
* [Our CI Process & The Gitea Bridge](#our-ci-process--the-gitea-bridge)
|
||||||
* [Pull Request Process](#pull-request-process)
|
* [Pull Request Process](#pull-request-process)
|
||||||
|
|
||||||
## Reporting Bugs
|
## Reporting Bugs
|
||||||
|
|
||||||
If you find a bug, please open an issue on our Gitea issue tracker. Please include as many details as possible, such as your OS, Python version, steps to reproduce, and any error messages.
|
If you find a bug, please open an issue on our GitHub issue tracker. Please include as many details as possible, such as your OS, Python version, steps to reproduce, and any error messages.
|
||||||
|
[Report a Bug](https://github.com/jono-rams/PolySolve/issues/new?assignees=&labels=bug&template=bug_report.md&title=)
|
||||||
|
|
||||||
## Suggesting Enhancements
|
## Suggesting Enhancements
|
||||||
|
|
||||||
If you have an idea for a new feature or an improvement, please open an issue to discuss it. This allows us to coordinate efforts and ensure the proposed change aligns with the project's goals.
|
If you have an idea for a new feature or an improvement, please open an issue to discuss it. This allows us to coordinate efforts and ensure the proposed change aligns with the project's goals.
|
||||||
|
[Suggest an Enhancement](https://github.com/jono-rams/PolySolve/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)
|
||||||
|
|
||||||
## Setting Up the Development Environment
|
## Setting Up the Development Environment
|
||||||
|
|
||||||
1. **Fork the repository** on Gitea.
|
1. **Fork the repository** on GitHub.
|
||||||
|
|
||||||
2. **Clone your fork** locally:
|
2. **Clone your fork** locally:
|
||||||
```bash
|
```bash
|
||||||
git clone [https://gitea.example.com/YourUsername/PolySolve.git](https://gitea.example.com/YourUsername/PolySolve.git)
|
git clone git clone https://github.com/YourUsername/PolySolve.git
|
||||||
cd PolySolve
|
cd PolySolve
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -58,34 +61,33 @@ We use `pytest` for automated testing. After setting up the development environm
|
|||||||
pytest
|
pytest
|
||||||
```
|
```
|
||||||
|
|
||||||
This will automatically discover and run all tests located in the `tests/` directory.
|
This will automatically discover and run all tests located in the `tests/` directory. All tests should pass before you submit your changes.
|
||||||
|
If you are adding a new feature or fixing a bug, please add a corresponding test case to ensure the code is working correctly and to prevent future regressions.
|
||||||
|
|
||||||
All tests should pass before you submit your changes. If you are adding a new feature or fixing a bug, please add a corresponding test case to ensure the code is working correctly and to prevent future regressions.
|
## Our CI Process & The Gitea Bridge
|
||||||
|
|
||||||
## CI & Automated Testing Environment
|
To ensure that all contributions are consistent and stable, our test suite is executed automatically in a controlled environment. Here’s how it works:
|
||||||
|
1. Our canonical source of truth and CI/CD runners are managed on our private Gitea instance.
|
||||||
|
2. When you open a Pull Request on GitHub, our "Gitea Bridge" bot automatically mirrors your changes to a corresponding PR on our Gitea instance.
|
||||||
|
3. The tests are run using Gitea Actions within our specific, reproducible environment.
|
||||||
|
4. The results (success or failure) are then reported back to your GitHub Pull Request via a status check named "Gitea CI Bridge".
|
||||||
|
|
||||||
To ensure that all contributions are consistent and stable, our test suite is executed automatically via Gitea Actions. Your pull request must pass all these checks before it can be merged.
|
Your pull request must pass all these checks before it can be merged.
|
||||||
|
|
||||||
Our CI environment is the ultimate source of truth. It is built using a custom Docker image to lock down dependencies and guarantee reproducibility.
|
|
||||||
|
|
||||||
### Reference Environment Specification
|
### Reference Environment Specification
|
||||||
|
|
||||||
|
You can replicate our CI environment exactly to minimize "it works on my machine" issues.
|
||||||
* **Base OS:** Ubuntu 24.04
|
* **Base OS:** Ubuntu 24.04
|
||||||
* **CUDA Toolkit:** 12.5.1
|
* **CUDA Toolkit:** 12.5.1
|
||||||
* **Base Docker Image:** `nvidia/cuda:12.5.1-devel-ubuntu24.04`
|
* **Base Docker Image:** `nvidia/cuda:12.5.1-devel-ubuntu24.04`
|
||||||
* **Node.js Version:** 20.x
|
* **Node.js Version:** 20.x
|
||||||
* **Python Versions Tested:** 3.8, 3.10, 3.12
|
* **Python Versions Tested:** 3.8, 3.10, 3.12
|
||||||
|
* **CI Docker Image:** You can pull the exact image used by our runners from Docker Hub:
|
||||||
### Reproducing the Environment
|
|
||||||
|
|
||||||
You can replicate our CI environment exactly by using the public Docker image we built for our runners. You can pull it from Docker Hub:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull c1ph3rd3v/gitea-runner-cuda-node:12.5.1-ubuntu24.04
|
docker pull c1ph3rd3v/gitea-runner-cuda-node:12.5.1-ubuntu24.04
|
||||||
```
|
```
|
||||||
|
|
||||||
Using this Docker image for your local development will ensure your code runs in the same environment as our automated tests, minimizing "it works on my machine" issues.
|
|
||||||
|
|
||||||
## Pull Request Process
|
## Pull Request Process
|
||||||
|
|
||||||
1. Create a new branch for your feature or bugfix from the `main` branch:
|
1. Create a new branch for your feature or bugfix from the `main` branch:
|
||||||
@@ -99,7 +101,7 @@ Using this Docker image for your local development will ensure your code runs in
|
|||||||
pytest
|
pytest
|
||||||
```
|
```
|
||||||
5. Commit your changes with a clear and descriptive commit message.
|
5. Commit your changes with a clear and descriptive commit message.
|
||||||
6. Push your branch to your fork on Gitea.
|
6. Push your branch to your fork on GitHub.
|
||||||
7. Open a pull request to the `main` branch of the original `PolySolve` repository. Please provide a clear title and description for your pull request.
|
7. Open a **Pull Request** to the `main` branch of the `jono-rams/PolySolve` repository on **GitHub**. Please provide a clear title and description for your pull request.
|
||||||
|
|
||||||
Once you submit your pull request, our automated CI tests will run. We will review your contribution and provide feedback as soon as possible. Thank you for your contribution!
|
Once you submit your pull request, our automated CI tests will run. We will review your contribution and provide feedback as soon as possible. Thank you for your contribution!
|
||||||
|
|||||||
BIN
PolySolve_Technical_Paper.pdf
Normal file
BIN
PolySolve_Technical_Paper.pdf
Normal file
Binary file not shown.
97
README.md
97
README.md
@@ -1,17 +1,20 @@
|
|||||||
# polysolve
|
<p align="center">
|
||||||
|
<img src="https://i.ibb.co/N22Gx6xq/Poly-Solve-Logo.png" alt="polysolve Logo" width="256">
|
||||||
|
</p>
|
||||||
|
|
||||||
[](https://pypi.org/project/polysolve/)
|
[](https://pypi.org/project/polysolve/)
|
||||||
[](https://pypi.org/project/polysolve/)
|
[](https://pypi.org/project/polysolve/)
|
||||||
|
|
||||||
A Python library for representing, manipulating, and solving polynomial equations using a high-performance genetic algorithm, with optional CUDA/GPU acceleration.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
* **Create and Manipulate Polynomials**: Easily define polynomials of any degree and perform arithmetic operations like addition, subtraction, and scaling.
|
* **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.
|
||||||
* **Genetic Algorithm Solver**: Find approximate real roots for complex polynomials where analytical solutions are difficult or impossible.
|
* **Numba Accelerated CPU Solver**: The default genetic algorithm is JIT-compiled with Numba for high-speed CPU performance, right out of the box.
|
||||||
* **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
|
* **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
|
||||||
|
* **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.
|
||||||
* **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
|
* **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
|
||||||
* **Simple API**: Designed to be intuitive and easy to integrate into any project.
|
* **Simple API**: Designed to be intuitive and easy to integrate into any project.
|
||||||
|
|
||||||
@@ -41,11 +44,12 @@ pip install polysolve[cuda12]
|
|||||||
Here is a simple example of how to define a quadratic function, find its properties, and solve for its roots.
|
Here is a simple example of how to define a quadratic function, find its properties, and solve for its roots.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from polysolve import Function, GA_Options, quadratic_solve
|
from polysolve import Function, GA_Options
|
||||||
|
|
||||||
# 1. Define the function f(x) = 2x^2 - 3x - 5
|
# 1. Define the function f(x) = 2x^2 - 3x - 5
|
||||||
|
# Coefficients can be integers or floats.
|
||||||
f1 = Function(largest_exponent=2)
|
f1 = Function(largest_exponent=2)
|
||||||
f1.set_constants([2, -3, -5])
|
f1.set_coeffs([2, -3, -5])
|
||||||
|
|
||||||
print(f"Function f1: {f1}")
|
print(f"Function f1: {f1}")
|
||||||
# > Function f1: 2x^2 - 3x - 5
|
# > Function f1: 2x^2 - 3x - 5
|
||||||
@@ -56,18 +60,23 @@ print(f"Value of f1 at x=5 is: {y_val}")
|
|||||||
# > Value of f1 at x=5 is: 30.0
|
# > Value of f1 at x=5 is: 30.0
|
||||||
|
|
||||||
# 3. Get the derivative: 4x - 3
|
# 3. Get the derivative: 4x - 3
|
||||||
df1 = f1.differential()
|
df1 = f1.derivative()
|
||||||
print(f"Derivative of f1: {df1}")
|
print(f"Derivative of f1: {df1}")
|
||||||
# > Derivative of f1: 4x - 3
|
# > Derivative of f1: 4x - 3
|
||||||
|
|
||||||
# 4. Find roots analytically using the quadratic formula
|
# 4. Get the 2nd derivative: 4
|
||||||
|
ddf1 = f1.nth_derivative(2)
|
||||||
|
print(f"2nd Derivative of f1: {ddf1}")
|
||||||
|
# > Derivative of f1: 4
|
||||||
|
|
||||||
|
# 5. Find roots analytically using the quadratic formula
|
||||||
# This is exact and fast for degree-2 polynomials.
|
# This is exact and fast for degree-2 polynomials.
|
||||||
roots_analytic = quadratic_solve(f1)
|
roots_analytic = f1.quadratic_solve()
|
||||||
print(f"Analytic roots: {sorted(roots_analytic)}")
|
print(f"Analytic roots: {sorted(roots_analytic)}")
|
||||||
# > Analytic roots: [-1.0, 2.5]
|
# > Analytic roots: [-1.0, 2.5]
|
||||||
|
|
||||||
# 5. Find roots with the genetic algorithm (CPU)
|
# 6. Find roots with the genetic algorithm (Numba CPU)
|
||||||
# This can solve polynomials of any degree.
|
# This is the default, JIT-compiled CPU solver.
|
||||||
ga_opts = GA_Options(num_of_generations=20)
|
ga_opts = GA_Options(num_of_generations=20)
|
||||||
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
||||||
print(f"Approximate roots from GA: {roots_ga[:2]}")
|
print(f"Approximate roots from GA: {roots_ga[:2]}")
|
||||||
@@ -81,6 +90,41 @@ print(f"Approximate roots from GA: {roots_ga[:2]}")
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Tuning the Genetic Algorithm
|
||||||
|
|
||||||
|
The `GA_Options` class gives you fine-grained control over the genetic algorithm's performance, letting you trade speed for accuracy.
|
||||||
|
|
||||||
|
The default options are balanced, but for very complex polynomials, you may want a more exhaustive search.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from polysolve import GA_Options
|
||||||
|
|
||||||
|
# Create a config for a deep search, optimized for finding
|
||||||
|
# *all* real roots (even if they are far apart).
|
||||||
|
ga_robust_search = GA_Options(
|
||||||
|
num_of_generations=50, # Run for more generations
|
||||||
|
data_size=500000, # Use a larger population
|
||||||
|
|
||||||
|
# --- Key Tuning Parameters for Multi-Root Finding ---
|
||||||
|
|
||||||
|
# Widen the parent pool to 75% to keep more "niches"
|
||||||
|
# (solution-clouds around different roots) alive.
|
||||||
|
selection_percentile=0.75,
|
||||||
|
|
||||||
|
# Increase the crossover blend factor to 0.75.
|
||||||
|
# This allows new solutions to be created further
|
||||||
|
# away from their parents, increasing exploration.
|
||||||
|
blend_alpha=0.75
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pass the custom options to the solver
|
||||||
|
roots = f1.get_real_roots(ga_accurate)
|
||||||
|
```
|
||||||
|
|
||||||
|
For a full breakdown of all parameters, including crossover_ratio, mutation_strength, and more, please see [the full GA_Options API Documentation](https://polysolve.jono-rams.work/docs/ga-options-api).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Development & Testing Environment
|
## Development & Testing Environment
|
||||||
|
|
||||||
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**.
|
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**.
|
||||||
@@ -89,10 +133,41 @@ While the code may work on other configurations, all contributions must pass the
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
[](http://makeapullrequest.com)
|
||||||
|
[](https://github.com/jono-rams/PolySolve/issues)
|
||||||
|
[](https://github.com/jono-rams/PolySolve/pulls)
|
||||||
|
|
||||||
Contributions are welcome! Whether it's a bug report, a feature request, or a pull request, please feel free to get involved.
|
Contributions are welcome! Whether it's a bug report, a feature request, or a pull request, please feel free to get involved.
|
||||||
|
|
||||||
Please read our `CONTRIBUTING.md` file for details on our code of conduct and the process for submitting pull requests.
|
Please read our `CONTRIBUTING.md` file for details on our code of conduct and the process for submitting pull requests.
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable -->
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://jono-rams.work"><img src="https://avatars.githubusercontent.com/u/29872001?v=4?s=100" width="100px;" alt="Jonathan Rampersad"/><br /><sub><b>Jonathan Rampersad</b></sub></a><br /><a href="https://github.com/jono-rams/PolySolve/commits?author=jono-rams" title="Maintenance">🚧</a> <a href="https://github.com/jono-rams/PolySolve/commits?author=jono-rams" title="Code">💻</a> <a href="https://github.com/jono-rams/PolySolve/commits?author=jono-rams" title="Documentation">📖</a> <a href="#infra-jono-rams" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td align="center" size="13px" colspan="7">
|
||||||
|
<img src="https://raw.githubusercontent.com/all-contributors/all-contributors-cli/1b8533af435da9854653492b1327a23a4dbd0a10/assets/logo-small.svg">
|
||||||
|
<a href="https://all-contributors.js.org/docs/en/bot/usage">Add your contributions</a>
|
||||||
|
</img>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- markdownlint-restore -->
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
[project]
|
[project]
|
||||||
# --- Core Metadata ---
|
# --- Core Metadata ---
|
||||||
name = "polysolve"
|
name = "polysolve"
|
||||||
version = "0.1.0"
|
version = "0.7.0"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
|
{ name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
|
||||||
]
|
]
|
||||||
@@ -33,7 +33,8 @@ classifiers = [
|
|||||||
|
|
||||||
# --- Dependencies ---
|
# --- Dependencies ---
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"numpy>=1.21"
|
"numpy>=1.21",
|
||||||
|
"numba"
|
||||||
]
|
]
|
||||||
|
|
||||||
# --- Optional Dependencies (Extras) ---
|
# --- Optional Dependencies (Extras) ---
|
||||||
@@ -42,5 +43,7 @@ cuda12 = ["cupy-cuda12x"]
|
|||||||
dev = ["pytest"]
|
dev = ["pytest"]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://gitea.jono-rams.work/jono/PolySolve"
|
Homepage = "https://polysolve.jono-rams.work"
|
||||||
"Bug Tracker" = "https://gitea.jono-rams.work/jono/PolySolve/issues"
|
Documentation = "https://polysolve.jono-rams.work/docs"
|
||||||
|
Repository = "https://github.com/jono-rams/PolySolve"
|
||||||
|
"Bug Tracker" = "https://github.com/jono-rams/PolySolve/issues"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import numpy.testing as npt
|
||||||
|
|
||||||
# Try to import cupy to check for CUDA availability
|
# Try to import cupy to check for CUDA availability
|
||||||
try:
|
try:
|
||||||
@@ -8,7 +9,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
_CUPY_AVAILABLE = False
|
_CUPY_AVAILABLE = False
|
||||||
|
|
||||||
from polysolve import Function, GA_Options, quadratic_solve
|
from polysolve import Function, GA_Options
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def quadratic_func() -> Function:
|
def quadratic_func() -> Function:
|
||||||
@@ -24,6 +25,29 @@ def linear_func() -> Function:
|
|||||||
f.set_coeffs([1, 10])
|
f.set_coeffs([1, 10])
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def m_func_1() -> Function:
|
||||||
|
f = Function(2)
|
||||||
|
f.set_coeffs([2, 3, 1])
|
||||||
|
return f
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def m_func_2() -> Function:
|
||||||
|
f = Function(1)
|
||||||
|
f.set_coeffs([5, -4])
|
||||||
|
return f
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def base_func():
|
||||||
|
f = Function(2)
|
||||||
|
f.set_coeffs([1, 2, 3])
|
||||||
|
return f
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def complex_func():
|
||||||
|
f = Function(2, [1, 2, 2])
|
||||||
|
return f
|
||||||
|
|
||||||
# --- Core Functionality Tests ---
|
# --- Core Functionality Tests ---
|
||||||
|
|
||||||
def test_solve_y(quadratic_func):
|
def test_solve_y(quadratic_func):
|
||||||
@@ -32,16 +56,23 @@ def test_solve_y(quadratic_func):
|
|||||||
assert quadratic_func.solve_y(0) == -5.0
|
assert quadratic_func.solve_y(0) == -5.0
|
||||||
assert quadratic_func.solve_y(-1) == 0.0
|
assert quadratic_func.solve_y(-1) == 0.0
|
||||||
|
|
||||||
def test_differential(quadratic_func):
|
def test_derivative(quadratic_func):
|
||||||
"""Tests the calculation of the function's derivative."""
|
"""Tests the calculation of the function's derivative."""
|
||||||
derivative = quadratic_func.differential()
|
derivative = quadratic_func.derivative()
|
||||||
assert derivative.largest_exponent == 1
|
assert derivative.largest_exponent == 1
|
||||||
# The derivative of 2x^2 - 3x - 5 is 4x - 3
|
# The derivative of 2x^2 - 3x - 5 is 4x - 3
|
||||||
assert np.array_equal(derivative.coefficients, [4, -3])
|
assert np.array_equal(derivative.coefficients, [4, -3])
|
||||||
|
|
||||||
|
def test_nth_derivative(quadratic_func):
|
||||||
|
"""Tests the calculation of the function's 2nd derivative."""
|
||||||
|
derivative = quadratic_func.nth_derivative(2)
|
||||||
|
assert derivative.largest_exponent == 0
|
||||||
|
# The derivative of 2x^2 - 3x - 5 is 4x - 3
|
||||||
|
assert np.array_equal(derivative.coefficients, [4])
|
||||||
|
|
||||||
def test_quadratic_solve(quadratic_func):
|
def test_quadratic_solve(quadratic_func):
|
||||||
"""Tests the analytical quadratic solver for exact roots."""
|
"""Tests the analytical quadratic solver for exact roots."""
|
||||||
roots = quadratic_solve(quadratic_func)
|
roots = quadratic_func.quadratic_solve()
|
||||||
# Sorting ensures consistent order for comparison
|
# Sorting ensures consistent order for comparison
|
||||||
assert sorted(roots) == [-1.0, 2.5]
|
assert sorted(roots) == [-1.0, 2.5]
|
||||||
|
|
||||||
@@ -61,13 +92,46 @@ def test_subtraction(quadratic_func, linear_func):
|
|||||||
assert result.largest_exponent == 2
|
assert result.largest_exponent == 2
|
||||||
assert np.array_equal(result.coefficients, [2, -4, -15])
|
assert np.array_equal(result.coefficients, [2, -4, -15])
|
||||||
|
|
||||||
def test_multiplication(linear_func):
|
def test_scalar_multiplication(linear_func):
|
||||||
"""Tests the multiplication of a Function object by a scalar."""
|
"""Tests the multiplication of a Function object by a scalar."""
|
||||||
# (x + 10) * 3 = 3x + 30
|
# (x + 10) * 3 = 3x + 30
|
||||||
result = linear_func * 3
|
result = linear_func * 3
|
||||||
assert result.largest_exponent == 1
|
assert result.largest_exponent == 1
|
||||||
assert np.array_equal(result.coefficients, [3, 30])
|
assert np.array_equal(result.coefficients, [3, 30])
|
||||||
|
|
||||||
|
def test_function_multiplication(m_func_1, m_func_2):
|
||||||
|
"""Tests the multiplication of two Function objects."""
|
||||||
|
# (2x^2 + 3x + 1) * (5x -4) = 10x^3 + 7x^2 - 7x -4
|
||||||
|
result = m_func_1 * m_func_2
|
||||||
|
assert result.largest_exponent == 3
|
||||||
|
assert np.array_equal(result.coefficients, [10, 7, -7, -4])
|
||||||
|
|
||||||
|
def test_equality(base_func):
|
||||||
|
"""Tests the __eq__ method for the Function class."""
|
||||||
|
|
||||||
|
# 1. Test for equality with a new, identical object
|
||||||
|
f_identical = Function(2)
|
||||||
|
f_identical.set_coeffs([1, 2, 3])
|
||||||
|
assert base_func == f_identical
|
||||||
|
|
||||||
|
# 2. Test for inequality (different coefficients)
|
||||||
|
f_different = Function(2)
|
||||||
|
f_different.set_coeffs([1, 9, 3])
|
||||||
|
assert base_func != f_different
|
||||||
|
|
||||||
|
# 3. Test for inequality (different degree)
|
||||||
|
f_diff_degree = Function(1)
|
||||||
|
f_diff_degree.set_coeffs([1, 2])
|
||||||
|
assert base_func != f_diff_degree
|
||||||
|
|
||||||
|
# 4. Test against a different type
|
||||||
|
assert base_func != "some_string"
|
||||||
|
assert base_func != 123
|
||||||
|
|
||||||
|
# 5. Test against an uninitialized Function
|
||||||
|
f_uninitialized = Function(2)
|
||||||
|
assert base_func != f_uninitialized
|
||||||
|
|
||||||
# --- Genetic Algorithm Root-Finding Tests ---
|
# --- Genetic Algorithm Root-Finding Tests ---
|
||||||
|
|
||||||
def test_get_real_roots_numpy(quadratic_func):
|
def test_get_real_roots_numpy(quadratic_func):
|
||||||
@@ -75,7 +139,7 @@ def test_get_real_roots_numpy(quadratic_func):
|
|||||||
Tests that the NumPy-based genetic algorithm approximates the roots correctly.
|
Tests that the NumPy-based genetic algorithm approximates the roots correctly.
|
||||||
"""
|
"""
|
||||||
# Using more generations for higher accuracy in testing
|
# Using more generations for higher accuracy in testing
|
||||||
ga_opts = GA_Options(num_of_generations=25, data_size=50000)
|
ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
|
||||||
|
|
||||||
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=False)
|
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=False)
|
||||||
|
|
||||||
@@ -83,11 +147,7 @@ def test_get_real_roots_numpy(quadratic_func):
|
|||||||
# We don't know which order they'll be in, so we check for presence.
|
# We don't know which order they'll be in, so we check for presence.
|
||||||
expected_roots = np.array([-1.0, 2.5])
|
expected_roots = np.array([-1.0, 2.5])
|
||||||
|
|
||||||
# Check that at least one found root is close to -1.0
|
npt.assert_allclose(np.sort(roots), np.sort(expected_roots), atol=1e-2)
|
||||||
assert np.any(np.isclose(roots, expected_roots[0], atol=1e-2))
|
|
||||||
|
|
||||||
# Check that at least one found root is close to 2.5
|
|
||||||
assert np.any(np.isclose(roots, expected_roots[1], atol=1e-2))
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not _CUPY_AVAILABLE, reason="CuPy is not installed, skipping CUDA test.")
|
@pytest.mark.skipif(not _CUPY_AVAILABLE, reason="CuPy is not installed, skipping CUDA test.")
|
||||||
@@ -98,13 +158,45 @@ def test_get_real_roots_cuda(quadratic_func):
|
|||||||
It will be skipped automatically if CuPy is not available.
|
It will be skipped automatically if CuPy is not available.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ga_opts = GA_Options(num_of_generations=25, data_size=50000)
|
ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
|
||||||
|
|
||||||
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=True)
|
roots = quadratic_func.get_real_roots(ga_opts, use_cuda=True)
|
||||||
|
|
||||||
expected_roots = np.array([-1.0, 2.5])
|
expected_roots = np.array([-1.0, 2.5])
|
||||||
|
|
||||||
# Verify that the CUDA implementation also finds the correct roots within tolerance.
|
# Verify that the CUDA implementation also finds the correct roots within tolerance.
|
||||||
assert np.any(np.isclose(roots, expected_roots[0], atol=1e-2))
|
npt.assert_allclose(np.sort(roots), np.sort(expected_roots), atol=1e-2)
|
||||||
assert np.any(np.isclose(roots, expected_roots[1], atol=1e-2))
|
|
||||||
|
def test_get_roots_numpy(complex_func):
|
||||||
|
"""
|
||||||
|
Tests that the NumPy-based genetic algorithm approximates the roots correctly.
|
||||||
|
"""
|
||||||
|
# Using more generations for higher accuracy in testing
|
||||||
|
ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
|
||||||
|
|
||||||
|
roots = complex_func.get_roots(ga_opts, use_cuda=False)
|
||||||
|
|
||||||
|
# Check if the algorithm found values close to the two known roots.
|
||||||
|
# We don't know which order they'll be in, so we check for presence.
|
||||||
|
expected_roots = np.array([-1.0-1.j, -1.0+1.j])
|
||||||
|
|
||||||
|
npt.assert_allclose(np.sort(roots), np.sort(expected_roots), atol=1e-2)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not _CUPY_AVAILABLE, reason="CuPy is not installed, skipping CUDA test.")
|
||||||
|
def test_get_roots_cuda(complex_func):
|
||||||
|
"""
|
||||||
|
Tests that the CUDA-based genetic algorithm approximates the roots correctly.
|
||||||
|
This test implicitly verifies that the CUDA kernel is functioning.
|
||||||
|
It will be skipped automatically if CuPy is not available.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
|
||||||
|
|
||||||
|
roots = complex_func.get_roots(ga_opts, use_cuda=True)
|
||||||
|
|
||||||
|
expected_roots = np.array([-1.0-1.j, -1+1.j])
|
||||||
|
|
||||||
|
# Verify that the CUDA implementation also finds the correct roots within tolerance.
|
||||||
|
npt.assert_allclose(np.sort(roots), np.sort(expected_roots), atol=1e-2)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user