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"): "ry", ("rzz", "rzz"): "rzz", ("rxx", "rxx"): "rxx", ("ryy", "ryy"): "ryy", ("crz", "crz"): "crz", ("crx", "crx"): "crx", ("cry", "cry"): "cry", ("cnot", "cnot"): "i", ("cz", "cz"): "i", ("cy", "cy"): "i", } def _find_next(qir: List[Dict[str, Any]], i: int) -> Optional[int]: index = qir[i]["index"] for j, item in enumerate(qir[i + 1 :]): 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] 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": None, "parameters": param, "index": qir[i]["index"], } del qir[j] flg = True 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] flg = 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): # copy: _merge mutates the list in place (del/reassign), so never alias # the caller's list qir = list(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, ) -> Tuple[Any, Optional[Dict[str, Any]]]: """ Compile a circuit by merging/replacing/pruning its QIR. Returns ``(circuit, info)`` — a tuple, not a bare circuit. ``info`` is the caller-supplied dict passed through unchanged. Unpack: ``c, info = simple_compile(c)``. :param circuit: circuit to compile :param info: optional dict, returned unchanged :param output: output format (``"tc"`` etc.) :param compiled_options: extra options forwarded to merge/prune/replace passes :return: ``(compiled_circuit, info)`` """ 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 " f"instructions in the middle of the circuit: found a " f"{d.get('name', 'measurement/reset')!r} instruction at " f"position {d.get('pos')} (qir length={len0}, only the final " f"position {len0 - 1} is allowed for such instructions)." ) 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)