keelung-0.9.0.0: src/Keelung/Constraint/R1C.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
-- | Rank-1 Constraint
module Keelung.Constraint.R1C (R1C (..), satisfy, freeVars) where
import Control.DeepSeq (NFData)
import Data.Field.Galois (GaloisField)
import Data.IntMap (IntMap)
import Data.IntMap qualified as IntMap
import Data.IntSet (IntSet)
import Data.Serialize (Serialize)
import GHC.Generics (Generic)
import Keelung.Data.Polynomial (Poly)
import Keelung.Data.Polynomial qualified as Poly
--------------------------------------------------------------------------------
-- | A Rank-1 Constraint is a relation between 3 polynomials
-- Ax * Bx = Cx
data R1C n = R1C (Either n (Poly n)) (Either n (Poly n)) (Either n (Poly n))
deriving (Generic, NFData)
instance (Eq n, Num n) => Eq (R1C n) where
R1C a b c == R1C a' b' c' =
-- if the RHS are the same
(c == c' && (a == a' && b == b' || a == b' && b == a'))
-- if the RHS are the negation of each other
|| ( negate' c == c'
&& ( negate' a == a' && b == b'
|| a == a' && negate' b == b'
|| negate' a == b' && b == a'
|| a == b' && negate' b == a'
)
)
where
negate' (Left n) = Left (-n)
negate' (Right p) = Right (Poly.negate p)
instance Functor R1C where
fmap f (R1C a b c) = R1C (fmapE a) (fmapE b) (fmapE c)
where
fmapE (Left x) = Left (f x)
fmapE (Right xs) = Right (fmap f xs)
instance Serialize n => Serialize (R1C n)
instance (Num n, Eq n, Ord n) => Ord (R1C n) where
compare x@(R1C a b c) y@(R1C e f g) = case (isRank1 x, isRank1 y) of
(True, False) -> LT
(False, True) -> GT
(True, True) ->
-- both are of rank 1
-- the one with a constant term on the RHS is considered smaller
case (c, g) of
(Left _, Right _) -> LT
(Right _, Left _) -> GT
(Left c0, Left c1) -> compare c0 c1 -- compare the constant terms
(Right v0, Right v1) -> compare v0 v1 -- compare the polynomials
(False, False) ->
-- both are of rank 2
compare (a, b, c) (e, f, g)
instance (Show n, Ord n, Eq n, Num n) => Show (R1C n) where
show (R1C aX bX cX) = case (aX, bX, cX) of
(Left 0, _, _) -> "0 = " ++ showVec cX
(_, Left 0, _) -> "0 = " ++ showVec cX
(Left 1, _, _) -> showVec bX ++ " = " ++ showVec cX
(_, Left 1, _) -> showVec aX ++ " = " ++ showVec cX
(_, _, _) -> showVecWithParen aX ++ " * " ++ showVecWithParen bX ++ " = " ++ showVec cX
where
showVec :: (Show n, Ord n, Eq n, Num n) => Either n (Poly n) -> String
showVec (Left c) = show c
showVec (Right xs) = show xs
-- wrap the string with parenthesis if it has more than 1 term
showVecWithParen :: (Show n, Ord n, Eq n, Num n) => Either n (Poly n) -> String
showVecWithParen (Left c) = showVec (Left c) -- no parenthesis
showVecWithParen (Right xs) =
let termNumber =
IntMap.size (Poly.coeffs xs)
+ if Poly.constant xs == 0
then 0
else 1
in if termNumber < 2
then showVec (Right xs)
else "(" ++ showVec (Right xs) ++ ")"
-- | See if a R1C is satified by a given assignment
satisfy :: GaloisField a => R1C a -> IntMap a -> Bool
satisfy constraint assignment
| R1C aV bV cV <- constraint =
evaluate aV assignment * evaluate bV assignment == evaluate cV assignment
where
evaluate :: GaloisField a => Either a (Poly a) -> IntMap a -> a
evaluate (Left x) _ = x
evaluate (Right p) w = Poly.evaluate p w
-- | Free variables in a R1C
freeVars :: R1C n -> IntSet
freeVars (R1C a b c) = freeVarsE a <> freeVarsE b <> freeVarsE c
where
freeVarsE (Left _) = mempty
freeVarsE (Right p) = Poly.vars p
-- | An R1C is of rank 1 if either side of the multiplication is a constant
isRank1 :: R1C n -> Bool
isRank1 (R1C a b _) = isConstant a || isConstant b
where
isConstant (Left _) = True
isConstant (Right _) = False