Open BoltzmannEntropy opened 1 year ago
One naive example for the expectance calculation could be
from paddle_quantum import Hamiltonian
from paddle_quantum.state import random_state
psi = random_state(2)
pauli_str = [(1.0, 'Z0, Z1'), (2.0, 'X0, Y1')]
H = Hamiltonian(pauli_str)
print("The expectation value of the observable ZZ + 2XY is", psi.expec_val(H))
Above example cannot be used in a QNN, in which case you should try
from paddle_quantum.loss import ExpecVal
loss_fcn = ExpecVal(H)
print("The loss value is", loss_fcn(psi))
Thank you very much. For teaching purposes, is this the preferred method?
%reset -f
import numpy as np
from numpy import pi as PI
import paddle
import paddle_quantum
from paddle import matmul
from paddle_quantum.ansatz import Circuit
from paddle_quantum.qinfo import random_pauli_str_generator, pauli_str_to_matrix
from paddle_quantum.linalg import dagger
from paddle_quantum import Hamiltonian
from paddle_quantum.state import random_state
x0=np.array([1/np.sqrt(2), 1/np.sqrt(2)])
x1=np.array([1,0])
x2=np.array([0,1])
state_lst=(x0,x1,x2)
pauli_str_lst = ([(1.0, 'X0')],[(1.0, 'Y0')],[(1.0, 'Z0')])
for gate in pauli_str_lst:
# print (gate)
H = Hamiltonian(gate)
for state in state_lst:
psi=paddle_quantum.State(paddle.to_tensor(state,dtype='complex64'), dtype='complex64')
print("Gate {}, State {}, Exp-val {}".format(gate, psi, psi.expec_val(H).numpy()))
Gate [(1.0, 'X0')], State [0.70710677+0.j 0.70710677+0.j], Exp-val [0.99999994]
Gate [(1.0, 'X0')], State [1.+0.j 0.+0.j], Exp-val [0.]
Gate [(1.0, 'X0')], State [0.+0.j 1.+0.j], Exp-val [0.]
Gate [(1.0, 'Y0')], State [0.70710677+0.j 0.70710677+0.j], Exp-val [0.]
Gate [(1.0, 'Y0')], State [1.+0.j 0.+0.j], Exp-val [0.]
Gate [(1.0, 'Y0')], State [0.+0.j 1.+0.j], Exp-val [0.]
Gate [(1.0, 'Z0')], State [0.70710677+0.j 0.70710677+0.j], Exp-val [0.]
Gate [(1.0, 'Z0')], State [1.+0.j 0.+0.j], Exp-val [1.]
Gate [(1.0, 'Z0')], State [0.+0.j 1.+0.j], Exp-val [-1.]
Thanks
Some codes in terms of Hamiltonian
and State
could be further simplified, as shown below
import numpy as np
import paddle_quantum as pq
from paddle_quantum import Hamiltonian
from paddle_quantum.state import State
pq.set_backend('state_vector')
pq.set_dtype('complex128')
x0 = np.array([1, 1]) / np.sqrt(2)
x1 = np.array([1, 0])
x2 = np.array([0, 1])
state_list = (x0,x1,x2)
pauli_str_list = ([(1.0, 'X0')],[(1.0, 'Y0')],[(1.0, 'Z0')])
for pauli_str in pauli_str_list:
H = Hamiltonian(pauli_str)
for vec in state_list:
psi = State(vec)
print(f"Observable {H.pauli_words}, State {psi.numpy()}, Exp val {psi.expec_val(H)}")
You can refer to the API documents of Hamiltonian
and State
for more information.
Here,
from sympy import Matrix, symbols, sqrt, init_printing
from sympy.physics.quantum import TensorProduct
from IPython.display import display_pretty
init_printing(use_latex=True)
U_I = Matrix([[1,0],
[0,1]])
U_H = 1/sqrt(2)*Matrix([[1, 1],
[1,-1]])
U_Z=Matrix(2,2,[1,0,0,-1])
# U_Z=Matrix(2,2,[1,0,0,-1])
U_ZZ = TensorProduct(U_Z,U_Z)
phi_plus = Matrix(1,4,np.array([1, 0, 0, 1])/np.sqrt(2)).T
# phi_plus*(U_ZZ*phi_plus)
U_ZZ, phi_plus, U_ZZ*phi_plus, U_Z, U_ZZ, phi_plus.T*(U_ZZ*phi_plus)
I get expectation value of 1 from phi_plus.T*(U_ZZ*phi_plus)
.
But in paddle:
# matmul(matmul(U, self.rho), dagger(U))
# paddle.kron(proj, X)
U_Z=paddle.to_tensor([[1,0],[0,-1]], dtype='complex128')
phi_plus = paddle.Tensor(np.array([1, 0, 0, 1])/np.sqrt(2))# | Phi^+ >
phi_plus=paddle.unsqueeze(phi_plus, axis=1)
# U_ZZ = paddle.kron(U_Z,pq.linalg.dagger(U_Z)).numpy()
U_ZZ = paddle.kron(U_Z,U_Z)
exp_val=(phi_plus.T*(U_ZZ*phi_plus)).numpy()
U_ZZ, phi_plus, U_ZZ*phi_plus, exp_val
I get a matrix instead of a scaler, what am I doing wrong?
In numpy:
U_Z=[[1,0],[0,-1]]
phi_plus = (np.matrix([1, 0, 0, 1])/np.sqrt(2)).T# | Phi^+ >
phi_plus.conj().T*(np.kron(U_Z, U_Z)*phi_plus)
matrix([[1.]])
?
The operation *
for matrix-like classes (such as sympy.Matrix
, numpy.matrix
) means matrix multiplication, whereas the operation *
for array-like classes (such as numpy.ndarray
, paddle.Tensor
) means entry-wise multiplication. Please use the symbol @
or the function paddle.matmul
instead.
One naive example for the expectance calculation could be
from paddle_quantum import Hamiltonian from paddle_quantum.state import random_state psi = random_state(2) pauli_str = [(1.0, 'Z0, Z1'), (2.0, 'X0, Y1')] H = Hamiltonian(pauli_str) print("The expectation value of the observable ZZ + 2XY is", psi.expec_val(H))
Above example cannot be used in a QNN, in which case you should try
from paddle_quantum.loss import ExpecVal loss_fcn = ExpecVal(H) print("The loss value is", loss_fcn(psi))
Hey, thank you, but what if I need to compute <ψ|H^2|ψ>, how to use the psi.expec_val(H)?
@imppresser see the example here: https://faculty.washington.edu/seattle/physics541/11solved.pdf
Hi there. The function State.expec_val()
only accepts inputs from class Hamiltonian
, to be compatible with the situation when the backend is switched from simulators to real quantum devices.
To calculate $\langle \psi | H^2 |\psi\rangle$, you can use the bra-ket notation and matrix multiplications to complete the job.
from paddle_quantum import Hamiltonian
from paddle_quantum.state import random_state
from paddle_quantum.qinfo import random_pauli_str_generator
num_qubits = 3
psi = random_state(num_qubits)
H = Hamiltonian(random_pauli_str_generator(num_qubits))
H_matrix = paddle.to_tensor(H.construct_h_matrix())
expect_value = psi.bra @ H_matrix @ H_matrix @ psi.ket
print("The pauli string of this Hamiltonian is \n", H.pauli_str)
print("The expectation value is", expect_value.item())
The pauli string of this Hamiltonian is
[[0.6976072696164013, 'Z1,Z0'], [0.6145107870116349, 'Z1,X0'], [-0.5089016842519825, 'Y1,Y2,Y0']]
The expectation value is (1.177162766456604-2.9802322387695312e-08j)
@imppresser see the example here: https://faculty.washington.edu/seattle/physics541/11solved.pdf
Thank you for your detailed lecture note, caculating the H^2 is surely the way. However, in my case need more general way of implementing code of H^2, because the variety of H. Very happy to find the course, it will help a lot!
Hi there. The function
State.expec_val()
only accepts inputs from classHamiltonian
, to be compatible with the situation when the backend is switched from simulators to real quantum devices.To calculate ⟨ψ|H2|ψ⟩, you can use the bra-ket notation and matrix multiplications to complete the job.
from paddle_quantum import Hamiltonian from paddle_quantum.state import random_state from paddle_quantum.qinfo import random_pauli_str_generator num_qubits = 3 psi = random_state(num_qubits) H = Hamiltonian(random_pauli_str_generator(num_qubits)) H_matrix = paddle.to_tensor(H.construct_h_matrix()) expect_value = psi.bra @ H_matrix @ H_matrix @ psi.ket print("The pauli string of this Hamiltonian is \n", H.pauli_str) print("The expectation value is", expect_value.item())
The pauli string of this Hamiltonian is [[0.6976072696164013, 'Z1,Z0'], [0.6145107870116349, 'Z1,X0'], [-0.5089016842519825, 'Y1,Y2,Y0']] The expectation value is (1.177162766456604-2.9802322387695312e-08j)
Thanks for the detailed feedback. I noticed the "expect_val()" function changed (for compatibility). The solution you give will help me out!
When N(as qubits) turns big like 18, or above, it shows errors for it cannot allocate this big memory. But using "ExpecVal(H)" would not cause error even as large as 20+. If it is possible to run big tasks while expect_value(H^2)?
Are you using a GPU? Can I see the a full code snippet?
Are you using a GPU? Can I see the a full code snippet?
Not using GPU. The problem is to construct h_matrix, it is natural cannot build 2^18 x 2^18 complex64 matrix on PC, it will need 500+GB.
You have reached another motivation to use the class Hamiltonian
. To be specific, the matrices of physical Hamiltonians are usually enriched with zero entries, i.e. the sparse matrix. The linear operations for such matrices can be sped up by tensor contractions (see paddle.einsum
used in Paddle Quantum), saving a significant amout of computational resources.
Regarding your question, multiplication for the Hamiltonian
class is not yet implemented. But, it has been included for future discussions. For now one naive and possibly expensive approach is to extract the Pauli strings of $H$, calculate the Pauli strings of $H^2$ using the identity of Pauli basis
$$\sigma_j \ \sigmak = \delta{jk} \ I + i \epsilon{jkl} \ \sigma{l},$$
and then feed the result back into ExpecVal
or State.expec_val
.
You have reached another motivation to use the class
Hamiltonian
. To be specific, the matrices of physical Hamiltonians are usually enriched with zero entries, i.e. the sparse matrix. The linear operations for such matrices can be sped up by tensor contractions (seepaddle.einsum
used in Paddle Quantum), saving a significant amout of computational resources.Regarding your question, multiplication for the
Hamiltonian
class is not yet implemented. But, it has been included for future discussions. For now one naive and possibly expensive approach is to extract the Pauli strings of H, calculate the Pauli strings of H2 using the identity of Pauli basis σj σk=δjk I+iϵjkl σl, and then feed the result back intoExpecVal
orState.expec_val
.
I will try this, thank you. Here I have two questions.
state.bra @ H_matrix @ state.ket
; loss is the result of ExpecVal(Hamiltonian)
; loss_expec_val is the result of State.expec_val(Hamiltonian)
Many thanks to your support!You have reached another motivation to use the class
Hamiltonian
. To be specific, the matrices of physical Hamiltonians are usually enriched with zero entries, i.e. the sparse matrix. The linear operations for such matrices can be sped up by tensor contractions (seepaddle.einsum
used in Paddle Quantum), saving a significant amout of computational resources.Regarding your question, multiplication for the
Hamiltonian
class is not yet implemented. But, it has been included for future discussions. For now one naive and possibly expensive approach is to extract the Pauli strings of H, calculate the Pauli strings of H2 using the identity of Pauli basis σj σk=δjk I+iϵjkl σl, and then feed the result back intoExpecVal
orState.expec_val
.
I tried to construct pauli string using σj σk=δjk I+iϵjkl σl
, one simple example like Z0X0=iY0
, the coefficient will need to multiply i
. An original pauli string [[1.0,'Z0, X0']] (cannot use directly, will cause error AssertionError: each Pauli operator should act on different qubit
), converted to [[1.0j,'Y0']], It will cause error, because it only accept coefficient as float
type.
- I use HVA Ansatz(VQE) to get the ground state energy in 1d Transverse-field Ising model, and can get energy below the real ground state energy, when N=4 with periodic boundry condition and certain transverse strength g=1, the ground state energy is -5.22625186 (Exact Diagonalization). The Loss gives me -5.226253032684326 which belows the real ground state energy. If that is possible, why?
- I use "state.bra @ H_matrix @ state.ket" to caculate the ⟨ψ|H|ψ⟩, the result is slightly different from the "ExpecVal or State.expec_val". How could that be?
The variations among State.expec_val
, state.bra @ H_matrix @ state.ket
and the theorectical value are due to the inevitable precision problem, which increases exponentially as the number of qubits gets larger (and hence motivates the development of quantum computers). Particularly, the precision differences between State.expec_val
and state.bra @ H_matrix @ state.ket
are essentially the difference between tensor contractions and matrix multiplications, where tensor contraction is slightly better since it deals with smaller matrices.
To relieve the precision problem when the qubit size is not too large, you can switch the data type (dtype) from complex64
to complex128
, improving the precision from $\mathcal{O}\left( \ 10^{-8}\ \right)$ to $\mathcal{O}\left( \ 10^{-16}\ \right)$ in average, as demonstrated in the following example:
import paddle_quantum as pq
from paddle_quantum import Hamiltonian
from paddle_quantum.state import ghz_state
from paddle_quantum.qinfo import random_pauli_str_generator
num_qubits = 4
pauli_str = random_pauli_str_generator(num_qubits, terms=10)
# Compute error from imaginary part of expectation value
def expect_error() -> float:
psi = ghz_state(num_qubits)
H = Hamiltonian(pauli_str)
H_matrix = paddle.to_tensor(H.construct_h_matrix())
value = psi.bra @ H_matrix @ H_matrix @ psi.ket
return abs(value.imag().item())
# Calculate the expectance value under complex64
pq.set_dtype('complex64')
error_64 = expect_error()
# Calculate the expectance value under complex128
pq.set_dtype('complex128')
error_128 = expect_error()
print("The error for complex64 is", error_64)
print("The error for complex128 is", error_128)
The error for complex64 is 1.1195256277574117e-08
The error for complex128 is 9.813077866773593e-18
Note that the function paddle_quantum.set_dype
should be excecuted at the very beginning of the program, so that every operation in Paddle Quantum can be computed under dtype complex128
. Also, as a trade-off, computations in complex128
are slower than those in complex64
.
I tried to construct pauli string using
σj σk=δjk I+iϵjkl σl
, one simple example likeZ0X0=iY0
, the coefficient will need to multiplyi
. An original pauli string [[1.0,'Z0, X0']] (cannot use directly, will cause errorAssertionError: each Pauli operator should act on different qubit
), converted to [[1.0j,'Y0']], It will cause error, because it only accept coefficient asfloat
type.
The reason why the class Hamiltonian
only accepts real coefficients, is that Hamiltonians are Hermitian matrices and thus have only real eigenvalues. For example ZX
cannot be a Hamiltonian since its eigenvalues are i
and -i
.
For the same reason, I believe every imaginary coefficient occured in one term of $H^2$ can be vanished somewhere for meeting its complex conjugates. For example, consider $H = X + Z$. Then
$$H^2 = X^2 + XZ + ZX + Z^2 = I - iY + iY + I = 2I.$$
Such logic may need to be considered during the construction of Pauli strings of $H^2$.
I tried to construct pauli string using
σj σk=δjk I+iϵjkl σl
, one simple example likeZ0X0=iY0
, the coefficient will need to multiplyi
. An original pauli string [[1.0,'Z0, X0']] (cannot use directly, will cause errorAssertionError: each Pauli operator should act on different qubit
), converted to [[1.0j,'Y0']], It will cause error, because it only accept coefficient asfloat
type.The reason why the class
Hamiltonian
only accepts real coefficients, is that Hamiltonians are Hermitian matrices and thus have only real eigenvalues. For exampleZX
cannot be a Hamiltonian since its eigenvalues arei
and-i
.For the same reason, I believe every imaginary coefficient occured in one term of H2 can be vanished somewhere for meeting its complex conjugates. For example, consider H=X+Z. Then
H2=X2+XZ+ZX+Z2=I−iY+iY+I=2I.
Such logic may need to be considered during the construction of Pauli strings of H2.
Yes, indeed. I retain every term when I trying to build the H^2 pauli string function. And I should care the imaginary coefficient can be vanished, thanks!
- I use HVA Ansatz(VQE) to get the ground state energy in 1d Transverse-field Ising model, and can get energy below the real ground state energy, when N=4 with periodic boundry condition and certain transverse strength g=1, the ground state energy is -5.22625186 (Exact Diagonalization). The Loss gives me -5.226253032684326 which belows the real ground state energy. If that is possible, why?
- I use "state.bra @ H_matrix @ state.ket" to caculate the ⟨ψ|H|ψ⟩, the result is slightly different from the "ExpecVal or State.expec_val". How could that be?
The variations among
State.expec_val
,state.bra @ H_matrix @ state.ket
and the theorectical value are due to the inevitable precision problem, which increases exponentially as the number of qubits gets larger (and hence motivates the development of quantum computers). Particularly, the precision differences betweenState.expec_val
andstate.bra @ H_matrix @ state.ket
are essentially the difference between tensor contractions and matrix multiplications, where tensor contraction is slightly better since it deals with smaller matrices.To relieve the precision problem when the qubit size is not too large, you can switch the data type (dtype) from
complex64
tocomplex128
, improving the precision from O( 10−8 ) to O( 10−16 ) in average, as demonstrated in the following example:import paddle_quantum as pq from paddle_quantum import Hamiltonian from paddle_quantum.state import ghz_state from paddle_quantum.qinfo import random_pauli_str_generator num_qubits = 4 pauli_str = random_pauli_str_generator(num_qubits, terms=10) # Compute error from imaginary part of expectation value def expect_error() -> float: psi = ghz_state(num_qubits) H = Hamiltonian(pauli_str) H_matrix = paddle.to_tensor(H.construct_h_matrix()) value = psi.bra @ H_matrix @ H_matrix @ psi.ket return abs(value.imag().item()) # Calculate the expectance value under complex64 pq.set_dtype('complex64') error_64 = expect_error() # Calculate the expectance value under complex128 pq.set_dtype('complex128') error_128 = expect_error() print("The error for complex64 is", error_64) print("The error for complex128 is", error_128)
The error for complex64 is 1.1195256277574117e-08 The error for complex128 is 9.813077866773593e-18
Note that the function
paddle_quantum.set_dype
should be excecuted at the very beginning of the program, so that every operation in Paddle Quantum can be computed under dtypecomplex128
. Also, as a trade-off, computations incomplex128
are slower than those incomplex64
.
Nice solution, I will try, thanks 👍
Hello, For a tutorial, I am trying to do something very simple, calculating the expectation value of:
Instead of:
How can i specify my own Hamiltonian to compare the value with the theoretical one? Thanks