Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ This project adheres to [Semantic Versioning], with the exception that minor rel

## [Unreleased]

### Added

- added direct MPO Process Tensor construction and temporal entanglement ([#508]) ([**@aaronleesander**])

### Fixed

- fixed TJM jump selection to align processes with site-sweep probabilities ([#506]) ([**@Pouri96**])
Expand Down Expand Up @@ -154,6 +158,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool

<!-- PR links -->

[#508]: https://github.com/munich-quantum-toolkit/yaqs/pull/508
[#506]: https://github.com/munich-quantum-toolkit/yaqs/pull/506
[#288]: https://github.com/munich-quantum-toolkit/yaqs/pull/288
[#482]: https://github.com/munich-quantum-toolkit/yaqs/pull/482
Expand Down
48 changes: 47 additions & 1 deletion docs/examples/characterization.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ mystnb:
Open quantum systems in YAQS couple a **probe qubit** (site 0) to an **environment** simulated by the remaining chain.
**Environmental memory** measures how long the environment keeps past control and measurement choices relevant for future probe responses, evaluated at a temporal cut $c$ in a sequence of interventions.

Use {meth}`~mqt.yaqs.memory_characterizer.MemoryCharacterizer.characterize` to probe the process: assemble the weighted **response matrix** $\widetilde{V}(c)$, then read $S_V(c)$, $R(c)=\exp(S_V(c))$, and the mode spectrum.
Use {meth}`~mqt.yaqs.memory_characterizer.MemoryCharacterizer.characterize` to probe **operational memory**: assemble the weighted **response matrix** $\widetilde{V}(c)$, then read $S_V(c)$, $R(c)=\exp(S_V(c))$, and the mode spectrum.

Alternatively, build a process tensor (default: direct MPO) and call `compute_temporal_entropy` for **temporal entanglement** $S_{PT}(c)$ of the multi-time process itself — a distinct quantity from $S_V(c)$.
For fast dynamics under control sequences, see {doc}`memory_surrogate`.

## Setup
Expand Down Expand Up @@ -209,6 +211,50 @@ fig.tight_layout()
`MemoryCharacterizer(representation="auto")` mirrors `Simulator`: `"vector"` selects MCWF, `"mps"` selects TJM for the **environment** chain.
With `"auto"`, MCWF is used when `hamiltonian.length <= vector_max_qubits` (default 10).

## Temporal entanglement from a process tensor

Operational memory ($S_V$) comes from probe responses.
**Temporal entanglement** $S_{PT}(c)$ is computed directly from a process tensor at the same causal cut.
By default, `build_process_tensor` uses direct MPO construction (`return_type="mpo"`).
Pass `return_type="dense"` for exhaustive tomography (required for `noise_model`):

```{code-cell} ipython3
k = 3
cut_pt = 2
timesteps = [0.1] * (k + 1)

pt_mpo = mc.build_process_tensor(ham, params, timesteps=timesteps)
pt_dense = mc.build_process_tensor(
ham,
params,
timesteps=timesteps,
return_type="dense",
)

s_mpo = pt_mpo.compute_temporal_entropy(cut_pt)
s_dense = pt_dense.compute_temporal_entropy(cut_pt)
print(
f"S_PT(c={cut_pt}): mpo={s_mpo['entropy']:.4f}, "
f"dense={s_dense['entropy']:.4f}, schmidt_rank={s_mpo['schmidt_rank']}"
)

# Same process tensor also supports operational memory via characterize:
pt_result = mc.characterize(
pt_mpo,
cut=cut_pt,
num_interventions=k,
n_pasts=6,
n_futures=6,
rng=np.random.default_rng(7),
)
print(f"S_V(c={cut_pt}) from process-tensor probes: {pt_result.entropy(cut_pt):.4f}")
```

Dense and uncapped MPO construction (`max_bond_dim=None`) agree on $S_{PT}$ for small $k$.
Use `return_type="dense"` when you need noise. The default `max_bond_dim=64` keeps direct
construction scalable; pass `max_bond_dim=None` for an exact noiseless MPO.
`characterize(pt, ...)` still builds $S_V$ from probe responses (native MPO `evaluate_probes`, without densifying for the V-matrix path).

## Related topics

- {doc}`quickstart` — minimal characterize and surrogate predict snippets
Expand Down
12 changes: 6 additions & 6 deletions docs/examples/memory_surrogate.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,21 +196,21 @@ Extend the per-leg list when `num_interventions > 1` to probe multi-step sequenc

## Validate against exact references

Build exhaustive process tensors for the same schedule.
Build process tensors for the same schedule.
By default, `build_process_tensor` returns an MPO from direct construction (noiseless).
Pass `return_type="dense"` for exhaustive tomography.
For process tensors, `rho0` in `predict` must match `pt.initial_rho` (the site-0 state after the initial leg of the reference schedule).

**Dense** and **MPO** implementations should agree on identical interventions.
Compare all three backends on a **stochastic sequence drawn from the training style** (`measure_prepare` here): pass a **fresh** `np.random.default_rng(seed)` to each `predict` call (reusing one RNG object advances its state between calls).

```{code-cell} ipython3
pt_mpo = mc.build_process_tensor(ham, params, timesteps=timesteps)
pt_dense = mc.build_process_tensor(
ham, params, timesteps=timesteps, return_type="dense", num_trajectories=48,
)
pt_mpo = mc.build_process_tensor(
ham, params, timesteps=timesteps, return_type="mpo", num_trajectories=48,
)

rho0 = pt_dense.initial_rho
rho0 = pt_mpo.initial_rho
compare_seed = 7

rho_dense = mc.predict(
Expand Down Expand Up @@ -248,7 +248,7 @@ fig.tight_layout()
```

Dense and MPO should overlap; the surrogate approximates the same draw at the reference `rho0`.
Held-out Hamiltonian rollouts above use random probe `rho0` values from data generation — a different setup than the fixed reference state baked into process tensors.
Held-out Hamiltonian rollouts above use random probe `rho0` values from data generation — a different setup than the fixed reference state stored on process tensors.

The same information functionals are available on either backend.
For this short horizon, conditional mutual information is near zero while QMI grows when more past legs are included:
Expand Down
3 changes: 2 additions & 1 deletion docs/examples/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ For larger circuits, compiler passes, and OpenQASM inputs, see {doc}`equivalence

## 4. Characterize environmental memory

Probe a probe qubit coupled to a short chain at an interior temporal cut. The memory spectrum and response matrix show how many independent past branches remain visible at the cut:
Probe a probe qubit coupled to a short chain at an interior temporal cut. The memory spectrum and response matrix show how many independent past branches remain visible at the cut ($S_V$).
For temporal entanglement of a process tensor ($S_{PT}$), see {doc}`characterization`.

```{code-cell} ipython3
import numpy as np
Expand Down
2 changes: 1 addition & 1 deletion src/mqt/yaqs/characterization/memory/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- :mod:`.sequences` — :func:`~mqt.yaqs.characterization.memory.backends.sequences.simulate_sequences` and pool workers
- :mod:`.exact` — :class:`~mqt.yaqs.characterization.memory.backends.exact.ExactBackend` and
:func:`~mqt.yaqs.characterization.memory.backends.exact.simulate_exact`
- :mod:`.tomography` — reference dense/MPO process tensors via exhaustive tomography
- :mod:`.tomography` — MPO process tensors via direct construction (default); dense via exhaustive tomography
- :mod:`.surrogates` — :class:`~mqt.yaqs.characterization.memory.backends.surrogates.model.ProcessTensorSurrogate`
and training-data workflow
"""
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
#
# Licensed under the MIT License

"""Process-tensor tomography (exact/exhaustive).
"""Process-tensor construction (dense tomography and direct MPO).

This subpackage constructs a process tensor from exhaustive discrete intervention sequences (size
``16**num_interventions`` for ``num_interventions`` steps), optionally under MCWF/TJM noise, and returns a
:class:`~mqt.yaqs.characterization.memory.backends.tomography.process_tensors.DenseProcessTensor` or
``return_type="mpo"`` (default) builds an MPO by direct construction (noiseless) and returns
:class:`~mqt.yaqs.characterization.memory.backends.tomography.process_tensors.MPOProcessTensor`.

``return_type="dense"`` runs exhaustive discrete-basis tomography
(``16**num_interventions`` sequences) and returns
:class:`~mqt.yaqs.characterization.memory.backends.tomography.process_tensors.DenseProcessTensor`.
"""

from .basis import TomographyBasis as TomographyBasis
Expand All @@ -21,3 +23,5 @@
from .data import SequenceData as SequenceData
from .process_tensors import DenseProcessTensor as DenseProcessTensor
from .process_tensors import MPOProcessTensor as MPOProcessTensor
from .process_tensors import compute_temporal_entropy as compute_temporal_entropy
from .process_tensors import evaluate_probes as evaluate_probes
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@
#
# Licensed under the MIT License

"""Process-tensor tomography workflow: exhaustive discrete-basis simulation.
"""Process-tensor construction: dense tomography or direct MPO growth.

**Public** (see ``__all__`` in :mod:`mqt.yaqs.characterization.memory.backends.tomography`):
:func:`build_process_tensor` (this module,
:mod:`mqt.yaqs.characterization.memory.backends.tomography.constructor`).

:func:`build_process_tensor` is the high-level user entry point returning a process tensor directly
(:class:`~mqt.yaqs.characterization.memory.backends.tomography.process_tensors.DenseProcessTensor` or
:class:`~mqt.yaqs.characterization.memory.backends.tomography.process_tensors.MPOProcessTensor`).
:func:`build_process_tensor` dispatches on ``return_type``:

- ``"mpo"`` (default) — direct MPO construction (noiseless); returns
:class:`~mqt.yaqs.characterization.memory.backends.tomography.process_tensors.MPOProcessTensor`.
- ``"dense"`` — exhaustive discrete-basis tomography (``16**num_interventions`` sequences),
optionally with noise; returns
:class:`~mqt.yaqs.characterization.memory.backends.tomography.process_tensors.DenseProcessTensor`.

The lower-level :func:`run_all_sequences` returns
:class:`~mqt.yaqs.characterization.memory.backends.tomography.data.SequenceData` covering all
``16**num_interventions`` Choi index sequences for ``num_interventions`` steps.
:class:`~mqt.yaqs.characterization.memory.backends.tomography.data.SequenceData` for the dense path.

**Execution model** — Same pattern as :mod:`mqt.yaqs.simulator` and
:mod:`mqt.yaqs.characterization.memory.backends.surrogates.workflow`: build a picklable payload, optionally
Expand Down Expand Up @@ -383,57 +387,83 @@ def build_process_tensor(
num_trajectories: int = 100,
basis: TomographyBasis = "tetrahedral",
basis_seed: int | None = None,
return_type: Literal["dense", "mpo"] = "dense",
return_type: Literal["dense", "mpo"] = "mpo",
# Dense reconstruction
check: bool = True,
atol: float = 1e-8,
# MPO reconstruction
compress_every: int = 100,
# Direct MPO construction
compress_every: int = 16,
tol: float = 1e-12,
max_bond_dim: int | None = None,
max_bond_dim: int | None = 64,
n_sweeps: int = 2,
solver: StochasticSolver | None = None,
initial_rho: np.ndarray | None = None,
initial_rho_atol: float = 1e-8,
_execution: ExecutionConfig | None = None,
) -> DenseProcessTensor | MPOProcessTensor:
"""Construct a process tensor via exhaustive discrete-basis tomography.

This simulates **every** ``16**num_interventions`` discrete basis sequence and returns a
process tensor directly:
"""Construct a process tensor as dense tomography or a direct MPO.

- ``return_type="dense"``: reconstruct and return a :class:`DenseProcessTensor`.
- ``return_type="mpo"``: build and return an :class:`MPOProcessTensor`.
- ``return_type="mpo"`` (default): direct MPO construction (noiseless only).
- ``return_type="dense"``: exhaustive discrete-basis tomography
(``16**num_interventions`` sequences; supports ``noise_model``).

Args:
operator: Hamiltonian MPO.
sim_params: Analog simulation parameters.
timesteps: Optional process-tensor schedule evolution durations (length
``num_interventions + 1``; defaults to ``[dt, dt]`` for one intervention leg).
noise_model: Optional open-system noise model.
parallel: Whether to parallelize over sequences.
num_trajectories: MCWF trajectories per sequence (forced to 1 when noiseless).
basis: Tomography basis name.
noise_model: Optional open-system noise model (dense tomography only).
parallel: Whether to parallelize dense tomography sequences or MPO construction.
num_trajectories: MCWF trajectories per sequence (dense path only).
basis: Tomography / Choi basis name.
basis_seed: Optional seed when ``basis="random"``.
return_type: ``"dense"`` or ``"mpo"`` process-tensor representation.
return_type: ``"mpo"`` (direct construction, default) or ``"dense"`` (tomography).
check: Run self-consistency check for dense reconstruction.
atol: Absolute tolerance for the dense self-check.
compress_every: MPO rank-1 accumulation compress interval.
compress_every: Direct-MPO rank-1 accumulation compress interval.
tol: MPO compression tolerance.
max_bond_dim: Optional MPO bond-dimension cap.
max_bond_dim: Cap on the branch ensemble / MPO bond dimension for direct construction.
Defaults to ``64`` for scalability; pass ``None`` for exact uncapped construction.
n_sweeps: MPO compression sweeps.
solver: Stochastic solver (``"MCWF"`` or ``"TJM"``).
initial_rho: Optional expected site-0 reference after ``U_0``; validated against the
computed tomography reference when provided.
initial_rho: Optional expected site-0 reference after ``U_0``.
initial_rho_atol: Tolerance for optional ``initial_rho`` validation.
_execution: Optional internal execution configuration.

Returns:
Dense or MPO process-tensor wrapper depending on ``return_type``.

Raises:
ValueError: If ``return_type`` is not ``"dense"`` or ``"mpo"``.
ValueError: If ``return_type`` is invalid, or ``noise_model`` is set with ``"mpo"``.
"""
if return_type == "mpo":
if noise_model is not None:
msg = (
"return_type='mpo' uses direct construction and does not support noise_model; use return_type='dense'."
)
raise ValueError(msg)
from .direct import build_process_tensor_direct # noqa: PLC0415

return build_process_tensor_direct(
operator,
sim_params,
timesteps,
basis=basis,
basis_seed=basis_seed,
tol=tol,
max_bond_dim=max_bond_dim,
n_sweeps=n_sweeps,
compress_every=compress_every,
solver=solver,
initial_rho=initial_rho,
initial_rho_atol=initial_rho_atol,
parallel=parallel,
_execution=_execution,
)
if return_type != "dense":
msg = f"Unknown return_type {return_type!r} (expected 'dense' or 'mpo')."
raise ValueError(msg)

data = _construct_data(
operator,
sim_params,
Expand All @@ -450,14 +480,4 @@ def build_process_tensor(
if initial_rho is not None:
validate_initial_rho(coerce_rho_matrix(initial_rho), data.initial_rho, atol=initial_rho_atol)

if return_type == "dense":
return data.to_dense_process_tensor(check=check, atol=atol)
if return_type == "mpo":
return data.to_mpo_process_tensor(
compress_every=compress_every,
tol=tol,
max_bond_dim=max_bond_dim,
n_sweeps=n_sweeps,
)
msg = f"Unknown return_type {return_type!r} (expected 'dense' or 'mpo')."
raise ValueError(msg)
return data.to_dense_process_tensor(check=check, atol=atol)
Loading
Loading