Cost Functions¶
Base Classes¶
- class ionworkspipeline.data_fits.objective_functions.costs.ObjectiveFunction(objective_weights: dict[str, float] | None = None, variable_weights: dict[str, float] | None = None)¶
Base class for all objective/cost functions.
Parameters¶
- objective_weightsdict[str, float], optional
Per-objective weights. Keys are objective names, values are weights. Missing objectives default to weight 1.0. A numerically named objective is weighted by its name written as text,
{"298.15": 2.0}.- variable_weightsdict[str, float], optional
Per-variable weights. Keys are variable names, values are weights. Missing variables default to weight 1.0.
Notes¶
Objective/variable scoping is configured after construction via
set_calculation_structure()(the source of truth), or the convenience wrappersset_objective_names()andset_variable_names(). The structure maps each objective name to the variables to compute for it, orNonefor all; an empty list computes none. It round-trips throughto_config()under thecalculation_structurekey.Extends:
ConfigMixinSee also:
ObjectiveFunction— field-level documentation.- __call__(**kwargs)¶
Call self as a function.
- property calculation_structure: dict[str, list[str] | None] | None¶
The configured calculation structure, or None to compute everything.
Maps each objective name to the explicit list of variable names to compute for it. A value of
Nonefor an objective computes all of that objective’s variables; an empty list computes none of them. A value ofNonefor the whole structure computes every objective and every variable present in the outputs.
- check_output_shapes(outputs: dict) None¶
Validate the model/data shapes this cost will score. No-op by default.
Element-wise costs override this to warn on length mismatches; it is called once at fit setup so the check stays out of the evaluation loop.
- combine(accumulator: ScalarAccumulator | ResidualAccumulator, other: ScalarAccumulator | ResidualAccumulator | float | ndarray[tuple[Any, ...], dtype[_ScalarT]]) ScalarAccumulator | ResidualAccumulator¶
Combine accumulator values.
Parameters¶
- accumulator
The accumulator to add to.
- other
Another accumulator, or a raw value to add with weight 1.0.
Returns¶
- Accumulator
The updated accumulator (same object, mutated in place).
- derivative(outputs: dict, param_names: list[str], mode: Literal['scalar', 'residuals'] = 'residuals') ndarray[tuple[Any, ...], dtype[_ScalarT]] | None¶
Analytic derivative w.r.t. the input parameters, or None for FD fallback.
Companion to
__call__():mode="residuals"returns the residual Jacobian(n_residuals, len(param_names)),mode="scalar"the scalar gradient(len(param_names),)— both in input-parameter space, columns ordered byparam_names. The base returns None; differentiable subclasses override.
- finalize_output(accumulator: ScalarAccumulator | ResidualAccumulator) float | ndarray[tuple[Any, ...], dtype[_ScalarT]]¶
Finalize an accumulator to its output value.
- classmethod from_schema(schema)¶
Build from an
ionworks_schemaobjective function/cost instance.Overrides
ConfigMixin.from_schema:calculation_structure/objective_namesare applied post-construction via setters, not constructor arguments, so they’re popped from the schema-derived kwargs before construction and re-applied via_apply_schema_scoping()— mirroringparse_cost’s “only one of the two” rule (a backstop; the schema’s own validator already enforces it).
- get_objective_names(outputs: dict) list[str]¶
Get the names of objectives to evaluate over.
Returns the calculation structure’s keys when a structure is set, otherwise every objective name present in
outputs.
- get_variable_names(objective_name: str, outputs: dict, model: dict | None = None) list[str]¶
Get the variable names to compute for a single objective.
Resolves the calculation structure for
objective_name: an explicit variable list is returned as-is (an empty list meaning “no variables”), whileNone— or an unset structure — returns every variable present in that objective’s model output.Parameters¶
- objective_namestr
Name of the objective whose variables to resolve.
- outputsdict
Objective-name -> output container, as passed to
__call__.- modeldict, optional
The already-unpacked model mapping for
objective_name, passed by callers that have it to avoid re-unpackingoutputs. Unpacked fromoutputswhen omitted.
Returns¶
- list[str]
The variable names to compute for
objective_name.
- property objective_names: list[str] | None¶
The configured objective names (structure keys), or None for all.
- property objective_weight_keys: list[str]¶
The objective names this cost carries an explicit weight for.
- property residual_scale_factor: float¶
Scaling factor for regularizer residuals to match scalar semantics.
Regularizers contribute
w * f(x)²in scalar mode. In residual mode they outputsqrt(w) * f(x), which after scalarization becomesscalarize_factor * w * f(x)²wherescalarize_factoris 1.0 for most costs but 0.5 for GaussianLogLikelihood. To preserve the scalar contribution, regularizer residuals are scaled bysqrt(residual_scale_factor)before accumulation, whereresidual_scale_factor = 1 / scalarize_factor.
- scalarize(value: float | ndarray[tuple[Any, ...], dtype[_ScalarT]], mode: Literal['scalar', 'residuals'] = 'scalar') float¶
Convert a cost value to a scalar.
For scalar mode, returns the value unchanged. For residual mode, computes the sum of squares.
- set_calculation_structure(structure: dict[str, list[str] | None] | None) None¶
Set the exact objective/variable structure this cost computes over.
This is the single source of truth for both objective-level and variable-level scoping;
set_objective_names()andset_variable_names()are thin wrappers over it.Parameters¶
- structuredict[str, list[str] | None] | None
Maps each objective name to the variable names to compute for it, referring to the
outputs[objective]model structure.Nonefor an objective computes all of its variables; an empty list computes none of them. PassingNonefor the whole structure restores the default of computing every objective and variable.
- set_objective_names(objective_names: list[str]) None¶
Restrict this cost to the given objectives (all variables of each).
Convenience wrapper over
set_calculation_structure()that maps every name toNone(compute all of that objective’s variables).
- set_variable_names(objective_name: str, variable_names: list[str] | None) None¶
Set which variables to compute for a single objective.
Updates (or creates) the calculation structure entry for
objective_name.Nonecomputes all of that objective’s variables; an empty list computes none. Other objectives already in the structure are left unchanged.Notes¶
Inside a
DataFit, scoping a subset of objectives is safe: binding fills in the other fit objectives with all variables. Evaluated directly, the structure is the full scope — only the objectives it names are computed.
- supports_analytic_derivative: bool = False¶
Whether
derivative()can return an analytic result (else FD fallback). False on the base; subclasses with a closed-form derivative opt in.
- class ionworkspipeline.data_fits.objective_functions.costs.ErrorFunction(normalization: Normalization | str | float | None = None, nan_values=None, objective_weights: dict[str, float] | None = None, variable_weights: dict[str, float] | None = None)¶
Base class for error-based cost functions.
Parameters¶
- normalizationNormalization | str | float, optional
How to normalize weights across variables with different magnitudes. Options: “mean” (default), “identity”, “range”, “sum_squares”, “mean_squares”, “root_mean_squares”, or a float constant. Legacy names “sse”, “mse”, “rmse” are mapped to new names.
- nan_valuesstr | float, optional
How to handle NaN values in model output. Defaults to 1e6. Options: float value, “mean”, “min”, or “raise”.
- objective_weightsdict[str, float], optional
Per-objective weights.
- variable_weightsdict[str, float], optional
Per-variable weights.
Extends:
ObjectiveFunctionSee also:
ErrorFunction— field-level documentation.- check_output_shapes(outputs: dict) None¶
Warn on mismatched-length model/data variables this cost scores.
Element-wise costs (SSE/MSE/RMSE/MAE/Max) combine
modelanddatapoint-by-point, so differing shapes silently produce a meaningless result — a common footgun when a single cost is applied to an objective exposing both a model-axis variable and a shorter data-axis one. This mirrors the objective/variable iteration of__call__()but only inspects shapes; it is run once at fit setup (not in the evaluation hot path) because a mismatch is a one-time configuration problem. Distribution metrics (Wasserstein) setrequires_equal_length = Falseand are skipped, since unequal-length sample sets are expected there.Parameters¶
- outputsdict
Mapping of objective name to its
(model, data)output container, as built byDataFit.evaluate_inputs_and_outputs.
- derivative(outputs: dict, param_names: list[str], mode: Literal['scalar', 'residuals'] = 'residuals') ndarray[tuple[Any, ...], dtype[_ScalarT]] | None¶
Analytic residual Jacobian (
residuals) or scalar gradient2 Jᵀr(scalar); see_residual_jacobian()for the block formula and theNone-fallback conditions.
- nan_values(model_value: ndarray[tuple[Any, ...], dtype[_ScalarT]], data_value: ndarray[tuple[Any, ...], dtype[_ScalarT]]) float¶
Return the scalar fill value implied by the configured NaN policy.
Kept for backward compatibility.
Error Functions¶
- class ionworkspipeline.data_fits.objective_functions.costs.Max(normalization: Normalization | str | float | None = None, nan_values=None, objective_weights: dict[str, float] | None = None, variable_weights: dict[str, float] | None = None)¶
Maximum error cost function.
Returns the maximum absolute difference between model and data: Max = max|model - data|
Extends:
ErrorFunctionSee also:
Max— field-level documentation.
- class ionworkspipeline.data_fits.objective_functions.costs.SSE(normalization: Normalization | str | float | None = None, nan_values=None, objective_weights: dict[str, float] | None = None, variable_weights: dict[str, float] | None = None)¶
Sum-of-squared-errors cost function.
Calculates the sum of squared differences between model and data: SSE = Σ(model - data)²
Extends:
ErrorFunctionSee also:
SSE— field-level documentation.
- class ionworkspipeline.data_fits.objective_functions.costs.MSE(normalization: Normalization | str | float | None = None, nan_values=None, objective_weights: dict[str, float] | None = None, variable_weights: dict[str, float] | None = None)¶
Mean-squared-error cost function.
Calculates the mean of squared differences: MSE = Σ(model - data)² / n
Extends:
ErrorFunctionSee also:
MSE— field-level documentation.
- class ionworkspipeline.data_fits.objective_functions.costs.RMSE(normalization: Normalization | str | float | None = None, nan_values=None, objective_weights: dict[str, float] | None = None, variable_weights: dict[str, float] | None = None)¶
Root-mean-squared-error cost function.
Calculates the square root of MSE: RMSE = √(Σ(model - data)² / n)
This cost function only supports scalar output, so it cannot be used with a least-squares optimizer — including when nested inside a
MultiCost.DataFitrejects that pairing at build time.Extends:
ErrorFunctionSee also:
RMSE— field-level documentation.
- class ionworkspipeline.data_fits.objective_functions.costs.MAE(normalization: Normalization | str | float | None = None, nan_values=None, objective_weights: dict[str, float] | None = None, variable_weights: dict[str, float] | None = None)¶
Mean-absolute-error cost function.
Calculates the mean of absolute differences: MAE = Σ|model - data| / n
Extends:
ErrorFunctionSee also:
MAE— field-level documentation.
- class ionworkspipeline.data_fits.objective_functions.costs.ChiSquare(variable_standard_deviations: dict[str, float], nan_values=None)¶
Chi-square cost function with per-variable standard deviations.
Calculates chi² = Σ((model - data) / σ)² where σ is the standard deviation for each variable.
Parameters¶
- variable_standard_deviationsdict[str, float]
Dictionary mapping variable names to their standard deviations.
- nan_valuesstr | float, optional
How to handle NaN values.
Notes¶
Does not accept
normalization,objective_weights, orvariable_weights: normalization is fixed at 1, and per-variable weights are derived fromvariable_standard_deviationsinstead.Extends:
ErrorFunctionSee also:
ChiSquare— field-level documentation.
Multi-Cost Functions¶
- class ionworkspipeline.data_fits.objective_functions.costs.MultiCost(costs: list[ObjectiveFunction | WeightedCost], normalization: Normalization | str | float | None = None, nan_values=None, objective_weights: dict[str, float] | None = None, variable_weights: dict[str, float] | None = None)¶
Cost function combining multiple costs with weights.
Parameters¶
- costslist
The component costs. A bare cost gets weight 1.0; wrap one in
WeightedCostto give it another. At least one component is required. A{"cost": <cost>, "weight": <float>}record — the wire shapeionworks_schema/.to_config()emit — is not accepted here; build aWeightedCostinstead. A{cost: weight}mapping is not accepted either.
Notes¶
normalization,nan_values,objective_weightsandvariable_weightsare rejected here —__call__()only weights and sums what its components return, so it cannot apply them. Set them on the component costs instead.calculation_structureis accepted only as an objective filter — every value must beNone. Per-variable scoping belongs on the component costs.Extends:
ErrorFunctionSee also:
MultiCost— field-level documentation.- check_output_shapes(outputs: dict) None¶
Delegate to each sub-cost (each has its own metric and scoping).
- derivative(outputs: dict, param_names: list[str], mode: Literal['scalar', 'residuals'] = 'residuals') ndarray[tuple[Any, ...], dtype[_ScalarT]] | None¶
Combine the child costs’ analytic derivatives.
Mirrors
__call__()’s child composition exactly: inmode="residuals"the children’s residual Jacobians are stacked, each scaled bysqrt(child_weight)(matching thesqrt(weight)the residual accumulator applies to each child block); inmode="scalar"the children’s gradients are summed withchild_weight. ReturnsNoneif any child cannot provide an analytic derivative, so the whole MultiCost falls back to finite differences.
- classmethod from_schema(schema)¶
Build from an
ionworks_schemaMultiCost, converting sub-costs.Overrides the generic
ObjectiveFunction.from_schema: its_convert_field_from_schemamaps only one level over thecostslist and can’t unwrap aWeightedCostrecord, so the sub-costs are converted here.costsis aMultiCost’s only constructor field (the rest are rejected), so there are no other kwargs to forward; scoping is read straight off the schema — whose own validator already enforces the “only one of the two” rule — and applied via_apply_schema_scoping().
- scalarize(value: float | ndarray[tuple[Any, ...], dtype[_ScalarT]], mode: Literal['scalar', 'residuals'] = 'scalar') float¶
Convert the finalized cost value to a scalar.
Uses the passed-in value directly, which includes any regularization terms added after MultiCost.__call__ returns. This ensures Evaluation.value_scalarized reflects the true total cost.
- set_calculation_structure(structure: dict[str, list[str] | None] | None) None¶
Set the objectives this MultiCost covers; reject per-variable scoping.
A MultiCost only weights and sums what its components return, so it has no variable axis to apply. The objective axis is honoured — that is how binding restricts a MultiCost to a fit’s objectives — so only explicit variable lists are refused. Scope variables on the component costs.
Parameters¶
- structuredict[str, list[str] | None] | None
Maps each objective name to
None. PassingNonefor the whole structure restores the default of computing every objective.
- property supports_analytic_derivative: bool¶
bool(x) -> bool
Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.
Design Functions¶
- class ionworkspipeline.data_fits.objective_functions.costs.DesignFunction(objective_weights: dict[str, float] | None = None)¶
Cost function for design optimization problems.
Used for optimization objectives where the goal is to maximize or minimize design metrics (energy density, power density, etc.) rather than fit to data.
Parameters¶
- objective_weightsdict[str, float], optional
Per-objective weights.
Notes¶
Does not accept
nan_valuesorvariable_weights: design metrics are scalar-per-objective, so there is no per-variable axis to weight or fill NaNs on.Extends:
ObjectiveFunctionSee also:
DesignFunction— field-level documentation.