Source code for pycif.plugins.modes.training.execute

"""Execute function for the training mode.

Full pipeline
-------------
1. Wrap the CIF obs operator as a plain-numpy object.
2. Read state / obs dimensions from controlvect and obsvect.
3. Build a default MLP surrogate (architecture from YAML config).
4. Draw training samples from the prior (xb ± std noise).
5. Train the surrogate against the live CIF operator.
6. Export the trained model to ONNX inside ``workdir``.
7. Reload the ONNX file as an :class:`OnnxSurrogateOperator` and return it
   as a drop-in replacement for the CIF obsoperator.
"""
import copy
import os
import numpy as np
from logging import info

from ....utils.check.errclass import CifError


# --------------------------------------------------------------------------- #
#  Helper                                                                       #
# --------------------------------------------------------------------------- #

def _make_sample_fn(xb: np.ndarray, std: np.ndarray, backend: str):
    """Return a callable that draws  x ~ N(xb, std)  as a backend tensor."""
    rng = np.random.default_rng()

    def sample_fn(_: int):
        x = xb + std * rng.standard_normal(len(xb))
        if backend == "torch":
            import torch
            return torch.tensor(x, dtype=torch.float32)
        else:
            import jax.numpy as jnp
            return jnp.array(x, dtype=jnp.float32)

    return sample_fn


# --------------------------------------------------------------------------- #
#  Entry point                                                                  #
# --------------------------------------------------------------------------- #

[docs] def execute(self, **kwargs): from .trainer import SurrogateTrainer # ------------------------------------------------------------------ # # 1. Wrap CIF operator # # ------------------------------------------------------------------ # cif_op = self.obsoperator # ------------------------------------------------------------------ # # 2. Read state / obs dimensions # # ------------------------------------------------------------------ # controlvect = self.controlvect xb = np.asarray(controlvect.xb, dtype=np.float64) n_state = self.controlvect.dim n_obs = self.obsvect.dim info(f"Training mode: state dimension={n_state}, obs dimension={n_obs}") # ------------------------------------------------------------------ # # 3. Build default model (framework chosen by backend) # # ------------------------------------------------------------------ # trainer = SurrogateTrainer(cif_op, backend=self.backend) model = trainer.build_default_model( n_state, n_obs, hidden_size=self.hidden_size, n_hidden_layers=self.n_hidden_layers, ) info( f" model: {n_state} -> [{self.hidden_size}] x {self.n_hidden_layers} -> {n_obs}") # ------------------------------------------------------------------ # # 4. Sample function from prior # # ------------------------------------------------------------------ # std = np.asarray(getattr(controlvect, "std", np.ones(n_state)), dtype=np.float64) sample_fn = _make_sample_fn(xb, std, self.backend) # ------------------------------------------------------------------ # # 5. Train # # ------------------------------------------------------------------ # info(f"Training surrogate: backend={self.backend!r}, n_steps={self.n_steps}, " f"adjoint_consistency={self.adjoint_consistency}") model, losses = trainer.train( model, sample_fn, n_steps=self.n_steps, run_mode=self.run_mode, reload_results=self.reload_results, adjoint_consistency=self.adjoint_consistency, lambda_adj=self.lambda_adj, ) info(f" Training complete. Final loss: {losses[-1]:.4e}") # ------------------------------------------------------------------ # # 6. Export to ONNX # # ------------------------------------------------------------------ # onnx_path = os.path.join(self.workdir, self.onnx_file) trainer.export(onnx_path, model, input_shape=(1, n_state)) info(f" Surrogate exported to {onnx_path}")