Skip to content

Classes

This part is the classes of the Python-Julia package which written in Python.

Dynamics

Class for simulating quantum dynamics governed by the Lindblad master equation.

The dynamics of a density matrix is described by the Lindblad master equation:

\[\begin{aligned} \partial_t\rho &=\mathcal{L}\rho \nonumber \\ &=-i[H,\rho]+\sum_i \gamma_i(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2} \{\rho,\Gamma^{\dagger}_i \Gamma_i \}), \end{aligned}\]
Symbols
  • \(\rho\): the evolved density matrix
  • \(H\): the Hamiltonian of the system
  • \(\Gamma_i\): the \(i\)th decay operator
  • \(\gamma_i\): the \(i\)th decay rate

Attributes:

Name Type Description
tspan array

Time points for the evolution.

rho0 array

Initial state (density matrix).

H0 array / list

Free Hamiltonian. It is a matrix when time-independent, or a list of matrices (with length equal to tspan) when time-dependent.

dH list

Derivatives of the free Hamiltonian with respect to the unknown parameters.
Each element is a matrix representing the partial derivative with respect to one parameter. For example, dH[0] is the derivative with respect to the first parameter.

decay list

Decay operators and corresponding decay rates. Input format:
decay=[[Γ₁, γ₁], [Γ₂, γ₂], ...], where Γ₁, Γ₂ are decay operators and γ₁, γ₂ are the corresponding decay rates.

Hc list

Control Hamiltonians.

ctrl list

Control coefficients for each control Hamiltonian.

Source code in quanestimation/base/Parameterization/GeneralDynamics.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
class Lindblad:
    r"""
    Class for simulating quantum dynamics governed by the Lindblad master equation.

    The dynamics of a density matrix is described by the Lindblad master equation:

    \begin{aligned}
        \partial_t\rho &=\mathcal{L}\rho \nonumber \\
        &=-i[H,\rho]+\sum_i \gamma_i(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
        \{\rho,\Gamma^{\dagger}_i \Gamma_i \}),
    \end{aligned}

    Symbols: 
        - $\rho$: the evolved density matrix
        - $H$: the Hamiltonian of the system
        - $\Gamma_i$: the $i$th decay operator
        - $\gamma_i$: the $i$th decay rate

    Attributes:
        tspan (np.array): 
            Time points for the evolution.
        rho0 (np.array): 
            Initial state (density matrix).
        H0 (np.array/list): 
            Free Hamiltonian. It is a matrix when time-independent, or a list of matrices 
            (with length equal to `tspan`) when time-dependent.
        dH (list): 
            Derivatives of the free Hamiltonian with respect to the unknown parameters.  
            Each element is a matrix representing the partial derivative with respect to 
            one parameter. For example, `dH[0]` is the derivative with respect to the 
            first parameter.
        decay (list): 
            Decay operators and corresponding decay rates. Input format:  
            `decay=[[Γ₁, γ₁], [Γ₂, γ₂], ...]`, where Γ₁, Γ₂ are decay operators and γ₁, γ₂ 
            are the corresponding decay rates.
        Hc (list): 
            Control Hamiltonians.
        ctrl (list, optional): 
            Control coefficients for each control Hamiltonian.
    """

    def __init__(self, tspan, rho0, H0, dH, decay=None, Hc=None, ctrl=None):
        r"""
        Initialize Lindblad dynamics.

        Parameters
        ----------
        tspan : np.array
            Time points for the evolution.
        rho0 : np.array
            Initial state (density matrix).
        H0 : np.array or list
            Free Hamiltonian. A matrix when time-independent, or a list of matrices
            when time-dependent.
        dH : list
            Derivatives of the free Hamiltonian with respect to parameters.
        decay : list, optional
            Decay operators and rates as ``[[Gamma_1, gamma_1], [Gamma_2, gamma_2], ...]``.
        Hc : list, optional
            Control Hamiltonians.
        ctrl : list, optional
            Control coefficients for each control Hamiltonian.
        """

        if decay is None:
            decay = []
        if Hc is None:
            Hc = []
        if ctrl is None:
            ctrl = []

        self.tspan = tspan
        self.rho0 = np.array(rho0, dtype=np.complex128)

        if isinstance(H0, np.ndarray):
            self.freeHamiltonian = np.array(H0, dtype=np.complex128)
        else:
            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0]

        if not isinstance(dH[0], np.ndarray):
            raise TypeError("The derivative of Hamiltonian should be a list!")

        if not dH:
            dH = [np.zeros((len(self.rho0), len(self.rho0)))]
        self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]

        if not decay:
            decay_opt = [np.zeros((len(self.rho0), len(self.rho0)))]
            self.gamma = [0.0]
        else:
            decay_opt = [decay[i][0] for i in range(len(decay))]
            self.gamma = [decay[i][1] for i in range(len(decay))]
        self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

        if not Hc:
            Hc = [np.zeros((len(self.rho0), len(self.rho0)))]
            ctrl = [np.zeros(len(self.tspan) - 1)]
            self.control_Hamiltonian = [np.array(x, dtype=np.complex128) for x in Hc]
            self.control_coefficients = ctrl
        elif not ctrl:
            ctrl = [np.zeros(len(self.tspan) - 1) for j in range(len(Hc))]
            self.control_Hamiltonian = Hc
            self.control_coefficients = ctrl
        else:
            ctrl_length = len(ctrl)
            ctrlnum = len(Hc)
            if ctrlnum < ctrl_length:
                raise TypeError(
                    "There are %d control Hamiltonians but %d coefficients sequences: \
                                too many coefficients sequences"
                    % (ctrlnum, ctrl_length)
                )
            elif ctrlnum > ctrl_length:
                warnings.warn(
                    "Not enough coefficients sequences: there are %d control Hamiltonians \
                            but %d coefficients sequences. The rest of the control sequences are\
                            set to be 0."
                    % (ctrlnum, ctrl_length),
                    DeprecationWarning,
                )

            number = math.ceil((len(self.tspan) - 1) / len(ctrl[0]))
            if len(self.tspan) - 1 % len(ctrl[0]) != 0:
                tnum = number * len(ctrl[0])
                self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
            self.control_Hamiltonian = Hc
            self.control_coefficients = ctrl

    def expm(self):
        r"""
        Calculate the density matrix and its derivatives using the matrix exponential method.

        The density matrix at the $j$th time interval is obtained by:

        $$
            \rho_j = e^{\Delta t \mathcal{L}} \rho_{j-1}
        $$

        where $\Delta t$ is the time interval and $\rho_{j-1}$ is the density matrix 
        at the previous time step.

        The derivative $\partial_{\textbf{x}}\rho_j$ is calculated as:

        $$
            \partial_{\textbf{x}}\rho_j = \Delta t (\partial_{\textbf{x}}\mathcal{L}) \rho_j
            + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1})
        $$

        Returns: 
            (tuple):
                rho (list): 
                    Density matrices at each time point in `tspan`.

                drho (list): 
                    Derivatives of the density matrices with respect to the unknown parameters.  
                    `drho[i][j]` is the derivative of the density matrix at the i-th time point 
                    with respect to the j-th parameter.
        """

        rho, drho = QJL.expm_py(
            self.tspan,
            self.rho0,
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.decay_opt,
            self.gamma,
            self.control_Hamiltonian,
            self.control_coefficients,
        )

        rho = _unwrap(rho)
        drho = _unwrap(drho)

        return rho, drho

    def ode(self):
        r"""
        Calculate the density matrix and its derivatives using an ODE solver.

        The density matrix at the $j$th time interval is obtained by:

        $$
            \rho_j = e^{\Delta t \mathcal{L}} \rho_{j-1},
        $$

        where $\Delta t$ is the time interval and $\rho_{j-1}$ is the density matrix 
        at the previous time step.

        The derivative $\partial_{\textbf{x}}\rho_j$ is calculated as:

        $$
            \partial_{\textbf{x}}\rho_j = \Delta t (\partial_{\textbf{x}}\mathcal{L}) \rho_j
            + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1}).
        $$

        Returns:
            (tuple):
                rho (list): 
                    Density matrices at each time point in `tspan`.

                drho (list): 
                    Derivatives of the density matrices with respect to the unknown parameters.  
                    `drho[i][j]` is the derivative of the density matrix at the i-th time point 
                    with respect to the j-th parameter.
        """

        rho, drho = QJL.ode_py(
            self.tspan,
            self.rho0,
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.decay_opt,
            self.gamma,
            self.control_Hamiltonian,
            self.control_coefficients,
        )

        rho = _unwrap(rho)
        drho = _unwrap(drho)

        return rho, drho

    def secondorder_derivative(self, d2H):
        r"""
        Calculate the density matrix, its first derivatives, and second derivatives 
        with respect to the unknown parameters.

        The density matrix at the $j$th time interval is obtained by:

        $$
            \rho_j = e^{\Delta t \mathcal{L}} \rho_{j-1}.
        $$

        The first derivative $\partial_{\textbf{x}}\rho_j$ is calculated as:

        $$
            \partial_{\textbf{x}}\rho_j = \Delta t (\partial_{\textbf{x}}\mathcal{L}) \rho_j
            + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1}).
        $$

        The second derivative $\partial_{\textbf{x}}^2\rho_j$ is calculated as:

        \begin{aligned}
            \partial_{\textbf{x}}^2\rho_j =& \Delta t (\partial_{\textbf{x}}^2\mathcal{L}) \rho_j 
            + \Delta t (\partial_{\textbf{x}}\mathcal{L}) \partial_{\textbf{x}}\rho_j \\
            &+ \Delta t (\partial_{\textbf{x}}\mathcal{L}) e^{\Delta t \mathcal{L}} \partial_{\textbf{x}}\rho_{j-1} 
            + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}^2\rho_{j-1}).
        \end{aligned}

        Args:
            d2H (list): Second-order derivatives of the free Hamiltonian with respect to the unknown parameters.  
                Each element is a matrix representing the second partial derivative with respect to 
                two parameters. For example, `d2H[0]` might be the second derivative with respect to 
                the first parameter twice, or a mixed partial derivative.

        Returns:
            (tuple):
                rho (list): 
                    Density matrices at each time point in `tspan`.

                drho (list): 
                    First derivatives of the density matrices with respect to the unknown parameters.

                d2rho (list): 
                    Second derivatives of the density matrices with respect to the unknown parameters.
        """

        d2H = [np.array(x, dtype=np.complex128) for x in d2H]
        rho, drho, d2rho = QJL.secondorder_derivative(
            self.tspan,
            self.rho0,
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            d2H,
            self.decay_opt,
            self.gamma,
            self.control_Hamiltonian,
            self.control_coefficients,
        )
        rho = _unwrap(rho)
        drho = _unwrap(drho)

        return rho, drho, d2rho

__init__(tspan, rho0, H0, dH, decay=None, Hc=None, ctrl=None)

Initialize Lindblad dynamics.

Parameters

tspan : np.array Time points for the evolution. rho0 : np.array Initial state (density matrix). H0 : np.array or list Free Hamiltonian. A matrix when time-independent, or a list of matrices when time-dependent. dH : list Derivatives of the free Hamiltonian with respect to parameters. decay : list, optional Decay operators and rates as [[Gamma_1, gamma_1], [Gamma_2, gamma_2], ...]. Hc : list, optional Control Hamiltonians. ctrl : list, optional Control coefficients for each control Hamiltonian.

Source code in quanestimation/base/Parameterization/GeneralDynamics.py
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def __init__(self, tspan, rho0, H0, dH, decay=None, Hc=None, ctrl=None):
    r"""
    Initialize Lindblad dynamics.

    Parameters
    ----------
    tspan : np.array
        Time points for the evolution.
    rho0 : np.array
        Initial state (density matrix).
    H0 : np.array or list
        Free Hamiltonian. A matrix when time-independent, or a list of matrices
        when time-dependent.
    dH : list
        Derivatives of the free Hamiltonian with respect to parameters.
    decay : list, optional
        Decay operators and rates as ``[[Gamma_1, gamma_1], [Gamma_2, gamma_2], ...]``.
    Hc : list, optional
        Control Hamiltonians.
    ctrl : list, optional
        Control coefficients for each control Hamiltonian.
    """

    if decay is None:
        decay = []
    if Hc is None:
        Hc = []
    if ctrl is None:
        ctrl = []

    self.tspan = tspan
    self.rho0 = np.array(rho0, dtype=np.complex128)

    if isinstance(H0, np.ndarray):
        self.freeHamiltonian = np.array(H0, dtype=np.complex128)
    else:
        self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0]

    if not isinstance(dH[0], np.ndarray):
        raise TypeError("The derivative of Hamiltonian should be a list!")

    if not dH:
        dH = [np.zeros((len(self.rho0), len(self.rho0)))]
    self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]

    if not decay:
        decay_opt = [np.zeros((len(self.rho0), len(self.rho0)))]
        self.gamma = [0.0]
    else:
        decay_opt = [decay[i][0] for i in range(len(decay))]
        self.gamma = [decay[i][1] for i in range(len(decay))]
    self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

    if not Hc:
        Hc = [np.zeros((len(self.rho0), len(self.rho0)))]
        ctrl = [np.zeros(len(self.tspan) - 1)]
        self.control_Hamiltonian = [np.array(x, dtype=np.complex128) for x in Hc]
        self.control_coefficients = ctrl
    elif not ctrl:
        ctrl = [np.zeros(len(self.tspan) - 1) for j in range(len(Hc))]
        self.control_Hamiltonian = Hc
        self.control_coefficients = ctrl
    else:
        ctrl_length = len(ctrl)
        ctrlnum = len(Hc)
        if ctrlnum < ctrl_length:
            raise TypeError(
                "There are %d control Hamiltonians but %d coefficients sequences: \
                            too many coefficients sequences"
                % (ctrlnum, ctrl_length)
            )
        elif ctrlnum > ctrl_length:
            warnings.warn(
                "Not enough coefficients sequences: there are %d control Hamiltonians \
                        but %d coefficients sequences. The rest of the control sequences are\
                        set to be 0."
                % (ctrlnum, ctrl_length),
                DeprecationWarning,
            )

        number = math.ceil((len(self.tspan) - 1) / len(ctrl[0]))
        if len(self.tspan) - 1 % len(ctrl[0]) != 0:
            tnum = number * len(ctrl[0])
            self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
        self.control_Hamiltonian = Hc
        self.control_coefficients = ctrl

expm()

Calculate the density matrix and its derivatives using the matrix exponential method.

The density matrix at the \(j\)th time interval is obtained by:

\[ \rho_j = e^{\Delta t \mathcal{L}} \rho_{j-1} \]

where \(\Delta t\) is the time interval and \(\rho_{j-1}\) is the density matrix at the previous time step.

The derivative \(\partial_{\textbf{x}}\rho_j\) is calculated as:

\[ \partial_{\textbf{x}}\rho_j = \Delta t (\partial_{\textbf{x}}\mathcal{L}) \rho_j + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1}) \]

Returns:

Type Description
tuple

rho (list): Density matrices at each time point in tspan.

drho (list): Derivatives of the density matrices with respect to the unknown parameters.
drho[i][j] is the derivative of the density matrix at the i-th time point with respect to the j-th parameter.

Source code in quanestimation/base/Parameterization/GeneralDynamics.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def expm(self):
    r"""
    Calculate the density matrix and its derivatives using the matrix exponential method.

    The density matrix at the $j$th time interval is obtained by:

    $$
        \rho_j = e^{\Delta t \mathcal{L}} \rho_{j-1}
    $$

    where $\Delta t$ is the time interval and $\rho_{j-1}$ is the density matrix 
    at the previous time step.

    The derivative $\partial_{\textbf{x}}\rho_j$ is calculated as:

    $$
        \partial_{\textbf{x}}\rho_j = \Delta t (\partial_{\textbf{x}}\mathcal{L}) \rho_j
        + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1})
    $$

    Returns: 
        (tuple):
            rho (list): 
                Density matrices at each time point in `tspan`.

            drho (list): 
                Derivatives of the density matrices with respect to the unknown parameters.  
                `drho[i][j]` is the derivative of the density matrix at the i-th time point 
                with respect to the j-th parameter.
    """

    rho, drho = QJL.expm_py(
        self.tspan,
        self.rho0,
        self.freeHamiltonian,
        self.Hamiltonian_derivative,
        self.decay_opt,
        self.gamma,
        self.control_Hamiltonian,
        self.control_coefficients,
    )

    rho = _unwrap(rho)
    drho = _unwrap(drho)

    return rho, drho

ode()

Calculate the density matrix and its derivatives using an ODE solver.

The density matrix at the \(j\)th time interval is obtained by:

\[ \rho_j = e^{\Delta t \mathcal{L}} \rho_{j-1}, \]

where \(\Delta t\) is the time interval and \(\rho_{j-1}\) is the density matrix at the previous time step.

The derivative \(\partial_{\textbf{x}}\rho_j\) is calculated as:

\[ \partial_{\textbf{x}}\rho_j = \Delta t (\partial_{\textbf{x}}\mathcal{L}) \rho_j + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1}). \]

Returns:

Type Description
tuple

rho (list): Density matrices at each time point in tspan.

drho (list): Derivatives of the density matrices with respect to the unknown parameters.
drho[i][j] is the derivative of the density matrix at the i-th time point with respect to the j-th parameter.

Source code in quanestimation/base/Parameterization/GeneralDynamics.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
def ode(self):
    r"""
    Calculate the density matrix and its derivatives using an ODE solver.

    The density matrix at the $j$th time interval is obtained by:

    $$
        \rho_j = e^{\Delta t \mathcal{L}} \rho_{j-1},
    $$

    where $\Delta t$ is the time interval and $\rho_{j-1}$ is the density matrix 
    at the previous time step.

    The derivative $\partial_{\textbf{x}}\rho_j$ is calculated as:

    $$
        \partial_{\textbf{x}}\rho_j = \Delta t (\partial_{\textbf{x}}\mathcal{L}) \rho_j
        + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1}).
    $$

    Returns:
        (tuple):
            rho (list): 
                Density matrices at each time point in `tspan`.

            drho (list): 
                Derivatives of the density matrices with respect to the unknown parameters.  
                `drho[i][j]` is the derivative of the density matrix at the i-th time point 
                with respect to the j-th parameter.
    """

    rho, drho = QJL.ode_py(
        self.tspan,
        self.rho0,
        self.freeHamiltonian,
        self.Hamiltonian_derivative,
        self.decay_opt,
        self.gamma,
        self.control_Hamiltonian,
        self.control_coefficients,
    )

    rho = _unwrap(rho)
    drho = _unwrap(drho)

    return rho, drho

secondorder_derivative(d2H)

Calculate the density matrix, its first derivatives, and second derivatives with respect to the unknown parameters.

The density matrix at the \(j\)th time interval is obtained by:

\[ \rho_j = e^{\Delta t \mathcal{L}} \rho_{j-1}. \]

The first derivative \(\partial_{\textbf{x}}\rho_j\) is calculated as:

\[ \partial_{\textbf{x}}\rho_j = \Delta t (\partial_{\textbf{x}}\mathcal{L}) \rho_j + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1}). \]

The second derivative \(\partial_{\textbf{x}}^2\rho_j\) is calculated as:

\[\begin{aligned} \partial_{\textbf{x}}^2\rho_j =& \Delta t (\partial_{\textbf{x}}^2\mathcal{L}) \rho_j + \Delta t (\partial_{\textbf{x}}\mathcal{L}) \partial_{\textbf{x}}\rho_j \\ &+ \Delta t (\partial_{\textbf{x}}\mathcal{L}) e^{\Delta t \mathcal{L}} \partial_{\textbf{x}}\rho_{j-1} + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}^2\rho_{j-1}). \end{aligned}\]

Parameters:

Name Type Description Default
d2H list

Second-order derivatives of the free Hamiltonian with respect to the unknown parameters.
Each element is a matrix representing the second partial derivative with respect to two parameters. For example, d2H[0] might be the second derivative with respect to the first parameter twice, or a mixed partial derivative.

required

Returns:

Type Description
tuple

rho (list): Density matrices at each time point in tspan.

drho (list): First derivatives of the density matrices with respect to the unknown parameters.

d2rho (list): Second derivatives of the density matrices with respect to the unknown parameters.

Source code in quanestimation/base/Parameterization/GeneralDynamics.py
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
def secondorder_derivative(self, d2H):
    r"""
    Calculate the density matrix, its first derivatives, and second derivatives 
    with respect to the unknown parameters.

    The density matrix at the $j$th time interval is obtained by:

    $$
        \rho_j = e^{\Delta t \mathcal{L}} \rho_{j-1}.
    $$

    The first derivative $\partial_{\textbf{x}}\rho_j$ is calculated as:

    $$
        \partial_{\textbf{x}}\rho_j = \Delta t (\partial_{\textbf{x}}\mathcal{L}) \rho_j
        + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1}).
    $$

    The second derivative $\partial_{\textbf{x}}^2\rho_j$ is calculated as:

    \begin{aligned}
        \partial_{\textbf{x}}^2\rho_j =& \Delta t (\partial_{\textbf{x}}^2\mathcal{L}) \rho_j 
        + \Delta t (\partial_{\textbf{x}}\mathcal{L}) \partial_{\textbf{x}}\rho_j \\
        &+ \Delta t (\partial_{\textbf{x}}\mathcal{L}) e^{\Delta t \mathcal{L}} \partial_{\textbf{x}}\rho_{j-1} 
        + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}^2\rho_{j-1}).
    \end{aligned}

    Args:
        d2H (list): Second-order derivatives of the free Hamiltonian with respect to the unknown parameters.  
            Each element is a matrix representing the second partial derivative with respect to 
            two parameters. For example, `d2H[0]` might be the second derivative with respect to 
            the first parameter twice, or a mixed partial derivative.

    Returns:
        (tuple):
            rho (list): 
                Density matrices at each time point in `tspan`.

            drho (list): 
                First derivatives of the density matrices with respect to the unknown parameters.

            d2rho (list): 
                Second derivatives of the density matrices with respect to the unknown parameters.
    """

    d2H = [np.array(x, dtype=np.complex128) for x in d2H]
    rho, drho, d2rho = QJL.secondorder_derivative(
        self.tspan,
        self.rho0,
        self.freeHamiltonian,
        self.Hamiltonian_derivative,
        d2H,
        self.decay_opt,
        self.gamma,
        self.control_Hamiltonian,
        self.control_coefficients,
    )
    rho = _unwrap(rho)
    drho = _unwrap(drho)

    return rho, drho, d2rho

Parameterization of a quantum state using Kraus operators.

The evolved density matrix \(\rho\) is given by

\[\begin{aligned} \rho=\sum_i K_i \rho_0 K_i^{\dagger}, \end{aligned}\]

where \(\rho_0\) is the initial density matrix and \(K_i\) are the Kraus operators.

Parameters:

Name Type Description Default
rho0 array

Initial density matrix.

required
K list

Kraus operators.

required
dK list

Derivatives of the Kraus operators with respect to the unknown parameters to be estimated. This is a nested list where the first index corresponds to the Kraus operator and the second index corresponds to the parameter. For example, dK[0][1] is the derivative of the second Kraus operator with respect to the first parameter.

required

Returns:

Type Description
tuple

rho (np.array): Evolved density matrix.

drho (list): Derivatives of the evolved density matrix with respect to the unknown parameters.
Each element in the list is a matrix representing the partial derivative of \(\rho\) with respect to one parameter.

Source code in quanestimation/base/Parameterization/NonDynamics.py
 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def Kraus(rho0, K, dK):
    r"""
    Parameterization of a quantum state using Kraus operators.

    The evolved density matrix $\rho$ is given by

    \begin{aligned}
        \rho=\sum_i K_i \rho_0 K_i^{\dagger},
    \end{aligned}

    where $\rho_0$ is the initial density matrix and $K_i$ are the Kraus operators.

    Args: 
        rho0 (np.array): 
            Initial density matrix.
        K (list): 
            Kraus operators.
        dK (list): 
            Derivatives of the Kraus operators with respect to the unknown parameters to be 
            estimated. This is a nested list where the first index corresponds to the Kraus operator 
            and the second index corresponds to the parameter. For example, `dK[0][1]` is the derivative 
            of the second Kraus operator with respect to the first parameter.

    Returns:
        (tuple):
            rho (np.array): 
                Evolved density matrix.

            drho (list): 
                Derivatives of the evolved density matrix with respect to the unknown parameters.  
                Each element in the list is a matrix representing the partial derivative of $\rho$ with 
                respect to one parameter.
    """

    k_num = len(K)
    para_num = len(dK[0])
    dK_reshape = [[dK[i][j] for i in range(k_num)] for j in range(para_num)]

    rho = sum([np.dot(Ki, np.dot(rho0, Ki.conj().T)) for Ki in K])
    drho = [sum([(np.dot(dKi, np.dot(rho0, Ki.conj().T))+ np.dot(Ki, np.dot(rho0, dKi.conj().T))) for (Ki, dKi) in zip(K, dKj)]) for dKj in dK_reshape]

    return rho, drho

Construct Lindblad dynamics for a single qubit under dephasing.

Hamiltonian H = rx·σx + ry·σy + rz·σz, with dephasing channel Γ = σz at rate γ. Delegates to Julia's QubitDephasing(r, para_est, gamma, tspan).

Parameters:

Name Type Description Default
r

Bloch vector components [rx, ry, rz].

required
para_est

Parameter to estimate, "x", "y", or "z".

required
gamma

Dephasing rate.

required
tspan

Time span for evolution.

required

Returns:

Type Description

Lindblad dynamics object (Julia Lindblad).

Source code in quanestimation/base/Parameterization/GeneralDynamics.py
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
def QubitDephasing(r, para_est, gamma, tspan):
    """Construct Lindblad dynamics for a single qubit under dephasing.

    Hamiltonian H = rx·σx + ry·σy + rz·σz, with dephasing
    channel Γ = σz at rate γ. Delegates to Julia's
    ``QubitDephasing(r, para_est, gamma, tspan)``.

    Args:
        r: Bloch vector components [rx, ry, rz].
        para_est: Parameter to estimate, ``"x"``, ``"y"``, or ``"z"``.
        gamma: Dephasing rate.
        tspan: Time span for evolution.

    Returns:
        Lindblad dynamics object (Julia Lindblad).
    """
    import numpy as np
    from juliacall import Main as jl, convert as jlconvert
    from quanestimation.base import QJL

    r_arr = np.asarray(r, dtype=np.float64)
    r_jl = jlconvert(jl.Vector[jl.Float64], list(r_arr))
    tspan_arr = np.asarray(tspan, dtype=np.float64)
    tspan_jl = jlconvert(jl.Vector[jl.Float64], list(tspan_arr))
    return QJL.QubitDephasing(r_jl, para_est, gamma, tspan_jl)

Control Waveforms

Predefined control coefficient functions for constructing controlled Hamiltonians. Each returns a callable object that evaluates the waveform at time t.

Zero control: c(t) = 0.

Source code in quanestimation/base/Parameterization/ControlWaveform.py
24
25
26
def ZeroCTRL():
    """Zero control: ``c(t) = 0``."""
    return _get_jl().ZeroCTRL()

Linear-in-time control: c(t) = k·t + c0.

Source code in quanestimation/base/Parameterization/ControlWaveform.py
29
30
31
def LinearCTRL(k=1.0, c0=0.0):
    """Linear-in-time control: ``c(t) = k·t + c0``."""
    return _get_jl().LinearCTRL(k=k, c0=c0)

Sinusoidal control: c(t) = A·sin(ω·t + φ).

Parameters:

Name Type Description Default
A

Amplitude.

1.0
omega

Angular frequency ω.

1.0
phi

Phase offset φ.

0.0
Source code in quanestimation/base/Parameterization/ControlWaveform.py
34
35
36
37
38
39
40
41
42
def SineCTRL(A=1.0, omega=1.0, phi=0.0):
    """Sinusoidal control: ``c(t) = A·sin(ω·t + φ)``.

    Args:
        A: Amplitude.
        omega: Angular frequency ω.
        phi: Phase offset φ.
    """
    return _get_jl().SineCTRL(**{"A": A, "ω": omega, "ϕ": phi})

Sawtooth-wave control.

Source code in quanestimation/base/Parameterization/ControlWaveform.py
45
46
47
def SawCTRL(k=1.0, n=1.0):
    """Sawtooth-wave control."""
    return _get_jl().SawCTRL(k=k, n=n)

Triangle-wave control.

Source code in quanestimation/base/Parameterization/ControlWaveform.py
50
51
52
def TriangleCTRL(k=1.0, n=1.0):
    """Triangle-wave control."""
    return _get_jl().TriangleCTRL(k=k, n=n)

Gaussian-pulse control: c(t) = A·exp(-(t-μ)²/(2σ)).

Parameters:

Name Type Description Default
A

Amplitude.

1.0
mu

Center μ.

0.0
sigma

Width σ (variance).

1.0
Source code in quanestimation/base/Parameterization/ControlWaveform.py
55
56
57
58
59
60
61
62
63
def GaussianCTRL(A=1.0, mu=0.0, sigma=1.0):
    """Gaussian-pulse control: ``c(t) = A·exp(-(t-μ)²/(2σ))``.

    Args:
        A: Amplitude.
        mu: Center μ.
        sigma: Width σ (variance).
    """
    return _get_jl().GaussianCTRL(**{"A": A, "μ": mu, "σ": sigma})

Gaussian-edge control.

Parameters:

Name Type Description Default
A

Amplitude.

1.0
sigma

Width σ.

1.0
Source code in quanestimation/base/Parameterization/ControlWaveform.py
66
67
68
69
70
71
72
73
def GaussianEdgeCTRL(A=1.0, sigma=1.0):
    """Gaussian-edge control.

    Args:
        A: Amplitude.
        sigma: Width σ.
    """
    return _get_jl().GaussianEdgeCTRL(**{"A": A, "σ": sigma})

NV Magnetometer

NV-center magnetometer estimation scheme.

Provides a high-level interface for constructing NV-center estimation schemes and evaluating quantum/classical Fisher information, Holevo Cramer-Rao bound, control optimization, error evaluation, and error control.

All defaults match Julia's NVMagnetometerScheme(; ...) constructor exactly (NVMagnetometer.jl:107-143).

Parameters

D : float Zero-field splitting in MHz. Default 2π·2870. gS : float Electron gyromagnetic ratio in MHz/mT. Default 2π·28.03. gI : float Nuclear gyromagnetic ratio in MHz/mT. Default 2π·4.32·10⁻³. A1 : float Transverse hyperfine coupling in MHz. Default 2π·3.65. A2 : float Longitudinal hyperfine coupling in MHz. Default 2π·3.03. B1 : float Magnetic field B_x in mT. Default 0.5. B2 : float Magnetic field B_y in mT. Default 0.5. B3 : float Magnetic field B_z in mT. Default 0.5. gamma : float Dephasing rate in MHz. Default . decay_opt : list, optional Decay operators. Default [S₃] (Julia-computed). init_state : np.ndarray, optional Initial state vector. Default (|+1⟩+|−1⟩)/√2. Hc : list, optional Control Hamiltonians. Default [S₁,S₂,S₃] (Julia-computed). ctrl : list or None, optional Control coefficient sequences. Default None (zeros). tspan : np.ndarray, optional Time span. Default 0:0.01:2.0. M : list or None, optional POVM measurement. Default None (SIC-POVM).

Source code in quanestimation/nv/scheme.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
class NVMagnetometerScheme:
    """NV-center magnetometer estimation scheme.

    Provides a high-level interface for constructing NV-center
    estimation schemes and evaluating quantum/classical Fisher
    information, Holevo Cramer-Rao bound, control optimization,
    error evaluation, and error control.

    All defaults match Julia's ``NVMagnetometerScheme(; ...)``
    constructor exactly (``NVMagnetometer.jl:107-143``).

    Parameters
    ----------
    D : float
        Zero-field splitting in MHz. Default ``2π·2870``.
    gS : float
        Electron gyromagnetic ratio in MHz/mT. Default ``2π·28.03``.
    gI : float
        Nuclear gyromagnetic ratio in MHz/mT. Default ``2π·4.32·10⁻³``.
    A1 : float
        Transverse hyperfine coupling in MHz. Default ``2π·3.65``.
    A2 : float
        Longitudinal hyperfine coupling in MHz. Default ``2π·3.03``.
    B1 : float
        Magnetic field B_x in mT. Default 0.5.
    B2 : float
        Magnetic field B_y in mT. Default 0.5.
    B3 : float
        Magnetic field B_z in mT. Default 0.5.
    gamma : float
        Dephasing rate in MHz. Default ``2π``.
    decay_opt : list, optional
        Decay operators. Default ``[S₃]`` (Julia-computed).
    init_state : np.ndarray, optional
        Initial state vector. Default ``(|+1⟩+|−1⟩)/√2``.
    Hc : list, optional
        Control Hamiltonians. Default ``[S₁,S₂,S₃]`` (Julia-computed).
    ctrl : list or None, optional
        Control coefficient sequences. Default None (zeros).
    tspan : np.ndarray, optional
        Time span. Default ``0:0.01:2.0``.
    M : list or None, optional
        POVM measurement. Default None (SIC-POVM).
    """

    def __init__(
        self,
        D=None,
        gS=None,
        gI=None,
        A1=None,
        A2=None,
        B1=None,
        B2=None,
        B3=None,
        gamma=None,
        decay_opt=None,
        init_state=None,
        Hc=None,
        ctrl=None,
        tspan=None,
        M=None,
    ):
        jl = _get_jl()
        kwargs = {}
        for name, val in [
            ("D", D), ("gS", gS), ("gI", gI),
            ("A1", A1), ("A2", A2),
            ("B1", B1), ("B2", B2), ("B3", B3),
            ("γ", gamma),
        ]:
            if val is not None:
                kwargs[name] = val
        for name, val in [
            ("decay_opt", decay_opt),
            ("init_state", init_state),
            ("Hc", Hc),
            ("ctrl", ctrl),
            ("tspan", tspan),
            ("M", M),
        ]:
            if val is not None:
                kwargs[name] = val
        self._jl_scheme = jl.NVMagnetometerScheme(**kwargs)

    def QFIM(self, **kwargs):
        """Quantum Fisher information matrix.

        Delegates to ``QFIM(nv::NVMagnetometerScheme)``
        (``NVMagnetometer.jl:284``).
        """
        return _get_jl().QFIM(self._jl_scheme, **kwargs)

    def CFIM(self, **kwargs):
        """Classical Fisher information matrix.

        Delegates to ``CFIM(nv::NVMagnetometerScheme)``
        (``NVMagnetometer.jl:292``).
        """
        return _get_jl().CFIM(self._jl_scheme, **kwargs)

    def HCRB(self, **kwargs):
        """Holevo Cramer-Rao bound.

        Delegates to ``HCRB(nv::NVMagnetometerScheme)``
        (``NVMagnetometer.jl:300``).
        """
        return _get_jl().HCRB(self._jl_scheme, **kwargs)

    def optimize(self, opt, algorithm=None, objective=None,
                 savefile=False):
        """Run control optimization.

        Converts the Python ``ControlOpt`` instance to a Julia
        ``ControlOpt`` struct before delegating to ``optimize!``.

        Args:
            opt: ``ControlOpt`` instance (Python).
            algorithm: Julia algorithm struct (default ``autoGRAPE()``).
            objective: Julia objective (default ``QFIM_obj()``).
            savefile: Whether to save results to file.
        """
        import juliacall
        from juliacall import Main as jl_main, convert as jlconvert

        jl_mod = _get_jl()
        if objective is None:
            objective = jl_mod.QFIM_obj()

        ctrl0 = getattr(opt, 'ctrl0', [])
        if ctrl0:
            ctrl_jl = jlconvert(jl_main.Vector[jl_main.Vector[jl_main.Float64]], ctrl0)
        else:
            ctrl_jl = None

        ctrl_bound = getattr(opt, 'ctrl_bound', [-float('inf'), float('inf')])
        ctrl_bound_jl = jlconvert(jl_main.Vector[jl_main.Float64], ctrl_bound)

        seed = getattr(opt, 'seed', 1234)
        max_episode = getattr(opt, 'max_episode', 100)

        julia_opt = jl_mod.ControlOpt(
            ctrl=ctrl_jl if ctrl0 else None,
            ctrl_bound=ctrl_bound_jl,
            seed=seed,
        )

        if algorithm is None:
            algorithm = jl_mod.autoGRAPE(max_episode=max_episode)

        return getattr(jl_mod, "optimize!")(
            self._jl_scheme, julia_opt,
            algorithm=algorithm,
            objective=objective,
            savefile=savefile,
        )

    def error_evaluation(self, **kwargs):
        """Evaluate estimation error.

        Delegates to ``error_evaluation(nv)``
        (``NVMagnetometer.jl:325``).
        """
        return _get_jl().error_evaluation(self._jl_scheme, **kwargs)

    def error_control(self, **kwargs):
        """Error control analysis.

        Delegates to ``error_control(nv)``
        (``NVMagnetometer.jl:334``).
        """
        return _get_jl().error_control(self._jl_scheme, **kwargs)

CFIM(**kwargs)

Classical Fisher information matrix.

Delegates to CFIM(nv::NVMagnetometerScheme) (NVMagnetometer.jl:292).

Source code in quanestimation/nv/scheme.py
115
116
117
118
119
120
121
def CFIM(self, **kwargs):
    """Classical Fisher information matrix.

    Delegates to ``CFIM(nv::NVMagnetometerScheme)``
    (``NVMagnetometer.jl:292``).
    """
    return _get_jl().CFIM(self._jl_scheme, **kwargs)

HCRB(**kwargs)

Holevo Cramer-Rao bound.

Delegates to HCRB(nv::NVMagnetometerScheme) (NVMagnetometer.jl:300).

Source code in quanestimation/nv/scheme.py
123
124
125
126
127
128
129
def HCRB(self, **kwargs):
    """Holevo Cramer-Rao bound.

    Delegates to ``HCRB(nv::NVMagnetometerScheme)``
    (``NVMagnetometer.jl:300``).
    """
    return _get_jl().HCRB(self._jl_scheme, **kwargs)

QFIM(**kwargs)

Quantum Fisher information matrix.

Delegates to QFIM(nv::NVMagnetometerScheme) (NVMagnetometer.jl:284).

Source code in quanestimation/nv/scheme.py
107
108
109
110
111
112
113
def QFIM(self, **kwargs):
    """Quantum Fisher information matrix.

    Delegates to ``QFIM(nv::NVMagnetometerScheme)``
    (``NVMagnetometer.jl:284``).
    """
    return _get_jl().QFIM(self._jl_scheme, **kwargs)

error_control(**kwargs)

Error control analysis.

Delegates to error_control(nv) (NVMagnetometer.jl:334).

Source code in quanestimation/nv/scheme.py
187
188
189
190
191
192
193
def error_control(self, **kwargs):
    """Error control analysis.

    Delegates to ``error_control(nv)``
    (``NVMagnetometer.jl:334``).
    """
    return _get_jl().error_control(self._jl_scheme, **kwargs)

error_evaluation(**kwargs)

Evaluate estimation error.

Delegates to error_evaluation(nv) (NVMagnetometer.jl:325).

Source code in quanestimation/nv/scheme.py
179
180
181
182
183
184
185
def error_evaluation(self, **kwargs):
    """Evaluate estimation error.

    Delegates to ``error_evaluation(nv)``
    (``NVMagnetometer.jl:325``).
    """
    return _get_jl().error_evaluation(self._jl_scheme, **kwargs)

optimize(opt, algorithm=None, objective=None, savefile=False)

Run control optimization.

Converts the Python ControlOpt instance to a Julia ControlOpt struct before delegating to optimize!.

Parameters:

Name Type Description Default
opt

ControlOpt instance (Python).

required
algorithm

Julia algorithm struct (default autoGRAPE()).

None
objective

Julia objective (default QFIM_obj()).

None
savefile

Whether to save results to file.

False
Source code in quanestimation/nv/scheme.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def optimize(self, opt, algorithm=None, objective=None,
             savefile=False):
    """Run control optimization.

    Converts the Python ``ControlOpt`` instance to a Julia
    ``ControlOpt`` struct before delegating to ``optimize!``.

    Args:
        opt: ``ControlOpt`` instance (Python).
        algorithm: Julia algorithm struct (default ``autoGRAPE()``).
        objective: Julia objective (default ``QFIM_obj()``).
        savefile: Whether to save results to file.
    """
    import juliacall
    from juliacall import Main as jl_main, convert as jlconvert

    jl_mod = _get_jl()
    if objective is None:
        objective = jl_mod.QFIM_obj()

    ctrl0 = getattr(opt, 'ctrl0', [])
    if ctrl0:
        ctrl_jl = jlconvert(jl_main.Vector[jl_main.Vector[jl_main.Float64]], ctrl0)
    else:
        ctrl_jl = None

    ctrl_bound = getattr(opt, 'ctrl_bound', [-float('inf'), float('inf')])
    ctrl_bound_jl = jlconvert(jl_main.Vector[jl_main.Float64], ctrl_bound)

    seed = getattr(opt, 'seed', 1234)
    max_episode = getattr(opt, 'max_episode', 100)

    julia_opt = jl_mod.ControlOpt(
        ctrl=ctrl_jl if ctrl0 else None,
        ctrl_bound=ctrl_bound_jl,
        seed=seed,
    )

    if algorithm is None:
        algorithm = jl_mod.autoGRAPE(max_episode=max_episode)

    return getattr(jl_mod, "optimize!")(
        self._jl_scheme, julia_opt,
        algorithm=algorithm,
        objective=objective,
        savefile=savefile,
    )

Control Optimization

The Hamiltonian of a controlled system can be written as \begin{align} H = H_0(\textbf{x})+\sum_{k=1}^K u_k(t) H_k, \end{align}

where \(H_0(\textbf{x})\) is the free evolution Hamiltonian with unknown parameters \(\textbf{x}\) and \(H_k\) represents the \(k\)th control Hamiltonian with \(u_k\) the corresponding control coefficient. In QuanEstimation, different algorithms are invoked to update the optimal control coefficients. The control optimization algorithms are
gradient ascent pulse engineering (GRAPE), GRAPE algorithm based on the automatic differentiation (auto-GRAPE), particle swarm optimization (PSO), differential evolution (DE) and deep deterministic policy gradients (DDPG).

Base

Attributes

savefile: bool -- Whether or not to save all the control coeffients.
If set True then the control coefficients and the values of the objective function obtained in all episodes will be saved during the training. If set False the control coefficients in the final episode and the values of the objective function in all episodes will be saved.

ctrl0: list of arrays -- Initial guesses of control coefficients.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load control coefficients in the current location.
If set True then the program will load control coefficients from "controls.csv" file in the current location and use it as the initial control coefficients.

Source code in quanestimation/base/ControlOpt/ControlStruct.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
class ControlSystem:
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the control coeffients.  
        If set `True` then the control coefficients and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the control coefficients in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **ctrl0:** `list of arrays`
        -- Initial guesses of control coefficients.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load control coefficients in the current location.  
        If set `True` then the program will load control coefficients from 
        "controls.csv" file in the current location and use it as the initial 
        control coefficients.
    """

    def __init__(self, savefile, ctrl0, seed, eps, load):
        r"""
        Initialize the control optimization system.

        Args:
            savefile (bool): Whether to save all control coefficients during
                training. If ``True``, all episodes are saved; if ``False``, only
                the final episode is saved.
            ctrl0 (list of arrays): Initial guesses of control coefficients.
            seed (int): Random seed.
            eps (float): Machine epsilon.
            load (bool): Whether to load control coefficients from
                ``controls.csv`` in the current directory as initial values.
        """
        self.savefile = savefile
        self.ctrl0 = ctrl0
        self.seed = seed
        self.eps = eps
        self.load = load

        self.QJLType_ctrl = QJL.Vector[QJL.Vector[QJL.Float64]]

    def load_save(self, cnum, max_episode):
        r"""
        Load control coefficients saved by the Julia backend from ``controls.dat``
        and save them as a ``controls.npy`` file.

        The Julia backend writes ``controls.dat`` atomically (temp → rename),
        and the file is deleted after a successful read to avoid stale data
        from interfering with subsequent runs.

        Args:
            cnum (int): Number of control Hamiltonian channels.
            max_episode (int): Maximum number of episodes.
        """
        load_and_save("controls.dat", "controls", "controls",
                       self.savefile, item_count=cnum, max_episode=max_episode,
                       nested=True)

    def dynamics(self, tspan, rho0, H0, dH, Hc, decay=None, ctrl_bound=None, dyn_method="expm"):
        r"""
        The dynamics of a density matrix is of the form 

        \begin{aligned}
            \partial_t\rho &=\mathcal{L}\rho \nonumber \\
            &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
            \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
        \end{aligned}

        where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
        system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
        operator and corresponding decay rate.

        Parameters
        ----------
        > **tspan:** `array`
            -- Time length for the evolution.

        > **rho0:** `matrix`
            -- Initial state (density matrix).

        > **H0:** `matrix or list`
            -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time-
            independent and a list of length equal to `tspan` when it is time-dependent.

        > **dH:** `list`
            -- Derivatives of the free Hamiltonian on the unknown parameters to be 
            estimated. For example, dH[0] is the derivative vector on the first 
            parameter.

        > **Hc:** `list`
            -- Control Hamiltonians.

        > **decay:** `list`
            -- Decay operators and the corresponding decay rates. Its input rule is 
            decay=[[$\Gamma_1$, $\gamma_1$], [$\Gamma_2$,$\gamma_2$],...], where $\Gamma_1$ 
            $(\Gamma_2)$ represents the decay operator and $\gamma_1$ $(\gamma_2)$ is the 
            corresponding decay rate.

        > **ctrl_bound:** `array`
            -- Lower and upper bounds of the control coefficients.
            `ctrl_bound[0]` represents the lower bound of the control coefficients and
            `ctrl_bound[1]` represents the upper bound of the control coefficients.

        > **dyn_method:** `string`
            -- Setting the method for solving the Lindblad dynamics. Options are:  
            "expm" (default) -- Matrix exponential.  
            "ode" -- Solving the differential equations directly.  
        """

        if decay is None:
            decay = []
        if ctrl_bound is None:
            ctrl_bound = []

        self.tspan = tspan
        self.rho0 = np.array(rho0, dtype=np.complex128)

        if dyn_method == "expm":
            self.dyn_method = "Expm"
        elif dyn_method == "ode":
            self.dyn_method = "Ode"

        if isinstance(H0, np.ndarray):
            self.freeHamiltonian = np.array(H0, dtype=np.complex128)
        else:
            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0[:-1]]

        if Hc == []:
            Hc = [np.zeros((len(self.rho0), len(self.rho0)))]
        self.control_Hamiltonian = [np.array(x, dtype=np.complex128) for x in Hc]

        if not isinstance(dH, list):
            raise TypeError("The derivative of Hamiltonian should be a list!")

        if dH == []:
            dH = [np.zeros((len(self.rho0), len(self.rho0)))]
        self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]
        if len(dH) == 1:
            self.para_type = "single_para"
        else:
            self.para_type = "multi_para"

        if decay == []:
            decay_opt = [np.zeros((len(self.rho0), len(self.rho0)))]
            self.gamma = [0.0]
        else:
            decay_opt = [decay[i][0] for i in range(len(decay))]
            self.gamma = [decay[i][1] for i in range(len(decay))]
        self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

        if ctrl_bound == []:
            self.ctrl_bound =  [float('-inf'), float('inf')]
        else:
            self.ctrl_bound = [float(ctrl_bound[0]), float(ctrl_bound[1])]
        jl = juliacall.Main
        self.ctrl_bound = juliacall.convert(jl.Vector[jl.Float64], self.ctrl_bound)

        if self.ctrl0 == []:
            if ctrl_bound == []:
                ctrl0 = [
                    2 * np.random.random(len(self.tspan) - 1)
                    - np.ones(len(self.tspan) - 1)
                    for i in range(len(self.control_Hamiltonian))
                ]
                self.control_coefficients = ctrl0
                self.ctrl0 = [np.array(ctrl0)]
            else:
                a = ctrl_bound[0]
                b = ctrl_bound[1]
                ctrl0 = [
                    (b - a) * np.random.random(len(self.tspan) - 1)
                    + a * np.ones(len(self.tspan) - 1)
                    for i in range(len(self.control_Hamiltonian))
                ]
            self.control_coefficients = ctrl0
            self.ctrl0 = [np.array(ctrl0)]
        elif len(self.ctrl0) >= 1:
            self.control_coefficients = [
                self.ctrl0[0][i] for i in range(len(self.control_Hamiltonian))
            ]
        self.ctrl0 = QJL.convert(self.QJLType_ctrl, [list(c) for c in self.ctrl0[0]])

        if self.load == True:
            if os.path.exists("controls.csv"):
                data = np.genfromtxt("controls.csv")[-len(self.control_Hamiltonian) :]
                self.control_coefficients = [data[i] for i in range(len(data))]

        ctrl_num = len(self.control_coefficients)
        Hc_num = len(self.control_Hamiltonian)
        if Hc_num < ctrl_num:
            raise TypeError(
                "There are %d control Hamiltonians but %d coefficients sequences: too many coefficients sequences"
                % (Hc_num, ctrl_num)
            )
        elif Hc_num > ctrl_num:
            warnings.warn(
                "Not enough coefficients sequences: there are %d control Hamiltonians but %d coefficients sequences. The rest of the control sequences are set to be 0."
                % (Hc_num, ctrl_num),
                DeprecationWarning,
            )
            for i in range(Hc_num - ctrl_num):
                self.control_coefficients = np.concatenate(
                    (
                        self.control_coefficients,
                        np.zeros(len(self.control_coefficients[0])),
                    )
                )

        if not isinstance(H0, np.ndarray):
            #### linear interpolation  ####
            f = interp1d(self.tspan, H0, axis=0)
        number = math.ceil((len(self.tspan) - 1) / len(self.control_coefficients[0]))
        if (len(self.tspan) - 1) % len(self.control_coefficients[0]) != 0:
            tnum = number * len(self.control_coefficients[0])
            self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
            if not isinstance(H0, np.ndarray):
                H0_inter = f(self.tspan)
                self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter[:-1]]


        self.opt = QJL.ControlOpt(
            ctrl = self.ctrl0,
            ctrl_bound=self.ctrl_bound, 
            seed=self.seed
        )
        decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
        dynamics = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.tspan,
            self.control_Hamiltonian,
            decay,
            ctrl = self.control_coefficients,
            dyn_method = self.dyn_method,
        )
        self.scheme = QJL.GeneralScheme(probe=self.rho0, param=dynamics)
        self.dynamics_type = "lindblad"

    def QFIM(self, W=None, LDtype="SLD"):
        r"""
        Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
        In single parameter estimation the objective function is QFI and in 
        multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).  
        """

        if W is None:
            W = []

        if LDtype != "SLD" and LDtype != "RLD" and LDtype != "LLD":
            raise ValueError(
                "{!r} is not a valid value for LDtype, supported values are 'SLD', 'RLD' and 'LLD'.".format(
                    LDtype
                )
            )

        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

        jl = juliacall.Main
        self.obj = QJL.QFIM_obj(W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps, LDtype=jl.Symbol(LDtype))
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)
        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save(len(self.control_Hamiltonian), max_num)

    def CFIM(self, M=None, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if M is None:
            M = []
        if W is None:
            W = []

        if M == []:
            M = SIC(len(self.rho0))
        M = [np.array(x, dtype=np.complex128) for x in M]

        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

        jl = juliacall.Main
        self.obj = QJL.CFIM_obj(M=juliacall.convert(jl.Vector[jl.Matrix[jl.ComplexF64]], M), W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)
        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save(len(self.control_Hamiltonian), max_num)

    def HCRB(self, W=None):
        """
        Choose HCRB as the objective function. 

        **Notes:** (1) In single parameter estimation, HCRB is equivalent to QFI, please
        choose QFI as the objective function. (2) GRAPE and auto-GRAPE are not available
        when the objective function is HCRB. Supported methods are PSO, DE and DDPG.

        Parameters
        ----------
        > **W:** `matrix` 
            -- Weight matrix.
        """

        if W is None:
            W = []

        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

        if len(self.Hamiltonian_derivative) == 1:
            print("Program terminated. In the single-parameter scenario, HCRB is equivalent to QFI. Please choose QFIM as the objective function."
                    )
        else:
            if W == []:
                W = np.eye(len(self.Hamiltonian_derivative))
            self.W = W  

            jl = juliacall.Main
            self.obj = QJL.HCRB_obj(W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
            getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)
            max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save(len(self.control_Hamiltonian), max_num)

    def mintime(self, f, W=None, M=None, method="binary", target="QFIM", LDtype="SLD"):
        """
        Search of the minimum time to reach a given value of the objective function.

        Parameters
        ----------
        > **f:** `float`
            -- The given value of the objective function.

        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        > **method:** `string`
            -- Methods for searching the minimum time to reach the given value of the 
            objective function. Options are:  
            "binary" (default) -- Binary search (logarithmic search).  
            "forward" -- Forward search from the beginning of time.  

        > **target:** `string`
            -- Objective functions for searching the minimum time to reach the given 
            value of the objective function. Options are:  
            "QFIM" (default) -- Choose QFI (QFIM) as the objective function.  
            "CFIM" -- Choose CFI (CFIM) as the objective function.  
            "HCRB" -- Choose HCRB as the objective function.  

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).  
        """
        jl = juliacall.Main

        if W is None:
            W = []
        if M is None:
            M = []

        if not (method == "binary" or method == "forward"):
            raise ValueError(
                "{!r} is not a valid value for method, supported values are 'binary' and 'forward'.".format(
                    method
                )
            )

        if self.dynamics_type != "lindblad":
            raise ValueError(
                "Supported type of dynamics is Lindblad."
                )
        if self.savefile == True:
            warnings.warn(
                    "savefile is set to be False",
                    DeprecationWarning,
                )

        if len(self.Hamiltonian_derivative) > 1:
            f = 1 / f

        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

        if M != []:
            M = [np.array(x, dtype=np.complex128) for x in M]
            self.obj = QJL.CFIM_obj(M=juliacall.convert(jl.Vector[jl.Matrix[jl.ComplexF64]], M),
                                     W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        else:
            if target == "HCRB":
                if self.para_type == "single_para":
                    print(
                        "Program terminated. In the single-parameter scenario, the HCRB is equivalent to the QFI. Please choose 'QFIM' as the objective function.")
                self.obj = QJL.HCRB_obj(W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
            elif target == "QFIM" or (
                LDtype == "SLD" or LDtype == "RLD" or LDtype == "LLD"
            ):
                self.obj = QJL.QFIM_obj(W=juliacall.convert(jl.Matrix[jl.Float64], self.W),
                                         eps=self.eps, LDtype=jl.Symbol(LDtype))
            else:
                raise ValueError(
                    "Please enter the correct values for target and LDtype. Supported target are 'QFIM', 'CFIM' and 'HCRB', supported LDtype are 'SLD', 'RLD' and 'LLD'."
                )

        QJL.mintime(method, f, self.scheme, self.opt, algorithm=self.alg, objective=self.obj)
        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save(len(self.control_Hamiltonian), max_num)

CFIM(M=None, W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

M: list -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ControlOpt/ControlStruct.py
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
def CFIM(self, M=None, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if M is None:
        M = []
    if W is None:
        W = []

    if M == []:
        M = SIC(len(self.rho0))
    M = [np.array(x, dtype=np.complex128) for x in M]

    if W == []:
        W = np.eye(len(self.Hamiltonian_derivative))
    self.W = W

    jl = juliacall.Main
    self.obj = QJL.CFIM_obj(M=juliacall.convert(jl.Vector[jl.Matrix[jl.ComplexF64]], M), W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)
    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save(len(self.control_Hamiltonian), max_num)

HCRB(W=None)

Choose HCRB as the objective function.

Notes: (1) In single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function. (2) GRAPE and auto-GRAPE are not available when the objective function is HCRB. Supported methods are PSO, DE and DDPG.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ControlOpt/ControlStruct.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
def HCRB(self, W=None):
    """
    Choose HCRB as the objective function. 

    **Notes:** (1) In single parameter estimation, HCRB is equivalent to QFI, please
    choose QFI as the objective function. (2) GRAPE and auto-GRAPE are not available
    when the objective function is HCRB. Supported methods are PSO, DE and DDPG.

    Parameters
    ----------
    > **W:** `matrix` 
        -- Weight matrix.
    """

    if W is None:
        W = []

    if W == []:
        W = np.eye(len(self.Hamiltonian_derivative))
    self.W = W

    if len(self.Hamiltonian_derivative) == 1:
        print("Program terminated. In the single-parameter scenario, HCRB is equivalent to QFI. Please choose QFIM as the objective function."
                )
    else:
        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W  

        jl = juliacall.Main
        self.obj = QJL.HCRB_obj(W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)
        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save(len(self.control_Hamiltonian), max_num)

QFIM(W=None, LDtype='SLD')

Choose QFI or \(\mathrm{Tr}(WF^{-1})\) as the objective function. In single parameter estimation the objective function is QFI and in multiparameter estimation it will be \(\mathrm{Tr}(WF^{-1})\).

Parameters

W: matrix -- Weight matrix.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Source code in quanestimation/base/ControlOpt/ControlStruct.py
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
def QFIM(self, W=None, LDtype="SLD"):
    r"""
    Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
    In single parameter estimation the objective function is QFI and in 
    multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).  
    """

    if W is None:
        W = []

    if LDtype != "SLD" and LDtype != "RLD" and LDtype != "LLD":
        raise ValueError(
            "{!r} is not a valid value for LDtype, supported values are 'SLD', 'RLD' and 'LLD'.".format(
                LDtype
            )
        )

    if W == []:
        W = np.eye(len(self.Hamiltonian_derivative))
    self.W = W

    jl = juliacall.Main
    self.obj = QJL.QFIM_obj(W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps, LDtype=jl.Symbol(LDtype))
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)
    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save(len(self.control_Hamiltonian), max_num)

__init__(savefile, ctrl0, seed, eps, load)

Initialize the control optimization system.

Parameters:

Name Type Description Default
savefile bool

Whether to save all control coefficients during training. If True, all episodes are saved; if False, only the final episode is saved.

required
ctrl0 list of arrays

Initial guesses of control coefficients.

required
seed int

Random seed.

required
eps float

Machine epsilon.

required
load bool

Whether to load control coefficients from controls.csv in the current directory as initial values.

required
Source code in quanestimation/base/ControlOpt/ControlStruct.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def __init__(self, savefile, ctrl0, seed, eps, load):
    r"""
    Initialize the control optimization system.

    Args:
        savefile (bool): Whether to save all control coefficients during
            training. If ``True``, all episodes are saved; if ``False``, only
            the final episode is saved.
        ctrl0 (list of arrays): Initial guesses of control coefficients.
        seed (int): Random seed.
        eps (float): Machine epsilon.
        load (bool): Whether to load control coefficients from
            ``controls.csv`` in the current directory as initial values.
    """
    self.savefile = savefile
    self.ctrl0 = ctrl0
    self.seed = seed
    self.eps = eps
    self.load = load

    self.QJLType_ctrl = QJL.Vector[QJL.Vector[QJL.Float64]]

dynamics(tspan, rho0, H0, dH, Hc, decay=None, ctrl_bound=None, dyn_method='expm')

The dynamics of a density matrix is of the form

\[\begin{aligned} \partial_t\rho &=\mathcal{L}\rho \nonumber \\ &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2} \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right), \end{aligned}\]

where \(\rho\) is the evolved density matrix, H is the Hamiltonian of the system, \(\Gamma_i\) and \(\gamma_i\) are the \(i\mathrm{th}\) decay operator and corresponding decay rate.

Parameters

tspan: array -- Time length for the evolution.

rho0: matrix -- Initial state (density matrix).

H0: matrix or list -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time- independent and a list of length equal to tspan when it is time-dependent.

dH: list -- Derivatives of the free Hamiltonian on the unknown parameters to be estimated. For example, dH[0] is the derivative vector on the first parameter.

Hc: list -- Control Hamiltonians.

decay: list -- Decay operators and the corresponding decay rates. Its input rule is decay=[[\(\Gamma_1\), \(\gamma_1\)], [\(\Gamma_2\),\(\gamma_2\)],...], where \(\Gamma_1\) \((\Gamma_2)\) represents the decay operator and \(\gamma_1\) \((\gamma_2)\) is the corresponding decay rate.

ctrl_bound: array -- Lower and upper bounds of the control coefficients. ctrl_bound[0] represents the lower bound of the control coefficients and ctrl_bound[1] represents the upper bound of the control coefficients.

dyn_method: string -- Setting the method for solving the Lindblad dynamics. Options are:
"expm" (default) -- Matrix exponential.
"ode" -- Solving the differential equations directly.

Source code in quanestimation/base/ControlOpt/ControlStruct.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def dynamics(self, tspan, rho0, H0, dH, Hc, decay=None, ctrl_bound=None, dyn_method="expm"):
    r"""
    The dynamics of a density matrix is of the form 

    \begin{aligned}
        \partial_t\rho &=\mathcal{L}\rho \nonumber \\
        &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
        \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
    \end{aligned}

    where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
    system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
    operator and corresponding decay rate.

    Parameters
    ----------
    > **tspan:** `array`
        -- Time length for the evolution.

    > **rho0:** `matrix`
        -- Initial state (density matrix).

    > **H0:** `matrix or list`
        -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time-
        independent and a list of length equal to `tspan` when it is time-dependent.

    > **dH:** `list`
        -- Derivatives of the free Hamiltonian on the unknown parameters to be 
        estimated. For example, dH[0] is the derivative vector on the first 
        parameter.

    > **Hc:** `list`
        -- Control Hamiltonians.

    > **decay:** `list`
        -- Decay operators and the corresponding decay rates. Its input rule is 
        decay=[[$\Gamma_1$, $\gamma_1$], [$\Gamma_2$,$\gamma_2$],...], where $\Gamma_1$ 
        $(\Gamma_2)$ represents the decay operator and $\gamma_1$ $(\gamma_2)$ is the 
        corresponding decay rate.

    > **ctrl_bound:** `array`
        -- Lower and upper bounds of the control coefficients.
        `ctrl_bound[0]` represents the lower bound of the control coefficients and
        `ctrl_bound[1]` represents the upper bound of the control coefficients.

    > **dyn_method:** `string`
        -- Setting the method for solving the Lindblad dynamics. Options are:  
        "expm" (default) -- Matrix exponential.  
        "ode" -- Solving the differential equations directly.  
    """

    if decay is None:
        decay = []
    if ctrl_bound is None:
        ctrl_bound = []

    self.tspan = tspan
    self.rho0 = np.array(rho0, dtype=np.complex128)

    if dyn_method == "expm":
        self.dyn_method = "Expm"
    elif dyn_method == "ode":
        self.dyn_method = "Ode"

    if isinstance(H0, np.ndarray):
        self.freeHamiltonian = np.array(H0, dtype=np.complex128)
    else:
        self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0[:-1]]

    if Hc == []:
        Hc = [np.zeros((len(self.rho0), len(self.rho0)))]
    self.control_Hamiltonian = [np.array(x, dtype=np.complex128) for x in Hc]

    if not isinstance(dH, list):
        raise TypeError("The derivative of Hamiltonian should be a list!")

    if dH == []:
        dH = [np.zeros((len(self.rho0), len(self.rho0)))]
    self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]
    if len(dH) == 1:
        self.para_type = "single_para"
    else:
        self.para_type = "multi_para"

    if decay == []:
        decay_opt = [np.zeros((len(self.rho0), len(self.rho0)))]
        self.gamma = [0.0]
    else:
        decay_opt = [decay[i][0] for i in range(len(decay))]
        self.gamma = [decay[i][1] for i in range(len(decay))]
    self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

    if ctrl_bound == []:
        self.ctrl_bound =  [float('-inf'), float('inf')]
    else:
        self.ctrl_bound = [float(ctrl_bound[0]), float(ctrl_bound[1])]
    jl = juliacall.Main
    self.ctrl_bound = juliacall.convert(jl.Vector[jl.Float64], self.ctrl_bound)

    if self.ctrl0 == []:
        if ctrl_bound == []:
            ctrl0 = [
                2 * np.random.random(len(self.tspan) - 1)
                - np.ones(len(self.tspan) - 1)
                for i in range(len(self.control_Hamiltonian))
            ]
            self.control_coefficients = ctrl0
            self.ctrl0 = [np.array(ctrl0)]
        else:
            a = ctrl_bound[0]
            b = ctrl_bound[1]
            ctrl0 = [
                (b - a) * np.random.random(len(self.tspan) - 1)
                + a * np.ones(len(self.tspan) - 1)
                for i in range(len(self.control_Hamiltonian))
            ]
        self.control_coefficients = ctrl0
        self.ctrl0 = [np.array(ctrl0)]
    elif len(self.ctrl0) >= 1:
        self.control_coefficients = [
            self.ctrl0[0][i] for i in range(len(self.control_Hamiltonian))
        ]
    self.ctrl0 = QJL.convert(self.QJLType_ctrl, [list(c) for c in self.ctrl0[0]])

    if self.load == True:
        if os.path.exists("controls.csv"):
            data = np.genfromtxt("controls.csv")[-len(self.control_Hamiltonian) :]
            self.control_coefficients = [data[i] for i in range(len(data))]

    ctrl_num = len(self.control_coefficients)
    Hc_num = len(self.control_Hamiltonian)
    if Hc_num < ctrl_num:
        raise TypeError(
            "There are %d control Hamiltonians but %d coefficients sequences: too many coefficients sequences"
            % (Hc_num, ctrl_num)
        )
    elif Hc_num > ctrl_num:
        warnings.warn(
            "Not enough coefficients sequences: there are %d control Hamiltonians but %d coefficients sequences. The rest of the control sequences are set to be 0."
            % (Hc_num, ctrl_num),
            DeprecationWarning,
        )
        for i in range(Hc_num - ctrl_num):
            self.control_coefficients = np.concatenate(
                (
                    self.control_coefficients,
                    np.zeros(len(self.control_coefficients[0])),
                )
            )

    if not isinstance(H0, np.ndarray):
        #### linear interpolation  ####
        f = interp1d(self.tspan, H0, axis=0)
    number = math.ceil((len(self.tspan) - 1) / len(self.control_coefficients[0]))
    if (len(self.tspan) - 1) % len(self.control_coefficients[0]) != 0:
        tnum = number * len(self.control_coefficients[0])
        self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
        if not isinstance(H0, np.ndarray):
            H0_inter = f(self.tspan)
            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter[:-1]]


    self.opt = QJL.ControlOpt(
        ctrl = self.ctrl0,
        ctrl_bound=self.ctrl_bound, 
        seed=self.seed
    )
    decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
    dynamics = QJL.Lindblad(
        self.freeHamiltonian,
        self.Hamiltonian_derivative,
        self.tspan,
        self.control_Hamiltonian,
        decay,
        ctrl = self.control_coefficients,
        dyn_method = self.dyn_method,
    )
    self.scheme = QJL.GeneralScheme(probe=self.rho0, param=dynamics)
    self.dynamics_type = "lindblad"

load_save(cnum, max_episode)

Load control coefficients saved by the Julia backend from controls.dat and save them as a controls.npy file.

The Julia backend writes controls.dat atomically (temp → rename), and the file is deleted after a successful read to avoid stale data from interfering with subsequent runs.

Parameters:

Name Type Description Default
cnum int

Number of control Hamiltonian channels.

required
max_episode int

Maximum number of episodes.

required
Source code in quanestimation/base/ControlOpt/ControlStruct.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def load_save(self, cnum, max_episode):
    r"""
    Load control coefficients saved by the Julia backend from ``controls.dat``
    and save them as a ``controls.npy`` file.

    The Julia backend writes ``controls.dat`` atomically (temp → rename),
    and the file is deleted after a successful read to avoid stale data
    from interfering with subsequent runs.

    Args:
        cnum (int): Number of control Hamiltonian channels.
        max_episode (int): Maximum number of episodes.
    """
    load_and_save("controls.dat", "controls", "controls",
                   self.savefile, item_count=cnum, max_episode=max_episode,
                   nested=True)

mintime(f, W=None, M=None, method='binary', target='QFIM', LDtype='SLD')

Search of the minimum time to reach a given value of the objective function.

Parameters

f: float -- The given value of the objective function.

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

method: string -- Methods for searching the minimum time to reach the given value of the objective function. Options are:
"binary" (default) -- Binary search (logarithmic search).
"forward" -- Forward search from the beginning of time.

target: string -- Objective functions for searching the minimum time to reach the given value of the objective function. Options are:
"QFIM" (default) -- Choose QFI (QFIM) as the objective function.
"CFIM" -- Choose CFI (CFIM) as the objective function.
"HCRB" -- Choose HCRB as the objective function.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Source code in quanestimation/base/ControlOpt/ControlStruct.py
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
def mintime(self, f, W=None, M=None, method="binary", target="QFIM", LDtype="SLD"):
    """
    Search of the minimum time to reach a given value of the objective function.

    Parameters
    ----------
    > **f:** `float`
        -- The given value of the objective function.

    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    > **method:** `string`
        -- Methods for searching the minimum time to reach the given value of the 
        objective function. Options are:  
        "binary" (default) -- Binary search (logarithmic search).  
        "forward" -- Forward search from the beginning of time.  

    > **target:** `string`
        -- Objective functions for searching the minimum time to reach the given 
        value of the objective function. Options are:  
        "QFIM" (default) -- Choose QFI (QFIM) as the objective function.  
        "CFIM" -- Choose CFI (CFIM) as the objective function.  
        "HCRB" -- Choose HCRB as the objective function.  

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).  
    """
    jl = juliacall.Main

    if W is None:
        W = []
    if M is None:
        M = []

    if not (method == "binary" or method == "forward"):
        raise ValueError(
            "{!r} is not a valid value for method, supported values are 'binary' and 'forward'.".format(
                method
            )
        )

    if self.dynamics_type != "lindblad":
        raise ValueError(
            "Supported type of dynamics is Lindblad."
            )
    if self.savefile == True:
        warnings.warn(
                "savefile is set to be False",
                DeprecationWarning,
            )

    if len(self.Hamiltonian_derivative) > 1:
        f = 1 / f

    if W == []:
        W = np.eye(len(self.Hamiltonian_derivative))
    self.W = W

    if M != []:
        M = [np.array(x, dtype=np.complex128) for x in M]
        self.obj = QJL.CFIM_obj(M=juliacall.convert(jl.Vector[jl.Matrix[jl.ComplexF64]], M),
                                 W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
    else:
        if target == "HCRB":
            if self.para_type == "single_para":
                print(
                    "Program terminated. In the single-parameter scenario, the HCRB is equivalent to the QFI. Please choose 'QFIM' as the objective function.")
            self.obj = QJL.HCRB_obj(W=juliacall.convert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        elif target == "QFIM" or (
            LDtype == "SLD" or LDtype == "RLD" or LDtype == "LLD"
        ):
            self.obj = QJL.QFIM_obj(W=juliacall.convert(jl.Matrix[jl.Float64], self.W),
                                     eps=self.eps, LDtype=jl.Symbol(LDtype))
        else:
            raise ValueError(
                "Please enter the correct values for target and LDtype. Supported target are 'QFIM', 'CFIM' and 'HCRB', supported LDtype are 'SLD', 'RLD' and 'LLD'."
            )

    QJL.mintime(method, f, self.scheme, self.opt, algorithm=self.alg, objective=self.obj)
    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save(len(self.control_Hamiltonian), max_num)

Control optimization with GRAPE and auto-GRAPE

Bases: ControlSystem

Attributes

savefile: bool -- Whether or not to save all the control coeffients.
If set True then the control coefficients and the values of the objective function obtained in all episodes will be saved during the training. If set False the control coefficients in the final episode and the values of the objective function in all episodes will be saved.

Adam: bool -- Whether or not to use Adam for updating control coefficients.

ctrl0: list of arrays -- Initial guesses of control coefficients.

max_episode: int -- The number of episodes.

epsilon: float -- Learning rate.

beta1: float -- The exponential decay rate for the first moment estimates.

beta2: float -- The exponential decay rate for the second moment estimates.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load control coefficients in the current location.
If set True then the program will load control coefficients from "controls.csv" file in the current location and use it as the initial control coefficients.

auto: bool -- Whether or not to invoke automatic differentiation algorithm to evaluate
the gradient. If set True then the gradient will be calculated with automatic differentiation algorithm otherwise it will be calculated using analytical method.

Source code in quanestimation/base/ControlOpt/GRAPE_Copt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
class GRAPE_Copt(Control.ControlSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the control coeffients.  
        If set `True` then the control coefficients and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the control coefficients in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **Adam:** `bool`
        -- Whether or not to use Adam for updating control coefficients.

    > **ctrl0:** `list of arrays`
        -- Initial guesses of control coefficients.

    > **max_episode:** `int`
        -- The number of episodes.

    > **epsilon:** `float`
        -- Learning rate.

    > **beta1:** `float`
        -- The exponential decay rate for the first moment estimates.

    > **beta2:** `float`
        -- The exponential decay rate for the second moment estimates.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load control coefficients in the current location.  
        If set `True` then the program will load control coefficients from 
        "controls.csv" file in the current location and use it as the initial 
        control coefficients.

    > **auto:** `bool`
        -- Whether or not to invoke automatic differentiation algorithm to evaluate  
        the gradient. If set `True` then the gradient will be calculated with 
        automatic differentiation algorithm otherwise it will be calculated 
        using analytical method.
    """

    def __init__(
        self,
        savefile=False,
        Adam=True,
        ctrl0=[],
        max_episode=300,
        epsilon=0.01,
        beta1=0.90,
        beta2=0.99,
        eps=1e-8,
        seed=1234,
        load=False,
        auto=True,
    ):
        r"""
        Initialize GRAPE (GRadient Ascent Pulse Engineering) control optimization.

        Supports both standard GRAPE and automatic-differentiation GRAPE (auto-GRAPE)
        with optional Adam optimizer.

        Args:
            savefile (bool, optional): Whether to save all control coefficients during
                training. Default ``False``.
            Adam (bool, optional): Whether to use the Adam optimizer. Default ``True``.
            ctrl0 (list of arrays, optional): Initial guesses of control coefficients.
                Default ``[]``.
            max_episode (int, optional): Number of training episodes. Default 300.
            epsilon (float, optional): Learning rate. Default 0.01.
            beta1 (float, optional): Exponential decay rate for first moment
                estimates (Adam). Default 0.90.
            beta2 (float, optional): Exponential decay rate for second moment
                estimates (Adam). Default 0.99.
            eps (float, optional): Machine epsilon. Default 1e-8.
            seed (int, optional): Random seed. Default 1234.
            load (bool, optional): Whether to load initial control coefficients
                from ``controls.csv``. Default ``False``.
            auto (bool, optional): Whether to use automatic differentiation.
                Default ``True``.
        """

        Control.ControlSystem.__init__(self, savefile, ctrl0, seed, eps, load)

        self.Adam = Adam
        self.max_episode = max_episode
        self.epsilon = epsilon
        self.beta1 = beta1
        self.beta2 = beta2
        self.mt = 0.0
        self.vt = 0.0
        self.auto = auto

    def QFIM(self, W=None, LDtype="SLD"):
        r"""
        Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
        In single parameter estimation the objective function is QFI and in 
        multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
        """

        if W is None:
            W = []

        if self.auto:
            if self.Adam:
                self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
            else:
                self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
        else:
            if (len(self.tspan) - 1) != len(self.control_coefficients[0]):
                warnings.warn("GRAPE is not available when the length of each control is not \
                               equal to the length of time, and is replaced by auto-GRAPE.",
                               DeprecationWarning)
                #### call autoGRAPE automatically ####
                if self.Adam:
                    self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
                else:
                    self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
            else:
                if LDtype == "SLD":
                    if self.Adam:
                        self.alg = QJL.GRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
                    else:
                        self.alg = QJL.GRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
                else:
                    raise ValueError("GRAPE is only available when LDtype is SLD.")

        super().QFIM(W, LDtype)

    def CFIM(self, M=None, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if M is None:
            M = []
        if W is None:
            W = []

        if self.auto:
            if self.Adam:
                self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
            else:
                self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
        else:
            if (len(self.tspan) - 1) != len(self.control_coefficients[0]):
                warnings.warn("GRAPE is not available when the length of each control is not \
                               equal to the length of time, and is replaced by auto-GRAPE.",
                               DeprecationWarning)
                #### call autoGRAPE automatically ####
                if self.Adam:
                    self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
                else:
                    self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
            else:    
                if self.Adam:
                    self.alg = QJL.GRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
                else:
                    self.alg = QJL.GRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)

        super().CFIM(M, W)

    def HCRB(self, W=None):
        """
        GRAPE and auto-GRAPE are not available when the objective function is HCRB. 
        Supported methods are PSO, DE and DDPG.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None:
            W = []
        raise ValueError(
            "GRAPE and auto-GRAPE are not available when the objective function is HCRB. Supported methods are 'PSO', 'DE' and 'DDPG'.",
        )

    def mintime(self, f, W=None, M=None, method="binary", target="QFIM", LDtype="SLD"):
        """
        Search of the minimum time to reach a given value of the objective function.

        Parameters
        ----------
        > **f:** `float`
            -- The given value of the objective function.

        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        > **method:** `string`
            -- Methods for searching the minimum time to reach the given value of the 
            objective function. Options are:  
            "binary" (default) -- Binary search (logarithmic search).  
            "forward" -- Forward search from the beginning of time.  

        > **target:** `string`
            -- Objective functions for searching the minimum time to reach the given 
            value of the objective function. Options are:  
            "QFIM" (default) -- Choose QFI (QFIM) as the objective function.  
            "CFIM" -- Choose CFI (CFIM) as the objective function.  
            "HCRB" -- Choose HCRB as the objective function.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if W is None:
            W = []
        if M is None:
            M = []

        if target == "HCRB":
            raise ValueError(
                "GRAPE and auto-GRAPE are not available when the objective function is HCRB. Supported methods are 'PSO', 'DE' and 'DDPG'.",
            )
        if self.auto:
            if self.Adam:
                self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
            else:
                self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
        else:

            if self.Adam:
                self.alg = QJL.GRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
            else:
                self.alg = QJL.GRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)

        super().mintime(f, W, M, method, target, LDtype)

CFIM(M=None, W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ControlOpt/GRAPE_Copt.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def CFIM(self, M=None, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if M is None:
        M = []
    if W is None:
        W = []

    if self.auto:
        if self.Adam:
            self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
        else:
            self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
    else:
        if (len(self.tspan) - 1) != len(self.control_coefficients[0]):
            warnings.warn("GRAPE is not available when the length of each control is not \
                           equal to the length of time, and is replaced by auto-GRAPE.",
                           DeprecationWarning)
            #### call autoGRAPE automatically ####
            if self.Adam:
                self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
            else:
                self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
        else:    
            if self.Adam:
                self.alg = QJL.GRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
            else:
                self.alg = QJL.GRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)

    super().CFIM(M, W)

HCRB(W=None)

GRAPE and auto-GRAPE are not available when the objective function is HCRB. Supported methods are PSO, DE and DDPG.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ControlOpt/GRAPE_Copt.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def HCRB(self, W=None):
    """
    GRAPE and auto-GRAPE are not available when the objective function is HCRB. 
    Supported methods are PSO, DE and DDPG.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None:
        W = []
    raise ValueError(
        "GRAPE and auto-GRAPE are not available when the objective function is HCRB. Supported methods are 'PSO', 'DE' and 'DDPG'.",
    )

QFIM(W=None, LDtype='SLD')

Choose QFI or \(\mathrm{Tr}(WF^{-1})\) as the objective function. In single parameter estimation the objective function is QFI and in multiparameter estimation it will be \(\mathrm{Tr}(WF^{-1})\).

Parameters

W: matrix -- Weight matrix.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Source code in quanestimation/base/ControlOpt/GRAPE_Copt.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def QFIM(self, W=None, LDtype="SLD"):
    r"""
    Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
    In single parameter estimation the objective function is QFI and in 
    multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
    """

    if W is None:
        W = []

    if self.auto:
        if self.Adam:
            self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
        else:
            self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
    else:
        if (len(self.tspan) - 1) != len(self.control_coefficients[0]):
            warnings.warn("GRAPE is not available when the length of each control is not \
                           equal to the length of time, and is replaced by auto-GRAPE.",
                           DeprecationWarning)
            #### call autoGRAPE automatically ####
            if self.Adam:
                self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
            else:
                self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
        else:
            if LDtype == "SLD":
                if self.Adam:
                    self.alg = QJL.GRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
                else:
                    self.alg = QJL.GRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
            else:
                raise ValueError("GRAPE is only available when LDtype is SLD.")

    super().QFIM(W, LDtype)

__init__(savefile=False, Adam=True, ctrl0=[], max_episode=300, epsilon=0.01, beta1=0.9, beta2=0.99, eps=1e-08, seed=1234, load=False, auto=True)

Initialize GRAPE (GRadient Ascent Pulse Engineering) control optimization.

Supports both standard GRAPE and automatic-differentiation GRAPE (auto-GRAPE) with optional Adam optimizer.

Parameters:

Name Type Description Default
savefile bool

Whether to save all control coefficients during training. Default False.

False
Adam bool

Whether to use the Adam optimizer. Default True.

True
ctrl0 list of arrays

Initial guesses of control coefficients. Default [].

[]
max_episode int

Number of training episodes. Default 300.

300
epsilon float

Learning rate. Default 0.01.

0.01
beta1 float

Exponential decay rate for first moment estimates (Adam). Default 0.90.

0.9
beta2 float

Exponential decay rate for second moment estimates (Adam). Default 0.99.

0.99
eps float

Machine epsilon. Default 1e-8.

1e-08
seed int

Random seed. Default 1234.

1234
load bool

Whether to load initial control coefficients from controls.csv. Default False.

False
auto bool

Whether to use automatic differentiation. Default True.

True
Source code in quanestimation/base/ControlOpt/GRAPE_Copt.py
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def __init__(
    self,
    savefile=False,
    Adam=True,
    ctrl0=[],
    max_episode=300,
    epsilon=0.01,
    beta1=0.90,
    beta2=0.99,
    eps=1e-8,
    seed=1234,
    load=False,
    auto=True,
):
    r"""
    Initialize GRAPE (GRadient Ascent Pulse Engineering) control optimization.

    Supports both standard GRAPE and automatic-differentiation GRAPE (auto-GRAPE)
    with optional Adam optimizer.

    Args:
        savefile (bool, optional): Whether to save all control coefficients during
            training. Default ``False``.
        Adam (bool, optional): Whether to use the Adam optimizer. Default ``True``.
        ctrl0 (list of arrays, optional): Initial guesses of control coefficients.
            Default ``[]``.
        max_episode (int, optional): Number of training episodes. Default 300.
        epsilon (float, optional): Learning rate. Default 0.01.
        beta1 (float, optional): Exponential decay rate for first moment
            estimates (Adam). Default 0.90.
        beta2 (float, optional): Exponential decay rate for second moment
            estimates (Adam). Default 0.99.
        eps (float, optional): Machine epsilon. Default 1e-8.
        seed (int, optional): Random seed. Default 1234.
        load (bool, optional): Whether to load initial control coefficients
            from ``controls.csv``. Default ``False``.
        auto (bool, optional): Whether to use automatic differentiation.
            Default ``True``.
    """

    Control.ControlSystem.__init__(self, savefile, ctrl0, seed, eps, load)

    self.Adam = Adam
    self.max_episode = max_episode
    self.epsilon = epsilon
    self.beta1 = beta1
    self.beta2 = beta2
    self.mt = 0.0
    self.vt = 0.0
    self.auto = auto

mintime(f, W=None, M=None, method='binary', target='QFIM', LDtype='SLD')

Search of the minimum time to reach a given value of the objective function.

Parameters

f: float -- The given value of the objective function.

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

method: string -- Methods for searching the minimum time to reach the given value of the objective function. Options are:
"binary" (default) -- Binary search (logarithmic search).
"forward" -- Forward search from the beginning of time.

target: string -- Objective functions for searching the minimum time to reach the given value of the objective function. Options are:
"QFIM" (default) -- Choose QFI (QFIM) as the objective function.
"CFIM" -- Choose CFI (CFIM) as the objective function.
"HCRB" -- Choose HCRB as the objective function.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ControlOpt/GRAPE_Copt.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
def mintime(self, f, W=None, M=None, method="binary", target="QFIM", LDtype="SLD"):
    """
    Search of the minimum time to reach a given value of the objective function.

    Parameters
    ----------
    > **f:** `float`
        -- The given value of the objective function.

    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    > **method:** `string`
        -- Methods for searching the minimum time to reach the given value of the 
        objective function. Options are:  
        "binary" (default) -- Binary search (logarithmic search).  
        "forward" -- Forward search from the beginning of time.  

    > **target:** `string`
        -- Objective functions for searching the minimum time to reach the given 
        value of the objective function. Options are:  
        "QFIM" (default) -- Choose QFI (QFIM) as the objective function.  
        "CFIM" -- Choose CFI (CFIM) as the objective function.  
        "HCRB" -- Choose HCRB as the objective function.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if W is None:
        W = []
    if M is None:
        M = []

    if target == "HCRB":
        raise ValueError(
            "GRAPE and auto-GRAPE are not available when the objective function is HCRB. Supported methods are 'PSO', 'DE' and 'DDPG'.",
        )
    if self.auto:
        if self.Adam:
            self.alg = QJL.autoGRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
        else:
            self.alg = QJL.autoGRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)
    else:

        if self.Adam:
            self.alg = QJL.GRAPE(Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2)
        else:
            self.alg = QJL.GRAPE(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)

    super().mintime(f, W, M, method, target, LDtype)

Control Optimization with PSO

Bases: ControlSystem

Attributes

savefile: bool -- Whether or not to save all the control coeffients.
If set True then the control coefficients and the values of the objective function obtained in all episodes will be saved during the training. If set False the control coefficients in the final episode and the values of the objective function in all episodes will be saved.

p_num: int -- The number of particles.

ctrl0: list of arrays -- Initial guesses of control coefficients.

max_episode: int or list -- If it is an integer, for example max_episode=1000, it means the program will continuously run 1000 episodes. However, if it is an array, for example max_episode=[1000,100], the program will run 1000 episodes in total but replace control coefficients of all the particles with global best every 100 episodes.

c0: float -- The damping factor that assists convergence, also known as inertia weight.

c1: float -- The exploitation weight that attracts the particle to its best previous position, also known as cognitive learning factor.

c2: float -- The exploitation weight that attracts the particle to the best position
in the neighborhood, also known as social learning factor.

seed: int -- Random seed.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load control coefficients in the current location.
If set True then the program will load control coefficients from "controls.csv" file in the current location and use it as the initial control coefficients.

Source code in quanestimation/base/ControlOpt/PSO_Copt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
class PSO_Copt(Control.ControlSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the control coeffients.  
        If set `True` then the control coefficients and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the control coefficients in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **p_num:** `int`
        -- The number of particles.

    > **ctrl0:** `list of arrays`
        -- Initial guesses of control coefficients.

    > **max_episode:** `int or list`
        -- If it is an integer, for example max_episode=1000, it means the 
        program will continuously run 1000 episodes. However, if it is an
        array, for example max_episode=[1000,100], the program will run 
        1000 episodes in total but replace control coefficients of all
        the particles with global best every 100 episodes.

    > **c0:** `float`
        -- The damping factor that assists convergence, also known as inertia weight.

    > **c1:** `float`
        -- The exploitation weight that attracts the particle to its best previous 
        position, also known as cognitive learning factor.

    > **c2:** `float`
        -- The exploitation weight that attracts the particle to the best position  
        in the neighborhood, also known as social learning factor.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load control coefficients in the current location.  
        If set `True` then the program will load control coefficients from 
        "controls.csv" file in the current location and use it as the initial 
        control coefficients.
    """

    def __init__(
        self,
        savefile=False,
        p_num=10,
        ctrl0=[],
        max_episode=[1000, 100],
        c0=1.0,
        c1=2.0,
        c2=2.0,
        seed=1234,
        eps=1e-8,
        load=False,
    ):
        r"""
        Initialize particle swarm optimization for control.

        Args:
            savefile (bool, optional): Whether to save all control coefficients
                during training. Default ``False``.
            p_num (int, optional): Number of particles. Default 10.
            ctrl0 (list of arrays, optional): Initial guesses of control
                coefficients. Default ``[]``.
            max_episode (int or list, optional): Number of episodes. If a list
                ``[total, reset_interval]``, particles are reset to the global
                best every ``reset_interval`` episodes. Default ``[1000, 100]``.
            c0 (float, optional): Inertia weight (damping factor).
                Default 1.0.
            c1 (float, optional): Cognitive learning factor. Default 2.0.
            c2 (float, optional): Social learning factor. Default 2.0.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
            load (bool, optional): Whether to load initial control coefficients
                from ``controls.csv``. Default ``False``.
        """

        Control.ControlSystem.__init__(self, savefile, ctrl0, seed, eps, load)

        is_int = isinstance(max_episode, int)
        self.max_episode = max_episode if is_int else QJL.Vector[QJL.Int64](max_episode)
        self.p_num = p_num
        self.c0 = c0
        self.c1 = c1
        self.c2 = c2

    def QFIM(self, W=None, LDtype="SLD"):
        r"""
        Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
        In single parameter estimation the objective function is QFI and in 
        multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
        """

        if W is None:
            W = []

        ini_particle = (QJL.Vector([self.ctrl0]), )
        self.alg = QJL.PSO(max_episode=self.max_episode, p_num=self.p_num, ini_particle=ini_particle, c0=self.c0, c1=self.c1, c2=self.c2)

        super().QFIM(W, LDtype)

    def CFIM(self, M=None, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if M is None:
            M = []
        if W is None:
            W = []

        ini_particle = (QJL.Vector([self.ctrl0]), )
        self.alg = QJL.PSO(max_episode=self.max_episode, p_num=self.p_num, ini_particle=ini_particle, c0=self.c0, c1=self.c1, c2=self.c2)

        super().CFIM(M, W)

    def HCRB(self, W=None):
        """
        Choose HCRB as the objective function. 

        **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose 
        QFI as the objective function.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None:
            W = []

        ini_particle = (QJL.Vector([self.ctrl0]), )
        self.alg = QJL.PSO(max_episode=self.max_episode, p_num=self.p_num, ini_particle=ini_particle, c0=self.c0, c1=self.c1, c2=self.c2)

        super().HCRB(W)

    def mintime(self, f, W=None, M=None, method="binary", target="QFIM", LDtype="SLD"):
        """
        Search of the minimum time to reach a given value of the objective function.

        Parameters
        ----------
        > **f:** `float`
            -- The given value of the objective function.

        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        > **method:** `string`
            -- Methods for searching the minimum time to reach the given value of the 
            objective function. Options are:  
            "binary" (default) -- Binary search (logarithmic search).  
            "forward" -- Forward search from the beginning of time.

        > **target:** `string`
            -- Objective functions for searching the minimum time to reach the given 
            value of the objective function. Options are:  
            "QFIM" (default) -- Choose QFI (QFIM) as the objective function.  
            "CFIM" -- Choose CFI (CFIM) as the objective function.  
            "HCRB" -- Choose HCRB as the objective function.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if W is None:
            W = []
        if M is None:
            M = []

        ini_particle = (QJL.Vector([self.ctrl0]),)
        self.alg = QJL.PSO(max_episode=self.max_episode, p_num=self.p_num, ini_particle=ini_particle, c0=self.c0, c1=self.c1, c2=self.c2)

        super().mintime(f, W, M, method, target, LDtype)

CFIM(M=None, W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ControlOpt/PSO_Copt.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def CFIM(self, M=None, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if M is None:
        M = []
    if W is None:
        W = []

    ini_particle = (QJL.Vector([self.ctrl0]), )
    self.alg = QJL.PSO(max_episode=self.max_episode, p_num=self.p_num, ini_particle=ini_particle, c0=self.c0, c1=self.c1, c2=self.c2)

    super().CFIM(M, W)

HCRB(W=None)

Choose HCRB as the objective function.

Note: in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ControlOpt/PSO_Copt.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def HCRB(self, W=None):
    """
    Choose HCRB as the objective function. 

    **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose 
    QFI as the objective function.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None:
        W = []

    ini_particle = (QJL.Vector([self.ctrl0]), )
    self.alg = QJL.PSO(max_episode=self.max_episode, p_num=self.p_num, ini_particle=ini_particle, c0=self.c0, c1=self.c1, c2=self.c2)

    super().HCRB(W)

QFIM(W=None, LDtype='SLD')

Choose QFI or \(\mathrm{Tr}(WF^{-1})\) as the objective function. In single parameter estimation the objective function is QFI and in multiparameter estimation it will be \(\mathrm{Tr}(WF^{-1})\).

Parameters

W: matrix -- Weight matrix.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Source code in quanestimation/base/ControlOpt/PSO_Copt.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def QFIM(self, W=None, LDtype="SLD"):
    r"""
    Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
    In single parameter estimation the objective function is QFI and in 
    multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
    """

    if W is None:
        W = []

    ini_particle = (QJL.Vector([self.ctrl0]), )
    self.alg = QJL.PSO(max_episode=self.max_episode, p_num=self.p_num, ini_particle=ini_particle, c0=self.c0, c1=self.c1, c2=self.c2)

    super().QFIM(W, LDtype)

__init__(savefile=False, p_num=10, ctrl0=[], max_episode=[1000, 100], c0=1.0, c1=2.0, c2=2.0, seed=1234, eps=1e-08, load=False)

Initialize particle swarm optimization for control.

Parameters:

Name Type Description Default
savefile bool

Whether to save all control coefficients during training. Default False.

False
p_num int

Number of particles. Default 10.

10
ctrl0 list of arrays

Initial guesses of control coefficients. Default [].

[]
max_episode int or list

Number of episodes. If a list [total, reset_interval], particles are reset to the global best every reset_interval episodes. Default [1000, 100].

[1000, 100]
c0 float

Inertia weight (damping factor). Default 1.0.

1.0
c1 float

Cognitive learning factor. Default 2.0.

2.0
c2 float

Social learning factor. Default 2.0.

2.0
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
load bool

Whether to load initial control coefficients from controls.csv. Default False.

False
Source code in quanestimation/base/ControlOpt/PSO_Copt.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def __init__(
    self,
    savefile=False,
    p_num=10,
    ctrl0=[],
    max_episode=[1000, 100],
    c0=1.0,
    c1=2.0,
    c2=2.0,
    seed=1234,
    eps=1e-8,
    load=False,
):
    r"""
    Initialize particle swarm optimization for control.

    Args:
        savefile (bool, optional): Whether to save all control coefficients
            during training. Default ``False``.
        p_num (int, optional): Number of particles. Default 10.
        ctrl0 (list of arrays, optional): Initial guesses of control
            coefficients. Default ``[]``.
        max_episode (int or list, optional): Number of episodes. If a list
            ``[total, reset_interval]``, particles are reset to the global
            best every ``reset_interval`` episodes. Default ``[1000, 100]``.
        c0 (float, optional): Inertia weight (damping factor).
            Default 1.0.
        c1 (float, optional): Cognitive learning factor. Default 2.0.
        c2 (float, optional): Social learning factor. Default 2.0.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
        load (bool, optional): Whether to load initial control coefficients
            from ``controls.csv``. Default ``False``.
    """

    Control.ControlSystem.__init__(self, savefile, ctrl0, seed, eps, load)

    is_int = isinstance(max_episode, int)
    self.max_episode = max_episode if is_int else QJL.Vector[QJL.Int64](max_episode)
    self.p_num = p_num
    self.c0 = c0
    self.c1 = c1
    self.c2 = c2

mintime(f, W=None, M=None, method='binary', target='QFIM', LDtype='SLD')

Search of the minimum time to reach a given value of the objective function.

Parameters

f: float -- The given value of the objective function.

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

method: string -- Methods for searching the minimum time to reach the given value of the objective function. Options are:
"binary" (default) -- Binary search (logarithmic search).
"forward" -- Forward search from the beginning of time.

target: string -- Objective functions for searching the minimum time to reach the given value of the objective function. Options are:
"QFIM" (default) -- Choose QFI (QFIM) as the objective function.
"CFIM" -- Choose CFI (CFIM) as the objective function.
"HCRB" -- Choose HCRB as the objective function.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ControlOpt/PSO_Copt.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def mintime(self, f, W=None, M=None, method="binary", target="QFIM", LDtype="SLD"):
    """
    Search of the minimum time to reach a given value of the objective function.

    Parameters
    ----------
    > **f:** `float`
        -- The given value of the objective function.

    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    > **method:** `string`
        -- Methods for searching the minimum time to reach the given value of the 
        objective function. Options are:  
        "binary" (default) -- Binary search (logarithmic search).  
        "forward" -- Forward search from the beginning of time.

    > **target:** `string`
        -- Objective functions for searching the minimum time to reach the given 
        value of the objective function. Options are:  
        "QFIM" (default) -- Choose QFI (QFIM) as the objective function.  
        "CFIM" -- Choose CFI (CFIM) as the objective function.  
        "HCRB" -- Choose HCRB as the objective function.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if W is None:
        W = []
    if M is None:
        M = []

    ini_particle = (QJL.Vector([self.ctrl0]),)
    self.alg = QJL.PSO(max_episode=self.max_episode, p_num=self.p_num, ini_particle=ini_particle, c0=self.c0, c1=self.c1, c2=self.c2)

    super().mintime(f, W, M, method, target, LDtype)

Control Optimization DE

Bases: ControlSystem

Attributes

savefile: bool --Whether or not to save all the control coeffients.
If set True then the control coefficients and the values of the objective function obtained in all episodes will be saved during the training. If set False the control coefficients in the final episode and the values of the objective function in all episodes will be saved.

p_num: int -- The number of populations.

ctrl0: list of arrays -- Initial guesses of control coefficients.

max_episode: int -- The number of episodes.

c: float -- Mutation constant.

cr: float -- Crossover constant.

seed: int -- Random seed.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load control coefficients in the current location.
If set True then the program will load control coefficients from "controls.csv" file in the current location and use it as the initial control coefficients.

Source code in quanestimation/base/ControlOpt/DE_Copt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
class DE_Copt(Control.ControlSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        --Whether or not to save all the control coeffients.  
        If set `True` then the control coefficients and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the control coefficients in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **p_num:** `int`
        -- The number of populations.

    > **ctrl0:** list of arrays
        -- Initial guesses of control coefficients.

    > **max_episode:** `int`
        -- The number of episodes.

    > **c:** `float`
        -- Mutation constant.

    > **cr:** `float`
        -- Crossover constant.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load control coefficients in the current location.  
        If set `True` then the program will load control coefficients from 
        "controls.csv" file in the current location and use it as the initial 
        control coefficients.
    """

    def __init__(
        self,
        savefile=False,
        p_num=10,
        ctrl0=[],
        max_episode=1000,
        c=1.0,
        cr=0.5,
        seed=1234,
        eps=1e-8,
        load=False,
    ):
        r"""
        Initialize differential evolution (DE) for control optimization.

        Args:
            savefile (bool, optional): Whether to save all control coefficients
                during training. Default ``False``.
            p_num (int, optional): Number of populations. Default 10.
            ctrl0 (list of arrays, optional): Initial guesses of control
                coefficients. Default ``[]``.
            max_episode (int, optional): Number of episodes. Default 1000.
            c (float, optional): Mutation constant. Default 1.0.
            cr (float, optional): Crossover constant. Default 0.5.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
            load (bool, optional): Whether to load initial control coefficients
                from ``controls.csv``. Default ``False``.
        """

        Control.ControlSystem.__init__(self, savefile, ctrl0, seed, eps, load)

        self.p_num = p_num
        self.max_episode = max_episode
        self.c = c
        self.cr = cr

    def QFIM(self, W=None, LDtype="SLD"):
        r"""
        Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
        In single parameter estimation the objective function is QFI and in 
        multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
        """

        if W is None:
            W = []

        ini_population = (QJL.Vector([self.ctrl0]), )
        self.alg = QJL.DE(max_episode=self.max_episode, p_num=self.p_num, ini_population=ini_population, c=self.c, cr=self.cr)

        super().QFIM(W, LDtype)

    def CFIM(self, M=None, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if M is None:
            M = []
        if W is None:
            W = []

        ini_population = (QJL.Vector([self.ctrl0]), )
        self.alg = QJL.DE(max_episode=self.max_episode, p_num=self.p_num, ini_population=ini_population, c=self.c, cr=self.cr)

        super().CFIM(M, W)

    def HCRB(self, W=None):
        """
        Choose HCRB as the objective function. 

        **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose 
        QFI as the objective function.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None:
            W = []

        ini_population = (QJL.Vector([self.ctrl0]), )
        self.alg = QJL.DE(max_episode=self.max_episode, p_num=self.p_num, ini_population=ini_population, c=self.c, cr=self.cr)

        super().HCRB(W)

    def mintime(self, f, W=None, M=None, method="binary", target="QFIM", LDtype="SLD"):
        """
        Search of the minimum time to reach a given value of the objective function.

        Parameters
        ----------
        > **f:** `float`
            -- The given value of the objective function.

        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        > **method:** `string`
            -- Methods for searching the minimum time to reach the given value of the 
            objective function. Options are:  
            "binary" (default) -- Binary search (logarithmic search).  
            "forward" -- Forward search from the beginning of time.

        > **target:** `string`
            -- Objective functions for searching the minimum time to reach the given 
            value of the objective function. Options are:<br>
            "QFIM" (default) -- Choose QFI (QFIM) as the objective function.<br>
            "CFIM" -- Choose CFI (CFIM) as the objective function.<br>
            "HCRB" -- Choose HCRB as the objective function.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if W is None:
            W = []
        if M is None:
            M = []

        ini_population = (QJL.Vector([self.ctrl0]), )
        self.alg = QJL.DE(max_episode=self.max_episode, p_num=self.p_num, ini_population=ini_population, c=self.c, cr=self.cr)

        super().mintime(f, W, M, method, target, LDtype)

CFIM(M=None, W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ControlOpt/DE_Copt.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def CFIM(self, M=None, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if M is None:
        M = []
    if W is None:
        W = []

    ini_population = (QJL.Vector([self.ctrl0]), )
    self.alg = QJL.DE(max_episode=self.max_episode, p_num=self.p_num, ini_population=ini_population, c=self.c, cr=self.cr)

    super().CFIM(M, W)

HCRB(W=None)

Choose HCRB as the objective function.

Note: in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ControlOpt/DE_Copt.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def HCRB(self, W=None):
    """
    Choose HCRB as the objective function. 

    **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose 
    QFI as the objective function.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None:
        W = []

    ini_population = (QJL.Vector([self.ctrl0]), )
    self.alg = QJL.DE(max_episode=self.max_episode, p_num=self.p_num, ini_population=ini_population, c=self.c, cr=self.cr)

    super().HCRB(W)

QFIM(W=None, LDtype='SLD')

Choose QFI or \(\mathrm{Tr}(WF^{-1})\) as the objective function. In single parameter estimation the objective function is QFI and in multiparameter estimation it will be \(\mathrm{Tr}(WF^{-1})\).

Parameters

W: matrix -- Weight matrix.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Source code in quanestimation/base/ControlOpt/DE_Copt.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def QFIM(self, W=None, LDtype="SLD"):
    r"""
    Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
    In single parameter estimation the objective function is QFI and in 
    multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
    """

    if W is None:
        W = []

    ini_population = (QJL.Vector([self.ctrl0]), )
    self.alg = QJL.DE(max_episode=self.max_episode, p_num=self.p_num, ini_population=ini_population, c=self.c, cr=self.cr)

    super().QFIM(W, LDtype)

__init__(savefile=False, p_num=10, ctrl0=[], max_episode=1000, c=1.0, cr=0.5, seed=1234, eps=1e-08, load=False)

Initialize differential evolution (DE) for control optimization.

Parameters:

Name Type Description Default
savefile bool

Whether to save all control coefficients during training. Default False.

False
p_num int

Number of populations. Default 10.

10
ctrl0 list of arrays

Initial guesses of control coefficients. Default [].

[]
max_episode int

Number of episodes. Default 1000.

1000
c float

Mutation constant. Default 1.0.

1.0
cr float

Crossover constant. Default 0.5.

0.5
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
load bool

Whether to load initial control coefficients from controls.csv. Default False.

False
Source code in quanestimation/base/ControlOpt/DE_Copt.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __init__(
    self,
    savefile=False,
    p_num=10,
    ctrl0=[],
    max_episode=1000,
    c=1.0,
    cr=0.5,
    seed=1234,
    eps=1e-8,
    load=False,
):
    r"""
    Initialize differential evolution (DE) for control optimization.

    Args:
        savefile (bool, optional): Whether to save all control coefficients
            during training. Default ``False``.
        p_num (int, optional): Number of populations. Default 10.
        ctrl0 (list of arrays, optional): Initial guesses of control
            coefficients. Default ``[]``.
        max_episode (int, optional): Number of episodes. Default 1000.
        c (float, optional): Mutation constant. Default 1.0.
        cr (float, optional): Crossover constant. Default 0.5.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
        load (bool, optional): Whether to load initial control coefficients
            from ``controls.csv``. Default ``False``.
    """

    Control.ControlSystem.__init__(self, savefile, ctrl0, seed, eps, load)

    self.p_num = p_num
    self.max_episode = max_episode
    self.c = c
    self.cr = cr

mintime(f, W=None, M=None, method='binary', target='QFIM', LDtype='SLD')

Search of the minimum time to reach a given value of the objective function.

Parameters

f: float -- The given value of the objective function.

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

method: string -- Methods for searching the minimum time to reach the given value of the objective function. Options are:
"binary" (default) -- Binary search (logarithmic search).
"forward" -- Forward search from the beginning of time.

target: string -- Objective functions for searching the minimum time to reach the given value of the objective function. Options are:
"QFIM" (default) -- Choose QFI (QFIM) as the objective function.
"CFIM" -- Choose CFI (CFIM) as the objective function.
"HCRB" -- Choose HCRB as the objective function.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ControlOpt/DE_Copt.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def mintime(self, f, W=None, M=None, method="binary", target="QFIM", LDtype="SLD"):
    """
    Search of the minimum time to reach a given value of the objective function.

    Parameters
    ----------
    > **f:** `float`
        -- The given value of the objective function.

    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    > **method:** `string`
        -- Methods for searching the minimum time to reach the given value of the 
        objective function. Options are:  
        "binary" (default) -- Binary search (logarithmic search).  
        "forward" -- Forward search from the beginning of time.

    > **target:** `string`
        -- Objective functions for searching the minimum time to reach the given 
        value of the objective function. Options are:<br>
        "QFIM" (default) -- Choose QFI (QFIM) as the objective function.<br>
        "CFIM" -- Choose CFI (CFIM) as the objective function.<br>
        "HCRB" -- Choose HCRB as the objective function.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if W is None:
        W = []
    if M is None:
        M = []

    ini_population = (QJL.Vector([self.ctrl0]), )
    self.alg = QJL.DE(max_episode=self.max_episode, p_num=self.p_num, ini_population=ini_population, c=self.c, cr=self.cr)

    super().mintime(f, W, M, method, target, LDtype)

State Optimization

The probe state is expanded as \(|\psi\rangle=\sum_i c_i|i\rangle\) in a specific basis, i.e., \(\{|i\rangle\}\). In state optimization, the search of the optimal probe states is equal to search of the normalized complex coefficients \(\{c_i\}\). In QuanEstimation, the state optimization algorithms are automatic differentiation (AD), reverse iterative (RI) algorithm, particle swarm optimization (PSO), differential evolution (DE), deep deterministic policy gradients (DDPG) and Nelder-Mead (NM).

Base

Attributes

savefile: bool -- Whether or not to save all the states. If set True then the states and the values of the objective function obtained in all episodes will be saved during the training. If set False the state in the final episode and the values of the objective function in all episodes will be saved.

psi0: list of arrays -- Initial guesses of states.

seed: int -- Random seed.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load states in the current location. If set True then the program will load state from "states.csv" file in the current location and use it as the initial state.

Source code in quanestimation/base/StateOpt/StateStruct.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
class StateSystem:
    """
    Attributes
    ----------
    > **savefile:**  `bool`
        -- Whether or not to save all the states.
        If set `True` then the states and the values of the objective function 
        obtained in all episodes will be saved during the training. If set `False` 
        the state in the final episode and the values of the objective function in 
        all episodes will be saved.

    > **psi0:** `list of arrays`
        -- Initial guesses of states.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load states in the current location.
        If set `True` then the program will load state from "states.csv"
        file in the current location and use it as the initial state.
    """

    def __init__(self, savefile, psi0, seed, eps, load):
        r"""
        Initialize the state optimization system.

        Args:
            savefile (bool): Whether to save all states during training.
                If ``True``, all episodes are saved; if ``False``, only the
                final episode is saved.
            psi0 (list of arrays): Initial guesses of probe states.
            seed (int): Random seed.
            eps (float): Machine epsilon.
            load (bool): Whether to load states from ``states.npy`` in the
                current directory as initial values.
        """

        self.savefile = savefile
        self.psi0 = psi0
        self.psi = psi0
        self.eps = eps
        self.seed = seed

        if load == True:
            if os.path.exists("states.npy"):
                self.psi0 = [np.load("states.npy", dtype=np.complex128)]

    def load_save(self, max_episode):
        r"""
        Load optimized states saved by the Julia backend from ``states.dat``
        and save them as a ``states.npy`` file.

        The Julia backend writes ``states.dat`` atomically (temp → rename),
        and the file is deleted after a successful read to avoid stale data
        from interfering with subsequent runs.

        Args:
            max_episode (int): Maximum number of episodes.
        """
        load_and_save("states.dat", "states", "states",
                       self.savefile, max_episode=max_episode,
                       nested=False, complex_view=True)

    def dynamics(self, tspan, H0, dH, Hc=None, ctrl=None, decay=None, dyn_method="expm"):
        r"""
        The dynamics of a density matrix is of the form 

        \begin{align}
        \partial_t\rho &=\mathcal{L}\rho \nonumber \\
        &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
        \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
        \end{align}

        where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
        system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
        operator and corresponding decay rate.

        Parameters
        ----------
        > **tspan:** `array`
            -- Time length for the evolution.

        > **H0:** `matrix or list`
            -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time-
            independent and a list of length equal to `tspan` when it is time-dependent.

        > **dH:** `list`
            -- Derivatives of the free Hamiltonian on the unknown parameters to be 
            estimated. For example, dH[0] is the derivative vector on the first 
            parameter.

        > **Hc:** `list`
            -- Control Hamiltonians.

        > **ctrl:** `list of arrays`
            -- Control coefficients.

        > **decay:** `list`
            -- Decay operators and the corresponding decay rates. Its input rule is 
            decay=[[$\Gamma_1$, $\gamma_1$], [$\Gamma_2$,$\gamma_2$],...], where $\Gamma_1$ 
            $(\Gamma_2)$ represents the decay operator and $\gamma_1$ $(\gamma_2)$ is the 
            corresponding decay rate.

        > **dyn_method:** `string`
            -- Setting the method for solving the Lindblad dynamics. Options are:  
            "expm" (default) -- Matrix exponential.  
            "ode" -- Solving the differential equations directly.
        """

        if Hc is None:
            Hc = []
        if ctrl is None:
            ctrl = []
        if decay is None:
            decay = []
        self.tspan = tspan

        if dyn_method == "expm":
            self.dyn_method = "Expm"
        elif dyn_method == "ode":
            self.dyn_method = "Ode"

        if Hc == [] or ctrl == []:
            if isinstance(H0, np.ndarray):
                self.freeHamiltonian = np.array(H0, dtype=np.complex128)
                self.dim = len(self.freeHamiltonian)
            else:
                self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0]
                self.dim = len(self.freeHamiltonian[0])
        else:
            ctrl_num = len(ctrl)
            Hc_num = len(Hc)
            if Hc_num < ctrl_num:
                raise TypeError(
                    "There are %d control Hamiltonians but %d coefficients sequences: \
                                 too many coefficients sequences."
                    % (Hc_num, ctrl_num)
                )
            elif Hc_num > ctrl_num:
                warnings.warn(
                    "Not enough coefficients sequences: there are %d control Hamiltonians \
                               but %d coefficients sequences. The rest of the control sequences are\
                               set to be 0."
                    % (Hc_num, ctrl_num),
                    DeprecationWarning,
                )
                for i in range(Hc_num - ctrl_num):
                    ctrl = np.concatenate((ctrl, np.zeros(len(ctrl[0]))))

            if len(ctrl[0]) == 1:
                if isinstance(H0, np.ndarray):
                    H0 = np.array(H0, dtype=np.complex128)
                    Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                    Htot = H0 + sum([Hc[i] * ctrl[i][0] for i in range(ctrl_num)])
                    self.freeHamiltonian = np.array(Htot, dtype=np.complex128)
                    self.dim = len(self.freeHamiltonian)
                else:
                    H0 = [np.array(x, dtype=np.complex128) for x in H0]
                    Htot = []
                    for i in range(len(H0)):
                        Htot.append(
                            H0[i] + sum([Hc[i] * ctrl[i][0] for i in range(ctrl_num)])
                        )
                    self.freeHamiltonian = [
                        np.array(x, dtype=np.complex128) for x in Htot
                    ]
                    self.dim = len(self.freeHamiltonian[0])
            else:
                if not isinstance(H0, np.ndarray):
                    #### linear interpolation  ####
                    f = interp1d(self.tspan, H0, axis=0)
                number = math.ceil((len(self.tspan) - 1) / len(ctrl[0]))
                if (len(self.tspan) - 1) % len(ctrl[0]) != 0:
                    tnum = number * len(ctrl[0])
                    self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
                    if not isinstance(H0, np.ndarray):
                        H0_inter = f(self.tspan)
                        H0 = [np.array(x, dtype=np.complex128) for x in H0_inter]

                if isinstance(H0, np.ndarray):
                    H0 = np.array(H0, dtype=np.complex128)
                    Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                    ctrl = [np.array(ctrl[i]).repeat(number) for i in range(len(Hc))]
                    Htot = []
                    for i in range(len(ctrl[0])):
                        S_ctrl = sum([Hc[j] * ctrl[j][i] for j in range(len(ctrl))])
                        Htot.append(H0 + S_ctrl)
                    self.freeHamiltonian = [
                        np.array(x, dtype=np.complex128) for x in Htot
                    ]
                    self.dim = len(self.freeHamiltonian)
                else:
                    H0 = [np.array(x, dtype=np.complex128) for x in H0]
                    Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                    ctrl = [np.array(ctrl[i]).repeat(number) for i in range(len(Hc))]
                    Htot = []
                    for i in range(len(ctrl[0])):
                        S_ctrl = sum([Hc[j] * ctrl[j][i] for j in range(len(ctrl))])
                        Htot.append(H0[i] + S_ctrl)
                    self.freeHamiltonian = [
                        np.array(x, dtype=np.complex128) for x in Htot
                    ]
                    self.dim = len(self.freeHamiltonian[0])

        QJLType_psi = QJL.Vector[QJL.Vector[QJL.ComplexF64]]
        if self.psi0 == []:
            np.random.seed(self.seed)
            r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
            r = r_ini / np.linalg.norm(r_ini)
            phi = 2 * np.pi * np.random.random(self.dim)
            psi0 = [r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)]
            self.psi0 = np.array(psi0)
            self.psi = QJL.convert(QJLType_psi, [self.psi0]) # Initial guesses of states (a list of arrays)
        else:
            self.psi0 = np.array(self.psi0[0], dtype=np.complex128)
            self.psi = QJL.convert(QJLType_psi, self.psi)

        if not isinstance(dH, list):
            raise TypeError("The derivative of Hamiltonian should be a list!")

        if dH == []:
            dH = [np.zeros((len(self.psi0), len(self.psi0)))]
        self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]

        if decay == []:
            decay_opt = [np.zeros((len(self.psi0), len(self.psi0)))]
            self.gamma = [0.0]
        else:
            decay_opt = [decay[i][0] for i in range(len(decay))]
            self.gamma = [decay[i][1] for i in range(len(decay))]
        self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

        self.opt = QJL.StateOpt(psi=jlconvert(jl.Vector[jl.ComplexF64], self.psi0), seed=self.seed)
        if any(self.gamma):
            decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
            dynamics = QJL.Lindblad(
                self.freeHamiltonian,
                self.Hamiltonian_derivative,
                self.tspan,
                decay=decay,
                dyn_method=self.dyn_method,
            )
        else:
            dynamics = QJL.Lindblad(
                self.freeHamiltonian,
                self.Hamiltonian_derivative,
                self.tspan,
                dyn_method=self.dyn_method,
            )
        scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
        self.scheme = scheme

        self.dynamics_type = "dynamics"
        if len(self.Hamiltonian_derivative) == 1:
            self.para_type = "single_para"
        else:
            self.para_type = "multi_para"

    def Kraus(self, K, dK):
        r"""
        The parameterization of a state is
        \begin{align}
        \rho=\sum_i K_i\rho_0K_i^{\dagger},
        \end{align} 

        where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator.

        Parameters
        ----------
        > **K:** `list`
            -- Kraus operators.

        > **dK:** `list`
            -- Derivatives of the Kraus operators on the unknown parameters to be 
            estimated. For example, dK[0] is the derivative vector on the first 
            parameter.
        """

        k_num = len(K)
        para_num = len(dK[0])
        self.para_num = para_num
        self.K = [np.array(x, dtype=np.complex128) for x in K]
        self.dK = [
            [np.array(dK[i][j], dtype=np.complex128) for j in range(para_num)]
            for i in range(k_num)
        ]

        self.dim = len(self.K[0])

        if self.psi0 == []:
            np.random.seed(self.seed)
            r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
            r = r_ini / np.linalg.norm(r_ini)
            phi = 2 * np.pi * np.random.random(self.dim)
            psi0 = [r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)]
            self.psi0 = np.array(psi0)  # Initial state (an array)
            self.psi = [self.psi0] # Initial guesses of states (a list of arrays)
        else:
            self.psi0 = np.array(self.psi0[0], dtype=np.complex128)
            self.psi = [np.array(psi, dtype=np.complex128) for psi in self.psi]

        self.opt = QJL.StateOpt(psi=jlconvert(jl.Vector[jl.ComplexF64], self.psi0), seed=self.seed)
        dynamics = QJL.Kraus(self.K, self.dK)
        scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
        self.scheme = scheme

        self.dynamics_type = "Kraus"
        if para_num == 1:
            self.para_type = "single_para"
        else:
            self.para_type = "multi_para"

    def QFIM(self, W=None, LDtype="SLD"):
        r"""
        Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
        In single parameter estimation the objective function is QFI and in 
        multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
        """

        if W is None:
            W = []
        if LDtype != "SLD" and LDtype != "RLD" and LDtype != "LLD":
            raise ValueError(
                "{!r} is not a valid value for LDtype, supported values are 'SLD', 'RLD' and 'LLD'.".format(
                    LDtype
                )
            )

        if self.dynamics_type == "dynamics":
            if W == []:
                W = np.eye(len(self.Hamiltonian_derivative))
            self.W = W

        elif self.dynamics_type == "Kraus":
            if W == []:
                W = np.eye(self.para_num)
            self.W = W

        self.obj = QJL.QFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps, LDtype=jl.Symbol(LDtype))
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save(max_num)

    def CFIM(self, M=None, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if M is None:
            M = []
        if W is None:
            W = []
        if M == []:
            M = SIC(len(self.psi0))
        M = [np.array(x, dtype=np.complex128) for x in M]

        if self.dynamics_type == "dynamics":
            if W == []:
                W = np.eye(len(self.Hamiltonian_derivative))
            self.W = W

        elif self.dynamics_type == "Kraus":
            if W == []:
                W = np.eye(self.para_num)
            self.W = W

        self.obj = QJL.CFIM_obj(M=jlconvert(jl.Vector[jl.Matrix[jl.ComplexF64]], M), W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save(max_num)

    def HCRB(self, W=None):
        """
        Choose HCRB as the objective function. 

        **Notes:** (1) In single parameter estimation, HCRB is equivalent to QFI, please  
        choose QFI as the objective function. (2) GRAPE and auto-GRAPE are not available
        when the objective function is HCRB. Supported methods are PSO, DE and DDPG.

        Parameters
        ----------
        > **W:** `matrix` 
            -- Weight matrix.
        """

        if W is None:
            W = []
        if self.dynamics_type == "dynamics":
            if W == []:
                W = np.eye(len(self.Hamiltonian_derivative))
            self.W = W
            if len(self.Hamiltonian_derivative) == 1:
                print("Program terminated. In the single-parameter scenario, the HCRB is equivalent to the QFI. Please choose 'QFIM' as the objective function"
                    )

        elif self.dynamics_type == "Kraus":
            if W == []:
                W = np.eye(self.para_num)
            self.W = W
            if len(self.dK) == 1:
                raise ValueError(
                    "In single parameter scenario, HCRB is equivalent to QFI. Please choose QFIM as the target function for control optimization",
                )
        else:
            raise ValueError(
                "Supported type of dynamics are Lindblad and Kraus."
                )

        self.obj = QJL.HCRB_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save(max_num)

CFIM(M=None, W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/StateOpt/StateStruct.py
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
def CFIM(self, M=None, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if M is None:
        M = []
    if W is None:
        W = []
    if M == []:
        M = SIC(len(self.psi0))
    M = [np.array(x, dtype=np.complex128) for x in M]

    if self.dynamics_type == "dynamics":
        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

    elif self.dynamics_type == "Kraus":
        if W == []:
            W = np.eye(self.para_num)
        self.W = W

    self.obj = QJL.CFIM_obj(M=jlconvert(jl.Vector[jl.Matrix[jl.ComplexF64]], M), W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save(max_num)

HCRB(W=None)

Choose HCRB as the objective function.

Notes: (1) In single parameter estimation, HCRB is equivalent to QFI, please
choose QFI as the objective function. (2) GRAPE and auto-GRAPE are not available when the objective function is HCRB. Supported methods are PSO, DE and DDPG.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/StateOpt/StateStruct.py
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
def HCRB(self, W=None):
    """
    Choose HCRB as the objective function. 

    **Notes:** (1) In single parameter estimation, HCRB is equivalent to QFI, please  
    choose QFI as the objective function. (2) GRAPE and auto-GRAPE are not available
    when the objective function is HCRB. Supported methods are PSO, DE and DDPG.

    Parameters
    ----------
    > **W:** `matrix` 
        -- Weight matrix.
    """

    if W is None:
        W = []
    if self.dynamics_type == "dynamics":
        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W
        if len(self.Hamiltonian_derivative) == 1:
            print("Program terminated. In the single-parameter scenario, the HCRB is equivalent to the QFI. Please choose 'QFIM' as the objective function"
                )

    elif self.dynamics_type == "Kraus":
        if W == []:
            W = np.eye(self.para_num)
        self.W = W
        if len(self.dK) == 1:
            raise ValueError(
                "In single parameter scenario, HCRB is equivalent to QFI. Please choose QFIM as the target function for control optimization",
            )
    else:
        raise ValueError(
            "Supported type of dynamics are Lindblad and Kraus."
            )

    self.obj = QJL.HCRB_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save(max_num)

Kraus(K, dK)

The parameterization of a state is \begin{align} \rho=\sum_i K_i\rho_0K_i^{\dagger}, \end{align}

where \(\rho\) is the evolved density matrix, \(K_i\) is the Kraus operator.

Parameters

K: list -- Kraus operators.

dK: list -- Derivatives of the Kraus operators on the unknown parameters to be estimated. For example, dK[0] is the derivative vector on the first parameter.

Source code in quanestimation/base/StateOpt/StateStruct.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
def Kraus(self, K, dK):
    r"""
    The parameterization of a state is
    \begin{align}
    \rho=\sum_i K_i\rho_0K_i^{\dagger},
    \end{align} 

    where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator.

    Parameters
    ----------
    > **K:** `list`
        -- Kraus operators.

    > **dK:** `list`
        -- Derivatives of the Kraus operators on the unknown parameters to be 
        estimated. For example, dK[0] is the derivative vector on the first 
        parameter.
    """

    k_num = len(K)
    para_num = len(dK[0])
    self.para_num = para_num
    self.K = [np.array(x, dtype=np.complex128) for x in K]
    self.dK = [
        [np.array(dK[i][j], dtype=np.complex128) for j in range(para_num)]
        for i in range(k_num)
    ]

    self.dim = len(self.K[0])

    if self.psi0 == []:
        np.random.seed(self.seed)
        r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
        r = r_ini / np.linalg.norm(r_ini)
        phi = 2 * np.pi * np.random.random(self.dim)
        psi0 = [r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)]
        self.psi0 = np.array(psi0)  # Initial state (an array)
        self.psi = [self.psi0] # Initial guesses of states (a list of arrays)
    else:
        self.psi0 = np.array(self.psi0[0], dtype=np.complex128)
        self.psi = [np.array(psi, dtype=np.complex128) for psi in self.psi]

    self.opt = QJL.StateOpt(psi=jlconvert(jl.Vector[jl.ComplexF64], self.psi0), seed=self.seed)
    dynamics = QJL.Kraus(self.K, self.dK)
    scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
    self.scheme = scheme

    self.dynamics_type = "Kraus"
    if para_num == 1:
        self.para_type = "single_para"
    else:
        self.para_type = "multi_para"

QFIM(W=None, LDtype='SLD')

Choose QFI or \(\mathrm{Tr}(WF^{-1})\) as the objective function. In single parameter estimation the objective function is QFI and in multiparameter estimation it will be \(\mathrm{Tr}(WF^{-1})\).

Parameters

W: matrix -- Weight matrix.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are: "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD). "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD). "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Source code in quanestimation/base/StateOpt/StateStruct.py
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
def QFIM(self, W=None, LDtype="SLD"):
    r"""
    Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
    In single parameter estimation the objective function is QFI and in 
    multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
    """

    if W is None:
        W = []
    if LDtype != "SLD" and LDtype != "RLD" and LDtype != "LLD":
        raise ValueError(
            "{!r} is not a valid value for LDtype, supported values are 'SLD', 'RLD' and 'LLD'.".format(
                LDtype
            )
        )

    if self.dynamics_type == "dynamics":
        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

    elif self.dynamics_type == "Kraus":
        if W == []:
            W = np.eye(self.para_num)
        self.W = W

    self.obj = QJL.QFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps, LDtype=jl.Symbol(LDtype))
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save(max_num)

__init__(savefile, psi0, seed, eps, load)

Initialize the state optimization system.

Parameters:

Name Type Description Default
savefile bool

Whether to save all states during training. If True, all episodes are saved; if False, only the final episode is saved.

required
psi0 list of arrays

Initial guesses of probe states.

required
seed int

Random seed.

required
eps float

Machine epsilon.

required
load bool

Whether to load states from states.npy in the current directory as initial values.

required
Source code in quanestimation/base/StateOpt/StateStruct.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def __init__(self, savefile, psi0, seed, eps, load):
    r"""
    Initialize the state optimization system.

    Args:
        savefile (bool): Whether to save all states during training.
            If ``True``, all episodes are saved; if ``False``, only the
            final episode is saved.
        psi0 (list of arrays): Initial guesses of probe states.
        seed (int): Random seed.
        eps (float): Machine epsilon.
        load (bool): Whether to load states from ``states.npy`` in the
            current directory as initial values.
    """

    self.savefile = savefile
    self.psi0 = psi0
    self.psi = psi0
    self.eps = eps
    self.seed = seed

    if load == True:
        if os.path.exists("states.npy"):
            self.psi0 = [np.load("states.npy", dtype=np.complex128)]

dynamics(tspan, H0, dH, Hc=None, ctrl=None, decay=None, dyn_method='expm')

The dynamics of a density matrix is of the form

\[\begin{align} \partial_t\rho &=\mathcal{L}\rho \nonumber \\ &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2} \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right), \end{align}\]

where \(\rho\) is the evolved density matrix, H is the Hamiltonian of the system, \(\Gamma_i\) and \(\gamma_i\) are the \(i\mathrm{th}\) decay operator and corresponding decay rate.

Parameters

tspan: array -- Time length for the evolution.

H0: matrix or list -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time- independent and a list of length equal to tspan when it is time-dependent.

dH: list -- Derivatives of the free Hamiltonian on the unknown parameters to be estimated. For example, dH[0] is the derivative vector on the first parameter.

Hc: list -- Control Hamiltonians.

ctrl: list of arrays -- Control coefficients.

decay: list -- Decay operators and the corresponding decay rates. Its input rule is decay=[[\(\Gamma_1\), \(\gamma_1\)], [\(\Gamma_2\),\(\gamma_2\)],...], where \(\Gamma_1\) \((\Gamma_2)\) represents the decay operator and \(\gamma_1\) \((\gamma_2)\) is the corresponding decay rate.

dyn_method: string -- Setting the method for solving the Lindblad dynamics. Options are:
"expm" (default) -- Matrix exponential.
"ode" -- Solving the differential equations directly.

Source code in quanestimation/base/StateOpt/StateStruct.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
def dynamics(self, tspan, H0, dH, Hc=None, ctrl=None, decay=None, dyn_method="expm"):
    r"""
    The dynamics of a density matrix is of the form 

    \begin{align}
    \partial_t\rho &=\mathcal{L}\rho \nonumber \\
    &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
    \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
    \end{align}

    where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
    system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
    operator and corresponding decay rate.

    Parameters
    ----------
    > **tspan:** `array`
        -- Time length for the evolution.

    > **H0:** `matrix or list`
        -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time-
        independent and a list of length equal to `tspan` when it is time-dependent.

    > **dH:** `list`
        -- Derivatives of the free Hamiltonian on the unknown parameters to be 
        estimated. For example, dH[0] is the derivative vector on the first 
        parameter.

    > **Hc:** `list`
        -- Control Hamiltonians.

    > **ctrl:** `list of arrays`
        -- Control coefficients.

    > **decay:** `list`
        -- Decay operators and the corresponding decay rates. Its input rule is 
        decay=[[$\Gamma_1$, $\gamma_1$], [$\Gamma_2$,$\gamma_2$],...], where $\Gamma_1$ 
        $(\Gamma_2)$ represents the decay operator and $\gamma_1$ $(\gamma_2)$ is the 
        corresponding decay rate.

    > **dyn_method:** `string`
        -- Setting the method for solving the Lindblad dynamics. Options are:  
        "expm" (default) -- Matrix exponential.  
        "ode" -- Solving the differential equations directly.
    """

    if Hc is None:
        Hc = []
    if ctrl is None:
        ctrl = []
    if decay is None:
        decay = []
    self.tspan = tspan

    if dyn_method == "expm":
        self.dyn_method = "Expm"
    elif dyn_method == "ode":
        self.dyn_method = "Ode"

    if Hc == [] or ctrl == []:
        if isinstance(H0, np.ndarray):
            self.freeHamiltonian = np.array(H0, dtype=np.complex128)
            self.dim = len(self.freeHamiltonian)
        else:
            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0]
            self.dim = len(self.freeHamiltonian[0])
    else:
        ctrl_num = len(ctrl)
        Hc_num = len(Hc)
        if Hc_num < ctrl_num:
            raise TypeError(
                "There are %d control Hamiltonians but %d coefficients sequences: \
                             too many coefficients sequences."
                % (Hc_num, ctrl_num)
            )
        elif Hc_num > ctrl_num:
            warnings.warn(
                "Not enough coefficients sequences: there are %d control Hamiltonians \
                           but %d coefficients sequences. The rest of the control sequences are\
                           set to be 0."
                % (Hc_num, ctrl_num),
                DeprecationWarning,
            )
            for i in range(Hc_num - ctrl_num):
                ctrl = np.concatenate((ctrl, np.zeros(len(ctrl[0]))))

        if len(ctrl[0]) == 1:
            if isinstance(H0, np.ndarray):
                H0 = np.array(H0, dtype=np.complex128)
                Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                Htot = H0 + sum([Hc[i] * ctrl[i][0] for i in range(ctrl_num)])
                self.freeHamiltonian = np.array(Htot, dtype=np.complex128)
                self.dim = len(self.freeHamiltonian)
            else:
                H0 = [np.array(x, dtype=np.complex128) for x in H0]
                Htot = []
                for i in range(len(H0)):
                    Htot.append(
                        H0[i] + sum([Hc[i] * ctrl[i][0] for i in range(ctrl_num)])
                    )
                self.freeHamiltonian = [
                    np.array(x, dtype=np.complex128) for x in Htot
                ]
                self.dim = len(self.freeHamiltonian[0])
        else:
            if not isinstance(H0, np.ndarray):
                #### linear interpolation  ####
                f = interp1d(self.tspan, H0, axis=0)
            number = math.ceil((len(self.tspan) - 1) / len(ctrl[0]))
            if (len(self.tspan) - 1) % len(ctrl[0]) != 0:
                tnum = number * len(ctrl[0])
                self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
                if not isinstance(H0, np.ndarray):
                    H0_inter = f(self.tspan)
                    H0 = [np.array(x, dtype=np.complex128) for x in H0_inter]

            if isinstance(H0, np.ndarray):
                H0 = np.array(H0, dtype=np.complex128)
                Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                ctrl = [np.array(ctrl[i]).repeat(number) for i in range(len(Hc))]
                Htot = []
                for i in range(len(ctrl[0])):
                    S_ctrl = sum([Hc[j] * ctrl[j][i] for j in range(len(ctrl))])
                    Htot.append(H0 + S_ctrl)
                self.freeHamiltonian = [
                    np.array(x, dtype=np.complex128) for x in Htot
                ]
                self.dim = len(self.freeHamiltonian)
            else:
                H0 = [np.array(x, dtype=np.complex128) for x in H0]
                Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                ctrl = [np.array(ctrl[i]).repeat(number) for i in range(len(Hc))]
                Htot = []
                for i in range(len(ctrl[0])):
                    S_ctrl = sum([Hc[j] * ctrl[j][i] for j in range(len(ctrl))])
                    Htot.append(H0[i] + S_ctrl)
                self.freeHamiltonian = [
                    np.array(x, dtype=np.complex128) for x in Htot
                ]
                self.dim = len(self.freeHamiltonian[0])

    QJLType_psi = QJL.Vector[QJL.Vector[QJL.ComplexF64]]
    if self.psi0 == []:
        np.random.seed(self.seed)
        r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
        r = r_ini / np.linalg.norm(r_ini)
        phi = 2 * np.pi * np.random.random(self.dim)
        psi0 = [r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)]
        self.psi0 = np.array(psi0)
        self.psi = QJL.convert(QJLType_psi, [self.psi0]) # Initial guesses of states (a list of arrays)
    else:
        self.psi0 = np.array(self.psi0[0], dtype=np.complex128)
        self.psi = QJL.convert(QJLType_psi, self.psi)

    if not isinstance(dH, list):
        raise TypeError("The derivative of Hamiltonian should be a list!")

    if dH == []:
        dH = [np.zeros((len(self.psi0), len(self.psi0)))]
    self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]

    if decay == []:
        decay_opt = [np.zeros((len(self.psi0), len(self.psi0)))]
        self.gamma = [0.0]
    else:
        decay_opt = [decay[i][0] for i in range(len(decay))]
        self.gamma = [decay[i][1] for i in range(len(decay))]
    self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

    self.opt = QJL.StateOpt(psi=jlconvert(jl.Vector[jl.ComplexF64], self.psi0), seed=self.seed)
    if any(self.gamma):
        decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
        dynamics = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.tspan,
            decay=decay,
            dyn_method=self.dyn_method,
        )
    else:
        dynamics = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.tspan,
            dyn_method=self.dyn_method,
        )
    scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
    self.scheme = scheme

    self.dynamics_type = "dynamics"
    if len(self.Hamiltonian_derivative) == 1:
        self.para_type = "single_para"
    else:
        self.para_type = "multi_para"

load_save(max_episode)

Load optimized states saved by the Julia backend from states.dat and save them as a states.npy file.

The Julia backend writes states.dat atomically (temp → rename), and the file is deleted after a successful read to avoid stale data from interfering with subsequent runs.

Parameters:

Name Type Description Default
max_episode int

Maximum number of episodes.

required
Source code in quanestimation/base/StateOpt/StateStruct.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def load_save(self, max_episode):
    r"""
    Load optimized states saved by the Julia backend from ``states.dat``
    and save them as a ``states.npy`` file.

    The Julia backend writes ``states.dat`` atomically (temp → rename),
    and the file is deleted after a successful read to avoid stale data
    from interfering with subsequent runs.

    Args:
        max_episode (int): Maximum number of episodes.
    """
    load_and_save("states.dat", "states", "states",
                   self.savefile, max_episode=max_episode,
                   nested=False, complex_view=True)

State optimization with AD

Bases: StateSystem

Attributes

savefile: bool -- Whether or not to save all the states.
If set True then the states and the values of the objective function obtained in all episodes will be saved during the training. If set False the state in the final episode and the values of the objective function in all episodes will be saved.

Adam: bool -- Whether or not to use Adam for updating states.

psi0: list of arrays -- Initial guesses of states.

max_episode: int -- The number of episodes.

epsilon: float -- Learning rate.

beta1: float -- The exponential decay rate for the first moment estimates.

beta2: float -- The exponential decay rate for the second moment estimates.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load states in the current location.
If set True then the program will load state from "states.csv" file in the current location and use it as the initial state.

Source code in quanestimation/base/StateOpt/AD_Sopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
class AD_Sopt(State.StateSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the states.  
        If set `True` then the states and the values of the objective function 
        obtained in all episodes will be saved during the training. If set `False` 
        the state in the final episode and the values of the objective function in 
        all episodes will be saved.

    > **Adam:** `bool`
        -- Whether or not to use Adam for updating states.

    > **psi0:** `list of arrays`
        -- Initial guesses of states.

    > **max_episode:** `int`
        -- The number of episodes.

    > **epsilon:** `float`
        -- Learning rate.

    > **beta1:** `float`
        -- The exponential decay rate for the first moment estimates.

    > **beta2:** `float`
        -- The exponential decay rate for the second moment estimates.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load states in the current location.  
        If set `True` then the program will load state from "states.csv"
        file in the current location and use it as the initial state.
    """

    def __init__(
        self,
        savefile=False,
        Adam=False,
        psi0=[],
        max_episode=300,
        epsilon=0.01,
        beta1=0.90,
        beta2=0.99,
        seed=1234,
        eps=1e-8,
        load=False,
    ):
        r"""
        Initialize automatic differentiation (AD) for state optimization.

        Args:
            savefile (bool, optional): Whether to save all states during
                training. Default ``False``.
            Adam (bool, optional): Whether to use the Adam optimizer.
                Default ``False``.
            psi0 (list of arrays, optional): Initial guesses of probe states.
                Default ``[]``.
            max_episode (int, optional): Number of training episodes.
                Default 300.
            epsilon (float, optional): Learning rate. Default 0.01.
            beta1 (float, optional): Exponential decay rate for first moment
                estimates (Adam). Default 0.90.
            beta2 (float, optional): Exponential decay rate for second moment
                estimates (Adam). Default 0.99.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
            load (bool, optional): Whether to load initial states from
                ``states.csv``. Default ``False``.
        """

        State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

        self.Adam = Adam
        self.max_episode = max_episode
        self.epsilon = epsilon
        self.beta1 = beta1
        self.beta2 = beta2
        self.mt = 0.0
        self.vt = 0.0

        if self.Adam:
            self.alg = QJL.AD(
                Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2,
            )
        else:
            self.alg = QJL.AD(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)

    def QFIM(self, W=None, LDtype="SLD"):
        r"""
        Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
        In single parameter estimation the objective function is QFI and in 
        multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
        """

        if W is None:
            W = []
        super().QFIM(W, LDtype)

    def CFIM(self, M=None, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if M is None:
            M = []
        if W is None:
            W = []
        super().CFIM(M, W)

    def HCRB(self, W=None):
        """
        AD is not available when the objective function is HCRB. 
        Supported methods are PSO, DE, DDPG and NM.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None:
            W = []
        raise ValueError(
            "AD is not available when the objective function is HCRB. Supported methods are 'PSO', 'DE', 'NM' and 'DDPG'.")

CFIM(M=None, W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/StateOpt/AD_Sopt.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def CFIM(self, M=None, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if M is None:
        M = []
    if W is None:
        W = []
    super().CFIM(M, W)

HCRB(W=None)

AD is not available when the objective function is HCRB. Supported methods are PSO, DE, DDPG and NM.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/StateOpt/AD_Sopt.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def HCRB(self, W=None):
    """
    AD is not available when the objective function is HCRB. 
    Supported methods are PSO, DE, DDPG and NM.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None:
        W = []
    raise ValueError(
        "AD is not available when the objective function is HCRB. Supported methods are 'PSO', 'DE', 'NM' and 'DDPG'.")

QFIM(W=None, LDtype='SLD')

Choose QFI or \(\mathrm{Tr}(WF^{-1})\) as the objective function. In single parameter estimation the objective function is QFI and in multiparameter estimation it will be \(\mathrm{Tr}(WF^{-1})\).

Parameters

W: matrix -- Weight matrix.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Source code in quanestimation/base/StateOpt/AD_Sopt.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def QFIM(self, W=None, LDtype="SLD"):
    r"""
    Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
    In single parameter estimation the objective function is QFI and in 
    multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
    """

    if W is None:
        W = []
    super().QFIM(W, LDtype)

__init__(savefile=False, Adam=False, psi0=[], max_episode=300, epsilon=0.01, beta1=0.9, beta2=0.99, seed=1234, eps=1e-08, load=False)

Initialize automatic differentiation (AD) for state optimization.

Parameters:

Name Type Description Default
savefile bool

Whether to save all states during training. Default False.

False
Adam bool

Whether to use the Adam optimizer. Default False.

False
psi0 list of arrays

Initial guesses of probe states. Default [].

[]
max_episode int

Number of training episodes. Default 300.

300
epsilon float

Learning rate. Default 0.01.

0.01
beta1 float

Exponential decay rate for first moment estimates (Adam). Default 0.90.

0.9
beta2 float

Exponential decay rate for second moment estimates (Adam). Default 0.99.

0.99
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
load bool

Whether to load initial states from states.csv. Default False.

False
Source code in quanestimation/base/StateOpt/AD_Sopt.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def __init__(
    self,
    savefile=False,
    Adam=False,
    psi0=[],
    max_episode=300,
    epsilon=0.01,
    beta1=0.90,
    beta2=0.99,
    seed=1234,
    eps=1e-8,
    load=False,
):
    r"""
    Initialize automatic differentiation (AD) for state optimization.

    Args:
        savefile (bool, optional): Whether to save all states during
            training. Default ``False``.
        Adam (bool, optional): Whether to use the Adam optimizer.
            Default ``False``.
        psi0 (list of arrays, optional): Initial guesses of probe states.
            Default ``[]``.
        max_episode (int, optional): Number of training episodes.
            Default 300.
        epsilon (float, optional): Learning rate. Default 0.01.
        beta1 (float, optional): Exponential decay rate for first moment
            estimates (Adam). Default 0.90.
        beta2 (float, optional): Exponential decay rate for second moment
            estimates (Adam). Default 0.99.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
        load (bool, optional): Whether to load initial states from
            ``states.csv``. Default ``False``.
    """

    State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

    self.Adam = Adam
    self.max_episode = max_episode
    self.epsilon = epsilon
    self.beta1 = beta1
    self.beta2 = beta2
    self.mt = 0.0
    self.vt = 0.0

    if self.Adam:
        self.alg = QJL.AD(
            Adam=True, max_episode=self.max_episode, epsilon=self.epsilon, beta1=self.beta1, beta2=self.beta2,
        )
    else:
        self.alg = QJL.AD(Adam=False, max_episode=self.max_episode, epsilon=self.epsilon)

State optimization with RI

Bases: StateSystem

Attributes

savefile: bool -- Whether or not to save all the states.
If set True then the states and the values of the objective function obtained in all episodes will be saved during the training. If set False the state in the final episode and the values of the objective function in all episodes will be saved.

psi0: list of arrays -- Initial guesses of states.

max_episode: int -- The number of episodes.

seed: int -- Random seed.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load states in the current location.
If set True then the program will load state from "states.csv" file in the current location and use it as the initial state.

Source code in quanestimation/base/StateOpt/RI_Sopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
class RI_Sopt(State.StateSystem):
    """
    Attributes
    ----------
    > **savefile:**  `bool`
        -- Whether or not to save all the states.  
        If set `True` then the states and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the state in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **psi0:** `list of arrays`
        -- Initial guesses of states.

    > **max_episode:** `int`
        -- The number of episodes.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load states in the current location.  
        If set `True` then the program will load state from "states.csv"
        file in the current location and use it as the initial state.
    """

    def __init__(
        self,
        savefile=False,
        psi0=[],
        max_episode=300,
        seed=1234,
        eps=1e-8,
        load=False,
    ):
        r"""
        Initialize random iteration (RI) for state optimization.

        Args:
            savefile (bool, optional): Whether to save all states during
                training. Default ``False``.
            psi0 (list of arrays, optional): Initial guesses of probe states.
                Default ``[]``.
            max_episode (int, optional): Number of episodes. Default 300.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
            load (bool, optional): Whether to load initial states from
                ``states.csv``. Default ``False``.
        """

        State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

        self.max_episode = max_episode
        self.seed = seed

    def QFIM(self, W=None, LDtype="SLD"):
        r"""
        Choose QFI as the objective function. 

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Only SLD can
            is available here.
        """
        if W is None:
            W = []
        self.alg = QJL.RI(
            max_episode=self.max_episode,
        )
        if self.dynamics_type != "Kraus":
            raise ValueError("Only the parameterization with Kraus operators is available.")

        if LDtype == "SLD":
            super().QFIM(W, LDtype)
        else:
            raise ValueError("Only SLD is available.")

    def CFIM(self, M=None, W=None):
        """
        Choose CFIM as the objective function. 

        **Note:** CFIM is not available.

        Parameters
        ----------
        > **M:** `list`
            -- POVM.

        > **W:** `matrix`
            -- Weight matrix.
        """
        if M is None:
            M = []
        if W is None:
            W = []
        raise ValueError("CFIM is not available.")

    def HCRB(self, W=None):
        """
        Choose HCRB as the objective function. 

        **Note:** Here HCRB is not available.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None:
            W = []
        raise ValueError("HCRB is not available.")

CFIM(M=None, W=None)

Choose CFIM as the objective function.

Note: CFIM is not available.

Parameters

M: list -- POVM.

W: matrix -- Weight matrix.

Source code in quanestimation/base/StateOpt/RI_Sopt.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def CFIM(self, M=None, W=None):
    """
    Choose CFIM as the objective function. 

    **Note:** CFIM is not available.

    Parameters
    ----------
    > **M:** `list`
        -- POVM.

    > **W:** `matrix`
        -- Weight matrix.
    """
    if M is None:
        M = []
    if W is None:
        W = []
    raise ValueError("CFIM is not available.")

HCRB(W=None)

Choose HCRB as the objective function.

Note: Here HCRB is not available.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/StateOpt/RI_Sopt.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def HCRB(self, W=None):
    """
    Choose HCRB as the objective function. 

    **Note:** Here HCRB is not available.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None:
        W = []
    raise ValueError("HCRB is not available.")

QFIM(W=None, LDtype='SLD')

Choose QFI as the objective function.

Parameters

W: matrix -- Weight matrix.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Only SLD can is available here.

Source code in quanestimation/base/StateOpt/RI_Sopt.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def QFIM(self, W=None, LDtype="SLD"):
    r"""
    Choose QFI as the objective function. 

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Only SLD can
        is available here.
    """
    if W is None:
        W = []
    self.alg = QJL.RI(
        max_episode=self.max_episode,
    )
    if self.dynamics_type != "Kraus":
        raise ValueError("Only the parameterization with Kraus operators is available.")

    if LDtype == "SLD":
        super().QFIM(W, LDtype)
    else:
        raise ValueError("Only SLD is available.")

__init__(savefile=False, psi0=[], max_episode=300, seed=1234, eps=1e-08, load=False)

Initialize random iteration (RI) for state optimization.

Parameters:

Name Type Description Default
savefile bool

Whether to save all states during training. Default False.

False
psi0 list of arrays

Initial guesses of probe states. Default [].

[]
max_episode int

Number of episodes. Default 300.

300
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
load bool

Whether to load initial states from states.csv. Default False.

False
Source code in quanestimation/base/StateOpt/RI_Sopt.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def __init__(
    self,
    savefile=False,
    psi0=[],
    max_episode=300,
    seed=1234,
    eps=1e-8,
    load=False,
):
    r"""
    Initialize random iteration (RI) for state optimization.

    Args:
        savefile (bool, optional): Whether to save all states during
            training. Default ``False``.
        psi0 (list of arrays, optional): Initial guesses of probe states.
            Default ``[]``.
        max_episode (int, optional): Number of episodes. Default 300.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
        load (bool, optional): Whether to load initial states from
            ``states.csv``. Default ``False``.
    """

    State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

    self.max_episode = max_episode
    self.seed = seed

State Optimization with PSO

Bases: StateSystem

Attributes

savefile: bool -- Whether or not to save all the states.
If set True then the states and the values of the objective function obtained in all episodes will be saved during the training. If set False the state in the final episode and the values of the objective function in all episodes will be saved.

p_num: int -- The number of particles.

psi0: list of arrays -- Initial guesses of states.

max_episode: int or list -- If it is an integer, for example max_episode=1000, it means the program will continuously run 1000 episodes. However, if it is an array, for example max_episode=[1000,100], the program will run 1000 episodes in total but replace states of all the particles with global best every 100 episodes.

c0: float -- The damping factor that assists convergence, also known as inertia weight.

c1: float -- The exploitation weight that attracts the particle to its best previous position, also known as cognitive learning factor.

c2: float -- The exploitation weight that attracts the particle to the best position
in the neighborhood, also known as social learning factor.

seed: int -- Random seed.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load states in the current location. If set True then the program will load state from "states.csv" file in the current location and use it as the initial state.

Source code in quanestimation/base/StateOpt/PSO_Sopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
class PSO_Sopt(State.StateSystem):
    """
    Attributes
    ----------
    > **savefile:**  `bool`
        -- Whether or not to save all the states.  
        If set `True` then the states and the values of the objective function 
        obtained in all episodes will be saved during the training. If set `False` 
        the state in the final episode and the values of the objective function 
        in all episodes will be saved.

    > **p_num:** `int`
        -- The number of particles.

    > **psi0:** `list of arrays`
        -- Initial guesses of states.

    > **max_episode:** `int or list`
        -- If it is an integer, for example max_episode=1000, it means the 
        program will continuously run 1000 episodes. However, if it is an
        array, for example max_episode=[1000,100], the program will run 
        1000 episodes in total but replace states of all  the particles 
        with global best every 100 episodes.

    > **c0:** `float`
        -- The damping factor that assists convergence, also known as inertia weight.

    > **c1:** `float`
        -- The exploitation weight that attracts the particle to its best previous 
        position, also known as cognitive learning factor.

    > **c2:** `float`
        -- The exploitation weight that attracts the particle to the best position  
        in the neighborhood, also known as social learning factor.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load states in the current location.
        If set `True` then the program will load state from "states.csv"
        file in the current location and use it as the initial state.
    """

    def __init__(
        self,
        savefile=False,
        p_num=10,
        psi0=[],
        max_episode=[1000, 100],
        c0=1.0,
        c1=2.0,
        c2=2.0,
        seed=1234,
        eps=1e-8,
        load=False,
    ):
        r"""
        Initialize particle swarm optimization for state optimization.

        Args:
            savefile (bool, optional): Whether to save all states during
                training. Default ``False``.
            p_num (int, optional): Number of particles. Default 10.
            psi0 (list of arrays, optional): Initial guesses of probe states.
                Default ``[]``.
            max_episode (int or list, optional): Number of episodes. If a list
                ``[total, reset_interval]``, particles are reset to the global
                best every ``reset_interval`` episodes. Default ``[1000, 100]``.
            c0 (float, optional): Inertia weight (damping factor).
                Default 1.0.
            c1 (float, optional): Cognitive learning factor. Default 2.0.
            c2 (float, optional): Social learning factor. Default 2.0.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
            load (bool, optional): Whether to load initial states from
                ``states.csv``. Default ``False``.
        """

        State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

        is_int = isinstance(max_episode, int)
        self.max_episode = max_episode if is_int else QJL.Vector[QJL.Int64](max_episode)
        self.p_num = p_num
        self.c0 = c0
        self.c1 = c1
        self.c2 = c2
        self.seed = seed

    def QFIM(self, W=None, LDtype="SLD"):
        r"""
        Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
        In single parameter estimation the objective function is QFI and in 
        multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
        """
        if W is None:
            W = []
        ini_particle = (self.psi,)
        self.alg = QJL.PSO(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_particle=ini_particle,
            c0=self.c0,
            c1=self.c1,
            c2=self.c2,
        )

        super().QFIM(W, LDtype)

    def CFIM(self, M=None, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """
        if M is None:
            M = []
        if W is None:
            W = []
        ini_particle = (self.psi,)
        self.alg = QJL.PSO(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_particle=ini_particle,
            c0=self.c0,
            c1=self.c1,
            c2=self.c2,
        )

        super().CFIM(M, W)

    def HCRB(self, W=None):
        """
        Choose HCRB as the objective function. 

        **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose 
        QFI as the objective function.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None:
            W = []
        ini_particle = (self.psi,)
        self.alg = QJL.PSO(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_particle=ini_particle,
            c0=self.c0,
            c1=self.c1,
            c2=self.c2,
        )

        super().HCRB(W)

CFIM(M=None, W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/StateOpt/PSO_Sopt.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def CFIM(self, M=None, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """
    if M is None:
        M = []
    if W is None:
        W = []
    ini_particle = (self.psi,)
    self.alg = QJL.PSO(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_particle=ini_particle,
        c0=self.c0,
        c1=self.c1,
        c2=self.c2,
    )

    super().CFIM(M, W)

HCRB(W=None)

Choose HCRB as the objective function.

Note: in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/StateOpt/PSO_Sopt.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def HCRB(self, W=None):
    """
    Choose HCRB as the objective function. 

    **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose 
    QFI as the objective function.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None:
        W = []
    ini_particle = (self.psi,)
    self.alg = QJL.PSO(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_particle=ini_particle,
        c0=self.c0,
        c1=self.c1,
        c2=self.c2,
    )

    super().HCRB(W)

QFIM(W=None, LDtype='SLD')

Choose QFI or \(\mathrm{Tr}(WF^{-1})\) as the objective function. In single parameter estimation the objective function is QFI and in multiparameter estimation it will be \(\mathrm{Tr}(WF^{-1})\).

Parameters

W: matrix -- Weight matrix.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Source code in quanestimation/base/StateOpt/PSO_Sopt.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def QFIM(self, W=None, LDtype="SLD"):
    r"""
    Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
    In single parameter estimation the objective function is QFI and in 
    multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
    """
    if W is None:
        W = []
    ini_particle = (self.psi,)
    self.alg = QJL.PSO(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_particle=ini_particle,
        c0=self.c0,
        c1=self.c1,
        c2=self.c2,
    )

    super().QFIM(W, LDtype)

__init__(savefile=False, p_num=10, psi0=[], max_episode=[1000, 100], c0=1.0, c1=2.0, c2=2.0, seed=1234, eps=1e-08, load=False)

Initialize particle swarm optimization for state optimization.

Parameters:

Name Type Description Default
savefile bool

Whether to save all states during training. Default False.

False
p_num int

Number of particles. Default 10.

10
psi0 list of arrays

Initial guesses of probe states. Default [].

[]
max_episode int or list

Number of episodes. If a list [total, reset_interval], particles are reset to the global best every reset_interval episodes. Default [1000, 100].

[1000, 100]
c0 float

Inertia weight (damping factor). Default 1.0.

1.0
c1 float

Cognitive learning factor. Default 2.0.

2.0
c2 float

Social learning factor. Default 2.0.

2.0
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
load bool

Whether to load initial states from states.csv. Default False.

False
Source code in quanestimation/base/StateOpt/PSO_Sopt.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def __init__(
    self,
    savefile=False,
    p_num=10,
    psi0=[],
    max_episode=[1000, 100],
    c0=1.0,
    c1=2.0,
    c2=2.0,
    seed=1234,
    eps=1e-8,
    load=False,
):
    r"""
    Initialize particle swarm optimization for state optimization.

    Args:
        savefile (bool, optional): Whether to save all states during
            training. Default ``False``.
        p_num (int, optional): Number of particles. Default 10.
        psi0 (list of arrays, optional): Initial guesses of probe states.
            Default ``[]``.
        max_episode (int or list, optional): Number of episodes. If a list
            ``[total, reset_interval]``, particles are reset to the global
            best every ``reset_interval`` episodes. Default ``[1000, 100]``.
        c0 (float, optional): Inertia weight (damping factor).
            Default 1.0.
        c1 (float, optional): Cognitive learning factor. Default 2.0.
        c2 (float, optional): Social learning factor. Default 2.0.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
        load (bool, optional): Whether to load initial states from
            ``states.csv``. Default ``False``.
    """

    State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

    is_int = isinstance(max_episode, int)
    self.max_episode = max_episode if is_int else QJL.Vector[QJL.Int64](max_episode)
    self.p_num = p_num
    self.c0 = c0
    self.c1 = c1
    self.c2 = c2
    self.seed = seed

State Optimization DE

Bases: StateSystem

Attributes

savefile: bool -- Whether or not to save all the states.
If set True then the states and the values of the objective function obtained in all episodes will be saved during the training. If set False the state in the final episode and the values of the objective function in all episodes will be saved.

p_num: int -- The number of populations.

psi0: list of arrays -- Initial guesses of states.

max_episode: int -- The number of episodes.

c: float -- Mutation constant.

cr: float -- Crossover constant.

seed: int -- Random seed.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load states in the current location.
If set True then the program will load state from "states.csv" file in the current location and use it as the initial state.

Source code in quanestimation/base/StateOpt/DE_Sopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
class DE_Sopt(State.StateSystem):
    """
    Attributes
    ----------
    > **savefile:**  `bool`
        -- Whether or not to save all the states.  
        If set `True` then the states and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the state in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **p_num:** `int`
        -- The number of populations.

    > **psi0:** `list of arrays`
        -- Initial guesses of states.

    > **max_episode:** `int`
        -- The number of episodes.

    > **c:** `float`
        -- Mutation constant.

    > **cr:** `float`
        -- Crossover constant.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load states in the current location.  
        If set `True` then the program will load state from "states.csv"
        file in the current location and use it as the initial state.
    """

    def __init__(
        self,
        savefile=False,
        p_num=10,
        psi0=[],
        max_episode=1000,
        c=1.0,
        cr=0.5,
        seed=1234,
        eps=1e-8,
        load=False,
    ):
        r"""
        Initialize differential evolution (DE) for state optimization.

        Args:
            savefile (bool, optional): Whether to save all states during
                training. Default ``False``.
            p_num (int, optional): Number of populations. Default 10.
            psi0 (list of arrays, optional): Initial guesses of probe states.
                Default ``[]``.
            max_episode (int, optional): Number of episodes. Default 1000.
            c (float, optional): Mutation constant. Default 1.0.
            cr (float, optional): Crossover constant. Default 0.5.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
            load (bool, optional): Whether to load initial states from
                ``states.csv``. Default ``False``.
        """

        State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

        self.p_num = p_num
        self.max_episode = max_episode
        self.c = c
        self.cr = cr
        self.seed = seed

    def QFIM(self, W=None, LDtype="SLD"):
        r"""
        Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
        In single parameter estimation the objective function is QFI and in 
        multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
        """
        if W is None:
            W = []
        ini_population = (self.psi,)
        self.alg = QJL.DE(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_population=ini_population,
            c=self.c,
            cr=self.cr,
        )
        super().QFIM(W, LDtype)

    def CFIM(self, M=None, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """
        if M is None:
            M = []
        if W is None:
            W = []
        ini_population = (self.psi,)
        self.alg = QJL.DE(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_population=ini_population,
            c=self.c,
            cr=self.cr,
        )
        super().CFIM(M, W)

    def HCRB(self, W=None):
        """
        Choose HCRB as the objective function. 

        **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose 
        QFI as the objective function.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None:
            W = []
        ini_population = (self.psi,)
        self.alg = QJL.DE(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_population=ini_population,
            c=self.c,
            cr=self.cr,
        )
        super().HCRB(W)

CFIM(M=None, W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/StateOpt/DE_Sopt.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def CFIM(self, M=None, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """
    if M is None:
        M = []
    if W is None:
        W = []
    ini_population = (self.psi,)
    self.alg = QJL.DE(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_population=ini_population,
        c=self.c,
        cr=self.cr,
    )
    super().CFIM(M, W)

HCRB(W=None)

Choose HCRB as the objective function.

Note: in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/StateOpt/DE_Sopt.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def HCRB(self, W=None):
    """
    Choose HCRB as the objective function. 

    **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose 
    QFI as the objective function.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None:
        W = []
    ini_population = (self.psi,)
    self.alg = QJL.DE(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_population=ini_population,
        c=self.c,
        cr=self.cr,
    )
    super().HCRB(W)

QFIM(W=None, LDtype='SLD')

Choose QFI or \(\mathrm{Tr}(WF^{-1})\) as the objective function. In single parameter estimation the objective function is QFI and in multiparameter estimation it will be \(\mathrm{Tr}(WF^{-1})\).

Parameters

W: matrix -- Weight matrix.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Source code in quanestimation/base/StateOpt/DE_Sopt.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def QFIM(self, W=None, LDtype="SLD"):
    r"""
    Choose QFI or $\mathrm{Tr}(WF^{-1})$ as the objective function. 
    In single parameter estimation the objective function is QFI and in 
    multiparameter estimation it will be $\mathrm{Tr}(WF^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).
    """
    if W is None:
        W = []
    ini_population = (self.psi,)
    self.alg = QJL.DE(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_population=ini_population,
        c=self.c,
        cr=self.cr,
    )
    super().QFIM(W, LDtype)

__init__(savefile=False, p_num=10, psi0=[], max_episode=1000, c=1.0, cr=0.5, seed=1234, eps=1e-08, load=False)

Initialize differential evolution (DE) for state optimization.

Parameters:

Name Type Description Default
savefile bool

Whether to save all states during training. Default False.

False
p_num int

Number of populations. Default 10.

10
psi0 list of arrays

Initial guesses of probe states. Default [].

[]
max_episode int

Number of episodes. Default 1000.

1000
c float

Mutation constant. Default 1.0.

1.0
cr float

Crossover constant. Default 0.5.

0.5
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
load bool

Whether to load initial states from states.csv. Default False.

False
Source code in quanestimation/base/StateOpt/DE_Sopt.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def __init__(
    self,
    savefile=False,
    p_num=10,
    psi0=[],
    max_episode=1000,
    c=1.0,
    cr=0.5,
    seed=1234,
    eps=1e-8,
    load=False,
):
    r"""
    Initialize differential evolution (DE) for state optimization.

    Args:
        savefile (bool, optional): Whether to save all states during
            training. Default ``False``.
        p_num (int, optional): Number of populations. Default 10.
        psi0 (list of arrays, optional): Initial guesses of probe states.
            Default ``[]``.
        max_episode (int, optional): Number of episodes. Default 1000.
        c (float, optional): Mutation constant. Default 1.0.
        cr (float, optional): Crossover constant. Default 0.5.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
        load (bool, optional): Whether to load initial states from
            ``states.csv``. Default ``False``.
    """

    State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

    self.p_num = p_num
    self.max_episode = max_episode
    self.c = c
    self.cr = cr
    self.seed = seed

Measurement Optimization

In QuanEstimation, three measurement optimization scenarios are considered. The first one is to optimize a set of rank-one projective measurement, it can be written in a specific basis \(\{|\phi_i\rangle\}\) with \(|\phi_i\rangle=\sum_j C_{ij}|j\rangle\) in the Hilbert space as \(\{|\phi_i\rangle\langle\phi_i|\}\). In this case, the goal is to search a set of optimal coefficients \(C_{ij}\). The second scenario is to find the optimal linear combination of an input measurement \(\{\Pi_j\}\). The third scenario is to find the optimal rotated measurement of an input measurement. After rotation, the new measurement is \(\{U\Pi_i U^{\dagger}\}\), where \(U=\prod_k \exp(i s_k\lambda_k)\) with \(\lambda_k\) a SU(\(N\)) generator and \(s_k\) a real number in the regime \([0,2\pi]\). In this scenario, the goal is to search a set of optimal coefficients \(s_k\). Here different algorithms are invoked to search the optimal measurement include particle swarm optimization (PSO) [1], differential evolution (DE) [2], and automatic differentiation (AD) [[3]] (#Baydin2018).

Base

Attributes


mtype: string -- The type of scenarios for the measurement optimization. Options are:
"projection" (default) -- Optimization of rank-one projective measurements.
"input" -- Find the optimal linear combination or the optimal rotated measurement of a given set of POVM.

minput: list -- In the case of optimization of rank-one projective measurements, the minput should keep empty. For finding the optimal linear combination and the optimal rotated measurement of a given set of POVM, the input rule are minput=["LC", [Pi1,Pi2,...], m] and minput=["LC", [Pi1,Pi2,...]] respectively. Here [Pi1,Pi2,...] represents a list of input POVM and m is the number of operators of the output measurement.

savefile: bool -- Whether or not to save all the measurements.
If set True then the measurements and the values of the objective function obtained in all episodes will be saved during the training. If set False the measurement in the final episode and the values of the objective function in all episodes will be saved.

measurement0: list of arrays -- Initial guesses of measurements.

seed: int -- Random seed.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load measurements in the current location.
If set True then the program will load measurement from "measurements.csv" file in the current location and use it as the initial measurement.

dyn_method: string -- The method for solving the Lindblad dynamcs. Options are: "expm" (default) -- matrix exponential. "ode" -- ordinary differential equation solvers.

Source code in quanestimation/base/MeasurementOpt/MeasurementStruct.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
class MeasurementSystem:
    """
    Attributes
    ----------
    > **mtype:** `string`
        -- The type of scenarios for the measurement optimization. Options are:  
        "projection" (default) -- Optimization of rank-one projective measurements.  
        "input" -- Find the optimal linear combination or the optimal rotated measurement 
        of a given set of POVM.

    > **minput:** `list`
        -- In the case of optimization of rank-one projective measurements, the 
        `minput` should keep empty. For finding the optimal linear combination and 
        the optimal rotated measurement of a given set of POVM, the input rule are 
        `minput=["LC", [Pi1,Pi2,...], m]` and `minput=["LC", [Pi1,Pi2,...]]` respectively.
        Here `[Pi1,Pi2,...]` represents a list of input POVM and `m` is the number of operators 
        of the output measurement. 

    > **savefile:** `bool`
        -- Whether or not to save all the measurements.  
        If set `True` then the measurements and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the measurement in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

   > **measurement0:** `list of arrays`
        -- Initial guesses of measurements.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load measurements in the current location.  
        If set `True` then the program will load measurement from "measurements.csv"
        file in the current location and use it as the initial measurement.

    > **dyn_method:** `string`
        -- The method for solving the Lindblad dynamcs. Options are:
        "expm" (default) -- matrix exponential.
        "ode" -- ordinary differential equation solvers.  
    """

    def __init__(self, mtype, minput, savefile, measurement0, seed, eps, load):
        r"""
        Initialize the measurement optimization system.

        Args:
            mtype (str): Measurement optimization scenario. Options are:
                ``"projection"`` -- Optimization of rank-one projective
                measurements; ``"input"`` -- Optimal linear combination or
                rotation of a given POVM set.
            minput (list): Additional parameters for the ``"input"`` scenario.
                Use ``["LC", [Pi1, Pi2, ...], m]`` for linear combination with
                ``m`` output operators, or ``["rotation", [Pi1, Pi2, ...]]``
                for rotation optimization.
            savefile (bool): Whether to save all measurements during training.
                If ``True``, all episodes are saved; if ``False``, only the
                final episode is saved.
            measurement0 (list of arrays): Initial guesses of measurements.
            seed (int): Random seed.
            eps (float): Machine epsilon.
            load (bool): Whether to load measurements from ``measurements.csv``
                in the current directory as initial values.
        """

        self.mtype = mtype
        self.minput = minput
        self.savefile = savefile
        self.eps = eps
        self.seed = seed
        self.load = load
        self.measurement0 = measurement0

    def load_save(self, mnum, max_episode):
        r"""
        Load optimized measurements saved by the Julia backend from
        ``measurements.dat`` and save them as a ``measurements.npy`` file.

        The Julia backend writes ``measurements.dat`` atomically (temp → rename),
        and the file is deleted after a successful read to avoid stale data
        from interfering with subsequent runs.

        Args:
            mnum (int): Number of measurement operators.
            max_episode (int): Maximum number of episodes.
        """
        load_and_save("measurements.dat", "measurements", "measurements",
                       self.savefile, item_count=mnum, max_episode=max_episode,
                       nested=True, complex_view=True)

    def dynamics(self, tspan, rho0, H0, dH, Hc=None, ctrl=None, decay=None, dyn_method="expm"):
        r"""
        The dynamics of a density matrix is of the form  

        \begin{align}
        \partial_t\rho &=\mathcal{L}\rho \nonumber \\
        &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
        \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
        \end{align} 

        where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
        system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
        operator and corresponding decay rate.

        Parameters
        ----------
        > **tspan:** `array`
            -- Time length for the evolution.

        > **rho0:** `matrix`
            -- Initial state (density matrix).

        > **H0:** `matrix or list`
            -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time-
            independent and a list of length equal to `tspan` when it is time-dependent.

        > **dH:** `list`
            -- Derivatives of the free Hamiltonian on the unknown parameters to be 
            estimated. For example, dH[0] is the derivative vector on the first 
            parameter.

        > **Hc:** `list`
            -- Control Hamiltonians.

        > **ctrl:** `list of arrays`
            -- Control coefficients.

        > **decay:** `list`
            -- Decay operators and the corresponding decay rates. Its input rule is 
            decay=[[$\Gamma_1$, $\gamma_1$], [$\Gamma_2$,$\gamma_2$],...], where $\Gamma_1$ 
            $(\Gamma_2)$ represents the decay operator and $\gamma_1$ $(\gamma_2)$ is the 
            corresponding decay rate.

        > **dyn_method:** `string`
            -- Setting the method for solving the Lindblad dynamics. Options are:  
            "expm" (default) -- Matrix exponential.  
            "ode" -- Solving the differential equations directly.
        """

        if Hc is None:
            Hc = []
        if ctrl is None:
            ctrl = []
        if decay is None:
            decay = []

        self.tspan = tspan
        self.rho0 = np.array(rho0, dtype=np.complex128)

        self.dynamics_type = "dynamics"

        if len(dH) == 1:
            self.para_type = "single_para"
        else:
            self.para_type = "multi_para"

        if dyn_method == "expm":
            self.dyn_method = "Expm"
        elif dyn_method == "ode":
            self.dyn_method = "Ode"

        if self.mtype == "projection":
            self.M_num = len(self.rho0)
            QJLType_C = QJL.Vector[QJL.Vector[QJL.ComplexF64]]

            if self.measurement0 == []:
                np.random.seed(self.seed)
                M = [[] for i in range(len(self.rho0))]
                for i in range(len(self.rho0)):
                    r_ini = 2 * np.random.random(len(self.rho0)) - np.ones(
                        len(self.rho0)
                    )
                    r = r_ini / np.linalg.norm(r_ini)
                    phi = 2 * np.pi * np.random.random(len(self.rho0))
                    M[i] = [r[j] * np.exp(1.0j * phi[j]) for j in range(len(self.rho0))]
                self.C = QJL.convert(QJLType_C, gramschmidt(np.array(M)))
                self.measurement0 = QJL.Vector([self.C])
            else:
                self.C = [self.measurement0[0][i] for i in range(len(self.rho0))]
                self.C = QJL.convert(QJLType_C, self.C)
                self.measurement0 = QJL.Vector([self.C])
            self.opt = QJL.Mopt_Projection(M=self.C, seed=self.seed)

        elif self.mtype == "input":
            if self.minput[0] == "LC":
                self.M_num = self.minput[2]
                ## optimize the combination of a set of SIC-POVM
                if self.minput[1] == []:
                    file_path = os.path.join(
                        os.path.dirname(os.path.dirname(__file__)),
                        "sic_fiducial_vectors/d%d.txt" % (len(self.rho0)),
                    )
                    data = np.loadtxt(file_path)
                    fiducial = data[:, 0] + data[:, 1] * 1.0j
                    fiducial = np.array(fiducial).reshape(len(fiducial), 1)
                    self.povm_basis = sic_povm(fiducial)
                else:
                    ## optimize the combination of a set of given POVMs
                    if not isinstance(self.minput[1], list):
                        raise TypeError("The given POVMs should be a list!")
                    else:
                        accu = len(str(int(1 / self.eps))) - 1
                        for i in range(len(self.minput[1])):
                            val, vec = np.linalg.eig(self.minput[1])
                            if np.all(val.round(accu) >= 0):
                                pass
                            else:
                                raise TypeError(
                                    "The given POVMs should be semidefinite!"
                                )
                        M = np.zeros(
                            (len(self.rho0), len(self.rho0)), dtype=np.complex128
                        )
                        for i in range(len(self.minput[1])):
                            M += self.minput[1][i]
                        if np.all(M.round(accu) - np.identity(len(self.rho0)) == 0):
                            pass
                        else:
                            raise TypeError(
                                "The sum of the given POVMs should be identity matrix!"
                            )
                        self.povm_basis = [
                            np.array(x, dtype=np.complex128) for x in self.minput[1]
                        ]

                if self.measurement0 == []:
                    np.random.seed(self.seed)
                    self.B = [
                        np.random.random(len(self.povm_basis))
                        for i in range(self.M_num)
                    ]
                    self.measurement0 = [self.B]
                elif len(self.measurement0) >= 1:
                    self.B = [self.measurement0[0][i] for i in range(self.M_num)]
                    self.measurement0 = [[m for m in m0] for m0 in self.measurement0]


                QJLType_B = QJL.Vector[QJL.Vector[QJL.Float64]]
                QJLType_pb = QJL.Vector[QJL.Matrix[QJL.ComplexF64]]
                QJLType_m0 = QJL.Vector[QJL.Vector[QJL.Vector[QJL.ComplexF64]]]
                self.B = QJL.convert(QJLType_B, self.B)
                self.povm_basis = QJL.convert(QJLType_pb, self.povm_basis)
                self.measurement0 = QJL.convert(QJLType_m0, self.measurement0)

                self.opt = QJL.Mopt_LinearComb(
                    B=self.B, POVM_basis=self.povm_basis, M_num=self.M_num, seed=self.seed
                )

            elif self.minput[0] == "rotation":
                self.M_num = len(self.minput[1])
                ## optimize the coefficients of the rotation matrix
                if not isinstance(self.minput[1], list):
                    raise TypeError("The given POVMs should be a list!")
                else:
                    if self.minput[1] == []:
                        raise TypeError("The initial POVM should not be empty!")
                    accu = len(str(int(1 / self.eps))) - 1
                    for i in range(len(self.minput[1])):
                        val, vec = np.linalg.eig(self.minput[1])
                        if np.all(val.round(accu) >= 0):
                            pass
                        else:
                            raise TypeError("The given POVMs should be semidefinite!")
                    M = np.zeros((len(self.rho0), len(self.rho0)), dtype=np.complex128)
                    for i in range(len(self.minput[1])):
                        M += self.minput[1][i]
                    if np.all(M.round(accu) - np.identity(len(self.rho0)) == 0):
                        pass
                    else:
                        raise TypeError(
                            "The sum of the given POVMs should be identity matrix!"
                        )
                    self.povm_basis = [
                        np.array(x, dtype=np.complex128) for x in self.minput[1]
                    ]
                    self.mtype = "rotation"

                if self.measurement0 == []:
                    np.random.seed(self.seed)
                    self.s = np.random.random(len(self.rho0) ** 2)
                    self.measurement0 = [self.s]
                elif len(self.measurement0) >= 1:
                    self.s = [
                        self.measurement0[0][i]
                        for i in range(len(self.rho0) * len(self.rho0))
                    ]

                self.s = QJL.Vector(self.s)
                QJLType_pb = QJL.Vector[QJL.Matrix[QJL.ComplexF64]]
                self.povm_basis = QJL.convert(QJLType_pb, self.povm_basis)
                self.opt = QJL.Mopt_Rotation(
                    s=self.s, POVM_basis=self.povm_basis, seed=self.seed
                )

            else:
                raise ValueError(
                    "{!r} is not a valid value for the first input of minput, supported values are 'LC' and 'rotation'.".format(
                        self.minput[0]
                    )
                )
        else:
            raise ValueError(
                "{!r} is not a valid value for mtype, supported values are 'projection' and 'input'.".format(
                    self.mtype
                )
            )

        if Hc == [] or ctrl == []:
            if isinstance(H0, np.ndarray):
                self.freeHamiltonian = np.array(H0, dtype=np.complex128)
            else:
                self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0]
        else:
            ctrl_num = len(ctrl)
            Hc_num = len(Hc)
            if Hc_num < ctrl_num:
                raise TypeError(
                    "There are %d control Hamiltonians but %d coefficients sequences: too many coefficients sequences"
                    % (Hc_num, ctrl_num)
                )
            elif Hc_num > ctrl_num:
                warnings.warn(
                    "Not enough coefficients sequences: there are %d control Hamiltonians but %d coefficients sequences. The rest of the control sequences are set to be 0."
                    % (Hc_num, ctrl_num),
                    DeprecationWarning,
                )
                for i in range(Hc_num - ctrl_num):
                    ctrl = np.concatenate((ctrl, np.zeros(len(ctrl[0]))))

            if len(ctrl[0]) == 1:
                if isinstance(H0, np.ndarray):
                    H0 = np.array(H0, dtype=np.complex128)
                    Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                    Htot = H0 + sum([Hc[i] * ctrl[i][0] for i in range(ctrl_num)])
                    self.freeHamiltonian = np.array(Htot, dtype=np.complex128)
                else:
                    H0 = [np.array(x, dtype=np.complex128) for x in H0]
                    Htot = []
                    for i in range(len(H0)):
                        Htot.append(
                            H0[i] + sum([Hc[i] * ctrl[i][0] for i in range(ctrl_num)])
                        )
                    self.freeHamiltonian = [
                        np.array(x, dtype=np.complex128) for x in Htot
                    ]
            else:
                if not isinstance(H0, np.ndarray):
                    #### linear interpolation  ####
                    f = interp1d(self.tspan, H0, axis=0)
                number = math.ceil((len(self.tspan) - 1) / len(ctrl[0]))
                if (len(self.tspan) - 1) % len(ctrl[0]) != 0:
                    tnum = number * len(ctrl[0])
                    self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
                    if not isinstance(H0, np.ndarray):
                        H0_inter = f(self.tspan)
                        H0 = [np.array(x, dtype=np.complex128) for x in H0_inter]

                if isinstance(H0, np.ndarray):
                    H0 = np.array(H0, dtype=np.complex128)
                    Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                    ctrl = [np.array(ctrl[i]).repeat(number) for i in range(len(Hc))]
                    Htot = []
                    for i in range(len(ctrl[0])):
                        S_ctrl = sum([Hc[j] * ctrl[j][i] for j in range(len(ctrl))])
                        Htot.append(H0 + S_ctrl)
                    self.freeHamiltonian = [
                        np.array(x, dtype=np.complex128) for x in Htot
                    ]
                else:
                    H0 = [np.array(x, dtype=np.complex128) for x in H0]
                    Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                    ctrl = [np.array(ctrl[i]).repeat(number) for i in range(len(Hc))]
                    Htot = []
                    for i in range(len(ctrl[0])):
                        S_ctrl = sum([Hc[j] * ctrl[j][i] for j in range(len(ctrl))])
                        Htot.append(H0[i] + S_ctrl)
                    self.freeHamiltonian = [
                        np.array(x, dtype=np.complex128) for x in Htot
                    ]

        if not isinstance(dH, list):
            raise TypeError("The derivative of Hamiltonian should be a list!")

        if dH == []:
            dH = [np.zeros((len(self.rho0), len(self.rho0)))]
        self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]

        if decay == []:
            decay_opt = [np.zeros((len(self.rho0), len(self.rho0)))]
            self.gamma = [0.0]
        else:
            decay_opt = [decay[i][0] for i in range(len(decay))]
            self.gamma = [decay[i][1] for i in range(len(decay))]
        self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

        decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
        dynamics = QJL.Lindblad(
            self.freeHamiltonian, self.Hamiltonian_derivative,
            self.tspan,
            decay=decay,
            dyn_method=self.dyn_method,
        )
        scheme = QJL.GeneralScheme(probe=self.rho0, param=dynamics)
        self.scheme = scheme

        self.dynamics_type = "dynamics"


    def Kraus(self, rho0, K, dK):
        r"""
        The parameterization of a state is
        \begin{align}
        \rho=\sum_i K_i\rho_0K_i^{\dagger},
        \end{align} 

        where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator.

        Parameters
        ----------
        > **rho0:** `matrix`
            -- Initial state (density matrix).

        > **K:** `list`
            -- Kraus operators.

        > **dK:** `list`
            -- Derivatives of the Kraus operators on the unknown parameters to be 
            estimated. For example, dK[0] is the derivative vector on the first 
            parameter.
        """
        k_num = len(K)
        para_num = len(dK[0])
        self.para_num = para_num
        self.dK = [
            [np.array(dK[i][j], dtype=np.complex128) for j in range(para_num)]
            for i in range(k_num)
        ]
        self.rho0 = np.array(rho0, dtype=np.complex128)
        self.K = [np.array(x, dtype=np.complex128) for x in K]

        if para_num == 1:
            self.para_type = "single_para"
        else:
            self.para_type = "multi_para"

        if self.mtype == "projection":
            self.M_num = len(self.rho0)
            if self.measurement0 == []:
                np.random.seed(self.seed)
                M = [[] for i in range(len(self.rho0))]
                for i in range(len(self.rho0)):
                    r_ini = 2 * np.random.random(len(self.rho0)) - np.ones(
                        len(self.rho0)
                    )
                    r = r_ini / np.linalg.norm(r_ini)
                    phi = 2 * np.pi * np.random.random(len(self.rho0))
                    M[i] = [r[j] * np.exp(1.0j * phi[j]) for j in range(len(self.rho0))]
                self.C = gramschmidt(np.array(M))
                self.measurement0 = [self.C]
            else:
                self.C = [self.measurement0[0][i] for i in range(len(self.rho0))]
                self.C = [np.array(x, dtype=np.complex128) for x in self.C]
            self.opt = QJL.Mopt_Projection(M=self.C, seed=self.seed)

        elif self.mtype == "input":
            if self.minput[0] == "LC":
                self.M_num = self.minput[2]
                ## optimize the combination of a set of SIC-POVM
                if self.minput[1] == []:
                    file_path = os.path.join(
                        os.path.dirname(os.path.dirname(__file__)),
                        "sic_fiducial_vectors/d%d.txt" % (len(self.rho0)),
                    )
                    data = np.loadtxt(file_path)
                    fiducial = data[:, 0] + data[:, 1] * 1.0j
                    fiducial = np.array(fiducial).reshape(len(fiducial), 1)
                    self.povm_basis = sic_povm(fiducial)
                else:
                    ## optimize the combination of a set of given POVMs
                    if not isinstance(self.minput[1], list):
                        raise TypeError("The given POVMs should be a list!")
                    else:
                        accu = len(str(int(1 / self.eps))) - 1
                        for i in range(len(self.minput[1])):
                            val, vec = np.linalg.eig(self.minput[1])
                            if np.all(val.round(accu) >= 0):
                                pass
                            else:
                                raise TypeError(
                                    "The given POVMs should be semidefinite!"
                                )
                        M = np.zeros(
                            (len(self.rho0), len(self.rho0)), dtype=np.complex128
                        )
                        for i in range(len(self.minput[1])):
                            M += self.minput[1][i]
                        if np.all(M.round(accu) - np.identity(len(self.rho0)) == 0):
                            pass
                        else:
                            raise TypeError(
                                "The sum of the given POVMs should be identity matrix!"
                            )
                        self.povm_basis = [
                            np.array(x, dtype=np.complex128) for x in self.minput[1]
                        ]

                if self.measurement0 == []:
                    np.random.seed(self.seed)
                    self.B = [
                        np.random.random(len(self.povm_basis))
                        for i in range(self.M_num)
                    ]
                    self.measurement0 = [np.array(self.B)]
                elif len(self.measurement0) >= 1:
                    self.B = [
                        self.measurement0[0][i] for i in range(len(self.povm_basis))
                    ]
                self.opt = QJL.Mopt_LinearComb(
                    B=self.B, POVM_basis=self.povm_basis, M_num=self.M_num, seed=self.seed
                )

            elif self.minput[0] == "rotation":
                self.M_num = len(self.minput[1])
                ## optimize the coefficients of the rotation matrix
                if not isinstance(self.minput[1], list):
                    raise TypeError("The given POVMs should be a list!")
                else:
                    if self.minput[1] == []:
                        raise TypeError("The initial POVM should not be empty!")
                    accu = len(str(int(1 / self.eps))) - 1
                    for i in range(len(self.minput[1])):
                        val, vec = np.linalg.eig(self.minput[1])
                        if np.all(val.round(accu) >= 0):
                            pass
                        else:
                            raise TypeError("The given POVMs should be semidefinite!")
                    M = np.zeros((len(self.rho0), len(self.rho0)), dtype=np.complex128)
                    for i in range(len(self.minput[1])):
                        M += self.minput[1][i]
                    if np.all(M.round(accu) - np.identity(len(self.rho0)) == 0):
                        pass
                    else:
                        raise TypeError(
                            "The sum of the given POVMs should be identity matrix!"
                        )
                    self.povm_basis = [
                        np.array(x, dtype=np.complex128) for x in self.minput[1]
                    ]
                    self.mtype = "rotation"

                if self.measurement0 == []:
                    np.random.seed(self.seed)
                    self.s = np.random.random(len(self.rho0) ** 2)
                    self.measurement0 = [self.s]
                elif len(self.measurement0) >= 1:
                    self.s = [
                        self.measurement0[0][i]
                        for i in range(len(self.rho0) * len(self.rho0))
                    ]

                self.opt = QJL.Mopt_Rotation(
                    s=self.s, POVM_basis=self.povm_basis, seed=self.seed
                )

            else:
                raise ValueError(
                    "{!r} is not a valid value for the first input of minput, supported values are 'LC' and 'rotation'.".format(
                        self.minput[0]
                    )
                )
        else:
            raise ValueError(
                "{!r} is not a valid value for mtype, supported values are 'projection' and 'input'.".format(
                    self.mtype
                )
            )

        dynamics = QJL.Kraus(self.K, self.dK)
        scheme = QJL.GeneralScheme(probe=self.rho0, param=dynamics)
        self.scheme = scheme

        self.dynamics_type = "Kraus"

    def CFIM(self, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None:
            W = []

        if self.dynamics_type == "dynamics":
            if W == []:
                W = np.eye(len(self.Hamiltonian_derivative))
            self.W = W
        elif self.dynamics_type == "Kraus":
            if W == []:
                W = np.eye(self.para_num)
            self.W = W
        else:
            raise ValueError(
                "Supported type of dynamics are Lindblad and Kraus."
                )

        self.obj = QJL.CFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)
        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save(self.M_num, max_num)

CFIM(W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/MeasurementOpt/MeasurementStruct.py
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
def CFIM(self, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None:
        W = []

    if self.dynamics_type == "dynamics":
        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W
    elif self.dynamics_type == "Kraus":
        if W == []:
            W = np.eye(self.para_num)
        self.W = W
    else:
        raise ValueError(
            "Supported type of dynamics are Lindblad and Kraus."
            )

    self.obj = QJL.CFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)
    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save(self.M_num, max_num)

Kraus(rho0, K, dK)

The parameterization of a state is \begin{align} \rho=\sum_i K_i\rho_0K_i^{\dagger}, \end{align}

where \(\rho\) is the evolved density matrix, \(K_i\) is the Kraus operator.

Parameters

rho0: matrix -- Initial state (density matrix).

K: list -- Kraus operators.

dK: list -- Derivatives of the Kraus operators on the unknown parameters to be estimated. For example, dK[0] is the derivative vector on the first parameter.

Source code in quanestimation/base/MeasurementOpt/MeasurementStruct.py
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
def Kraus(self, rho0, K, dK):
    r"""
    The parameterization of a state is
    \begin{align}
    \rho=\sum_i K_i\rho_0K_i^{\dagger},
    \end{align} 

    where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator.

    Parameters
    ----------
    > **rho0:** `matrix`
        -- Initial state (density matrix).

    > **K:** `list`
        -- Kraus operators.

    > **dK:** `list`
        -- Derivatives of the Kraus operators on the unknown parameters to be 
        estimated. For example, dK[0] is the derivative vector on the first 
        parameter.
    """
    k_num = len(K)
    para_num = len(dK[0])
    self.para_num = para_num
    self.dK = [
        [np.array(dK[i][j], dtype=np.complex128) for j in range(para_num)]
        for i in range(k_num)
    ]
    self.rho0 = np.array(rho0, dtype=np.complex128)
    self.K = [np.array(x, dtype=np.complex128) for x in K]

    if para_num == 1:
        self.para_type = "single_para"
    else:
        self.para_type = "multi_para"

    if self.mtype == "projection":
        self.M_num = len(self.rho0)
        if self.measurement0 == []:
            np.random.seed(self.seed)
            M = [[] for i in range(len(self.rho0))]
            for i in range(len(self.rho0)):
                r_ini = 2 * np.random.random(len(self.rho0)) - np.ones(
                    len(self.rho0)
                )
                r = r_ini / np.linalg.norm(r_ini)
                phi = 2 * np.pi * np.random.random(len(self.rho0))
                M[i] = [r[j] * np.exp(1.0j * phi[j]) for j in range(len(self.rho0))]
            self.C = gramschmidt(np.array(M))
            self.measurement0 = [self.C]
        else:
            self.C = [self.measurement0[0][i] for i in range(len(self.rho0))]
            self.C = [np.array(x, dtype=np.complex128) for x in self.C]
        self.opt = QJL.Mopt_Projection(M=self.C, seed=self.seed)

    elif self.mtype == "input":
        if self.minput[0] == "LC":
            self.M_num = self.minput[2]
            ## optimize the combination of a set of SIC-POVM
            if self.minput[1] == []:
                file_path = os.path.join(
                    os.path.dirname(os.path.dirname(__file__)),
                    "sic_fiducial_vectors/d%d.txt" % (len(self.rho0)),
                )
                data = np.loadtxt(file_path)
                fiducial = data[:, 0] + data[:, 1] * 1.0j
                fiducial = np.array(fiducial).reshape(len(fiducial), 1)
                self.povm_basis = sic_povm(fiducial)
            else:
                ## optimize the combination of a set of given POVMs
                if not isinstance(self.minput[1], list):
                    raise TypeError("The given POVMs should be a list!")
                else:
                    accu = len(str(int(1 / self.eps))) - 1
                    for i in range(len(self.minput[1])):
                        val, vec = np.linalg.eig(self.minput[1])
                        if np.all(val.round(accu) >= 0):
                            pass
                        else:
                            raise TypeError(
                                "The given POVMs should be semidefinite!"
                            )
                    M = np.zeros(
                        (len(self.rho0), len(self.rho0)), dtype=np.complex128
                    )
                    for i in range(len(self.minput[1])):
                        M += self.minput[1][i]
                    if np.all(M.round(accu) - np.identity(len(self.rho0)) == 0):
                        pass
                    else:
                        raise TypeError(
                            "The sum of the given POVMs should be identity matrix!"
                        )
                    self.povm_basis = [
                        np.array(x, dtype=np.complex128) for x in self.minput[1]
                    ]

            if self.measurement0 == []:
                np.random.seed(self.seed)
                self.B = [
                    np.random.random(len(self.povm_basis))
                    for i in range(self.M_num)
                ]
                self.measurement0 = [np.array(self.B)]
            elif len(self.measurement0) >= 1:
                self.B = [
                    self.measurement0[0][i] for i in range(len(self.povm_basis))
                ]
            self.opt = QJL.Mopt_LinearComb(
                B=self.B, POVM_basis=self.povm_basis, M_num=self.M_num, seed=self.seed
            )

        elif self.minput[0] == "rotation":
            self.M_num = len(self.minput[1])
            ## optimize the coefficients of the rotation matrix
            if not isinstance(self.minput[1], list):
                raise TypeError("The given POVMs should be a list!")
            else:
                if self.minput[1] == []:
                    raise TypeError("The initial POVM should not be empty!")
                accu = len(str(int(1 / self.eps))) - 1
                for i in range(len(self.minput[1])):
                    val, vec = np.linalg.eig(self.minput[1])
                    if np.all(val.round(accu) >= 0):
                        pass
                    else:
                        raise TypeError("The given POVMs should be semidefinite!")
                M = np.zeros((len(self.rho0), len(self.rho0)), dtype=np.complex128)
                for i in range(len(self.minput[1])):
                    M += self.minput[1][i]
                if np.all(M.round(accu) - np.identity(len(self.rho0)) == 0):
                    pass
                else:
                    raise TypeError(
                        "The sum of the given POVMs should be identity matrix!"
                    )
                self.povm_basis = [
                    np.array(x, dtype=np.complex128) for x in self.minput[1]
                ]
                self.mtype = "rotation"

            if self.measurement0 == []:
                np.random.seed(self.seed)
                self.s = np.random.random(len(self.rho0) ** 2)
                self.measurement0 = [self.s]
            elif len(self.measurement0) >= 1:
                self.s = [
                    self.measurement0[0][i]
                    for i in range(len(self.rho0) * len(self.rho0))
                ]

            self.opt = QJL.Mopt_Rotation(
                s=self.s, POVM_basis=self.povm_basis, seed=self.seed
            )

        else:
            raise ValueError(
                "{!r} is not a valid value for the first input of minput, supported values are 'LC' and 'rotation'.".format(
                    self.minput[0]
                )
            )
    else:
        raise ValueError(
            "{!r} is not a valid value for mtype, supported values are 'projection' and 'input'.".format(
                self.mtype
            )
        )

    dynamics = QJL.Kraus(self.K, self.dK)
    scheme = QJL.GeneralScheme(probe=self.rho0, param=dynamics)
    self.scheme = scheme

    self.dynamics_type = "Kraus"

__init__(mtype, minput, savefile, measurement0, seed, eps, load)

Initialize the measurement optimization system.

Parameters:

Name Type Description Default
mtype str

Measurement optimization scenario. Options are: "projection" -- Optimization of rank-one projective measurements; "input" -- Optimal linear combination or rotation of a given POVM set.

required
minput list

Additional parameters for the "input" scenario. Use ["LC", [Pi1, Pi2, ...], m] for linear combination with m output operators, or ["rotation", [Pi1, Pi2, ...]] for rotation optimization.

required
savefile bool

Whether to save all measurements during training. If True, all episodes are saved; if False, only the final episode is saved.

required
measurement0 list of arrays

Initial guesses of measurements.

required
seed int

Random seed.

required
eps float

Machine epsilon.

required
load bool

Whether to load measurements from measurements.csv in the current directory as initial values.

required
Source code in quanestimation/base/MeasurementOpt/MeasurementStruct.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def __init__(self, mtype, minput, savefile, measurement0, seed, eps, load):
    r"""
    Initialize the measurement optimization system.

    Args:
        mtype (str): Measurement optimization scenario. Options are:
            ``"projection"`` -- Optimization of rank-one projective
            measurements; ``"input"`` -- Optimal linear combination or
            rotation of a given POVM set.
        minput (list): Additional parameters for the ``"input"`` scenario.
            Use ``["LC", [Pi1, Pi2, ...], m]`` for linear combination with
            ``m`` output operators, or ``["rotation", [Pi1, Pi2, ...]]``
            for rotation optimization.
        savefile (bool): Whether to save all measurements during training.
            If ``True``, all episodes are saved; if ``False``, only the
            final episode is saved.
        measurement0 (list of arrays): Initial guesses of measurements.
        seed (int): Random seed.
        eps (float): Machine epsilon.
        load (bool): Whether to load measurements from ``measurements.csv``
            in the current directory as initial values.
    """

    self.mtype = mtype
    self.minput = minput
    self.savefile = savefile
    self.eps = eps
    self.seed = seed
    self.load = load
    self.measurement0 = measurement0

dynamics(tspan, rho0, H0, dH, Hc=None, ctrl=None, decay=None, dyn_method='expm')

The dynamics of a density matrix is of the form

\[\begin{align} \partial_t\rho &=\mathcal{L}\rho \nonumber \\ &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2} \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right), \end{align}\]

where \(\rho\) is the evolved density matrix, H is the Hamiltonian of the system, \(\Gamma_i\) and \(\gamma_i\) are the \(i\mathrm{th}\) decay operator and corresponding decay rate.

Parameters

tspan: array -- Time length for the evolution.

rho0: matrix -- Initial state (density matrix).

H0: matrix or list -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time- independent and a list of length equal to tspan when it is time-dependent.

dH: list -- Derivatives of the free Hamiltonian on the unknown parameters to be estimated. For example, dH[0] is the derivative vector on the first parameter.

Hc: list -- Control Hamiltonians.

ctrl: list of arrays -- Control coefficients.

decay: list -- Decay operators and the corresponding decay rates. Its input rule is decay=[[\(\Gamma_1\), \(\gamma_1\)], [\(\Gamma_2\),\(\gamma_2\)],...], where \(\Gamma_1\) \((\Gamma_2)\) represents the decay operator and \(\gamma_1\) \((\gamma_2)\) is the corresponding decay rate.

dyn_method: string -- Setting the method for solving the Lindblad dynamics. Options are:
"expm" (default) -- Matrix exponential.
"ode" -- Solving the differential equations directly.

Source code in quanestimation/base/MeasurementOpt/MeasurementStruct.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
def dynamics(self, tspan, rho0, H0, dH, Hc=None, ctrl=None, decay=None, dyn_method="expm"):
    r"""
    The dynamics of a density matrix is of the form  

    \begin{align}
    \partial_t\rho &=\mathcal{L}\rho \nonumber \\
    &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
    \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
    \end{align} 

    where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
    system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
    operator and corresponding decay rate.

    Parameters
    ----------
    > **tspan:** `array`
        -- Time length for the evolution.

    > **rho0:** `matrix`
        -- Initial state (density matrix).

    > **H0:** `matrix or list`
        -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time-
        independent and a list of length equal to `tspan` when it is time-dependent.

    > **dH:** `list`
        -- Derivatives of the free Hamiltonian on the unknown parameters to be 
        estimated. For example, dH[0] is the derivative vector on the first 
        parameter.

    > **Hc:** `list`
        -- Control Hamiltonians.

    > **ctrl:** `list of arrays`
        -- Control coefficients.

    > **decay:** `list`
        -- Decay operators and the corresponding decay rates. Its input rule is 
        decay=[[$\Gamma_1$, $\gamma_1$], [$\Gamma_2$,$\gamma_2$],...], where $\Gamma_1$ 
        $(\Gamma_2)$ represents the decay operator and $\gamma_1$ $(\gamma_2)$ is the 
        corresponding decay rate.

    > **dyn_method:** `string`
        -- Setting the method for solving the Lindblad dynamics. Options are:  
        "expm" (default) -- Matrix exponential.  
        "ode" -- Solving the differential equations directly.
    """

    if Hc is None:
        Hc = []
    if ctrl is None:
        ctrl = []
    if decay is None:
        decay = []

    self.tspan = tspan
    self.rho0 = np.array(rho0, dtype=np.complex128)

    self.dynamics_type = "dynamics"

    if len(dH) == 1:
        self.para_type = "single_para"
    else:
        self.para_type = "multi_para"

    if dyn_method == "expm":
        self.dyn_method = "Expm"
    elif dyn_method == "ode":
        self.dyn_method = "Ode"

    if self.mtype == "projection":
        self.M_num = len(self.rho0)
        QJLType_C = QJL.Vector[QJL.Vector[QJL.ComplexF64]]

        if self.measurement0 == []:
            np.random.seed(self.seed)
            M = [[] for i in range(len(self.rho0))]
            for i in range(len(self.rho0)):
                r_ini = 2 * np.random.random(len(self.rho0)) - np.ones(
                    len(self.rho0)
                )
                r = r_ini / np.linalg.norm(r_ini)
                phi = 2 * np.pi * np.random.random(len(self.rho0))
                M[i] = [r[j] * np.exp(1.0j * phi[j]) for j in range(len(self.rho0))]
            self.C = QJL.convert(QJLType_C, gramschmidt(np.array(M)))
            self.measurement0 = QJL.Vector([self.C])
        else:
            self.C = [self.measurement0[0][i] for i in range(len(self.rho0))]
            self.C = QJL.convert(QJLType_C, self.C)
            self.measurement0 = QJL.Vector([self.C])
        self.opt = QJL.Mopt_Projection(M=self.C, seed=self.seed)

    elif self.mtype == "input":
        if self.minput[0] == "LC":
            self.M_num = self.minput[2]
            ## optimize the combination of a set of SIC-POVM
            if self.minput[1] == []:
                file_path = os.path.join(
                    os.path.dirname(os.path.dirname(__file__)),
                    "sic_fiducial_vectors/d%d.txt" % (len(self.rho0)),
                )
                data = np.loadtxt(file_path)
                fiducial = data[:, 0] + data[:, 1] * 1.0j
                fiducial = np.array(fiducial).reshape(len(fiducial), 1)
                self.povm_basis = sic_povm(fiducial)
            else:
                ## optimize the combination of a set of given POVMs
                if not isinstance(self.minput[1], list):
                    raise TypeError("The given POVMs should be a list!")
                else:
                    accu = len(str(int(1 / self.eps))) - 1
                    for i in range(len(self.minput[1])):
                        val, vec = np.linalg.eig(self.minput[1])
                        if np.all(val.round(accu) >= 0):
                            pass
                        else:
                            raise TypeError(
                                "The given POVMs should be semidefinite!"
                            )
                    M = np.zeros(
                        (len(self.rho0), len(self.rho0)), dtype=np.complex128
                    )
                    for i in range(len(self.minput[1])):
                        M += self.minput[1][i]
                    if np.all(M.round(accu) - np.identity(len(self.rho0)) == 0):
                        pass
                    else:
                        raise TypeError(
                            "The sum of the given POVMs should be identity matrix!"
                        )
                    self.povm_basis = [
                        np.array(x, dtype=np.complex128) for x in self.minput[1]
                    ]

            if self.measurement0 == []:
                np.random.seed(self.seed)
                self.B = [
                    np.random.random(len(self.povm_basis))
                    for i in range(self.M_num)
                ]
                self.measurement0 = [self.B]
            elif len(self.measurement0) >= 1:
                self.B = [self.measurement0[0][i] for i in range(self.M_num)]
                self.measurement0 = [[m for m in m0] for m0 in self.measurement0]


            QJLType_B = QJL.Vector[QJL.Vector[QJL.Float64]]
            QJLType_pb = QJL.Vector[QJL.Matrix[QJL.ComplexF64]]
            QJLType_m0 = QJL.Vector[QJL.Vector[QJL.Vector[QJL.ComplexF64]]]
            self.B = QJL.convert(QJLType_B, self.B)
            self.povm_basis = QJL.convert(QJLType_pb, self.povm_basis)
            self.measurement0 = QJL.convert(QJLType_m0, self.measurement0)

            self.opt = QJL.Mopt_LinearComb(
                B=self.B, POVM_basis=self.povm_basis, M_num=self.M_num, seed=self.seed
            )

        elif self.minput[0] == "rotation":
            self.M_num = len(self.minput[1])
            ## optimize the coefficients of the rotation matrix
            if not isinstance(self.minput[1], list):
                raise TypeError("The given POVMs should be a list!")
            else:
                if self.minput[1] == []:
                    raise TypeError("The initial POVM should not be empty!")
                accu = len(str(int(1 / self.eps))) - 1
                for i in range(len(self.minput[1])):
                    val, vec = np.linalg.eig(self.minput[1])
                    if np.all(val.round(accu) >= 0):
                        pass
                    else:
                        raise TypeError("The given POVMs should be semidefinite!")
                M = np.zeros((len(self.rho0), len(self.rho0)), dtype=np.complex128)
                for i in range(len(self.minput[1])):
                    M += self.minput[1][i]
                if np.all(M.round(accu) - np.identity(len(self.rho0)) == 0):
                    pass
                else:
                    raise TypeError(
                        "The sum of the given POVMs should be identity matrix!"
                    )
                self.povm_basis = [
                    np.array(x, dtype=np.complex128) for x in self.minput[1]
                ]
                self.mtype = "rotation"

            if self.measurement0 == []:
                np.random.seed(self.seed)
                self.s = np.random.random(len(self.rho0) ** 2)
                self.measurement0 = [self.s]
            elif len(self.measurement0) >= 1:
                self.s = [
                    self.measurement0[0][i]
                    for i in range(len(self.rho0) * len(self.rho0))
                ]

            self.s = QJL.Vector(self.s)
            QJLType_pb = QJL.Vector[QJL.Matrix[QJL.ComplexF64]]
            self.povm_basis = QJL.convert(QJLType_pb, self.povm_basis)
            self.opt = QJL.Mopt_Rotation(
                s=self.s, POVM_basis=self.povm_basis, seed=self.seed
            )

        else:
            raise ValueError(
                "{!r} is not a valid value for the first input of minput, supported values are 'LC' and 'rotation'.".format(
                    self.minput[0]
                )
            )
    else:
        raise ValueError(
            "{!r} is not a valid value for mtype, supported values are 'projection' and 'input'.".format(
                self.mtype
            )
        )

    if Hc == [] or ctrl == []:
        if isinstance(H0, np.ndarray):
            self.freeHamiltonian = np.array(H0, dtype=np.complex128)
        else:
            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0]
    else:
        ctrl_num = len(ctrl)
        Hc_num = len(Hc)
        if Hc_num < ctrl_num:
            raise TypeError(
                "There are %d control Hamiltonians but %d coefficients sequences: too many coefficients sequences"
                % (Hc_num, ctrl_num)
            )
        elif Hc_num > ctrl_num:
            warnings.warn(
                "Not enough coefficients sequences: there are %d control Hamiltonians but %d coefficients sequences. The rest of the control sequences are set to be 0."
                % (Hc_num, ctrl_num),
                DeprecationWarning,
            )
            for i in range(Hc_num - ctrl_num):
                ctrl = np.concatenate((ctrl, np.zeros(len(ctrl[0]))))

        if len(ctrl[0]) == 1:
            if isinstance(H0, np.ndarray):
                H0 = np.array(H0, dtype=np.complex128)
                Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                Htot = H0 + sum([Hc[i] * ctrl[i][0] for i in range(ctrl_num)])
                self.freeHamiltonian = np.array(Htot, dtype=np.complex128)
            else:
                H0 = [np.array(x, dtype=np.complex128) for x in H0]
                Htot = []
                for i in range(len(H0)):
                    Htot.append(
                        H0[i] + sum([Hc[i] * ctrl[i][0] for i in range(ctrl_num)])
                    )
                self.freeHamiltonian = [
                    np.array(x, dtype=np.complex128) for x in Htot
                ]
        else:
            if not isinstance(H0, np.ndarray):
                #### linear interpolation  ####
                f = interp1d(self.tspan, H0, axis=0)
            number = math.ceil((len(self.tspan) - 1) / len(ctrl[0]))
            if (len(self.tspan) - 1) % len(ctrl[0]) != 0:
                tnum = number * len(ctrl[0])
                self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
                if not isinstance(H0, np.ndarray):
                    H0_inter = f(self.tspan)
                    H0 = [np.array(x, dtype=np.complex128) for x in H0_inter]

            if isinstance(H0, np.ndarray):
                H0 = np.array(H0, dtype=np.complex128)
                Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                ctrl = [np.array(ctrl[i]).repeat(number) for i in range(len(Hc))]
                Htot = []
                for i in range(len(ctrl[0])):
                    S_ctrl = sum([Hc[j] * ctrl[j][i] for j in range(len(ctrl))])
                    Htot.append(H0 + S_ctrl)
                self.freeHamiltonian = [
                    np.array(x, dtype=np.complex128) for x in Htot
                ]
            else:
                H0 = [np.array(x, dtype=np.complex128) for x in H0]
                Hc = [np.array(x, dtype=np.complex128) for x in Hc]
                ctrl = [np.array(ctrl[i]).repeat(number) for i in range(len(Hc))]
                Htot = []
                for i in range(len(ctrl[0])):
                    S_ctrl = sum([Hc[j] * ctrl[j][i] for j in range(len(ctrl))])
                    Htot.append(H0[i] + S_ctrl)
                self.freeHamiltonian = [
                    np.array(x, dtype=np.complex128) for x in Htot
                ]

    if not isinstance(dH, list):
        raise TypeError("The derivative of Hamiltonian should be a list!")

    if dH == []:
        dH = [np.zeros((len(self.rho0), len(self.rho0)))]
    self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]

    if decay == []:
        decay_opt = [np.zeros((len(self.rho0), len(self.rho0)))]
        self.gamma = [0.0]
    else:
        decay_opt = [decay[i][0] for i in range(len(decay))]
        self.gamma = [decay[i][1] for i in range(len(decay))]
    self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

    decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
    dynamics = QJL.Lindblad(
        self.freeHamiltonian, self.Hamiltonian_derivative,
        self.tspan,
        decay=decay,
        dyn_method=self.dyn_method,
    )
    scheme = QJL.GeneralScheme(probe=self.rho0, param=dynamics)
    self.scheme = scheme

    self.dynamics_type = "dynamics"

load_save(mnum, max_episode)

Load optimized measurements saved by the Julia backend from measurements.dat and save them as a measurements.npy file.

The Julia backend writes measurements.dat atomically (temp → rename), and the file is deleted after a successful read to avoid stale data from interfering with subsequent runs.

Parameters:

Name Type Description Default
mnum int

Number of measurement operators.

required
max_episode int

Maximum number of episodes.

required
Source code in quanestimation/base/MeasurementOpt/MeasurementStruct.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def load_save(self, mnum, max_episode):
    r"""
    Load optimized measurements saved by the Julia backend from
    ``measurements.dat`` and save them as a ``measurements.npy`` file.

    The Julia backend writes ``measurements.dat`` atomically (temp → rename),
    and the file is deleted after a successful read to avoid stale data
    from interfering with subsequent runs.

    Args:
        mnum (int): Number of measurement operators.
        max_episode (int): Maximum number of episodes.
    """
    load_and_save("measurements.dat", "measurements", "measurements",
                   self.savefile, item_count=mnum, max_episode=max_episode,
                   nested=True, complex_view=True)

Measurement optimization with AD

Bases: MeasurementSystem

Attributes

savefile: bool -- Whether or not to save all the measurements.
If set True then the measurements and the values of the objective function obtained in all episodes will be saved during the training. If set False the measurement in the final episode and the values of the objective function in all episodes will be saved.

Adam: bool -- Whether or not to use Adam for updating measurements.

measurement0: list of arrays -- Initial guesses of measurements.

max_episode: int -- The number of episodes.

epsilon: float -- Learning rate.

beta1: float -- The exponential decay rate for the first moment estimates.

beta2: float -- The exponential decay rate for the second moment estimates.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load measurements in the current location.
If set True then the program will load measurement from "measurements.csv" file in the current location and use it as the initial measurement.

Source code in quanestimation/base/MeasurementOpt/AD_Mopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
class AD_Mopt(Measurement.MeasurementSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the measurements.  
        If set `True` then the measurements and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the measurement in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **Adam:** `bool`
        -- Whether or not to use Adam for updating measurements.

    > **measurement0:** `list of arrays`
        -- Initial guesses of measurements.

    > **max_episode:** `int`
        -- The number of episodes.

    > **epsilon:** `float`
        -- Learning rate.

    > **beta1:** `float`
        -- The exponential decay rate for the first moment estimates.

    > **beta2:** `float`
        -- The exponential decay rate for the second moment estimates.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load measurements in the current location.  
        If set `True` then the program will load measurement from "measurements.csv"
        file in the current location and use it as the initial measurement.
    """

    def __init__(
        self,
        mtype,
        minput,
        savefile=False,
        Adam=False,
        measurement0=[],
        max_episode=300,
        epsilon=0.01,
        beta1=0.90,
        beta2=0.99,
        seed=1234,
        eps=1e-8,
        load=False,
    ):
        r"""
        Initialize automatic differentiation (AD) for measurement optimization.

        Supports both gradient descent and Adam optimizer. Note: AD is only
        available for ``mtype="input"`` (not for projective measurements).

        Args:
            mtype (str): Measurement optimization scenario. ``"projection"`` or
                ``"input"``.
            minput (list): Additional input parameters for the ``"input"``
                scenario.
            savefile (bool, optional): Whether to save all measurements during
                training. Default ``False``.
            Adam (bool, optional): Whether to use the Adam optimizer.
                Default ``False``.
            measurement0 (list of arrays, optional): Initial guesses of
                measurements. Default ``[]``.
            max_episode (int, optional): Number of training episodes.
                Default 300.
            epsilon (float, optional): Learning rate. Default 0.01.
            beta1 (float, optional): Exponential decay rate for first moment
                estimates (Adam). Default 0.90.
            beta2 (float, optional): Exponential decay rate for second moment
                estimates (Adam). Default 0.99.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
            load (bool, optional): Whether to load initial measurements from
                ``measurements.csv``. Default ``False``.
        """

        Measurement.MeasurementSystem.__init__(
            self, mtype, minput, savefile, measurement0, seed, eps, load 
        )

        self.Adam = Adam
        self.max_episode = max_episode
        self.epsilon = epsilon
        self.beta1 = beta1
        self.beta2 = beta2
        self.mt = 0.0
        self.vt = 0.0
        self.seed = seed

        if self.Adam:
            self.alg = QJL.AD(
                Adam=True,
                max_episode=self.max_episode,
                epsilon=self.epsilon,
                beta1=self.beta1,
                beta2=self.beta2,
            )
        else:
            self.alg = QJL.AD(
                Adam=False,
                max_episode=self.max_episode,
                epsilon=self.epsilon,
            )

    def CFIM(self, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None:
            W = []

        if self.mtype == "projection":
            raise ValueError(
                "AD is not available when mtype is projection. Supported methods are 'PSO' and 'DE'.",
            )
        else:
            super().CFIM(W)

CFIM(W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/MeasurementOpt/AD_Mopt.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def CFIM(self, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None:
        W = []

    if self.mtype == "projection":
        raise ValueError(
            "AD is not available when mtype is projection. Supported methods are 'PSO' and 'DE'.",
        )
    else:
        super().CFIM(W)

__init__(mtype, minput, savefile=False, Adam=False, measurement0=[], max_episode=300, epsilon=0.01, beta1=0.9, beta2=0.99, seed=1234, eps=1e-08, load=False)

Initialize automatic differentiation (AD) for measurement optimization.

Supports both gradient descent and Adam optimizer. Note: AD is only available for mtype="input" (not for projective measurements).

Parameters:

Name Type Description Default
mtype str

Measurement optimization scenario. "projection" or "input".

required
minput list

Additional input parameters for the "input" scenario.

required
savefile bool

Whether to save all measurements during training. Default False.

False
Adam bool

Whether to use the Adam optimizer. Default False.

False
measurement0 list of arrays

Initial guesses of measurements. Default [].

[]
max_episode int

Number of training episodes. Default 300.

300
epsilon float

Learning rate. Default 0.01.

0.01
beta1 float

Exponential decay rate for first moment estimates (Adam). Default 0.90.

0.9
beta2 float

Exponential decay rate for second moment estimates (Adam). Default 0.99.

0.99
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
load bool

Whether to load initial measurements from measurements.csv. Default False.

False
Source code in quanestimation/base/MeasurementOpt/AD_Mopt.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def __init__(
    self,
    mtype,
    minput,
    savefile=False,
    Adam=False,
    measurement0=[],
    max_episode=300,
    epsilon=0.01,
    beta1=0.90,
    beta2=0.99,
    seed=1234,
    eps=1e-8,
    load=False,
):
    r"""
    Initialize automatic differentiation (AD) for measurement optimization.

    Supports both gradient descent and Adam optimizer. Note: AD is only
    available for ``mtype="input"`` (not for projective measurements).

    Args:
        mtype (str): Measurement optimization scenario. ``"projection"`` or
            ``"input"``.
        minput (list): Additional input parameters for the ``"input"``
            scenario.
        savefile (bool, optional): Whether to save all measurements during
            training. Default ``False``.
        Adam (bool, optional): Whether to use the Adam optimizer.
            Default ``False``.
        measurement0 (list of arrays, optional): Initial guesses of
            measurements. Default ``[]``.
        max_episode (int, optional): Number of training episodes.
            Default 300.
        epsilon (float, optional): Learning rate. Default 0.01.
        beta1 (float, optional): Exponential decay rate for first moment
            estimates (Adam). Default 0.90.
        beta2 (float, optional): Exponential decay rate for second moment
            estimates (Adam). Default 0.99.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
        load (bool, optional): Whether to load initial measurements from
            ``measurements.csv``. Default ``False``.
    """

    Measurement.MeasurementSystem.__init__(
        self, mtype, minput, savefile, measurement0, seed, eps, load 
    )

    self.Adam = Adam
    self.max_episode = max_episode
    self.epsilon = epsilon
    self.beta1 = beta1
    self.beta2 = beta2
    self.mt = 0.0
    self.vt = 0.0
    self.seed = seed

    if self.Adam:
        self.alg = QJL.AD(
            Adam=True,
            max_episode=self.max_episode,
            epsilon=self.epsilon,
            beta1=self.beta1,
            beta2=self.beta2,
        )
    else:
        self.alg = QJL.AD(
            Adam=False,
            max_episode=self.max_episode,
            epsilon=self.epsilon,
        )

Measurement Optimization with PSO

Bases: MeasurementSystem

Attributes

savefile: bool -- Whether or not to save all the measurements.
If set True then the measurements and the values of the objective function obtained in all episodes will be saved during the training. If set False the measurement in the final episode and the values of the objective function in all episodes will be saved.

p_num: int -- The number of particles.

measurement0: list of arrays -- Initial guesses of measurements.

max_episode: int or list -- If it is an integer, for example max_episode=1000, it means the program will continuously run 1000 episodes. However, if it is an array, for example max_episode=[1000,100], the program will run 1000 episodes in total but replace measurements of all the particles with global best every 100 episodes.

c0: float -- The damping factor that assists convergence, also known as inertia weight.

c1: float -- The exploitation weight that attracts the particle to its best previous position, also known as cognitive learning factor.

c2: float -- The exploitation weight that attracts the particle to the best position
in the neighborhood, also known as social learning factor.

seed: int -- Random seed.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load measurements in the current location.
If set True then the program will load measurement from "measurements.csv" file in the current location and use it as the initial measurement.

Source code in quanestimation/base/MeasurementOpt/PSO_Mopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class PSO_Mopt(Measurement.MeasurementSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the measurements.  
        If set `True` then the measurements and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the measurement in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **p_num:** `int`
        -- The number of particles.

    > **measurement0:** `list of arrays`
        -- Initial guesses of measurements.

    > **max_episode:** `int or list`
        -- If it is an integer, for example max_episode=1000, it means the 
        program will continuously run 1000 episodes. However, if it is an
        array, for example max_episode=[1000,100], the program will run 
        1000 episodes in total but replace measurements of all  the particles 
        with global best every 100 episodes.

    > **c0:** `float`
        -- The damping factor that assists convergence, also known as inertia weight.

    > **c1:** `float`
        -- The exploitation weight that attracts the particle to its best previous 
        position, also known as cognitive learning factor.

    > **c2:** `float`
        -- The exploitation weight that attracts the particle to the best position  
        in the neighborhood, also known as social learning factor.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load measurements in the current location.  
        If set `True` then the program will load measurement from "measurements.csv"
        file in the current location and use it as the initial measurement.
    """

    def __init__(
        self,
        mtype,
        minput,
        savefile=False,
        p_num=10,
        measurement0=[],
        max_episode=[1000, 100],
        c0=1.0,
        c1=2.0,
        c2=2.0,
        seed=1234,
        eps=1e-8,
        load=False,
    ):
        r"""
        Initialize particle swarm optimization for measurement optimization.

        Args:
            mtype (str): Measurement optimization scenario. ``"projection"`` or
                ``"input"``.
            minput (list): Additional input parameters for the ``"input"``
                scenario.
            savefile (bool, optional): Whether to save all measurements during
                training. Default ``False``.
            p_num (int, optional): Number of particles. Default 10.
            measurement0 (list of arrays, optional): Initial guesses of
                measurements. Default ``[]``.
            max_episode (int or list, optional): Number of episodes. If a list
                ``[total, reset_interval]``, particles are reset to the global
                best every ``reset_interval`` episodes. Default ``[1000, 100]``.
            c0 (float, optional): Inertia weight (damping factor).
                Default 1.0.
            c1 (float, optional): Cognitive learning factor. Default 2.0.
            c2 (float, optional): Social learning factor. Default 2.0.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
            load (bool, optional): Whether to load initial measurements from
                ``measurements.csv``. Default ``False``.
        """

        Measurement.MeasurementSystem.__init__(
            self, mtype, minput, savefile, measurement0, seed, eps, load
        )

        self.p_num = p_num
        is_int = isinstance(max_episode, int)
        self.max_episode = max_episode if is_int else QJL.Vector[QJL.Int64](max_episode)
        self.c0 = c0
        self.c1 = c1
        self.c2 = c2
        self.seed = seed

    def CFIM(self, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None:
            W = []

        ini_particle = (self.measurement0,)
        self.alg = QJL.PSO(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_particle=ini_particle,
            c0=self.c0,
            c1=self.c1,
            c2=self.c2,
        )

        super().CFIM(W)

CFIM(W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/MeasurementOpt/PSO_Mopt.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def CFIM(self, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None:
        W = []

    ini_particle = (self.measurement0,)
    self.alg = QJL.PSO(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_particle=ini_particle,
        c0=self.c0,
        c1=self.c1,
        c2=self.c2,
    )

    super().CFIM(W)

__init__(mtype, minput, savefile=False, p_num=10, measurement0=[], max_episode=[1000, 100], c0=1.0, c1=2.0, c2=2.0, seed=1234, eps=1e-08, load=False)

Initialize particle swarm optimization for measurement optimization.

Parameters:

Name Type Description Default
mtype str

Measurement optimization scenario. "projection" or "input".

required
minput list

Additional input parameters for the "input" scenario.

required
savefile bool

Whether to save all measurements during training. Default False.

False
p_num int

Number of particles. Default 10.

10
measurement0 list of arrays

Initial guesses of measurements. Default [].

[]
max_episode int or list

Number of episodes. If a list [total, reset_interval], particles are reset to the global best every reset_interval episodes. Default [1000, 100].

[1000, 100]
c0 float

Inertia weight (damping factor). Default 1.0.

1.0
c1 float

Cognitive learning factor. Default 2.0.

2.0
c2 float

Social learning factor. Default 2.0.

2.0
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
load bool

Whether to load initial measurements from measurements.csv. Default False.

False
Source code in quanestimation/base/MeasurementOpt/PSO_Mopt.py
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def __init__(
    self,
    mtype,
    minput,
    savefile=False,
    p_num=10,
    measurement0=[],
    max_episode=[1000, 100],
    c0=1.0,
    c1=2.0,
    c2=2.0,
    seed=1234,
    eps=1e-8,
    load=False,
):
    r"""
    Initialize particle swarm optimization for measurement optimization.

    Args:
        mtype (str): Measurement optimization scenario. ``"projection"`` or
            ``"input"``.
        minput (list): Additional input parameters for the ``"input"``
            scenario.
        savefile (bool, optional): Whether to save all measurements during
            training. Default ``False``.
        p_num (int, optional): Number of particles. Default 10.
        measurement0 (list of arrays, optional): Initial guesses of
            measurements. Default ``[]``.
        max_episode (int or list, optional): Number of episodes. If a list
            ``[total, reset_interval]``, particles are reset to the global
            best every ``reset_interval`` episodes. Default ``[1000, 100]``.
        c0 (float, optional): Inertia weight (damping factor).
            Default 1.0.
        c1 (float, optional): Cognitive learning factor. Default 2.0.
        c2 (float, optional): Social learning factor. Default 2.0.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
        load (bool, optional): Whether to load initial measurements from
            ``measurements.csv``. Default ``False``.
    """

    Measurement.MeasurementSystem.__init__(
        self, mtype, minput, savefile, measurement0, seed, eps, load
    )

    self.p_num = p_num
    is_int = isinstance(max_episode, int)
    self.max_episode = max_episode if is_int else QJL.Vector[QJL.Int64](max_episode)
    self.c0 = c0
    self.c1 = c1
    self.c2 = c2
    self.seed = seed

Measurement Optimization with DE

Bases: MeasurementSystem

Attributes

savefile: bool -- Whether or not to save all the measurements.
If set True then the measurements and the values of the objective function obtained in all episodes will be saved during the training. If set False the measurement in the final episode and the values of the objective function in all episodes will be saved.

p_num: int -- The number of populations.

measurement0: list of arrays -- Initial guesses of measurements.

max_episode: int -- The number of episodes.

c: float -- Mutation constant.

cr: float -- Crossover constant.

seed: int -- Random seed.

eps: float -- Machine epsilon.

load: bool -- Whether or not to load measurements in the current location.
If set True then the program will load measurement from "measurements.csv" file in the current location and use it as the initial measurement.

Source code in quanestimation/base/MeasurementOpt/DE_Mopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
class DE_Mopt(Measurement.MeasurementSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the measurements.  
        If set `True` then the measurements and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the measurement in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **p_num:** `int`
        -- The number of populations.

    > **measurement0:** `list of arrays`
        -- Initial guesses of measurements.

    > **max_episode:** `int`
        -- The number of episodes.

    > **c:** `float`
        -- Mutation constant.

    > **cr:** `float`
        -- Crossover constant.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    > **load:** `bool`
        -- Whether or not to load measurements in the current location.  
        If set `True` then the program will load measurement from "measurements.csv"
        file in the current location and use it as the initial measurement.
    """

    def __init__(
        self,
        mtype,
        minput,
        savefile=False,
        p_num=10,
        measurement0=[],
        max_episode=1000,
        c=1.0,
        cr=0.5,
        seed=1234,
        eps=1e-8,
        load=False,
    ):
        r"""
        Initialize differential evolution (DE) for measurement optimization.

        Args:
            mtype (str): Measurement optimization scenario. ``"projection"`` or
                ``"input"``.
            minput (list): Additional input parameters for the ``"input"``
                scenario.
            savefile (bool, optional): Whether to save all measurements during
                training. Default ``False``.
            p_num (int, optional): Number of populations. Default 10.
            measurement0 (list of arrays, optional): Initial guesses of
                measurements. Default ``[]``.
            max_episode (int, optional): Number of episodes. Default 1000.
            c (float, optional): Mutation constant. Default 1.0.
            cr (float, optional): Crossover constant. Default 0.5.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
            load (bool, optional): Whether to load initial measurements from
                ``measurements.csv``. Default ``False``.
        """

        Measurement.MeasurementSystem.__init__(
            self, mtype, minput, savefile, measurement0, seed, eps, load
        )

        self.p_num = p_num
        self.max_episode = max_episode
        self.c = c
        self.cr = cr

    def CFIM(self, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None:
            W = []

        ini_population = (self.measurement0,)
        self.alg = QJL.DE(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_population=ini_population,
            c=self.c,
            cr=self.cr,
        )
        super().CFIM(W)

CFIM(W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/MeasurementOpt/DE_Mopt.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def CFIM(self, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None:
        W = []

    ini_population = (self.measurement0,)
    self.alg = QJL.DE(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_population=ini_population,
        c=self.c,
        cr=self.cr,
    )
    super().CFIM(W)

__init__(mtype, minput, savefile=False, p_num=10, measurement0=[], max_episode=1000, c=1.0, cr=0.5, seed=1234, eps=1e-08, load=False)

Initialize differential evolution (DE) for measurement optimization.

Parameters:

Name Type Description Default
mtype str

Measurement optimization scenario. "projection" or "input".

required
minput list

Additional input parameters for the "input" scenario.

required
savefile bool

Whether to save all measurements during training. Default False.

False
p_num int

Number of populations. Default 10.

10
measurement0 list of arrays

Initial guesses of measurements. Default [].

[]
max_episode int

Number of episodes. Default 1000.

1000
c float

Mutation constant. Default 1.0.

1.0
cr float

Crossover constant. Default 0.5.

0.5
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
load bool

Whether to load initial measurements from measurements.csv. Default False.

False
Source code in quanestimation/base/MeasurementOpt/DE_Mopt.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def __init__(
    self,
    mtype,
    minput,
    savefile=False,
    p_num=10,
    measurement0=[],
    max_episode=1000,
    c=1.0,
    cr=0.5,
    seed=1234,
    eps=1e-8,
    load=False,
):
    r"""
    Initialize differential evolution (DE) for measurement optimization.

    Args:
        mtype (str): Measurement optimization scenario. ``"projection"`` or
            ``"input"``.
        minput (list): Additional input parameters for the ``"input"``
            scenario.
        savefile (bool, optional): Whether to save all measurements during
            training. Default ``False``.
        p_num (int, optional): Number of populations. Default 10.
        measurement0 (list of arrays, optional): Initial guesses of
            measurements. Default ``[]``.
        max_episode (int, optional): Number of episodes. Default 1000.
        c (float, optional): Mutation constant. Default 1.0.
        cr (float, optional): Crossover constant. Default 0.5.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
        load (bool, optional): Whether to load initial measurements from
            ``measurements.csv``. Default ``False``.
    """

    Measurement.MeasurementSystem.__init__(
        self, mtype, minput, savefile, measurement0, seed, eps, load
    )

    self.p_num = p_num
    self.max_episode = max_episode
    self.c = c
    self.cr = cr

Comprehensive Optimization

In order to obtain the optimal parameter estimation schemes, it is necessary to simultaneously optimize the probe state, control and measurement. The comprehensive optimization for the probe state and measurement (SM), the probe state and control (SC), the control and measurement (CM) and the probe state, control and measurement (SCM) are proposed for this. In QuanEstimation, the comprehensive optimization algorithms are particle swarm optimization (PSO), differential evolution (DE), and automatic differentiation (AD).

Base

Attributes

savefile: bool -- Whether or not to save all the optimized variables (probe states, control coefficients and measurements).
If set True then the optimized variables and the values of the objective function obtained in all episodes will be saved during the training. If set False the optimized variables in the final episode and the values of the objective function in all episodes will be saved.

psi0: list of arrays -- Initial guesses of states.

ctrl0: list of arrays -- Initial guesses of control coefficients.

measurement0: list of arrays -- Initial guesses of measurements.

seed: int -- Random seed.

eps: float -- Machine epsilon.

Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
class ComprehensiveSystem:
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the optimized variables (probe states, 
        control coefficients and measurements).  
        If set `True` then the optimized variables and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the optimized variables in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **psi0:** `list of arrays`
        -- Initial guesses of states.

    > **ctrl0:** `list of arrays`
        -- Initial guesses of control coefficients.

    > **measurement0:** `list of arrays`
        -- Initial guesses of measurements.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.
    """

    def __init__(self, savefile, psi0, ctrl0, measurement0, seed, eps):
        r"""
        Initialize the comprehensive optimization system.

        Args:
            savefile (bool): Whether to save all optimized variables during training.
                If ``True``, variables from all episodes are saved; if ``False``,
                only the final episode variables are saved.
            psi0 (list of arrays): Initial guesses of probe states.
            ctrl0 (list of arrays): Initial guesses of control coefficients.
            measurement0 (list of arrays): Initial guesses of measurements.
            seed (int): Random seed.
            eps (float): Machine epsilon.
        """

        self.savefile = savefile
        self.ctrl0 = ctrl0
        self.psi0 = psi0
        self.eps = eps
        self.seed = seed
        self.measurement0 = measurement0

    def load_save_ctrls(self, cnum, max_episode):
        r"""
        Load control coefficients saved by the Julia backend from ``controls.dat``
        and save them as a ``controls.npy`` file for later analysis.

        The Julia backend writes ``controls.dat`` atomically (temp → rename),
        and the file is deleted after a successful read to avoid stale data
        from interfering with subsequent runs.

        Args:
            cnum (int): Number of control Hamiltonian channels.
            max_episode (int): Maximum number of episodes.
        """
        load_and_save("controls.dat", "controls", "controls",
                       self.savefile, item_count=cnum, max_episode=max_episode,
                       nested=True)

    def load_save_states(self, max_episode):
        r"""
        Load optimized probe states saved by the Julia backend from ``states.dat``
        and save them as a ``states.npy`` file.

        The Julia backend writes ``states.dat`` atomically (temp → rename),
        and the file is deleted after a successful read to avoid stale data
        from interfering with subsequent runs.

        Args:
            max_episode (int): Maximum number of episodes.
        """
        load_and_save("states.dat", "states", "states",
                       self.savefile, max_episode=max_episode,
                       nested=False, complex_view=True)

    def load_save_meas(self, mnum, max_episode):
        r"""
        Load optimized measurements saved by the Julia backend from
        ``measurements.dat`` and save them as a ``measurements.npy`` file.

        The Julia backend writes ``measurements.dat`` atomically (temp → rename),
        and the file is deleted after a successful read to avoid stale data
        from interfering with subsequent runs.

        Args:
            mnum (int): Number of measurement operators.
            max_episode (int): Maximum number of episodes.
        """
        load_and_save("measurements.dat", "measurements", "measurements",
                       self.savefile, item_count=mnum, max_episode=max_episode,
                       nested=True, complex_view=True)

    def dynamics(self, tspan, H0, dH, Hc=None, ctrl=None, decay=None, ctrl_bound=None, dyn_method="expm"):
        r"""
        The dynamics of a density matrix is of the form 

        \begin{align}
        \partial_t\rho &=\mathcal{L}\rho \nonumber \\
        &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
        \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
        \end{align} 

        where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
        system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
        operator and corresponding decay rate.

        Parameters
        ----------
        > **tspan:** `array`
            -- Time length for the evolution.

        > **H0:** `matrix or list`
            -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time-
            independent and a list of length equal to `tspan` when it is time-dependent.

        > **dH:** `list`
            -- Derivatives of the free Hamiltonian on the unknown parameters to be 
            estimated. For example, dH[0] is the derivative vector on the first 
            parameter.

        > **Hc:** `list`
            -- Control Hamiltonians.

        > **ctrl:** `list of arrays`
            -- Control coefficients.

        > **decay:** `list`
            -- Decay operators and the corresponding decay rates. Its input rule is 
            decay=[[$\Gamma_1$, $\gamma_1$], [$\Gamma_2$,$\gamma_2$],...], where $\Gamma_1$ 
            $(\Gamma_2)$ represents the decay operator and $\gamma_1$ $(\gamma_2)$ is the 
            corresponding decay rate.

        > **ctrl_bound:** `array`
            -- Lower and upper bounds of the control coefficients.
            `ctrl_bound[0]` represents the lower bound of the control coefficients and
            `ctrl_bound[1]` represents the upper bound of the control coefficients.

        > **dyn_method:** `string`
            -- Setting the method for solving the Lindblad dynamics. Options are:  
            "expm" (default) -- Matrix exponential.  
            "ode" -- Solving the differential equations directly. 
        """

        if Hc is None: Hc = []
        if ctrl is None: ctrl = []
        if decay is None: decay = []
        if ctrl_bound is None: ctrl_bound = []

        self.tspan = tspan
        self.ctrl = ctrl
        self.Hc = Hc

        if dyn_method == "expm":
            self.dyn_method = "Expm"
        elif dyn_method == "ode":
            self.dyn_method = "Ode"

        if isinstance(H0, np.ndarray):
            self.freeHamiltonian = np.array(H0, dtype=np.complex128)
            self.dim = len(self.freeHamiltonian)
        else:
            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0]
            self.dim = len(self.freeHamiltonian[0])

        if self.psi0 == []:
            np.random.seed(self.seed)
            r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
            r = r_ini / np.linalg.norm(r_ini)
            phi = 2 * np.pi * np.random.random(self.dim)
            psi = np.array([r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)])
            self.psi0 = np.array(psi)
            self.psi = jlconvert(jl.Vector[jl.ComplexF64], list(self.psi0))
        else:
            self.psi0 = np.array(self.psi0[0], dtype=np.complex128)
            self.psi = jlconvert(jl.Vector[jl.ComplexF64], list(self.psi0))

        if Hc == []:
            Hc = [np.zeros((self.dim, self.dim))]
        self.control_Hamiltonian = [np.array(x, dtype=np.complex128) for x in Hc]

        if not isinstance(dH, list):
            raise TypeError("The derivative of Hamiltonian should be a list!")

        if dH == []:
            dH = [np.zeros((self.dim, self.dim))]
        self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]

        if len(dH) == 1:
            self.para_type = "single_para"
        else:
            self.para_type = "multi_para"

        if decay == []:
            decay_opt = [np.zeros((self.dim, self.dim))]
            self.gamma = [0.0]
        else:
            decay_opt = [decay[i][0] for i in range(len(decay))]
            self.gamma = [decay[i][1] for i in range(len(decay))]
        self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

        if ctrl_bound == []:
            self.ctrl_bound = [-np.inf, np.inf]
        else:
            self.ctrl_bound = [float(ctrl_bound[0]), float(ctrl_bound[1])]

        if self.ctrl0 == []:
            if ctrl_bound == []:
                ctrl0 = [
                    2 * np.random.random(len(self.tspan) - 1)
                    - np.ones(len(self.tspan) - 1)
                    for i in range(len(self.control_Hamiltonian))
                ]
            else:
                a = ctrl_bound[0]
                b = ctrl_bound[1]
                ctrl0 = [
                    (b - a) * np.random.random(len(self.tspan) - 1)
                    + a * np.ones(len(self.tspan) - 1)
                    for i in range(len(self.control_Hamiltonian))
                ]
            self.control_coefficients = ctrl0
            self.ctrl0 = [np.array(ctrl0)]

        elif len(self.ctrl0) >= 1:
            self.control_coefficients = [
                self.ctrl0[0][i] for i in range(len(self.control_Hamiltonian))
            ]

        ctrl_num = len(self.control_coefficients)
        Hc_num = len(self.control_Hamiltonian)
        if Hc_num < ctrl_num:
            raise TypeError(
                "There are %d control Hamiltonians but %d coefficients sequences: \
                                too many coefficients sequences"
                % (Hc_num, ctrl_num)
            )
        elif Hc_num > ctrl_num:
            warnings.warn(
                "Not enough coefficients sequences: there are %d control Hamiltonians \
                            but %d coefficients sequences. The rest of the control sequences are\
                            set to be 0."
                % (Hc_num, ctrl_num),
                DeprecationWarning,
            )
            for i in range(Hc_num - ctrl_num):
                self.control_coefficients = np.concatenate(
                    (
                        self.control_coefficients,
                        np.zeros(len(self.control_coefficients[0])),
                    )
                )

        QJLType_ctrl = QJL.Vector[QJL.Vector[QJL.Float64]]
        self.ctrl0 = QJL.convert(QJLType_ctrl, [list(c) for c in self.ctrl0[0]])

        QJLType_C = QJL.Vector[QJL.Vector[QJL.ComplexF64]]
        if self.measurement0 == []:
            np.random.seed(self.seed)
            M = [[] for i in range(self.dim)]
            for i in range(self.dim):
                r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
                r = r_ini / np.linalg.norm(r_ini)
                phi = 2 * np.pi * np.random.random(self.dim)
                M[i] = [r[j] * np.exp(1.0j * phi[j]) for j in range(self.dim)]
            self.C = QJL.convert(QJLType_C, gramschmidt(np.array(M)))
            self.measurement0 = self.C

        if not isinstance(H0, np.ndarray):
            #### linear interpolation  ####
            f = interp1d(self.tspan, H0, axis=0)
        number = math.ceil((len(self.tspan) - 1) / len(self.control_coefficients[0]))
        if (len(self.tspan) - 1) % len(self.control_coefficients[0]) != 0:
            tnum = number * len(self.control_coefficients[0])
            self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
            if not isinstance(H0, np.ndarray):
                H0_inter = f(self.tspan)
                self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter]

        self.dynamics_type = "dynamics"

    def Kraus(self, K, dK):
        r"""
        The parameterization of a state is
        \begin{align}
        \rho=\sum_i K_i\rho_0K_i^{\dagger},
        \end{align} 

        where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator.

        Parameters
        ----------
        > **K:** `list`
            -- Kraus operators.

        > **dK:** `list`
            -- Derivatives of the Kraus operators on the unknown parameters to be 
            estimated. For example, dK[0] is the derivative vector on the first 
            parameter.
        """

        k_num = len(K)
        para_num = len(dK[0])
        self.para_num = para_num
        dK_tp = [
            [np.array(dK[i][j], dtype=np.complex128) for j in range(para_num)]
            for i in range(k_num)
        ]
        self.K = [np.array(x, dtype=np.complex128) for x in K]
        self.dK = dK_tp

        if para_num == 1:
            self.para_type = "single_para"
        else:
            self.para_type = "multi_para"

        self.dim = len(K[0])
        if self.psi0 == []:
            np.random.seed(self.seed)
            r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
            r = r_ini / np.linalg.norm(r_ini)
            phi = 2 * np.pi * np.random.random(self.dim)
            psi = np.array([r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)])
            self.psi0 = np.array(psi)
            self.psi = jlconvert(jl.Vector[jl.ComplexF64], list(self.psi0))
        else:
            self.psi0 = np.array(self.psi0[0], dtype=np.complex128)
            self.psi = jlconvert(jl.Vector[jl.ComplexF64], list(self.psi0))

        if self.measurement0 == []:
            np.random.seed(self.seed)
            M = [[] for i in range(self.dim)]
            for i in range(self.dim):
                r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
                r = r_ini / np.linalg.norm(r_ini)
                phi = 2 * np.pi * np.random.random(self.dim)
                M[i] = [r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)]
            self.C = gramschmidt(np.array(M))
            self.measurement0 = [np.array([self.C[i] for i in range(len(self.psi))])]
        elif len(self.measurement0) >= 1:
            self.C = [self.measurement0[0][i] for i in range(len(self.psi))]
            self.C = [np.array(x, dtype=np.complex128) for x in self.C]

        dynamics = QJL.Kraus(self.K, self.dK)
        self.scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)

        self.dynamics_type = "Kraus"

    def SC(self, W=None, M=None, target="QFIM", LDtype="SLD"):
        """
        Comprehensive optimization of the probe state and control (SC).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        > **target:** `string`
            -- Objective functions for comprehensive optimization. Options are:  
            "QFIM" (default) -- choose QFI (QFIM) as the objective function.  
            "CFIM" -- choose CFI (CFIM) as the objective function.  
            "HCRB" -- choose HCRB as the objective function.  

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD). 

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if W is None: W = []
        if M is None: M = []

        if self.dynamics_type != "dynamics":
            raise ValueError(
                "Supported type of dynamics is Lindblad."
                )

        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

        if M != []:
            M = [np.array(x, dtype=np.complex128) for x in M]
            self.obj = QJL.CFIM_obj(M=jlconvert(jl.Vector[jl.Matrix[jl.ComplexF64]], M), W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        else:
            if target == "HCRB":
                if self.para_type == "single_para":
                    print(
                        "Program terminated. In the single-parameter scenario, the HCRB is equivalent to the QFI. Please choose 'QFIM' as the objective function"
                    )
                else:
                    self.obj = QJL.HCRB_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
            elif target == "QFIM" and (
                LDtype == "SLD" or LDtype == "RLD" or LDtype == "LLD"
            ):
                self.obj = QJL.QFIM_obj(
                    W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps, LDtype=jl.Symbol(LDtype)
                )
            elif target == "CFIM":
                M = SIC(len(self.psi))
                self.obj = QJL.CFIM_obj(M=jlconvert(jl.Vector[jl.Matrix[jl.ComplexF64]], M), W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
            else:
                raise ValueError(
                    "Please enter the correct values for target and LDtype. Supported target are 'QFIM', 'CFIM' and 'HCRB', supported LDtype are 'SLD', 'RLD' and 'LLD'."
                )

        self.opt = QJL.StateControlOpt(
            psi=self.psi, ctrl=self.ctrl0, ctrl_bound=jlconvert(jl.Vector[jl.Float64], self.ctrl_bound), seed=self.seed
        )
        decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
        dynamics = QJL.Lindblad(
            self.freeHamiltonian, self.Hamiltonian_derivative,
            self.tspan, self.control_Hamiltonian, decay,
            ctrl=self.control_coefficients,
            dyn_method=self.dyn_method,
        )
        self.scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save_states(max_num)
        self.load_save_ctrls(len(self.control_Hamiltonian), max_num)

    def CM(self, rho0, W=None):
        """
        Comprehensive optimization of the control and measurement (CM).

        Parameters
        ----------
        > **rho0:** `matrix`
            -- Initial state (density matrix).

        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None: W = []

        if self.dynamics_type != "dynamics":
            raise ValueError(
                "Supported type of dynamics is Lindblad."
                )

        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

        self.rho0 = np.array(rho0, dtype=np.complex128)

        self.obj = QJL.CFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        self.opt = QJL.ControlMeasurementOpt(
            ctrl=self.ctrl0, M=self.C, ctrl_bound=jlconvert(jl.Vector[jl.Float64], self.ctrl_bound), seed=self.seed
        )
        decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
        dynamics = QJL.Lindblad(
            self.freeHamiltonian, self.Hamiltonian_derivative,
            self.tspan, self.control_Hamiltonian, decay,
            ctrl=self.control_coefficients,
            dyn_method=self.dyn_method,
        )
        self.scheme = QJL.GeneralScheme(probe=self.rho0, param=dynamics)
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save_ctrls(len(self.control_Hamiltonian), max_num)
        self.load_save_meas(self.dim, max_num)

    def SM(self, W=None):
        """
        Comprehensive optimization of the probe state and measurement (SM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None: W = []

        if self.dynamics_type == "dynamics":
            if W == []:
                W = np.eye(len(self.Hamiltonian_derivative))
            self.W = W

            if self.Hc == [] or self.ctrl == []:
                freeHamiltonian = self.freeHamiltonian
            else:
                ctrl_num = len(self.ctrl)
                Hc_num = len(self.control_Hamiltonian)
                if Hc_num < ctrl_num:
                    raise TypeError(
                        "There are %d control Hamiltonians but %d coefficients sequences: \
                                 too many coefficients sequences."
                        % (Hc_num, ctrl_num)
                    )
                elif Hc_num > ctrl_num:
                    warnings.warn(
                        "Not enough coefficients sequences: there are %d control Hamiltonians \
                               but %d coefficients sequences. The rest of the control sequences are\
                               set to be 0."
                        % (Hc_num, ctrl_num),
                        DeprecationWarning,
                    )
                    for i in range(Hc_num - ctrl_num):
                        self.ctrl = np.concatenate(
                            (self.ctrl, np.zeros(len(self.ctrl[0])))
                        )
                if len(self.ctrl[0]) == 1:
                    if isinstance(self.freeHamiltonian, np.ndarray):
                        H0 = np.array(self.freeHamiltonian, dtype=np.complex128)
                        Hc = [
                            np.array(x, dtype=np.complex128)
                            for x in self.control_Hamiltonian
                        ]
                        Htot = H0 + sum(
                            [
                                self.control_Hamiltonian[i] * self.ctrl[i][0]
                                for i in range(ctrl_num)
                            ]
                        )
                        freeHamiltonian = np.array(Htot, dtype=np.complex128)
                    else:
                        H0 = [
                            np.array(x, dtype=np.complex128)
                            for x in self.freeHamiltonian
                        ]
                        Htot = []
                        for i in range(len(H0)):
                            Htot.append(
                                H0[i]
                                + sum(
                                    [
                                        self.control_Hamiltonian[i] * self.ctrl[i][0]
                                        for i in range(ctrl_num)
                                    ]
                                )
                            )
                        freeHamiltonian = [
                            np.array(x, dtype=np.complex128) for x in Htot
                        ]
                else:
                    if not isinstance(self.freeHamiltonian, np.ndarray):
                        #### linear interpolation  ####
                        f = interp1d(self.tspan, self.freeHamiltonian, axis=0)
                    number = math.ceil((len(self.tspan) - 1) / len(self.ctrl[0]))
                    if (len(self.tspan) - 1) % len(self.ctrl[0]) != 0:
                        tnum = number * len(self.ctrl[0])
                        self.tspan = np.linspace(
                            self.tspan[0], self.tspan[-1], tnum + 1
                        )
                        if not isinstance(self.freeHamiltonian, np.ndarray):
                            H0_inter = f(self.tspan)
                            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter]

                    if isinstance(self.freeHamiltonian, np.ndarray):
                        H0 = np.array(self.freeHamiltonian, dtype=np.complex128)
                        Hc = [
                            np.array(x, dtype=np.complex128)
                            for x in self.control_Hamiltonian
                        ]
                        self.ctrl = [np.array(self.ctrl[i]).repeat(number) for i in range(len(Hc))]
                        Htot = []
                        for i in range(len(self.ctrl[0])):
                            S_ctrl = sum(
                                [Hc[j] * self.ctrl[j][i] for j in range(len(self.ctrl))]
                            )
                            Htot.append(H0 + S_ctrl)
                        freeHamiltonian = [
                            np.array(x, dtype=np.complex128) for x in Htot
                        ]
                    else:
                        H0 = [
                            np.array(x, dtype=np.complex128)
                            for x in self.freeHamiltonian
                        ]
                        Hc = [
                            np.array(x, dtype=np.complex128)
                            for x in self.control_Hamiltonian
                        ]
                        self.ctrl = [np.array(self.ctrl[i]).repeat(number) for i in range(len(Hc))]
                        Htot = []
                        for i in range(len(self.ctrl[0])):
                            S_ctrl = sum(
                                [Hc[j] * self.ctrl[j][i] for j in range(len(self.ctrl))]
                            )
                            Htot.append(H0[i] + S_ctrl)
                        freeHamiltonian = [
                            np.array(x, dtype=np.complex128) for x in Htot
                        ]

            decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
            dynamics = QJL.Lindblad(
                freeHamiltonian, self.Hamiltonian_derivative,
                self.tspan, decay=decay,
                dyn_method=self.dyn_method,
            )
            self.scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
        elif self.dynamics_type == "Kraus":
            if W == []:
                W = np.eye(self.para_num)
            self.W = W
        else:
            raise ValueError(
                "Supported type of dynamics are Lindblad and Kraus."
                )

        self.obj = QJL.CFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        self.opt = QJL.StateMeasurementOpt(psi=self.psi, M=self.C, seed=self.seed)
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save_states(max_num)
        self.load_save_meas(self.dim, max_num)

    def SCM(self, W=None):
        """
        Comprehensive optimization of the probe state, control and measurement (SCM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None: W = []

        if self.dynamics_type != "dynamics":
            raise ValueError(
                "Supported type of dynamics is Lindblad."
                )
        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

        self.obj = QJL.CFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        self.opt = QJL.StateControlMeasurementOpt(
            psi=self.psi, ctrl=self.ctrl0, M=self.C, ctrl_bound=jlconvert(jl.Vector[jl.Float64], self.ctrl_bound), seed=self.seed
        )
        decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
        dynamics = QJL.Lindblad(
            self.freeHamiltonian, self.Hamiltonian_derivative,
            self.tspan, self.control_Hamiltonian, decay,
            ctrl=self.control_coefficients,
            dyn_method=self.dyn_method,
        )
        self.scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
        getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

        max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
        self.load_save_states(max_num)
        self.load_save_ctrls(len(self.control_Hamiltonian), max_num)
        self.load_save_meas(self.dim, max_num)

CM(rho0, W=None)

Comprehensive optimization of the control and measurement (CM).

Parameters

rho0: matrix -- Initial state (density matrix).

W: matrix -- Weight matrix.

Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
def CM(self, rho0, W=None):
    """
    Comprehensive optimization of the control and measurement (CM).

    Parameters
    ----------
    > **rho0:** `matrix`
        -- Initial state (density matrix).

    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None: W = []

    if self.dynamics_type != "dynamics":
        raise ValueError(
            "Supported type of dynamics is Lindblad."
            )

    if W == []:
        W = np.eye(len(self.Hamiltonian_derivative))
    self.W = W

    self.rho0 = np.array(rho0, dtype=np.complex128)

    self.obj = QJL.CFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
    self.opt = QJL.ControlMeasurementOpt(
        ctrl=self.ctrl0, M=self.C, ctrl_bound=jlconvert(jl.Vector[jl.Float64], self.ctrl_bound), seed=self.seed
    )
    decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
    dynamics = QJL.Lindblad(
        self.freeHamiltonian, self.Hamiltonian_derivative,
        self.tspan, self.control_Hamiltonian, decay,
        ctrl=self.control_coefficients,
        dyn_method=self.dyn_method,
    )
    self.scheme = QJL.GeneralScheme(probe=self.rho0, param=dynamics)
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save_ctrls(len(self.control_Hamiltonian), max_num)
    self.load_save_meas(self.dim, max_num)

Kraus(K, dK)

The parameterization of a state is \begin{align} \rho=\sum_i K_i\rho_0K_i^{\dagger}, \end{align}

where \(\rho\) is the evolved density matrix, \(K_i\) is the Kraus operator.

Parameters

K: list -- Kraus operators.

dK: list -- Derivatives of the Kraus operators on the unknown parameters to be estimated. For example, dK[0] is the derivative vector on the first parameter.

Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
def Kraus(self, K, dK):
    r"""
    The parameterization of a state is
    \begin{align}
    \rho=\sum_i K_i\rho_0K_i^{\dagger},
    \end{align} 

    where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator.

    Parameters
    ----------
    > **K:** `list`
        -- Kraus operators.

    > **dK:** `list`
        -- Derivatives of the Kraus operators on the unknown parameters to be 
        estimated. For example, dK[0] is the derivative vector on the first 
        parameter.
    """

    k_num = len(K)
    para_num = len(dK[0])
    self.para_num = para_num
    dK_tp = [
        [np.array(dK[i][j], dtype=np.complex128) for j in range(para_num)]
        for i in range(k_num)
    ]
    self.K = [np.array(x, dtype=np.complex128) for x in K]
    self.dK = dK_tp

    if para_num == 1:
        self.para_type = "single_para"
    else:
        self.para_type = "multi_para"

    self.dim = len(K[0])
    if self.psi0 == []:
        np.random.seed(self.seed)
        r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
        r = r_ini / np.linalg.norm(r_ini)
        phi = 2 * np.pi * np.random.random(self.dim)
        psi = np.array([r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)])
        self.psi0 = np.array(psi)
        self.psi = jlconvert(jl.Vector[jl.ComplexF64], list(self.psi0))
    else:
        self.psi0 = np.array(self.psi0[0], dtype=np.complex128)
        self.psi = jlconvert(jl.Vector[jl.ComplexF64], list(self.psi0))

    if self.measurement0 == []:
        np.random.seed(self.seed)
        M = [[] for i in range(self.dim)]
        for i in range(self.dim):
            r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
            r = r_ini / np.linalg.norm(r_ini)
            phi = 2 * np.pi * np.random.random(self.dim)
            M[i] = [r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)]
        self.C = gramschmidt(np.array(M))
        self.measurement0 = [np.array([self.C[i] for i in range(len(self.psi))])]
    elif len(self.measurement0) >= 1:
        self.C = [self.measurement0[0][i] for i in range(len(self.psi))]
        self.C = [np.array(x, dtype=np.complex128) for x in self.C]

    dynamics = QJL.Kraus(self.K, self.dK)
    self.scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)

    self.dynamics_type = "Kraus"

SC(W=None, M=None, target='QFIM', LDtype='SLD')

Comprehensive optimization of the probe state and control (SC).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

target: string -- Objective functions for comprehensive optimization. Options are:
"QFIM" (default) -- choose QFI (QFIM) as the objective function.
"CFIM" -- choose CFI (CFIM) as the objective function.
"HCRB" -- choose HCRB as the objective function.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
def SC(self, W=None, M=None, target="QFIM", LDtype="SLD"):
    """
    Comprehensive optimization of the probe state and control (SC).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    > **target:** `string`
        -- Objective functions for comprehensive optimization. Options are:  
        "QFIM" (default) -- choose QFI (QFIM) as the objective function.  
        "CFIM" -- choose CFI (CFIM) as the objective function.  
        "HCRB" -- choose HCRB as the objective function.  

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD). 

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if W is None: W = []
    if M is None: M = []

    if self.dynamics_type != "dynamics":
        raise ValueError(
            "Supported type of dynamics is Lindblad."
            )

    if W == []:
        W = np.eye(len(self.Hamiltonian_derivative))
    self.W = W

    if M != []:
        M = [np.array(x, dtype=np.complex128) for x in M]
        self.obj = QJL.CFIM_obj(M=jlconvert(jl.Vector[jl.Matrix[jl.ComplexF64]], M), W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
    else:
        if target == "HCRB":
            if self.para_type == "single_para":
                print(
                    "Program terminated. In the single-parameter scenario, the HCRB is equivalent to the QFI. Please choose 'QFIM' as the objective function"
                )
            else:
                self.obj = QJL.HCRB_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        elif target == "QFIM" and (
            LDtype == "SLD" or LDtype == "RLD" or LDtype == "LLD"
        ):
            self.obj = QJL.QFIM_obj(
                W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps, LDtype=jl.Symbol(LDtype)
            )
        elif target == "CFIM":
            M = SIC(len(self.psi))
            self.obj = QJL.CFIM_obj(M=jlconvert(jl.Vector[jl.Matrix[jl.ComplexF64]], M), W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
        else:
            raise ValueError(
                "Please enter the correct values for target and LDtype. Supported target are 'QFIM', 'CFIM' and 'HCRB', supported LDtype are 'SLD', 'RLD' and 'LLD'."
            )

    self.opt = QJL.StateControlOpt(
        psi=self.psi, ctrl=self.ctrl0, ctrl_bound=jlconvert(jl.Vector[jl.Float64], self.ctrl_bound), seed=self.seed
    )
    decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
    dynamics = QJL.Lindblad(
        self.freeHamiltonian, self.Hamiltonian_derivative,
        self.tspan, self.control_Hamiltonian, decay,
        ctrl=self.control_coefficients,
        dyn_method=self.dyn_method,
    )
    self.scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save_states(max_num)
    self.load_save_ctrls(len(self.control_Hamiltonian), max_num)

SCM(W=None)

Comprehensive optimization of the probe state, control and measurement (SCM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
def SCM(self, W=None):
    """
    Comprehensive optimization of the probe state, control and measurement (SCM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None: W = []

    if self.dynamics_type != "dynamics":
        raise ValueError(
            "Supported type of dynamics is Lindblad."
            )
    if W == []:
        W = np.eye(len(self.Hamiltonian_derivative))
    self.W = W

    self.obj = QJL.CFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
    self.opt = QJL.StateControlMeasurementOpt(
        psi=self.psi, ctrl=self.ctrl0, M=self.C, ctrl_bound=jlconvert(jl.Vector[jl.Float64], self.ctrl_bound), seed=self.seed
    )
    decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
    dynamics = QJL.Lindblad(
        self.freeHamiltonian, self.Hamiltonian_derivative,
        self.tspan, self.control_Hamiltonian, decay,
        ctrl=self.control_coefficients,
        dyn_method=self.dyn_method,
    )
    self.scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save_states(max_num)
    self.load_save_ctrls(len(self.control_Hamiltonian), max_num)
    self.load_save_meas(self.dim, max_num)

SM(W=None)

Comprehensive optimization of the probe state and measurement (SM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
def SM(self, W=None):
    """
    Comprehensive optimization of the probe state and measurement (SM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None: W = []

    if self.dynamics_type == "dynamics":
        if W == []:
            W = np.eye(len(self.Hamiltonian_derivative))
        self.W = W

        if self.Hc == [] or self.ctrl == []:
            freeHamiltonian = self.freeHamiltonian
        else:
            ctrl_num = len(self.ctrl)
            Hc_num = len(self.control_Hamiltonian)
            if Hc_num < ctrl_num:
                raise TypeError(
                    "There are %d control Hamiltonians but %d coefficients sequences: \
                             too many coefficients sequences."
                    % (Hc_num, ctrl_num)
                )
            elif Hc_num > ctrl_num:
                warnings.warn(
                    "Not enough coefficients sequences: there are %d control Hamiltonians \
                           but %d coefficients sequences. The rest of the control sequences are\
                           set to be 0."
                    % (Hc_num, ctrl_num),
                    DeprecationWarning,
                )
                for i in range(Hc_num - ctrl_num):
                    self.ctrl = np.concatenate(
                        (self.ctrl, np.zeros(len(self.ctrl[0])))
                    )
            if len(self.ctrl[0]) == 1:
                if isinstance(self.freeHamiltonian, np.ndarray):
                    H0 = np.array(self.freeHamiltonian, dtype=np.complex128)
                    Hc = [
                        np.array(x, dtype=np.complex128)
                        for x in self.control_Hamiltonian
                    ]
                    Htot = H0 + sum(
                        [
                            self.control_Hamiltonian[i] * self.ctrl[i][0]
                            for i in range(ctrl_num)
                        ]
                    )
                    freeHamiltonian = np.array(Htot, dtype=np.complex128)
                else:
                    H0 = [
                        np.array(x, dtype=np.complex128)
                        for x in self.freeHamiltonian
                    ]
                    Htot = []
                    for i in range(len(H0)):
                        Htot.append(
                            H0[i]
                            + sum(
                                [
                                    self.control_Hamiltonian[i] * self.ctrl[i][0]
                                    for i in range(ctrl_num)
                                ]
                            )
                        )
                    freeHamiltonian = [
                        np.array(x, dtype=np.complex128) for x in Htot
                    ]
            else:
                if not isinstance(self.freeHamiltonian, np.ndarray):
                    #### linear interpolation  ####
                    f = interp1d(self.tspan, self.freeHamiltonian, axis=0)
                number = math.ceil((len(self.tspan) - 1) / len(self.ctrl[0]))
                if (len(self.tspan) - 1) % len(self.ctrl[0]) != 0:
                    tnum = number * len(self.ctrl[0])
                    self.tspan = np.linspace(
                        self.tspan[0], self.tspan[-1], tnum + 1
                    )
                    if not isinstance(self.freeHamiltonian, np.ndarray):
                        H0_inter = f(self.tspan)
                        self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter]

                if isinstance(self.freeHamiltonian, np.ndarray):
                    H0 = np.array(self.freeHamiltonian, dtype=np.complex128)
                    Hc = [
                        np.array(x, dtype=np.complex128)
                        for x in self.control_Hamiltonian
                    ]
                    self.ctrl = [np.array(self.ctrl[i]).repeat(number) for i in range(len(Hc))]
                    Htot = []
                    for i in range(len(self.ctrl[0])):
                        S_ctrl = sum(
                            [Hc[j] * self.ctrl[j][i] for j in range(len(self.ctrl))]
                        )
                        Htot.append(H0 + S_ctrl)
                    freeHamiltonian = [
                        np.array(x, dtype=np.complex128) for x in Htot
                    ]
                else:
                    H0 = [
                        np.array(x, dtype=np.complex128)
                        for x in self.freeHamiltonian
                    ]
                    Hc = [
                        np.array(x, dtype=np.complex128)
                        for x in self.control_Hamiltonian
                    ]
                    self.ctrl = [np.array(self.ctrl[i]).repeat(number) for i in range(len(Hc))]
                    Htot = []
                    for i in range(len(self.ctrl[0])):
                        S_ctrl = sum(
                            [Hc[j] * self.ctrl[j][i] for j in range(len(self.ctrl))]
                        )
                        Htot.append(H0[i] + S_ctrl)
                    freeHamiltonian = [
                        np.array(x, dtype=np.complex128) for x in Htot
                    ]

        decay = [(self.decay_opt[i], self.gamma[i]) for i in range(len(self.decay_opt))]
        dynamics = QJL.Lindblad(
            freeHamiltonian, self.Hamiltonian_derivative,
            self.tspan, decay=decay,
            dyn_method=self.dyn_method,
        )
        self.scheme = QJL.GeneralScheme(probe=self.psi0, param=dynamics)
    elif self.dynamics_type == "Kraus":
        if W == []:
            W = np.eye(self.para_num)
        self.W = W
    else:
        raise ValueError(
            "Supported type of dynamics are Lindblad and Kraus."
            )

    self.obj = QJL.CFIM_obj(W=jlconvert(jl.Matrix[jl.Float64], self.W), eps=self.eps)
    self.opt = QJL.StateMeasurementOpt(psi=self.psi, M=self.C, seed=self.seed)
    getattr(QJL, "optimize!")(self.scheme, self.opt, algorithm=self.alg, objective=self.obj, savefile=self.savefile)

    max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0]
    self.load_save_states(max_num)
    self.load_save_meas(self.dim, max_num)

__init__(savefile, psi0, ctrl0, measurement0, seed, eps)

Initialize the comprehensive optimization system.

Parameters:

Name Type Description Default
savefile bool

Whether to save all optimized variables during training. If True, variables from all episodes are saved; if False, only the final episode variables are saved.

required
psi0 list of arrays

Initial guesses of probe states.

required
ctrl0 list of arrays

Initial guesses of control coefficients.

required
measurement0 list of arrays

Initial guesses of measurements.

required
seed int

Random seed.

required
eps float

Machine epsilon.

required
Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def __init__(self, savefile, psi0, ctrl0, measurement0, seed, eps):
    r"""
    Initialize the comprehensive optimization system.

    Args:
        savefile (bool): Whether to save all optimized variables during training.
            If ``True``, variables from all episodes are saved; if ``False``,
            only the final episode variables are saved.
        psi0 (list of arrays): Initial guesses of probe states.
        ctrl0 (list of arrays): Initial guesses of control coefficients.
        measurement0 (list of arrays): Initial guesses of measurements.
        seed (int): Random seed.
        eps (float): Machine epsilon.
    """

    self.savefile = savefile
    self.ctrl0 = ctrl0
    self.psi0 = psi0
    self.eps = eps
    self.seed = seed
    self.measurement0 = measurement0

dynamics(tspan, H0, dH, Hc=None, ctrl=None, decay=None, ctrl_bound=None, dyn_method='expm')

The dynamics of a density matrix is of the form

\[\begin{align} \partial_t\rho &=\mathcal{L}\rho \nonumber \\ &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2} \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right), \end{align}\]

where \(\rho\) is the evolved density matrix, H is the Hamiltonian of the system, \(\Gamma_i\) and \(\gamma_i\) are the \(i\mathrm{th}\) decay operator and corresponding decay rate.

Parameters

tspan: array -- Time length for the evolution.

H0: matrix or list -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time- independent and a list of length equal to tspan when it is time-dependent.

dH: list -- Derivatives of the free Hamiltonian on the unknown parameters to be estimated. For example, dH[0] is the derivative vector on the first parameter.

Hc: list -- Control Hamiltonians.

ctrl: list of arrays -- Control coefficients.

decay: list -- Decay operators and the corresponding decay rates. Its input rule is decay=[[\(\Gamma_1\), \(\gamma_1\)], [\(\Gamma_2\),\(\gamma_2\)],...], where \(\Gamma_1\) \((\Gamma_2)\) represents the decay operator and \(\gamma_1\) \((\gamma_2)\) is the corresponding decay rate.

ctrl_bound: array -- Lower and upper bounds of the control coefficients. ctrl_bound[0] represents the lower bound of the control coefficients and ctrl_bound[1] represents the upper bound of the control coefficients.

dyn_method: string -- Setting the method for solving the Lindblad dynamics. Options are:
"expm" (default) -- Matrix exponential.
"ode" -- Solving the differential equations directly.

Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
def dynamics(self, tspan, H0, dH, Hc=None, ctrl=None, decay=None, ctrl_bound=None, dyn_method="expm"):
    r"""
    The dynamics of a density matrix is of the form 

    \begin{align}
    \partial_t\rho &=\mathcal{L}\rho \nonumber \\
    &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
    \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
    \end{align} 

    where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
    system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
    operator and corresponding decay rate.

    Parameters
    ----------
    > **tspan:** `array`
        -- Time length for the evolution.

    > **H0:** `matrix or list`
        -- Free Hamiltonian. It is a matrix when the free Hamiltonian is time-
        independent and a list of length equal to `tspan` when it is time-dependent.

    > **dH:** `list`
        -- Derivatives of the free Hamiltonian on the unknown parameters to be 
        estimated. For example, dH[0] is the derivative vector on the first 
        parameter.

    > **Hc:** `list`
        -- Control Hamiltonians.

    > **ctrl:** `list of arrays`
        -- Control coefficients.

    > **decay:** `list`
        -- Decay operators and the corresponding decay rates. Its input rule is 
        decay=[[$\Gamma_1$, $\gamma_1$], [$\Gamma_2$,$\gamma_2$],...], where $\Gamma_1$ 
        $(\Gamma_2)$ represents the decay operator and $\gamma_1$ $(\gamma_2)$ is the 
        corresponding decay rate.

    > **ctrl_bound:** `array`
        -- Lower and upper bounds of the control coefficients.
        `ctrl_bound[0]` represents the lower bound of the control coefficients and
        `ctrl_bound[1]` represents the upper bound of the control coefficients.

    > **dyn_method:** `string`
        -- Setting the method for solving the Lindblad dynamics. Options are:  
        "expm" (default) -- Matrix exponential.  
        "ode" -- Solving the differential equations directly. 
    """

    if Hc is None: Hc = []
    if ctrl is None: ctrl = []
    if decay is None: decay = []
    if ctrl_bound is None: ctrl_bound = []

    self.tspan = tspan
    self.ctrl = ctrl
    self.Hc = Hc

    if dyn_method == "expm":
        self.dyn_method = "Expm"
    elif dyn_method == "ode":
        self.dyn_method = "Ode"

    if isinstance(H0, np.ndarray):
        self.freeHamiltonian = np.array(H0, dtype=np.complex128)
        self.dim = len(self.freeHamiltonian)
    else:
        self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0]
        self.dim = len(self.freeHamiltonian[0])

    if self.psi0 == []:
        np.random.seed(self.seed)
        r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
        r = r_ini / np.linalg.norm(r_ini)
        phi = 2 * np.pi * np.random.random(self.dim)
        psi = np.array([r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)])
        self.psi0 = np.array(psi)
        self.psi = jlconvert(jl.Vector[jl.ComplexF64], list(self.psi0))
    else:
        self.psi0 = np.array(self.psi0[0], dtype=np.complex128)
        self.psi = jlconvert(jl.Vector[jl.ComplexF64], list(self.psi0))

    if Hc == []:
        Hc = [np.zeros((self.dim, self.dim))]
    self.control_Hamiltonian = [np.array(x, dtype=np.complex128) for x in Hc]

    if not isinstance(dH, list):
        raise TypeError("The derivative of Hamiltonian should be a list!")

    if dH == []:
        dH = [np.zeros((self.dim, self.dim))]
    self.Hamiltonian_derivative = [np.array(x, dtype=np.complex128) for x in dH]

    if len(dH) == 1:
        self.para_type = "single_para"
    else:
        self.para_type = "multi_para"

    if decay == []:
        decay_opt = [np.zeros((self.dim, self.dim))]
        self.gamma = [0.0]
    else:
        decay_opt = [decay[i][0] for i in range(len(decay))]
        self.gamma = [decay[i][1] for i in range(len(decay))]
    self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt]

    if ctrl_bound == []:
        self.ctrl_bound = [-np.inf, np.inf]
    else:
        self.ctrl_bound = [float(ctrl_bound[0]), float(ctrl_bound[1])]

    if self.ctrl0 == []:
        if ctrl_bound == []:
            ctrl0 = [
                2 * np.random.random(len(self.tspan) - 1)
                - np.ones(len(self.tspan) - 1)
                for i in range(len(self.control_Hamiltonian))
            ]
        else:
            a = ctrl_bound[0]
            b = ctrl_bound[1]
            ctrl0 = [
                (b - a) * np.random.random(len(self.tspan) - 1)
                + a * np.ones(len(self.tspan) - 1)
                for i in range(len(self.control_Hamiltonian))
            ]
        self.control_coefficients = ctrl0
        self.ctrl0 = [np.array(ctrl0)]

    elif len(self.ctrl0) >= 1:
        self.control_coefficients = [
            self.ctrl0[0][i] for i in range(len(self.control_Hamiltonian))
        ]

    ctrl_num = len(self.control_coefficients)
    Hc_num = len(self.control_Hamiltonian)
    if Hc_num < ctrl_num:
        raise TypeError(
            "There are %d control Hamiltonians but %d coefficients sequences: \
                            too many coefficients sequences"
            % (Hc_num, ctrl_num)
        )
    elif Hc_num > ctrl_num:
        warnings.warn(
            "Not enough coefficients sequences: there are %d control Hamiltonians \
                        but %d coefficients sequences. The rest of the control sequences are\
                        set to be 0."
            % (Hc_num, ctrl_num),
            DeprecationWarning,
        )
        for i in range(Hc_num - ctrl_num):
            self.control_coefficients = np.concatenate(
                (
                    self.control_coefficients,
                    np.zeros(len(self.control_coefficients[0])),
                )
            )

    QJLType_ctrl = QJL.Vector[QJL.Vector[QJL.Float64]]
    self.ctrl0 = QJL.convert(QJLType_ctrl, [list(c) for c in self.ctrl0[0]])

    QJLType_C = QJL.Vector[QJL.Vector[QJL.ComplexF64]]
    if self.measurement0 == []:
        np.random.seed(self.seed)
        M = [[] for i in range(self.dim)]
        for i in range(self.dim):
            r_ini = 2 * np.random.random(self.dim) - np.ones(self.dim)
            r = r_ini / np.linalg.norm(r_ini)
            phi = 2 * np.pi * np.random.random(self.dim)
            M[i] = [r[j] * np.exp(1.0j * phi[j]) for j in range(self.dim)]
        self.C = QJL.convert(QJLType_C, gramschmidt(np.array(M)))
        self.measurement0 = self.C

    if not isinstance(H0, np.ndarray):
        #### linear interpolation  ####
        f = interp1d(self.tspan, H0, axis=0)
    number = math.ceil((len(self.tspan) - 1) / len(self.control_coefficients[0]))
    if (len(self.tspan) - 1) % len(self.control_coefficients[0]) != 0:
        tnum = number * len(self.control_coefficients[0])
        self.tspan = np.linspace(self.tspan[0], self.tspan[-1], tnum + 1)
        if not isinstance(H0, np.ndarray):
            H0_inter = f(self.tspan)
            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter]

    self.dynamics_type = "dynamics"

load_save_ctrls(cnum, max_episode)

Load control coefficients saved by the Julia backend from controls.dat and save them as a controls.npy file for later analysis.

The Julia backend writes controls.dat atomically (temp → rename), and the file is deleted after a successful read to avoid stale data from interfering with subsequent runs.

Parameters:

Name Type Description Default
cnum int

Number of control Hamiltonian channels.

required
max_episode int

Maximum number of episodes.

required
Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def load_save_ctrls(self, cnum, max_episode):
    r"""
    Load control coefficients saved by the Julia backend from ``controls.dat``
    and save them as a ``controls.npy`` file for later analysis.

    The Julia backend writes ``controls.dat`` atomically (temp → rename),
    and the file is deleted after a successful read to avoid stale data
    from interfering with subsequent runs.

    Args:
        cnum (int): Number of control Hamiltonian channels.
        max_episode (int): Maximum number of episodes.
    """
    load_and_save("controls.dat", "controls", "controls",
                   self.savefile, item_count=cnum, max_episode=max_episode,
                   nested=True)

load_save_meas(mnum, max_episode)

Load optimized measurements saved by the Julia backend from measurements.dat and save them as a measurements.npy file.

The Julia backend writes measurements.dat atomically (temp → rename), and the file is deleted after a successful read to avoid stale data from interfering with subsequent runs.

Parameters:

Name Type Description Default
mnum int

Number of measurement operators.

required
max_episode int

Maximum number of episodes.

required
Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def load_save_meas(self, mnum, max_episode):
    r"""
    Load optimized measurements saved by the Julia backend from
    ``measurements.dat`` and save them as a ``measurements.npy`` file.

    The Julia backend writes ``measurements.dat`` atomically (temp → rename),
    and the file is deleted after a successful read to avoid stale data
    from interfering with subsequent runs.

    Args:
        mnum (int): Number of measurement operators.
        max_episode (int): Maximum number of episodes.
    """
    load_and_save("measurements.dat", "measurements", "measurements",
                   self.savefile, item_count=mnum, max_episode=max_episode,
                   nested=True, complex_view=True)

load_save_states(max_episode)

Load optimized probe states saved by the Julia backend from states.dat and save them as a states.npy file.

The Julia backend writes states.dat atomically (temp → rename), and the file is deleted after a successful read to avoid stale data from interfering with subsequent runs.

Parameters:

Name Type Description Default
max_episode int

Maximum number of episodes.

required
Source code in quanestimation/base/ComprehensiveOpt/ComprehensiveStruct.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def load_save_states(self, max_episode):
    r"""
    Load optimized probe states saved by the Julia backend from ``states.dat``
    and save them as a ``states.npy`` file.

    The Julia backend writes ``states.dat`` atomically (temp → rename),
    and the file is deleted after a successful read to avoid stale data
    from interfering with subsequent runs.

    Args:
        max_episode (int): Maximum number of episodes.
    """
    load_and_save("states.dat", "states", "states",
                   self.savefile, max_episode=max_episode,
                   nested=False, complex_view=True)

Comprehensive optimization with AD

Bases: ComprehensiveSystem

Attributes

savefile: bool -- Whether or not to save all the optimized variables (probe states, control coefficients and measurements).
If set True then the optimized variables and the values of the objective function obtained in all episodes will be saved during the training. If set False the optimized variables in the final episode and the values of the objective function in all episodes will be saved.

Adam: bool -- Whether or not to use Adam for updating.

psi0: list of arrays -- Initial guesses of states.

ctrl0: list of arrays -- Initial guesses of control coefficients.

measurement0: list of arrays -- Initial guesses of measurements.

max_episode: int -- The number of episodes.

epsilon: float -- Learning rate.

beta1: float -- The exponential decay rate for the first moment estimates.

beta2: float -- The exponential decay rate for the second moment estimates.

seed: int -- Random seed.

eps: float -- Machine epsilon.

Source code in quanestimation/base/ComprehensiveOpt/AD_Compopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
class AD_Compopt(Comp.ComprehensiveSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the optimized variables (probe states, 
        control coefficients and measurements).  
        If set `True` then the optimized variables and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the optimized variables in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **Adam:** `bool`
        -- Whether or not to use Adam for updating.

    > **psi0:** `list of arrays`
        -- Initial guesses of states.

    > **ctrl0:** `list of arrays`
        -- Initial guesses of control coefficients.

    > **measurement0:** `list of arrays`
        -- Initial guesses of measurements.

    > **max_episode:** `int`
        -- The number of episodes.

    > **epsilon:** `float`
        -- Learning rate.

    > **beta1:** `float`
        -- The exponential decay rate for the first moment estimates.

    > **beta2:** `float`
        -- The exponential decay rate for the second moment estimates.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.
    """
    def __init__(
        self,
        savefile=False,
        Adam=False,
        psi0=[],
        ctrl0=[],
        measurement0=[],
        max_episode=300,
        epsilon=0.01,
        beta1=0.90,
        beta2=0.99,
        seed=1234,
        eps=1e-8,
    ):
        r"""
        Initialize automatic differentiation (AD) for comprehensive optimization.

        Note: AD is only available when the objective function target is
        ``"QFIM"`` (not ``"CFIM"`` or ``"HCRB"``).

        Args:
            savefile (bool, optional): Whether to save all optimized variables
                during training. Default ``False``.
            Adam (bool, optional): Whether to use the Adam optimizer.
                Default ``False``.
            psi0 (list of arrays, optional): Initial guesses of probe states.
                Default ``[]``.
            ctrl0 (list of arrays, optional): Initial guesses of control
                coefficients. Default ``[]``.
            measurement0 (list of arrays, optional): Initial guesses of
                measurements. Default ``[]``.
            max_episode (int, optional): Number of training episodes.
                Default 300.
            epsilon (float, optional): Learning rate. Default 0.01.
            beta1 (float, optional): Exponential decay rate for first moment
                estimates (Adam). Default 0.90.
            beta2 (float, optional): Exponential decay rate for second moment
                estimates (Adam). Default 0.99.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
        """

        Comp.ComprehensiveSystem.__init__(
            self, savefile, psi0, ctrl0, measurement0, seed, eps
        )

        self.Adam = Adam
        self.max_episode = max_episode
        self.epsilon = epsilon
        self.beta1 = beta1
        self.beta2 = beta2
        self.mt = 0.0
        self.vt = 0.0
        self.seed = seed

    def SC(self, W=None, M=None, target="QFIM", LDtype="SLD"):
        """
        Comprehensive optimization of the probe state and control (SC).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        > **target:** `string`
            -- Objective functions for searching the minimum time to reach the given 
            value of the objective function. Options are:  
            "QFIM" (default) -- choose QFI (QFIM) as the objective function.  
            "CFIM" -- choose CFI (CFIM) as the objective function.  
            "HCRB" -- choose HCRB as the objective function.  

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD). 

        **Note:** AD is only available when target is 'QFIM'.
        """
        if W is None: W = []
        if M is None: M = []
        if M != []:
            raise ValueError(
                "AD is not available when target is 'CFIM'. Supported methods are 'PSO' and 'DE'.",
            )
        elif target == "HCRB":
            raise ValueError(
                "AD is not available when the target function is HCRB. Supported methods are 'PSO' and 'DE'.",
            )

        if self.Adam:
            self.alg = QJL.AD(
                Adam=True,
                max_episode=self.max_episode,
                epsilon=self.epsilon,
                beta1=self.beta1,
                beta2=self.beta2,
            )
        else:
            self.alg = QJL.AD(
                Adam=False,
                max_episode=self.max_episode,
                epsilon=self.epsilon,
            )

        super().SC(W, M, target, LDtype)

SC(W=None, M=None, target='QFIM', LDtype='SLD')

Comprehensive optimization of the probe state and control (SC).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

target: string -- Objective functions for searching the minimum time to reach the given value of the objective function. Options are:
"QFIM" (default) -- choose QFI (QFIM) as the objective function.
"CFIM" -- choose CFI (CFIM) as the objective function.
"HCRB" -- choose HCRB as the objective function.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Note: AD is only available when target is 'QFIM'.

Source code in quanestimation/base/ComprehensiveOpt/AD_Compopt.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def SC(self, W=None, M=None, target="QFIM", LDtype="SLD"):
    """
    Comprehensive optimization of the probe state and control (SC).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    > **target:** `string`
        -- Objective functions for searching the minimum time to reach the given 
        value of the objective function. Options are:  
        "QFIM" (default) -- choose QFI (QFIM) as the objective function.  
        "CFIM" -- choose CFI (CFIM) as the objective function.  
        "HCRB" -- choose HCRB as the objective function.  

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD). 

    **Note:** AD is only available when target is 'QFIM'.
    """
    if W is None: W = []
    if M is None: M = []
    if M != []:
        raise ValueError(
            "AD is not available when target is 'CFIM'. Supported methods are 'PSO' and 'DE'.",
        )
    elif target == "HCRB":
        raise ValueError(
            "AD is not available when the target function is HCRB. Supported methods are 'PSO' and 'DE'.",
        )

    if self.Adam:
        self.alg = QJL.AD(
            Adam=True,
            max_episode=self.max_episode,
            epsilon=self.epsilon,
            beta1=self.beta1,
            beta2=self.beta2,
        )
    else:
        self.alg = QJL.AD(
            Adam=False,
            max_episode=self.max_episode,
            epsilon=self.epsilon,
        )

    super().SC(W, M, target, LDtype)

__init__(savefile=False, Adam=False, psi0=[], ctrl0=[], measurement0=[], max_episode=300, epsilon=0.01, beta1=0.9, beta2=0.99, seed=1234, eps=1e-08)

Initialize automatic differentiation (AD) for comprehensive optimization.

Note: AD is only available when the objective function target is "QFIM" (not "CFIM" or "HCRB").

Parameters:

Name Type Description Default
savefile bool

Whether to save all optimized variables during training. Default False.

False
Adam bool

Whether to use the Adam optimizer. Default False.

False
psi0 list of arrays

Initial guesses of probe states. Default [].

[]
ctrl0 list of arrays

Initial guesses of control coefficients. Default [].

[]
measurement0 list of arrays

Initial guesses of measurements. Default [].

[]
max_episode int

Number of training episodes. Default 300.

300
epsilon float

Learning rate. Default 0.01.

0.01
beta1 float

Exponential decay rate for first moment estimates (Adam). Default 0.90.

0.9
beta2 float

Exponential decay rate for second moment estimates (Adam). Default 0.99.

0.99
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
Source code in quanestimation/base/ComprehensiveOpt/AD_Compopt.py
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def __init__(
    self,
    savefile=False,
    Adam=False,
    psi0=[],
    ctrl0=[],
    measurement0=[],
    max_episode=300,
    epsilon=0.01,
    beta1=0.90,
    beta2=0.99,
    seed=1234,
    eps=1e-8,
):
    r"""
    Initialize automatic differentiation (AD) for comprehensive optimization.

    Note: AD is only available when the objective function target is
    ``"QFIM"`` (not ``"CFIM"`` or ``"HCRB"``).

    Args:
        savefile (bool, optional): Whether to save all optimized variables
            during training. Default ``False``.
        Adam (bool, optional): Whether to use the Adam optimizer.
            Default ``False``.
        psi0 (list of arrays, optional): Initial guesses of probe states.
            Default ``[]``.
        ctrl0 (list of arrays, optional): Initial guesses of control
            coefficients. Default ``[]``.
        measurement0 (list of arrays, optional): Initial guesses of
            measurements. Default ``[]``.
        max_episode (int, optional): Number of training episodes.
            Default 300.
        epsilon (float, optional): Learning rate. Default 0.01.
        beta1 (float, optional): Exponential decay rate for first moment
            estimates (Adam). Default 0.90.
        beta2 (float, optional): Exponential decay rate for second moment
            estimates (Adam). Default 0.99.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
    """

    Comp.ComprehensiveSystem.__init__(
        self, savefile, psi0, ctrl0, measurement0, seed, eps
    )

    self.Adam = Adam
    self.max_episode = max_episode
    self.epsilon = epsilon
    self.beta1 = beta1
    self.beta2 = beta2
    self.mt = 0.0
    self.vt = 0.0
    self.seed = seed

Comprehensive Optimization with PSO

Bases: ComprehensiveSystem

Attributes

savefile: bool -- Whether or not to save all the optimized variables (probe states, control coefficients and measurements).
If set True then the optimized variables and the values of the objective function obtained in all episodes will be saved during the training. If set False the optimized variables in the final episode and the values of the objective function in all episodes will be saved.

p_num: int -- The number of particles.

psi0: list of arrays -- Initial guesses of states.

ctrl0: list of arrays -- Initial guesses of control coefficients.

measurement0: list of arrays -- Initial guesses of measurements.

max_episode: int or list -- If it is an integer, for example max_episode=1000, it means the program will continuously run 1000 episodes. However, if it is an array, for example max_episode=[1000,100], the program will run 1000 episodes in total but replace states of all the particles with global best every 100 episodes.

c0: float -- The damping factor that assists convergence, also known as inertia weight.

c1: float -- The exploitation weight that attracts the particle to its best previous position, also known as cognitive learning factor.

c2: float -- The exploitation weight that attracts the particle to the best position
in the neighborhood, also known as social learning factor.

seed: int -- Random seed.

eps: float -- Machine epsilon.

Source code in quanestimation/base/ComprehensiveOpt/PSO_Compopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
class PSO_Compopt(Comp.ComprehensiveSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the optimized variables (probe states, 
        control coefficients and measurements).  
        If set `True` then the optimized variables and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the optimized variables in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **p_num:** `int`
        -- The number of particles.

    > **psi0:** `list of arrays`
        -- Initial guesses of states.

    > **ctrl0:** `list of arrays`
        -- Initial guesses of control coefficients.

    > **measurement0:** `list of arrays`
        -- Initial guesses of measurements.

    > **max_episode:** `int or list`
        -- If it is an integer, for example max_episode=1000, it means the 
        program will continuously run 1000 episodes. However, if it is an
        array, for example max_episode=[1000,100], the program will run 
        1000 episodes in total but replace states of all  the particles 
        with global best every 100 episodes.

    > **c0:** `float`
        -- The damping factor that assists convergence, also known as inertia weight.

    > **c1:** `float`
        -- The exploitation weight that attracts the particle to its best previous 
        position, also known as cognitive learning factor.

    > **c2:** `float`
        -- The exploitation weight that attracts the particle to the best position  
        in the neighborhood, also known as social learning factor.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.
    """

    def __init__(
        self,
        savefile=False,
        p_num=10,
        psi0=[],
        ctrl0=[],
        measurement0=[],
        max_episode=[1000, 100],
        c0=1.0,
        c1=2.0,
        c2=2.0,
        seed=1234,
        eps=1e-8,
    ):
        r"""
        Initialize particle swarm optimization for comprehensive optimization.

        Args:
            savefile (bool, optional): Whether to save all optimized variables
                during training. Default ``False``.
            p_num (int, optional): Number of particles. Default 10.
            psi0 (list of arrays, optional): Initial guesses of probe states.
                Default ``[]``.
            ctrl0 (list of arrays, optional): Initial guesses of control
                coefficients. Default ``[]``.
            measurement0 (list of arrays, optional): Initial guesses of
                measurements. Default ``[]``.
            max_episode (int or list, optional): Number of episodes. If a list
                ``[total, reset_interval]``, particles are reset to the global
                best every ``reset_interval`` episodes. Default ``[1000, 100]``.
            c0 (float, optional): Inertia weight (damping factor).
                Default 1.0.
            c1 (float, optional): Cognitive learning factor. Default 2.0.
            c2 (float, optional): Social learning factor. Default 2.0.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
        """

        Comp.ComprehensiveSystem.__init__(
            self, savefile, psi0, ctrl0, measurement0, seed, eps
        )

        self.p_num = p_num
        self.max_episode = max_episode
        self.c0 = c0
        self.c1 = c1
        self.c2 = c2
        self.seed = seed

    def SC(self, W=None, M=None, target="QFIM", LDtype="SLD"):
        """
        Comprehensive optimization of the probe state and control (SC).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        > **target:** `string`
            -- Objective functions for searching the minimum time to reach the given 
            value of the objective function. Options are:  
            "QFIM" (default) -- choose QFI (QFIM) as the objective function.  
            "CFIM" -- choose CFI (CFIM) as the objective function.  
            "HCRB" -- choose HCRB as the objective function.  

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD). 

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """
        if W is None: W = []
        if M is None: M = []
        ini_particle = (
            [self.psi], 
            [self.ctrl0]
       )

        self.alg = QJL.PSO(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_particle=ini_particle,
            c0=self.c0,
            c1=self.c1,
            c2=self.c2,
        )
        super().SC(W, M, target, LDtype)

    def CM(self, rho0, W=None):
        """
        Comprehensive optimization of the control and measurement (CM).

        Parameters
        ----------
        > **rho0:** `matrix`
            -- Initial state (density matrix).

        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None: W = []
        ini_particle = ([self.ctrl0], [self.measurement0])
        self.alg = QJL.PSO(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_particle=ini_particle,
            c0=self.c0,
            c1=self.c1,
            c2=self.c2,
        )

        super().CM(rho0, W)

    def SM(self, W=None):
        """
        Comprehensive optimization of the probe state and measurement (SM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None: W = []
        ini_particle = ([self.psi], [self.measurement0])
        self.alg = QJL.PSO(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_particle=ini_particle,
            c0=self.c0,
            c1=self.c1,
            c2=self.c2,
        )

        super().SM(W)

    def SCM(self, W=None):
        """
        Comprehensive optimization of the probe state, the control and measurements (SCM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None: W = []
        ini_particle = ([self.psi], [self.ctrl0], [self.measurement0])
        self.alg = QJL.PSO(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_particle=ini_particle,
            c0=self.c0,
            c1=self.c1,
            c2=self.c2,
        )

        super().SCM(W)

CM(rho0, W=None)

Comprehensive optimization of the control and measurement (CM).

Parameters

rho0: matrix -- Initial state (density matrix).

W: matrix -- Weight matrix.

Source code in quanestimation/base/ComprehensiveOpt/PSO_Compopt.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def CM(self, rho0, W=None):
    """
    Comprehensive optimization of the control and measurement (CM).

    Parameters
    ----------
    > **rho0:** `matrix`
        -- Initial state (density matrix).

    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None: W = []
    ini_particle = ([self.ctrl0], [self.measurement0])
    self.alg = QJL.PSO(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_particle=ini_particle,
        c0=self.c0,
        c1=self.c1,
        c2=self.c2,
    )

    super().CM(rho0, W)

SC(W=None, M=None, target='QFIM', LDtype='SLD')

Comprehensive optimization of the probe state and control (SC).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

target: string -- Objective functions for searching the minimum time to reach the given value of the objective function. Options are:
"QFIM" (default) -- choose QFI (QFIM) as the objective function.
"CFIM" -- choose CFI (CFIM) as the objective function.
"HCRB" -- choose HCRB as the objective function.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ComprehensiveOpt/PSO_Compopt.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def SC(self, W=None, M=None, target="QFIM", LDtype="SLD"):
    """
    Comprehensive optimization of the probe state and control (SC).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    > **target:** `string`
        -- Objective functions for searching the minimum time to reach the given 
        value of the objective function. Options are:  
        "QFIM" (default) -- choose QFI (QFIM) as the objective function.  
        "CFIM" -- choose CFI (CFIM) as the objective function.  
        "HCRB" -- choose HCRB as the objective function.  

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD). 

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """
    if W is None: W = []
    if M is None: M = []
    ini_particle = (
        [self.psi], 
        [self.ctrl0]
   )

    self.alg = QJL.PSO(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_particle=ini_particle,
        c0=self.c0,
        c1=self.c1,
        c2=self.c2,
    )
    super().SC(W, M, target, LDtype)

SCM(W=None)

Comprehensive optimization of the probe state, the control and measurements (SCM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ComprehensiveOpt/PSO_Compopt.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def SCM(self, W=None):
    """
    Comprehensive optimization of the probe state, the control and measurements (SCM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None: W = []
    ini_particle = ([self.psi], [self.ctrl0], [self.measurement0])
    self.alg = QJL.PSO(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_particle=ini_particle,
        c0=self.c0,
        c1=self.c1,
        c2=self.c2,
    )

    super().SCM(W)

SM(W=None)

Comprehensive optimization of the probe state and measurement (SM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ComprehensiveOpt/PSO_Compopt.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def SM(self, W=None):
    """
    Comprehensive optimization of the probe state and measurement (SM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None: W = []
    ini_particle = ([self.psi], [self.measurement0])
    self.alg = QJL.PSO(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_particle=ini_particle,
        c0=self.c0,
        c1=self.c1,
        c2=self.c2,
    )

    super().SM(W)

__init__(savefile=False, p_num=10, psi0=[], ctrl0=[], measurement0=[], max_episode=[1000, 100], c0=1.0, c1=2.0, c2=2.0, seed=1234, eps=1e-08)

Initialize particle swarm optimization for comprehensive optimization.

Parameters:

Name Type Description Default
savefile bool

Whether to save all optimized variables during training. Default False.

False
p_num int

Number of particles. Default 10.

10
psi0 list of arrays

Initial guesses of probe states. Default [].

[]
ctrl0 list of arrays

Initial guesses of control coefficients. Default [].

[]
measurement0 list of arrays

Initial guesses of measurements. Default [].

[]
max_episode int or list

Number of episodes. If a list [total, reset_interval], particles are reset to the global best every reset_interval episodes. Default [1000, 100].

[1000, 100]
c0 float

Inertia weight (damping factor). Default 1.0.

1.0
c1 float

Cognitive learning factor. Default 2.0.

2.0
c2 float

Social learning factor. Default 2.0.

2.0
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
Source code in quanestimation/base/ComprehensiveOpt/PSO_Compopt.py
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def __init__(
    self,
    savefile=False,
    p_num=10,
    psi0=[],
    ctrl0=[],
    measurement0=[],
    max_episode=[1000, 100],
    c0=1.0,
    c1=2.0,
    c2=2.0,
    seed=1234,
    eps=1e-8,
):
    r"""
    Initialize particle swarm optimization for comprehensive optimization.

    Args:
        savefile (bool, optional): Whether to save all optimized variables
            during training. Default ``False``.
        p_num (int, optional): Number of particles. Default 10.
        psi0 (list of arrays, optional): Initial guesses of probe states.
            Default ``[]``.
        ctrl0 (list of arrays, optional): Initial guesses of control
            coefficients. Default ``[]``.
        measurement0 (list of arrays, optional): Initial guesses of
            measurements. Default ``[]``.
        max_episode (int or list, optional): Number of episodes. If a list
            ``[total, reset_interval]``, particles are reset to the global
            best every ``reset_interval`` episodes. Default ``[1000, 100]``.
        c0 (float, optional): Inertia weight (damping factor).
            Default 1.0.
        c1 (float, optional): Cognitive learning factor. Default 2.0.
        c2 (float, optional): Social learning factor. Default 2.0.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
    """

    Comp.ComprehensiveSystem.__init__(
        self, savefile, psi0, ctrl0, measurement0, seed, eps
    )

    self.p_num = p_num
    self.max_episode = max_episode
    self.c0 = c0
    self.c1 = c1
    self.c2 = c2
    self.seed = seed

Comprehensive Optimization with DE

Bases: ComprehensiveSystem

Attributes

savefile: bool -- Whether or not to save all the optimized variables (probe states, control coefficients and measurements).
If set True then the optimized variables and the values of the objective function obtained in all episodes will be saved during the training. If set False the optimized variables in the final episode and the values of the objective function in all episodes will be saved.

p_num: int -- The number of populations.

psi0: list of arrays -- Initial guesses of states.

ctrl0: list of arrays -- Initial guesses of control coefficients.

measurement0: list of arrays -- Initial guesses of measurements.

max_episode: int -- The number of episodes.

c: float -- Mutation constant.

cr: float -- Crossover constant.

seed: int -- Random seed.

eps: float -- Machine epsilon.

Source code in quanestimation/base/ComprehensiveOpt/DE_Compopt.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
class DE_Compopt(Comp.ComprehensiveSystem):
    """
    Attributes
    ----------
    > **savefile:** `bool`
        -- Whether or not to save all the optimized variables (probe states, 
        control coefficients and measurements).  
        If set `True` then the optimized variables and the values of the 
        objective function obtained in all episodes will be saved during 
        the training. If set `False` the optimized variables in the final 
        episode and the values of the objective function in all episodes 
        will be saved.

    > **p_num:** `int`
        -- The number of populations.

    > **psi0:** `list of arrays`
        -- Initial guesses of states.

    > **ctrl0:** `list of arrays`
        -- Initial guesses of control coefficients.

    > **measurement0:** `list of arrays`
        -- Initial guesses of measurements.

    > **max_episode:** `int`
        -- The number of episodes.

    > **c:** `float`
        -- Mutation constant.

    > **cr:** `float`
        -- Crossover constant.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.
    """
    def __init__(
        self,
        savefile=False,
        p_num=10,
        psi0=[],
        ctrl0=[],
        measurement0=[],
        max_episode=1000,
        c=1.0,
        cr=0.5,
        seed=1234,
        eps=1e-8,
    ):
        r"""
        Initialize differential evolution (DE) for comprehensive optimization.

        Args:
            savefile (bool, optional): Whether to save all optimized variables
                during training. Default ``False``.
            p_num (int, optional): Number of populations. Default 10.
            psi0 (list of arrays, optional): Initial guesses of probe states.
                Default ``[]``.
            ctrl0 (list of arrays, optional): Initial guesses of control
                coefficients. Default ``[]``.
            measurement0 (list of arrays, optional): Initial guesses of
                measurements. Default ``[]``.
            max_episode (int, optional): Number of episodes. Default 1000.
            c (float, optional): Mutation constant. Default 1.0.
            cr (float, optional): Crossover constant. Default 0.5.
            seed (int, optional): Random seed. Default 1234.
            eps (float, optional): Machine epsilon. Default 1e-8.
        """

        Comp.ComprehensiveSystem.__init__(
            self, savefile, psi0, ctrl0, measurement0, seed, eps
        )

        self.p_num = p_num
        self.max_episode = max_episode
        self.c = c
        self.cr = cr
        self.seed = seed

    def SC(self, W=None, M=None, target="QFIM", LDtype="SLD"):
        """
        Comprehensive optimization of the probe state and control (SC).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        > **target:** `string`
            -- Objective functions for searching the minimum time to reach the given 
            value of the objective function. Options are:  
            "QFIM" (default) -- choose QFI (QFIM) as the objective function.  
            "CFIM" -- choose CFI (CFIM) as the objective function.  
            "HCRB" -- choose HCRB as the objective function.  

        > **LDtype:** `string`
            -- Types of QFI (QFIM) can be set as the objective function. Options are:  
            "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
            "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
            "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD). 

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """
        if W is None: W = []
        if M is None: M = []
        ini_population = ([self.psi], [self.ctrl0])
        self.alg = QJL.DE(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_population=ini_population,
            c=self.c,
            cr=self.cr,
        )
        super().SC(W, M, target, LDtype)

    def CM(self, rho0, W=None):
        """
        Comprehensive optimization of the control and measurement (CM).

        Parameters
        ----------
        > **rho0:** `matrix`
            -- Initial state (density matrix).

        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None: W = []
        ini_population = ([self.ctrl0], [self.measurement0])
        self.alg = QJL.DE(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_population=ini_population,
            c=self.c,
            cr=self.cr,
        )
        super().CM(rho0, W)

    def SM(self, W=None):
        """
        Comprehensive optimization of the probe state and measurement (SM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None: W = []
        ini_population = ([self.psi], [self.measurement0])
        self.alg = QJL.DE(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_population=ini_population,
            c=self.c,
            cr=self.cr,
        )
        super().SM(W)

    def SCM(self, W=None):
        """
        Comprehensive optimization of the probe state, control and measurement (SCM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        if W is None: W = []
        ini_population = ([self.psi], [self.ctrl0], [self.measurement0])
        self.alg = QJL.DE(
            max_episode=self.max_episode,
            p_num=self.p_num,
            ini_population=ini_population,
            c=self.c,
            cr=self.cr,
        )
        super().SCM(W)

CM(rho0, W=None)

Comprehensive optimization of the control and measurement (CM).

Parameters

rho0: matrix -- Initial state (density matrix).

W: matrix -- Weight matrix.

Source code in quanestimation/base/ComprehensiveOpt/DE_Compopt.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def CM(self, rho0, W=None):
    """
    Comprehensive optimization of the control and measurement (CM).

    Parameters
    ----------
    > **rho0:** `matrix`
        -- Initial state (density matrix).

    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None: W = []
    ini_population = ([self.ctrl0], [self.measurement0])
    self.alg = QJL.DE(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_population=ini_population,
        c=self.c,
        cr=self.cr,
    )
    super().CM(rho0, W)

SC(W=None, M=None, target='QFIM', LDtype='SLD')

Comprehensive optimization of the probe state and control (SC).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

target: string -- Objective functions for searching the minimum time to reach the given value of the objective function. Options are:
"QFIM" (default) -- choose QFI (QFIM) as the objective function.
"CFIM" -- choose CFI (CFIM) as the objective function.
"HCRB" -- choose HCRB as the objective function.

LDtype: string -- Types of QFI (QFIM) can be set as the objective function. Options are:
"SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).
"RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).
"LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/ComprehensiveOpt/DE_Compopt.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def SC(self, W=None, M=None, target="QFIM", LDtype="SLD"):
    """
    Comprehensive optimization of the probe state and control (SC).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    > **target:** `string`
        -- Objective functions for searching the minimum time to reach the given 
        value of the objective function. Options are:  
        "QFIM" (default) -- choose QFI (QFIM) as the objective function.  
        "CFIM" -- choose CFI (CFIM) as the objective function.  
        "HCRB" -- choose HCRB as the objective function.  

    > **LDtype:** `string`
        -- Types of QFI (QFIM) can be set as the objective function. Options are:  
        "SLD" (default) -- QFI (QFIM) based on symmetric logarithmic derivative (SLD).  
        "RLD" -- QFI (QFIM) based on right logarithmic derivative (RLD).  
        "LLD" -- QFI (QFIM) based on left logarithmic derivative (LLD). 

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """
    if W is None: W = []
    if M is None: M = []
    ini_population = ([self.psi], [self.ctrl0])
    self.alg = QJL.DE(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_population=ini_population,
        c=self.c,
        cr=self.cr,
    )
    super().SC(W, M, target, LDtype)

SCM(W=None)

Comprehensive optimization of the probe state, control and measurement (SCM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ComprehensiveOpt/DE_Compopt.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
def SCM(self, W=None):
    """
    Comprehensive optimization of the probe state, control and measurement (SCM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None: W = []
    ini_population = ([self.psi], [self.ctrl0], [self.measurement0])
    self.alg = QJL.DE(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_population=ini_population,
        c=self.c,
        cr=self.cr,
    )
    super().SCM(W)

SM(W=None)

Comprehensive optimization of the probe state and measurement (SM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/ComprehensiveOpt/DE_Compopt.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def SM(self, W=None):
    """
    Comprehensive optimization of the probe state and measurement (SM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    if W is None: W = []
    ini_population = ([self.psi], [self.measurement0])
    self.alg = QJL.DE(
        max_episode=self.max_episode,
        p_num=self.p_num,
        ini_population=ini_population,
        c=self.c,
        cr=self.cr,
    )
    super().SM(W)

__init__(savefile=False, p_num=10, psi0=[], ctrl0=[], measurement0=[], max_episode=1000, c=1.0, cr=0.5, seed=1234, eps=1e-08)

Initialize differential evolution (DE) for comprehensive optimization.

Parameters:

Name Type Description Default
savefile bool

Whether to save all optimized variables during training. Default False.

False
p_num int

Number of populations. Default 10.

10
psi0 list of arrays

Initial guesses of probe states. Default [].

[]
ctrl0 list of arrays

Initial guesses of control coefficients. Default [].

[]
measurement0 list of arrays

Initial guesses of measurements. Default [].

[]
max_episode int

Number of episodes. Default 1000.

1000
c float

Mutation constant. Default 1.0.

1.0
cr float

Crossover constant. Default 0.5.

0.5
seed int

Random seed. Default 1234.

1234
eps float

Machine epsilon. Default 1e-8.

1e-08
Source code in quanestimation/base/ComprehensiveOpt/DE_Compopt.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def __init__(
    self,
    savefile=False,
    p_num=10,
    psi0=[],
    ctrl0=[],
    measurement0=[],
    max_episode=1000,
    c=1.0,
    cr=0.5,
    seed=1234,
    eps=1e-8,
):
    r"""
    Initialize differential evolution (DE) for comprehensive optimization.

    Args:
        savefile (bool, optional): Whether to save all optimized variables
            during training. Default ``False``.
        p_num (int, optional): Number of populations. Default 10.
        psi0 (list of arrays, optional): Initial guesses of probe states.
            Default ``[]``.
        ctrl0 (list of arrays, optional): Initial guesses of control
            coefficients. Default ``[]``.
        measurement0 (list of arrays, optional): Initial guesses of
            measurements. Default ``[]``.
        max_episode (int, optional): Number of episodes. Default 1000.
        c (float, optional): Mutation constant. Default 1.0.
        cr (float, optional): Crossover constant. Default 0.5.
        seed (int, optional): Random seed. Default 1234.
        eps (float, optional): Machine epsilon. Default 1e-8.
    """

    Comp.ComprehensiveSystem.__init__(
        self, savefile, psi0, ctrl0, measurement0, seed, eps
    )

    self.p_num = p_num
    self.max_episode = max_episode
    self.c = c
    self.cr = cr
    self.seed = seed

Adaptive measurement schemes

In QuanEstimation, the Hamiltonian of the adaptive system should be written as \(H(\textbf{x}+\textbf{u})\) with \(\textbf{x}\) the unknown parameters and \(\textbf{u}\) the tunable parameters. The tunable parameters \(\textbf{u}\) are used to let the Hamiltonian work at the optimal point \(\textbf{x}_{\mathrm{opt}}\).

Adaptive measurement

Attributes

x: list -- The regimes of the parameters for the integral.

p: multidimensional array -- The prior distribution.

rho0: matrix -- Initial state (density matrix).

method: string -- Choose the method for updating the tunable parameters (u). Options are:
"FOP" (default) -- Fix optimal point.
"MI" -- mutual information.

savefile: bool -- Whether or not to save all the posterior distributions.
If set True then three files "pout.npy", "xout.npy" and "y.npy" will be generated including the posterior distributions, the estimated values, and the experimental results in the iterations. If set False the posterior distribution in the final iteration, the estimated values and the experimental results in all iterations will be saved in "pout.npy", "xout.npy" and "y.npy".

max_episode: int -- The number of episodes.

eps: float -- Machine epsilon.

Source code in quanestimation/base/AdaptiveScheme/Adapt.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
class Adapt:
    """
    Attributes
    ----------
    > **x:** `list`
        -- The regimes of the parameters for the integral.

    > **p:** `multidimensional array`
        -- The prior distribution.

    > **rho0:** `matrix`
        -- Initial state (density matrix).

    > **method:** `string`
        -- Choose the method for updating the tunable parameters (u). Options are:  
        "FOP" (default) -- Fix optimal point.  
        "MI" -- mutual information.

    > **savefile:** `bool`
        -- Whether or not to save all the posterior distributions.  
        If set `True` then three files "pout.npy", "xout.npy" and "y.npy" will be 
        generated including the posterior distributions, the estimated values, and
        the experimental results in the iterations. If set `False` the posterior 
        distribution in the final iteration, the estimated values and the experimental 
        results in all iterations will be saved in "pout.npy", "xout.npy" and "y.npy". 

    > **max_episode:** `int`
        -- The number of episodes.

    > **eps:** `float`
        -- Machine epsilon.
    """

    def __init__(self, x, p, rho0, method="FOP", savefile=False, max_episode=1000, eps=1e-8):
        r"""
        Initialize the adaptive scheme for quantum parameter estimation.

        Args:
            x (list): The regimes of the parameters for the integral.
            p (ndarray): The prior distribution.
            rho0 (matrix): Initial state (density matrix).
            method (str, optional): Method for updating the tunable parameters.
                "FOP" (default) -- Fix optimal point. "MI" -- mutual information.
            savefile (bool, optional): Whether to save all posterior distributions.
                Default `False`.
            max_episode (int, optional): The number of episodes. Default 1000.
            eps (float, optional): Machine epsilon. Default 1e-8.
        """

        self.x = x
        self.p = p
        self.rho0 = np.array(rho0, dtype=np.complex128)
        self.max_episode = max_episode
        self.eps = eps
        self.para_num = len(x)
        self.savefile = savefile
        self.method = method
        self.decay = []
        self.k_num = 0

    def dynamics(self, tspan, H, dH, Hc=None, ctrl=None, decay=None, dyn_method="expm"):
        r"""
        Dynamics of the density matrix of the form 

        \begin{align}
        \partial_t\rho &=\mathcal{L}\rho \nonumber \\
        &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
        \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
        \end{align} 

        where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
        system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
        operator and decay rate.

        Parameters
        ----------
        > **tspan:** `array`
            -- Time length for the evolution.

        > **H0:** `multidimensional list`
            -- Free Hamiltonian with respect to the values in x.

        > **dH:** `multidimensional list`
            -- Derivatives of the free Hamiltonian with respect to the unknown parameters 
            to be estimated.

        > **Hc:** `list`
            -- Control Hamiltonians.

        > **ctrl:** `list`
            -- Control coefficients.

        > **decay:** `list`
            -- Decay operators and the corresponding decay rates. Its input rule is 
            `decay=[[Gamma1, gamma1], [Gamma2,gamma2],...]`, where `Gamma1 (Gamma2)` 
            represents the decay operator and `gamma1 (gamma2)` is the corresponding 
            decay rate.

        > **dyn_method:** `string`
            -- Setting the method for solving the Lindblad dynamics. Options are:  
            "expm" (default) -- Matrix exponential.  
            "ode" -- Solving the differential equations directly.
        """

        if Hc is None: Hc = []
        if ctrl is None: ctrl = []
        if decay is None: decay = []

        self.tspan = tspan
        self.H = H
        self.dH = dH
        self.Hc = Hc
        self.ctrl = ctrl
        self.decay = decay

        self.dynamic_type = "dynamics"
        self.dyn_method = dyn_method

    def Kraus(self, K, dK):
        r"""
        Dynamics of the density matrix of the form 
        \begin{align}
        \rho=\sum_i K_i\rho_0K_i^{\dagger}
        \end{align}

        where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator.

        Parameters
        ----------
        > **K:** `multidimensional list`
            -- Kraus operator(s) with respect to the values in x.

        > **dK:** `multidimensional list`
            -- Derivatives of the Kraus operator(s) with respect to the unknown parameters 
            to be estimated.
        """

        self.K = K
        self.dK = dK
        self.k_num = len(K[0])

        self.dynamic_type = "Kraus"

    def CFIM(self, M=None, W=None):
        r"""
        Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
        In single parameter estimation the objective function is CFI and 
        in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.

        > **M:** `list of matrices`
            -- A set of positive operator-valued measure (POVM). The default measurement 
            is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

        **Note:** 
            SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
            which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
            solutions.html).
        """

        if M is None: M = []
        if W is None: W = []

        if M == []:
            M = SIC(len(self.rho0))
        if W == []:
            W = np.eye(len(self.x))
        self.W = W

        if self.dynamic_type == "dynamics":
            adaptive_dynamics(
                self.x,
                self.p,
                M,
                self.tspan,
                self.rho0,
                self.H,
                self.dH,
                self.decay,
                self.Hc,
                self.ctrl,
                W,
                self.max_episode,
                self.eps,
                self.savefile,
                self.method,
                dyn_method=self.dyn_method,
            )
        elif self.dynamic_type == "Kraus":
            adaptive_Kraus(
                self.x,
                self.p,
                M,
                self.rho0,
                self.K,
                self.dK,
                W,
                self.max_episode,
                self.eps,
                self.savefile,
                self.method
            )
        else:
            raise ValueError(
                "{!r} is not a valid value for type of dynamics, supported values are 'dynamics' and 'Kraus'.".format(
                    self.dynamic_type
                )
            )

    def Mopt(self, W=None):
        r"""
        Measurement optimization for the optimal x.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """

        if W is None: W = []

        if W == []:
            W = np.identity(self.para_num)
        else:
            W = W

        if self.dynamic_type == "dynamics":
            if self.para_num == 1:
                F = []
                if self.dyn_method == "expm":
                    for i in range(len(self.H)):
                        dynamics = Lindblad(
                            self.tspan,
                            self.rho0,
                            self.H[i],
                            self.dH[i],
                            decay=self.decay,
                            Hc=self.Hc,
                            ctrl=self.ctrl,
                        )
                        rho_tp, drho_tp = dynamics.expm()
                        rho, drho = rho_tp[-1], drho_tp[-1]
                        F_tp = QFIM(rho, drho)
                        F.append(F_tp)
                elif self.dyn_method == "ode":
                    for i in range(len(self.H)):
                        dynamics = Lindblad(
                            self.tspan,
                            self.rho0,
                            self.H[i],
                            self.dH[i],
                            decay=self.decay,
                            Hc=self.Hc,
                            ctrl=self.ctrl,
                        )
                        rho_tp, drho_tp = dynamics.ode()
                        rho, drho = rho_tp[-1], drho_tp[-1]
                        F_tp = QFIM(rho, drho)
                        F.append(F_tp)
                idx = np.argmax(F)
                H_res, dH_res = self.H[idx], self.dH[idx]
            else:
                p_ext = extract_ele(self.p, self.para_num)
                H_ext = extract_ele(self.H, self.para_num)
                dH_ext = extract_ele(self.dH, self.para_num)

                p_list, H_list, dH_list = [], [], []
                for p_ele, H_ele, dH_ele in zip(p_ext, H_ext, dH_ext):
                    p_list.append(p_ele)
                    H_list.append(H_ele)
                    dH_list.append(dH_ele)

                F = []
                if self.dyn_method == "expm":
                    for i in range(len(p_list)):
                        dynamics = Lindblad(
                            self.tspan,
                            self.rho0,
                            H_list[i],
                            dH_list[i],
                            decay=self.decay,
                            Hc=self.Hc,
                            ctrl=self.ctrl,
                        )
                        rho_tp, drho_tp = dynamics.expm()
                        rho, drho = rho_tp[-1], drho_tp[-1]
                        F_tp = QFIM(rho, drho)
                        if np.linalg.det(F_tp) < self.eps:
                            F.append(self.eps)
                        else:
                            F.append(1.0 / np.trace(np.dot(W, np.linalg.inv(F_tp))))
                elif self.dyn_method == "ode":
                    for i in range(len(p_list)):
                        dynamics = Lindblad(
                            self.tspan,
                            self.rho0,
                            H_list[i],
                            dH_list[i],
                            decay=self.decay,
                            Hc=self.Hc,
                            ctrl=self.ctrl,
                        )
                        rho_tp, drho_tp = dynamics.ode()
                        rho, drho = rho_tp[-1], drho_tp[-1]
                        F_tp = QFIM(rho, drho)
                        if np.linalg.det(F_tp) < self.eps:
                            F.append(self.eps)
                        else:
                            F.append(1.0 / np.trace(np.dot(W, np.linalg.inv(F_tp))))
                idx = np.argmax(F)
                H_res, dH_res = H_list[idx], dH_list[idx]
            m = MeasurementOpt(mtype="projection", minput=[], method="DE")
            m.dynamics(
                self.tspan,
                self.rho0,
                H_res,
                dH_res,
                Hc=self.Hc,
                ctrl=self.ctrl,
                decay=self.decay,
                dyn_method=self.dyn_method,
            )
            m.CFIM(W=W)
        elif self.dynamic_type == "Kraus":
            if self.para_num == 1:
                F = []
                for hi in range(len(self.K)):
                    rho_tp = sum(
                        [np.dot(Ki, np.dot(self.rho0, Ki.conj().T)) for Ki in self.K[hi]]
                    )
                    drho_tp = sum(
                        [
                            np.dot(dKi, np.dot(self.rho0, Ki.conj().T))
                            + np.dot(Ki, np.dot(self.rho0, dKi.conj().T))
                            for (Ki, dKi) in zip(self.K[hi], self.dK[hi])
                        ]
                    )
                    F_tp = QFIM(rho_tp, drho_tp)
                    F.append(F_tp)

                idx = np.argmax(F)
                K_res, dK_res = self.K[idx], self.dK[idx]
            else:
                p_shape = np.shape(self.p)

                p_ext = extract_ele(self.p, self.para_num)
                K_ext = extract_ele(self.K, self.para_num)
                dK_ext = extract_ele(self.dK, self.para_num)

                p_list, K_list, dK_list = [], [], []
                for K_ele, dK_ele in zip(K_ext, dK_ext):
                    p_list.append(p_ele)
                    K_list.append(K_ele)
                    dK_list.append(dK_ele)
                F = []
                for hi in range(len(p_list)):
                    rho_tp = sum(
                        [np.dot(Ki, np.dot(self.rho0, Ki.conj().T)) for Ki in K_list[hi]]
                    )
                    dK_reshape = [
                        [dK_list[hi][i][j] for i in range(self.k_num)]
                        for j in range(self.para_num)
                    ]
                    drho_tp = [
                        sum(
                            [
                                np.dot(dKi, np.dot(self.rho0, Ki.conj().T))
                                + np.dot(Ki, np.dot(self.rho0, dKi.conj().T))
                                for (Ki, dKi) in zip(K_list[hi], dKj)
                            ]
                        )
                        for dKj in dK_reshape
                    ]
                    F_tp = QFIM(rho_tp, drho_tp)
                    if np.linalg.det(F_tp) < self.eps:
                        F.append(self.eps)
                    else:
                        F.append(1.0 / np.trace(np.dot(W, np.linalg.inv(F_tp))))
                F = np.array(F).reshape(p_shape)
                idx = np.where(np.array(F) == np.max(np.array(F)))
                K_res, dK_res = K_list[idx], dK_list[idx]
            m = MeasurementOpt(mtype="projection", minput=[], method="DE")
            m.Kraus(self.rho0, K_res, dK_res, decay=self.decay)
            m.CFIM(W=W)
        else:
            raise ValueError(
                "{!r} is not a valid value for type of dynamics, supported values are 'dynamics' and 'Kraus'.".format(
                    self.dynamic_type
                )
            )

CFIM(M=None, W=None)

Choose CFI or \(\mathrm{Tr}(WI^{-1})\) as the objective function. In single parameter estimation the objective function is CFI and in multiparameter estimation it will be \(\mathrm{Tr}(WI^{-1})\).

Parameters

W: matrix -- Weight matrix.

M: list of matrices -- A set of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

Note: SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from here.

Source code in quanestimation/base/AdaptiveScheme/Adapt.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
def CFIM(self, M=None, W=None):
    r"""
    Choose CFI or $\mathrm{Tr}(WI^{-1})$ as the objective function. 
    In single parameter estimation the objective function is CFI and 
    in multiparameter estimation it will be $\mathrm{Tr}(WI^{-1})$.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.

    > **M:** `list of matrices`
        -- A set of positive operator-valued measure (POVM). The default measurement 
        is a set of rank-one symmetric informationally complete POVM (SIC-POVM).

    **Note:** 
        SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state 
        which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/
        solutions.html).
    """

    if M is None: M = []
    if W is None: W = []

    if M == []:
        M = SIC(len(self.rho0))
    if W == []:
        W = np.eye(len(self.x))
    self.W = W

    if self.dynamic_type == "dynamics":
        adaptive_dynamics(
            self.x,
            self.p,
            M,
            self.tspan,
            self.rho0,
            self.H,
            self.dH,
            self.decay,
            self.Hc,
            self.ctrl,
            W,
            self.max_episode,
            self.eps,
            self.savefile,
            self.method,
            dyn_method=self.dyn_method,
        )
    elif self.dynamic_type == "Kraus":
        adaptive_Kraus(
            self.x,
            self.p,
            M,
            self.rho0,
            self.K,
            self.dK,
            W,
            self.max_episode,
            self.eps,
            self.savefile,
            self.method
        )
    else:
        raise ValueError(
            "{!r} is not a valid value for type of dynamics, supported values are 'dynamics' and 'Kraus'.".format(
                self.dynamic_type
            )
        )

Kraus(K, dK)

Dynamics of the density matrix of the form \begin{align} \rho=\sum_i K_i\rho_0K_i^{\dagger} \end{align}

where \(\rho\) is the evolved density matrix, \(K_i\) is the Kraus operator.

Parameters

K: multidimensional list -- Kraus operator(s) with respect to the values in x.

dK: multidimensional list -- Derivatives of the Kraus operator(s) with respect to the unknown parameters to be estimated.

Source code in quanestimation/base/AdaptiveScheme/Adapt.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def Kraus(self, K, dK):
    r"""
    Dynamics of the density matrix of the form 
    \begin{align}
    \rho=\sum_i K_i\rho_0K_i^{\dagger}
    \end{align}

    where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator.

    Parameters
    ----------
    > **K:** `multidimensional list`
        -- Kraus operator(s) with respect to the values in x.

    > **dK:** `multidimensional list`
        -- Derivatives of the Kraus operator(s) with respect to the unknown parameters 
        to be estimated.
    """

    self.K = K
    self.dK = dK
    self.k_num = len(K[0])

    self.dynamic_type = "Kraus"

Mopt(W=None)

Measurement optimization for the optimal x.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/base/AdaptiveScheme/Adapt.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
def Mopt(self, W=None):
    r"""
    Measurement optimization for the optimal x.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """

    if W is None: W = []

    if W == []:
        W = np.identity(self.para_num)
    else:
        W = W

    if self.dynamic_type == "dynamics":
        if self.para_num == 1:
            F = []
            if self.dyn_method == "expm":
                for i in range(len(self.H)):
                    dynamics = Lindblad(
                        self.tspan,
                        self.rho0,
                        self.H[i],
                        self.dH[i],
                        decay=self.decay,
                        Hc=self.Hc,
                        ctrl=self.ctrl,
                    )
                    rho_tp, drho_tp = dynamics.expm()
                    rho, drho = rho_tp[-1], drho_tp[-1]
                    F_tp = QFIM(rho, drho)
                    F.append(F_tp)
            elif self.dyn_method == "ode":
                for i in range(len(self.H)):
                    dynamics = Lindblad(
                        self.tspan,
                        self.rho0,
                        self.H[i],
                        self.dH[i],
                        decay=self.decay,
                        Hc=self.Hc,
                        ctrl=self.ctrl,
                    )
                    rho_tp, drho_tp = dynamics.ode()
                    rho, drho = rho_tp[-1], drho_tp[-1]
                    F_tp = QFIM(rho, drho)
                    F.append(F_tp)
            idx = np.argmax(F)
            H_res, dH_res = self.H[idx], self.dH[idx]
        else:
            p_ext = extract_ele(self.p, self.para_num)
            H_ext = extract_ele(self.H, self.para_num)
            dH_ext = extract_ele(self.dH, self.para_num)

            p_list, H_list, dH_list = [], [], []
            for p_ele, H_ele, dH_ele in zip(p_ext, H_ext, dH_ext):
                p_list.append(p_ele)
                H_list.append(H_ele)
                dH_list.append(dH_ele)

            F = []
            if self.dyn_method == "expm":
                for i in range(len(p_list)):
                    dynamics = Lindblad(
                        self.tspan,
                        self.rho0,
                        H_list[i],
                        dH_list[i],
                        decay=self.decay,
                        Hc=self.Hc,
                        ctrl=self.ctrl,
                    )
                    rho_tp, drho_tp = dynamics.expm()
                    rho, drho = rho_tp[-1], drho_tp[-1]
                    F_tp = QFIM(rho, drho)
                    if np.linalg.det(F_tp) < self.eps:
                        F.append(self.eps)
                    else:
                        F.append(1.0 / np.trace(np.dot(W, np.linalg.inv(F_tp))))
            elif self.dyn_method == "ode":
                for i in range(len(p_list)):
                    dynamics = Lindblad(
                        self.tspan,
                        self.rho0,
                        H_list[i],
                        dH_list[i],
                        decay=self.decay,
                        Hc=self.Hc,
                        ctrl=self.ctrl,
                    )
                    rho_tp, drho_tp = dynamics.ode()
                    rho, drho = rho_tp[-1], drho_tp[-1]
                    F_tp = QFIM(rho, drho)
                    if np.linalg.det(F_tp) < self.eps:
                        F.append(self.eps)
                    else:
                        F.append(1.0 / np.trace(np.dot(W, np.linalg.inv(F_tp))))
            idx = np.argmax(F)
            H_res, dH_res = H_list[idx], dH_list[idx]
        m = MeasurementOpt(mtype="projection", minput=[], method="DE")
        m.dynamics(
            self.tspan,
            self.rho0,
            H_res,
            dH_res,
            Hc=self.Hc,
            ctrl=self.ctrl,
            decay=self.decay,
            dyn_method=self.dyn_method,
        )
        m.CFIM(W=W)
    elif self.dynamic_type == "Kraus":
        if self.para_num == 1:
            F = []
            for hi in range(len(self.K)):
                rho_tp = sum(
                    [np.dot(Ki, np.dot(self.rho0, Ki.conj().T)) for Ki in self.K[hi]]
                )
                drho_tp = sum(
                    [
                        np.dot(dKi, np.dot(self.rho0, Ki.conj().T))
                        + np.dot(Ki, np.dot(self.rho0, dKi.conj().T))
                        for (Ki, dKi) in zip(self.K[hi], self.dK[hi])
                    ]
                )
                F_tp = QFIM(rho_tp, drho_tp)
                F.append(F_tp)

            idx = np.argmax(F)
            K_res, dK_res = self.K[idx], self.dK[idx]
        else:
            p_shape = np.shape(self.p)

            p_ext = extract_ele(self.p, self.para_num)
            K_ext = extract_ele(self.K, self.para_num)
            dK_ext = extract_ele(self.dK, self.para_num)

            p_list, K_list, dK_list = [], [], []
            for K_ele, dK_ele in zip(K_ext, dK_ext):
                p_list.append(p_ele)
                K_list.append(K_ele)
                dK_list.append(dK_ele)
            F = []
            for hi in range(len(p_list)):
                rho_tp = sum(
                    [np.dot(Ki, np.dot(self.rho0, Ki.conj().T)) for Ki in K_list[hi]]
                )
                dK_reshape = [
                    [dK_list[hi][i][j] for i in range(self.k_num)]
                    for j in range(self.para_num)
                ]
                drho_tp = [
                    sum(
                        [
                            np.dot(dKi, np.dot(self.rho0, Ki.conj().T))
                            + np.dot(Ki, np.dot(self.rho0, dKi.conj().T))
                            for (Ki, dKi) in zip(K_list[hi], dKj)
                        ]
                    )
                    for dKj in dK_reshape
                ]
                F_tp = QFIM(rho_tp, drho_tp)
                if np.linalg.det(F_tp) < self.eps:
                    F.append(self.eps)
                else:
                    F.append(1.0 / np.trace(np.dot(W, np.linalg.inv(F_tp))))
            F = np.array(F).reshape(p_shape)
            idx = np.where(np.array(F) == np.max(np.array(F)))
            K_res, dK_res = K_list[idx], dK_list[idx]
        m = MeasurementOpt(mtype="projection", minput=[], method="DE")
        m.Kraus(self.rho0, K_res, dK_res, decay=self.decay)
        m.CFIM(W=W)
    else:
        raise ValueError(
            "{!r} is not a valid value for type of dynamics, supported values are 'dynamics' and 'Kraus'.".format(
                self.dynamic_type
            )
        )

__init__(x, p, rho0, method='FOP', savefile=False, max_episode=1000, eps=1e-08)

Initialize the adaptive scheme for quantum parameter estimation.

Parameters:

Name Type Description Default
x list

The regimes of the parameters for the integral.

required
p ndarray

The prior distribution.

required
rho0 matrix

Initial state (density matrix).

required
method str

Method for updating the tunable parameters. "FOP" (default) -- Fix optimal point. "MI" -- mutual information.

'FOP'
savefile bool

Whether to save all posterior distributions. Default False.

False
max_episode int

The number of episodes. Default 1000.

1000
eps float

Machine epsilon. Default 1e-8.

1e-08
Source code in quanestimation/base/AdaptiveScheme/Adapt.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def __init__(self, x, p, rho0, method="FOP", savefile=False, max_episode=1000, eps=1e-8):
    r"""
    Initialize the adaptive scheme for quantum parameter estimation.

    Args:
        x (list): The regimes of the parameters for the integral.
        p (ndarray): The prior distribution.
        rho0 (matrix): Initial state (density matrix).
        method (str, optional): Method for updating the tunable parameters.
            "FOP" (default) -- Fix optimal point. "MI" -- mutual information.
        savefile (bool, optional): Whether to save all posterior distributions.
            Default `False`.
        max_episode (int, optional): The number of episodes. Default 1000.
        eps (float, optional): Machine epsilon. Default 1e-8.
    """

    self.x = x
    self.p = p
    self.rho0 = np.array(rho0, dtype=np.complex128)
    self.max_episode = max_episode
    self.eps = eps
    self.para_num = len(x)
    self.savefile = savefile
    self.method = method
    self.decay = []
    self.k_num = 0

dynamics(tspan, H, dH, Hc=None, ctrl=None, decay=None, dyn_method='expm')

Dynamics of the density matrix of the form

\[\begin{align} \partial_t\rho &=\mathcal{L}\rho \nonumber \\ &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2} \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right), \end{align}\]

where \(\rho\) is the evolved density matrix, H is the Hamiltonian of the system, \(\Gamma_i\) and \(\gamma_i\) are the \(i\mathrm{th}\) decay operator and decay rate.

Parameters

tspan: array -- Time length for the evolution.

H0: multidimensional list -- Free Hamiltonian with respect to the values in x.

dH: multidimensional list -- Derivatives of the free Hamiltonian with respect to the unknown parameters to be estimated.

Hc: list -- Control Hamiltonians.

ctrl: list -- Control coefficients.

decay: list -- Decay operators and the corresponding decay rates. Its input rule is decay=[[Gamma1, gamma1], [Gamma2,gamma2],...], where Gamma1 (Gamma2) represents the decay operator and gamma1 (gamma2) is the corresponding decay rate.

dyn_method: string -- Setting the method for solving the Lindblad dynamics. Options are:
"expm" (default) -- Matrix exponential.
"ode" -- Solving the differential equations directly.

Source code in quanestimation/base/AdaptiveScheme/Adapt.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def dynamics(self, tspan, H, dH, Hc=None, ctrl=None, decay=None, dyn_method="expm"):
    r"""
    Dynamics of the density matrix of the form 

    \begin{align}
    \partial_t\rho &=\mathcal{L}\rho \nonumber \\
    &=-i[H,\rho]+\sum_i \gamma_i\left(\Gamma_i\rho\Gamma^{\dagger}_i-\frac{1}{2}
    \left\{\rho,\Gamma^{\dagger}_i \Gamma_i \right\}\right),
    \end{align} 

    where $\rho$ is the evolved density matrix, H is the Hamiltonian of the 
    system, $\Gamma_i$ and $\gamma_i$ are the $i\mathrm{th}$ decay 
    operator and decay rate.

    Parameters
    ----------
    > **tspan:** `array`
        -- Time length for the evolution.

    > **H0:** `multidimensional list`
        -- Free Hamiltonian with respect to the values in x.

    > **dH:** `multidimensional list`
        -- Derivatives of the free Hamiltonian with respect to the unknown parameters 
        to be estimated.

    > **Hc:** `list`
        -- Control Hamiltonians.

    > **ctrl:** `list`
        -- Control coefficients.

    > **decay:** `list`
        -- Decay operators and the corresponding decay rates. Its input rule is 
        `decay=[[Gamma1, gamma1], [Gamma2,gamma2],...]`, where `Gamma1 (Gamma2)` 
        represents the decay operator and `gamma1 (gamma2)` is the corresponding 
        decay rate.

    > **dyn_method:** `string`
        -- Setting the method for solving the Lindblad dynamics. Options are:  
        "expm" (default) -- Matrix exponential.  
        "ode" -- Solving the differential equations directly.
    """

    if Hc is None: Hc = []
    if ctrl is None: ctrl = []
    if decay is None: decay = []

    self.tspan = tspan
    self.H = H
    self.dH = dH
    self.Hc = Hc
    self.ctrl = ctrl
    self.decay = decay

    self.dynamic_type = "dynamics"
    self.dyn_method = dyn_method

Attributes

x: list -- The regimes of the parameters for the integral.

p: multidimensional array -- The prior distribution.

rho0: matrix -- Initial state (density matrix).

Source code in quanestimation/base/AdaptiveScheme/Adapt_MZI.py
  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
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
class Adapt_MZI:
    """
    Attributes
    ----------
    > **x:** `list`
        -- The regimes of the parameters for the integral.

    > **p:** `multidimensional array`
        -- The prior distribution.

    > **rho0:** `matrix`
        -- Initial state (density matrix).

    """

    def __init__(self, x, p, rho0):
        r"""
        Initialize the adaptive MZI (Mach-Zehnder interferometer) estimation.

        Args:
            x (list): The regimes of the parameters for the integral.
            p (ndarray): The prior distribution.
            rho0 (matrix): Initial state (density matrix).
        """

        self.x = x
        self.p = p
        self.rho0 = rho0
        self.N = int(np.sqrt(len(rho0))) - 1
        # self.a = annihilation(self.N + 1)

    def general(self):
        r"""
        Set the MZI type to general, indicating the use of a general
        Mach-Zehnder interferometer scheme with two input ports.
        """
        self.MZI_type = "general"

    def online(self, target="sharpness", output="phi"):
        """
        Parameters
        ----------
        > **target:** `string`
            -- Setting the target function for calculating the tunable phase. Options are:  
            "sharpness" (default) -- Sharpness.  
            "MI" -- Mutual information. 

        > **output:** `string`
            -- The output the class. Options are:  
            "phi" (default) -- The tunable phase.  
            "dphi" -- Phase difference. 
        """
        phi = QJL.adaptMZI_online(
            self.x, self.p, self.rho0, output, target
        )

    def offline(
        self,
        target="sharpness",
        method="DE",
        p_num=10,
        deltaphi0=[],
        c=1.0,
        cr=0.5,
        c0=1.0,
        c1=2.0,
        c2=2.0,
        seed=1234,
        max_episode=1000,
        eps=1e-8,
    ):
        """
        Parameters
        ----------
        > **target:** `string`
            -- Setting the target function for calculating the tunable phase. Options are:  
            "sharpness" (default) -- Sharpness.  
            "MI" -- Mutual information. 

        > **method:** `string`
            -- The method for the adaptive phase estimation. Options are:  
            "DE" (default) -- DE algorithm for the adaptive phase estimation.    
            "PSO" -- PSO algorithm for the adaptive phase estimation.

        If the `method=DE`, the parameters are:
        > **p_num:** `int`
            -- The number of populations.

        > **deltaphi0:** `list`
            -- Initial guesses of phase difference.

        > **max_episode:** `int`
            -- The number of episodes.

        > **c:** `float`
            -- Mutation constant.

        > **cr:** `float`
            -- Crossover constant.

        > **seed:** `int`
            -- Random seed.

        > **eps:** `float`
            -- Machine epsilon.

        If the `method=PSO`, the parameters are:

        > **deltaphi0:** `list`
            -- Initial guesses of phase difference.

        > **max_episode:** `int or list`
            -- If it is an integer, for example max_episode=1000, it means the 
            program will continuously run 1000 episodes. However, if it is an
            array, for example max_episode=[1000,100], the program will run 
            1000 episodes in total but replace states of all  the particles 
            with global best every 100 episodes.

        > **c0:** `float`
            -- The damping factor that assists convergence, also known as inertia weight.

        > **c1:** `float`
            -- The exploitation weight that attracts the particle to its best previous 
            position, also known as cognitive learning factor.

        > **c2:** `float`
            -- The exploitation weight that attracts the particle to the best position  
            in the neighborhood, also known as social learning factor.

        > **eps:** `float`
            -- Machine epsilon.
        """
        comb_tp = brgd(self.N)
        comb = [
            np.array([int(list(comb_tp[i])[j]) for j in range(self.N)])
            for i in range(2**self.N)
        ]

        if method == "DE":
            QJL.DE_deltaphiOpt(
                self.x,
                self.p,
                self.rho0,
                comb,
                p_num,
                deltaphi0,
                c,
                cr,
                seed,
                max_episode,
                target,
                eps,
            )
        elif method == "PSO":
            QJL.PSO_deltaphiOpt(
                self.x,
                self.p,
                self.rho0,
                comb,
                p_num,
                deltaphi0,
                c0,
                c1,
                c2,
                seed,
                max_episode,
                target,
                eps,
            )
        else:
            raise ValueError(
                "{!r} is not a valid value for method, supported values are 'DE' and 'PSO'.".format(
                    method
                )
            )

__init__(x, p, rho0)

Initialize the adaptive MZI (Mach-Zehnder interferometer) estimation.

Parameters:

Name Type Description Default
x list

The regimes of the parameters for the integral.

required
p ndarray

The prior distribution.

required
rho0 matrix

Initial state (density matrix).

required
Source code in quanestimation/base/AdaptiveScheme/Adapt_MZI.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def __init__(self, x, p, rho0):
    r"""
    Initialize the adaptive MZI (Mach-Zehnder interferometer) estimation.

    Args:
        x (list): The regimes of the parameters for the integral.
        p (ndarray): The prior distribution.
        rho0 (matrix): Initial state (density matrix).
    """

    self.x = x
    self.p = p
    self.rho0 = rho0
    self.N = int(np.sqrt(len(rho0))) - 1

general()

Set the MZI type to general, indicating the use of a general Mach-Zehnder interferometer scheme with two input ports.

Source code in quanestimation/base/AdaptiveScheme/Adapt_MZI.py
37
38
39
40
41
42
def general(self):
    r"""
    Set the MZI type to general, indicating the use of a general
    Mach-Zehnder interferometer scheme with two input ports.
    """
    self.MZI_type = "general"

offline(target='sharpness', method='DE', p_num=10, deltaphi0=[], c=1.0, cr=0.5, c0=1.0, c1=2.0, c2=2.0, seed=1234, max_episode=1000, eps=1e-08)

Parameters

target: string -- Setting the target function for calculating the tunable phase. Options are:
"sharpness" (default) -- Sharpness.
"MI" -- Mutual information.

method: string -- The method for the adaptive phase estimation. Options are:
"DE" (default) -- DE algorithm for the adaptive phase estimation.
"PSO" -- PSO algorithm for the adaptive phase estimation.

If the method=DE, the parameters are:

p_num: int -- The number of populations.

deltaphi0: list -- Initial guesses of phase difference.

max_episode: int -- The number of episodes.

c: float -- Mutation constant.

cr: float -- Crossover constant.

seed: int -- Random seed.

eps: float -- Machine epsilon.

If the method=PSO, the parameters are:

deltaphi0: list -- Initial guesses of phase difference.

max_episode: int or list -- If it is an integer, for example max_episode=1000, it means the program will continuously run 1000 episodes. However, if it is an array, for example max_episode=[1000,100], the program will run 1000 episodes in total but replace states of all the particles with global best every 100 episodes.

c0: float -- The damping factor that assists convergence, also known as inertia weight.

c1: float -- The exploitation weight that attracts the particle to its best previous position, also known as cognitive learning factor.

c2: float -- The exploitation weight that attracts the particle to the best position
in the neighborhood, also known as social learning factor.

eps: float -- Machine epsilon.

Source code in quanestimation/base/AdaptiveScheme/Adapt_MZI.py
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def offline(
    self,
    target="sharpness",
    method="DE",
    p_num=10,
    deltaphi0=[],
    c=1.0,
    cr=0.5,
    c0=1.0,
    c1=2.0,
    c2=2.0,
    seed=1234,
    max_episode=1000,
    eps=1e-8,
):
    """
    Parameters
    ----------
    > **target:** `string`
        -- Setting the target function for calculating the tunable phase. Options are:  
        "sharpness" (default) -- Sharpness.  
        "MI" -- Mutual information. 

    > **method:** `string`
        -- The method for the adaptive phase estimation. Options are:  
        "DE" (default) -- DE algorithm for the adaptive phase estimation.    
        "PSO" -- PSO algorithm for the adaptive phase estimation.

    If the `method=DE`, the parameters are:
    > **p_num:** `int`
        -- The number of populations.

    > **deltaphi0:** `list`
        -- Initial guesses of phase difference.

    > **max_episode:** `int`
        -- The number of episodes.

    > **c:** `float`
        -- Mutation constant.

    > **cr:** `float`
        -- Crossover constant.

    > **seed:** `int`
        -- Random seed.

    > **eps:** `float`
        -- Machine epsilon.

    If the `method=PSO`, the parameters are:

    > **deltaphi0:** `list`
        -- Initial guesses of phase difference.

    > **max_episode:** `int or list`
        -- If it is an integer, for example max_episode=1000, it means the 
        program will continuously run 1000 episodes. However, if it is an
        array, for example max_episode=[1000,100], the program will run 
        1000 episodes in total but replace states of all  the particles 
        with global best every 100 episodes.

    > **c0:** `float`
        -- The damping factor that assists convergence, also known as inertia weight.

    > **c1:** `float`
        -- The exploitation weight that attracts the particle to its best previous 
        position, also known as cognitive learning factor.

    > **c2:** `float`
        -- The exploitation weight that attracts the particle to the best position  
        in the neighborhood, also known as social learning factor.

    > **eps:** `float`
        -- Machine epsilon.
    """
    comb_tp = brgd(self.N)
    comb = [
        np.array([int(list(comb_tp[i])[j]) for j in range(self.N)])
        for i in range(2**self.N)
    ]

    if method == "DE":
        QJL.DE_deltaphiOpt(
            self.x,
            self.p,
            self.rho0,
            comb,
            p_num,
            deltaphi0,
            c,
            cr,
            seed,
            max_episode,
            target,
            eps,
        )
    elif method == "PSO":
        QJL.PSO_deltaphiOpt(
            self.x,
            self.p,
            self.rho0,
            comb,
            p_num,
            deltaphi0,
            c0,
            c1,
            c2,
            seed,
            max_episode,
            target,
            eps,
        )
    else:
        raise ValueError(
            "{!r} is not a valid value for method, supported values are 'DE' and 'PSO'.".format(
                method
            )
        )

online(target='sharpness', output='phi')

Parameters

target: string -- Setting the target function for calculating the tunable phase. Options are:
"sharpness" (default) -- Sharpness.
"MI" -- Mutual information.

output: string -- The output the class. Options are:
"phi" (default) -- The tunable phase.
"dphi" -- Phase difference.

Source code in quanestimation/base/AdaptiveScheme/Adapt_MZI.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def online(self, target="sharpness", output="phi"):
    """
    Parameters
    ----------
    > **target:** `string`
        -- Setting the target function for calculating the tunable phase. Options are:  
        "sharpness" (default) -- Sharpness.  
        "MI" -- Mutual information. 

    > **output:** `string`
        -- The output the class. Options are:  
        "phi" (default) -- The tunable phase.  
        "dphi" -- Phase difference. 
    """
    phi = QJL.adaptMZI_online(
        self.x, self.p, self.rho0, output, target
    )