T3Tangent.probe_transpose ========================= .. py:method:: t3toolbox.manifold.T3Tangent.probe_transpose(ztildes, ww, frame, sum_over_probes = False) :staticmethod: .. code-block:: python def probe_transpose( ztildes: typ.Sequence[NDArray], # probe residuals, len=d, elm_shape=W+K+C+(Ni,) ww: typ.Sequence[NDArray], # probing vectors, len=d, elm_shape=W+(Ni,) frame: bvf.T3Frame, sum_over_probes: bool = False, # True: sum the probe stack W (Gauss-Newton J^T r) ) -> 'T3Tangent': Apply the transpose ``(J^(s))^T`` of the probe map to residuals; returns a T3Tangent at ``frame``. The adjoint of :py:meth:`probe`. The residuals ``ztildes`` live in the forward probe space, ``elm_shape = W + K + C + (Ni,)`` (probe stack ``W`` outer, optional tangent batch ``K``, frame stack ``C`` inner -- the output space of a ``K``-stacked :py:meth:`probe`; ``K`` is empty in the common case). The tangent batch ``K`` is always carried to the result's tangent stack; the probe stack ``W`` is summed or kept per ``sum_over_probes``: - ``sum_over_probes=False`` (default): each probe residual becomes one tangent -- the result's tangent stack is ``W + K`` (frame stack ``C``). - ``sum_over_probes=True``: the probe stack is summed -- the result's tangent stack is ``K`` (frame stack ``C``) -- the usual Gauss-Newton ``J^T r`` (a single tangent when ``K = ()``). ``False`` is the **primary** transpose (``W`` a passthrough stack); ``True`` is the derived contraction ``sum_over_probes=True == Σ_W sum_over_probes=False``. See *Batching & stacking* §11 (``docs/batching_and_stacking.md``) for which mode to use and why. Bare ``(J^(s))^T`` (no gauge projector). See Section 6.2.3 (Algorithm 8) of Alger et al. (2026). .. seealso:: :py:obj:`probe` .. rubric:: Examples Adjoint identity `` = `` (sum over probes): >>> import numpy as np >>> import t3toolbox.tucker_tensor_train as t3 >>> import t3toolbox.frame_variations_format as bvf >>> import t3toolbox.manifold as t3m >>> x = t3.TuckerTensorTrain.randn((10, 11, 12), (5, 6, 4), (1, 2, 3, 1)) >>> frame, _ = bvf.t3_orthogonal_representations(x) >>> v = t3m.MANIFOLD.randn(frame) >>> ww = (np.random.randn(2, 10), np.random.randn(2, 11), np.random.randn(2, 12)) >>> z = (np.random.randn(2, 10), np.random.randn(2, 11), np.random.randn(2, 12)) >>> Jv = v.probe(ww) >>> JTz = t3m.T3Tangent.probe_transpose(z, ww, frame, sum_over_probes=True) >>> lhs = float(np.sum([np.sum(a * b) for a, b in zip(z, Jv)])) >>> print(bool(abs(lhs - float(JTz.corewise_inner(v))) < 1e-9)) True Without summing, the result is a tangent-stacked T3Tangent (V = the probe stack): >>> JTz_batch = t3m.T3Tangent.probe_transpose(z, ww, frame) # sum_over_probes=False >>> print(JTz_batch.tangent_stack_shape, JTz_batch.frame_stack_shape) (2,) () With ``K``-stacked residuals (``W + K + C``), the tangent batch ``K`` is carried through: >>> zb = tuple(np.random.randn(2, 3, N) for N in (10, 11, 12)) # W=(2,), K=(3,), C=() >>> print(t3m.T3Tangent.probe_transpose(zb, ww, frame, sum_over_probes=True).tangent_stack_shape) (3,) >>> print(t3m.T3Tangent.probe_transpose(zb, ww, frame).tangent_stack_shape) # sum=False -> W + K (2, 3) ``sum_over_probes=True`` is exactly the probe-stack (``W``) sum of the ``False`` result: >>> kept = t3m.T3Tangent.probe_transpose(z, ww, frame) # W stays a stack >>> summed = t3m.T3Tangent.probe_transpose(z, ww, frame, sum_over_probes=True) # W summed >>> dU_sum = tuple(np.sum(c, axis=0) for c in kept.variations.tucker_variations) # sum the W axis >>> err = max(float(np.linalg.norm(a - b)) ... for a, b in zip(dU_sum, summed.variations.tucker_variations)) >>> print(bool(err < 1e-9)) True