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 +26/−0
- app/Simulator.hs +4/−2
- app/StimToCHP.hs +7/−1
- app/VerifyLargeTableau.hs +12/−12
- src/SymplecticCHP.hs +54/−30
- src/SymplecticCHP/LargeTableau.hs +31/−36
- symplectic-chp.cabal +3/−3
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