Module qose.featuremaps

Feature maps


This module contains feature maps. Each feature map function takes an input vector x and weights, and constructs a circuit that maps these two to a quantum state. The feature map function can be called in a qnode. A feature map has the following positional arguments: weights, x, wires. It can have optional keyword arguments. Each feature map comes with a function that generates initial parameters for that particular feature map. The function get_embedding_info can be used to get information about the number of parameters and gates for any of the embeddings

Expand source code
"""
Feature maps
************
This module contains feature maps. Each feature map function
takes an input vector x and weights, and constructs a circuit that maps
these two to a quantum state. The feature map function can be called in a qnode.
A feature map has the following positional arguments: weights, x, wires. It can have optional
keyword arguments.
Each feature map comes with a function that generates initial parameters
for that particular feature map.
The function get_embedding_info can be used to get information about the number of
parameters and  gates for any of the embeddings
"""
import numpy as np
import pennylane as qml


def _entanglerZ(w, wire1, wire2):
    """

    Args:
      w: 
      wire1: 
      wire2: 

    Returns:

    """
    qml.CNOT(wires=[wire2, wire1])
    qml.RZ(2 * w, wires=wire1)
    qml.CNOT(wires=[wire2, wire1])


def qaoa(x, wires, n_layers=1):
    """Ising-coupling QAOA feature map, according to arXiv1812.11075.
    Example one layer, 4 wires, 2 inputs:
    
     |0> - R_x(x1) - |^| -------- |_| - R_y(w7)  -
     |0> - R_x(x2) - |_|-|^| ---------- R_y(w8)  -
     |0> - ___H___ ------|_|-|^| ------ R_y(w9)  -
     |0> - ___H___ ----------|_| -|^| - R_y(w10) -
    
    After the last layer, another block of R_x(x_i) rotations is applied.

    Args:
      weights: trainable weights of shape 2*n_layers*n_wires
      x: input, len(x) is <= len(wires)
      wires: list of wires on which the feature map acts
      n_layers: number of repetitions of the first layer (Default value = 1)

    Returns:

    """

    n_wires = len(wires)
    weights = pars_qaoa(n_wires, n_layers)

    if n_wires == 1:
        n_weights_needed = n_layers
    else:
        n_weights_needed = 2 * n_wires * n_layers

    if len(x) > n_wires:
        raise ValueError("Feat map can encode at most {} features (which is the "
                         "number of wires), got {}.".format(n_wires, len(x)))

    if len(weights) != n_weights_needed:
        raise ValueError("Feat map needs {} weights, got {}."
                         .format(n_weights_needed, len(weights)))

    for l in range(n_layers):

        # inputs
        for i in range(n_wires):
            # Either feed in feature
            if i < len(x):
                qml.RX(x[i], wires=wires[i])
            # or a Hadamard
            else:
                qml.Hadamard(wires=wires[i])

        # 1-d nearest neighbour coupling
        if n_wires == 1:
            qml.RY(weights[l], wires=wires[0])

        else:
            for i in range(n_wires):
                if i < n_wires - 1:
                    _entanglerZ(weights[l * 2 * n_wires + i], wires[i], wires[i + 1])
                else:
                    # enforce periodic boundary condition
                    _entanglerZ(weights[l * 2 * n_wires + i], wires[i], wires[0])
            # local fields
            for i in range(n_wires):
                qml.RY(weights[l * 2 * n_wires + n_wires + i], wires=wires[i])

    # repeat feature encoding once more at the end
    for i in range(n_wires):
        # Either feed in feature
        if i < len(x):
            qml.RX(x[i], wires=wires[i])
        # or a Hadamard
        else:
            qml.Hadamard(wires=wires[i])


def pars_qaoa(n_wires, n_layers):
    """Initial weight generator for 1-d qaoa feature map

    Args:
      n_wires: number of wires
      n_layers: number of layers

    Returns:
      array of weights

    """
    if n_wires == 1:
        return 0.001 * np.ones(n_layers)
    else:
        return 0.001 * np.ones(n_wires * n_layers * 2)


def XXZ(x, wires, n_layers=1):
    """

    Args:
      w_zz: trainable weights for ZZ gates
      w_rot: trainable weights for RX gates
      x: input, len(x) is <= len(wires)
      wires: list of wires on which the feature map acts
      n_layers: number of repetitions of the first layer (Default value = 1)

    Returns:

    """
    n_wires = len(wires)
    w_zz, w_rot = pars_xxz(x, n_wires, n_layers)

    if n_wires == 1:
        raise ValueError("use at least 2 wires to enable entangling gates")
    else:
        n_weights_needed = (n_wires + n_wires - len(x)) * n_layers

    if len(x) > n_wires:
        raise ValueError("Feat map can encode at most {} features (which is the "
                         "number of wires), got {}.".format(n_wires, len(x)))
    l_total = len(w_zz) + len(w_rot)
    if l_total != n_weights_needed:
        raise ValueError("Feat map needs {} number of weights, got {}."
                         .format(n_weights_needed, l_total))

    for l in range(n_layers):

        # hadamards
        for i in range(n_wires):
            qml.Hadamard(wires=wires[i])

        # nearest neighbour coupling

        for i in range(n_wires):
            if i < n_wires - 1:
                _entanglerZ(w_zz[l * n_wires + i], wires[i], wires[i + 1])
            else:
                # enforce periodic boundary condition
                _entanglerZ(w_zz[l * n_wires + i], wires[i], wires[0])

        # feature encoding
        for i in range(n_wires):
            # Either feed in feature
            if i < len(x):
                qml.RX(x[i], wires=wires[i])
            # or a Hadamard
            else:
                qml.RX(w_rot[i - l], wires=wires[i])


def pars_xxz(x, n_wires, n_layers):
    """Initial weight generator for xxz feature map

    Args:
      n_wires: number of wires
      n_layers: number of layers
      x: 

    Returns:
      array of weights

    """
    w_zz = 0.001 * np.ones(n_wires * n_layers)
    w_rot = 0.001 * np.ones((n_wires - len(x)) * n_layers)
    return w_zz, w_rot


def aspuru(x, wires, n_layers=1):
    """Circuits ID = 5 in arXiv:1905.10876 paper

    Args:
      weights: trainable weights
      x: input, len(x) is <= len(wires)
      wires: list of wires on which the feature map acts
      n_layers: number of repetitions of the first layer (Default value = 1)

    Returns:

    """
    data_size = len(x)
    n_wires = len(wires)
    weights = pars_aspuru(x, n_wires, n_layers)

    weights_each_layer = (n_wires * (n_wires + 3) - 2 * data_size)
    n_weights_needed = weights_each_layer * n_layers

    if len(x) > n_wires:
        raise ValueError("Feat map can encode at most {} features (which is the "
                         "number of wires), got {}.".format(n_wires, len(x)))

    if len(weights) != n_weights_needed:
        raise ValueError("Feat map needs {} weights, got {}."
                         .format(n_weights_needed, len(weights)))

    for l in range(n_layers):

        # inputs
        for i in range(data_size):
            if i < len(x):
                qml.RX(x[i], wires=wires[i])

        for i in range(len(x), n_wires):
            qml.RX(weights[weights_each_layer * l + i - data_size], wires=wires[i])

        for i in range(n_wires):
            qml.RZ(weights[weights_each_layer * l + n_wires - data_size + i], wires=wires[i])

        for i in reversed(range(n_wires)):
            for j in reversed(range(n_wires)):
                if j == i:
                    continue

                qml.CRZ(weights[weights_each_layer * l + 2 * n_wires - data_size + i * (n_wires - 1) + j],
                        wires=[wires[i], wires[j]])

        for i in range(data_size):
            qml.RX(x[i], wires=wires[i])

        for i in range(len(x), n_wires):
            qml.RX(weights[weights_each_layer * l + n_wires * (n_wires + 1) - data_size + i],
                   wires=wires[i])

        for i in range(n_wires):
            qml.RZ(weights[weights_each_layer * l + n_wires * (n_wires + 2) - 2 * data_size + i], wires=wires[i])


def pars_aspuru(x, n_wires, n_layers):
    """

    Args:
      x: 
      n_wires: 
      n_layers: 

    Returns:

    """
    weights_each_layer = (n_wires * (n_wires + 3) - 2 * len(x))

    return 0.001 * np.ones(n_layers * weights_each_layer)


def random_embed(x, wires, n_layers=1):
    """random enbedding circuit

    Args:
      weights: trainable weights
      x: input, len(x) is <= len(wires)
      wires: list of wires on which the feature map acts
      n_layers: number of repetitions of the first layer (Default value = 1)

    Returns:

    """
    n_wires = len(wires)
    weights = pars_random(x, n_wires, n_layers)

    n_weights_needed = n_layers * n_wires

    if len(weights) != n_weights_needed:
        raise ValueError("Feat map needs {} weights, got {}."
                         .format(n_weights_needed, len(weights)))

    gate_set = [qml.RX, qml.RY, qml.RZ]
    for l in range(n_layers):
        i = 0
        while i < len(x):
            gate = np.random.choice(gate_set)
            gate(x[i], wires=wires[i])
            i = i + 1
        for i in range(n_wires):
            gate = np.random.choice(gate_set)
            gate(weights[l * n_wires + i - n_wires], wires=wires[i])
        qml.broadcast(qml.CNOT, wires=range(n_wires), pattern="ring")


def pars_random(x, n_wires, n_layers):
    """

    Args:
      x: 
      n_wires: 
      n_layers: 

    Returns:

    """
    return 0.001 * np.ones(n_wires * n_layers)


def get_embedding_info(name, x, n_wires, n_layers):
    """get information about the number of weights, entangling gates, number of gates with
    input/non-trainable parameters and number of non-parametrized gates (hadamards) in an embedding

    Args:
      name: name of embedding used
      x: input, len(x) is <= len(wires)
      n_wires: number of wires used in the architecture
      n_layers: number of layers used in the architecture

    Returns:

    """
    rem = n_wires - len(x)
    if name == "qaoa":
        return 2 * n_wires * n_layers, n_wires * n_layers, (n_layers * len(x)) + len(x), (n_layers * rem) + rem
    if name == "xxz":
        return (n_wires + n_wires - len(x)) * n_layers, n_wires * n_layers, len(x) * n_layers, n_wires * n_layers
    if name == "aspuru":
        return n_layers * (n_wires * (n_wires + 3) - 2 * len(x)), (n_wires - 1) * n_wires, len(x) * 2 * n_layers, 0
    if name == "angle":
        return 0, 0, len(x), 0
    if name == "amplitude":
        return 0, 0, len(x), 0
    if name == "random":
        return n_layers * n_wires, n_layers * n_wires, n_layers * len(x), 0

Functions

def XXZ(x, wires, n_layers=1)

Args

w_zz
trainable weights for ZZ gates
w_rot
trainable weights for RX gates
x
input, len(x) is <= len(wires)
wires
list of wires on which the feature map acts
n_layers
number of repetitions of the first layer (Default value = 1)

Returns:

Expand source code
def XXZ(x, wires, n_layers=1):
    """

    Args:
      w_zz: trainable weights for ZZ gates
      w_rot: trainable weights for RX gates
      x: input, len(x) is <= len(wires)
      wires: list of wires on which the feature map acts
      n_layers: number of repetitions of the first layer (Default value = 1)

    Returns:

    """
    n_wires = len(wires)
    w_zz, w_rot = pars_xxz(x, n_wires, n_layers)

    if n_wires == 1:
        raise ValueError("use at least 2 wires to enable entangling gates")
    else:
        n_weights_needed = (n_wires + n_wires - len(x)) * n_layers

    if len(x) > n_wires:
        raise ValueError("Feat map can encode at most {} features (which is the "
                         "number of wires), got {}.".format(n_wires, len(x)))
    l_total = len(w_zz) + len(w_rot)
    if l_total != n_weights_needed:
        raise ValueError("Feat map needs {} number of weights, got {}."
                         .format(n_weights_needed, l_total))

    for l in range(n_layers):

        # hadamards
        for i in range(n_wires):
            qml.Hadamard(wires=wires[i])

        # nearest neighbour coupling

        for i in range(n_wires):
            if i < n_wires - 1:
                _entanglerZ(w_zz[l * n_wires + i], wires[i], wires[i + 1])
            else:
                # enforce periodic boundary condition
                _entanglerZ(w_zz[l * n_wires + i], wires[i], wires[0])

        # feature encoding
        for i in range(n_wires):
            # Either feed in feature
            if i < len(x):
                qml.RX(x[i], wires=wires[i])
            # or a Hadamard
            else:
                qml.RX(w_rot[i - l], wires=wires[i])
def aspuru(x, wires, n_layers=1)

Circuits ID = 5 in arXiv:1905.10876 paper

Args

weights
trainable weights
x
input, len(x) is <= len(wires)
wires
list of wires on which the feature map acts
n_layers
number of repetitions of the first layer (Default value = 1)

Returns:

Expand source code
def aspuru(x, wires, n_layers=1):
    """Circuits ID = 5 in arXiv:1905.10876 paper

    Args:
      weights: trainable weights
      x: input, len(x) is <= len(wires)
      wires: list of wires on which the feature map acts
      n_layers: number of repetitions of the first layer (Default value = 1)

    Returns:

    """
    data_size = len(x)
    n_wires = len(wires)
    weights = pars_aspuru(x, n_wires, n_layers)

    weights_each_layer = (n_wires * (n_wires + 3) - 2 * data_size)
    n_weights_needed = weights_each_layer * n_layers

    if len(x) > n_wires:
        raise ValueError("Feat map can encode at most {} features (which is the "
                         "number of wires), got {}.".format(n_wires, len(x)))

    if len(weights) != n_weights_needed:
        raise ValueError("Feat map needs {} weights, got {}."
                         .format(n_weights_needed, len(weights)))

    for l in range(n_layers):

        # inputs
        for i in range(data_size):
            if i < len(x):
                qml.RX(x[i], wires=wires[i])

        for i in range(len(x), n_wires):
            qml.RX(weights[weights_each_layer * l + i - data_size], wires=wires[i])

        for i in range(n_wires):
            qml.RZ(weights[weights_each_layer * l + n_wires - data_size + i], wires=wires[i])

        for i in reversed(range(n_wires)):
            for j in reversed(range(n_wires)):
                if j == i:
                    continue

                qml.CRZ(weights[weights_each_layer * l + 2 * n_wires - data_size + i * (n_wires - 1) + j],
                        wires=[wires[i], wires[j]])

        for i in range(data_size):
            qml.RX(x[i], wires=wires[i])

        for i in range(len(x), n_wires):
            qml.RX(weights[weights_each_layer * l + n_wires * (n_wires + 1) - data_size + i],
                   wires=wires[i])

        for i in range(n_wires):
            qml.RZ(weights[weights_each_layer * l + n_wires * (n_wires + 2) - 2 * data_size + i], wires=wires[i])
def get_embedding_info(name, x, n_wires, n_layers)

get information about the number of weights, entangling gates, number of gates with input/non-trainable parameters and number of non-parametrized gates (hadamards) in an embedding

Args

name
name of embedding used
x
input, len(x) is <= len(wires)
n_wires
number of wires used in the architecture
n_layers
number of layers used in the architecture

Returns:

Expand source code
def get_embedding_info(name, x, n_wires, n_layers):
    """get information about the number of weights, entangling gates, number of gates with
    input/non-trainable parameters and number of non-parametrized gates (hadamards) in an embedding

    Args:
      name: name of embedding used
      x: input, len(x) is <= len(wires)
      n_wires: number of wires used in the architecture
      n_layers: number of layers used in the architecture

    Returns:

    """
    rem = n_wires - len(x)
    if name == "qaoa":
        return 2 * n_wires * n_layers, n_wires * n_layers, (n_layers * len(x)) + len(x), (n_layers * rem) + rem
    if name == "xxz":
        return (n_wires + n_wires - len(x)) * n_layers, n_wires * n_layers, len(x) * n_layers, n_wires * n_layers
    if name == "aspuru":
        return n_layers * (n_wires * (n_wires + 3) - 2 * len(x)), (n_wires - 1) * n_wires, len(x) * 2 * n_layers, 0
    if name == "angle":
        return 0, 0, len(x), 0
    if name == "amplitude":
        return 0, 0, len(x), 0
    if name == "random":
        return n_layers * n_wires, n_layers * n_wires, n_layers * len(x), 0
def pars_aspuru(x, n_wires, n_layers)

Args

x
 
n_wires
 
n_layers
 

Returns:

Expand source code
def pars_aspuru(x, n_wires, n_layers):
    """

    Args:
      x: 
      n_wires: 
      n_layers: 

    Returns:

    """
    weights_each_layer = (n_wires * (n_wires + 3) - 2 * len(x))

    return 0.001 * np.ones(n_layers * weights_each_layer)
def pars_qaoa(n_wires, n_layers)

Initial weight generator for 1-d qaoa feature map

Args

n_wires
number of wires
n_layers
number of layers

Returns

array of weights

Expand source code
def pars_qaoa(n_wires, n_layers):
    """Initial weight generator for 1-d qaoa feature map

    Args:
      n_wires: number of wires
      n_layers: number of layers

    Returns:
      array of weights

    """
    if n_wires == 1:
        return 0.001 * np.ones(n_layers)
    else:
        return 0.001 * np.ones(n_wires * n_layers * 2)
def pars_random(x, n_wires, n_layers)

Args

x
 
n_wires
 
n_layers
 

Returns:

Expand source code
def pars_random(x, n_wires, n_layers):
    """

    Args:
      x: 
      n_wires: 
      n_layers: 

    Returns:

    """
    return 0.001 * np.ones(n_wires * n_layers)
def pars_xxz(x, n_wires, n_layers)

Initial weight generator for xxz feature map

Args

n_wires
number of wires
n_layers
number of layers
x
 

Returns

array of weights

Expand source code
def pars_xxz(x, n_wires, n_layers):
    """Initial weight generator for xxz feature map

    Args:
      n_wires: number of wires
      n_layers: number of layers
      x: 

    Returns:
      array of weights

    """
    w_zz = 0.001 * np.ones(n_wires * n_layers)
    w_rot = 0.001 * np.ones((n_wires - len(x)) * n_layers)
    return w_zz, w_rot
def qaoa(x, wires, n_layers=1)

Ising-coupling QAOA feature map, according to arXiv1812.11075. Example one layer, 4 wires, 2 inputs:

|0> - R_x(x1) - |^| -------- || - R_y(w7) - |0> - R_x(x2) - ||-|^| ---------- R_y(w8) - |0> - H ------||-|^| ------ R_y(w9) - |0> - ___H___ ----------|| -|^| - R_y(w10) -

After the last layer, another block of R_x(x_i) rotations is applied.

Args

weights
trainable weights of shape 2n_layersn_wires
x
input, len(x) is <= len(wires)
wires
list of wires on which the feature map acts
n_layers
number of repetitions of the first layer (Default value = 1)

Returns:

Expand source code
def qaoa(x, wires, n_layers=1):
    """Ising-coupling QAOA feature map, according to arXiv1812.11075.
    Example one layer, 4 wires, 2 inputs:
    
     |0> - R_x(x1) - |^| -------- |_| - R_y(w7)  -
     |0> - R_x(x2) - |_|-|^| ---------- R_y(w8)  -
     |0> - ___H___ ------|_|-|^| ------ R_y(w9)  -
     |0> - ___H___ ----------|_| -|^| - R_y(w10) -
    
    After the last layer, another block of R_x(x_i) rotations is applied.

    Args:
      weights: trainable weights of shape 2*n_layers*n_wires
      x: input, len(x) is <= len(wires)
      wires: list of wires on which the feature map acts
      n_layers: number of repetitions of the first layer (Default value = 1)

    Returns:

    """

    n_wires = len(wires)
    weights = pars_qaoa(n_wires, n_layers)

    if n_wires == 1:
        n_weights_needed = n_layers
    else:
        n_weights_needed = 2 * n_wires * n_layers

    if len(x) > n_wires:
        raise ValueError("Feat map can encode at most {} features (which is the "
                         "number of wires), got {}.".format(n_wires, len(x)))

    if len(weights) != n_weights_needed:
        raise ValueError("Feat map needs {} weights, got {}."
                         .format(n_weights_needed, len(weights)))

    for l in range(n_layers):

        # inputs
        for i in range(n_wires):
            # Either feed in feature
            if i < len(x):
                qml.RX(x[i], wires=wires[i])
            # or a Hadamard
            else:
                qml.Hadamard(wires=wires[i])

        # 1-d nearest neighbour coupling
        if n_wires == 1:
            qml.RY(weights[l], wires=wires[0])

        else:
            for i in range(n_wires):
                if i < n_wires - 1:
                    _entanglerZ(weights[l * 2 * n_wires + i], wires[i], wires[i + 1])
                else:
                    # enforce periodic boundary condition
                    _entanglerZ(weights[l * 2 * n_wires + i], wires[i], wires[0])
            # local fields
            for i in range(n_wires):
                qml.RY(weights[l * 2 * n_wires + n_wires + i], wires=wires[i])

    # repeat feature encoding once more at the end
    for i in range(n_wires):
        # Either feed in feature
        if i < len(x):
            qml.RX(x[i], wires=wires[i])
        # or a Hadamard
        else:
            qml.Hadamard(wires=wires[i])
def random_embed(x, wires, n_layers=1)

random enbedding circuit

Args

weights
trainable weights
x
input, len(x) is <= len(wires)
wires
list of wires on which the feature map acts
n_layers
number of repetitions of the first layer (Default value = 1)

Returns:

Expand source code
def random_embed(x, wires, n_layers=1):
    """random enbedding circuit

    Args:
      weights: trainable weights
      x: input, len(x) is <= len(wires)
      wires: list of wires on which the feature map acts
      n_layers: number of repetitions of the first layer (Default value = 1)

    Returns:

    """
    n_wires = len(wires)
    weights = pars_random(x, n_wires, n_layers)

    n_weights_needed = n_layers * n_wires

    if len(weights) != n_weights_needed:
        raise ValueError("Feat map needs {} weights, got {}."
                         .format(n_weights_needed, len(weights)))

    gate_set = [qml.RX, qml.RY, qml.RZ]
    for l in range(n_layers):
        i = 0
        while i < len(x):
            gate = np.random.choice(gate_set)
            gate(x[i], wires=wires[i])
            i = i + 1
        for i in range(n_wires):
            gate = np.random.choice(gate_set)
            gate(weights[l * n_wires + i - n_wires], wires=wires[i])
        qml.broadcast(qml.CNOT, wires=range(n_wires), pattern="ring")