# Coefficient Workflows This page is a quick map for staged coefficient work. The detailed examples remain in [fitting](fitting.md), 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: ```python 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: ```python 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: ```python 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: ```python 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: ```python 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.