mfsolve 0.1.0 → 0.2.0
raw patch · 3 files changed
+516/−206 lines, 3 filesdep +mtlPVP ok
version bump matches the API change (PVP)
Dependencies added: mtl
API changes (from Hackage documentation)
- Math.MFSolve: emptyDeps :: Dependencies v n
- Math.MFSolve: instance Hashable n => Hashable (UnaryFun n)
- Math.MFSolve: instance Show n => Show (DepError n)
- Math.MFSolve: simpleExpr :: (Num n, Eq n) => Expr v n -> SimpleExpr v n
- Math.MFSolve: solveEqs :: Dependencies v n -> [Dependencies v n -> Either (DepError n) (Dependencies v n)] -> Either (DepError n) (Dependencies v n)
+ Math.MFSolve: UndefinedVar :: v -> DepError v n
+ Math.MFSolve: UnknownVar :: v -> n -> DepError v n
+ Math.MFSolve: addEquation :: (Hashable n, Hashable v, RealFrac (Phase n), Ord v, Floating n) => Dependencies v n -> Expr v n -> Either (DepError v n) (Dependencies v n)
+ Math.MFSolve: data MFSolver v n a
+ Math.MFSolve: dependencies :: MonadState (Dependencies v n) m => m (Dependencies v n)
+ Math.MFSolve: eliminate :: (Hashable n, Show n, Hashable v, RealFrac (Phase n), Ord v, Show v, Floating n) => Dependencies v n -> v -> (Dependencies v n, [Expr v n])
+ Math.MFSolve: eliminateM :: (MonadState (Dependencies v n) m, Hashable n, Hashable v, Show n, Show v, RealFrac n, Ord v, Floating n) => v -> m [Expr v n]
+ Math.MFSolve: evalExpr :: Floating n => (v -> n) -> SimpleExpr v n -> n
+ Math.MFSolve: evalSimple :: Floating m => (n -> m) -> (v -> m) -> SimpleExpr v n -> m
+ Math.MFSolve: evalSolver :: MFSolver v n a -> Dependencies v n -> Either (DepError v n) a
+ Math.MFSolve: execSolver :: MFSolver v n a -> Dependencies v n -> Either (DepError v n) (Dependencies v n)
+ Math.MFSolve: fromSimple :: (Floating n, Ord n, Ord v) => SimpleExpr v n -> Expr v n
+ Math.MFSolve: getKnownM :: (MonadState (Dependencies v n) m, Hashable v, Eq v) => v -> m (Either [v] n)
+ Math.MFSolve: getValue :: (MonadState (Dependencies v n) m, MonadError (DepError v n) m, Eq v, Hashable v) => v -> m n
+ Math.MFSolve: hasVar :: (Num t, Eq v, Eq t) => v -> Expr v t -> Bool
+ Math.MFSolve: ignore :: MonadError (DepError v n) m => m () -> m ()
+ Math.MFSolve: instance (Show n, Show v) => Show (DepError v n)
+ Math.MFSolve: instance (Show v, Show n, Typeable v, Typeable n) => Exception (DepError v n)
+ Math.MFSolve: instance Applicative (MFSolver v n)
+ Math.MFSolve: instance Functor (MFSolver v n)
+ Math.MFSolve: instance Monad (MFSolver v n)
+ Math.MFSolve: instance MonadError (DepError v n) (MFSolver v n)
+ Math.MFSolve: instance MonadState (Dependencies v n) (MFSolver v n)
+ Math.MFSolve: instance Typeable DepError
+ Math.MFSolve: noDeps :: Dependencies v n
+ Math.MFSolve: runSolver :: MFSolver v n a -> Dependencies v n -> Either (DepError v n) (Dependencies v n, a)
+ Math.MFSolve: toSimple :: (Num n, Eq n) => Expr v n -> SimpleExpr v n
+ Math.MFSolve: unsafeSolve :: (Typeable n, Typeable v, Show n, Show v) => MFSolver v n a -> Dependencies v n -> a
+ Math.MFSolve: varDefinedM :: (MonadState (Dependencies v n) m, Hashable v, Eq v) => v -> m Bool
- Math.MFSolve: (=&=) :: (Hashable n, Hashable v, RealFrac (Phase n), Ord v, Floating n) => (Expr v n, Expr v n) -> (Expr v n, Expr v n) -> Dependencies v n -> Either (DepError n) (Dependencies v n)
+ Math.MFSolve: (=&=) :: (MonadState (Dependencies v n) m, MonadError (DepError v n) m, Eq v, Hashable v, Hashable n, RealFrac n, Floating n, Ord v) => (Expr v n, Expr v n) -> (Expr v n, Expr v n) -> m ()
- Math.MFSolve: (===) :: (Hashable n, Hashable v, RealFrac (Phase n), Ord v, Floating n) => Expr v n -> Expr v n -> Dependencies v n -> Either (DepError n) (Dependencies v n)
+ Math.MFSolve: (===) :: (MonadState (Dependencies v n) m, MonadError (DepError v n) m, Eq v, Hashable v, Hashable n, RealFrac n, Floating n, Ord v) => Expr v n -> Expr v n -> m ()
- Math.MFSolve: InconsistentEq :: n -> DepError n
+ Math.MFSolve: InconsistentEq :: n -> DepError v n
- Math.MFSolve: RedundantEq :: DepError n
+ Math.MFSolve: RedundantEq :: DepError v n
- Math.MFSolve: data DepError n
+ Math.MFSolve: data DepError v n
- Math.MFSolve: getKnown :: (Eq v, Hashable v) => Dependencies v n -> v -> Either [v] n
+ Math.MFSolve: getKnown :: (Eq v, Hashable v) => v -> Dependencies v n -> Either [v] n
- Math.MFSolve: showVars :: (Show n, Show v, Show a, Ord n, Ord v, Floating n) => Either (DepError a) (Dependencies v n) -> IO ()
+ Math.MFSolve: showVars :: (Show n, Show v, Ord n, Ord v, Floating n) => Either (DepError v n) (Dependencies v n) -> IO ()
- Math.MFSolve: varDefined :: (Eq v, Hashable v) => Dependencies v n -> v -> Bool
+ Math.MFSolve: varDefined :: (Eq v, Hashable v) => v -> Dependencies v n -> Bool
Files
- Math/MFSolve.hs +480/−201
- mfsolve.cabal +3/−2
- tests/test.hs +33/−3
Math/MFSolve.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveGeneric, PatternGuards, ViewPatterns #-}+{-# LANGUAGE DeriveGeneric, PatternGuards, PatternSynonyms, MultiParamTypeClasses, FlexibleContexts, DeriveDataTypeable #-} {-| Module : Math.MFSolve@@ -7,7 +7,7 @@ License : BSD-3 Maintainer : kristof@resonata.be Stability : unstable-Portability : portable+Portability : ghc This module implements an equation solver that solves and evaluates expressions on the fly. It is based on Prof. D.E.Knuth's@@ -29,45 +29,44 @@ Solve linear equations: -> showVars $ solveEqs emptyDeps-> [ 2*x + y === 5,-> x - y === 1]+> showVars $ flip execSolver noDeps $ do+> 2*x + y === 5+> x - y === 1 > x = 2.0 > y = 1.0 Solve for angle (pi/4): -> showVars $ solveEqs emptyDeps-> [ sin(t) === 1/sqrt(2) ]+> showVars $ flip execSolver noDeps $ sin(t) === 1/sqrt(2) > t = 0.7853981633974484 Solve for angle (pi/3) and amplitude: -> showVars $ solveEqs emptyDeps-> [ a*sin(x) === sqrt 3,-> a*cos(x) === 1 ]+> showVars $ flip execSolver noDeps $ do+> a*sin(x) === sqrt 3+> a*cos(x) === 1 > x = 1.0471975511965979 > a = 2.0 Allow nonlinear expression with unknown variables: -> showVars $ solveEqs emptyDeps-> [ sin(sqrt(x)) === y,-> x === 2]+> showVars $ flip execSolver noDeps $ do+> sin(sqrt(x)) === y+> x === 2 >x = 2.0 >y = 0.9877659459927355 Find the angle and amplitude when using a rotation matrix: -> showVars $ solveEqs emptyDeps-> [ a*cos t*x - a*sin t*y === 30,-> a*sin t*x + a*cos t*y === 40,-> x === 10,-> y === 10 ]+> showVars $ flip execSolver noDeps $ do+> a*cos t*x - a*sin t*y === 30+> a*sin t*x + a*cos t*y === 40+> x === 10+> y === 10 > x = 10.0 > y = 10.0@@ -77,26 +76,34 @@ -} module Math.MFSolve- (SimpleExpr(..), Expr, LinExpr(..), UnaryOp(..), BinaryOp(..),+ (-- * Types+ MFSolver, SimpleExpr(..), Expr, LinExpr(..), UnaryOp(..), BinaryOp(..), Dependencies, DepError(..), SimpleVar(..),+ -- * Expressions+ evalSimple, evalExpr, fromSimple, toSimple, makeVariable,+ makeConstant, hasVar, + -- * Dependencies getKnown, knownVars, varDefined, nonlinearEqs, dependendVars,- simpleExpr, emptyDeps, makeVariable, makeConstant,- (===), (=&=), solveEqs, showVars)+ noDeps, + eliminate, addEquation,+ -- * Monadic Interface+ dependencies, getValue, getKnownM, varDefinedM, eliminateM,+ (=&=), (===), ignore, + runSolver, evalSolver, execSolver, unsafeSolve, showVars) where import qualified Data.HashMap.Strict as M import qualified Data.HashSet as H import GHC.Generics+import Control.Monad.Except+import Control.Monad.State+import Control.Exception+import Data.Typeable+import Control.Applicative hiding (Const) import Data.Hashable import Data.Maybe import Data.List import Data.Function(on)-import Control.Monad -infixr 1 === , =&=---- | _labeled_ black box mathematical functions-data UnaryFun n = UnaryFun UnaryOp (n -> n)- data BinaryOp = Add | Mul deriving Eq data UnaryOp =@@ -117,7 +124,7 @@ -- | An mathematical expression of several variables. data Expr v n = Expr (LinExpr v n) [TrigTerm v n] [NonLinExpr v n]- deriving Generic+ deriving (Generic) -- | A linear expression of several variables. -- For example: @2*a + 3*b + 2@ would be represented as@@ -137,7 +144,7 @@ -- Any other term data NonLinExpr v n = - UnaryApp (UnaryFun n) (Expr v n) |+ UnaryApp UnaryOp (Expr v n) | MulExp (Expr v n) (Expr v n) | SinExp (Expr v n) deriving Generic@@ -149,10 +156,12 @@ type TrigEq2 v n = M.HashMap (Period v n) (M.HashMap v (Expr v n)) +pattern LinearE l = Expr l [] []+pattern ConstE c = Expr (LinExpr c []) [] []+pattern LConst c = LinExpr c []+ instance (Hashable v, Hashable n) => Hashable (LinExpr v n) instance (Hashable v, Hashable n) => Hashable (NonLinExpr v n)-instance (Hashable n) => Hashable (UnaryFun n) where- hashWithSalt s (UnaryFun o _) = hashWithSalt s o instance Hashable UnaryOp instance (Hashable v, Hashable n) => Hashable (Expr v n) instance Hashable SimpleVar@@ -176,7 +185,11 @@ [Expr v n] -- | An error type for '===', '=&=' and 'solveEq':-data DepError n =+data DepError v n =+ -- | 'UndefinedVar' @v@: The variable is not defined.+ UndefinedVar v |+ -- | 'UnknownVar' @v@: The variable is defined but dependend an other variables.+ UnknownVar v n | -- | 'InconsistentEq' @a@: The equation was reduced to the -- impossible equation `a == 0` for nonzero a, which means the -- equation is inconsistent with previous equations.@@ -184,10 +197,45 @@ -- | 'RedundantEq': The equation was reduced to the redundant equation 0 == 0, which -- means it doesn't add any information. RedundantEq+ deriving Typeable +instance (Show v, Show n, Typeable v, Typeable n) => Exception (DepError v n)+ instance (Ord n, Num n, Eq n, Show v, Show n) => Show (Expr v n) where- show e = show (simpleExpr e)+ show e = show (toSimple e) +-- | A monad for solving equations. Basicly just a state and exception monad.+newtype MFSolver v n a = MFSolver {+ -- | Unwrap a solver monad as a function.+ runSolver :: Dependencies v n -> Either (DepError v n) (Dependencies v n, a) }++instance Monad (MFSolver v n) where+ MFSolver s >>= f = MFSolver $ \dep ->+ case s dep of+ Left err -> Left err+ Right (dep2, val) ->+ runSolver (f val) dep2+ return a = MFSolver $ \dep -> Right (dep, a)++instance (MonadState (Dependencies v n)) (MFSolver v n) where+ get = MFSolver $ \dep -> Right (dep, dep)+ put dep = MFSolver $ const $ Right (dep, ())+ +instance (MonadError (DepError v n)) (MFSolver v n) where+ throwError e = MFSolver $ const $ Left e+ catchError s h = MFSolver $ \dep ->+ case runSolver s dep of+ Left e -> runSolver (h e) dep+ v -> v++instance Functor (MFSolver v n) where+ fmap f s = MFSolver $ \dep ->+ fmap f <$> runSolver s dep ++instance Applicative (MFSolver v n) where+ (<*>) = ap+ pure = return+ withParens :: (Show t1, Show t, Ord t1, Num t1, Eq t1) => SimpleExpr t t1 -> [BinaryOp] -> String withParens e@(SEBin op _ _) ops | op `elem` ops = "(" ++ show e ++ ")"@@ -236,58 +284,74 @@ instance (Floating n, Ord n, Ord v) => Num (Expr v n) where (+) = addExpr (*) = mulExpr- negate = mulExpr (makeConstant (-1))- abs = unExpr (UnaryFun Abs abs)- signum = unExpr (UnaryFun Signum signum)- fromInteger = makeConstant . fromInteger+ negate = mulExpr (ConstE (-1))+ abs = unExpr Abs+ signum = unExpr Signum+ fromInteger = ConstE . fromInteger instance (Floating n, Ord n, Ord v) => Fractional (Expr v n) where- recip = unExpr (UnaryFun Recip (1.0/))- fromRational = makeConstant . fromRational+ recip = unExpr Recip+ fromRational = ConstE . fromRational instance (Floating n, Ord n, Ord v) => Floating (Expr v n) where- pi = makeConstant pi- exp = unExpr (UnaryFun Exp exp)- log = unExpr (UnaryFun Log log)+ pi = ConstE pi+ exp = unExpr Exp+ log = unExpr Log sin = sinExpr- cos a = sinExpr (a + makeConstant (pi/2))- cosh = unExpr (UnaryFun Cosh cosh)- atanh = unExpr (UnaryFun Atanh atanh)- tan = unExpr (UnaryFun Tan tan)- sinh = unExpr (UnaryFun Sinh sinh)- asin = unExpr (UnaryFun Asin asin)- acos = unExpr (UnaryFun Acos acos)- asinh = unExpr (UnaryFun Asinh asinh)- acosh = unExpr (UnaryFun Acosh acosh)- atan = unExpr (UnaryFun Atan atan)+ cos a = sinExpr (a + ConstE (pi/2))+ cosh = unExpr Cosh+ atanh = unExpr Atanh+ tan = unExpr Tan+ sinh = unExpr Sinh+ asin = unExpr Asin+ acos = unExpr Acos+ asinh = unExpr Asinh+ acosh = unExpr Acosh+ atan = unExpr Atan instance (Show n, Floating n, Ord n, Ord v, Show v) =>Show (Dependencies v n) where show dep@(Dependencies _ lin _ _ _) = unlines (map showLin (M.toList lin) ++ map showNl (nonlinearEqs dep))- where showLin (v, e) = show v ++ " = " ++ show (linExpr e)+ where showLin (v, e) = show v ++ " = " ++ show (LinearE e) showNl e = show e ++ " = 0" -instance (Show n) => Show (DepError n) where+instance (Show n, Show v) => Show (DepError v n) where show (InconsistentEq a) = "Inconsistent equations, off by " ++ show a show RedundantEq = "Redundant Equation."-+ show (UndefinedVar v) =+ error ("Variable is undefined: " ++ show v)+ show (UnknownVar v n) =+ error ("Value of variable not known: " ++ show v ++ " = " ++ show n) addSimple :: (Num t1, Eq t1) => SimpleExpr t t1 -> SimpleExpr t t1 -> SimpleExpr t t1 addSimple (Const 0) e = e addSimple e (Const 0) = e addSimple e1 e2 = SEBin Add e1 e2 -linToSimple :: (Num t1, Eq t1) => LinExpr t t1 -> SimpleExpr t t1+seHasVar :: Eq v => v -> SimpleExpr v t -> Bool+seHasVar v1 (Var v2) = v1 == v2+seHasVar _ (Const _) = False+seHasVar v (SEBin _ e1 e2) =+ seHasVar v e1 ||+ seHasVar v e2+seHasVar v (SEUn _ e) = seHasVar v e++-- | The expression contains the given variable.+hasVar :: (Num t, Eq v, Eq t) => v -> Expr v t -> Bool+hasVar v = seHasVar v . toSimple++linToSimple :: (Num n, Eq n) => LinExpr v n -> SimpleExpr v n linToSimple (LinExpr v t) = Const v `addSimple` foldr (addSimple.mul) (Const 0) t where mul (v2, 1) = Var v2 mul (v2, c) = SEBin Mul (Const c) (Var v2)- ++ trigToSimple :: (Num n, Eq n) => TrigTerm v n -> SimpleExpr v n trigToSimple (theta, t) = foldr (addSimple.makeSin) (Const 0) t@@ -295,42 +359,74 @@ makeSin (alpha, n) = SEBin Mul (linToSimple n) (SEUn Sin angle) where- angle | alpha == 0 =- linToSimple (LinExpr 0 theta)- | otherwise =- SEBin Add (linToSimple (LinExpr 0 theta))- (Const alpha)+ angle = linToSimple (LinExpr alpha theta) nonlinToSimple :: (Num n, Eq n) => NonLinExpr v n -> SimpleExpr v n-nonlinToSimple (UnaryApp (UnaryFun o _) e) =- SEUn o (simpleExpr e)+nonlinToSimple (UnaryApp o e) =+ SEUn o (toSimple e) nonlinToSimple (MulExp e1 e2) =- SEBin Mul (simpleExpr e1) (simpleExpr e2)+ SEBin Mul (toSimple e1) (toSimple e2) nonlinToSimple (SinExp e) =- SEUn Sin (simpleExpr e)+ SEUn Sin (toSimple e) -- | Convert an `Expr` to a `SimpleExpr`.-simpleExpr :: (Num n, Eq n) => Expr v n -> SimpleExpr v n-simpleExpr (Expr lin trig nonlin) =+toSimple :: (Num n, Eq n) => Expr v n -> SimpleExpr v n+toSimple (Expr lin trig nonlin) = linToSimple lin `addSimple` foldr (addSimple.trigToSimple) (Const 0) trig `addSimple` foldr (addSimple.nonlinToSimple) (Const 0) nonlin -zeroTerm :: (Num n) => LinExpr v n-zeroTerm = LinExpr 0 []+evalBin :: (Floating n) => BinaryOp -> n -> n -> n+evalBin Add = (+)+evalBin Mul = (*) -linExpr :: LinExpr v n -> Expr v n-linExpr lt = Expr lt [] []+evalUn :: (Floating n) => UnaryOp -> n -> n+evalUn Sin = sin+evalUn Abs = abs+evalUn Recip = recip+evalUn Signum = signum+evalUn Exp = exp+evalUn Log = log+evalUn Cos = cos+evalUn Cosh = cosh+evalUn Atanh = atanh+evalUn Tan = tan+evalUn Sinh = sinh+evalUn Asin = asin+evalUn Acos = acos+evalUn Asinh = asinh+evalUn Acosh = acosh+evalUn Atan = atan +-- | evaluate a simple expression using the given substitution.+evalSimple :: Floating m => (n -> m) -> (v -> m) -> SimpleExpr v n -> m+evalSimple _ s (Var v) = s v+evalSimple g _ (Const c) = g c+evalSimple g s (SEBin f e1 e2) =+ evalBin f (evalSimple g s e1) (evalSimple g s e2)+evalSimple g s (SEUn f e) =+ evalUn f (evalSimple g s e)++-- | Make a expression from a simple expression.+fromSimple :: (Floating n, Ord n, Ord v) => SimpleExpr v n -> Expr v n+fromSimple = evalSimple makeConstant makeVariable++-- | Evaluate the expression given a variable substitution.+evalExpr :: (Floating n) => (v -> n) -> SimpleExpr v n -> n+evalExpr = evalSimple id++zeroTerm :: (Num n) => LinExpr v n+zeroTerm = LConst 0+ -- | Create an expression from a constant makeConstant :: n -> Expr v n-makeConstant c = linExpr (LinExpr c [])+makeConstant = ConstE -- | Create an expression from a variable makeVariable :: Num n => v -> Expr v n-makeVariable v = linExpr (LinExpr 0 [(v, 1)])+makeVariable v = LinearE $ LinExpr 0 [(v, 1)] trigExpr :: (Num n) => [TrigTerm v n] -> Expr v n trigExpr t = Expr zeroTerm t []@@ -338,16 +434,12 @@ nonlinExpr :: Num n => [NonLinExpr v n] -> Expr v n nonlinExpr = Expr zeroTerm [] -getConst :: LinExpr t a -> Maybe a-getConst (LinExpr a []) = Just a-getConst _ = Nothing--getLin :: Expr t n -> Maybe (LinExpr t n)-getLin (Expr lt [] []) = Just lt-getLin _ = Nothing+isConst :: LinExpr v n -> Bool+isConst (LConst _) = True+isConst _ = False -getConstExpr :: Expr t b -> Maybe b-getConstExpr e = getLin e >>= getConst+linVars :: LinExpr v n -> [v]+linVars (LinExpr _ v) = map fst v addLin :: (Ord v, Num n, Eq n) => LinExpr v n -> LinExpr v n -> LinExpr v n addLin (LinExpr c1 terms1) (LinExpr c2 terms2) =@@ -379,10 +471,11 @@ where mergeTerms (alpha, n) ((beta, m):rest) = case addTrigTerm alpha n beta m of- Just (_, LinExpr 0 []) -> rest+ Just (_, LConst 0) -> rest Just (gamma, o) -> mergeTerms (gamma, o) rest- Nothing -> (beta, m) : mergeTerms (alpha, n) rest+ Nothing ->+ (beta, m) : mergeTerms (alpha, n) rest mergeTerms a [] = [a] addTrigTerm :: (Ord a, Ord t, Floating a) => a -> LinExpr t a -> a -> LinExpr t a -> Maybe (a, LinExpr t a)@@ -442,25 +535,25 @@ -- linear amplitude mul1 t = nonlinExpr [MulExp (trigExpr [(theta, [t])])- (Expr lt [] [])]+ (Expr lt [] [])] -- constant * (linear + trig) mulExpr :: (Ord a, Ord t, Floating a) => Expr t a -> Expr t a -> Expr t a-mulExpr (getConstExpr -> Just c) (Expr lt2 trig []) =+mulExpr (ConstE c) (Expr lt2 trig []) = Expr (mulLinExpr c lt2) (map (mulConstTrig c) trig) [] -mulExpr (Expr lt2 trig []) (getConstExpr -> Just c) =+mulExpr (Expr lt2 trig []) (ConstE c) = Expr (mulLinExpr c lt2) (map (mulConstTrig c) trig) [] -- linear * (constant + trig)-mulExpr (getLin -> Just lt) (Expr (getConst -> Just c) trig []) =- linExpr (mulLinExpr c lt) ++mulExpr (LinearE lt) (Expr (LConst c) trig []) =+ LinearE (mulLinExpr c lt) + foldr ((+).mulLinTrig lt) 0 trig -mulExpr (Expr (getConst -> Just c) trig []) (getLin -> Just lt) =- linExpr (mulLinExpr c lt) ++mulExpr (Expr (LConst c) trig []) (LinearE lt) =+ LinearE (mulLinExpr c lt) + foldr ((+).mulLinTrig lt) 0 trig -- anything else@@ -468,13 +561,12 @@ sinExpr :: Floating n => Expr v n -> Expr v n sinExpr (Expr (LinExpr c t) [] [])- | null t = makeConstant (sin c)+ | null t = ConstE (sin c) | otherwise = trigExpr [(t, [(c, LinExpr 1 [])])] sinExpr e = nonlinExpr [SinExp e] -unExpr :: Num n => UnaryFun n -> Expr v n -> Expr v n-unExpr (UnaryFun _ f) e- | Just c <- getConstExpr e = makeConstant (f c)+unExpr :: Floating n => UnaryOp -> Expr v n -> Expr v n+unExpr f (ConstE c) = ConstE (evalUn f c) unExpr f e = nonlinExpr [UnaryApp f e] substVarLin :: (Ord v, Num n, Eq n) => (v -> Maybe (LinExpr v n)) -> LinExpr v n -> LinExpr v n@@ -493,42 +585,45 @@ substVarTrig :: (Ord v, Ord n, Floating n) => (v -> Maybe (LinExpr v n)) -> ([(v, n)], [(n, LinExpr v n)]) -> Expr v n substVarTrig s (period, terms) =- let period2 = linExpr $ substVarLin s (LinExpr 0 period)- terms2 = map (fmap $ linExpr.substVarLin s) terms- in foldr (\(p,a) -> (+ (a * sin (makeConstant p + period2))))+ let period2 = LinearE $ substVarLin s (LinExpr 0 period)+ terms2 = map (fmap $ LinearE . substVarLin s) terms+ in foldr (\(p,a) -> (+ (a * sin (ConstE p + period2)))) 0 terms2 subst :: (Ord n, Ord v, Floating n) => (v -> Maybe (LinExpr v n)) -> Expr v n -> Expr v n subst s (Expr lt trig nl) =- linExpr (substVarLin s lt) ++ LinearE (substVarLin s lt) + foldr ((+).substVarTrig s) 0 trig + foldr ((+).substVarNonLin s) 0 nl -- | An empty set of dependencies.-emptyDeps :: Dependencies v n-emptyDeps = Dependencies M.empty M.empty [] M.empty []+noDeps :: Dependencies v n+noDeps = Dependencies M.empty M.empty [] M.empty [] simpleSubst :: Eq a => a -> b -> a -> Maybe b simpleSubst x y z | x == z = Just y | otherwise = Nothing --- | Make the expressions on both sides equal, and add the result to the Set of--- dependencies.-(===) :: (Hashable n, Hashable v, RealFrac (Phase n), Ord v,- Floating n) => Expr v n -> Expr v n- -> Dependencies v n- -> Either (DepError n) (Dependencies v n)-(===) lhs rhs deps = addEq deps (lhs - rhs)+trigToExpr :: (Ord n, Ord v, Floating n) => TrigEq v n -> Expr v n+trigToExpr (p, a, ph, c) =+ LinearE a * sin(LinearE $ LinExpr ph p) ++ ConstE c -addEqs :: (Hashable v, Hashable n, RealFrac (Phase n), Ord v, Floating n) => Dependencies v n -> [Expr v n] -> Either (DepError n) (Dependencies v n)-addEqs = foldM addEq+trig2ToExpr :: (Ord v, Floating n, Ord n) => M.HashMap v (Expr v n) -> [Expr v n]+trig2ToExpr =+ map (\(v,e)-> makeVariable v-e)+ . M.toList -addEq :: (Hashable n, Hashable v, RealFrac (Phase n), Ord v,+addEqs :: (Hashable v, Hashable n, RealFrac (Phase n), Ord v, Floating n) => Dependencies v n -> [Expr v n] -> Either (DepError v n) (Dependencies v n)+addEqs = foldM addEquation++-- | @addEquation e d@: Add the equation @e = 0@ to the system d.+addEquation :: (Hashable n, Hashable v, RealFrac (Phase n), Ord v, Floating n) => Dependencies v n- -> Expr v n -> Either (DepError n) (Dependencies v n)-addEq deps@(Dependencies _ lin _ _ _) expr =+ -> Expr v n -> Either (DepError v n) (Dependencies v n)+addEquation deps@(Dependencies _ lin _ _ _) expr = addEq0 deps $ -- substitute known and dependend variables subst (flip M.lookup lin) expr@@ -556,57 +651,67 @@ select [] = [] select (x:xs) = (x,xs) : [(y,x:ys) | (y,ys) <- select xs]- -addEq0 :: (Hashable v, Hashable n, RealFrac (Phase n), Ord v, Floating n) => Dependencies v n -> Expr v n -> Either (DepError n) (Dependencies v n)++-- substitute v for lt in all linear equations+-- if insertp is true, then add v = tl to equations+substDep :: (Hashable v, Ord v, Num n, Eq n) =>+ M.HashMap v (H.HashSet v) -> M.HashMap v (LinExpr v n)+ -> v -> LinExpr v n -> Bool + -> (M.HashMap v (H.HashSet v), LinearMap v n)+substDep vdep lin v lt insertp =+ -- variables that depend on v+ let depVars = fromMaybe H.empty (M.lookup v vdep)+ -- substitute v in all dependend variables and (optionally) add+ -- v as dependend variable+ lin' = (if insertp then M.insert v lt+ else id) $+ H.foldl' (flip $ M.adjust $+ substVarLin $+ simpleSubst v lt)+ lin depVars+ -- add dependency link from independend variables to the+ -- substituted equations and (optionally) v, and remove v (since+ -- it has become dependend, so no variable can depend on it).+ depVars2 | insertp = H.insert v depVars+ | otherwise = depVars+ -- exclude dependend variable v if k has been canceled+ tryUnion k m1 m2 =+ let xs = H.intersection m1 m2+ hasvar v2 = case M.lookup v2 lin' of+ Nothing -> False+ Just (LinExpr _ vs) ->+ any ((==k).fst) vs+ in H.filter hasvar xs+ `H.union` H.difference m1 xs+ `H.union` H.difference m2 xs+ vdep' = H.foldl'+ (\mp k -> M.insertWith (tryUnion k) k depVars2 mp)+ (M.delete v vdep)+ (H.fromList $ linVars lt)+ in (vdep', lin')++addEq0 :: (Hashable v, Hashable n, RealFrac (Phase n), Ord v, Floating n) => Dependencies v n -> Expr v n -> Either (DepError v n) (Dependencies v n) -- adding a constant equation-addEq0 _ (getConstExpr -> Just c) =- if c == 0 then Left RedundantEq- else Left (InconsistentEq c)+addEq0 _ (ConstE c) =+ Left $ if c == 0 then RedundantEq+ else InconsistentEq c -- adding a linear equation addEq0 (Dependencies vdep lin trig trig2 nonlin) (Expr lt [] []) = let (v, _, lt2) = splitMax lt- -- variables that depend on v- depVars = fromMaybe H.empty (M.lookup v vdep)- -- substitute v in all dependend variables- -- and add v as dependend variable- lin' = M.insert v lt2 $- H.foldl' (flip $ M.adjust $ substVarLin $- simpleSubst v lt2) lin depVars- -- independend variables substituted- ltVars = case lt2 of- LinExpr _ vars -> map fst vars- -- add dependency link from independend variables to the- -- substituted equations and v, and remove v (since it has- -- become dependend, so no variable can depend on it).- depVars2 = H.insert v depVars- vdep' = H.foldl'- (\mp k -> M.insertWith H.union k depVars2 mp)- (M.delete v vdep) (H.fromList ltVars)- -- simply substitute var in nonlinear eqs- nonlin' = map (subst (simpleSubst v lt2)) nonlin- -- substitute and evaluate trigonometric equations- trigSubst (p, a, ph, c) =- subst (simpleSubst v lt2) $- sin (linExpr $ LinExpr ph p) *- linExpr a + makeConstant c- newTrig = map trigSubst trig- trigSubst2 (v2, ex) =- subst (simpleSubst v lt2) $- makeVariable v2 - ex- newTrig2 =- map trigSubst2 $- concatMap M.toList $- M.elems trig2+ (vdep', lin') = substDep vdep lin v lt2 True - in addEqs (Dependencies vdep' lin' [] M.empty []) (newTrig++newTrig2++nonlin')+ -- Add nonlinear equations again to the system.+ trig' = map trigToExpr trig+ trig2' = concatMap trig2ToExpr $ M.elems trig2+ in addEqs (Dependencies vdep' lin' [] M.empty []) (trig'++trig2'++nonlin) -- adding a sine equation addEq0 deps@(Dependencies vdep lin trig trig2 nl)- (Expr (LinExpr c lt) [(theta, [(alpha, getConst -> Just n)])] []) =+ (Expr (LinExpr c lt) [(theta, [(alpha, LConst n)])] []) = if null lt then -- reduce a sine to linear equation- addEq0 deps (linExpr $ LinExpr (alpha - asin (-c/n)) theta)+ addEq0 deps (LinearE $ LinExpr (alpha - asin (-c/n)) theta) else -- add a variable dependency on the sine equation case M.lookup theta trig2 of@@ -614,18 +719,18 @@ Nothing -> addSin (LinExpr c lt) alpha n Just map2 -> case foldr ((+).doSubst)- (makeConstant c +- makeConstant n *- sin (linExpr $ LinExpr alpha theta))+ (ConstE c ++ ConstE n *+ sin (LinearE $ LinExpr alpha theta)) lt of- Expr lt2 [(_, [(alpha2, getConst -> Just n2)])] []- | isNothing(getConst lt2)+ Expr lt2 [(_, [(alpha2, LConst n2)])] []+ | not $ isConst lt2 -> addSin lt2 alpha2 n2 e2 -> addEq0 deps e2 where doSubst (v,c2) = case M.lookup v map2 of- Nothing -> makeVariable v * makeConstant c2- Just e2 -> e2 * makeConstant c2+ Nothing -> makeVariable v * ConstE c2+ Just e2 -> e2 * ConstE c2 where addSin l' a' n' = let (v, c', r) = splitMax l'@@ -636,12 +741,13 @@ in Right $ Dependencies vdep lin trig trig2' nl -- adding the first sine equation-addEq0 (Dependencies d lin [] trig2 nl) (Expr (getConst -> Just c) [(theta, [(alpha, n)])] []) =+addEq0 (Dependencies d lin [] trig2 nl)+ (Expr (LConst c) [(theta, [(alpha, n)])] []) = Right $ Dependencies d lin [(theta, n, alpha, c)] trig2 nl -- try reducing this equation with another sine equation addEq0 (Dependencies deps lin trig trig2 nl)- (Expr (getConst -> Just x) [(theta, [(a, n)])] []) =+ (Expr (LConst x) [(theta, [(a, n)])] []) = case mapMaybe similarTrig $ select trig of -- no matching equation found [] -> Right $ Dependencies deps lin ((theta, n, a, x):trig) trig2 nl@@ -657,8 +763,8 @@ theta2 = atan (-y*d/e)-b + (if (d*e) < 0 then pi else 0) n2 = sqrt(y*y + e*e/(d*d))- lin1 = linExpr $ LinExpr (-theta2) theta- lin2 = linExpr n - makeConstant n2+ lin1 = LinearE $ LinExpr (-theta2) theta+ lin2 = LinearE n - ConstE n2 where similarTrig ((t,m,b,y),rest) | Just r <- termIsMultiple m n,@@ -671,6 +777,122 @@ addEq0 (Dependencies d lin trig trig2 nonlin) e = Right $ Dependencies d lin trig trig2 (e:nonlin) +deleteDep :: (Hashable k, Hashable b, Eq k, Eq b) =>+ M.HashMap b (H.HashSet k)+ -> M.HashMap k (LinExpr b n) -> k+ -> Maybe (M.HashMap b (H.HashSet k), M.HashMap k (LinExpr b n), LinExpr b n)+deleteDep vdep lin v =+ case M.lookup v lin of+ Nothing -> Nothing+ Just lt -> Just (vdep', lin', lt)+ where+ -- delete equation of v+ lin' = M.delete v lin+ -- delete v from dependencies+ vdep' = H.foldl'+ (flip $ M.adjust $ H.delete v)+ vdep (H.fromList $ linVars lt)++-- | Eliminate an variable from the equations. Returns the eliminated+-- equations. Before elimination it performs substitution to minimize+-- the number of eliminated equations.+eliminate :: (Hashable n, Show n, Hashable v, RealFrac (Phase n), Ord v, Show v,+ Floating n) => Dependencies v n -> v -> (Dependencies v n, [Expr v n])+eliminate (Dependencies vdep lin trig trig2 nonlin) v+ | Just (vdep', lin', lt) <- deleteDep vdep lin v =+ -- v is dependend, so doesn't appear in other equations+ (Dependencies vdep' lin' trig trig2 nonlin,+ [LinearE lt - makeVariable v])+ | Just vars <- M.lookup v vdep,+ (v2:_) <- H.toList vars =+ -- v is independend, and appears in a linear equation+ case deleteDep vdep lin v2 of+ Nothing ->+ error $ "Internal error: found empty dependency on " ++ show v2+ Just (vdep', lin', lt) ->+ -- rearrange the deleted equation in terms of v+ let lt2 = snd $ reArrange v2 lt v+ -- substitute v in all equations+ (vdep'', lin'') = substDep vdep' lin' v lt2 False+ trig' = map trigToExpr trig+ trig2' = concatMap trig2ToExpr $ M.elems trig2+ deps = Dependencies vdep'' lin'' [] M.empty []+ e = [LinearE lt2 - makeVariable v]+ -- use addEq0 since substitution is unnecessary+ in case foldM addEq0+ deps $+ map (subst $ simpleSubst v lt2)+ (trig'++trig2'++nonlin) of+ Left _ -> (deps, e) --shouldn't happen+ Right d -> (d, e)+ | otherwise =+ let (l, trig2') =+ M.foldrWithKey trigFold+ ([], M.empty) trig2+ trigFold p t (l2, m2) =+ let (l3, m1) = elimTrig p t v+ mp | M.null m1 = m2+ | otherwise = M.insert p m1 m2+ in (l3++l2, mp)+ + (nlWith, nlWithout) =+ partition (hasVar v) $+ map trigToExpr trig ++ nonlin+ deps = Dependencies vdep lin [] trig2' []+ in case foldM addEq0 deps+ nlWithout of+ Left _ -> (deps, nlWith++l) --shouldn't happen+ Right d -> (d, nlWith++l)++-- v2 = c2*v + b + c+reArrange :: (Show v, Ord v, Fractional n, Eq n) =>+ v -> LinExpr v n -> v -> (n, LinExpr v n)+reArrange v2 (LinExpr c vars) v =+ case find ((==v).fst.fst) $ select vars of+ Nothing ->+ error $ "Internal error: variable " ++ show v +++ " not in linear expression "+ Just ((_,c2), r) ->+ (c2,+ LinExpr (c/negate c2) r+ `addLin` LinExpr 0 [(v2, 1/c2)])++reArrangeTrig :: (Show v, Ord t1, Ord v, Floating t1) => v -> Expr v t1 -> v -> Expr v t1+reArrangeTrig v2 (Expr lt trig _) v =+ let (c2, lt2) = reArrange v2 lt v+ in LinearE lt2 - trigExpr trig / ConstE c2+ +elimTrig :: (Show v, Ord v, Hashable v, Floating n, Ord n) =>+ Period v n -> M.HashMap v (Expr v n) -> v+ -> ([Expr v n], M.HashMap v (Expr v n))+elimTrig p m v+ -- period contains the variable, remove all eqs+ | any ((==v).fst) p =+ (trig2ToExpr m, M.empty)+ -- the variable is dependend in:+ -- v = e (== sin(p+const) + linear)+ -- remove the eq+ | Just e <- M.lookup v m =+ ([makeVariable v - e],+ M.delete v m)+ -- the variable is independent in:+ -- v2 = e (== sin(p+const) + const*v + linear)+ -- rearrange, and substitute+ | Just (v2, e) <-+ find (hasVar v.snd) $ M.toList m =+ let e2 = reArrangeTrig v2 e v+ substOne (v3, c)+ | v == v3 = e2 * ConstE c+ | otherwise = makeVariable v3 * ConstE c+ doSubst (Expr (LinExpr c lt) trig _) =+ foldr ((+).substOne) + (ConstE c + trigExpr trig) lt+ in ([makeVariable v - e],+ M.map doSubst $ M.delete v2 m)+ -- variable not found+ | otherwise =+ ([], m)+ dmod :: RealFrac a => a -> a -> a dmod a b = abs((a/b) - fromInteger (round (a/b)) * b) @@ -678,15 +900,15 @@ -- equation splitMax :: (Ord b, Fractional b, Eq v) => LinExpr v b -> (v, b, LinExpr v b) splitMax (LinExpr c t) =- let (v,c2) = maximumBy (compare `on` (abs.snd)) t+ let ((v,c2),r) = maximumBy (compare `on` (abs.snd.fst)) $+ select t in (v, c2, LinExpr (-c/c2) $- map (fmap (negate.(/c2))) $- filter ((/= v).fst) t)+ map (fmap (negate.(/c2))) r) -- | Return True if the variable is known or dependend.-varDefined :: (Eq v, Hashable v) => Dependencies v n -> v -> Bool-varDefined (Dependencies _ dep _ _ _) v =+varDefined :: (Eq v, Hashable v) => v -> Dependencies v n -> Bool+varDefined v (Dependencies _ dep _ _ _) = case M.lookup v dep of Nothing -> False _ -> True@@ -717,8 +939,8 @@ -- | Return the value of the variable, or a list of variables -- it depends on. Only linear dependencies are shown.-getKnown :: (Eq v, Hashable v) => Dependencies v n -> v -> Either [v] n-getKnown (Dependencies _ lin _ _ _) var =+getKnown :: (Eq v, Hashable v) => v -> Dependencies v n -> Either [v] n+getKnown var (Dependencies _ lin _ _ _) = case M.lookup var lin of Nothing -> Left [] Just (LinExpr a []) ->@@ -726,12 +948,7 @@ Just (LinExpr _ v) -> Left $ map fst v -trigToExpr :: (Ord n, Ord v, Floating n) => TrigEq v n -> Expr v n-trigToExpr (p, a, ph, c) =- linExpr a * sin(linExpr $ LinExpr ph p) +- makeConstant c---- | Give all nonlinear equations as an `Expr` equal to 0.+-- | Return all nonlinear equations @e_i@, where @e_i = 0@. nonlinearEqs :: (Ord n, Ord v, Floating n) => Dependencies v n -> [Expr v n] nonlinearEqs (Dependencies _ _ trig trig2 nl) = map trigToExpr trig ++@@ -739,28 +956,90 @@ (concatMap M.toList (M.elems trig2)) ++ nl --- | Make the pairs of expressions on both sides equal, and add the--- result to the Set of dependencies. No error is signaled if the--- equation for one of the sides is redundant for example in (x, 0) ==--- (y, 0).-(=&=) :: (Hashable n, Hashable v, RealFrac (Phase n), Ord v, Floating n) => (Expr v n, Expr v n) -> (Expr v n, Expr v n) -> Dependencies v n -> Either (DepError n) (Dependencies v n)-(=&=) (a, b) (c, d) dep = - case (a === c) dep of- Left RedundantEq ->- (b === d) dep- Right res ->- case (b === d) res of- Left RedundantEq -> Right res- Right res2 -> Right res2- err -> err- err -> err---- | Solve a list of equations in order. Returns either a new set of--- dependencies, or signals an error.-solveEqs :: Dependencies v n -> [Dependencies v n -> Either (DepError n) (Dependencies v n)] -> Either (DepError n) (Dependencies v n)-solveEqs = foldM $ flip ($)---- | Show all variables and equations.-showVars :: (Show n, Show v, Show a, Ord n, Ord v, Floating n) => Either (DepError a) (Dependencies v n) -> IO ()+-- | Show all variables and equations. Useful in combination with `execSolver`.+showVars :: (Show n, Show v, Ord n, Ord v, Floating n) => Either (DepError v n) (Dependencies v n) -> IO () showVars (Left e) = print e showVars (Right dep) = print dep++-- | Get the dependencies from a state monad. Specialized version of `get`.+dependencies :: (MonadState (Dependencies v n) m) => m (Dependencies v n)+dependencies = get++-- | Return the value of the variable or throw an error.+getValue :: (MonadState (Dependencies v n) m,+ MonadError (DepError v n) m,+ Eq v, Hashable v) =>+ v -> m n+getValue v = do+ v2 <- getKnownM v+ case v2 of+ Right e -> return e+ Left _ -> throwError $ UndefinedVar v++-- | Monadic version of `varDefined`.+varDefinedM :: (MonadState (Dependencies v n) m, Hashable v, Eq v) =>+ v -> m Bool+varDefinedM v = varDefined v `liftM` dependencies++-- | Monadic version of `getKnown`.+getKnownM :: (MonadState (Dependencies v n) m, Hashable v, Eq v) =>+ v -> m (Either [v] n)+getKnownM v = getKnown v `liftM` dependencies++-- | Monadic version of `eliminate`.+eliminateM :: (MonadState (Dependencies v n) m, Hashable n, Hashable v,+ Show n, Show v, RealFrac n, Ord v, Floating n) =>+ v -> m [Expr v n]+eliminateM v = do+ dep <- dependencies+ let (dep2, e) = eliminate dep v+ put dep2+ return e++infixr 1 === , =&=++-- | Make the expressions on both sides equal+(===) :: (MonadState (Dependencies v n) m,+ MonadError (DepError v n) m,+ Eq v, Hashable v, Hashable n,+ RealFrac n, Floating n, Ord v) =>+ Expr v n -> Expr v n -> m ()+(===) lhs rhs = do+ deps <- dependencies+ case addEquation deps (lhs - rhs) of+ Left e -> throwError e+ Right dep -> put dep++-- | Make the pairs of expressions on both sides equal. No error is+-- signaled if the equation for one of the sides is `Redundant` for+-- example in (x, 0) == (y, 0).+(=&=) :: (MonadState (Dependencies v n) m,+ MonadError (DepError v n) m,+ Eq v, Hashable v, Hashable n,+ RealFrac n, Floating n, Ord v) =>+ (Expr v n, Expr v n) -> (Expr v n, Expr v n) -> m ()+(=&=) (a, b) (c, d) =+ do ignore $ a === c+ b === d++-- | Succeed even when trowing a `RedundantEq` error.+ignore :: MonadError (DepError v n) m => m () -> m ()+ignore m = m `catchError` (+ \e -> case e of+ RedundantEq -> return ()+ _ -> throwError e)+ +-- | Return the result of solving the equations, or throw the error as an exception.+unsafeSolve :: (Typeable n, Typeable v, Show n, Show v) => MFSolver v n a -> Dependencies v n -> a+unsafeSolve s dep = case runSolver s dep of+ Right (_, v) -> v+ Left e -> throw e++-- | Return the result of solving the equations or an error.+evalSolver :: MFSolver v n a -> Dependencies v n -> Either (DepError v n) a+evalSolver s dep = snd <$> runSolver s dep ++-- | Run the solver and return the dependencies or an error.+execSolver :: MFSolver v n a -> Dependencies v n -> + Either (DepError v n) (Dependencies v n)+execSolver s dep = fst <$> runSolver s dep
mfsolve.cabal view
@@ -1,5 +1,5 @@ Name: mfsolve-Version: 0.1.0+Version: 0.2.0 Synopsis: Equation solver and calculator à la metafont Category: Math Copyright: Kristof Bastiaensen (2015)@@ -21,9 +21,10 @@ Library Ghc-options: -Wall- Build-depends: base >= 3 && < 5, unordered-containers > 0.2, hashable >= 0.1.2+ Build-depends: base >= 3 && < 5, unordered-containers > 0.2, hashable >= 0.1.2, mtl >= 2.2.1 Exposed-Modules: Math.MFSolve+ extensions: DeriveGeneric, PatternGuards, PatternSynonyms test-suite test type: exitcode-stdio-1.0
tests/test.hs view
@@ -1,3 +1,5 @@+{-# Language ViewPatterns #-}+ import Test.Tasty import Test.Tasty.HUnit import Math.MFSolve@@ -43,6 +45,13 @@ infixr 1 ?= (?=) :: TestExpr -> TestExpr -> TestEq +zero :: (Num n, Eq n) => Expr v n -> Bool+zero (toSimple -> Const 0) = True+zero _ = False++instance (Floating n, Eq n, Ord n, Ord v) => Eq (Expr v n) where+ a == b = zero $ a-b+ TestExpr a b ?= TestExpr c d = (\s -> testEq (a s) (c s) (show b) (show d), (b,d))@@ -95,8 +104,8 @@ solveOne :: [TestEq] -> String solveOne eqs = - case solveEqs emptyDeps $- map (uncurry (===) . snd) eqs+ case solveEqs noDeps $+ map (flip addEquation . uncurry (-) . snd) eqs of Left RedundantEq -> "Found redundant equation"@@ -117,7 +126,28 @@ Just er -> "Solution didn't match equations:\n" ++ show d ++ er- ++sysHasVar :: (Ord t, Ord v, Floating t) => Dependencies v t -> v -> Bool+sysHasVar s v = + any ((== v).fst) (knownVars s) ||+ any (\(v2, LinExpr _ vs) ->+ v==v2 || any ((== v).fst) vs)+ (dependendVars s) ||+ any (hasVar v) (nonlinearEqs s)++{-++eliminate v from eqs+check number of eliminated equations+eqs2 = add v to eqs+check eqs and eqs2 for equality++-}+++tryElim v n eqs =+ undefined+ tests :: TestTree tests = testGroup "Tests" [unitTests]