Source code for tensorcircuit.applications.layers

"""
Module for functions adding layers of circuits
"""
import itertools
import logging
import sys
from typing import Sequence, Union, Any, Optional, Tuple, List

import networkx as nx
import numpy as np
import tensorflow as tf

from ..circuit import Circuit
from ..densitymatrix import DMCircuit
from ..gates import num_to_tensor, array_to_tensor, _swap_matrix
from ..channels import depolarizingchannel
from ..abstractcircuit import sgates

logger = logging.getLogger(__name__)

try:
    import cirq
except ImportError as e:
    logger.warning(e)
    logger.warning("Therefore some functionality in %s may not work" % __name__)


thismodule = sys.modules[__name__]

Tensor = Any  # tf.Tensor
Graph = Any  # nx.Graph
Symbol = Any  # sympy.Symbol


def _resolve(symbol: Union[Symbol, Tensor], i: int = 0) -> Tensor:
    """
    Make sure the layer is compatible with both multi-param and single param requirements。

    What could be the input: list/tuple of sympy.symbol, tf.tensor with 1D or 0D shape
    """
    if isinstance(symbol, list) or isinstance(symbol, tuple):
        return symbol[i]
    elif tf.is_tensor(symbol):  # tf.tensor of 1D or 2D
        if len(symbol.shape) == 1:
            return symbol[i]
        else:  # len(shape) == 0
            return symbol
    else:  # sympy.symbol
        return symbol


[docs]def generate_double_gate(gates: str) -> None: d1, d2 = gates[0], gates[1] def f( circuit: Circuit, qubit1: int, qubit2: int, symbol: Union[Tensor, float] ) -> Circuit: if d1 == "x": circuit.H(qubit1) # type: ignore elif d1 == "y": circuit.rx(qubit1, theta=num_to_tensor(-np.pi / 2)) # type: ignore if d2 == "x": circuit.H(qubit2) # type: ignore elif d2 == "y": circuit.rx(qubit2, theta=num_to_tensor(-np.pi / 2)) # type: ignore circuit.CNOT(qubit1, qubit2) # type: ignore circuit.rz(qubit2, theta=symbol) # type: ignore circuit.CNOT(qubit1, qubit2) # type: ignore if d1 == "x": circuit.H(qubit1) # type: ignore elif d1 == "y": circuit.rx(qubit1, theta=num_to_tensor(np.pi / 2)) # type: ignore if d2 == "x": circuit.H(qubit2) # type: ignore elif d2 == "y": circuit.rx(qubit2, theta=num_to_tensor(np.pi / 2)) # type: ignore return circuit f.__doc__ = """%sgate""" % gates setattr(thismodule, gates + "gate", f)
[docs]def generate_gate_layer(gate: str) -> None: r""" $$e^{-i\theta \sigma}$$ :param gate: :type gate: str :return: """ def f( circuit: Circuit, symbol: Union[Tensor, float] = None, g: Graph = None ) -> Circuit: # compatible with graph mode symbol0 = _resolve(symbol) if gate.lower() in sgates: for n in range(circuit._nqubits): getattr(circuit, gate)(n) else: for n in range(circuit._nqubits): getattr(circuit, gate)(n, theta=2 * symbol0) return circuit f.__doc__ = """%slayer""" % gate f.__repr__ = """%slayer""" % gate # type: ignore f.__trainable__ = False if gate in sgates else True # type: ignore setattr(thismodule, gate + "layer", f)
[docs]def generate_any_gate_layer(gate: str) -> None: r""" $$e^{-i\theta_i \sigma}$$ :param gate: :type gate: str :return: """ def f( circuit: Circuit, symbol: Union[Tensor, float] = None, g: Graph = None ) -> Circuit: # compatible with graph mode if gate.lower() in sgates: for n in range(circuit._nqubits): getattr(circuit, gate)(n) else: for n in range(circuit._nqubits): getattr(circuit, gate)(n, theta=2 * symbol[n]) # type: ignore return circuit f.__doc__ = """any%slayer""" % gate f.__repr__ = """any%slayer""" % gate # type: ignore f.__trainable__ = False if gate in sgates else True # type: ignore setattr(thismodule, "any" + gate + "layer", f)
[docs]def generate_any_double_gate_layer(gates: str) -> None: def f(circuit: Circuit, symbol: Union[Tensor, float], g: Graph = None) -> Circuit: if g is None: g = nx.complete_graph(circuit._nqubits) for i, e in enumerate(g.edges): qubit1, qubit2 = e getattr(thismodule, gates + "gate")( circuit, qubit1, qubit2, -symbol[i] * g[e[0]][e[1]].get("weight", 1.0) * 2, # type: ignore ) ## should be better as * 2 # e^{-i\theta H}, H=-ZZ return circuit f.__doc__ = """any%slayer""" % gates f.__repr__ = """any%slayer""" % gates # type: ignore f.__trainable__ = True # type: ignore setattr(thismodule, "any" + gates + "layer", f)
[docs]def generate_double_gate_layer(gates: str) -> None: def f(circuit: Circuit, symbol: Union[Tensor, float], g: Graph = None) -> Circuit: symbol0 = _resolve(symbol) if g is None: g = nx.complete_graph(circuit._nqubits) for e in g.edges: qubit1, qubit2 = e getattr(thismodule, gates + "gate")( circuit, qubit1, qubit2, -symbol0 * g[e[0]][e[1]].get("weight", 1.0) * 2 ) ## should be better as * 2 # e^{-i\theta H}, H=-ZZ return circuit f.__doc__ = """%slayer""" % gates f.__repr__ = """%slayer""" % gates # type: ignore f.__trainable__ = True # type: ignore setattr(thismodule, gates + "layer", f)
[docs]def generate_double_gate_layer_bitflip(gates: str) -> None: # deprecated, as API are consistent now for DMCircuit and Circuit def f( circuit: DMCircuit, symbol: Union[Tensor, float], g: Graph, *params: float ) -> DMCircuit: symbol0 = _resolve(symbol) for e in g.edges: qubit1, qubit2 = e getattr(thismodule, gates + "gate")( circuit, qubit1, qubit2, -symbol0 * g[e[0]][e[1]].get("weight", 1.0) * 2, ) ## should be better as * 2 # e^{-i\theta H}, H=-ZZ circuit.apply_general_kraus( depolarizingchannel(params[0], params[1], params[2]), [(e[0],)] ) circuit.apply_general_kraus( depolarizingchannel(params[0], params[1], params[2]), [(e[1],)] ) return circuit f.__doc__ = """%slayer_bitflip""" % gates f.__repr__ = """%slayer_bitflip""" % gates # type: ignore f.__trainable__ = True # type: ignore setattr(thismodule, gates + "layer_bitflip", f)
[docs]def generate_double_gate_layer_bitflip_mc(gates: str) -> None: def f( circuit: Circuit, symbol: Union[Tensor, float], g: Graph, *params: float ) -> Circuit: symbol0 = _resolve(symbol) for e in g.edges: qubit1, qubit2 = e getattr(thismodule, gates + "gate")( circuit, qubit1, qubit2, -symbol0 * g[e[0]][e[1]].get("weight", 1.0) * 2, ) ## should be better as * 2 # e^{-i\theta H}, H=-ZZ circuit.depolarizing(e[0], px=params[0], py=params[1], pz=params[2]) # type: ignore circuit.depolarizing(e[1], px=params[0], py=params[1], pz=params[2]) # type: ignore return circuit f.__doc__ = """%slayer_bitflip_mc""" % gates f.__repr__ = """%slayer_bitflip_mc""" % gates # type: ignore f.__trainable__ = True # type: ignore setattr(thismodule, gates + "layer_bitflip_mc", f)
[docs]def generate_any_double_gate_layer_bitflip_mc(gates: str) -> None: def f( circuit: Circuit, symbol: Union[Tensor, float], g: Graph = None, *params: float ) -> Circuit: if g is None: g = nx.complete_graph(circuit._nqubits) for i, e in enumerate(g.edges): qubit1, qubit2 = e getattr(thismodule, gates + "gate")( circuit, qubit1, qubit2, -symbol[i] * g[e[0]][e[1]].get("weight", 1.0) * 2, # type: ignore ) ## should be better as * 2 # e^{-i\theta H}, H=-ZZ circuit.depolarizing(e[0], px=params[0], py=params[1], pz=params[2]) # type: ignore circuit.depolarizing(e[1], px=params[0], py=params[1], pz=params[2]) # type: ignore return circuit f.__doc__ = """any%slayer_bitflip_mc""" % gates f.__repr__ = """any%slayer_bitflip_mc""" % gates # type: ignore f.__trainable__ = True # type: ignore setattr(thismodule, "any" + gates + "layer_bitflip_mc", f)
[docs]def generate_double_layer_block(gates: Tuple[str]) -> None: d1, d2 = gates[0], gates[1] # type: ignore def f(circuit: Circuit, symbol: Tensor, g: Graph = None) -> Circuit: if g is None: g = nx.complete_graph(circuit._nqubits) getattr(thismodule, d1 + "layer")(circuit, symbol[0], g) getattr(thismodule, d2 + "layer")(circuit, symbol[1], g) return circuit f.__doc__ = """%s_%s_block""" % (d1, d2) f.__repr__ = """%s_%s_block""" % (d1, d2) # type: ignore f.__trainable__ = False if (d1 in sgates) and (d2 in sgates) else True # type: ignore setattr(thismodule, "%s_%s_block" % (d1, d2), f)
[docs]def anyswaplayer(circuit: Circuit, symbol: Tensor, g: Graph) -> Circuit: for i, e in enumerate(g.edges): qubit1, qubit2 = e circuit.exp1( # type: ignore qubit1, qubit2, unitary=array_to_tensor(_swap_matrix), theta=symbol[i] * g[e[0]][e[1]].get("weight", 1.0), ) return circuit
[docs]def anyswaplayer_bitflip_mc( circuit: Circuit, symbol: Tensor, g: Graph, px: float, py: float, pz: float ) -> Circuit: for i, e in enumerate(g.edges): qubit1, qubit2 = e circuit.exp1( # type: ignore qubit1, qubit2, unitary=array_to_tensor(_swap_matrix), theta=symbol[i] * g[e[0]][e[1]].get("weight", 1.0), ) circuit.depolarizing(e[0], px=px, py=py, pz=pz) # type: ignore circuit.depolarizing(e[1], px=px, py=py, pz=pz) # type: ignore return circuit
for gate in ["rx", "ry", "rz", "H", "I"]: generate_gate_layer(gate) generate_any_gate_layer(gate) for gates in itertools.product(*[["x", "y", "z"] for _ in range(2)]): gates = gates[0] + gates[1] generate_double_gate(gates) # type: ignore generate_double_gate_layer(gates) # type: ignore generate_any_double_gate_layer(gates) # type: ignore generate_double_gate_layer_bitflip(gates) # type: ignore generate_double_gate_layer_bitflip_mc(gates) # type: ignore generate_any_double_gate_layer_bitflip_mc(gates) # type: ignore for gates in itertools.product( *[["rx", "ry", "rz", "xx", "yy", "zz"] for _ in range(2)] ): generate_double_layer_block(gates) # type: ignore
[docs]def bitfliplayer(ci: DMCircuit, g: Graph, px: float, py: float, pz: float) -> None: n = len(g.nodes) for i in range(n): ci.apply_general_kraus(depolarizingchannel(px, py, pz), [(i,)]) bitfliplayer.__repr__ = """bitfliplayer""" # type: ignore bitfliplayer.__trainable__ = True # type: ignore
[docs]def bitfliplayer_mc(ci: Circuit, g: Graph, px: float, py: float, pz: float) -> None: n = len(g.nodes) for i in range(n): ci.depolarizing(i, px=px, py=py, pz=pz) # type: ignore bitfliplayer.__repr__ = """bitfliplayer_mc""" # type: ignore bitfliplayer.__trainable__ = True # type: ignore
# TODO(@refraction-ray): should move and refactor the above functions to templates/layers. ## below is similar layer but in cirq API instead of tensrocircuit native API ## special notes to the API, the arguments order are different due to historical reason compared to tc layers API ## and we have no attention to further maintain the cirq codebase below, availability is not guaranteend
[docs]def generate_qubits(g: Graph) -> List[Any]: return sorted([v for _, v in g.nodes.data("qubit")])
try: basis_rotation = { "x": (cirq.H, cirq.H), "y": (cirq.rx(-np.pi / 2), cirq.rx(np.pi / 2)), "z": None, }
[docs] def generate_cirq_double_gate(gates: str) -> None: d1, d2 = gates[0], gates[1] r1, r2 = basis_rotation[d1], basis_rotation[d2] def f( circuit: cirq.Circuit, qubit1: cirq.GridQubit, qubit2: cirq.GridQubit, symbol: Symbol, ) -> cirq.Circuit: if r1 is not None: circuit.append(r1[0](qubit1)) if r2 is not None: circuit.append(r2[0](qubit2)) circuit.append(cirq.CNOT(qubit1, qubit2)) circuit.append(cirq.rz(symbol)(qubit2)) circuit.append(cirq.CNOT(qubit1, qubit2)) if r1 is not None: circuit.append(r1[1](qubit1)) if r2 is not None: circuit.append(r2[1](qubit2)) return circuit f.__doc__ = """%sgate""" % gates setattr(thismodule, "cirq" + gates + "gate", f)
[docs] def cirqswapgate( circuit: cirq.Circuit, qubit1: cirq.GridQubit, qubit2: cirq.GridQubit, symbol: Symbol, ) -> cirq.Circuit: circuit.append(cirq.SwapPowGate(exponent=symbol)(qubit1, qubit2)) return circuit
[docs] def cirqcnotgate( circuit: cirq.Circuit, qubit1: cirq.GridQubit, qubit2: cirq.GridQubit, symbol: Symbol, ) -> cirq.Circuit: circuit.append(cirq.CNOT(qubit1, qubit2)) return circuit
[docs] def generate_cirq_gate_layer(gate: str) -> None: r""" $$e^{-i\theta \sigma}$$ :param gate: :type gate: str :return: """ def f( circuit: cirq.Circuit, g: Graph, symbol: Symbol, qubits: Optional[Sequence[Any]] = None, ) -> cirq.Circuit: symbol0 = _resolve(symbol[0]) if not qubits: qubits = generate_qubits(g) rotation = getattr(cirq, gate) if isinstance(rotation, cirq.Gate): circuit.append(rotation.on_each(qubits)) else: # function circuit.append(rotation(2.0 * symbol0).on_each(qubits)) return circuit f.__doc__ = """%slayer""" % gate f.__repr__ = """%slayer""" % gate # type: ignore f.__trainable__ = False if isinstance(getattr(cirq, gate), cirq.Gate) else True # type: ignore setattr(thismodule, "cirq" + gate + "layer", f)
[docs] def generate_cirq_any_gate_layer(gate: str) -> None: r""" $$e^{-i\theta \sigma}$$ :param gate: :type gate: str :return: """ def f( circuit: cirq.Circuit, g: Graph, symbol: Symbol, qubits: Optional[Sequence[Any]] = None, ) -> cirq.Circuit: if not qubits: qubits = generate_qubits(g) rotation = getattr(cirq, gate) for i, q in enumerate(qubits): circuit.append(rotation(2.0 * symbol[i])(q)) return circuit f.__doc__ = """any%slayer""" % gate f.__repr__ = """any%slayer""" % gate # type: ignore f.__trainable__ = True # type: ignore setattr(thismodule, "cirqany" + gate + "layer", f)
[docs] def generate_cirq_double_gate_layer(gates: str) -> None: def f( circuit: cirq.Circuit, g: Graph, symbol: Symbol, qubits: Optional[Sequence[Any]] = None, ) -> cirq.Circuit: symbol0 = _resolve(symbol) for e in g.edges: qubit1 = g.nodes[e[0]]["qubit"] qubit2 = g.nodes[e[1]]["qubit"] getattr(thismodule, "cirq" + gates + "gate")( circuit, qubit1, qubit2, -symbol0 * g[e[0]][e[1]]["weight"] * 2 ) ## should be better as * 2 # e^{-i\theta H}, H=-ZZ return circuit f.__doc__ = """%slayer""" % gates f.__repr__ = """%slayer""" % gates # type: ignore f.__trainable__ = True # type: ignore setattr(thismodule, "cirq" + gates + "layer", f)
[docs] def generate_cirq_any_double_gate_layer(gates: str) -> None: """ The following function should be used to generate layers with special case. As its soundness depends on the nature of the task or problem, it doesn't always make sense. :param gates: :type gates: str :return: """ def f( circuit: cirq.Circuit, g: Graph, symbol: Symbol, qubits: Optional[Sequence[Any]] = None, ) -> cirq.Circuit: for i, e in enumerate(g.edges): qubit1 = g.nodes[e[0]]["qubit"] qubit2 = g.nodes[e[1]]["qubit"] getattr(thismodule, "cirq" + gates + "gate")( circuit, qubit1, qubit2, -symbol[i] * g[e[0]][e[1]]["weight"] * 2 ) ## should be better as * 2 # e^{-i\theta H}, H=-ZZ return circuit f.__doc__ = """any%slayer""" % gates f.__repr__ = """any%slayer""" % gates # type: ignore f.__trainable__ = True # type: ignore setattr(thismodule, "cirqany" + gates + "layer", f)
for gate in ["rx", "ry", "rz", "H"]: generate_cirq_gate_layer(gate) if gate != "H": generate_cirq_any_gate_layer(gate) for gates in itertools.product(*[["x", "y", "z"] for _ in range(2)]): gates = gates[0] + gates[1] generate_cirq_double_gate(gates) # type: ignore generate_cirq_double_gate_layer(gates) # type: ignore generate_cirq_any_double_gate_layer(gates) # type: ignore generate_cirq_double_gate_layer("swap") generate_cirq_any_double_gate_layer("swap") generate_cirq_double_gate_layer("cnot") except NameError as e: logger.warning(e) logger.warning("cirq layer generation disabled")