v0.2.0 #10
@ -1,10 +0,0 @@
|
|||||||
## Contributors
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
[](#contributors)
|
|
||||||
<!-- markdownlint-restore -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
11
README.md
11
README.md
@ -56,17 +56,22 @@ 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
|
||||||
|
df1 = f1.nth_derivative(2)
|
||||||
|
print(f"2nd Derivative of f1: {df1}")
|
||||||
|
# > 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 = quadratic_solve(f1)
|
||||||
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 (CPU)
|
||||||
# This can solve polynomials of any degree.
|
# This can solve polynomials of any degree.
|
||||||
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)
|
||||||
|
@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
[project]
|
[project]
|
||||||
# --- Core Metadata ---
|
# --- Core Metadata ---
|
||||||
name = "polysolve"
|
name = "polysolve"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
|
{ name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
|
||||||
]
|
]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import math
|
import math
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List, Optional
|
from typing import List, Optional, Union
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
# Attempt to import CuPy for CUDA acceleration.
|
# Attempt to import CuPy for CUDA acceleration.
|
||||||
@ -108,6 +108,11 @@ class Function:
|
|||||||
def largest_exponent(self) -> int:
|
def largest_exponent(self) -> int:
|
||||||
"""Returns the largest exponent of the function."""
|
"""Returns the largest exponent of the function."""
|
||||||
return self._largest_exponent
|
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:
|
def solve_y(self, x_val: float) -> float:
|
||||||
"""
|
"""
|
||||||
@ -129,15 +134,66 @@ class Function:
|
|||||||
Returns:
|
Returns:
|
||||||
Function: A new Function object representing the derivative.
|
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()
|
self._check_initialized()
|
||||||
if self._largest_exponent == 0:
|
if self._largest_exponent == 0:
|
||||||
raise ValueError("Cannot differentiate a constant (Function of degree 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.
|
||||||
|
"""
|
||||||
|
self._check_initialized()
|
||||||
|
if self._largest_exponent == 0:
|
||||||
|
raise ValueError("Cannot differentiate a constant (Function of degree 0).")
|
||||||
|
|
||||||
derivative_coefficients = np.polyder(self.coefficients)
|
derivative_coefficients = np.polyder(self.coefficients)
|
||||||
|
|
||||||
diff_func = Function(self._largest_exponent - 1)
|
diff_func = Function(self._largest_exponent - 1)
|
||||||
diff_func.set_coeffs(derivative_coefficients.tolist())
|
diff_func.set_coeffs(derivative_coefficients.tolist())
|
||||||
return diff_func
|
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:
|
def get_real_roots(self, options: GA_Options = GA_Options(), use_cuda: bool = False) -> np.ndarray:
|
||||||
"""
|
"""
|
||||||
@ -336,34 +392,55 @@ class Function:
|
|||||||
result_func = Function(len(new_coefficients) - 1)
|
result_func = Function(len(new_coefficients) - 1)
|
||||||
result_func.set_coeffs(new_coefficients.tolist())
|
result_func.set_coeffs(new_coefficients.tolist())
|
||||||
return result_func
|
return result_func
|
||||||
|
|
||||||
def __mul__(self, scalar: int) -> 'Function':
|
def _multiply_by_scalar(self, scalar: Union[int, float]) -> 'Function':
|
||||||
"""Multiplies the function by a scalar constant."""
|
"""Helper method to multiply the function by a scalar constant."""
|
||||||
self._check_initialized()
|
self._check_initialized() # It's good practice to check here too
|
||||||
if not isinstance(scalar, (int, float)):
|
|
||||||
return NotImplemented
|
|
||||||
if scalar == 0:
|
|
||||||
raise ValueError("Cannot multiply a function by 0.")
|
|
||||||
|
|
||||||
|
if scalar == 0:
|
||||||
|
result_func = Function(0)
|
||||||
|
result_func.set_coeffs([0])
|
||||||
|
return result_func
|
||||||
|
|
||||||
new_coefficients = self.coefficients * scalar
|
new_coefficients = self.coefficients * scalar
|
||||||
|
|
||||||
result_func = Function(self._largest_exponent)
|
result_func = Function(self._largest_exponent)
|
||||||
result_func.set_coeffs(new_coefficients.tolist())
|
result_func.set_coeffs(new_coefficients.tolist())
|
||||||
return result_func
|
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)."""
|
"""Handles scalar multiplication from the right (e.g., 3 * func)."""
|
||||||
|
|
||||||
return self.__mul__(scalar)
|
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)."""
|
"""Performs in-place multiplication by a scalar (func *= 3)."""
|
||||||
self._check_initialized()
|
|
||||||
if not isinstance(scalar, (int, float)):
|
self.coefficients *= other
|
||||||
return NotImplemented
|
|
||||||
if scalar == 0:
|
|
||||||
raise ValueError("Cannot multiply a function by 0.")
|
|
||||||
|
|
||||||
self.coefficients *= scalar
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
@ -408,9 +485,13 @@ if __name__ == '__main__':
|
|||||||
print(f"Value of f1 at x=5 is: {y}") # Expected: 2*(25) - 3*(5) - 5 = 50 - 15 - 5 = 30
|
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
|
# Find the derivative: 4x - 3
|
||||||
df1 = f1.differential()
|
df1 = f1.derivative()
|
||||||
print(f"Derivative of f1: {df1}")
|
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 ---
|
# --- Root Finding ---
|
||||||
# 1. Analytical solution for quadratic
|
# 1. Analytical solution for quadratic
|
||||||
roots_analytic = quadratic_solve(f1)
|
roots_analytic = quadratic_solve(f1)
|
||||||
@ -448,4 +529,18 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Multiplication: (x + 10) * 3 = 3x + 30
|
# Multiplication: (x + 10) * 3 = 3x + 30
|
||||||
f_mul = f2 * 3
|
f_mul = f2 * 3
|
||||||
print(f"f2 * 3 = {f_mul}")
|
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])
|
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
|
||||||
|
|
||||||
# --- Core Functionality Tests ---
|
# --- Core Functionality Tests ---
|
||||||
|
|
||||||
def test_solve_y(quadratic_func):
|
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(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_solve(quadratic_func)
|
||||||
@ -61,13 +80,20 @@ 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])
|
||||||
|
|
||||||
# --- 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):
|
||||||
|
Reference in New Issue
Block a user