Compare commits
30 Commits
78970c8288
...
main
Author | SHA1 | Date | |
---|---|---|---|
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 |
29
.all-contributorsrc
Normal file
29
.all-contributorsrc
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"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": [
|
||||
"code",
|
||||
"doc",
|
||||
"infra"
|
||||
]
|
||||
}
|
||||
],
|
||||
"commitType": "docs"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
@ -8,23 +8,26 @@ First off, thank you for considering contributing! We welcome any contributions,
|
||||
* [Suggesting Enhancements](#suggesting-enhancements)
|
||||
* [Setting Up the Development Environment](#setting-up-the-development-environment)
|
||||
* [Running Tests](#running-tests)
|
||||
* [Our CI Process & The Gitea Bridge](#our-ci-process--the-gitea-bridge)
|
||||
* [Pull Request Process](#pull-request-process)
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
1. **Fork the repository** on Gitea.
|
||||
1. **Fork the repository** on GitHub.
|
||||
|
||||
2. **Clone your fork** locally:
|
||||
```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
|
||||
```
|
||||
|
||||
@ -58,34 +61,33 @@ We use `pytest` for automated testing. After setting up the development environm
|
||||
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.
|
||||
|
||||
Our CI environment is the ultimate source of truth. It is built using a custom Docker image to lock down dependencies and guarantee reproducibility.
|
||||
Your pull request must pass all these checks before it can be merged.
|
||||
|
||||
### Reference Environment Specification
|
||||
|
||||
You can replicate our CI environment exactly to minimize "it works on my machine" issues.
|
||||
* **Base OS:** Ubuntu 24.04
|
||||
* **CUDA Toolkit:** 12.5.1
|
||||
* **Base Docker Image:** `nvidia/cuda:12.5.1-devel-ubuntu24.04`
|
||||
* **Node.js Version:** 20.x
|
||||
* **Python Versions Tested:** 3.8, 3.10, 3.12
|
||||
|
||||
### 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:
|
||||
* **CI Docker Image:** You can pull the exact image used by our runners from Docker Hub:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
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
|
||||
```
|
||||
5. Commit your changes with a clear and descriptive commit message.
|
||||
6. Push your branch to your fork on Gitea.
|
||||
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.
|
||||
6. Push your branch to your fork on GitHub.
|
||||
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!
|
||||
|
49
README.md
49
README.md
@ -1,4 +1,6 @@
|
||||
# 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/)
|
||||
@ -9,7 +11,7 @@ A Python library for representing, manipulating, and solving polynomial equation
|
||||
|
||||
## Key Features
|
||||
|
||||
* **Create and Manipulate Polynomials**: Easily define polynomials of any degree and perform arithmetic operations like addition, subtraction, and scaling.
|
||||
* **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.
|
||||
* **Genetic Algorithm Solver**: Find approximate real roots for complex polynomials where analytical solutions are difficult or impossible.
|
||||
* **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
|
||||
* **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
|
||||
@ -44,6 +46,7 @@ Here is a simple example of how to define a quadratic function, find its propert
|
||||
from polysolve import Function, GA_Options, quadratic_solve
|
||||
|
||||
# 1. Define the function f(x) = 2x^2 - 3x - 5
|
||||
# Coefficients can be integers or floats.
|
||||
f1 = Function(largest_exponent=2)
|
||||
f1.set_constants([2, -3, -5])
|
||||
|
||||
@ -56,17 +59,22 @@ print(f"Value of f1 at x=5 is: {y_val}")
|
||||
# > Value of f1 at x=5 is: 30.0
|
||||
|
||||
# 3. Get the derivative: 4x - 3
|
||||
df1 = f1.differential()
|
||||
df1 = f1.derivative()
|
||||
print(f"Derivative of f1: {df1}")
|
||||
# > 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.
|
||||
roots_analytic = quadratic_solve(f1)
|
||||
print(f"Analytic roots: {sorted(roots_analytic)}")
|
||||
# > Analytic roots: [-1.0, 2.5]
|
||||
|
||||
# 5. Find roots with the genetic algorithm (CPU)
|
||||
# 6. Find roots with the genetic algorithm (CPU)
|
||||
# This can solve polynomials of any degree.
|
||||
ga_opts = GA_Options(num_of_generations=20)
|
||||
roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
|
||||
@ -89,10 +97,41 @@ While the code may work on other configurations, all contributions must pass the
|
||||
|
||||
## 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.
|
||||
|
||||
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="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
|
||||
|
||||
This project is licensed under the MIT License - see the `LICENSE` file for details.
|
||||
|
@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
||||
[project]
|
||||
# --- Core Metadata ---
|
||||
name = "polysolve"
|
||||
version = "0.1.0"
|
||||
version = "0.3.2"
|
||||
authors = [
|
||||
{ name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
|
||||
]
|
||||
@ -42,5 +42,7 @@ cuda12 = ["cupy-cuda12x"]
|
||||
dev = ["pytest"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://gitea.jono-rams.work/jono/PolySolve"
|
||||
"Bug Tracker" = "https://gitea.jono-rams.work/jono/PolySolve/issues"
|
||||
Homepage = "https://polysolve.jono-rams.work"
|
||||
Documentation = "https://polysolve.jono-rams.work/docs"
|
||||
Repository = "https://github.com/jono-rams/PolySolve"
|
||||
"Bug Tracker" = "https://github.com/jono-rams/PolySolve/issues"
|
||||
|
@ -1,7 +1,7 @@
|
||||
import math
|
||||
import numpy as np
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Union
|
||||
import warnings
|
||||
|
||||
# Attempt to import CuPy for CUDA acceleration.
|
||||
@ -12,8 +12,32 @@ try:
|
||||
except ImportError:
|
||||
_CUPY_AVAILABLE = False
|
||||
|
||||
# The CUDA kernel for the fitness function
|
||||
_FITNESS_KERNEL = """
|
||||
# The CUDA kernels for the fitness function
|
||||
_FITNESS_KERNEL_FLOAT = """
|
||||
extern "C" __global__ void fitness_kernel(
|
||||
const double* coefficients,
|
||||
int num_coefficients,
|
||||
const double* x_vals,
|
||||
double* ranks,
|
||||
int size,
|
||||
double y_val)
|
||||
{
|
||||
int idx = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
if (idx < size)
|
||||
{
|
||||
double ans = 0;
|
||||
int lrgst_expo = num_coefficients - 1;
|
||||
for (int i = 0; i < num_coefficients; ++i)
|
||||
{
|
||||
ans += coefficients[i] * pow(x_vals[idx], (double)(lrgst_expo - i));
|
||||
}
|
||||
|
||||
ans -= y_val;
|
||||
ranks[idx] = (ans == 0) ? 1.7976931348623157e+308 : fabs(1.0 / ans);
|
||||
}
|
||||
}
|
||||
"""
|
||||
_FITNESS_KERNEL_INT = """
|
||||
extern "C" __global__ void fitness_kernel(
|
||||
const long long* coefficients,
|
||||
int num_coefficients,
|
||||
@ -38,6 +62,7 @@ extern "C" __global__ void fitness_kernel(
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class GA_Options:
|
||||
"""
|
||||
@ -76,13 +101,14 @@ class Function:
|
||||
self.coefficients: Optional[np.ndarray] = None
|
||||
self._initialized = False
|
||||
|
||||
def set_coeffs(self, coefficients: List[int]):
|
||||
def set_coeffs(self, coefficients: List[Union[int, float]]):
|
||||
"""
|
||||
Sets the coefficients of the polynomial.
|
||||
|
||||
Args:
|
||||
coefficients (List[int]): A list of integer coefficients. The list size
|
||||
must be largest_exponent + 1.
|
||||
coefficients (List[Union[int, float]]): A list of integer or float
|
||||
coefficients. The list size
|
||||
must be largest_exponent + 1.
|
||||
|
||||
Raises:
|
||||
ValueError: If the input is invalid.
|
||||
@ -96,7 +122,13 @@ class Function:
|
||||
if coefficients[0] == 0 and self._largest_exponent > 0:
|
||||
raise ValueError("The first constant (for the largest exponent) cannot be 0.")
|
||||
|
||||
self.coefficients = np.array(coefficients, dtype=np.int64)
|
||||
# Check if any coefficient is a float
|
||||
is_float = any(isinstance(c, float) for c in coefficients)
|
||||
|
||||
# Choose the dtype based on the input
|
||||
target_dtype = np.float64 if is_float else np.int64
|
||||
|
||||
self.coefficients = np.array(coefficients, dtype=target_dtype)
|
||||
self._initialized = True
|
||||
|
||||
def _check_initialized(self):
|
||||
@ -109,6 +141,11 @@ class Function:
|
||||
"""Returns the largest exponent of the function."""
|
||||
return self._largest_exponent
|
||||
|
||||
@property
|
||||
def degree(self) -> int:
|
||||
"""Returns the largest exponent of the function."""
|
||||
return self._largest_exponent
|
||||
|
||||
def solve_y(self, x_val: float) -> float:
|
||||
"""
|
||||
Solves for y given an x value. (i.e., evaluates the polynomial at x).
|
||||
@ -126,6 +163,26 @@ class Function:
|
||||
"""
|
||||
Calculates the derivative of the function.
|
||||
|
||||
Returns:
|
||||
Function: A new Function object representing the derivative.
|
||||
"""
|
||||
warnings.warn(
|
||||
"The 'differential' function has been renamed. Please use 'derivative' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
|
||||
self._check_initialized()
|
||||
if self._largest_exponent == 0:
|
||||
raise ValueError("Cannot differentiate a constant (Function of degree 0).")
|
||||
|
||||
return self.derivitive()
|
||||
|
||||
|
||||
def derivative(self) -> 'Function':
|
||||
"""
|
||||
Calculates the derivative of the function.
|
||||
|
||||
Returns:
|
||||
Function: A new Function object representing the derivative.
|
||||
"""
|
||||
@ -139,6 +196,37 @@ class Function:
|
||||
diff_func.set_coeffs(derivative_coefficients.tolist())
|
||||
return diff_func
|
||||
|
||||
|
||||
def nth_derivative(self, n: int) -> 'Function':
|
||||
"""
|
||||
Calculates the nth derivative of the function.
|
||||
|
||||
Args:
|
||||
n (int): The order of the derivative to calculate.
|
||||
|
||||
Returns:
|
||||
Function: A new Function object representing the nth derivative.
|
||||
"""
|
||||
self._check_initialized()
|
||||
|
||||
if not isinstance(n, int) or n < 1:
|
||||
raise ValueError("Derivative order 'n' must be a positive integer.")
|
||||
|
||||
if n > self.largest_exponent:
|
||||
function = Function(0)
|
||||
function.set_coeffs([0])
|
||||
return function
|
||||
|
||||
if n == 1:
|
||||
return self.derivative()
|
||||
|
||||
function = self
|
||||
for _ in range(n):
|
||||
function = function.derivative()
|
||||
|
||||
return function
|
||||
|
||||
|
||||
def get_real_roots(self, options: GA_Options = GA_Options(), use_cuda: bool = False) -> np.ndarray:
|
||||
"""
|
||||
Uses a genetic algorithm to find the approximate real roots of the function (where y=0).
|
||||
@ -219,11 +307,16 @@ class Function:
|
||||
|
||||
def _solve_x_cuda(self, y_val: float, options: GA_Options) -> np.ndarray:
|
||||
"""Genetic algorithm implementation using CuPy (GPU/CUDA)."""
|
||||
# Load the raw CUDA kernel
|
||||
fitness_gpu = cupy.RawKernel(_FITNESS_KERNEL, 'fitness_kernel')
|
||||
|
||||
# Move coefficients to GPU
|
||||
d_coefficients = cupy.array(self.coefficients, dtype=cupy.int64)
|
||||
# Check the dtype of our coefficients array
|
||||
if self.coefficients.dtype == np.float64:
|
||||
fitness_gpu = cupy.RawKernel(_FITNESS_KERNEL_FLOAT, 'fitness_kernel')
|
||||
d_coefficients = cupy.array(self.coefficients, dtype=cupy.float64)
|
||||
elif self.coefficients.dtype == np.int64:
|
||||
fitness_gpu = cupy.RawKernel(_FITNESS_KERNEL_INT, 'fitness_kernel')
|
||||
d_coefficients = cupy.array(self.coefficients, dtype=cupy.int64)
|
||||
else:
|
||||
raise TypeError(f"Unsupported dtype for CUDA solver: {self.coefficients.dtype}")
|
||||
|
||||
# Create initial random solutions on the GPU
|
||||
d_solutions = cupy.random.uniform(
|
||||
@ -281,12 +374,16 @@ class Function:
|
||||
power = self._largest_exponent - i
|
||||
|
||||
# Coefficient part
|
||||
if c == 1 and power != 0:
|
||||
coeff_val = c
|
||||
if c == int(c):
|
||||
coeff_val = int(c)
|
||||
|
||||
if coeff_val == 1 and power != 0:
|
||||
coeff = ""
|
||||
elif c == -1 and power != 0:
|
||||
elif coeff_val == -1 and power != 0:
|
||||
coeff = "-"
|
||||
else:
|
||||
coeff = str(c)
|
||||
coeff = str(coeff_val)
|
||||
|
||||
# Variable part
|
||||
if power == 0:
|
||||
@ -300,7 +397,7 @@ class Function:
|
||||
sign = ""
|
||||
if i > 0:
|
||||
sign = " + " if c > 0 else " - "
|
||||
coeff = str(abs(c))
|
||||
coeff = str(abs(coeff_val))
|
||||
if abs(c) == 1 and power != 0:
|
||||
coeff = "" # Don't show 1 for non-constant terms
|
||||
|
||||
@ -337,13 +434,14 @@ class Function:
|
||||
result_func.set_coeffs(new_coefficients.tolist())
|
||||
return result_func
|
||||
|
||||
def __mul__(self, scalar: int) -> 'Function':
|
||||
"""Multiplies the function by a scalar constant."""
|
||||
self._check_initialized()
|
||||
if not isinstance(scalar, (int, float)):
|
||||
return NotImplemented
|
||||
def _multiply_by_scalar(self, scalar: Union[int, float]) -> 'Function':
|
||||
"""Helper method to multiply the function by a scalar constant."""
|
||||
self._check_initialized() # It's good practice to check here too
|
||||
|
||||
if scalar == 0:
|
||||
raise ValueError("Cannot multiply a function by 0.")
|
||||
result_func = Function(0)
|
||||
result_func.set_coeffs([0])
|
||||
return result_func
|
||||
|
||||
new_coefficients = self.coefficients * scalar
|
||||
|
||||
@ -351,19 +449,39 @@ class Function:
|
||||
result_func.set_coeffs(new_coefficients.tolist())
|
||||
return result_func
|
||||
|
||||
def __rmul__(self, scalar: int) -> 'Function':
|
||||
def _multiply_by_function(self, other: 'Function') -> 'Function':
|
||||
"""Helper method for polynomial multiplication (Function * Function)."""
|
||||
self._check_initialized()
|
||||
other._check_initialized()
|
||||
|
||||
# np.polymul performs convolution of coefficients to multiply polynomials
|
||||
new_coefficients = np.polymul(self.coefficients, other.coefficients)
|
||||
|
||||
# The degree of the resulting polynomial is derived from the new coefficients
|
||||
new_degree = len(new_coefficients) - 1
|
||||
|
||||
result_func = Function(new_degree)
|
||||
result_func.set_coeffs(new_coefficients.tolist())
|
||||
return result_func
|
||||
|
||||
def __mul__(self, other: Union['Function', int, float]) -> 'Function':
|
||||
"""Multiplies the function by a scalar constant."""
|
||||
if isinstance(other, (int, float)):
|
||||
return self._multiply_by_scalar(other)
|
||||
elif isinstance(other, self.__class__):
|
||||
return self._multiply_by_function(other)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def __rmul__(self, scalar: Union[int, float]) -> 'Function':
|
||||
"""Handles scalar multiplication from the right (e.g., 3 * func)."""
|
||||
|
||||
return self.__mul__(scalar)
|
||||
|
||||
def __imul__(self, scalar: int) -> 'Function':
|
||||
def __imul__(self, other: Union['Function', int, float]) -> 'Function':
|
||||
"""Performs in-place multiplication by a scalar (func *= 3)."""
|
||||
self._check_initialized()
|
||||
if not isinstance(scalar, (int, float)):
|
||||
return NotImplemented
|
||||
if scalar == 0:
|
||||
raise ValueError("Cannot multiply a function by 0.")
|
||||
|
||||
self.coefficients *= scalar
|
||||
self.coefficients *= other
|
||||
return self
|
||||
|
||||
|
||||
@ -408,9 +526,13 @@ if __name__ == '__main__':
|
||||
print(f"Value of f1 at x=5 is: {y}") # Expected: 2*(25) - 3*(5) - 5 = 50 - 15 - 5 = 30
|
||||
|
||||
# Find the derivative: 4x - 3
|
||||
df1 = f1.differential()
|
||||
df1 = f1.derivative()
|
||||
print(f"Derivative of f1: {df1}")
|
||||
|
||||
# Find the second derivative: 4
|
||||
ddf1 = f1.nth_derivative(2)
|
||||
print(f"Second derivative of f1: {ddf1}")
|
||||
|
||||
# --- Root Finding ---
|
||||
# 1. Analytical solution for quadratic
|
||||
roots_analytic = quadratic_solve(f1)
|
||||
@ -449,3 +571,17 @@ if __name__ == '__main__':
|
||||
# Multiplication: (x + 10) * 3 = 3x + 30
|
||||
f_mul = f2 * 3
|
||||
print(f"f2 * 3 = {f_mul}")
|
||||
|
||||
# f3 represents 2x^2 + 3x + 1
|
||||
f3 = Function(2)
|
||||
f3.set_coeffs([2, 3, 1])
|
||||
print(f"Function f3: {f3}")
|
||||
|
||||
# f4 represents 5x - 4
|
||||
f4 = Function(1)
|
||||
f4.set_coeffs([5, -4])
|
||||
print(f"Function f4: {f4}")
|
||||
|
||||
# Multiply the two functions
|
||||
product_func = f3 * f4
|
||||
print(f"f3 * f4 = {product_func}")
|
||||
|
@ -24,6 +24,18 @@ def linear_func() -> Function:
|
||||
f.set_coeffs([1, 10])
|
||||
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
|
||||
|
||||
# --- Core Functionality Tests ---
|
||||
|
||||
def test_solve_y(quadratic_func):
|
||||
@ -32,13 +44,20 @@ def test_solve_y(quadratic_func):
|
||||
assert quadratic_func.solve_y(0) == -5.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."""
|
||||
derivative = quadratic_func.differential()
|
||||
derivative = quadratic_func.derivative()
|
||||
assert derivative.largest_exponent == 1
|
||||
# The derivative of 2x^2 - 3x - 5 is 4x - 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):
|
||||
"""Tests the analytical quadratic solver for exact roots."""
|
||||
roots = quadratic_solve(quadratic_func)
|
||||
@ -61,13 +80,20 @@ def test_subtraction(quadratic_func, linear_func):
|
||||
assert result.largest_exponent == 2
|
||||
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."""
|
||||
# (x + 10) * 3 = 3x + 30
|
||||
result = linear_func * 3
|
||||
assert result.largest_exponent == 1
|
||||
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])
|
||||
|
||||
# --- Genetic Algorithm Root-Finding Tests ---
|
||||
|
||||
def test_get_real_roots_numpy(quadratic_func):
|
||||
|
Reference in New Issue
Block a user