Conversation
…em for ppvm. Focused on moving files to more isolated modules to have better appreciation on module responsibility
…Action and FermionSite
There was a problem hiding this comment.
👋 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-fileslocally; 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!
|
david-pl
left a comment
There was a problem hiding this comment.
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
| impl Halvable for f64 { | ||
| #[inline] | ||
| fn half(&self) -> Self { | ||
| *self / 2.0 |
There was a problem hiding this comment.
I'm surprised that this holds up in the exact x.half() + x.half() == x condition.
There was a problem hiding this comment.
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.
…of individual bits.
…d structs can be added later if deemed necessary
…anization. So, we now have RotationOne, RotationOneBatch, RotationTwo, and RotationTwoBatch
…operations with respect to the consumer. This allows sepcialized or optimal implementation dependent on the consumer, rather than just inheriting it.
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], |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Related question: do we have any consumers of this trait that don't run a custom impl? Do we really need the fallback loops?
There was a problem hiding this comment.
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.
david-pl
left a comment
There was a problem hiding this comment.
Some minor stuff I still found, and some responses to @Roger-luo here. But it's almost there.
|
|
||
| /// Accumulates a borrowed coefficient. | ||
| #[inline] | ||
| fn add_assign_ref(&mut self, rhs: &Self) { |
There was a problem hiding this comment.
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?
| } | ||
|
|
||
| /// Batched Clifford gates: apply the same gate to many qubits in one call. | ||
| pub trait CliffordBatch: Clifford { |
There was a problem hiding this comment.
Related question: do we have any consumers of this trait that don't run a custom impl? Do we really need the fallback loops?
| 0 => Phase::Pos1, | ||
| 1 => Phase::PosI, | ||
| 2 => Phase::Neg1, | ||
| _ => Phase::NegI, |
There was a problem hiding this comment.
This could silently introduce false results if there's a bug somewhere such that k & 3 > 3.
There was a problem hiding this comment.
in #204, Sum<S, P> overrides only cnot_many and inherits the loops for x_many, y_many, and the other _many operators.
…hashing, gathering keys and KeyColumnMut for key construction, resevrqation, and removal
david-pl
left a comment
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
c.clone() is no longer needed because of the AddAssign bound.
| impl Halvable for f64 { | ||
| #[inline] | ||
| fn half(&self) -> Self { | ||
| *self / 2.0 |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 |
|
good catch, this is a part I'm not sure - I moved 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. |
A patch to move the changes to ppvm-trait from roger's PR #204
Additionally, the following changes were made: