Changing wires of an operator

Say I am given an operator (or a tensor of operators) which I might not know, and I want to use them, but on other wires than the ones they are specified on. Can I do this?

A bit of context: I use qml.Hamiltonian objects together with the qml.ExpvalCost. Now I want to “rearrange” the wires to which each of the operators in the Hamiltonian is applies. That is, create a new Hamiltonian object that has the same operators and the same coefficients, but the wires to which each is applied is different.

As an example, say I have the Hamiltonian below :

term1 = qml.operation.Tensor(qml.PauliZ(0), qml.PauliX(1), qml.PauliZ(2))
term2 = qml.operation.Tensor(qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2))
H = qml.Hamiltonian([1,1], [term1, term2])

but I don’t want to measure that, but rather [X0 Z1 Z2] + [Z0 X1 X2]. I know I can retrieve the two terms with

H.ops

and get the individual operators using

H.ops[0].non_identity_obs

From there however, is there a way to “copy” the operators with the freedom to set the wires?
If I restrict to e.g. Paulis, I could make a list of allowed operators and check each using its name or type, but I would like to keep it more general (and if possible allow for parametrized gates?)

Any tips and pointers are appreciated! Thank you

Hi @scichy,
as far as I am aware, there currently is no UI functionality to do this.
However, it is certainly doable, by manipulating the private _wires of each factor in the Tensor objects, for each term in the Hamiltonian:
Start by defining the wires remapping as a dict:

wires_map = {0: 1, 1: 0, 2: 2}

Then, iterate over all terms in H, and over all observable factors in each term, and apply this remapping via qml.wires.Wires.map:

new_ops = []
for op in H.ops:
    new_obs = []
    for ob in op.obs:
        ob._wires = ob.wires.map(wires_map)
        new_obs.append(ob)
    new_ops.append(qml.operation.Tensor(*new_obs))
new_H = qml.Hamiltonian(H.coeffs, new_ops)

This works specifically for Hamiltonian, but of course the inner loop could also just be applied to a single Tensor object, or the map approach without any loop to a single Observable like qml.PauliX(3).

To make it a bit more convenient, here is a function that tackles all three cases:

def map_wires(H, wires_map):
    """Map the wires of an Observable according to a wires map.
    
    Args:
        H (Hamiltonian or Tensor or Observable): Hamiltonian to remap the wires of.
        wires_map (dict): Wires map with `(origin, destination)` pairs as key-value pairs.
    
    Returns:
        Hamiltonian or Tensor or Observable: A copy of the original Hamiltonian with remapped wires.
    """
    if isinstance(H, qml.Hamiltonian):
        new_ops = [map_wires(op, wires_map) for op in H.ops]
        new_H = qml.Hamiltonian(H.coeffs, new_ops)
    elif isinstance(H, qml.operation.Tensor):
        new_obs = [map_wires(ob, wires_map) for ob in H.obs]
        new_H = qml.operation.Tensor(*new_obs)
    elif isinstance(H, qml.operation.Observable):
        new_H = copy.copy(H)
        new_H._wires = new_H.wires.map(wires_map)
        
    return new_H

Please note that this is untested custom code, so look out for bugs etc.
Also it is not optimized for performance…
It seems to be doing what we want, though :slight_smile:

wires_map = {i: i+10 for i in range(5)}
Hs = [
    qml.PauliX(4),
    qml.operation.Tensor(qml.PauliX(0), qml.Hadamard(2), qml.PauliZ(4)),
    qml.Hamiltonian(
        [1,1],
        [
            qml.operation.Tensor(qml.PauliZ(0), qml.PauliX(1), qml.PauliZ(2)),
            qml.operation.Tensor(qml.PauliX(0), qml.PauliZ(1), qml.PauliX(2)),
        ],
    ),
]
>>> for H in Hs:
>>>     print(H)
>>>     new_H = map_wires(H, wires_map)
>>>     print(new_H)
PauliX(wires=[4])
PauliX(wires=[14])
PauliX(wires=[0]) @ Hadamard(wires=[2]) @ PauliZ(wires=[4])
PauliX(wires=[10]) @ Hadamard(wires=[12]) @ PauliZ(wires=[14])
  (1) [Z0 X1 Z2]
+ (1) [X0 Z1 X2]
  (1) [Z10 X11 Z12]
+ (1) [X10 Z11 X12]

Hope this helps! Let me know if you have any questions.

Side note: ExpvalCost is deprecated, you may just use expval on a Hamiltonian object :slight_smile: (see e.g. here)

Hi @dwierichs!

Thanks a lot for that! I had found out about the _wires member, but I was reticent of touching a private member… but if there is no other option I will do, your code surely seems to implement what I was looking for, thank you :slight_smile:

Having an official way to create a new op with updated wires has been on my mind for a while. Due to other people having the same need, I will see if I can bump it up in priority for development.

3 Likes