Source code for qmlhc.metrics.causal_indefiniteness

# -*- coding: utf-8 -*-
r"""

This module implements a computational metric for quantifying *causal
indefiniteness* based on the trace distance to classically ordered
(causally separable) reference processes, as introduced in the paper
:ref:`Pre-Temporal Model of Quantum Causal Order <theory>`

The implementation is intentionally minimal and framework-agnostic.
All objects are represented as square complex matrices, allowing the
metric to be applied not only to process matrices in the strict
process-matrix formalism, but also to abstract operators or effective
representations used in numerical simulations, diagnostics, or learning
frameworks.

Conceptual definition
---------------------
Causal indefiniteness is quantified by measuring how far a given operator
``W`` lies from the convex set of processes that admit a definite causal
order. This distance defines a continuous scale:

- ``λ(W) = 0`` indicates a causally separable (classically ordered) process.
- ``λ(W) > 0`` indicates the presence of genuinely indefinite causal structure.
- Intermediate values correspond to partial or weakening causal indefiniteness.

The metric is designed to behave continuously and monotonically under
physical decoherence and coarse-graining operations.

Computable form implemented here
--------------------------------

Rather than minimizing over the full convex set of causally separable
processes, this module implements the *computable instantiation* used in
the paper for the bipartite, two-order scenario.

Two definite-order reference matrices are assumed:

- ``W_AB`` representing the order :math:`A \prec B`
- ``W_BA`` representing the order :math:`B \prec A`

From these, a one-parameter family of classically ordered reference
processes is constructed:

.. math::

   V(q) = \frac{1}{2} \left[ q \, W_{AB} + (1 - q) \, W_{BA} \right],
   \qquad q \in [0, 1]

The causal-indefiniteness measure is then computed as the minimum trace
distance between ``W`` and this reference family:

.. math::

   \lambda(W) = \min_{q} \, D\!\left(W, V(q)\right)

where the trace distance is defined as

.. math::

   D(A, B) = \frac{1}{2} \left\| A - B \right\|_{1}

This formulation captures the transition between indefinite and definite
causal structure in a numerically stable and interpretable way.

Numerical and structural assumptions
------------------------------------
- All input matrices are assumed to be square and of equal shape.
- Inputs are expected to be Hermitian or approximately Hermitian.
  Optional symmetrization is provided to suppress numerical artifacts.
- No specific tensor-product structure is assumed. Any required embedding
  (e.g., control systems or ancillary spaces) must be handled by the caller.
- The minimization over ``q`` is performed using a deterministic grid search,
  favoring robustness and reproducibility over asymptotic optimality.

Intended use
------------
This metric is designed as a *computational primitive* rather than a full
causal-separability solver. Typical use cases include:

- Monitoring the decay of causal indefiniteness under noise or decoherence
- Characterizing intermediate (pre-temporal) causal regimes
- Providing a scalar diagnostic for simulations, optimization loops,
  or hybrid quantum-classical workflows
- Serving as a plug-in metric within larger numerical or learning frameworks

Public interface
----------------
- ``trace_distance``: Trace distance between two (approximately) Hermitian matrices
- ``reference_process``: Construction of the convex reference process :math:`V(q)`
- ``lambda_w_trace``: Computation of the causal-indefiniteness measure :math:`\lambda(W)`
"""



from __future__ import annotations

from typing import Tuple

import numpy as np

from ..core.types import Array


def _validate_square_same_shape(*mats: Array) -> Tuple[int, int]:
    """Validate that all matrices are square and share the same shape."""
    if not mats:
        raise ValueError("At least one matrix is required.")

    shape = mats[0].shape
    if len(shape) != 2 or shape[0] != shape[1]:
        raise ValueError(f"Expected a square matrix, got shape={shape}.")
    for M in mats[1:]:
        if M.shape != shape:
            raise ValueError(f"All matrices must share the same shape. Got {shape} vs {M.shape}.")
    return int(shape[0]), int(shape[1])


[docs] def trace_norm_hermitian(X: Array, *, symmetrize: bool = True) -> float: r""" Compute the trace norm :math:`\|X\|_{1}` for an (approximately) Hermitian matrix. For Hermitian :math:`X`, the trace norm equals the sum of the absolute values of its eigenvalues. Parameters ---------- X: Square matrix. symmetrize: If ``True``, replace :math:`X` with :math:`(X + X^{\dagger}) / 2` before eigendecomposition to remove tiny numerical non-Hermiticity. Returns ------- float The trace norm :math:`\|X\|_{1}`. """ _validate_square_same_shape(X) H = X if symmetrize: H = 0.5 * (H + H.conj().T) eigs = np.linalg.eigvalsh(H) return float(np.sum(np.abs(eigs)))
[docs] def trace_distance(A: Array, B: Array, *, symmetrize: bool = True) -> float: """ Trace distance D(A,B) = 1/2 ||A - B||_1. This function is intended for Hermitian (or nearly Hermitian) inputs, which is the standard case for process/Choi operators. Parameters ---------- A, B: Square matrices with the same shape. symmetrize: If True, symmetrize (A-B) prior to computing eigenvalues. Returns ------- float Trace distance D(A,B). """ _validate_square_same_shape(A, B) return 0.5 * trace_norm_hermitian(A - B, symmetrize=symmetrize)
[docs] def reference_process( q: float, W_AB: Array, W_BA: Array, *, half_factor: bool = True, ) -> Array: """ Build the paper's convex reference process V(q). V(q) = 1/2 [ q * W_AB + (1-q) * W_BA ]. Parameters ---------- q: Mixing parameter in [0,1]. W_AB: Definite-order reference branch for A≺B, embedded in the same space as W. W_BA: Definite-order reference branch for B≺A, embedded in the same space as W. half_factor: Whether to include the explicit 1/2 factor used in the manuscript. Returns ------- np.ndarray Reference matrix V(q). """ if not (0.0 <= float(q) <= 1.0): raise ValueError(f"q must be in [0,1], got q={q}.") _validate_square_same_shape(W_AB, W_BA) V = float(q) * W_AB + (1.0 - float(q)) * W_BA if half_factor: V = 0.5 * V return V
[docs] def lambda_w_trace( W: Array, W_AB: Array, W_BA: Array, *, q_grid: int = 81, symmetrize: bool = True, half_factor: bool = True, ) -> float: r""" Compute the causal-indefiniteness measure :math:`\lambda(W)` via trace distance. This matches the manuscript's computable instantiation: - :math:`\lambda(W) = \min_{q \in [0,1]} D\!\left(W, V(q)\right)`. - :math:`V(q) = \frac{1}{2}\left[q\,W_{AB} + (1-q)\,W_{BA}\right]` (when ``half_factor=True``). - :math:`D(A,B) = \frac{1}{2}\left\|A - B\right\|_{1}`. The minimization is performed by a deterministic grid search over :math:`q \in [0, 1]`. For most practical uses (monitoring, diagnostics, regularization), this provides robust and deterministic behavior. Parameters ---------- W: Target process/operator matrix. W_AB: Definite-order reference branch (:math:`A \prec B`), embedded in ``W``'s matrix space. W_BA: Definite-order reference branch (:math:`B \prec A`), embedded in ``W``'s matrix space. q_grid: Number of grid points for :math:`q \in [0,1]`. Typical resolutions are in the 50–80 range; the default ``81`` provides a convenient inclusive grid. symmetrize: If ``True``, symmetrize :math:`(W - V(q))` before eigendecomposition. half_factor: If ``True``, use the manuscript convention :math:`V(q) = \frac{1}{2}\left[q\,W_{AB} + (1-q)\,W_{BA}\right]`. Returns ------- float The minimized trace distance :math:`\lambda(W)`. """ _validate_square_same_shape(W, W_AB, W_BA) if q_grid < 2: raise ValueError(f"q_grid must be >= 2, got q_grid={q_grid}.") qs = np.linspace(0.0, 1.0, int(q_grid)) best = float("inf") for q in qs: Vq = reference_process(q, W_AB, W_BA, half_factor=half_factor) d = trace_distance(W, Vq, symmetrize=symmetrize) if d < best: best = d return float(best)