Skip to content
Draft
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
1 change: 1 addition & 0 deletions polynomial/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
from .frozen import FrozenPolynomial, ZeroPolynomial
from .binomial import Binomial, LinearBinomial
from .trinomial import Trinomial, QuadraticTrinomial
from .quadrinomial import Quadrinomial, CubicQuadrinomial
94 changes: 94 additions & 0 deletions polynomial/quadrinomial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""This module defines different types of quadrinomials and their methods."""

from cmath import sqrt as csqrt
from polynomial.core import (
Polynomial,
Monomial,
Constant,
FixedDegreePolynomial,
FixedTermPolynomial
)


class Quadrinomial(FixedTermPolynomial, valid_term_counts=(0, 1, 2, 3, 4)):
"""Implements single-variable mathematical trinomials."""

def __init__(self,
monomial1=None,
monomial2=None,
monomial3=None,
monomial4=None):
"""Initialize the quadrinomial with 4 monomials.

The arguments can also be 2-tuples in the form:
(coefficient, degree)
"""
if not monomial1:
monomial1 = Monomial(1, 1)
if not monomial2:
monomial2 = Monomial(1, 2)
if not monomial3:
monomial3 = Monomial(1, 3)
if not monomial4:
monomial4 = Monomial(1, 4)
args = [monomial1, monomial2, monomial3, monomial4]
Polynomial.__init__(self, args, from_monomials=True)

def __repr__(self):
"""Return repr(self)."""
terms = self.terms
assert len(terms) == 4
t1, t2, t3, t4 = terms
return (
"Quadrinomial(Monomial({0}, {1}), Monomial({2}, {3}), "
"Monomial({4}, {5}), Monomial({6}, {7}))"
.format(*t1, *t2, *t3, *t4)
)


class CubicQuadrinomial(FixedDegreePolynomial, Quadrinomial, valid_degrees=3):
"""Implements cubic polynomials and their related methods."""

def __init__(self, a=1, b=1, c=1, d=1):
"""Initialize the quadrinomial as ax^3 + bx^2 + cx + d."""
if a == 0:
raise ValueError("Object not a cubic quadrinomial since a==0!")
Polynomial.__init__(self, a, b, c, d)

@property
def complex_roots(self):
"""Return a 3-tuple with the complex roots of ax^3 + bx^2 + cx + d = 0.

This method uses the general cubic roots formula.
"""
a, b, c, d = self.a, self.b, self.c, self.d

delta0 = b*b - 3*a*c
delta1 = 2*b*b*b - 9*a*b*c + 27*a*a*d
cardano1 = ((delta1 + csqrt(delta1**2 - 4*(delta0**3))) / 2) ** (1/3)
cardano2 = ((delta1 - csqrt(delta1**2 - 4*(delta0**3))) / 2) ** (1/3)

cardano = cardano2 if not cardano1 else cardano1

xi = (-1 + csqrt(-3)) / 2

roots = [(b + (xi**k)*cardano + (delta0 / (xi**k)*cardano)) / (-3 * a)
for k in range(3)]

return tuple(roots)

@property
def complex_factors(self):
"""Return (a, (x-x_0), (x-x_1), (x-x_2)), where x_i are the roots."""
roots = self.complex_roots
return (Constant(self.a),
Polynomial(1, -roots[0]),
Polynomial(1, -roots[1]),
Polynomial(1, -roots[2]))

def __repr__(self):
"""Return repr(self)."""
return (
"CubicQuadrinomial({0!r}, {1!r}, {2!r}, {3!r})"
.format(self.a, self.b, self.c, self.d)
)
42 changes: 42 additions & 0 deletions tests/test_polynomials_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
LinearBinomial,
Trinomial,
QuadraticTrinomial,
Quadrinomial,
CubicQuadrinomial,
)


Expand Down Expand Up @@ -152,6 +154,42 @@ def test_quadratic_trinomial_default_init(self):

self.assertEqual(expected, qt)

def test_quadrinomial_init_from_monomials(self):
"""Ensure a quadrinomial is successfully initialized from monomials."""
m1 = Monomial(3, 3)
m2 = Monomial(4, 4)
m3 = Monomial(5, 5)
expected = Polynomial([m1, m2, m3], from_monomials=True)

t = Trinomial(m1, m2, m3)

self.assertEqual(expected, t)

def test_quadrinomial_default_init(self):
"""Test that the default quadrinomial is 'x^4 + x^3 + x^2 + x'."""
expected = Polynomial(1, 1, 1, 1, 0)

q = Quadrinomial()

self.assertEqual(expected, q)

def test_cubic_quadrinomial_init(self):
"""Test that a cubic quadrinomial is successfully initialized."""
a, b, c, d = 2, 3, 4, 5
expected = Polynomial(a, b, c, d)

cq = CubicQuadrinomial(a, b, c, d)

self.assertEqual(expected, cq)

def test_cubic_quadrinomial_default_init(self):
"""Test that the default cubic quadrinomial is 'x^3 + x^2 + x + 1'."""
expected = Polynomial(1, 1, 1, 1)

cq = CubicQuadrinomial()

self.assertEqual(expected, cq)

def test_linear_binomial_fails_leading_zero(self):
"""Test that LinearBinomial(0, ?) raises a ValueError."""
self.assertRaises(ValueError, LinearBinomial, 0, 1)
Expand All @@ -160,6 +198,10 @@ def test_quadratic_trinomial_fails_leading_zero(self):
"""Test that QuadraticTrinomial(0, ?) raises a ValueError."""
self.assertRaises(ValueError, QuadraticTrinomial, 0, 1)

def test_cubic_quadrinomial_fails_leading_zero(self):
"""Test that CubicQuadrinomial(0, ?) raises a ValueError."""
self.assertRaises(ValueError, CubicQuadrinomial, 0, 1)

def test_monomial_degree_positive_int(self):
"""Test that monomial only accepts a positive int."""
self.assertRaises(ValueError, Monomial, 1, -1)
Expand Down
44 changes: 44 additions & 0 deletions tests/test_polynomials_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ZeroPolynomial,
LinearBinomial,
QuadraticTrinomial,
CubicQuadrinomial,
DegreeError,
TermError,
)
Expand Down Expand Up @@ -730,6 +731,36 @@ def test_negative_discriminant_roots(self):
self.assertEqual(exp_real_factors, res_real_factors)
self.assertEqual(exp_complex_factors, res_complex_factors)

def test_cubic_formula_triple_root_correct(self):
"""Test that the cubic formula correctly computes a triple root."""
cq = CubicQuadrinomial(1, -3j, -3, 1j)
exp_complex_roots = (1j, 1j, 1j)
exp_complex_factors = (1,
LinearBinomial(1, -1j),
LinearBinomial(1, -1j),
LinearBinomial(1, -1j))

res_complex_roots = cq.complex_roots
res_complex_factors = cq.complex_factors

self.assertEqual(exp_complex_roots, res_complex_roots)
self.assertEqual(exp_complex_factors, res_complex_factors)

def test_cubic_formula_double_root_correct(self):
"""Test that the cubic formula correctly computes a double root."""
cq = CubicQuadrinomial(1, -4, 5, -2)
exp_complex_roots = (1, 1, 2)
exp_complex_factors = (1,
LinearBinomial(1, -1),
LinearBinomial(1, -1),
LinearBinomial(1, -2))

res_complex_roots = cq.complex_roots
res_complex_factors = cq.complex_factors

self.assertEqual(exp_complex_roots, res_complex_roots)
self.assertEqual(exp_complex_factors, res_complex_factors)

def test_zero_instance_mutable(self):
"""Test that zero instances are mutable."""
zp = Polynomial.zero_instance()
Expand Down Expand Up @@ -972,6 +1003,7 @@ def test_setting_empty_terms_mutable_degree(self):
def test_setting_empty_terms_immutable_degree(self):
"""Test setting empty terms."""
immutable = [
CubicQuadrinomial(1, 2, 3, 4),
QuadraticTrinomial(1, 2, 3),
LinearBinomial(1, 2),
]
Expand Down Expand Up @@ -1021,13 +1053,15 @@ def test_pos(self):
d = ZeroPolynomial()
e = QuadraticTrinomial(1, 3, 7)
f = LinearBinomial(9, 2)
g = CubicQuadrinomial(5, 4, 1, 7)

self._assert_polynomials_are_the_same(a, +a)
self._assert_polynomials_are_the_same(b, +b)
self._assert_polynomials_are_the_same(c, +c)
self._assert_polynomials_are_the_same(d, +d)
self._assert_polynomials_are_the_same(e, +e)
self._assert_polynomials_are_the_same(f, +f)
self._assert_polynomials_are_the_same(g, +g)

def test_div_by_zero(self):
"""Test that division by 0 is not possible."""
Expand All @@ -1038,6 +1072,7 @@ def test_div_by_zero(self):
ZeroPolynomial(),
QuadraticTrinomial(1, 3, 7),
LinearBinomial(9, 2),
CubicQuadrinomial(5, 4, 1, 7),
]

for val in to_test:
Expand All @@ -1056,6 +1091,7 @@ def test_pow_by_negative(self):
ZeroPolynomial(),
QuadraticTrinomial(1, 3, 7),
LinearBinomial(9, 2),
CubicQuadrinomial(5, 4, 1, 7),
]

for val in to_test:
Expand All @@ -1071,6 +1107,7 @@ def test_pow_by_non_integer(self):
ZeroPolynomial(),
QuadraticTrinomial(1, 3, 7),
LinearBinomial(9, 2),
CubicQuadrinomial(5, 4, 1, 7),
]

for val in to_test:
Expand Down Expand Up @@ -1109,6 +1146,7 @@ def test_inequality(self):
ZeroPolynomial(),
QuadraticTrinomial(1, 3, 7),
LinearBinomial(9, 2),
CubicQuadrinomial(5, 4, 1, 7),
7
]

Expand Down Expand Up @@ -1144,6 +1182,7 @@ def test_ipow_zero_gives_one(self):
ZeroPolynomial(),
QuadraticTrinomial(1, 3, 7),
LinearBinomial(9, 2),
CubicQuadrinomial(5, 4, 1, 7),
]

one_maps = {
Expand All @@ -1153,6 +1192,7 @@ def test_ipow_zero_gives_one(self):
ZeroPolynomial: Constant(1),
QuadraticTrinomial: Polynomial(1),
LinearBinomial: Polynomial(1),
CubicQuadrinomial: Polynomial(1),
}

for val in to_test:
Expand Down Expand Up @@ -1334,12 +1374,16 @@ def test_setitem_raises_error(self):
"""Test that setitem raises error on invalid inputs."""
lb = LinearBinomial(5, 1)
qt = QuadraticTrinomial(1, 2, 3)
cq = CubicQuadrinomial(4, 3, 2, 1)
self.assertRaises(DegreeError, lb.__setattr__, "a", 0)
self.assertRaises(DegreeError, qt.__setattr__, "a", 0)
self.assertRaises(DegreeError, cq.__setattr__, "a", 0)
lb = LinearBinomial(5, 1)
qt = QuadraticTrinomial(1, 2, 3)
cq = CubicQuadrinomial(4, 3, 2, 1)
self.assertRaises(DegreeError, lb.__setitem__, 1, 0)
self.assertRaises(DegreeError, qt.__setitem__, 2, 0)
self.assertRaises(DegreeError, cq.__setitem__, 3, 0)

def test_monomial_can_only_have_one_or_no_terms(self):
"""Test that setting terms works correctly."""
Expand Down
21 changes: 20 additions & 1 deletion tests/test_polynomials_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
Monomial,
Polynomial,
QuadraticTrinomial,
CubicQuadrinomial,
ZeroPolynomial,
FrozenPolynomial,
Trinomial,
Binomial
Binomial,
Quadrinomial
)


Expand Down Expand Up @@ -92,6 +94,23 @@ def test_quadratic_trinomial(self):

self.assertEqual(expect, r)

def test_quadrinomial(self):
"""Test that repr() output of a Quadrinomial is valid."""
expect = "Quadrinomial(Monomial(6, 9), Monomial(5, 5), "\
"Monomial(2, 3), Monomial(1, 1))"

r = repr(Quadrinomial((6, 9), (5, 5), (2, 3), (1, 1)))

self.assertEqual(expect, r)

def test_cubic_quadrinomial(self):
"""Test that repr() output of a CubicQuadrinomial is valid."""
expect = "CubicQuadrinomial(1, -4, 2, 0)"

r = repr(CubicQuadrinomial(1, -4, 2, 0))

self.assertEqual(expect, r)

def test_frozen(self):
"""Test that repr() output of a FrozenPolynomial is valid."""
expect = "FrozenPolynomial(1, 2, 3)"
Expand Down