{- | Module : Data.Matrix.AsXYZ.ParseXYZ Copyright : (c) Jun Narumi 2018-2020 License : BSD3 Maintainer : narumij@gmail.com Stability : experimental Portability : ? Jones-Faithful notation parser for spacegroup. -} module Data.Matrix.AsXYZ.ParseXYZ ( ReadNum(..), equivalentPositions, transformPpABC, transformQqXYZ, ratio, floating, integral, sign, minus, overlap, elementBody, ) where import Control.Monad import Data.Char import Data.Maybe import Data.List import Text.ParserCombinators.Parsec import Data.Ratio import Data.Ratio.Slash import Data.Ratio.ParseFloat (readFloatingPoint) import Data.Matrix (fromList,fromLists,Matrix(..),joinBlocks,(<->)) import Data.Matrix.AsXYZ.Common (Var(..),Val(..)) -- | Jones-Faithful notation parser -- -- >>> parse (equivalentPositions integral) "" "x+1,y+2,z+3" -- Right [[1,0,0,1],[0,1,0,2],[0,0,1,3]] equivalentPositions :: Num a =>               ReadNum a -- ^ ratio or floating or integral              -> CharParser () [[a]] equivalentPositions = components xyz -- | Same as equivalentPositions but uses abc instead of xyz -- -- >>> parse (transformPpABC integral) "" "a+1,b+2,c+3" -- Right [[1,0,0,1],[0,1,0,2],[0,0,1,3]] transformPpABC :: Num a => ReadNum a -> CharParser () [[a]] transformPpABC = components abc -- | Alias of equivalentPositions -- -- >>> parse (transformQqXYZ integral) "" "x+1,y+2,z+3" -- Right [[1,0,0,1],[0,1,0,2],[0,0,1,3]] transformQqXYZ :: Num a => ReadNum a -> CharParser () [[a]] transformQqXYZ = components xyz -- | Converter of 3 kind of number (int,float,ratio) string to rational -- -- >>> parse (equivalentPositions ratio) "" "x+1,y+2,z+3" -- Right [[1 % 1,0 % 1,0 % 1,1 % 1],[0 % 1,1 % 1,0 % 1,2 % 1],[0 % 1,0 % 1,1 % 1,3 % 1]] ratio :: Integral a => ReadNum (Ratio a) ratio (I s) = Right $ getRatio . read $ s ratio (R s) = Right $ getRatio . read $ s ratio (F s) = Right $ readFloatingPoint s -- | Converter of integral number description to integral -- -- This can not read ratio and floating string (e.g. '1/2', '0.1') -- -- >>> parse (equivalentPositions integral) "" "x+1,y+2,z+3" -- Right [[1,0,0,1],[0,1,0,2],[0,0,1,3]] integral :: Integral a => ReadNum a integral (I s) = Right $ fromIntegral (read s :: Integer) integral (R s) = Left $ "cannot convert to integer from " ++ s ++ "." integral (F s) = Left $ "cannot convert to integer from " ++ s ++ "." -- | Converter of 3 kind of number description to floating point -- -- >>> parse (equivalentPositions floating) "" "x+1,y+2,z+3" -- Right [[1.0,0.0,0.0,1.0],[0.0,1.0,0.0,2.0],[0.0,0.0,1.0,3.0]] floating :: Floating a => ReadNum a floating v = fromRational <$> ratio v -- | Type of numeric type information generated in the middle type Value = Val String v c = f $ toLower <$> c where f (Just 'x') = X f (Just 'a') = X f (Just 'y') = Y f (Just 'b') = Y f (Just 'z') = Z f (Just 'c') = Z f Nothing = W sign :: CharParser () Char sign = oneOf "-+" zero :: CharParser () String zero = do char '0' return "0" num :: CharParser () String num = do x <- oneOf "123456789" xs <- many digit return $ x : xs int :: CharParser () String int = zero <|> num integer :: CharParser () Value integer = do i <- int return (I i) float :: CharParser () Value float = do i <- option "" int char '.' f <- many digit return (F $ i ++ "." ++ f ) fract :: CharParser () Value fract = do n <- many1 digit option () spaces char '/' option () spaces d <- many1 digit return (R $ n ++ "/" ++ d) number' :: CharParser () Value number' = try fract <|> try float <|> integer type ReadNum b = Value -> Either String b number :: ReadNum b -> CharParser () b number numRead = do n <- number' case numRead n of Left s -> fail s Right nn -> return nn elementBody :: CharParser () Char -> ReadNum a -> CharParser () (Maybe a, Maybe Char) elementBody var conv = do n <- optionMaybe (number conv) option () spaces v <- optionMaybe var option () spaces guard (isJust n || isJust v) return (n,v) minus :: Num a => Maybe Char -> (a -> a) minus (Just '-') = negate minus (Just '+') = id minus Nothing = id one :: Num a => CharParser () Char -> ReadNum a -> CharParser () (Var a) one var numRead = do s <- optionMaybe sign option () spaces (n,l) <- elementBody var numRead return $ v l . minus s . fromMaybe 1 $ n other :: Num a => CharParser () Char -> ReadNum a -> CharParser () (Var a) other var numRead = do s <- sign option () spaces (n,l) <- elementBody var numRead return $ v l . minus (Just s) . fromMaybe 1 $ n overlap :: Eq a => [a] -> Bool overlap n = (length . nub) n /= length n constructRow :: Num a => [Var a] -> [a] constructRow = map (fromMaybe 0 . listToMaybe . catMaybes) . transpose . map toArray where toArray (X n) = [Just n,Nothing,Nothing,Nothing] toArray (Y n) = [Nothing,Just n,Nothing,Nothing] toArray (Z n) = [Nothing,Nothing,Just n,Nothing] toArray (W n) = [Nothing,Nothing,Nothing,Just n] component :: Num b => CharParser () Char -> ReadNum b -> CharParser () [b] component var numRead = do option () spaces x <- one var numRead xs <- many (other var numRead) option () spaces let mm = x : xs if overlap (map void mm) then fail "overlaps var type" else return (constructRow mm) components :: Num a => CharParser () Char -> ReadNum a -> CharParser () [[a]] components var conv = do a <- component var conv char ',' b <- component var conv char ',' c <- component var conv return [a,b,c] xyz :: CharParser () Char xyz = oneOf "xyzXYZ" abc :: CharParser () Char abc = oneOf "abcABC"