Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use fewer allocations and more performant functions in runcircuit #304

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
PLEASE NOTE THIS IS PRE-RELEASE SOFTWARE

# PastaQ.jl: design and benchmarking quantum hardware
PastaQ.jl is a Julia software toolbox providing a range of computational methods for quantum computing applications. Some examples are the simulation of quancum circuits, the design of quantum gates, noise characterization and performance benchmarking. PastaQ relies on tensor-network representations of quantum states and processes, and borrows well-refined techniques from the field of machine learning and data science, such as probabilistic modeling and automatic differentiation.
PastaQ.jl is a Julia software toolbox providing a range of computational methods for quantum computing applications. Some examples are the simulation of quantum circuits, the design of quantum gates, noise characterization and performance benchmarking. PastaQ relies on tensor-network representations of quantum states and processes, and borrows well-refined techniques from the field of machine learning and data science, such as probabilistic modeling and automatic differentiation.

![alt text](assets/readme_summary.jpg)

Expand Down Expand Up @@ -67,7 +67,7 @@ gates = [("X" , 1), # Pauli X on qubit 1
# [4] ((dim=2|id=980|"Qubit,Site,n=4"), (dim=2|id=357|"Link,n=1"))
```

In this next example, we create a circuit to prepare the GHZ state, and sample projective measurements in the computational basis. We then execture the circuit in the presence of noise, where a local noise channel is applied to each gate. A noise model is described as `noisemodel = ("noisename", (noiseparams...))`, in which case it is applied to each gate identically. To distinguish between one- and two-qubit gates, for example, the following syntax can be used: `noisemodel = (1 => noise1, 2 => noise2)`. For more sophisticated noise models (such as gate-dependent noise), please refer to the documentation.
In this next example, we create a circuit to prepare the GHZ state, and sample projective measurements in the computational basis. We then execute the circuit in the presence of noise, where a local noise channel is applied to each gate. A noise model is described as `noisemodel = ("noisename", (noiseparams...))`, in which case it is applied to each gate identically. To distinguish between one- and two-qubit gates, for example, the following syntax can be used: `noisemodel = (1 => noise1, 2 => noise2)`. For more sophisticated noise models (such as gate-dependent noise), please refer to the documentation.

```julia
using PastaQ
Expand Down Expand Up @@ -142,10 +142,10 @@ circuit = randomcircuit(n; depth = depth,
# maxlinkdim(ψ) = 908
```

#### Variational quantum eingensolver
We show how to perform a ground state search of a many-body hamiltonian $H$ using the variational quantum eigensolver (VQE). The VQE algorithm, based on the variational principle, consists of an iterative optimization of an objective function $\langle \psi(\theta)|H|\psi(\theta)\rangle/\langle\psi(\theta)|\psi(\theta)\rangle$, where $|\psi(\theta)\rangle = U(\theta)|0\rangle$ is the output wavefunction of a parametrized quantum circuit $U(\theta)$.
#### Variational quantum eigensolver
We show how to perform a ground state search of a many-body Hamiltonian $H$ using the variational quantum eigensolver (VQE). The VQE algorithm, based on the variational principle, consists of an iterative optimization of an objective function $\langle \psi(\theta)|H|\psi(\theta)\rangle/\langle\psi(\theta)|\psi(\theta)\rangle$, where $|\psi(\theta)\rangle = U(\theta)|0\rangle$ is the output wavefunction of a parametrized quantum circuit $U(\theta)$.

In the following example, we consider a quantum Ising model with 10 spins, and perform the optimization by leveraging Automatic Differentiation techniques (AD), provided by the package Zygote.jl. Specifically, we build a variational circuit using built-in circuit-contruction functions, and optimize the expectation value of the Hamiltonian using a gradient-based approach and the LBFGS optimizer. The gradients are evaluated through AD, providing a flexible interface in defining custom variational circuit ansatze.
In the following example, we consider a quantum Ising model with 10 spins, and perform the optimization by leveraging Automatic Differentiation techniques (AD), provided by the package Zygote.jl. Specifically, we build a variational circuit using built-in circuit-construction functions, and optimize the expectation value of the Hamiltonian using a gradient-based approach and the LBFGS optimizer. The gradients are evaluated through AD, providing a flexible interface in defining custom variational circuit ansatze.

```julia
using PastaQ
Expand Down
8 changes: 2 additions & 6 deletions src/circuits/runcircuit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,8 @@ function runcircuit(
# which was added using the `insertnoise` function. If so, one should call directly
# the `choimatrix` function.
circuit_tensors = buildcircuit(hilbert, circuit; noise, device, eltype)
if circuit_tensors isa Vector{<:ITensor}
inds_sizes = [length(inds(g)) for g in circuit_tensors]
else
inds_sizes = vcat([[length(inds(g)) for g in layer] for layer in circuit_tensors]...)
end
noiseflag = any(x -> x % 2 == 1, inds_sizes)
layers = circuit_tensors isa Vector{<:ITensor} ? (circuit_tensors,) : circuit_tensors
Copy link
Collaborator

Choose a reason for hiding this comment

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

Even better would be to split off into a function:

to_layers(ts::Vector{ITensor}) = [ts]
to_layers(ts::Vector{Vector{ITensor}}) = ts

I'm weary of using a Tuple since our convention is that a layered circuit is nested layers of Vector, though I understand it avoids an allocation. I don't think we're worried about the performance of this part of the code though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense. I'm guessing that allocating here would change the efficiency of computing noise_flags very little if at all.

Would to_layers be more generally useful? You could use it elsewhere to support the convention a bit. If not, you might consider [circuit_tensors] : circuit_tensors.

In any case, part of the point of this PR is that code serves as an example. Even though using a Tuple doesn't affect the behavior here, someone could copy the code or idea to another location where your convention is expected to be followed. So using an Array makes sense.

Furthermore, there are plans to add more escape analysis to the Julia compiler in the not too distant future. I imagine the compiler will do [x] $\rightarrow$ (x,) for you in simple cases like this.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's better to move into a function, I could imagine that would be useful in other places (and I could imagine expanding the functionality to convert other structures into our standard layered circuit format).

noiseflag = any(isodd, (length(inds(g)) for layer in layers for g in layer))

# Unitary operator for the circuit
if process && !noiseflag
Expand Down
Loading