def subvector_procedure(e_val, initial_values=None, samples=None, device=None, backend=None, noise=None) -> CallableVector: """ take an expectation value and return its (qng style) gradient as a CallableVector. Parameters ---------- e_val: ExpectationValueImpl: the expectation value whose gradient is to be obtained initial_values: dict, optional: a dictionary of initial values with which Objectives in the qgt should be compiled samples: int, optional: the number of samples with which Objectives in the qgt should be compiled device: typing depends on backend, but in general, str (Default value = None): the device (real, or simulated, and specific to the backend) with which Objectives in the qgt should be compiled backend: str, optional: the backend with which Objectives in the qgt should be compiled noise: str or NoiseModel, optional: the noise model with which Objectives in the qgt should be compiled Returns ------- CallableVector, the gradient of an ExpectationValue in callable format. """ vect = qng_circuit_grad(e_val) out = [] for entry in vect: out.append(compile_objective(entry, variables=initial_values, samples=samples, device=device, backend=backend, noise=noise)) return CallableVector(out)
def subvector_procedure(eval, initial_values=None, samples=None, backend=None, noise=None): vect = qng_circuit_grad(eval) out = [] for entry in vect: out.append( compile_objective(entry, variables=initial_values, samples=samples, backend=backend, noise=noise)) return CallableVector(out)
def qng_metric_tensor_blocks(expectation, initial_values=None, samples=None, backend=None, backend_options=None, noise=None): U = expectation.U moments = U.canonical_moments sub = [ QCircuit.from_moments(moments[:i]) for i in range(1, len(moments), 2) ] parametric_moms = [moments[i] for i in range(1, len(moments) + 1, 2)] generators = [] for pm in parametric_moms: set = [] if len(pm.gates) != 0: for gate in pm.gates: gen = get_generator(gate) set.append(gen) if len(set) != 0: generators.append(set) else: generators.append(None) blocks = [] for i, set in enumerate(generators): if set is None: pass else: block = [[0 for _ in range(len(set))] for _ in range(len(set))] for k, gen1 in enumerate(set): for q, gen2 in enumerate(set): if k == q: arg = (ExpectationValue(U=sub[i], H=gen1 * gen1) - ExpectationValue(U=sub[i], H=gen1)**2) / 4 else: arg = (ExpectationValue(U=sub[i], H=gen1 * gen2) - ExpectationValue(U=sub[i], H=gen1) * ExpectationValue(U=sub[i], H=gen2)) / 4 block[k][q] = compile_objective( arg, variables=initial_values, samples=samples, backend=backend, noise=noise, backend_options=backend_options) blocks.append(block) return blocks
def get_qng_combos(objective, func=stokes_block, initial_values=None, samples=None, backend=None, device=None, noise=None) -> typing.List[typing.Dict]: """ get all the objects needed to evaluate the qng for some objective; return them in a list of dictionaries. Parameters ---------- objective: Objective: the Objective whose qng is sought. func: callable: (Default = stokes_block): the function used to obtain the (blocks of) the qgt. Default uses stokes_block, defined above. initial_values: dict, optional: a dictionary indicating the intial parameters with which to compile all objectives appearing in the qng. samples: int, optional: the number of samples with which to compile all objectives appearing in the qng. Default: none. backend: str, optional: the backend with which to compile all objectives appearing in the qng. default: pick for you. device: optional: the device with which to compile all objectives appearing in the qng. Default: no device use or emulation. noise: str or NoiseModel, optional: the noise model with which to compile all objectives appearing in the qng. Default: no noise. Returns ------- list of dicts: a list of dictionaries, each entry corresponding to the qng for 1 argument of objective, in the order of said objectives. """ combos = [] vars = objective.extract_variables() compiled = compile_multitarget(gate=objective) compiled = compile_trotterized_gate(gate=compiled) compiled = compile_h_power(gate=compiled) compiled = compile_power_gate(gate=compiled) compiled = compile_controlled_phase(gate=compiled) compiled = compile_controlled_rotation(gate=compiled) for i, arg in enumerate(compiled.args): if not isinstance(arg, ExpectationValueImpl): ### this is a variable, no QNG involved mat = QngMatrix([[[1]]]) vec = CallableVector([__grad_inner(arg, arg)]) mapping = {0: {v: __grad_inner(arg, v) for v in vars}} else: ### if the arg is an expectationvalue, we need to build some qngs and mappings! blocks = func(arg, initial_values=initial_values, samples=samples, device=device, backend=backend, noise=noise) mat = QngMatrix(blocks) vec = subvector_procedure(arg, initial_values=initial_values, samples=samples, device=device, backend=backend, noise=noise) mapping = {} self_pars = get_self_pars(arg.U) for j, p in enumerate(self_pars): indict = {} for v in p.extract_variables(): gi = __grad_inner(p, v) if isinstance(gi, Objective): g = compile_objective(gi, variables=initial_values, samples=samples, device=device, backend=backend, noise=noise) else: g = gi indict[v] = g mapping[j] = indict posarg = jax.grad(compiled.transformation, i) p = Objective(compiled.args, transformation=posarg) pos = compile_objective(p, variables=initial_values, samples=samples, device=device, backend=backend, noise=noise) combos.append(qng_dict(arg, mat, vec, mapping, pos)) return combos
def stokes_block( expectation, initial_values=None, samples=None, device=None, backend=None, noise=None ) -> typing.List[typing.List[typing.List[typing.Union[float, Objective]]]]: """ returns the blocks of the layerwise block-diagonal approximation to the qgt. The default for all qng-based optimizations, as a method for obtaining the qgt. See: Stokes et. al, https://arxiv.org/abs/1909.02108 Parameters ---------- expectation: ExpectationValueImpl: the expectation value whose qgt is to be built. initial_values: dict, optional: a dictionary of initial values with which Objectives in the qgt should be compiled samples: int, optional: the number of samples with which Objectives in the qgt should be compiled device: optional: the device (real, or simulated, and specific to the backend) with which Objectives in the qgt should be compiled. Generally, a string, but backend specific types also accepted. backend: str, optional: the backend with which Objectives in the qgt should be compiled noise: str or NoiseModel, optional: the noise model with which Objectives in the qgt should be compiled Returns ------- list of list of lists: list of list of lists representing the blocks of the block diagonal layerwise approx to the qgt. """ U = expectation.U ### orders the circuit into alternating layer ansatz, where a moment is all simultaneous gates moments = U.canonical_moments ### rebuild the sub circuits used in the expectation values that populate the QGT sub = [ QCircuit.from_moments(moments[:i]) for i in range(1, len(moments), 2) ] ### this is the list of just the moments which are parametrized. parametric_moms = [moments[i] for i in range(1, len(moments) + 1, 2)] generators = [] ### generators is a list of lists, ultimately, where each sublist is all the generators in order ### for a given parametric layer (if said layer is ### occupied, which it might not be! a layer can be nothing, I.E, the identity.) for pm in parametric_moms: set = [] if len(pm.gates) != 0: for gate in pm.gates: ### get_generator takes a gaussian gate, and returns the pauli that is its generator. ### See that function for detail. gen = get_generator(gate) set.append(gen) if len(set) != 0: generators.append(set) else: ### blank sets get passed over generators.append(None) blocks = [] for i, set in enumerate(generators): if set is None: pass else: ### a block is a list of lists, and indexing it should correspond to indexing a matrix in A[row][column] fashion. ### alternate functions could have the whole QGT be a single block, but you need to have as a return a List of (List of Lists)!!!! block = [[0 for _ in range(len(set))] for _ in range(len(set))] for k, gen1 in enumerate(set): for q, gen2 in enumerate(set): ### make sure you compile the objectives! otherwise this bad boy will not run if k == q: arg = (ExpectationValue(U=sub[i], H=gen1 * gen1) - ExpectationValue(U=sub[i], H=gen1)**2) / 4 else: arg = (ExpectationValue(U=sub[i], H=gen1 * gen2) - ExpectationValue(U=sub[i], H=gen1) * ExpectationValue(U=sub[i], H=gen2)) / 4 block[k][q] = compile_objective(arg, variables=initial_values, samples=samples, backend=backend, device=device, noise=noise) blocks.append(block) return blocks
def __call__(self, objective: Objective, maxiter=None, variables: typing.List[Variable] = None, initial_values: typing.Dict[Variable, numbers.Real] = None, previous=None, phoenics_config=None, save_to_file=False, file_name=None, *args, **kwargs): active_angles, passive_angles, variables = self.initialize_variables(objective, initial_values=initial_values, variables=variables) if maxiter is None: maxiter = 10 obs = [] bird = self._make_phoenics_object(objective, passive_angles, phoenics_config, *args, **kwargs) if previous is not None: if type(previous) is str: try: obs = pickle.load(open(previous, 'rb')) except: print( 'failed to load previous observations, which are meant to be a pickle file. Starting fresh.') elif type(previous) is list: if all([type(k) == dict for k in previous]): obs = previous else: print('previous observations were not in the correct format (list of dicts). Starting fresh.') if not (type(file_name) == str or file_name == None): raise TequilaException('file_name must be a string, or None!') best = None best_angles = None # avoid multiple compilations compiled_objective = compile_objective(objective=objective, backend=self.backend, backend_options=self.backend_options, samples=self.samples, noise=self.noise) if not self.silent: print('phoenics has recieved') print("objective: \n") print(objective) print("noise model : {}".format(self.noise)) print("samples : {}".format(self.samples)) print("maxiter : {}".format(maxiter)) print("variables : {}".format(objective.extract_variables())) print("passive var : {}".format(passive_angles)) print("backend options {} ".format(self.backend), self.backend_options) print('now lets begin') for i in range(0, maxiter): with warnings.catch_warnings(): np.testing.suppress_warnings() warnings.simplefilter("ignore") warnings.filterwarnings("ignore", category=FutureWarning) if len(obs) >= 1: precs = bird.recommend(observations=obs) else: precs = bird.recommend() runs = [] recs = self._process_for_sim(precs, passive_angles=passive_angles) start = time.time() for j, rec in enumerate(recs): En = compiled_objective(variables=rec, samples=self.samples, noise=self.noise, backend_options=self.backend_options) runs.append((rec, En)) if not self.silent: if self.print_level > 2: print("energy = {:+2.8f} , angles=".format(En), rec) else: print("energy = {:+2.8f}".format(En)) stop = time.time() if not self.silent: print("Quantum Objective evaluations: {}s Wall-Time".format(stop-start)) for run in runs: angles = run[0] E = run[1] if best is None: best = E best_angles = angles else: if self._minimize: if E < best: best = E best_angles = angles else: if E > best: best = E best_angles = angles if self.save_history: self.history.energies.append(E) self.history.angles.append(angles) obs.append(self._process_for_phoenics(angles, E, passive_angles=passive_angles)) if file_name is not None: with open(file_name, 'wb') as file: pickle.dump(obs, file) if not self.silent: print("best energy after {} iterations : {:+2.8f}".format(self.maxiter, best)) return PhoenicsReturnType(energy=best, angles=best_angles, history=self.history, observations=obs,object=bird)
def __call__(self, objective: Objective, maxiter: int = None, passives: typing.Dict[Variable, numbers.Real] = None, samples: int = None, backend: str = None, noise=None, previous=None, phoenics_config=None, save_to_file=False, file_name=None, *args, **kwargs): backend_options = {} if 'backend_options' in kwargs: backend_options = kwargs['backend_options'] if maxiter is None: maxiter = 10 bird = self._make_phoenics_object(objective, passives, phoenics_config, *args, **kwargs) if previous is not None: if type(previous) is str: try: obs = pickle.load(open(previous, 'rb')) except: print( 'failed to load previous observations, which are meant to be a pickle file. Please try again or seek assistance. Starting fresh.') obs = [] elif type(previous) is list: if all([type(k) == dict for k in previous]): obs = previous else: print( 'previous observations were not in the correct format (list of dicts). Are you sure you gave me the right info? Starting fresh.') obs = [] else: obs = [] if save_to_file is True: if type(file_name) is str: pass elif file_name is None: raise TequilaException( 'You have asked me to save phoenics observations without telling me where to do so! please provide a file_name') else: raise TequilaException('file_name must be a string!') ### this line below just gets the damn compiler to run, since that argument is necessary init = {key: np.pi for key in objective.extract_variables()} best = None best_angles = None # avoid multiple compilations compiled_objective = compile_objective(objective=objective, backend=backend, samples=samples, noise=noise) if not self.silent: print('phoenics has recieved') print("objective: \n") print(objective) print("noise model : {}".format(noise)) print("samples : {}".format(samples)) print("maxiter : {}".format(maxiter)) print("variables : {}".format(objective.extract_variables())) print("passive var : {}".format(passives)) print("backend options {} ".format(backend), backend_options) print('now lets begin') for i in range(0, maxiter): with warnings.catch_warnings(): np.testing.suppress_warnings() warnings.simplefilter("ignore") warnings.filterwarnings("ignore", category=FutureWarning) if len(obs) >= 1: precs = bird.recommend(observations=obs) else: precs = bird.recommend() runs = [] recs = self._process_for_sim(precs, passives=passives) start = time.time() for i, rec in enumerate(recs): En = compiled_objective(variables=rec, samples=samples, noise=noise, **backend_options) runs.append((rec, En)) if not self.silent: print("energy = {:+2.8f} , angles=".format(En), rec) stop = time.time() if not self.silent: print("Quantum Objective evaluations: {}s Wall-Time".format(stop-start)) for run in runs: angles = run[0] E = run[1] if best is None: best = E best_angles = angles else: if self._minimize: if E < best: best = E best_angles = angles else: if E > best: best = E best_angles = angles if self.save_history: self.history.energies.append(E) self.history.angles.append(angles) obs.append(self._process_for_phoenics(angles, E, passives=passives)) if save_to_file is True: with open(file_name, 'wb') as file: pickle.dump(obs, file) if not self.silent: print("best energy after {} iterations : {:+2.8f}".format(self.maxiter, best)) return PhoenicsReturnType(energy=best, angles=best_angles, history=self.history, observations=obs)
def get_qng_combos(objective, initial_values=None, samples=None, backend=None, backend_options=None, noise=None): combos = [] vars = objective.extract_variables() compiled = compile_multitarget(gate=objective) compiled = compile_trotterized_gate(gate=compiled) compiled = compile_h_power(gate=compiled) compiled = compile_power_gate(gate=compiled) compiled = compile_controlled_phase(gate=compiled) compiled = compile_controlled_rotation(gate=compiled) for i, arg in enumerate(compiled.args): if not isinstance(arg, ExpectationValueImpl): ### this is a variable, no QNG involved mat = QngMatrix([[[1]]]) vec = CallableVector([__grad_inner(arg, arg)]) mapping = {0: {v: __grad_inner(arg, v) for v in vars}} else: ### if the arg is an expectationvalue, we need to build some qngs and mappings! blocks = qng_metric_tensor_blocks(arg, initial_values=initial_values, samples=samples, backend=backend, noise=noise, backend_options=backend_options) mat = QngMatrix(blocks) vec = subvector_procedure(arg, initial_values=initial_values, samples=samples, backend=backend, noise=noise, backend_options=backend_options) mapping = {} self_pars = get_self_pars(arg.U) for j, p in enumerate(self_pars): indict = {} for v in p.extract_variables(): gi = __grad_inner(p, v) if isinstance(gi, Objective): g = compile_objective(gi, variables=initial_values, samples=samples, backend=backend, noise=noise, backend_options=backend_options) else: g = gi indict[v] = g mapping[j] = indict posarg = jax.grad(compiled.transformation, argnums=i) p = Objective(compiled.args, transformation=posarg) pos = compile_objective(p, variables=initial_values, samples=samples, backend=backend, noise=noise, backend_options=backend_options) combos.append(qng_dict(arg, mat, vec, mapping, pos)) return combos
def __call__(self, objective: Objective, maxiter=None, variables: typing.List[Variable] = None, initial_values: typing.Dict[Variable, numbers.Real] = None, previous=None, phoenics_config=None, file_name=None, *args, **kwargs): """ Perform optimization with phoenics. Parameters ---------- objective: Objective the objective to optimize. maxiter: int: (Default value = None) if not None, overwrite the init maxiter with new number. variables: list: (Default value = None) which variables to optimize over. If None: all of the variables in objective are used. initial_values: dict: (Default value = None) an initial point to begin optimization from. Random, if None. previous: previous observations, formatted for phoenics, to use in optimization. For use by advanced users. phoenics_config: a config for a phoenics object. file_name: a file args kwargs Returns ------- PhoenicsResults: the results of optimization by phoenics. """ objective = objective.contract() active_angles, passive_angles, variables = self.initialize_variables( objective, initial_values=initial_values, variables=variables) if maxiter is None: maxiter = 10 obs = [] bird = self._make_phoenics_object(objective, passive_angles, phoenics_config, *args, **kwargs) if previous is not None: if type(previous) is str: try: obs = pickle.load(open(previous, 'rb')) except: print( 'failed to load previous observations, which are meant to be a pickle file. Starting fresh.' ) elif type(previous) is list: if all([type(k) == dict for k in previous]): obs = previous else: print( 'previous observations were not in the correct format (list of dicts). Starting fresh.' ) if not (type(file_name) == str or file_name == None): raise TequilaException( 'file_name must be a string, or None. Recieved {}'.format( type(file_name))) best = None best_angles = None # avoid multiple compilations compiled_objective = compile_objective(objective=objective, backend=self.backend, device=self.device, samples=self.samples, noise=self.noise) if not self.silent: print('phoenics has recieved') print("objective: \n") print(objective) print("noise model : {}".format(self.noise)) print("samples : {}".format(self.samples)) print("maxiter : {}".format(maxiter)) print("variables : {}".format(objective.extract_variables())) print("passive var : {}".format(passive_angles)) print('now lets begin') for i in range(0, maxiter): with warnings.catch_warnings(): np.testing.suppress_warnings() warnings.simplefilter("ignore") warnings.filterwarnings("ignore", category=FutureWarning) precs = bird.recommend(observations=obs) runs = [] recs = self._process_for_sim(precs, passive_angles=passive_angles) start = time.time() for j, rec in enumerate(recs): En = compiled_objective(variables=rec, samples=self.samples, noise=self.noise) runs.append((rec, En)) if not self.silent: if self.print_level > 2: print("energy = {:+2.8f} , angles=".format(En), rec) else: print("energy = {:+2.8f}".format(En)) stop = time.time() if not self.silent: print("Quantum Objective evaluations: {}s Wall-Time".format( stop - start)) for run in runs: angles = run[0] E = run[1] if best is None: best = E best_angles = angles else: if self._minimize: if E < best: best = E best_angles = angles else: if E > best: best = E best_angles = angles if self.save_history: self.history.energies.append(E) self.history.angles.append(angles) obs.append( self._process_for_phoenics(angles, E, passive_angles=passive_angles)) if file_name is not None: with open(file_name, 'wb') as file: pickle.dump(obs, file) if not self.silent: print("best energy after {} iterations : {:+2.8f}".format( self.maxiter, best)) return PhoenicsResults(energy=best, variables=best_angles, history=self.history, observations=obs, phoenics_instance=bird)