Source code for pdft.coherence

"""Mutual coherence of a learned basis with the pixel basis.

For compression, the only thing that matters about a basis is how few
coefficients it needs. For any task that recovers an image from a *subset of
its pixels* --- inpainting, completion, compressed sensing --- a second
quantity governs the outcome, and it is not sparsity::

    mu(U) = N * max_ij |U_ij|^2   in [1, N]

with N the operator dimension. mu = 1 is maximal incoherence with the pixel
basis, the most favourable case for recovery from pointwise samples; mu = N is
the worst, an atom supported on a single pixel, invisible to any sample set
that misses it. Sample-complexity bounds for recovery from random pixel
observations scale linearly in mu (Candes & Plan 2011, Krahmer & Ward 2014).

A trained basis can drift in mu while its compression loss improves, so the
quantity is worth being able to measure. This module measures it --- and, more
usefully, certifies when it cannot drift at all.

The guarantee
-------------
Let the circuit act on n wires, and suppose every gate is diagonal in the
computational basis except for exactly one Hadamard per wire. Then::

    |U_ij| = N^{-1/2}   for every i, j and every parameter value,

so mu(U) = 1 identically over the whole parameter space, and sqrt(N) U is a
complex Hadamard matrix (Tadej & Zyczkowski 2006).

*Proof.* Track the amplitude vector produced by an input basis vector.
Diagonal gates multiply amplitudes by unit moduli and never move amplitude
between the two values of any wire; a permutation permutes. Consider the moment
the Hadamard on wire q is applied. Every earlier gate is diagonal or a Hadamard
on a different wire, so none has moved amplitude across wire q, and the vector
is supported on one value of it: one branch carries a, the other 0. Since
H (a, 0)^T = (a, a)^T / sqrt(2), the gate replaces each modulus by two equal
moduli ``|a|/sqrt(2)`` and never combines unequal ones. Each wire receives exactly
one Hadamard, so every amplitude ends at 2^{-n/2}, and the remaining diagonal
gates and permutations preserve moduli. QED

The practical consequence is a classification of this package's bases by which
gates are left free during training:

  * free only the controlled-phase gates --- the compact-CP tensors --- and
    mu is pinned at 1 no matter how hard the basis is trained, on any
    objective, with no penalty, constraint or monitoring;
  * free the Hadamard / U(4) gates as well and the guarantee is void. It is
    void in practice, not only in principle: randomising them on an (m, n) =
    (3, 3) QFTBasis reaches mu = 24.8 of a possible 64, and on RichBasis ---
    which has no diagonal gates at all --- mu = 32.5.

`certify_flat_modulus` performs that check for a given training configuration,
taking the same `frozen_indices` that `train_basis_batched` accepts, so the
question "does this training run preserve incoherence?" can be answered before
the run rather than measured after it.

Discovered while applying this package's basis family to image *completion*,
where the reversal matters: the transform that compresses an image best is not
the one that completes it best.
"""

from __future__ import annotations

from dataclasses import dataclass, field

import jax
import jax.numpy as jnp

from .circuit.builder import is_compact_cp

Array = jax.Array

__all__ = [
    "FlatModulusCertificate",
    "certify_flat_modulus",
    "coherence",
    "dense_operator",
    "diagonal_tensor_indices",
    "is_flat_modulus",
]


[docs] def dense_operator(basis) -> Array: """The basis as an explicit matrix acting on `vec(image)`. Column j is the transform of the image that is 1 at pixel j and 0 elsewhere, so the result is (2^m 2^n) x (2^m 2^n). This is a diagnostic, not a code path: it costs N^2 transforms of an N-pixel image and is meant for small m, n. Nothing in training needs it --- `certify_flat_modulus` gives the parameter-space guarantee without forming it. """ rows, cols = basis.image_size dim = rows * cols eye = jnp.eye(dim, dtype=jnp.complex128) return jax.vmap(lambda e: basis.forward_transform(e.reshape(rows, cols)).reshape(-1))(eye).T
[docs] def coherence(basis, operator: Array | None = None) -> float: """``mu(U) = N max_ij |U_ij|^2``, in [1, N]. 1 is maximal incoherence. Pass `operator` to reuse a matrix from `dense_operator`. """ u = dense_operator(basis) if operator is None else operator return float(u.shape[0] * jnp.max(jnp.abs(u) ** 2))
[docs] def is_flat_modulus(basis, operator: Array | None = None, atol: float = 1e-8) -> bool: """True if ``|U_ij| = N^{-1/2}`` everywhere, i.e. sqrt(N) U is complex Hadamard.""" u = dense_operator(basis) if operator is None else operator return bool(jnp.allclose(jnp.abs(u), u.shape[0] ** -0.5, atol=atol))
[docs] def diagonal_tensor_indices(basis) -> list[int]: """Indices of the tensors that are diagonal in the computational basis. These are the compact controlled-phase gates, `[[1, 1], [1, e^{i phi}]]`, whose only freedom is a phase. Freeing exactly these is what preserves mu == 1; see the module docstring. """ return [i for i, t in enumerate(basis.tensors) if is_compact_cp(t)]
[docs] @dataclass(frozen=True) class FlatModulusCertificate: """Whether mu == 1 holds over the whole reachable parameter set. `holds` is the answer; `reason` says why in one line. `mu` is the coherence at the basis's current parameters, and `offending_indices` names the trainable tensors that are not diagonal, which are exactly the ones that would have to be frozen for the guarantee to apply. """ holds: bool reason: str mu: float offending_indices: list[int] = field(default_factory=list) def __bool__(self) -> bool: return self.holds
[docs] def certify_flat_modulus( basis, frozen_indices: list[int] | None = None, *, atol: float = 1e-8, ) -> FlatModulusCertificate: """Certify that training cannot raise mu above 1. Two conditions, both necessary. The basis must be flat-modulus *now*, and every tensor left trainable must be diagonal --- then by the proposition in the module docstring, mu == 1 for every value those parameters can take. `frozen_indices` is the same argument `train_basis_batched` accepts, so:: cert = certify_flat_modulus(basis, frozen_indices=frozen) if not cert: raise ValueError(cert.reason) result = train_basis_batched(basis, frozen_indices=frozen, ...) Frozen non-diagonal gates are fine: a fixed Hadamard is what the proposition assumes. It is *training* them that voids it. """ operator = dense_operator(basis) mu = coherence(basis, operator) if not is_flat_modulus(basis, operator, atol=atol): return FlatModulusCertificate( holds=False, reason=( f"basis is not flat-modulus at its current parameters " f"(mu = {mu:.6g}); the guarantee applies to circuits with one " f"Hadamard per wire and diagonal gates elsewhere" ), mu=mu, ) frozen = set(frozen_indices or []) diagonal = set(diagonal_tensor_indices(basis)) offending = [i for i in range(len(basis.tensors)) if i not in frozen and i not in diagonal] if offending: return FlatModulusCertificate( holds=False, reason=( f"mu = {mu:.6g} now, but {len(offending)} trainable tensor(s) " f"are not diagonal, so training may raise it; freeze indices " f"{offending} to obtain the guarantee" ), mu=mu, offending_indices=offending, ) return FlatModulusCertificate( holds=True, reason=( f"mu == 1 for every reachable parameter value: all " f"{len(basis.tensors) - len(frozen)} trainable tensor(s) are diagonal" ), mu=mu, )