Skip to content

Return the signed half-angle from Angle::half_angle_sin_cos for negative angles - #604

Merged
ciaranra merged 1 commit into
devfrom
angle-half-angle-signed
Aug 27, 2026
Merged

Return the signed half-angle from Angle::half_angle_sin_cos for negative angles#604
ciaranra merged 1 commit into
devfrom
angle-half-angle-signed

Conversation

@ciaranra

Copy link
Copy Markdown
Member

Closes #599. Split out of #601 so it can land on its own: it is a pre-existing defect, independent of the T-gate convention work, and much smaller.

The defect

Angle::half_angle_sin_cos computed the half-angle by halving the stored fixed-point fraction:

let half = Self::new(self.fraction / T::from_u32(2).expect("2 must be representable"));
half.sin_cos()

Angle stores an unsigned fraction of a full turn in [0, 2^BITS), so an angle with a negative principal value is stored wrapped, near the top of the range. Halving that stored value gives the true half-angle plus pi, so both returned components come back negated:

theta = -pi/4 : returned (+0.382683, -0.923880), correct (-0.382683, +0.923880)  -> factor -1
theta = -pi/8 : returned (+0.195090, -0.980785), correct (-0.195090, +0.980785)  -> factor -1
theta = +pi/4 : returned (+0.382683, +0.923880), correct (+0.382683, +0.923880)  -> agrees

Positive principal values were correct, so the defect was invisible to any test exercising only positive angles.

Why it went unnoticed

Rotation gates are built as RZ(theta) = cos(theta/2) I - i sin(theta/2) Z. Negating both half-angle components yields -RZ(theta): a global phase of -1. That is unobservable in probabilities, in density-matrix evolution, and in any comparison that quotients global phase -- which is what PECOS's state-comparison helpers do. It becomes observable only where absolute amplitudes matter or a tracked global phase is exposed.

Affected callers on dev

  • crates/pecos-simulators/src/stab_vec.rs -- non-Clifford RZ materialisation, for negative angles
  • exp/pecos-stab-tn/src/stab_mps/mast.rs -- its correction_angle can be negative, so that branch was applying -RZ(phi)

Both are fixed by this change; neither needed an edit of its own.

The fix

Corrected in the helper rather than at the call sites, and kept in fixed point. The sign test is made on the fraction, not on a converted f64, which is deliberate and strictly more accurate near half a turn: fractions one unit above HALF_TURN have a negative principal value but round to exactly pi in f64, so an f64-based test would call them positive. The doc comment says not to "simplify" this into agreement with to_radians_signed.

The boundary is strictly greater than half a turn, so exactly half a turn keeps the +pi principal value, matching to_radians_signed's (-pi, pi] range. This matters: RZ(pi) must be -iZ, and using the top bit alone would have negated it to +iZ.

Verification

  • cargo test -p pecos-core -- passes, including a new generic test over Angle8, Angle16, Angle32, Angle64 and Angle128 covering positive, negative, zero, exact Clifford, half-turn and near-full-turn inputs
  • cargo test -p pecos-simulators -- passes
  • cargo test -p pecos-stab-tn --lib -- 341 passed
  • Full CI lint recipe (clippy from inside each crate directory with --locked, plus pecos --no-default-features), cargo fmt --check and pre-commit -- clean

No existing expected value needed changing: the affected results were off by a global phase that no current test pinned.

Relationship to #601

#601 contains this same fix, because a correct Tdg depends on it. If this lands first, #601 should be rebased and the overlap will drop out.

@ciaranra
ciaranra merged commit c5cce51 into dev Aug 27, 2026
78 of 85 checks passed
@ciaranra
ciaranra deleted the angle-half-angle-signed branch August 27, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Angle::half_angle_sin_cos returns -sin(theta/2), -cos(theta/2) for negative angles

1 participant