Download Notebook

Gate and gate_typical

[1]:
import numpy as np
np.set_printoptions(linewidth=200)

Gate

Mathematically a quantum gate is a linear trace-preserving and completely positive (L-TPCP) map on the space of quantum states, and there are several different matrix representations for quantum gate. In Quara, a class Gate is based on the Hilbert-Schmidt matrix representation of a gate with respect to the matrix basis in the CompositeSystem.
This matrix representation is denote hs in Quara. hs is a 2-dimensional numpy array.
Example.
\(X\) maps each element of basis as follows \(X = \begin{bmatrix} 0 & 1 \\ 1 & 0 \\ \end{bmatrix}\) maps each element of basis \(B = \{ I/\sqrt{2}, X/\sqrt{2}, Y/\sqrt{2}, Z/\sqrt{2} \}\) as follows:
  • \(I/\sqrt{2} \mapsto X \cdot I/\sqrt{2} \cdot X^\dagger = I/\sqrt{2} = [1, 0, 0, 0]^T\) on basis \(B\).

  • \(X/\sqrt{2} \mapsto X \cdot X/\sqrt{2} \cdot X^\dagger = X/\sqrt{2} = [0, 1, 0, 0]^T\) on basis \(B\).

  • \(Y/\sqrt{2} \mapsto X \cdot Y/\sqrt{2} \cdot X^\dagger = -Y/\sqrt{2} = [0, 0, -1, 0]^T\) on basis \(B\).

  • \(Z/\sqrt{2} \mapsto X \cdot Z/\sqrt{2} \cdot X^\dagger = -Z/\sqrt{2} = [0, 0, 0, -1]^T\) on basis \(B\).

Therefore, the Hilbert-Schmidt matrix representation hs of \(X\) is \(\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & -1 & 0 \\ 0 & 0 & 0 & -1 \end{bmatrix}\).

The methods for generating a Gate includes the following:

  • Generate from gate_typical module

  • Generate Gate object directly

Generate from gate_typical module by specifying CompositeSystem and gate name (ex. “x”).

[2]:
from quara.objects.composite_system_typical import generate_composite_system
from quara.objects.gate_typical import generate_gate_from_gate_name

c_sys = generate_composite_system("qubit", 1)
gate = generate_gate_from_gate_name("x", c_sys)
print(gate)
Type:
Gate

Dim:
2

HS:
[[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0. -1.  0.]
 [ 0.  0.  0. -1.]]

Generate Gate object directly using CompositeSystem and a numpy array.

[3]:
from quara.objects.composite_system import CompositeSystem
from quara.objects.elemental_system import ElementalSystem
from quara.objects.matrix_basis import get_normalized_pauli_basis
from quara.objects.gate import Gate

basis = get_normalized_pauli_basis(1)
e_sys = ElementalSystem(0, basis)
c_sys = CompositeSystem([e_sys])
hs = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1]], dtype=np.float64)
gate = Gate(c_sys, hs)
print(gate)
Type:
Gate

Dim:
2

HS:
[[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0. -1.  0.]
 [ 0.  0.  0. -1.]]

specific properties

The property hs of Gate is a 2-dimensional numpy array specified by the constructor argument hs.

[4]:
gate = Gate(c_sys, hs)
print(f"hs: \n{gate.hs}")
hs:
[[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0. -1.  0.]
 [ 0.  0.  0. -1.]]

The property dim of Gate is the size of square matrix hs.

[5]:
print(f"dim: {gate.dim}")
print(f"size of square matrix hs: {int(np.sqrt(hs.shape[0]))}")
dim: 2
size of square matrix hs: 2

functions to check constraints

The is_eq_constraint_satisfied() function returns True, if and only if hs is TP(trace-preserving map), i.e. if and only if the first row of hs is equal to \([ 1, 0, \dots, 0 ]\).

[6]:
print(f"is_eq_constraint_satisfied(): {gate.is_eq_constraint_satisfied()}")
print(f"is_tp(): {gate.is_tp()}")
print(f"hs[0]: {gate.hs[0]}")
is_eq_constraint_satisfied(): True
is_tp(): True
hs[0]: [1. 0. 0. 0.]

The is_ineq_constraint_satisfied() function returns True, if and only if hs is CP(Complete-Positivity-Preserving), i.e. if and only if Choi matrix of hs is positive semidifinite matrix.

[7]:
print(f"is_ineq_constraint_satisfied(): {gate.is_ineq_constraint_satisfied()}")
print(f"is_cp(): {gate.is_cp()}")
is_ineq_constraint_satisfied(): True
is_cp(): True

projection functions

calc_proj_eq_constraint() function calculates the projection of Gate on equal constraint.
This function replaces the first row of hs with \([ 1, 0, \dots, 0 ]\).
[8]:
hs = np.array(range(16), dtype=np.float64).reshape((4, 4))
print(f"hs: \n{gate.hs}")
hs:
[[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0. -1.  0.]
 [ 0.  0.  0. -1.]]
[9]:
gate = Gate(c_sys, hs, is_physicality_required=False)
proj_gate = gate.calc_proj_eq_constraint()
print(f"hs: \n{proj_gate.hs}")
hs:
[[ 1.  0.  0.  0.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]
 [12. 13. 14. 15.]]

calc_proj_ineq_constraint() function calculates the projection of Gate with hs on inequal constraint as follows:

  • Let \(\text{Choi}\) be Choi matrix of hs

  • Executes singular value decomposition on \(\text{Choi}\), \(\text{Choi} = U \Lambda U^{\dagger}\), where \(\Lambda = \text{diag}[\lambda_0, \dots , \lambda_{d-1}]\), and \(\lambda_{i} \in \mathbb{R}\).

  • \(\lambda^{\prime}_{i} := \begin{cases} \lambda_{i} & (\lambda_{i} \geq 0) \\ 0 & (\lambda_{i} < 0) \end{cases}\)

  • \(\Lambda^{\prime} = \text{diag}[\lambda^{\prime}_0, \dots , \lambda^{\prime}_{d-1}]\)

  • \(\text{Choi}^{\prime} = U \Lambda^{\prime} U^{\dagger}\)

  • Let \(\text{HS}^{\prime}\) be Hilbert-Schmidt matrix representation of \(\text{Choi}^{\prime}\)

  • The projection of Gate is Gate with hs = \(\text{HS}^{\prime}\).

[10]:
gate = Gate(c_sys, hs, is_physicality_required=False)
proj_gate = gate.calc_proj_ineq_constraint()
print(f"hs: \n{proj_gate.hs}")
hs:
[[15.84558996  4.43570942  5.29265833  6.14960724]
 [ 2.63097854  2.34702553  3.08437192  3.44443746]
 [ 4.98440409  4.50900796  4.73510284  5.71575945]
 [ 7.33782964  6.29370952  7.14039548  7.60980059]]

functions to transform parameters

to_stacked_vector() function returns a one-dimensional numpy array of all variables. This is equal to flattened hs.

[11]:
print(f"to_stacked_vector(): {gate.to_stacked_vector()}")
print(f"lattened hs: {gate.hs.flatten()}")
to_stacked_vector(): [ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15.]
lattened hs: [ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15.]
If on_para_eq_constraint is True, then the first row of hs is equal to \([1, 0, \dots, 0]\). Thus, Gate is characterized by the second and subsequent rows of hs.
Therefore, to_var() function returns the flattened second and subsequent rows of hs, where on_para_eq_constraint is True.
[12]:
# on_para_eq_constraint=True
gate = Gate(c_sys, hs, is_physicality_required=False, on_para_eq_constraint=True)
print(f"to_var(): {gate.to_var()}")
to_var(): [ 4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15.]
[13]:
# on_para_eq_constraint=False
gate = Gate(c_sys, hs, is_physicality_required=False, on_para_eq_constraint=False)
print(f"to_var(): {gate.to_var()}")
to_var(): [ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9. 10. 11. 12. 13. 14. 15.]

functions to generate special objects

[14]:
zero_gate = gate.generate_zero_obj()
print(f"zero: \n{zero_gate.hs}")
origin_gate = gate.generate_origin_obj()
print(f"origin: \n{origin_gate.hs}")
zero:
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]
origin:
[[1. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

supports arithmetic operations

[15]:
hs1 = np.array(range(16), dtype=np.float64).reshape((4, 4))
gate1 = Gate(c_sys, hs1, is_physicality_required=False)
hs2 = np.array(range(16, 32), dtype=np.float64).reshape((4, 4))
gate2 = Gate(c_sys, hs2, is_physicality_required=False)

print(gate1.hs)
print(gate2.hs)
[[ 0.  1.  2.  3.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]
 [12. 13. 14. 15.]]
[[16. 17. 18. 19.]
 [20. 21. 22. 23.]
 [24. 25. 26. 27.]
 [28. 29. 30. 31.]]
[16]:
print(f"sum: \n{(gate1 + gate2).hs}")
print(f"subtraction: \n{(gate1 - gate2).hs}")
print(f"right multiplication: \n{(2 * gate1).hs}")
print(f"left multiplication: \n{(gate1 * 2).hs}")
print(f"division: \n{(gate1 / 2).hs}")
sum:
[[16. 18. 20. 22.]
 [24. 26. 28. 30.]
 [32. 34. 36. 38.]
 [40. 42. 44. 46.]]
subtraction:
[[-16. -16. -16. -16.]
 [-16. -16. -16. -16.]
 [-16. -16. -16. -16.]
 [-16. -16. -16. -16.]]
right multiplication:
[[ 0.  2.  4.  6.]
 [ 8. 10. 12. 14.]
 [16. 18. 20. 22.]
 [24. 26. 28. 30.]]
left multiplication:
[[ 0.  2.  4.  6.]
 [ 8. 10. 12. 14.]
 [16. 18. 20. 22.]
 [24. 26. 28. 30.]]
division:
[[0.  0.5 1.  1.5]
 [2.  2.5 3.  3.5]
 [4.  4.5 5.  5.5]
 [6.  6.5 7.  7.5]]

calc_gradient functions

Calculates gradient of Gate with variable index.

[17]:
grad_gate = gate.calc_gradient(0)
print(f"hs: \n{grad_gate.hs}")
hs:
[[1. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

convert_basis function

Returns hs converted to the specified basis.

[18]:
from quara.objects.matrix_basis import get_comp_basis

gate = generate_gate_from_gate_name("x", c_sys)
converted_hs = gate.convert_basis(get_comp_basis())
print(f"hs: \n{converted_hs}")
hs:
[[-2.23711432e-17+0.j  0.00000000e+00+0.j  0.00000000e+00+0.j  1.00000000e+00+0.j]
 [ 0.00000000e+00+0.j  0.00000000e+00+0.j  1.00000000e+00+0.j  0.00000000e+00+0.j]
 [ 0.00000000e+00+0.j  1.00000000e+00+0.j  0.00000000e+00+0.j  0.00000000e+00+0.j]
 [ 1.00000000e+00+0.j  0.00000000e+00+0.j  0.00000000e+00+0.j -2.23711432e-17+0.j]]

to_choi_matrix

Returns Choi matrix of Gate.

[19]:
gate = generate_gate_from_gate_name("x", c_sys)
print(f"to_choi_matrix(): \n{gate.to_choi_matrix()}")
print(f"to_choi_matrix_with_dict(): \n{gate.to_choi_matrix_with_dict()}")
print(f"to_choi_matrix_with_sparsity(): \n{gate.to_choi_matrix_with_sparsity()}")
to_choi_matrix():
[[0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]
to_choi_matrix_with_dict():
[[0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]
to_choi_matrix_with_sparsity():
[[0.+0.j 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 1.+0.j 1.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]

to_kraus_matrices

Returns Kraus matrices of Gate.

[20]:
gate = generate_gate_from_gate_name("x", c_sys)
print(f"to_kraus_matrices(): \n{gate.to_kraus_matrices()}")
to_kraus_matrices():
[array([[0.+0.j, 1.+0.j],
       [1.+0.j, 0.+0.j]])]

to_process_matrix

Returns process matrix of Gate.

[21]:
gate = generate_gate_from_gate_name("x", c_sys)
print(f"to_process_matrix(): \n{gate.to_process_matrix()}")
to_process_matrix():
[[-2.23711432e-17+0.j  0.00000000e+00+0.j  0.00000000e+00+0.j  0.00000000e+00+0.j]
 [ 0.00000000e+00+0.j  1.00000000e+00+0.j  1.00000000e+00+0.j  0.00000000e+00+0.j]
 [ 0.00000000e+00+0.j  1.00000000e+00+0.j  1.00000000e+00+0.j  0.00000000e+00+0.j]
 [ 0.00000000e+00+0.j  0.00000000e+00+0.j  0.00000000e+00+0.j -2.23711432e-17+0.j]]

some utility functions

[22]:
print(f"is_tp(): {gate.is_tp()}")
print(f"is_cp(): {gate.is_cp()}")
is_tp(): True
is_cp(): True

gate_typical

generate_gate_object_from_gate_name_object_name() function in gate_typical module can easily generate objects related to Gate.
The generate_gate_object_from_gate_name_object_name() function has the following arguments:
  • The string that can be specified for gate_name can be checked by executing the get_gate_names() function. The tensor product of state_name “a”, “b” is written “a_b”.

  • object_name can be the following string:

  • “unitary_mat” - unitary matrix of the gate.

  • “gate_mat” - The Hilbert-Schmidt matrix representation of the gate.

  • “gate” - Gate object.

  • c_sys - CompositeSystem of objects related to Gate. Specify when object_name is “gate”.

  • is_physicality_required - Whether the generated object is physicality required, by default True.

[23]:
from quara.objects.gate_typical import (
    get_gate_names,
    generate_gate_object_from_gate_name_object_name,
)

#get_gate_names()

object_name = “unitary_mat”

[24]:
mat = generate_gate_object_from_gate_name_object_name("x", "unitary_mat")
print(mat)
[[0.+0.j 1.+0.j]
 [1.+0.j 0.+0.j]]

object_name = “gate_mat”

[25]:
mat = generate_gate_object_from_gate_name_object_name("x", "gate_mat")
print(mat)
[[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0. -1.  0.]
 [ 0.  0.  0. -1.]]

object_name = “gate”

[26]:
c_sys = generate_composite_system("qubit", 1)
gate = generate_gate_object_from_gate_name_object_name("x", "gate", c_sys=c_sys)
print(gate)
Type:
Gate

Dim:
2

HS:
[[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0. -1.  0.]
 [ 0.  0.  0. -1.]]

Download Notebook