def test_strip_slap(): import numpy as np from qoc.models.dummy import Dummy from qoc.core.common import ( slap_controls, strip_controls, ) big = 100 pstate = Dummy() pstate.complex_controls = True shape_range = np.arange(big) + 1 for step_count in shape_range: for control_count in shape_range: pstate.controls_shape = controls_shape = (step_count, control_count) pstate.max_control_norms = np.ones(control_count) * 2 controls = np.random.rand( *controls_shape) + 1j * np.random.rand(*controls_shape) stripped_controls = strip_controls(pstate, controls) assert (stripped_controls.ndim == 1) assert (not (stripped_controls.dtype in (np.complex64, np.complex128))) transformed_controls = slap_controls(pstate.complex_controls, stripped_controls, pstate.controls_shape) assert (np.allclose(controls, transformed_controls)) assert (controls.shape == transformed_controls.shape) #ENDFOR #ENDFOR pstate.complex_controls = False for step_count in shape_range: for control_count in shape_range: pstate.controls_shape = controls_shape = (step_count, control_count) pstate.max_control_norms = np.ones(control_count) controls = np.random.rand(*controls_shape) stripped_controls = strip_controls(pstate.complex_controls, controls) assert (stripped_controls.ndim == 1) assert (not (stripped_controls.dtype in (np.complex64, np.complex128))) transformed_controls = slap_controls(pstate.complex_controls, stripped_controls, pstate.controls_shape) assert (np.allclose(controls, transformed_controls)) assert (controls.shape == transformed_controls.shape)
def _esdj_wrap(controls, pstate, reporter, result): """ Do intermediary work between the optimizer feeding controls to the jacobian of _evaluate_schroedinger_discrete. Args: controls pstate reporter result Returns: grads """ # Convert the controls from optimizer format to cost function format. controls = slap_controls(pstate.complex_controls, controls, pstate.controls_shape) # Rescale the controls to their maximum norm. clip_control_norms(controls, pstate.max_control_norms) # Impose user boundary conditions. if pstate.impose_control_conditions is not None: controls = pstate.impose_control_conditions(controls) # Evaluate the jacobian. error, grads = (ans_jacobian(_evaluate_schroedinger_discrete, 0)(controls, pstate, reporter)) # Autograd defines the derivative of a function of complex inputs as # df_dz = du_dx - i * du_dy for z = x + iy, f(z) = u(x, y) + iv(x, y). # For optimization, we care about df_dz = du_dx + i * du_dy. if pstate.complex_controls: grads = np.conjugate(grads) # The states need to be unwrapped from their autograd box. if isinstance(reporter.final_states, Box): final_states = reporter.final_states._value else: final_states = reporter.final_states # Update best configuration. if error < result.best_error: result.best_controls = controls result.best_error = error result.best_final_states = final_states result.best_iteration = reporter.iteration # Save and log optimization progress. pstate.log_and_save(controls, error, final_states, grads, reporter.iteration) reporter.iteration += 1 # Convert the gradients from cost function to optimizer format. grads = strip_controls(pstate.complex_controls, grads) # Determine if optimization should terminate. if error <= pstate.min_error: terminate = True else: terminate = False return grads, terminate
def _grape_schroedinger_discrete_time(pstate): """ Perform GRAPE for the schroedinger equation with time discrete parameters. Use autograd to compute evolution gradients. Args: pstate :: qoc.models.GrapeSchroedingerDiscreteState - information required to perform the optimization Returns: result :: qoc.models.GrapeResult - an object that tracks important information about the optimization """ # Autograd does not allow multiple return values from # a differentiable function. # Scipy's minimization algorithms require us to provide # functions that they evaluate on their own schedule. # The best solution to track mutable objects, that I can think of, # is to use a reporter object. reporter = Dummy() reporter.iteration = 0 result = GrapeSchroedingerResult() # Convert the controls from cost function format to optimizer format. initial_controls = strip_controls(pstate.complex_controls, pstate.initial_controls) # Run the optimization. pstate.optimizer.run(_esd_wrap, pstate.iteration_count, initial_controls, _esdj_wrap, args=(pstate, reporter, result)) return result
def test_adam(): import numpy as np from qoc.core.common import (strip_controls, slap_controls) from qoc.models.dummy import Dummy from qoc.standard.optimizers.adam import Adam # Check that the update method was implemented correctly # using hand-checked values. adam = Adam() grads = np.array([[0, 1], [2, 3]]) params = np.array([[0, 1], [2, 3]], dtype=np.float64) params1 = np.array([[0, 0.999], [1.999, 2.999]]) params2 = np.array([[0, 0.99900003], [1.99900001, 2.99900001]]) adam.run(None, 0, params, None, None) params1_test = adam.update(params, grads) params2_test = adam.update(params1, grads) assert (np.allclose(params1_test, params1)) assert (np.allclose(params2_test, params2)) # Check that complex mapping works and params # without gradients are unaffected. gstate = Dummy() gstate.complex_controls = True grads = np.array([[1 + 1j, 0 + 0j], [0 + 0j, -1 - 1j]]) params = np.array([[1 + 2j, 3 + 4j], [5 + 6j, 7 + 8j]]) gstate.controls_shape = params.shape gstate.max_param_norms = np.ones(gstate.controls_shape[0]) * 10 flat_controls = strip_controls(gstate.complex_controls, params) flat_grads = strip_controls(gstate.complex_controls, grads) adam.run(None, 0, flat_controls, None, None) params1 = adam.update(flat_grads, flat_controls) params1 = slap_controls(gstate.complex_controls, params1, gstate.controls_shape) assert (np.allclose(params1[0][1], params[0][1])) assert (np.allclose(params1[1][0], params[1][0]))
def _eldj_wrap(controls, pstate, reporter, result): """ Do intermediary work between the optimizer feeding controls to the jacobian of _evaluate_indblad_discrete. Args: controls pstate reporter result Returns: grads """ # Convert the controls from optimizer format to cost function format. controls = slap_controls(pstate.complex_controls, controls, pstate.controls_shape) clip_control_norms(pstate.max_control_norms, controls) # Evaluate the jacobian. total_error, grads = (ans_jacobian(_evaluate_lindblad_discrete, 0)(controls, pstate, reporter)) # Autograd defines the derivative of a function of complex inputs as # df_dz = du_dx - i * du_dy for z = x + iy, f(z) = u(x, y) + iv(x, y). # For optimization, we care about df_dz = du_dx + i * du_dy. if pstate.complex_controls: grads = conjugate(grads) # The states need to be unwrapped from their autograd box. if isinstance(reporter.final_densities, Box): final_densities = reporter.final_densities._value # Update best configuration. if total_error < result.best_total_error: result.best_controls = controls result.best_final_densities = final_densities result.best_iteration = reporter.iteration result.best_total_error = total_error # Save and log optimization progress. pstate.log_and_save( controls, final_densities, total_error, grads, reporter.iteration, ) reporter.iteration += 1 # Convert the gradients from cost function to optimizer format. grads = strip_controls(pstate.complex_controls, grads) return grads
def grape_schroedinger_discrete( control_count, control_eval_count, costs, evolution_time, hamiltonian, initial_states, system_eval_count, complex_controls=False, cost_eval_step=1, impose_control_conditions=None, initial_controls=None, interpolation_policy=InterpolationPolicy.LINEAR, iteration_count=1000, log_iteration_step=10, magnus_policy=MagnusPolicy.M2, max_control_norms=None, min_error=0, optimizer=Adam(), save_file_path=None, save_intermediate_states=False, save_iteration_step=0, ): """ This method optimizes the evolution of a set of states under the schroedinger equation for time-discrete control parameters. Args: control_count :: int - This is the number of control parameters that qoc should optimize over. I.e. it is the length of the `controls` array passed to the hamiltonian. control_eval_count :: int >= 2 - This value determines where definite values of the control parameters are evaluated. This value is used as: `control_eval_times`= numpy.linspace(0, `evolution_time`, `control_eval_count`). costs :: iterable(qoc.models.cost.Cost) - This list specifies all the cost functions that the optimizer should evaluate. This list defines the criteria for an "optimal" control set. evolution_time :: float - This value specifies the duration of the system's evolution. hamiltonian :: (controls :: ndarray (control_count), time :: float) -> hamiltonian_matrix :: ndarray (hilbert_size x hilbert_size) - This function provides the system's hamiltonian given a set of control parameters and a time value. initial_states :: ndarray (state_count x hilbert_size x 1) - This array specifies the states that should be evolved under the specified system. These are the states at the beginning of the evolution. system_eval_count :: int >= 2 - This value determines how many times during the evolution the system is evaluated, including the initial value of the system. For the schroedinger evolution, this value determines the time step of integration. This value is used as: `system_eval_times` = numpy.linspace(0, `evolution_time`, `system_eval_count`). complex_controls :: bool - This value determines if the control parameters are complex-valued. If some controls are real only or imaginary only while others are complex, real only and imaginary only controls can be simulated by taking the real or imaginary part of a complex control. cost_eval_step :: int >= 1- This value determines how often step-costs are evaluated. The units of this value are in system_eval steps. E.g. if this value is 2, step-costs will be computed every 2 system_eval steps. impose_control_conditions :: (controls :: (control_eval_count x control_count)) -> (controls :: (control_eval_count x control_count)) - This function is called after every optimization update. Example uses include setting boundary conditions on the control parameters. initial_controls :: ndarray (control_step_count x control_count) - This array specifies the control parameters at each control step. These values will be used to determine the `controls` argument passed to the `hamiltonian` function at each time step for the first iteration of optimization. interpolation_policy :: qoc.models.interpolationpolicy.InterpolationPolicy - This value specifies how control parameters should be interpreted at points where they are not defined. iteration_count :: int - This value determines how many total system evolutions the optimizer will perform to determine the optimal control set. log_iteration_step :: int - This value determines how often qoc logs progress to stdout. This value is specified in units of system steps, of which there are `control_step_count` * `system_step_multiplier`. Set this value to 0 to disable logging. magnus_policy :: qoc.models.magnuspolicy.MagnusPolicy - This value specifies what method should be used to perform the magnus expansion of the system matrix for ode integration. Choosing a higher order magnus expansion will yield more accuracy, but it will result in a longer compute time. max_control_norms :: ndarray (control_count) - This array specifies the element-wise maximum norm that each control is allowed to achieve. If, in optimization, the value of a control exceeds its maximum norm, the control will be rescaled to its maximum norm. Note that for non-complex values, this feature acts exactly as absolute value clipping. min_error :: float - This value is the threshold below which optimization will terminate. optimizer :: class instance - This optimizer object defines the gradient-based procedure for minimizing the total contribution of all cost functions with respect to the control parameters. save_file_path :: str - This is the full path to the file where information about program execution will be stored. E.g. "./out/foo.h5" save_intermediate_densities :: bool - If this value is set to True, qoc will write the densities to the save file after every system_eval step. save_intermediate_states :: bool - If this value is set to True, qoc will write the states to the save file after every system_eval step. save_iteration_step :: int - This value determines how often qoc saves progress to the save file specified by `save_file_path`. This value is specified in units of system steps, of which there are `control_step_count` * `system_step_multiplier`. Set this value to 0 to disable saving. Returns: result :: qoc.models.schroedingermodels.GrapeSchroedingerResult """ # Initialize the controls. initial_controls, max_control_norms = initialize_controls( complex_controls, control_count, control_eval_count, evolution_time, initial_controls, max_control_norms) # Construct the program state. pstate = GrapeSchroedingerDiscreteState( complex_controls, control_count, control_eval_count, cost_eval_step, costs, evolution_time, hamiltonian, impose_control_conditions, initial_controls, initial_states, interpolation_policy, iteration_count, log_iteration_step, max_control_norms, magnus_policy, min_error, optimizer, save_file_path, save_intermediate_states, save_iteration_step, system_eval_count, ) pstate.log_and_save_initial() # Autograd does not allow multiple return values from # a differentiable function. # Scipy's minimization algorithms require us to provide # functions that they evaluate on their own schedule. # The best solution to track mutable objects, that I can think of, # is to use a reporter object. reporter = Dummy() reporter.iteration = 0 result = GrapeSchroedingerResult() # Convert the controls from cost function format to optimizer format. initial_controls = strip_controls(pstate.complex_controls, pstate.initial_controls) # Run the optimization. pstate.optimizer.run(_esd_wrap, pstate.iteration_count, initial_controls, _esdj_wrap, args=(pstate, reporter, result)) return result
def grape_lindblad_discrete( control_count, control_step_count, costs, evolution_time, initial_densities, complex_controls=False, hamiltonian=None, initial_controls=None, interpolation_policy=InterpolationPolicy.LINEAR, iteration_count=1000, lindblad_data=None, log_iteration_step=10, max_control_norms=None, minimum_error=0, operation_policy=OperationPolicy.CPU, optimizer=Adam(), save_file_path=None, save_iteration_step=0, system_step_multiplier=1, ): """ This method optimizes the evolution of a set of states under the lindblad equation for time-discrete control parameters. Args: complex_controls control_count control_step_count costs evolution_time hamiltonian initial_controls initial_densities interpolation_policy iteration_count lindblad_data log_iteration_step max_control_norms minimum_error operation_policy optimizer save_file_path save_iteration_step system_step_multiplier Returns: result """ # Initialize the controls. initial_controls, max_control_norms = initialize_controls( complex_controls, control_count, control_step_count, evolution_time, initial_controls, max_control_norms, ) # Construct the program state. pstate = GrapeLindbladDiscreteState( complex_controls, control_count, control_step_count, costs, evolution_time, hamiltonian, initial_controls, initial_densities, interpolation_policy, iteration_count, lindblad_data, log_iteration_step, max_control_norms, minimum_error, operation_policy, optimizer, save_file_path, save_iteration_step, system_step_multiplier, ) pstate.log_and_save_initial() # Autograd does not allow multiple return values from # a differentiable function. # Scipy's minimization algorithms require us to provide # functions that they evaluate on their own schedule. # The best solution to track mutable objects, that I can think of, # is to use a reporter object. reporter = Dummy() reporter.iteration = 0 result = GrapeLindbladResult() # Convert the controls from cost function format to optimizer format. initial_controls = strip_controls(pstate.complex_controls, pstate.initial_controls) # Run the optimization. pstate.optimizer.run(_eld_wrap, pstate.iteration_count, initial_controls, _eldj_wrap, args=(pstate, reporter, result)) return result