lib/roots.ex

defmodule Chi2fit.Roots do

  # Copyright 2015-2017 Pieter Rijken
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.

  @moduledoc """
  Solves roots for linear, quadratic, and cubic equations.
  """

  @doc """
  Returns the real roots of polynoms of order 1, 2 and 3 as a list.
  
  ## Examples
  
      Solve `2.0*x + 5.0 = 0`
      iex> solve [2.0,5.0]
      [-2.5]
      
      iex> solve [2.0,-14.0,24.0]
      [4.0,3.0]
      
      iex> solve [1.0,0.0,5.0,6.0]
      [-0.9999999999999999]
  """
  @spec solve([float]) :: [float]
  def solve([0.0|rest]), do: solve rest

  def solve([a1,a0]), do: [-a0/a1]

  def solve([a2,a1,a0]) do
    sqr = a1*a1-4*a2*a0
    cond do
      sqr == 0 -> -a1/2/a2
      sqr > 0 -> [(-a1+:math.sqrt(sqr))/2/a2,(-a1-:math.sqrt(sqr))/2/a2]
      true -> []
    end
  end

  def solve([1.0,0.0,p,q]) do
    ## For details see equations (83) and (84) in http://mathworld.wolfram.com/CubicFormula.html
    c = -0.5*q*:math.pow(3/abs(p),1.5)
    cond do
      p>0 ->
        [:math.sinh(1.0/3.0*:math.asinh(c))]
      c>=1 ->
        [:math.cosh(1.0/3.0*:math.acosh(c))]
      c<=-1 ->
        [-:math.cosh(1.0/3.0*:math.acosh(abs(c)))]
      true ->
        ## Three real solutions
        [:math.cos(1.0/3.0*:math.acos(c)),:math.cos(1.0/3.0*:math.acos(c) + 2*:math.pi()/3.0),:math.cos(1.0/3.0*:math.acos(c) + 4*:math.pi()/3.0)]
    end
    |> Enum.map(&(&1*2*:math.sqrt(abs(p)/3.0)))
  end
  def solve([1.0,a2,a1,a0]), do: solve([1.0,0.0,(3*a1-a2*a2)/3.0,(2*a2*a2*a2-9*a1*a2+27*a0)/27.0]) |> Enum.map(&(&1-a2/3.0))
  def solve([a3,a2,a1,a0]), do: solve([1.0,a2/a3,a1/a3,a0/a3])

end