module Common where
import qualified Data.Traversable as Trav
import qualified Data.Foldable as Fold
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.List.HT as ListHT
import qualified Data.List as List
import qualified Data.NonEmpty.Class as NonEmptyC
import qualified Data.NonEmpty as NonEmpty
import Data.Map (Map)
import Data.Set (Set)
import Data.Maybe (mapMaybe, isNothing)
import Data.Tuple.HT (mapPair, mapSnd, swap)
import qualified Text.Parsec as Parsec
import Text.Parsec.String (Parser)
import Text.Printf (printf)
import qualified Shell.Utility.Log as Log
import Shell.Utility.Verbosity (Verbosity)
import Control.Monad (when)
import Control.Applicative (liftA2, (<$>))
type Reactant = Map String Integer
parseReactant :: Parser Reactant
parseReactant =
fmap (Map.fromListWith (+)) $
Parsec.many1 $ do
element <- liftA2 (:) Parsec.upper (Parsec.many Parsec.lower)
kStr <- Parsec.many Parsec.digit
case (kStr, read kStr) of
("", _) -> return (element, 1)
{- ToDo:
should be an unrecoverable parser error
in order to not generate confusing additional errors,
e.g. when parsing C0
-}
(_, 0) ->
Parsec.unexpected $
printf "element %s has multiplicity zero" element
(_, k) -> return (element, k)
preprocessArguments ::
Verbosity -> [String] -> IO (Set Reactant, [(Reactant, String)])
preprocessArguments verbosity args = do
let (brokenArgs,reactants) =
ListHT.unzipEithers $
map (\arg -> flip (,) arg <$> Parsec.parse parseReactant arg arg)
args
case brokenArgs of
_:_ -> fail $ concatMap ("\n\n"++) $ map show brokenArgs
[] -> do
let m = Map.fromListWith NonEmptyC.append $
map (mapSnd NonEmpty.singleton) reactants
let duplicatesGroups =
Map.filter
(\xs -> case xs of NonEmpty.Cons _ (_:_) -> True; _ -> False)
m
Fold.for_ duplicatesGroups $ \duplicates ->
Log.warn verbosity $
printf "duplicate reactants: %s\n"
(List.intercalate ", " $ NonEmpty.flatten duplicates)
return
(Map.keysSet m,
map snd $ filter fst $ snd $
Trav.mapAccumL
(\seenSoFar p@(reactant,_name) ->
(Set.insert reactant seenSoFar,
(Set.notMember reactant seenSoFar, p)))
Set.empty reactants)
mapFromListWithoutDuplicates :: (Ord k) => [(k,a)] -> Either [k] (Map k a)
mapFromListWithoutDuplicates xs =
let m = Map.fromListWith (\_ _ -> Nothing) $ map (mapSnd Just) xs in
case Trav.sequence m of
Just table -> Right table
Nothing -> Left $ Map.keys $ Map.filter isNothing m
cancelIntegerVector :: (Functor f, Foldable f) => f Integer -> f Integer
cancelIntegerVector v =
let d = Fold.foldl gcd 0 v
in fmap (flip div d) v
integerLinearCombination :: Map Reactant Integer -> Map String Integer
integerLinearCombination =
Map.unionsWith (+) . Map.elems .
Map.mapWithKey (\r k -> fmap (k*) r)
formatEquation :: [(Reactant,String)] -> Map Reactant Integer -> String
formatEquation rs reactantMap =
let tagged =
mapMaybe
(\(r,name) ->
let n = reactantMap Map.! r in
case compare n 0 of
EQ -> Nothing
GT -> Just $ (True,(n,name))
LT -> Just $ (False,(-n,name)))
rs
(lhs,rhs) =
(case tagged of ((True,_):_) -> id; _ -> swap) $
mapPair (map snd, map snd) $
List.partition fst tagged
formatSum =
List.intercalate " + " .
map (\(n,str) -> if n==1 then str else printf "%d %s" n str)
in formatSum lhs ++ " <=> " ++ formatSum rhs
displaySolutions ::
Verbosity -> [(Reactant, String)] -> [Map Reactant Integer] -> IO ()
displaySolutions verbosity reactants sols = do
Fold.for_ sols $ \sol -> do
putStrLn . formatEquation reactants $ sol
let probe = integerLinearCombination sol
when (Fold.any (0/=) probe) $
Log.warn verbosity $ "probe failed\n" ++ show probe ++ "\n"
let unused =
if null sols
then Set.fromList $ map fst reactants
else Map.keysSet $
Map.filter id $ Map.unionsWith (&&) $ map (fmap (0==)) sols
when (not $ Set.null unused) $
Log.warn verbosity $
printf "Unused reactants: %s\n" $ List.intercalate ", " $ map snd $
filter (flip Set.member unused . fst) $ reactants