Git Product home page Git Product logo

polynomialroots.jl's Introduction

PolynomialRoots.jl

Build Status Code Coverage
Build Status
Build Status

Introduction

PolynomialRoots.jl is a library for finding roots of complex univariate polynomials, written in Julia.

This is an implementation in Julia of the General Complex Polynomial Root Solver, written in Fortran, by Jan Skowron and Andrew Gould. All the credits goes to them for the original algorithm.

The root finding algorithm employed in this library is described in

  • J. Skowron & A. Gould, 2012, "General Complex Polynomial Root Solver and Its Further Optimization for Binary Microlenses", arXiv:1203.1034

This algorithm aims to be fast and precise, more than the well known zroots procedure described in Numerical Recipes books, whose implementations in C and Fortran are not available as free software, according to the definition of the Free Software Foundation.

PolynomialRoots.jl can also take advantage of native arbitrary precision capabilities of Julia and achieve more precise results.

Note: the adopted algorithm can give highly inaccurate results for polynomials of order above ~30. This can be mitigated by using multiple precision calculations (see example below).

Installation

PolynomialRoots.jl is available for Julia 1.0 and later versions, and can be installed with Julia built-in package manager. In a Julia session run the command

julia> Pkg.add("PolynomialRoots")

You may need to update your package list with Pkg.update() in order to get the latest version of PolynomialRoots.jl.

Older versions are available also for Julia 0.4-0.7.

Usage

After installing the package, run

using PolynomialRoots

or put this command into your Julia script.

PolynomialRoots.jl provides two functions to find the roots of complex polynomials

roots(polynomial[, roots, polish=true, epsilon=1e-20])
roots5(polynomial[, roots, epsilon=1e-20])

roots can be used to solve polynomials of any degree, roots5 is tailored to (and works only for) polynomials of degree 5. This function exists because one of the methods to calculate gravitational microlensing amplification by a binary lens requires solving a fifth-order complex polynomial. roots5 should be more robust than roots for this class of polynomials.

The mandatory argument for both functions is:

  • polynomial, the vector holding coefficients of the polynomial you want to solve, in ascending order, from the lowest to the highest. Coefficients can be complex and real

Optional arguments are:

  • roots: if you roughly know in advance the position of the roots, you can pass the vector with the known roots to speed up convergence. roots vector must be one element shorther than polynomial. In roots5, the roots will be only polished. Elements of the vector can be complex and real
  • polish (boolean keyword, only for roots): if set to true, after all roots have been found by dividing original polynomial by each root found, all roots will be polished using full polynomial. Default is false
  • epsilon (floating point optional keyword): this is used to determine the stopping criterion described in Skowron & Gould paper. If not set, it defaults to machine precision of polynomial (and roots) argument(s). This is not the precision with which the roots will be calculated.

The functions return in output the Complex vector with all roots of polynomial. Note: if roots optional argument is provided, it is not changed in-place.

Examples

Find the roots of

(x - i)*(x - 2)*(x - 3*i)*(x - 4)*(x - 5*i) =
  x^5 - (9*i + 6)*x^4 + (54*i - 15)*x^3 + (138 - 57*i)*x^2 - (184 + 90*i)*x + 120*i

This is a fifth-order polynomial, so we can find its zeros with both roots and roots5:

julia> roots([120im, -(184 + 90im), (138 - 57im), (54im - 15), -(6 + 9im), 1])
5-element Array{Complex{Float64},1}:
 -1.55431e-15+5.0im
 4.0+1.77636e-16im
  1.55028e-15+3.0im
 -1.04196e-16+1.0im
 2.0-2.00595e-16im

julia> roots5([120im, -(184 + 90im), (138 - 57im), (54im - 15), -(6 + 9im), 1])
5-element Array{Complex{Float64},1}:
  5.92119e-16+5.0im
  4.0-1.4803e-16im
 2.0+1.88202e-16im
 -1.88738e-15+3.0im
  2.10942e-15+1.0im

PolynomialRoots.jl handles polynomials with high-multiplicity roots as well. For example, consider

(x + 1)^5 = x^5 + 5x^4 + 10x^3 + 10x^2 + 5x + 1

You can find its roots with

julia> roots([1, 5, 10, 10, 5, 1])
5-element Array{Complex{Float64},1}:
 -1.0+0.0im
 -1.0+0.0im
 -1.0+0.0im
 -1.0+0.0im
 -1.0+0.0im

julia> roots5([1, 5, 10, 10, 5, 1])
5-element Array{Complex{Float64},1}:
 -1.0+0.0im
 -1.0+0.0im
 -1.0+0.0im
 -1.0+0.0im
 -1.0+0.0im

Arbitrary precision

Due to limited precision of Float64 type, extraction of roots of polynomials can give inaccurate results, even for low-order polynomials. This is caused, i.e., by catastrophic cancellation in computation of discriminant Δ = sqrt(b^2 - 4ac) of second-order polynomials. For example, the actual roots of

94906265.625*x^2 - 189812534*x + 94906268.375

are

x_1 = 1.000000028975958
x_2 = 1.000000000000000

but when you try to calculate them in double-precision you get

julia> r = roots([94906268.375, -189812534, 94906265.625]);

julia> r[1]
1.0000000144879793 - 0.0im

julia> r[2]
1.0000000144879788 + 0.0im

If you are interested in double-precision accuracy, you can work around this problem by calculating the roots with higher precision and then transforming the result to double-precision. Julia natively supports multiple precision calculations, so what you have to do is only to pass BigFloat numbers to roots function (note: in order to correctly initialize an arbitrary precision floating point constant it is better to use the big string literal, see http://docs.julialang.org/en/stable/stdlib/numbers/#Base.BigFloat):

julia> r = roots([big"94906268.375", -189812534, big"94906265.625"]);

julia> Float64(r[1])
1.0000000289759583

julia> Float64(r[2])
1.0

Note that in this case there is a trade-off between speed and higher accuracy and precision.

Related projects

Another Julia package for finding roots of complex polynomials is Polynomials.jl, by Jameson Nash and other contributors. This package does much more than finding roots of polynomials (among other features, it can integrate and differentiate polynomials). In order to solve the polynomial, Polynomials.jl calculates eigenvalues of its companion matrix, but PolynomialRoots.jl is usually faster by up to an order of magnitude and often slightly more precise. In addition, Polynomials cannot extract roots in arbitrary precision. If you are after speed and precision, PolynomialRoots.jl can still be a better option.

Development

The package is developed at https://github.com/giordano/PolynomialRoots.jl. There you can submit bug reports, make suggestions, and propose pull requests.

History

The ChangeLog of the package is available in NEWS.md file in top directory.

License

The PolynomialRoots.jl package is licensed under version 2.0 of the Apache License or the GNU Lesser General Public License version 3 or any later version, at your option. These are the same licenses used by the General Complex Polynomial Root Solver.

A custom in the scientific comunity is (regardless of the licence you chose to use or distribute this software under) that if this code was important in the scientific process or for the results of your scientific work, we kindly ask you for the appropriate citation of the paper Skowron & Gould 2012 (http://arxiv.org/abs/1203.1034), and we would be greatful if you pass the information about the proper citation to anyone whom you redistribute this software to.

The authors of the General Complex Polynomial Root Solver, the original Fortran library (http://www.astrouw.edu.pl/~jskowron/cmplx_roots_sg/) from which PolynomialRoots.jl has been translated, are Jan Skowron, Andrew Gould.

The original author of PolynomialRoots.jl is Mosè Giordano.

polynomialroots.jl's People

Contributors

giordano avatar juliatagbot avatar lkapelevich avatar moelf avatar staticfloat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

polynomialroots.jl's Issues

Wrong results

I did some test computations and found the following troubling result:

julia> using DynamicPolynomials

julia> using PolynomialRoots

julia> function poly(roots)
           @polyvar x
           d = length(roots)
           f = prod(x - xi for xi in roots)
           [coefficient(f, x^i) for i in 0:d]
       end
poly (generic function with 1 method)

julia> d = 20
20

julia> c = [randn(ComplexF64); randn(ComplexF64, d - 1) * 1e-4; 1]
21-element Array{Complex{Float64},1}:
     0.6038117420721143 - 0.4692317923460019im    
   8.677470319492543e-5 - 1.9856488102339103e-5im 
 -2.8145901548439397e-5 + 2.5958816494607353e-6im 
  -9.933810484391282e-5 - 0.0001480730021139855im 
  -8.820204766296887e-5 - 0.0001042484315957653im 
    7.88021087360911e-5 + 2.5513881910383407e-5im 
  -5.007828702552179e-5 - 4.850423221617593e-7im  
   3.788521654994467e-5 + 2.0944404532776053e-5im 
 -1.1315308780483735e-5 - 1.8001817822660527e-5im 
 0.00011608934805185687 + 0.0001503409434982439im 
   4.125906444101907e-5 + 9.978779625362144e-5im  
   -5.52502458307844e-5 + 1.244622053838063e-5im  
   3.170867062110031e-5 - 1.9111588344365587e-5im 
   9.754810243956861e-5 + 6.369172830914271e-5im  
   7.028073862086189e-5 - 3.3819410763212794e-5im 
  0.0001238375611115403 - 5.144632426358618e-5im  
  -5.263257681962449e-5 + 6.227546399484231e-5im  
  4.6728130048484104e-5 - 0.00011791878030739642im
 0.00010423858969876596 - 0.00011299961775497805im
  -8.557623396259921e-5 - 2.825091927864975e-5im  
                    1.0 + 0.0im                   

julia> r = roots(c)
20-element Array{Complex{Float64},1}:
   7.802412795926557 - 4.178757163336667im 
 -1.2054492306931206 + 8.768496484683855im 
  -8.768489889160787 - 1.2054534486770816im
  -4.178754803019869 - 7.80240884258056im  
   8.768498428644229 + 1.2054564365850402im
  1.2054578038795405 - 8.768493818879023im 
   6.385317333146652 + 6.129227118201205im 
  -6.129219730795856 + 6.38531432406573im  
   3.856072372001663 - 7.966827038663316im 
 -7.9668235583329405 - 3.856068311866256im 
  -7.802404396639403 + 4.1787600200142485im
   4.178763518989671 + 7.802411631716743im 
   6.129228142519897 - 6.385311565395753im 
  -6.385308625464333 - 6.129224228027822im 
    8.71184335999314 - 1.5631567624600453im
   1.563163615363657 + 8.711841873146625im 
  -3.856063894157322 + 7.966829725325496im 
   7.966832198295311 + 3.856071278929593im 
 -1.5631549515439616 - 8.711839169108076im 
  -8.711834912718764 + 1.563159707245344im 

julia> c - poly(r)
21-element Array{Complex{Float64},1}:
   -7.987123734845452e18 + 3.4639346941464863e18im 
       896.0000867747032 + 1151.999980143512im     
      -16.00002814590155 + 18.00000259588165im     
      -8.000099338104844 - 14.500148073002114im    
     0.24991179795233703 - 0.18760424843159576im   
      0.1563288021087361 + 0.17190051388191038im   
   -0.007862578287025522 - 4.850423221617593e-7im  
 -0.00020625540845005532 + 0.000753366279532776im  
  -1.1315308780483735e-5 - 0.00020110728657266052im
   2.4536613676856873e-5 - 2.513513072050611e-5im  
   2.6352546265659437e-6 + 1.5593416637776903e-6im 
  -2.6542446384986325e-8 + 1.0805906987477109e-7im 
  -1.7764205750584404e-8 + 6.601467340955743e-9im  
  -9.523320931490147e-10 - 4.682197821015313e-9im  
  5.2134349463363316e-11 - 3.523188618357654e-10im 
   5.818546518080239e-12 + 2.4179138238041714e-13im
   5.263254309923089e-12 + 7.162041142441677e-13im 
    1.88754143337546e-13 - 2.443320881988925e-13im 
  -4.330127294054076e-14 - 1.3839515471125718e-14im
  -2.160977560089483e-15 - 1.5989373182683647e-15im
                     0.0 + 0.0im          

I am not sure whether this is a problem with the algorithm or the specific implementation.

Add new labels (Documentation/Testing)

I'm currently contributing an automatic PR labeler to this package as part of my task in Google Code In.

I believe that by adding "documentation" (for detecting change in a markdown file) and "Testing" (detecting a change in the /test folder) labels, the package contributing can be made much easier.

Regards,
PseudoCodeNerd

Problem when last coefficient is not zero.

I realized that when the last coefficient of the polynomial is not zero, the algorithm might get confused. For example:

roots([0, 1, 4, 1, 0, 0, 0]) produces
-0.0 - 0.0im
-0.0 - 0.0im
-6120.14...+ 1922.269..im
-3.73... + 0.0im
-0.267... + 0.0im
0.0 + 0.0im

but the actual result:
roots([0, 1, 4, 1]) produces
-3.732...+ 0.0im
-0.267... - 0.0im
0.0 + 0.0im

roots has issue finding the root of a polynomial with degree 14

Hi,

I am trying to write a general function to compute the Internal Rate of Return of a bond. This is basically a root finding problem and I was trying to use PolynomialRoots.jl as a root finder. The roots function works fine up to a degree of 13 yet is not able to find the "correct" roots when the degree of the polynomial is increased to 14. For example 1.03 is a root of both polynomials p1 and p2 where:

julia> p1 = [103, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, -100]
14-element Array{Int64,1}:
  103
    3
    3
    3
    3
    3
    3
    3
    3
    3
    3
    3
    3
 -100

julia> p2 = [103, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, -100]
15-element Array{Int64,1}:
  103
    3
    3
    3
    3
    3
    3
    3
    3
    3
    3
    3
    3
    3
 -100

Yet when roots is executed for both polynomials the following outputs are obtained:

julia> roots(p1)
13-element Array{Complex{Float64},1}:
      1.03-2.13163e-16im
 -0.354605-0.935016im
 -0.748511+0.663123im
  0.120537+0.992709im
  0.885456-0.464723im
 -0.748511-0.663123im
  0.885456+0.464723im
 -0.354605+0.935016im
 -0.970942-0.239316im
  0.120537-0.992709im
 -0.970942+0.239316im
  0.568065-0.822984im
  0.568065+0.822984im

julia> roots(p2)
14-element Array{Complex{Float64},1}:
  0.697266-0.419327im
 0.0780983+0.692255im
 -0.705236+0.0995877im
 -0.255796-0.709392im
  0.891654-0.00461915im
 -0.532087-0.516335im
 -0.292549+0.555004im
  0.684213+0.400383im
  0.421208-0.663104im
 -0.692196-0.226128im
 0.0834591-0.763406im
 -0.568103+0.389205im
  0.405608+0.623887im
 -0.185538+0.54199im

Notice that 1.03 is included in the first array yet not the second array. Yet 1.03 is a known root of both polynomials. So roots misses this root for the second polynomial.

Thanks in advance.

Why restricted to AbstractFloat?

This package doesn't work with interval arithmetic because it restricts to AbstractFloat:

julia> roots([1.,@interval(2),3])
ERROR: MethodError: no method matching roots!(::Array{Complex{Interval{Float64}},1}, ::Array{Complex{Interval{Float64}},1}, ::Float64, ::Int64, ::Bool)
Closest candidates are:
  roots!(::Array{Complex{T<:AbstractFloat},1}, ::Array{Complex{T<:AbstractFloat},1}, ::E<:AbstractFloat, ::Integer, ::Bool) where {T<:AbstractFloat, E<:AbstractFloat} at /Users/sheehanolver/.julia/packages/PolynomialRoots/gl0Ka/src/PolynomialRoots.jl:568
Stacktrace:
 [1] #roots#2(::Float64, ::Bool, ::typeof(roots), ::Array{Interval{Float64},1}) at /Users/sheehanolver/.julia/packages/PolynomialRoots/gl0Ka/src/PolynomialRoots.jl:613
 [2] roots(::Array{Interval{Float64},1}) at /Users/sheehanolver/.julia/packages/PolynomialRoots/gl0Ka/src/PolynomialRoots.jl:612
 [3] top-level scope at REPL[7]:1

sometimes returns bogus duplicate roots

As noted on discourse, the roots function seems to return bogus roots, which are all nearly identical, for some polynomials. It happens pretty often for me if you just randomly try different polynomials of degree 6. Here is one example:

c = [0.1767237967771577 - 0.8611076733845967im, 0.00017422934461020398 - 9.453901157858016e-6im, 2.8796166645520005e-5 - 6.235942236501559e-5im, 0.00011096228622067326 + 6.305229743814886e-5im, -4.383872407211953e-5 - 3.677479055394419e-5im, -5.464453142543401e-6 + 6.577470932911938e-5im, 1.0 + 0.0im]
r = roots(c, polish=true)

returns 6 nearly duplicate "roots"

6-element Vector{ComplexF64}:
 1.0126353489009172 + 7.1699666737369805im
  1.012635472445031 + 7.169966573453136im
 1.0126355118156847 + 7.169966616995025im
 1.0126353647489426 + 7.169966609253118im
 1.0126354389431256 + 7.169966613009901im
  1.012635426566087 + 7.169966602733274im

which are not roots at all:

julia> evalpoly.(r, Ref(c))
6-element Vector{ComplexF64}:
 -96023.81180290483 + 107521.12470406065im
  -96023.7931286698 + 107521.12824563321im
 -96023.79406644785 + 107521.13519457991im
 -96023.80469749296 + 107521.12117898901im
 -96023.79932061117 + 107521.12823827742im
 -96023.79933709523 + 107521.12631673364im

The correct roots are returned by Polynomials.roots, and they are completely different from the values returned above:

julia> import Polynomials

julia> r2 = Polynomials.roots(Polynomials.Polynomial(c))
6-element Vector{ComplexF64}:
   -0.936358238274595 - 0.28505582219111214im
   -0.714980912681819 + 0.6683037772456318im
 -0.22129525444596665 - 0.9534008044046407im
  0.22128062807035184 + 0.9533676310467337im
   0.7150642724349877 - 0.6683776196201094im
   0.9362949693501847 + 0.2850970632141694im

julia> evalpoly.(r2, Ref(c))
6-element Vector{ComplexF64}:
   8.881784197001252e-16 + 1.4432899320127035e-15im
   5.218048215738236e-15 - 3.3306690738754696e-15im
 -2.7755575615628914e-17 - 4.9960036108132044e-15im
  -2.248201624865942e-15 + 2.9976021664879227e-15im
  1.7763568394002505e-15 - 2.55351295663786e-15im
   4.440892098500626e-16 + 2.55351295663786e-15im

JET: MethodError for zero polynomial

Complaint from JET.jl:

roots(poly::Vector{Int64}) @ PolynomialRoots /src/PolynomialRoots.jl:610
│┌ roots(poly::Vector{Int64}; epsilon::Float64, polish::Bool) @ PolynomialRoots /src/PolynomialRoots.jl:617
││ no matching method found `(::Colon)(::Int64, ::Nothing)` (1/2 union split): (1 PolynomialRoots.:(:) last_nz::Union{Nothing, Int64})
│└────────────────────

So Line 617 errors with a MethodError for ::Colon if the input vector has no nonzero element.

MWE:

using PolynomialRoots
roots([0,0,0])

roots hangs forever with input

@giordano Is roots guaranteed to converge? Should there be a maximum number of iterations or a timeout?

I have attached a jld file with coefficients that are likely numerically unfriendly. roots hangs unless epsilon is significantly larger than 1E-8 or so. Let me know if I can help debug, I would love to learn more about this awesome implementation!

badPolynomial.zip

Segmentation fault

Hi,

I am using this package to compute the roots of a 3rd degree polynomial in a large function.

Basically if I did dR=roots([dφt,2*A,3*B,(1/α)]) (where all the parameters are all previously defined) dans tried to access dR[1] I had a seg fault. But I was able to show dR: dR = Complex{Float64}[-4.56475-7.96937im,-4.56475+7.96937im,9.12951+0.0im]

But if I do dR_1=roots([dφt,2*A,3*B,(1/α)]); dR = copy(dR_1)

I don't have the seg fault and I can manipulate dR fine... Is that normal?

I am running on Julia 0.5.2 and ubuntu 16.04
Thanks

PolynomialRoots Not found in Julia 1.0.1

I'm not sure why but this package does not seem to be in the registery.

julia> Pkg.add("PolynomialRoots")
  Updating registry at `~/programs/JuliaPro-1.0.1.1/JuliaPro/pkgs-1.0.1.1/registries/JuliaPro`
  Updating git-repo `https://pkg.juliacomputing.com/registry/JuliaPro`
ERROR: The following package names could not be resolved:
 * PolynomialRoots (not found in project, manifest or registry)
Please specify by known `name=uuid`.

I can still import it in julia 0.6.4 though.

Support for StaticArrays (MVector)

This package is strictly coded for Julia native Vectors. I can't tell just by looking at the source if there would be a performance speedup by generalizing the types to allow for S or MVectors of the StaticArrays package, but it's the preferred Julia style to allow as general type arguments as possible.

Support Windows

CmplxRoots.jl hasn't been tested on Windows and probably current build script won't work on that system. Also an AppVeyor recipe to test the package would be useful.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.