def S(qubit: QubitDesignator) -> Gate: """Produces the S gate:: S = [[1, 0], [0, 1j]] This gate is a single qubit S-gate. :param qubit: The qubit apply the gate to. :returns: A Gate object. """ return Gate(name="S", params=[], qubits=[unpack_qubit(qubit)])
def H(qubit: QubitDesignator) -> Gate: """Produces the Hadamard gate:: H = (1 / sqrt(2)) * [[1, 1], [1, -1]] Produces the H instruction. This gate is a single qubit Hadamard gate. :param qubit: The qubit apply the gate to. :returns: A Gate object. """ return Gate(name="H", params=[], qubits=[unpack_qubit(qubit)])
def Y(qubit: QubitDesignator) -> Gate: """Produces the Y gate:: Y = [[0, 0 - 1j], [0 + 1j, 0]] This gate is a single qubit Y-gate. :param qubit: The qubit apply the gate to. :returns: A Gate object. """ return Gate(name="Y", params=[], qubits=[unpack_qubit(qubit)])
def X(qubit: QubitDesignator) -> Gate: """Produces the X ("NOT") gate:: X = [[0, 1], [1, 0]] This gate is a single qubit X-gate. :param qubit: The qubit apply the gate to. :returns: A Gate object. """ return Gate(name="X", params=[], qubits=[unpack_qubit(qubit)])
def exitGate(self, ctx: QuilParser.GateContext): gate_name = ctx.name().getText() params = list(map(_param, ctx.param())) qubits = list(map(_qubit, ctx.qubit())) # The Gate.controlled() method *prepends* the CONTROLLED modifier to the gate. But the # parser works from the outside-in. Therefore the controlled qubits would be in reverse # order. We reverse here to fix that. modifiers = [mod.getText() for mod in ctx.modifier()][::-1] control_qubits = qubits[0:len(list(filter(lambda str: str == "CONTROLLED", modifiers)))][::-1] target_qubits = qubits[len(control_qubits):] if gate_name in QUANTUM_GATES: if params: gate = QUANTUM_GATES[gate_name](*params, *target_qubits) else: gate = QUANTUM_GATES[gate_name](*target_qubits) else: gate = Gate(gate_name, params, target_qubits) for modifier in modifiers: if modifier == "CONTROLLED": gate.controlled(control_qubits.pop(0)) elif modifier == "DAGGER": gate.dagger() else: raise ValueError(f"Unsupported gate modifier {modifier}.") self.result.append(gate)
def RX(angle, qubit): """Produces the RX gate:: RX(phi) = [[cos(phi / 2), -1j * sin(phi / 2)], [-1j * sin(phi / 2), cos(phi / 2)]] This gate is a single qubit X-rotation. :param angle: The angle to rotate around the x-axis on the bloch sphere. :param qubit: The qubit apply the gate to. :returns: A Gate object. """ return Gate(name="RX", params=[angle], qubits=[unpack_qubit(qubit)])
def quil_gates(native_gates=None): """Generates quil versions of a gate set (the QSCOUT native gates, by default). :returns: A mapping of gate names to functions that take classical parameters and qubit indices and build pyquil gates. :rtype: dict .. warning:: PyQuil simulators will give an error if QSCOUT native gates are passed to them! """ dkt = {} if native_gates is None: from qscout.v1.std import NATIVE_GATES native_gates = NATIVE_GATES gates = {} for gate in native_gates: if gate.ideal_unitary is None: continue # pyquil expects non-parametrized gates to be matrices and # parametrized ones to be functions that return matrices. quil_name = gate.name.upper() classical_count = len(gate.classical_parameters) if classical_count == 0: gates[quil_name] = ( lambda quil_name, classical_count: (lambda *args: Gate( name=quil_name, params=args[:classical_count], qubits=[unpack_qubit(q) for q in args[classical_count:]], )))(quil_name, classical_count) else: gates[quil_name] = (lambda quil_name: (lambda *args: Gate( name=quil_name, params=[], qubits=[unpack_qubit(q) for q in args], )))(quil_name) return gates
def exitGate(self, ctx: QuilParser.GateContext): gate_name = ctx.name().getText() modifiers = [mod.getText() for mod in ctx.modifier()] params = list(map(_param, ctx.param())) qubits = list(map(_qubit, ctx.qubit())) # The parsed string 'DAGGER CONTROLLED X 0 1' gives # modifiers ['DAGGER', 'CONTROLLED'] # qubits ['0', '1'] # # We will build such gates by applying modifiers from right to left, # e.g. X 1 -> CONTROLLED X 0 1 -> DAGGER CONTROLLED X 0 1 # Some gate modifiers increase the arity of the base gate. # The new qubit arguments prefix the old ones. modifier_qubits = [] for m in modifiers: if m in ["CONTROLLED", "FORKED"]: modifier_qubits.append(qubits[len(modifier_qubits)]) base_qubits = qubits[len(modifier_qubits):] # Each FORKED doubles the number of parameters, # e.g. FORKED RX(0.5, 1.5) 0 1 has two. forked_offset = len(params) >> modifiers.count("FORKED") base_params = params[:forked_offset] if gate_name in QUANTUM_GATES: if base_params: gate = QUANTUM_GATES[gate_name](*base_params, *base_qubits) else: gate = QUANTUM_GATES[gate_name](*base_qubits) else: gate = Gate(gate_name, base_params, base_qubits) # Track the last param used (for FORKED) for modifier in modifiers[::-1]: if modifier == "CONTROLLED": gate.controlled(modifier_qubits.pop()) elif modifier == "DAGGER": gate.dagger() elif modifier == 'FORKED': gate.forked(modifier_qubits.pop(), params[forked_offset:(2 * forked_offset)]) forked_offset *= 2 else: raise ValueError(f"Unsupported gate modifier {modifier}.") self.result.append(gate)
def RY(angle, qubit): """Produces the RY instruction. RY(phi) = [[cos(phi / 2), -sin(phi / 2)], [sin(phi / 2), cos(phi / 2)]] This gate is a single qubit Y-rotation. :param angle: The angle to rotate around the y-axis on the bloch sphere. :param qubit: The qubit apply the gate to. :returns: A Gate object. """ return Gate(name="RY", params=[angle], qubits=[unpack_qubit(qubit)])
def RZ(angle, qubit): """Produces the RZ instruction. RZ(phi) = [[cos(phi / 2) - 1j * sin(phi / 2), 0] [0, cos(phi / 2) + 1j * sin(phi / 2)]] This gate is a single qubit Z-rotation. :param angle: The angle to rotate around the z-axis on the bloch sphere. :param qubit: The qubit apply the gate to. :returns: A Gate object. """ return Gate(name="RZ", params=[angle], qubits=[unpack_qubit(qubit)])
def PHASE(angle, qubit): """Produces the PHASE instruction. PHASE(phi) = [[1, 0], [0, exp(1j * phi)]] This is the same as the RZ gate. :param angle: The angle to rotate around the z-axis on the bloch sphere. :param qubit: The qubit apply the gate to. :returns: A Gate object. """ return Gate(name="PHASE", params=[angle], qubits=[unpack_qubit(qubit)])
def exitGate(self, ctx): # type: (QuilParser.GateContext) -> None gate_name = ctx.name().getText() params = list(map(_param, ctx.param())) qubits = list(map(_qubit, ctx.qubit())) if gate_name in STANDARD_GATES: if params: self.result.append(STANDARD_GATES[gate_name](*params)(*qubits)) else: self.result.append(STANDARD_GATES[gate_name](*qubits)) else: self.result.append(Gate(gate_name, params, qubits))
def gates_in_isa(isa): """ Generate the full gateset associated with an ISA. :param ISA isa: The instruction set architecture for a QPU. :return: A sequence of Gate objects encapsulating all gates compatible with the ISA. :rtype: Sequence[Gate] """ gates = [] for q in isa.qubits: if q.dead: # TODO: dead qubits may in the future lead to some implicit re-indexing continue if q.type in ["Xhalves"]: gates.extend([ Gate("I", [], [unpack_qubit(q.id)]), Gate("RX", [np.pi / 2], [unpack_qubit(q.id)]), Gate("RX", [-np.pi / 2], [unpack_qubit(q.id)]), Gate("RX", [np.pi], [unpack_qubit(q.id)]), Gate("RX", [-np.pi], [unpack_qubit(q.id)]), Gate("RZ", [THETA], [unpack_qubit(q.id)]), ]) else: # pragma no coverage raise ValueError("Unknown qubit type: {}".format(q.type)) for e in isa.edges: if e.dead: continue targets = [unpack_qubit(t) for t in e.targets] if e.type in ["CZ", "ISWAP"]: gates.append(Gate(e.type, [], targets)) gates.append(Gate(e.type, [], targets[::-1])) elif e.type in ["CPHASE"]: gates.append(Gate(e.type, [THETA], targets)) gates.append(Gate(e.type, [THETA], targets[::-1])) else: # pragma no coverage raise ValueError("Unknown edge type: {}".format(e.type)) return gates
def _noisy_instruction(instruction): """ Translate an ordinary gate instruction into its noisy version, where applicable. In an attempt to closely model the QPU, noisy versions of RX(+-pi/2) and CZ are supported; I and parametric RZ are returned as-is (noiseless), and other gates are not allowed. Pragmas are returned as-is. Note: this function doesn't actually define the noisy gates. Please see :py:func`add_noise_to_program` for a full solution. :param instruction: The instruction :return: A noisy version of the instruction """ if not isinstance(instruction, Gate): return instruction if instruction.name == 'RZ': return instruction if instruction.name == 'I': return instruction if instruction.name == 'RX': assert len(instruction.params) == 1 assert len(instruction.qubits) == 1 if instruction.params[0] == np.pi / 2.0: return Gate('noisy-x-plus90', [], instruction.qubits) if instruction.params[0] == -np.pi / 2.0: return Gate('noisy-x-minus90', [], instruction.qubits) raise ValueError("Can't add noise to a parametric gate. " "Try compiling to RX(pi/2) or RX(-pi/2)") if instruction.name == 'CZ': return Gate('noisy-cz', [], instruction.qubits) raise ValueError( 'Gate {} is not in the native instruction set'.format(instruction))
def SWAP(q1: QubitDesignator, q2: QubitDesignator) -> Gate: """Produces a SWAP gate which swaps the state of two qubits:: SWAP = [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]] :param q1: Qubit 1. :param q2: Qubit 2. :returns: A Gate object. """ return Gate(name="SWAP", params=[], qubits=[unpack_qubit(q) for q in (q1, q2)])
def XY(angle: ParameterDesignator, q1: QubitDesignator, q2: QubitDesignator) -> Gate: """Produces a parameterized ISWAP gate:: XY(phi) = [[1, 0, 0, 0], [0, cos(phi/2), 1j * sin(phi/2), 0], [0, 1j * sin(phi/2), cos(phi/2), 0], [0, 0, 0, 1] :param angle: The angle of the rotation to apply to the population 1 subspace. :param q1: Qubit 1. :param q2: Qubit 2. :returns: A Gate object. """ return Gate(name="XY", params=[angle], qubits=[unpack_qubit(q) for q in (q1, q2)])
def address_qubits(program, qubit_mapping=None): """ Takes a program which contains placeholders and assigns them all defined values. Either all qubits must be defined or all undefined. If qubits are undefined, you may provide a qubit mapping to specify how placeholders get mapped to actual qubits. If a mapping is not provided, integers 0 through N are used. This function will also instantiate any label placeholders. :param program: The program. :param qubit_mapping: A dictionary-like object that maps from :py:class:`QubitPlaceholder` to :py:class:`Qubit` or ``int`` (but not both). :return: A new Program with all qubit and label placeholders assigned to real qubits and labels. """ fake_qubits, real_qubits, qubits = _what_type_of_qubit_does_it_use(program) if real_qubits: if qubit_mapping is not None: warnings.warn( "A qubit mapping was provided but the program does not " "contain any placeholders to map!") return program if qubit_mapping is None: qubit_mapping = {qp: Qubit(i) for i, qp in enumerate(qubits)} else: if all(isinstance(v, Qubit) for v in qubit_mapping.values()): pass # we good elif all(isinstance(v, int) for v in qubit_mapping.values()): qubit_mapping = {k: Qubit(v) for k, v in qubit_mapping.items()} else: raise ValueError( "Qubit mapping must map to type Qubit or int (but not both)") result = [] for instr in program: # Remap qubits on Gate and Measurement instructions if isinstance(instr, Gate): remapped_qubits = [qubit_mapping[q] for q in instr.qubits] result.append(Gate(instr.name, instr.params, remapped_qubits)) elif isinstance(instr, Measurement): result.append( Measurement(qubit_mapping[instr.qubit], instr.classical_reg)) # Otherwise simply add it to the result else: result.append(instr) return Program(result)
def CPHASE00(angle, control, target): """Produces a CPHASE00 instruction. CPHASE00(phi) = diag([exp(1j * phi), 1, 1, 1]) This gate applies to two qubit arguments to produce the variant of the controlled phase instruction that affects the state 00. :param angle: The input phase angle to apply when both qubits are in the ground state. :param control: Qubit 1. :param target: Qubit 2. :returns: A Gate object. """ qubits = [unpack_qubit(q) for q in (control, target)] return Gate(name="CPHASE00", params=[angle], qubits=qubits)
def CPHASE(angle, control, target): """Produces a CPHASE instruction, which is a synonym for CPHASE11. CPHASE(phi) = diag([1, 1, 1, exp(1j * phi)]) This gate applies to two qubit arguments to produce the variant of the controlled phase instruction that affects the state 11. :param angle: The input phase angle to apply when both qubits are in the excited state. :param control: Qubit 1. :param target: Qubit 2. :returns: A Gate object. """ qubits = [unpack_qubit(q) for q in (control, target)] return Gate(name="CPHASE", params=[angle], qubits=qubits)
def I(qubit): """Produces the I instruction. I = [1, 0] [0, 1] This gate is a single qubit identity gate. Note that this gate is different that the NOP instruction as noise channels are typically still applied during the duration of identity gates. Identities will also block parallelization like any other gate. :param qubit: The qubit apply the gate to. :returns: A Gate object. """ return Gate(name="I", params=[], qubits=[unpack_qubit(qubit)])
def SWAP(q1, q2): """Produces a SWAP instruction. SWAP = [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]] This gate swaps the state of two qubits. :param q1: Qubit 1. :param q2: Qubit 2. :returns: A Gate object. """ return Gate(name="SWAP", params=[], qubits=[unpack_qubit(q) for q in (q1, q2)])
def tk_to_pyquil(tkcirc: Union[Circuit, PhysicalCircuit], active_reset: bool = False) -> Program: """ Convert a :math:`\\mathrm{t|ket}\\rangle` :py:class:`Circuit` to a :py:class:`pyquil.Program` . :param tkcirc: A circuit to be converted :return: The converted circuit """ circ = tkcirc if isinstance(tkcirc, PhysicalCircuit): circ = tkcirc._get_circuit() p = Program() if len(circ.q_regs) != 1: raise NotImplementedError( "Cannot convert circuit with multiple quantum registers to PyQuil") cregmap = {} for _, reg in circ.c_regs.items(): if reg.size() == 0: continue name = reg.name if name == 'c': name = 'ro' quil_reg = p.declare(name, 'BIT', reg.size()) cregmap.update({reg: quil_reg}) if active_reset: p.reset() for command in circ: op = command.op qubits = [Qubit(qb.index) for qb in command.qubits] optype = op.get_type() if optype == OpType.Measure: bits = [cregmap[b.reg][b.index] for b in command.bits] p += Measurement(*qubits, *bits) continue try: gatetype = _known_quil_gate_rev[optype] except KeyError as error: raise NotImplementedError( "Cannot convert tket Op to pyquil gate: " + op.get_name()) from error if len(command.controls) != 0: raise NotImplementedError( "Cannot convert conditional gates from tket to PyQuil") params = [float((p * pi).evalf()) for p in op.get_params()] g = Gate(gatetype, params, qubits) p += g return p
def ISWAP(q1: QubitDesignator, q2: QubitDesignator) -> Gate: """Produces an ISWAP gate:: ISWAP = [[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]] This gate swaps the state of two qubits, applying a -i phase to q1 when it is in the 1 state and a -i phase to q2 when it is in the 0 state. :param q1: Qubit 1. :param q2: Qubit 2. :returns: A Gate object. """ return Gate(name="ISWAP", params=[], qubits=[unpack_qubit(q) for q in (q1, q2)])
def CNOT(control: QubitDesignator, target: QubitDesignator) -> Gate: """Produces a controlled-NOT (controlled-X) gate:: CNOT = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] This gate applies to two qubit arguments to produce the controlled-not gate instruction. :param control: The control qubit. :param target: The target qubit. The target qubit has an X-gate applied to it if the control qubit is in the ``|1>`` state. :returns: A Gate object. """ return Gate(name="CNOT", params=[], qubits=[unpack_qubit(q) for q in (control, target)])
def CNOT(control, target): """Produces a CNOT instruction. CNOT = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]] This gate applies to two qubit arguments to produce the controlled-not gate instruction. :param control: The control qubit. :param target: The target qubit. The target qubit has an X-gate applied to it if the control qubit is in the excited state. :returns: A Gate object. """ return Gate(name="CNOT", params=[], qubits=[unpack_qubit(q) for q in (control, target)])
def CPHASE10(angle, control, target): """Produces a controlled-phase gate that phases the ``|10>`` state:: CPHASE10(phi) = diag([1, 1, exp(1j * phi), 1]) This gate applies to two qubit arguments to produce the variant of the controlled phase instruction that affects the state 10. :param angle: The input phase angle to apply when q2 is in the ``|1>`` state and q1 is in the ``|0>`` state. :param control: Qubit 1. :param target: Qubit 2. :returns: A Gate object. """ qubits = [unpack_qubit(q) for q in (control, target)] return Gate(name="CPHASE10", params=[angle], qubits=qubits)
def PSWAP(angle: ParameterDesignator, q1: QubitDesignator, q2: QubitDesignator) -> Gate: """Produces a parameterized SWAP gate:: PSWAP(phi) = [[1, 0, 0, 0], [0, 0, exp(1j * phi), 0], [0, exp(1j * phi), 0, 0], [0, 0, 0, 1]] :param angle: The angle of the phase to apply to the swapped states. This phase is applied to q1 when it is in the 1 state and to q2 when it is in the 0 state. :param q1: Qubit 1. :param q2: Qubit 2. :returns: A Gate object. """ return Gate(name="PSWAP", params=[angle], qubits=[unpack_qubit(q) for q in (q1, q2)])
def ISWAP(q1, q2): """Produces an ISWAP instruction. ISWAP = [[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]] This gate swaps the state of two qubits, applying a -i phase to q1 when it is in the excited state and a -i phase to q2 when it is in the ground state. :param q1: Qubit 1. :param q2: Qubit 2. :returns: A Gate object. """ return Gate(name="ISWAP", params=[], qubits=[unpack_qubit(q) for q in (q1, q2)])
def CZ(control: QubitDesignator, target: QubitDesignator) -> Gate: """Produces a controlled-Z gate:: CZ = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]] This gate applies to two qubit arguments to produce the controlled-Z gate instruction. :param control: The control qubit. :param target: The target qubit. The target qubit has an Z-gate applied to it if the control qubit is in the excited state. :returns: A Gate object. """ return Gate(name="CZ", params=[], qubits=[unpack_qubit(q) for q in (control, target)])
def PSWAP(angle, q1, q2): """Produces a PSWAP instruction. PSWAP(phi) = [[1, 0, 0, 0], [0, 0, exp(1j * phi), 0], [0, exp(1j * phi), 0, 0], [0, 0, 0, 1]] This is a parameterized swap gate. :param angle: The angle of the phase to apply to the swapped states. This phase is applied to q1 when it is in the excited state and to q2 when it is in the ground state. :param q1: Qubit 1. :param q2: Qubit 2. :returns: A Gate object. """ return Gate(name="PSWAP", params=[angle], qubits=[unpack_qubit(q) for q in (q1, q2)])