Skip to content

PPVM-traits-2 Patch - #219

Open
JonhasA wants to merge 18 commits into
mainfrom
trait-2/ppvm-traits-2
Open

JonhasA wants to merge 18 commits into
mainfrom
trait-2/ppvm-traits-2

Conversation

@JonhasA

@JonhasA JonhasA commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

A patch to move the changes to ppvm-trait from roger's PR #204

Additionally, the following changes were made:

  1. Added arithmetic module for defining traits to numeric types
  2. added gates module for defining traits for Clifford/ channel / measure/ rotation ops
  3. container module now has additional files related to storage or engine configuration
  4. Moved PREFER_MOVED_RKEY outside of coefficient and into storage.rs in containers/. Engine will configure the key rather than having coefficient module be responsible for it.
  5. Added a LossState trait to be associated with word sites rather than having it in PauliBits.
  6. Isolated functionality related to fermionic factors into fermion_factor.rs
  7. Pauli type is used explicitly for rotation operations
  8. Moves container tests beside their implementations and adds regression coverage for batch hash invalidation and Pauli-channel factors.
  9. Moves pauli_error_factors into the optional PauliErrorFactors channel capability, preserving the generic default and numeric specializations.

…em for ppvm. Focused on moving files to more isolated modules to have better appreciation on module responsibility

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋 Thanks for opening your first pull request against PPVM!

A quick note on contribution terms: by submitting this PR you
agree that your contribution is licensed under the
Apache License 2.0
and that you accept the
PPVM Contributor License Agreement.
Please skim those before a maintainer reviews — opening this PR
counts as your acceptance.

A few things that will speed up review:

  • Read CONTRIBUTING.md
    for the workflow, build commands, and style notes.
  • Run prek run --all-files locally; CI runs the same checks.
  • Use Conventional Commits
    for commit messages.

We'll get to your PR as soon as we can. Thanks for contributing!

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://QuEraComputing.github.io/ppvm/pr-preview/pr-219/

Built to branch gh-pages at 2026-09-28 15:49 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@david-pl david-pl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall a solid first breakdown of the huge PR, but I think we should make some changes here.

Also, on a more general note: while I get that proving things with lean is beneficial, I think this introduces unneeded complexity at points. For example, to fulfill criteria so this is provably a ring we introduce some traits that are sometimes a bit odd, e.g. Halvable.
cc @Roger-luo

Comment thread crates/ppvm-traits-2/src/arithmetic/coefficient.rs Outdated
impl Halvable for f64 {
#[inline]
fn half(&self) -> Self {
*self / 2.0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised that this holds up in the exact x.half() + x.half() == x condition.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there is an edge case here: this doesn't hold for f64::NAN, which is a perfectly valid f64, but x.half() == x if x is NAN.

I'm not saying this bothers me overly much, but as I said I'd also be happy not to go the full formal ring way anyway.

Comment thread crates/ppvm-traits-2/src/containers/batch.rs Outdated
Comment thread crates/ppvm-traits-2/src/containers/batch.rs
Comment thread crates/ppvm-traits-2/src/containers/graded.rs Outdated
Comment thread crates/ppvm-traits-2/src/algebra.rs Outdated
Comment thread crates/ppvm-traits-2/src/fermion_factor.rs Outdated
Comment thread crates/ppvm-traits-2/src/fermion_factor.rs Outdated
Comment thread crates/ppvm-traits-2/src/loss.rs
Comment thread crates/ppvm-traits-2/src/word.rs Outdated
@Roger-luo

Copy link
Copy Markdown
Collaborator

Also, on a more general note: while I get that proving things with lean is beneficial, I think this introduces unneeded complexity at points. For example, to fulfill criteria so this is provably a ring we introduce some traits that are sometimes a bit odd, e.g. Halvable.

For reference, One use case is symbolic expressions. The minimum requirement of a symbolic expression is that it can be divided by half, and it doesn't matter what number that half comes from.

&mut self,
qubit0: usize,
qubit1: usize,
p: [C; 3],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might need similar treatment, like the pauli channrl error coefficient factors, too, because we assume the three parameters also satisfy the normalization condition, right?

}

/// Batched Clifford gates: apply the same gate to many qubits in one call.
pub trait CliffordBatch: Clifford {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking if there is a way to generalize this based on the Clifford trait, but I didn't really find a good way to define a broadcast trait in general, so I ended up doing this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related question: do we have any consumers of this trait that don't run a custom impl? Do we really need the fallback loops?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think technically you could have a batch version of the gate implementation, with the consideration of SIMD and parallel operation. For example, if you are applying a batch with a static, known size ahead of time, say it is an X gate, you could come up with the flipping rules ahead of time that modify a batch of bits instead of a single bit in advance.

In principle, that would be faster because then you can utilize SIMD operations and parallel access to your containers.

@Roger-luo
Roger-luo requested a review from david-pl September 20, 2026 23:42

@david-pl david-pl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor stuff I still found, and some responses to @Roger-luo here. But it's almost there.

Comment thread crates/ppvm-traits-2/src/gates/channel.rs

/// Accumulates a borrowed coefficient.
#[inline]
fn add_assign_ref(&mut self, rhs: &Self) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point before was to require that trait and then remove this method. It's much more natural to just write *self += rhs and it's also done for the most part (which is why this method was only used once). So, just remove it?

Comment thread crates/ppvm-traits-2/src/containers/batch.rs
}

/// Batched Clifford gates: apply the same gate to many qubits in one call.
pub trait CliffordBatch: Clifford {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related question: do we have any consumers of this trait that don't run a custom impl? Do we really need the fallback loops?

Comment thread crates/ppvm-traits-2/src/algebra.rs Outdated
0 => Phase::Pos1,
1 => Phase::PosI,
2 => Phase::Neg1,
_ => Phase::NegI,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could silently introduce false results if there's a bug somewhere such that k & 3 > 3.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in #204, Sum<S, P> overrides only cnot_many and inherits the loops for x_many, y_many, and the other _many operators.

@david-pl david-pl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The left-over clones need to go. And I just realized that there's no rng on PauliSum. Why was it added to the trait @Roger-luo ?

fn accumulate_batch(&mut self, terms: &TermBatch<K, C>) {
for (k, c) in terms.iter() {
self.entry(k.clone())
.and_modify(|e| *e += c.clone())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This no longer needs to c.clone() now due to the AddAssign bound we added.

self.as_slice()
.iter()
.find(|(k, _)| k == key)
.map(|(_, c)| c.clone())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c.clone() is no longer needed because of the AddAssign bound.

impl Halvable for f64 {
#[inline]
fn half(&self) -> Self {
*self / 2.0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there is an edge case here: this doesn't hold for f64::NAN, which is a perfectly valid f64, but x.half() == x if x is NAN.

I'm not saying this bothers me overly much, but as I said I'd also be happy not to go the full formal ring way anyway.

&mut self,
qubit: usize,
probabilities: [C; 3],
rng: &mut R,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, PauliSum is deterministic in noise channels. Why are we passing in an rng here? PauliSum doesn't have one. @Roger-luo ?

/// Correlated two-qubit loss channel.
pub trait CorrelatedLossChannel<C: Coefficient> {
/// Apply correlated loss: `p[0]` loses both qubits when both are present;
/// `p[1]` loses either one when both are present; `p[2]` loses the remaining

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a recent fix to the correlated loss convention here: #205

This docstring seems to have regressed now, probably because #204 was based off the old state.

@Roger-luo

Copy link
Copy Markdown
Collaborator

good catch, this is a part I'm not sure - I moved rng out because it's an external state not necessarily (should) be managed by the PauliSum/tableau which is a quantum state, I think it's more natural to have it passed as a argument and let external/global managing it so it's more explicit who is sharing the RNG with what seed.

On the other hand, RNG is technically part of the entire execution state, so it could make sense to let the state object carrying it.

This branch has not been deployed

No deployments
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.

3 participants