"""String Operator - Utilities (:mod:`qurry.qurries.string_operator.utils`)"""
from typing import Union, Literal, TypedDict, Optional
import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister
StringOperatorUnits = Optional[tuple[Literal["rx", "ry", "rz"], float]]
"""Available string operator units.
- tuple[Literal["rx", "ry", "rz"], float]: A tuple containing:
- "rx": Rotation around the x-axis.
- "ry": Rotation around the y-axis.
- "rz": Rotation around the z-axis.
- float: The angle of rotation in radians.
and do the measurement on the qubit.
- None: No operation and measurement is performed on the qubit.
"""
[docs]
class StringOperatorLib(TypedDict):
r"""String Operator Library.
Which is defined by following the equation:
.. math::
S^O(g) = \langle\psi|\hat{O_i}
\left(\prod_{j = i+2}^{k-2} \hat{\sigma}_j^x \right) \hat{O_i}|\psi\rangle
- i: When :math:`\hat{O_i} = \hat{O'_k} = \mathbb{1}`,
denoted as :math:`S^{\mathbb{1}}(g)`, i for identity operator.
- zy: When :math:`\hat{O_i} = \hat{\sigma}_i^z\hat{\sigma}_{i+1}^y` and
:math:`\hat{O'_i} = \hat{\sigma}_{k-1}^y\hat{\sigma}_k^y`,
denoted as :math:`S^{\sigma^{zy}}(g)` for ZY operator.
"""
i: dict[Union[int, Literal["filling"]], StringOperatorUnits]
r"""Identity string operator.
.. math::
\hat{O_i} = \hat{O'_k} = \mathbb{1}
"""
zy: dict[Union[int, Literal["filling"]], StringOperatorUnits]
r"""ZY string operator.
.. math::
\hat{O'_i} = \hat{\sigma}_{k-1}^y\hat{\sigma}_k^y,
\hat{O'_i} = \hat{\sigma}_{k-1}^y\hat{\sigma}_k^y
"""
StringOperatorLibType = Literal["i", "zy"]
"""Available string operator types.
- "i": Identity string operator.
- "zy": ZY string operator.
"""
StringOperatorDirection = Literal["x", "y"]
"""Available string operator directions.
- "x": String operator in the X direction.
- "y": String operator in the Y direction.
"""
STRING_OPERATOR: dict[StringOperatorDirection, StringOperatorLib] = {
"x": {
"i": {
0: None,
"filling": ("ry", -np.pi / 2),
-1: None,
},
"zy": {
0: ("rz", 0),
1: ("rx", np.pi / 2),
"filling": ("ry", -np.pi / 2),
-2: ("rx", np.pi / 2),
-1: ("rz", 0),
},
},
"y": {
"i": {
0: None,
"filling": ("rx", np.pi / 2),
-1: None,
},
"zy": {
0: ("rz", 0),
1: ("ry", -np.pi / 2),
"filling": ("rx", np.pi / 2),
-2: ("ry", -np.pi / 2),
-1: ("rz", 0),
},
},
}
r"""Available string operator library.
- "x": Available string operator library for the X direction.
- "y": Available string operator library for the Y direction.
.. math::
S^O(g) = \langle\psi|\hat{O_i}
\left(\prod_{j = i+2}^{k-2} \hat{\sigma}_j^x \right) \hat{O_i}|\psi\rangle
- i: When :math:`\hat{O_i} = \hat{O'_k} = \mathbb{1}`,
denoted as :math:`S^{\mathbb{1}}(g)`, i for identity operator.
- zy: When :math:`\hat{O_i} = \hat{\sigma}_i^z\hat{\sigma}_{i+1}^y` and
:math:`\hat{O'_i} = \hat{\sigma}_{k-1}^y\hat{\sigma}_k^y`,
denoted as :math:`S^{\sigma^{zy}}(g)` for ZY operator.
"""
[docs]
def circuit_method(
target_circuit: QuantumCircuit,
target_key: str,
i: int,
k: int,
str_op: StringOperatorLibType = "i",
on_dir: StringOperatorDirection = "x",
) -> QuantumCircuit:
"""Build the circuit for the experiment.
Args:
target_circuit (QuantumCircuit): Target circuit.
target_key (str): Target key.
i (int): The index of beginning qubits in the quantum circuit.
k (int): The index of ending qubits in the quantum circuit.
str_op (StringOperatorLibType): The string operator.
on_dir (StringOperatorDirection): The direction of the string operator, either "x" or "y".
Returns:
QuantumCircuit: The circuit for the experiment.
"""
if i >= k:
raise ValueError(f"i: {i} is not less than k: {k}.")
if on_dir not in STRING_OPERATOR:
raise ValueError("The `on_dir` must be either 'x' or 'y'.")
if str_op not in STRING_OPERATOR[on_dir]:
raise ValueError(f"The `str_op` must be one of {list(STRING_OPERATOR[on_dir])}.")
if k - i + 1 < len(STRING_OPERATOR[on_dir][str_op]):
raise ValueError(
f"The `k - i` must be greater than or equal to {len(STRING_OPERATOR[on_dir][str_op])}. "
f"But got k: {k} - i: {i} = {k - i}."
)
old_name = "" if isinstance(target_circuit.name, str) else target_circuit.name
qc_exp1 = target_circuit.copy(
f"{target_key}_{i}_{k}_{str_op}_{on_dir}" + ("" if old_name else f".{old_name}")
)
c_meas1 = ClassicalRegister(k - i + 1, "c_m1")
qc_exp1.add_register(c_meas1)
qc_exp1.barrier()
string_op_lib = STRING_OPERATOR[on_dir][str_op]
index_map = {op + ((k + 1) if op < 0 else i): op for op in string_op_lib if isinstance(op, int)}
operations = {
idx: string_op_lib[index_map.get(idx, "filling")] for idx in range(i, k + 1) # type: ignore
}
for ci, (qi, move) in enumerate(operations.items()):
if move is None:
continue
if move[0] == "rx":
qc_exp1.rx(move[1], qi)
elif move[0] == "ry":
qc_exp1.ry(move[1], qi)
qc_exp1.measure(qc_exp1.qubits[qi], c_meas1[ci])
return qc_exp1