Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Help to transpile a parameterized circuit with more than 3 controls #13192

Closed
Augusto12 opened this issue Sep 19, 2024 · 10 comments
Closed

Help to transpile a parameterized circuit with more than 3 controls #13192

Augusto12 opened this issue Sep 19, 2024 · 10 comments
Labels
bug Something isn't working

Comments

@Augusto12
Copy link

Augusto12 commented Sep 19, 2024

Environment

  • Qiskit version: 1.2.1
  • Python version: 3.10.14
  • Operating system: Ubuntu 20.04.6 LTS (WSL2)

What is happening?

I can't transpile a parameterized circuit with more than 3 controls

How can we reproduce the issue?

from qiskit.compiler import transpile
from qiskit.circuit import QuantumCircuit, ParameterVector

p = ParameterVector('p',4)

pqc = QuantumCircuit(4)
for i in range(4):
    pqc.ry(p[i],i)

pqc = pqc.control(4,0,annotated=True)
pqc = transpile(pqc, basis_gates=['u1', 'u2', 'u3', 'cx'], optimization_level=3)

What should happen?

I got the error:
QiskitError: 'Cannot synthesize MCRY with unbound parameter: p[0].'

Any suggestions?

No response

@Augusto12 Augusto12 added the bug Something isn't working label Sep 19, 2024
@ACE07-Sev
Copy link

Apparently, it's because they haven't implemented the q_ancillae in add_control() method of qiskit.circuit.add_control.py.

def control(
    operation: Gate | ControlledGate,
    num_ctrl_qubits: int | None = 1,
    label: str | None = None,
    ctrl_state: str | int | None = None,
) -> ControlledGate:
    """Return controlled version of gate using controlled rotations. This function
    first checks the name of the operation to see if it knows of a method from which
    to generate a controlled version. Currently, these are ``x``, ``rx``, ``ry``, and ``rz``.
    If a method is not directly known, it calls the unroller to convert to `u1`, `u3`,
    and `cx` gates.

    Args:
        operation: The gate used to create the ControlledGate.
        num_ctrl_qubits: The number of controls to add to gate (default=1).
        label: An optional gate label.
        ctrl_state: The control state in decimal or as
            a bitstring (e.g. '111'). If specified as a bitstring the length
            must equal num_ctrl_qubits, MSB on left. If None, use
            2**num_ctrl_qubits-1.

    Returns:
        Controlled version of gate.

    Raises:
        CircuitError: gate contains non-gate in definition
    """
    from math import pi

    # pylint: disable=cyclic-import
    from qiskit.circuit import controlledgate

    ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits)

    q_control = QuantumRegister(num_ctrl_qubits, name="control")
    q_target = QuantumRegister(operation.num_qubits, name="target")
    q_ancillae = None  # TODO: add

Which causes the q_ancillae to be [] when mcry is called. Since it's empty, the length of it is 0, which means that

if mode is None:
        # if enough ancillary qubits are provided, use the 'v-chain' method
        additional_vchain = MCXGate.get_num_ancilla_qubits(len(control_qubits), "v-chain")
        if len(ancillary_qubits) >= additional_vchain:
            mode = "basic"
        else:
            mode = "noancilla"

Will always choose "noancilla" given MCXGate.get_num_ancilla_qubits(4, "v-chain") will evaluate to 2. So, the method uses "noancilla" and because you have 4 control qubits:

elif mode == "noancilla":
        n_c = len(control_qubits)
        if n_c == 1:  # cu
            _apply_cu(
                self, theta, 0, 0, control_qubits[0], target_qubit, use_basis_gates=use_basis_gates
            )
        elif n_c < 4:
            theta_step = theta * (1 / (2 ** (n_c - 1)))
            _apply_mcu_graycode(
                self,
                theta_step,
                0,
                0,
                control_qubits,
                target_qubit,
                use_basis_gates=use_basis_gates,
            )
        else:
            if isinstance(theta, ParameterExpression):
                raise QiskitError(f"Cannot synthesize MCRY with unbound parameter: {theta}.")

            cgate = _mcsu2_real_diagonal(
                RYGate(theta).to_matrix(),
                num_controls=len(control_qubits),
                use_basis_gates=use_basis_gates,
            )
            self.compose(cgate, control_qubits + [target_qubit], inplace=True)

It will go to the else block, and since you're using a ParameterExpression it raises the error. I think you have to wait until they finish adding the TODO part.

@alexanderivrii
Copy link
Member

alexanderivrii commented Sep 22, 2024

Indeed, we currently do not have methods to transpile parameterized MCRY gates with unbound parameters, regardless of the number of ancillas, @Cryoris and @ShellyGarion please correct me if I am wrong. For instance, the function _mcsu2_real_diagonal, used in the code snippet above, requires all of the parameters to be floats and does not work with general parameter expressions.

What you can do it to call assign_parameters prior to transpilation (this was recently added in #12869), e.g. the following code does work:

from qiskit.compiler import transpile
from qiskit.circuit import QuantumCircuit, ParameterVector

p = ParameterVector('p',4)

pqc = QuantumCircuit(4)
for i in range(4):
    pqc.ry(p[i],i)

pqc = pqc.control(4,0,annotated=True)
pqc = pqc.assign_parameters([1, 2, 3, 4])  # <------ BINDING PARAMETERS
pqc = transpile(pqc, basis_gates=['u1', 'u2', 'u3', 'cx'], optimization_level=3)

Also note that by default assign_parameters returns a new circuit, but you can also (alternatively) use
pqc.assign_parameters([1, 2, 3, 4], inplace=True)

@Cryoris
Copy link
Contributor

Cryoris commented Oct 11, 2024

@Augusto12 does this resolve the problem in your workflow? 🙂

@Augusto12
Copy link
Author

No. I'm using parameterized circuits in a machine learning workflow with the lib qiskit_machine_learning. When I put the inputs, the pipeline calls some transpilation tasks that break the flow.

@Cryoris
Copy link
Contributor

Cryoris commented Oct 11, 2024

Could you post a minimally reproducing example of what you're doing? It might be something that can be resolved in the machine learning package itself 🙂 You could open an issue there directly or post it here and we might transfer it.

@Augusto12
Copy link
Author

Sure!

import numpy as np
from qiskit.primitives import Sampler 
from qiskit.circuit import QuantumCircuit, ParameterVector
from qiskit.circuit.library import RealAmplitudes
from qiskit_machine_learning.algorithms.classifiers import VQC
from qiskit_algorithms.optimizers import GradientDescent

CTRL_QUBITS = 3
DATA_QUBITS = 4

total_qubits = CTRL_QUBITS + DATA_QUBITS

p = ParameterVector('p',4)

feature_map = QuantumCircuit(DATA_QUBITS)
for i in range(DATA_QUBITS):
    feature_map.ry(p[i],i)

feature_map = feature_map.control(CTRL_QUBITS,0,annotated=True)
#feature_map = feature_map.decompose(reps=3)

ansatz = QuantumCircuit(total_qubits)
ansatz.compose(RealAmplitudes(num_qubits=DATA_QUBITS, reps=3),qubits=range(CTRL_QUBITS,total_qubits),inplace=True)

vqc = VQC(
        sampler=Sampler(),
        feature_map=feature_map,
        ansatz=ansatz,
        optimizer=GradientDescent(maxiter=200)
    )

fake_feature_data = np.array([[1,2,3,4],[1,1,2,2]])
fake_label_data = np.array([1,2])

vqc.fit(fake_feature_data, fake_label_data)

If you uncomment the line #feature_map = feature_map.decompose(reps=3), it works for CTRL_QUBITS <= 3. If you set CTRL_QUBITS >= 4, with or without pre-transpilation, it doesn't work. I think that this issue is related to the transpilation of annotated operations.

@Cryoris
Copy link
Contributor

Cryoris commented Oct 14, 2024

Ok I see, the problem here is that VQC uses the SamplerQNN internally, which computes the gradients with the parameter-shift rule. To apply that gradient rule, qiskit-algorithms tries to express the circuit in single-qubit Pauli rotations, which it knows how to deal with. But we cannot unroll the circuit to single-qubit Paulis since the synthesis algorithm only works for fixed parameters.

There's two possible resolutions to that

  1. (easy) Use a finite difference gradient, which doesn't care about how the circuit is implemented. This option is currently not exposed in VQC, but could be added. Alternatively you could build your own classifier and use SamplerQNN, which allows to select the gradient.
  2. (better) Extend the parameter shift rule to handle controlled Paulis. If my brain didn't break on a Monday morning, controlled Pauli rotations should have the same two distinct eigenvalues as the Pauli rotations, so the standard parameter-shift rule still applies. We could adapt the ParameterShiftGradient classes (both for sampler and estimator) to not unroll controlled Paulis.

Neither of those things are handled in this repository, depending on which we would like to pursue, we should open an issue in qiskit-machine-learning (for 1.) or in qiskit-algorithms (for 2.).

@Augusto12
Copy link
Author

Ok. Thank you very much.

@Cryoris
Copy link
Contributor

Cryoris commented Oct 15, 2024

As a workaround you could use auxiliary qubits to implement the n-controlled, parameterized RY, for example something like

from qiskit.circuit import Parameter
from qiskit.circuit.library import RYGate, MCMTVChain

y = Parameter("y")
mcry = MCMTVChain(RYGate(y), num_ctrl_qubits, 1)

This circuit implements the controlled RY on the top num_ctrl_qubits + 1 qubits and uses num_ctrl_qubits - 1 additional auxiliary qubits.

@ShellyGarion
Copy link
Member

This should be fixed now that #13507 is merged. If this is not so - please re-open this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants