diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 294b6ba..554e958 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,7 +1,10 @@ 0.2.0.0 ------- -* allow parsing maximization problem as specified in +* Support some features described in “General OPB Format” https://www.cril.univ-artois.fr/PB24/OPBgeneral.pdf + * allow both min and max keywords in the objective function + * allow any usual relational operator in constraints + * allow to use Unicode characters and the UTF-8 encoding for relational operators 0.1.11.0 ------- diff --git a/pseudo-boolean.cabal b/pseudo-boolean.cabal index 6088237..90d04b0 100644 --- a/pseudo-boolean.cabal +++ b/pseudo-boolean.cabal @@ -67,7 +67,9 @@ library deepseq >=1.4.4.0, hashable >=1.2.7.0 && <1.5.0.0, void, - OptDir >= 0.1.0 + OptDir >= 0.1.0, + utf8-string >=1.0.1.1 && <1.1, + text >=1.2.3.1 && <3.0 hs-source-dirs: src default-language: Haskell2010 diff --git a/src/Data/PseudoBoolean/Attoparsec.hs b/src/Data/PseudoBoolean/Attoparsec.hs index 6da8436..479f422 100644 --- a/src/Data/PseudoBoolean/Attoparsec.hs +++ b/src/Data/PseudoBoolean/Attoparsec.hs @@ -39,6 +39,7 @@ import Data.Attoparsec.ByteString.Char8 hiding (isDigit) import qualified Data.Attoparsec.ByteString.Lazy as L import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy as BSLazy +import qualified Data.ByteString.UTF8 as UTF8 import Data.Char import Data.Maybe import Data.PseudoBoolean.Types @@ -157,7 +158,19 @@ objective_type = (string "min:" >> return OptMin) <|> (string "max:" >> return O -- ::= ">=" | "=" relational_operator :: Parser Op -relational_operator = (string ">=" >> return Ge) <|> (string "=" >> return Eq) +relational_operator = msum + [ string "=" >> return Eq + , string "!=" >> return NEq + , string ">=" >> return Ge + , string ">" >> return Gt + , string "<=" >> return Le + , string "<" >> return Lt + , u8string "≠" >> return NEq + , u8string "≥" >> return Ge + , u8string "≤" >> return Le + ] + where + u8string = string . UTF8.fromString -- ::= "x" variablename :: Parser Var diff --git a/src/Data/PseudoBoolean/Builder.hs b/src/Data/PseudoBoolean/Builder.hs index f51f261..a1cf104 100644 --- a/src/Data/PseudoBoolean/Builder.hs +++ b/src/Data/PseudoBoolean/Builder.hs @@ -99,7 +99,11 @@ showConstraint (lhs, op, rhs) = showSum lhs <> f op <> fromString " " <> fromString (show rhs) <> fromString ";\n" where f Eq = fromString "=" + f NEq = fromString "!=" + f Gt = fromString ">" f Ge = fromString ">=" + f Lt = fromString "<" + f Le = fromString "<=" showSoftConstraint :: (Monoid a, IsString a) => SoftConstraint -> a showSoftConstraint (cost, constr) = diff --git a/src/Data/PseudoBoolean/ByteStringBuilder.hs b/src/Data/PseudoBoolean/ByteStringBuilder.hs index 91f556e..efdacf1 100644 --- a/src/Data/PseudoBoolean/ByteStringBuilder.hs +++ b/src/Data/PseudoBoolean/ByteStringBuilder.hs @@ -107,7 +107,11 @@ showConstraint (lhs, op, rhs) = showSum lhs <> f op <> char7 ' ' <> integerDec rhs <> string7 ";\n" where f Eq = char7 '=' + f NEq = string7 "!=" + f Gt = string7 ">" f Ge = string7 ">=" + f Lt = string7 "<" + f Le = string7 "<=" showSoftConstraint :: SoftConstraint -> Builder showSoftConstraint (cost, constr) = diff --git a/src/Data/PseudoBoolean/Megaparsec.hs b/src/Data/PseudoBoolean/Megaparsec.hs index b4e52e8..0a72451 100644 --- a/src/Data/PseudoBoolean/Megaparsec.hs +++ b/src/Data/PseudoBoolean/Megaparsec.hs @@ -1,5 +1,6 @@ {-# LANGUAGE BangPatterns, FlexibleContexts, TypeFamilies, ConstraintKinds #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -Wall #-} ----------------------------------------------------------------------------- @@ -42,8 +43,10 @@ module Data.PseudoBoolean.Megaparsec import Prelude hiding (sum) import Control.Monad import Data.ByteString.Lazy (ByteString) -import qualified Data.ByteString.Lazy.Char8 as BL +import qualified Data.ByteString.Lazy as BL +import qualified Data.ByteString.Lazy.UTF8 as UTF8 import Data.Maybe +import Data.Proxy import Data.String import Data.Word import Data.Void @@ -173,8 +176,21 @@ objective_type :: C e s m => m OptDir objective_type = (try (string "min:") >> return OptMin) <|> (string "max:" >> return OptMax) -- ::= ">=" | "=" -relational_operator :: C e s m => m Op -relational_operator = (string ">=" >> return Ge) <|> (string "=" >> return Eq) +relational_operator :: forall e s m. C e s m => m Op +relational_operator = msum $ map try + [ string "=" >> return Eq + , string "!=" >> return NEq + , string ">=" >> return Ge + , string ">" >> return Gt + , string "<=" >> return Le + , string "<" >> return Lt + , u8string "≠" >> return NEq + , u8string "≥" >> return Ge + , u8string "≤" >> return Le + ] + where + -- XXX: We cannot assume Tokens s ~ ByteString + u8string s = label s (string . tokensToChunk (Proxy :: Proxy s) . BL.unpack . UTF8.fromString $ s) -- ::= "x" variablename :: C e s m => m Var @@ -224,7 +240,7 @@ type ParseError = MP.ParseErrorBundle BL.ByteString Void -- | Parse a OPB format string containing pseudo boolean problem. parseOPBString :: String -> String -> Either ParseError Formula -parseOPBString info s = parse (formula <* eof) info (BL.pack s) +parseOPBString info s = parse (formula <* eof) info (UTF8.fromString s) -- | Parse a OPB format lazy bytestring containing pseudo boolean problem. parseOPBByteString :: String -> ByteString -> Either ParseError Formula @@ -287,7 +303,7 @@ softconstraint = do -- | Parse a WBO format string containing weighted boolean optimization problem. parseWBOString :: String -> String -> Either ParseError SoftFormula -parseWBOString info s = parse (softformula <* eof) info (BL.pack s) +parseWBOString info s = parse (softformula <* eof) info (UTF8.fromString s) -- | Parse a WBO format lazy bytestring containing pseudo boolean problem. parseWBOByteString :: String -> ByteString -> Either ParseError SoftFormula diff --git a/src/Data/PseudoBoolean/Parsec.hs b/src/Data/PseudoBoolean/Parsec.hs index f2ab8e3..df9eb02 100644 --- a/src/Data/PseudoBoolean/Parsec.hs +++ b/src/Data/PseudoBoolean/Parsec.hs @@ -37,9 +37,10 @@ module Data.PseudoBoolean.Parsec import Prelude hiding (sum) import Control.Monad import Data.ByteString.Lazy (ByteString) +import qualified Data.ByteString.Lazy as BL import Data.Maybe +import qualified Data.Text.Lazy.Encoding as TL import Text.Parsec -import qualified Text.Parsec.ByteString.Lazy as ParsecBS import Data.PseudoBoolean.Types import Data.PseudoBoolean.Internal.TextUtil @@ -156,7 +157,17 @@ objective_type = (try (string "min:") >> return OptMin) <|> (string "max:" >> re -- ::= ">=" | "=" relational_operator :: Stream s m Char => ParsecT s u m Op -relational_operator = (string ">=" >> return Ge) <|> (string "=" >> return Eq) +relational_operator = msum $ map try + [ string "=" >> return Eq + , string "!=" >> return NEq + , string ">=" >> return Ge + , string ">" >> return Gt + , string "<=" >> return Le + , string "<" >> return Lt + , string "≠" >> return NEq + , string "≥" >> return Ge + , string "≤" >> return Le + ] -- ::= "x" variablename :: Stream s m Char => ParsecT s u m Var @@ -215,7 +226,9 @@ parseOPBByteString = parse (formula <* eof) -- | Parse a OPB file containing pseudo boolean problem. parseOPBFile :: FilePath -> IO (Either ParseError Formula) -parseOPBFile = ParsecBS.parseFromFile (formula <* eof) +parseOPBFile fname = do + input <- BL.readFile fname + return $ runP (formula <* eof) () fname (TL.decodeUtf8 input) -- ::= @@ -277,4 +290,6 @@ parseWBOByteString = parse (softformula <* eof) -- | Parse a WBO file containing weighted boolean optimization problem. parseWBOFile :: FilePath -> IO (Either ParseError SoftFormula) -parseWBOFile = ParsecBS.parseFromFile (softformula <* eof) +parseWBOFile fname = do + input <- BL.readFile fname + return $ runP (softformula <* eof) () fname (TL.decodeUtf8 input) diff --git a/src/Data/PseudoBoolean/Types.hs b/src/Data/PseudoBoolean/Types.hs index 973b298..113d29b 100644 --- a/src/Data/PseudoBoolean/Types.hs +++ b/src/Data/PseudoBoolean/Types.hs @@ -75,7 +75,11 @@ type Constraint = (Sum, Op, Integer) -- | Relational operators data Op = Ge -- ^ /greater than or equal/ + | Le -- ^ /lesser than or equal/ + | Gt -- ^ /greater than/ + | Lt -- ^ /lesser than/ | Eq -- ^ /equal/ + | NEq -- ^ /not equal/ deriving (Eq, Ord, Show, Read, Enum, Bounded, Typeable, Data, Generic) instance NFData Op diff --git a/test/TestPBFile.hs b/test/TestPBFile.hs index ebb6dbf..4a64cea 100644 --- a/test/TestPBFile.hs +++ b/test/TestPBFile.hs @@ -48,6 +48,20 @@ case_invalid_lhs_empty_sum = checkOPBFile "test/samples/invalid-lhs-empty-sum.op case_invalid_lhs_empty_sum_wbo = checkWBOFile "test/samples/invalid-lhs-empty-sum.wbo" case_general_testlin_max_file = checkOPBFile "test/samples/general/testlin-max.pb" +case_general_relational_operator_file = checkOPBFile "test/samples/general/test-relational-operator.pb" +case_general_relational_operator_unicode_file = checkOPBFile "test/samples/general/test-relational-operator-unicode.pb" +case_general_relational_operator = checkOPBString "general relational operator" exampleGeneralRelationalOperator +case_general_relational_operator_unicode = checkOPBString "general relational operator unicode" exampleGeneralRelationalOperatorUnicode + +case_general_relational_operator_unicode_equivalence = do + Right expected <- parseOPBFile "test/samples/general/test-relational-operator.pb" + Right opbP <- parseOPBFile "test/samples/general/test-relational-operator-unicode.pb" + expected @?= opbP + Right opbM <- M.parseOPBFile "test/samples/general/test-relational-operator-unicode.pb" + expected @?= opbM + Right opbA <- A.parseOPBFile "test/samples/general/test-relational-operator-unicode.pb" + expected @?= opbA + case_trailing_junk = do isError (parseOPBString "" trailingJunk) @?= True @@ -299,6 +313,30 @@ exampleWBO3 = unlines $ , "-1 x3 -1 x4 >= -1 ;" ] +exampleGeneralRelationalOperator :: String +exampleGeneralRelationalOperator = unlines $ + [ "* #variable= 7 #constraint= 6" + , "max: 1 x1 ;" + , "+1 x1 +2 x2 >= 1;" + , "-1 x2 -2 x3 <= -1;" + , "+1 x3 +2 x4 > 1;" + , "-1 x4 -2 x5 < 0;" + , "+1 x5 +2 x6 = 3;" + , "-1 x6 -2 x7 != -3;" + ] + +exampleGeneralRelationalOperatorUnicode :: String +exampleGeneralRelationalOperatorUnicode = unlines $ + [ "* #variable= 7 #constraint= 6" + , "max: 1 x1 ;" + , "+1 x1 +2 x2 ≥ 1;" + , "-1 x2 -2 x3 ≤ -1;" + , "+1 x3 +2 x4 > 1;" + , "-1 x4 -2 x5 < 0;" + , "+1 x5 +2 x6 = 3;" + , "-1 x6 -2 x7 ≠ -3;" + ] + ------------------------------------------------------------------------ -- Utilities diff --git a/test/samples/general/test-relational-operator-unicode.pb b/test/samples/general/test-relational-operator-unicode.pb new file mode 100644 index 0000000..442ab73 --- /dev/null +++ b/test/samples/general/test-relational-operator-unicode.pb @@ -0,0 +1,8 @@ +* #variable= 7 #constraint= 6 +max: 1 x1 ; ++1 x1 +2 x2 ≥ 1; +-1 x2 -2 x3 ≤ -1; ++1 x3 +2 x4 > 1; +-1 x4 -2 x5 < 0; ++1 x5 +2 x6 = 3; +-1 x6 -2 x7 ≠ -3; diff --git a/test/samples/general/test-relational-operator.pb b/test/samples/general/test-relational-operator.pb new file mode 100644 index 0000000..ca3890f --- /dev/null +++ b/test/samples/general/test-relational-operator.pb @@ -0,0 +1,8 @@ +* #variable= 7 #constraint= 6 +max: 1 x1 ; ++1 x1 +2 x2 >= 1; +-1 x2 -2 x3 <= -1; ++1 x3 +2 x4 > 1; +-1 x4 -2 x5 < 0; ++1 x5 +2 x6 = 3; +-1 x6 -2 x7 != -3;