{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Data.SBV.Core.AlgReals (
AlgReal(..)
, AlgRealPoly(..)
, mkPolyReal
, algRealToSMTLib2
, algRealToHaskell
, mergeAlgReals
, isExactRational
, algRealStructuralEqual
, algRealStructuralCompare)
where
import Data.List (sortBy, isPrefixOf, partition)
import Data.Ratio ((%), numerator, denominator)
import Data.Function (on)
import System.Random
import Test.QuickCheck (Arbitrary(..))
data AlgReal = AlgRational Bool Rational
| AlgPolyRoot (Integer, AlgRealPoly) (Maybe String)
isExactRational :: AlgReal -> Bool
isExactRational (AlgRational True _) = True
isExactRational _ = False
newtype AlgRealPoly = AlgRealPoly [(Integer, Integer)]
deriving (Eq, Ord)
mkPolyReal :: Either (Bool, String) (Integer, [(Integer, Integer)]) -> AlgReal
mkPolyReal (Left (exact, str))
= case (str, break (== '.') str) of
("", (_, _)) -> AlgRational exact 0
(_, (x, '.':y)) -> AlgRational exact (read (x++y) % (10 ^ length y))
(_, (x, _)) -> AlgRational exact (read x % 1)
mkPolyReal (Right (k, coeffs))
= AlgPolyRoot (k, AlgRealPoly (normalize coeffs)) Nothing
where normalize :: [(Integer, Integer)] -> [(Integer, Integer)]
normalize = merge . sortBy (flip compare `on` snd)
merge [] = []
merge [x] = [x]
merge ((a, b):r@((c, d):xs))
| b == d = merge ((a+c, b):xs)
| True = (a, b) : merge r
instance Show AlgRealPoly where
show (AlgRealPoly xs) = chkEmpty (join (concat [term p | p@(_, x) <- xs, x /= 0])) ++ " = " ++ show c
where c = -1 * head ([k | (k, 0) <- xs] ++ [0])
term ( 0, _) = []
term ( 1, 1) = [ "x"]
term ( 1, p) = [ "x^" ++ show p]
term (-1, 1) = ["-x"]
term (-1, p) = ["-x^" ++ show p]
term (k, 1) = [show k ++ "x"]
term (k, p) = [show k ++ "x^" ++ show p]
join [] = ""
join (k:ks) = k ++ s ++ join ks
where s = case ks of
[] -> ""
(y:_) | "-" `isPrefixOf` y -> ""
| "+" `isPrefixOf` y -> ""
| True -> "+"
chkEmpty s = if null s then "0" else s
instance Show AlgReal where
show (AlgRational exact a) = showRat exact a
show (AlgPolyRoot (i, p) mbApprox) = "root(" ++ show i ++ ", " ++ show p ++ ")" ++ maybe "" app mbApprox
where app v | last v == '?' = " = " ++ init v ++ "..."
| True = " = " ++ v
lift1 :: String -> (Rational -> Rational) -> AlgReal -> AlgReal
lift1 _ o (AlgRational e a) = AlgRational e (o a)
lift1 nm _ a = error $ "AlgReal." ++ nm ++ ": unsupported argument: " ++ show a
lift2 :: String -> (Rational -> Rational -> Rational) -> AlgReal -> AlgReal -> AlgReal
lift2 _ o (AlgRational True a) (AlgRational True b) = AlgRational True (a `o` b)
lift2 nm _ a b = error $ "AlgReal." ++ nm ++ ": unsupported arguments: " ++ show (a, b)
instance Eq AlgReal where
AlgRational True a == AlgRational True b = a == b
a == b = error $ "AlgReal.==: unsupported arguments: " ++ show (a, b)
instance Ord AlgReal where
AlgRational True a `compare` AlgRational True b = a `compare` b
a `compare` b = error $ "AlgReal.compare: unsupported arguments: " ++ show (a, b)
algRealStructuralEqual :: AlgReal -> AlgReal -> Bool
AlgRational a b `algRealStructuralEqual` AlgRational c d = (a, b) == (c, d)
AlgPolyRoot a b `algRealStructuralEqual` AlgPolyRoot c d = (a, b) == (c, d)
_ `algRealStructuralEqual` _ = False
algRealStructuralCompare :: AlgReal -> AlgReal -> Ordering
AlgRational a b `algRealStructuralCompare` AlgRational c d = (a, b) `compare` (c, d)
AlgRational _ _ `algRealStructuralCompare` AlgPolyRoot _ _ = LT
AlgPolyRoot _ _ `algRealStructuralCompare` AlgRational _ _ = GT
AlgPolyRoot a b `algRealStructuralCompare` AlgPolyRoot c d = (a, b) `compare` (c, d)
instance Num AlgReal where
(+) = lift2 "+" (+)
(*) = lift2 "*" (*)
(-) = lift2 "-" (-)
negate = lift1 "negate" negate
abs = lift1 "abs" abs
signum = lift1 "signum" signum
fromInteger = AlgRational True . fromInteger
instance Fractional AlgReal where
(AlgRational True _) / (AlgRational True b) | b == 0 = 0
a / b = lift2 "/" (/) a b
fromRational = AlgRational True
instance Real AlgReal where
toRational (AlgRational True v) = v
toRational x = error $ "AlgReal.toRational: Argument cannot be represented as a rational value: " ++ algRealToHaskell x
instance Random Rational where
random g = (a % b', g'')
where (a, g') = random g
(b, g'') = random g'
b' = if 0 < b then b else 1 - b
randomR (l, h) g = (r * d + l, g'')
where (b, g') = random g
b' = if 0 < b then b else 1 - b
(a, g'') = randomR (0, b') g'
r = a % b'
d = h - l
instance Random AlgReal where
random g = let (a, g') = random g in (AlgRational True a, g')
randomR (AlgRational True l, AlgRational True h) g = let (a, g') = randomR (l, h) g in (AlgRational True a, g')
randomR lh _ = error $ "AlgReal.randomR: unsupported bounds: " ++ show lh
algRealToSMTLib2 :: AlgReal -> String
algRealToSMTLib2 (AlgRational True r)
| m == 0 = "0.0"
| m < 0 = "(- (/ " ++ show (abs m) ++ ".0 " ++ show n ++ ".0))"
| True = "(/ " ++ show m ++ ".0 " ++ show n ++ ".0)"
where (m, n) = (numerator r, denominator r)
algRealToSMTLib2 r@(AlgRational False _)
= error $ "SBV: Unexpected inexact rational to be converted to SMTLib2: " ++ show r
algRealToSMTLib2 (AlgPolyRoot (i, AlgRealPoly xs) _) = "(root-obj (+ " ++ unwords (concatMap term xs) ++ ") " ++ show i ++ ")"
where term (0, _) = []
term (k, 0) = [coeff k]
term (1, 1) = ["x"]
term (1, p) = ["(^ x " ++ show p ++ ")"]
term (k, 1) = ["(* " ++ coeff k ++ " x)"]
term (k, p) = ["(* " ++ coeff k ++ " (^ x " ++ show p ++ "))"]
coeff n | n < 0 = "(- " ++ show (abs n) ++ ")"
| True = show n
algRealToHaskell :: AlgReal -> String
algRealToHaskell (AlgRational True r) = "((" ++ show r ++ ") :: Rational)"
algRealToHaskell r = error $ "SBV.algRealToHaskell: Unsupported argument: " ++ show r
showRat :: Bool -> Rational -> String
showRat exact r = p $ case f25 (denominator r) [] of
Nothing -> show r
Just (noOfZeros, num) -> let present = length num
in neg $ case noOfZeros `compare` present of
LT -> let (b, a) = splitAt (present - noOfZeros) num in b ++ "." ++ if null a then "0" else a
EQ -> "0." ++ num
GT -> "0." ++ replicate (noOfZeros - present) '0' ++ num
where p = if exact then id else (++ "...")
neg = if r < 0 then ('-':) else id
f25 :: Integer -> [Integer] -> Maybe (Int, String)
f25 1 sofar = let (ts, fs) = partition (== 2) sofar
[lts, lfs] = map length [ts, fs]
noOfZeros = lts `max` lfs
in Just (noOfZeros, show (abs (numerator r) * factor ts fs))
f25 v sofar = let (q2, r2) = v `quotRem` 2
(q5, r5) = v `quotRem` 5
in case (r2, r5) of
(0, _) -> f25 q2 (2 : sofar)
(_, 0) -> f25 q5 (5 : sofar)
_ -> Nothing
factor [] fs = product [2 | _ <- fs]
factor ts [] = product [5 | _ <- ts]
factor (_:ts) (_:fs) = factor ts fs
mergeAlgReals :: String -> AlgReal -> AlgReal -> AlgReal
mergeAlgReals _ f@(AlgRational exact r) (AlgPolyRoot kp Nothing)
| exact = f
| True = AlgPolyRoot kp (Just (showRat False r))
mergeAlgReals _ (AlgPolyRoot kp Nothing) f@(AlgRational exact r)
| exact = f
| True = AlgPolyRoot kp (Just (showRat False r))
mergeAlgReals _ f@(AlgRational e1 r1) s@(AlgRational e2 r2)
| (e1, r1) == (e2, r2) = f
| e1 = f
| e2 = s
mergeAlgReals m _ _ = error m
instance Arbitrary AlgReal where
arbitrary = AlgRational True `fmap` arbitrary