diff --git a/filter_functions/basis.py b/filter_functions/basis.py index 35d563e..d42f05d 100644 --- a/filter_functions/basis.py +++ b/filter_functions/basis.py @@ -249,17 +249,16 @@ def isorthonorm(self) -> bool: # unitary. if self.ndim == 2: # Only one basis element - dim = 1 + self._isorthonorm = True else: # Size of the result after multiplication dim = self.shape[0] - - U = self.reshape((dim, -1)) - actual = U.conj() @ U.T - target = np.identity(dim) - atol = self._eps*(self.d**2)**3 - self._isorthonorm = np.allclose(actual.view(ndarray), target, - atol=atol, rtol=self._rtol) + U = self.reshape((dim, -1)) + actual = U.conj() @ U.T + target = np.identity(dim) + atol = self._eps*(self.d**2)**3 + self._isorthonorm = np.allclose(actual.view(ndarray), target, + atol=atol, rtol=self._rtol) return self._isorthonorm diff --git a/filter_functions/numeric.py b/filter_functions/numeric.py index 54e73a2..63c646c 100644 --- a/filter_functions/numeric.py +++ b/filter_functions/numeric.py @@ -581,6 +581,7 @@ def liouville_representation(U: ndarray, basis: Basis) -> ndarray: :math:`\mathbb{C}^{d\times d}` with :math:`d` the dimension of the Hilbert space. """ + U = np.asanyarray(U) if basis.btype == 'GGM' and basis.d > 12: # Can do closed form expansion and overhead compensated path = ['einsum_path', (0, 1), (0, 1)] diff --git a/filter_functions/plotting.py b/filter_functions/plotting.py index aa586cf..ff411a6 100644 --- a/filter_functions/plotting.py +++ b/filter_functions/plotting.py @@ -58,20 +58,20 @@ 'plot_infidelity_convergence', 'plot_error_transfer_matrix'] -def get_bloch_vector(states: Sequence[State], outtype: str = 'np') -> ndarray: +def get_bloch_vector(states: Sequence[State]) -> ndarray: r""" Get the Bloch vector from quantum states. """ - if outtype == 'np': - a = np.array([[(states[i].T.conj() @ util.P_np[j+1] @ states[i])[0, 0] - for i in range(len(states))] for j in range(3)]) - else: + if isinstance(states[0], Qobj): a = np.empty((3, len(states))) X, Y, Z = util.P_qt[1:] for i, state in enumerate(states): a[:, i] = [expect(X, state), expect(Y, state), expect(Z, state)] + else: + a = np.einsum('...ij,kil,...lm->k...', np.conj(states), util.P_np[1:], + states) return a diff --git a/filter_functions/pulse_sequence.py b/filter_functions/pulse_sequence.py index e0fbcc1..6620a86 100644 --- a/filter_functions/pulse_sequence.py +++ b/filter_functions/pulse_sequence.py @@ -842,6 +842,11 @@ def _parse_args(H_c: Hamiltonian, H_n: Hamiltonian, dt: Coefficients, Function to parse the arguments given at instantiation of the PulseSequence object. """ + + if not hasattr(dt, '__getitem__'): + raise TypeError('Expected a sequence of time steps, not {}'.format( + type(dt))) + dt = np.asarray(dt) # Check the time argument for data type and monotonicity (should be # increasing) diff --git a/tests/test_basis.py b/tests/test_basis.py index 23ba271..d6e0340 100644 --- a/tests/test_basis.py +++ b/tests/test_basis.py @@ -26,12 +26,42 @@ import numpy as np import filter_functions as ff +from sparse import COO from tests import testutil class BasisTest(testutil.TestCase): - def test_basis_class(self): + def test_basis_constructor(self): + """Test the constructor for several failure modes""" + + # Constructing from given elements should check for __getitem__ + with self.assertRaises(TypeError): + _ = ff.Basis(1) + + # All elements should be either COO, Qobj, or ndarray + elems = [ff.util.P_np[1], ff.util.P_qt[2], + COO.from_numpy(ff.util.P_np[3]), ff.util.P_qt[0].data] + with self.assertRaises(TypeError): + _ = ff.Basis(elems) + + # Too many elements + with self.assertRaises(ValueError): + _ = ff.Basis(np.random.randn(5, 2, 2)) + + # Properly normalized + self.assertTrue(ff.Basis.pauli(1) == ff.Basis(ff.util.P_np)) + + # Warns if orthonormal basis couldn't be generated + basis = ff.Basis( + [np.pad(b, (0, 1), 'constant') for b in ff.Basis.pauli(1)], + skip_check=True, + btype='Pauli' + ) + with self.assertWarns(UserWarning): + _ = ff.Basis(basis) + + def test_basis_properties(self): """Basis orthonormal and of correct dimensions""" d = np.random.randint(2, 17) ggm_basis = ff.Basis.ggm(d) @@ -41,6 +71,9 @@ def test_basis_class(self): btypes = ('Pauli', 'GGM') bases = (pauli_basis, ggm_basis) for btype, base in zip(btypes, bases): + base.tidyup(eps_scale=0) + self.assertTrue(base == base) + self.assertFalse(base == ff.Basis.ggm(d+1)) self.assertEqual(btype, base.btype) if not btype == 'Pauli': self.assertEqual(d, base.d) @@ -51,6 +84,8 @@ def test_basis_class(self): # Check if __contains__ works as expected self.assertTrue(base[np.random.randint(0, (2**n)**2)] in base) # Check if all elements of each basis are orthonormal and hermitian + self.assertArrayEqual(base.T, + base.view(np.ndarray).swapaxes(-1, -2)) self.assertTrue(base.isorthonorm) self.assertTrue(base.isherm) # Check if basis spans the whole space and all elems are traceless @@ -58,6 +93,9 @@ def test_basis_class(self): self.assertTrue(base.iscomplete) # Check sparse representation self.assertArrayEqual(base.sparse.todense(), base) + # Test sparse cache + self.assertArrayEqual(base.sparse.todense(), base) + if base.d < 8: # Test very resource intense self.assertArrayAlmostEqual(base.four_element_traces.todense(), @@ -67,6 +105,10 @@ def test_basis_class(self): base._print_checks() + basis = ff.util.P_np[1].view(ff.Basis) + self.assertTrue(basis.isorthonorm) + self.assertArrayEqual(basis.T, basis.view(np.ndarray).T) + def test_basis_expansion_and_normalization(self): """Correct expansion of operators and normalization of bases""" for _ in range(10): diff --git a/tests/test_core.py b/tests/test_core.py index dd931e6..cdeb0a3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -28,9 +28,14 @@ from numpy.random import choice, randint, randn import filter_functions as ff -from filter_functions.numeric import (calculate_control_matrix_from_atomic, - calculate_control_matrix_from_scratch, - diagonalize, liouville_representation) +from filter_functions.numeric import ( + calculate_control_matrix_from_atomic, + calculate_control_matrix_from_scratch, + calculate_error_vector_correlation_functions, + calculate_pulse_correlation_filter_function, + diagonalize, + liouville_representation +) from tests import testutil @@ -50,6 +55,10 @@ def test_pulse_sequence_constructor(self): # Not enough positional arguments ff.PulseSequence(H_c, H_n) + with self.assertRaises(TypeError): + # dt not a sequence + ff.PulseSequence(H_c, H_n, dt[0]) + idx = randint(0, 5) with self.assertRaises(ValueError): # negative dt @@ -204,7 +213,7 @@ def test_pulse_sequence_constructor(self): print(pulse) # Hit __copy__ method - pulse_copy = copy(pulse) + _ = copy(pulse) # Fewer identifiers than opers pulse_2 = ff.PulseSequence( @@ -218,6 +227,140 @@ def test_pulse_sequence_constructor(self): self.assertArrayEqual(pulse_2.n_oper_identifiers, ('B_0', 'Y')) def test_pulse_sequence_attributes(self): + """Test attributes of single instance""" + X, Y, Z = ff.util.P_np[1:] + n_dt = randint(1, 10) + + # trivial case + A = ff.PulseSequence([[X, randn(n_dt), 'X']], + [[Z, randn(n_dt), 'Z']], + np.abs(randn(n_dt))) + self.assertFalse(A == 1) + self.assertTrue(A != 1) + + # different number of time steps + B = ff.PulseSequence([[X, randn(n_dt+1), 'X']], + [[Z, randn(n_dt+1), 'Z']], + np.abs(randn(n_dt+1))) + self.assertFalse(A == B) + self.assertTrue(A != B) + + # different time steps + B = ff.PulseSequence( + list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), + list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), + np.abs(randn(n_dt)) + ) + self.assertFalse(A == B) + self.assertTrue(A != B) + + # different control opers + B = ff.PulseSequence( + list(zip([Y], A.c_coeffs, A.c_oper_identifiers)), + list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), + A.dt + ) + self.assertFalse(A == B) + self.assertTrue(A != B) + + # different control coeffs + B = ff.PulseSequence( + list(zip(A.c_opers, [randn(n_dt)], A.c_oper_identifiers)), + list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), + A.dt + ) + self.assertFalse(A == B) + self.assertTrue(A != B) + + # different noise opers + B = ff.PulseSequence( + list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), + list(zip([Y], A.n_coeffs, A.n_oper_identifiers)), + A.dt + ) + self.assertFalse(A == B) + self.assertTrue(A != B) + + # different noise coeffs + B = ff.PulseSequence( + list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), + list(zip(A.n_opers, [randn(n_dt)], A.n_oper_identifiers)), + A.dt + ) + self.assertFalse(A == B) + self.assertTrue(A != B) + + # different control oper identifiers + B = ff.PulseSequence( + list(zip(A.c_opers, A.c_coeffs, ['foobar'])), + list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), + A.dt + ) + self.assertFalse(A == B) + self.assertTrue(A != B) + + # different noise oper identifiers + B = ff.PulseSequence( + list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), + list(zip(A.n_opers, A.n_coeffs, ['foobar'])), + A.dt + ) + self.assertFalse(A == B) + self.assertTrue(A != B) + + # different bases + elem = testutil.rand_herm(2) + elem -= np.eye(2)*np.trace(elem)/2 + B = ff.PulseSequence( + list(zip(A.c_opers, A.c_coeffs, A.c_oper_identifiers)), + list(zip(A.n_opers, A.n_coeffs, A.n_oper_identifiers)), + A.dt, + ff.Basis([elem]) + ) + self.assertFalse(A == B) + self.assertTrue(A != B) + + # Test for attributes + for attr in A.__dict__.keys(): + if not (attr.startswith('_') or '_' + attr in A.__dict__.keys()): + # not a cached attribute + with self.assertRaises(AttributeError): + _ = A.is_cached(attr) + else: + self.assertFalse(A.is_cached(attr)) + + # Test cleanup + C = ff.concatenate((A, A), calc_pulse_correlation_ff=True, + omega=ff.util.get_sample_frequencies(A)) + C.diagonalize() + C.cache_filter_function(ff.util.get_sample_frequencies(A)) + attrs = ['_HD', '_HV', '_Q'] + for attr in attrs: + self.assertIsNotNone(getattr(C, attr)) + + C.cleanup() + for attr in attrs: + self.assertIsNone(getattr(C, attr)) + + C.diagonalize() + attrs.extend(['_R', '_total_phases', '_total_Q', '_total_Q_liouville']) + for attr in attrs: + self.assertIsNotNone(getattr(C, attr)) + + C.cleanup('greedy') + for attr in attrs: + self.assertIsNone(getattr(C, attr)) + + C.cache_filter_function(ff.util.get_sample_frequencies(A)) + attrs.extend(['omega', '_F', '_F_pc']) + for attr in attrs: + self.assertIsNotNone(getattr(C, attr)) + + C.cleanup('all') + for attr in attrs: + self.assertIsNone(getattr(C, attr)) + + def test_pulse_sequence_attributes_concat(self): """Test attributes of concatenated sequence.""" X, Y, Z = ff.util.P_np[1:] n_dt_1 = randint(5, 11) @@ -242,12 +385,15 @@ def test_pulse_sequence_attributes(self): pulse_12 = pulse_1 @ pulse_2 pulse_21 = pulse_2 @ pulse_1 + with self.assertRaises(TypeError): + _ = pulse_1 @ randn(2, 2) + # Concatenate pulses with same operators but different labels with self.assertRaises(ValueError): pulse_1 @ pulse_3 # Test nbytes property - nbytes = pulse_1.nbytes + _ = pulse_1.nbytes self.assertArrayEqual(pulse_12.dt, [*dt_1, *dt_2]) self.assertArrayEqual(pulse_21.dt, [*dt_2, *dt_1]) @@ -400,6 +546,9 @@ def test_pulse_correlation_filter_function(self): pulse_2 = ff.concatenate([pulses['X'], pulses['Y']], calc_pulse_correlation_ff=True) + with self.assertRaises(ValueError): + calculate_pulse_correlation_filter_function(pulse_1._R) + # Check if the filter functions on the diagonals are real F = pulse_2.get_pulse_correlation_filter_function() diag_1 = np.eye(2, dtype=bool) @@ -451,6 +600,21 @@ def test_pulse_correlation_filter_function(self): self.assertAlmostEqual(infid_1.sum(), infid_2.sum()) self.assertArrayAlmostEqual(infid_1, infid_2.sum(axis=(0, 1))) + def test_calculate_error_vector_correlation_functions(self): + """Test raises of numeric.error_transfer_matrix""" + pulse = ff.PulseSequence([[ff.util.P_np[1], [np.pi/2]]], + [[ff.util.P_np[1], [1]]], + [1]) + + omega = randn(43) + # single spectrum + S = randn(78) + for i in range(4): + with self.assertRaises(ValueError): + calculate_error_vector_correlation_functions( + pulse, np.tile(S, [1]*i), omega + ) + def test_infidelity_convergence(self): import matplotlib matplotlib.use('Agg') diff --git a/tests/test_plotting.py b/tests/test_plotting.py index dfd7305..6de92d2 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -35,7 +35,9 @@ from numpy.random import randint, randn import filter_functions as ff -from filter_functions.plotting import (init_bloch_sphere, +from filter_functions.plotting import (get_bloch_vector, + get_states_from_prop, + init_bloch_sphere, plot_bloch_vector_evolution, plot_filter_function, plot_pulse_correlation_filter_function, @@ -69,6 +71,26 @@ class PlottingTest(testutil.TestCase): + def test_get_bloch_vector(self): + states = [qt.rand_ket(2) for _ in range(10)] + bloch_vectors_qt = get_bloch_vector(states) + bloch_vectors_np = get_bloch_vector([state.full() for state in states]) + + for bv_qt, bv_np in zip(bloch_vectors_qt, bloch_vectors_np): + self.assertArrayAlmostEqual(bv_qt, bv_np) + + def test_get_states_from_prop(self): + P = testutil.rand_unit(2, 10) + Q = np.empty((11, 2, 2), dtype=complex) + Q[0] = np.identity(2) + for i in range(10): + Q[i+1] = P[i] @ Q[i] + + psi0 = qt.rand_ket(2) + states_piecewise = get_states_from_prop(P, psi0, 'piecewise') + states_total = get_states_from_prop(Q[1:], psi0, 'total') + self.assertArrayAlmostEqual(states_piecewise, states_total) + def test_plot_bloch_vector_evolution(self): two_qubit_pulse = ff.PulseSequence( [[qt.tensor(qt.sigmax(), qt.sigmax()), [np.pi/2]]], @@ -96,6 +118,13 @@ def test_plot_pulse_train(self): # Call with default args fig, ax, leg = plot_pulse_train(simple_pulse) + # Call with no axes but figure + fig = plt.figure() + fig, ax, leg = plot_pulse_train(simple_pulse, fig=fig) + + # Call with axes but no figure + fig, ax, leg = plot_pulse_train(simple_pulse, axes=ax) + # Call with custom args c_oper_identifiers = sample( complicated_pulse.c_oper_identifiers.tolist(), randint(2, 4) @@ -126,8 +155,16 @@ def test_plot_pulse_train(self): def test_plot_filter_function(self): # Call with default args + simple_pulse.cleanup('all') fig, ax, leg = plot_filter_function(simple_pulse) + # Call with no axes but figure + fig = plt.figure() + fig, ax, leg = plot_filter_function(simple_pulse, fig=fig) + + # Call with axes but no figure + fig, ax, leg = plot_filter_function(simple_pulse, axes=ax) + # Non-default args n_oper_identifiers = sample( complicated_pulse.n_oper_identifiers.tolist(), randint(2, 4) @@ -249,6 +286,9 @@ def test_plot_error_transfer_matrix(self): U = ff.error_transfer_matrix(simple_pulse, S, omega) fig, grid = plot_error_transfer_matrix(U=U) + # Log colorscale + fig, grid = plot_error_transfer_matrix(U=U, colorscale='log') + # Non-default args n_oper_inds = sample(range(len(complicated_pulse.n_opers)), randint(2, 4)) diff --git a/tests/test_precision.py b/tests/test_precision.py index b7ffc91..22f4059 100644 --- a/tests/test_precision.py +++ b/tests/test_precision.py @@ -27,12 +27,29 @@ from opt_einsum import contract import filter_functions as ff -from filter_functions.analytic import CPMG, SE, UDD +from filter_functions import analytic +from filter_functions.numeric import ( + calculate_error_vector_correlation_functions, + liouville_representation +) from tests import testutil class PrecisionTest(testutil.TestCase): + def test_FID(self): + """FID""" + tau = abs(randn()) + FID_pulse = ff.PulseSequence([[ff.util.P_np[1]/2, [0]]], + [[ff.util.P_np[3]/2, [1]]], + [tau]) + + omega = ff.util.get_sample_frequencies(FID_pulse, 50, spacing='linear') + # Comparison to filter function defined with omega**2 + F = FID_pulse.get_filter_function(omega).squeeze()*omega**2 + + self.assertArrayAlmostEqual(F, analytic.FID(omega*tau), atol=1e-10) + def test_SE(self): """Spin echo""" tau = np.pi @@ -49,7 +66,7 @@ def test_SE(self): # Comparison to filter function defined with omega**2 F = SE_pulse.get_filter_function(omega)[0, 0]*omega**2 - self.assertArrayAlmostEqual(F, SE(omega*tau), atol=1e-10) + self.assertArrayAlmostEqual(F, analytic.SE(omega*tau), atol=1e-10) # Test again with a factor of one between the noise operators and # coefficients @@ -60,7 +77,7 @@ def test_SE(self): # Comparison to filter function defined with omega**2 F = SE_pulse.get_filter_function(omega)[0, 0]*omega**2 - self.assertArrayAlmostEqual(F, SE(omega*tau), atol=1e-10) + self.assertArrayAlmostEqual(F, analytic.SE(omega*tau), atol=1e-10) def test_6_pulse_CPMG(self): """6-pulse CPMG""" @@ -78,7 +95,7 @@ def test_6_pulse_CPMG(self): # Comparison to filter function defined with omega**2 F = CPMG_pulse.get_filter_function(omega)[0, 0]*omega**2 - self.assertArrayAlmostEqual(F, CPMG(omega*tau, n), atol=1e-10) + self.assertArrayAlmostEqual(F, analytic.CPMG(omega*tau, n), atol=1e-10) def test_6_pulse_UDD(self): """6-pulse UDD""" @@ -97,7 +114,86 @@ def test_6_pulse_UDD(self): # Comparison to filter function defined with omega**2 F = UDD_pulse.get_filter_function(omega)[0, 0]*omega**2 - self.assertArrayAlmostEqual(F, UDD(omega*tau, n), atol=1e-10) + self.assertArrayAlmostEqual(F, analytic.UDD(omega*tau, n), atol=1e-10) + + def test_6_pulse_PDD(self): + """6-pulse PDD""" + tau = np.pi + tau_pi = 1e-9 + omega = np.logspace(0, 3, 100) + omega = np.concatenate([-omega[::-1], omega]) + n = 6 + + H_c, dt = testutil.generate_dd_hamiltonian(n, tau=tau, tau_pi=tau_pi, + dd_type='pdd') + + H_n = [[ff.util.P_np[3]/2, np.ones_like(dt)]] + + PDD_pulse = ff.PulseSequence(H_c, H_n, dt) + # Comparison to filter function defined with omega**2 + F = PDD_pulse.get_filter_function(omega)[0, 0]*omega**2 + + self.assertArrayAlmostEqual(F, analytic.PDD(omega*tau, n), atol=1e-10) + + def test_5_pulse_CDD(self): + """5-pulse CDD""" + tau = np.pi + tau_pi = 1e-9 + omega = np.logspace(0, 3, 100) + omega = np.concatenate([-omega[::-1], omega]) + n = 3 + + H_c, dt = testutil.generate_dd_hamiltonian(n, tau=tau, tau_pi=tau_pi, + dd_type='cdd') + + H_n = [[ff.util.P_np[3]/2, np.ones_like(dt)]] + + CDD_pulse = ff.PulseSequence(H_c, H_n, dt) + # Comparison to filter function defined with omega**2 + F = CDD_pulse.get_filter_function(omega)[0, 0]*omega**2 + + self.assertArrayAlmostEqual(F, analytic.CDD(omega*tau, n), atol=1e-10) + + def test_liouville_representation(self): + """Test the calculation of the transfer matrix""" + dd = np.arange(2, 18, 4) + + for d in dd: + # Works with different bases + if d == 4: + basis = ff.Basis.pauli(2) + else: + basis = ff.Basis.ggm(d) + + U = testutil.rand_unit(d, 2) + + # Works on matrices and arrays of matrices + U_liouville = liouville_representation(U[0], basis) + U_liouville = liouville_representation(U, basis) + + # should have dimension d^2 x d^2 + self.assertEqual(U_liouville.shape, (U.shape[0], d**2, d**2)) + # Real + self.assertTrue(np.isreal(U_liouville).all()) + # Hermitian for unitary input + self.assertArrayAlmostEqual( + U_liouville.swapaxes(-1, -2) @ U_liouville, + np.tile(np.eye(d**2), (U.shape[0], 1, 1)), + atol=np.finfo(float).eps*d**2 + ) + + if d == 2: + U_liouville = liouville_representation(ff.util.P_np[1:], + basis) + self.assertArrayAlmostEqual(U_liouville[0], + np.diag([1, 1, -1, -1]), + atol=np.finfo(float).eps) + self.assertArrayAlmostEqual(U_liouville[1], + np.diag([1, -1, 1, -1]), + atol=np.finfo(float).eps) + self.assertArrayAlmostEqual(U_liouville[2], + np.diag([1, -1, -1, 1]), + atol=np.finfo(float).eps) def test_diagonalization_cnot(self): """CNOT""" @@ -292,7 +388,7 @@ def test_single_qubit_error_transfer_matrix(self): # Check that _single_qubit_error_transfer_matrix and # _multi_qubit_... # give the same - u_kl = ff.numeric.calculate_error_vector_correlation_functions( + u_kl = calculate_error_vector_correlation_functions( pulse, S, omega, n_oper_identifiers ) U_multi = np.zeros_like(U) @@ -311,7 +407,7 @@ def test_single_qubit_error_transfer_matrix(self): # Check that _single_qubit_error_transfer_matrix and # _multi_qubit_... # give the same - u_kl = ff.numeric.calculate_error_vector_correlation_functions( + u_kl = calculate_error_vector_correlation_functions( pulse, S, omega, n_oper_identifiers ) U_multi = np.zeros_like(U) @@ -332,7 +428,7 @@ def test_single_qubit_error_transfer_matrix(self): # Check that _single_qubit_error_transfer_matrix and # _multi_qubit_... # give the same - u_kl = ff.numeric.calculate_error_vector_correlation_functions( + u_kl = calculate_error_vector_correlation_functions( pulse, S, omega, n_oper_identifiers ) U_multi = np.zeros_like(U) diff --git a/tests/test_util.py b/tests/test_util.py index e5cae24..4dd6699 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -463,6 +463,11 @@ def test_oper_equiv(self): self.assertFalse(result[0]) def test_dot_HS(self): + U, V = randint(0, 100, (2, 2, 2)) + S = util.dot_HS(U, V) + T = util.dot_HS(U, V, eps=0) + self.assertArrayEqual(S, T) + for d in randint(2, 10, (5,)): U = qt.rand_herm(d) V = qt.rand_herm(d) diff --git a/tests/testutil.py b/tests/testutil.py index f5c6a22..3a15686 100644 --- a/tests/testutil.py +++ b/tests/testutil.py @@ -84,12 +84,26 @@ def generate_dd_hamiltonian(n, tau=10, tau_pi=1e-2, dd_type='cpmg', *pulse_type* toggles between a primitive NOT-pulse and a dynamically corrected gate. """ + def cdd_odd(l, t): + return np.array([*cdd_even(l-1, t/2), t/2, *cdd_even(l-1, t/2) + t/2]) + + def cdd_even(l, t): + if l == 0: + return np.array([]) + + return np.array([*cdd_odd(l-1, t/2), *cdd_odd(l-1, t/2) + t/2]) + if dd_type == 'cpmg': delta = np.array([0] + [(l - 0.5)/n for l in range(1, n+1)]) elif dd_type == 'udd': delta = np.array( [0] + [np.sin(np.pi*l/(2*n + 2))**2 for l in range(1, n+1)] ) + elif dd_type == 'pdd': + delta = np.array([0] + [l/(n + 1) for l in range(1, n+1)]) + elif dd_type == 'cdd': + delta = cdd_odd(n, 1) if n % 2 else cdd_even(n, 1) + delta = np.insert(delta, 0, 0) if pulse_type == 'primitive': tau_p = tau_pi @@ -102,7 +116,7 @@ def generate_dd_hamiltonian(n, tau=10, tau_pi=1e-2, dd_type='cpmg', s = np.array([]) t = np.array([0]) - for i in range(n): + for i in range(len(delta) - 1): s = np.append(s, s_p) t = np.append(t, t_p + (delta*tau)[i+1] - tau_p/2) t = np.append(t, tau)