Source code for qarp.blocks._primitives.synthesized_time_evolution_block
from copy import deepcopy
from typing import List, Optional
import numpy as np
from scipy.linalg import expm
from qarp.blocks._block import SimpleBlock
from qarp.operators import QubitOperator
[docs]
class SynthesizedTimeEvolutionBlock(SimpleBlock):
"""Pattern A leaf: synthesize ``U(t) = exp(-i H t)`` for a Hamiltonian H.
Builds the time-evolution unitary numerically via ``scipy.linalg.expm`` and
delegates to ``self.unitary_synthesis(...)`` (qarpx Quantum Shannon
Decomposition) for the circuit synthesis.
"""
def __init__(
self,
operator: QubitOperator,
n_qubits: int,
time: Optional[float] = None,
target_qubits: Optional[List[int]] = None,
name: str = "SynthTimeEvoBlock",
):
"""
Args:
operator: The Hamiltonian (must be Hermitian).
n_qubits: Number of qubits the operator acts on.
time: Evolution time `t`. May be set later via ``set_time``;
``build()`` will raise if called before `time` is set.
target_qubits, name: standard Block kwargs.
"""
super().__init__(
n_qubits,
target_qubits=target_qubits,
name=name,
)
self.operator = operator
self.time = time
# qarpx-LSB (qubit q ↔ bit q) — the convention synthesis expects.
self.operator_matrix = operator.sparse_matrix(n_qubits).toarray()
self._validate_inputs()
def _validate_inputs(self) -> None:
expected_dim = 2**self.n_qubits
if self.operator_matrix.shape != (expected_dim, expected_dim):
raise ValueError(
f"Operator matrix dimension {self.operator_matrix.shape} does not match "
f"expected size ({expected_dim}, {expected_dim}) for n_qubits={self.n_qubits}"
)
if not np.allclose(self.operator_matrix, self.operator_matrix.conj().T, atol=1e-10):
raise ValueError("Input operator is not Hermitian (required for time evolution)")
[docs]
def set_time(self, time_value: float) -> "SynthesizedTimeEvolutionBlock":
new_object = deepcopy(self)
new_object.time = time_value
# ``Block.__deepcopy__`` preserves ``built_=True`` and the C++
# commands_ buffer, so a plain ``build()`` short-circuits on the
# idempotency guard. Reset both flags so ``build_vanilla`` re-emits
# the synthesis at the new time.
new_object._built = False
new_object.set_commands([])
new_object.set_built(False)
new_object.build()
return new_object
[docs]
def build_vanilla(self) -> None:
if self.time is None:
raise ValueError("Time parameter is not set. Please set time before building.")
unitary_matrix = expm(-1j * self.operator_matrix * self.time)
self.unitary_synthesis(unitary_matrix)