packages feed

ideas-math-1.0: src/Domain/LinearAlgebra/EquationsRules.hs

-----------------------------------------------------------------------------
-- Copyright 2013, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------
module Domain.LinearAlgebra.EquationsRules
   ( ruleCoverAllEquations, ruleUncoverEquation, ruleScaleEquation
   , ruleBackSubstitution, ruleIdentifyFreeVariables, ruleExchangeEquations
   , ruleEliminateVar, ruleCoverUpEquation, ruleInconsistentSystem
   , ruleDropEquation, equationsRules
   , simplifySystem, remaining, linId
   ) where

import Control.Monad
import Data.List
import Data.Maybe
import Domain.LinearAlgebra.LinearSystem
import Domain.LinearAlgebra.LinearView
import Domain.LinearAlgebra.MatrixRules (covered, getCovered) -- for context
import Domain.Math.Data.Relation
import Domain.Math.Expr
import Domain.Math.Simplification (simplify)
import Ideas.Common.Library hiding (simplify)
import Ideas.Common.Utils
import Prelude

linId :: Id
linId = newId "linearalgebra.linsystem"

equationsRules :: [Rule (Context (LinearSystem Expr))]
equationsRules =
   [ ruleExchangeEquations, ruleEliminateVar, ruleDropEquation
   , ruleInconsistentSystem
   , ruleScaleEquation, ruleBackSubstitution, ruleIdentifyFreeVariables
   , ruleCoverUpEquation, ruleUncoverEquation, ruleCoverAllEquations
   ]

ruleExchangeEquations :: Rule (Context (LinearSystem Expr))
ruleExchangeEquations = describe "Exchange two equations" $
   simplifySystem $ ruleTrans (linId, "exchange") $
   supplyContextParameters exchangeEquations $ \ls -> do
      mv  <- minvar ls
      eqs <- remaining ls
      i   <- findIndexM (elem mv . getVarsSystem . return) eqs
      cov <- getCovered
      return (cov, cov + i)

ruleEliminateVar :: Rule (Context (LinearSystem Expr))
ruleEliminateVar = describe "Eliminate a variable (using addition)" $
   simplifySystem $ ruleTrans (linId, "eliminate") $
   supplyContextParameters addEquations $ \ls -> do
      mv <- minvar ls
      hd:rest <- remaining ls
      let getCoef = coefficientOf mv . leftHandSide
      (i, coef) <- msum [ return (i, c) | (i, eq) <- zip [0..] rest, let c = getCoef eq, c /= 0 ]
      guard (getCoef hd /= 0)
      let v = negate coef / getCoef hd
      cov <- getCovered
      return (i + cov + 1, cov, v)

ruleDropEquation :: Rule (Context (LinearSystem Expr))
ruleDropEquation = describe "Drop trivial equations (such as 0=0)" $
   simplifySystem $ ruleTrans (linId, "trivial") $ makeTransLiftContext $ \ls -> do
      i   <- findIndexM (fromMaybe False . testConstants (==)) ls
      cov <- getCovered
      let f n = if i < n then n-1 else n
      covered := f cov
      return (deleteIndex i ls)

ruleInconsistentSystem :: Rule (Context (LinearSystem Expr))
ruleInconsistentSystem = describe "Inconsistent system (0=1)" $
   simplifySystem $ ruleTrans (linId, "inconsistent") $ makeTransLiftContext $ \ls -> do
      let stop = [0 :==: 1]
      guard (invalidSystem ls && ls /= stop)
      covered := 1
      return stop

ruleScaleEquation :: Rule (Context (LinearSystem Expr))
ruleScaleEquation = describe "Scale equation to one" $
   simplifySystem $ ruleTrans (linId, "scale") $
   supplyContextParameters scaleEquation $ \ls -> do
      cov <- getCovered
      eq  <- elementAt cov ls
      let expr = leftHandSide eq
      mv <- minvar ls
      guard (coefficientOf mv expr /= 0)
      let coef = 1 / coefficientOf mv expr
      return (cov, coef)

ruleBackSubstitution :: Rule (Context (LinearSystem Expr))
ruleBackSubstitution = describe "Back substitution" $
   simplifySystem $ ruleTrans (linId, "subst") $
   supplyContextParameters addEquations $ \ls -> do
      cov <- getCovered
      eq  <- elementAt cov ls
      let expr = leftHandSide eq
      mv <- headM (vars expr)
      i  <- findIndexM ((/= 0) . coefficientOf mv . leftHandSide) (take cov ls)
      let coef = negate $ coefficientOf mv (leftHandSide (ls !! i))
      return (i, cov, coef)

ruleIdentifyFreeVariables :: IsLinear a => Rule (Context (LinearSystem a))
ruleIdentifyFreeVariables = describe "Identify free variables" $
   liftToContext $ minorRule (linId, "freevars") $ \ls ->
      let vs = [ head ys | ys <- map (vars . leftHandSide) ls, not (null ys) ]
          f eq =
             let (e1, e2) = splitLinearExpr (`notElem` vs) (leftHandSide eq) -- constant ends up in e1
             in e2 :==: rightHandSide eq - e1
      in Just (map f ls)

ruleCoverUpEquation :: Rule (Context (LinearSystem a))
ruleCoverUpEquation = describe "Cover up first equation" $
   minor $ ruleTrans (linId, "coverup") $ changeCover succ

ruleUncoverEquation :: Rule (Context (LinearSystem a))
ruleUncoverEquation = describe "Uncover one equation" $
   minor $ ruleTrans (linId, "uncover") $ changeCover pred

ruleCoverAllEquations :: Rule (Context (LinearSystem a))
ruleCoverAllEquations = describe "Cove all equations" $
   minorRule (linId, "coverall") $ \cls -> do
      ls <- fromContext cls
      Just (insertRef covered (length ls) cls)

-- local helper functions
deleteIndex :: Int -> [a] -> [a]
deleteIndex i xs = ys ++ drop 1 zs
 where (ys, zs) = splitAt i xs

testConstants :: IsLinear a => (a -> a -> Bool) -> Equation a -> Maybe Bool
testConstants f (lhs :==: rhs)
   | hasNoVar lhs && hasNoVar rhs = Just (f lhs rhs)
   | otherwise = Nothing

-- simplify a linear system
simplifySystem :: Rule (Context (LinearSystem Expr)) -> Rule (Context (LinearSystem Expr))
simplifySystem = doAfter $ changeInContext (map (fmap f))
 where f = simplifyWith (fmap simplify) linearView

---------------------------------------------------------------------------------
-- Parameterized transformations

exchangeEquations :: ParamTrans (Int, Int) (LinearSystem a)
exchangeEquations = parameter2 "equation 1" "equation 2" exchange
 where
   exchange i j
      | i > j     = exchange j i
      | otherwise = transMaybe $ \xs -> do
           guard (i/=j && validEquation i xs && validEquation j xs)
           let (begin, x:rest) = splitAt i xs
               (middle, y:end) = splitAt (j-i-1) rest
           return $ begin++[y]++middle++[x]++end

scaleEquation :: (Eq a, Reference a, IsLinear a) => ParamTrans (Int, a) (LinearSystem a)
scaleEquation = parameter2 "equation" "scale factor" $ \i a -> transMaybe $ \xs -> do
   guard (a `notElem` [0,1])
   changeAt i (fmap (a*)) xs

addEquations :: (Reference a, IsLinear a) => ParamTrans (Int, Int, a) (LinearSystem a)
addEquations = parameter3 "equation 1" "equation 2" "scale factor" $ \i j a -> transMaybe $ \xs -> do
   guard (i/=j)
   j1 :==: j2 <- liftM (fmap (a*)) (elementAt j xs)
   let f (i1 :==: i2) = i1+j1 :==: i2+j2
   changeAt i f xs

changeCover :: (Int -> Int) -> Transformation (Context (LinearSystem a))
changeCover f = makeTransLiftContext_ $ \ls -> do
   new <- liftM f getCovered
   guard (new >= 0 && new <= length ls)
   covered := new

-- local helper function
validEquation :: Int -> [a] -> Bool
validEquation n xs = n >= 0 && n < length xs

-- | The equations that remain to be solved
remaining :: LinearSystem a -> EnvMonad (Equations a)
remaining ls = do
   cov <- getCovered
   return (drop cov ls)

-- | The minimal variable in the remaining equations
minvar :: IsLinear a => LinearSystem a -> EnvMonad String
minvar ls = do
   xs <- liftM getVarsSystem (remaining ls)
   guard (not $ null xs)
   return (minimum xs)