def test_01_6_unitary_hadamard_grad(self): """ control.pulseoptim: Hadamard gate gradient check assert that gradient approx and exact gradient match in tolerance """ # Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 10 evo_time = 10 # Create the optim objects optim = cpo.create_pulse_optimizer(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, dyn_type='UNIT', init_pulse_type='LIN', gen_stats=True) dyn = optim.dynamics init_amps = optim.pulse_generator.gen_pulse().reshape([-1, 1]) dyn.initialize_controls(init_amps) # Check the exact gradient func = optim.fid_err_func_wrapper grad = optim.fid_err_grad_wrapper x0 = dyn.ctrl_amps.flatten() grad_diff = check_grad(func, grad, x0) assert_almost_equal(grad_diff, 0.0, decimal=6, err_msg="Unitary gradient outside tolerance")
def count_waves(n_ts, evo_time, ptype, freq=None, num_waves=None): # Any dyn config will do #Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) pulse_params = {} if freq is not None: pulse_params['freq'] = freq if num_waves is not None: pulse_params['num_waves'] = num_waves optim = cpo.create_pulse_optimizer(H_d, H_c, U_0, U_targ, n_ts, evo_time, dyn_type='UNIT', init_pulse_type=ptype, init_pulse_params=pulse_params, gen_stats=False) pgen = optim.pulse_generator pulse = pgen.gen_pulse() # count number of waves zero_cross = pulse[0:-2]*pulse[1:-1] < 0 return (sum(zero_cross) + 1) / 2
def test_11_time_dependent_drift(self): """ control.pulseoptim: Hadamard gate with fixed and time varying drift assert that goal is achieved for both and that different control pulses are produced (only) when they should be """ # Hadamard H_0 = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 20 evo_time = 10 drift_amps_flat = np.ones([n_ts], dtype=float) dript_amps_step = [np.round(float(k)/n_ts) for k in range(n_ts)] # Run the optimisations result_fixed = cpo.optimize_pulse_unitary(H_0, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=True) assert_(result_fixed.goal_achieved, msg="Fixed drift goal not achieved. " "Terminated due to: {}, with infidelity: {}".format( result_fixed.termination_reason, result_fixed.fid_err)) H_d = [drift_amps_flat[k]*H_0 for k in range(n_ts)] result_flat = cpo.optimize_pulse_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=True) assert_(result_flat.goal_achieved, msg="Flat drift goal not achieved. " "Terminated due to: {}, with infidelity: {}".format( result_flat.termination_reason, result_flat.fid_err)) # Check fixed and flat produced the same pulse assert_almost_equal(result_fixed.final_amps, result_flat.final_amps, decimal=9, err_msg="Flat and fixed drift result in " "different control pules") H_d = [dript_amps_step[k]*H_0 for k in range(n_ts)] result_step = cpo.optimize_pulse_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=True) assert_(result_step.goal_achieved, msg="Step drift goal not achieved. " "Terminated due to: {}, with infidelity: {}".format( result_step.termination_reason, result_step.fid_err)) # Check step and flat produced different results assert_(np.any( np.abs(result_flat.final_amps - result_step.final_amps) > 1e-3), msg="Flat and step drift result in " "the same control pules")
def test_01_4_unitary_hadamard_qobj(self): """ control.pulseoptim: Hadamard gate with linear initial pulses (Qobj) assert that goal is achieved """ # Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 10 evo_time = 10 # Run the optimisation #Try with Qobj propagation result = cpo.optimize_pulse_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', dyn_params={'oper_dtype':Qobj}, gen_stats=True) assert_(result.goal_achieved, msg="Hadamard goal not achieved " "(Qobj propagation). " "Terminated due to: {}, with infidelity: {}".format( result.termination_reason, result.fid_err))
def test_01_1_unitary_hadamard(self): """ control.pulseoptim: Hadamard gate with linear initial pulses assert that goal is achieved and fidelity error is below threshold """ # Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 10 evo_time = 10 # Run the optimisation result = cpo.optimize_pulse_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=True) assert_(result.goal_achieved, msg="Hadamard goal not achieved. " "Terminated due to: {}, with infidelity: {}".format( result.termination_reason, result.fid_err)) assert_almost_equal(result.fid_err, 0.0, decimal=10, err_msg="Hadamard infidelity too high")
def test_01_2_unitary_hadamard_no_stats(self): """ control.pulseoptim: Hadamard gate with linear initial pulses (no stats) assert that goal is achieved """ # Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 10 evo_time = 10 # Run the optimisation #Try without stats result = cpo.optimize_pulse_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=False) assert_(result.goal_achieved, msg="Hadamard goal not achieved " "(no stats). " "Terminated due to: {}, with infidelity: {}".format( result.termination_reason, result.fid_err))
def test_08_crab(self): """ control.pulseoptim: Hadamard gate using CRAB algorithm Apply guess and ramping pulse assert that goal is achieved and fidelity error is below threshold assert that starting amplitude is zero """ # Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 12 evo_time = 10 # Run the optimisation result = cpo.opt_pulse_crab_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-5, alg_params={'crab_pulse_params':{'randomize_coeffs':False, 'randomize_freqs':False}}, init_coeff_scaling=0.5, guess_pulse_type='GAUSSIAN', guess_pulse_params={'variance':0.1*evo_time}, guess_pulse_scaling=1.0, guess_pulse_offset=1.0, amp_lbound=None, amp_ubound=None, ramping_pulse_type='GAUSSIAN_EDGE', ramping_pulse_params={'decay_time':evo_time/100.0}, gen_stats=True) assert_(result.goal_achieved, msg="Hadamard goal not achieved. " "Terminated due to: {}, with infidelity: {}".format( result.termination_reason, result.fid_err)) assert_almost_equal(result.fid_err, 0.0, decimal=3, err_msg="Hadamard infidelity too high") assert_almost_equal(result.final_amps[0, 0], 0.0, decimal=3, err_msg="lead in amplitude not zero") # Repeat with Qobj integration result = cpo.opt_pulse_crab_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-5, alg_params={'crab_pulse_params':{'randomize_coeffs':False, 'randomize_freqs':False}}, dyn_params={'oper_dtype':Qobj}, init_coeff_scaling=0.5, guess_pulse_type='GAUSSIAN', guess_pulse_params={'variance':0.1*evo_time}, guess_pulse_scaling=1.0, guess_pulse_offset=1.0, amp_lbound=None, amp_ubound=None, ramping_pulse_type='GAUSSIAN_EDGE', ramping_pulse_params={'decay_time':evo_time/100.0}, gen_stats=True) assert_(result.goal_achieved, msg="Hadamard goal not achieved" "(Qobj integration). " "Terminated due to: {}, with infidelity: {}".format( result.termination_reason, result.fid_err))
def test_hadamard_explicit(self): test = gates.hadamard_transform(3).full() expected = np.array([[1, 1, 1, 1, 1, 1, 1, 1], [1, -1, 1, -1, 1, -1, 1, -1], [1, 1, -1, -1, 1, 1, -1, -1], [1, -1, -1, 1, 1, -1, -1, 1], [1, 1, 1, 1, -1, -1, -1, -1], [1, -1, 1, -1, -1, 1, -1, 1], [1, 1, -1, -1, -1, -1, 1, 1], [1, -1, -1, 1, -1, 1, 1, -1]]) expected = expected / np.sqrt(8) np.testing.assert_allclose(test, expected)
def test_id_with_T1_T2(self): """ Test for identity evolution with relaxation t1 and t2 """ # setup a = destroy(2) Hadamard = hadamard_transform(1) ex_state = basis(2, 1) mines_state = (basis(2, 1) - basis(2, 0)).unit() end_time = 2. tlist = np.arange(0, end_time + 0.02, 0.02) t1 = 1. t2 = 0.5 # test t1 test = Processor(1, t1=t1) # zero ham evolution test.add_pulse(Pulse(identity(2), 0, tlist, False)) result = test.run_state(ex_state, e_ops=[a.dag() * a]) assert_allclose(result.expect[0][-1], np.exp(-1. / t1 * end_time), rtol=1e-5, err_msg="Error in t1 time simulation") # test t2 test = Processor(1, t2=t2) test.add_pulse(Pulse(identity(2), 0, tlist, False)) result = test.run_state(init_state=mines_state, e_ops=[Hadamard * a.dag() * a * Hadamard]) assert_allclose(result.expect[0][-1], np.exp(-1. / t2 * end_time) * 0.5 + 0.5, rtol=1e-5, err_msg="Error in t2 time simulation") # test t1 and t2 t1 = np.random.rand(1) + 0.5 t2 = np.random.rand(1) * 0.5 + 0.5 test = Processor(1, t1=t1, t2=t2) test.add_pulse(Pulse(identity(2), 0, tlist, False)) result = test.run_state(init_state=mines_state, e_ops=[Hadamard * a.dag() * a * Hadamard]) assert_allclose(result.expect[0][-1], np.exp(-1. / t2 * end_time) * 0.5 + 0.5, rtol=1e-5, err_msg="Error in t1 & t2 simulation, " "with t1={} and t2={}".format(t1, t2))
def test_04_unitarity(self): """ control: unitarity checking (via dump) Dump out processing data and use to check unitary evolution """ # Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 1000 evo_time = 4 result = cpo.optimize_pulse_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-9, init_pulse_type='LIN', dyn_params={'dumping':'FULL'}, gen_stats=True) # check dumps were generated optim = result.optimizer dyn = optim.dynamics assert_(dyn.dump is not None, msg='dynamics dump not created') # Use the dump to check unitarity of all propagators and evo_ops dyn.unitarity_tol = 1e-14 nu_prop = 0 nu_fwd_evo = 0 nu_onto_evo = 0 for d in dyn.dump.evo_dumps: for k in range(dyn.num_tslots): if not dyn._is_unitary(d.prop[k]): nu_prop += 1 if not dyn._is_unitary(d.fwd_evo[k]): nu_fwd_evo += 1 if not dyn._is_unitary(d.onto_evo[k]): nu_onto_evo += 1 assert_(nu_prop==0, msg="{} propagators found to be non-unitary".format(nu_prop)) assert_(nu_fwd_evo==0, msg="{} fwd evo ops found to be non-unitary".format( nu_fwd_evo)) assert_(nu_onto_evo==0, msg="{} onto evo ops found to be non-unitary".format( nu_onto_evo))
def test_01_5_unitary_hadamard_oo(self): """ control.pulseoptim: Hadamard gate with linear initial pulses (OO) assert that goal is achieved and pulseoptim method achieves same result as OO method """ # Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 10 evo_time = 10 # Run the optimisation optim = cpo.create_pulse_optimizer(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, dyn_type='UNIT', init_pulse_type='LIN', gen_stats=True) dyn = optim.dynamics init_amps = optim.pulse_generator.gen_pulse().reshape([-1, 1]) dyn.initialize_controls(init_amps) result_oo = optim.run_optimization() # Run the pulseoptim func result_po = cpo.optimize_pulse_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=True) assert_almost_equal(result_oo.fid_err, result_po.fid_err, decimal=10, err_msg="OO and pulseoptim methods produce " "different results for Hadamard")
def test_01_3_unitary_hadamard_tau(self): """ control.pulseoptim: Hadamard gate with linear initial pulses (tau) assert that goal is achieved """ # Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) # Run the optimisation #Try setting timeslots with tau array tau = np.arange(1.0, 10.0, 1.0) result = cpo.optimize_pulse_unitary(H_d, H_c, U_0, U_targ, tau=tau, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=False) assert_(result.goal_achieved, msg="Hadamard goal not achieved " "(tau as timeslots). " "Terminated due to: {}, with infidelity: {}".format( result.termination_reason, result.fid_err))
import scipy.optimize import qutip from qutip.control import pulseoptim as cpo from qutip.qip.algorithms import qft from qutip.qip.operations.gates import hadamard_transform import qutip.control.loadparams _sx = qutip.sigmax() _sy = qutip.sigmay() _sz = qutip.sigmaz() _sp = qutip.sigmap() _sm = qutip.sigmam() _si = qutip.identity(2) _project_0 = qutip.basis(2, 0).proj() _hadamard = hadamard_transform(1) # We have a whole bunch of different physical systems we want to test the # optimiser for, but the logic for testing them is largely the same. To avoid # having to explicitly parametrise over five linked parameters repeatedly, we # group them into a record type, so that all the optimisation functions can # then simply be parametrised over a single argument. # # We supply `kwargs` as a property of the system because the initial pulse type # and dynamics solver to use vary, especially if the system is unitary. _System = collections.namedtuple('_System', ['system', 'controls', 'initial', 'target', 'kwargs']) # Simple Hadamard gate. _hadamard_kwargs = {'num_tslots': 10, 'evo_time': 10, 'gen_stats': True,
def had_mixture(x): id_chan = to_choi(qeye(2)) S_eye = to_super(id_chan) S_H = to_super(hadamard_transform()) return (1 - x) * S_eye + x * S_H
def test_12_time_dependent_ctrls(self): """ control.pulseoptim: Hadamard gate with fixed and time varying ctrls assert that goal is achieved for both and that different control pulses are produced (only) when they should be. """ # Hadamard H_0 = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 20 evo_time = 10 # Run the optimisations result_fixed = cpo.optimize_pulse_unitary(H_0, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=True) assert_(result_fixed.goal_achieved, msg="Fixed ctrls goal not achieved. " "Terminated due to: {}, with infidelity: {}".format( result_fixed.termination_reason, result_fixed.fid_err)) H_c_t = [] for k in range(n_ts): H_c_t.append([sigmax()]) result_tdcs = cpo.optimize_pulse_unitary(H_0, H_c_t, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=True) assert_(result_tdcs.goal_achieved, msg="td same ctrl goal not achieved. " "Terminated due to: {}, with infidelity: {}".format( result_tdcs.termination_reason, result_tdcs.fid_err)) # Check fixed and same produced the same pulse assert_almost_equal(result_fixed.final_amps, result_tdcs.final_amps, decimal=9, err_msg="same and fixed ctrls result in " "different control pules") H_c_t = [] for k in range(n_ts): if k % 3 == 0: H_c_t.append([sigmax()]) else: H_c_t.append([identity(2)]) result_tdcv = cpo.optimize_pulse_unitary(H_0, H_c_t, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-10, init_pulse_type='LIN', gen_stats=True) assert_(result_tdcv.goal_achieved, msg="true td ctrls goal not achieved. " "Terminated due to: {}, with infidelity: {}".format( result_tdcv.termination_reason, result_tdcv.fid_err)) # Check that identity control tslots don't vary for k in range(n_ts): if k % 3 != 0: assert_almost_equal(result_tdcv.initial_amps[k, 0], result_tdcv.final_amps[k, 0], decimal=9, err_msg=("timeslot {} amps should remain " "fixed").format(k))
def test_03_dumping(self): """ control: data dumping Dump out processing data, check file counts """ self.setUp() N_EXP_OPTIMDUMP_FILES = 10 N_EXP_DYNDUMP_FILES = 49 # Hadamard H_d = sigmaz() H_c = [sigmax()] U_0 = identity(2) U_targ = hadamard_transform(1) n_ts = 1000 evo_time = 4 dump_folder = str(uuid.uuid4()) qtrl_dump_dir = os.path.expanduser(os.path.join('~', dump_folder)) self.tmp_dirs.append(qtrl_dump_dir) optim_dump_dir = os.path.join(qtrl_dump_dir, 'optim') dyn_dump_dir = os.path.join(qtrl_dump_dir, 'dyn') result = cpo.optimize_pulse_unitary(H_d, H_c, U_0, U_targ, n_ts, evo_time, fid_err_targ=1e-9, init_pulse_type='LIN', optim_params={'dumping':'FULL', 'dump_to_file':True, 'dump_dir':optim_dump_dir}, dyn_params={'dumping':'FULL', 'dump_to_file':True, 'dump_dir':dyn_dump_dir}, gen_stats=True) # check dumps were generated optim = result.optimizer dyn = optim.dynamics assert_(optim.dump is not None, msg='optimizer dump not created') assert_(dyn.dump is not None, msg='dynamics dump not created') # Count files that were output nfiles = len(os.listdir(optim.dump.dump_dir)) assert_(nfiles == N_EXP_OPTIMDUMP_FILES, msg="{} optimizer dump files generated, {} expected".format( nfiles, N_EXP_OPTIMDUMP_FILES)) nfiles = len(os.listdir(dyn.dump.dump_dir)) assert_(nfiles == N_EXP_DYNDUMP_FILES, msg="{} dynamics dump files generated, {} expected".format( nfiles, N_EXP_DYNDUMP_FILES)) # dump all to specific file stream fpath = os.path.expanduser(os.path.join('~', str(uuid.uuid4()))) self.tmp_files.append(fpath) with open(fpath, 'wb') as f: optim.dump.writeout(f) assert_(os.stat(fpath).st_size > 0, msg="Nothing written to optimizer dump file") fpath = os.path.expanduser(os.path.join('~', str(uuid.uuid4()))) self.tmp_files.append(fpath) with open(fpath, 'wb') as f: dyn.dump.writeout(f) assert_(os.stat(fpath).st_size > 0, msg="Nothing written to dynamics dump file") self.tearDown()
def test_09_load_params(self): """ control.pulseoptim: Hadamard gate (loading config from file) compare with result produced by pulseoptim method """ H_d = sigmaz() H_c = sigmax() U_0 = identity(2) U_targ = hadamard_transform(1) cfg = optimconfig.OptimConfig() cfg.param_fname = "Hadamard_params.ini" cfg.param_fpath = os.path.join(os.path.dirname(__file__), cfg.param_fname) cfg.pulse_type = "ZERO" loadparams.load_parameters(cfg.param_fpath, config=cfg) dyn = dynamics.DynamicsUnitary(cfg) dyn.target = U_targ dyn.initial = U_0 dyn.drift_dyn_gen = H_d dyn.ctrl_dyn_gen = [H_c] loadparams.load_parameters(cfg.param_fpath, dynamics=dyn) dyn.init_timeslots() n_ts = dyn.num_tslots n_ctrls = dyn.num_ctrls pgen = pulsegen.create_pulse_gen(pulse_type=cfg.pulse_type, dyn=dyn) loadparams.load_parameters(cfg.param_fpath, pulsegen=pgen) tc = termcond.TerminationConditions() loadparams.load_parameters(cfg.param_fpath, term_conds=tc) if cfg.optim_method == 'BFGS': optim = optimizer.OptimizerBFGS(cfg, dyn) elif cfg.optim_method == 'FMIN_L_BFGS_B': optim = optimizer.OptimizerLBFGSB(cfg, dyn) elif cfg.optim_method is None: raise errors.UsageError("Optimisation algorithm must be specified " "via 'optim_method' parameter") else: optim = optimizer.Optimizer(cfg, dyn) optim.method = cfg.optim_method loadparams.load_parameters(cfg.param_fpath, optim=optim) sts = stats.Stats() dyn.stats = sts optim.stats = sts optim.config = cfg optim.dynamics = dyn optim.pulse_generator = pgen optim.termination_conditions = tc init_amps = np.zeros([n_ts, n_ctrls]) for j in range(n_ctrls): init_amps[:, j] = pgen.gen_pulse() dyn.initialize_controls(init_amps) result = optim.run_optimization() result2 = cpo.optimize_pulse_unitary(H_d, list([H_c]), U_0, U_targ, 6, 6, fid_err_targ=1e-10, init_pulse_type='LIN', amp_lbound=-1.0, amp_ubound=1.0, gen_stats=True) assert_almost_equal(result.final_amps, result2.final_amps, decimal=5, err_msg="Pulses do not match")
def test_06_lindbladian(self): """ control.pulseoptim: amplitude damping channel Lindbladian dynamics assert that fidelity error is below threshold """ Sx = sigmax() Sz = sigmaz() Si = identity(2) Sd = Qobj(np.array([[0, 1], [0, 0]])) Sm = Qobj(np.array([[0, 0], [1, 0]])) Sd_m = Qobj(np.array([[1, 0], [0, 0]])) gamma = 0.1 L0_Ad = gamma*(2*tensor(Sm, Sd.trans()) - (tensor(Sd_m, Si) + tensor(Si, Sd_m.trans()))) LC_x = -1j*(tensor(Sx, Si) - tensor(Si, Sx)) LC_z = -1j*(tensor(Sz, Si) - tensor(Si, Sz)) drift = L0_Ad ctrls = [LC_z, LC_x] n_ctrls = len(ctrls) initial = tensor(Si, Si) had_gate = hadamard_transform(1) target_DP = tensor(had_gate, had_gate) n_ts = 10 evo_time = 5 result = cpo.optimize_pulse(drift, ctrls, initial, target_DP, n_ts, evo_time, fid_err_targ=1e-3, max_iter=200, init_pulse_type='LIN', gen_stats=True) assert_(result.fid_err < 0.1, msg="Fidelity higher than expected") # Repeat with Qobj propagation result = cpo.optimize_pulse(drift, ctrls, initial, target_DP, n_ts, evo_time, fid_err_targ=1e-3, max_iter=200, init_pulse_type='LIN', dyn_params={'oper_dtype':Qobj}, gen_stats=True) assert_(result.fid_err < 0.1, msg="Fidelity higher than expected (Qobj propagation)") # Check same result is achieved using the create objects method optim = cpo.create_pulse_optimizer(drift, ctrls, initial, target_DP, n_ts, evo_time, fid_err_targ=1e-3, init_pulse_type='LIN', gen_stats=True) dyn = optim.dynamics p_gen = optim.pulse_generator init_amps = np.zeros([n_ts, n_ctrls]) for j in range(n_ctrls): init_amps[:, j] = p_gen.gen_pulse() dyn.initialize_controls(init_amps) # Check the exact gradient func = optim.fid_err_func_wrapper grad = optim.fid_err_grad_wrapper x0 = dyn.ctrl_amps.flatten() grad_diff = check_grad(func, grad, x0) assert_almost_equal(grad_diff, 0.0, decimal=7, err_msg="Frechet gradient outside tolerance") result2 = optim.run_optimization() assert_almost_equal(result.fid_err, result2.fid_err, decimal=3, err_msg="Direct and indirect methods produce " "different results for ADC")