From b3c602fc044accf174678bf769ed6fbcf3677e97 Mon Sep 17 00:00:00 2001 From: Oct0bass Date: Mon, 4 Aug 2025 13:12:07 -0400 Subject: [PATCH 1/3] Check stabilizer bounds for single and two qubit operators --- src/symbolic_cliffords.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index bc1ec4284..56c61b64f 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -60,6 +60,7 @@ Base.@propagate_inbounds setzbit(xzs::AbstractMatrix{T}, r::Int, c::Int, z::T, s function _apply!(stab::AbstractStabilizer, gate::G; phases::Val{B}=Val(true)) where {B, G<:AbstractSingleQubitOperator} s = tab(stab) c = gate.q + @boundscheck c <= nqubits(stab) || throw(DimensionMismatch("attempt to apply operator acting on qubit $c to $(nqubits(stab))-qubit stabilizer")) @inbounds @simd for r in eachindex(s) x = getxbit(s, r, c) z = getzbit(s, r, c) @@ -158,6 +159,7 @@ end function _apply!(stab::AbstractStabilizer, op::SingleQubitOperator; phases::Val{B}=Val(true)) where B # TODO Generated functions that simplify the whole `if phases` branch might be a good optimization, but a quick benchmakr comparing sHadamard to SingleQubitOperator(sHadamard) did not show a worthwhile difference. s = tab(stab) c = op.q + @boundscheck c <= nqubits(stab) || throw(DimensionMismatch("attempt to apply operator acting on qubit $c to $(nqubits(stab))-qubit stabilizer")) Tₘₑ = eltype(s.xzs) sh = getshift(Tₘₑ, c) xx,zx,xz,zz = Tₘₑ.((op.xx,op.zx,op.xz,op.zz)) .<< sh @@ -271,6 +273,8 @@ function _apply!(stab::AbstractStabilizer, gate::G; phases::Val{B}=Val(true)) wh s = tab(stab) q1 = gate.q1 q2 = gate.q2 + maxopqubit = max(q1, q2) + @boundscheck maxopqubit <= nqubits(stab) || throw(DimensionMismatch("attempt to apply operator affecting qubit $maxopqubit to stabilizer of length $(nqubits(stab))")) Tₘₑ = eltype(s.xzs) shift = getshift(Tₘₑ, q1) - getshift(Tₘₑ, q2) @inbounds @simd for r in eachindex(s) From ed2e2b7ad495e3953ec93649689aca0b84380972 Mon Sep 17 00:00:00 2001 From: Oct0bass Date: Mon, 4 Aug 2025 13:51:43 -0400 Subject: [PATCH 2/3] use new standardized throw messages --- src/symbolic_cliffords.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 9fafa96f0..fbd044515 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -60,7 +60,7 @@ Base.@propagate_inbounds setzbit(xzs::AbstractMatrix{T}, r::Int, c::Int, z::T, s function _apply!(stab::AbstractStabilizer, gate::AbstractSingleQubitOperator; phases::Val{B}=Val(true)) where {B} s = tab(stab) c = gate.q - @boundscheck c <= nqubits(stab) || throw(DimensionMismatch("attempt to apply operator acting on qubit $c to $(nqubits(stab))-qubit stabilizer")) + @boundscheck c <= nqubits(stab) || throw(THROW_BOUNDS) @inbounds @simd for r in eachindex(s) x = getxbit(s, r, c) z = getzbit(s, r, c) @@ -177,7 +177,7 @@ end function _apply!(stab::AbstractStabilizer, op::SingleQubitOperator; phases::Val{B}=Val(true)) where B # TODO Generated functions that simplify the whole `if phases` branch might be a good optimization, but a quick benchmakr comparing sHadamard to SingleQubitOperator(sHadamard) did not show a worthwhile difference. s = tab(stab) c = op.q - @boundscheck c <= nqubits(stab) || throw(DimensionMismatch("attempt to apply operator acting on qubit $c to $(nqubits(stab))-qubit stabilizer")) + @boundscheck c <= nqubits(stab) || throw(THROW_BOUNDS) Tₘₑ = eltype(s.xzs) sh = getshift(Tₘₑ, c) xx,zx,xz,zz = Tₘₑ.((op.xx,op.zx,op.xz,op.zz)) .<< sh @@ -295,7 +295,7 @@ function _apply!(stab::AbstractStabilizer, gate::AbstractTwoQubitOperator; phase q1 = gate.q1 q2 = gate.q2 maxopqubit = max(q1, q2) - @boundscheck maxopqubit <= nqubits(stab) || throw(DimensionMismatch("attempt to apply operator affecting qubit $maxopqubit to stabilizer of length $(nqubits(stab))")) + @boundscheck maxopqubit <= nqubits(stab) || throw(THROW_BOUNDS) Tₘₑ = eltype(s.xzs) shift = getshift(Tₘₑ, q1) - getshift(Tₘₑ, q2) @inbounds @simd for r in eachindex(s) From 53e4213613c7b2ed768d37e987d4bd43cbd12fd1 Mon Sep 17 00:00:00 2001 From: Oct0bass Date: Mon, 4 Aug 2025 13:55:34 -0400 Subject: [PATCH 3/3] add checks to _apply_inv! --- src/symbolic_cliffords.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index fbd044515..3d982992a 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -75,6 +75,7 @@ end function _apply_inv!(stab::AbstractStabilizer, gate::AbstractSingleQubitOperator; phases::Val{B}=Val(true)) where {B} # code repetition with the corresponding `_apply` s = tab(stab) c = gate.q + @boundscheck c <= nqubits(stab) || throw(THROW_BOUNDS) @inbounds @simd for r in eachindex(s) x = getxbit(s, r, c) z = getzbit(s, r, c) @@ -294,8 +295,7 @@ function _apply!(stab::AbstractStabilizer, gate::AbstractTwoQubitOperator; phase s = tab(stab) q1 = gate.q1 q2 = gate.q2 - maxopqubit = max(q1, q2) - @boundscheck maxopqubit <= nqubits(stab) || throw(THROW_BOUNDS) + @boundscheck max(q1, q2) <= nqubits(stab) || throw(THROW_BOUNDS) Tₘₑ = eltype(s.xzs) shift = getshift(Tₘₑ, q1) - getshift(Tₘₑ, q2) @inbounds @simd for r in eachindex(s) @@ -321,6 +321,7 @@ function _apply_inv!(stab::AbstractStabilizer, gate::AbstractTwoQubitOperator; p s = tab(stab) q1 = gate.q1 q2 = gate.q2 + @boundscheck max(q1, q2) <= nqubits(stab) || throw(THROW_BOUNDS) Tₘₑ = eltype(s.xzs) shift = getshift(Tₘₑ, q1) - getshift(Tₘₑ, q2) @inbounds @simd for r in eachindex(s)