Esempio n. 1
0
    def test_state_gradient3(self, method):
        """Test the state gradient 3

        Tr(|psi><psi|Z) = sin(a)sin(c(a)) = sin(a)sin(cos(a)+1)
        Tr(|psi><psi|X) = cos(a)
        d<H>/da = - 0.5 sin(a) - 1 cos(a)sin(cos(a)+1) + 1 sin^2(a)cos(cos(a)+1)
        """
        ham = 0.5 * X - 1 * Z
        a = Parameter('a')
        # b = Parameter('b')
        params = a
        x = Symbol('x')
        expr = cos(x) + 1
        c = ParameterExpression({a: x}, expr)

        q = QuantumRegister(1)
        qc = QuantumCircuit(q)
        qc.h(q)
        qc.rz(a, q[0])
        qc.rx(c, q[0])
        op = ~StateFn(ham) @ CircuitStateFn(primitive=qc, coeff=1.)

        state_grad = Gradient(grad_method=method).convert(operator=op, params=params)
        values_dict = [{a: np.pi / 4}, {a: 0}, {a: np.pi / 2}]
        correct_values = [-1.1220, -0.9093, 0.0403]
        for i, value_dict in enumerate(values_dict):
            np.testing.assert_array_almost_equal(state_grad.assign_parameters(value_dict).eval(),
                                                 correct_values[i],
                                                 decimal=1)
Esempio n. 2
0
def param_to_qiskit(
    p: sympy.Expr,
    symb_map: Dict[Parameter,
                   sympy.Symbol]) -> Union[float, ParameterExpression]:
    ppi = p * sympy.pi
    if len(ppi.free_symbols) == 0:
        return float(ppi.evalf())
    else:
        return ParameterExpression(symb_map, ppi)
Esempio n. 3
0
    def test_coeffs(self):
        """ListOp.coeffs test"""
        sum1 = SummedOp(
            [(0 + 1j) * X, (1 / np.sqrt(2) + 1j / np.sqrt(2)) * Z], 0.5
        ).collapse_summands()
        self.assertAlmostEqual(sum1.coeffs[0], 0.5j)
        self.assertAlmostEqual(sum1.coeffs[1], (1 + 1j) / (2 * np.sqrt(2)))

        a_param = Parameter("a")
        b_param = Parameter("b")
        param_exp = ParameterExpression({a_param: 1, b_param: 0}, Symbol("a") ** 2 + Symbol("b"))
        sum2 = SummedOp([X, (1 / np.sqrt(2) - 1j / np.sqrt(2)) * Y], param_exp).collapse_summands()
        self.assertIsInstance(sum2.coeffs[0], ParameterExpression)
        self.assertIsInstance(sum2.coeffs[1], ParameterExpression)

        # Nested ListOp
        sum_nested = SummedOp([X, sum1])
        self.assertRaises(TypeError, lambda: sum_nested.coeffs)
Esempio n. 4
0
    def parameter_expression_grad(
            param_expr: ParameterExpression,
            param: ParameterExpression) -> Union[ParameterExpression, float]:
        """Get the derivative of a parameter expression w.r.t. the given parameter.

        Args:
            param_expr: The Parameter Expression for which we compute the derivative
            param: Parameter w.r.t. which we want to take the derivative

        Returns:
            ParameterExpression representing the gradient of param_expr w.r.t. param
        """
        if not isinstance(param_expr, ParameterExpression):
            return 0.0

        if param not in param_expr._parameter_symbols:
            return 0.0

        import sympy as sy
        expr = param_expr._symbol_expr
        keys = param_expr._parameter_symbols[param]
        expr_grad = sy.N(0)
        if not isinstance(keys, IterableAbc):
            keys = [keys]
        if HAS_SYMENGINE:
            expr_grad = 0
            for key in keys:
                expr_grad += symengine.Derivative(expr, key)
        else:
            expr_grad = sy.N(0)
            for key in keys:
                expr_grad += sy.Derivative(expr, key).doit()

        # generate the new dictionary of symbols
        # this needs to be done since in the derivative some symbols might disappear (e.g.
        # when deriving linear expression)
        parameter_symbols = {}
        for parameter, symbol in param_expr._parameter_symbols.items():
            if symbol in expr_grad.free_symbols:
                parameter_symbols[parameter] = symbol

        if len(parameter_symbols) > 0:
            return ParameterExpression(parameter_symbols, expr=expr_grad)
        return float(expr_grad)  # if no free symbols left, convert to float
Esempio n. 5
0
    def to_qiskit(self, qreg, creg):
        """Converts a Gate object to a qiskit object.

        Args:
            qreg: QuantumRegister
                Optional feature in case the original circuit is not contructed from qiskit.
                Then we will use a single QuantumRegister for all of the qubits for the qiskit
                QuantumCircuit object.
        Returns:
            A list of length N*3 where N is the number of gates used in the
            decomposition. For each gate, the items appended to the list are,
            in order, the qiskit gate object, the qubits involved in the gate
            (described by a tuple of the quantum register and the index), and
            lastly, the classical register (for now the classical register is
            always empty, except for MEASURE)
        """

        if self.name not in ALL_GATES:
            sys.exit("Gate currently not supported.")

        qiskit_qubits = []
        qiskit_bits = []
        for q in self.qubits:
            qiskit_qubits.append(QiskitQubit(qreg, q.index))
            qiskit_bits.append(QiskitClbit(creg, q.index))

        if len(self.params) > 0:
            params = copy.copy(self.params)
            for i in range(len(params)):
                if isinstance(params[i], sympy.Basic):
                    params[i] = ParameterExpression({}, params[i])
        # single-qubit gates
        if self.name == "I":
            return [qiskit.extensions.standard.IGate(), [qiskit_qubits[0]], []]
        if self.name == "X":
            return [qiskit.extensions.standard.XGate(), [qiskit_qubits[0]], []]
        if self.name == "Y":
            return [qiskit.extensions.standard.YGate(), [qiskit_qubits[0]], []]
        if self.name == "Z":
            return [qiskit.extensions.standard.ZGate(), [qiskit_qubits[0]], []]
        if self.name == "H":
            return [qiskit.extensions.standard.HGate(), [qiskit_qubits[0]], []]
        if self.name == "T":
            return [qiskit.extensions.standard.TGate(), [qiskit_qubits[0]], []]
        if self.name == "S":
            return [qiskit.extensions.standard.SGate(), [qiskit_qubits[0]], []]
        if self.name == "Rx":
            return [
                qiskit.extensions.standard.RXGate(params[0]),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "Ry":
            return [
                qiskit.extensions.standard.RYGate(params[0]),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "Rz" or self.name == "PHASE":
            return [
                qiskit.extensions.standard.RZGate(params[0]),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "ZXZ":  # PhasedXPowGate gate (from cirq)
            # Hard-coded decomposition is used for now.
            return [
                qiskit.extensions.standard.RXGate(-params[0]),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.RZGate(params[1]),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.RXGate(params[0]),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "RH":  # HPowGate (from cirq)
            # Hard-coded decomposition is used for now.
            return [
                qiskit.extensions.standard.RYGate(pi / 4),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.RZGate(params[0]),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.RYGate(-pi / 4),
                [qiskit_qubits[0]],
                [],
            ]

        # two-qubit gates
        if self.name == "CNOT":
            return [
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
            ]
        if self.name == "CZ":
            return [
                qiskit.extensions.standard.CzGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
            ]
        if self.name == "CPHASE":
            return [
                qiskit.extensions.standard.RXGate(pi / 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RYGate(pi - params[0] / 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CzGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RYGate(-(pi - params[0] / 2)),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RXGate(-pi),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CzGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RXGate(pi / 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RZGate(params[0] / 2),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "SWAP":
            return [
                qiskit.extensions.standard.SwapGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
            ]
        if self.name == "XX":
            # Hard-coded decomposition is used for now. The compilation is inspired by the approach described in arXiv:1001.3855 [quant-ph]
            return [
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RZGate(params[0] * 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[1]],
                [],
            ]
        if self.name == "YY":
            # Hard-coded decomposition is used for now. The compilation is inspired by the approach described in arXiv:1001.3855 [quant-ph]
            return [
                qiskit.extensions.standard.SGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.SGate(),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RZGate(params[0] * 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.SdgGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.SdgGate(),
                [qiskit_qubits[1]],
                [],
            ]
        if self.name == "ZZ":
            # Hard-coded decomposition is used for now. The compilation is inspired by the approach described in arXiv:1001.3855 [quant-ph]
            return [
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RZGate(params[0] * 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
            ]
        if self.name == "CCX":
            return [
                qiskit.extensions.standard.ToffoliGate(),
                [qiskit_qubits[0], qiskit_qubits[1], qiskit_qubits[2]],
                [],
            ]
        if self.name == "MCT":
            gate = MCTGate(
                self.all_circuit_qubits,
                control_qubits=self.control_qubits,
                target_qubits=self.target_qubits,
            )
            return gate.qiskit_data
        if self.name == "MCU1":
            gate = PhaseOracle(
                self.params[0],
                self.all_circuit_qubits,
                control_qubits=self.control_qubits,
                target_qubits=self.target_qubits,
            )
            return gate.qiskit_data
        if self.name == "MCRY":
            gate = MCRY(
                self.params[0],
                self.all_circuit_qubits,
                control_qubits=self.control_qubits,
                target_qubits=self.target_qubits,
            )
            return gate.qiskit_data
        if self.name == "MEASURE":
            return [
                qiskit.circuit.measure.Measure(), qiskit_qubits, qiskit_bits
            ]
        if self.name == "BARRIER":
            return [
                qiskit.extensions.standard.Barrier(len(qiskit_qubits)),
                qiskit_qubits,
                [],
            ]
Esempio n. 6
0
    def to_qiskit(self, qreg=None):
        """Converts a Gate object to a qiskit object.

        Args:
            qreg: QuantumRegister
                Optional feature in case the original circuit is not contructed from qiskit.
                Then we will use a single QuantumRegister for all of the qubits for the qiskit
                QuantumCircuit object.
        Returns:
            A list of length N*3 where N is the number of gates used in the
            decomposition. For each gate, the items appended to the list are,
            in order, the qiskit gate object, the qubits involved in the gate
            (described by a tuple of the quantum register and the index), and
            lastly, the classical register (for now the classical register is
            always empty, except for MEASURE)
        """

        if self.name not in ALL_GATES:
            sys.exit("Gate currently not supported.")

        qiskit_qubits = []
        qiskit_bits = []
        for q in self.qubits:
            if q.info["label"] == "qiskit":
                # QuantumRegister info is stored as a string, so must parse
                #   and recreate register
                q_qreg_num = int(q.info["qreg"][q.info["qreg"].find("(") +
                                                1:q.info["qreg"].find(",")])
                q_qreg_label = q.info["qreg"][q.info["qreg"].find("'") +
                                              1:q.info["qreg"].rfind("'")]
                q_qreg = QuantumRegister(q_qreg_num, q_qreg_label)
                qiskit_qubit = QiskitQubit(q_qreg, q.info["num"])
                qiskit_qubits.append(qiskit_qubit)

                if "creg" in q.info.keys():
                    q_creg_num = int(
                        q.info["creg"][q.info["creg"].find("(") +
                                       1:q.info["creg"].find(",")])
                    q_creg_label = q.info["creg"][q.info["creg"].find("'") +
                                                  1:q.info["creg"].rfind("'")]
                    q_creg = ClassicalRegister(q_creg_num, q_creg_label)
                    qiskit_clbit = QiskitClbit(q_creg, q.info["num"])
                    qiskit_bits.append(qiskit_clbit)
            else:
                if qreg is None:
                    Exception(
                        "Gate doesn't come from qiskit and qreg is not provided. Please provide a qreg parameter."
                    )
                qiskit_qubit = QiskitQubit(qreg, q.index)
                qiskit_qubits.append(qiskit_qubit)
        if len(self.params) > 0:
            params = copy.copy(self.params)
            for i in range(len(params)):
                if isinstance(params[i], sympy.Basic):
                    params[i] = ParameterExpression({}, params[i])
        # single-qubit gates
        if self.name == "I":
            return [qiskit.extensions.standard.IGate(), [qiskit_qubits[0]], []]
        if self.name == "X":
            return [qiskit.extensions.standard.XGate(), [qiskit_qubits[0]], []]
        if self.name == "Y":
            return [qiskit.extensions.standard.YGate(), [qiskit_qubits[0]], []]
        if self.name == "Z":
            return [qiskit.extensions.standard.ZGate(), [qiskit_qubits[0]], []]
        if self.name == "H":
            return [qiskit.extensions.standard.HGate(), [qiskit_qubits[0]], []]
        if self.name == "T":
            return [qiskit.extensions.standard.TGate(), [qiskit_qubits[0]], []]
        if self.name == "S":
            return [qiskit.extensions.standard.SGate(), [qiskit_qubits[0]], []]
        if self.name == "Rx":
            return [
                qiskit.extensions.standard.RXGate(params[0]),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "Ry":
            return [
                qiskit.extensions.standard.RYGate(params[0]),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "Rz" or self.name == "PHASE":
            return [
                qiskit.extensions.standard.RZGate(params[0]),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "ZXZ":  # PhasedXPowGate gate (from cirq)
            # Hard-coded decomposition is used for now.
            return [
                qiskit.extensions.standard.RXGate(-params[0]),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.RZGate(params[1]),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.RXGate(params[0]),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "RH":  # HPowGate (from cirq)
            # Hard-coded decomposition is used for now.
            return [
                qiskit.extensions.standard.RYGate(pi / 4),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.RZGate(params[0]),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.RYGate(-pi / 4),
                [qiskit_qubits[0]],
                [],
            ]

        # two-qubit gates
        if self.name == "CNOT":
            return [
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
            ]
        if self.name == "CZ":
            return [
                qiskit.extensions.standard.CzGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
            ]
        if self.name == "CPHASE":
            return [
                qiskit.extensions.standard.RXGate(pi / 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RYGate(pi - params[0] / 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CzGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RYGate(-(pi - params[0] / 2)),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RXGate(-pi),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CzGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RXGate(pi / 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RZGate(params[0] / 2),
                [qiskit_qubits[0]],
                [],
            ]
        if self.name == "SWAP":
            return [
                qiskit.extensions.standard.SwapGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
            ]
        if self.name == "XX":
            # Hard-coded decomposition is used for now. The compilation is inspired by the approach described in arXiv:1001.3855 [quant-ph]
            return [
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RZGate(params[0] * 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[1]],
                [],
            ]
        if self.name == "YY":
            # Hard-coded decomposition is used for now. The compilation is inspired by the approach described in arXiv:1001.3855 [quant-ph]
            return [
                qiskit.extensions.standard.SGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.SGate(),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RZGate(params[0] * 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.HGate(),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.SdgGate(),
                [qiskit_qubits[0]],
                [],
                qiskit.extensions.standard.SdgGate(),
                [qiskit_qubits[1]],
                [],
            ]
        if self.name == "ZZ":
            # Hard-coded decomposition is used for now. The compilation is inspired by the approach described in arXiv:1001.3855 [quant-ph]
            return [
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.RZGate(params[0] * 2),
                [qiskit_qubits[1]],
                [],
                qiskit.extensions.standard.CnotGate(),
                [qiskit_qubits[0], qiskit_qubits[1]],
                [],
            ]
        if self.name == 'CCX':
            return [
                qiskit.extensions.standard.ToffoliGate(),
                [qiskit_qubits[0], qiskit_qubits[1], qiskit_qubits[2]], []
            ]
        if self.name == 'MCT':
            gate = MCTGate(self.all_circuit_qubits,
                           control_qubits=self.control_qubits,
                           target_qubits=self.target_qubits)
            return gate.qiskit_data
        if self.name == 'MCU1':
            gate = PhaseOracle(self.params[0],
                               self.all_circuit_qubits,
                               control_qubits=self.control_qubits,
                               target_qubits=self.target_qubits)
            return gate.qiskit_data
        if self.name == 'MCRY':
            gate = MCRY(self.params[0],
                        self.all_circuit_qubits,
                        control_qubits=self.control_qubits,
                        target_qubits=self.target_qubits)
            return gate.qiskit_data
        if self.name == "MEASURE":
            return [
                qiskit.circuit.measure.Measure(), qiskit_qubits, qiskit_bits
            ]
        if self.name == "BARRIER":
            return [
                qiskit.extensions.standard.Barrier(len(qiskit_qubits)),
                qiskit_qubits,
                [],
            ]
    def _attempt_bind(self, template_sublist, circuit_sublist):
        """
        Copies the template and attempts to bind any parameters,
        i.e. attempts to solve for a valid parameter assignment.
        template_sublist and circuit_sublist match up to the
        assignment of the parameters. For example the template

        .. parsed-literal::

                 ┌───────────┐                  ┌────────┐
            q_0: ┤ P(-1.0*β) ├──■────────────■──┤0       ├
                 ├───────────┤┌─┴─┐┌──────┐┌─┴─┐│  CZ(β) │
            q_1: ┤ P(-1.0*β) ├┤ X ├┤ P(β) ├┤ X ├┤1       ├
                 └───────────┘└───┘└──────┘└───┘└────────┘

        should only maximally match once in the circuit

        .. parsed-literal::

                 ┌───────┐
            q_0: ┤ P(-2) ├──■────────────■────────────────────────────
                 ├───────┤┌─┴─┐┌──────┐┌─┴─┐┌──────┐
            q_1: ┤ P(-2) ├┤ X ├┤ P(2) ├┤ X ├┤ P(3) ├──■────────────■──
                 └┬──────┤└───┘└──────┘└───┘└──────┘┌─┴─┐┌──────┐┌─┴─┐
            q_2: ─┤ P(3) ├──────────────────────────┤ X ├┤ P(3) ├┤ X ├
                  └──────┘                          └───┘└──────┘└───┘

        However, up until attempt bind is called, the soft matching
        will have found two matches due to the parameters.
        The first match can be satisfied with β=2. However, the
        second match would imply both β=3 and β=-3 which is impossible.
        Attempt bind detects inconsistencies by solving a system of equations
        given by the parameter expressions in the sub-template and the
        value of the parameters in the gates of the sub-circuit. If a
        solution is found then the match is valid and the parameters
        are assigned. If not, None is returned.

        In order to resolve the conflict of the same parameter names in the
        circuit and template, each variable in the template sublist is
        re-assigned to a new dummy parameter with a completely separate name
        if it clashes with one that exists in an input circuit.

        Args:
            template_sublist (list): part of the matched template.
            circuit_sublist (list): part of the matched circuit.

        Returns:
            DAGDependency: A deep copy of the template with
                the parameters bound. If no binding satisfies the
                parameter constraints, returns None.
        """
        import sympy as sym
        from sympy.parsing.sympy_parser import parse_expr

        circuit_params, template_params = [], []
        # Set of all parameter names that are present in the circuits to be optimised.
        circuit_params_set = set()

        template_dag_dep = copy.deepcopy(self.template_dag_dep)

        # add parameters from circuit to circuit_params
        for idx, _ in enumerate(template_sublist):
            qc_idx = circuit_sublist[idx]
            parameters = self.circuit_dag_dep.get_node(qc_idx).op.params
            circuit_params += parameters
            for parameter in parameters:
                if isinstance(parameter, ParameterExpression):
                    circuit_params_set.update(x.name
                                              for x in parameter.parameters)

        _dummy_counter = itertools.count()

        def dummy_parameter():
            # Strictly not _guaranteed_ to avoid naming clashes, but if someone's calling their
            # parameters this then that's their own fault.
            return Parameter(f"_qiskit_template_dummy_{next(_dummy_counter)}")

        # Substitutions for parameters that have clashing names between the input circuits and the
        # defined templates.
        template_clash_substitutions = collections.defaultdict(dummy_parameter)

        # add parameters from template to template_params, replacing parameters with names that
        # clash with those in the circuit.
        for t_idx in template_sublist:
            node = template_dag_dep.get_node(t_idx)
            sub_node_params = []
            for t_param_exp in node.op.params:
                if isinstance(t_param_exp, ParameterExpression):
                    for t_param in t_param_exp.parameters:
                        if t_param.name in circuit_params_set:
                            new_param = template_clash_substitutions[
                                t_param.name]
                            t_param_exp = t_param_exp.assign(
                                t_param, new_param)
                sub_node_params.append(t_param_exp)
                template_params.append(t_param_exp)
            node.op.params = sub_node_params

        for node in template_dag_dep.get_nodes():
            sub_node_params = []
            for param_exp in node.op.params:
                if isinstance(param_exp, ParameterExpression):
                    for param in param_exp.parameters:
                        if param.name in template_clash_substitutions:
                            param_exp = param_exp.assign(
                                param,
                                template_clash_substitutions[param.name])
                sub_node_params.append(param_exp)

            node.op.params = sub_node_params

        # Create the fake binding dict and check
        equations, circ_dict, temp_symbols, sol, fake_bind = [], {}, {}, {}, {}
        for circuit_param, template_param in zip(circuit_params,
                                                 template_params):
            if isinstance(template_param, ParameterExpression):
                if isinstance(circuit_param, ParameterExpression):
                    circ_param_sym = circuit_param.sympify()
                else:
                    circ_param_sym = parse_expr(str(circuit_param))
                equations.append(
                    sym.Eq(template_param.sympify(), circ_param_sym))

                for param in template_param.parameters:
                    temp_symbols[param] = param.sympify()

                if isinstance(circuit_param, ParameterExpression):
                    for param in circuit_param.parameters:
                        circ_dict[param] = param.sympify()
            elif template_param != circuit_param:
                # Both are numeric parameters, but aren't equal.
                return None

        if not temp_symbols:
            return template_dag_dep

        # Check compatibility by solving the resulting equation
        sym_sol = sym.solve(equations, set(temp_symbols.values()))
        for key in sym_sol:
            try:
                sol[str(key)] = ParameterExpression(circ_dict, sym_sol[key])
            except TypeError:
                return None

        if not sol:
            return None

        for key in temp_symbols:
            fake_bind[key] = sol[str(key)]

        for node in template_dag_dep.get_nodes():
            bound_params = []
            for param_exp in node.op.params:
                if isinstance(param_exp, ParameterExpression):
                    for param in param_exp.parameters:
                        if param in fake_bind:
                            if fake_bind[param] not in bound_params:
                                param_exp = param_exp.assign(
                                    param, fake_bind[param])
                else:
                    param_exp = float(param_exp)
                bound_params.append(param_exp)

            node.op.params = bound_params

        return template_dag_dep