Пример #1
0
    def _build(self) -> None:
        """Construct the circuit representing the desired state vector."""
        super()._build()

        num_qubits = self.num_qubits

        if num_qubits == 0:
            return

        circuit = QuantumCircuit(*self.qregs, name=self.name)
        for j in reversed(range(num_qubits)):
            circuit.h(j)
            num_entanglements = max(
                0,
                j - max(0, self.approximation_degree - (num_qubits - j - 1)))
            for k in reversed(range(j - num_entanglements, j)):
                lam = np.pi / (2**(j - k))
                circuit.cp(lam, j, k)

            if self.insert_barriers:
                circuit.barrier()

        if self._do_swaps:
            for i in range(num_qubits // 2):
                circuit.swap(i, num_qubits - i - 1)

        if self._inverse:
            circuit._data = circuit.inverse()

        wrapped = circuit.to_instruction(
        ) if self.insert_barriers else circuit.to_gate()
        self.compose(wrapped, qubits=self.qubits, inplace=True)
Пример #2
0
    def _build(self) -> None:
        """Construct the circuit representing the desired state vector."""
        super()._build()

        num_qubits = self.num_qubits

        if num_qubits == 0:
            return

        circuit = QuantumCircuit(*self.qregs, name=self.name)
        for j in reversed(range(num_qubits)):
            circuit.h(j)
            num_entanglements = max(
                0,
                j - max(0, self.approximation_degree - (num_qubits - j - 1)))
            for k in reversed(range(j - num_entanglements, j)):
                # Use negative exponents so that the angle safely underflows to zero, rather than
                # using a temporary variable that overflows to infinity in the worst case.
                lam = np.pi * (2.0**(k - j))
                circuit.cp(lam, j, k)

            if self.insert_barriers:
                circuit.barrier()

        if self._do_swaps:
            for i in range(num_qubits // 2):
                circuit.swap(i, num_qubits - i - 1)

        if self._inverse:
            circuit._data = circuit.inverse()

        wrapped = circuit.to_instruction(
        ) if self.insert_barriers else circuit.to_gate()
        self.compose(wrapped, qubits=self.qubits, inplace=True)
Пример #3
0
    def _gate_rules_to_qiskit_circuit(self, node, params):
        """From a gate definition in qasm, to a QuantumCircuit format."""
        rules = []
        qreg = QuantumRegister(node['n_bits'])
        bit_args = {node['bits'][i]: q for i, q in enumerate(qreg)}
        exp_args = {node['args'][i]: Real(q) for i, q in enumerate(params)}

        for child_op in node['body'].children:
            qparams = []
            eparams = []
            for param_list in child_op.children[1:]:
                if param_list.type == 'id_list':
                    qparams = [bit_args[param.name] for param in param_list.children]
                elif param_list.type == 'expression_list':
                    for param in param_list.children:
                        eparams.append(param.sym(nested_scope=[exp_args]))
            op = self._create_op(child_op.name, params=eparams)
            rules.append((op, qparams, []))
        circ = QuantumCircuit(qreg)
        circ._data = rules
        return circ
Пример #4
0
    def apply_grad_gate(circuit, gate, param_index, grad_gate, grad_coeff, qr_superpos,
                        open_ctrl=False, trim_after_grad_gate=False):
        """Util function to apply a gradient gate for the linear combination of unitaries method.

        Replaces the ``gate`` instance in ``circuit`` with ``grad_gate`` using ``qr_superpos`` as
        superposition qubit. Also adds the appropriate sign-fix gates on the superposition qubit.

        Args:
            circuit (QuantumCircuit): The circuit in which to do the replacements.
            gate (Gate): The gate instance to replace.
            param_index (int): The index of the parameter in ``gate``.
            grad_gate (Gate): A controlled gate encoding the gradient of ``gate``.
            grad_coeff (float): A coefficient to the gradient component. Might not be one if the
                gradient contains multiple summed terms.
            qr_superpos (QuantumRegister): A ``QuantumRegister`` of size 1 contained in ``circuit``
                that is used as control for ``grad_gate``.
            open_ctrl (bool): If True use an open control for ``grad_gate`` instead of closed.
            trim_after_grad_gate (bool): If True remove all gates after the ``grad_gate``. Can
                be used to reduce the circuit depth in e.g. computing an overlap of gradients.

        Returns:
            QuantumCircuit: A copy of the original circuit with the gradient gate added.

        Raises:
            RuntimeError: If ``gate`` is not in ``circuit``.
        """
        # copy the input circuit taking the gates by reference
        out = QuantumCircuit(*circuit.qregs)
        out._data = circuit._data.copy()
        out._parameter_table = ParameterTable({
            param: values.copy() for param, values in circuit._parameter_table.items()
        })

        # get the data index and qubits of the target gate  TODO use built-in
        gate_idx, gate_qubits = None, None
        for i, (op, qarg, _) in enumerate(out._data):
            if op is gate:
                gate_idx, gate_qubits = i, qarg
                break
        if gate_idx is None:
            raise RuntimeError('The specified gate could not be found in the circuit data.')

        # initialize replacement instructions
        replacement = []

        # insert the phase fix before the target gate better documentation
        sign = np.sign(grad_coeff)
        is_complex = np.iscomplex(grad_coeff)

        if sign < 0 and is_complex:
            replacement.append((SdgGate(), qr_superpos[:], []))
        elif sign < 0:
            replacement.append((ZGate(), qr_superpos[:], []))
        elif is_complex:
            replacement.append((SGate(), qr_superpos[:], []))
        # else no additional gate required

        # open control if specified
        if open_ctrl:
            replacement += [(XGate(), qr_superpos[:], [])]

        # compute the replacement
        if isinstance(gate, UGate) and param_index == 0:
            theta = gate.params[2]
            rz_plus, rz_minus = RZGate(theta), RZGate(-theta)
            replacement += [(rz_plus, [qubit], []) for qubit in gate_qubits]
            replacement += [(RXGate(np.pi / 2), [qubit], []) for qubit in gate_qubits]
            replacement.append((grad_gate, qr_superpos[:] + gate_qubits, []))
            replacement += [(RXGate(-np.pi / 2), [qubit], []) for qubit in gate_qubits]
            replacement += [(rz_minus, [qubit], []) for qubit in gate_qubits]

            # update parametertable if necessary
            if isinstance(theta, ParameterExpression):
                out._update_parameter_table(rz_plus)
                out._update_parameter_table(rz_minus)

            if open_ctrl:
                replacement += [(XGate(), qr_superpos[:], [])]

            if not trim_after_grad_gate:
                replacement.append((gate, gate_qubits, []))

        elif isinstance(gate, UGate) and param_index == 1:
            # gradient gate is applied after the original gate in this case
            replacement.append((gate, gate_qubits, []))
            replacement.append((grad_gate, qr_superpos[:] + gate_qubits, []))
            if open_ctrl:
                replacement += [(XGate(), qr_superpos[:], [])]

        else:
            replacement.append((grad_gate, qr_superpos[:] + gate_qubits, []))
            if open_ctrl:
                replacement += [(XGate(), qr_superpos[:], [])]
            if not trim_after_grad_gate:
                replacement.append((gate, gate_qubits, []))

        # replace the parameter we compute the derivative of with the replacement
        # TODO can this be done more efficiently?
        if trim_after_grad_gate:  # remove everything after the gradient gate
            out._data[gate_idx:] = replacement
            # reset parameter table
            table = ParameterTable()
            for op, _, _ in out._data:
                for idx, param_expression in enumerate(op.params):
                    if isinstance(param_expression, ParameterExpression):
                        for param in param_expression.parameters:
                            if param not in table.keys():
                                table[param] = [(op, idx)]
                            else:
                                table[param].append((op, idx))

            out._parameter_table = table

        else:
            out._data[gate_idx:gate_idx + 1] = replacement

        return out
Пример #5
0
    def apply_grad_gate(
        circuit,
        gate,
        param_index,
        grad_gate,
        grad_coeff,
        qr_superpos,
        open_ctrl=False,
        trim_after_grad_gate=False,
    ):
        """Util function to apply a gradient gate for the linear combination of unitaries method.
        Replaces the ``gate`` instance in ``circuit`` with ``grad_gate`` using ``qr_superpos`` as
        superposition qubit. Also adds the appropriate sign-fix gates on the superposition qubit.

        Args:
            circuit (QuantumCircuit): The circuit in which to do the replacements.
            gate (Gate): The gate instance to replace.
            param_index (int): The index of the parameter in ``gate``.
            grad_gate (Gate): A controlled gate encoding the gradient of ``gate``.
            grad_coeff (float): A coefficient to the gradient component. Might not be one if the
                gradient contains multiple summed terms.
            qr_superpos (QuantumRegister): A ``QuantumRegister`` of size 1 contained in ``circuit``
                that is used as control for ``grad_gate``.
            open_ctrl (bool): If True use an open control for ``grad_gate`` instead of closed.
            trim_after_grad_gate (bool): If True remove all gates after the ``grad_gate``. Can
                be used to reduce the circuit depth in e.g. computing an overlap of gradients.

        Returns:
            QuantumCircuit: A copy of the original circuit with the gradient gate added.

        Raises:
            RuntimeError: If ``gate`` is not in ``circuit``.
        """
        qr_superpos_qubits = tuple(qr_superpos)
        # copy the input circuit taking the gates by reference
        out = QuantumCircuit(*circuit.qregs)
        out._data = circuit._data.copy()
        out._parameter_table = ParameterTable({
            param: values.copy()
            for param, values in circuit._parameter_table.items()
        })

        # get the data index and qubits of the target gate  TODO use built-in
        gate_idx, gate_qubits = None, None
        for i, instruction in enumerate(out._data):
            if instruction.operation is gate:
                gate_idx, gate_qubits = i, instruction.qubits
                break
        if gate_idx is None:
            raise RuntimeError(
                "The specified gate could not be found in the circuit data.")

        # initialize replacement instructions
        replacement = []

        # insert the phase fix before the target gate better documentation
        sign = np.sign(grad_coeff)
        is_complex = np.iscomplex(grad_coeff)

        if sign < 0 and is_complex:
            replacement.append(
                CircuitInstruction(SdgGate(), qr_superpos_qubits, ()))
        elif sign < 0:
            replacement.append(
                CircuitInstruction(ZGate(), qr_superpos_qubits, ()))
        elif is_complex:
            replacement.append(
                CircuitInstruction(SGate(), qr_superpos_qubits, ()))
        # else no additional gate required

        # open control if specified
        if open_ctrl:
            replacement += [
                CircuitInstruction(XGate(), qr_superpos_qubits, [])
            ]

        # compute the replacement
        if isinstance(gate, UGate) and param_index == 0:
            theta = gate.params[2]
            rz_plus, rz_minus = RZGate(theta), RZGate(-theta)
            replacement += [
                CircuitInstruction(rz_plus, (qubit, ), ())
                for qubit in gate_qubits
            ]
            replacement += [
                CircuitInstruction(RXGate(np.pi / 2), (qubit, ), ())
                for qubit in gate_qubits
            ]
            replacement.append(
                CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits,
                                   []))
            replacement += [
                CircuitInstruction(RXGate(-np.pi / 2), (qubit, ), ())
                for qubit in gate_qubits
            ]
            replacement += [
                CircuitInstruction(rz_minus, (qubit, ), ())
                for qubit in gate_qubits
            ]

            # update parametertable if necessary
            if isinstance(theta, ParameterExpression):
                # This dangerously subverts ParameterTable by abusing the fact that binding will
                # mutate the exact instruction instance, and relies on all instances of `rz_plus`
                # that were added before being the same in memory, which QuantumCircuit usually
                # ensures is not the case.  I'm leaving this as close to its previous form as
                # possible, to avoid introducing further complications, but this whole method
                # accesses internal attributes of `QuantumCircuit` and needs rewriting.
                # - Jake Lishman, 2022-03-02.
                out._update_parameter_table(
                    CircuitInstruction(rz_plus, (gate_qubits[0], ), ()))
                out._update_parameter_table(
                    CircuitInstruction(rz_minus, (gate_qubits[0], ), ()))

            if open_ctrl:
                replacement.append(
                    CircuitInstruction(XGate(), qr_superpos_qubits, ()))

            if not trim_after_grad_gate:
                replacement.append(CircuitInstruction(gate, gate_qubits, ()))

        elif isinstance(gate, UGate) and param_index == 1:
            # gradient gate is applied after the original gate in this case
            replacement.append(CircuitInstruction(gate, gate_qubits, ()))
            replacement.append(
                CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits,
                                   ()))
            if open_ctrl:
                replacement.append(
                    CircuitInstruction(XGate(), qr_superpos_qubits, ()))

        else:
            replacement.append(
                CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits,
                                   ()))
            if open_ctrl:
                replacement.append(
                    CircuitInstruction(XGate(), qr_superpos_qubits, ()))
            if not trim_after_grad_gate:
                replacement.append(CircuitInstruction(gate, gate_qubits, ()))

        # replace the parameter we compute the derivative of with the replacement
        # TODO can this be done more efficiently?
        if trim_after_grad_gate:  # remove everything after the gradient gate
            out._data[gate_idx:] = replacement
            # reset parameter table
            table = ParameterTable()
            for instruction in out._data:
                for idx, param_expression in enumerate(
                        instruction.operation.params):
                    if isinstance(param_expression, ParameterExpression):
                        for param in param_expression.parameters:
                            if param not in table.keys():
                                table[param] = ParameterReferences(
                                    ((instruction.operation, idx), ))
                            else:
                                table[param].add((instruction.operation, idx))

            out._parameter_table = table

        else:
            out._data[gate_idx:gate_idx + 1] = replacement

        return out