def __grad_expectationvalue(E: ExpectationValueImpl, variable: Variable): ''' implements the analytic partial derivative of a unitary as it would appear in an expectation value. See the paper. :param unitary: the unitary whose gradient should be obtained :param variables (list, dict, str): the variables with respect to which differentiation should be performed. :return: vector (as dict) of dU/dpi as Objective (without hamiltonian) ''' hamiltonian = E.H unitary = E.U assert (unitary.verify()) # fast return if possible if variable not in unitary.extract_variables(): return 0.0 param_gates = unitary._parameter_map[variable] dO = Objective() for idx_g in param_gates: idx, g = idx_g # failsafe if g.is_controlled(): raise TequilaException( "controlled gate in gradient: Compiler was not called. Gate is {}" .format(g)) if not hasattr(g, "eigenvalues_magnitude"): raise TequilaException('No shift found for gate {}'.format(g)) dOinc = __grad_shift_rule(unitary, g, idx, variable, hamiltonian) dO += dOinc assert dO is not None return dO
def qng_circuit_grad(E: ExpectationValueImpl): ''' implements the analytic partial derivatives of a unitary as it would appear in an expectation value, taking all parameters as final (only useful for qng, invalid method otherwise!) :param E: the Excpectation whose gradient should be obtained :return: vector (as dict) of dU/dpi as Objectives. ''' hamiltonian = E.H unitary = E.U # fast return if possible out = [] for i, g in enumerate(unitary.gates): if g.is_parametrized(): if g.is_controlled(): raise TequilaException( "controlled gate in qng circuit gradient: Compiler was not called" ) if hasattr(g, "shift"): if hasattr(g._parameter, 'extract_variables'): shifter = qng_grad_gaussian(unitary, g, i, hamiltonian) out.append(shifter) else: print(g, type(g)) raise TequilaException('No shift found for gate {}'.format(g)) if out is None: raise TequilaException("caught a dead circuit in qng gradient") return out
def binary(self, n_qubits: int = None): if len(self._data.keys()) == 0: maxq = 1 else: maxq = max(self._data.keys()) + 1 if n_qubits is None: n_qubits = maxq if n_qubits < maxq: raise TequilaException( "PauliString acts on qubit number larger than n_qubits given\n PauliString=" + self.__repr__() + ", n_qubits=" + str(n_qubits)) binary = numpy.zeros(2 * n_qubits) for k, v in self._data.items(): if v.upper() == "X": binary[k] = 1 elif v.upper() == "Y": binary[k] = 1 binary[n_qubits + k] = 1 elif v.upper() == "Z": binary[n_qubits + k] = 1 else: raise TequilaException("Unknown Pauli: %" + str(v)) return BinaryPauli(coeff=self.coeff, binary=binary)
def make_excitation_generator( self, indices: typing.Iterable[typing.Tuple[int, int]]) -> QubitHamiltonian: """ Notes ---------- Creates the transformed hermitian generator of UCC type unitaries: M(a^\dagger_{a_0} a_{i_0} a^\dagger{a_1}a_{i_1} ... - h.c.) where the qubit map M depends is self.transformation Parameters ---------- indices : typing.Iterable[typing.Tuple[int, int]] : List of tuples [(a_0, i_0), (a_1, i_1), ... ] - recommended format, in spin-orbital notation (alpha odd numbers, beta even numbers) can also be given as one big list: [a_0, i_0, a_1, i_1 ...] Returns ------- type 1j*Transformed qubit excitation operator, depends on self.transformation """ # check indices and convert to list of tuples if necessary if len(indices) == 0: raise TequilaException( "make_excitation_operator: no indices given") elif not isinstance(indices[0], typing.Iterable): if len(indices) % 2 != 0: raise TequilaException( "make_excitation_generator: unexpected input format of indices\n" "use list of tuples as [(a_0, i_0),(a_1, i_1) ...]\n" "or list as [a_0, i_0, a_1, i_1, ... ]\n" "you gave: {}".format(indices)) converted = [(indices[2 * i], indices[2 * i + 1]) for i in range(len(indices) // 2)] else: converted = indices # convert to openfermion input format ofi = [] dag = [] for pair in converted: assert (len(pair) == 2) ofi += [ (int(pair[0]), 1), (int(pair[1]), 0) ] # openfermion does not take other types of integers like numpy.int64 dag += [(int(pair[0]), 0), (int(pair[1]), 1)] op = openfermion.FermionOperator(tuple(ofi), 1.j) # 1j makes it hermitian op += openfermion.FermionOperator(tuple(reversed(dag)), -1.j) qop = QubitHamiltonian(qubit_hamiltonian=self.transformation(op)) # check if the operator is hermitian and cast coefficients to floats # in order to avoid trouble with the simulation backends assert qop.is_hermitian() for k, v in qop.qubit_operator.terms.items(): qop.qubit_operator.terms[k] = to_float(v) qop = qop.simplify() return qop
def get_angle(name: str) -> list: i = name.find('(') j = name.find(')') if j == -1: raise TequilaException("Invalid specification {}".format(name)) angles_str = name[i + 1:j].split(',') angles = [] for angle in angles_str: try: phase = float(angle) except ValueError: if angle.find('pi') == -1: raise TequilaException("Invalid specification {}".format(name)) angle = angle.replace('pi', '') try: if angle.find('*') != -1: angle = angle.replace('*', '') phase = float(angle) * pi elif angle.find('/') != -1: angle = angle.replace('/', '') phase = pi / float(angle) elif len(angle) == 0: phase = pi else: phase = float(angle) except ValueError: raise TequilaException("Invalid specification {}".format(name)) angles.append(phase) 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 is_binary(self): if not isinstance(self.binary, np.ndarray): raise TequilaException( 'Unknown representation of binary vector. Got ' + str(self.binary) + ' with type ' + type(self.binary)) if not all([x == 1 or x == 0 for x in self.binary]): raise TequilaException( 'Not all number in the binary vector is 0 or 1. Got ' + str(self.binary))
def build_device_circuit(self, ignore_failures=False): """ Attempts to configure a cirq circuit to run on a device Parameters ---------- ignore_failures: bool: whether or not to include gates in the circuit that fail to compile. Ignore; currently under construction. Returns ------- cirq.Circuit the circuit, reconfigured for the device. """ c = self.circuit device = self.device line = None circuit = None if isinstance(device, cirq.Device): if isinstance(device, cirq.google.devices.XmonDevice) or isinstance( device, cirq.google.devices.serializable_device. SerializableDevice): options = ['xmon', 'xmon_partial_cz', 'sqrt_iswap', 'sycamore'] if device in [cirq.google.Sycamore, cirq.google.Sycamore23]: options = [ 'sycamore', 'sqrt_iswap', 'xmon', 'xmon_partial_cz' ] for option in options: try: line = cirq.google.line_on_device( device, length=len(self.abstract_circuit.qubits)) circuit = cirq.google.optimized_for_sycamore( circuit=c, new_device=device, optimizer_type=option, qubit_map=lambda q: line[q.x]) except: line = None pass if circuit is None: raise TequilaCirqException( 'could not optimize for device={}'.format(device)) else: ### under construction (potentially on other branches) raise TequilaException( 'Only known and Xmon devices currently functional. Sorry!') else: raise TequilaException( 'build_device_circuit demands a cirq.Device object; received {}, of type {}' .format(str(device), type(device))) if line is not None: for k in self.qubit_map.keys(): self.qubit_map[k].instance = line[self.qubit_map[k].instance.x] return circuit
def parse_from_open_qasm_2(qasm_code: str, rigorous: bool = True) -> QCircuit: lines = qasm_code.splitlines() clean_code = [] # ignore comments for line in lines: if line.find("//") != -1: clean_line = line[0:line.find("//")].strip() else: clean_line = line.strip() if clean_line: clean_code.append(clean_line) if clean_code[0].startswith("OPENQASM"): clean_code.pop(0) elif rigorous: raise TequilaException("File must start with the 'OPENQASM' directive") if clean_code[0].startswith('include "qelib1.inc";'): clean_code.pop(0) elif rigorous: raise TequilaException( "File must import standard library (qelib1.inc)") code_circuit = "\n".join(clean_code) # separate the custom command definitions from the normal commands custom_gates_map: Dict[str, QCircuit] = {} while True: i = code_circuit.find("gate ") if i == -1: break j = code_circuit.find("}", i) custom_name, custom_circuit = parse_custom_gate( code_circuit[i:j + 1], custom_gates_map=custom_gates_map) custom_gates_map[custom_name] = custom_circuit code_circuit = code_circuit[:i] + code_circuit[j + 1:] # parse regular commands commands = [s.strip() for s in code_circuit.split(";") if s.strip()] qregisters: Dict[str, int] = {} circuit = QCircuit() for c in commands: partial_circuit = parse_command(command=c, custom_gates_map=custom_gates_map, qregisters=qregisters) if partial_circuit is not None: circuit += partial_circuit return circuit
def __grad_objective(objective: typing.Union[Objective, VectorObjective], variable: Variable): if isinstance(objective, VectorObjective): return __grad_vector_objective(objective, variable) else: args = objective.args transformation = objective.transformation dO = None processed_expectationvalues = {} for i, arg in enumerate(args): if __AUTOGRAD__BACKEND__ == "jax": df = jax.grad(transformation, argnums=i) elif __AUTOGRAD__BACKEND__ == "autograd": df = jax.grad(transformation, argnum=i) else: raise TequilaException( "Can't differentiate without autograd or jax") # We can detect one simple case where the outer derivative is const=1 if transformation is None or transformation == identity: outer = 1.0 else: outer = Objective(args=args, transformation=df) if hasattr(arg, "U"): # save redundancies if arg in processed_expectationvalues: inner = processed_expectationvalues[arg] else: inner = __grad_inner(arg=arg, variable=variable) processed_expectationvalues[arg] = inner else: # this means this inner derivative is purely variable dependent inner = __grad_inner(arg=arg, variable=variable) if inner == 0.0: # don't pile up zero expectationvalues continue if dO is None: dO = outer * inner else: dO = dO + outer * inner if dO is None: raise TequilaException("caught None in __grad_objective") return dO
def __grad_gaussian(unitary, g, i, variable, hamiltonian): ''' function for getting the gradients of gaussian gates. NOTE: you had better compile first. :param unitary: QCircuit: the QCircuit object containing the gate to be differentiated :param g: a parametrized: the gate being differentiated :param i: Int: the position in unitary at which g appears :param variable: Variable or String: the variable with respect to which gate g is being differentiated :param hamiltonian: the hamiltonian with respect to which unitary is to be measured, in the case that unitary is contained within an ExpectationValue :return: an Objective, whose calculation yields the gradient of g w.r.t variable ''' if not hasattr(g, "shift"): raise TequilaException("No shift found for gate {}".format(g)) # neo_a and neo_b are the shifted versions of gate g needed to evaluate its gradient shift_a = g._parameter + np.pi / (4 * g.shift) shift_b = g._parameter - np.pi / (4 * g.shift) neo_a = copy.deepcopy(g) neo_a._parameter = shift_a neo_b = copy.deepcopy(g) neo_b._parameter = shift_b U1 = unitary.replace_gates(positions=[i], circuits=[neo_a]) w1 = g.shift * __grad_inner(g.parameter, variable) U2 = unitary.replace_gates(positions=[i], circuits=[neo_b]) w2 = -g.shift * __grad_inner(g.parameter, variable) Oplus = ExpectationValueImpl(U=U1, H=hamiltonian) Ominus = ExpectationValueImpl(U=U2, H=hamiltonian) dOinc = w1 * Objective(args=[Oplus]) + w2 * Objective(args=[Ominus]) return dOinc
def __add__(self, other): if isinstance(other, Moment): gates = [g.copy() for g in (self.gates + other.gates)] result = QCircuit(gates=gates) result._min_n_qubits = max(self.as_circuit()._min_n_qubits, other._min_n_qubits) if result.depth == 1: result = Moment(gates=result.gates) result._min_n_qubits = max(self.as_circuit()._min_n_qubits, other._min_n_qubits) elif isinstance(other, QCircuit) and not isinstance(other, Moment): if not other.is_primitive(): gates = [g.copy() for g in (self.gates + other.gates)] result = QCircuit(gates=gates) result._min_n_qubits = max(self.as_circuit()._min_n_qubits, other._min_n_qubits) else: try: result = self.add_gate(other.gates[0]) result._min_n_qubits += len(other.qubits) except: result = self.as_circuit() + QCircuit.wrap_gate(other) result._min_n_qubits = max(self.as_circuit()._min_n_qubits, QCircuit.wrap_gate(other)._min_n_qubits) else: if isinstance(other, QGateImpl): try: result = self.add_gate(other) result._min_n_qubits += len(other.qubits) except: result = self.as_circuit() + QCircuit.wrap_gate(other) result._min_n_qubits = max(self.as_circuit()._min_n_qubits, QCircuit.wrap_gate(other)._min_n_qubits) else: raise TequilaException( 'cannot add moments to types other than QCircuit,Moment,or Gate; recieved summand of type {}'.format( str(type(other)))) return result
def do_make_molecule(self, molecule=None, nuclear_repulsion=None, one_body_integrals=None, two_body_integrals=None, *args, **kwargs) -> MolecularData: if molecule is None: if one_body_integrals is not None and two_body_integrals is not None: if nuclear_repulsion is None: warnings.warn( "PySCF Interface: No constant part (nuclear repulsion)", TequilaWarning) nuclear_repulsion = 0.0 molecule = super().do_make_molecule( nuclear_repulsion=nuclear_repulsion, one_body_integrals=one_body_integrals, two_body_integrals=two_body_integrals, *args, **kwargs) else: raise TequilaException( "not here yet, use openfermionpyscf and feed the integrals to the init" ) return molecule
def n_qubits(self, other): self._min_n_qubits = other if other < self.max_qubit() + 1: raise TequilaException( "You are trying to set n_qubits to " + str(other) + " but your circuit needs at least: " + str( self.max_qubit() + 1)) return self
def qng_grad_gaussian(unitary, g, i, hamiltonian): ''' qng function for getting the gradients of gaussian gates. THIS variant of the function does not seek out underlying gate parameters; it treats each variable 'as is'. This treatment is necessary for the QNG but is incorrect elsewhere. :param unitary: QCircuit: the QCircuit object containing the gate to be differentiated :param g: a parametrized: the gate being differentiated :param i: Int: the position in unitary at which g appears :param variable: Variable or String: the variable with respect to which gate g is being differentiated :param hamiltonian: the hamiltonian with respect to which unitary is to be measured, in the case that unitary is contained within an ExpectationValue :return: a list of objectives; the gradient of the Exp. with respect to each of its (internal) parameters ''' if not hasattr(g, "shift"): raise TequilaException("No shift found for gate {}".format(g)) # neo_a and neo_b are the shifted versions of gate g needed to evaluate its gradient shift_a = g._parameter + numpy.pi / (4 * g.shift) shift_b = g._parameter - numpy.pi / (4 * g.shift) neo_a = copy.deepcopy(g) neo_a._parameter = shift_a neo_b = copy.deepcopy(g) neo_b._parameter = shift_b U1 = unitary.replace_gates(positions=[i], circuits=[neo_a]) w1 = g.shift U2 = unitary.replace_gates(positions=[i], circuits=[neo_b]) w2 = -g.shift Oplus = ExpectationValueImpl(U=U1, H=hamiltonian) Ominus = ExpectationValueImpl(U=U2, H=hamiltonian) dOinc = w1 * Objective(args=[Oplus]) + w2 * Objective(args=[Ominus]) return dOinc
def __grad_expectationvalue(E: ExpectationValueImpl, variable: Variable): ''' implements the analytic partial derivative of a unitary as it would appear in an expectation value. See the paper. :param unitary: the unitary whose gradient should be obtained :param variables (list, dict, str): the variables with respect to which differentiation should be performed. :return: vector (as dict) of dU/dpi as Objective (without hamiltonian) ''' hamiltonian = E.H unitary = E.U if not (unitary.verify()): raise TequilaException("error in grad_expectationvalue unitary is {}".format(unitary)) # fast return if possible if variable not in unitary.extract_variables(): return 0.0 param_gates = unitary._parameter_map[variable] dO = Objective() for idx_g in param_gates: idx, g = idx_g dOinc = __grad_shift_rule(unitary, g, idx, variable, hamiltonian) dO += dOinc assert dO is not None return dO
def __grad_shift_rule(unitary, g, i, variable, hamiltonian): ''' function for getting the gradients of directly differentiable gates. Expects precompiled circuits. :param unitary: QCircuit: the QCircuit object containing the gate to be differentiated :param g: a parametrized: the gate being differentiated :param i: Int: the position in unitary at which g appears :param variable: Variable or String: the variable with respect to which gate g is being differentiated :param hamiltonian: the hamiltonian with respect to which unitary is to be measured, in the case that unitary is contained within an ExpectationValue :return: an Objective, whose calculation yields the gradient of g w.r.t variable ''' # possibility for overwride in custom gate construction if hasattr(g, "shifted_gates"): inner_grad=__grad_inner(g.parameter, variable) shifted = g.shifted_gates() dOinc = Objective() for x in shifted: w,g = x Ux = unitary.replace_gates(positions=[i], circuits=[g]) wx = w*inner_grad Ex = Objective.ExpectationValue(U=Ux, H=hamiltonian) dOinc += wx*Ex return dOinc else: raise TequilaException('No shift found for gate {}\nWas the compiler called?'.format(g))
def compile_to_cc(gate) -> QCircuit: if not gate.is_controlled: return QCircuit.wrap_gate(gate) cl = len(gate.control) target = gate.target control = gate.control if cl <= 2: return QCircuit.wrap_gate(gate) name = gate.name back = QCircuit() if name in ['X', 'x', 'Y', 'y', 'Z', 'z', 'H', 'h']: if isinstance(gate, PowerGateImpl): power = gate.parameter else: power = 1.0 new = PowerGateImpl(name=name, power=power, target=target, control=control) back += compile_power_gate(gate=new, cut=True) elif isinstance(gate, RotationGateImpl): partial = compile_controlled_rotation(gate=gate) back += compile_to_cc(gate=partial) elif isinstance(gate, PhaseGateImpl): partial = compile_controlled_phase(gate=gate) back += compile_to_cc(gate=partial) else: print(gate) raise TequilaException('frankly, what the f**k is this gate?') return back
def assign_axis(axis): if axis in QubitHamiltonian.axis_to_string: return QubitHamiltonian.axis_to_string[axis] elif hasattr(axis, "upper"): return axis.upper() else: raise TequilaException("unknown initialization for pauli operator: {}".format(axis))
def __init__(self, molecule, indices:str): """ Parameters ---------- molecule: a tequila molecule object indices a list of indices defining UCC operations indices refer to spin-orbitals e.g. indices = [[(0,2),(1,3)], [(0,2)], [(1,3)]] can be a string for predefined pools supported are UpCCD, UpCCSD, UpCCGD, and UpCCGSD """ self.molecule = molecule if isinstance(indices, str): if not "CC" in indices.upper(): raise TequilaException("Pool of type {} not yet supported.\nCreate your own by passing the initialized indices".format(indices)) generalized = True if "G" in indices.upper() else False paired = True if "P" in indices.upper() else False singles = True if "S" in indices.upper() else False doubles = True if "D" in indices.upper() else False indices = [] if doubles: indices += self.make_indices_doubles(generalized=generalized, paired=paired) if singles: indices += self.make_indices_singles(generalized=generalized) indices = [tuple(k) for k in indices] super().__init__(generators=indices)
def with_gates(self, gates): """ with gate, but on multiple gates. Parameters ---------- gates: list of QGates Returns ------- Moment: a new Moment, with the desired gates insert into self. """ gl = list(gates) first_overlap = [] for g in gl: for q in g.qubits: if q not in first_overlap: first_overlap.append(q) else: raise TequilaException('cannot have a moment with multiple operations acting on the same qubit!') new = self.with_gate(gl[0]) for g in gl[1:]: new = new.with_gate(g) new.sort_gates() return new
def check_device(self, device): """ Verify if a device is valid. Parameters ---------- device: a cirq.Device or the name of a known cirq device. Returns ------- None """ if device is None: return if isinstance(device, cirq.Device): return else: assert isinstance(device, str) if device.lower() in [ 'foxtail', 'sycamore', 'sycamore23', 'bristlecone' ]: pass else: raise TequilaException( 'requested device {} could not be found!'.format(device))
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 get_generator(gate) -> paulis.QubitHamiltonian: """ get the generator of a gaussian gate as a Qubit hamiltonian. Relies on the name of the gate. Parameters ---------- gate: QGateImpl: QGateImpl object or inheritor thereof, with name corresponding to its generator in some fashion. Returns ------- QubitHamiltonian: the generator of the gate acting, on the gate's target. """ if gate.name.lower() == 'rx': gen = paulis.X(gate.target[0]) elif gate.name.lower() == 'ry': gen = paulis.Y(gate.target[0]) elif gate.name.lower() == 'rz': gen = paulis.Z(gate.target[0]) elif gate.name.lower() == 'phase': gen = paulis.Qm(gate.target[0]) else: print(gate.name.lower()) raise TequilaException( 'cant get the generator of a non Gaussian gate, you fool!') return gen
def __call__(self, wfn): if hasattr(wfn, "apply_qubitoperator"): return wfn.apply_qubitoperator(self) else: raise TequilaException( "Not sure what to do here with {} and {} ...".format( self, wfn))
def __init__(self, abstract_circuit: QCircuit, variables, qubit_map=None, noise=None, device=None, *args, **kwargs): """ Parameters ---------- abstract_circuit: QCircuit: Tequila unitary to compile to cirq variables: dict: values of all variables in the circuit, to compile with. qubit_map: dictionary: a qubit map which maps the abstract qubits in the abstract_circuit to the qubits on the backend there is no need to initialize the corresponding backend types the dictionary should simply be {int:int} (preferred) or {int:name} if None the default will map to qubits 0 ... n_qubits -1 in the backend noise: Noise to apply to the circuit. device: device on which to emulatedly execute all sampling. args kwargs """ self.op_lookup = { 'I': (cirq.ops.IdentityGate, None), 'X': (cirq.ops.common_gates.XPowGate, map_1), 'Y': (cirq.ops.common_gates.YPowGate, map_1), 'Z': (cirq.ops.common_gates.ZPowGate, map_1), 'H': (cirq.ops.common_gates.HPowGate, map_1), 'Rx': (cirq.ops.common_gates.XPowGate, map_2), 'Ry': (cirq.ops.common_gates.YPowGate, map_2), 'Rz': (cirq.ops.common_gates.ZPowGate, map_2), 'SWAP': (cirq.ops.SwapPowGate, None), } self.tq_to_sympy = {} self.counter = 0 if device is not None: self.compiler_arguments['cc_max'] = True super().__init__(abstract_circuit=abstract_circuit, variables=variables, noise=noise, qubit_map=qubit_map, device=device, *args, **kwargs) if len(self.tq_to_sympy.keys()) is None: self.sympy_to_tq = None self.resolver = None else: self.sympy_to_tq = {v: k for k, v in self.tq_to_sympy.items()} self.resolver = cirq.ParamResolver({k: v(variables) for k, v in self.sympy_to_tq.items()}) if self.device is not None: self.circuit = self.build_device_circuit() if self.noise is not None: if self.noise == 'device': raise TequilaException('cannot get device noise for cirq yet, sorry!') self.noise_lookup = { 'bit flip': [lambda x: cirq.bit_flip(x)], 'phase flip': [lambda x: cirq.phase_flip(x)], 'phase damp': [cirq.phase_damp], 'amplitude damp': [cirq.amplitude_damp], 'phase-amplitude damp': [cirq.amplitude_damp, cirq.phase_damp], 'depolarizing': [lambda x: cirq.depolarize(p=(3 / 4) * x)] } self.circuit = self.build_noisy_circuit(self.noise)
def parse_custom_gate( gate_custom: str, custom_gates_map: Dict[str, QCircuit]) -> (str, QCircuit): """ Parse custom gates code Args: gate_custom: code with custom gates """ gate_custom = gate_custom[5:] spec, body = gate_custom.split("{", 1) if "(" in spec: i = spec.find("(") j = spec.find(")") if spec[i + 1:j].strip(): raise TequilaException( "Parameters for custom gates not supported: {}".format(spec)) spec = spec[:i] + spec[j + 1:] spec = spec.strip() if " " in spec: name, qargs = spec.split(" ", 1) name = name.strip() qargs = qargs.strip() else: raise TequilaException( "Custom gate specification doesn't have any arguments: {}".format( spec)) custom_qregisters: Dict[str, int] = {} for qarg in qargs.split(','): custom_qregisters[qarg] = len(custom_qregisters) body = body[:-1].strip() commands = [s.strip() for s in body.split(";") if s.strip()] custom_circuit = QCircuit() for c in commands: partial_circuit = parse_command(command=c, custom_gates_map=custom_gates_map, qregisters=custom_qregisters) if partial_circuit is not None: custom_circuit += partial_circuit return name, custom_circuit
def binary_operator(cls, left, right, op): """ Core arithmetical method for creating differentiable callables of two Tequila Objectives and or Variables. this function, usually called by the convenience magic-methods of Observable objects, constructs a new Objective whose Transformation is the JoinedTransformation of the lower arguments and transformations of the left and right objects, alongside op (if they are or can be rendered as objectives). In case one of left or right is a number, calls unary_operator instead. Parameters ---------- left: the left hand argument to op right: the right hand argument to op. op: callable: an operation; a function object. Returns ------- Objective: an objective whose Transformation is op acting on left and right. """ if isinstance(right, numbers.Number): if isinstance(left, Objective): return cls.unary_operator(left=left, op=lambda E: op(E, right)) else: raise TequilaException( 'BinaryOperator method called on types ' + str(type(left)) + ',' + str(type(right))) elif isinstance(left, numbers.Number): if isinstance(right, Objective): return cls.unary_operator(left=right, op=lambda E: op(left, E)) else: raise TequilaException( 'BinaryOperator method called on types ' + str(type(left)) + ',' + str(type(right))) else: split_at = len(left.args) return Objective(args=left.args + right.args, transformation=JoinedTransformation( left=left.transformation, right=right.transformation, split=split_at, op=op))
def from_moments(moments: typing.List): """ Raises ------ TequilaException """ raise TequilaException( 'this method should never be called from Moment. Call from the QCircuit class itself instead.')
def compile_power_base(gate): """ Base case of compile_power_gate: convert a 1-qubit parametrized power gate into rotation gates. Parameters ---------- gate: the gate. Returns ------- A QCircuit; the result of compilation. """ if not isinstance(gate, PowerGateImpl): return QCircuit.wrap_gate(gate) if gate.is_controlled(): return QCircuit.wrap_gate(gate) power = gate.power if gate.name.lower() in ['h', 'hadamard']: ### off by global phase of Exp[ pi power /2] theta = power * numpy.pi result = QCircuit() result += Ry(angle=-numpy.pi / 4, target=gate.target) result += Rz(angle=theta, target=gate.target) result += Ry(angle=numpy.pi / 4, target=gate.target) elif gate.name == 'X': ### off by global phase of Exp[ pi power /2] ''' if we wanted to do it formally we would use the following a=-numpy.pi/2 b=numpy.pi/2 theta = power*numpy.pi result = QCircuit() result+= Rz(angle=b,target=gate.target) result+= Ry(angle=theta,target=gate.target) result+= Rz(angle=a,target=gate.target) ''' result = Rx(angle=power * numpy.pi, target=gate.target) elif gate.name == 'Y': ### off by global phase of Exp[ pi power /2] theta = power * numpy.pi result = QCircuit() result += Ry(angle=theta, target=gate.target) elif gate.name == 'Z': ### off by global phase of Exp[ pi power /2] a = 0 b = power * numpy.pi theta = 0 result = QCircuit() result += Rz(angle=b, target=gate.target) else: raise TequilaException('passed a gate with name ' + gate.name + ', which cannot be handled!') return result