diff --git a/quanestimation/AdaptiveScheme/Adapt.py b/quanestimation/AdaptiveScheme/Adapt.py index bda0a657..83cf964c 100755 --- a/quanestimation/AdaptiveScheme/Adapt.py +++ b/quanestimation/AdaptiveScheme/Adapt.py @@ -22,18 +22,18 @@ class Adapt: -- Initial state (density matrix). > **method:** `string` - -- Choose the method for updating the tunable parameters (u). Options are: - "FOP" (default) -- Fix optimal point. + -- 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 + -- 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". - + 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. @@ -41,8 +41,9 @@ class Adapt: -- Machine epsilon. """ - def __init__(self, x, p, rho0, method="FOP", savefile=False, max_episode=1000, eps=1e-8): - + 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) @@ -108,7 +109,7 @@ def dynamics(self, tspan, H, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): def Kraus(self, K, dK): r""" - Dynamics of the density matrix of the form + Dynamics of the density matrix of the form \begin{align} \rho=\sum_i K_i\rho_0K_i^{\dagger} \end{align} @@ -121,7 +122,7 @@ def Kraus(self, K, dK): -- 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 + -- Derivatives of the Kraus operator(s) with respect to the unknown parameters to be estimated. """ @@ -132,8 +133,8 @@ def Kraus(self, K, dK): 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 + 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 @@ -142,11 +143,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -188,7 +189,7 @@ def CFIM(self, M=[], W=[]): self.max_episode, self.eps, self.savefile, - self.method + self.method, ) else: raise ValueError( @@ -314,7 +315,10 @@ def Mopt(self, W=[]): 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]] + [ + np.dot(Ki, np.dot(self.rho0, Ki.conj().T)) + for Ki in self.K[hi] + ] ) drho_tp = sum( [ @@ -343,7 +347,10 @@ def Mopt(self, W=[]): 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]] + [ + 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)] @@ -376,9 +383,26 @@ def Mopt(self, W=[]): self.dynamic_type ) ) - -def adaptive_dynamics(x, p, M, tspan, rho0, H, dH, decay, Hc, ctrl, W, max_episode, eps, savefile, method, dyn_method="expm"): + +def adaptive_dynamics( + x, + p, + M, + tspan, + rho0, + H, + dH, + decay, + Hc, + ctrl, + W, + max_episode, + eps, + savefile, + method, + dyn_method="expm", +): para_num = len(x) dim = np.shape(rho0)[0] if para_num == 1: @@ -389,19 +413,23 @@ def adaptive_dynamics(x, p, M, tspan, rho0, H, dH, decay, Hc, ctrl, W, max_episo rho_all = [] if dyn_method == "expm": for hi in range(p_num): - dynamics = Lindblad(tspan, rho0, H[hi], dH[hi], decay=decay, Hc=Hc, ctrl=ctrl) + dynamics = Lindblad( + tspan, rho0, H[hi], dH[hi], decay=decay, Hc=Hc, ctrl=ctrl + ) rho_tp, drho_tp = dynamics.expm() F_tp = CFIM(rho_tp[-1], drho_tp[-1], M) F.append(F_tp) rho_all.append(rho_tp[-1]) elif dyn_method == "ode": for hi in range(p_num): - dynamics = Lindblad(tspan, rho0, H[hi], dH[hi], decay=decay, Hc=Hc, ctrl=ctrl) + dynamics = Lindblad( + tspan, rho0, H[hi], dH[hi], decay=decay, Hc=Hc, ctrl=ctrl + ) rho_tp, drho_tp = dynamics.ode() F_tp = CFIM(rho_tp[-1], drho_tp[-1], M) F.append(F_tp) rho_all.append(rho_tp[-1]) - + u = 0.0 if method == "FOP": idx = np.argmax(F) @@ -410,27 +438,35 @@ def adaptive_dynamics(x, p, M, tspan, rho0, H, dH, decay, Hc, ctrl, W, max_episo if savefile == False: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_FOP_singlepara(p, p_num, x, u, rho_all, M, dim, x_opt, ei) + p, x_out, res_exp, u = iter_FOP_singlepara( + p, p_num, x, u, rho_all, M, dim, x_opt, ei + ) xout.append(x_out) y.append(res_exp) savefile_false(p, xout, y) else: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_FOP_singlepara(p, p_num, x, u, rho_all, M, dim, x_opt, ei) + p, x_out, res_exp, u = iter_FOP_singlepara( + p, p_num, x, u, rho_all, M, dim, x_opt, ei + ) savefile_true([np.array(p)], x_out, res_exp) elif method == "MI": if savefile == False: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_MI_singlepara(p, p_num, x, u, rho_all, M, dim, ei) + p, x_out, res_exp, u = iter_MI_singlepara( + p, p_num, x, u, rho_all, M, dim, ei + ) xout.append(x_out) y.append(res_exp) savefile_false(p, xout, y) else: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_MI_singlepara(p, p_num, x, u, rho_all, M, dim, ei) + p, x_out, res_exp, u = iter_MI_singlepara( + p, p_num, x, u, rho_all, M, dim, ei + ) savefile_true([np.array(p)], x_out, res_exp) else: #### miltiparameter senario #### @@ -454,7 +490,9 @@ def adaptive_dynamics(x, p, M, tspan, rho0, H, dH, decay, Hc, ctrl, W, max_episo rho_all = [] if dyn_method == "expm": for hi in range(p_num): - dynamics = Lindblad(tspan, rho0, H_list[hi], dH_list[hi], decay=decay, Hc=Hc, ctrl=ctrl) + dynamics = Lindblad( + tspan, rho0, H_list[hi], dH_list[hi], decay=decay, Hc=Hc, ctrl=ctrl + ) rho_tp, drho_tp = dynamics.expm() F_tp = CFIM(rho_tp[-1], drho_tp[-1], M) if np.linalg.det(F_tp) < eps: @@ -464,7 +502,9 @@ def adaptive_dynamics(x, p, M, tspan, rho0, H, dH, decay, Hc, ctrl, W, max_episo rho_all.append(rho_tp[-1]) elif dyn_method == "ode": for hi in range(p_num): - dynamics = Lindblad(tspan, rho0, H_list[hi], dH_list[hi], decay=decay, Hc=Hc, ctrl=ctrl) + dynamics = Lindblad( + tspan, rho0, H_list[hi], dH_list[hi], decay=decay, Hc=Hc, ctrl=ctrl + ) rho_tp, drho_tp = dynamics.ode() F_tp = CFIM(rho_tp[-1], drho_tp[-1], M) if np.linalg.det(F_tp) < eps: @@ -482,27 +522,58 @@ def adaptive_dynamics(x, p, M, tspan, rho0, H, dH, decay, Hc, ctrl, W, max_episo if savefile == False: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_FOP_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, x_opt, ei, p_shape) + p, x_out, res_exp, u = iter_FOP_multipara( + p, + p_num, + para_num, + x, + x_list, + u, + rho_all, + M, + dim, + x_opt, + ei, + p_shape, + ) xout.append(x_out) y.append(res_exp) savefile_false(p, xout, y) else: for ei in range(max_episode): - p, x_out, res_exp, u = iter_FOP_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, x_opt, ei, p_shape) + p, x_out, res_exp, u = iter_FOP_multipara( + p, + p_num, + para_num, + x, + x_list, + u, + rho_all, + M, + dim, + x_opt, + ei, + p_shape, + ) savefile_true(np.array(p), x_out, res_exp) elif method == "MI": if savefile == False: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_MI_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_shape) + p, x_out, res_exp, u = iter_MI_multipara( + p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_shape + ) xout.append(x_out) y.append(res_exp) savefile_false(p, xout, y) else: for ei in range(max_episode): - p, x_out, res_exp, u = iter_MI_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_shape) + p, x_out, res_exp, u = iter_MI_multipara( + p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_shape + ) savefile_true(np.array(p), x_out, res_exp) + def adaptive_Kraus(x, p, M, rho0, K, dK, W, max_episode, eps, savefile, method): para_num = len(x) dim = np.shape(rho0)[0] @@ -513,7 +584,18 @@ def adaptive_Kraus(x, p, M, rho0, K, dK, W, max_episode, eps, savefile, method): rho_all = [] for hi in range(p_num): rho_tp = sum([np.dot(Ki, np.dot(rho0, Ki.conj().T)) for Ki in K[hi]]) - drho_tp = [sum([(np.dot(dKi, np.dot(rho0, Ki.conj().T)) + np.dot(Ki, np.dot(rho0, dKi.conj().T))) for (Ki, dKi) in zip(K[hi], dKj)]) for dKj in dK[hi]] + drho_tp = [ + sum( + [ + ( + np.dot(dKi, np.dot(rho0, Ki.conj().T)) + + np.dot(Ki, np.dot(rho0, dKi.conj().T)) + ) + for (Ki, dKi) in zip(K[hi], dKj) + ] + ) + for dKj in dK[hi] + ] F_tp = CFIM(rho_tp, drho_tp, M) F.append(F_tp) rho_all.append(rho_tp) @@ -526,25 +608,33 @@ def adaptive_Kraus(x, p, M, rho0, K, dK, W, max_episode, eps, savefile, method): if savefile == False: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_FOP_singlepara(p, p_num, x, u, rho_all, M, dim, x_opt, ei) + p, x_out, res_exp, u = iter_FOP_singlepara( + p, p_num, x, u, rho_all, M, dim, x_opt, ei + ) xout.append(x_out) y.append(res_exp) savefile_false(p, xout, y) else: for ei in range(max_episode): - p, x_out, res_exp, u = iter_FOP_singlepara(p, p_num, x, u, rho_all, M, dim, x_opt, ei) + p, x_out, res_exp, u = iter_FOP_singlepara( + p, p_num, x, u, rho_all, M, dim, x_opt, ei + ) savefile_true([np.array(p)], x_out, res_exp) elif method == "MI": if savefile == False: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_MI_singlepara(p, p_num, x, u, rho_all, M, dim, ei) + p, x_out, res_exp, u = iter_MI_singlepara( + p, p_num, x, u, rho_all, M, dim, ei + ) xout.append(x_out) y.append(res_exp) savefile_false(p, xout, y) else: for ei in range(max_episode): - p, x_out, res_exp, u = iter_MI_singlepara(p, p_num, x, u, rho_all, M, dim, ei) + p, x_out, res_exp, u = iter_MI_singlepara( + p, p_num, x, u, rho_all, M, dim, ei + ) savefile_true([np.array(p)], x_out, res_exp) else: #### miltiparameter senario #### @@ -568,8 +658,19 @@ def adaptive_Kraus(x, p, M, rho0, K, dK, W, max_episode, eps, savefile, method): rho_all = [] for hi in range(p_num): rho_tp = sum([np.dot(Ki, np.dot(rho0, Ki.conj().T)) for Ki in K_list[hi]]) - dK_reshape = [[dK_list[hi][i][j] for i in range(k_num)] for j in range(para_num)] - drho_tp = [sum([np.dot(dKi, np.dot(rho0, Ki.conj().T))+ np.dot(Ki, np.dot(rho0, dKi.conj().T)) for (Ki, dKi) in zip(K_list[hi], dKj)]) for dKj in dK_reshape] + dK_reshape = [ + [dK_list[hi][i][j] for i in range(k_num)] for j in range(para_num) + ] + drho_tp = [ + sum( + [ + np.dot(dKi, np.dot(rho0, Ki.conj().T)) + + np.dot(Ki, np.dot(rho0, dKi.conj().T)) + for (Ki, dKi) in zip(K_list[hi], dKj) + ] + ) + for dKj in dK_reshape + ] F_tp = CFIM(rho_tp, drho_tp, M) if np.linalg.det(F_tp) < eps: F.append(eps) @@ -586,27 +687,58 @@ def adaptive_Kraus(x, p, M, rho0, K, dK, W, max_episode, eps, savefile, method): if savefile == False: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_FOP_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, x_opt, ei, p_shape) + p, x_out, res_exp, u = iter_FOP_multipara( + p, + p_num, + para_num, + x, + x_list, + u, + rho_all, + M, + dim, + x_opt, + ei, + p_shape, + ) xout.append(x_out) y.append(res_exp) savefile_false(p, xout, y) else: for ei in range(max_episode): - p, x_out, res_exp, u = iter_FOP_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, x_opt, ei, p_shape) + p, x_out, res_exp, u = iter_FOP_multipara( + p, + p_num, + para_num, + x, + x_list, + u, + rho_all, + M, + dim, + x_opt, + ei, + p_shape, + ) savefile_true(np.array(p), x_out, res_exp) elif method == "MI": if savefile == False: y, xout = [], [] for ei in range(max_episode): - p, x_out, res_exp, u = iter_MI_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_shape) + p, x_out, res_exp, u = iter_MI_multipara( + p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_shape + ) xout.append(x_out) y.append(res_exp) savefile_false(p, xout, y) else: for ei in range(max_episode): - p, x_out, res_exp, u = iter_MI_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_shape) + p, x_out, res_exp, u = iter_MI_multipara( + p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_shape + ) savefile_true(np.array(p), x_out, res_exp) + def iter_FOP_singlepara(p, p_num, x, u, rho_all, M, dim, x_opt, ei): rho = [np.zeros((dim, dim), dtype=np.complex128) for i in range(p_num)] for hj in range(p_num): @@ -622,7 +754,7 @@ def iter_FOP_singlepara(p, p_num, x, u, rho_all, M, dim, x_opt, ei): arr = np.array([pyx[m] * p[m] for m in range(p_num)]) py = simpson(arr, x[0]) p_update = pyx * p / py - + for i in range(p_num): if x[0][0] < (x[0][i] + u) < x[0][-1]: p[i] = p_update[i] @@ -639,11 +771,23 @@ def iter_FOP_singlepara(p, p_num, x, u, rho_all, M, dim, x_opt, ei): raise ValueError("please increase the regime of the parameters.") return p, x_out, res_exp, u -def iter_FOP_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, x_opt, ei, p_shape): + +def iter_FOP_multipara( + p, p_num, para_num, x, x_list, u, rho_all, M, dim, x_opt, ei, p_shape +): rho = [np.zeros((dim, dim), dtype=np.complex128) for i in range(p_num)] for hj in range(p_num): - idx_list = [np.argmin(np.abs(x[i] - (x_list[hj][i] + u[i]))) for i in range(para_num)] - x_idx = int(sum([idx_list[i] * np.prod(np.array(p_shape[(i + 1) :])) for i in range(para_num)])) + idx_list = [ + np.argmin(np.abs(x[i] - (x_list[hj][i] + u[i]))) for i in range(para_num) + ] + x_idx = int( + sum( + [ + idx_list[i] * np.prod(np.array(p_shape[(i + 1) :])) + for i in range(para_num) + ] + ) + ) rho[hj] = rho_all[x_idx] print("The tunable parameter are %s" % (u)) res_exp = input("Please enter the experimental result: ") @@ -657,19 +801,22 @@ def iter_FOP_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, x_opt, arr = simpson(arr, x[si]) py = arr p_update = p * pyx / py - + p_lis = np.zeros(p_num) p_ext = extract_ele(p_update, para_num) p_up_lis = [] for p_ele in p_ext: p_up_lis.append(p_ele) for i in range(p_num): - res = [x_list[0][ri] < (x_list[i][ri] + u[ri]) < x_list[-1][ri] for ri in range(para_num)] + res = [ + x_list[0][ri] < (x_list[i][ri] + u[ri]) < x_list[-1][ri] + for ri in range(para_num) + ] if all(res): - p_lis[i] = p_up_lis[i] + p_lis[i] = p_up_lis[i] p = p_lis.reshape(p_shape) - + p_idx = np.unravel_index(p.argmax(), p.shape) x_out = [x[i][p_idx[i]] for i in range(para_num)] @@ -682,6 +829,7 @@ def iter_FOP_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, x_opt, raise ValueError("please increase the regime of the parameters.") return p, x_out, res_exp, u + def iter_MI_singlepara(p, p_num, x, u, rho_all, M, dim, ei): rho = [np.zeros((dim, dim), dtype=np.complex128) for i in range(p_num)] for hj in range(p_num): @@ -698,7 +846,7 @@ def iter_MI_singlepara(p, p_num, x, u, rho_all, M, dim, ei): arr = np.array([pyx[m] * p[m] for m in range(p_num)]) py = simpson(arr, x[0]) p_update = pyx * p / py - + for i in range(p_num): if x[0][0] < (x[0][i] + u) < x[0][-1]: p[i] = p_update[i] @@ -717,9 +865,11 @@ def iter_MI_singlepara(p, p_num, x, u, rho_all, M, dim, ei): rho_u[hk] = rho_all[x_idx] value_tp = np.zeros(p_num) for mi in range(len(M)): - pyx_tp = np.array([np.real(np.trace(np.dot(rho_u[xi], M[mi]))) for xi in range(p_num)]) + pyx_tp = np.array( + [np.real(np.trace(np.dot(rho_u[xi], M[mi]))) for xi in range(p_num)] + ) mean_tp = simpson(np.array([pyx_tp[i] * p[i] for i in range(p_num)]), x[0]) - value_tp += pyx_tp*np.log2(pyx_tp/mean_tp) + value_tp += pyx_tp * np.log2(pyx_tp / mean_tp) # arr = np.array([value_tp[i] * p[i] for i in range(p_num)]) arr = np.zeros(p_num) for i in range(p_num): @@ -733,11 +883,21 @@ def iter_MI_singlepara(p, p_num, x, u, rho_all, M, dim, ei): raise ValueError("please increase the regime of the parameters.") return p, x_out, res_exp, u + def iter_MI_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_shape): rho = [np.zeros((dim, dim), dtype=np.complex128) for i in range(p_num)] for hj in range(p_num): - idx_list = [np.argmin(np.abs(x[i] - (x_list[hj][i] + u[i]))) for i in range(para_num)] - x_idx = int(sum([idx_list[i] * np.prod(np.array(p_shape[(i + 1) :])) for i in range(para_num)])) + idx_list = [ + np.argmin(np.abs(x[i] - (x_list[hj][i] + u[i]))) for i in range(para_num) + ] + x_idx = int( + sum( + [ + idx_list[i] * np.prod(np.array(p_shape[(i + 1) :])) + for i in range(para_num) + ] + ) + ) rho[hj] = rho_all[x_idx] print("The tunable parameter are %s" % (u)) res_exp = input("Please enter the experimental result: ") @@ -751,16 +911,19 @@ def iter_MI_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_s arr = simpson(arr, x[si]) py = arr p_update = p * pyx / py - + p_lis = np.zeros(p_num) p_ext = extract_ele(p_update, para_num) p_up_lis = [] for p_ele in p_ext: p_up_lis.append(p_ele) for i in range(p_num): - res = [x_list[0][ri] < (x_list[i][ri] + u[ri]) < x_list[-1][ri] for ri in range(para_num)] + res = [ + x_list[0][ri] < (x_list[i][ri] + u[ri]) < x_list[-1][ri] + for ri in range(para_num) + ] if all(res): - p_lis[i] = p_up_lis[i] + p_lis[i] = p_up_lis[i] p = p_lis.reshape(p_shape) @@ -772,21 +935,33 @@ def iter_MI_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_s for ui in range(p_num): rho_u = [np.zeros((dim, dim), dtype=np.complex128) for i in range(p_num)] for hj in range(p_num): - idx_list = [np.argmin(np.abs(x[i] - (x_list[hj][i] + x_list[ui][i]))) for i in range(para_num)] - x_idx = int(sum([idx_list[i] * np.prod(np.array(p_shape[(i + 1) :])) for i in range(para_num)])) + idx_list = [ + np.argmin(np.abs(x[i] - (x_list[hj][i] + x_list[ui][i]))) + for i in range(para_num) + ] + x_idx = int( + sum( + [ + idx_list[i] * np.prod(np.array(p_shape[(i + 1) :])) + for i in range(para_num) + ] + ) + ) rho_u[hj] = rho_all[x_idx] value_tp = np.zeros(p_shape) for mi in range(len(M)): - pyx_list_tp = np.array([np.real(np.trace(np.dot(rho_u[xi], M[mi]))) for xi in range(p_num)]) + pyx_list_tp = np.array( + [np.real(np.trace(np.dot(rho_u[xi], M[mi]))) for xi in range(p_num)] + ) pyx_tp = pyx_list_tp.reshape(p_shape) mean_tp = p * pyx_tp for si in reversed(range(para_num)): mean_tp = simpson(mean_tp, x[si]) - value_tp += pyx_tp*np.log2(pyx_tp/mean_tp) + value_tp += pyx_tp * np.log2(pyx_tp / mean_tp) # value_int = p * value_tp # for sj in reversed(range(para_num)): - # value_int = simpson(value_int, x[sj]) + # value_int = simpson(value_int, x[sj]) arr = np.zeros(p_num) p_ext = extract_ele(p, para_num) @@ -796,12 +971,15 @@ def iter_MI_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_s p_lis.append(p_ele) value_lis.append(value_ele) for hj in range(p_num): - res = [x_list[0][ri] < (x_list[hj][ri] + x_list[ui][ri]) < x_list[-1][ri] for ri in range(para_num)] + res = [ + x_list[0][ri] < (x_list[hj][ri] + x_list[ui][ri]) < x_list[-1][ri] + for ri in range(para_num) + ] if all(res): - arr[hj] = p_lis[hj] * value_lis[hj] + arr[hj] = p_lis[hj] * value_lis[hj] value_int = arr.reshape(p_shape) for sj in reversed(range(para_num)): - value_int = simpson(value_int, x[sj]) + value_int = simpson(value_int, x[sj]) MI[ui] = value_int p_idx = np.unravel_index(MI.argmax(), p.shape) @@ -813,34 +991,36 @@ def iter_MI_multipara(p, p_num, para_num, x, x_list, u, rho_all, M, dim, ei, p_s raise ValueError("please increase the regime of the parameters.") return p, x_out, res_exp, u + def savefile_true(p, xout, y): - fp = open('pout.csv','a') - fp.write('\n') + fp = open("pout.csv", "a") + fp.write("\n") np.savetxt(fp, p) fp.close() - fx = open('xout.csv','a') - fx.write('\n') + fx = open("xout.csv", "a") + fx.write("\n") np.savetxt(fx, [xout]) fx.close() - fy = open('y.csv','a') - fy.write('\n') + fy = open("y.csv", "a") + fy.write("\n") np.savetxt(fy, [y]) fy.close() + def savefile_false(p, xout, y): - fp = open('pout.csv','a') - fp.write('\n') + fp = open("pout.csv", "a") + fp.write("\n") np.savetxt(fp, np.array(p)) fp.close() - fx = open('xout.csv','a') - fx.write('\n') + fx = open("xout.csv", "a") + fx.write("\n") np.savetxt(fx, np.array(xout)) fx.close() - fy = open('y.csv','a') - fy.write('\n') + fy = open("y.csv", "a") + fy.write("\n") np.savetxt(fy, np.array(y)) fy.close() diff --git a/quanestimation/AdaptiveScheme/Adapt_MZI.py b/quanestimation/AdaptiveScheme/Adapt_MZI.py index 7b326ce3..1c3330f9 100755 --- a/quanestimation/AdaptiveScheme/Adapt_MZI.py +++ b/quanestimation/AdaptiveScheme/Adapt_MZI.py @@ -17,8 +17,8 @@ class Adapt_MZI: -- Initial state (density matrix). """ - def __init__(self, x, p, rho0): + def __init__(self, x, p, rho0): self.x = x self.p = p self.rho0 = rho0 @@ -33,18 +33,16 @@ 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. + -- 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. + -- 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 - ) + phi = QJL.adaptMZI_online(self.x, self.p, self.rho0, output, target) def offline( self, @@ -65,13 +63,13 @@ def offline( Parameters ---------- > **target:** `string` - -- Setting the target function for calculating the tunable phase. Options are: - "sharpness" (default) -- Sharpness. - "MI" -- Mutual information. + -- 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. + -- 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: @@ -83,7 +81,7 @@ def offline( > **max_episode:** `int` -- The number of episodes. - + > **c:** `float` -- Mutation constant. @@ -95,28 +93,28 @@ def offline( > **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 + -- 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 + 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 + -- 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 + -- The exploitation weight that attracts the particle to the best position in the neighborhood, also known as social learning factor. > **eps:** `float` @@ -127,7 +125,7 @@ def offline( 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, diff --git a/quanestimation/AsymptoticBound/AnalogCramerRao.py b/quanestimation/AsymptoticBound/AnalogCramerRao.py index 74f901da..b6f7d29b 100644 --- a/quanestimation/AsymptoticBound/AnalogCramerRao.py +++ b/quanestimation/AsymptoticBound/AnalogCramerRao.py @@ -13,31 +13,31 @@ def HCRB(rho, drho, W, eps=1e-8): The HCRB is defined as: $$ - \min_{\{X_i\}} \left\{ \mathrm{Tr}(\mathrm{Re}Z) + \mathrm{Tr}(| \mathrm{Im} Z |) \right\}, + \min_{\{X_i\}} \left\{ \mathrm{Tr}(\mathrm{Re}Z) + \mathrm{Tr}(| \mathrm{Im} Z |) \right\}, $$ where $Z_{ij} = \mathrm{Tr}(\rho X_i X_j)$ and $V$ is the covariance matrix. Args: - rho (np.array): + rho (np.array): Density matrix. - drho (list): - Derivatives of the density matrix with respect to unknown parameters. + drho (list): + Derivatives of the density matrix with respect to unknown parameters. For example, `drho[0]` is the derivative with respect to the first parameter. - W (np.array): + W (np.array): Weight matrix for the bound. - eps (float, optional): + eps (float, optional): Machine epsilon for numerical stability. - Returns: - (float): + Returns: + (float): The value of the Holevo Cramer-Rao bound. Raises: TypeError: If `drho` is not a list. Notes: - In the single-parameter scenario, the HCRB is equivalent to the QFI. + In the single-parameter scenario, the HCRB is equivalent to the QFI. For a rank-one weight matrix, the HCRB is equivalent to the inverse of the QFIM. """ @@ -50,7 +50,7 @@ def HCRB(rho, drho, W, eps=1e-8): "Returning QFI value." ) return QFIM(rho, drho, eps=eps) - + if matrix_rank(W) == 1: print( "For rank-one weight matrix, HCRB is equivalent to QFIM. " @@ -70,8 +70,7 @@ def HCRB(rho, drho, W, eps=1e-8): vec_drho = [] for param_idx in range(num_params): components = [ - np.real(np.trace(drho[param_idx] @ basis_mat)) - for basis_mat in basis + np.real(np.trace(drho[param_idx] @ basis_mat)) for basis_mat in basis ] vec_drho.append(np.array(components)) @@ -91,12 +90,7 @@ def HCRB(rho, drho, W, eps=1e-8): X = cp.Variable((num, num_params)) # Define constraints - constraints = [ - cp.bmat([ - [V, X.T @ R.conj().T], - [R @ X, np.identity(num)] - ]) >> 0 - ] + constraints = [cp.bmat([[V, X.T @ R.conj().T], [R @ X, np.identity(num)]]) >> 0] # Add linear constraints for i in range(num_params): @@ -113,6 +107,7 @@ def HCRB(rho, drho, W, eps=1e-8): return problem.value + def NHB(rho, drho, W): r""" Calculation of the Nagaoka-Hayashi bound (NHB) via the semidefinite program (SDP). @@ -120,58 +115,55 @@ def NHB(rho, drho, W): The NHB is defined as: $$ - \min_{X} \left\{ \mathrm{Tr}[W \mathrm{Re}(Z)] + \|\sqrt{W} \mathrm{Im}(Z) \sqrt{W}\|_1 \right\}, + \min_{X} \left\{ \mathrm{Tr}[W \mathrm{Re}(Z)] + \|\sqrt{W} \mathrm{Im}(Z) \sqrt{W}\|_1 \right\}, $$ - + where $Z_{ij} = \mathrm{Tr}(\rho X_i X_j)$ and $V$ is the covariance matrix. Args: - rho (np.array): + rho (np.array): Density matrix. - drho (list): - Derivatives of the density matrix with respect to unknown parameters. + drho (list): + Derivatives of the density matrix with respect to unknown parameters. For example, `drho[0]` is the derivative with respect to the first parameter. - W (np.array): + W (np.array): Weight matrix for the bound. - Returns: - (float): + Returns: + (float): The value of the Nagaoka-Hayashi bound. Raises: - TypeError: + TypeError: If `drho` is not a list. """ if not isinstance(drho, list): raise TypeError("drho must be a list of derivative matrices") - + dim = len(rho) num_params = len(drho) - + # Initialize a temporary matrix for L components L_temp = [[None for _ in range(num_params)] for _ in range(num_params)] - + # Create Hermitian variables for the upper triangle and mirror to lower triangle for i in range(num_params): for j in range(i, num_params): L_temp[i][j] = cp.Variable((dim, dim), hermitian=True) if i != j: L_temp[j][i] = L_temp[i][j] - + # Construct the block matrix L L_blocks = [cp.hstack(L_temp[i]) for i in range(num_params)] L = cp.vstack(L_blocks) - + # Create Hermitian variables for X X = [cp.Variable((dim, dim), hermitian=True) for _ in range(num_params)] - + # Construct the block matrix constraint - block_matrix = cp.bmat([ - [L, cp.vstack(X)], - [cp.hstack(X), np.identity(dim)] - ]) + block_matrix = cp.bmat([[L, cp.vstack(X)], [cp.hstack(X), np.identity(dim)]]) constraints = [block_matrix >> 0] - + # Add trace constraints for i in range(num_params): constraints.append(cp.trace(X[i] @ rho) == 0) @@ -180,7 +172,7 @@ def NHB(rho, drho, W): constraints.append(cp.trace(X[i] @ drho[j]) == 1) else: constraints.append(cp.trace(X[i] @ drho[j]) == 0) - + # Define and solve the optimization problem objective = cp.Minimize(cp.real(cp.trace(cp.kron(W, rho) @ L))) prob = cp.Problem(objective, constraints) diff --git a/quanestimation/AsymptoticBound/CramerRao.py b/quanestimation/AsymptoticBound/CramerRao.py index 7fe7a6a0..88d04b3f 100755 --- a/quanestimation/AsymptoticBound/CramerRao.py +++ b/quanestimation/AsymptoticBound/CramerRao.py @@ -5,11 +5,12 @@ from scipy.integrate import quad from scipy.stats import norm, poisson, rayleigh, gamma + def CFIM(rho, drho, M=[], eps=1e-8): r""" Calculation of the classical Fisher information matrix for the chosen measurements. - This function computes the classical Fisher information (CFI) and classical Fisher + This function computes the classical Fisher information (CFI) and classical Fisher information matrix (CFIM) for a density matrix. The entry of CFIM $\mathcal{I}$ is defined as @@ -17,41 +18,41 @@ def CFIM(rho, drho, M=[], eps=1e-8): \mathcal{I}_{ab}=\sum_y\frac{1}{p(y|\textbf{x})}[\partial_a p(y|\textbf{x})][\partial_b p(y|\textbf{x})], $$ - Symbols: + Symbols: - $p(y|\textbf{x})=\mathrm{Tr}(\rho\Pi_y)$. - $\rho$: the parameterized density matrix. - Args: - rho (np.array): + Args: + rho (np.array): Density matrix. - drho (list): - List of derivative matrices of the density matrix on the unknown - parameters to be estimated. For example, drho[0] is the derivative + drho (list): + List of derivative matrices of the density matrix on the unknown + parameters to be estimated. For example, drho[0] is the derivative matrix on the first parameter. - M (list, optional): - List of positive operator-valued measure (POVM). The default + M (list, optional): + List of positive operator-valued measure (POVM). The default measurement is a set of rank-one symmetric informationally complete POVM (SIC-POVM). - eps (float, optional): + eps (float, optional): Machine epsilon for numerical stability. Returns: - (float/np.array): - For single parameter estimation (the length of drho is equal to one), the output is CFI + (float/np.array): + For single parameter estimation (the length of drho is equal to one), the output is CFI and for multiparameter estimation (the length of drho is more than one), it returns CFIM. Raises: TypeError: If drho is not a list. - TypeError: If M is not a list. + TypeError: If M is not a list. Example: rho = np.array([[0.5, 0], [0, 0.5]]); drho = [np.array([[1, 0], [0, -1]])]; - cfim = CFIM(rho, drho); - - Notes: - SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state + cfim = CFIM(rho, drho); + + Notes: + SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from [here](https://www.physics.umb.edu/Research/QBism/solutions.html). """ @@ -67,23 +68,23 @@ def CFIM(rho, drho, M=[], eps=1e-8): num_measurements = len(M) num_params = len(drho) cfim_res = np.zeros([num_params, num_params]) - + for i in range(num_measurements): povm_element = M[i] p = np.real(np.trace(rho @ povm_element)) c_add = np.zeros([num_params, num_params]) - + if p > eps: for param_i in range(num_params): drho_i = drho[param_i] dp_i = np.real(np.trace(drho_i @ povm_element)) - + for param_j in range(param_i, num_params): drho_j = drho[param_j] dp_j = np.real(np.trace(drho_j @ povm_element)) c_add[param_i][param_j] = np.real(dp_i * dp_j / p) c_add[param_j][param_i] = np.real(dp_i * dp_j / p) - + cfim_res += c_add if num_params == 1: @@ -96,49 +97,49 @@ def FIM(p, dp, eps=1e-8): r""" Calculation of the classical Fisher information matrix (CFIM) for a given probability distributions. - This function computes the classical Fisher information matrix (CFIM) for a given probability + This function computes the classical Fisher information matrix (CFIM) for a given probability distributions. The entry of FIM $I$ is defined as $$ I_{ab}=\sum_{y}\frac{1}{p_y}[\partial_a p_y][\partial_b p_y], $$ - Symbols: + Symbols: - $\{p_y\}$: a set of the discrete probability distribution. - Args: - p (np.array): + Args: + p (np.array): The probability distribution. - dp (list): - Derivatives of the probability distribution on the unknown parameters to + dp (list): + Derivatives of the probability distribution on the unknown parameters to be estimated. For example, dp[0] is the derivative vector on the first parameter. - eps (float, optional): + eps (float, optional): Machine epsilon. Returns: - (float/np.array): - For single parameter estimation (the length of drho is equal to one), the output is CFI + (float/np.array): + For single parameter estimation (the length of drho is equal to one), the output is CFI and for multiparameter estimation (the length of drho is more than one), it returns CFIM. """ num_params = len(dp) num_measurements = len(p) fim_matrix = np.zeros([num_params, num_params]) - + for outcome_idx in range(num_measurements): p_value = p[outcome_idx] fim_add = np.zeros([num_params, num_params]) - + if p_value > eps: for param_i in range(num_params): dp_i = dp[param_i][outcome_idx] - + for param_j in range(param_i, num_params): dp_j = dp[param_j][outcome_idx] term = np.real(dp_i * dp_j / p_value) fim_add[param_i][param_j] = term fim_add[param_j][param_i] = term - + fim_matrix += fim_add if num_params == 1: @@ -146,31 +147,32 @@ def FIM(p, dp, eps=1e-8): else: return fim_matrix + def FI_Expt(data_true, data_shifted, delta_x, ftype="norm"): """ Calculate the classical Fisher information (CFI) based on experimental data. Args: - data_true (np.array): + data_true (np.array): Experimental data obtained at the true parameter value. - data_shifted (np.array): + data_shifted (np.array): Experimental data obtained at parameter value shifted by delta_x. - delta_x (float): + delta_x (float): Small known parameter shift. - ftype (str, optional): - Probability distribution of the data. Options: - - "norm": normal distribution (default). - - "gamma": gamma distribution. - - "rayleigh": Rayleigh distribution. - - "poisson": Poisson distribution. - - Returns: - (float): + ftype (str, optional): + Probability distribution of the data. Options: + - "norm": normal distribution (default). + - "gamma": gamma distribution. + - "rayleigh": Rayleigh distribution. + - "poisson": Poisson distribution. + + Returns: + (float): Classical Fisher information Raises: - ValueError: - If `ftype` is not one of the supported types ("norm", "poisson", "gamma", "rayleigh"). + ValueError: + If `ftype` is not one of the supported types ("norm", "poisson", "gamma", "rayleigh"). Notes: The current implementation may be unstable and is subject to future modification. @@ -183,25 +185,25 @@ def FI_Expt(data_true, data_shifted, delta_x, ftype="norm"): norm.pdf(x, mu_true, std_true) * norm.pdf(x, mu_shifted, std_shifted) ) fidelity, _ = quad(f_function, -np.inf, np.inf) - + elif ftype == "gamma": a_true, alpha_true, beta_true = gamma.fit(data_true) a_shifted, alpha_shifted, beta_shifted = gamma.fit(data_shifted) f_function = lambda x: np.sqrt( - gamma.pdf(x, a_true, alpha_true, beta_true) * - gamma.pdf(x, a_shifted, alpha_shifted, beta_shifted) + gamma.pdf(x, a_true, alpha_true, beta_true) + * gamma.pdf(x, a_shifted, alpha_shifted, beta_shifted) ) - fidelity, _ = quad(f_function, 0., np.inf) - + fidelity, _ = quad(f_function, 0.0, np.inf) + elif ftype == "rayleigh": mean_true, var_true = rayleigh.fit(data_true) mean_shifted, var_shifted = rayleigh.fit(data_shifted) f_function = lambda x: np.sqrt( - rayleigh.pdf(x, mean_true, var_true) * - rayleigh.pdf(x, mean_shifted, var_shifted) + rayleigh.pdf(x, mean_true, var_true) + * rayleigh.pdf(x, mean_shifted, var_shifted) ) fidelity, _ = quad(f_function, -np.inf, np.inf) - + elif ftype == "poisson": k_max = max(max(data_true) + 1, max(data_shifted) + 1) k_values = np.arange(k_max) @@ -210,14 +212,14 @@ def FI_Expt(data_true, data_shifted, delta_x, ftype="norm"): p_true /= np.sum(p_true) p_shifted /= np.sum(p_shifted) fidelity = np.sum(np.sqrt(p_true * p_shifted)) - + else: valid_types = ["norm", "poisson", "gamma", "rayleigh"] raise ValueError( f"Invalid distribution type: '{ftype}'. " f"Supported types are: {', '.join(valid_types)}" ) - + fisher_information = 8 * (1 - fidelity) / delta_x**2 return fisher_information @@ -232,7 +234,7 @@ def SLD(rho, drho, rep="original", eps=1e-8): \partial_{a}\rho=\frac{1}{2}(\rho L_{a}+L_{a}\rho) $$ - with $\rho$ the parameterized density matrix. The entries of SLD can be calculated as + with $\rho$ the parameterized density matrix. The entries of SLD can be calculated as $$ \langle\lambda_i|L_{a}|\lambda_j\rangle=\frac{2\langle\lambda_i| \partial_{a}\rho |\lambda_j\rangle}{\lambda_i+\lambda_j @@ -241,26 +243,26 @@ def SLD(rho, drho, rep="original", eps=1e-8): for $\lambda_i~(\lambda_j) \neq 0$. If $\lambda_i=\lambda_j=0$, the entry of SLD is set to be zero. Args: - rho (np.array): + rho (np.array): Density matrix. - drho (list): - Derivatives of the density matrix on the unknown parameters to be + drho (list): + Derivatives of the density matrix on the unknown parameters to be estimated. For example, drho[0] is the derivative vector on the first parameter. - rep (str, optional): - The basis for the SLDs. Options: - - "original" (default): basis same as input density matrix + rep (str, optional): + The basis for the SLDs. Options: + - "original" (default): basis same as input density matrix - "eigen": basis same as eigenspace of density matrix - eps (float, optional): + eps (float, optional): Machine epsilon. Returns: - (np.array/list): - For single parameter estimation (i.e., length of `drho` equals 1), returns a matrix. + (np.array/list): + For single parameter estimation (i.e., length of `drho` equals 1), returns a matrix. For multiparameter estimation (i.e., length of `drho` is larger than 1), returns a list of matrices. Raises: - TypeError: If `drho` is not a list. - ValueError: If `rep` has invalid value. + TypeError: If `drho` is not a list. + ValueError: If `rep` has invalid value. """ if not isinstance(drho, list): @@ -275,7 +277,7 @@ def SLD(rho, drho, rep="original", eps=1e-8): # Handle pure state case if np.abs(1 - purity) < eps: sld_original = [2 * d for d in drho] - + for i in range(num_params): if rep == "original": slds[i] = sld_original[i] @@ -285,27 +287,33 @@ def SLD(rho, drho, rep="original", eps=1e-8): slds[i] = eigenvectors.conj().T @ sld_original[i] @ eigenvectors else: valid_reps = ["original", "eigen"] - raise ValueError(f"Invalid rep value: '{rep}'. Valid options: {valid_reps}") - + raise ValueError( + f"Invalid rep value: '{rep}'. Valid options: {valid_reps}" + ) + return slds[0] if num_params == 1 else slds # Handle mixed state case eigenvalues, eigenvectors = np.linalg.eig(rho) eigenvalues = np.real(eigenvalues) - + for param_idx in range(num_params): sld_eigenbasis = np.zeros((dim, dim), dtype=np.complex128) - + for i in range(dim): for j in range(dim): if eigenvalues[i] + eigenvalues[j] > eps: # Calculate matrix element in eigenbasis - numerator = 2 * (eigenvectors[:, i].conj().T @ drho[param_idx] @ eigenvectors[:, j]) + numerator = 2 * ( + eigenvectors[:, i].conj().T + @ drho[param_idx] + @ eigenvectors[:, j] + ) sld_eigenbasis[i, j] = numerator / (eigenvalues[i] + eigenvalues[j]) - + # Handle any potential infinities sld_eigenbasis[np.isinf(sld_eigenbasis)] = 0.0 - + # Transform to requested basis if rep == "original": slds[param_idx] = eigenvectors @ sld_eigenbasis @ eigenvectors.conj().T @@ -314,7 +322,7 @@ def SLD(rho, drho, rep="original", eps=1e-8): else: valid_reps = ["original", "eigen"] raise ValueError(f"Invalid rep value: '{rep}'. Valid options: {valid_reps}") - + return slds[0] if num_params == 1 else slds @@ -327,38 +335,38 @@ def RLD(rho, drho, rep="original", eps=1e-8): \partial_{a}\rho=\rho \mathcal{R}_a $$ - with $\rho$ the parameterized density matrix. The entries of RLD can be calculated as + with $\rho$ the parameterized density matrix. The entries of RLD can be calculated as $$ - \langle\lambda_i| \mathcal{R}_{a} |\lambda_j\rangle=\frac{1}{\lambda_i}\langle\lambda_i| - \partial_a\rho |\lambda_j\rangle + \langle\lambda_i| \mathcal{R}_{a} |\lambda_j\rangle=\frac{1}{\lambda_i}\langle\lambda_i| + \partial_a\rho |\lambda_j\rangle $$ for $\lambda_i\neq 0$. Args: - rho (np.array): - Density matrix. - drho (list): - Derivatives of the density matrix on the unknown parameters to be + rho (np.array): + Density matrix. + drho (list): + Derivatives of the density matrix on the unknown parameters to be estimated. For example, drho[0] is the derivative vector on the first parameter. - rep (str, optional): - The basis for the RLD(s). Options: - - "original" (default): basis same as input density matrix. + rep (str, optional): + The basis for the RLD(s). Options: + - "original" (default): basis same as input density matrix. - "eigen": basis same as eigenspace of density matrix. - eps (float, optional): + eps (float, optional): Machine epsilon. Returns: - (np.array/list): - For single parameter estimation (i.e., length of `drho` equals 1), returns a matrix. + (np.array/list): + For single parameter estimation (i.e., length of `drho` equals 1), returns a matrix. For multiparameter estimation (i.e., length of `drho` is larger than 1), returns a list of matrices. Raises: TypeError: If `drho` is not a list. ValueError: If `rep` has invalid value or RLD doesn't exist. """ - + if not isinstance(drho, list): raise TypeError("drho must be a list of derivative matrices") @@ -368,19 +376,17 @@ def RLD(rho, drho, rep="original", eps=1e-8): eigenvalues, eigenvectors = np.linalg.eig(rho) eigenvalues = np.real(eigenvalues) - + for param_idx in range(num_params): rld_eigenbasis = np.zeros((dim, dim), dtype=np.complex128) - + for i in range(dim): for j in range(dim): # Calculate matrix element in eigenbasis element = ( - eigenvectors[:, i].conj().T - @ drho[param_idx] - @ eigenvectors[:, j] + eigenvectors[:, i].conj().T @ drho[param_idx] @ eigenvectors[:, j] ) - + if np.abs(eigenvalues[i]) > eps: rld_eigenbasis[i, j] = element / eigenvalues[i] else: @@ -389,17 +395,13 @@ def RLD(rho, drho, rep="original", eps=1e-8): "RLD does not exist. It only exists when the support of " "drho is contained in the support of rho." ) - + # Handle any potential infinities rld_eigenbasis[np.isinf(rld_eigenbasis)] = 0.0 - + # Transform to requested basis if rep == "original": - rld_list[param_idx] = ( - eigenvectors - @ rld_eigenbasis - @ eigenvectors.conj().T - ) + rld_list[param_idx] = eigenvectors @ rld_eigenbasis @ eigenvectors.conj().T elif rep == "eigen": rld_list[param_idx] = rld_eigenbasis else: @@ -407,7 +409,7 @@ def RLD(rho, drho, rep="original", eps=1e-8): raise ValueError( f"Invalid rep value: '{rep}'. Valid options: {', '.join(valid_reps)}" ) - + return rld_list[0] if num_params == 1 else rld_list @@ -421,36 +423,36 @@ def LLD(rho, drho, rep="original", eps=1e-8): \partial_{a}\rho=\mathcal{R}_a^{\dagger}\rho. $$ - The entries of LLD can be calculated as + The entries of LLD can be calculated as $$ - \langle\lambda_i| \mathcal{R}_{a}^{\dagger} |\lambda_j\rangle=\frac{1}{\lambda_j}\langle\lambda_i| - \partial_a\rho |\lambda_j\rangle + \langle\lambda_i| \mathcal{R}_{a}^{\dagger} |\lambda_j\rangle=\frac{1}{\lambda_j}\langle\lambda_i| + \partial_a\rho |\lambda_j\rangle $$ for $\lambda_j\neq 0$. - Args: - rho (np.array): + Args: + rho (np.array): Density matrix. - drho (list): - Derivatives of the density matrix on the unknown parameters to be estimated. + drho (list): + Derivatives of the density matrix on the unknown parameters to be estimated. For example, drho[0] is the derivative vector on the first parameter. - rep (str, optional): - The basis for the LLD(s). Options: - - "original" (default): basis same as input density matrix. + rep (str, optional): + The basis for the LLD(s). Options: + - "original" (default): basis same as input density matrix. - "eigen": basis same as eigenspace of density matrix. - eps (float, optional): + eps (float, optional): Machine epsilon. Returns: - (np.array/list): - For single parameter estimation (i.e., length of `drho` equals 1), returns a matrix. + (np.array/list): + For single parameter estimation (i.e., length of `drho` equals 1), returns a matrix. For multiparameter estimation (i.e., length of `drho` is larger than 1), returns a list of matrices. Raises: - TypeError: If `drho` is not a list. - ValueError: If `rep` has invalid value or LLD doesn't exist. + TypeError: If `drho` is not a list. + ValueError: If `rep` has invalid value or LLD doesn't exist. """ if not isinstance(drho, list): @@ -462,19 +464,17 @@ def LLD(rho, drho, rep="original", eps=1e-8): eigenvalues, eigenvectors = np.linalg.eig(rho) eigenvalues = np.real(eigenvalues) - + for param_idx in range(param_num): lld_eigenbasis = np.zeros((dim, dim), dtype=np.complex128) - + for i in range(dim): for j in range(dim): # Calculate matrix element in eigenbasis element = ( - eigenvectors[:, i].conj().T - @ drho[param_idx] - @ eigenvectors[:, j] + eigenvectors[:, i].conj().T @ drho[param_idx] @ eigenvectors[:, j] ) - + if np.abs(eigenvalues[j]) > eps: lld_eigenbasis[i, j] = element / eigenvalues[j] else: @@ -483,17 +483,13 @@ def LLD(rho, drho, rep="original", eps=1e-8): "LLD does not exist. It only exists when the support of " "drho is contained in the support of rho." ) - + # Handle any potential infinities lld_eigenbasis[np.isinf(lld_eigenbasis)] = 0.0 - + # Transform to requested basis if rep == "original": - lld_list[param_idx] = ( - eigenvectors - @ lld_eigenbasis - @ eigenvectors.conj().T - ) + lld_list[param_idx] = eigenvectors @ lld_eigenbasis @ eigenvectors.conj().T elif rep == "eigen": lld_list[param_idx] = lld_eigenbasis else: @@ -501,13 +497,13 @@ def LLD(rho, drho, rep="original", eps=1e-8): raise ValueError( f"Invalid rep value: '{rep}'. Valid options: {', '.join(valid_reps)}" ) - + return lld_list[0] if param_num == 1 else lld_list def QFIM(rho, drho, LDtype="SLD", exportLD=False, eps=1e-8): r""" - Calculate the quantum Fisher information (QFI) and quantum Fisher + Calculate the quantum Fisher information (QFI) and quantum Fisher information matrix (QFIM) for all types. The entry of QFIM $\mathcal{F}$ is defined as: @@ -527,32 +523,32 @@ def QFIM(rho, drho, LDtype="SLD", exportLD=False, eps=1e-8): with $\mathcal{R}_a$ being the RLD or LLD operator. Args: - rho (np.array): + rho (np.array): Density matrix. - drho (list): - Derivatives of the density matrix with respect to the unknown parameters. - Each element in the list is a matrix of the same dimension as `rho` and - represents the partial derivative of the density matrix with respect to - one parameter. For example, `drho[0]` is the derivative with respect to + drho (list): + Derivatives of the density matrix with respect to the unknown parameters. + Each element in the list is a matrix of the same dimension as `rho` and + represents the partial derivative of the density matrix with respect to + one parameter. For example, `drho[0]` is the derivative with respect to the first parameter. - LDtype (str, optional): - Specifies the type of logarithmic derivative to use for QFI/QFIM calculation: - - "SLD": Symmetric Logarithmic Derivative (default). - - "RLD": Right Logarithmic Derivative. - - "LLD": Left Logarithmic Derivative. - exportLD (bool, optional): - Whether to export the values of logarithmic derivatives. - eps (float, optional): - Machine epsilon. + LDtype (str, optional): + Specifies the type of logarithmic derivative to use for QFI/QFIM calculation: + - "SLD": Symmetric Logarithmic Derivative (default). + - "RLD": Right Logarithmic Derivative. + - "LLD": Left Logarithmic Derivative. + exportLD (bool, optional): + Whether to export the values of logarithmic derivatives. + eps (float, optional): + Machine epsilon. Returns: - (float/np.array): - For single parameter estimation (i.e., length of `drho` equals 1), returns QFI. - For multiparameter estimation (i.e., length of `drho` is larger than 1), returns QFIM. + (float/np.array): + For single parameter estimation (i.e., length of `drho` equals 1), returns QFI. + For multiparameter estimation (i.e., length of `drho` is larger than 1), returns QFIM. Raises: TypeError: If `drho` is not a list. - ValueError: If `LDtype` is not one of the supported types ("SLD", "RLD", "LLD"). + ValueError: If `LDtype` is not one of the supported types ("SLD", "RLD", "LLD"). """ if not isinstance(drho, list): @@ -588,7 +584,9 @@ def QFIM(rho, drho, LDtype="SLD", exportLD=False, eps=1e-8): sld_list = SLD(rho, drho, eps=eps) for i in range(num_params): for j in range(i, num_params): - anticommutator = sld_list[i] @ sld_list[j] + sld_list[j] @ sld_list[i] + anticommutator = ( + sld_list[i] @ sld_list[j] + sld_list[j] @ sld_list[i] + ) qfim_result[i, j] = np.real(0.5 * np.trace(rho @ anticommutator)) qfim_result[j, i] = qfim_result[i, j] log_derivatives = sld_list @@ -626,62 +624,59 @@ def QFIM(rho, drho, LDtype="SLD", exportLD=False, eps=1e-8): def QFIM_Kraus(rho0, K, dK, LDtype="SLD", exportLD=False, eps=1e-8): r""" - Calculation of the quantum Fisher information (QFI) and quantum Fisher + Calculation of the quantum Fisher information (QFI) and quantum Fisher information matrix (QFIM) for a quantum channel described by Kraus operators. The quantum channel is given by - + $$ \rho=\sum_{i} K_i \rho_0 K_i^{\dagger}, $$ where $\rho_0$ is the initial state and $\{K_i\}$ are the Kraus operators. - The derivatives of the density matrix $\partial_a\rho$ are calculated from the + The derivatives of the density matrix $\partial_a\rho$ are calculated from the derivatives of the Kraus operators $\{\partial_a K_i\}$ as - + $$ \partial_a\rho=\sum_{i}\left[(\partial_a K_i)\rho_0 K_i^{\dagger}+K_i\rho_0(\partial_a K_i)^{\dagger}\right]. $$ - Then the QFI (QFIM) is calculated via the function `QFIM` with the evolved state + Then the QFI (QFIM) is calculated via the function `QFIM` with the evolved state $\rho$ and its derivatives $\{\partial_a\rho\}$. Args: - rho0 (np.array): + rho0 (np.array): Initial density matrix. - K (list): + K (list): Kraus operators. - dK (list): - Derivatives of the Kraus operators. It is a nested list where the first index - corresponds to the parameter and the second index corresponds to the Kraus operator index. - For example, `dK[0][1]` is the derivative of the second Kraus operator with respect + dK (list): + Derivatives of the Kraus operators. It is a nested list where the first index + corresponds to the parameter and the second index corresponds to the Kraus operator index. + For example, `dK[0][1]` is the derivative of the second Kraus operator with respect to the first parameter. - LDtype (str, optional): - Types of QFI (QFIM) can be set as the objective function. Options: - - "SLD" (default): QFI (QFIM) based on symmetric logarithmic derivative. - - "RLD": QFI (QFIM) based on right logarithmic derivative. - - "LLD": QFI (QFIM) based on left logarithmic derivative. - exportLD (bool, optional): - Whether to export the values of logarithmic derivatives. - eps (float, optional): - Machine epsilon. + LDtype (str, optional): + Types of QFI (QFIM) can be set as the objective function. Options: + - "SLD" (default): QFI (QFIM) based on symmetric logarithmic derivative. + - "RLD": QFI (QFIM) based on right logarithmic derivative. + - "LLD": QFI (QFIM) based on left logarithmic derivative. + exportLD (bool, optional): + Whether to export the values of logarithmic derivatives. + eps (float, optional): + Machine epsilon. Returns: - (float/np.array): - For single parameter estimation (the length of dK is equal to one), the output is QFI + (float/np.array): + For single parameter estimation (the length of dK is equal to one), the output is QFI and for multiparameter estimation (the length of dK is more than one), it returns QFIM. """ # Transpose dK: from [parameters][operators] to [operators][parameters] - dK_transposed = [ - [dK[i][j] for i in range(len(K))] - for j in range(len(dK[0])) - ] - + dK_transposed = [[dK[i][j] for i in range(len(K))] for j in range(len(dK[0]))] + # Compute the evolved density matrix rho = sum(Ki @ rho0 @ Ki.conj().T for Ki in K) - + # Compute the derivatives of the density matrix drho = [ sum( @@ -690,44 +685,44 @@ def QFIM_Kraus(rho0, K, dK, LDtype="SLD", exportLD=False, eps=1e-8): ) for dKj in dK_transposed ] - + return QFIM(rho, drho, LDtype=LDtype, exportLD=exportLD, eps=eps) def QFIM_Bloch(r, dr, eps=1e-8): r""" - Calculation of the quantum Fisher information (QFI) and quantum Fisher + Calculation of the quantum Fisher information (QFI) and quantum Fisher information matrix (QFIM) in Bloch representation. The Bloch vector representation of a quantum state is defined as - + $$ \rho = \frac{1}{d}\left(\mathbb{I} + \sum_{i=1}^{d^2-1} r_i \lambda_i\right), $$ - + where $\lambda_i$ are the generators of SU(d) group. Args: - r (np.array): + r (np.array): Parameterized Bloch vector. - dr (list): - Derivatives of the Bloch vector with respect to the unknown parameters. - Each element in the list is a vector of the same length as `r` and - represents the partial derivative of the Bloch vector with respect to - one parameter. For example, `dr[0]` is the derivative with respect to + dr (list): + Derivatives of the Bloch vector with respect to the unknown parameters. + Each element in the list is a vector of the same length as `r` and + represents the partial derivative of the Bloch vector with respect to + one parameter. For example, `dr[0]` is the derivative with respect to the first parameter. - eps (float, optional): - Machine epsilon. + eps (float, optional): + Machine epsilon. Returns: - (float/np.array): - For single parameter estimation (the length of `dr` is equal to one), - the output is QFI and for multiparameter estimation (the length of `dr` + (float/np.array): + For single parameter estimation (the length of `dr` is equal to one), + the output is QFI and for multiparameter estimation (the length of `dr` is more than one), it returns QFIM. Raises: - TypeError: If `dr` is not a list. - ValueError: If the dimension of the Bloch vector is invalid. + TypeError: If `dr` is not a list. + ValueError: If the dimension of the Bloch vector is invalid. """ if not isinstance(dr, list): @@ -749,7 +744,7 @@ def QFIM_Bloch(r, dr, eps=1e-8): # Handle single-qubit system if dim == 2: r_norm = np.linalg.norm(r) ** 2 - + # Pure state case if np.abs(r_norm - 1.0) < eps: for i in range(num_params): @@ -777,8 +772,8 @@ def QFIM_Bloch(r, dr, eps=1e-8): for i in range(dim**2 - 1): for j in range(i, dim**2 - 1): anticommutator = ( - lambda_generators[i] @ lambda_generators[j] + - lambda_generators[j] @ lambda_generators[i] + lambda_generators[i] @ lambda_generators[j] + + lambda_generators[j] @ lambda_generators[i] ) G[i, j] = 0.5 * np.trace(rho @ anticommutator) G[j, i] = G[i, j] @@ -801,34 +796,34 @@ def QFIM_Bloch(r, dr, eps=1e-8): def QFIM_Gauss(R, dR, D, dD): r""" - Calculation of the quantum Fisher information (QFI) and quantum + Calculation of the quantum Fisher information (QFI) and quantum Fisher information matrix (QFIM) for Gaussian states. - The Gaussian state is characterized by its first-order moment (displacement vector) - and second-order moment (covariance matrix). The QFIM is calculated using the + The Gaussian state is characterized by its first-order moment (displacement vector) + and second-order moment (covariance matrix). The QFIM is calculated using the method described in [1]. Args: - R (np.array): + R (np.array): First-order moment (displacement vector). - dR (list): - Derivatives of the first-order moment with respect to the unknown parameters. - Each element in the list is a vector of the same length as `R` and represents the partial - derivative of the displacement vector with respect to one parameter. For example, `dR[0]` + dR (list): + Derivatives of the first-order moment with respect to the unknown parameters. + Each element in the list is a vector of the same length as `R` and represents the partial + derivative of the displacement vector with respect to one parameter. For example, `dR[0]` is the derivative with respect to the first parameter. - D (np.array): + D (np.array): Second-order moment (covariance matrix). - dD (list): - Derivatives of the second-order moment with respect to the unknown parameters. - Each element in the list is a matrix of the same dimension as `D` and - represents the partial derivative of the covariance matrix with respect to - one parameter. For example, `dD[0]` is the derivative with respect to + dD (list): + Derivatives of the second-order moment with respect to the unknown parameters. + Each element in the list is a matrix of the same dimension as `D` and + represents the partial derivative of the covariance matrix with respect to + one parameter. For example, `dD[0]` is the derivative with respect to the first parameter. Returns: - (float/np.array): - For single parameter estimation (the length of `dR` is equal to one), - the output is QFI and for multiparameter estimation (the length of `dR` + (float/np.array): + For single parameter estimation (the length of `dR` is equal to one), + the output is QFI and for multiparameter estimation (the length of `dR` is more than one), it returns QFIM. Notes: @@ -842,10 +837,7 @@ def QFIM_Gauss(R, dR, D, dD): # Compute the covariance matrix from the second-order moments and displacement cov_matrix = np.array( - [ - [D[i][j] - R[i] * R[j] for j in range(2 * m)] - for i in range(2 * m) - ] + [[D[i][j] - R[i] * R[j] for j in range(2 * m)] for i in range(2 * m)] ) # Compute the derivatives of the covariance matrix @@ -925,7 +917,9 @@ def QFIM_Gauss(R, dR, D, dD): g.append(g_k) # Initialize the matrices G for each parameter - G_matrices = [np.zeros((2 * m, 2 * m), dtype=np.complex128) for _ in range(num_params)] + G_matrices = [ + np.zeros((2 * m, 2 * m), dtype=np.complex128) for _ in range(num_params) + ] # Construct the matrices G for each parameter for k in range(num_params): diff --git a/quanestimation/BayesianBound/BayesCramerRao.py b/quanestimation/BayesianBound/BayesCramerRao.py index 52008d64..4b3815ec 100644 --- a/quanestimation/BayesianBound/BayesCramerRao.py +++ b/quanestimation/BayesianBound/BayesCramerRao.py @@ -10,47 +10,47 @@ def BCFIM(x, p, rho, drho, M=[], eps=1e-8): r""" Calculation of the Bayesian classical Fisher information matrix (BCFIM). - This function computes the Bayesian classical Fisher information (BCFI) or Bayesian classical + This function computes the Bayesian classical Fisher information (BCFI) or Bayesian classical Fisher information matrix (BCFIM). The BCFIM is defined as: $$ \mathcal{I}_{\mathrm{Bayes}} = \int p(\textbf{x}) \mathcal{I} \, \mathrm{d}\textbf{x}. $$ - where $\mathcal{I}$ is the classical Fisher information matrix (CFIM) and $p(\textbf{x})$ + where $\mathcal{I}$ is the classical Fisher information matrix (CFIM) and $p(\textbf{x})$ is the prior distribution. Args: - x (list): - Parameter regimes for integration. Each element is an array + x (list): + Parameter regimes for integration. Each element is an array representing the values of one parameter. - p (np.array): - Prior distribution over the parameter space. Must have the same dimensions + p (np.array): + Prior distribution over the parameter space. Must have the same dimensions as the product of the lengths of the arrays in `x`. - rho (list): - Parameterized density matrices. Each element corresponds to + rho (list): + Parameterized density matrices. Each element corresponds to a point in the parameter space defined by `x`. - drho (list): - Derivatives of the density matrices with respect to the parameters. For single parameter estimation (length of `x` is 1), - `drho` is a list of derivatives at each parameter point. For multiparameter estimation, `drho` is a - multidimensional list where `drho[i]` is a list of derivatives with respect to each parameter at the i-th parameter point, + drho (list): + Derivatives of the density matrices with respect to the parameters. For single parameter estimation (length of `x` is 1), + `drho` is a list of derivatives at each parameter point. For multiparameter estimation, `drho` is a + multidimensional list where `drho[i]` is a list of derivatives with respect to each parameter at the i-th parameter point, and `drho[i][j]` is the derivative of the density matrix at the i-th parameter point with respect to the j-th parameter. - M (list, optional): + M (list, optional): Positive operator-valued measure (POVM). Default is a set of rank-one symmetric informationally complete POVM (SIC-POVM). - eps (float, optional): + eps (float, optional): Machine epsilon for numerical stability. Returns: - (float/np.array): - For single parameter estimation (length of `x` is 1), returns BCFI. + (float/np.array): + For single parameter estimation (length of `x` is 1), returns BCFI. For multiparameter estimation (length of `x` > 1), returns BCFIM. Raises: - TypeError: + TypeError: If `M` is provided but not a list. Notes: - SIC-POVM is calculated using Weyl-Heisenberg covariant SIC-POVM fiducial states + SIC-POVM is calculated using Weyl-Heisenberg covariant SIC-POVM fiducial states available at [http://www.physics.umb.edu/Research/QBism/solutions.html](http://www.physics.umb.edu/Research/QBism/solutions.html). """ @@ -122,35 +122,35 @@ def BQFIM(x, p, rho, drho, LDtype="SLD", eps=1e-8): r""" Calculation of the Bayesian quantum Fisher information matrix (BQFIM). - This function computes the Bayesian quantum Fisher information (BQFI) or Bayesian quantum + This function computes the Bayesian quantum Fisher information (BQFI) or Bayesian quantum Fisher information matrix (BQFIM). The BQFIM is defined as: $$ \mathcal{F}_{\mathrm{Bayes}} = \int p(\textbf{x}) \mathcal{F} \, \mathrm{d}\textbf{x}. $$ - where $\mathcal{F}$ is the quantum Fisher information matrix (QFIM) and $p(\textbf{x})$ + where $\mathcal{F}$ is the quantum Fisher information matrix (QFIM) and $p(\textbf{x})$ is the prior distribution. Args: - x (list): Parameter regimes for integration. Each element is an array + x (list): Parameter regimes for integration. Each element is an array representing the values of one parameter. - p (np.array): Prior distribution over the parameter space. Must have the same dimensions + p (np.array): Prior distribution over the parameter space. Must have the same dimensions as the product of the lengths of the arrays in `x`. - rho (list): Parameterized density matrices. Each element corresponds to + rho (list): Parameterized density matrices. Each element corresponds to a point in the parameter space defined by `x`. - drho (list): Derivatives of the density matrices with respect to - the parameters. `drho[i][j]` is the derivative of the density matrix at the i-th + drho (list): Derivatives of the density matrices with respect to + the parameters. `drho[i][j]` is the derivative of the density matrix at the i-th parameter point with respect to the j-th parameter. - LDtype (str, optional): Type of logarithmic derivative (default: "SLD"). Options: - - "SLD": Symmetric logarithmic derivative - - "RLD": Right logarithmic derivative - - "LLD": Left logarithmic derivative + LDtype (str, optional): Type of logarithmic derivative (default: "SLD"). Options: + - "SLD": Symmetric logarithmic derivative + - "RLD": Right logarithmic derivative + - "LLD": Left logarithmic derivative eps (float, optional): Machine epsilon for numerical stability. Returns: - (float/np.array): - For single parameter estimation (length of `x` is 1), returns BQFI. + (float/np.array): + For single parameter estimation (length of `x` is 1), returns BQFI. For multiparameter estimation (length of `x` > 1), returns BQFIM. """ @@ -220,19 +220,19 @@ def BCRB(x, p, dp, rho, drho, M=[], b=[], db=[], btype=1, eps=1e-8): **Type 1:** $$ - \mathrm{cov} \geq \int p(\textbf{x}) \left( B \mathcal{I}^{-1} B + \mathrm{cov} \geq \int p(\textbf{x}) \left( B \mathcal{I}^{-1} B + \textbf{b} \textbf{b}^{\mathrm{T}} \right) \mathrm{d}\textbf{x}. $$ **Type 2:** $$ - \mathrm{cov} \geq \mathcal{B} \mathcal{I}_{\mathrm{Bayes}}^{-1} \mathcal{B} + \mathrm{cov} \geq \mathcal{B} \mathcal{I}_{\mathrm{Bayes}}^{-1} \mathcal{B} + \int p(\textbf{x}) \textbf{b} \textbf{b}^{\mathrm{T}} \mathrm{d}\textbf{x}. $$ **Type 3:** $$ - \mathrm{cov} \geq \int p(\textbf{x}) \mathcal{G} \left( \mathcal{I}_p + \mathrm{cov} \geq \int p(\textbf{x}) \mathcal{G} \left( \mathcal{I}_p + \mathcal{I} \right)^{-1} \mathcal{G}^{\mathrm{T}} \mathrm{d}\textbf{x}. $$ @@ -247,35 +247,35 @@ def BCRB(x, p, dp, rho, drho, M=[], b=[], db=[], btype=1, eps=1e-8): - $\mathcal{G}_{ab} = [\partial_b \ln p(\textbf{x})][\textbf{b}]_a + B_{aa}\delta_{ab}$ Args: - x (list): + x (list): Parameter regimes for integration. - p (np.array): - Prior distribution over the parameter space. Must have the same dimensions + p (np.array): + Prior distribution over the parameter space. Must have the same dimensions as the product of the lengths of the arrays in `x`. - dp (list): + dp (list): Derivatives of the prior distribution with respect to the parameters. - rho (list): - Parameterized density matrices. Each element corresponds to + rho (list): + Parameterized density matrices. Each element corresponds to a point in the parameter space defined by `x`. - drho (list): - Derivatives of the density matrices with respect to - the parameters. `drho[i][j]` is the derivative of the density matrix at the i-th + drho (list): + Derivatives of the density matrices with respect to + the parameters. `drho[i][j]` is the derivative of the density matrix at the i-th parameter point with respect to the j-th parameter. - M (list, optional): - Positive operator-valued measure (POVM). Default is + M (list, optional): + Positive operator-valued measure (POVM). Default is a set of rank-one symmetric informationally complete POVM (SIC-POVM). - b (list, optional): + b (list, optional): Bias vector. Default is zero bias. - db (list, optional): + db (list, optional): Derivatives of the bias vector. Default is zero. - btype (int, optional): + btype (int, optional): Type of BCRB to calculate (1, 2, or 3). - eps (float, optional): + eps (float, optional): Machine epsilon for numerical stability. Returns: - (float/np.array): - For single parameter estimation (length of `x` is 1), returns BCRB. + (float/np.array): + For single parameter estimation (length of `x` is 1), returns BCRB. For multiparameter estimation (length of `x` > 1), returns BCRB matrix. Raises: @@ -283,7 +283,7 @@ def BCRB(x, p, dp, rho, drho, M=[], b=[], db=[], btype=1, eps=1e-8): NameError: If `btype` is not in {1, 2, 3}. Notes: - SIC-POVM is calculated using Weyl-Heisenberg covariant SIC-POVM fiducial states + SIC-POVM is calculated using Weyl-Heisenberg covariant SIC-POVM fiducial states available at [http://www.physics.umb.edu/Research/QBism/solutions.html](http://www.physics.umb.edu/Research/QBism/solutions.html). """ @@ -331,7 +331,10 @@ def BCRB(x, p, dp, rho, drho, M=[], b=[], db=[], btype=1, eps=1e-8): return F elif btype == 3: I_tp = [np.real(dp[i] * dp[i] / p[i] ** 2) for i in range(p_num)] - arr = [p[j]*(dp[j]*b[j]/p[j]+(1 + db[j]))**2 / (I_tp[j] + F_tp[j]) for j in range(p_num)] + arr = [ + p[j] * (dp[j] * b[j] / p[j] + (1 + db[j])) ** 2 / (I_tp[j] + F_tp[j]) + for j in range(p_num) + ] F = simpson(arr, x[0]) return F else: @@ -464,9 +467,11 @@ def BCRB(x, p, dp, rho, drho, M=[], b=[], db=[], btype=1, eps=1e-8): for pm in range(para_num): for pn in range(para_num): if pm == pn: - G_tp[pm][pn] = dp_list[i][pn]*b_list[i][pm]/p_list[i]+(1.0 + db_list[i][pm]) + G_tp[pm][pn] = dp_list[i][pn] * b_list[i][pm] / p_list[ + i + ] + (1.0 + db_list[i][pm]) else: - G_tp[pm][pn] = dp_list[i][pn]*b_list[i][pm]/p_list[i] + G_tp[pm][pn] = dp_list[i][pn] * b_list[i][pm] / p_list[i] I_tp[pm][pn] = dp_list[i][pm] * dp_list[i][pn] / p_list[i] ** 2 F_tot = G_tp @ np.linalg.pinv(F_tp + I_tp) @ G_tp.T @@ -490,10 +495,10 @@ def BCRB(x, p, dp, rho, drho, M=[], b=[], db=[], btype=1, eps=1e-8): def BQCRB(x, p, dp, rho, drho, b=[], db=[], btype=1, LDtype="SLD", eps=1e-8): r""" - Calculation of the Bayesian quantum Cramer-Rao bound (BQCRB). - + Calculation of the Bayesian quantum Cramer-Rao bound (BQCRB). + The covariance matrix with a prior distribution $p(\textbf{x})$ is defined as - + $$ \mathrm{cov}(\hat{\textbf{x}},\{\Pi_y\})=\int p(\textbf{x})\sum_y\mathrm{Tr} (\rho\Pi_y)(\hat{\textbf{x}}-\textbf{x})(\hat{\textbf{x}}-\textbf{x})^{\mathrm{T}} @@ -503,7 +508,7 @@ def BQCRB(x, p, dp, rho, drho, b=[], db=[], btype=1, LDtype="SLD", eps=1e-8): Symbols: - $\textbf{x}=(x_0,x_1,\dots)^{\mathrm{T}}$: the unknown parameters to be estimated and the integral $\int\mathrm{d}\textbf{x}:=\iiint\mathrm{d}x_0\mathrm{d}x_1\cdots$. - - $\{\Pi_y\}$: a set of positive operator-valued measure (POVM). + - $\{\Pi_y\}$: a set of positive operator-valued measure (POVM). - $\rho$: the parameterized density matrix. This function calculates three types of the BQCRB. The first one is @@ -512,8 +517,8 @@ def BQCRB(x, p, dp, rho, drho, b=[], db=[], btype=1, LDtype="SLD", eps=1e-8): \mathrm{cov}(\hat{\textbf{x}},\{\Pi_y\})\geq\int p(\textbf{x})\left(B\mathcal{F}^{-1}B +\textbf{b}\textbf{b}^{\mathrm{T}}\right)\mathrm{d}\textbf{x}, $$ - - Symbols: + + Symbols: - $\textbf{b}$ and $\textbf{b}'$: the vectors of biase and its derivatives on parameters. - $B$: a diagonal matrix with the $i$th entry $B_{ii}=1+[\textbf{b}']_{i}$ - $\mathcal{F}$: the QFIM for all types. @@ -525,52 +530,52 @@ def BQCRB(x, p, dp, rho, drho, b=[], db=[], btype=1, LDtype="SLD", eps=1e-8): \mathcal{B}+\int p(\textbf{x})\textbf{b}\textbf{b}^{\mathrm{T}}\mathrm{d}\textbf{x}, $$ - Symbols: - - $\mathcal{B}=\int p(\textbf{x})B\mathrm{d}\textbf{x}$: the average of $B$ + Symbols: + - $\mathcal{B}=\int p(\textbf{x})B\mathrm{d}\textbf{x}$: the average of $B$ - $\mathcal{F}_{\mathrm{Bayes}}=\int p(\textbf{x})\mathcal{F}\mathrm{d}\textbf{x}$: the average QFIM. The third one is - + $$ \mathrm{cov}(\hat{\textbf{x}},\{\Pi_y\})\geq \int p(\textbf{x}) \mathcal{G}\left(\mathcal{I}_p+\mathcal{F}\right)^{-1}\mathcal{G}^{\mathrm{T}}\mathrm{d}\textbf{x}. $$ - Symbols: + Symbols: - $[\mathcal{I}_{p}]_{ab}:=[\partial_a \ln p(\textbf{x})][\partial_b \ln p(\textbf{x})]$. - $\mathcal{G}_{ab}:=[\partial_b\ln p(\textbf{x})][\textbf{b}]_a+B_{aa}\delta_{ab}$. Args: - x (list): + x (list): The regimes of the parameters for the integral. - p (np.array, multidimensional): + p (np.array, multidimensional): The prior distribution. - rho (list, multidimensional): + rho (list, multidimensional): Parameterized density matrix. - drho (list, multidimensional): + drho (list, multidimensional): Derivatives of the parameterized density matrix (rho) with respect to the unknown parameters to be estimated. - b (list): + b (list): Vector of biases of the form $\textbf{b}=(b(x_0),b(x_1),\dots)^{\mathrm{T}}$. - db (list): - Derivatives of b with respect to the unknown parameters to be estimated, It should be + db (list): + Derivatives of b with respect to the unknown parameters to be estimated, It should be expressed as $\textbf{b}'=(\partial_0 b(x_0),\partial_1 b(x_1),\dots)^{\mathrm{T}}$. - btype (int): - Types of the BQCRB. Options are: - 1 (default) -- It means to calculate the first type of the BQCRB. + btype (int): + Types of the BQCRB. Options are: + 1 (default) -- It means to calculate the first type of the BQCRB. 2 -- It means to calculate the second type of the BQCRB. 3 -- It means to calculate the third type of the BCRB. - LDtype (str): - 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). + LDtype (str): + 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). - eps (float,optional): + eps (float,optional): Machine epsilon. Returns: - (float/np.array): - For single parameter estimation (the length of `x` equals to one), the - output is a float and for multiparameter estimation (the length of `x` is larger than one), + (float/np.array): + For single parameter estimation (the length of `x` equals to one), the + output is a float and for multiparameter estimation (the length of `x` is larger than one), it returns a matrix. """ @@ -614,7 +619,10 @@ def BQCRB(x, p, dp, rho, drho, b=[], db=[], btype=1, LDtype="SLD", eps=1e-8): return F elif btype == 3: I_tp = [np.real(dp[i] * dp[i] / p[i] ** 2) for i in range(p_num)] - arr = [p[j]*(dp[j]*b[j]/p[j]+(1 + db[j]))**2 / (I_tp[j] + F_tp[j]) for j in range(p_num)] + arr = [ + p[j] * (dp[j] * b[j] / p[j] + (1 + db[j])) ** 2 / (I_tp[j] + F_tp[j]) + for j in range(p_num) + ] F = simpson(arr, x[0]) return F else: @@ -741,9 +749,11 @@ def BQCRB(x, p, dp, rho, drho, b=[], db=[], btype=1, LDtype="SLD", eps=1e-8): for pm in range(para_num): for pn in range(para_num): if pm == pn: - G_tp[pm][pn] = dp_list[i][pn]*b_list[i][pm]/p_list[i]+(1.0 + db_list[i][pm]) + G_tp[pm][pn] = dp_list[i][pn] * b_list[i][pm] / p_list[ + i + ] + (1.0 + db_list[i][pm]) else: - G_tp[pm][pn] = dp_list[i][pn]*b_list[i][pm]/p_list[i] + G_tp[pm][pn] = dp_list[i][pn] * b_list[i][pm] / p_list[i] I_tp[pm][pn] = dp_list[i][pm] * dp_list[i][pn] / p_list[i] ** 2 F_tot = G_tp @ np.linalg.pinv(F_tp + I_tp) @ G_tp.T @@ -783,34 +793,34 @@ def VTB(x, p, dp, rho, drho, M=[], eps=1e-8): \mathrm{cov} \geq \left(\mathcal{I}_{\mathrm{prior}} + \mathcal{I}_{\mathrm{Bayes}}\right)^{-1}. $$ - Symbols: - - $\mathcal{I}_{\mathrm{prior}} = \int p(\textbf{x}) \mathcal{I}_{p} \, \mathrm{d}\textbf{x}$ - is the classical Fisher information matrix (CFIM) for the prior distribution $p(\textbf{x})$. - - $\mathcal{I}_{\mathrm{Bayes}} = \int p(\textbf{x}) \mathcal{I} \, \mathrm{d}\textbf{x}$ + Symbols: + - $\mathcal{I}_{\mathrm{prior}} = \int p(\textbf{x}) \mathcal{I}_{p} \, \mathrm{d}\textbf{x}$ + is the classical Fisher information matrix (CFIM) for the prior distribution $p(\textbf{x})$. + - $\mathcal{I}_{\mathrm{Bayes}} = \int p(\textbf{x}) \mathcal{I} \, \mathrm{d}\textbf{x}$ is the average CFIM over the prior. Args: - x (list): + x (list): Parameter regimes for integration. - p (np.array): + p (np.array): Prior distribution. - dp (list): + dp (list): Derivatives of the prior distribution with respect to the parameters. - rho (list): + rho (list): Parameterized density matrices. - drho (list): + drho (list): Derivatives of the density matrices with respect to the parameters. - M (list, optional): + M (list, optional): Positive operator-valued measure (POVM). Default is SIC-POVM. - eps (float, optional): + eps (float, optional): Machine epsilon. Returns: - (float/np.array): + (float/np.array): For single parameter: float. For multiple parameters: matrix. - Notes: - SIC-POVM uses Weyl-Heisenberg covariant fiducial states from + Notes: + SIC-POVM uses Weyl-Heisenberg covariant fiducial states from [http://www.physics.umb.edu/Research/QBism/solutions.html](http://www.physics.umb.edu/Research/QBism/solutions.html). """ @@ -860,23 +870,21 @@ def VTB(x, p, dp, rho, drho, M=[], eps=1e-8): else: if type(M) != list: raise TypeError("Please make sure M is a list!") - + F_list = [ - [[0.0 for i in range(len(p_list))] for j in range(para_num)] - for k in range(para_num) - ] + [[0.0 for i in range(len(p_list))] for j in range(para_num)] + for k in range(para_num) + ] I_list = [ - [[0.0 for i in range(len(p_list))] for j in range(para_num)] - for k in range(para_num) - ] + [[0.0 for i in range(len(p_list))] for j in range(para_num)] + for k in range(para_num) + ] for i in range(len(p_list)): F_tp = CFIM(rho_list[i], drho_list[i], M=M, eps=eps) for pj in range(para_num): for pk in range(para_num): F_list[pj][pk][i] = F_tp[pj][pk] - I_list[pj][pk][i] = ( - dp_list[i][pj] * dp_list[i][pk] / p_list[i] ** 2 - ) + I_list[pj][pk][i] = dp_list[i][pj] * dp_list[i][pk] / p_list[i] ** 2 F_res = np.zeros([para_num, para_num]) I_res = np.zeros([para_num, para_num]) @@ -895,6 +903,7 @@ def VTB(x, p, dp, rho, drho, M=[], eps=1e-8): I_res[para_j][para_i] = arr2 return np.linalg.pinv(F_res + I_res) + def QVTB(x, p, dp, rho, drho, LDtype="SLD", eps=1e-8): r""" Calculate the quantum Van Trees bound (QVTB), a Bayesian version of the quantum Cramer-Rao bound. @@ -914,32 +923,32 @@ def QVTB(x, p, dp, rho, drho, LDtype="SLD", eps=1e-8): $$ Symbols: - - $\mathcal{I}_{\mathrm{prior}} = \int p(\textbf{x}) \mathcal{I}_{p} \, \mathrm{d}\textbf{x}$: + - $\mathcal{I}_{\mathrm{prior}} = \int p(\textbf{x}) \mathcal{I}_{p} \, \mathrm{d}\textbf{x}$: the classical Fisher information matrix (CFIM) for the prior distribution $p(\textbf{x})$. - - $\mathcal{F}_{\mathrm{Bayes}} = \int p(\textbf{x}) \mathcal{F} \, \mathrm{d}\textbf{x}$: + - $\mathcal{F}_{\mathrm{Bayes}} = \int p(\textbf{x}) \mathcal{F} \, \mathrm{d}\textbf{x}$: the average quantum Fisher information matrix (QFIM) over the prior. Args: - x (list): + x (list): Parameter regimes for integration. - p (np.array): + p (np.array): Prior distribution. - dp (list): + dp (list): Derivatives of the prior distribution with respect to the parameters. - rho (list): + rho (list): Parameterized density matrices. - drho (list): + drho (list): Derivatives of the density matrices with respect to the parameters. - LDtype (string, optional): - Type of logarithmic derivative (default: "SLD"). Options: - - "SLD": Symmetric logarithmic derivative. - - "RLD": Right logarithmic derivative. - - "LLD": Left logarithmic derivative. - eps (float, optional): + LDtype (string, optional): + Type of logarithmic derivative (default: "SLD"). Options: + - "SLD": Symmetric logarithmic derivative. + - "RLD": Right logarithmic derivative. + - "LLD": Left logarithmic derivative. + eps (float, optional): Machine epsilon. - Returns: - (float/np.array): + Returns: + (float/np.array): For single parameter: float. For multiple parameters: matrix. """ para_num = len(x) @@ -976,21 +985,19 @@ def QVTB(x, p, dp, rho, drho, LDtype="SLD", eps=1e-8): dp_list = [dpi for dpi in dp_ext] F_list = [ - [[0.0 for i in range(len(p_list))] for j in range(para_num)] - for k in range(para_num) - ] + [[0.0 for i in range(len(p_list))] for j in range(para_num)] + for k in range(para_num) + ] I_list = [ - [[0.0 for i in range(len(p_list))] for j in range(para_num)] - for k in range(para_num) - ] + [[0.0 for i in range(len(p_list))] for j in range(para_num)] + for k in range(para_num) + ] for i in range(len(p_list)): F_tp = QFIM(rho_list[i], drho_list[i], LDtype=LDtype, eps=eps) for pj in range(para_num): for pk in range(para_num): F_list[pj][pk][i] = F_tp[pj][pk] - I_list[pj][pk][i] = ( - dp_list[i][pj] * dp_list[i][pk] / p_list[i] ** 2 - ) + I_list[pj][pk][i] = dp_list[i][pj] * dp_list[i][pk] / p_list[i] ** 2 F_res = np.zeros([para_num, para_num]) I_res = np.zeros([para_num, para_num]) @@ -1030,7 +1037,7 @@ def OBB(x, p, dp, rho, drho, d2rho, LDtype="SLD", eps=1e-8): $$ \mathrm{var}(\hat{x},\{\Pi_y\}) \geq \int p(x) \left( \frac{(1+b')^2}{F} + b^2 \right) \mathrm{d}x $$ - + Symbols: - $b$: bias, $b'$: its derivative. - $F$: quantum Fisher information (QFI). @@ -1038,31 +1045,31 @@ def OBB(x, p, dp, rho, drho, d2rho, LDtype="SLD", eps=1e-8): This bound is solved using a boundary value problem approach. Args: - x (np.array): + x (np.array): Parameter regime for integration. - p (np.array): + p (np.array): Prior distribution. - dp (np.array): + dp (np.array): Derivative of the prior distribution with respect to the parameter. - rho (list): + rho (list): Parameterized density matrices. - drho (list): + drho (list): First derivatives of the density matrices with respect to the parameter. - d2rho (list): + d2rho (list): Second-order derivatives of the density matrices with respect to the parameter. - LDtype (str, optional): - Type of logarithmic derivative (default: "SLD"). Options: - - "SLD": Symmetric logarithmic derivative. - - "RLD": Right logarithmic derivative. - - "LLD": Left logarithmic derivative. - eps (float, optional): + LDtype (str, optional): + Type of logarithmic derivative (default: "SLD"). Options: + - "SLD": Symmetric logarithmic derivative. + - "RLD": Right logarithmic derivative. + - "LLD": Left logarithmic derivative. + eps (float, optional): Machine epsilon. - Returns: - (float): + Returns: + (float): The optimal biased bound value for single parameter estimation. - Notes: + Notes: This function uses a boundary value problem solver to compute the optimal bias function. """ diff --git a/quanestimation/BayesianBound/BayesEstimation.py b/quanestimation/BayesianBound/BayesEstimation.py index e4280041..654021b2 100644 --- a/quanestimation/BayesianBound/BayesEstimation.py +++ b/quanestimation/BayesianBound/BayesEstimation.py @@ -7,9 +7,9 @@ def Bayes(x, p, rho, y, M=None, estimator="mean", savefile=False): """ - Bayesian estimation. The prior distribution is updated via the posterior - distribution obtained by Bayes' rule. The estimated value of parameters are - updated via either the expectation value of the distribution or maximum a + Bayesian estimation. The prior distribution is updated via the posterior + distribution obtained by Bayes' rule. The estimated value of parameters are + updated via either the expectation value of the distribution or maximum a posteriori probability (MAP). Args: @@ -17,68 +17,68 @@ def Bayes(x, p, rho, y, M=None, estimator="mean", savefile=False): p (np.ndarray): The prior distribution as a multidimensional array. rho (list): Parameterized density matrix as a multidimensional list. y (np.ndarray): The experimental results obtained in practice. - M (list, optional): A set of positive operator-valued measure (POVM). - Defaults to a set of rank-one symmetric informationally complete + M (list, optional): A set of positive operator-valued measure (POVM). + Defaults to a set of rank-one symmetric informationally complete POVM (SIC-POVM). - estimator (str, optional): Estimators for the bayesian estimation. + estimator (str, optional): Estimators for the bayesian estimation. Options are: "mean" (default) - The expectation value of the distribution. "MAP" - Maximum a posteriori probability. - savefile (bool, optional): Whether to save all posterior distributions. - If True, generates "pout.npy" and "xout.npy" containing all - posterior distributions and estimated values across iterations. - If False, only saves the final posterior distribution and all + savefile (bool, optional): Whether to save all posterior distributions. + If True, generates "pout.npy" and "xout.npy" containing all + posterior distributions and estimated values across iterations. + If False, only saves the final posterior distribution and all estimated values. Defaults to False. Returns: - (tuple): - pout (np.ndarray): + (tuple): + pout (np.ndarray): The posterior distribution in the final iteration. - xout (float/list): + xout (float/list): The estimated values in the final iteration. Raises: - TypeError: + TypeError: If `M` is not a list. - ValueError: + ValueError: If estimator is not "mean" or "MAP". - Note: - SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state + Note: + SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/solutions.html). """ if M is None: M = [] - + para_num = len(x) max_episode = len(y) - + # Single parameter scenario if para_num == 1: if not M: M = SIC(len(rho[0])) elif not isinstance(M, list): raise TypeError("M must be a list") - + x_out = [] p_out = [] - + for mi in range(max_episode): res_exp = int(y[mi]) pyx = np.zeros(len(x[0])) - + # Calculate conditional probabilities for xi in range(len(x[0])): p_tp = np.real(np.trace(rho[xi] @ M[res_exp])) pyx[xi] = p_tp - + # Update posterior distribution arr = [pyx[m] * p[m] for m in range(len(x[0]))] py = simpson(arr, x[0]) p_update = pyx * p / py p = p_update / np.linalg.norm(p_update) - + # Handle estimator type if estimator == "mean": mean = simpson([p[m] * x[0][m] for m in range(len(x[0]))], x[0]) @@ -88,14 +88,15 @@ def Bayes(x, p, rho, y, M=None, estimator="mean", savefile=False): x_out.append(x[0][indx]) else: raise ValueError( - "Invalid estimator: {}. Supported values are 'mean' and 'MAP'" - .format(estimator) + "Invalid estimator: {}. Supported values are 'mean' and 'MAP'".format( + estimator + ) ) - + # Save intermediate results if requested if savefile: p_out.append(p) - + # Save final results if savefile: np.save("pout", p_out) @@ -105,7 +106,7 @@ def Bayes(x, p, rho, y, M=None, estimator="mean", savefile=False): np.save("pout", p) np.save("xout", x_out) return p, x_out[-1] - + # Multiparameter scenario else: p_shape = np.shape(p) @@ -122,19 +123,19 @@ def Bayes(x, p, rho, y, M=None, estimator="mean", savefile=False): M = SIC(dim) elif not isinstance(M, list): raise TypeError("M must be a list") - + x_out = [] p_out = [] # Always initialize as list - + for mi in range(max_episode): res_exp = int(y[mi]) pyx_list = np.zeros(len(p_list)) - + # Calculate conditional probabilities for xi in range(len(p_list)): p_tp = np.real(np.trace(rho_list[xi] @ M[res_exp])) pyx_list[xi] = p_tp - + # Reshape and update posterior distribution pyx = pyx_list.reshape(p_shape) arr = p * pyx @@ -143,7 +144,7 @@ def Bayes(x, p, rho, y, M=None, estimator="mean", savefile=False): py = arr p_update = p * pyx / py p = p_update / np.linalg.norm(p_update) - + # Handle estimator type if estimator == "mean": mean = integ(x, p) @@ -157,11 +158,11 @@ def Bayes(x, p, rho, y, M=None, estimator="mean", savefile=False): raise ValueError( f"Invalid estimator: {estimator}. Supported values are 'mean' and 'MAP'" ) - + # Save intermediate results if requested if savefile: p_out.append(p) - + # Save final results if savefile: np.save("pout", p_out) @@ -178,34 +179,34 @@ def MLE(x, rho, y, M=[], savefile=False): Maximum likelihood estimation (MLE) for parameter estimation. Args: - x (list): + x (list): The regimes of the parameters for the integral. - rho (list): + rho (list): Parameterized density matrix as a multidimensional list. - y (np.ndarray): + y (np.ndarray): The experimental results obtained in practice. - M (list, optional): - A set of positive operator-valued measure (POVM). Defaults to a set of rank-one + M (list, optional): + A set of positive operator-valued measure (POVM). Defaults to a set of rank-one symmetric informationally complete POVM (SIC-POVM). - savefile (bool, optional): - Whether to save all likelihood functions. If True, generates "Lout.npy" and - "xout.npy" containing all likelihood functions and estimated values across - iterations. If False, only saves the final likelihood function and all + savefile (bool, optional): + Whether to save all likelihood functions. If True, generates "Lout.npy" and + "xout.npy" containing all likelihood functions and estimated values across + iterations. If False, only saves the final likelihood function and all estimated values. Defaults to False. Returns: - (tuple): - Lout (np.ndarray): + (tuple): + Lout (np.ndarray): The likelihood function in the final iteration. - xout (float/list): + xout (float/list): The estimated values in the final iteration. Raises: TypeError: If `M` is not a list. - Note: - SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state + Note: + SIC-POVM is calculated by the Weyl-Heisenberg covariant SIC-POVM fiducial state which can be downloaded from [here](http://www.physics.umb.edu/Research/QBism/solutions.html). """ if M is None: @@ -300,62 +301,64 @@ def MLE(x, rho, y, M=[], savefile=False): np.save("xout", x_out) return L_tp, x_out[-1] + def integ(x, p): """ Compute the mean for each parameter by integrating over the other parameters. - + Args: x: List of arrays, the parameter ranges p: Multidimensional array, the probability distribution - + Returns: List of mean values for each parameter """ para_num = len(x) mean = [] - + for i in range(para_num): # Create a list of axes to integrate over (all except current axis i) axes = [j for j in range(para_num) if j != i] - + # Integrate over all other axes to get marginal distribution for axis i p_marginal = p for axis in sorted(axes, reverse=True): p_marginal = simpson(p_marginal, x[axis], axis=axis) - + # Compute mean for current parameter mean_val = simpson(x[i] * p_marginal, x[i]) mean.append(mean_val) - + return mean + def BayesCost(x, p, xest, rho, M, W=[], eps=1e-8): """ Calculation of the average Bayesian cost with a quadratic cost function. Args: - x (list): + x (list): The regimes of the parameters for the integral. - p (array): + p (array): The prior distribution as a multidimensional array. - xest (float or list): + xest (float or list): The estimators. xest is a float for single-parameter estimation and a list for multi-parameter estimation. - rho (list): + rho (list): Parameterized density matrix as a multidimensional list. - M (list): + M (list): A set of positive operator-valued measure (POVM). - W (array, optional): + W (array, optional): Weight matrix. Defaults to an identity matrix. - eps (float, optional): + eps (float, optional): Machine epsilon. Returns: - (float): + (float): The average Bayesian cost. Raises: - TypeError: + TypeError: If `M` is not a list. """ if M is None: @@ -372,13 +375,22 @@ def BayesCost(x, p, xest, rho, M, W=[], eps=1e-8): else: if type(M) != list: raise TypeError("Please make sure M is a list!") - + # if isinstance(x, np.ndarray): - # x = [x] + # x = [x] p_num = len(x[0]) - value = [p[i]*sum([np.trace(rho[i] @ M[mi])*(x[0][i]-xest)**2 for mi in range(len(M))]) for i in range(p_num)] + value = [ + p[i] + * sum( + [ + np.trace(rho[i] @ M[mi]) * (x[0][i] - xest) ** 2 + for mi in range(len(M)) + ] + ) + for i in range(p_num) + ] C = simpson(value, x[0]) return np.real(C) else: @@ -396,13 +408,13 @@ def BayesCost(x, p, xest, rho, M, W=[], eps=1e-8): x_list = [] for x_ele in x_pro: x_list.append([x_ele[i] for i in range(para_num)]) - + dim = len(rho_list[0]) p_num = len(p_list) - + if W == []: W = np.identity(para_num) - + if M == []: M = SIC(dim) else: @@ -414,32 +426,38 @@ def BayesCost(x, p, xest, rho, M, W=[], eps=1e-8): x_tp = np.array(x_list[i]) xCx = 0.0 for mi in range(len(M)): - xCx += np.trace(rho_list[i] @ M[mi])*np.dot((x_tp-xest[mi]).reshape(1, -1), W @ (x_tp-xest[mi]).reshape(-1, 1))[0][0] - value[i] = p_list[i]*xCx + xCx += ( + np.trace(rho_list[i] @ M[mi]) + * np.dot( + (x_tp - xest[mi]).reshape(1, -1), + W @ (x_tp - xest[mi]).reshape(-1, 1), + )[0][0] + ) + value[i] = p_list[i] * xCx C = np.array(value).reshape(p_shape) for si in reversed(range(para_num)): C = simpson(C, x[si]) return np.real(C) - - + + def BCB(x, p, rho, W=[], eps=1e-8): """ Calculation of the Bayesian cost bound with a quadratic cost function. Args: - x (list): + x (list): The regimes of the parameters for the integral. - p (array): + p (array): The prior distribution as a multidimensional array. - rho (list): + rho (list): Parameterized density matrix as a multidimensional list. - W (array, optional): + W (array, optional): Weight matrix. Defaults to an identity matrix. - eps (float, optional): + eps (float, optional): Machine epsilon. Defaults to 1e-8. Returns: - (float): + (float): The value of the minimum Bayesian cost. Note: @@ -450,14 +468,14 @@ def BCB(x, p, rho, W=[], eps=1e-8): # single-parameter scenario dim = len(rho[0]) p_num = len(x[0]) - value = [p[i]*x[0][i]**2 for i in range(p_num)] + value = [p[i] * x[0][i] ** 2 for i in range(p_num)] delta2_x = simpson(value, x[0]) rho_avg = np.zeros((dim, dim), dtype=np.complex128) rho_pri = np.zeros((dim, dim), dtype=np.complex128) for di in range(dim): for dj in range(dim): - rho_avg_arr = [p[m]*rho[m][di][dj] for m in range(p_num)] - rho_pri_arr = [p[n]*x[0][n]*rho[n][di][dj] for n in range(p_num)] + rho_avg_arr = [p[m] * rho[m][di][dj] for m in range(p_num)] + rho_pri_arr = [p[n] * x[0][n] * rho[n][di][dj] for n in range(p_num)] rho_avg[di][dj] = simpson(rho_avg_arr, x[0]) rho_pri[di][dj] = simpson(rho_pri_arr, x[0]) Lambda = Lambda_avg(rho_avg, [rho_pri], eps=eps) @@ -481,15 +499,15 @@ def BCB(x, p, rho, W=[], eps=1e-8): x_list = [] for x_ele in x_pro: x_list.append([x_ele[i] for i in range(para_num)]) - + if W == []: W = np.identity(para_num) - + value = [0.0 for i in range(p_num)] for i in range(p_num): x_tp = np.array(x_list[i]) xCx = np.dot(x_tp.reshape(1, -1), np.dot(W, x_tp.reshape(-1, 1)))[0][0] - value[i] = p_list[i]*xCx + value[i] = p_list[i] * xCx delta2_x = np.array(value).reshape(p_shape) for si in reversed(range(para_num)): delta2_x = simpson(delta2_x, x[si]) @@ -497,14 +515,17 @@ def BCB(x, p, rho, W=[], eps=1e-8): rho_pri = [np.zeros((dim, dim), dtype=np.complex128) for i in range(para_num)] for di in range(dim): for dj in range(dim): - rho_avg_arr = [p_list[m]*rho_list[m][di][dj] for m in range(p_num)] + rho_avg_arr = [p_list[m] * rho_list[m][di][dj] for m in range(p_num)] rho_avg_tp = np.array(rho_avg_arr).reshape(p_shape) for si in reversed(range(para_num)): rho_avg_tp = simpson(rho_avg_tp, x[si]) rho_avg[di][dj] = rho_avg_tp for para_i in range(para_num): - rho_pri_arr = [p_list[n]*x_list[n][para_i]*rho_list[n][di][dj] for n in range(p_num)] + rho_pri_arr = [ + p_list[n] * x_list[n][para_i] * rho_list[n][di][dj] + for n in range(p_num) + ] rho_pri_tp = np.array(rho_pri_arr).reshape(p_shape) for si in reversed(range(para_num)): rho_pri_tp = simpson(rho_pri_tp, x[si]) @@ -515,10 +536,11 @@ def BCB(x, p, rho, W=[], eps=1e-8): for para_m in range(para_num): for para_n in range(para_num): Mat += W[para_m][para_n] * np.dot(Lambda[para_m], Lambda[para_n]) - - minBC = delta2_x-np.real(np.trace(rho_avg @ Mat)) + + minBC = delta2_x - np.real(np.trace(rho_avg @ Mat)) return minBC - + + def Lambda_avg(rho_avg, rho_pri, eps=1e-8): para_num = len(rho_pri) dim = len(rho_avg) @@ -526,11 +548,17 @@ def Lambda_avg(rho_avg, rho_pri, eps=1e-8): val, vec = np.linalg.eig(rho_avg) val = np.real(val) for para_i in range(0, para_num): - Lambda_eig = np.array([[0.0 + 0.0 * 1.0j for i in range(0, dim)] for i in range(0, dim)]) + Lambda_eig = np.array( + [[0.0 + 0.0 * 1.0j for i in range(0, dim)] for i in range(0, dim)] + ) for fi in range(0, dim): for fj in range(0, dim): if np.abs(val[fi] + val[fj]) > eps: - Lambda_eig[fi][fj] = 2* (vec[:, fi].conj().transpose() @ rho_pri[para_i]@ vec[:, fj]) / (val[fi] + val[fj]) + Lambda_eig[fi][fj] = ( + 2 + * (vec[:, fi].conj().transpose() @ rho_pri[para_i] @ vec[:, fj]) + / (val[fi] + val[fj]) + ) Lambda_eig[Lambda_eig == np.inf] = 0.0 - Lambda[para_i] = vec @ Lambda_eig @ vec.conj().transpose() + Lambda[para_i] = vec @ Lambda_eig @ vec.conj().transpose() return Lambda diff --git a/quanestimation/BayesianBound/ZivZakai.py b/quanestimation/BayesianBound/ZivZakai.py index 79f0165c..47a7cd92 100644 --- a/quanestimation/BayesianBound/ZivZakai.py +++ b/quanestimation/BayesianBound/ZivZakai.py @@ -9,9 +9,11 @@ def trace_norm(A, eps): else: return np.sum(np.linalg.svd(A, compute_uv=False)) + def helstrom_dm(rho, sigma, eps, P0=0.5): return np.real((1 - trace_norm(P0 * rho - (1 - P0) * sigma, eps)) / 2) + # def helstrom_vec(psi, phi, n=1): # return np.real((1 - np.sqrt(1 - fidelity(psi, phi) ** n)) / 2) diff --git a/quanestimation/BayesianBound/__init__.py b/quanestimation/BayesianBound/__init__.py index dee095a0..87ea0813 100644 --- a/quanestimation/BayesianBound/__init__.py +++ b/quanestimation/BayesianBound/__init__.py @@ -10,12 +10,7 @@ from quanestimation.BayesianBound.ZivZakai import ( QZZB, ) -from quanestimation.BayesianBound.BayesEstimation import ( - Bayes, - MLE, - BCB, - BayesCost -) +from quanestimation.BayesianBound.BayesEstimation import Bayes, MLE, BCB, BayesCost __all__ = [ "BCFIM", diff --git a/quanestimation/Common/Common.py b/quanestimation/Common/Common.py index 27182a1c..646e0559 100755 --- a/quanestimation/Common/Common.py +++ b/quanestimation/Common/Common.py @@ -11,7 +11,7 @@ def load_julia(): """ Load Julia and initialize QuanEstimation module. - + Returns: jl.Main.QuanEstimation: Julia module for quantum estimation """ @@ -23,13 +23,13 @@ def load_julia(): def mat_vec_convert(A): """ Convert between matrix and vector representations. - + Args: - A (np.array): - Input matrix or vector. - + A (np.array): + Input matrix or vector. + Returns: - (np.array): + (np.array): Converted matrix or vector. """ if A.shape[1] == 1: @@ -42,38 +42,29 @@ def mat_vec_convert(A): def suN_unsorted(n): """ Generate unsorted SU(N) generators. - + Args: - n (float): + n (float): Dimension of the system. - + Returns: - (tuple): + (tuple): Tuple of symmetric, antisymmetric, and diagonal generators. """ U, V, W = [], [], [] for i in range(1, n): for j in range(0, i): - U_tp = csc_matrix( - ([1.0, 1.0], ([i, j], [j, i])), - shape=(n, n) - ).toarray() - V_tp = csc_matrix( - ([1.0j, -1.0j], ([i, j], [j, i])), - shape=(n, n) - ).toarray() + U_tp = csc_matrix(([1.0, 1.0], ([i, j], [j, i])), shape=(n, n)).toarray() + V_tp = csc_matrix(([1.0j, -1.0j], ([i, j], [j, i])), shape=(n, n)).toarray() U.append(U_tp) V.append(V_tp) diag = [] for i in range(n - 1): - mat_tp = csc_matrix( - ([1, -1], ([i, i + 1], [i, i + 1])), - shape=(n, n) - ).toarray() + mat_tp = csc_matrix(([1, -1], ([i, i + 1], [i, i + 1])), shape=(n, n)).toarray() diag_tp = np.diagonal(mat_tp) diag.append(Matrix(diag_tp)) - + W_gs = GramSchmidt(diag, True) for k in range(len(W_gs)): W_tp = np.fromiter(W_gs[k], dtype=complex) @@ -85,13 +76,13 @@ def suN_unsorted(n): def suN_generator(n): """ Generate sorted SU(N) generators. - + Args: - n (float): - Dimension of the system. - + n (float): + Dimension of the system. + Returns: - (list): + (list): List of SU(N) generators. """ symm, anti_symm, diag = suN_unsorted(n) @@ -132,13 +123,13 @@ def suN_generator(n): def gramschmidt(A): """ Perform Gram-Schmidt orthogonalization. - + Args: - A (list): + A (list): List of vectors to orthogonalize. - + Returns: - (list): + (list): List of orthonormal vectors. """ dim = len(A) @@ -157,15 +148,15 @@ def gramschmidt(A): def basis(dim, index): """ Generate basis vector. - + Args: - dim (float): + dim (float): Dimension of Hilbert space. - index (float): + index (float): Index of basis vector. - + Returns: - (np.array): + (np.array): Basis vector as column vector. """ x = np.zeros(dim) @@ -176,13 +167,13 @@ def basis(dim, index): def sic_povm(fiducial): """ Generate SIC-POVM from fiducial state. - + Args: - fiducial (np.array): + fiducial (np.array): the fiducial state vector. - + Returns: - (list): + (list): List of POVM elements. """ d = fiducial.shape[0] @@ -218,17 +209,17 @@ def sic_povm(fiducial): def SIC(dim): """ Generate SIC-POVM for given dimension. - + Args: - dim (float): + dim (float): Dimension of the system. - + Returns: - (list): + (list): List of SIC-POVM elements. - + Raises: - ValueError: + ValueError: If dimension > 151. """ if dim <= 151: @@ -242,19 +233,17 @@ def SIC(dim): M = sic_povm(fiducial) return M else: - raise ValueError( - "The dimension of the space should be less or equal to 151." - ) + raise ValueError("The dimension of the space should be less or equal to 151.") def extract_ele(element, n): """ Recursively extract elements. - + Args: - element (np.array/list): + element (np.array/list): Input element - n (int/float): + n (int/float): Depth of extraction. """ if n: @@ -267,13 +256,13 @@ def extract_ele(element, n): def annihilation(n): """ Create annihilation operator. - + Args: - n (int/float): + n (int/float): Dimension of space. - + Returns: - (np.array): + (np.array): Annihilation operator matrix. """ data = np.sqrt(np.arange(1, n, dtype=complex)) @@ -287,13 +276,13 @@ def annihilation(n): def brgd(n): """ Generate binary reflected Gray code. - + Args: - n (int/float): + n (int/float): Number of bits - + Returns: - (list): + (list): List of Gray code sequences """ if n == 1: @@ -310,23 +299,23 @@ def brgd(n): def BayesInput(x, func, dfunc, channel="dynamics"): """ Generate input variables for Bayesian estimation. - + Args: - x (np.array): + x (np.array): Parameter regimes - func (callable): + func (callable): Function returning H or K - dfunc (callable): + dfunc (callable): Function returning dH or dK - channel (str, optional): + channel (str, optional): "dynamics" or "Kraus" (default: "dynamics") - + Returns: - (tuple): + (tuple): Tuple of (H_list, dH_list) or (K_list, dK_list) - + Raises: - ValueError: + ValueError: For invalid channel. """ x_all = product(*x) @@ -348,20 +337,21 @@ def BayesInput(x, func, dfunc, channel="dynamics"): "'dynamics' or 'Kraus'.".format(channel) ) + def fidelity(input1, input2): """ Compute the fidelity between two quantum states. - + For state vectors (1D arrays), the fidelity is defined as |<ψ|φ>|². For density matrices (2D arrays), the fidelity is defined as [tr(√(√ρ σ √ρ))]². - + Args: input1 (np.ndarray): First quantum state (vector or density matrix) input2 (np.ndarray): Second quantum state (vector or density matrix) - + Returns: float: Fidelity between the two states - + Raises: TypeError: If inputs are not numpy arrays ValueError: If inputs are not both vectors or both matrices @@ -369,19 +359,19 @@ def fidelity(input1, input2): """ if not (isinstance(input1, np.ndarray) and isinstance(input2, np.ndarray)): raise TypeError("Inputs must be numpy arrays") - + if input1.ndim == 1 and input2.ndim == 1: # Vector case overlap = np.vdot(input1, input2) return np.abs(overlap) ** 2 - + elif input1.ndim != 1: # Matrix case if input1.shape != input2.shape: raise ValueError("Input matrices must have the same shape") - + rho_sqrt = sqrtm(input1) - product = rho_sqrt @ input2 @ rho_sqrt + product = rho_sqrt @ input2 @ rho_sqrt fidelity_sqrt = np.trace(sqrtm(product)) return np.real(fidelity_sqrt) ** 2 diff --git a/quanestimation/Common/_julia_project.py b/quanestimation/Common/_julia_project.py index 4097f5f0..789d25ab 100644 --- a/quanestimation/Common/_julia_project.py +++ b/quanestimation/Common/_julia_project.py @@ -1,4 +1,3 @@ - import os, logging import julia_project, julia_project_basic from julia_project import JuliaProject @@ -10,22 +9,35 @@ # This is useful for testing purposes to avoid unnecessary compilation # and speed up the test execution. # It can be overridden by setting the environment variable before running tests. - os.environ["QuanEstimation_COMPILE"] = "y" + os.environ["QuanEstimation_COMPILE"] = "y" pkg_path = Path(__file__).parent.parent -def run_pkg_commands_monkey_patch(project_path, commands, julia_exe=None, depot_path=None, clog=False, no_stderr=False): + +def run_pkg_commands_monkey_patch( + project_path, commands, julia_exe=None, depot_path=None, clog=False, no_stderr=False +): if not os.path.isdir(project_path) and not os.path.isfile(project_path): raise FileNotFoundError(f"{project_path} does not exist") if os.path.isfile(project_path): - project_path = os.path.dirname(project_path) # julia commands run 250ms faster if we do this. why? + project_path = os.path.dirname( + project_path + ) # julia commands run 250ms faster if we do this. why? com = f'import Pkg; Pkg.activate(raw"{project_path}"); ' + commands - return run_julia(commands=com, julia_exe=julia_exe, depot_path=depot_path, clog=clog, no_stderr=no_stderr) + return run_julia( + commands=com, + julia_exe=julia_exe, + depot_path=depot_path, + clog=clog, + no_stderr=no_stderr, + ) + def _load_julia_utils_monkey_patch(self): - srcdir = os.path.dirname(os.path.realpath(__file__)) - utilf = os.path.join(srcdir, "utils.jl") - self.calljulia.seval(f'include(raw"{utilf}")') + srcdir = os.path.dirname(os.path.realpath(__file__)) + utilf = os.path.join(srcdir, "utils.jl") + self.calljulia.seval(f'include(raw"{utilf}")') + ## Remove munual monkey patch when fixed @@ -35,12 +47,12 @@ def _load_julia_utils_monkey_patch(self): project = JuliaProject( name="quanestimation", package_path=pkg_path, - version_spec = "1.11", + version_spec="1.11", sys_image_dir="sys_image", sys_image_file_base=None, - env_prefix = 'QuanEstimation_', - logging_level = logging.INFO, # or logging.WARN, + env_prefix="QuanEstimation_", + logging_level=logging.INFO, # or logging.WARN, console_logging=False, # post_init_hook=_post_init_hook, # Run this after ensure_init - calljulia = "juliacall" + calljulia="juliacall", ) diff --git a/quanestimation/ComprehensiveOpt/AD_Compopt.py b/quanestimation/ComprehensiveOpt/AD_Compopt.py index 033ac4db..d0340063 100644 --- a/quanestimation/ComprehensiveOpt/AD_Compopt.py +++ b/quanestimation/ComprehensiveOpt/AD_Compopt.py @@ -7,12 +7,12 @@ 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 + -- 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` @@ -29,7 +29,7 @@ class AD_Compopt(Comp.ComprehensiveSystem): > **max_episode:** `int` -- The number of episodes. - + > **epsilon:** `float` -- Learning rate. @@ -45,6 +45,7 @@ class AD_Compopt(Comp.ComprehensiveSystem): > **eps:** `float` -- Machine epsilon. """ + def __init__( self, savefile=False, @@ -59,7 +60,6 @@ def __init__( seed=1234, eps=1e-8, ): - Comp.ComprehensiveSystem.__init__( self, savefile, psi0, ctrl0, measurement0, seed, eps ) @@ -83,22 +83,22 @@ def SC(self, W=[], M=[], target="QFIM", LDtype="SLD"): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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. + -- 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). - + -- 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 != []: diff --git a/quanestimation/ComprehensiveOpt/ComprehensiveStruct.py b/quanestimation/ComprehensiveOpt/ComprehensiveStruct.py index cec3c445..1b2ffe11 100644 --- a/quanestimation/ComprehensiveOpt/ComprehensiveStruct.py +++ b/quanestimation/ComprehensiveOpt/ComprehensiveStruct.py @@ -14,12 +14,12 @@ 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 + -- 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` @@ -39,59 +39,86 @@ class ComprehensiveSystem: """ 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') + 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)]) + 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 - + else: + pass + def load_save_ctrls_alt(self, cnum, max_episode): if os.path.exists("controls.dat"): - fl = h5py.File("controls.dat",'r') + 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)]) + 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)]) + controls = np.array([dset[:, i] for i in range(cnum)]) np.save("controls", controls) - else: pass - + else: + pass + def load_save_states(self, max_episode): if os.path.exists("states.dat"): - fl = h5py.File("states.dat",'r') + 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)]) + psi = np.array( + [np.array(fl[dset[i]]).view("complex") for i in range(max_episode)] + ) else: - psi = np.array(dset).view('complex') + psi = np.array(dset).view("complex") np.save("states", psi) - else: pass - + else: + pass + def load_save_meas(self, mnum, max_episode): if os.path.exists("measurements.dat"): - fl = h5py.File("measurements.dat",'r') + 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)]) + 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)]) + mdata = np.array( + [np.array(fl[dset[j]]).view("complex") for j in range(mnum)] + ) np.save("measurements", mdata) - else: pass + else: + pass - def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], ctrl_bound=[], dyn_method="expm"): + 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 @@ -166,7 +193,9 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], ctrl_bound=[], dyn_m 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) + 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) @@ -246,12 +275,15 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], ctrl_bound=[], dyn_m np.zeros(len(self.control_coefficients[0])), ) ) - else: pass - + 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_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) @@ -271,16 +303,21 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], ctrl_bound=[], dyn_m if type(H0) != np.ndarray: #### linear interpolation #### f = interp1d(self.tspan, H0, axis=0) - else: pass + 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.freeHamiltonian = [ + np.array(x, dtype=np.complex128) for x in H0_inter[:-1] + ] + else: + pass + else: + pass self.dynamics_type = "dynamics" @@ -289,7 +326,7 @@ def Kraus(self, K, dK): The parameterization of a state is \begin{align} \rho=\sum_i K_i\rho_0K_i^{\dagger}, - \end{align} + \end{align} where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator. @@ -299,8 +336,8 @@ def Kraus(self, K, dK): -- 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 + -- Derivatives of the Kraus operators on the unknown parameters to be + estimated. For example, dK[0] is the derivative vector on the first parameter. """ @@ -328,7 +365,9 @@ def Kraus(self, K, dK): 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) + 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) @@ -361,31 +400,29 @@ def SC(self, W=[], M=[], target="QFIM", LDtype="SLD"): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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. + -- 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). + -- 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 + **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." - ) + raise ValueError("Supported type of dynamics is Lindblad.") if W == []: W = np.eye(len(self.Hamiltonian_derivative)) @@ -405,9 +442,7 @@ def SC(self, W=[], M=[], target="QFIM", LDtype="SLD"): 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 - ) + 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) @@ -417,7 +452,10 @@ def SC(self, W=[], M=[], target="QFIM", LDtype="SLD"): ) self.opt = QJL.StateControlOpt( - psi=self.psi, ctrl=self.control_coefficients, ctrl_bound=self.ctrl_bound, seed=self.seed + psi=self.psi, + ctrl=self.control_coefficients, + ctrl_bound=self.ctrl_bound, + seed=self.seed, ) self.output = QJL.Output(self.opt, save=self.savefile) @@ -430,14 +468,16 @@ def SC(self, W=[], M=[], target="QFIM", LDtype="SLD"): self.tspan, self.decay_opt, self.gamma, - dyn_method = self.dyn_method, - ) + 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] + 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) @@ -455,9 +495,7 @@ def CM(self, rho0, W=[]): """ if self.dynamics_type != "dynamics": - raise ValueError( - "Supported type of dynamics is Lindblad." - ) + raise ValueError("Supported type of dynamics is Lindblad.") if W == []: W = np.eye(len(self.Hamiltonian_derivative)) @@ -467,7 +505,10 @@ def CM(self, rho0, W=[]): 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 + ctrl=self.control_coefficients, + M=self.C, + ctrl_bound=self.ctrl_bound, + seed=self.seed, ) self.output = QJL.Output(self.opt, save=self.savefile) @@ -480,15 +521,17 @@ def CM(self, rho0, W=[]): self.tspan, self.decay_opt, self.gamma, - dyn_method =self.dyn_method, - ) + 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] + 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) @@ -570,7 +613,8 @@ def SM(self, W=[]): if type(self.freeHamiltonian) != np.ndarray: #### linear interpolation #### f = interp1d(self.tspan, self.freeHamiltonian, axis=0) - else: pass + 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]) @@ -579,9 +623,13 @@ def SM(self, W=[]): ) 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 + 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) @@ -589,7 +637,10 @@ def SM(self, W=[]): 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))] + 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( @@ -608,7 +659,10 @@ def SM(self, W=[]): 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))] + 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( @@ -626,16 +680,14 @@ def SM(self, W=[]): self.tspan, self.decay_opt, self.gamma, - dyn_method = self.dyn_method, + 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." - ) + 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) @@ -646,10 +698,12 @@ def SM(self, W=[]): ) QJL.run(system) - max_num = self.max_episode if type(self.max_episode) == int else self.max_episode[0] + 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). @@ -661,16 +715,18 @@ def SCM(self, W=[]): """ if self.dynamics_type != "dynamics": - raise ValueError( - "Supported type of dynamics is Lindblad." - ) + 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 + 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) @@ -683,20 +739,22 @@ def SCM(self, W=[]): 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] + 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) -def ComprehensiveOpt(savefile=False, method="DE", **kwargs): +def ComprehensiveOpt(savefile=False, method="DE", **kwargs): if method == "AD": return compopt.AD_Compopt(savefile=savefile, **kwargs) elif method == "PSO": diff --git a/quanestimation/ComprehensiveOpt/DE_Compopt.py b/quanestimation/ComprehensiveOpt/DE_Compopt.py index fb95edf2..8851baca 100644 --- a/quanestimation/ComprehensiveOpt/DE_Compopt.py +++ b/quanestimation/ComprehensiveOpt/DE_Compopt.py @@ -7,12 +7,12 @@ 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 + -- 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` @@ -29,7 +29,7 @@ class DE_Compopt(Comp.ComprehensiveSystem): > **max_episode:** `int` -- The number of episodes. - + > **c:** `float` -- Mutation constant. @@ -42,6 +42,7 @@ class DE_Compopt(Comp.ComprehensiveSystem): > **eps:** `float` -- Machine epsilon. """ + def __init__( self, savefile=False, @@ -55,7 +56,6 @@ def __init__( seed=1234, eps=1e-8, ): - Comp.ComprehensiveSystem.__init__( self, savefile, psi0, ctrl0, measurement0, seed, eps ) @@ -76,24 +76,24 @@ def SC(self, W=[], M=[], target="QFIM", LDtype="SLD"): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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. + -- 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). + -- 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 + **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). """ @@ -105,7 +105,7 @@ def SC(self, W=[], M=[], target="QFIM", LDtype="SLD"): self.c, self.cr, ) - + super().SC(W, M, target, LDtype) def CM(self, rho0, W=[]): diff --git a/quanestimation/ComprehensiveOpt/PSO_Compopt.py b/quanestimation/ComprehensiveOpt/PSO_Compopt.py index 4a141b45..8b79631b 100644 --- a/quanestimation/ComprehensiveOpt/PSO_Compopt.py +++ b/quanestimation/ComprehensiveOpt/PSO_Compopt.py @@ -7,12 +7,12 @@ 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 + -- 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` @@ -28,21 +28,21 @@ class PSO_Compopt(Comp.ComprehensiveSystem): -- Initial guesses of measurements. > **max_episode:** `int or list` - -- If it is an integer, for example max_episode=1000, it means the + -- 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 + 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 + -- 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 + -- The exploitation weight that attracts the particle to the best position in the neighborhood, also known as social learning factor. > **seed:** `int` @@ -66,7 +66,6 @@ def __init__( seed=1234, eps=1e-8, ): - Comp.ComprehensiveSystem.__init__( self, savefile, psi0, ctrl0, measurement0, seed, eps ) @@ -88,32 +87,29 @@ def SC(self, W=[], M=[], target="QFIM", LDtype="SLD"): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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. + -- 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). + -- 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 + **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 - ) - + ini_particle = (self.psi, self.ctrl0) + self.alg = QJL.PSO( QJL.Vector[QJL.Int64](self.max_episode), self.p_num, @@ -187,5 +183,5 @@ def SCM(self, W=[]): self.c1, self.c2, ) - + super().SCM(W) diff --git a/quanestimation/ControlOpt/ControlStruct.py b/quanestimation/ControlOpt/ControlStruct.py index ad19d2c0..e3d98256 100644 --- a/quanestimation/ControlOpt/ControlStruct.py +++ b/quanestimation/ControlOpt/ControlStruct.py @@ -8,16 +8,18 @@ from quanestimation.Common.Common import SIC from quanestimation import QJL from juliacall import Main as jl, convert as jlconvert + + 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 + -- 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` @@ -27,9 +29,9 @@ class ControlSystem: -- 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 + -- 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. """ @@ -38,21 +40,29 @@ def __init__(self, savefile, ctrl0, eps, load): self.ctrl0 = ctrl0 self.eps = eps self.load = load - - self.QJLType_ctrl = QJL.Vector[QJL.Vector[QJL.Vector[QJL.Float64]]] - + + 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') + 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)]) + 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 + else: + pass - def dynamics(self, tspan, rho0, H0, dH, Hc, decay=[], ctrl_bound=[], dyn_method="expm"): + 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 @@ -140,7 +150,7 @@ def dynamics(self, tspan, rho0, H0, dH, Hc, decay=[], ctrl_bound=[], dyn_method= self.decay_opt = [np.array(x, dtype=np.complex128) for x in decay_opt] if ctrl_bound == []: - self.ctrl_bound = [-jl.Inf, jl.Inf] + 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) @@ -169,7 +179,9 @@ def dynamics(self, tspan, rho0, H0, dH, Hc, decay=[], ctrl_bound=[], dyn_method= 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]) + 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"): @@ -196,28 +208,31 @@ def dynamics(self, tspan, rho0, H0, dH, Hc, decay=[], ctrl_bound=[], dyn_method= np.zeros(len(self.control_coefficients[0])), ) ) - else: pass + else: + pass if type(H0) != np.ndarray: #### linear interpolation #### f = interp1d(self.tspan, H0, axis=0) - else: pass + 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.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 + ctrl=self.ctrl0, ctrl_bound=self.ctrl_bound, seed=self.seed ) self.dynamic = jl.QuanEstimation.Lindblad( self.freeHamiltonian, @@ -228,7 +243,7 @@ def dynamics(self, tspan, rho0, H0, dH, Hc, decay=[], ctrl_bound=[], dyn_method= self.tspan, self.decay_opt, self.gamma, - dyn_method = self.dyn_method, + dyn_method=self.dyn_method, ) self.output = QJL.Output(self.opt, save=self.savefile) @@ -236,8 +251,8 @@ def dynamics(self, tspan, rho0, H0, dH, Hc, decay=[], ctrl_bound=[], dyn_method= 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 + 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 @@ -246,10 +261,10 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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": @@ -263,20 +278,20 @@ def QFIM(self, W=[], LDtype="SLD"): W = np.eye(len(self.Hamiltonian_derivative)) self.W = W - self.obj = QJL.QFIM_obj( - self.W, self.eps, self.para_type, LDtype - ) + 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] + 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 + 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 @@ -285,11 +300,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -307,12 +322,14 @@ def CFIM(self, M=[], W=[]): 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] + 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. + 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 @@ -320,7 +337,7 @@ def HCRB(self, W=[]): Parameters ---------- - > **W:** `matrix` + > **W:** `matrix` -- Weight matrix. """ @@ -329,19 +346,24 @@ def HCRB(self, W=[]): 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." - ) + 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.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] + 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"): @@ -357,27 +379,27 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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. + -- 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. + -- 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). + -- 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"): @@ -388,14 +410,12 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): ) if self.dynamics_type != "lindblad": - raise ValueError( - "Supported type of dynamics is Lindblad." - ) + raise ValueError("Supported type of dynamics is Lindblad.") if self.savefile == True: warnings.warn( - "savefile is set to be False", - DeprecationWarning, - ) + "savefile is set to be False", + DeprecationWarning, + ) self.output = QJL.Output(self.opt) if len(self.Hamiltonian_derivative) > 1: @@ -412,16 +432,13 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): 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 - ) + "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 - ) + 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'." @@ -431,11 +448,13 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): 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] + 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 ControlOpt(savefile=False, method="auto-GRAPE", **kwargs): +def ControlOpt(savefile=False, method="auto-GRAPE", **kwargs): if method == "auto-GRAPE": return ctrl.GRAPE_Copt(savefile=savefile, **kwargs, auto=True) elif method == "GRAPE": @@ -445,20 +464,20 @@ def ControlOpt(savefile=False, method="auto-GRAPE", **kwargs): elif method == "DE": return ctrl.DE_Copt(savefile=savefile, **kwargs) elif method == "DDPG": - raise ValueError( - "'DDPG' is currently deprecated and will be fixed soon." - ) + raise ValueError("'DDPG' is currently deprecated and will be fixed soon.") # return ctrl.DDPG_Copt(savefile=savefile, **kwargs) else: raise ValueError( - "{!r} is not a valid value for method, supported values are 'auto-GRAPE', 'GRAPE', 'PSO', 'DE', 'DDPG'.".format(method + "{!r} is not a valid value for method, supported values are 'auto-GRAPE', 'GRAPE', 'PSO', 'DE', 'DDPG'.".format( + method ) ) + def csv2npy_controls(controls, num): C_save = [] N = int(len(controls) / num) for ci in range(N): C_tp = controls[ci * num : (ci + 1) * num] C_save.append(C_tp) - np.save("controls", C_save) \ No newline at end of file + np.save("controls", C_save) diff --git a/quanestimation/ControlOpt/DDPG_Copt.py b/quanestimation/ControlOpt/DDPG_Copt.py index 7d398c77..c15cf5ca 100644 --- a/quanestimation/ControlOpt/DDPG_Copt.py +++ b/quanestimation/ControlOpt/DDPG_Copt.py @@ -7,11 +7,11 @@ class DDPG_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 + -- 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` @@ -19,7 +19,7 @@ class DDPG_Copt(Control.ControlSystem): > **max_episode:** `int` -- The number of episodes. - + > **layer_num:** `int` -- The number of layers (include the input and output layer). @@ -33,9 +33,9 @@ class DDPG_Copt(Control.ControlSystem): -- 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 + -- 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. """ @@ -50,7 +50,6 @@ def __init__( eps=1e-8, load=False, ): - Control.ControlSystem.__init__(self, savefile, ctrl0, eps, load) self.max_episode = max_episode @@ -59,14 +58,12 @@ def __init__( self.seed = seed - self.alg = QJL.DDPG( - self.max_episode, self.layer_num, self.layer_dim, self.seed - ) + self.alg = QJL.DDPG(self.max_episode, self.layer_num, self.layer_dim, self.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 + 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 @@ -75,9 +72,9 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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). """ @@ -85,8 +82,8 @@ def QFIM(self, W=[], LDtype="SLD"): 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 + 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 @@ -95,11 +92,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -108,9 +105,9 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - Choose HCRB as the objective function. + Choose HCRB as the objective function. - **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose + **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function. Parameters @@ -133,30 +130,30 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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). + -- 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. + -- 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). + -- 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 + **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). """ diff --git a/quanestimation/ControlOpt/DE_Copt.py b/quanestimation/ControlOpt/DE_Copt.py index 5820e363..5f173025 100644 --- a/quanestimation/ControlOpt/DE_Copt.py +++ b/quanestimation/ControlOpt/DE_Copt.py @@ -8,11 +8,11 @@ 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 + --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` @@ -23,7 +23,7 @@ class DE_Copt(Control.ControlSystem): > **max_episode:** `int` -- The number of episodes. - + > **c:** `float` -- Mutation constant. @@ -37,9 +37,9 @@ class DE_Copt(Control.ControlSystem): -- 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 + -- 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. """ @@ -55,7 +55,6 @@ def __init__( eps=1e-8, load=False, ): - Control.ControlSystem.__init__(self, savefile, ctrl0, eps, load) self.p_num = p_num @@ -66,8 +65,8 @@ def __init__( 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 + 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 @@ -76,12 +75,12 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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, ) + ini_population = (self.ctrl0,) self.alg = QJL.DE( self.max_episode, self.p_num, @@ -94,8 +93,8 @@ def QFIM(self, W=[], LDtype="SLD"): 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 + 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 @@ -104,15 +103,15 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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, ) + ini_population = (self.ctrl0,) self.alg = QJL.DE( self.max_episode, self.p_num, @@ -125,9 +124,9 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - Choose HCRB as the objective function. + Choose HCRB as the objective function. - **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose + **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function. Parameters @@ -135,8 +134,8 @@ def HCRB(self, W=[]): > **W:** `matrix` -- Weight matrix. """ - - ini_population = (self.ctrl0, ) + + ini_population = (self.ctrl0,) self.alg = QJL.DE( self.max_episode, self.p_num, @@ -160,34 +159,34 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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). + -- 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 + -- 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). + -- 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 + **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, ) + ini_population = (self.ctrl0,) self.alg = QJL.DE( self.max_episode, self.p_num, @@ -195,5 +194,5 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): self.c, self.cr, ) - + super().mintime(f, W, M, method, target, LDtype) diff --git a/quanestimation/ControlOpt/GRAPE_Copt.py b/quanestimation/ControlOpt/GRAPE_Copt.py index a5e40b2d..fb8d4cf9 100644 --- a/quanestimation/ControlOpt/GRAPE_Copt.py +++ b/quanestimation/ControlOpt/GRAPE_Copt.py @@ -2,16 +2,17 @@ import quanestimation.ControlOpt.ControlStruct as Control from quanestimation import QJL + 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 + -- 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` @@ -22,7 +23,7 @@ class GRAPE_Copt(Control.ControlSystem): > **max_episode:** `int` -- The number of episodes. - + > **epsilon:** `float` -- Learning rate. @@ -36,18 +37,18 @@ class GRAPE_Copt(Control.ControlSystem): -- 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 + -- 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 + -- 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, @@ -62,7 +63,6 @@ def __init__( load=False, auto=True, ): - Control.ControlSystem.__init__(self, savefile, ctrl0, eps, load) self.Adam = Adam @@ -77,8 +77,8 @@ def __init__( 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 + 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 @@ -87,9 +87,9 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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). """ @@ -102,9 +102,11 @@ def QFIM(self, W=[], LDtype="SLD"): 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 \ + 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) + DeprecationWarning, + ) #### call autoGRAPE automatically #### if self.Adam: self.alg = QJL.autoGRAPE( @@ -117,7 +119,7 @@ def QFIM(self, W=[], 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: @@ -127,8 +129,8 @@ def QFIM(self, W=[], LDtype="SLD"): 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 + 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 @@ -137,11 +139,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -155,9 +157,11 @@ def CFIM(self, M=[], W=[]): 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 \ + 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) + DeprecationWarning, + ) #### call autoGRAPE automatically #### if self.Adam: self.alg = QJL.autoGRAPE( @@ -165,7 +169,7 @@ def CFIM(self, M=[], W=[]): ) else: self.alg = QJL.autoGRAPE(self.max_episode, self.epsilon) - else: + else: if self.Adam: self.alg = QJL.GRAPE( self.max_episode, self.epsilon, self.beta1, self.beta2 @@ -177,7 +181,7 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - GRAPE and auto-GRAPE are not available when the objective function is HCRB. + GRAPE and auto-GRAPE are not available when the objective function is HCRB. Supported methods are PSO, DE and DDPG. Parameters @@ -202,30 +206,30 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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. + -- 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. + -- 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). + -- 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 + **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). """ @@ -242,11 +246,10 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): 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 - ) + self.max_episode, self.epsilon, self.beta1, self.beta2 + ) else: self.alg = QJL.GRAPE(self.max_episode, self.epsilon) diff --git a/quanestimation/ControlOpt/PSO_Copt.py b/quanestimation/ControlOpt/PSO_Copt.py index 2fdf8d77..31454f11 100644 --- a/quanestimation/ControlOpt/PSO_Copt.py +++ b/quanestimation/ControlOpt/PSO_Copt.py @@ -7,11 +7,11 @@ 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 + -- 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` @@ -21,21 +21,21 @@ class PSO_Copt(Control.ControlSystem): -- Initial guesses of control coefficients. > **max_episode:** `int or list` - -- If it is an integer, for example max_episode=1000, it means the + -- 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 + 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 + -- 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 + -- The exploitation weight that attracts the particle to the best position in the neighborhood, also known as social learning factor. > **seed:** `int` @@ -45,9 +45,9 @@ class PSO_Copt(Control.ControlSystem): -- 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 + -- 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. """ @@ -64,7 +64,6 @@ def __init__( eps=1e-8, load=False, ): - Control.ControlSystem.__init__(self, savefile, ctrl0, eps, load) is_int = isinstance(max_episode, int) @@ -77,8 +76,8 @@ def __init__( 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 + 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 @@ -87,12 +86,12 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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, ) + ini_particle = (self.ctrl0,) self.alg = QJL.PSO( self.max_episode, self.p_num, @@ -106,8 +105,8 @@ def QFIM(self, W=[], LDtype="SLD"): 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 + 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 @@ -116,15 +115,15 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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, ) + ini_particle = (self.ctrl0,) self.alg = QJL.PSO( self.max_episode, self.p_num, @@ -138,9 +137,9 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - Choose HCRB as the objective function. + Choose HCRB as the objective function. - **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose + **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function. Parameters @@ -148,7 +147,7 @@ def HCRB(self, W=[]): > **W:** `matrix` -- Weight matrix. """ - ini_particle = (self.ctrl0, ) + ini_particle = (self.ctrl0,) self.alg = QJL.PSO( self.max_episode, self.p_num, @@ -173,30 +172,30 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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). + -- 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. + -- 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). + -- 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 + **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). """ @@ -209,5 +208,5 @@ def mintime(self, f, W=[], M=[], method="binary", target="QFIM", LDtype="SLD"): self.c1, self.c2, ) - + super().mintime(f, W, M, method, target, LDtype) diff --git a/quanestimation/ControlOpt/__init__.py b/quanestimation/ControlOpt/__init__.py index 410bdd20..ebb32eb7 100644 --- a/quanestimation/ControlOpt/__init__.py +++ b/quanestimation/ControlOpt/__init__.py @@ -12,6 +12,7 @@ from quanestimation.ControlOpt.PSO_Copt import ( PSO_Copt, ) + # from quanestimation.ControlOpt.DDPG_Copt import ( # DDPG_Copt, # ) diff --git a/quanestimation/MeasurementOpt/AD_Mopt.py b/quanestimation/MeasurementOpt/AD_Mopt.py index 43d7b5bb..c902b8b9 100644 --- a/quanestimation/MeasurementOpt/AD_Mopt.py +++ b/quanestimation/MeasurementOpt/AD_Mopt.py @@ -7,11 +7,11 @@ 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 + -- 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` @@ -22,7 +22,7 @@ class AD_Mopt(Measurement.MeasurementSystem): > **max_episode:** `int` -- The number of episodes. - + > **epsilon:** `float` -- Learning rate. @@ -36,7 +36,7 @@ class AD_Mopt(Measurement.MeasurementSystem): -- Machine epsilon. > **load:** `bool` - -- Whether or not to load measurements in the current location. + -- 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. """ @@ -56,9 +56,8 @@ def __init__( eps=1e-8, load=False, ): - Measurement.MeasurementSystem.__init__( - self, mtype, minput, savefile, measurement0, seed, eps, load + self, mtype, minput, savefile, measurement0, seed, eps, load ) self.Adam = Adam @@ -71,16 +70,14 @@ def __init__( self.seed = seed if self.Adam: - self.alg = QJL.AD( - self.max_episode, self.epsilon, self.beta1, self.beta2 - ) + 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 + 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 @@ -88,7 +85,7 @@ def CFIM(self, W=[]): > **W:** `matrix` -- Weight matrix. """ - + if self.mtype == "projection": raise ValueError( "AD is not available when mtype is projection. Supported methods are 'PSO' and 'DE'.", diff --git a/quanestimation/MeasurementOpt/DE_Mopt.py b/quanestimation/MeasurementOpt/DE_Mopt.py index 62f4b4a4..6ea74775 100644 --- a/quanestimation/MeasurementOpt/DE_Mopt.py +++ b/quanestimation/MeasurementOpt/DE_Mopt.py @@ -7,11 +7,11 @@ 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 + -- 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` @@ -22,7 +22,7 @@ class DE_Mopt(Measurement.MeasurementSystem): > **max_episode:** `int` -- The number of episodes. - + > **c:** `float` -- Mutation constant. @@ -36,7 +36,7 @@ class DE_Mopt(Measurement.MeasurementSystem): -- Machine epsilon. > **load:** `bool` - -- Whether or not to load measurements in the current location. + -- 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. """ @@ -55,7 +55,6 @@ def __init__( eps=1e-8, load=False, ): - Measurement.MeasurementSystem.__init__( self, mtype, minput, savefile, measurement0, seed, eps, load ) @@ -67,8 +66,8 @@ def __init__( 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 + 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 diff --git a/quanestimation/MeasurementOpt/MeasurementStruct.py b/quanestimation/MeasurementOpt/MeasurementStruct.py index c1becc70..3fceddb0 100644 --- a/quanestimation/MeasurementOpt/MeasurementStruct.py +++ b/quanestimation/MeasurementOpt/MeasurementStruct.py @@ -11,52 +11,51 @@ 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. + 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 @@ -67,16 +66,29 @@ def __init__(self, mtype, minput, savefile, measurement0, seed, eps, load): def load_save(self, mnum, max_episode): if os.path.exists("measurements.dat"): - fl = h5py.File("measurements.dat",'r') + 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)]) + 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)]) + mdata = np.array( + [np.array(fl[dset[j]]).view("complex") for j in range(mnum)] + ) np.save("measurements", mdata) - else: pass + else: + pass - def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): + def dynamics( + self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm" + ): r""" The dynamics of a density matrix is of the form @@ -143,7 +155,7 @@ def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="ex 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))] @@ -214,17 +226,19 @@ def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="ex 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 + B=self.B, + POVM_basis=self.povm_basis, + M_num=self.M_num, + seed=self.seed, ) elif self.minput[0] == "rotation": @@ -307,7 +321,8 @@ def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="ex ) for i in range(Hc_num - ctrl_num): ctrl = np.concatenate((ctrl, np.zeros(len(ctrl[0])))) - else: pass + else: + pass if len(ctrl[0]) == 1: if type(H0) == np.ndarray: @@ -329,7 +344,8 @@ def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="ex if type(H0) != np.ndarray: #### linear interpolation #### f = interp1d(self.tspan, H0, axis=0) - else: pass + 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]) @@ -337,8 +353,10 @@ def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="ex 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 + else: + pass + else: + pass if type(H0) == np.ndarray: H0 = np.array(H0, dtype=np.complex128) @@ -386,7 +404,7 @@ def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="ex self.tspan, self.decay_opt, self.gamma, - dyn_method = self.dyn_method, + dyn_method=self.dyn_method, ) else: self.dynamic = QJL.Lindblad( @@ -394,19 +412,18 @@ def dynamics(self, tspan, rho0, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="ex self.Hamiltonian_derivative, self.rho0, self.tspan, - dyn_method = self.dyn_method, + dyn_method=self.dyn_method, ) self.output = QJL.Output(self.opt, save=self.savefile) - - self.dynamics_type = "dynamics" + 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} + \end{align} where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator. @@ -419,8 +436,8 @@ def Kraus(self, rho0, K, dK): -- 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 + -- 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) @@ -511,7 +528,10 @@ def Kraus(self, rho0, K, dK): 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 + B=self.B, + POVM_basis=self.povm_basis, + M_num=self.M_num, + seed=self.seed, ) elif self.minput[0] == "rotation": @@ -577,8 +597,8 @@ def Kraus(self, rho0, K, dK): 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 + 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 @@ -596,25 +616,22 @@ def CFIM(self, W=[]): W = np.eye(self.para_num) self.W = W else: - raise ValueError( - "Supported type of dynamics are Lindblad and Kraus." - ) + raise ValueError("Supported type of dynamics are Lindblad and Kraus.") - self.obj = QJL.CFIM_obj( - [], self.W, self.eps, self.para_type - ) #### m=[] + 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] + max_num = ( + self.max_episode if type(self.max_episode) == int else self.max_episode[0] + ) self.load_save(self.M_num, max_num) def MeasurementOpt( mtype="projection", minput=[], savefile=False, method="DE", **kwargs ): - if method == "AD": return Measure.AD_Mopt(mtype, minput, savefile=savefile, **kwargs) elif method == "PSO": diff --git a/quanestimation/MeasurementOpt/PSO_Mopt.py b/quanestimation/MeasurementOpt/PSO_Mopt.py index b6b1df94..2c9bb0c4 100644 --- a/quanestimation/MeasurementOpt/PSO_Mopt.py +++ b/quanestimation/MeasurementOpt/PSO_Mopt.py @@ -7,11 +7,11 @@ 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 + -- 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` @@ -21,21 +21,21 @@ class PSO_Mopt(Measurement.MeasurementSystem): -- Initial guesses of measurements. > **max_episode:** `int or list` - -- If it is an integer, for example max_episode=1000, it means the + -- 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 + 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 + -- 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 + -- The exploitation weight that attracts the particle to the best position in the neighborhood, also known as social learning factor. > **seed:** `int` @@ -45,7 +45,7 @@ class PSO_Mopt(Measurement.MeasurementSystem): -- Machine epsilon. > **load:** `bool` - -- Whether or not to load measurements in the current location. + -- 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. """ @@ -65,7 +65,6 @@ def __init__( eps=1e-8, load=False, ): - Measurement.MeasurementSystem.__init__( self, mtype, minput, savefile, measurement0, seed, eps, load ) @@ -80,8 +79,8 @@ def __init__( 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 + 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 @@ -98,5 +97,5 @@ def CFIM(self, W=[]): self.c1, self.c2, ) - + super().CFIM(W) diff --git a/quanestimation/Parameterization/GeneralDynamics.py b/quanestimation/Parameterization/GeneralDynamics.py index 473d503c..6cc7d2cd 100644 --- a/quanestimation/Parameterization/GeneralDynamics.py +++ b/quanestimation/Parameterization/GeneralDynamics.py @@ -46,7 +46,6 @@ class Lindblad: """ def __init__(self, tspan, rho0, H0, dH, decay=[], Hc=[], ctrl=[]): - self.tspan = tspan self.rho0 = np.array(rho0, dtype=np.complex128) @@ -113,8 +112,8 @@ def expm(self): $$ \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 + + where $\Delta t$ is the time interval and $\rho_{j-1}$ is the density matrix at the previous time step. The derivative $\partial_{\textbf{x}}\rho_j$ is calculated as: @@ -124,14 +123,14 @@ def expm(self): + e^{\Delta t \mathcal{L}} (\partial_{\textbf{x}}\rho_{j-1}) $$ - Returns: + Returns: (tuple): - rho (list): + rho (list): Density matrices at each time point in `tspan`. - drho (list): - Derivatives of the density matrices with respect to the unknown parameters. - `drho[i][j]` is the derivative of the density matrix at the i-th time point + drho (list): + Derivatives of the density matrices with respect to the unknown parameters. + `drho[i][j]` is the derivative of the density matrix at the i-th time point with respect to the j-th parameter. """ @@ -145,15 +144,22 @@ def expm(self): self.control_Hamiltonian, self.control_coefficients, ) - + # Convert Julia matrices to numpy arrays safely - rho = [np.array(rho_i) if not hasattr(rho_i, '_jl') else np.array(rho_i._jl) for rho_i in rho] + rho = [ + np.array(rho_i) if not hasattr(rho_i, "_jl") else np.array(rho_i._jl) + for rho_i in rho + ] drho = [ - [np.array(drho_ij) if not hasattr(drho_ij, '_jl') else np.array(drho_ij._jl) - for drho_ij in drho_i] + [ + np.array(drho_ij) + if not hasattr(drho_ij, "_jl") + else np.array(drho_ij._jl) + for drho_ij in drho_i + ] for drho_i in drho ] - + return rho, drho def ode(self): @@ -165,8 +171,8 @@ def ode(self): $$ \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 + + where $\Delta t$ is the time interval and $\rho_{j-1}$ is the density matrix at the previous time step. The derivative $\partial_{\textbf{x}}\rho_j$ is calculated as: @@ -178,12 +184,12 @@ def ode(self): Returns: (tuple): - rho (list): + rho (list): Density matrices at each time point in `tspan`. - drho (list): - Derivatives of the density matrices with respect to the unknown parameters. - `drho[i][j]` is the derivative of the density matrix at the i-th time point + drho (list): + Derivatives of the density matrices with respect to the unknown parameters. + `drho[i][j]` is the derivative of the density matrix at the i-th time point with respect to the j-th parameter. """ @@ -197,17 +203,24 @@ def ode(self): self.control_Hamiltonian, self.control_coefficients, ) - + # Convert Julia matrices to numpy arrays safely - rho = [np.array(rho_i) if not hasattr(rho_i, '_jl') else np.array(rho_i._jl) for rho_i in rho] + rho = [ + np.array(rho_i) if not hasattr(rho_i, "_jl") else np.array(rho_i._jl) + for rho_i in rho + ] drho = [ - [np.array(drho_ij) if not hasattr(drho_ij, '_jl') else np.array(drho_ij._jl) - for drho_ij in drho_i] + [ + np.array(drho_ij) + if not hasattr(drho_ij, "_jl") + else np.array(drho_ij._jl) + for drho_ij in drho_i + ] for drho_i in drho ] - + return rho, drho - + def secondorder_derivative(self, d2H): r""" Calculate the density matrix, its first derivatives, and second derivatives @@ -267,5 +280,5 @@ def secondorder_derivative(self, d2H): ) 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, d2rho diff --git a/quanestimation/Parameterization/NonDynamics.py b/quanestimation/Parameterization/NonDynamics.py index 6a3feafa..92db598a 100644 --- a/quanestimation/Parameterization/NonDynamics.py +++ b/quanestimation/Parameterization/NonDynamics.py @@ -6,32 +6,32 @@ def Kraus(rho0, K, dK): Parameterization of a quantum state using Kraus operators. The evolved density matrix $\rho$ is given by - + \begin{aligned} \rho=\sum_i K_i \rho_0 K_i^{\dagger}, \end{aligned} where $\rho_0$ is the initial density matrix and $K_i$ are the Kraus operators. - Args: - rho0 (np.array): + Args: + rho0 (np.array): Initial density matrix. - K (list): + K (list): Kraus operators. - dK (list): - Derivatives of the Kraus operators with respect to the unknown parameters to be - estimated. This is a nested list where the first index corresponds to the Kraus operator - and the second index corresponds to the parameter. For example, `dK[0][1]` is the derivative + dK (list): + Derivatives of the Kraus operators with respect to the unknown parameters to be + estimated. This is a nested list where the first index corresponds to the Kraus operator + and the second index corresponds to the parameter. For example, `dK[0][1]` is the derivative of the second Kraus operator with respect to the first parameter. Returns: (tuple): - rho (np.array): + rho (np.array): Evolved density matrix. - - drho (list): - Derivatives of the evolved density matrix with respect to the unknown parameters. - Each element in the list is a matrix representing the partial derivative of $\rho$ with + + drho (list): + Derivatives of the evolved density matrix with respect to the unknown parameters. + Each element in the list is a matrix representing the partial derivative of $\rho$ with respect to one parameter. """ @@ -40,6 +40,17 @@ def Kraus(rho0, K, dK): dK_reshape = [[dK[i][j] for i in range(k_num)] for j in range(para_num)] rho = sum([np.dot(Ki, np.dot(rho0, Ki.conj().T)) for Ki in K]) - drho = [sum([(np.dot(dKi, np.dot(rho0, Ki.conj().T))+ np.dot(Ki, np.dot(rho0, dKi.conj().T))) for (Ki, dKi) in zip(K, dKj)]) for dKj in dK_reshape] + drho = [ + sum( + [ + ( + np.dot(dKi, np.dot(rho0, Ki.conj().T)) + + np.dot(Ki, np.dot(rho0, dKi.conj().T)) + ) + for (Ki, dKi) in zip(K, dKj) + ] + ) + for dKj in dK_reshape + ] return rho, drho diff --git a/quanestimation/Parameterization/__init__.py b/quanestimation/Parameterization/__init__.py index 1123810a..4e62f4cf 100644 --- a/quanestimation/Parameterization/__init__.py +++ b/quanestimation/Parameterization/__init__.py @@ -8,5 +8,5 @@ __all__ = [ "Lindblad", "secondorder_derivative", - "Kraus", + "Kraus", ] diff --git a/quanestimation/Resource/Resource.py b/quanestimation/Resource/Resource.py index 58f6d802..4c7e8740 100644 --- a/quanestimation/Resource/Resource.py +++ b/quanestimation/Resource/Resource.py @@ -1,5 +1,6 @@ import numpy as np + def SpinSqueezing(rho, basis="Dicke", output="KU"): r""" Calculation of the spin squeezing parameter for a density matrix. @@ -19,22 +20,22 @@ def SpinSqueezing(rho, basis="Dicke", output="KU"): $$ Args: - rho (np.array): + rho (np.array): Density matrix. - basis (str, optional): + basis (str, optional): Basis to use: "Dicke" (default) or "Pauli". - output (str, optional): - Type of spin squeezing to calculate: - - "KU": Kitagawa-Ueda squeezing parameter. - - "WBIMH": Wineland et al. squeezing parameter. + output (str, optional): + Type of spin squeezing to calculate: + - "KU": Kitagawa-Ueda squeezing parameter. + - "WBIMH": Wineland et al. squeezing parameter. Returns: - (float): + (float): Spin squeezing parameter. Raises: - ValueError: If `basis` has invalid value. - ValueError: If `output` has invalid value. + ValueError: If `basis` has invalid value. + ValueError: If `output` has invalid value. """ if basis == "Pauli": @@ -42,7 +43,7 @@ def SpinSqueezing(rho, basis="Dicke", output="KU"): j = N / 2 coef = 4.0 / float(N) sp = np.array([[0.0, 1.0], [0.0, 0.0]]) - sz = np.array([[1., 0.], [0., -1.]]) + sz = np.array([[1.0, 0.0], [0.0, -1.0]]) jp = [] jz = [] for i in range(N): @@ -54,12 +55,10 @@ def SpinSqueezing(rho, basis="Dicke", output="KU"): jz_tp = np.kron(np.identity(2 ** (N - 1)), sz) else: jp_tp = np.kron( - np.identity(2 ** i), - np.kron(sp, np.identity(2 ** (N - 1 - i))) + np.identity(2**i), np.kron(sp, np.identity(2 ** (N - 1 - i))) ) jz_tp = np.kron( - np.identity(2 ** i), - np.kron(sz, np.identity(2 ** (N - 1 - i))) + np.identity(2**i), np.kron(sz, np.identity(2 ** (N - 1 - i))) ) jp.append(jp_tp) jz.append(jz_tp) @@ -67,11 +66,10 @@ def SpinSqueezing(rho, basis="Dicke", output="KU"): Jz = 0.5 * sum(jz) elif basis == "Dicke": N = len(rho) - 1 - j = N / 2 - coef = 4.0 / float(N) + j = N / 2 + coef = 4.0 / float(N) offdiag = [ - np.sqrt(float(j * (j + 1) - m * (m + 1))) - for m in np.arange(j, -j - 1, -1) + np.sqrt(float(j * (j + 1) - m * (m + 1))) for m in np.arange(j, -j - 1, -1) ][1:] # Ensure we create a complex array Jp = np.diag(offdiag, 1).astype(complex) @@ -79,12 +77,12 @@ def SpinSqueezing(rho, basis="Dicke", output="KU"): else: valid_types = ["Dicke", "Pauli"] raise ValueError( - f"Invalid basis: '{basis}'. Supported types: {', '.join(valid_types)}" - ) - + f"Invalid basis: '{basis}'. Supported types: {', '.join(valid_types)}" + ) + Jx = 0.5 * (Jp + np.conj(Jp).T) Jy = -0.5 * 1j * (Jp - np.conj(Jp).T) - + Jx_mean = np.trace(rho @ Jx) Jy_mean = np.trace(rho @ Jy) Jz_mean = np.trace(rho @ Jz) @@ -100,17 +98,18 @@ def SpinSqueezing(rho, basis="Dicke", output="KU"): costheta = Jz_mean / np.sqrt(Jx_mean**2 + Jy_mean**2 + Jz_mean**2) sintheta = np.sin(np.arccos(costheta)) cosphi = Jx_mean / np.sqrt(Jx_mean**2 + Jy_mean**2) - sinphi = (np.sin(np.arccos(cosphi)) if Jy_mean > 0 - else np.sin(2 * np.pi - np.arccos(cosphi))) - + sinphi = ( + np.sin(np.arccos(cosphi)) + if Jy_mean > 0 + else np.sin(2 * np.pi - np.arccos(cosphi)) + ) + Jn1 = -Jx * sinphi + Jy * cosphi - Jn2 = (-Jx * costheta * cosphi - - Jy * costheta * sinphi - + Jz * sintheta) + Jn2 = -Jx * costheta * cosphi - Jy * costheta * sinphi + Jz * sintheta A = np.trace(rho @ (Jn1 @ Jn1 - Jn2 @ Jn2)) B = np.trace(rho @ (Jn1 @ Jn2 + Jn2 @ Jn1)) C = np.trace(rho @ (Jn1 @ Jn1 + Jn2 @ Jn2)) - + V_minus = 0.5 * (C - np.sqrt(A**2 + B**2)) V_minus = np.real(V_minus) xi = coef * V_minus @@ -118,57 +117,57 @@ def SpinSqueezing(rho, basis="Dicke", output="KU"): if output == "KU": pass elif output == "WBIMH": - xi = (N / 2)**2 * xi / (Jx_mean**2 + Jy_mean**2 + Jz_mean**2) + xi = (N / 2) ** 2 * xi / (Jx_mean**2 + Jy_mean**2 + Jz_mean**2) else: valid_types = ["KU", "WBIMH"] raise ValueError( - f"Invalid basis: '{basis}'. Supported types: {', '.join(valid_types)}" - ) + f"Invalid basis: '{basis}'. Supported types: {', '.join(valid_types)}" + ) return xi def TargetTime(f, tspan, func, *args, **kwargs): r""" - Calculation of the time to reach a given precision limit. + Calculation of the time to reach a given precision limit. - This function finds the earliest time $t$ in `tspan` where the objective - function `func` reaches or crosses the target value $f$. The first argument + This function finds the earliest time $t$ in `tspan` where the objective + function `func` reaches or crosses the target value $f$. The first argument of func must be the time variable. Args: - f (float): + f (float): The target value of the objective function. - tspan (array): + tspan (array): Time points for the evolution. - func (callable): + func (callable): The objective function to evaluate. Must return a float. - *args (tuple): + *args (tuple): Positional arguments to pass to `func`. - **kwargs (dict): + **kwargs (dict): Keyword arguments to pass to `func`. Returns: - (float): + (float): Time to reach the given target precision. """ # Check if we're already at the target at the first point f0 = func(tspan[0], *args, **kwargs) if np.isclose(f0, f, atol=1e-8): return tspan[0] - + # Iterate through time points for i in range(1, len(tspan)): f1 = func(tspan[i], *args, **kwargs) - + # Check if we've crossed the target if (f0 - f) * (f1 - f) <= 0: return tspan[i] elif np.isclose(f1, f, atol=1e-8): return tspan[i] - + f0 = f1 - + # No crossing found print("No time is found in the given time span to reach the target.") diff --git a/quanestimation/StateOpt/AD_Sopt.py b/quanestimation/StateOpt/AD_Sopt.py index 66ff2701..db8cf4d0 100644 --- a/quanestimation/StateOpt/AD_Sopt.py +++ b/quanestimation/StateOpt/AD_Sopt.py @@ -7,10 +7,10 @@ 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 + -- 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` @@ -21,7 +21,7 @@ class AD_Sopt(State.StateSystem): > **max_episode:** `int` -- The number of episodes. - + > **epsilon:** `float` -- Learning rate. @@ -35,7 +35,7 @@ class AD_Sopt(State.StateSystem): -- Machine epsilon. > **load:** `bool` - -- Whether or not to load states in the current location. + -- 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. """ @@ -53,7 +53,6 @@ def __init__( eps=1e-8, load=False, ): - State.StateSystem.__init__(self, savefile, psi0, seed, eps, load) self.Adam = Adam @@ -66,15 +65,18 @@ def __init__( if self.Adam: self.alg = QJL.AD( - self.max_episode, self.epsilon, self.beta1, self.beta2, + 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 + 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 @@ -83,9 +85,9 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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). """ @@ -93,8 +95,8 @@ def QFIM(self, W=[], LDtype="SLD"): 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 + 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 @@ -103,11 +105,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -116,7 +118,7 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - AD is not available when the objective function is HCRB. + AD is not available when the objective function is HCRB. Supported methods are PSO, DE, DDPG and NM. Parameters @@ -124,6 +126,7 @@ def HCRB(self, W=[]): > **W:** `matrix` -- Weight matrix. """ - + raise ValueError( - "AD is not available when the objective function is HCRB. Supported methods are 'PSO', 'DE', 'NM' and 'DDPG'.") + "AD is not available when the objective function is HCRB. Supported methods are 'PSO', 'DE', 'NM' and 'DDPG'." + ) diff --git a/quanestimation/StateOpt/DDPG_Sopt.py b/quanestimation/StateOpt/DDPG_Sopt.py index de302740..10888b6d 100644 --- a/quanestimation/StateOpt/DDPG_Sopt.py +++ b/quanestimation/StateOpt/DDPG_Sopt.py @@ -7,11 +7,11 @@ class DDPG_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 + -- 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` @@ -19,7 +19,7 @@ class DDPG_Sopt(State.StateSystem): > **max_episode:** `int` -- The number of episodes. - + > **layer_num:** `int` -- The number of layers (include the input and output layer). @@ -33,7 +33,7 @@ class DDPG_Sopt(State.StateSystem): -- Machine epsilon. > **load:** `bool` - -- Whether or not to load states in the current location. + -- 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. """ @@ -49,7 +49,6 @@ def __init__( eps=1e-8, load=False, ): - State.StateSystem.__init__(self, savefile, psi0, seed, eps, load) self.max_episode = max_episode @@ -58,14 +57,12 @@ def __init__( self.seed = seed - self.alg = QJL.DDPG( - self.max_episode, self.layer_num, self.layer_dim, self.seed - ) + self.alg = QJL.DDPG(self.max_episode, self.layer_num, self.layer_dim, self.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 + 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 @@ -74,9 +71,9 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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). """ @@ -84,8 +81,8 @@ def QFIM(self, W=[], LDtype="SLD"): 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 + 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 @@ -94,11 +91,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -107,9 +104,9 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - Choose HCRB as the objective function. + Choose HCRB as the objective function. - **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose + **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function. Parameters diff --git a/quanestimation/StateOpt/DE_Sopt.py b/quanestimation/StateOpt/DE_Sopt.py index 561dce0e..98129ec2 100644 --- a/quanestimation/StateOpt/DE_Sopt.py +++ b/quanestimation/StateOpt/DE_Sopt.py @@ -7,11 +7,11 @@ 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 + -- 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` @@ -22,7 +22,7 @@ class DE_Sopt(State.StateSystem): > **max_episode:** `int` -- The number of episodes. - + > **c:** `float` -- Mutation constant. @@ -36,7 +36,7 @@ class DE_Sopt(State.StateSystem): -- Machine epsilon. > **load:** `bool` - -- Whether or not to load states in the current location. + -- 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. """ @@ -53,7 +53,6 @@ def __init__( eps=1e-8, load=False, ): - State.StateSystem.__init__(self, savefile, psi0, seed, eps, load) self.p_num = p_num @@ -64,8 +63,8 @@ def __init__( 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 + 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 @@ -74,9 +73,9 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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,) @@ -91,8 +90,8 @@ def QFIM(self, W=[], LDtype="SLD"): 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 + 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 @@ -101,11 +100,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -121,9 +120,9 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - Choose HCRB as the objective function. + Choose HCRB as the objective function. - **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose + **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function. Parameters diff --git a/quanestimation/StateOpt/NM_Sopt.py b/quanestimation/StateOpt/NM_Sopt.py index f6ea9d74..aad82f8e 100644 --- a/quanestimation/StateOpt/NM_Sopt.py +++ b/quanestimation/StateOpt/NM_Sopt.py @@ -7,11 +7,11 @@ class NM_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 + -- 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` @@ -22,7 +22,7 @@ class NM_Sopt(State.StateSystem): > **max_episode:** `int` -- The number of episodes. - + > **ar:** `float` -- Reflection constant. @@ -42,7 +42,7 @@ class NM_Sopt(State.StateSystem): -- Machine epsilon. > **load:** `bool` - -- Whether or not to load states in the current location. + -- 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. """ @@ -61,7 +61,6 @@ def __init__( eps=1e-8, load=False, ): - State.StateSystem.__init__(self, savefile, psi0, seed, eps, load) self.p_num = p_num @@ -74,8 +73,8 @@ def __init__( 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 + 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 @@ -84,9 +83,9 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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_state = self.psi @@ -104,8 +103,8 @@ def QFIM(self, W=[], LDtype="SLD"): 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 + 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 @@ -114,11 +113,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -137,9 +136,9 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - Choose HCRB as the objective function. + Choose HCRB as the objective function. - **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose + **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function. Parameters diff --git a/quanestimation/StateOpt/PSO_Sopt.py b/quanestimation/StateOpt/PSO_Sopt.py index 954426b3..58cf839f 100644 --- a/quanestimation/StateOpt/PSO_Sopt.py +++ b/quanestimation/StateOpt/PSO_Sopt.py @@ -7,10 +7,10 @@ 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 + -- 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` @@ -20,21 +20,21 @@ class PSO_Sopt(State.StateSystem): -- Initial guesses of states. > **max_episode:** `int or list` - -- If it is an integer, for example max_episode=1000, it means the + -- 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 + 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 + -- 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 + -- The exploitation weight that attracts the particle to the best position in the neighborhood, also known as social learning factor. > **seed:** `int` @@ -62,7 +62,6 @@ def __init__( eps=1e-8, load=False, ): - State.StateSystem.__init__(self, savefile, psi0, seed, eps, load) """ @@ -111,8 +110,8 @@ def __init__( 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 + 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 @@ -121,9 +120,9 @@ def QFIM(self, W=[], LDtype="SLD"): -- 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). + -- 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,) @@ -140,8 +139,8 @@ def QFIM(self, W=[], LDtype="SLD"): 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 + 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 @@ -150,11 +149,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -172,9 +171,9 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - Choose HCRB as the objective function. + Choose HCRB as the objective function. - **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose + **Note:** in single parameter estimation, HCRB is equivalent to QFI, please choose QFI as the objective function. Parameters @@ -191,5 +190,5 @@ def HCRB(self, W=[]): self.c1, self.c2, ) - + super().HCRB(W) diff --git a/quanestimation/StateOpt/RI_Sopt.py b/quanestimation/StateOpt/RI_Sopt.py index 04c8607b..db26fcd6 100644 --- a/quanestimation/StateOpt/RI_Sopt.py +++ b/quanestimation/StateOpt/RI_Sopt.py @@ -7,11 +7,11 @@ 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 + -- 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` @@ -27,7 +27,7 @@ class RI_Sopt(State.StateSystem): -- Machine epsilon. > **load:** `bool` - -- Whether or not to load states in the current location. + -- 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. """ @@ -41,7 +41,6 @@ def __init__( eps=1e-8, load=False, ): - State.StateSystem.__init__(self, savefile, psi0, seed, eps, load) self.max_episode = max_episode @@ -49,7 +48,7 @@ def __init__( def QFIM(self, W=[], LDtype="SLD"): r""" - Choose QFI as the objective function. + Choose QFI as the objective function. Parameters ---------- @@ -64,8 +63,10 @@ def QFIM(self, W=[], LDtype="SLD"): self.max_episode, ) if self.dynamics_type != "Kraus": - raise ValueError("Only the parameterization with Kraus operators is available.") - + raise ValueError( + "Only the parameterization with Kraus operators is available." + ) + if LDtype == "SLD": super().QFIM(W, LDtype) else: @@ -73,7 +74,7 @@ def QFIM(self, W=[], LDtype="SLD"): def CFIM(self, M=[], W=[]): """ - Choose CFIM as the objective function. + Choose CFIM as the objective function. **Note:** CFIM is not available. @@ -81,7 +82,7 @@ def CFIM(self, M=[], W=[]): ---------- > **M:** `list` -- POVM. - + > **W:** `matrix` -- Weight matrix. """ @@ -89,7 +90,7 @@ def CFIM(self, M=[], W=[]): def HCRB(self, W=[]): """ - Choose HCRB as the objective function. + Choose HCRB as the objective function. **Note:** Here HCRB is not available. diff --git a/quanestimation/StateOpt/StateStruct.py b/quanestimation/StateOpt/StateStruct.py index 6aa108a9..6a21c045 100644 --- a/quanestimation/StateOpt/StateStruct.py +++ b/quanestimation/StateOpt/StateStruct.py @@ -15,9 +15,9 @@ class StateSystem: ---------- > **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 + 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` @@ -36,7 +36,6 @@ class StateSystem: """ def __init__(self, savefile, psi0, seed, eps, load): - self.savefile = savefile self.psi0 = psi0 self.psi = psi0 @@ -49,14 +48,17 @@ def __init__(self, savefile, psi0, seed, eps, load): def load_save(self, max_episode): if os.path.exists("states.dat"): - fl = h5py.File("states.dat",'r') + 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)]) + psi = np.array( + [np.array(fl[dset[i]]).view("complex") for i in range(max_episode)] + ) else: - psi = np.array(dset).view('complex') + psi = np.array(dset).view("complex") np.save("states", psi) - else: pass + else: + pass def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): r""" @@ -137,7 +139,8 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): ) for i in range(Hc_num - ctrl_num): ctrl = np.concatenate((ctrl, np.zeros(len(ctrl[0])))) - else: pass + else: + pass if len(ctrl[0]) == 1: if type(H0) == np.ndarray: @@ -161,7 +164,8 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): if type(H0) != np.ndarray: #### linear interpolation #### f = interp1d(self.tspan, H0, axis=0) - else: pass + 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]) @@ -169,8 +173,10 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): 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 + else: + pass + else: + pass if type(H0) == np.ndarray: H0 = np.array(H0, dtype=np.complex128) @@ -196,7 +202,7 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): 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) @@ -205,11 +211,13 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): 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) + 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!") @@ -234,7 +242,7 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): self.tspan, self.decay_opt, self.gamma, - dyn_method = self.dyn_method, + dyn_method=self.dyn_method, ) else: self.dynamic = QJL.Lindblad( @@ -242,7 +250,7 @@ def dynamics(self, tspan, H0, dH, Hc=[], ctrl=[], decay=[], dyn_method="expm"): self.Hamiltonian_derivative, list(self.psi0), self.tspan, - dyn_method = self.dyn_method, + dyn_method=self.dyn_method, ) self.output = QJL.Output(self.opt, save=self.savefile) @@ -257,7 +265,7 @@ def Kraus(self, K, dK): The parameterization of a state is \begin{align} \rho=\sum_i K_i\rho_0K_i^{\dagger}, - \end{align} + \end{align} where $\rho$ is the evolved density matrix, $K_i$ is the Kraus operator. @@ -267,8 +275,8 @@ def Kraus(self, K, dK): -- 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 + -- Derivatives of the Kraus operators on the unknown parameters to be + estimated. For example, dK[0] is the derivative vector on the first parameter. """ @@ -290,7 +298,7 @@ def Kraus(self, K, dK): 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) + 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] @@ -307,8 +315,8 @@ def Kraus(self, K, dK): 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 + 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 @@ -342,21 +350,23 @@ def QFIM(self, W=[], LDtype="SLD"): else: pass - self.obj = QJL.QFIM_obj( - self.W, self.eps, self.para_type, LDtype - ) + 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] + 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 + 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 @@ -365,11 +375,11 @@ def CFIM(self, M=[], W=[]): -- Weight matrix. > **M:** `list of matrices` - -- A set of positive operator-valued measure (POVM). The default measurement + -- 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 + **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). """ @@ -394,31 +404,37 @@ def CFIM(self, M=[], W=[]): ) QJL.run(system) - max_num = self.max_episode if isinstance(self.max_episode, int) else self.max_episode[0] + 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. + Choose HCRB as the objective function. - **Notes:** (1) In single parameter estimation, HCRB is equivalent to QFI, please + **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` + > **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 + 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 == []: @@ -428,24 +444,26 @@ def HCRB(self, W=[]): raise ValueError( "In single parameter scenario, HCRB is equivalent to QFI. Please choose QFIM as the target function for control optimization", ) - else: pass + else: + pass else: - raise ValueError( - "Supported type of dynamics are Lindblad and Kraus." - ) + 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 + 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] + max_num = ( + self.max_episode + if isinstance(self.max_episode, int) + else self.max_episode[0] + ) self.load_save(max_num) def StateOpt(savefile=False, method="AD", **kwargs): - if method == "AD": return stateoptimize.AD_Sopt(savefile=savefile, **kwargs) elif method == "PSO": @@ -453,9 +471,7 @@ def StateOpt(savefile=False, method="AD", **kwargs): elif method == "DE": return stateoptimize.DE_Sopt(savefile=savefile, **kwargs) elif method == "DDPG": - raise ValueError( - "'DDPG' is currently deprecated and will be fixed soon." - ) + raise ValueError("'DDPG' is currently deprecated and will be fixed soon.") # return stateoptimize.DDPG_Sopt(savefile=savefile, **kwargs) elif method == "NM": return stateoptimize.NM_Sopt(savefile=savefile, **kwargs) diff --git a/quanestimation/StateOpt/__init__.py b/quanestimation/StateOpt/__init__.py index 12f235d4..0477dd43 100644 --- a/quanestimation/StateOpt/__init__.py +++ b/quanestimation/StateOpt/__init__.py @@ -12,6 +12,7 @@ from quanestimation.StateOpt.PSO_Sopt import ( PSO_Sopt, ) + # from quanestimation.StateOpt.DDPG_Sopt import ( # DDPG_Sopt, # ) diff --git a/quanestimation/__init__.py b/quanestimation/__init__.py index e262cfaf..60b41a7d 100644 --- a/quanestimation/__init__.py +++ b/quanestimation/__init__.py @@ -20,7 +20,8 @@ SLD, ) from quanestimation.AsymptoticBound.AnalogCramerRao import ( - HCRB, NHB, + HCRB, + NHB, ) from quanestimation.BayesianBound.BayesCramerRao import ( BCFIM, @@ -34,12 +35,7 @@ from quanestimation.BayesianBound.ZivZakai import ( QZZB, ) -from quanestimation.BayesianBound.BayesEstimation import ( - Bayes, - MLE, - BCB, - BayesCost -) +from quanestimation.BayesianBound.BayesEstimation import Bayes, MLE, BCB, BayesCost from quanestimation.Common.Common import ( load_julia, @@ -81,6 +77,7 @@ from quanestimation.ControlOpt.PSO_Copt import ( PSO_Copt, ) + # from quanestimation.ControlOpt.DDPG_Copt import ( # DDPG_Copt, # ) @@ -126,6 +123,7 @@ from quanestimation.StateOpt.PSO_Sopt import ( PSO_Sopt, ) + # from quanestimation.StateOpt.DDPG_Sopt import ( # DDPG_Sopt, # ) diff --git a/setup.py b/setup.py index d9ad9f7f..6477823b 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ install_requires=requirements, license="BSD 3-Clause License", long_description=readme + "\n\n" + history, - long_description_content_type = "text/markdown", + long_description_content_type="text/markdown", include_package_data=True, keywords="quanestimation", name="quanestimation", diff --git a/test/conftest.py b/test/conftest.py index a57ffc6c..75ad847f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,2 +1,5 @@ import os -os.environ["QuanEstimation_COMPILE"] = "n" # Disable Julia project compilation for testing \ No newline at end of file + +os.environ[ + "QuanEstimation_COMPILE" +] = "n" # Disable Julia project compilation for testing diff --git a/test/test_AnalogCramerRao.py b/test/test_AnalogCramerRao.py index c1b893f9..96dfc8d5 100644 --- a/test/test_AnalogCramerRao.py +++ b/test/test_AnalogCramerRao.py @@ -1,9 +1,7 @@ import pytest import numpy as np -from quanestimation.AsymptoticBound.AnalogCramerRao import ( - HCRB, - NHB -) +from quanestimation.AsymptoticBound.AnalogCramerRao import HCRB, NHB + def test_HCRB_NHB() -> None: """ @@ -13,35 +11,33 @@ def test_HCRB_NHB() -> None: # Parameterized state theta = np.pi / 3 phi = 0.0 - psi = np.array( - [[np.cos(theta / 2)], - [np.sin(theta / 2) * np.exp(1j * phi)]] - ) + psi = np.array([[np.cos(theta / 2)], [np.sin(theta / 2) * np.exp(1j * phi)]]) rho = psi @ psi.conj().T - W = np.array( - [[1.0, 0], - [0, np.sin(theta) ** 2]] - ) - + W = np.array([[1.0, 0], [0, np.sin(theta) ** 2]]) + # Density matrix derivatives - drho_theta = np.array([ - [-np.sin(theta) / 2, np.cos(theta) * np.exp(-1j * phi) / 2], - [np.cos(theta) * np.exp(1j * phi) / 2, np.sin(theta) / 2] - ]) - drho_phi = np.array([ - [0, -1j * np.sin(theta) / 2 * np.exp(-1j * phi)], - [1j * np.sin(theta) / 2 * np.exp(1j * phi), 0] - ]) + drho_theta = np.array( + [ + [-np.sin(theta) / 2, np.cos(theta) * np.exp(-1j * phi) / 2], + [np.cos(theta) * np.exp(1j * phi) / 2, np.sin(theta) / 2], + ] + ) + drho_phi = np.array( + [ + [0, -1j * np.sin(theta) / 2 * np.exp(-1j * phi)], + [1j * np.sin(theta) / 2 * np.exp(1j * phi), 0], + ] + ) drho = [drho_theta, drho_phi] - + # Calculate bounds result_hcrb = HCRB(rho, drho, W) result_nhb = NHB(rho, drho, W) - + # Expected results expected_hcrb = 4 expected_nhb = 4 - + # Verify calculations assert np.allclose(result_hcrb, expected_hcrb) assert np.allclose(result_nhb, expected_nhb) @@ -53,56 +49,39 @@ def test_HCRB_NHB_invalid_input() -> None: Checks TypeError raised for invalid density matrix. """ # Invalid density matrix - rho = np.array( - [[0.5, 0.5], - [0.5, 0.5]] - ) - drho_invalid = np.array( - [[1, 2], - [2, 1]] - ) # Dummy derivative - W = np.array( - [[1.0, 0], - [0, 1]] - ) # Dummy weight matrix - + rho = np.array([[0.5, 0.5], [0.5, 0.5]]) + drho_invalid = np.array([[1, 2], [2, 1]]) # Dummy derivative + W = np.array([[1.0, 0], [0, 1]]) # Dummy weight matrix + with pytest.raises(TypeError): HCRB(rho, drho_invalid, W) with pytest.raises(TypeError): NHB(rho, drho_invalid, W) + def test_HCRB_print(capfd): """ Test print statements in HCRB. Checks correct messages printed for special cases. """ - rho = np.array( - [[0.5, 0.5], - [0.5, 0.5]] - ) - + rho = np.array([[0.5, 0.5], [0.5, 0.5]]) + # Single parameter case - drho1 = [np.array( - [[1, 2], - [2, 1]] - )] - W = np.array( - [[1.0, 0], - [0, 1]] - ) + drho1 = [np.array([[1, 2], [2, 1]])] + W = np.array([[1.0, 0], [0, 1]]) HCRB(rho, drho1, W) out, _ = capfd.readouterr() - assert "In single parameter scenario, HCRB is equivalent to QFI. Returning QFI value." in out - + assert ( + "In single parameter scenario, HCRB is equivalent to QFI. Returning QFI value." + in out + ) + # Rank-one weight matrix case - drho2 = [ - np.array([[1, 0], [0, 0]]), - np.array([[0, 1], [1, 0]]) - ] - W1 = np.array( - [[1.0, 0], - [0, 0]] - ) # Rank-one weight matrix + drho2 = [np.array([[1, 0], [0, 0]]), np.array([[0, 1], [1, 0]])] + W1 = np.array([[1.0, 0], [0, 0]]) # Rank-one weight matrix HCRB(rho, drho2, W1) out, _ = capfd.readouterr() - assert "For rank-one weight matrix, HCRB is equivalent to QFIM. Returning Tr(W @ inv(QFIM))." in out + assert ( + "For rank-one weight matrix, HCRB is equivalent to QFIM. Returning Tr(W @ inv(QFIM))." + in out + ) diff --git a/test/test_BayesCramerRao.py b/test/test_BayesCramerRao.py index 2fcd1b73..a58351f0 100644 --- a/test/test_BayesCramerRao.py +++ b/test/test_BayesCramerRao.py @@ -4,18 +4,19 @@ from quanestimation.BayesianBound.BayesCramerRao import ( BCFIM, BQFIM, - BCRB, - VTB, - BQCRB, - QVTB + BCRB, + VTB, + BQCRB, + QVTB, ) from quanestimation.Parameterization.GeneralDynamics import Lindblad + def test_bayesian_bound_singleparameter() -> None: """ - Test function for Bayesian bounds in quantum estimation for single parameter + Test function for Bayesian bounds in quantum estimation for single parameter scenario. - + This function tests various Bayesian bounds including: - Bayesian Cramer-Rao Bound (BCRB) - Van Trees Bound (VTB) @@ -26,133 +27,198 @@ def test_bayesian_bound_singleparameter() -> None: """ # Initial state rho0 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) - + # Free Hamiltonian parameters b_val, omega0 = 0.5 * np.pi, 1.0 sigma_x = np.array([[0.0, 1.0], [1.0, 0.0]]) sigma_z = np.array([[1.0, 0.0], [0.0, -1.0]]) - + # Hamiltonian function - hamiltonian_func = lambda x: 0.5 * b_val * omega0 * ( - sigma_x * np.cos(x) + sigma_z * np.sin(x) + hamiltonian_func = ( + lambda x: 0.5 * b_val * omega0 * (sigma_x * np.cos(x) + sigma_z * np.sin(x)) ) - + # Derivative of Hamiltonian d_hamiltonian_func = lambda x: [ 0.5 * b_val * omega0 * (-sigma_x * np.sin(x) + sigma_z * np.cos(x)) ] - + # Prior distribution parameters x_values = np.linspace(-0.5 * np.pi, 0.5 * np.pi, 100) mu_val, eta_val = 0.0, 0.2 - + # Probability density function and its derivative - prob_density = lambda x, mu, eta: np.exp( - -(x - mu) ** 2 / (2 * eta ** 2) - ) / (eta * np.sqrt(2 * np.pi)) - + prob_density = lambda x, mu, eta: np.exp(-((x - mu) ** 2) / (2 * eta**2)) / ( + eta * np.sqrt(2 * np.pi) + ) + d_prob_density = lambda x, mu, eta: -( - (x - mu) * np.exp(-(x - mu) ** 2 / (2 * eta ** 2)) - ) / (eta ** 3 * np.sqrt(2 * np.pi)) - + (x - mu) * np.exp(-((x - mu) ** 2) / (2 * eta**2)) + ) / (eta**3 * np.sqrt(2 * np.pi)) + prob_values = np.array([prob_density(x_val, mu_val, eta_val) for x_val in x_values]) - d_prob_values = np.array([d_prob_density(x_val, mu_val, eta_val) for x_val in x_values]) - + d_prob_values = np.array( + [d_prob_density(x_val, mu_val, eta_val) for x_val in x_values] + ) + # Normalize the distribution norm_factor = simpson(prob_values, x_values) prob_normalized = prob_values / norm_factor d_prob_normalized = d_prob_values / norm_factor - + # Time evolution parameters time_span = np.linspace(0.0, 1.0, 50) - + # Prepare arrays for states and derivatives final_states = [] d_final_states = [] - + # Evolve the system for each parameter value for x_values_idx in x_values: hamiltonian = hamiltonian_func(x_values_idx) d_hamiltonian = d_hamiltonian_func(x_values_idx) - + dynamics = Lindblad(time_span, rho0, hamiltonian, d_hamiltonian) states, d_states = dynamics.expm() - + final_states.append(states[-1]) - d_final_states.append(d_states[-1]) + d_final_states.append(d_states[-1]) # Test BCFIM - cfim = BCFIM([x_values], prob_normalized, final_states, d_final_states, M = [], eps = 1e-8) + cfim = BCFIM( + [x_values], prob_normalized, final_states, d_final_states, M=[], eps=1e-8 + ) expected_cfim = 1.5342635936313218 - assert np.allclose(cfim, expected_cfim, atol = 1e-3) + assert np.allclose(cfim, expected_cfim, atol=1e-3) with pytest.raises(TypeError): - cfim = BCFIM([x_values], prob_normalized, final_states, d_final_states, M = 1., eps = 1e-8) + cfim = BCFIM( + [x_values], prob_normalized, final_states, d_final_states, M=1.0, eps=1e-8 + ) # Test BQFIM - qfim = BQFIM([x_values], prob_normalized, final_states, d_final_states, LDtype = "SLD", eps = 1e-8) + qfim = BQFIM( + [x_values], + prob_normalized, + final_states, + d_final_states, + LDtype="SLD", + eps=1e-8, + ) expected_qfim = 1.9629583923945833 - assert np.allclose(qfim, expected_qfim, atol = 1e-3) + assert np.allclose(qfim, expected_qfim, atol=1e-3) # Test BCRB type 1 - bcrb1 = BCRB([x_values], prob_normalized, [], final_states, d_final_states, M = [], btype = 1) + bcrb1 = BCRB( + [x_values], prob_normalized, [], final_states, d_final_states, M=[], btype=1 + ) expected_bcrb1 = 0.654654507602925 - assert np.allclose(bcrb1, expected_bcrb1, atol = 1e-3) + assert np.allclose(bcrb1, expected_bcrb1, atol=1e-3) # Test BCRB type 2 - bcrb2 = BCRB([x_values], prob_normalized, [], final_states, d_final_states, M = [], btype = 2) + bcrb2 = BCRB( + [x_values], prob_normalized, [], final_states, d_final_states, M=[], btype=2 + ) expected_bcrb2 = 0.651778484577857 - assert np.allclose(bcrb2, expected_bcrb2, atol = 1e-3) - + assert np.allclose(bcrb2, expected_bcrb2, atol=1e-3) + # Test BCRB type 3 - bcrb3 = BCRB([x_values], prob_normalized, d_prob_normalized, final_states, d_final_states, M = [], btype = 3) + bcrb3 = BCRB( + [x_values], + prob_normalized, + d_prob_normalized, + final_states, + d_final_states, + M=[], + btype=3, + ) expected_bcrb3 = 0.16522254719803486 - assert np.allclose(bcrb3, expected_bcrb3, atol = 1e-3) + assert np.allclose(bcrb3, expected_bcrb3, atol=1e-3) # Test Van Trees Bound - vtb = VTB([x_values], prob_normalized, d_prob_normalized, final_states, d_final_states, M = []) + vtb = VTB( + [x_values], + prob_normalized, + d_prob_normalized, + final_states, + d_final_states, + M=[], + ) expected_vtb = 0.03768712089828974 - assert np.allclose(vtb, expected_vtb, atol = 1e-3) + assert np.allclose(vtb, expected_vtb, atol=1e-3) # Test BQCRB type 1 - bqcrb1 = BQCRB([x_values], prob_normalized, [], final_states, d_final_states, btype = 1) + bqcrb1 = BQCRB( + [x_values], prob_normalized, [], final_states, d_final_states, btype=1 + ) expected_bqcrb1 = 0.5097987285760552 - assert np.allclose(bqcrb1, expected_bqcrb1, atol = 1e-3) + assert np.allclose(bqcrb1, expected_bqcrb1, atol=1e-3) # Test BQCRB type 2 - bqcrb2 = BQCRB([x_values], prob_normalized, [], final_states, d_final_states, btype = 2) + bqcrb2 = BQCRB( + [x_values], prob_normalized, [], final_states, d_final_states, btype=2 + ) expected_bqcrb2 = 0.5094351484343563 - assert np.allclose(bqcrb2, expected_bqcrb2, atol = 1e-3) + assert np.allclose(bqcrb2, expected_bqcrb2, atol=1e-3) # Test BQCRB type 3 - bqcrb3 = BQCRB([x_values], prob_normalized, d_prob_normalized, final_states, d_final_states, btype = 3) + bqcrb3 = BQCRB( + [x_values], + prob_normalized, + d_prob_normalized, + final_states, + d_final_states, + btype=3, + ) expected_bqcrb3 = 0.14347116223111836 - assert np.allclose(bqcrb3, expected_bqcrb3, atol = 1e-3) + assert np.allclose(bqcrb3, expected_bqcrb3, atol=1e-3) # Test Quantum Van Trees Bound - qvtb = QVTB([x_values], prob_normalized, d_prob_normalized, final_states, d_final_states) + qvtb = QVTB( + [x_values], prob_normalized, d_prob_normalized, final_states, d_final_states + ) expected_qvtb = 0.037087918374800306 - assert np.allclose(qvtb, expected_qvtb, atol = 1e-3) + assert np.allclose(qvtb, expected_qvtb, atol=1e-3) with pytest.raises(TypeError): - BCFIM([x_values], prob_normalized, final_states, d_final_states, M = 1., eps = 1e-8) + BCFIM( + [x_values], prob_normalized, final_states, d_final_states, M=1.0, eps=1e-8 + ) - with pytest.raises(TypeError): - BCRB([x_values], prob_normalized, [], final_states, d_final_states, M = 1., btype = 1) + with pytest.raises(TypeError): + BCRB( + [x_values], + prob_normalized, + [], + final_states, + d_final_states, + M=1.0, + btype=1, + ) - with pytest.raises(TypeError): - VTB([x_values], prob_normalized, d_prob_normalized, final_states, d_final_states, M = 1.) + with pytest.raises(TypeError): + VTB( + [x_values], + prob_normalized, + d_prob_normalized, + final_states, + d_final_states, + M=1.0, + ) + + with pytest.raises(NameError): + BCRB( + [x_values], prob_normalized, [], final_states, d_final_states, M=[], btype=4 + ) with pytest.raises(NameError): - BCRB([x_values], prob_normalized, [], final_states, d_final_states, M = [], btype = 4) + BQCRB([x_values], prob_normalized, [], final_states, d_final_states, btype=4) - with pytest.raises(NameError): - BQCRB([x_values], prob_normalized, [], final_states, d_final_states, btype = 4) -def test_bayesian_bound_multiparameter() -> None: +def test_bayesian_bound_multiparameter() -> None: """ Test function for Bayesian bounds in quantum estimation for multiparameter scenario. - + This function tests various Bayesian bounds including: - Bayesian Cramer-Rao Bound (BCRB) - Van Trees Bound (VTB) @@ -163,39 +229,46 @@ def test_bayesian_bound_multiparameter() -> None: """ # Initial state rho0 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) - + # Free Hamiltonian parameters b_val = 0.5 * np.pi sigma_x = np.array([[0.0, 1.0], [1.0, 0.0]]) sigma_z = np.array([[1.0, 0.0], [0.0, -1.0]]) - + # Hamiltonian function - hamiltonian_func = lambda omega0, x: 0.5 * b_val * omega0 * ( - sigma_x * np.cos(x) + sigma_z * np.sin(x) + hamiltonian_func = ( + lambda omega0, x: 0.5 + * b_val + * omega0 + * (sigma_x * np.cos(x) + sigma_z * np.sin(x)) ) - + # Derivative of Hamiltonian d_hamiltonian_func = lambda omega0, x: [ 0.5 * b_val * (sigma_x * np.cos(x) + sigma_z * np.sin(x)), - 0.5 * b_val * omega0 * (-sigma_x * np.sin(x) + sigma_z * np.cos(x)) + 0.5 * b_val * omega0 * (-sigma_x * np.sin(x) + sigma_z * np.cos(x)), ] - + # Prior distribution parameters x_values = np.linspace(-0.5 * np.pi, 0.5 * np.pi, 20) omega0_values = np.linspace(1, 2, 20) all_parameter_values = [omega0_values, x_values] - + # Joint probability density function (Gaussian for both parameters) mu_omega0, mu_x = 1.5, 0.0 eta_omega0, eta_x = 0.2, 0.2 prob_density = lambda omega0, x: ( - np.exp(-(omega0 - mu_omega0)**2 / (2 * eta_omega0**2)) / (eta_omega0 * np.sqrt(2 * np.pi)) - * np.exp(-(x - mu_x)**2 / (2 * eta_x**2)) / (eta_x * np.sqrt(2 * np.pi)) + np.exp(-((omega0 - mu_omega0) ** 2) / (2 * eta_omega0**2)) + / (eta_omega0 * np.sqrt(2 * np.pi)) + * np.exp(-((x - mu_x) ** 2) / (2 * eta_x**2)) + / (eta_x * np.sqrt(2 * np.pi)) ) d_prob_density = lambda omega0, x: ( - [-(omega0 - mu_omega0) / (eta_omega0**2) * prob_density(omega0, x), - -(x - mu_x) / (eta_x**2) * prob_density(omega0, x)] + [ + -(omega0 - mu_omega0) / (eta_omega0**2) * prob_density(omega0, x), + -(x - mu_x) / (eta_x**2) * prob_density(omega0, x), + ] ) # Generate probability values @@ -207,7 +280,7 @@ def test_bayesian_bound_multiparameter() -> None: prob_values_unnormalized[i, j] = prob_density(omega0_i, x_values_j) d_prob_density_unnormalized = d_prob_density(omega0_i, x_values_j) d_prob_tp.append(d_prob_density_unnormalized) - + d_prob_normalized.append(d_prob_tp) # Normalize the distribution @@ -220,11 +293,11 @@ def test_bayesian_bound_multiparameter() -> None: # Time evolution parameters time_span = np.linspace(0.0, 1.0, 50) - + # Prepare arrays for states and derivatives final_states = [] d_final_states = [] - + # Evolve the system for each parameter combination for omega0_i in omega0_values: row_rho = [] @@ -236,104 +309,172 @@ def test_bayesian_bound_multiparameter() -> None: dynamics = Lindblad(time_span, rho0, hamiltonian, d_hamiltonian) states, d_states = dynamics.expm() - + row_rho.append(states[-1]) row_drho.append(d_states[-1]) - final_states.append(row_rho) - d_final_states.append(row_drho) + final_states.append(row_rho) + d_final_states.append(row_drho) # Test BCFIM - cfim = BCFIM(all_parameter_values, prob_normalized, final_states, d_final_states, M = [], eps = 1e-8) - expected_cfim = np.array( - [[0.0360, 0.], - [0., 2.1649]] + cfim = BCFIM( + all_parameter_values, + prob_normalized, + final_states, + d_final_states, + M=[], + eps=1e-8, ) - assert np.allclose(cfim, expected_cfim, atol = 1e-3) - + expected_cfim = np.array([[0.0360, 0.0], [0.0, 2.1649]]) + assert np.allclose(cfim, expected_cfim, atol=1e-3) + # Test BQFIM - qfim = BQFIM(all_parameter_values, prob_normalized, final_states, d_final_states, LDtype="SLD", eps=1e-8) - expected_qfim = np.array( - [[0.0948, 0.], - [0., 3.3352]] + qfim = BQFIM( + all_parameter_values, + prob_normalized, + final_states, + d_final_states, + LDtype="SLD", + eps=1e-8, ) - assert np.allclose(qfim, expected_qfim, atol = 1e-3) + expected_qfim = np.array([[0.0948, 0.0], [0.0, 3.3352]]) + assert np.allclose(qfim, expected_qfim, atol=1e-3) # Test BCRB type 1 - bcrb1 = BCRB(all_parameter_values, prob_normalized, [], final_states, d_final_states, M = [], btype = 1) - expected_bcrb1 = np.array( - [[188.85062035, 0.63311697], - [0.63311697, 0.62231953]] + bcrb1 = BCRB( + all_parameter_values, + prob_normalized, + [], + final_states, + d_final_states, + M=[], + btype=1, ) - assert np.allclose(bcrb1, expected_bcrb1, atol = 1e-3) - + expected_bcrb1 = np.array([[188.85062035, 0.63311697], [0.63311697, 0.62231953]]) + assert np.allclose(bcrb1, expected_bcrb1, atol=1e-3) + # Test BCRB type 2 - bcrb2 = BCRB(all_parameter_values, prob_normalized, [], final_states, d_final_states, M = [], btype = 2) - expected_bcrb2 = np.array( - [[27.7234240, 0.002116], - [0.002116, 0.461910]] + bcrb2 = BCRB( + all_parameter_values, + prob_normalized, + [], + final_states, + d_final_states, + M=[], + btype=2, ) - assert np.allclose(bcrb2, expected_bcrb2, atol = 1e-3) + expected_bcrb2 = np.array([[27.7234240, 0.002116], [0.002116, 0.461910]]) + assert np.allclose(bcrb2, expected_bcrb2, atol=1e-3) # # Test BCRB type 3 - bcrb3 = BCRB(all_parameter_values, prob_normalized, d_prob_normalized, final_states, d_final_states, M = [], btype = 3) - expected_bcrb3 = np.array( - [[2.52942056, -0.00943802], - [-0.00943802, 0.38853841]] + bcrb3 = BCRB( + all_parameter_values, + prob_normalized, + d_prob_normalized, + final_states, + d_final_states, + M=[], + btype=3, ) - assert np.allclose(bcrb3, expected_bcrb3, atol = 1e-3) + expected_bcrb3 = np.array([[2.52942056, -0.00943802], [-0.00943802, 0.38853841]]) + assert np.allclose(bcrb3, expected_bcrb3, atol=1e-3) # # Test Van Trees Bound - vtb = VTB(all_parameter_values, prob_normalized, d_prob_normalized, final_states, d_final_states, M = []) - expected_vtb = np.array( - [[0.04382, 0.], - [0., 0.03681]] + vtb = VTB( + all_parameter_values, + prob_normalized, + d_prob_normalized, + final_states, + d_final_states, + M=[], ) - assert np.allclose(vtb, expected_vtb, atol = 1e-3) + expected_vtb = np.array([[0.04382, 0.0], [0.0, 0.03681]]) + assert np.allclose(vtb, expected_vtb, atol=1e-3) # Test BQCRB type 1 - bqcrb1 = BQCRB(all_parameter_values, prob_normalized, [], final_states, d_final_states, btype = 1) - expected_bqcrb1 = np.array( - [[45.48725379, 0.33691038], - [0.33691038, 0.36839637]] + bqcrb1 = BQCRB( + all_parameter_values, prob_normalized, [], final_states, d_final_states, btype=1 ) - assert np.allclose(bqcrb1, expected_bqcrb1, atol = 1e-3) + expected_bqcrb1 = np.array([[45.48725379, 0.33691038], [0.33691038, 0.36839637]]) + assert np.allclose(bqcrb1, expected_bqcrb1, atol=1e-3) # Test BQCRB type 2 - bqcrb2 = BQCRB(all_parameter_values, prob_normalized, [], final_states, d_final_states, btype = 2) - expected_bqcrb2 = np.array( - [[10.542814, 0.0015452], - [0.0015452, 0.29983117]] + bqcrb2 = BQCRB( + all_parameter_values, prob_normalized, [], final_states, d_final_states, btype=2 ) - assert np.allclose(bqcrb2, expected_bqcrb2, atol = 1e-3) + expected_bqcrb2 = np.array([[10.542814, 0.0015452], [0.0015452, 0.29983117]]) + assert np.allclose(bqcrb2, expected_bqcrb2, atol=1e-3) # Test BQCRB type 3 - bqcrb3 = BQCRB(all_parameter_values, prob_normalized, d_prob_normalized, final_states, d_final_states, btype = 3) - expected_bqcrb3 = np.array( - [[1.39714369, -0.00959793], - [-0.00959793, 0.25794208]] + bqcrb3 = BQCRB( + all_parameter_values, + prob_normalized, + d_prob_normalized, + final_states, + d_final_states, + btype=3, ) - assert np.allclose(bqcrb3, expected_bqcrb3, atol = 1e-3) + expected_bqcrb3 = np.array([[1.39714369, -0.00959793], [-0.00959793, 0.25794208]]) + assert np.allclose(bqcrb3, expected_bqcrb3, atol=1e-3) # # Test Quantum Van Trees Bound - qvtb = QVTB(all_parameter_values, prob_normalized, d_prob_normalized, final_states, d_final_states) - expected_qvtb = np.array( - [[0.04371, 0.], - [0., 0.03529]] + qvtb = QVTB( + all_parameter_values, + prob_normalized, + d_prob_normalized, + final_states, + d_final_states, ) - assert np.allclose(qvtb, expected_qvtb, atol = 1e-3) - + expected_qvtb = np.array([[0.04371, 0.0], [0.0, 0.03529]]) + assert np.allclose(qvtb, expected_qvtb, atol=1e-3) + with pytest.raises(TypeError): - BCFIM(all_parameter_values, prob_normalized, final_states, d_final_states, M = 1., eps = 1e-8) + BCFIM( + all_parameter_values, + prob_normalized, + final_states, + d_final_states, + M=1.0, + eps=1e-8, + ) - with pytest.raises(TypeError): - BCRB(all_parameter_values, prob_normalized, [], final_states, d_final_states, M = 1., btype = 1) + with pytest.raises(TypeError): + BCRB( + all_parameter_values, + prob_normalized, + [], + final_states, + d_final_states, + M=1.0, + btype=1, + ) - with pytest.raises(TypeError): - VTB(all_parameter_values, prob_normalized, d_prob_normalized, final_states, d_final_states, M = 1.) + with pytest.raises(TypeError): + VTB( + all_parameter_values, + prob_normalized, + d_prob_normalized, + final_states, + d_final_states, + M=1.0, + ) with pytest.raises(NameError): - BCRB(all_parameter_values, prob_normalized, [], final_states, d_final_states, M = [], btype = 4) - - with pytest.raises(NameError): - BQCRB(all_parameter_values, prob_normalized, [], final_states, d_final_states, btype = 4) + BCRB( + all_parameter_values, + prob_normalized, + [], + final_states, + d_final_states, + M=[], + btype=4, + ) + with pytest.raises(NameError): + BQCRB( + all_parameter_values, + prob_normalized, + [], + final_states, + d_final_states, + btype=4, + ) diff --git a/test/test_BayesEstimation.py b/test/test_BayesEstimation.py index 4ddf1733..67e98bad 100644 --- a/test/test_BayesEstimation.py +++ b/test/test_BayesEstimation.py @@ -1,5 +1,5 @@ import pytest -import numpy as np +import numpy as np import random import os from scipy.integrate import simpson @@ -11,32 +11,33 @@ ) from quanestimation.Parameterization.GeneralDynamics import Lindblad + def test_Bayes_singleparameter() -> None: # initial state rho0 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) - + # free Hamiltonian B, omega0 = np.pi / 2.0, 1.0 sx = np.array([[0.0, 1.0], [1.0, 0.0]]) sz = np.array([[1.0, 0.0], [0.0, -1.0]]) - + H0_func = lambda x: 0.5 * B * omega0 * (sx * np.cos(x) + sz * np.sin(x)) - + # derivative of the free Hamiltonian on x dH_func = lambda x: [0.5 * B * omega0 * (-sx * np.sin(x) + sz * np.cos(x))] - + # measurement M1 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) M2 = 0.5 * np.array([[1.0, -1.0], [-1.0, 1.0]]) M = [M1, M2] - + # prior distribution x = np.linspace(0.0, 0.5 * np.pi, 1000) p = (1.0 / (x[-1] - x[0])) * np.ones(len(x)) - + # time length for the evolution tspan = np.linspace(0.0, 1.0, 10) - + # dynamics rho = [] for xi in x: @@ -49,43 +50,37 @@ def test_Bayes_singleparameter() -> None: random.seed(1234) y = [0 for _ in range(500)] res_rand = random.sample(range(len(y)), 125) - + for i in res_rand: y[i] = 1 - pout_MAP, xout_MAP = Bayes( - [x], p, rho, y, M = M, estimator = "MAP", savefile = False - ) + pout_MAP, xout_MAP = Bayes([x], p, rho, y, M=M, estimator="MAP", savefile=False) pout_MAP_max = max(pout_MAP) expected_xout_MAP = 0.7861843477451934 expected_pout_MAP_max = 0.15124761081089924 - assert np.allclose(xout_MAP, expected_xout_MAP, atol = 1e-3) - assert np.allclose(pout_MAP_max, expected_pout_MAP_max, atol = 1e-3) + assert np.allclose(xout_MAP, expected_xout_MAP, atol=1e-3) + assert np.allclose(pout_MAP_max, expected_pout_MAP_max, atol=1e-3) expected_xout_MLE = 0.7861843477451934 - _, xout_MLE = MLE([x], rho, y, M = M, savefile=False) - assert np.allclose(xout_MLE, expected_xout_MLE, atol = 1e-3) + _, xout_MLE = MLE([x], rho, y, M=M, savefile=False) + assert np.allclose(xout_MLE, expected_xout_MLE, atol=1e-3) - pout_mean, xout_mean = Bayes( - [x], p, rho, y, M=M, estimator="mean", savefile=False - ) + pout_mean, xout_mean = Bayes([x], p, rho, y, M=M, estimator="mean", savefile=False) pout_mean_max = max(pout_mean) expected_xout_mean = 0.01158475411409417 expected_pout_mean_max = 0.15124761081089924 - assert np.allclose(xout_mean, expected_xout_mean, atol = 1e-3) - assert np.allclose(pout_mean_max, expected_pout_mean_max, atol = 1e-3) - + assert np.allclose(xout_mean, expected_xout_mean, atol=1e-3) + assert np.allclose(pout_mean_max, expected_pout_mean_max, atol=1e-3) + # Clean up generated files for filename in ["pout.npy", "xout.npy", "Lout.npy"]: if os.path.exists(filename): os.remove(filename) # Test saving functionality - pout_mean, xout_mean = Bayes( - [x], p, rho, y, M=M, estimator="mean", savefile=True - ) + pout_mean, xout_mean = Bayes([x], p, rho, y, M=M, estimator="mean", savefile=True) assert os.path.exists("pout.npy") assert os.path.exists("xout.npy") @@ -93,47 +88,53 @@ def test_Bayes_singleparameter() -> None: for filename in ["pout.npy", "xout.npy", "Lout.npy"]: if os.path.exists(filename): os.remove(filename) - + with pytest.raises(TypeError): - Bayes([x], p, rho, y, M=1., estimator="mean", savefile=False) + Bayes([x], p, rho, y, M=1.0, estimator="mean", savefile=False) with pytest.raises(ValueError): - Bayes([x], p, rho, y, M=M, estimator="invalid_estimator", savefile=False) + Bayes([x], p, rho, y, M=M, estimator="invalid_estimator", savefile=False) + + with pytest.raises(TypeError): + _, xout_MLE = MLE([x], rho, y, M=1.0, savefile=False) - with pytest.raises(TypeError): - _, xout_MLE = MLE([x], rho, y, M=1., savefile=False) def test_Bayes_multiparameter() -> None: # Initial state rho0 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) - + # Free Hamiltonian parameters b_val = 0.5 * np.pi sigma_x = np.array([[0.0, 1.0], [1.0, 0.0]]) sigma_z = np.array([[1.0, 0.0], [0.0, -1.0]]) - + # Hamiltonian function - hamiltonian_func = lambda omega0, x: 0.5 * b_val * omega0 * ( - sigma_x * np.cos(x) + sigma_z * np.sin(x) + hamiltonian_func = ( + lambda omega0, x: 0.5 + * b_val + * omega0 + * (sigma_x * np.cos(x) + sigma_z * np.sin(x)) ) # Derivative of Hamiltonian d_hamiltonian_func = lambda omega0, x: [ 0.5 * b_val * (sigma_x * np.cos(x) + sigma_z * np.sin(x)), - 0.5 * b_val * omega0 * (-sigma_x * np.sin(x) + sigma_z * np.cos(x)) + 0.5 * b_val * omega0 * (-sigma_x * np.sin(x) + sigma_z * np.cos(x)), ] - + # Prior distribution parameters x_values = np.linspace(-0.5 * np.pi, 0.5 * np.pi, 20) omega0_values = np.linspace(1, 2, 20) all_parameter_values = [omega0_values, x_values] - + # Joint probability density function (Gaussian for both parameters) mu_omega0, mu_x = 1.5, 0.0 eta_omega0, eta_x = 0.2, 0.2 prob_density = lambda omega0, x: ( - np.exp(-(omega0 - mu_omega0)**2 / (2 * eta_omega0**2)) / (eta_omega0 * np.sqrt(2 * np.pi)) - * np.exp(-(x - mu_x)**2 / (2 * eta_x**2)) / (eta_x * np.sqrt(2 * np.pi)) + np.exp(-((omega0 - mu_omega0) ** 2) / (2 * eta_omega0**2)) + / (eta_omega0 * np.sqrt(2 * np.pi)) + * np.exp(-((x - mu_x) ** 2) / (2 * eta_x**2)) + / (eta_x * np.sqrt(2 * np.pi)) ) # Generate probability values @@ -152,7 +153,7 @@ def test_Bayes_multiparameter() -> None: random.seed(1234) y = [0 for _ in range(500)] res_rand = random.sample(range(len(y)), 125) - + for i in res_rand: y[i] = 1 @@ -172,34 +173,46 @@ def test_Bayes_multiparameter() -> None: dynamics = Lindblad(time_span, rho0, hamiltonian, d_hamiltonian) states, _ = dynamics.expm() - + row_rho.append(states[-1]) - final_states.append(row_rho) + final_states.append(row_rho) pout_MAP, xout_MAP = Bayes( - all_parameter_values, prob_normalized, final_states, y, M = None, estimator = "MAP", savefile = False + all_parameter_values, + prob_normalized, + final_states, + y, + M=None, + estimator="MAP", + savefile=False, ) expected_xout_MAP = [2.0, 0.5787144361875933] pout_MAP_max = np.max(pout_MAP) expected_pout_MAP_max = 0.9977126619164614 - assert np.allclose(xout_MAP, expected_xout_MAP, atol = 1e-3) - assert np.allclose(pout_MAP_max, expected_pout_MAP_max, atol = 1e-3) + assert np.allclose(xout_MAP, expected_xout_MAP, atol=1e-3) + assert np.allclose(pout_MAP_max, expected_pout_MAP_max, atol=1e-3) expected_xout_MLE = [2.0, 0.5787144361875933] - _, xout_MLE = MLE(all_parameter_values, final_states, y, M = [], savefile = False) - assert np.allclose(xout_MLE, expected_xout_MLE, atol = 1e-3) + _, xout_MLE = MLE(all_parameter_values, final_states, y, M=[], savefile=False) + assert np.allclose(xout_MLE, expected_xout_MLE, atol=1e-3) pout_mean, xout_mean = Bayes( - all_parameter_values, prob_normalized, final_states, y, M = None, estimator="mean", savefile=False + all_parameter_values, + prob_normalized, + final_states, + y, + M=None, + estimator="mean", + savefile=False, ) pout_mean_max = np.max(pout_mean) expected_xout_mean = [0.01124410514670476, 0.0032666400994622027] expected_pout_mean_max = 0.9977126619164614 - assert np.allclose(xout_mean, expected_xout_mean, atol = 1e-3) - assert np.allclose(pout_mean_max, expected_pout_mean_max, atol = 1e-3) + assert np.allclose(xout_mean, expected_xout_mean, atol=1e-3) + assert np.allclose(pout_mean_max, expected_pout_mean_max, atol=1e-3) # Clean up generated files for filename in ["pout.npy", "xout.npy", "Lout.npy"]: @@ -207,98 +220,113 @@ def test_Bayes_multiparameter() -> None: os.remove(filename) with pytest.raises(TypeError): - Bayes(all_parameter_values, prob_normalized, final_states, y, M = 1., estimator = "mean", savefile = False) + Bayes( + all_parameter_values, + prob_normalized, + final_states, + y, + M=1.0, + estimator="mean", + savefile=False, + ) with pytest.raises(ValueError): - Bayes(all_parameter_values, prob_normalized, final_states, y, M = None, estimator = "invalid_estimator", savefile = False) + Bayes( + all_parameter_values, + prob_normalized, + final_states, + y, + M=None, + estimator="invalid_estimator", + savefile=False, + ) + + with pytest.raises(TypeError): + _, xout_MLE = MLE(all_parameter_values, final_states, y, M=1.0, savefile=False) - with pytest.raises(TypeError): - _, xout_MLE = MLE(all_parameter_values, final_states, y, M = 1., savefile = False) def test_BCB_singleparameter() -> None: # initial state rho0 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) - + # free Hamiltonian B, omega0 = np.pi / 2.0, 1.0 sx = np.array([[0.0, 1.0], [1.0, 0.0]]) sz = np.array([[1.0, 0.0], [0.0, -1.0]]) - + H0_func = lambda x: 0.5 * B * omega0 * (sx * np.cos(x) + sz * np.sin(x)) - + # derivative of the free Hamiltonian on x dH_func = lambda x: [0.5 * B * omega0 * (-sx * np.sin(x) + sz * np.cos(x))] - + # prior distribution x = np.linspace(0.0, 0.5 * np.pi, 1000) p = (1.0 / (x[-1] - x[0])) * np.ones(len(x)) - + # time length for the evolution tspan = np.linspace(0.0, 1.0, 10) - + # dynamics - rho = [] + rho = [] for xi in x: H0 = H0_func(xi) dH = dH_func(xi) dynamics = Lindblad(tspan, rho0, H0, dH) rho_tp, _ = dynamics.expm() - rho.append(rho_tp[-1]) + rho.append(rho_tp[-1]) - result = BCB([x], p, rho, W = [], eps = 1e-8) + result = BCB([x], p, rho, W=[], eps=1e-8) expected_result = 0.16139667479361308 - assert np.allclose(result, expected_result, atol = 1e-3) + assert np.allclose(result, expected_result, atol=1e-3) + def test_BayesCost_singleparameter() -> None: - # initial state + # initial state rho0 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) - + # free Hamiltonian B, omega0 = np.pi / 2.0, 1.0 sx = np.array([[0.0, 1.0], [1.0, 0.0]]) sz = np.array([[1.0, 0.0], [0.0, -1.0]]) - + H0_func = lambda x: 0.5 * B * omega0 * (sx * np.cos(x) + sz * np.sin(x)) - + # derivative of the free Hamiltonian on x dH_func = lambda x: [0.5 * B * omega0 * (-sx * np.sin(x) + sz * np.cos(x))] - + # prior distribution x = np.linspace(0.0, 0.5 * np.pi, 1000) p = (1.0 / (x[-1] - x[0])) * np.ones(len(x)) - + # time length for the evolution tspan = np.linspace(0.0, 1.0, 10) - + # dynamics - rho = [] + rho = [] for xi in x: H0 = H0_func(xi) dH = dH_func(xi) dynamics = Lindblad(tspan, rho0, H0, dH) rho_tp, _ = dynamics.expm() - rho.append(rho_tp[-1]) + rho.append(rho_tp[-1]) random.seed(1234) y = [0 for _ in range(500)] res_rand = random.sample(range(len(y)), 125) - + for i in res_rand: - y[i] = 1 + y[i] = 1 - pout_mean, xout_mean = Bayes( - [x], p, rho, y, M = [], estimator = "mean", savefile = False - ) + pout_mean, xout_mean = Bayes([x], p, rho, y, M=[], estimator="mean", savefile=False) # Clean up generated files for filename in ["pout.npy", "xout.npy", "Lout.npy"]: if os.path.exists(filename): - os.remove(filename) + os.remove(filename) - result = BayesCost([x], pout_mean, xout_mean, rho, M = []) + result = BayesCost([x], pout_mean, xout_mean, rho, M=[]) expected_result = 0.0029817568167127637 - assert np.allclose(result, expected_result, atol = 1e-3) + assert np.allclose(result, expected_result, atol=1e-3) with pytest.raises(TypeError): - BayesCost([x], pout_mean, xout_mean, rho, M = 1.) - + BayesCost([x], pout_mean, xout_mean, rho, M=1.0) diff --git a/test/test_Common.py b/test/test_Common.py index 7898589a..0184f018 100644 --- a/test/test_Common.py +++ b/test/test_Common.py @@ -10,7 +10,7 @@ SIC, annihilation, brgd, - fidelity + fidelity, ) @@ -22,9 +22,9 @@ def test_basis() -> None: def test_gramschmidt() -> None: """Test Gram-Schmidt process for orthonormalizing vectors.""" - A = np.array([[1., 0.], [1., 1.]], dtype=np.complex128) + A = np.array([[1.0, 0.0], [1.0, 1.0]], dtype=np.complex128) result = gramschmidt(A) - expected = np.array([[1.+0.j, 0.+0.j], [0.+0.j, 1.+0.j]]) + expected = np.array([[1.0 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 1.0 + 0.0j]]) assert np.allclose(result, expected) @@ -32,9 +32,9 @@ def test_suN_generator() -> None: """Test generation of SU(N) generators.""" # Test SU(2) generators (Pauli matrices) result = suN_generator(2) - sx = np.array([[0., 1.], [1., 0.]], dtype=np.complex128) - sy = np.array([[0., -1j], [1j, 0.]], dtype=np.complex128) - sz = np.array([[1., 0.j], [0.j, -1.]], dtype=np.complex128) + sx = np.array([[0.0, 1.0], [1.0, 0.0]], dtype=np.complex128) + sy = np.array([[0.0, -1j], [1j, 0.0]], dtype=np.complex128) + sz = np.array([[1.0, 0.0j], [0.0j, -1.0]], dtype=np.complex128) sall = [sx, sy, sz] assert all(np.allclose(result[i], sall[i]) for i in range(3)) @@ -45,21 +45,27 @@ def test_suN_generator() -> None: expected_3 = np.array([[1, 0, 0], [0, -1, 0], [0, 0, 0]]) expected_4 = np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]) expected_5 = np.array([[0, 0, -1j], [0, 0, 0], [1j, 0, 0]]) - expected_6 = np.array([[0, 0, 0.], [0, 0, 1.], [0., 1., 0]]) - expected_7 = np.array([[0, 0, 0.], [0, 0, -1j], [0., 1j, 0]]) - expected_8 = (1/np.sqrt(3)) * np.array([[1, 0, 0], [0, 1, 0], [0, 0, -2]]) + expected_6 = np.array([[0, 0, 0.0], [0, 0, 1.0], [0.0, 1.0, 0]]) + expected_7 = np.array([[0, 0, 0.0], [0, 0, -1j], [0.0, 1j, 0]]) + expected_8 = (1 / np.sqrt(3)) * np.array([[1, 0, 0], [0, 1, 0], [0, 0, -2]]) expect = [ - expected_1, expected_2, expected_3, expected_4, - expected_5, expected_6, expected_7, expected_8 + expected_1, + expected_2, + expected_3, + expected_4, + expected_5, + expected_6, + expected_7, + expected_8, ] assert all(np.allclose(su3[i], expect[i]) for i in range(8)) def test_mat_vec_convert() -> None: """Test matrix to vector conversion and vice versa.""" - A = np.array([[1., 2.], [3., 4.]]) + A = np.array([[1.0, 2.0], [3.0, 4.0]]) result_A = mat_vec_convert(A) - expected_A = np.array([[1.], [2.], [3.], [4.]]) + expected_A = np.array([[1.0], [2.0], [3.0], [4.0]]) result_inv = mat_vec_convert(result_A) assert np.allclose(result_A, expected_A) assert np.allclose(result_inv, A) @@ -69,14 +75,30 @@ def test_SIC() -> None: """Test generation of SIC-POVM.""" result = SIC(2) expected = [ - np.array([[0.39433757+0.j, 0.14433757+0.14433757j], - [0.14433757-0.14433757j, 0.10566243+0.j]]), - np.array([[0.39433757+0.j, -0.14433757-0.14433757j], - [-0.14433757+0.14433757j, 0.10566243+0.j]]), - np.array([[0.10566243+0.j, 0.14433757-0.14433757j], - [0.14433757+0.14433757j, 0.39433757+0.j]]), - np.array([[0.10566243+0.j, -0.14433757+0.14433757j], - [-0.14433757-0.14433757j, 0.39433757+0.j]]) + np.array( + [ + [0.39433757 + 0.0j, 0.14433757 + 0.14433757j], + [0.14433757 - 0.14433757j, 0.10566243 + 0.0j], + ] + ), + np.array( + [ + [0.39433757 + 0.0j, -0.14433757 - 0.14433757j], + [-0.14433757 + 0.14433757j, 0.10566243 + 0.0j], + ] + ), + np.array( + [ + [0.10566243 + 0.0j, 0.14433757 - 0.14433757j], + [0.14433757 + 0.14433757j, 0.39433757 + 0.0j], + ] + ), + np.array( + [ + [0.10566243 + 0.0j, -0.14433757 + 0.14433757j], + [-0.14433757 - 0.14433757j, 0.39433757 + 0.0j], + ] + ), ] assert all(np.allclose(result[i], expected[i]) for i in range(4)) @@ -87,7 +109,7 @@ def test_SIC() -> None: def test_annihilation() -> None: """Test generation of annihilation operator.""" result = annihilation(2) - expected = np.array([[0., 1.], [0., 0.]]) + expected = np.array([[0.0, 1.0], [0.0, 0.0]]) assert np.allclose(result, expected) @@ -96,7 +118,7 @@ def test_brgd() -> None: result1 = brgd(1) expected1 = ["0", "1"] result2 = brgd(2) - expected2 = ['00', '01', '11', '10'] + expected2 = ["00", "01", "11", "10"] assert result1 == expected1 assert result2 == expected2 @@ -109,13 +131,10 @@ def test_BayesInput() -> None: xspan = [np.linspace(0, 1, 2)] result_H, result_dH = BayesInput(xspan, H, dH, channel="dynamics") expected_H = [ - np.array([[0., 0.], [0., 0.]]), - np.array([[1., 0.], [0., -1.]]) - ] - expected_dH = [ - np.array([[1, 0], [0, -1]]), - np.array([[1, 0], [0, -1]]) + np.array([[0.0, 0.0], [0.0, 0.0]]), + np.array([[1.0, 0.0], [0.0, -1.0]]), ] + expected_dH = [np.array([[1, 0], [0, -1]]), np.array([[1, 0], [0, -1]])] assert all(np.allclose(result_H[i], expected_H[i]) for i in range(2)) assert all(np.allclose(result_dH[i], expected_dH[i]) for i in range(2)) @@ -125,34 +144,30 @@ def test_BayesInput() -> None: # Test with Kraus operators K = lambda x: [ - np.array([[1, 0], [0, np.sqrt(1-x)]]), - np.array([[0, np.sqrt(x)], [0, 0]]) + np.array([[1, 0], [0, np.sqrt(1 - x)]]), + np.array([[0, np.sqrt(x)], [0, 0]]), + ] + dK = lambda x: [ + [ + np.array([[0, 0], [0, -0.5 / np.sqrt(1 - x)]]), + np.array([[0, 0.5 / np.sqrt(x)], [0, 0]]), + ] ] - dK = lambda x: [[ - np.array([[0, 0], [0, -0.5/np.sqrt(1-x)]]), - np.array([[0, 0.5/np.sqrt(x)], [0, 0]]) - ]] xspan_K = [np.linspace(0.1, 0.5, 2)] result_K, result_dK = BayesInput(xspan_K, K, dK, channel="Kraus") expected_K = [ - [ - np.array([[1, 0], [0, np.sqrt(0.9)]]), - np.array([[0, np.sqrt(0.1)], [0, 0]]) - ], - [ - np.array([[1, 0], [0, np.sqrt(0.5)]]), - np.array([[0, np.sqrt(0.5)], [0, 0]]) - ] + [np.array([[1, 0], [0, np.sqrt(0.9)]]), np.array([[0, np.sqrt(0.1)], [0, 0]])], + [np.array([[1, 0], [0, np.sqrt(0.5)]]), np.array([[0, np.sqrt(0.5)], [0, 0]])], ] expected_dK = [ [ - np.array([[0, 0], [0, -0.5/np.sqrt(0.9)]]), - np.array([[0, 0.5/np.sqrt(0.1)], [0, 0]]) + np.array([[0, 0], [0, -0.5 / np.sqrt(0.9)]]), + np.array([[0, 0.5 / np.sqrt(0.1)], [0, 0]]), ], [ - np.array([[0, 0], [0, -0.5/np.sqrt(0.5)]]), - np.array([[0, 0.5/np.sqrt(0.5)], [0, 0]]) - ] + np.array([[0, 0], [0, -0.5 / np.sqrt(0.5)]]), + np.array([[0, 0.5 / np.sqrt(0.5)], [0, 0]]), + ], ] assert all(np.allclose(result_K[i], expected_K[i]) for i in range(2)) assert all(np.allclose(result_dK[i], expected_dK[i]) for i in range(2)) @@ -177,6 +192,7 @@ def test_extract_ele() -> None: result = list(extract_ele(nested, n)) assert result == [1, 2, 3, [4, 5]] + def test_fidelity() -> None: """Test fidelity function for quantum states.""" rho1 = np.array([[0.5, 0.5], [0.5, 0.5]]) @@ -189,16 +205,16 @@ def test_fidelity() -> None: psi = np.array([1, 0]) phi = np.array([0, 1]) result_vec = fidelity(psi, phi) - expected_vec = 0. + expected_vec = 0.0 assert np.isclose(result_vec, expected_vec) rho3 = np.array([0, 0, 1]) with pytest.raises(ValueError): fidelity(rho1, rho3) - + rho4 = np.array([[1, 0, 0], [0, 0, 0], [0, 0, 0]]) with pytest.raises(ValueError): fidelity(rho1, rho4) with pytest.raises(ValueError): - fidelity(psi, rho1) \ No newline at end of file + fidelity(psi, rho1) diff --git a/test/test_CramerRao.py b/test/test_CramerRao.py index 777669a2..a6110064 100644 --- a/test/test_CramerRao.py +++ b/test/test_CramerRao.py @@ -1,16 +1,16 @@ import pytest import numpy as np from quanestimation.AsymptoticBound.CramerRao import ( - QFIM, - CFIM, - QFIM_Kraus, - QFIM_Bloch, - QFIM_Gauss, - LLD, - RLD, - FIM, - FI_Expt, - SLD + QFIM, + CFIM, + QFIM_Kraus, + QFIM_Bloch, + QFIM_Gauss, + LLD, + RLD, + FIM, + FI_Expt, + SLD, ) @@ -22,33 +22,38 @@ def test_CramerRao_SLD() -> None: # Parameterized state theta = np.pi / 4 phi = np.pi / 4 - rho = np.array([ - [np.cos(theta)**2, np.cos(theta) * np.sin(theta) * np.exp(-1j * phi)], - [np.cos(theta) * np.sin(theta) * np.exp(1j * phi), np.sin(theta)**2] - ]) - + rho = np.array( + [ + [np.cos(theta) ** 2, np.cos(theta) * np.sin(theta) * np.exp(-1j * phi)], + [np.cos(theta) * np.sin(theta) * np.exp(1j * phi), np.sin(theta) ** 2], + ] + ) + # State derivatives drho = [ - np.array([ - [-np.sin(2 * theta), 2 * np.cos(2 * theta) * np.exp(-1j * phi)], - [2 * np.cos(2 * theta) * np.exp(1j * phi), np.sin(2 * theta)] - ]), - np.array([ - [0, -1j * np.cos(theta) * np.sin(theta) * np.exp(-1j * phi)], - [1j * np.cos(theta) * np.sin(theta) * np.exp(1j * phi), 0] - ]) + np.array( + [ + [-np.sin(2 * theta), 2 * np.cos(2 * theta) * np.exp(-1j * phi)], + [2 * np.cos(2 * theta) * np.exp(1j * phi), np.sin(2 * theta)], + ] + ), + np.array( + [ + [0, -1j * np.cos(theta) * np.sin(theta) * np.exp(-1j * phi)], + [1j * np.cos(theta) * np.sin(theta) * np.exp(1j * phi), 0], + ] + ), ] - + # Calculate QFIM result = QFIM(rho, drho, LDtype="SLD") # Measurement operators - M = [np.array([[1.0, 0.0], [0.0, 0.0]]), - np.array([[0.0, 0.0], [0.0, 1.0]])] + M = [np.array([[1.0, 0.0], [0.0, 0.0]]), np.array([[0.0, 0.0], [0.0, 1.0]])] # Calculate CFIM resultc = CFIM(rho, drho, M) - + # Verify results - expected_qfim = np.array([[4.0, 0.0], [0.0, np.sin(2 * theta)**2]]) + expected_qfim = np.array([[4.0, 0.0], [0.0, np.sin(2 * theta) ** 2]]) expected_cfim = np.array([[4.0, 0.0], [0.0, 0.0]]) assert np.allclose(result, expected_qfim) assert np.allclose(resultc, expected_cfim) @@ -74,17 +79,23 @@ def test_CFIM_singleparameter() -> None: """ # Parameterized state theta = np.pi / 4 - rho = np.array([ - [np.cos(theta)**2, np.sin(theta) * np.cos(theta)], - [np.sin(theta) * np.cos(theta), np.sin(theta)**2] - ]) - + rho = np.array( + [ + [np.cos(theta) ** 2, np.sin(theta) * np.cos(theta)], + [np.sin(theta) * np.cos(theta), np.sin(theta) ** 2], + ] + ) + # State derivative - drho = [np.array([ - [-np.sin(2 * theta), 2 * np.cos(2 * theta)], - [2 * np.cos(2 * theta), np.sin(2 * theta)] - ])] - + drho = [ + np.array( + [ + [-np.sin(2 * theta), 2 * np.cos(2 * theta)], + [2 * np.cos(2 * theta), np.sin(2 * theta)], + ] + ) + ] + # Calculate CFIM result = CFIM(rho, drho, []) assert np.allclose(result, 2.0) @@ -99,16 +110,16 @@ def test_QFIM_Kraus() -> None: K0 = np.array([[1, 0], [0, np.sqrt(0.5)]]) K1 = np.array([[np.sqrt(0.5), 0], [0, 0]]) K = [K0, K1] - + # Kraus operator derivatives dK = [ [np.array([[0, 0], [0, -0.5 / np.sqrt(0.5)]])], - [np.array([[0, 0.5 / np.sqrt(0.5)], [0, 0]])] + [np.array([[0, 0.5 / np.sqrt(0.5)], [0, 0]])], ] - + # Probe state rho0 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) - + # Calculate QFIM result = QFIM_Kraus(rho0, K, dK) assert np.allclose(result, 1.5) @@ -123,31 +134,32 @@ def test_QFIM_Bloch() -> None: theta = np.pi / 4 phi = np.pi / 2 eta = 0.8 - b = eta * np.array([ - np.sin(2 * theta) * np.cos(phi), - np.sin(2 * theta) * np.sin(phi), - np.cos(2 * theta) - ]) - + b = eta * np.array( + [ + np.sin(2 * theta) * np.cos(phi), + np.sin(2 * theta) * np.sin(phi), + np.cos(2 * theta), + ] + ) + # Bloch vector derivatives - db_theta = eta * np.array([ - 2 * np.cos(2 * theta) * np.cos(phi), - 2 * np.cos(2 * theta) * np.sin(phi), - -2 * np.sin(2 * theta) - ]) - db_phi = eta * np.array([ - -np.sin(2 * theta) * np.sin(phi), - np.sin(2 * theta) * np.cos(phi), - 0 - ]) + db_theta = eta * np.array( + [ + 2 * np.cos(2 * theta) * np.cos(phi), + 2 * np.cos(2 * theta) * np.sin(phi), + -2 * np.sin(2 * theta), + ] + ) + db_phi = eta * np.array( + [-np.sin(2 * theta) * np.sin(phi), np.sin(2 * theta) * np.cos(phi), 0] + ) db = [db_theta, db_phi] - + # Calculate QFIM result = QFIM_Bloch(b, db) - expected = np.array([ - [4.0 * eta**2, 0.0], - [0.0, eta**2 * np.sin(2 * theta)**2] - ]) + expected = np.array( + [[4.0 * eta**2, 0.0], [0.0, eta**2 * np.sin(2 * theta) ** 2]] + ) assert np.allclose(result, expected) @@ -160,31 +172,32 @@ def test_QFIM_Bloch_pure() -> None: theta = np.pi / 4 phi = np.pi / 2 eta = 1.0 - b = eta * np.array([ - np.sin(2 * theta) * np.cos(phi), - np.sin(2 * theta) * np.sin(phi), - np.cos(2 * theta) - ]) - + b = eta * np.array( + [ + np.sin(2 * theta) * np.cos(phi), + np.sin(2 * theta) * np.sin(phi), + np.cos(2 * theta), + ] + ) + # Bloch vector derivatives - db_theta = eta * np.array([ - 2 * np.cos(2 * theta) * np.cos(phi), - 2 * np.cos(2 * theta) * np.sin(phi), - -2 * np.sin(2 * theta) - ]) - db_phi = eta * np.array([ - -np.sin(2 * theta) * np.sin(phi), - np.sin(2 * theta) * np.cos(phi), - 0 - ]) + db_theta = eta * np.array( + [ + 2 * np.cos(2 * theta) * np.cos(phi), + 2 * np.cos(2 * theta) * np.sin(phi), + -2 * np.sin(2 * theta), + ] + ) + db_phi = eta * np.array( + [-np.sin(2 * theta) * np.sin(phi), np.sin(2 * theta) * np.cos(phi), 0] + ) db = [db_theta, db_phi] - + # Calculate QFIM result = QFIM_Bloch(b, db) - expected = np.array([ - [4.0 * eta**2, 0.0], - [0.0, eta**2 * np.sin(2 * theta)**2] - ]) + expected = np.array( + [[4.0 * eta**2, 0.0], [0.0, eta**2 * np.sin(2 * theta) ** 2]] + ) assert np.allclose(result, expected) @@ -196,28 +209,40 @@ def test_QFIM_Bloch_highdimension() -> None: # Bloch vector parameters theta = np.pi / 4 phi = np.pi / 2 - b = np.array([ - np.sin(2 * theta) * np.cos(phi), - np.sin(2 * theta) * np.sin(phi), - np.cos(2 * theta), - 0.0, 0.0, 0.0, 0.0, 0.0 - ]) - + b = np.array( + [ + np.sin(2 * theta) * np.cos(phi), + np.sin(2 * theta) * np.sin(phi), + np.cos(2 * theta), + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ] + ) + # Bloch vector derivative - db = [np.array([ - 2 * np.cos(2 * theta) * np.cos(phi), - 2 * np.cos(2 * theta) * np.sin(phi), - -2 * np.sin(2 * theta), - 0.0, 0.0, 0.0, 0.0, 0.0 - ])] - + db = [ + np.array( + [ + 2 * np.cos(2 * theta) * np.cos(phi), + 2 * np.cos(2 * theta) * np.sin(phi), + -2 * np.sin(2 * theta), + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ] + ) + ] + # Calculate QFIM result = QFIM_Bloch(b, db) assert np.allclose(result, 8.0) - b_invalid = np.array([ - 0.0, 0.0, 0.0, 0.0, 0.0 - ]) + b_invalid = np.array([0.0, 0.0, 0.0, 0.0, 0.0]) with pytest.raises(ValueError): QFIM_Bloch(b_invalid, db) @@ -232,31 +257,33 @@ def test_QFIM_Gauss_multiparameter() -> None: beta = 0.5 lamb = 1 / np.tanh(beta / 2) mu = np.array([0.0, 0.0]) - sigma = lamb * np.array([ - [np.cosh(2 * r), -np.sinh(2 * r)], - [-np.sinh(2 * r), np.cosh(2 * r)] - ]) - + sigma = lamb * np.array( + [[np.cosh(2 * r), -np.sinh(2 * r)], [-np.sinh(2 * r), np.cosh(2 * r)]] + ) + # Derivatives dmu = [np.array([0.0, 0.0]), np.array([0.0, 0.0])] - dlamb = -0.5 / (np.sinh(beta / 2)**2) + dlamb = -0.5 / (np.sinh(beta / 2) ** 2) dsigma = [ - dlamb * np.array([ - [np.cosh(2 * r), -np.sinh(2 * r)], - [-np.sinh(2 * r), np.cosh(2 * r)] - ]), - lamb * 2 * np.array([ - [np.sinh(2 * r), -np.cosh(2 * r)], - [-np.cosh(2 * r), np.sinh(2 * r)] - ]) + dlamb + * np.array( + [[np.cosh(2 * r), -np.sinh(2 * r)], [-np.sinh(2 * r), np.cosh(2 * r)]] + ), + lamb + * 2 + * np.array( + [[np.sinh(2 * r), -np.cosh(2 * r)], [-np.cosh(2 * r), np.sinh(2 * r)]] + ), ] - + # Calculate QFIM result = QFIM_Gauss(mu, dmu, sigma, dsigma) - expected = np.array([ - [(lamb**2 - 1)**2 / (2 * (4 * lamb**2 - 1)), 0.0], - [0.0, 8 * lamb**2 / (4 * lamb**2 + 1)] - ]) + expected = np.array( + [ + [(lamb**2 - 1) ** 2 / (2 * (4 * lamb**2 - 1)), 0.0], + [0.0, 8 * lamb**2 / (4 * lamb**2 + 1)], + ] + ) assert np.allclose(result, expected) @@ -270,22 +297,23 @@ def test_QFIM_Gauss_singleparameter() -> None: beta = 0.5 lamb = 1 / np.tanh(beta / 2) mu = np.array([0.0, 0.0]) - sigma = lamb * np.array([ - [np.cosh(2 * r), -np.sinh(2 * r)], - [-np.sinh(2 * r), np.cosh(2 * r)] - ]) - + sigma = lamb * np.array( + [[np.cosh(2 * r), -np.sinh(2 * r)], [-np.sinh(2 * r), np.cosh(2 * r)]] + ) + # Derivatives dmu = [np.array([0.0, 0.0])] - dlamb = -0.5 / (np.sinh(beta / 2)**2) - dsigma = [dlamb * np.array([ - [np.cosh(2 * r), -np.sinh(2 * r)], - [-np.sinh(2 * r), np.cosh(2 * r)] - ])] - + dlamb = -0.5 / (np.sinh(beta / 2) ** 2) + dsigma = [ + dlamb + * np.array( + [[np.cosh(2 * r), -np.sinh(2 * r)], [-np.sinh(2 * r), np.cosh(2 * r)]] + ) + ] + # Calculate QFIM result = QFIM_Gauss(mu, dmu, sigma, dsigma) - expected = (lamb**2 - 1)**2 / (2 * (4 * lamb**2 - 1)) + expected = (lamb**2 - 1) ** 2 / (2 * (4 * lamb**2 - 1)) assert np.allclose(result, expected) @@ -298,32 +326,53 @@ def test_QFIM_LLD_singleparameter() -> None: theta = np.pi / 4 phi = np.pi / 4 eta = 0.8 - rho = 0.5 * np.array([ - [1 + eta * np.cos(2 * theta), - eta * np.sin(2 * theta) * np.exp(-1j * phi)], - [eta * np.sin(2 * theta) * np.exp(1j * phi), - 1 - eta * np.cos(2 * theta)] - ]) - + rho = 0.5 * np.array( + [ + [1 + eta * np.cos(2 * theta), eta * np.sin(2 * theta) * np.exp(-1j * phi)], + [eta * np.sin(2 * theta) * np.exp(1j * phi), 1 - eta * np.cos(2 * theta)], + ] + ) + # State derivative - drho = [0.5 * np.array([ - [0.0, -1j * eta * np.sin(2 * theta) * np.exp(-1j * phi)], - [1j * eta * np.sin(2 * theta) * np.exp(1j * phi), 0.0] - ])] - + drho = [ + 0.5 + * np.array( + [ + [0.0, -1j * eta * np.sin(2 * theta) * np.exp(-1j * phi)], + [1j * eta * np.sin(2 * theta) * np.exp(1j * phi), 0.0], + ] + ) + ] + # Calculate LLD result = LLD(rho, drho, rep="original") - expected = (1 / (1 - eta**2)) * np.array([ - [1j * eta**2 * np.sin(2 * theta)**2, - -1j * eta * (1 + eta * np.cos(2 * theta)) * np.sin(2 * theta) * np.exp(-1j * phi)], - [1j * eta * (1 - eta * np.cos(2 * theta)) * np.sin(2 * theta) * np.exp(1j * phi), - -1j * eta**2 * np.sin(2 * theta)**2] - ]) + expected = (1 / (1 - eta**2)) * np.array( + [ + [ + 1j * eta**2 * np.sin(2 * theta) ** 2, + -1j + * eta + * (1 + eta * np.cos(2 * theta)) + * np.sin(2 * theta) + * np.exp(-1j * phi), + ], + [ + 1j + * eta + * (1 - eta * np.cos(2 * theta)) + * np.sin(2 * theta) + * np.exp(1j * phi), + -1j * eta**2 * np.sin(2 * theta) ** 2, + ], + ] + ) assert np.allclose(result, expected) # Calculate QFIM result_QFIM = QFIM(rho, drho, LDtype="LLD") - expected_QFIM = eta**2 * np.sin(2 * theta)**2 * (3 * eta**2 + 1) / (1 - eta**2)**2 + expected_QFIM = ( + eta**2 * np.sin(2 * theta) ** 2 * (3 * eta**2 + 1) / (1 - eta**2) ** 2 + ) assert np.allclose(result_QFIM, expected_QFIM) # Test eigen representation @@ -346,32 +395,51 @@ def test_QFIM_RLD_singleparameter() -> None: theta = np.pi / 4 phi = np.pi / 4 eta = 0.8 - rho = 0.5 * np.array([ - [1 + eta * np.cos(2 * theta), - eta * np.sin(2 * theta) * np.exp(-1j * phi)], - [eta * np.sin(2 * theta) * np.exp(1j * phi), - 1 - eta * np.cos(2 * theta)] - ]) - + rho = 0.5 * np.array( + [ + [1 + eta * np.cos(2 * theta), eta * np.sin(2 * theta) * np.exp(-1j * phi)], + [eta * np.sin(2 * theta) * np.exp(1j * phi), 1 - eta * np.cos(2 * theta)], + ] + ) + # State derivative - drho = [0.5 * np.array([ - [0.0, -1j * eta * np.sin(2 * theta) * np.exp(-1j * phi)], - [1j * eta * np.sin(2 * theta) * np.exp(1j * phi), 0.0] - ])] - + drho = [ + 0.5 + * np.array( + [ + [0.0, -1j * eta * np.sin(2 * theta) * np.exp(-1j * phi)], + [1j * eta * np.sin(2 * theta) * np.exp(1j * phi), 0.0], + ] + ) + ] + # Calculate RLD result = RLD(rho, drho, rep="original") - expected = (1 / (1 - eta**2)) * np.array([ - [-1j * eta**2 * np.sin(2 * theta)**2, - -1j * eta * (1 - eta * np.cos(2 * theta)) * np.exp(-1j * phi) * np.sin(2 * theta)], - [1j * eta * (1 + np.cos(2 * theta)) * np.exp(1j * phi) * np.sin(2 * theta), - 1j * eta**2 * np.sin(2 * theta)**2] - ]) + expected = (1 / (1 - eta**2)) * np.array( + [ + [ + -1j * eta**2 * np.sin(2 * theta) ** 2, + -1j + * eta + * (1 - eta * np.cos(2 * theta)) + * np.exp(-1j * phi) + * np.sin(2 * theta), + ], + [ + 1j + * eta + * (1 + np.cos(2 * theta)) + * np.exp(1j * phi) + * np.sin(2 * theta), + 1j * eta**2 * np.sin(2 * theta) ** 2, + ], + ] + ) assert np.allclose(result, expected) # Calculate QFIM result_QFIM = QFIM(rho, drho, LDtype="RLD") - expected_QFIM = eta**2 * np.sin(2 * theta)**2 / (1.0 - eta**2) + expected_QFIM = eta**2 * np.sin(2 * theta) ** 2 / (1.0 - eta**2) assert np.allclose(result_QFIM, expected_QFIM) # Test eigen representation @@ -392,17 +460,12 @@ def test_FIM_singleparameter() -> None: """ x = 1.0 theta = np.pi / 3 - p = np.array([ - np.cos(x * theta)**2, - np.sin(x * theta)**2 - ]) - dp = [np.array([ - -x * np.sin(2 * x * theta), - x * np.sin(2 * x * theta) - ])] + p = np.array([np.cos(x * theta) ** 2, np.sin(x * theta) ** 2]) + dp = [np.array([-x * np.sin(2 * x * theta), x * np.sin(2 * x * theta)])] result = FIM(p, dp) assert np.allclose(result, 4.0) + def test_FIM_multiparameter() -> None: """ Test Fisher Information Matrix (FIM) for multiple parameters. @@ -410,27 +473,16 @@ def test_FIM_multiparameter() -> None: """ x = 1.0 theta = np.pi / 3 - p = np.array([ - np.cos(x * theta)**2, - np.sin(x * theta)**2 - ]) + p = np.array([np.cos(x * theta) ** 2, np.sin(x * theta) ** 2]) dp = [ - np.array([ - -theta * np.sin(2 * x * theta), - theta * np.sin(2 * x * theta) - ]), - np.array([ - -x * np.sin(2 * x * theta), - x * np.sin(2 * x * theta) - ]) + np.array([-theta * np.sin(2 * x * theta), theta * np.sin(2 * x * theta)]), + np.array([-x * np.sin(2 * x * theta), x * np.sin(2 * x * theta)]), ] result = FIM(p, dp) - expected = np.array([ - [4.38649084, 4.1887902], - [4.1887902, 4.0] - ]) + expected = np.array([[4.38649084, 4.1887902], [4.1887902, 4.0]]) assert np.allclose(result, expected) + def test_FI_Expt() -> None: """ Test Fisher Information calculation for experimental data. @@ -440,7 +492,7 @@ def test_FI_Expt() -> None: dx = 0.001 y1_norm = np.random.normal(loc=0.0, scale=1.0, size=1000) y2_norm = np.random.normal(loc=dx, scale=1.0, size=1000) - + result_norm = FI_Expt(y1_norm, y2_norm, dx, ftype="norm") result_gamma = FI_Expt(y1_norm, y2_norm, dx, ftype="gamma") result_rayleigh = FI_Expt(y1_norm, y2_norm, dx, ftype="rayleigh") @@ -452,14 +504,16 @@ def test_FI_Expt() -> None: result_poisson = FI_Expt(y1_poi, y2_poi, dx_poisson, ftype="poisson") # Verify results are floats - assert all(isinstance(res, float) for res in [ - result_norm, result_gamma, result_rayleigh, result_poisson - ]) + assert all( + isinstance(res, float) + for res in [result_norm, result_gamma, result_rayleigh, result_poisson] + ) # Test invalid distribution type with pytest.raises(ValueError): FI_Expt(y1_norm, y2_norm, dx, ftype="invalid") + def test_invalid_input() -> None: """ Test input validation for functions in the Cramer-Rao module. @@ -467,19 +521,11 @@ def test_invalid_input() -> None: """ # Test CFIM with invalid inputs with pytest.raises(TypeError): - CFIM( - np.array([[1, 0], [0, 1]]), - np.array([[1, 0], [0, 1]]), - None - ) + CFIM(np.array([[1, 0], [0, 1]]), np.array([[1, 0], [0, 1]]), None) # Test QFIM with invalid LDtype with pytest.raises(ValueError): - QFIM( - np.array([[1, 0], [0, 1]]), - [np.array([[1, 0], [0, 1]])], - LDtype="invalid" - ) + QFIM(np.array([[1, 0], [0, 1]]), [np.array([[1, 0], [0, 1]])], LDtype="invalid") # Test QFIM with invalid drho with pytest.raises(TypeError): diff --git a/test/test_Parameterization.py b/test/test_Parameterization.py index 6f72b189..fe4037bc 100644 --- a/test/test_Parameterization.py +++ b/test/test_Parameterization.py @@ -4,56 +4,34 @@ from quanestimation.Parameterization.NonDynamics import Kraus from quanestimation.Parameterization.GeneralDynamics import Lindblad + def test_Kraus() -> None: """ Test the Kraus function for quantum state evolution and derivatives. - + This test verifies: 1. The evolved density matrix is computed correctly. - 2. The derivatives of the evolved density matrix with respect to + 2. The derivatives of the evolved density matrix with respect to parameters are computed correctly. """ # Kraus operators - K0 = np.array([ - [1, 0], - [0, np.sqrt(0.5)] - ]) - K1 = np.array([ - [np.sqrt(0.5), 0], - [0, 0] - ]) + K0 = np.array([[1, 0], [0, np.sqrt(0.5)]]) + K1 = np.array([[np.sqrt(0.5), 0], [0, 0]]) K = [K0, K1] - + # Kraus operator derivatives dK = [ - [np.array([ - [0, 0], - [0, -0.5 / np.sqrt(0.5)] - ])], - [np.array([ - [0, 0.5 / np.sqrt(0.5)], - [0, 0] - ])] + [np.array([[0, 0], [0, -0.5 / np.sqrt(0.5)]])], + [np.array([[0, 0.5 / np.sqrt(0.5)], [0, 0]])], ] - + # Probe state - rho0 = 0.5 * np.array([ - [1.0, 1.0], - [1.0, 1.0] - ]) + rho0 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) rho, drho = Kraus(rho0, K, dK) - - expected_rho = np.array([ - [0.75, 0.35355339], - [0.35355339, 0.25] - ]) - expected_drho = [ - np.array([ - [0.5, -0.35355339], - [-0.35355339, -0.5] - ]) - ] + + expected_rho = np.array([[0.75, 0.35355339], [0.35355339, 0.25]]) + expected_drho = [np.array([[0.5, -0.35355339], [-0.35355339, -0.5]])] assert np.allclose(rho, expected_rho) for i in range(len(drho)): @@ -63,115 +41,106 @@ def test_Kraus() -> None: def test_Lindblad() -> None: """ Test the Lindblad function for quantum state evolution and derivatives. - + This test verifies: - Correct evolution of a quantum state under Lindblad dynamics - Proper calculation of parameter derivatives - Handling of dissipation effects with decay operators - + Test scenario: Two-level system with spontaneous emission. """ # Initial state - initial_state = 0.5 * np.array([ - [1.0, 1.0], - [1.0, 1.0] - ]) - + initial_state = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) + # Free Hamiltonian parameters frequency = 1.0 - sz = np.array([ - [1.0, 0.0], - [0.0, -1.0] - ]) + sz = np.array([[1.0, 0.0], [0.0, -1.0]]) hamiltonian = 0.5 * frequency * sz - + # Derivative of Hamiltonian with respect to frequency hamiltonian_derivative = [0.5 * sz] - + # Dissipation operators - sp = np.array([ - [0.0, 1.0], - [0.0, 0.0] - ]) - sm = np.array([ - [0.0, 0.0], - [1.0, 0.0] - ]) + sp = np.array([[0.0, 1.0], [0.0, 0.0]]) + sm = np.array([[0.0, 0.0], [1.0, 0.0]]) decay_operators = [[sp, 0.0], [sm, 0.1]] - + # Time points for evolution tspan = np.linspace(0.0, 1.0, 10) # control Hamiltonians and coefficients - sx = np.array([[0., 1.], [1., 0.]]) + sx = np.array([[0.0, 1.0], [1.0, 0.0]]) control_amplitudes = np.zeros(len(tspan)) - + # Create Lindblad dynamics dynamics = Lindblad( - tspan, - initial_state, - hamiltonian, - hamiltonian_derivative, - decay_operators, + tspan, + initial_state, + hamiltonian, + hamiltonian_derivative, + decay_operators, Hc=[sx], - ctrl=[control_amplitudes] + ctrl=[control_amplitudes], ) final_state_expm, state_derivatives_expm = dynamics.expm() final_state_ode, state_derivatives_ode = dynamics.ode() - + # Expected final state - expected_final_state = np.array([ - [0.45241871 + 0.j, 0.25697573 - 0.40021598j], - [0.25697573 + 0.40021598j, 0.54758129 + 0.j] - ]) - assert np.allclose(final_state_expm[-1], expected_final_state, atol = 1e-6) - assert np.allclose(final_state_ode[-1], expected_final_state, atol = 1e-6) + expected_final_state = np.array( + [ + [0.45241871 + 0.0j, 0.25697573 - 0.40021598j], + [0.25697573 + 0.40021598j, 0.54758129 + 0.0j], + ] + ) + assert np.allclose(final_state_expm[-1], expected_final_state, atol=1e-6) + assert np.allclose(final_state_ode[-1], expected_final_state, atol=1e-6) # Expected derivative of final state final_state_derivative_expm = state_derivatives_expm[-1] final_state_derivative_ode = state_derivatives_ode[-1] expected_derivative_expm = [ - np.array([ - [0.0 + 0.j, -0.40021598 - 0.25697573j], - [-0.40021598 + 0.25697573j, 0.0 + 0.j] - ]) + np.array( + [ + [0.0 + 0.0j, -0.40021598 - 0.25697573j], + [-0.40021598 + 0.25697573j, 0.0 + 0.0j], + ] + ) ] expected_derivative_ode = [ - np.array([ - [0.+0.j, -0.40182466-0.25365255j], - [-0.40182466+0.25365255j, 0.+0.j]]) + np.array( + [ + [0.0 + 0.0j, -0.40182466 - 0.25365255j], + [-0.40182466 + 0.25365255j, 0.0 + 0.0j], + ] + ) ] - for i in range(len(final_state_derivative_expm)): assert np.allclose( - final_state_derivative_expm[i], - expected_derivative_expm[i], - atol=1e-6 + final_state_derivative_expm[i], expected_derivative_expm[i], atol=1e-6 ) for i in range(len(final_state_derivative_ode)): assert np.allclose( - final_state_derivative_ode[i], - expected_derivative_ode[i], - atol=1e-6 + final_state_derivative_ode[i], expected_derivative_ode[i], atol=1e-6 ) with pytest.raises(TypeError): - dynamics = Lindblad( - tspan, - initial_state, - hamiltonian, - np.array([0., 1.]), # Incorrect type for derivative - decay_operators - ) - + dynamics = Lindblad( + tspan, + initial_state, + hamiltonian, + np.array([0.0, 1.0]), # Incorrect type for derivative + decay_operators, + ) + + # def test_Lindblad_secondorder_derivative(): # """ # Test the second-order derivative of the Lindblad dynamics. - + # This test verifies: -# - the second-order derivative of the state under the Lindblad dynamics. +# - the second-order derivative of the state under the Lindblad dynamics. # Test scenario: Two-level system with spontaneous emission. # """ @@ -180,7 +149,7 @@ def test_Lindblad() -> None: # [1.0, 1.0], # [1.0, 1.0] # ]) - + # # Free Hamiltonian parameters # frequency = 1.0 # sz = np.array([ @@ -192,37 +161,37 @@ def test_Lindblad() -> None: # # Derivative of Hamiltonian with respect to frequency # hamiltonian_derivative = [0.5 * sz] # hamiltonian_second_derivative = [np.zeros((2, 2))] - + # # Dissipation operators # sp = np.array([ # [0.0, 1.0], # [0.0, 0.0] -# ]) +# ]) # sm = np.array([ # [0.0, 0.0], # [1.0, 0.0] -# ]) +# ]) # decay_operators = [[sp, 0.0], [sm, 0.1]] - + # # Time points for evolution # tspan = np.linspace(0.0, 1.0, 10) # # control Hamiltonians and coefficients # sx = np.array([[0., 1.], [1., 0.]]) # control_amplitudes = np.zeros(len(tspan)) - + # # Create Lindblad dynamics # dynamics = Lindblad( -# tspan, -# initial_state, -# hamiltonian, -# hamiltonian_derivative, -# decay_operators, +# tspan, +# initial_state, +# hamiltonian, +# hamiltonian_derivative, +# decay_operators, # Hc=[sx], # ctrl=[control_amplitudes] # ) # final_state, state_derivatives, state_second_derivative = dynamics.secondorder_derivative(hamiltonian_second_derivative) - + # # Expected final state # expected_final_state = np.array([ # [0.45241871 + 0.j, 0.25697573 - 0.40021598j], diff --git a/test/test_Resource.py b/test/test_Resource.py index 14e850bf..a9c0c7da 100644 --- a/test/test_Resource.py +++ b/test/test_Resource.py @@ -1,15 +1,13 @@ import pytest -from quanestimation.Resource.Resource import ( - SpinSqueezing, - TargetTime -) +from quanestimation.Resource.Resource import SpinSqueezing, TargetTime import numpy as np + def test_SpinSqueezing_Dicke() -> None: """ - Test the SpinSqueezing function with valid input for Dicke basis + Test the SpinSqueezing function with valid input for Dicke basis and check exception handling. - + This test verifies: 1. The function returns the expected value for valid input 2. The function raises NameError for invalid output type @@ -21,20 +19,20 @@ def test_SpinSqueezing_Dicke() -> None: [0.0 + 1.0j, 0.0 + 0.0j, 0.0 - 1.22474487j, 0.0 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 0.0 + 1.22474487j, 0.0 + 0.0j, 0.0 - 1.22474487j, 0.0 + 0.0j], [0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 1.22474487j, 0.0 + 0.0j, 0.0 - 1.0j], - [0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 1.0j, 0.0 + 0.0j] + [0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 1.0j, 0.0 + 0.0j], ] ) - + jz_matrix = np.array( [ [2.0, 0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, -1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, -2.0] + [0.0, 0.0, 0.0, 0.0, -2.0], ] ) - + xi_param = 0.1 density_matrix = 0.5 * xi_param * (jz_matrix**2 - jy_matrix**2) @@ -52,12 +50,13 @@ def test_SpinSqueezing_Dicke() -> None: SpinSqueezing(density_matrix, basis="Dicke", output="invalid") with pytest.raises(ValueError): - SpinSqueezing(density_matrix, basis="invalid", output="KU") + SpinSqueezing(density_matrix, basis="invalid", output="KU") + def test_SpinSqueezing_Pauli() -> None: """ Test the SpinSqueezing function with Pauli basis. - + This test verifies: The function returns the expected value for valid input. """ @@ -66,9 +65,9 @@ def test_SpinSqueezing_Pauli() -> None: # sz = np.array([[1., 0.], [0., -1.]]) # ide = np.identity(2) - # jy_matrix = np.kron(ide, np.kron(sy, ide)) + np.kron(sy, np.kron(ide, ide)) + np.kron(ide, np.kron(ide, sy)) + # jy_matrix = np.kron(ide, np.kron(sy, ide)) + np.kron(sy, np.kron(ide, ide)) + np.kron(ide, np.kron(ide, sy)) # jz_matrix = np.kron(ide, np.kron(sz, ide)) + np.kron(sz, np.kron(ide, ide)) + np.kron(ide, np.kron(ide, sz)) - + # xi_param = 0.1 # density_matrix = 0.5 * xi_param * (jz_matrix**2 - jy_matrix**2) a = np.array([1, 0, 0, 0, 0, 0, 0, 0]) @@ -76,19 +75,20 @@ def test_SpinSqueezing_Pauli() -> None: # Test valid output type result = SpinSqueezing(density_matrix, basis="Pauli", output="KU") - expected = 1. + expected = 1.0 assert np.allclose(result, expected) + def test_SpinSqueezing_nomean() -> None: """ Test the SpinSqueezing function with a density matrix that has no mean values of Jx, Jy, and Jz. - + This test verifies: The function raises ValueError when the density matrix does not have a valid spin squeezing. """ # Create a density matrix with no mean values rho = np.array([[0.5, 0.0], [0.0, 0.5]]) - + with pytest.raises(ValueError): SpinSqueezing(rho, basis="Pauli", output="KU") @@ -103,11 +103,12 @@ def test_TargetTime() -> None: testfunc = lambda t, omega: np.cos(omega * t) tspan = np.linspace(0, np.pi, 1000000) - target = 0. - result = TargetTime(target, tspan, testfunc, 1.) + target = 0.0 + result = TargetTime(target, tspan, testfunc, 1.0) expected = np.pi / 2 assert np.allclose(result, expected, atol=1e-4) + def test_TargetTime_no_crossing(capfd): """ Test the TargetTime function when no crossing occurs. @@ -115,13 +116,13 @@ def test_TargetTime_no_crossing(capfd): This test verifies: The function returns None when no crossing is found. """ - + testfunc = lambda t: np.cos(t) tspan = np.linspace(0, 1, 100) - target = 2. # No crossing with cos(t) - + target = 2.0 # No crossing with cos(t) + result = TargetTime(target, tspan, testfunc) out, _ = capfd.readouterr() - assert "No time is found in the given time span to reach the target." in out - assert result is None + assert "No time is found in the given time span to reach the target." in out + assert result is None diff --git a/test/test_ZivZakai.py b/test/test_ZivZakai.py index 1588f857..b916ca42 100644 --- a/test/test_ZivZakai.py +++ b/test/test_ZivZakai.py @@ -4,39 +4,42 @@ from quanestimation.BayesianBound.ZivZakai import QZZB from quanestimation.Parameterization.GeneralDynamics import Lindblad + def test_ZivZakai() -> None: # initial state - rho0 = 0.5 * np.array([[1., 1.], [1., 1.]]) + rho0 = 0.5 * np.array([[1.0, 1.0], [1.0, 1.0]]) # free Hamiltonian B, omega0 = 0.5 * np.pi, 1.0 - sx = np.array([[0., 1.], [1., 0.]]) - sz = np.array([[1., 0.], [0., -1.]]) + sx = np.array([[0.0, 1.0], [1.0, 0.0]]) + sz = np.array([[1.0, 0.0], [0.0, -1.0]]) H0_func = lambda x: 0.5 * B * omega0 * (sx * np.cos(x) + sz * np.sin(x)) # derivative of the free Hamiltonian on x dH_func = lambda x: [0.5 * B * omega0 * (-sx * np.sin(x) + sz * np.cos(x))] # prior distribution x = np.linspace(-0.5 * np.pi, 0.5 * np.pi, 100) mu, eta = 0.0, 0.2 - p_func = lambda x, mu, eta: np.exp(-(x-mu)**2 / (2 * eta**2))/(eta * np.sqrt(2 * np.pi)) + p_func = lambda x, mu, eta: np.exp(-((x - mu) ** 2) / (2 * eta**2)) / ( + eta * np.sqrt(2 * np.pi) + ) p_tp = [p_func(x[i], mu, eta) for i in range(len(x))] # normalization of the distribution c = simpson(p_tp, x) - p = p_tp/c + p = p_tp / c # time length for the evolution - tspan = np.linspace(0., 1., 50) + tspan = np.linspace(0.0, 1.0, 50) # dynamics rho = [np.zeros((len(rho0), len(rho0)), dtype=np.complex128) for i in range(len(x))] - drho = [[np.zeros((len(rho0), len(rho0)), dtype=np.complex128)] for i in range(len(x))] + drho = [ + [np.zeros((len(rho0), len(rho0)), dtype=np.complex128)] for i in range(len(x)) + ] for i in range(len(x)): H0_tp = H0_func(x[i]) dH_tp = dH_func(x[i]) dynamics = Lindblad(tspan, rho0, H0_tp, dH_tp) rho_tp, drho_tp = dynamics.expm() rho[i] = rho_tp[-1] - drho[i] = drho_tp[-1] + drho[i] = drho_tp[-1] f_QZZB = QZZB([x], p, rho) - expected_QZZB = 0.028521709437588784 - assert np.allclose(f_QZZB, expected_QZZB) - - + expected_QZZB = 0.028521709437588784 + assert np.allclose(f_QZZB, expected_QZZB)