@@ -7,61 +7,112 @@ class to define your observable, and a :class:`ExpectationMeasure` to perform
7
7
8
8
import copy
9
9
from numbers import Complex
10
+ from typing import Optional , TYPE_CHECKING
10
11
from warnings import warn
11
- from typing import Optional
12
12
13
13
import numpy as np
14
- import numpy .typing as npt
15
14
from qiskit .circuit import Parameter
16
15
from sympy import Expr
17
16
from typeguard import typechecked
18
17
18
+ if TYPE_CHECKING :
19
+ from cirq .circuits .circuit import Circuit as Cirq_Circuit
20
+
19
21
from mpqp .core .instruction .gates .native_gates import SWAP
20
22
from mpqp .core .instruction .measurement .measure import Measure
23
+ from mpqp .core .instruction .measurement .pauli_string import PauliString
21
24
from mpqp .core .languages import Language
22
25
from mpqp .tools .errors import NumberQubitsError
26
+ from mpqp .tools .generics import Matrix , one_lined_repr
23
27
from mpqp .tools .maths import is_hermitian
24
- from mpqp .tools .generics import one_lined_repr
25
28
26
29
27
30
@typechecked
28
31
class Observable :
29
32
"""Class defining an observable, used for evaluating expectation values.
30
33
31
- An observable can be defined by using a hermitian matrix, or using a combination of operators in a specific
32
- basis (Kraus, Pauli, ...).
33
-
34
- For the moment, on can only define the observable using a matrix.
34
+ An observable can be defined by using a Hermitian matrix, or using a combination of operators in a specific
35
+ basis Pauli.
35
36
36
37
Example:
37
38
>>> matrix = np.array([[1, 0], [0, -1]])
39
+ >>> pauli_string = 3 * I @ Z + 4 * X @ Y
38
40
>>> obs = Observable(matrix)
41
+ >>> obs2 = Observable(pauli_string)
39
42
40
43
Args:
41
- matrix: Hermitian matrix representing the observable.
44
+ observable : can be either a Hermitian matrix representing the observable or PauliString representing the observable.
45
+
46
+ Raises:
47
+ ValueError: If the input matrix is not Hermitian or does not have a square shape.
48
+ NumberQubitsError: If the number of qubits in the input observable does
49
+ not match the number of target qubits.
50
+
42
51
"""
43
52
44
- def __init__ (self , matrix : npt .NDArray [np .complex64 ] | list [list [Complex ]]):
45
- self .nb_qubits = int (np .log2 (len (matrix )))
46
- """Number of qubits of this observable."""
47
- self .matrix = np .array (matrix )
48
- """See parameter description."""
53
+ def __init__ (self , observable : Matrix | PauliString ):
54
+ self ._matrix = None
55
+ self ._pauli_string = None
49
56
50
- basis_states = 2 ** self .nb_qubits
51
- if self .matrix .shape != (basis_states , basis_states ):
52
- raise ValueError (
53
- f"The size of the matrix { self .matrix .shape } doesn't neatly fit on a"
54
- " quantum register. It should be a square matrix of size a power"
55
- " of two."
56
- )
57
+ if isinstance (observable , PauliString ):
58
+ self .nb_qubits = observable .nb_qubits
59
+ self ._pauli_string = observable .simplify ()
60
+ else :
61
+ self .nb_qubits = int (np .log2 (len (observable )))
62
+ """Number of qubits of this observable."""
63
+ self ._matrix = np .array (observable )
64
+
65
+ basis_states = 2 ** self .nb_qubits
66
+ if self .matrix .shape != (basis_states , basis_states ):
67
+ raise ValueError (
68
+ f"The size of the matrix { self .matrix .shape } doesn't neatly fit on a"
69
+ " quantum register. It should be a square matrix of size a power"
70
+ " of two."
71
+ )
57
72
58
- if not is_hermitian (self .matrix ):
59
- raise ValueError (
60
- "The matrix in parameter is not hermitian (cannot define an observable)"
61
- )
73
+ if not is_hermitian (self .matrix ):
74
+ raise ValueError (
75
+ "The matrix in parameter is not hermitian (cannot define an observable)"
76
+ )
77
+
78
+ @property
79
+ def matrix (self ) -> Matrix :
80
+ """
81
+ Returns the matrix representation of the observable.
82
+
83
+ Returns:
84
+ np.ndarray: The matrix representation of the observable.
85
+ """
86
+ if self ._matrix is None :
87
+ self ._matrix = self .pauli_string .to_matrix ()
88
+ matrix = copy .deepcopy (self ._matrix ).astype (np .complex64 )
89
+ return matrix
90
+
91
+ @property
92
+ def pauli_string (self ) -> PauliString :
93
+ """
94
+ Returns the PauliString representation of the observable.
95
+
96
+ Returns:
97
+ PauliString: The PauliString representation of the observable.
98
+ """
99
+ if self ._pauli_string is None :
100
+ self ._pauli_string = PauliString .from_matrix (self .matrix )
101
+ pauli_string = copy .deepcopy (self ._pauli_string )
102
+ return pauli_string
103
+
104
+ @pauli_string .setter
105
+ def pauli_string (self , pauli_string : PauliString ):
106
+ self ._pauli_string = pauli_string
107
+ self ._matrix = None
108
+
109
+ @matrix .setter
110
+ def matrix (self , matrix : Matrix ):
111
+ self ._matrix = matrix
112
+ self ._pauli_string = None
62
113
63
114
def __repr__ (self ) -> str :
64
- return f"Observable ({ one_lined_repr (self .matrix )} )"
115
+ return f"{ type ( self ). __name__ } ({ one_lined_repr (self .matrix )} )"
65
116
66
117
def __mult__ (self , other : Expr | Complex ) -> Observable :
67
118
"""3M-TODO"""
@@ -73,6 +124,70 @@ def subs(
73
124
"""3M-TODO"""
74
125
...
75
126
127
+ def to_other_language (
128
+ self , language : Language , circuit : Optional [Cirq_Circuit ] = None
129
+ ):
130
+ """
131
+ Converts the observable to the representation of another quantum programming language.
132
+
133
+ Example:
134
+ >>> obs = Observable(np.diag([0.7, -1, 1, 1]))
135
+ >>> obs_qiskit = obs.to_other_language(Language.QISKIT)
136
+ >>> print(obs_qiskit)
137
+ <bound method Observable.to_qiskit_observable of Observable(array([[ 0.7, 0. , 0. , 0. ], [ 0. , -1. , 0. , 0. ], [ 0. , 0. , 1. , 0. ], [ 0. , 0. , 0. , 1. ]]))>
138
+
139
+ Args:
140
+ language (str): The target programming language ('qiskit', 'pyquil', 'braket', 'cirq').
141
+ circuit: The Cirq circuit associated with the observable (required for 'cirq' language).
142
+
143
+ Returns:
144
+ Depends on the target language.
145
+ """
146
+ if language == Language .QISKIT :
147
+ from qiskit .quantum_info import Operator
148
+
149
+ return Operator (self .matrix )
150
+ elif language == Language .MY_QLM :
151
+ from qat .core .wrappers .observable import Observable as QLM_Observable
152
+
153
+ return QLM_Observable (self .nb_qubits , matrix = self .matrix )
154
+ elif language == Language .BRAKET :
155
+ from braket .circuits .observables import Hermitian
156
+
157
+ return Hermitian (self .matrix )
158
+ elif language == Language .CIRQ :
159
+ if circuit is None :
160
+ raise ValueError ("Circuit must be specified for cirq_observable." )
161
+ from cirq .ops .identity import I as Cirq_I
162
+ from cirq .ops .pauli_gates import X as Cirq_X , Y as Cirq_Y , Z as Cirq_Z
163
+
164
+ all_qubits = set (
165
+ q for moment in circuit for op in moment .operations for q in op .qubits
166
+ )
167
+ all_qubits_list = sorted (all_qubits )
168
+
169
+ cirq_pauli_string = None
170
+ pauli_gate_map = {"I" : Cirq_I , "X" : Cirq_X , "Y" : Cirq_Y , "Z" : Cirq_Z }
171
+ for monomial in self .pauli_string .monomials :
172
+ cirq_monomial = None
173
+ for index , atom in enumerate (monomial .atoms ):
174
+ cirq_atom = pauli_gate_map [atom .label ](all_qubits_list [index ])
175
+ cirq_monomial = (
176
+ cirq_atom
177
+ if cirq_monomial is None
178
+ else cirq_monomial * cirq_atom
179
+ )
180
+ cirq_monomial *= monomial .coef
181
+ cirq_pauli_string = (
182
+ cirq_monomial
183
+ if cirq_pauli_string is None
184
+ else cirq_pauli_string + cirq_monomial
185
+ )
186
+
187
+ return cirq_pauli_string
188
+ else :
189
+ raise ValueError (f"Unsupported language: { language } " )
190
+
76
191
77
192
@typechecked
78
193
class ExpectationMeasure (Measure ):
@@ -120,6 +235,7 @@ def __init__(
120
235
super ().__init__ (targets , shots , label )
121
236
self .observable = observable
122
237
"""See parameter description."""
238
+ # Raise an error if the number of target qubits does not match the size of the observable.
123
239
if self .nb_qubits != observable .nb_qubits :
124
240
raise NumberQubitsError (
125
241
f"{ self .nb_qubits } , the number of target qubit(s) doesn't match"
@@ -172,6 +288,8 @@ def to_other_language(
172
288
) -> None :
173
289
if qiskit_parameters is None :
174
290
qiskit_parameters = set ()
291
+ #TODO : incoherence here, if the language is Qiskit we raise a NotImplementedError, and otherwise we say that
292
+ # only qiskit is supported
175
293
if language == Language .QISKIT :
176
294
raise NotImplementedError (
177
295
"Qiskit does not implement these kind of measures"
0 commit comments