stim-parser 0.2.0.1 → 0.3.0.0
raw patch · 9 files changed
+138/−64 lines, 9 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- StimParser.Parse: parseTag :: Parser Tag
+ StimParser.DEM.Expr: [ddTag] :: DEMDetector -> !Maybe Tag
+ StimParser.DEM.Expr: [doTag] :: DEMObservable -> !Maybe Tag
+ StimParser.Expr: instance GHC.Classes.Eq StimParser.Expr.Tag
+ StimParser.ParseUtils: parseTag :: Parser Tag
+ StimParser.ParseUtils: tagContentForbidden :: Char -> Bool
- StimParser.DEM.Expr: DEMDetector :: !DetectorId -> ![Double] -> DEMDetector
+ StimParser.DEM.Expr: DEMDetector :: !DetectorId -> ![Double] -> !Maybe Tag -> DEMDetector
- StimParser.DEM.Expr: DEMObservable :: !ObservableId -> DEMObservable
+ StimParser.DEM.Expr: DEMObservable :: !ObservableId -> !Maybe Tag -> DEMObservable
Files
- CHANGELOG.md +14/−0
- src/StimParser/DEM/Expr.hs +7/−3
- src/StimParser/DEM/Parse.hs +6/−3
- src/StimParser/Expr.hs +1/−1
- src/StimParser/Parse.hs +0/−9
- src/StimParser/ParseUtils.hs +14/−0
- stim-parser.cabal +1/−1
- test/Test/DEM/Expr.hs +34/−34
- test/Test/DEM/Parse.hs +61/−13
CHANGELOG.md view
@@ -1,5 +1,19 @@ # Revision history for stim-parser +## 0.3.0.0 -- 2026-07-20++* Add optional tag support for DEM detector and observable declarations.+ Stim's DEM format allows `detector[tag](...) D#` and+ `logical_observable[tag] L#`; these are now parsed and exposed in the AST.+ This is a **breaking API change**: `DEMDetector` and `DEMObservable`+ gained new `ddTag` / `doTag` fields of type `Maybe Tag`.+* Move `parseTag` from `StimParser.Parse` to `StimParser.ParseUtils` so it+ can be shared by the DEM parser. Modules importing `parseTag` from+ `StimParser.Parse` should import it from `StimParser.ParseUtils` instead.+* Add `Eq` instance for `StimParser.Expr.Tag`.+* Add DEM parser regression tests for tagged detectors/observables and+ rejection of double tags.+ ## 0.2.0.1 -- 2026-07-18 * Fix noise-channel parsing order in `parseStim`.
src/StimParser/DEM/Expr.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE InstanceSigs #-} module StimParser.DEM.Expr where +import StimParser.Expr (Tag)+ -- | A detector index (check node in the bipartite graph). newtype DetectorId = DetectorId { unDetectorId :: Int } deriving (Show, Eq, Ord)@@ -30,11 +32,13 @@ data DEMDetector = DEMDetector { ddId :: !DetectorId , ddCoords :: ![Double]+ , ddTag :: !(Maybe Tag) } deriving (Show, Eq) -- | A logical observable declaration: logical_observable L0 data DEMObservable = DEMObservable- { doId :: !ObservableId+ { doId :: !ObservableId+ , doTag :: !(Maybe Tag) } deriving (Show, Eq) -- | Shift detectors instruction: shift_detectors(1, 0) 0@@ -93,11 +97,11 @@ DEMInstrRepeat n body -> flattenInstrs coordShift detShift (concat (replicate n body) ++ is) - DEMInstrDetector (DEMDetector (DetectorId did) coords) ->+ DEMInstrDetector (DEMDetector (DetectorId did) coords tag) -> let shiftPadded = padCoords (length coords) coordShift shiftedCoords = zipWith (+) shiftPadded coords shiftedId = DetectorId (did + detShift)- in DEMInstrDetector (DEMDetector shiftedId shiftedCoords) : flattenInstrs coordShift detShift is+ in DEMInstrDetector (DEMDetector shiftedId shiftedCoords tag) : flattenInstrs coordShift detShift is DEMInstrError err -> DEMInstrError err : flattenInstrs coordShift detShift is
src/StimParser/DEM/Parse.hs view
@@ -2,9 +2,10 @@ module StimParser.DEM.Parse where import Text.Megaparsec-import Text.Megaparsec.Char+import Text.Megaparsec.Char () import StimParser.ParseUtils+import StimParser.Expr (Tag) import StimParser.DEM.Expr -- | Parse a complete DEM string.@@ -40,16 +41,18 @@ parseDEMDetector :: Parser DEMDetector parseDEMDetector = do lstring "detector"+ tag <- optional parseTag :: Parser (Maybe Tag) coords <- parseTupleNumber did <- DetectorId <$> (lstring "D" *> parseInt)- return $ DEMDetector did coords+ return $ DEMDetector did coords tag -- | Parse a logical observable declaration: logical_observable L0 parseDEMObservable :: Parser DEMObservable parseDEMObservable = do lstring "logical_observable"+ tag <- optional parseTag :: Parser (Maybe Tag) oid <- ObservableId <$> (lstring "L" *> parseInt)- return $ DEMObservable oid+ return $ DEMObservable oid tag -- | Parse a shift_detectors instruction: shift_detectors(1, 0) 0 or shift_detectors 96 --
src/StimParser/Expr.hs view
@@ -151,7 +151,7 @@ -- General Instruction Tag (v1.15+) -- Examples: TICK[100ns], X_ERROR[custom](0.1) 0 newtype Tag = Tag String- deriving (Show)+ deriving (Show, Eq) -- Annotations data AnnTy =
src/StimParser/Parse.hs view
@@ -88,15 +88,6 @@ parseEnum [] = error "value error" parseEnum xs = msum $ map parseShowCI $ sortOn (negate . length . show) xs --- | Parse an optional tag: [tag_content]--- Tag content can be any character except ], \r, \n-parseTag :: Parser Tag-parseTag = do- lstring "["- content <- many $ satisfy (\c -> c /= ']' && c /= '\r' && c /= '\n')- lstring "]"- return $ Tag content- parseGateTy :: Parser GateTy parseGateTy = parseEnum gateTyList
src/StimParser/ParseUtils.hs view
@@ -17,6 +17,8 @@ import Data.Maybe import Data.Char (toLower) +import StimParser.Expr (Tag (..))+ type Parser = Parsec Void String -- type Parser = ParsecT Void String (State Env) @@ -100,6 +102,18 @@ parseVar :: Parser String parseVar = do lexeme $ safeManyTill L.charLiteral (lookAhead (try space1 <|> eof))++-- | Parse an optional tag: [tag_content]+-- Tag content can be any character except ], \r, \n+tagContentForbidden :: Char -> Bool+tagContentForbidden c = c == ']' || c == '\r' || c == '\n'++parseTag :: Parser Tag+parseTag = do+ lstring "["+ content <- Text.Megaparsec.many $ satisfy (not . tagContentForbidden)+ lstring "]"+ return $ Tag content excludePredict :: Parser a -> Parser () excludePredict p = lookAhead $ notFollowedBy p
stim-parser.cabal view
@@ -20,7 +20,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.2.0.1+version: 0.3.0.0 -- A short (one-line) description of the package. synopsis: A parser combinator library for STIM quantum circuit files
test/Test/DEM/Expr.hs view
@@ -31,8 +31,8 @@ let dem = DEM [ DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0)]) , DEMInstrError (DEMError 0.02 [TargetDetector (DetectorId 1), TargetObservable (ObservableId 0)])- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])- , DEMInstrObservable (DEMObservable (ObservableId 0))+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)+ , DEMInstrObservable (DEMObservable (ObservableId 0) Nothing) ] in dem ~=? flattenDEM dem ]@@ -73,25 +73,25 @@ [ "flatten shift detectors" ~: let input = DEM [ DEMInstrShift (DEMShift [1.0, 0.0] 0)- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])- , DEMInstrDetector (DEMDetector (DetectorId 1) [2.0, 3.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)+ , DEMInstrDetector (DEMDetector (DetectorId 1) [2.0, 3.0] Nothing) ] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0])- , DEMInstrDetector (DEMDetector (DetectorId 1) [3.0, 3.0])+ [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0] Nothing)+ , DEMInstrDetector (DEMDetector (DetectorId 1) [3.0, 3.0] Nothing) ] in expected ~=? flattenDEM input , "flatten cumulative shift" ~: let input = DEM [ DEMInstrShift (DEMShift [1.0] 0)- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0] Nothing) , DEMInstrShift (DEMShift [2.0] 0)- , DEMInstrDetector (DEMDetector (DetectorId 1) [0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 1) [0.0] Nothing) ] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0])- , DEMInstrDetector (DEMDetector (DetectorId 1) [3.0])+ [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0] Nothing)+ , DEMInstrDetector (DEMDetector (DetectorId 1) [3.0] Nothing) ] in expected ~=? flattenDEM input ]@@ -103,15 +103,15 @@ let input = DEM [ DEMInstrRepeat 2 [ DEMInstrShift (DEMShift [1.0] 0)- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0] Nothing) ] ] -- Note: shifts are cumulative across repeat iterations. -- Iteration 1: shift=[1.0], detector gets [1.0] -- Iteration 2: shift becomes [2.0], detector gets [2.0] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0])- , DEMInstrDetector (DEMDetector (DetectorId 0) [2.0])+ [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0] Nothing)+ , DEMInstrDetector (DEMDetector (DetectorId 0) [2.0] Nothing) ] in expected ~=? flattenDEM input ]@@ -122,22 +122,22 @@ [ "flatten detector id shift" ~: let input = DEM [ DEMInstrShift (DEMShift [0.0] 5)- , DEMInstrDetector (DEMDetector (DetectorId 0) [1.0])- , DEMInstrDetector (DEMDetector (DetectorId 1) [2.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [1.0] Nothing)+ , DEMInstrDetector (DEMDetector (DetectorId 1) [2.0] Nothing) ] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 5) [1.0])- , DEMInstrDetector (DEMDetector (DetectorId 6) [2.0])+ [ DEMInstrDetector (DEMDetector (DetectorId 5) [1.0] Nothing)+ , DEMInstrDetector (DEMDetector (DetectorId 6) [2.0] Nothing) ] in expected ~=? flattenDEM input , "flatten combined coord and id shift" ~: let input = DEM [ DEMInstrShift (DEMShift [1.0, 0.0] 10)- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing) ] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 10) [1.0, 0.0])+ [ DEMInstrDetector (DEMDetector (DetectorId 10) [1.0, 0.0] Nothing) ] in expected ~=? flattenDEM input ]@@ -148,20 +148,20 @@ [ "2D shift on 3D detector" ~: let input = DEM [ DEMInstrShift (DEMShift [1.0, 0.0] 0)- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0, 0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0, 0.0] Nothing) ] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0, 0.0])+ [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0, 0.0] Nothing) ] in expected ~=? flattenDEM input , "3D shift on 2D detector" ~: let input = DEM [ DEMInstrShift (DEMShift [1.0, 0.0, 0.0] 0)- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing) ] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0])+ [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0] Nothing) ] in expected ~=? flattenDEM input ]@@ -172,23 +172,23 @@ [ "bare shift preserves coords" ~: let input = DEM [ DEMInstrShift (DEMShift [1.0, 0.5] 0)- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing) , DEMInstrShift (DEMShift [] 10)- , DEMInstrDetector (DEMDetector (DetectorId 1) [0.0, 0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 1) [0.0, 0.0] Nothing) ] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.5])- , DEMInstrDetector (DEMDetector (DetectorId 11) [1.0, 0.5])+ [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.5] Nothing)+ , DEMInstrDetector (DEMDetector (DetectorId 11) [1.0, 0.5] Nothing) ] in expected ~=? flattenDEM input , "bare shift only det id" ~: let input = DEM [ DEMInstrShift (DEMShift [] 5)- , DEMInstrDetector (DEMDetector (DetectorId 0) [2.0, 3.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [2.0, 3.0] Nothing) ] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 5) [2.0, 3.0])+ [ DEMInstrDetector (DEMDetector (DetectorId 5) [2.0, 3.0] Nothing) ] in expected ~=? flattenDEM input @@ -197,10 +197,10 @@ [ DEMInstrShift (DEMShift [1.0] 0) , DEMInstrShift (DEMShift [] 5) , DEMInstrShift (DEMShift [2.0] 0)- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0] Nothing) ] expected = DEM- [ DEMInstrDetector (DEMDetector (DetectorId 5) [3.0])+ [ DEMInstrDetector (DEMDetector (DetectorId 5) [3.0] Nothing) ] in expected ~=? flattenDEM input ]@@ -211,8 +211,8 @@ [ "flatten is idempotent on simple" ~: let dem = DEM [ DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0)])- , DEMInstrDetector (DEMDetector (DetectorId 1) [1.0, 2.0])- , DEMInstrObservable (DEMObservable (ObservableId 0))+ , DEMInstrDetector (DEMDetector (DetectorId 1) [1.0, 2.0] Nothing)+ , DEMInstrObservable (DEMObservable (ObservableId 0) Nothing) ] in flattenDEM dem ~=? flattenDEM (flattenDEM dem) @@ -220,7 +220,7 @@ let dem = DEM [ DEMInstrRepeat 2 [ DEMInstrShift (DEMShift [1.0] 0)- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0] Nothing) ] ] in flattenDEM dem ~=? flattenDEM (flattenDEM dem)
test/Test/DEM/Parse.hs view
@@ -3,6 +3,7 @@ import Test.HUnit import Text.Megaparsec (runParser) +import StimParser.Expr (Tag (..)) import StimParser.DEM.Expr import StimParser.DEM.Parse import StimParser.ParseUtils (run)@@ -30,6 +31,7 @@ , testParseDEMEmpty , testParseDEMCaret , testParseDEMEofEnforced+ , testParseDEMTagged ] -- | Parse individual error instructions@@ -76,19 +78,19 @@ testParseDEMDetector :: Test testParseDEMDetector = TestList [ "detector 2D" ~:- DEMDetector (DetectorId 0) [0.0, 0.0]+ DEMDetector (DetectorId 0) [0.0, 0.0] Nothing ~=? run parseDEMDetector "detector(0, 0) D0" , "detector 3D" ~:- DEMDetector (DetectorId 5) [1.0, 2.0, 0.0]+ DEMDetector (DetectorId 5) [1.0, 2.0, 0.0] Nothing ~=? run parseDEMDetector "detector(1, 2, 0) D5" , "detector negative coords" ~:- DEMDetector (DetectorId 1) [-1.0, 0.5]+ DEMDetector (DetectorId 1) [-1.0, 0.5] Nothing ~=? run parseDEMDetector "detector(-1, 0.5) D1" , "detector 1D" ~:- DEMDetector (DetectorId 3) [2.0]+ DEMDetector (DetectorId 3) [2.0] Nothing ~=? run parseDEMDetector "detector(2) D3" ] @@ -96,11 +98,11 @@ testParseDEMObservable :: Test testParseDEMObservable = TestList [ "observable L0" ~:- DEMObservable (ObservableId 0)+ DEMObservable (ObservableId 0) Nothing ~=? run parseDEMObservable "logical_observable L0" , "observable L5" ~:- DEMObservable (ObservableId 5)+ DEMObservable (ObservableId 5) Nothing ~=? run parseDEMObservable "logical_observable L5" ] @@ -167,8 +169,8 @@ expected = DEM [ DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0), TargetDetector (DetectorId 1), TargetObservable (ObservableId 0)]) , DEMInstrError (DEMError 0.005 [TargetDetector (DetectorId 1), TargetDetector (DetectorId 2)])- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])- , DEMInstrObservable (DEMObservable (ObservableId 0))+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)+ , DEMInstrObservable (DEMObservable (ObservableId 0) Nothing) ] in expected ~=? run parseDEM input @@ -176,7 +178,7 @@ let input = "repeat 2 { error(0.01) D0 } detector(0, 0) D0" expected = DEM [ DEMInstrRepeat 2 [DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0)])]- , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])+ , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing) ] in expected ~=? run parseDEM input @@ -207,7 +209,7 @@ ~=? run parseDEMError "error( 0.01 ) D0" , "detector with tabs" ~:- DEMDetector (DetectorId 0) [1.0, 2.0]+ DEMDetector (DetectorId 0) [1.0, 2.0] Nothing ~=? run parseDEMDetector "detector(1,\t2)\tD0" ] @@ -246,7 +248,7 @@ ~=? run parseDEMError "error(0.01) D0 # end of line comment" , "block comment in tuple" ~:- DEMDetector (DetectorId 0) [1.0, 2.0]+ DEMDetector (DetectorId 0) [1.0, 2.0] Nothing ~=? run parseDEMDetector "detector(1, (* x *) 2) D0" ] @@ -254,11 +256,11 @@ testParseDEMScientificCoords :: Test testParseDEMScientificCoords = TestList [ "scientific notation in detector coords" ~:- DEMDetector (DetectorId 0) [150.0, 0.0]+ DEMDetector (DetectorId 0) [150.0, 0.0] Nothing ~=? run parseDEMDetector "detector(1.5e2, 0) D0" , "negative scientific in coords" ~:- DEMDetector (DetectorId 1) [-1.5e-2, 2.0]+ DEMDetector (DetectorId 1) [-1.5e-2, 2.0] Nothing ~=? run parseDEMDetector "detector(-1.5e-2, 2) D1" ] @@ -353,4 +355,50 @@ case result of Left _ -> return () -- expected to fail Right _ -> assertFailure "parser should fail on unclosed parenthesis"+ ]+++-- | Tagged detectors and observables (Stim v1.15+).+testParseDEMTagged :: Test+testParseDEMTagged = TestList+ [ "tagged detector" ~:+ DEMDetector (DetectorId 1) [1.0, 0.0] (Just (Tag "choi"))+ ~=? run parseDEMDetector "detector[choi](1, 0) D1"++ , "tagged observable" ~:+ DEMObservable (ObservableId 0) (Just (Tag "choi"))+ ~=? run parseDEMObservable "logical_observable[choi] L0"++ , "untagged detector still parses" ~:+ DEMDetector (DetectorId 0) [0.0, 0.0] Nothing+ ~=? run parseDEMDetector "detector(0, 0) D0"++ , "untagged observable still parses" ~:+ DEMObservable (ObservableId 5) Nothing+ ~=? run parseDEMObservable "logical_observable L5"++ , "tagged detector in full DEM" ~:+ let input = unlines+ [ "error(0.01) D0"+ , "detector[choi](1, 0) D1"+ , "logical_observable[choi] L0"+ ]+ expected = DEM+ [ DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0)])+ , DEMInstrDetector (DEMDetector (DetectorId 1) [1.0, 0.0] (Just (Tag "choi")))+ , DEMInstrObservable (DEMObservable (ObservableId 0) (Just (Tag "choi")))+ ]+ in expected ~=? run parseDEM input++ , "double tag on detector rejected" ~: TestCase $ do+ let result = runParser parseDEM "" "detector[t1][t2](0, 0) D0"+ case result of+ Left _ -> return ()+ Right _ -> assertFailure "double tags should be rejected"++ , "double tag on observable rejected" ~: TestCase $ do+ let result = runParser parseDEM "" "logical_observable[t1][t2] L0"+ case result of+ Left _ -> return ()+ Right _ -> assertFailure "double tags should be rejected" ]