def preamble(objective: Objective, compile_args: dict = None, input_vars: list = None): """ Helper function for interfaces to ml backends. Parameters ---------- objective: Objective: the objective to manipulate and compile. compile_args: dict, optional: a dictionary of args that can be passed as kwargs to tq.compile input_vars: list, optional: a list of variables of the objective to specify as input, rather than itnernal weights. Returns ------- tuple the compiled objective, it's compile arguments, its weight variables, dicts for the weight and input gradients, and a dictionary that links positions in an array to each variable (parses parameters). """ def var_sorter(e): return hash(e.name) all_vars = objective.extract_variables() all_vars.sort(key=var_sorter) compile_args = check_compiler_args(compile_args) weight_vars = [] if input_vars is None: input_vars = [] weight_vars = all_vars else: input_vars = [assign_variable(v) for v in input_vars] for var in all_vars: if var not in input_vars: weight_vars.append(assign_variable(var)) init_vals = compile_args['initial_values'] if init_vals is not None: for k in init_vals.keys(): if assign_variable(k) in input_vars: raise TequilaMLException( 'initial_values contained key {},' 'which is meant to be an input variable.'.format(k)) compile_args['initial_values'] = format_variable_dictionary(init_vals) comped = compile(objective, **compile_args) gradients = get_gradients(objective, compile_args) w_grad, i_grad = separate_gradients(gradients, weight_vars=weight_vars, input_vars=input_vars) first, second = get_variable_orders(weight_vars, input_vars) return comped, compile_args, input_vars, weight_vars, i_grad, w_grad, first, second
def _initialize_power_gate(name: str, target: typing.Union[list, int], generator, control: typing.Union[list, int] = None, power=None, angle=None) -> QCircuit: target = list_assignment(target) if angle is not None: angle = assign_variable(angle) if power is not None: power = power * angle / np.pi else: power = angle / np.pi if power is None or power in [1, 1.0]: gates = [ impl.QGateImpl(name=name, target=q, control=control, generator=generator(q)) for q in target ] else: gates = [ impl.PowerGateImpl(name=name, power=power, target=q, control=control, generator=generator(q)) for q in target ] return QCircuit.wrap_gate(gates)
def __init__(self, name, parameter: UnionParam, target: UnionList, control: UnionList = None): super().__init__(name=name, target=target, control=control) self._parameter = assign_variable(variable=parameter)
def __call__(self, wfn: QubitWaveFunction) -> QCircuit: """ :param coeffs: The QubitWaveFunction you want to initialize :return: """ try: assert (len(wfn) == len(self._target_space)) for key in wfn.keys(): try: assert (key in self._target_space) except AssertionError: print("key=", key.binary, " not found in target space") except AssertionError: raise TequilaException( "UnaryStatePrep was not initialized for the basis states in your wavefunction\n" "You gave:\n" + str(wfn) + "\n" "But the target_space is " + str([k.binary for k in self._target_space]) + "\n") angles = self._evaluate_angles(wfn=wfn) # construct new circuit with evaluated angles result = QCircuit() for g in self._abstract_circuit.gates: g2 = copy.deepcopy(g) if hasattr(g, "parameter"): symbol = g.parameter # the module needs repairing .... g2._parameter = assign_variable( -angles[-symbol()] ) # the minus follows mahas convention since the circuits are daggered in the end result += g2 return result
def angles(self, wfn: QubitWaveFunction) -> typing.Dict[typing.Hashable, float]: sympy_angles = self._evaluate_angles(wfn=wfn) angles = { assign_variable(str(key)): value for key, value in sympy_angles.items() } return angles
def grad(objective: typing.Union[Objective, VectorObjective], variable: Variable = None, no_compile=False, *args, **kwargs): ''' wrapper function for getting the gradients of Objectives,ExpectationValues, Unitaries (including single gates), and Transforms. :param obj (QCircuit,ParametrizedGateImpl,Objective,ExpectationValue,Transform,Variable): structure to be differentiated :param variables (list of Variable): parameter with respect to which obj should be differentiated. default None: total gradient. return: dictionary of Objectives, if called on gate, circuit, exp.value, or objective; if Variable or Transform, returns number. ''' if variable is None: # None means that all components are created variables = objective.extract_variables() result = {} if len(variables) == 0: raise TequilaException( "Error in gradient: Objective has no variables") for k in variables: assert (k is not None) result[k] = grad(objective, k, no_compile=no_compile) return result else: variable = assign_variable(variable) if no_compile: compiled = objective else: compiler = Compiler(multitarget=True, trotterized=True, hadamard_power=True, power=True, controlled_phase=True, controlled_rotation=True, gradient_mode=True) compiled = compiler(objective, variables=[variable]) if variable not in compiled.extract_variables(): raise TequilaException( "Error in taking gradient. Objective does not depend on variable {} " .format(variable)) if isinstance(objective, ExpectationValueImpl): return __grad_expectationvalue(E=objective, variable=variable) elif objective.is_expectationvalue(): return __grad_expectationvalue(E=compiled.args[-1], variable=variable) elif isinstance(compiled, Objective) or isinstance(compiled, VectorObjective): return __grad_objective(objective=compiled, variable=variable) else: raise TequilaException( "Gradient not implemented for other types than ExpectationValue and Objective." )
def ExpPauli(paulistring: typing.Union[PauliString, str], angle, control: typing.Union[list, int] = None): """Exponentiated Pauligate: ExpPauli(PauliString, angle) = exp(-i* angle/2* PauliString) Parameters ---------- paulistring : given as PauliString structure or as string or dict or list if given as string: Format should be like X(0)Y(3)Z(2) if given as list: Format should be like [(0,'X'),(3,'Y'),(2,'Z')] if given as dict: Format should be like { 0:'X', 3:'Y', 2:'Z' } angle : the angle (will be multiplied by paulistring coefficient if there is one) control : control qubits paulistring: typing.Union[PauliString : str] : control: typing.Union[list : int] : (Default value = None) Returns ------- type Gate wrapped in circuit """ if isinstance(paulistring, str): ps = PauliString.from_string(string=paulistring) elif isinstance(paulistring, list): ps = PauliString.from_openfermion(key=list) elif isinstance(paulistring, dict): ps = PauliString(data=paulistring) else: ps = paulistring # Failsave: If the paulistring contains just one pauli matrix # it is better to initialize a rotational gate due to strange conventions in some simulators if len(ps.items()) == 1: target, axis = tuple(ps.items())[0] return QCircuit.wrap_gate( impl.RotationGateImpl(axis=axis, target=target, angle=ps.coeff * assign_variable(angle), control=control)) else: return QCircuit.wrap_gate( impl.ExponentialPauliGateImpl(paulistring=ps, angle=angle, control=control))
def Trotterized(generators: typing.List[QubitHamiltonian], steps: int, angles: typing.Union[ typing.List[typing.Hashable], typing.List[numbers.Real], typing.List[Variable]] = None, control: typing.Union[list, int] = None, parameters: TrotterParameters = None) -> QCircuit: """ Parameters ---------- generators : list of generators angles : coefficients for each generator steps : trotter steps control : control qubits parameters : Additional Trotter parameters, if None then defaults are used generators: typing.List[QubitHamiltonian] : steps: int : angles: typing.Union[typing.List[typing.Hashable] : typing.List[numbers.Real] : typing.List[Variable]] : (Default value = None) control: typing.Union[list : int] : (Default value = None) parameters: TrotterParameters : (Default value = None) Returns ------- QCircuit """ # convenience if not (isinstance(generators, list) or isinstance(generators, tuple)): generators = [generators] if not (isinstance(angles, list) or isinstance(angles, tuple)): angles = [angles] if parameters is None: parameters = TrotterParameters() assigned_angles = [assign_variable(angle) for angle in angles] return QCircuit.wrap_gate( TrotterizedGateImpl(generators=generators, angles=assigned_angles, steps=steps, control=control, **parameters.__dict__))
def extract_angles(self, key: str) -> typing.Dict[numbers.Integral, numbers.Real]: """ :param key: the key specifiying which angle shall be extracted :return: dictionary with dictionary_key=iteration, dictionary_value=angle[key] """ angles = {} for i, d in enumerate(self.angles): if key in d: angles[i] = d[assign_variable(key)] return angles
def extract_gradients(self, key: str) -> typing.Dict[numbers.Integral, numbers.Real]: """ :param key: the key specifiying which gradient shall be extracted :return: dictionary with dictionary_key=iteration, dictionary_value=gradient[key] """ gradients = {} for i, d in enumerate(self.gradients): if key in d: gradients[i] = d[assign_variable(key)] return gradients
def __init__(self, name, parameter: UnionParam, target: UnionList, control: UnionList = None): super().__init__(name=name, target=target, control=control) if isinstance(parameter, VectorObjective): raise TequilaException( 'Received VectorObjective {} as parameter. This is forbidden.'. format(parameter)) self._parameter = assign_variable(variable=parameter)
def __init__(self, method: str = "L-BFGS-B", tol: numbers.Real = None, method_options=None, method_bounds=None, method_constraints=None, silent: bool = True, **kwargs): """ Parameters ---------- method: str: Default = 'L-BFGS-B': The scipy optimization method passed as string. tol: float, optional: See scipy documentation for the method you picked method_options: optional: See scipy documentation for the method you picked method_bounds: optional: See scipy documentation for the method you picked method_constraints: optional: See scipy documentation for the method you picked silent: bool: if False the optimizer prints out all evaluated energies """ super().__init__(**kwargs) if hasattr(method, "upper"): self.method = method.upper() else: self.method = method self.tol = tol self.method_options = method_options if method_bounds is not None: method_bounds = { assign_variable(k): v for k, v in method_bounds.items() } self.method_bounds = method_bounds self.silent = silent if method_options is None: self.method_options = {'maxiter': self.maxiter} else: self.method_options = method_options if 'maxiter' not in method_options: self.method_options['maxiter'] = self.maxiter self.method_options['disp'] = not silent if method_constraints is None: self.method_constraints = () else: self.method_constraints = method_constraints
def GeneralizedRotation(angle: typing.Union[typing.List[typing.Hashable], typing.List[numbers.Real]], generator: QubitHamiltonian, control: typing.Union[list, int] = None, eigenvalues_magnitude: float = 0.5, steps: int = 1, assume_real=False) -> QCircuit: """ Notes -------- A gates which is shift-rule differentiable - its generator only has two distinguishable eigenvalues - it is then differentiable by the shift rule - eigenvalues_magnitude needs to be given upon initialization (this is "r" from Schuld et. al. and the default is r=1/2) - the generator will not (!) be verified to fullfill the properties Compiling will be done in analogy to a trotterized gate with steps=1 as default The gate will act in the same way as rotations and exppauli gates .. math:: U_{G}(\\text{angle}) = e^{-i\\frac{\\text{angle}}{2} G} Parameters ---------- angle numeric type or hashable symbol or tequila objective generator tequila QubitHamiltonian or any other structure with paulistrings control list of control qubits eigenvalues_magnitude magnitude of eigenvalues, in most papers referred to as "r" (default 0.5) steps possible Trotterization steps (default 1) Returns ------- The gate wrapped in a circuit """ return QCircuit.wrap_gate( impl.GeneralizedRotationImpl( angle=assign_variable(angle), generator=generator, control=control, eigenvalues_magnitude=eigenvalues_magnitude, steps=steps, assume_real=assume_real))
def get_circuit(self): """ :return: Return the abstract circuit with tequila parameters """ result = QCircuit() for g in self._abstract_circuit.gates: g2 = copy.deepcopy(g) if hasattr(g, "parameter"): symbol = g.parameter name = str(-symbol) # kill the minus from the dagger g2._parameter = assign_variable(name) result += g2 return result
def __init__(self, method: str = "L-BFGS-B", tol: numbers.Real = None, method_options=None, method_bounds=None, method_constraints=None, silent: bool = True, **kwargs): """ Optimize a circuit to minimize a given objective using scipy See the Optimizer class for all other parameters to initialize :param method: The scipy method passed as string :param use_gradient: do gradient based optimization :param tol: See scipy documentation for the method you picked :param method_options: See scipy documentation for the method you picked :param method_bounds: See scipy documentation for the method you picked :param method_constraints: See scipy documentation for the method you picked :param silent: if False the optimizer print out all evaluated energies :param use_gradient: select if gradients shall be used. Can be done automatically for most methods """ super().__init__(**kwargs) if hasattr(method, "upper"): self.method = method.upper() else: self.method = method self.tol = tol self.method_options = method_options if method_bounds is not None: method_bounds = { assign_variable(k): v for k, v in method_bounds.items() } self.method_bounds = method_bounds self.silent = silent if method_options is None: self.method_options = {'maxiter': self.maxiter} else: self.method_options = method_options if 'maxiter' not in method_options: self.method_options['maxiter'] = self.maxiter self.method_options['disp'] = not silent if method_constraints is None: self.method_constraints = () else: self.method_constraints = method_constraints
def extract_gradients(self, key: str) -> typing.Dict[numbers.Integral, numbers.Real]: """ convenience function to get the gradients of some variable out of the history. Parameters ---------- key: str: the name of the variable whose gradients are sought Returns ------- dict: a dictionary, representing the gradient of variable 'key' over time. """ gradients = {} for i, d in enumerate(self.gradients): if key in d: gradients[i] = d[assign_variable(key)] return gradients
def __init__(self, angle, target=None, generator=None, p0=None, assume_real=True, control=None, compile_options=None): angle = assign_variable(angle) if generator is None: assert target is not None assert p0 is None generator = paulis.I() p0a = paulis.I() p0b = paulis.I() for i in range(len(target) // 2): generator *= paulis.Sp(target[2 * i]) * paulis.Sm( target[2 * i + 1]) p0a *= paulis.Qp(target[2 * i]) * paulis.Qm(target[2 * i + 1]) p0b *= paulis.Qm(target[2 * i]) * paulis.Qp(target[2 * i + 1]) generator = (1.0j * (generator - generator.dagger())).simplify() p0 = paulis.I() - p0a - p0b else: assert generator is not None assert p0 is not None super().__init__(name="QubitExcitation", parameter=angle, target=self.extract_targets(generator), control=control) self.generator = generator if control is not None: # augment p0 for control qubits # Qp = 1/2(1+Z) = |0><0| p0 = p0 * paulis.Qp(control) self.p0 = p0 self.assume_real = assume_real if compile_options is None: self.compile_options = "optimize" else: self.compile_options = compile_options
def GeneralizedRotation(angle: typing.Union[typing.List[typing.Hashable], typing.List[numbers.Real]], generator: QubitHamiltonian, control: typing.Union[list, int] = None, shift: float = 0.5, steps: int = 1) -> QCircuit: """ Notes -------- A gates which is shift-rule differentiable - its generator only has two distinguishable eigenvalues - it is then differentiable by the shift rule - shift needs to be given upon initialization (otherwise its default is 1/2) - the generator will not be verified to fullfill the properties Compiling will be done in analogy to a trotterized gate with steps=1 as default The gate will act in the same way as rotations and exppauli gates .. math:: U_{G}(\\text{angle}) = e^{-i\\frac{\\text{angle}}{2} G} Parameters ---------- angle generator control shift steps Returns ------- The gate wrapped in a circuit """ return QCircuit.wrap_gate( GeneralizedRotationImpl(angle=assign_variable(angle), generator=generator, control=control, shift=shift, steps=steps))
def _initialize_power_gate(name: str, target: typing.Union[list, int], generator, control: typing.Union[list, int] = None, power=None, angle=None, *args, **kwargs) -> QCircuit: target = list_assignment(target) # allow angle instead of power in initialization for more consistency # if angle is given we just convert it if angle is not None: angle = assign_variable(angle) if power is not None: power = power * angle / np.pi else: power = angle / np.pi if power is None or power in [1, 1.0]: gates = [ impl.QGateImpl(name=name, target=q, control=control, generator=generator(q)) for q in target ] else: gates = [ impl.PowerGateImpl(name=name, power=power, target=q, control=control, generator=generator(q), *args, **kwargs) for q in target ] return QCircuit.wrap_gate(gates)
def plot(self, property: typing.Union[str, typing.List[str]] = 'energies', key: str = None, filename=None, baselines: typing.Dict[str, float] = None, *args, **kwargs): """ Convenience function to plot the progress of the optimizer over time. Parameters ---------- property: (list of) str: Default = 'energies' which property (eg angles, energies, gradients) to plot. Default: plot energies over time. key: str, optional: if property is 'angles' or 'gradients', key allows you to plot just an individual variables' property. Default: plot everything filename, optional: if give, plot to this file; else, plot to terminal. Default: plot to terminal. baselines: dict, optional: dictionary of plotting axis baseline information. Default: use whatever matplotlib auto-generates. args: args. kwargs: kwargs. Returns ------- None """ from matplotlib import pyplot as plt from matplotlib.ticker import MaxNLocator fig = plt.figure() fig.gca().xaxis.set_major_locator(MaxNLocator(integer=True)) import pickle if baselines is not None: for k, v in baselines.items(): plt.axhline(y=v, label=k) if hasattr(property, "lower"): properties = [property.lower()] else: properties = property labels = None if 'labels' in kwargs: labels = kwargs['labels'] elif 'label' in kwargs: labels = kwargs['label'] if hasattr(labels, "lower"): labels = [labels] * len(properties) for k, v in kwargs.items(): if hasattr(plt, k): f = getattr(plt, k) if callable(f): f(v) else: f = v if key is None: keys = [[k for k in self.angles[-1].keys()]] * len(properties) elif isinstance(key, typing.Hashable): keys = [[assign_variable(key)]] * len(properties) else: key = [assign_variable(k) for k in key] keys = [key] * len(properties) for i, p in enumerate(properties): try: label = labels[i] except: label = p if p == "energies": data = getattr(self, "extract_" + p)() plt.plot(list(data.keys()), list(data.values()), label=str(label), marker='o', linestyle='--') else: for k in keys[i]: data = getattr(self, "extract_" + p)(key=k) plt.plot(list(data.keys()), list(data.values()), label=str(label) + " " + str(k), marker='o', linestyle='--') loc = 'best' if 'loc' in kwargs: loc = kwargs['loc'] plt.legend(loc=loc) if filename is None: plt.show() else: pickle.dump(fig, open(filename + ".pickle", "wb")) plt.savefig(fname=filename + ".pdf", **kwargs)
def parameter(self, other): self.parameter = assign_variable(variable=other)
def __init__(self, angle, generator, p0, assume_real=True, control=None): angle = assign_variable(angle) super().__init__(name="QubitExcitation", parameter=angle, target=self.extract_targets(generator), control=control, eigenvalues_magnitude = 0.25) self.generator = generator self.p0 = p0 self.assume_real = assume_real
def U(theta, phi, lambd, target: typing.Union[list, int], control: typing.Union[list, int] = None) -> QCircuit: """ Notes ---------- Convenient gate, one of the abstract gates defined by OpenQASM. .. math:: U(\\theta, \\phi, \\lambda) = R_z(\\phi)R_x(-\\pi/2)R_z(\\theta)R_x(\\pi/2)R_z(\\lambda) U(\\theta, \\phi, \\lambda) = \\begin{pmatrix} e^{-i \\frac{\\phi}{2}} & 0 \\\\ 0 & e^{i \\frac{\\phi}{2}} \\end{pmatrix} \\begin{pmatrix} \\cos{-\\frac{\\pi}{4}} & -i \\sin{-\\frac{\\pi}{4}} \\\\ -i \\sin{-\\frac{\\pi}{4}} & \\cos{-\\frac{\\pi}{4}} \\end{pmatrix} \\begin{pmatrix} e^{-i \\frac{\\theta}{2}} & 0 \\\\ 0 & e^{i \\frac{\\theta}{2}} \\end{pmatrix} \\begin{pmatrix} \\cos{\\frac{\\pi}{4}} & -i \\sin{\\frac{\\pi}{4}} \\\\ -i \\sin{\\frac{\\pi}{4}} & \\cos{\\frac{\\pi}{4}} \\end{pmatrix} \\begin{pmatrix} e^{-i \\frac{\\lambda}{2}} & 0 \\\\ 0 & e^{i \\frac{\\lambda}{2}} \\end{pmatrix} U(\\theta, \\phi, \\lambda) = \\begin{pmatrix} \\cos{\\frac{\\theta}{2}} & -e^{i \\lambda} \\sin{\\frac{\\theta}{2}} \\\\ e^{i \\phi} \\sin{\\frac{\\theta}{2}} & e^{i (\\phi+\\lambda)} \\cos{\\frac{\\theta}{2}} \\end{pmatrix} Parameters ---------- theta first parameter angle phi second parameter angle lamnd third parameter angle target int or list of int control int or list of int Returns ------- QCircuit object """ theta = assign_variable(theta) phi = assign_variable(phi) lambd = assign_variable(lambd) pi_half = assign_variable(np.pi / 2) return Rz(angle=lambd, target=target, control=control) + \ Rx(angle=pi_half, target=target, control=control) + \ Rz(angle=theta, target=target, control=control) + \ Rx(angle=-pi_half, target=target, control=control) + \ Rz(angle=phi, target=target, control=control)
def Trotterized(generator: QubitHamiltonian = None, steps: int = 1, angle: typing.Union[typing.Hashable, numbers.Real, Variable] = None, control: typing.Union[list, int] = None, randomize=False, *args, **kwargs) -> QCircuit: """ Parameters ---------- generator : generator of the gate U = e^{-i\frac{angle}{2} G } angles : coefficients for each generator steps : trotter steps control : control qubits generators: QubitHamiltonian : The generator of the gate steps: int : Trotter Steps angle: typing.Hashable : A symbol that will be converted to a tq.Variable numbers.Real : A fixed real number Variable : A tequila Variable control: control qubits Returns ------- QCircuit """ # downward compatibility if "generators" in kwargs: if generator is None: if len(kwargs["generators"]) > 1: if "angles" not in kwargs: angles = [angle] * len(kwargs["generators"]) else: angles = kwargs["angles"] result = QCircuit() for angle, g in zip(angles, kwargs["generators"]): result += Trotterized(generator=g, angle=angle, steps=steps, control=control, randomize=randomize) return result else: generator = kwargs["generators"][0] else: raise Exception( "Trotterized: You gave generators={} and generator={}".format( generator, kwargs["generators"])) if "angles" in kwargs: if angle is None: if len(kwargs["angles"]) > 1: raise Exception( "multiple angles given, but only one generator") angle = kwargs["angles"][0] else: raise Exception( "Trotterized: You gave angles={} and angle={}".format( angle, kwargs["angles"])) angle = assign_variable(angle) return QCircuit.wrap_gate( impl.TrotterizedGateImpl(generator=generator, angle=angle, steps=steps, control=control, randomize=randomize, **kwargs))
def minimize(objective: Objective, gradient: typing.Union[str, typing.Dict[Variable, Objective]] = None, hessian: typing.Union[str, typing.Dict[typing.Tuple[Variable, Variable], Objective]] = None, qng: bool = None, initial_values: typing.Dict[typing.Hashable, numbers.Real] = None, variables: typing.List[typing.Hashable] = None, samples: int = None, maxiter: int = 100, backend: str = None, backend_options: dict = None, noise: NoiseModel = None, method: str = "BFGS", tol: float = 1.e-3, method_options: dict = None, method_bounds: typing.Dict[typing.Hashable, numbers.Real] = None, method_constraints=None, silent: bool = False, save_history: bool = True, *args, **kwargs) -> SciPyReturnType: """ Parameters ---------- objective: Objective : The tequila objective to optimize gradient: typing.Union[str, typing.Dict[Variable, Objective], None] : (Default value = None) : '2-point', 'cs' or '3-point' for numerical gradient evaluation (does not work in combination with all optimizers), dictionary of variables and tequila objective to define own gradient, None for automatic construction (default) hessian: typing.Union[str, typing.Dict[Variable, Objective], None] : (Default value = None) : '2-point', 'cs' or '3-point' for numerical gradient evaluation (does not work in combination with all optimizers), dictionary (keys:tuple of variables, values:tequila objective) to define own gradient, None for automatic construction (default) qng: bool : (Default value = False) : whether or not, in the event that a gradient-based method is to be used, the qng, rather than the standard gradient, should be employed. NOTE: throws an error for anything but a single expectationvalue with no passive angles. initial_values: typing.Dict[typing.Hashable, numbers.Real]: (Default value = None): Initial values as dictionary of Hashable types (variable keys) and floating point numbers. If given None they will all be set to zero variables: typing.List[typing.Hashable] : (Default value = None) List of Variables to optimize samples: int : (Default value = None) samples/shots to take in every run of the quantum circuits (None activates full wavefunction simulation) maxiter: int : (Default value = 100) backend: str : (Default value = None) Simulator backend, will be automatically chosen if set to None backend_options: dict: (Default value = None) Additional options for the backend Will be unpacked and passed to the compiled objective in every call noise: NoiseModel: (Default value =None) a NoiseModel to apply to all expectation values in the objective. method: str : (Default value = "BFGS") Optimization method (see scipy documentation, or 'available methods') tol: float : (Default value = 1.e-3) Convergence tolerance for optimization (see scipy documentation) method_options: dict : (Default value = None) Dictionary of options (see scipy documentation) method_bounds: typing.Dict[typing.Hashable, typing.Tuple[float, float]]: (Default value = None) bounds for the variables (see scipy documentation) method_constraints : (Default value = None) (see scipy documentation silent: bool : (Default value = False) No printout if True save_history: bool: (Default value = True) Save the history throughout the optimization Returns ------- """ # bring into right format variables = format_variable_list(variables) initial_values = format_variable_dictionary(initial_values) if isinstance(gradient, dict) or hasattr(gradient, "items"): gradient = format_variable_dictionary(gradient) if isinstance(hessian, dict) or hasattr(hessian, "items"): hessian = {(assign_variable(k[0]), assign_variable([k[1]])): v for k, v in hessian.items()} method_bounds = format_variable_dictionary(method_bounds) # set defaults all_variables = objective.extract_variables() if variables is None: variables = all_variables if initial_values is None: initial_values = {k: numpy.random.uniform(0, 2 * numpy.pi) for k in all_variables} else: # autocomplete initial values, warn if you did detected = False for k in all_variables: if k not in initial_values: initial_values[k] = numpy.random.uniform(0, 2 * numpy.pi) detected = True if detected and not silent: print("WARNING: initial_variables given but not complete: Autocomplete with random number") optimizer = OptimizerSciPy(save_history=save_history, maxiter=maxiter, method=method, method_options=method_options, method_bounds=method_bounds, method_constraints=method_constraints, silent=silent, tol=tol) if initial_values is not None: initial_values = {assign_variable(k): v for k, v in initial_values.items()} return optimizer(objective=objective, qng=qng, backend=backend, backend_options=backend_options, gradient=gradient, hessian=hessian, initial_values=initial_values, variables=variables, noise=noise, samples=samples, *args, **kwargs)
def dagger(self): result = copy.deepcopy(self) result._parameter = assign_variable(-self.parameter) return result
def minimize(objective, method: str = "bfgs", variables: list = None, initial_values: typing.Union[dict, numbers.Number] = 0.0, maxiter: int = None, *args, **kwargs): """ Parameters ---------- method: str: The optimization method (e.g. bfgs, cobyla, nelder-mead, ...) see 'tq.optimizers.show_available_methods()' for an overview objective: tq.Objective: The abstract tequila objective to be optimized variables: list of names: The variables which shall be optimized given as list Can be passed as list of names or list of tq variables initial_values: dict: Initial values for the optimization, passed as dictionary with the variable names as keys. Alternatively `zero`, `random` or a single number are accepted maxiter: maximum number of iterations kwargs: further keyword arguments for the actual minimization functions can also be called directly as tq.minimize_modulename e.g. tq.minimize_scipy See their documentation for more details example: gradient keyword: gradient (Default Value: None): instructions for gradient compilation can be a dictionary of tequila objectives representing the gradients or a string/dictionary giving instructions for numerical gradients examples are gradient = '2-point' gradient = {'method':'2-point', 'stepsize': 1.e-4} gradient = {'method':Callable, 'stepsize': 1.e-4} see optimizer_base.py for method examples gradient = None: analytical gradients are compiled Returns ------- """ for k, v in INSTALLED_OPTIMIZERS.items(): if method.lower() in v.methods or method.upper() in v.methods: if hasattr(initial_values, "lower") and initial_values.lower() == "zero": initial_values = { assign_variable(k): 0.0 for k in objective.extract_variables() } elif isinstance(initial_values, numbers.Number): initial_values = { assign_variable(k): initial_values for k in objective.extract_variables() } if initial_values is not None: initial_values = { assign_variable(k): v for k, v in initial_values.items() } return v.minimize(objective=objective, method=method, variables=variables, initial_values=initial_values, maxiter=maxiter, *args, **kwargs) raise TequilaOptimizerException( "Could not find optimization method {} in tequila optimizers. You might miss dependencies" )
def minimize(objective: Objective, gradient: typing.Union[str, typing.Dict[Variable, Objective]] = None, hessian: typing.Union[str, typing.Dict[typing.Tuple[Variable, Variable], Objective]] = None, initial_values: typing.Dict[typing.Hashable, numbers.Real] = None, variables: typing.List[typing.Hashable] = None, samples: int = None, maxiter: int = 100, backend: str = None, backend_options: dict = None, noise: NoiseModel = None, device: str = None, method: str = "BFGS", tol: float = 1.e-3, method_options: dict = None, method_bounds: typing.Dict[typing.Hashable, numbers.Real] = None, method_constraints=None, silent: bool = False, save_history: bool = True, *args, **kwargs) -> SciPyResults: """ Parameters ---------- objective: Objective : The tequila objective to optimize gradient: typing.Union[str, typing.Dict[Variable, Objective], None] : Default value = None): '2-point', 'cs' or '3-point' for numerical gradient evaluation (does not work in combination with all optimizers), dictionary of variables and tequila objective to define own gradient, None for automatic construction (default) Other options include 'qng' to use the quantum natural gradient. hessian: typing.Union[str, typing.Dict[Variable, Objective], None], optional: '2-point', 'cs' or '3-point' for numerical gradient evaluation (does not work in combination with all optimizers), dictionary (keys:tuple of variables, values:tequila objective) to define own gradient, None for automatic construction (default) initial_values: typing.Dict[typing.Hashable, numbers.Real], optional: Initial values as dictionary of Hashable types (variable keys) and floating point numbers. If given None they will all be set to zero variables: typing.List[typing.Hashable], optional: List of Variables to optimize samples: int, optional: samples/shots to take in every run of the quantum circuits (None activates full wavefunction simulation) maxiter: int : (Default value = 100): max iters to use. backend: str, optional: Simulator backend, will be automatically chosen if set to None backend_options: dict, optional: Additional options for the backend Will be unpacked and passed to the compiled objective in every call noise: NoiseModel, optional: a NoiseModel to apply to all expectation values in the objective. method: str : (Default = "BFGS"): Optimization method (see scipy documentation, or 'available methods') tol: float : (Default = 1.e-3): Convergence tolerance for optimization (see scipy documentation) method_options: dict, optional: Dictionary of options (see scipy documentation) method_bounds: typing.Dict[typing.Hashable, typing.Tuple[float, float]], optional: bounds for the variables (see scipy documentation) method_constraints: optional: (see scipy documentation silent: bool : No printout if True save_history: bool: Save the history throughout the optimization Returns ------- SciPyReturnType: the results of optimization """ if isinstance(gradient, dict) or hasattr(gradient, "items"): if all([isinstance(x, Objective) for x in gradient.values()]): gradient = format_variable_dictionary(gradient) if isinstance(hessian, dict) or hasattr(hessian, "items"): if all([isinstance(x, Objective) for x in hessian.values()]): hessian = {(assign_variable(k[0]), assign_variable([k[1]])): v for k, v in hessian.items()} method_bounds = format_variable_dictionary(method_bounds) # set defaults optimizer = OptimizerSciPy(save_history=save_history, maxiter=maxiter, method=method, method_options=method_options, method_bounds=method_bounds, method_constraints=method_constraints, silent=silent, backend=backend, backend_options=backend_options, device=device, samples=samples, noise=noise, tol=tol, *args, **kwargs) if initial_values is not None: initial_values = { assign_variable(k): v for k, v in initial_values.items() } return optimizer(objective=objective, gradient=gradient, hessian=hessian, initial_values=initial_values, variables=variables, *args, **kwargs)
def __call__(self, objective: Objective, initial_values: typing.Dict[Variable, numbers.Real], variables: typing.List[Variable], gradient: typing.Dict[Variable, Objective] = None, qng: bool = False, hessian: typing.Dict[typing.Tuple[Variable, Variable], Objective] = None, samples: int = None, backend: str = None, backend_options: dict = None, noise: NoiseModel = None, reset_history: bool = True, *args, **kwargs) -> SciPyReturnType: """ Optimizes with scipy and gives back the optimized angles Get the optimized energies over the history :param objective: The tequila Objective to minimize :param initial_valuesxx: initial values for the objective :param return_scipy_output: chose if the full scipy output shall be returned :param reset_history: reset the history before optimization starts (has no effect if self.save_history is False) :return: tuple of optimized energy ,optimized angles and scipy output """ infostring = "Starting {method} optimization\n".format(method=self.method) infostring += "Objective: {} expectationvalues\n".format(objective.count_expectationvalues()) if self.save_history and reset_history: self.reset_history() active_angles = {} for v in variables: active_angles[v] = initial_values[v] passive_angles = {} for k, v in initial_values.items(): if k not in active_angles.keys(): passive_angles[k] = v # Transform the initial value directory into (ordered) arrays param_keys, param_values = zip(*active_angles.items()) param_values = numpy.array(param_values) bounds = None if self.method_bounds is not None: bounds = {k: None for k in active_angles} for k, v in self.method_bounds.items(): if k in bounds: bounds[k] = v infostring += "bounds : {}\n".format(self.method_bounds) names, bounds = zip(*bounds.items()) assert (names == param_keys) # make sure the bounds are not shuffled # do the compilation here to avoid costly recompilation during the optimization compiled_objective = compile(objective=objective, variables=initial_values, backend=backend, noise=noise, samples=samples, *args, **kwargs) E = _EvalContainer(objective=compiled_objective, param_keys=param_keys, samples=samples, passive_angles=passive_angles, save_history=self.save_history, backend_options = backend_options, silent=self.silent) # compile gradients if self.method in self.gradient_based_methods + self.hessian_based_methods and not isinstance(gradient, str): compiled_grad_objectives = dict() if gradient is None: gradient = {assign_variable(k): grad(objective=objective, variable=k) for k in active_angles.keys()} else: gradient = {assign_variable(k): v for k, v in gradient.items()} grad_exval = [] for k in active_angles.keys(): if k not in gradient: raise Exception("No gradient for variable {}".format(k)) grad_exval.append(gradient[k].count_expectationvalues()) compiled_grad_objectives[k] = compile(objective=gradient[k], variables=initial_values, samples=samples, noise=noise, backend=backend, *args, **kwargs) if qng: combos = get_qng_combos(objective, samples=samples, backend=backend, noise=noise, initial_values=initial_values) dE = _QngContainer(combos=combos, param_keys=param_keys, samples=samples, passive_angles=passive_angles, save_history=self.save_history, silent=self.silent, backend_options=backend_options) else: dE = _GradContainer(objective=compiled_grad_objectives, param_keys=param_keys, samples=samples, passive_angles=passive_angles, save_history=self.save_history, silent=self.silent, backend_options=backend_options) infostring += "Gradients: {} expectationvalues (min={}, max={})\n".format(sum(grad_exval), min(grad_exval), max(grad_exval)) else: # use numerical gradient dE = gradient infostring += "Gradients: {}\n".format(gradient) # compile hessian if self.method in self.hessian_based_methods and not isinstance(hessian, str): if isinstance(gradient, str): raise TequilaScipyException("Can not use numerical gradients for Hessian based methods") if qng is True: raise TequilaScipyException('Quantum Natural Hessian not yet well-defined, sorry!') compiled_hess_objectives = dict() hess_exval = [] for i, k in enumerate(active_angles.keys()): for j, l in enumerate(active_angles.keys()): if j > i: continue hess = grad(gradient[k], l) compiled_hess = compile(objective=hess, variables=initial_values, samples=samples, noise=noise, backend=backend, *args, **kwargs) compiled_hess_objectives[(k, l)] = compiled_hess compiled_hess_objectives[(l, k)] = compiled_hess hess_exval.append(compiled_hess.count_expectationvalues()) ddE = _HessContainer(objective=compiled_hess_objectives, param_keys=param_keys, samples=samples, passive_angles=passive_angles, save_history=self.save_history, silent=self.silent) infostring += "Hessian: {} expectationvalues (min={}, max={})\n".format(sum(hess_exval), min(hess_exval), max(hess_exval)) else: infostring += "Hessian: {}\n".format(hessian) if self.method != "TRUST-CONSTR" and hessian is not None: raise TequilaScipyException("numerical hessians only for trust-constr method") ddE = hessian if not self.silent: print("ObjectiveType is {}".format(type(compiled_objective))) print(infostring) print("backend: {}".format(compiled_objective.backend)) print("samples: {}".format(samples)) print("{} active variables".format(len(active_angles))) # get the number of real scipy iterations for better histories real_iterations = [] Es = [] callback = lambda x, *args: real_iterations.append(len(E.history) - 1) res = scipy.optimize.minimize(E, x0=param_values, jac=dE, hess=ddE, args=(Es,), method=self.method, tol=self.tol, bounds=bounds, constraints=self.method_constraints, options=self.method_options, callback=callback) # failsafe since callback is not implemented everywhere if len(real_iterations) == 0: real_iterations = range(len(E.history)) else: real_iterations = [0] + real_iterations if self.save_history: self.history.energies = [E.history[i] for i in real_iterations] self.history.energy_evaluations = E.history self.history.angles = [E.history_angles[i] for i in real_iterations] self.history.angles_evaluations = E.history_angles if dE is not None and not isinstance(dE, str): # can currently only save gradients if explicitly evaluated # and will fail for hessian based approaches # need better callback functions try: if self.method not in self.hessian_based_methods: self.history.gradients = [dE.history[i] for i in real_iterations] except: print("WARNING: History could not assign the stored gradients") self.history.gradients_evaluations = dE.history if ddE is not None and not isinstance(ddE, str): # hessians are not evaluated in the same frequencies as energies # therefore we can not store the "real" iterations currently self.history.hessians_evaluations = ddE.history E_final = res.fun angles_final = dict((param_keys[i], res.x[i]) for i in range(len(param_keys))) angles_final = {**angles_final, **passive_angles} return SciPyReturnType(energy=E_final, angles=format_variable_dictionary(angles_final), history=self.history, scipy_output=res)
def Trotterized(generators: typing.List[QubitHamiltonian] = None, steps: int = 1, angles: typing.Union[typing.List[typing.Hashable], typing.List[numbers.Real], typing.List[Variable]] = None, control: typing.Union[list, int] = None, parameters: TrotterParameters = None, *args, **kwargs) -> QCircuit: """ Parameters ---------- generators : list of generators angles : coefficients for each generator steps : trotter steps control : control qubits parameters : Additional Trotter parameters, if None then defaults are used generators: typing.List[QubitHamiltonian] : steps: int : angles: typing.Union[typing.List[typing.Hashable] : typing.List[numbers.Real] : typing.List[Variable]] : (Default value = None) control: typing.Union[list : int] : (Default value = None) parameters: TrotterParameters : (Default value = None) Returns ------- QCircuit """ # convenience if "generator" in kwargs: if generators is None: generators = [kwargs["generator"]] else: raise Exception( "Trotterized: You gave generators={} and generator={}".format( angles, kwargs["generator"])) if "angle" in kwargs: if angles is None: angles = [kwargs["angle"]] * len(generators) else: raise Exception( "Trotterized: You gave angles={} and angle={}".format( angles, kwargs["angle"])) # more convenience if not (isinstance(generators, list) or isinstance(generators, tuple)): generators = [generators] if not (isinstance(angles, list) or isinstance(angles, tuple)): angles = [angles] if parameters is None: parameters = TrotterParameters() assigned_angles = [assign_variable(angle) for angle in angles] return QCircuit.wrap_gate( impl.TrotterizedGateImpl(generators=generators, angles=assigned_angles, steps=steps, control=control, **parameters.__dict__))