Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed polynomial division #2130

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 61 additions & 18 deletions algorithms/maths/polynomial.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,28 +438,19 @@ def __floordiv__(self, other: Union[int, float, Fraction, Monomial]):
# def __truediv__(self, other: Union[int, float, Fraction, Monomial, Polynomial]) -> Polynomial:
def __truediv__(self, other: Union[int, float, Fraction, Monomial]):
"""
For Polynomials, only division by a monomial
is defined.

TODO: Implement polynomial / polynomial.
For Polynomial division, no remainder is provided. Must use poly_long_division() to capture remainder
"""
if isinstance(other, int) or isinstance(other, float) or isinstance(other, Fraction):
return self.__truediv__( Monomial({}, other) )
elif isinstance(other, Monomial):
poly_temp = reduce(lambda acc, val: acc + val, map(lambda x: x / other, [z for z in self.all_monomials()]), Polynomial([Monomial({}, 0)]))
return poly_temp
elif isinstance(other, Polynomial):
if Monomial({}, 0) in other.all_monomials():
if len(other.all_monomials()) == 2:
temp_set = {x for x in other.all_monomials() if x != Monomial({}, 0)}
only = temp_set.pop()
return self.__truediv__(only)
elif len(other.all_monomials()) == 1:
temp_set = {x for x in other.all_monomials()}
only = temp_set.pop()
return self.__truediv__(only)

raise ValueError('Can only divide a polynomial by an int, float, Fraction, or a Monomial.')
# Call long division
quotient, remainder = self.poly_long_division(other)
return quotient # Return just the quotient, remainder is ignored here

raise ValueError('Can only divide a polynomial by an int, float, Fraction, Monomial, or Polynomial.')

return

Expand Down Expand Up @@ -526,7 +517,59 @@ def subs(self, substitutions: Union[int, float, Fraction, Dict[int, Union[int, f

def __str__(self) -> str:
"""
Get a string representation of
the polynomial.
Get a properly formatted string representation of the polynomial.
"""
sorted_monos = sorted(self.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True),
reverse=True)
return ' + '.join(str(m) for m in sorted_monos if m.coeff != Fraction(0, 1))

def poly_long_division(self, other: 'Polynomial') -> tuple['Polynomial', 'Polynomial']:
"""
return ' + '.join(str(m) for m in self.all_monomials() if m.coeff != Fraction(0, 1))
Perform polynomial long division
Returns (quotient, remainder)
"""
if not isinstance(other, Polynomial):
raise ValueError("Can only divide by another Polynomial.")

if len(other.all_monomials()) == 0:
raise ValueError("Cannot divide by zero polynomial.")

quotient = Polynomial([])
remainder = self.clone()

divisor_monos = sorted(other.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True),
reverse=True)
divisor_lead = divisor_monos[0]

while remainder.all_monomials() and max(remainder.variables(), default=-1) >= max(other.variables(),
default=-1):
remainder_monos = sorted(remainder.all_monomials(), key=lambda m: sorted(m.variables.items(), reverse=True),
reverse=True)
remainder_lead = remainder_monos[0]

if not all(remainder_lead.variables.get(var, 0) >= divisor_lead.variables.get(var, 0) for var in
divisor_lead.variables):
break

lead_quotient = remainder_lead / divisor_lead
quotient = quotient + Polynomial([lead_quotient]) # Convert Monomial to Polynomial

remainder = remainder - (
Polynomial([lead_quotient]) * other) # Convert Monomial to Polynomial before multiplication

return quotient, remainder

dividend = Polynomial([
Monomial({1: 3}, 4), # 4(a_1)^3
Monomial({1: 2}, 3), # 3(a_1)^2
Monomial({1: 1}, -2), # -2(a_1)
Monomial({}, 5) # +5
])

divisor = Polynomial([
Monomial({1: 1}, 2), # 2(a_1)
Monomial({}, -1) # -1
])

quotient = dividend / divisor
print("Quotient:", quotient)
70 changes: 48 additions & 22 deletions tests/test_polynomial.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,27 +105,6 @@ def test_polynomial_multiplication(self):
]))
return

def test_polynomial_division(self):

# Should raise a ValueError if the divisor is not a monomial
# or a polynomial with only one term.
self.assertRaises(ValueError, lambda x, y: x / y, self.p5, self.p3)
self.assertRaises(ValueError, lambda x, y: x / y, self.p6, self.p4)

self.assertEqual(self.p3 / self.p2, Polynomial([
Monomial({}, 1),
Monomial({1: 1, 2: -1}, 0.75)
]))
self.assertEqual(self.p7 / self.m1, Polynomial([
Monomial({1: -1, 2: -3}, 2),
Monomial({1: 0, 2: -4}, 1.5)
]))
self.assertEqual(self.p7 / self.m1, Polynomial([
Monomial({1: -1, 2: -3}, 2),
Monomial({2: -4}, 1.5)
]))
return

def test_polynomial_variables(self):
# The zero polynomial has no variables.

Expand Down Expand Up @@ -172,4 +151,51 @@ def test_polynomial_clone(self):
self.assertEqual(self.p5.clone(), Polynomial([
Monomial({1: -1, 3: 2}, 1)
]))
return
return

def test_polynomial_long_division(self):
"""
Test polynomial long division
"""

# Dividend: 4a_1^3 + 3a_1^2 - 2a_1 + 5
dividend = Polynomial([
Monomial({1: 3}, 4), # 4(a_1)^3
Monomial({1: 2}, 3), # 3(a_1)^2
Monomial({1: 1}, -2), # -2(a_1)
Monomial({}, 5) # +5
])

# Divisor: 2a_1 - 1
divisor = Polynomial([
Monomial({1: 1}, 2), # 2(a_1)
Monomial({}, -1) # -1
])

# Expected Quotient: 2a_1^2 + (5/2)a_1 + 1/4
expected_quotient = Polynomial([
Monomial({1: 2}, 2), # 2(a_1)^2
Monomial({1: 1}, Fraction(5, 2)), # (5/2)(a_1)
Monomial({}, Fraction(1, 4)) # +1/4
])

# Expected Remainder: 21/4
expected_remainder = Polynomial([
Monomial({}, Fraction(21, 4)) # 21/4
])

quotient_long_div, remainder_long_div = dividend.poly_long_division(divisor)

quotient_truediv = dividend / divisor # Calls __truediv__, which returns only the quotient

# Check if quotient from poly_long_division matches expected
self.assertEqual(quotient_long_div, expected_quotient)

# Check if remainder from poly_long_division matches expected
self.assertEqual(remainder_long_div, expected_remainder)

# Check if quotient from __truediv__ matches quotient from poly_long_division
self.assertEqual(quotient_truediv, quotient_long_div)

return