qml.broadcast

broadcast(unitary, wires, pattern, parameters=None, kwargs=None)[source]

Applies a unitary multiple times to a specific pattern of wires.

The unitary, defined by the argument unitary, is either a quantum operation (such as RX()), or a user-supplied template. Depending on the chosen pattern, unitary is applied to a wire or a subset of wires:

  • pattern="single" applies a single-wire unitary to each one of the \(M\) wires:

    ../../_images/broadcast_single.png
  • pattern="double" applies a two-wire unitary to \(\lfloor \frac{M}{2} \rfloor\) subsequent pairs of wires:

    ../../_images/broadcast_double.png
  • pattern="double_odd" applies a two-wire unitary to \(\lfloor \frac{M-1}{2} \rfloor\) subsequent pairs of wires, starting with the second wire:

    ../../_images/broadcast_double_odd.png
  • pattern="chain" applies a two-wire unitary to all \(M-1\) neighbouring pairs of wires:

    ../../_images/broadcast_chain.png
  • pattern="ring" applies a two-wire unitary to all \(M\) neighbouring pairs of wires, where the last wire is considered to be a neighbour to the first one:

    ../../_images/broadcast_ring.png

    Note

    For 2 wires, the ring pattern is automatically replaced by pattern = 'chain' to avoid a mere repetition of the unitary.

  • pattern="pyramid" applies a two-wire unitary to wire pairs shaped in a pyramid declining to the right:

    ../../_images/broadcast_pyramid.png
  • pattern="all_to_all" applies a two-wire unitary to wire pairs that connect all wires to each other:

    ../../_images/broadcast_alltoall.png
  • A custom pattern can be passed by providing a list of wire lists to pattern. The unitary is applied to each set of wires specified in the list.

    ../../_images/broadcast_custom.png

Each unitary may depend on a different set of parameters. These are passed as a list by the parameters argument.

For more details, see Usage Details below.

Parameters
  • unitary (func) – quantum gate or template

  • pattern (str) – specifies the wire pattern of the broadcast

  • parameters (list) – sequence of parameters for each gate applied

  • wires (Iterable or Wires) – Wires that the template acts on. Accepts an iterable of numbers or strings, or a Wires object.

  • kwargs (dict) – dictionary of auxilliary parameters for unitary

Raises

ValueError – if inputs do not have the correct format

Broadcasting single gates

In the simplest case the unitary is typically an Operation() object implementing a quantum gate.

import pennylane as qml
from pennylane import broadcast

dev = qml.device('default.qubit', wires=3)

@qml.qnode(dev)
def circuit(pars):
    broadcast(unitary=qml.RX, pattern="single", wires=[0,1,2], parameters=pars)
    return qml.expval(qml.PauliZ(0))

circuit([1, 1, 2])

This is equivalent to the following circuit:

@qml.qnode(dev)
def circuit(pars):
    qml.RX(pars[0], wires=[0])
    qml.RX(pars[1], wires=[1])
    qml.RX(pars[2], wires=[2])
    return qml.expval(qml.PauliZ(0))

circuit([1, 1, 2])

Broadcasting templates

Alternatively, one can broadcast a built-in or user-defined template:

from pennylane.templates import template

@template
def mytemplate(pars, wires):
    qml.Hadamard(wires=wires)
    qml.RY(pars, wires=wires)

dev = qml.device('default.qubit', wires=3)

@qml.qnode(dev)
def circuit(pars):
    broadcast(unitary=mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
    return qml.expval(qml.PauliZ(0))

print(circuit([1, 1, 0.1]))

Constant unitaries

If the unitary argument does not take parameters, no parameters argument is passed to broadcast():

dev = qml.device('default.qubit', wires=3)

@qml.qnode(dev)
def circuit():
    broadcast(unitary=qml.Hadamard, pattern="single", wires=[0,1,2])
    return qml.expval(qml.PauliZ(0))

circuit()

Multiple parameters in unitary

The unitary, whether it is a single gate or a user-defined template, can take multiple parameters. For example:

from pennylane.templates import template

@template
def mytemplate(pars1, pars2, wires):
    qml.Hadamard(wires=wires)
    qml.RY(pars1, wires=wires)
    qml.RX(pars2, wires=wires)

@qml.qnode(dev)
def circuit(pars):
    broadcast(unitary=mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
    return qml.expval(qml.PauliZ(0))

circuit([[1, 1], [2, 1], [0.1, 1]])

In general, the unitary takes D parameters and must have the following signature:

unitary(parameter1, parameter2, ... parameterD, wires, **kwargs)

If unitary does not depend on parameters (\(D=0\)), the signature is

unitary(wires, **kwargs)

As a result, parameters must be a list or array of length-\(D\) lists or arrays.

If \(D\) becomes large, the signature can be simplified by wrapping each entry in parameters:

@template
def mytemplate(pars, wires):
    qml.Hadamard(wires=wires)
    qml.RY(pars[0], wires=wires)
    qml.RX(pars[1], wires=wires)

@qml.qnode(dev)
def circuit(pars):
    broadcast(unitary=mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
    return qml.expval(qml.PauliZ(0))

print(circuit([[[1, 1]], [[2, 1]], [[0.1, 1]]]))

If the number of parameters for each wire does not match the unitary, an error gets thrown:

@template
def mytemplate(pars1, pars2, wires):
    qml.Hadamard(wires=wires)
    qml.RY(pars1, wires=wires)
    qml.RX(pars2, wires=wires)

@qml.qnode(dev)
def circuit(pars):
    broadcast(unitary=mytemplate, pattern="single", wires=[0, 1, 2], parameters=pars)
    return qml.expval(qml.PauliZ(0))
>>> circuit([1, 2, 3]))
TypeError: mytemplate() missing 1 required positional argument: 'pars2'

Keyword arguments

The unitary can be a template that takes additional keyword arguments.

@template
def mytemplate(wires, h=True):
    if h:
        qml.Hadamard(wires=wires)
    qml.T(wires=wires)

@qml.qnode(dev)
def circuit(hadamard=None):
    broadcast(unitary=mytemplate, pattern="single", wires=[0, 1, 2], kwargs={'h': hadamard})
    return qml.expval(qml.PauliZ(0))

circuit(hadamard=False)

Different patterns

The basic usage of the different patterns works as follows:

  • Double pattern

    dev = qml.device('default.qubit', wires=4)
    
    @qml.qnode(dev)
    def circuit(pars):
        broadcast(unitary=qml.CRot, pattern='double',
                  wires=[0,1,2,3], parameters=pars)
        return qml.expval(qml.PauliZ(0))
    
    pars1 = [-1, 2.5, 3]
    pars2 = [-1, 4, 2]
    
    circuit([pars1, pars2])
    
  • Double-odd pattern

    dev = qml.device('default.qubit', wires=4)
    
    @qml.qnode(dev)
    def circuit(pars):
        broadcast(unitary=qml.CRot, pattern='double_odd',
                  wires=[0,1,2,3], parameters=pars)
        return qml.expval(qml.PauliZ(0))
    
    pars1 = [-5.3, 2.3, 3]
    
    circuit([pars1])
    
  • Chain pattern

    dev = qml.device('default.qubit', wires=4)
    
    @qml.qnode(dev)
    def circuit(pars):
        broadcast(unitary=qml.CRot, pattern='chain',
                  wires=[0,1,2,3], parameters=pars)
        return qml.expval(qml.PauliZ(0))
    
    pars1 = [1.8, 2, 3]
    pars2 = [-1, 3, 1]
    pars3 = [2, -1.2, 4]
    
    circuit([pars1, pars2, pars3])
    
  • Ring pattern

    In general, the number of parameter sequences has to match the number of wires:

    dev = qml.device('default.qubit', wires=3)
    
    @qml.qnode(dev)
    def circuit(pars):
        broadcast(unitary=qml.CRot, pattern='ring',
                  wires=[0,1,2], parameters=pars)
        return qml.expval(qml.PauliZ(0))
    
    pars1 = [1, -2.2, 3]
    pars2 = [-1, 3, 1]
    pars3 = [2.6, 1, 4]
    
    circuit([pars1, pars2, pars3])
    

    However, there is an exception for 2 wires, where only one set of parameters is needed. This avoids repeating a gate over the same wires twice:

    dev = qml.device('default.qubit', wires=2)
    
    @qml.qnode(dev)
    def circuit(pars):
        broadcast(unitary=qml.CRot, pattern='ring',
                  wires=[0,1], parameters=pars)
        return qml.expval(qml.PauliZ(0))
    
    pars1 = [-3.2, 2, 1.2]
    
    circuit([pars1])
    
  • Pyramid pattern

    dev = qml.device('default.qubit', wires=4)
    
    @qml.qnode(dev)
    def circuit(pars):
        broadcast(unitary=qml.CRot, pattern='pyramid',
                  wires=[0,1,2,3], parameters=pars)
        return qml.expval(qml.PauliZ(0))
    
    pars1 = [1.1, 2, 3]
    pars2 = [-1, 3, 1]
    pars3 = [2, 1, 4.2]
    
    circuit([pars1, pars2, pars3])
    
  • All-to-all pattern

    dev = qml.device('default.qubit', wires=4)
    
    @qml.qnode(dev)
    def circuit(pars):
        broadcast(unitary=qml.CRot, pattern='ring',
                  wires=[0,1,2,3], parameters=pars)
        return qml.expval(qml.PauliZ(0))
    
    pars1 = [1, 2, 3]
    pars2 = [-1, 3, 1]
    pars3 = [2, 1, 4]
    pars4 = [-1, -2, -3]
    pars5 = [2, 1, 4]
    pars6 = [3, -2, -3]
    
    circuit([pars1, pars2, pars3, pars4, pars5, pars6])
    
  • Custom pattern

    For a custom pattern, the wire lists for each application of the unitary is passed to pattern:

    dev = qml.device('default.qubit', wires=5)
    
    pattern = [[0, 1], [3, 4]]
    
    @qml.qnode(dev)
    def circuit():
        broadcast(unitary=qml.CNOT, pattern=pattern,
                  wires=range(5))
        return qml.expval(qml.PauliZ(0))
    
    circuit()
    

    When using a parametrized unitary, make sure that the number of wire lists in pattern corresponds to the number of parameters in parameters.

    pattern = [[0, 1], [3, 4]]
    
    @qml.qnode(dev)
    def circuit(pars):
        broadcast(unitary=qml.CRot, pattern=pattern,
                  wires=range(5), parameters=pars)
        return qml.expval(qml.PauliZ(0))
    
    pars1 = [1, 2, 3]
    pars2 = [-1, 3, 1]
    pars = [pars1, pars2]
    
    assert len(pars) == len(pattern)
    
    circuit(pars)