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] [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" },
] ]

View File

@ -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.
@ -109,6 +109,11 @@ class Function:
"""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:
""" """
Solves for y given an x value. (i.e., evaluates the polynomial at x). Solves for y given an x value. (i.e., evaluates the polynomial at x).
@ -126,6 +131,26 @@ class Function:
""" """
Calculates the derivative of the 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: Returns:
Function: A new Function object representing the derivative. Function: A new Function object representing the derivative.
""" """
@ -139,6 +164,37 @@ class Function:
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:
""" """
Uses a genetic algorithm to find the approximate real roots of the function (where y=0). Uses a genetic algorithm to find the approximate real roots of the function (where y=0).
@ -337,13 +393,14 @@ class Function:
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: 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 new_coefficients = self.coefficients * scalar
@ -351,19 +408,39 @@ class Function:
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)):
return NotImplemented
if scalar == 0:
raise ValueError("Cannot multiply a function by 0.")
self.coefficients *= scalar self.coefficients *= other
return self 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 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}")
# --- Root Finding --- # --- Root Finding ---
@ -449,3 +526,17 @@ 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}")

View File

@ -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, [19, 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):