issue with converting parametrizeable RZX gate in pennylane

Hello, my group is currently working on running code on a eagle ibm computer. We want parts of our code to be in pennylane and other parts to be in qiskit. We design parametrizable ansatz in both frameworks. To make this works, we use qml.from_qiskit converter to run qiskit’s ansatz in pennylane. This workflow was working fine with us until we tried to use the RZX gate from qiskit.

From what i understand, because pennylane does not have the RZX gate, it tries to generate the matrix from it, but it cannot do it because it expect a float and not some parametrizable value.

An alternative way to run the parametrized qiskit code would be to do the following in the circuit_pennylane_ansatz:

def circuit_pennylane_ansatz(thetas):
    qml.from_qiskit(global_qc.assign_parameters(thetas))()
    return [qml.expval(qml.PauliZ(i)) for i in range(3)]

but i would still expect qml.from_qiskit(global_qc)(thetas) to work even if a gate’s matrix is reconstructed and it has qiskit’s Parameters inside.

here’s a minimal working example showcasing the issue. Intended to be ran in a jupyter notebook.

Cell 1 :

# Put code here
from qiskit_ibm_runtime.fake_provider import FakeSherbrooke
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
import pennylane as qml
from pennylane import numpy as np
import time

global_qc = None
def three_rzx_qubit_eagle_ansatz_init():
    theta = ParameterVector("angles",2)

    qc = QuantumCircuit(3)
    qc.rzx(theta[0], 1, 0) # comment these two line and
    qc.rzx(theta[1], 1, 2) # uncomment the lines below to check that it works with other gates
    # qc.rz(theta[0],0)
    # qc.ecr(0,1)
    # qc.rz(theta[1],1)
    # qc.ecr(1,2)

    rng = np.random.default_rng(int(time.time()))
    init_thetas = rng.uniform(0.0, 2.0 * np.pi, size=2).astype(float)

    global global_qc
    global_qc = qc 
    return init_thetas

dev = qml.device("default.qubit", wires=3)
@qml.qnode(dev, diff_method="best")
def circuit_pennylane_ansatz(thetas):
    qml.from_qiskit(global_qc)(thetas)
    return [qml.expval(qml.PauliZ(i)) for i in range(3)]

def cost(thetas):
    oldtime = time.time()
    energy = circuit_pennylane_ansatz(thetas)
    print(f"circuit time: {time.time() - oldtime}")
    return qml.math.real(sum(energy))

Cell 2 :

maxiter=4
opt = qml.SPSAOptimizer(maxiter)
params = three_rzx_qubit_eagle_ansatz_init()
print(params)
for n in range(maxiter):
    params, prev_energy = opt.step_and_cost(cost, params)
    print(f"--- Step: {n}, Energy: {prev_energy}")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[19], line 6
      4 print(params)
      5 for n in range(maxiter):
----> 6     params, prev_energy = opt.step_and_cost(cost, params)
      7     print(f"--- Step: {n}, Energy: {prev_energy}")

File c:\Users\Kuift\AppData\Local\Programs\Python\Python312\Lib\site-packages\pennylane\optimize\spsa.py:198, in SPSAOptimizer.step_and_cost(self, objective_fn, *args, **kwargs)
    184 def step_and_cost(self, objective_fn, *args, **kwargs):
    185     r"""Update the parameter array :math:`\hat{\theta}_k` with one step of the
    186     optimizer and return the step and the corresponding objective function. The number
    187     of steps stored by the ``k`` attribute of the optimizer is counted internally when calling ``step_and_cost`` and ``cost``.
   (...)
    196         objective function output prior to the step.
    197     """
--> 198     g = self.compute_grad(objective_fn, args, kwargs)
    200     new_args = self.apply_grad(g, args)
    202     self.k += 1

File c:\Users\Kuift\AppData\Local\Programs\Python\Python312\Lib\site-packages\pennylane\optimize\spsa.py:265, in SPSAOptimizer.compute_grad(self, objective_fn, args, kwargs)
    263         thetaminus[index] = arg - multiplier
    264         delta.append(di)
--> 265 yplus = objective_fn(*thetaplus, **kwargs)
    266 yminus = objective_fn(*thetaminus, **kwargs)
...
    425     # real.  This second attempt at a cast is a way of unifying the behavior to the
    426     # more expected form for our users.
    427     cval = complex(self)

TypeError: ParameterExpression with unbound parameters (dict_keys([ParameterVectorElement(angles[0])])) cannot be cast to a float.

qml.about() output :

Name: pennylaneVersion: 0.42.2Summary: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.Home-page:Author:Author-email:License-Expression: Apache-2.0Location: c:\Users\Kuift\AppData\Local\Programs\Python\Python312\Lib\site-packagesRequires: appdirs, autograd, autoray, cachetools, diastatic-malt, networkx, numpy, packaging, pennylane-lightning, requests, rustworkx, scipy, tomlkit, typing_extensionsRequired-by: PennyLane-qiskit, pennylane-qulacs, pennylane_lightning
Platform info:           Windows-10-10.0.19045-SP0Python version:          3.12.6Numpy version:           1.26.4Scipy version:           1.15.2Installed devices:
  • bluequbit.cpu (bluequbit-0.12.0b1)
    
  • default.clifford (pennylane-0.42.2)
    
  • default.gaussian (pennylane-0.42.2)
    
  • default.mixed (pennylane-0.42.2)
    
  • default.qubit (pennylane-0.42.2)
    
  • default.qutrit (pennylane-0.42.2)
    
  • default.qutrit.mixed (pennylane-0.42.2)
    
  • default.tensor (pennylane-0.42.2)
    
  • null.qubit (pennylane-0.42.2)
    
  • reference.qubit (pennylane-0.42.2)
    
  • lightning.qubit (pennylane_lightning-0.42.0)
    
  • qiskit.aer (PennyLane-qiskit-0.42.0)
    
  • qiskit.basicaer (PennyLane-qiskit-0.42.0)
    
  • qiskit.basicsim (PennyLane-qiskit-0.42.0)
    
  • qiskit.remote (PennyLane-qiskit-0.42.0)
    
  • qulacs.simulator (pennylane-qulacs-0.40.0)
    

Hi @Kuift , welcome to the Forum!

Thank you for posting this question. Just to clarify, does the circuit_pennylane_ansatz option work for now?

I understand that we want qml.from_qiskit(global_qc)(thetas) to work, but I just want to know if you have something that works in the meantime or if this is blocking work.

Yes! The alternative solution works and we are not blocked by this right now.

Albeit, there is something else bothering our workflow: If we want to run a transpiled qiskit circuit that target an eagle device into pennylane, the gates are often on qubits like {81,82,83,84}. Because of that, you end up unable to run your code in pennylane by simply calling qml.from_qiskit because there are no such wires on your 4 qubits pennylane device. I am looking into qml.map_wires to fix this somehow, it probably deserves a post of its own. The used qubits can change between runs so we cannot just statically set a wire map.

The idea is that, after transpilation onto an eagle quantum computer, qiskit circuit will list all 127 qubits even though there are gates on only 4 of those qubits. Of those 4, it will be on qubits numbers that are way outside the wires of pennylane’s device. qml.from_qiskit then freaks out.

Hi @Kuift ,

I understand the problem. Thanks for pointing it out. If you use a PennyLane default.qubit device without specifying the number of wires, does this solve the issue? Specifying the number of wires is optional since release 0.33. So you can create a PennyLane device as:

dev = qml.device("default.qubit")

I’m thinking that using from_qiskit with a device that can self-detect the number of wires needed could solve the issue.

Let me know if this works for you!

Alright so, it works when removing the wires argument! but new issue arise when measuring using qml.expval(hamiltonian): it will measure the hamiltonian (constructed from a sparsepauliop object) at qubits 0 through 2, but the gates are on the qubits 33, 39 and 40. I am using lightning.gpu. I could provide a code example as I think the problem will be somewhat involved.

here’s an image of the resulting circuit :

edit : After writing this, I just figured; specifying wires when calling qml.from_qiskit_op(hamiltonian) might fix it. Is there a way to get used wires (wires that are not empty) in pennylane ? ideally without having to execute the circuit first. note : the hamiltonian we use is symmetric and so qubits ordering don’t really matter, but it does need to measure the qubits being used. If there’s a better implementation that pops to mind that does what i’m describing better, please link it ! Or a general method or something. Right now, the ordering don’t matter, but it might in the future.

doing new_sparse_hamiltonian = mysparsepauliop_hamiltonian.apply_layout(qiskit_ansatz_circuit.layout) does not fix the issue

Hi @Kuift ,

I’m glad my previous suggestion worked.

For your current problem I don’t have a good answer at the moment. We have qml.Tracker and qml.specs which tell you how many qubits are being used but they don’t tell you which ones are being used.

I’ll check with the team to see if they know of something else that could work.

1 Like

Hi @Kuift ,

I just wanted to let you know that I’ve been looking into this issue and have some leads. It looks like there might be some workarounds to fix the wires issue by using qiskit.remote but I don’t have a working example yet so I’ll let you know if I manage to make one!