Skip to content

Classes

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

Dynamics

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 the corresponding decay rate.

Attributes

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 with the length equal to tspan when it is time-dependent.

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

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.

Hc: list -- Control Hamiltonians.

ctrl: list of arrays -- Control coefficients.

Source code in quanestimation/Parameterization/GeneralDynamics.py
  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
class Lindblad:
    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 the corresponding decay rate.

    Attributes
    ----------
    > **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 with the length equal to `tspan` when it is 
        time-dependent.

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

    > **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.

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

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

    def __init__(self, tspan, rho0, H0, dH, decay=[], Hc=[], ctrl=[]):

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

        if type(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 type(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"""
        Calculation of the density matrix and its derivatives on the unknown parameters 
        with matrix exponential method (expm). The density matrix at $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 for the $(j-1)$th time interval.
        $\partial_{\textbf{x}}\rho_j$ is calculated as
        \begin{align}
        \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}).
        \end{align}

        """

        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 = [np.array(rho_i) for rho_i in rho]
        drho = [[np.array(drho_ij) for drho_ij in drho_i] for drho_i in drho]

        return rho, drho

    def ode(self):
        r"""
        Calculation of the density matrix and its derivatives on the unknown parameters 
        with ordinary differential equations (ODE) solver.
        The density matrix at $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 for the $(j-1)$th time interval.
        $\partial_{\textbf{x}}\rho_j$ is calculated as
        \begin{align}
        \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}).
        \end{align}

        """

        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 = [np.array(rho_i) for rho_i in rho]
        drho = [[np.array(drho_ij) for drho_ij in drho_i] for drho_i in drho]

        return rho, drho

    def secondorder_derivative(self, d2H):
        r"""
        Calculation of the density matrix and its derivatives and the second derivatives
        on $\textbf{x}$. The density matrix at $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 for the $(j-1)$th time interval.
        $\partial_{\textbf{x}}\rho_j$ is calculated via
        \begin{align}
        \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}).
        \end{align}

        $\partial_{\textbf{x}}^2\rho_j$ is solved as
        \begin{align}
        \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{align}

        Parameters
        ----------
        > **d2H:** `list`
            -- Second order derivatives of the free Hamiltonian on the unknown parameters 
            to be estimated.
        """

        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 = [np.array(rho_i) for rho_i in rho]
        drho = [[np.array(drho_ij) for drho_ij in drho_i] for drho_i in drho]
        #d2rho = 
        return rho, drho, d2rho

expm()

Calculation of the density matrix and its derivatives on the unknown parameters with matrix exponential method (expm). The density matrix at \(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 for the \((j-1)\)th time interval. \(\partial_{\textbf{x}}\rho_j\) is calculated as \begin{align} \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}). \end{align}

Source code in quanestimation/Parameterization/GeneralDynamics.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
def expm(self):
    r"""
    Calculation of the density matrix and its derivatives on the unknown parameters 
    with matrix exponential method (expm). The density matrix at $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 for the $(j-1)$th time interval.
    $\partial_{\textbf{x}}\rho_j$ is calculated as
    \begin{align}
    \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}).
    \end{align}

    """

    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 = [np.array(rho_i) for rho_i in rho]
    drho = [[np.array(drho_ij) for drho_ij in drho_i] for drho_i in drho]

    return rho, drho

ode()

Calculation of the density matrix and its derivatives on the unknown parameters with ordinary differential equations (ODE) solver. The density matrix at \(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 for the \((j-1)\)th time interval. \(\partial_{\textbf{x}}\rho_j\) is calculated as \begin{align} \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}). \end{align}

Source code in quanestimation/Parameterization/GeneralDynamics.py
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
def ode(self):
    r"""
    Calculation of the density matrix and its derivatives on the unknown parameters 
    with ordinary differential equations (ODE) solver.
    The density matrix at $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 for the $(j-1)$th time interval.
    $\partial_{\textbf{x}}\rho_j$ is calculated as
    \begin{align}
    \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}).
    \end{align}

    """

    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 = [np.array(rho_i) for rho_i in rho]
    drho = [[np.array(drho_ij) for drho_ij in drho_i] for drho_i in drho]

    return rho, drho

secondorder_derivative(d2H)

Calculation of the density matrix and its derivatives and the second derivatives on \(\textbf{x}\). The density matrix at \(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 for the \((j-1)\)th time interval. \(\partial_{\textbf{x}}\rho_j\) is calculated via \begin{align} \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}). \end{align}

\(\partial_{\textbf{x}}^2\rho_j\) is solved as \begin{align} \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{align}

Parameters

d2H: list -- Second order derivatives of the free Hamiltonian on the unknown parameters to be estimated.

Source code in quanestimation/Parameterization/GeneralDynamics.py
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
def secondorder_derivative(self, d2H):
    r"""
    Calculation of the density matrix and its derivatives and the second derivatives
    on $\textbf{x}$. The density matrix at $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 for the $(j-1)$th time interval.
    $\partial_{\textbf{x}}\rho_j$ is calculated via
    \begin{align}
    \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}).
    \end{align}

    $\partial_{\textbf{x}}^2\rho_j$ is solved as
    \begin{align}
    \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{align}

    Parameters
    ----------
    > **d2H:** `list`
        -- Second order derivatives of the free Hamiltonian on the unknown parameters 
        to be estimated.
    """

    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 = [np.array(rho_i) for rho_i in rho]
    drho = [[np.array(drho_ij) for drho_ij in drho_i] for drho_i in drho]
    #d2rho = 
    return rho, drho, d2rho

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/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
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, eps, load):
        self.savefile = savefile
        self.ctrl0 = ctrl0
        self.eps = eps
        self.load = load

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

    def load_save(self, cnum, max_episode):
        if os.path.exists("controls.dat"):
            fl = h5py.File("controls.dat",'r')
            dset = fl["controls"]
            if self.savefile:
                controls = np.array([[np.array(fl[fl[dset[i]][j]]) for j in range(cnum)] for i in range(max_episode)])
            else:
                controls = np.array([np.array(fl[dset[j]]) for j in range(cnum)])
            np.save("controls", controls)
        else: pass

    def dynamics(self, tspan, rho0, H0, dH, Hc, decay=[], ctrl_bound=[], 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.

        > **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.  
        """

        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 type(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 type(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 =  [-jl.Inf, jl.Inf]
        else:
            self.ctrl_bound = [float(ctrl_bound[0]), float(ctrl_bound[1])]
        self.ctrl_bound = jl.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))
            ]
        ## TODO
        self.ctrl0 = QJL.convert(self.QJLType_ctrl, [[c for c in ctrls ]for ctrls in self.ctrl0])

        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])),
                    )
                )
        else: pass

        if type(H0) != np.ndarray:
            #### linear interpolation  ####
            f = interp1d(self.tspan, H0, axis=0)
        else: pass
        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 type(H0) != np.ndarray:
                H0_inter = f(self.tspan)
                self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter[:-1]]
            else: pass

        else: pass


        self.opt = QJL.ControlOpt(
            ctrl = self.ctrl0,
            ctrl_bound=self.ctrl_bound, 
            seed=self.seed
        )
        self.dynamic = jl.QuanEstimation.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.control_Hamiltonian,
            self.control_coefficients,
            self.rho0,
            self.tspan,
            self.decay_opt,
            self.gamma,
            dyn_method = self.dyn_method,
        )
        self.output = QJL.Output(self.opt, save=self.savefile)

        self.dynamics_type = "lindblad"

    def QFIM(self, W=[], 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 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

        self.obj = QJL.QFIM_obj(
            self.W, self.eps, self.para_type, LDtype
        )
        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)
        max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
        self.load_save(len(self.control_Hamiltonian), max_num)

    def CFIM(self, M=[], W=[]):
        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 == []:
            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

        self.obj = QJL.CFIM_obj(M, self.W, self.eps, self.para_type)
        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)
        max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
        self.load_save(len(self.control_Hamiltonian), max_num)

    def HCRB(self, W=[]):
        """
        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 == []:
            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  

            self.obj = QJL.HCRB_obj(self.W, self.eps, self.para_type)
            system = QJL.QuanEstSystem(
                self.opt, self.alg, self.obj, self.dynamic, self.output
            )
            QJL.run(system)
            max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
        self.load_save(len(self.control_Hamiltonian), max_num)

    def mintime(self, f, W=[], M=[], 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).  
        """

        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,
                )
        self.output = QJL.Output(self.opt)

        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, self.W, self.eps, self.para_type)
        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(
                    self.W, self.eps, self.para_type
                )
            elif target == "QFIM" or (
                LDtype == "SLD" and LDtype == "LLD" and LDtype == "RLD"
            ):
                self.obj = QJL.QFIM_obj(
                    self.W, self.eps, self.para_type, 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'."
                )

        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.mintime(method, f, system)
        max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
        self.load_save(len(self.control_Hamiltonian), max_num)

CFIM(M=[], W=[])

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/ControlOpt/ControlStruct.py
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
def CFIM(self, M=[], W=[]):
    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 == []:
        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

    self.obj = QJL.CFIM_obj(M, self.W, self.eps, self.para_type)
    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)
    max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
    self.load_save(len(self.control_Hamiltonian), max_num)

HCRB(W=[])

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/ControlOpt/ControlStruct.py
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
def HCRB(self, W=[]):
    """
    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 == []:
        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  

        self.obj = QJL.HCRB_obj(self.W, self.eps, self.para_type)
        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)
        max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
    self.load_save(len(self.control_Hamiltonian), max_num)

QFIM(W=[], 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/ControlOpt/ControlStruct.py
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
def QFIM(self, W=[], 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 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

    self.obj = QJL.QFIM_obj(
        self.W, self.eps, self.para_type, LDtype
    )
    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)
    max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
    self.load_save(len(self.control_Hamiltonian), max_num)

dynamics(tspan, rho0, H0, dH, Hc, decay=[], ctrl_bound=[], 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.

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/ControlOpt/ControlStruct.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
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
def dynamics(self, tspan, rho0, H0, dH, Hc, decay=[], ctrl_bound=[], 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.

    > **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.  
    """

    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 type(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 type(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 =  [-jl.Inf, jl.Inf]
    else:
        self.ctrl_bound = [float(ctrl_bound[0]), float(ctrl_bound[1])]
    self.ctrl_bound = jl.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))
        ]
    ## TODO
    self.ctrl0 = QJL.convert(self.QJLType_ctrl, [[c for c in ctrls ]for ctrls in self.ctrl0])

    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])),
                )
            )
    else: pass

    if type(H0) != np.ndarray:
        #### linear interpolation  ####
        f = interp1d(self.tspan, H0, axis=0)
    else: pass
    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 type(H0) != np.ndarray:
            H0_inter = f(self.tspan)
            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter[:-1]]
        else: pass

    else: pass


    self.opt = QJL.ControlOpt(
        ctrl = self.ctrl0,
        ctrl_bound=self.ctrl_bound, 
        seed=self.seed
    )
    self.dynamic = jl.QuanEstimation.Lindblad(
        self.freeHamiltonian,
        self.Hamiltonian_derivative,
        self.control_Hamiltonian,
        self.control_coefficients,
        self.rho0,
        self.tspan,
        self.decay_opt,
        self.gamma,
        dyn_method = self.dyn_method,
    )
    self.output = QJL.Output(self.opt, save=self.savefile)

    self.dynamics_type = "lindblad"

mintime(f, W=[], M=[], 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/ControlOpt/ControlStruct.py
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
def mintime(self, f, W=[], M=[], 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).  
    """

    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,
            )
    self.output = QJL.Output(self.opt)

    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, self.W, self.eps, self.para_type)
    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(
                self.W, self.eps, self.para_type
            )
        elif target == "QFIM" or (
            LDtype == "SLD" and LDtype == "LLD" and LDtype == "RLD"
        ):
            self.obj = QJL.QFIM_obj(
                self.W, self.eps, self.para_type, 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'."
            )

    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.mintime(method, f, system)
    max_num = self.max_episode if type(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/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
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,
    ):

        Control.ControlSystem.__init__(self, savefile, ctrl0, 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
        self.auto = auto

    def QFIM(self, W=[], 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 self.auto:
            if self.Adam:
                self.alg = QJL.autoGRAPE(
                    self.max_episode, self.epsilon, self.beta1, self.beta2
                )
            else:
                self.alg = QJL.autoGRAPE(self.max_episode, 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(
                        self.max_episode, self.epsilon, self.beta1, self.beta2
                    )
                else:
                    self.alg = QJL.autoGRAPE(self.max_episode, self.epsilon)
            else:
                if LDtype == "SLD":
                    if self.Adam:
                        self.alg = QJL.GRAPE(
                            self.max_episode, self.epsilon, self.beta1, self.beta2
                            )
                    else:
                        self.alg = QJL.GRAPE(self.max_episode, self.epsilon)
                else:
                    raise ValueError("GRAPE is only available when LDtype is SLD.")

        super().QFIM(W, LDtype)

    def CFIM(self, M=[], W=[]):
        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 self.auto:
            if self.Adam:
                self.alg = QJL.autoGRAPE(
                    self.max_episode, self.epsilon, self.beta1, self.beta2
                )
            else:
                self.alg = QJL.autoGRAPE(self.max_episode, 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(
                        self.max_episode, self.epsilon, self.beta1, self.beta2
                    )
                else:
                    self.alg = QJL.autoGRAPE(self.max_episode, self.epsilon)
            else:    
                if self.Adam:
                    self.alg = QJL.GRAPE(
                        self.max_episode, self.epsilon, self.beta1, self.beta2
                    )
                else:
                    self.alg = QJL.GRAPE(self.max_episode, self.epsilon)

        super().CFIM(M, W)

    def HCRB(self, W=[]):
        """
        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.
        """
        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=[], M=[], 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 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(
                    self.max_episode, self.epsilon, self.beta1, self.beta2
                )
            else:
                self.alg = QJL.autoGRAPE(self.max_episode, self.epsilon)
        else:

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

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

CFIM(M=[], W=[])

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/ControlOpt/GRAPE_Copt.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def CFIM(self, M=[], W=[]):
    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 self.auto:
        if self.Adam:
            self.alg = QJL.autoGRAPE(
                self.max_episode, self.epsilon, self.beta1, self.beta2
            )
        else:
            self.alg = QJL.autoGRAPE(self.max_episode, 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(
                    self.max_episode, self.epsilon, self.beta1, self.beta2
                )
            else:
                self.alg = QJL.autoGRAPE(self.max_episode, self.epsilon)
        else:    
            if self.Adam:
                self.alg = QJL.GRAPE(
                    self.max_episode, self.epsilon, self.beta1, self.beta2
                )
            else:
                self.alg = QJL.GRAPE(self.max_episode, self.epsilon)

    super().CFIM(M, W)

HCRB(W=[])

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/ControlOpt/GRAPE_Copt.py
178
179
180
181
182
183
184
185
186
187
188
189
190
def HCRB(self, W=[]):
    """
    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.
    """
    raise ValueError(
        "GRAPE and auto-GRAPE are not available when the objective function is HCRB. Supported methods are 'PSO', 'DE' and 'DDPG'.",
    )

QFIM(W=[], 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/ControlOpt/GRAPE_Copt.py
 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
def QFIM(self, W=[], 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 self.auto:
        if self.Adam:
            self.alg = QJL.autoGRAPE(
                self.max_episode, self.epsilon, self.beta1, self.beta2
            )
        else:
            self.alg = QJL.autoGRAPE(self.max_episode, 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(
                    self.max_episode, self.epsilon, self.beta1, self.beta2
                )
            else:
                self.alg = QJL.autoGRAPE(self.max_episode, self.epsilon)
        else:
            if LDtype == "SLD":
                if self.Adam:
                    self.alg = QJL.GRAPE(
                        self.max_episode, self.epsilon, self.beta1, self.beta2
                        )
                else:
                    self.alg = QJL.GRAPE(self.max_episode, self.epsilon)
            else:
                raise ValueError("GRAPE is only available when LDtype is SLD.")

    super().QFIM(W, LDtype)

mintime(f, W=[], M=[], 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/ControlOpt/GRAPE_Copt.py
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 mintime(self, f, W=[], M=[], 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 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(
                self.max_episode, self.epsilon, self.beta1, self.beta2
            )
        else:
            self.alg = QJL.autoGRAPE(self.max_episode, self.epsilon)
    else:

        if self.Adam:
            self.alg = QJL.GRAPE(
                    self.max_episode, self.epsilon, self.beta1, self.beta2
                )
        else:
            self.alg = QJL.GRAPE(self.max_episode, 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/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
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,
    ):

        Control.ControlSystem.__init__(self, savefile, ctrl0, 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=[], 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).
        """
        ini_particle = (self.ctrl0, )
        self.alg = QJL.PSO(
            self.max_episode,
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().QFIM(W, LDtype)

    def CFIM(self, M=[], W=[]):
        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).
        """
        ini_particle = (self.ctrl0, )
        self.alg = QJL.PSO(
            self.max_episode,
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().CFIM(M, W)

    def HCRB(self, W=[]):
        """
        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.
        """
        ini_particle = (self.ctrl0, )
        self.alg = QJL.PSO(
            self.max_episode,
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().HCRB(W)

    def mintime(self, f, W=[], M=[], 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).
        """
        ini_particle = (self.ctrl0,)
        self.alg = QJL.PSO(
            self.max_episode,
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

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

CFIM(M=[], W=[])

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/ControlOpt/PSO_Copt.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
def CFIM(self, M=[], W=[]):
    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).
    """
    ini_particle = (self.ctrl0, )
    self.alg = QJL.PSO(
        self.max_episode,
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().CFIM(M, W)

HCRB(W=[])

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/ControlOpt/PSO_Copt.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def HCRB(self, W=[]):
    """
    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.
    """
    ini_particle = (self.ctrl0, )
    self.alg = QJL.PSO(
        self.max_episode,
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().HCRB(W)

QFIM(W=[], 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/ControlOpt/PSO_Copt.py
 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
def QFIM(self, W=[], 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).
    """
    ini_particle = (self.ctrl0, )
    self.alg = QJL.PSO(
        self.max_episode,
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().QFIM(W, LDtype)

mintime(f, W=[], M=[], 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/ControlOpt/PSO_Copt.py
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
def mintime(self, f, W=[], M=[], 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).
    """
    ini_particle = (self.ctrl0,)
    self.alg = QJL.PSO(
        self.max_episode,
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        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/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
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,
    ):

        Control.ControlSystem.__init__(self, savefile, ctrl0, 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=[], 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).
        """
        ini_population = (self.ctrl0, )
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )

        super().QFIM(W, LDtype)

    def CFIM(self, M=[], W=[]):
        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).
        """
        ini_population = (self.ctrl0, )
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )

        super().CFIM(M, W)

    def HCRB(self, W=[]):
        """
        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.
        """

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

        super().HCRB(W)

    def mintime(self, f, W=[], M=[], 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).
        """
        ini_population = (self.ctrl0, )
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )

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

CFIM(M=[], W=[])

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/ControlOpt/DE_Copt.py
 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
def CFIM(self, M=[], W=[]):
    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).
    """
    ini_population = (self.ctrl0, )
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )

    super().CFIM(M, W)

HCRB(W=[])

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/ControlOpt/DE_Copt.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def HCRB(self, W=[]):
    """
    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.
    """

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

    super().HCRB(W)

QFIM(W=[], 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/ControlOpt/DE_Copt.py
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
def QFIM(self, W=[], 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).
    """
    ini_population = (self.ctrl0, )
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )

    super().QFIM(W, LDtype)

mintime(f, W=[], M=[], 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/ControlOpt/DE_Copt.py
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
def mintime(self, f, W=[], M=[], 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).
    """
    ini_population = (self.ctrl0, )
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        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/StateOpt/StateStruct.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
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):

        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):
        if os.path.exists("states.dat"):
            fl = h5py.File("states.dat",'r')
            dset = fl["states"]
            if self.savefile:
                psi = np.array([np.array(fl[dset[i]]).view('complex') for i in range(max_episode)])
            else:
                psi = np.array(dset).view('complex')
            np.save("states", psi)
        else: pass

    def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], 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.
        """

        self.tspan = tspan

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

        if Hc == [] or ctrl == []:
            if type(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]))))
            else: pass

            if len(ctrl[0]) == 1:
                if type(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 type(H0) != np.ndarray:
                    #### linear interpolation  ####
                    f = interp1d(self.tspan, H0, axis=0)
                else: pass
                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 type(H0) != np.ndarray:
                        H0_inter = f(self.tspan)
                        H0 = [np.array(x, dtype=np.complex128) for x in H0_inter]
                    else: pass
                else: pass

                if type(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 type(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=self.psi0, seed=self.seed)
        if any(self.gamma):
            self.dynamic = QJL.Lindblad(
                self.freeHamiltonian,
                self.Hamiltonian_derivative,
                list(self.psi0),
                self.tspan,
                self.decay_opt,
                self.gamma,
                dyn_method = self.dyn_method,
            )
        else:
            self.dynamic = QJL.Lindblad(
                self.freeHamiltonian,
                self.Hamiltonian_derivative,
                list(self.psi0),
                self.tspan,
                dyn_method = self.dyn_method,
            )
        self.output = QJL.Output(self.opt, save=self.savefile)

        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=self.psi0, seed=self.seed)
        self.dynamic = QJL.Kraus(list(self.psi0), self.K, self.dK)
        self.output = QJL.Output(self.opt, save=self.savefile)

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

    def QFIM(self, W=[], 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 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
        else:
            pass

        self.obj = QJL.QFIM_obj(
            self.W, self.eps, self.para_type, LDtype
        )
        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)

        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=[], W=[]):
        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 == []:
            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, self.W, self.eps, self.para_type)
        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)

        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=[]):
        """
        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 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"
                    )
            else: pass

        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: pass
        else:
            raise ValueError(
                "Supported type of dynamics are Lindblad and Kraus."
                )

        self.obj = QJL.HCRB_obj(self.W, self.eps, self.para_type)
        system = QJL.QuanEstSystem(
                self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)

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

CFIM(M=[], W=[])

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/StateOpt/StateStruct.py
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
def CFIM(self, M=[], W=[]):
    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 == []:
        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, self.W, self.eps, self.para_type)
    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)

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

HCRB(W=[])

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/StateOpt/StateStruct.py
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
def HCRB(self, W=[]):
    """
    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 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"
                )
        else: pass

    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: pass
    else:
        raise ValueError(
            "Supported type of dynamics are Lindblad and Kraus."
            )

    self.obj = QJL.HCRB_obj(self.W, self.eps, self.para_type)
    system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)

    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/StateOpt/StateStruct.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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
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=self.psi0, seed=self.seed)
    self.dynamic = QJL.Kraus(list(self.psi0), self.K, self.dK)
    self.output = QJL.Output(self.opt, save=self.savefile)

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

QFIM(W=[], 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/StateOpt/StateStruct.py
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
def QFIM(self, W=[], 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 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
    else:
        pass

    self.obj = QJL.QFIM_obj(
        self.W, self.eps, self.para_type, LDtype
    )
    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)

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

dynamics(tspan, H0, dH, Hc=[], ctrl=[], decay=[], 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/StateOpt/StateStruct.py
 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
def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], 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.
    """

    self.tspan = tspan

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

    if Hc == [] or ctrl == []:
        if type(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]))))
        else: pass

        if len(ctrl[0]) == 1:
            if type(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 type(H0) != np.ndarray:
                #### linear interpolation  ####
                f = interp1d(self.tspan, H0, axis=0)
            else: pass
            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 type(H0) != np.ndarray:
                    H0_inter = f(self.tspan)
                    H0 = [np.array(x, dtype=np.complex128) for x in H0_inter]
                else: pass
            else: pass

            if type(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 type(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=self.psi0, seed=self.seed)
    if any(self.gamma):
        self.dynamic = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            list(self.psi0),
            self.tspan,
            self.decay_opt,
            self.gamma,
            dyn_method = self.dyn_method,
        )
    else:
        self.dynamic = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            list(self.psi0),
            self.tspan,
            dyn_method = self.dyn_method,
        )
    self.output = QJL.Output(self.opt, save=self.savefile)

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

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/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
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,
    ):

        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(
                self.max_episode, self.epsilon, self.beta1, self.beta2,
            )
        else:
            self.alg = QJL.AD(self.max_episode, self.epsilon)

    def QFIM(self, W=[], 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).
        """

        super().QFIM(W, LDtype)

    def CFIM(self, M=[], W=[]):
        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).
        """

        super().CFIM(M, W)

    def HCRB(self, W=[]):
        """
        AD is not available when the objective function is HCRB. 
        Supported methods are PSO, DE, DDPG and NM.

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

        raise ValueError(
            "AD is not available when the objective function is HCRB. Supported methods are 'PSO', 'DE', 'NM' and 'DDPG'.")

CFIM(M=[], W=[])

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/StateOpt/AD_Sopt.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def CFIM(self, M=[], W=[]):
    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).
    """

    super().CFIM(M, W)

HCRB(W=[])

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/StateOpt/AD_Sopt.py
117
118
119
120
121
122
123
124
125
126
127
128
129
def HCRB(self, W=[]):
    """
    AD is not available when the objective function is HCRB. 
    Supported methods are PSO, DE, DDPG and NM.

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

    raise ValueError(
        "AD is not available when the objective function is HCRB. Supported methods are 'PSO', 'DE', 'NM' and 'DDPG'.")

QFIM(W=[], 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/StateOpt/AD_Sopt.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def QFIM(self, W=[], 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).
    """

    super().QFIM(W, LDtype)

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/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
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,
    ):

        State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

        self.max_episode = max_episode
        self.seed = seed

    def QFIM(self, W=[], 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.
        """
        self.alg = QJL.RI(
            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=[], W=[]):
        """
        Choose CFIM as the objective function. 

        **Note:** CFIM is not available.

        Parameters
        ----------
        > **M:** `list`
            -- POVM.

        > **W:** `matrix`
            -- Weight matrix.
        """
        raise ValueError("CFIM is not available.")

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

        **Note:** Here HCRB is not available.

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        raise ValueError("HCRB is not available.")

CFIM(M=[], W=[])

Choose CFIM as the objective function.

Note: CFIM is not available.

Parameters

M: list -- POVM.

W: matrix -- Weight matrix.

Source code in quanestimation/StateOpt/RI_Sopt.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def CFIM(self, M=[], W=[]):
    """
    Choose CFIM as the objective function. 

    **Note:** CFIM is not available.

    Parameters
    ----------
    > **M:** `list`
        -- POVM.

    > **W:** `matrix`
        -- Weight matrix.
    """
    raise ValueError("CFIM is not available.")

HCRB(W=[])

Choose HCRB as the objective function.

Note: Here HCRB is not available.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/StateOpt/RI_Sopt.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def HCRB(self, W=[]):
    """
    Choose HCRB as the objective function. 

    **Note:** Here HCRB is not available.

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    raise ValueError("HCRB is not available.")

QFIM(W=[], 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/StateOpt/RI_Sopt.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def QFIM(self, W=[], 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.
    """
    self.alg = QJL.RI(
        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.")

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/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
189
190
191
192
193
194
195
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,
    ):

        State.StateSystem.__init__(self, savefile, psi0, seed, eps, load)

        """
        --------
        inputs
        --------
        p_num:
           --description: the number of particles.
           --type: int

        psi0:
           --description: initial guesses of states (kets).
           --type: array

        max_episode:
            --description: max number of the training episodes.
            --type: int

        c0:
            --description: damping factor that assists convergence.
            --type: float

        c1:
            --description: exploitation weight that attract the particle to its best 
            previous position.
            --type: float

        c2:
            --description: exploitation weight that attract the particle to the best 
            position in the neighborhood.
            --type: float

        seed:
            --description: random seed.
            --type: int

        """

        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=[], 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).
        """
        ini_particle = (self.psi,)
        self.alg = QJL.PSO(
            self.max_episode,
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().QFIM(W, LDtype)

    def CFIM(self, M=[], W=[]):
        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).
        """
        ini_particle = (self.psi,)
        self.alg = QJL.PSO(
            self.max_episode,
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().CFIM(M, W)

    def HCRB(self, W=[]):
        """
        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.
        """
        ini_particle = (self.psi,)
        self.alg = QJL.PSO(
            self.max_episode,
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().HCRB(W)

CFIM(M=[], W=[])

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/StateOpt/PSO_Sopt.py
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
def CFIM(self, M=[], W=[]):
    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).
    """
    ini_particle = (self.psi,)
    self.alg = QJL.PSO(
        self.max_episode,
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().CFIM(M, W)

HCRB(W=[])

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/StateOpt/PSO_Sopt.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def HCRB(self, W=[]):
    """
    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.
    """
    ini_particle = (self.psi,)
    self.alg = QJL.PSO(
        self.max_episode,
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().HCRB(W)

QFIM(W=[], 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/StateOpt/PSO_Sopt.py
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
def QFIM(self, W=[], 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).
    """
    ini_particle = (self.psi,)
    self.alg = QJL.PSO(
        self.max_episode,
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().QFIM(W, LDtype)

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/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
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,
    ):

        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=[], 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).
        """
        ini_population = (self.psi,)
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )
        super().QFIM(W, LDtype)

    def CFIM(self, M=[], W=[]):
        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).
        """
        ini_population = (self.psi,)
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )
        super().CFIM(M, W)

    def HCRB(self, W=[]):
        """
        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.
        """
        ini_population = (self.psi,)
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )
        super().HCRB(W)

CFIM(M=[], W=[])

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/StateOpt/DE_Sopt.py
 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
def CFIM(self, M=[], W=[]):
    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).
    """
    ini_population = (self.psi,)
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )
    super().CFIM(M, W)

HCRB(W=[])

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/StateOpt/DE_Sopt.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def HCRB(self, W=[]):
    """
    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.
    """
    ini_population = (self.psi,)
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )
    super().HCRB(W)

QFIM(W=[], 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/StateOpt/DE_Sopt.py
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
def QFIM(self, W=[], 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).
    """
    ini_population = (self.psi,)
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )
    super().QFIM(W, LDtype)

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/MeasurementOpt/MeasurementStruct.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
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):

        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):
        if os.path.exists("measurements.dat"):
            fl = h5py.File("measurements.dat",'r')
            dset = fl["measurements"]
            if self.savefile:
                mdata = np.array([[np.array(fl[fl[dset[i]][j]]).view('complex') for j in range(mnum)] for i in range(max_episode)])
            else:
                mdata = np.array([np.array(fl[dset[j]]).view('complex') for j in range(mnum)])
            np.save("measurements", mdata)
        else: pass

    def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], 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.
        """

        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 type(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 type(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, Lambda=[], 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 type(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]))))
            else: pass

            if len(ctrl[0]) == 1:
                if type(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 type(H0) != np.ndarray:
                    #### linear interpolation  ####
                    f = interp1d(self.tspan, H0, axis=0)
                else: pass
                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 type(H0) != np.ndarray:
                        H0_inter = f(self.tspan)
                        H0 = [np.array(x, dtype=np.complex128) for x in H0_inter]
                    else: pass
                else: pass

                if type(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 type(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]

        if any(self.gamma):
            self.dynamic = QJL.Lindblad(
                self.freeHamiltonian,
                self.Hamiltonian_derivative,
                self.rho0,
                self.tspan,
                self.decay_opt,
                self.gamma,
                dyn_method = self.dyn_method,
            )
        else:
            self.dynamic = QJL.Lindblad(
                self.freeHamiltonian,
                self.Hamiltonian_derivative,
                self.rho0,
                self.tspan,
                dyn_method = self.dyn_method,
            )
        self.output = QJL.Output(self.opt, save=self.savefile)

        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 type(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 type(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, Lambda=[], 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
                )
            )

        self.dynamic = QJL.Kraus(self.rho0, self.K, self.dK)
        self.output = QJL.Output(self.opt, save=self.savefile)

        self.dynamics_type = "Kraus"

    def CFIM(self, W=[]):
        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 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(
            [], self.W, self.eps, self.para_type
        )  #### m=[]
        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)
        max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
        self.load_save(self.M_num, max_num)

CFIM(W=[])

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/MeasurementOpt/MeasurementStruct.py
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
def CFIM(self, W=[]):
    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 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(
        [], self.W, self.eps, self.para_type
    )  #### m=[]
    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)
    max_num = self.max_episode if type(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/MeasurementOpt/MeasurementStruct.py
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
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 type(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 type(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, Lambda=[], 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
            )
        )

    self.dynamic = QJL.Kraus(self.rho0, self.K, self.dK)
    self.output = QJL.Output(self.opt, save=self.savefile)

    self.dynamics_type = "Kraus"

dynamics(tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], 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/MeasurementOpt/MeasurementStruct.py
 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
def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], 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.
    """

    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 type(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 type(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, Lambda=[], 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 type(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]))))
        else: pass

        if len(ctrl[0]) == 1:
            if type(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 type(H0) != np.ndarray:
                #### linear interpolation  ####
                f = interp1d(self.tspan, H0, axis=0)
            else: pass
            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 type(H0) != np.ndarray:
                    H0_inter = f(self.tspan)
                    H0 = [np.array(x, dtype=np.complex128) for x in H0_inter]
                else: pass
            else: pass

            if type(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 type(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]

    if any(self.gamma):
        self.dynamic = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.rho0,
            self.tspan,
            self.decay_opt,
            self.gamma,
            dyn_method = self.dyn_method,
        )
    else:
        self.dynamic = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.rho0,
            self.tspan,
            dyn_method = self.dyn_method,
        )
    self.output = QJL.Output(self.opt, save=self.savefile)

    self.dynamics_type = "dynamics"

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/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
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,
    ):

        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(
                self.max_episode, self.epsilon, self.beta1, self.beta2
            )
        else:
            self.alg = QJL.AD(self.max_episode, self.epsilon)

    def CFIM(self, W=[]):
        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 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=[])

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/MeasurementOpt/AD_Mopt.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def CFIM(self, W=[]):
    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 self.mtype == "projection":
        raise ValueError(
            "AD is not available when mtype is projection. Supported methods are 'PSO' and 'DE'.",
        )
    else:
        super().CFIM(W)

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/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
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,
    ):

        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=[]):
        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.
        """
        ini_particle = (self.measurement0,)
        self.alg = QJL.PSO(
            self.max_episode,
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().CFIM(W)

CFIM(W=[])

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/MeasurementOpt/PSO_Mopt.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def CFIM(self, W=[]):
    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.
    """
    ini_particle = (self.measurement0,)
    self.alg = QJL.PSO(
        self.max_episode,
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().CFIM(W)

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/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
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,
    ):

        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=[]):
        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.
        """
        ini_population = (self.measurement0,)
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )
        super().CFIM(W)

CFIM(W=[])

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/MeasurementOpt/DE_Mopt.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def CFIM(self, W=[]):
    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.
    """
    ini_population = (self.measurement0,)
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )
    super().CFIM(W)

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/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
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
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):

        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):
        if os.path.exists("controls.dat"):
            fl = h5py.File("controls.dat",'r')
            dset = fl["controls"]
            if self.savefile:
                controls = np.array([[np.array(fl[fl[dset[i]][j]]) for j in range(cnum)] for i in range(max_episode)])
            else:
                controls = np.array([np.array(fl[dset[j]]) for j in range(cnum)])
            np.save("controls", controls)
        else: pass

    def load_save_ctrls_alt(self, cnum, max_episode):
        if os.path.exists("controls.dat"):
            fl = h5py.File("controls.dat",'r')
            dset = fl["controls"]
            if self.savefile:
                controls = np.array([[np.array(fl[fl[dset[i]][j]]) for j in range(cnum)] for i in range(max_episode)])
            else:
                controls = np.array([dset[:,i] for i in range(cnum)])
            np.save("controls", controls)
        else: pass

    def load_save_states(self, max_episode):
        if os.path.exists("states.dat"):
            fl = h5py.File("states.dat",'r')
            dset = fl["states"]
            if self.savefile:
                psi = np.array([np.array(fl[dset[i]]).view('complex') for i in range(max_episode)])
            else:
                psi = np.array(dset).view('complex')
            np.save("states", psi)
        else: pass

    def load_save_meas(self, mnum, max_episode):
        if os.path.exists("measurements.dat"):
            fl = h5py.File("measurements.dat",'r')
            dset = fl["measurements"]
            if self.savefile:
                mdata = np.array([[np.array(fl[fl[dset[i]][j]]).view('complex') for j in range(mnum)] for i in range(max_episode)])
            else:
                mdata = np.array([np.array(fl[dset[j]]).view('complex') for j in range(mnum)])
            np.save("measurements", mdata)
        else: pass

    def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], ctrl_bound=[], 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. 
        """

        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 type(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[:-1]]
            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)
            psi = np.array([r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)])
            self.psi0 = np.array(psi)
            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 Hc == []:
            Hc = [np.zeros((self.dim, self.dim))]
        self.control_Hamiltonian = [np.array(x, dtype=np.complex128) for x in Hc]

        if type(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])),
                    )
                )
        else: pass

        # ## TODO
        QJLType_ctrl = QJL.Vector[QJL.Vector[QJL.Vector[QJL.Float64]]] 
        self.ctrl0 = QJL.convert(QJLType_ctrl, [[c for c in ctrls ]for ctrls in self.ctrl0])

        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 = 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])

        if type(H0) != np.ndarray:
            #### linear interpolation  ####
            f = interp1d(self.tspan, H0, axis=0)
        else: pass
        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 type(H0) != np.ndarray:
                H0_inter = f(self.tspan)
                self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter[:-1]]
            else: pass
        else: pass

        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])
        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)
            psi = np.array([r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)])
            self.psi0 = np.array(psi)
            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 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]

        self.dynamic = QJL.Kraus(list(self.psi0), self.K, self.dK)

        self.dynamics_type = "Kraus"

    def SC(self, W=[], M=[], 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 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, self.W, self.eps, self.para_type)
        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(self.W, self.eps, self.para_type)
            elif target == "QFIM" and (
                LDtype == "SLD" or LDtype == "RLD" or LDtype == "LLD"
            ):
                self.obj = QJL.QFIM_obj(
                    self.W, self.eps, self.para_type, LDtype
                )
            elif target == "CFIM":
                M = SIC(len(self.psi))
                self.obj = QJL.CFIM_obj(M, self.W, self.eps, self.para_type)
            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.control_coefficients, ctrl_bound=self.ctrl_bound, seed=self.seed
        )
        self.output = QJL.Output(self.opt, save=self.savefile)

        self.dynamic = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.control_Hamiltonian,
            self.control_coefficients,
            list(self.psi0),
            self.tspan,
            self.decay_opt,
            self.gamma,
            dyn_method = self.dyn_method,
            )
        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)

        max_num = self.max_episode if type(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=[]):
        """
        Comprehensive optimization of the control and measurement (CM).

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

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

        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([], self.W, self.eps, self.para_type)
        self.opt = QJL.ControlMeasurementOpt(
            ctrl=self.control_coefficients, M=self.C, ctrl_bound=self.ctrl_bound, seed=self.seed
        )
        self.output = QJL.Output(self.opt, save=self.savefile)

        self.dynamic = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.control_Hamiltonian,
            self.control_coefficients,
            self.rho0,
            self.tspan,
            self.decay_opt,
            self.gamma,
            dyn_method =self.dyn_method,
            )

        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)

        max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
        self.load_save_ctrls_alt(len(self.control_Hamiltonian), max_num)
        self.load_save_meas(self.dim, max_num)

    def SM(self, W=[]):
        """
        Comprehensive optimization of the probe state and measurement (SM).

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

        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])))
                        )
                else:
                    pass

                if len(self.ctrl[0]) == 1:
                    if type(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 type(self.freeHamiltonian) != np.ndarray:
                        #### linear interpolation  ####
                        f = interp1d(self.tspan, self.freeHamiltonian, axis=0)
                    else: pass
                    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 type(self.freeHamiltonian) != np.ndarray:
                            H0_inter = f(self.tspan)
                            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter]
                        else: pass
                    else: pass

                    if type(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
                        ]

            self.dynamic = QJL.Lindblad(
                freeHamiltonian,
                self.Hamiltonian_derivative,
                list(self.psi0),
                self.tspan,
                self.decay_opt,
                self.gamma,
                dyn_method = self.dyn_method,
            )
        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([], self.W, self.eps, self.para_type)
        self.opt = QJL.StateMeasurementOpt(psi=list(self.psi), M=self.C, seed=self.seed)
        self.output = QJL.Output(self.opt, save=self.savefile)

        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)

        max_num = self.max_episode if type(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=[]):
        """
        Comprehensive optimization of the probe state, control and measurement (SCM).

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

        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([], self.W, self.eps, self.para_type)
        self.opt = QJL.StateControlMeasurementOpt(
            psi=self.psi, ctrl=self.control_coefficients, M=self.C, ctrl_bound=self.ctrl_bound, seed=self.seed
        )
        self.output = QJL.Output(self.opt, save=self.savefile)

        self.dynamic = QJL.Lindblad(
            self.freeHamiltonian,
            self.Hamiltonian_derivative,
            self.control_Hamiltonian,
            self.control_coefficients,
            list(self.psi0),
            self.tspan,
            self.decay_opt,
            self.gamma,
            )

        system = QJL.QuanEstSystem(
            self.opt, self.alg, self.obj, self.dynamic, self.output
        )
        QJL.run(system)

        max_num = self.max_episode if type(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=[])

Comprehensive optimization of the control and measurement (CM).

Parameters

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

W: matrix -- Weight matrix.

Source code in quanestimation/ComprehensiveOpt/ComprehensiveStruct.py
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
def CM(self, rho0, W=[]):
    """
    Comprehensive optimization of the control and measurement (CM).

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

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

    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([], self.W, self.eps, self.para_type)
    self.opt = QJL.ControlMeasurementOpt(
        ctrl=self.control_coefficients, M=self.C, ctrl_bound=self.ctrl_bound, seed=self.seed
    )
    self.output = QJL.Output(self.opt, save=self.savefile)

    self.dynamic = QJL.Lindblad(
        self.freeHamiltonian,
        self.Hamiltonian_derivative,
        self.control_Hamiltonian,
        self.control_coefficients,
        self.rho0,
        self.tspan,
        self.decay_opt,
        self.gamma,
        dyn_method =self.dyn_method,
        )

    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)

    max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
    self.load_save_ctrls_alt(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/ComprehensiveOpt/ComprehensiveStruct.py
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
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])
    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)
        psi = np.array([r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)])
        self.psi0 = np.array(psi)
        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 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]

    self.dynamic = QJL.Kraus(list(self.psi0), self.K, self.dK)

    self.dynamics_type = "Kraus"

SC(W=[], M=[], 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/ComprehensiveOpt/ComprehensiveStruct.py
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
def SC(self, W=[], M=[], 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 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, self.W, self.eps, self.para_type)
    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(self.W, self.eps, self.para_type)
        elif target == "QFIM" and (
            LDtype == "SLD" or LDtype == "RLD" or LDtype == "LLD"
        ):
            self.obj = QJL.QFIM_obj(
                self.W, self.eps, self.para_type, LDtype
            )
        elif target == "CFIM":
            M = SIC(len(self.psi))
            self.obj = QJL.CFIM_obj(M, self.W, self.eps, self.para_type)
        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.control_coefficients, ctrl_bound=self.ctrl_bound, seed=self.seed
    )
    self.output = QJL.Output(self.opt, save=self.savefile)

    self.dynamic = QJL.Lindblad(
        self.freeHamiltonian,
        self.Hamiltonian_derivative,
        self.control_Hamiltonian,
        self.control_coefficients,
        list(self.psi0),
        self.tspan,
        self.decay_opt,
        self.gamma,
        dyn_method = self.dyn_method,
        )
    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)

    max_num = self.max_episode if type(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=[])

Comprehensive optimization of the probe state, control and measurement (SCM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/ComprehensiveOpt/ComprehensiveStruct.py
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
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
def SCM(self, W=[]):
    """
    Comprehensive optimization of the probe state, control and measurement (SCM).

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

    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([], self.W, self.eps, self.para_type)
    self.opt = QJL.StateControlMeasurementOpt(
        psi=self.psi, ctrl=self.control_coefficients, M=self.C, ctrl_bound=self.ctrl_bound, seed=self.seed
    )
    self.output = QJL.Output(self.opt, save=self.savefile)

    self.dynamic = QJL.Lindblad(
        self.freeHamiltonian,
        self.Hamiltonian_derivative,
        self.control_Hamiltonian,
        self.control_coefficients,
        list(self.psi0),
        self.tspan,
        self.decay_opt,
        self.gamma,
        )

    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)

    max_num = self.max_episode if type(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=[])

Comprehensive optimization of the probe state and measurement (SM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/ComprehensiveOpt/ComprehensiveStruct.py
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
def SM(self, W=[]):
    """
    Comprehensive optimization of the probe state and measurement (SM).

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

    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])))
                    )
            else:
                pass

            if len(self.ctrl[0]) == 1:
                if type(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 type(self.freeHamiltonian) != np.ndarray:
                    #### linear interpolation  ####
                    f = interp1d(self.tspan, self.freeHamiltonian, axis=0)
                else: pass
                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 type(self.freeHamiltonian) != np.ndarray:
                        H0_inter = f(self.tspan)
                        self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter]
                    else: pass
                else: pass

                if type(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
                    ]

        self.dynamic = QJL.Lindblad(
            freeHamiltonian,
            self.Hamiltonian_derivative,
            list(self.psi0),
            self.tspan,
            self.decay_opt,
            self.gamma,
            dyn_method = self.dyn_method,
        )
    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([], self.W, self.eps, self.para_type)
    self.opt = QJL.StateMeasurementOpt(psi=list(self.psi), M=self.C, seed=self.seed)
    self.output = QJL.Output(self.opt, save=self.savefile)

    system = QJL.QuanEstSystem(
        self.opt, self.alg, self.obj, self.dynamic, self.output
    )
    QJL.run(system)

    max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0]
    self.load_save_states(max_num)
    self.load_save_meas(self.dim, max_num)

dynamics(tspan, H0, dH, Hc=[], ctrl=[], decay=[], ctrl_bound=[], 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/ComprehensiveOpt/ComprehensiveStruct.py
 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
def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], ctrl_bound=[], 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. 
    """

    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 type(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[:-1]]
        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)
        psi = np.array([r[i] * np.exp(1.0j * phi[i]) for i in range(self.dim)])
        self.psi0 = np.array(psi)
        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 Hc == []:
        Hc = [np.zeros((self.dim, self.dim))]
    self.control_Hamiltonian = [np.array(x, dtype=np.complex128) for x in Hc]

    if type(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])),
                )
            )
    else: pass

    # ## TODO
    QJLType_ctrl = QJL.Vector[QJL.Vector[QJL.Vector[QJL.Float64]]] 
    self.ctrl0 = QJL.convert(QJLType_ctrl, [[c for c in ctrls ]for ctrls in self.ctrl0])

    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 = 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])

    if type(H0) != np.ndarray:
        #### linear interpolation  ####
        f = interp1d(self.tspan, H0, axis=0)
    else: pass
    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 type(H0) != np.ndarray:
            H0_inter = f(self.tspan)
            self.freeHamiltonian = [np.array(x, dtype=np.complex128) for x in H0_inter[:-1]]
        else: pass
    else: pass

    self.dynamics_type = "dynamics"

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/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
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,
    ):

        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=[], M=[], 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 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.QuanEstimation.AD(
                self.max_episode, self.epsilon, self.beta1, self.beta2
            )
        else:
            self.alg = QJL.QuanEstimation.AD(self.max_episode, self.epsilon)

        super().SC(W, M, target, LDtype)

SC(W=[], M=[], 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/ComprehensiveOpt/AD_Compopt.py
 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
def SC(self, W=[], M=[], 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 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.QuanEstimation.AD(
            self.max_episode, self.epsilon, self.beta1, self.beta2
        )
    else:
        self.alg = QJL.QuanEstimation.AD(self.max_episode, self.epsilon)

    super().SC(W, M, target, LDtype)

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/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
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,
    ):

        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=[], M=[], 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).
        """
        ini_particle = (
            self.psi, 
            self.ctrl0
       )

        self.alg = QJL.PSO(
            QJL.Vector[QJL.Int64](self.max_episode),
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )
        super().SC(W, M, target, LDtype)

    def CM(self, rho0, W=[]):
        """
        Comprehensive optimization of the control and measurement (CM).

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

        > **W:** `matrix`
            -- Weight matrix.
        """
        ini_particle = (self.ctrl0, self.measurement0)
        self.alg = QJL.PSO(
            QJL.Vector[QJL.Int64](self.max_episode),
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().CM(rho0, W)

    def SM(self, W=[]):
        """
        Comprehensive optimization of the probe state and measurement (SM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        ini_particle = (self.psi, self.measurement0)
        self.alg = QJL.PSO(
            QJL.Vector[QJL.Int64](self.max_episode),
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().SM(W)

    def SCM(self, W=[]):
        """
        Comprehensive optimization of the probe state, the control and measurements (SCM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        ini_particle = (self.psi, self.ctrl0, self.measurement0)
        self.alg = QJL.PSO(
            QJL.Vector[QJL.Int64](self.max_episode),
            self.p_num,
            ini_particle,
            self.c0,
            self.c1,
            self.c2,
        )

        super().SCM(W)

CM(rho0, W=[])

Comprehensive optimization of the control and measurement (CM).

Parameters

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

W: matrix -- Weight matrix.

Source code in quanestimation/ComprehensiveOpt/PSO_Compopt.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def CM(self, rho0, W=[]):
    """
    Comprehensive optimization of the control and measurement (CM).

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

    > **W:** `matrix`
        -- Weight matrix.
    """
    ini_particle = (self.ctrl0, self.measurement0)
    self.alg = QJL.PSO(
        QJL.Vector[QJL.Int64](self.max_episode),
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().CM(rho0, W)

SC(W=[], M=[], 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/ComprehensiveOpt/PSO_Compopt.py
 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
def SC(self, W=[], M=[], 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).
    """
    ini_particle = (
        self.psi, 
        self.ctrl0
   )

    self.alg = QJL.PSO(
        QJL.Vector[QJL.Int64](self.max_episode),
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )
    super().SC(W, M, target, LDtype)

SCM(W=[])

Comprehensive optimization of the probe state, the control and measurements (SCM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/ComprehensiveOpt/PSO_Compopt.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def SCM(self, W=[]):
    """
    Comprehensive optimization of the probe state, the control and measurements (SCM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    ini_particle = (self.psi, self.ctrl0, self.measurement0)
    self.alg = QJL.PSO(
        QJL.Vector[QJL.Int64](self.max_episode),
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().SCM(W)

SM(W=[])

Comprehensive optimization of the probe state and measurement (SM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/ComprehensiveOpt/PSO_Compopt.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def SM(self, W=[]):
    """
    Comprehensive optimization of the probe state and measurement (SM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    ini_particle = (self.psi, self.measurement0)
    self.alg = QJL.PSO(
        QJL.Vector[QJL.Int64](self.max_episode),
        self.p_num,
        ini_particle,
        self.c0,
        self.c1,
        self.c2,
    )

    super().SM(W)

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/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
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,
    ):

        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=[], M=[], 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).
        """
        ini_population = (self.psi, self.ctrl0)
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )

        super().SC(W, M, target, LDtype)

    def CM(self, rho0, W=[]):
        """
        Comprehensive optimization of the control and measurement (CM).

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

        > **W:** `matrix`
            -- Weight matrix.
        """
        ini_population = (self.ctrl0, self.measurement0)
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )

        super().CM(rho0, W)

    def SM(self, W=[]):
        """
        Comprehensive optimization of the probe state and measurement (SM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        ini_population = (self.psi, self.measurement0)
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )

        super().SM(W)

    def SCM(self, W=[]):
        """
        Comprehensive optimization of the probe state, control and measurement (SCM).

        Parameters
        ----------
        > **W:** `matrix`
            -- Weight matrix.
        """
        ini_population = (self.psi, self.ctrl0, self.measurement0)
        self.alg = QJL.DE(
            self.max_episode,
            self.p_num,
            ini_population,
            self.c,
            self.cr,
        )

        super().SCM(W)

CM(rho0, W=[])

Comprehensive optimization of the control and measurement (CM).

Parameters

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

W: matrix -- Weight matrix.

Source code in quanestimation/ComprehensiveOpt/DE_Compopt.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def CM(self, rho0, W=[]):
    """
    Comprehensive optimization of the control and measurement (CM).

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

    > **W:** `matrix`
        -- Weight matrix.
    """
    ini_population = (self.ctrl0, self.measurement0)
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )

    super().CM(rho0, W)

SC(W=[], M=[], 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/ComprehensiveOpt/DE_Compopt.py
 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
def SC(self, W=[], M=[], 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).
    """
    ini_population = (self.psi, self.ctrl0)
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )

    super().SC(W, M, target, LDtype)

SCM(W=[])

Comprehensive optimization of the probe state, control and measurement (SCM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/ComprehensiveOpt/DE_Compopt.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def SCM(self, W=[]):
    """
    Comprehensive optimization of the probe state, control and measurement (SCM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    ini_population = (self.psi, self.ctrl0, self.measurement0)
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )

    super().SCM(W)

SM(W=[])

Comprehensive optimization of the probe state and measurement (SM).

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/ComprehensiveOpt/DE_Compopt.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def SM(self, W=[]):
    """
    Comprehensive optimization of the probe state and measurement (SM).

    Parameters
    ----------
    > **W:** `matrix`
        -- Weight matrix.
    """
    ini_population = (self.psi, self.measurement0)
    self.alg = QJL.DE(
        self.max_episode,
        self.p_num,
        ini_population,
        self.c,
        self.cr,
    )

    super().SM(W)

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/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
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):

        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

    def dynamics(self, tspan, H, dH, Hc=[], ctrl=[], decay=[], 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.
        """

        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.dynamic_type = "Kraus"

    def CFIM(self, M=[], W=[]):
        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 == []:
            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=[]):
        r"""
        Measurement optimization for the optimal x.

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

        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,
                            self.H_list[i],
                            self.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,
                            self.H_list[i],
                            self.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 = self.H_list[idx], self.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 = self.K_list[idx], self.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=[], W=[])

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/AdaptiveScheme/Adapt.py
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
def CFIM(self, M=[], W=[]):
    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 == []:
        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/AdaptiveScheme/Adapt.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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.dynamic_type = "Kraus"

Mopt(W=[])

Measurement optimization for the optimal x.

Parameters

W: matrix -- Weight matrix.

Source code in quanestimation/AdaptiveScheme/Adapt.py
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
def Mopt(self, W=[]):
    r"""
    Measurement optimization for the optimal x.

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

    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,
                        self.H_list[i],
                        self.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,
                        self.H_list[i],
                        self.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 = self.H_list[idx], self.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 = self.K_list[idx], self.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
            )
        )

dynamics(tspan, H, dH, Hc=[], ctrl=[], decay=[], 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/AdaptiveScheme/Adapt.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
103
104
105
106
107
def dynamics(self, tspan, H, dH, Hc=[], ctrl=[], decay=[], 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.
    """

    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/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
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):

        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):
        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
                )
            )

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/AdaptiveScheme/Adapt_MZI.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
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
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/AdaptiveScheme/Adapt_MZI.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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
    )