-----------------------------------------------------------------------------
-- |
-- Module      :  ToySolver.Data.AlgebraicNumber.Complex
-- Copyright   :  (c) Masahiro Sakai 2013
-- License     :  BSD-style
-- 
-- Maintainer  :  masahiro.sakai@gmail.com
-- Stability   :  provisional
-- Portability :  portable
--
-- Algebraic Numbers
-- 
-----------------------------------------------------------------------------
module ToySolver.Data.AlgebraicNumber.Complex
    (
    -- * Rectangular form
      AComplex((:+))

    -- * Properties
    , realPart          -- :: AComplex -> AReal
    , imagPart          -- :: AComplex -> AReal
    , magnitude         -- :: AComplex -> AReal
    , minimalPolynomial -- :: AComplex -> UPolynomial Rational

    -- * Operations
    , conjugate         -- :: AComplex -> AComplex
    ) where

import Control.Monad
import qualified Data.Sign as Sign
import qualified Data.Map as Map
import Data.Maybe

import qualified ToySolver.CAD as CAD
import qualified ToySolver.Data.AlgebraicNumber.Real as AReal
import ToySolver.Data.AlgebraicNumber.Real (AReal)
import qualified ToySolver.Data.AlgebraicNumber.Root as Root
import qualified ToySolver.Data.Polynomial as P
import ToySolver.Data.Polynomial (Polynomial, UPolynomial, X (..))

infix  6  :+

-- -----------------------------------------------------------------------------
-- The Complex type

-- | Complex numbers are an algebraic type.
--
-- For a complex number @z@, @'abs' z@ is a number with the magnitude of @z@,
-- but oriented in the positive real direction, whereas @'signum' z@
-- has the phase of @z@, but unit magnitude.
data AComplex = !AReal :+ !AReal
    deriving (Eq, Show)

-- -----------------------------------------------------------------------------
-- Functions over Complex

-- | Extracts the real part of a complex number.
realPart :: AComplex -> AReal
realPart (x :+ _) = x

-- | Extracts the imaginary part of a complex number.
imagPart :: AComplex -> AReal
imagPart (_ :+ y) = y

-- | The conjugate of a complex number.
conjugate :: AComplex -> AComplex
conjugate (x :+ y) =  x :+ (-y)

-- | The nonnegative magnitude of a complex number.
magnitude :: AComplex -> AReal
magnitude (x :+ y) = AReal.nthRoot 2 (x*x + y*y)

-- -----------------------------------------------------------------------------
-- Instances of Complex

instance  Num AComplex where
    (x:+y) + (x':+y')   =  (x+x') :+ (y+y')
    (x:+y) - (x':+y')   =  (x-x') :+ (y-y')
    (x:+y) * (x':+y')   =  (x*x'-y*y') :+ (x*y'+y*x')
    negate (x:+y)       =  negate x :+ negate y
    abs z               =  magnitude z :+ 0
    signum (0:+0)       =  0
    signum z@(x:+y)     =  x/r :+ y/r  where r = magnitude z
    fromInteger n       =  fromInteger n :+ 0

instance  Fractional AComplex  where
    (x:+y) / (x':+y')   =  (x*x'+y*y') / d :+ (y*x'-x*y') / d
                           where d   = x'*x' + y'*y'
    fromRational a      =  fromRational a :+ 0

-- -----------------------------------------------------------------------------

-- | The polynomial of which the algebraic number is root.
minimalPolynomial :: AComplex -> UPolynomial Rational
minimalPolynomial z@(x :+ y) =
  head [q | (q,_) <- P.factor p, P.deg q > 0, P.eval (\X -> z) (P.mapCoeff fromRational q) == 0]
  where
    p = Root.findPoly (P.var a + P.var b * P.var i) ps
      where
        a, b, i :: Int
        i = 0
        a = 1
        b = 2
        ps =
          [ (P.var i) ^ 2 + 1
          , P.subst (AReal.minimalPolynomial x) (\X -> P.var a)
          , P.subst (AReal.minimalPolynomial y) (\X -> P.var b)
          ]

-- -----------------------------------------------------------------------------

-- | Roots of the polynomial
roots :: UPolynomial Rational -> [AComplex]
roots f = do
  let cs1 = [ (u, [Sign.Zero]), (v, [Sign.Zero]) ] 
  (cs2, cells2) <- CAD.project [(P.toUPolynomialOf p 0, ss) | (p,ss) <- cs1]
  let tmp2 = CAD.project [(P.toUPolynomialOf p 1, ss) | (p,ss) <- cs2]
  (cs3, cells3) <- tmp2
  guard $ and [Sign.signOf v `elem` ss | (p,ss) <- cs3, let v = P.eval (\_ -> undefined) p]

  let m3 = Map.empty
  yval <- catMaybes [CAD.findSample m3 cell | cell <- cells3]
  let m2 = Map.insert 1 yval m3
  xval <- catMaybes [CAD.findSample m2 cell | cell <- cells2]

  return $ xval :+ yval

  where

    -- f(x + yi) = u(x,y) + v(x,y)i
    f1 :: P.Polynomial Rational Int
    f1 = P.subst f (\X -> P.var 0 + P.var 1 * P.var 2)
    f2 = P.toUPolynomialOf (P.reduce P.grevlex f1 [P.var 2 * P.var 2 + 1]) 2 
    u = P.coeff P.mone f2
    v = P.coeff (P.var X) f2

-- -----------------------------------------------------------------------------

test1 = minimalPolynomial (sqrt2 :+ sqrt3)
  where
    sqrt2 = AReal.nthRoot 2 2
    sqrt3 = AReal.nthRoot 2 3

test2 = magnitude (sqrt2 :+ sqrt3)
  where
    sqrt2 = AReal.nthRoot 2 2
    sqrt3 = AReal.nthRoot 2 3

test3 = roots p
  where
    x = P.var X
    p = x - 2

test4 = roots p
  where
    x = P.var X
    p = x^2 - 2

test5 = roots p
  where
    x = P.var X
    p = x^3 - 1

test6 = roots p
  where
    x = P.var X
    p = x^4 + 2*x^2 + 25