packages feed

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 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"   ]