packages feed

symplectic-chp 0.1.0.1 → 0.2.0.0

raw patch · 7 files changed

+137/−84 lines, 7 filesdep ~stim-parserPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: stim-parser

API changes (from Hackage documentation)

- SymplecticCHP.LargeTableau: LargeCNOT :: !Int -> !Int -> LargeSymplecticGate
- SymplecticCHP.LargeTableau: LargeHadamard :: !Int -> LargeLocalSymplectic
- SymplecticCHP.LargeTableau: LargeLocal :: !LargeLocalSymplectic -> LargeSymplecticGate
- SymplecticCHP.LargeTableau: LargePhase :: !Int -> LargeLocalSymplectic
- SymplecticCHP.LargeTableau: data LargeLocalSymplectic
- SymplecticCHP.LargeTableau: data LargeSymplecticGate
- SymplecticCHP.LargeTableau: instance GHC.Classes.Eq SymplecticCHP.LargeTableau.LargeLocalSymplectic
- SymplecticCHP.LargeTableau: instance GHC.Classes.Eq SymplecticCHP.LargeTableau.LargeSymplecticGate
- SymplecticCHP.LargeTableau: instance GHC.Show.Show SymplecticCHP.LargeTableau.LargeLocalSymplectic
- SymplecticCHP.LargeTableau: instance GHC.Show.Show SymplecticCHP.LargeTableau.LargeSymplecticGate
+ SymplecticCHP: measureWithGen :: forall (n :: Nat). KnownNat n => Tableau n Pauli -> Pauli -> StdGen -> ((Tableau n Pauli, MeasurementResult), StdGen)
+ SymplecticCHP: runWithSeed :: Int -> StdGen -> Clifford a -> (SomeTableau, a)
+ SymplecticCHP.LargeTableau: CNOT :: !Int -> !Int -> SymplecticGate
+ SymplecticCHP.LargeTableau: Hadamard :: !Int -> LocalSymplectic
+ SymplecticCHP.LargeTableau: Local :: !LocalSymplectic -> SymplecticGate
+ SymplecticCHP.LargeTableau: Phase :: !Int -> LocalSymplectic
+ SymplecticCHP.LargeTableau: data LocalSymplectic
+ SymplecticCHP.LargeTableau: data SymplecticGate
+ SymplecticCHP.LargeTableau: largeMeasureWithGen :: LargeTableau -> LargePauli -> StdGen -> ((LargeTableau, LargeMeasurementResult), StdGen)
- SymplecticCHP: Clifford :: (SomeTableau -> IO (SomeTableau, a)) -> Clifford a
+ SymplecticCHP: Clifford :: (SomeTableau -> StdGen -> (SomeTableau, StdGen, a)) -> Clifford a
- SymplecticCHP: [runClifford] :: Clifford a -> SomeTableau -> IO (SomeTableau, a)
+ SymplecticCHP: [runClifford] :: Clifford a -> SomeTableau -> StdGen -> (SomeTableau, StdGen, a)
- SymplecticCHP.LargeTableau: largeApplyGate :: LargeSymplecticGate -> LargeTableau -> LargeTableau
+ SymplecticCHP.LargeTableau: largeApplyGate :: SymplecticGate -> LargeTableau -> LargeTableau

Files

CHANGELOG.md view
@@ -1,5 +1,31 @@ # Revision history for symplectic-chp +## 0.2.0.0 -- 2026-07-20++### Breaking API Changes++* **Deterministic `Clifford` monad**: `Clifford` now threads a `StdGen` instead of+  living in `IO`. The type changed from+  `SomeTableau -> IO (SomeTableau, a)` to+  `SomeTableau -> StdGen -> (SomeTableau, StdGen, a)`.+  This makes simulations reproducible when a seed is supplied.+* **Seeded runner**: added `runWithSeed :: Int -> StdGen -> Clifford a -> (SomeTableau, a)`.+* **LargeTableau gate unification**: removed `LargeLocalSymplectic` and+  `LargeSymplecticGate`; `LargeTableau` now reuses `LocalSymplectic` and+  `SymplecticGate` from `SymplecticCHP`.++### Bug Fixes++* **`--seed` flag now works**: the CLI seed was previously accepted but ignored;+  random measurements now derive from the supplied seed.++### Other Changes++* Updated `stim-parser` dependency to `>= 0.4 && < 0.5` and adapted to the+  `AnnTarget` change in annotation AST.+* Removed unused/incomplete modules `SymplecticCHP.Core`, `SymplecticCHP.Types`,+  and `SymplecticCHP.Storage`.+ ## 0.1.0.1 -- 2026-07-17  * Support `stim-parser` 0.2.0.0: relax upper bound to `< 0.3`.  
app/Simulator.hs view
@@ -11,7 +11,7 @@   ) where  import Control.Monad (foldM)-import System.Random (randomIO)+import System.Random (randomIO, mkStdGen)  import SymplecticCHP   ( Clifford@@ -21,6 +21,7 @@   , gate   , measurePauli   , runWith+  , runWithSeed   , getTableau   , nQubitsSome   , rowsSome@@ -50,7 +51,8 @@ runCHPCircuitWithSeed :: Int -> CHPCircuit -> IO SimulationResult runCHPCircuitWithSeed seed circuit = do   let n = numQubits circuit-  (tableau, outcomes) <- runWith n (runOperations (operations circuit))+      gen = mkStdGen seed+      (tableau, outcomes) = runWithSeed n gen (runOperations (operations circuit))   return $ SimulationResult     { finalTableau = tableau     , measurementOutcomes = reverse outcomes  -- Reverse to get chronological order
app/StimToCHP.hs view
@@ -12,6 +12,7 @@   ) where  import Data.Bits (setBit)+import Data.Maybe (mapMaybe) import Data.Word (Word64) import qualified Data.Set as Set @@ -211,7 +212,12 @@          piQubit (PauliInd _ idx) = idx  -- PauliInd contains qubit index directly     -    annotationQubits (Ann _ _ _ qs) = Set.fromList $ map qubitIndex qs+    annotationQubits (Ann _ _ _ targets) = Set.fromList $ mapMaybe annTargetQubit targets++    annTargetQubit :: AnnTarget -> Maybe Int+    annTargetQubit (AnnQ i)       = Just i+    annTargetQubit (AnnPauli _ i) = Just i+    annTargetQubit (AnnRec _)     = Nothing  -- record references are not qubits  -- | Extract the qubit index from a Q value. qubitIndex :: Q -> Int
app/VerifyLargeTableau.hs view
@@ -48,10 +48,10 @@   | otherwise =        let tab0 = largeEmpty n           -- Apply H to even qubits-          tab1 = foldl (\t i -> largeApplyGate (LargeLocal (LargeHadamard i)) t) +          tab1 = foldl (\t i -> largeApplyGate (Local (Hadamard i)) t)                         tab0 [0,2..n-2]           -- Apply CNOT from even to odd-          tab2 = foldl (\t i -> largeApplyGate (LargeCNOT i (i+1)) t) +          tab2 = foldl (\t i -> largeApplyGate (CNOT i (i+1)) t)                         tab1 [0,2..n-2]       in tab2 @@ -108,8 +108,8 @@   | n < 2 = error "createRepCodeLarge: need at least 2 qubits"   | otherwise =       let tab0 = largeEmpty n-          tab1 = largeApplyGate (LargeLocal (LargeHadamard 0)) tab0-          tab2 = foldl (\t i -> largeApplyGate (LargeCNOT 0 i) t) tab1 [1..n-1]+          tab1 = largeApplyGate (Local (Hadamard 0)) tab0+          tab2 = foldl (\t i -> largeApplyGate (CNOT 0 i) t) tab1 [1..n-1]       in tab2  -- | Test 2: Repetition Code State@@ -162,15 +162,15 @@      -- Create |+⟩ states on all qubits   let tab0 = largeEmpty n-      tab1 = foldl (\t i -> largeApplyGate (LargeLocal (LargeHadamard i)) t) +      tab1 = foldl (\t i -> largeApplyGate (Local (Hadamard i)) t)                     tab0 [0..n-1]      -- Apply S to all qubits (first time)-  let tab2 = foldl (\t i -> largeApplyGate (LargeLocal (LargePhase i)) t) +  let tab2 = foldl (\t i -> largeApplyGate (Local (Phase i)) t)                     tab1 [0..n-1]      -- Apply S to all qubits (second time)-  let tab3 = foldl (\t i -> largeApplyGate (LargeLocal (LargePhase i)) t) +  let tab3 = foldl (\t i -> largeApplyGate (Local (Phase i)) t)                     tab2 [0..n-1]      mid <- getCurrentTime@@ -206,16 +206,16 @@   case gateType of     0 -> do -- Hadamard       q <- randomRIO (0, n-1)-      return $ largeApplyGate (LargeLocal (LargeHadamard q)) tab+      return $ largeApplyGate (Local (Hadamard q)) tab     1 -> do -- Phase       q <- randomRIO (0, n-1)-      return $ largeApplyGate (LargeLocal (LargePhase q)) tab+      return $ largeApplyGate (Local (Phase q)) tab     2 -> do -- CNOT       c <- randomRIO (0, n-1)       t <- randomRIO (0, n-1)       if c == t          then return tab-        else return $ largeApplyGate (LargeCNOT c t) tab+        else return $ largeApplyGate (CNOT c t) tab     _ -> return tab  -- | Test 4: Random circuits preserve validity@@ -269,13 +269,13 @@     putStrLn $ "  Creation: " ++ show (diffUTCTime mid1 start)          -- Apply 100 Hadamards-    let tab1 = foldl (\t i -> largeApplyGate (LargeLocal (LargeHadamard (i `mod` n))) t) +    let tab1 = foldl (\t i -> largeApplyGate (Local (Hadamard (i `mod` n))) t)                       tab0 [0..99]     mid2 <- getCurrentTime     putStrLn $ "  100 Hadamards: " ++ show (diffUTCTime mid2 mid1)          -- Apply 100 CNOTs-    let tab2 = foldl (\t i -> largeApplyGate (LargeCNOT (i `mod` n) ((i+1) `mod` n)) t) +    let tab2 = foldl (\t i -> largeApplyGate (CNOT (i `mod` n) ((i+1) `mod` n)) t)                       tab1 [0..99]     mid3 <- getCurrentTime     putStrLn $ "  100 CNOTs: " ++ show (diffUTCTime mid3 mid2)
src/SymplecticCHP.hs view
@@ -23,7 +23,7 @@ import Data.Word import Data.Proxy (Proxy(..)) import Data.Kind (Type)-import System.Random (randomRIO)+import System.Random (StdGen, mkStdGen, randomR, randomIO) import Data.List (sortOn, groupBy) import Data.Function (on) import Data.Maybe (fromJust, isJust)@@ -561,14 +561,12 @@ updateLagrangian :: KnownNat n => Finite n -> v -> Lagrangian n v -> Lagrangian n v updateLagrangian i v (Lagrangian vs) = Lagrangian (VS.unsafeUpd vs [(fromIntegral $ Finite.getFinite i, v)]) --- | Measurement as state update (symplectic transvection)-measure :: forall n. KnownNat n => Tableau n Pauli -> Pauli -> IO (Tableau n Pauli, MeasurementResult)-measure tab@(Tableau s d) p-  | isDeterminate tab p = do-      let outcome = computePhase tab p-      return (tab, Determinate outcome)+-- | Measurement as state update (symplectic transvection), pure variant using a StdGen.+measureWithGen :: forall n. KnownNat n => Tableau n Pauli -> Pauli -> StdGen -> ((Tableau n Pauli, MeasurementResult), StdGen)+measureWithGen tab@(Tableau s d) p g+  | isDeterminate tab p = ((tab, Determinate detOutcome), g)   -  | otherwise = do+  | otherwise =       let Just j = findAntiCommutingStab tab p           Just jFin = intToFinite j           s_j = indexLagrangian s jFin@@ -581,17 +579,26 @@                    else s_k) (lagrangianBasis s)                      newDestabBasis = updateVector j s_j (lagrangianBasis d)-      -      outcome <- randomRIO (0, 1) :: IO Int-      -      let Pauli x z r = p-          p' = Pauli x z ((r + if outcome == 0 then 2 else 0) `mod` 4)+          +          (randOutcome, g') = randomR (0, 1) g :: (Int, StdGen)+          +          Pauli x z r = p+          p' = Pauli x z ((r + if randOutcome == 0 then 2 else 0) `mod` 4)           finalStabBasis = updateVector j p' newStabBasis           finalStabs = Lagrangian finalStabBasis           newDestabs = Lagrangian newDestabBasis       -      return (Tableau finalStabs newDestabs, Random (outcome == 1))+      in ((Tableau finalStabs newDestabs, Random (randOutcome == 1)), g')+  where+    detOutcome = computePhase tab p +-- | Measurement as state update (symplectic transvection)+measure :: forall n. KnownNat n => Tableau n Pauli -> Pauli -> IO (Tableau n Pauli, MeasurementResult)+measure tab p = do+  seed <- randomIO+  let (result, _) = measureWithGen tab p (mkStdGen seed)+  return result+ -- | Compute deterministic measurement outcome via symplectic decomposition computePhase :: forall n. KnownNat n => Tableau n Pauli -> Pauli -> Bool computePhase (Tableau s d) p = @@ -609,34 +616,42 @@ data SomeTableau where   SomeTableau :: KnownNat n => Tableau n Pauli -> SomeTableau -newtype Clifford a = Clifford { runClifford :: SomeTableau -> IO (SomeTableau, a) }+-- | Clifford monad threading both the tableau and a random-number generator.+-- This makes simulations deterministic and reproducible when a seed is supplied.+newtype Clifford a = Clifford { runClifford :: SomeTableau -> StdGen -> (SomeTableau, StdGen, a) }  instance Functor Clifford where-  fmap f (Clifford g) = Clifford $ \t -> do (t', x) <- g t; return (t', f x)+  fmap f (Clifford g) = Clifford $ \t g0 ->+    let (t', g1, x) = g t g0+    in (t', g1, f x)  instance Applicative Clifford where-  pure x = Clifford $ \t -> return (t, x)-  Clifford f <*> Clifford x = Clifford $ \t -> do-    (t', f') <- f t; (t'', x') <- x t'; return (t'', f' x')+  pure x = Clifford $ \t g -> (t, g, x)+  Clifford f <*> Clifford x = Clifford $ \t g0 ->+    let (t', g1, f') = f t g0+        (t'', g2, x') = x t' g1+    in (t'', g2, f' x')  instance Monad Clifford where   return = pure-  Clifford x >>= f = Clifford $ \t -> do (t', x') <- x t; runClifford (f x') t'+  Clifford x >>= f = Clifford $ \t g0 ->+    let (t', g1, x') = x t g0+    in runClifford (f x') t' g1  gate :: SymplecticGate -> Clifford ()-gate g = Clifford $ \t -> case t of-  SomeTableau tab -> return (SomeTableau (evolveTableau tab g), ())+gate g = Clifford $ \t g0 -> (evolveTableauSome t g, g0, ())  measurePauli :: Pauli -> Clifford Bool-measurePauli p = Clifford $ \t -> case t of-  SomeTableau tab -> do-    (t', res) <- measure tab p-    case res of-      Determinate b -> return (SomeTableau t', b)-      Random b      -> return (SomeTableau t', b)+measurePauli p = Clifford $ \t g0 -> case t of+  SomeTableau tab ->+    let ((tab', res), g1) = measureWithGen tab p g0+        outcome = case res of+          Determinate b -> b+          Random b      -> b+    in (SomeTableau tab', g1, outcome)  getTableau :: Clifford SomeTableau-getTableau = Clifford $ \t -> return (t, t)+getTableau = Clifford $ \t g -> (t, g, t)  withNatProxy :: KnownNat n => Proxy n -> (KnownNat n => Tableau n Pauli) -> Tableau n Pauli withNatProxy _ t = t@@ -648,8 +663,17 @@       GHC.TypeNats.SomeNat (proxy :: Proxy n) ->          SomeTableau (emptyTableau :: Tableau n Pauli) +-- | Run a Clifford computation with a freshly generated random seed. runWith :: Int -> Clifford a -> IO (SomeTableau, a)-runWith n (Clifford f) = f (emptyTableauN n)+runWith n c = do+  seed <- randomIO+  return $ runWithSeed n (mkStdGen seed) c++-- | Run a Clifford computation with an explicit random generator (for reproducibility).+runWithSeed :: Int -> StdGen -> Clifford a -> (SomeTableau, a)+runWithSeed n gen c =+  let (t, _, a) = runClifford c (emptyTableauN n) gen+  in (t, a)  -- ============================================================================ -- PART XIV: BACKWARD COMPATIBILITY HELPERS
src/SymplecticCHP/LargeTableau.hs view
@@ -11,9 +11,9 @@   , lpPauliY   , lpOmega   , lpMultiply-    -- * Gates (redefined to avoid circular imports)-  , LargeLocalSymplectic(..)-  , LargeSymplecticGate(..)+    -- * Gates (reused from SymplecticCHP)+  , SymplecticGate(..)+  , LocalSymplectic(..)   , LargeMeasurementResult(..)     -- * Large Tableau   , LargeTableau(..)@@ -21,6 +21,7 @@   , largeNQubits   , largeApplyGate   , largeMeasure+  , largeMeasureWithGen     -- * Validation   , largeIsValid   , largeIsDeterminate@@ -29,24 +30,11 @@ import Data.Bits (Bits(..), popCount, xor) import Data.Word (Word64) import SymplecticCHP.BitVec+import SymplecticCHP (SymplecticGate(..), LocalSymplectic(..)) -import System.Random (randomRIO)+import System.Random (StdGen, mkStdGen, randomR, randomIO) import qualified Data.Vector as V --- ============================================================================--- Gate Types (local copies to avoid circular imports)--- ============================================================================--data LargeLocalSymplectic -  = LargeHadamard !Int-  | LargePhase !Int-  deriving (Show, Eq)--data LargeSymplecticGate-  = LargeLocal !LargeLocalSymplectic-  | LargeCNOT !Int !Int-  deriving (Show, Eq)- data LargeMeasurementResult = LargeDeterminate Bool | LargeRandom Bool   deriving (Show, Eq) @@ -118,13 +106,13 @@ largeNQubits = ltN  -- | Apply gate to large tableau-largeApplyGate :: LargeSymplecticGate -> LargeTableau -> LargeTableau+largeApplyGate :: SymplecticGate -> LargeTableau -> LargeTableau largeApplyGate g (LargeTableau s d n) =   LargeTableau (V.map (lpApplyGate g) s) (V.map (lpApplyGate g) d) n  -- | Apply gate to large Pauli-lpApplyGate :: LargeSymplecticGate -> LargePauli -> LargePauli-lpApplyGate (LargeLocal (LargeHadamard i)) (LargePauli x z r n) =+lpApplyGate :: SymplecticGate -> LargePauli -> LargePauli+lpApplyGate (Local (Hadamard i)) (LargePauli x z r n) =   let xi = bvTestBit x i       zi = bvTestBit z i       x' = if zi then bvSetBit (bvClearBit x i) i else bvClearBit x i@@ -132,7 +120,7 @@       r' = (r + if xi && zi then 2 else 0) `mod` 4   in LargePauli x' z' r' n -lpApplyGate (LargeLocal (LargePhase i)) (LargePauli x z r n) =+lpApplyGate (Local (Phase i)) (LargePauli x z r n) =   let xi = bvTestBit x i       zi = bvTestBit z i       -- Z' = Z XOR X@@ -140,7 +128,7 @@       r' = (r + if xi && not zi then 1 else 0) `mod` 4   in LargePauli x z' r' n -lpApplyGate (LargeCNOT c t) (LargePauli x z r n) =+lpApplyGate (CNOT c t) (LargePauli x z r n) =   let xc = bvTestBit x c       zc = bvTestBit z c       xt = bvTestBit x t@@ -153,16 +141,14 @@       r' = (r + phaseTerm) `mod` 4   in LargePauli x' z' r' n --- | Measurement on large tableau-largeMeasure :: LargeTableau -> LargePauli -> IO (LargeTableau, LargeMeasurementResult)-largeMeasure tab@(LargeTableau s d n) p-  | largeIsDeterminate tab p = do-      let outcome = largeComputePhase tab p-      return (tab, LargeDeterminate outcome)-  | otherwise = do+-- | Pure measurement on large tableau using an explicit random generator.+largeMeasureWithGen :: LargeTableau -> LargePauli -> StdGen -> ((LargeTableau, LargeMeasurementResult), StdGen)+largeMeasureWithGen tab@(LargeTableau s d n) p g+  | largeIsDeterminate tab p = ((tab, LargeDeterminate detOutcome), g)+  | otherwise =       case largeFindAntiCommuting tab p of-        Nothing -> error "Internal error in largeMeasure"-        Just j -> do+        Nothing -> error "Internal error in largeMeasureWithGen"+        Just j ->           let s_j = s V.! j               -- Update other stabilizers               newStabs = V.imap (\k s_k ->@@ -173,12 +159,21 @@                        else s_k) s               -- New destabilizer is old stabilizer               newDestabs = d V.// [(j, s_j)]-          -          outcome <- randomRIO (0, 1) :: IO Int-          let p' = p { lpPhase = (lpPhase p + if outcome == 0 then 2 else 0) `mod` 4 }+              +              (randOutcome, g') = randomR (0, 1) g :: (Int, StdGen)+              p' = p { lpPhase = (lpPhase p + if randOutcome == 0 then 2 else 0) `mod` 4 }               finalStabs = newStabs V.// [(j, p')]           -          return (LargeTableau finalStabs newDestabs n, LargeRandom (outcome == 1))+          in ((LargeTableau finalStabs newDestabs n, LargeRandom (randOutcome == 1)), g')+  where+    detOutcome = largeComputePhase tab p++-- | Measurement on large tableau+largeMeasure :: LargeTableau -> LargePauli -> IO (LargeTableau, LargeMeasurementResult)+largeMeasure tab p = do+  seed <- randomIO+  let (result, _) = largeMeasureWithGen tab p (mkStdGen seed)+  return result  -- | Check if measurement is deterministic largeIsDeterminate :: LargeTableau -> LargePauli -> Bool
symplectic-chp.cabal view
@@ -28,7 +28,7 @@ -- PVP summary:     +-+------- breaking API changes --                  | | +----- non-breaking API additions --                  | | | +--- code changes with no API change-version:            0.1.0.1+version:            0.2.0.0  -- A short (one-line) description of the package. synopsis:           CHP Clifford simulator using symplectic geometry@@ -134,7 +134,7 @@         , filepath            >= 1.4     && < 1.6         , directory           >= 1.3     && < 1.4         , containers          >= 0.6     && < 0.8-        , stim-parser         >= 0.1     && < 0.3+        , stim-parser         >= 0.4     && < 0.5     build-tool-depends:         hspec-discover:hspec-discover  >= 2.10 && < 2.12     default-language: GHC2021@@ -159,7 +159,7 @@     -- Other library packages from which modules are imported.     build-depends:           , base                >= 4.17    && < 4.22-          , stim-parser         >= 0.1     && < 0.3+          , stim-parser         >= 0.4     && < 0.5           , containers          >= 0.6     && < 0.8           , symplectic-chp           , random              >= 1.2     && < 1.3