Source code for pycif.plugins.modes.adjtl_test.testing_adj

import numpy as np
import copy
from logging import info


def _apply_finite_difference_tl(data_tl_ref, data_tl_prime):
    """Turn two saved forward runs into a finite-difference TL increment.

    For every transform / sub-simulation / trid / date recorded in
    ``data_tl_prime`` (the perturbed run ``H(xb + dx)``), computes
    ``incr = spec_prime - spec_ref`` against the matching entry in
    ``data_tl_ref`` (the reference run ``H(xb)``) and stores it back into
    ``data_tl_prime`` in place, for both its ``"outputs"`` and
    ``"inputs"`` sides. Entries without a ``"spec"`` field, or whose
    ``"spec"`` is an object dtype, are left untouched.

    Args:
        data_tl_ref: ``obsoper.data_tl`` saved from the reference run.
        data_tl_prime: ``obsoper.data_tl`` from the perturbed run, mutated
            in place with the computed ``"incr"`` entries.
    """
    for transform in data_tl_prime:
        ref0 = data_tl_ref[transform]
        prime0 = data_tl_prime[transform]
        for ddi in prime0:
            ref1 = ref0[ddi]
            prime1 = prime0[ddi]
            for inout in ("outputs", "inputs"):
                for trid in prime1[inout]:
                    ref2 = ref1[inout][trid]
                    prime2 = prime1[inout][trid]
                    for ddi_trid in prime2:
                        ref3 = ref2[ddi_trid]
                        prime3 = prime2[ddi_trid]
                        for transf_trid in prime3:
                            if "spec" not in ref3[transf_trid]:
                                continue

                            if ref3[transf_trid]["spec"].dtype != "O":
                                prime3[transf_trid]["incr"] = (
                                    prime3[transf_trid]["spec"]
                                    - ref3[transf_trid]["spec"]
                                )


[docs] def testing_AD_whole(self, accuracy, testspace, **kwargs): """Verify the adjoint identity ``<H·dx, H·dx> == <dx, H*·H·dx>``. Runs either the tangent-linear operator or two finite-difference forward runs (when ``use_forward`` is set) to obtain ``H·dx``, then runs the adjoint to obtain ``H*·H·dx``. The two inner products are printed and their relative discrepancy expressed as a multiple of machine epsilon is returned. Args: self (Plugin): mode plugin with all configuration attributes. accuracy (float): machine epsilon (``np.finfo(np.float64).eps``). testspace (str): ``'control'`` to work in control space or ``'chi'`` to include the square-root-B mapping. **kwargs: forwarded verbatim to the observation operator. Returns: float: ``floor(|<dx, H*(H·dx)> / <H·dx, H·dx> - 1| / epsilon)``, the test ratio in multiples of machine accuracy. Values up to a few tens are typically acceptable. """ # Working directory workdir = self.workdir # Control vector controlvect = self.controlvect # Observation operator obsoper = self.obsoperator # Obsvervation vector obsvect = self.obsvect # Simulation window datei = self.datei datef = self.datef # Some verbose info("Computing the test of the adjoint") # Running the tangent linear code of the model: H(xb)(dx) if not self.use_forward: obsvect = obsoper.obsoper( controlvect, obsvect, "tl", datei=datei, datef=datef, workdir=workdir, reload_results=self.reload_results, check_transforms=self.check_transforms, **kwargs ) else: # Reference simulation: H(xb) obsvect_ref = obsoper.obsoper( controlvect, obsvect, "fwd", datei=datei, datef=datef, workdir=workdir, reload_results=self.reload_results, check_transforms=self.check_transforms, run_id=1, **kwargs ) ysim_ref = copy.deepcopy(obsvect_ref.ysim) # Save check transforms if self.check_transforms: data_tl_save = copy.deepcopy(obsoper.data_tl) del obsoper.data_tl # Perturbed simulation: H(xb + dx) controlvect.x = copy.deepcopy(controlvect.xb + controlvect.dx_save) obsvect_dy = obsoper.obsoper( controlvect, obsvect, "fwd", datei=datei, datef=datef, workdir=workdir, reload_results=self.reload_results, check_transforms=self.check_transforms, run_id=2, **kwargs ) # Compute the TL of check_transform if self.check_transforms: _apply_finite_difference_tl(data_tl_save, obsoper.data_tl) # Replace dy by difference between ref and perturbed control vector obsvect.dy = obsvect_dy.ysim - ysim_ref # Putting xb and x back to there original values controlvect.x -= controlvect.dx_save # Computing < H.dx, H.dx > scaleprod1 = np.nansum(np.power(obsvect.dy, 2)) # Putting increments in the observation vector obsvect.dy = np.where(np.isnan(obsvect.dy), 0, obsvect.dy) # Running the observation operator controlvect = obsoper.obsoper( controlvect, obsvect, "adj", datei=datei, datef=datef, workdir=workdir, reload_results=self.reload_results, check_transforms=self.check_transforms, **kwargs ) # Computing < dx, H*(H.dx) > if testspace == "control": scaleprod2 = np.nansum(controlvect.dx_save * controlvect.dx) elif testspace == "chi": scaleprod2 = np.nansum( controlvect.sqrtbprod_ad( controlvect.dx, **kwargs) * controlvect.chi ) # Final verbose info(f'Machine accuracy: {accuracy}') info(f'< H.dx, H.dx > = {scaleprod1:.17E}') info(f'< dx, H*(H.dx) > = {scaleprod2:.17E}') info(f'The difference is {np.abs(scaleprod2 / scaleprod1 - 1) / accuracy:.1E} times the machine accuracy') # Return test return np.floor(np.abs(scaleprod2 / scaleprod1 - 1) / accuracy)