packages feed

Eq-1.0: EqManips/Algorithm/Utils.hs

-- | Utility function/types used in the project.
module EqManips.Algorithm.Utils ( biAssocM, biAssoc
                                , asAMonad
                                , fromEmptyMonad 
                                , treeIfyFormula,  treeIfyBinOp 
                                , listifyFormula, listifyBinOp 
                                , isFormulaConstant, isFormulaConstant' 
                                , isFormulaInteger, isFormulaScalar 
                                , isConstantNegative, negateConstant
                                , sortFormula, invSortFormula, sortBinOp  
                                
                                -- | Count nodes in basic formula
                                , nodeCount     
                                -- | Same version with form info.
                                , nodeCount'    
                                , needParenthesis 
                                , needParenthesisPrio 
                                , interspereseS 
                                , concatS 
                                , concatMapS 
                                , collectSymbols, collectSymbols'

                                -- | Translate complex into "simpler" format,
                                -- intended for display use only!
                                , complexTranslate 
                                ) where

import Control.Applicative
import qualified Data.Monoid as Monoid

import Data.Monoid( All( .. ), mempty )
import EqManips.Algorithm.EmptyMonad
import EqManips.Propreties
import EqManips.Types
import {-# SOURCE #-} EqManips.FormulaIterator
import Data.List( foldl', sortBy )

-----------------------------------------------------------
--          Parsing formula
-----------------------------------------------------------
-- | Count the number of nodes in a formula.
nodeCount :: FormulaPrim -> Int
nodeCount = Monoid.getSum . foldf 
   (\_ a -> Monoid.Sum $ Monoid.getSum a + 1)
   (Monoid.Sum 0)

nodeCount' :: Formula anyForm -> Int
nodeCount' (Formula a) = nodeCount a

-- | Perform a semantic sorting on formula, trying to put numbers
-- front and rassembling terms
sortFormula :: Formula ListForm -> Formula ListForm
sortFormula (Formula a) = Formula 
                        $ (depthFormulaPrimTraversal `asAMonad` sortBinOp compare) a

-- | Sort a binary operator, used by sortFormula to sort globally
-- a formula
sortBinOp :: (FormulaPrim -> FormulaPrim -> Ordering) -> FormulaPrim -> FormulaPrim
sortBinOp f (BinOp _ op lst)
    | op `hasProp` Associativ && op `hasProp` Commutativ = binOp op $ sortBy f lst
sortBinOp _f a = a

invSortFormula :: Formula ListForm -> Formula ListForm
invSortFormula (Formula f) =
    Formula $ (depthFormulaPrimTraversal `asAMonad` sortBinOp cmp) f
        where cmp a = invOrd . compare a
              invOrd GT = LT
              invOrd LT = GT
              invOrd EQ = EQ

-- | listify a whole formula
listifyFormula :: Formula TreeForm -> Formula ListForm
listifyFormula (Formula a) = Formula $
    (depthFormulaPrimTraversal `asAMonad` listifyBinOp) a


-- | Given a binary operator in binary tree form,
-- transform it in list form.
listifyBinOp :: FormulaPrim -> FormulaPrim
listifyBinOp (BinOp _ op lst) = binOp op $ translate lst
    where translate = flatten (op `obtainProp` AssocSide)
          flatten OpAssocRight = rightLister
          flatten OpAssocLeft 
                | op `hasProp` Associativ = rightLister . leftLister
                | otherwise = leftLister

          leftLister = foldr lefter []

          -- left associative operator packing.
          lefter (BinOp _ op' fl) acc
                | op == op' = foldr lefter acc fl
          lefter final acc = final : acc

          rightLister = foldl' righter []
          -- right associative operator packing.
          righter acc (BinOp _ op' fl)
                | op' == op = foldl' righter acc fl
          righter acc e = acc ++ [e]

listifyBinOp a = a

-- | treeify a whole formula
treeIfyFormula :: Formula ListForm -> Formula TreeForm
treeIfyFormula (Formula a) = Formula f
    where f :: FormulaPrim
          f = depthFormulaPrimTraversal `asAMonad` treeIfyBinOp $ a

-- | Given a formula where all binops are in list
-- forms, transform it back to binary tree.
treeIfyBinOp :: FormulaPrim -> FormulaPrim
treeIfyBinOp (BinOp _ _ []) = error "treeIfyBinOp - empty binop"
treeIfyBinOp f@(BinOp _ _ [_]) = error ("treeIfyBinOp - Singleton binop " ++ show f)
treeIfyBinOp f@(BinOp _ _ [_,_]) = f
treeIfyBinOp (BinOp _ op lst) = innerNode (op `obtainProp` AssocSide) lst
        where innerNode OpAssocLeft (fx:fy:fs) = 
                foldl' innerLeft (binOp op [fx, fy]) fs
              innerNode OpAssocRight lst' = innerRight lst'
              innerNode _ _ = error "treeIfyBinOp - weird unhandled case"

              innerRight [a,b] = binOp op [a,b]
              innerRight (fx:fs) = binOp op [fx, innerRight fs]
              innerRight _ = error "treeIfyBinOp - bleh right"

              innerLeft acc fx = binOp op [acc, fx]
treeIfyBinOp f = f

-- | Little helper to help to know if a formula renderer
-- need to put parenthesis around the current node regarding
-- his parent node.
needParenthesis :: Bool         -- ^ if the node is on the right side of parent operator
                -> BinOperator  -- ^ Parent operator
                -> BinOperator  -- ^ This node operator
                -> Bool
needParenthesis v =
    needParenthesisPrio v . (`obtainProp` Priority)

-- | Little helper to know if a renderer need to put parenthesis
-- given his parent's priority
needParenthesisPrio :: Bool        -- ^ If the node is on the right side of parent operator
                    -> Int         -- ^ Parent operator priority
                    -> BinOperator -- ^ This node operator
                    -> Bool
-- for right associative operators, it's reversed.
needParenthesisPrio True parentPrio op
    | op `obtainProp` AssocSide == OpAssocRight =
        (op `obtainProp` Priority) > parentPrio
    | otherwise =
        (op `obtainProp` Priority) >= parentPrio

needParenthesisPrio False parentPrio op
    | op `obtainProp` AssocSide == OpAssocRight =
        (op `obtainProp` Priority) >= parentPrio
    | otherwise =
        (op `obtainProp` Priority) > parentPrio

-- | Bi associate operation on a list of elements.
-- Can be used for reduction of formula.
biAssoc :: (a -> a -> Either a (a,a)) 
        -> (a -> a -> Either a (a,a)) 
        -> [a] -> [a]
biAssoc f finv = fromEmptyMonad 
               . biAssocM (\a -> return . f a) 
                          (\a -> return . finv a)

-- | same as biAssoc, but use monads.
{-
{-# SPECIALIZE biAssocM :: (FormulaPrim -> FormulaPrim -> EqContext (Either FormulaPrim (FormulaPrim,FormulaPrim))) 
                        -> (FormulaPrim -> FormulaPrim -> EqContext (Either FormulaPrim (FormulaPrim,FormulaPrim)))
                        -> [FormulaPrim] -> EqContext [FormulaPrim] #-}
                        -}
biAssocM :: (Monad m, Functor m)
         => (a -> a -> m (Either a (a,a))) 
         -> (a -> a -> m (Either a (a,a))) 
         -> [a] -> m [a]
biAssocM f finv lst = assocInner f lst
    where assocInner _ [] = return []
          assocInner _ [x] = return [x]
          assocInner f' [x,y] = f' x y >>= \val -> case val of
              Left v -> return [v]
              Right (v1, v2) -> return [v1, v2]
          assocInner f' (x:y:xs) = f' x y >>= \val -> case val of
              Left v -> assocInner f' (v:xs)
              Right (v1, v2) -> (v1:) <$> assocInner finv (v2:xs)

-- | Work like concat on list, but instead
-- just combine functions of kind of ShowS.
-- The function is generalized
concatS :: [a -> a] -> (a -> a)
concatS []  = id
concatS lst = foldr1 (.) lst

-- | Work like concatMap, but instead use 
-- function combination.
concatMapS :: (a -> b -> b) -> [a] -> (b -> b)
concatMapS f = concatS . map f

-- | Same functionality as intersperse but combine function
-- instead of concatenation
interspereseS :: (a -> a) -> [a -> a] -> a -> a
interspereseS what within =
   foldl' (\acc e -> e . what . acc) lastOne reversed
    where (lastOne : reversed) = reverse within

-- | Collect all the symbols present in the formula.
-- Symbols can be present multiple times
collectSymbols :: FormulaPrim -> [String]
collectSymbols = foldf symbolCollector []
    where symbolCollector (Variable v) acc = v:acc
          symbolCollector _ acc = acc

collectSymbols' :: Formula anyKind -> [String]
collectSymbols' (Formula a) = collectSymbols a

isFormulaInteger :: FormulaPrim -> Bool
isFormulaInteger = getAll . foldf isConstant mempty
    where isConstant (Variable _) _ = All False
          isConstant (Sum _ _ _ _) _ = All False
          isConstant (Poly _ _) _ = All False
          isConstant (Product _ _ _ _) _ = All False
          isConstant (Derivate _ _ _) _ = All False
          isConstant (Integrate _ _ _ _ _) _ = All False
          isConstant (Lambda _ _) _ = All False
          isConstant (App _ _ _) _ = All False
          isConstant (Block _ _ _) _ = All False
          --
          isConstant (CFloat _) _ = All False
          isConstant (CInteger _) _ = All True
          isConstant (Complex _ _) _ = All False
          isConstant (Fraction _) _ = All True
          isConstant (Truth _) _ = All False
          isConstant (NumEntity _) _ = All False
          --
          isConstant (UnOp _ op _) a = isValidUnop op a
          isConstant (BinOp _ _ _) a = a
          isConstant (Meta _ _ _) a = a
          isConstant (Matrix _ 1 1 _) a = a
          isConstant (Matrix _ _ _ _) _ = All False
          isConstant (Indexes _ _ _) _ = All False
          isConstant (List _ _) _ = All False

          isValidUnop OpNegate a = a
          isValidUnop OpAbs a = a
          isValidUnop OpFactorial _ = All True
          isValidUnop OpCeil _ = All True
          isValidUnop OpFloor _ = All True
          isValidUnop _ _ = All False

isFormulaScalar :: FormulaPrim -> Bool
isFormulaScalar (CFloat _) = True
isFormulaScalar (CInteger _) = True
isFormulaScalar (Fraction _) = True
-- next case is "fishy"
isFormulaScalar (Complex _ (a,b)) = isFormulaScalar a && isFormulaScalar b
isFormulaScalar (UnOp _ OpNegate f) = isFormulaScalar f
isFormulaScalar _ = False

negateConstant :: FormulaPrim -> FormulaPrim
negateConstant (CFloat a) = CFloat (-a)
negateConstant (CInteger a) = CInteger (-a)
negateConstant (Fraction a) = Fraction (-a)
negateConstant (UnOp _ OpNegate c) = c
negateConstant a = a

isConstantNegative :: FormulaPrim -> Bool
isConstantNegative (CFloat a) = a < 0
isConstantNegative (CInteger a) = a < 0
isConstantNegative (Fraction a) = a < 0
isConstantNegative (UnOp _ OpNegate c) =
    not $ isConstantNegative c
isConstantNegative _ = False

-- | Translate a complex to a simpler formula using '+' and '*'
-- Perform mandatory simplification
complexTranslate :: (FormulaPrim, FormulaPrim) -> FormulaPrim
complexTranslate (a,b)
    | isZero b = a
    | isZero a && isOne b = Variable "i"
    | isZero a = Variable "i" * b
    | otherwise = a + Variable "i" * b
    where isZero (CInteger 0) = True
          isZero (CFloat 0.0) = True
          isZero _ = False

          isOne (CInteger 1) = True
          isOne (CFloat 1.0) = True
          isOne _            = False

-- | Tell if a formula can be reduced to a scalar somehow
isFormulaConstant :: FormulaPrim -> Bool
isFormulaConstant = getAll . foldf isConstant mempty
    where isConstant (Variable _) _ = All False
          isConstant (Poly _ _) _ = All False
          isConstant (Sum _ _ _ _) _ = All False
          isConstant (Product _ _ _ _) _ = All False
          isConstant (Derivate _ _ _) _ = All False
          isConstant (Integrate _ _ _ _ _) _ = All False
          isConstant (Lambda _ _) _ = All False
          isConstant (App _ _ _) _ = All False
          isConstant (Block _ _ _) _ = All False
          --
          isConstant (CFloat _) _ = All True
          isConstant (CInteger _) _ = All True
          isConstant (Truth _) _ = All True
          isConstant (NumEntity _) _ = All True
          isConstant (Fraction _) _ = All True
          isConstant (List _ _) _ = All False
          isConstant (Indexes _ _ _) _ = All False

          --
          isConstant (Complex _ _) a = a
          isConstant (UnOp _ _ _) a = a
          isConstant (BinOp _ _ _) a = a
          isConstant (Meta _ _ _) a = a
          isConstant (Matrix _ 1 1 _) a = a
          isConstant (Matrix _ _ _ _) _ = All False

-- | Tell if a formula in any form can be reduced
-- to a scalar somehow
isFormulaConstant' :: Formula anyKind -> Bool
isFormulaConstant' (Formula a) = isFormulaConstant a