"""
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)