Source code for tensorcircuit.compiler.simple_compiler

"""
Very simple transformations that qiskit may even fail or hard to control
"""

from typing import Any, Dict, List, Optional, Tuple, Union
from copy import copy

import numpy as np

from ..abstractcircuit import AbstractCircuit
from ..cons import backend
from ..quantum import QuOperator
from .. import gates
from ..utils import is_sequence


[docs]def replace_r(circuit: AbstractCircuit, **kws: Any) -> AbstractCircuit: qir = circuit.to_qir() c: Any = type(circuit)(**circuit.circuit_param) for d in qir: if "parameters" not in d: c.apply_general_gate_delayed(d["gatef"], d["name"], mpo=d["mpo"])( c, *d["index"], split=d["split"] ) else: if d["gatef"].n == "rx": c.h(*d["index"]) c.rz(*d["index"], theta=d["parameters"].get("theta", 0.0)) c.h(*d["index"]) elif d["gatef"].n == "ry": c.sd(*d["index"]) c.h(*d["index"]) c.rz(*d["index"], theta=d["parameters"].get("theta", 0.0)) c.h(*d["index"]) c.s(*d["index"]) elif d["gatef"].n == "rzz": c.cx(*d["index"]) c.rz(d["index"][1], theta=d["parameters"].get("theta", 0.0)) c.cx(*d["index"]) elif d["gatef"].n == "rxx": c.h(d["index"][0]) c.h(d["index"][1]) c.cx(*d["index"]) c.rz(d["index"][1], theta=d["parameters"].get("theta", 0.0)) c.cx(*d["index"]) c.h(d["index"][0]) c.h(d["index"][1]) elif d["gatef"].n == "ryy": c.sd(d["index"][0]) c.sd(d["index"][1]) c.h(d["index"][0]) c.h(d["index"][1]) c.cx(*d["index"]) c.rz(d["index"][1], theta=d["parameters"].get("theta", 0.0)) c.cx(*d["index"]) c.h(d["index"][0]) c.h(d["index"][1]) c.s(d["index"][0]) c.s(d["index"][1]) else: c.apply_general_variable_gate_delayed( d["gatef"], d["name"], mpo=d["mpo"] )(c, *d["index"], **d["parameters"], split=d["split"]) return c # type: ignore
[docs]def replace_u(circuit: AbstractCircuit, **kws: Any) -> AbstractCircuit: qir = circuit.to_qir() c: Any = type(circuit)(**circuit.circuit_param) for d in qir: if "parameters" not in d: c.apply_general_gate_delayed(d["gatef"], d["name"], mpo=d["mpo"])( c, *d["index"], split=d["split"] ) else: if d["gatef"].n == "u": c.rz(*d["index"], theta=d["parameters"].get("lbd", 0) - np.pi / 2) c.h(*d["index"]) c.rz(*d["index"], theta=d["parameters"].get("theta", 0)) c.h(*d["index"]) c.rz(*d["index"], theta=np.pi / 2 + d["parameters"].get("phi", 0)) else: c.apply_general_variable_gate_delayed( d["gatef"], d["name"], mpo=d["mpo"] )(c, *d["index"], **d["parameters"], split=d["split"]) return c # type: ignore
def _get_matrix(qir_item: Dict[str, Any]) -> Any: if "gate" in qir_item: op = qir_item["gate"] else: op = qir_item["gatef"](**qir_item["parameters"]) if isinstance(op, QuOperator): m = backend.numpy(op.eval_matrix()) else: m = backend.numpy(backend.reshapem(op.tensor)) return m
[docs]def prune( circuit: Union[AbstractCircuit, List[Dict[str, Any]]], rtol: float = 1e-3, atol: float = 1e-3, **kws: Any ) -> Any: if isinstance(circuit, list): qir = circuit output = "qir" else: qir = circuit.to_qir() output = "tc" if output in ["tc", "circuit"]: c: Any = type(circuit)(**circuit.circuit_param) # type: ignore for d in qir: m = _get_matrix(d) if not np.allclose( m / (m[0, 0] + 1e-8), np.eye(m.shape[0]), rtol=rtol, atol=atol ): # upto a phase if "parameters" not in d: c.apply_general_gate_delayed(d["gatef"], d["name"], mpo=d["mpo"])( c, *d["index"], split=d["split"] ) else: c.apply_general_variable_gate_delayed( d["gatef"], d["name"], mpo=d["mpo"] )(c, *d["index"], **d["parameters"], split=d["split"]) return c elif output in ["qir"]: nqir = [] for d in qir: m = _get_matrix(d) if not np.allclose( m / (m[0, 0] + 1e-8), np.eye(m.shape[0]), rtol=rtol, atol=atol ): nqir.append(d) # upto a phase return nqir
# upto global phase default_merge_rules = { ("s", "s"): "z", ("sd", "sd"): "z", ("t", "t"): "s", ("td", "td"): "sd", ("x", "y"): "z", ("y", "x"): "z", ("x", "z"): "y", ("z", "x"): "y", ("z", "y"): "x", ("y", "z"): "x", ("x", "x"): "i", ("y", "y"): "i", ("z", "z"): "i", ("h", "h"): "i", ("rz", "rz"): "rz", ("rx", "rx"): "rx", ("ry", "ry"): "rx", ("rzz", "rzz"): "rzz", ("rxx", "rxx"): "rxx", ("ryy", "ryy"): "ryy", ("crz", "crz"): "crz", ("crx", "crx"): "crx", ("cry", "cry"): "crx", ("cnot", "cnot"): "i", ("cz", "cz"): "i", ("cy", "cy"): "i", } def _find_next(qir: List[Dict[str, Any]], i: int) -> Optional[int]: # if qir[i] is None: # return None index = qir[i]["index"] for j, item in enumerate(qir[i + 1 :]): # if item is not None: if item["index"] == index: return j + i + 1 for ind in item["index"]: if ind in index: return None return None def _get_theta(qir_item: Dict[str, Any]) -> float: theta = qir_item["parameters"].get("theta", 0.0) if is_sequence(theta) and len(theta) == 1: return theta[0] # type: ignore return theta # type: ignore def _merge( qir: List[Dict[str, Any]], rules: Dict[Tuple[str, ...], str] ) -> Tuple[List[Dict[str, Any]], bool]: i = 0 flg = False while i < len(qir) - 1: j = _find_next(qir, i) if j is not None: if (qir[i]["gatef"].n, qir[j]["gatef"].n) in rules: nn = rules[(qir[i]["gatef"].n, qir[j]["gatef"].n)] if nn == "i": del qir[i] del qir[j - 1] # qir[i] = None # qir[j] = None else: param = {} if nn.startswith("r") or nn.startswith("cr"): param = {"theta": _get_theta(qir[i]) + _get_theta(qir[j])} qir[i] = { "gatef": getattr(gates, nn), "name": nn, "mpo": False, "split": False, "parameters": param, "index": qir[i]["index"], } del qir[j] # qir[j] = None flg = True # return qir, flg elif ( qir[i]["gatef"].n == qir[j]["gatef"].n + "d" or qir[i]["gatef"].n + "d" == qir[j]["gatef"].n ): del qir[i] del qir[j - 1] # qir[i] = None # qir[j] = None flg = True # return qir, True i += 1 return qir, flg
[docs]def merge( circuit: Union[AbstractCircuit, List[Dict[str, Any]]], rules: Optional[Dict[Tuple[str, ...], str]] = None, **kws: Any ) -> Any: merge_rules = copy(default_merge_rules) if rules is not None: merge_rules.update(rules) # type: ignore if isinstance(circuit, list): qir = circuit output = "qir" else: qir = circuit.to_qir() output = "tc" flg = True while flg: qir, flg = _merge(qir, merge_rules) # type: ignore if output in ["qir"]: return qir elif output in ["tc", "circuit"]: c: Any = type(circuit).from_qir(qir, circuit.circuit_param) # type: ignore return c
[docs]def simple_compile( circuit: Any, info: Optional[Dict[str, Any]] = None, output: str = "tc", compiled_options: Optional[Dict[str, Any]] = None, ) -> Any: if compiled_options is None: compiled_options = {} len0 = len(circuit.to_qir()) for d in circuit._extra_qir: if d["pos"] < len0 - 1: raise ValueError( "TC's simple compiler doesn't support measurement/reset instructions \ in the middle of the circuit" ) c = replace_r(circuit, **compiled_options) c = replace_u(c, **compiled_options) qir = c.to_qir() len0 = len(qir) qir = merge(qir, **compiled_options) qir = prune(qir, **compiled_options) len1 = len(qir) while len1 != len0: len0 = len1 qir = merge(qir, **compiled_options) if len(qir) == len0: break qir = prune(qir, **compiled_options) len1 = len(qir) c = type(circuit).from_qir(qir, circuit.circuit_param) for d in circuit._extra_qir: d["pos"] = len1 c._extra_qir.append(d) return (c, info)