{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE Trustworthy #-}
module Data.Complex.Cyclotomic
( Cyclotomic
, i
, e
, sqrtInteger
, sqrtRat
, sinDeg
, cosDeg
, sinRev
, cosRev
, gaussianRat
, polarRat
, polarRatDeg
, polarRatRev
, conj
, real
, imag
, isReal
, isRat
, isGaussianRat
, toComplex
, toReal
, toRat
, goldenRatio
, dft
, dftInv
, rootsQuadEq
, heron
)
where
import Data.List
( nub
)
import Data.Ratio
( (%)
, numerator
, denominator
)
import Data.Complex
( Complex(..)
, realPart
)
import qualified Data.Map as M
( Map
, empty
, singleton
, lookup
, keys
, elems
, size
, fromList
, toList
, mapKeys
, filter
, insertWith
, delete
, map
, unionWith
, findWithDefault
, fromListWith
)
import Math.NumberTheory.ArithmeticFunctions
( runFunction
, totientA
, smallOmegaA
, isNFreeA
)
import Math.NumberTheory.Primes
( unPrime
, factorise
)
data Cyclotomic = Cyclotomic { order :: Integer
, coeffs :: M.Map Integer Rational
} deriving (Eq)
instance Num Cyclotomic where
(+) = sumCyc
(*) = prodCyc
(-) c1 c2 = sumCyc c1 (aInvCyc c2)
negate = aInvCyc
abs = absVal
signum = sigNum
fromInteger 0 = zeroCyc
fromInteger n = Cyclotomic 1 (M.singleton 0 (fromIntegral n))
instance Fractional Cyclotomic where
recip = invCyc
fromRational 0 = zeroCyc
fromRational r = Cyclotomic 1 (M.singleton 0 r)
e :: Integer -> Cyclotomic
e n
| n < 1 = error "e requires a positive integer"
| n == 1 = Cyclotomic 1 (M.singleton 0 1)
| otherwise = cyclotomic n $ convertToBase n (M.singleton 1 1)
instance Show Cyclotomic where
show (Cyclotomic n mp)
| mp == M.empty = "0"
| otherwise = leadingTerm rat n ex ++ followingTerms n xs
where ((ex,rat):xs) = M.toList mp
showBaseExp :: Integer -> Integer -> String
showBaseExp n 1 = "e(" ++ show n ++ ")"
showBaseExp n ex = "e(" ++ show n ++ ")^" ++ show ex
leadingTerm :: Rational -> Integer -> Integer -> String
leadingTerm r _ 0 = showRat r
leadingTerm r n ex
| r == 1 = t
| r == (-1) = '-' : t
| r > 0 = showRat r ++ "*" ++ t
| r < 0 = "-" ++ showRat (abs r) ++ "*" ++ t
| otherwise = ""
where t = showBaseExp n ex
followingTerms :: Integer -> [(Integer,Rational)] -> String
followingTerms _ [] = ""
followingTerms n ((ex,rat):xs) = followingTerm rat n ex ++ followingTerms n xs
followingTerm :: Rational -> Integer -> Integer -> String
followingTerm r n ex
| r == 1 = " + " ++ t
| r == (-1) = " - " ++ t
| r > 0 = " + " ++ showRat r ++ "*" ++ t
| r < 0 = " - " ++ showRat (abs r) ++ "*" ++ t
| otherwise = ""
where t = showBaseExp n ex
showRat :: Rational -> String
showRat r
| d == 1 = show n
| otherwise = show n ++ "/" ++ show d
where
n = numerator r
d = denominator r
eb :: Integer -> Cyclotomic
eb n
| n < 1 = error "eb needs a positive integer"
| n `mod` 2 /= 1 = error "eb needs an odd integer"
| n == 1 = zeroCyc
| otherwise = let en = e n
in sum [en^(k*k `mod` n) | k <- [1..(n-1) `div` 2]]
sqrt2 :: Cyclotomic
sqrt2 = e 8 - e 8 ^ (3 :: Int)
sqrtInteger :: Integer -> Cyclotomic
sqrtInteger n
| n == 0 = zeroCyc
| n < 0 = i * sqrtPositiveInteger (-n)
| otherwise = sqrtPositiveInteger n
sqrtPositiveInteger :: Integer -> Cyclotomic
sqrtPositiveInteger n
| n < 1 = error "sqrtPositiveInteger needs a positive integer"
| otherwise = let factors = factorise n
factor = product [unPrime p ^ (m `div` 2) | (p, m) <- factors]
nn = product [unPrime p ^ (m `mod` 2) | (p, m) <- factors]
in case nn `mod` 4 of
1 -> fromInteger factor * (2 * eb nn + 1)
2 -> fromInteger factor * sqrt2 * sqrtPositiveInteger (nn `div` 2)
3 -> fromInteger factor * (-i) * (2 * eb nn + 1)
_ -> fromInteger factor * 2 * sqrtPositiveInteger (nn `div` 4)
sqrtRat :: Rational -> Cyclotomic
sqrtRat r = prodRatCyc (1 % fromInteger den) (sqrtInteger (numerator r * den))
where
den = denominator r
i :: Cyclotomic
i = e 4
gaussianRat :: Rational -> Rational -> Cyclotomic
gaussianRat p q = fromRational p + fromRational q * i
polarRat :: Rational
-> Rational
-> Cyclotomic
polarRat r s
= let p = numerator s
q = denominator s
in case p >= 0 of
True -> fromRational r * e q ^ p
False -> conj $ fromRational r * e q ^ (-p)
polarRatDeg :: Rational
-> Rational
-> Cyclotomic
polarRatDeg r deg
= let s = deg / 360
p = numerator s
q = denominator s
in case p >= 0 of
True -> fromRational r * e q ^ p
False -> conj $ fromRational r * e q ^ (-p)
polarRatRev :: Rational
-> Rational
-> Cyclotomic
polarRatRev r s
= let p = numerator s
q = denominator s
in case p >= 0 of
True -> fromRational r * e q ^ p
False -> conj $ fromRational r * e q ^ (-p)
conj :: Cyclotomic -> Cyclotomic
conj (Cyclotomic n mp)
= mkCyclotomic n (M.mapKeys (\k -> (n-k) `mod` n) mp)
real :: Cyclotomic -> Cyclotomic
real z = (z + conj z) / 2
imag :: Cyclotomic -> Cyclotomic
imag z = (z - conj z) / (2*i)
absVal :: Cyclotomic -> Cyclotomic
absVal c = let modsq = c * conj c
in case toRat modsq of
Just msq -> sqrtRat msq
Nothing -> error "abs not available for this number"
sigNum :: Cyclotomic -> Cyclotomic
sigNum 0 = zeroCyc
sigNum c = let modsq = c * conj c
in if isRat modsq then c / absVal c
else error "signum not available for this number"
convertToBase :: Integer -> M.Map Integer Rational -> M.Map Integer Rational
convertToBase n mp = foldr (\(p,r) m -> replace n p r m) mp (extraneousPowers n)
removeZeros :: M.Map Integer Rational -> M.Map Integer Rational
removeZeros = M.filter (/= 0)
cyclotomic :: Integer -> M.Map Integer Rational -> Cyclotomic
cyclotomic ord = tryReduce . tryRational . gcdReduce . Cyclotomic ord
mkCyclotomic :: Integer -> M.Map Integer Rational -> Cyclotomic
mkCyclotomic ord = cyclotomic ord . removeZeros . convertToBase ord
gcdReduce :: Cyclotomic -> Cyclotomic
gcdReduce cyc@(Cyclotomic n mp) = case gcdCyc cyc of
1 -> cyc
d -> Cyclotomic (n `div` d) (M.mapKeys (`div` d) mp)
gcdCyc :: Cyclotomic -> Integer
gcdCyc (Cyclotomic n mp) = gcdList (n:M.keys mp)
tryRational :: Cyclotomic -> Cyclotomic
tryRational c
| lenCyc c == fromIntegral phi && sqfree
= case equalCoefficients c of
Nothing -> c
Just r -> fromRational $ (-1)^(nrp `mod` 2)*r
| otherwise
= c
where
(phi,nrp,sqfree) = phiNrpSqfree (order c)
phiNrpSqfree :: Integer -> (Integer, Int, Bool)
phiNrpSqfree = runFunction $ (,,) <$> totientA <*> smallOmegaA <*> isNFreeA 2
equalCoefficients :: Cyclotomic -> Maybe Rational
equalCoefficients (Cyclotomic _ mp)
= case ts of
[] -> Nothing
(x:_) -> if equal ts then Just x else Nothing
where
ts = M.elems mp
lenCyc :: Cyclotomic -> Int
lenCyc (Cyclotomic _ mp) = M.size $ removeZeros mp
tryReduce :: Cyclotomic -> Cyclotomic
tryReduce c
= foldr reduceByPrime c squareFreeOddFactors
where
squareFreeOddFactors = [unPrime p | (p, m) <- factorise (order c), unPrime p > 2, m <= 1]
reduceByPrime :: Integer -> Cyclotomic -> Cyclotomic
reduceByPrime p c@(Cyclotomic n _)
= case mapM (\r -> equalReplacements p r c) [0,p..n-p] of
Just cfs -> Cyclotomic (n `div` p) $ removeZeros $ M.fromList $ zip [0..(n `div` p)-1] (map negate cfs)
Nothing -> c
equalReplacements :: Integer -> Integer -> Cyclotomic -> Maybe Rational
equalReplacements p r (Cyclotomic n mp)
= case [M.findWithDefault 0 k mp | k <- replacements n p r] of
[] -> error "equalReplacements generated empty list"
(x:xs) | equal (x:xs) -> Just x
_ -> Nothing
replacements :: Integer -> Integer -> Integer -> [Integer]
replacements n p r = takeWhile (>= 0) [r-s,r-2*s..] ++ takeWhile (< n) [r+s,r+2*s..]
where s = n `div` p
replace :: Integer -> Integer -> Integer -> M.Map Integer Rational -> M.Map Integer Rational
replace n p r mp = case M.lookup r mp of
Nothing -> mp
Just rat -> foldr (\k m -> M.insertWith (+) k (-rat) m) (M.delete r mp) (replacements n p r)
includeMods :: Integer -> Integer -> Integer -> [Integer]
includeMods n q start = [start] ++ takeWhile (>= 0) [start-q,start-2*q..] ++ takeWhile (< n) [start+q,start+2*q..]
removeExps :: Integer -> Integer -> Integer -> [Integer]
removeExps n 2 q = concatMap (includeMods n q) $ map ((n `div` q) *) [q `div` 2..q-1]
removeExps n p q = concatMap (includeMods n q) $ map ((n `div` q) *) [-m..m]
where m = (q `div` p - 1) `div` 2
pqPairs :: Integer -> [(Integer, Integer)]
pqPairs n = map (\(p, k) -> (unPrime p, unPrime p ^ k)) (factorise n)
extraneousPowers :: Integer -> [(Integer,Integer)]
extraneousPowers n
| n < 1 = error "extraneousPowers needs a positive integer"
| otherwise = nub $ concat [[(p,r) | r <- removeExps n p q] | (p,q) <- pqPairs n]
sumCyc :: Cyclotomic -> Cyclotomic -> Cyclotomic
sumCyc (Cyclotomic o1 map1) (Cyclotomic o2 map2)
= let ord = lcm o1 o2
m1 = ord `div` o1
m2 = ord `div` o2
map1' = M.mapKeys (m1*) map1
map2' = M.mapKeys (m2*) map2
in mkCyclotomic ord $ M.unionWith (+) map1' map2'
prodCyc :: Cyclotomic -> Cyclotomic -> Cyclotomic
prodCyc (Cyclotomic o1 map1) (Cyclotomic o2 map2)
= let ord = lcm o1 o2
m1 = ord `div` o1
m2 = ord `div` o2
in mkCyclotomic ord $ M.fromListWith (+)
[((m1*e1+m2*e2) `mod` ord,c1*c2) | (e1,c1) <- M.toList map1, (e2,c2) <- M.toList map2]
prodRatCyc :: Rational -> Cyclotomic -> Cyclotomic
prodRatCyc 0 _ = zeroCyc
prodRatCyc r (Cyclotomic ord mp) = Cyclotomic ord $ M.map (r*) mp
zeroCyc :: Cyclotomic
zeroCyc = Cyclotomic 1 M.empty
aInvCyc :: Cyclotomic -> Cyclotomic
aInvCyc = prodRatCyc (-1)
multiplyExponents :: Integer -> Cyclotomic -> Cyclotomic
multiplyExponents j (Cyclotomic n mp)
| gcd j n /= 1 = error "multiplyExponents needs gcd == 1"
| otherwise = mkCyclotomic n (M.mapKeys (\k -> j*k `mod` n) mp)
productOfGaloisConjugates :: Cyclotomic -> Cyclotomic
productOfGaloisConjugates c
= product [multiplyExponents j c | j <- [2..ord], gcd j ord == 1]
where
ord = order c
invCyc :: Cyclotomic -> Cyclotomic
invCyc z = case toRat (z * prod) of
Just r -> prodRatCyc (1 / r) prod
Nothing -> error "invCyc: product of Galois conjugates not rational; this is a bug, please inform package maintainer"
where
prod = productOfGaloisConjugates z
isReal :: Cyclotomic -> Bool
isReal c = c == conj c
isRat :: Cyclotomic -> Bool
isRat (Cyclotomic 1 _) = True
isRat _ = False
isGaussianRat :: Cyclotomic -> Bool
isGaussianRat c = isRat (real c) && isRat (imag c)
toComplex :: RealFloat a => Cyclotomic -> Complex a
toComplex c = sum [fromRational r * en^p | (p,r) <- M.toList (coeffs c)]
where en = exp (0 :+ 2*pi/n)
n = fromIntegral (order c)
toReal :: RealFloat a => Cyclotomic -> Maybe a
toReal c
| isReal c = Just $ realPart (toComplex c)
| otherwise = Nothing
toRat :: Cyclotomic -> Maybe Rational
toRat (Cyclotomic 1 mp)
| mp == M.empty = Just 0
| otherwise = M.lookup 0 mp
toRat _ = Nothing
sinDeg :: Rational -> Cyclotomic
sinDeg d = let n = d / 360
nm = abs (numerator n)
dn = denominator n
a = e dn^nm
in fromRational(signum d) * (a - conj a) / (2*i)
cosDeg :: Rational -> Cyclotomic
cosDeg d = let n = d / 360
nm = abs (numerator n)
dn = denominator n
a = e dn^nm
in (a + conj a) / 2
sinRev :: Rational -> Cyclotomic
sinRev n = let nm = abs (numerator n)
dn = denominator n
a = e dn^nm
in fromRational(signum n) * (a - conj a) / (2*i)
cosRev :: Rational -> Cyclotomic
cosRev n = let nm = abs (numerator n)
dn = denominator n
a = e dn^nm
in (a + conj a) / 2
gcdList :: [Integer] -> Integer
gcdList [] = error "gcdList called on empty list"
gcdList (n:ns) = foldr gcd n ns
equal :: Eq a => [a] -> Bool
equal [] = True
equal [_] = True
equal (x:y:ys) = x == y && equal (y:ys)
goldenRatio :: Cyclotomic
goldenRatio = (1 + sqrtRat 5) / 2
dft :: [Cyclotomic] -> [Cyclotomic]
dft cs = [sum $ zipWith (*) [conj (e m^(k*n)) | n <- [0..]] cs | k <- [0..m-1]]
where m = fromIntegral $ length cs
dftInv :: [Cyclotomic] -> [Cyclotomic]
dftInv cs = [minv * sum (zipWith (*) [e m^(k*n) | n <- [0..]] cs) | k <- [0..m-1]]
where m = fromIntegral $ length cs
minv = fromRational (1 % m)
rootsQuadEq :: Rational
-> Rational
-> Rational
-> Maybe (Cyclotomic,Cyclotomic)
rootsQuadEq a b c
| a == 0 = Nothing
| otherwise = Just ((-bb + sqrtDisc)/(2*aa),(-bb - sqrtDisc)/(2*aa))
where
aa = fromRational a
bb = fromRational b
sqrtDisc = sqrtRat (b*b - 4*a*c)
heron :: Rational
-> Rational
-> Rational
-> Cyclotomic
heron a b c
= sqrtRat (s * (s-a) * (s-b) * (s-c))
where
s = (a + b + c) / 2