Skip to main content

Chapter 47 Creating a Polynomial Module

This chapter aims to walk through the steps of creating a reasonably complex module for polynomials. This builds on the parametric data type first introduced using polynomials in Section 12.3, the modules and test suite in Chapter 23 and the ability to parse strings found in Chapter 26. The goal is to use all of these pieces to build up to a module that has quite a bit of functionality of polynomials.

Section 47.1 Polynomial Module

We first start by placing a lot of the code that we developed in Section 12.3 into a module. This will look like
module Poly
export Polynomial
struct Polynomial{T <: Number}
  coeffs::Vector{T}
end
function Base.show(io::IO, p::Polynomial)
  print(io, strip(reduce((str, n) -> "$str $(n==1 ? "" : "+") $(p.coeffs[n]) x^$(n-1)", 1:length(p.coeffs), init="")))
end
end # of the module Poly
Note we have called the module Poly to not get ourselves confused over the struct Polynomial. Place this code into a file called Poly.jl. Recall that we also developed code for addition/subtraction and scalar multiplication. We will add this later. Also, we have adapted the Base.show command to trim the ends of the string of any whitespace. We will load the Revise package and then load this file as
using Revise
includet("Poly.jl")
using .Poly
where it is important that we use the includet function from the Revise package. Recall that this allows us to update the package and then reuse structs and functions in the package without restarting the kernel. Test that this is working with
p1 = Polynomial([1,2,3])
and you shouldn’t get any errors and see the output 1 x^0 + 2 x^1 + 3 x^2.

Section 47.2 Testing the Polynomial Module

We are going to start testing the module immediately. Here’s a fair straightforward test suite that just creates the polynomial and tests each are the correct type:
using .Poly
using Test
function isequal(x::Polynomial,y::Polynomial)
    return x.coeffs == y.coeffs
end
## test the creation of polynomials
poly1 = Polynomial([1,2,3])
poly2 = Polynomial([1.0,2.0,3.0])
poly3 = Polynomial([2//3,3//4,5//8])
poly4 = Polynomial([im,2+0im,3-2im,-im])
poly5 = Polynomial([n for n=1:6])
poly6 = Polynomial([1,1.5,2//3])
@testset "Creating a Polynomial as a Vector of Coefficients" begin
  @test isa(poly1, Polynomial)
  @test typeof(poly1) == Polynomial{Int64}
  @test isa(poly2, Polynomial)
  @test typeof(poly2) == Polynomial{Float64}
  @test isa(poly3, Polynomial)
  @test typeof(poly3) == Polynomial{Rational{Int64}}
  @test isa(poly4, Polynomial)
  @test typeof(poly4) == Polynomial{Complex{Int64}}
  @test isa(poly5, Polynomial)
  @test typeof(poly5) == Polynomial{Int64}
  @test isa(poly6, Polynomial)
  @test typeof(poly6) == Polynomial{Float64}
end
Save these lines to a file called test-polynomial.jl and then run the code with
include("test-polynomial.jl")
where this time we use the include command. We don’t need the includet since this is not a module that needs to be tracked. Upon running this, you should see:
Test Summary:                                     | Pass  Total  Time
Creating a Polynomial as a Vector of Coefficients |   12     12  0.2s
This test code creates 6 polynomials of different types and then tests that each is the correct type. Note that for poly6, the array of coefficients is a mix of integers, floats and rationals, but Julia automatically changes it to an array of Floats.

Section 47.3 Testing the Stringification

The other part of the code that we have included in the module is that of the Base.show command. Recall that this is usually called when producing output, however it is also called when a polynomial is wrapped with the string method. For example,
p1 = Polynomial([1,2,3])
string(p1)
returns "1 x^0 + 2 x^1 + 3 x^2". So we can set up a testset and a few tests for the first few polynomials in the test suite with
@testset "Base.show" begin
  @test string(poly1) == "1 x^0 + 2 x^1 + 3 x^2"
  @test string(poly2) == "1.0 x^0 + 2.0 x^1 + 3.0 x^2"
  @test string(poly3) == "2//3 x^0 + 3//4 x^1 + 5//8 x^2"
end
and running the test code results in a all passing. The stringificaiton of poly4 is a bit of a problem because the complex numbers should be wrapped in parentheses.

Check Your Understanding 47.1.

(a)
Update the Base.show command to handle complex numbers by wrapping each coefficient in parentheses.
Hint.
Test the type of the coefficient using the typeof command. If it is of type Complex surround the coefficient with ().
(b)
Add a test to the test suite for the complex polynomial poly4.

Section 47.4 Test Driven Development

The standard for developing code today falls into what is called Test Driven Development or TDD. In short, this means that before any code is written, the test suites are created. For developers that have never done this before, it is very difficult way to develop because developers typically just want to write code--why test? We’ll show how this is done for the rest of the chapter and show how it can be beneficial.
Although we have already written a little code, we will switch to writing tests before writing code.

Section 47.5 Tests for Addition, Subtraction and Multiplication

We first start with tests for Addition, subtraction, scalar multiplication and polynomial multiplication. We have already written this code earlier, but we’ll write the tests before including the code. We’ll start with handling polynomials with integer coefficients. If we start with the following polynomials
poly10 = Polynomial([1, 2, 3])
poly11 = Polynomial([-2,1,0,1])
then we can run tests on these with
@testset "Addition, Subtraction, Multiplication and Constant Multiplication" begin
  @test poly10+poly11 == Polynomial([-1,3,3,1])
  @test poly11-poly10 == Polynomial([-3,-1,-3,1])
  @test 4*poly10 == Polynomial([4,8,12])
  @test poly10*poly11 == Polynomial([-2,-3,-4,4,3])
end
and recall that the polynomial multiplication is performed using distribution (extended FOIL).

Section 47.6 Creating tests for string constructors

Although we have a constructor for creating a polynomial from the coefficients, it’s also nice to have a constructor that parses a string. For example, if we have
poly21 = Polynomial("-3+10x-5x^4")
poly22 = Polynomial("x^2+2x+3")
poly23 = Polynomial("2x-1")
and then include the following tests at the bottom of the test file:
@testset "Creating a Polynomial from a string with integer coefficients" begin
  @test isa(poly21, Polynomial)
  @test typeof(poly21) == Polynomial{Int64}
  @test isequal(poly21, Polynomial([-3,10,0,0,-5]))
  @test isa(poly22, Polynomial)
  @test typeof(poly22) == Polynomial{Int64}
  @test isequal(poly22, Polynomial([3,2,1]))
  @test isa(poly23, Polynomial)
  @test typeof(poly23) == Polynomial{Int64}
  @test isequal(poly23, Polynomial([-1,2]))
end
We would like to handle polynomials with coefficients of other types: floats, rational and complex numbers. We construct the following polynomails:
poly31 = Polynomial([1.0, 2.0, 3.0])
poly32 = Polynomial([0,4.0,0,-1.0])
poly33 = Polynomial([1/2,1/4,1/8,1/10])
poly34 = Polynomial("3.0x^2+2.0x+1.0")
poly35 = Polynomial("-1.0x^3+4.0")
poly36 = Polynomial("0.5+0.25x+0.125x^2+0.1x^3")
poly41 = Polynomial([1//2, 1//3, 1//4])
poly42 = Polynomial([1,0,0,1//6])
poly43 = Polynomial([-1//8,0,0,0,1])
poly44 = Polynomial("1//4x^2+1//3x+1//2")
poly45 = Polynomial("1//6x^3+1")
poly46 = Polynomial("x^4-1//8")
poly51 = Polynomial([im, 2+im, 3-2im])
poly52 = Polynomial([im,0,1])
poly53 = Polynomial([im,2,3im,4])
poly54 = Polynomial("(3-2im)x^2+(2+im)x+im")
poly55 = Polynomial("im+x^2")
poly56 = Polynomial("4x^3+3imx^2+2x+im")
After defining these, each of these should then be tested for constructions and other operations (stringification, addition, subtraction and multiplication). All of these can be found here.

Section 47.7 Testing polynomial evaluation

The last set of tests that we are going to develop are those of polynomial evaluation.