Compare commits

..

6 Commits

Author SHA1 Message Date
25f20a4db2 v0.2.0
Some checks failed
Run Python Tests / test (3.12) (pull_request) Failing after 11s
Run Python Tests / test (3.10) (pull_request) Failing after 15s
Run Python Tests / test (3.8) (pull_request) Failing after 11s
2025-06-17 14:26:45 -04:00
ee414ea0dc feat: Added function * function multiplication 2025-06-17 14:26:26 -04:00
8656b558b4 feat: Added alternative degree property to return largest_exponent 2025-06-17 14:12:19 -04:00
30a5189928 fix: multiplying by 0 returns a function object representing 0 2025-06-17 14:08:36 -04:00
3d2c724ad4 feat: Add nth derivative function and fix: typo derivitive->derivative 2025-06-17 14:06:45 -04:00
a761efe28e fix: Renamed differential function to derivitive 2025-06-17 13:45:51 -04:00
3 changed files with 142 additions and 25 deletions

View File

@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
# --- Core Metadata ---
name = "polysolve"
version = "0.1.1"
version = "0.2.0"
authors = [
{ name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
]

View File

@ -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.
@ -108,6 +108,11 @@ class Function:
def largest_exponent(self) -> int:
"""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:
"""
@ -129,15 +134,66 @@ class 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.
"""
self._check_initialized()
if self._largest_exponent == 0:
raise ValueError("Cannot differentiate a constant (Function of degree 0).")
derivative_coefficients = np.polyder(self.coefficients)
diff_func = Function(self._largest_exponent - 1)
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:
"""
@ -336,34 +392,55 @@ class Function:
result_func = Function(len(new_coefficients) - 1)
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
if scalar == 0:
raise ValueError("Cannot multiply a function by 0.")
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:
result_func = Function(0)
result_func.set_coeffs([0])
return result_func
new_coefficients = self.coefficients * scalar
result_func = Function(self._largest_exponent)
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,7 +485,7 @@ 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}")
# --- Root Finding ---
@ -448,4 +525,18 @@ if __name__ == '__main__':
# Multiplication: (x + 10) * 3 = 3x + 30
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}")

View File

@ -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, [19, 7, -7, -4])
# --- Genetic Algorithm Root-Finding Tests ---
def test_get_real_roots_numpy(quadratic_func):