diff --git a/docs/src/assets/images/Riemannian.png b/docs/src/assets/images/Riemannian.png new file mode 100644 index 00000000..7a46dc10 Binary files /dev/null and b/docs/src/assets/images/Riemannian.png differ diff --git a/docs/src/examples/8.riemannian-gradient-flow/main.jl b/docs/src/examples/8.riemannian-gradient-flow/main.jl index 50429fd2..93d1bcf6 100644 --- a/docs/src/examples/8.riemannian-gradient-flow/main.jl +++ b/docs/src/examples/8.riemannian-gradient-flow/main.jl @@ -125,20 +125,25 @@ end; # Finally, let's try it out. # We initialize the state ``|0\rangle`` and apply several optimization steps. -circuit = chain(n) -pauli_strings = generate_2local_pauli_strings(n) -history = Float64[] - -for i=1:100 - cost = step_and_cost!(n, circuit, h, 0.01, pauli_strings) - push!(history, cost) -end - -Plots.plot(history, legend=false) -Plots.plot!(1:100, [w[1] for i=1:100]) -xlabel!("steps") -ylabel!("energy") +# ```julia +# circuit = chain(n) +# pauli_strings = generate_2local_pauli_strings(n) +# history = Float64[] + +# for i=1:100 +# cost = step_and_cost!(n, circuit, h, 0.01, pauli_strings) +# push!(history, cost) +# end + +# Plots.plot(history, legend=false) +# Plots.plot!(1:100, [w[1] for i=1:100]) +# xlabel!("steps") +# ylabel!("energy") +# ``` +# ```@raw html +# Riemannian gradient flow +# ``` # When we compare the final states achieved with the Riemannian gradient flow # optimizer and with the standard VQE we can notice that the former has lower quality. # This is because the Riemannian gradient flow optimizer has only a local view of the cost landscape diff --git a/docs/src/man/registers.md b/docs/src/man/registers.md index e82e35a5..51c36f43 100644 --- a/docs/src/man/registers.md +++ b/docs/src/man/registers.md @@ -11,9 +11,163 @@ end # [Quantum Registers](@id registers) +## Constructing quantum states + A quantum register is a quantum state or a batch of quantum states. -`Yao` provides two types of quantum registers [`ArrayReg`](@ref) and [`BatchedArrayReg`](@ref). +Qubits in a Yao register can be active or inactive. +Only active qubits are visible to quantum operators, which enables applying quantum operators on a subset of qubits. +For example, Suppose we want to run a quantum Fourier transformation circuit of size 4 on qubits `(1, 3, 5, 7)` with the [`focus!`](@ref) function, +we first set these qubits to active qubits the rest to inactive, then we apply the circuit on the active qubits, and finally we switch back to the original configuration with the [`relax!`](@ref) function. + +`Yao` provides two types of quantum registers [`ArrayReg`](@ref) and [`BatchedArrayReg`](@ref). Both use matrices as the storage. +For example, for a quantum register with ``a`` active qubits, ``r`` remaining qubits and batch size ``b``, the storage is as follows. + +![](../assets/images/regstorage.svg) + +The first dimension of size ``2^a`` is for active qubits, only this subset of qubits are allowed to interact with quantum operators. Since we reshaped the state vector into a matrix, applying a quantum operator can be conceptually represented as a matrix-matrix multiplication. + +Various quantum states can be created with the following functions. +```@repl register +using Yao +reg = ArrayReg([0, 1, -1+0.0im, 0]) # a unnormalized Bell state |01⟩ - |10⟩ +statevec(reg) # a quantum state is represented as a vector +print_table(reg) +``` + +```@repl register +reg_zero = zero_state(3) # create a zero state |000⟩ +print_table(reg_zero) +``` +```@repl register +reg_rand = rand_state(ComplexF32, 3) # a random state +``` + +```@repl register +reg_uniform = uniform_state(ComplexF32, 3) # a uniform state +print_table(reg_uniform) +``` + +```@repl register +reg_prod = product_state(bit"110") # a product state +bit"110"[3] # the bit string is in little-endian format +print_table(reg_prod) +``` + +```@repl register +reg_ghz = ghz_state(3) # a GHZ state +print_table(reg_ghz) +von_neumann_entropy(reg_ghz, (1, 3)) / log(2) # entanglement entropy between qubits (1, 3) and (2,) +``` + +```@repl register +reg_rand3 = rand_state(3, nlevel=3) # a random qutrit state +reg_prod3 = product_state(dit"120;3") # a qudit product state, what follows ";" symbol denotes the number of levels +print_table(reg_prod3) +``` + +```@repl register +reg_batch = rand_state(3; nbatch=2) # a batch of 2 random qubit states +print_table(reg_batch) +reg_view = viewbatch(reg_batch, 1) # view the first state in the batch +print_table(reg_view) +``` + +```@repl register +reg = rand_state(3; nlevel=4, nbatch=2) +nqudits(reg) # the total number of qudits +nactive(reg) # the number of active qubits +nremain(reg) # the number of remaining qubits +nbatch(reg) # the batch size +nlevel(reg) # the number of levels of each qudit +basis(reg) # the basis of the register +focus!(reg, 1:2) # set on the first two qubits as active +nactive(reg) # the number of active qubits +basis(reg) # the basis of the register +relax!(reg) # set all qubits as active +nactive(reg) # the number of active qubits +reorder!(reg, (3,1,2)) # reorder the qubits + +reg1 = product_state(bit"111"); +reg2 = ghz_state(3); +fidelity(reg1, reg2) # the fidelity between two states +tracedist(reg1, reg2) # the trace distance between two states +``` + +## Arithmetic operations + +The list of arithmetic operations for [`ArrayReg`](@ref) include +* `+` +* `-` +* `*` +* `/` (scalar) +* `adjoint` + + +```@repl register +reg1 = rand_state(3) +reg2 = rand_state(3) +reg3 = reg1 + reg2 # addition +normalize!(reg3) # normalize the state +isnormalized(reg3) # check if the state is normalized +reg1 - reg2 # subtraction +reg1 * 2 # scalar multiplication +reg1 / 2 # scalar division +reg1' # adjoint +reg1' * reg1 # inner product +``` + +## Register operations +```@repl register +reg0 = rand_state(3) +append_qudits!(reg0, 2) # append 2 qubits +insert_qudits!(reg0, 2, 2) # insert 2 qubits at the 2nd position +``` + +Comparing with using matrix multiplication for quantum simulation, using specialized instructions are much faster and memory efficient. These instructions are specified with the [`instruct!`](@ref) function. + +```@repl register +reg = zero_state(2) +instruct!(reg, Val(:H), (1,)) # apply a Hadamard gate on the first qubit +print_table(reg) +``` + +## Measurement + +We use the [`measure!`](@ref) function returns the measurement outcome and collapses the state after the measurement. +We also have some "cheating" version [`measure`](@ref) that does not collapse states to facilitate classical simulation. + +```@repl register +measure!(reg0, 1) # measure the qubit, the state collapses +measure!(reg0) # measure all qubits +measure(reg0, 3) # measure the qubit 3 times, the state does not collapse (hacky) +reorder!(reg0, 7:-1:1) # reorder the qubits +measure!(reg0) +invorder!(reg0) # reverse the order of qubits +measure!(reg0) +measure!(RemoveMeasured(), reg0, 2:4) # remove the measured qubits +reg0 + +reg1 = ghz_state(3) +select!(reg1, bit"111") # post-select the |111⟩ state +isnormalized(reg1) # check if the state is normalized +``` + +## Density matrices + +```@repl register +reg = rand_state(3) +rho = density_matrix(reg) # the density matrix of the state +rand_density_matrix(3) # a random density matrix +completely_mixed_state(3) # a completely mixed state +partial_tr(rho, 1) # partial trace on the first qubit +purify(rho) # purify the state +von_neumann_entropy(rho) # von Neumann entropy +mutual_information(rho, 1, 2) # mutual information between qubits 1 and 2 +``` + +## API +The constructors and functions for quantum registers are listed below. ```@docs AbstractRegister AbstractArrayReg @@ -21,8 +175,6 @@ ArrayReg BatchedArrayReg ``` -We define some shortcuts to create simulated quantum states easier: - ```@docs arrayreg product_state @@ -34,12 +186,7 @@ ghz_state clone ``` -In a register, qubits are distinguished as active and inactive (or remaining). -The total number of qubits is the number of active qubits plus the number of remaining qubits. -Only active qubits are visible to quantum operators and the number of these qubits are the *size* of a register. -Making this distinction of qubits allows writing reusable quantum circuits. -For example, Suppose we want to run a quantum Fourier transformation circuit of size 4 on qubits `(1, 3, 5, 7)`, -we first set the target qubits to active qubits the reset to inactive, then we apply the circuit on it, finally we unset the inactive qubits. +The following functions are for querying the properties of a quantum register. ```@docs nqudits @@ -54,15 +201,7 @@ relax! exchange_sysenv ``` -## Storage - -Both [`ArrayReg`](@ref) and [`BatchedArrayReg`](@ref) use matrices as the storage. For example, for a quantum register with ``a`` active qubits, ``r`` remaining qubits and batch size ``b``, the storage is as follows - -![](../assets/images/regstorage.svg) - -The first dimension of size ``2^a`` is for active qubits, only this subset of qubits are allowed to interact with blocks. Since we reshaped the state vector into a matrix, applying a quantum operator can always be represented as a matrix-matrix multiplication . In practice, most gates have in-place implementation that does not require constructing the operator matrix explicitly. - -You can access different views of the storage of an [`ArrayReg`](@ref) with the following functions: +The following functions are for querying the state of a quantum register. ```@docs state @@ -75,23 +214,7 @@ viewbatch transpose_storage ``` -## Operations - -The list of arithmetic operations for [`ArrayReg`](@ref) include -* `+` -* `-` -* `*` -* `/` (scalar) -* `adjoint` - -Then the inner product can be computed as follows. - -```julia -julia> reg = rand_state(3); - -julia> reg' * reg -0.9999999999999998 + 0.0im -``` +The following functions are for arithmetic operations on quantum registers. ```@docs AdjointArrayReg @@ -112,7 +235,7 @@ fidelity tracedist ``` -## Resource management and addressing +The following functions are for adding and reordering qubits in a quantum register. ```@docs insert_qudits! @@ -123,22 +246,13 @@ reorder! invorder! ``` -Only a subset of qubits that does not interact with other qubits can be removed, the best approach is first measuring it in computational basis first. -It can be done with the [`measure!`](@ref) function by setting the first argument to `RemoveMeasured()`. - -## Instruction set - -Although we have matrix representation for Yao blocks, specialized instructions are much faster and memory efficient than using the matrix-matrix product. -These instructions are specified with the `instruct!` function listed bellow. +The `instruct!` function is for applying quantum operators on a quantum register. ```@docs YaoArrayRegister.instruct! ``` -## Measurement - -We have a true measure function `measure!` that collapses the state after the measurement. -We also have some "cheating" functions to facilitate classical simulation. +The following functions are for measurement and post-selection. ```@docs measure! @@ -150,7 +264,7 @@ probs most_probable ``` -## Density matrices +The following functions are for density matrices. ```@docs DensityMatrix diff --git a/docs/src/man/symbolic.md b/docs/src/man/symbolic.md index 8c3b4939..b8e67120 100644 --- a/docs/src/man/symbolic.md +++ b/docs/src/man/symbolic.md @@ -11,7 +11,22 @@ end # Symbolic Computation -Symbolic Computation support for Yao +The symbolic engine of Yao is based on [SymEngine.jl](https://github.com/symengine/SymEngine.jl). It allows one to define quantum circuits with symbolic parameters and perform symbolic computation on them. Two macro/functions play a key role in the symbolic computation: +- `@vars` for defining symbolic variables +- `subs` for substituting symbolic variables with concrete values + +```@repl sym +using Yao +@vars θ +circuit = chain(2, put(1=>H), put(2=>Ry(θ))) +mat(circuit) +new_circuit = subs(circuit, θ=>π/2) +mat(new_circuit) +``` + +## API + +The following functions are for working with symbolic states. ```@docs @ket_str diff --git a/docs/src/quick-start.md b/docs/src/quick-start.md index 9452a8a9..5807def6 100644 --- a/docs/src/quick-start.md +++ b/docs/src/quick-start.md @@ -11,11 +11,11 @@ is the [`ArrayReg`](@ref), you can create it by feeding a state vector to it, e. ```@repl quick-start using Yao -ArrayReg(rand(ComplexF64, 2^3)) -zero_state(5) -rand_state(5) -product_state(bit"10100") -ghz_state(5) +ArrayReg(randn(ComplexF64, 2^3)) # a random unnormalized 3-qubit state +zero_state(5) # |00000⟩ +rand_state(5) # a random state +product_state(bit"10100") # |10100⟩ +ghz_state(5) # (|00000⟩ + |11111⟩)/√2 ``` the internal quantum state can be accessed via [`statevec`](@ref) method @@ -26,10 +26,9 @@ statevec(ghz_state(2)) for more functionalities about registers please refer to the manual of [Registers](@ref registers). -## Create quantum circuit with Yao blocks +## Create quantum circuit -Yao uses the quantum "block"s to describe quantum circuits, e.g -the following code creates a 2-qubit circuit +Yao introduces an abstract representation for linear maps, called "block"s, which can be used to represent quantum circuits, Hamiltonians, and other quantum operations. The following code creates a 2-qubit circuit ```@repl quick-start chain(2, put(1=>H), put(2=>X)) @@ -39,48 +38,54 @@ where `H` gate is at 1st qubit, `X` gate is at 2nd qubit. A more advanced example is the quantum Fourier transform circuit ```@repl quick-start -A(i, j) = control(i, j=>shift(2π/(1<<(i-j+1)))) +A(i, j) = control(i, j=>shift(2π/(1<<(i-j+1)))) # a cphase gate B(n, k) = chain(n, j==k ? put(k=>H) : A(j, k) for j in k:n) qft(n) = chain(B(n, k) for k in 1:n) -qft(3) +circuit = qft(3) # a 3-qubit QFT circuit +mat(circuit) # the matrix representation of the circuit +apply!(zero_state(3), circuit) # apply the circuit to a zero state ``` -## Create Hamiltonian with Yao blocks +More details about available blocks can be found in the manual of [Blocks](@ref blocks). -the quantum "block"s are expressions on quantum operators, thus, it can -also be used to represent a Hamiltonian, e.g we can create a simple Ising -Hamiltonian on 1D chain as following +## Create Hamiltonian + +We can create a simple Ising Hamiltonian on 1D chain as following ```@repl quick-start -sum(kron(5, i=>Z, mod1(i+1, 5)=>Z) for i in 1:5) +h = sum([kron(5, i=>Z, mod1(i+1, 5)=>Z) for i in 1:5]) # a 5-qubit Ising Hamiltonian +mat(h) # the matrix representation of the Hamiltonian ``` -## Automatic differentiate a Yao block +## Differentiating a quantum circuit Yao has its own automatic differentiation rule implemented, this allows one obtain gradients of a loss function by simply putting a `'` mark following [`expect`](@ref) or [`fidelity`](@ref), e.g +To obtain the gradient of the quantum Fourier transform circuit with respect to its parameters, one can use the following code ```@repl quick-start -expect'(X, zero_state(1)=>Rx(0.2)) +grad_state, grad_circuit_params = expect'(kron(X, X, I2) + kron(I2, X, X), zero_state(3)=>qft(3)) ``` - -or for fidelity - +where `kron(X, X, I2) + kron(I2, X, X)` is the target Hamiltonian, `zero_state(3)` is the initial state, `qft(3)` is the quantum Fourier transform circuit. +The return value is a vector, each corresponding to the gradient of the loss function with respect to a parameter in the circuit. +The list of parameters can be obtained by [`parameters`](@ref) function. ```@repl quick-start -fidelity'(zero_state(1)=>Rx(0.1), zero_state(1)=>Rx(0.2)) +parameters(qft(3)) ``` -## Combine Yao with ChainRules/Zygote - +To obtain the gradient of the fidelity between a state parameterized by a quantum circuit and a target state, one can use the following code -## Symbolic calculation with Yao block -Yao supports symbolic calculation of quantum circuit via `SymEngine`. We can show +```@repl quick-start +((grad_state1, grad_circuit1), grad_state2) = fidelity'(zero_state(3)=>qft(3), ghz_state(3)) +``` +where `zero_state(3)` is the initial state, `qft(3)` is the quantum Fourier transform circuit, `ghz_state(3)` is the target state. +The automatic differentiation functionality can also be accessed by interfacing with the machine learning libraries [`Zygote`](https://github.com/FluxML/Zygote.jl). ## Plot quantum circuits -The component package `YaoPlots` provides plotting for quantum circuits and ZX diagrams. +The component package `YaoPlots` provides plotting for quantum circuits and ZX diagrams. You can use it to visualize your quantum circuits in [`VSCode`](https://code.visualstudio.com/), [`Jupyter`](https://jupyter.org/) notebook or [`Pluto`](https://github.com/fonsp/Pluto.jl) notebook. ```@example quick-start using Yao.EasyBuild, Yao.YaoPlots @@ -88,4 +93,6 @@ using Compose # show a qft circuit vizcircuit(qft_circuit(5)) -``` \ No newline at end of file +``` + +More details about the plotting can be found in the manual: [Quantum Circuit Visualization](@ref). \ No newline at end of file diff --git a/lib/YaoArrayRegister/src/density_matrix.jl b/lib/YaoArrayRegister/src/density_matrix.jl index b3b91adf..6e3b02e6 100644 --- a/lib/YaoArrayRegister/src/density_matrix.jl +++ b/lib/YaoArrayRegister/src/density_matrix.jl @@ -106,7 +106,7 @@ end function YaoAPI.purify(r::DensityMatrix{D}; num_env::Int = nactive(r)) where {D} Ne = D ^ num_env Ns = size(r.state, 1) - R, U = eigen!(r.state) + R, U = eigen(r.state) state = view(U, :, Ns-Ne+1:Ns) .* sqrt.(abs.(view(R, Ns-Ne+1:Ns)')) return ArrayReg{D}(state) end