Skip to content

Commit 66b7e94

Browse files
authored
Merge pull request #136 from MSRudolph/dev
Performance fixes: - dispatch to Base.iterate() for PauliSum - replace Pauli rotation gate with gate bitmask in commutes() - inline iterate(), add!(), sett!()
2 parents e93d27c + d931782 commit 66b7e94

File tree

5 files changed

+45
-47
lines changed

5 files changed

+45
-47
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "PauliPropagation"
22
uuid = "293282d5-3c99-4fb6-92d0-fd3280a19750"
33
authors = ["Manuel S. Rudolph"]
4-
version = "0.7.0"
4+
version = "0.7.1"
55

66
[deps]
77
AcceleratedKernels = "6a4ca0a5-0e36-4168-a932-d9be78d558f1"

README.md

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,6 @@ Pkg.add(url="https://github.com/MSRudolph/PauliPropagation.jl.git", rev="branchn
3131
where you can use the keyword `rev="branchname"` to install development versions of the package.
3232
We don't recommend using branches other than `main` or `dev`.
3333

34-
### Clone the repository and install locally
35-
Navigate to a local directory where you want to clone this repository into and run the following in a terminal
36-
```bash
37-
git clone git@github.com:MSRudolph/PauliPropagation.jl.git
38-
```
39-
Inside this cloned repository, you can now freely import `PauliPropagation` or install it into your environment.\
40-
Alternatively, you can push the relative path to the cloned repository to the Julia package load path called `LOAD_PATH` via
41-
```julia
42-
rel_path = "your/relative/path/PauliPropagation"
43-
push!(LOAD_PATH,rel_path);
44-
```
45-
This may require that you have no global installation of `PauliPropagation` in your enviroment.
4634

4735
### A note on installing Julia
4836
It is recommended to install julia using `juliaup` with instructions from [here](https://github.com/JuliaLang/juliaup). Then, Julia's _long-term support_ version (currently a `1.10` version) can be installed via
@@ -139,20 +127,19 @@ Therefore, the trace is equivalent to the sum over the coefficients of Pauli str
139127
```
140128

141129
## Important Notes and Caveats
142-
- Circuits are specified in the _Schrödinger_ picture, as if operated upon states. Behind the scenes, `propagate()` will (by default) apply the _adjoint_ circuit upon the passed `PauliSum` which is treated as the observable operator.
143-
- Schrödinger propagation is planned but not yet supported _except_ through manually passing the _adjoint_ of the intended circuit to `propagate()`. This is often easy. For instance, with the circuit order reversed, angles in `PauliRotation` gates are negated, and `CliffordGate` are passed to `transposecliffordmap()`.
130+
- Circuits are specified in the _Schrödinger_ picture, as if operated upon states. Behind the scenes, `propagate()` will (by default) apply the _adjoint_ circuit upon the passed `PauliSum` which is treated as the observable operator. The default can be changed by passing `heisenberg=false` to `propagate()`, though it will not make simulating dense quantum states efficient.
131+
- Schrödinger propagation via `heisenberg=false` is supported since version `0.7`, but not for all gates. So far, we natively support `PauliRotation`, `CliffordGate`, and `<:PauliNoise` gates. `ImaginaryPauliRotation` is _only_ supported with `heisenberg=false`.
144132
- While Pauli propagation can, in principle, be used for _extended_ stabilizer simulation, we do not currently support sub-exponential strong simulation of stabilizer states.
145-
- Sampling quantum states is currently not supported.
133+
- Sampling quantum states is currently not supported, but is coming soon.
146134
- Many underlying data structures and functions can be used for other purposes involving Pauli operators.
147135

148136
All of the above can be addressed by writing the additional missing code due to the nice extensibility of Julia.
149137

150138
## Upcoming Features
151139
This package is still work-in-progress. You will probably find certain features that you would like to have and that are currently missing.\
152140
Here are some features that we want to implement in the future. Feel free to contribute!
153-
- **Multi-threading and improved scalability**. Currently, PauliPropagation.jl works uses a single CPU thread and may run your hardware out of memory. Future versions should be even faster and with options to trade-off computational runtime and memory requirements.
154-
- **Easier Schrödinger picture propagation**. Currently, the default is Heisenberg and there is no easy way to transpose the gates.
155-
- **A fast and flexible Surrogate version**. Currently, we provide a version of the Pauli propagation Surrogate that is _good_ and _works_, at least for Pauli gates and Clifford gates. Stay tuned for a whole lot more.
141+
- **GPU acceleration**. Since version `0.7`, we provide a PauliPropagationCUDA extension in `ext/`. So far, it only works with `PauliRotation` gates and is not yet maximally performant.
142+
- **Stochastic evolution**. Propagation methods are mainly memory-limited. We aim to change this and introduce time vs memory trade-offs.
156143

157144
## How to contribute
158145
We have a Slack channel `#pauli-propagation` in the [Julia Slack](https://join.slack.com/t/julialang/shared_invite/zt-2zljxdwnl-kSXbwuwFHeERyxSD3iFJdQ).
@@ -187,13 +174,15 @@ If you are publishing research using `PauliPropagation.jl`, please cite this lib
187174
```
188175

189176
## Related publications
190-
Some of the developers of this package are co-authors in the following papers using Pauli propagation and (at least parts of) this code:
177+
Some of the developers of this package are co-authors in the following papers using Pauli propagation and (at least parts of) this code.
178+
If you are using our package, please consider citing some of these works:
191179
- [Classical simulations of noisy variational quantum circuits](https://arxiv.org/abs/2306.05400)
192180
- [Classical surrogate simulation of quantum systems with LOWESA](https://arxiv.org/abs/2308.09109)
193181
- [Quantum Convolutional Neural Networks are (Effectively) Classically Simulable](https://arxiv.org/abs/2408.12739)
194182
- [Classically estimating observables of noiseless quantum circuits](https://arxiv.org/abs/2409.01706)
195183
- [Efficient quantum-enhanced classical simulation for patches of quantum landscapes](https://arxiv.org/abs/2411.19896)
196184
- [Simulating quantum circuits with arbitrary local noise using Pauli Propagation](https://arxiv.org/abs/2501.13101)
197185
- [Leveraging Symmetry Merging in Pauli Propagation](https://arxiv.org/abs/2512.12094)
186+
- [Thermal State Simulation with Pauli and Majorana Propagation](https://www.arxiv.org/abs/2602.04878)
198187

199188
And more are coming up.

src/Base/termsum.jl

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,17 @@ end
9595

9696

9797
### Default functions defined for all TermSum types
98-
function Base.iterate(term_sum::AbstractTermSum)
98+
@inline function Base.iterate(term_sum::AbstractTermSum, args...)
99+
return _iterate(StorageType(term_sum), term_sum, args...)
100+
end
101+
102+
@inline function _iterate(::DictStorage, term_sum::AbstractTermSum, args...)
103+
dict_storage = storage(term_sum)
104+
return iterate(dict_storage, args...)
105+
end
106+
107+
108+
@inline function _iterate(::StorageType, term_sum::AbstractTermSum)
99109
# 1. Create the iterator we are delegating to
100110
iter = zip(terms(term_sum), coefficients(term_sum))
101111

@@ -107,7 +117,7 @@ function Base.iterate(term_sum::AbstractTermSum)
107117
return next === nothing ? nothing : (next[1], (iter, next[2]))
108118
end
109119

110-
function Base.iterate(term_sum::AbstractTermSum, state)
120+
@inline function _iterate(::StorageType, term_sum::AbstractTermSum, state)
111121
# 1. Unpack the state tuple
112122
(iter, inner_state) = state
113123

@@ -138,16 +148,16 @@ end
138148
add!(term_sum::AbstractTermSum, term, coeff)
139149
140150
Add `coeff` to the coefficient of `term` in `term_sum`.
141-
Calls `add!(StorageType(term_sum), term_sum, term, coeff)` internally.
142-
For custom behavior, overload `storage()` and/or `add!` for the specific TermSum type.
151+
Calls `_add!(StorageType(term_sum), term_sum, term, coeff)` internally.
152+
For custom behavior, overload `storage()` and/or `_add!` for the specific TermSum type.
143153
"""
144-
function add!(term_sum::AbstractTermSum, term, coeff)
145-
add!(StorageType(term_sum), term_sum, term, coeff)
154+
@inline function add!(term_sum::AbstractTermSum, term, coeff)
155+
_add!(StorageType(term_sum), term_sum, term, coeff)
146156
return term_sum
147157
end
148158

149159

150-
function add!(::DictStorage, term_sum::AbstractTermSum, term, coeff)
160+
@inline function _add!(::DictStorage, term_sum::AbstractTermSum, term, coeff)
151161
dict_storage = storage(term_sum)
152162
if haskey(dict_storage, term)
153163
dict_storage[term] += coeff
@@ -157,7 +167,7 @@ function add!(::DictStorage, term_sum::AbstractTermSum, term, coeff)
157167
return term_sum
158168
end
159169

160-
function add!(::ArrayStorage, term_sum::AbstractTermSum, term, coeff)
170+
@inline function _add!(::ArrayStorage, term_sum::AbstractTermSum, term, coeff)
161171
terms_vec, coeffs_vec = storage(term_sum)
162172
ind = findfirst(t -> t == term, terms_vec)
163173
if !isnothing(ind)
@@ -169,20 +179,20 @@ function add!(::ArrayStorage, term_sum::AbstractTermSum, term, coeff)
169179
return term_sum
170180
end
171181

172-
function add!(::StorageType, term_sum::AbstractTermSum, term, coeff)
173-
thrownotimplemented(typeof(term_sum), :add!)
182+
@inline function _add!(::StorageType, term_sum::AbstractTermSum, term, coeff)
183+
thrownotimplemented(typeof(term_sum), :_add!)
174184
end
175185

176186

177187
function add!(term_sum1::AbstractTermSum, term_sum2::AbstractTermSum)
178-
for (term, coeff) in zip(terms(term_sum2), coefficients(term_sum2))
188+
for (term, coeff) in term_sum2
179189
add!(term_sum1, term, coeff)
180190
end
181191
return term_sum1
182192
end
183193

184194
function subtract!(term_sum1::AbstractTermSum, term_sum2::AbstractTermSum)
185-
for (term, coeff) in zip(terms(term_sum2), coefficients(term_sum2))
195+
for (term, coeff) in term_sum2
186196
add!(term_sum1, term, -coeff)
187197
end
188198
return term_sum1
@@ -193,23 +203,22 @@ end
193203
set!(term_sum::AbstractTermSum, term, coeff)
194204
195205
Set the coefficient of `term` in `term_sum` to `coeff`.
196-
Calls `set!(StorageType(term_sum), term_sum, term, coeff)` internally.
197-
For custom behavior, overload `storage()` and/or `set!` for the specific TermSum type.
206+
Calls `_set!(StorageType(term_sum), term_sum, term, coeff)` internally.
207+
For custom behavior, overload `storage()` and/or `_set!` for the specific TermSum type.
198208
"""
199-
set!(term_sum::AbstractTermSum, term, coeff) = set!(StorageType(term_sum), term_sum, term, coeff)
209+
@inline function set!(term_sum::AbstractTermSum, term, coeff)
210+
_set!(StorageType(term_sum), term_sum, term, coeff)
211+
return term_sum
212+
end
200213

201-
"""
202-
set!(term_storage::Dict, term, coeff)
203214

204-
Default implementation of `set!()` for `Dict` storage.
205-
"""
206-
function set!(::DictStorage, term_sum::AbstractTermSum, term, coeff)
215+
@inline function _set!(::DictStorage, term_sum::AbstractTermSum, term, coeff)
207216
dict_storage = storage(term_sum)
208217
dict_storage[term] = coeff
209218
return term_sum
210219
end
211220

212-
function set!(::ArrayStorage, term_sum::AbstractTermSum, term, coeff)
221+
@inline function _set!(::ArrayStorage, term_sum::AbstractTermSum, term, coeff)
213222
terms_vec, coeffs_vec = storage(term_sum)
214223
ind = findfirst(t -> t == term, terms_vec)
215224
if !isnothing(ind)
@@ -221,13 +230,13 @@ function set!(::ArrayStorage, term_sum::AbstractTermSum, term, coeff)
221230
return term_sum
222231
end
223232

224-
function set!(::StorageType, term_sum::AbstractTermSum, term, coeff)
233+
@inline function _set!(ST::StorageType, term_sum::AbstractTermSum, term, coeff)
225234
old_coeff = getcoeff(term_sum, term)
226235
if old_coeff == zero(coefftype(term_sum))
227-
add!(term_sum, term, coeff)
236+
_add!(ST, term_sum, term, coeff)
228237
else
229238
delta = coeff - old_coeff
230-
add!(term_sum, term, delta)
239+
_add!(ST, term_sum, term, delta)
231240
end
232241
return term_sum
233242
end

src/Propagation/specializations.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ function PropagationBase.applytoall!(gate::PauliRotation, prop_cache::PauliPropa
3030
# loop over all Pauli strings and their coefficients in the Pauli sum
3131
for (pstr, coeff) in psum
3232

33-
if commutes(gate, pstr)
33+
if commutes(gate_mask, pstr)
3434
# if the gate commutes with the pauli string, do nothing
3535
continue
3636
end

src/Visualization/treetracker.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,12 @@ function PropagationBase.applytoall!(gate::PauliRotation, prop_cache::PauliPropa
175175
aux_psum = auxsum(prop_cache)
176176

177177
# Convert PauliRotation to MaskedPauliRotation for efficiency
178-
gate_mask = symboltoint(nqubits(psum), gate.symbols, gate.qinds)
178+
gate_mask = symboltoint(paulitype(psum), gate.symbols, gate.qinds)
179179

180180
# Loop over all Pauli strings and their coefficients in the Pauli sum
181181
for (pstr, coeff) in psum
182182
pstr_str = format_pauli_string(pstr, psum.nqubits)
183-
if commutes(gate, pstr)
183+
if commutes(gate_mask, pstr)
184184
# If the gate commutes, create a new child node and edge
185185
pstr_str = format_pauli_string(pstr, psum.nqubits)
186186
gate_symbol = isempty(gate.symbols) ? "?" : string(gate.symbols[1])

0 commit comments

Comments
 (0)