Coefficient Workflows

This page is a quick map for staged coefficient work. The detailed examples remain in fitting, but the ownership split is:

  • ufp.leastsquares.CoefficientSelector selects fitted or frozen coefficient blocks for direct least squares;

  • ufp.coefficients.CoefficientSelector is the same public selector, exposed next to coefficient-copy helpers for staged workflows;

  • ufp.coefficients copies, zeros, clones, and validates compatible physical coefficient channels;

  • ufp.projection writes prior functions or existing spline channels into ordinary coefficient tensors before runtime evaluation starts;

  • ufp.training.freeze_model_coefficients() applies the same selector language to optimizer-time freezing.

Selection

Use string blocks when whole families are enough:

from ufp.leastsquares import LinearFitter

LinearFitter(model, fit_blocks=["threebody"])
LinearFitter(model, freeze_blocks=["twobody"])

Use CoefficientSelector when a workflow needs one physical channel or a specific knot range:

from ufp.coefficients import CoefficientSelector
from ufp.leastsquares import LinearFitter

selector = CoefficientSelector(
    block="twobody",
    channel=(1, 8),
    coeff_slice=slice(2, 6),
)

LinearFitter(model, fit_blocks=[selector])

Pair and two-body channels use (Z_i, Z_j). Three-body and 2D-triplet channels use source-distinguished triplets (Z_center, Z_neighbor_1, Z_neighbor_2); neighbor entries are canonicalized according to the term’s symmetry rules.

Interchange

Use coefficient interchange when one fit should initialize another model without copying incompatible channels by position:

from ufp.coefficients import (
    clone_model_with_zeroed_coefficients,
    copy_matching_coefficients,
)

mixed = clone_model_with_zeroed_coefficients(mixed_template)
report = copy_matching_coefficients(source_model, mixed)

report.copied_count
report.skipped

The helpers validate channel identity, grid, cutoff, spline family, dtype, device, active masks, symmetry, and category ordering. Use copy_coefficient_channel() for one explicit source/target selector pair when silent skipping would hide a workflow error.

Projection

Projection is offline model preparation. It samples a callable or existing spline channel, solves for spline coefficients, writes those coefficients into a normal term, and returns diagnostics:

import torch

from ufp.projection import project_radial_function

projection = project_radial_function(
    lambda r: torch.exp(-r),
    coeff_size=12,
    full_support_start=0.0,
    cutoff=6.0,
    dtype=torch.float64,
)

projection.coeffs
projection.diagnostics.summary.max_value_rmse

Runtime term evaluation does not call projection code. After projection, least squares and training see ordinary coefficient tensors.

Freezing

The training freeze helper uses the same selector objects as least squares:

import torch

from ufp.coefficients import CoefficientSelector
from ufp.training import freeze_model_coefficients

freeze_state = freeze_model_coefficients(
    model,
    [CoefficientSelector(block="pair", coeff_slice=slice(0, 2))],
)

optimizer = torch.optim.AdamW(model.parameters(), lr=1.0e-3)
freeze_state.wrap_optimizer(optimizer)

The wrapper masks gradients, restores frozen entries after optimizer steps, and clears optimizer state entries for the frozen coefficients.