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 wrappers set_objective_names() and set_variable_names(). The structure maps each objective name to the variables to compute for it, or None for all; an empty list computes none. It round-trips through to_config() under the calculation_structure key.

Extends: ConfigMixin

See 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 None for an objective computes all of that objective’s variables; an empty list computes none of them. A value of None for 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 by param_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_schema objective function/cost instance.

Overrides ConfigMixin.from_schema: calculation_structure/ objective_names are 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() — mirroring parse_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”), while None — 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-unpacking outputs. Unpacked from outputs when 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.

objective_weights(name: str, default: float = 1.0) float

Get the weight for an objective by name.

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 output sqrt(w) * f(x), which after scalarization becomes scalarize_factor * w * f(x)² where scalarize_factor is 1.0 for most costs but 0.5 for GaussianLogLikelihood. To preserve the scalar contribution, regularizer residuals are scaled by sqrt(residual_scale_factor) before accumulation, where residual_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() and set_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. None for an objective computes all of its variables; an empty list computes none of them. Passing None for 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 to None (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. None computes 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.

to_config() dict

Convert the cost function to parser configuration format.

property variable_weight_keys: list[str]

The variable names this cost carries an explicit weight for.

variable_weights(name: str, default: float = 1.0) float

Get the weight for a variable by name.

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: ObjectiveFunction

See 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 model and data point-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) set requires_equal_length = False and are skipped, since unequal-length sample sets are expected there.

Parameters

outputsdict

Mapping of objective name to its (model, data) output container, as built by DataFit.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 gradient 2 Jᵀr (scalar); see _residual_jacobian() for the block formula and the None-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.

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.

to_config() dict

Convert the cost function to parser configuration format.

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: ErrorFunction

See 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: ErrorFunction

See 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: ErrorFunction

See 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. DataFit rejects that pairing at build time.

Extends: ErrorFunction

See 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: ErrorFunction

See 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, or variable_weights: normalization is fixed at 1, and per-variable weights are derived from variable_standard_deviations instead.

Extends: ErrorFunction

See also: ChiSquare — field-level documentation.

to_config() dict

Convert the cost function to parser configuration format.

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 WeightedCost to give it another. At least one component is required. A {"cost": <cost>, "weight": <float>} record — the wire shape ionworks_schema/.to_config() emit — is not accepted here; build a WeightedCost instead. A {cost: weight} mapping is not accepted either.

Notes

normalization, nan_values, objective_weights and variable_weights are rejected here — __call__() only weights and sums what its components return, so it cannot apply them. Set them on the component costs instead.

calculation_structure is accepted only as an objective filter — every value must be None. Per-variable scoping belongs on the component costs.

Extends: ErrorFunction

See 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: in mode="residuals" the children’s residual Jacobians are stacked, each scaled by sqrt(child_weight) (matching the sqrt(weight) the residual accumulator applies to each child block); in mode="scalar" the children’s gradients are summed with child_weight. Returns None if any child cannot provide an analytic derivative, so the whole MultiCost falls back to finite differences.

classmethod from_schema(schema)

Build from an ionworks_schema MultiCost, converting sub-costs.

Overrides the generic ObjectiveFunction.from_schema: its _convert_field_from_schema maps only one level over the costs list and can’t unwrap a WeightedCost record, so the sub-costs are converted here. costs is a MultiCost’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. Passing None for 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.

property supports_residuals: 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.

property supports_scalar: 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_values or variable_weights: design metrics are scalar-per-objective, so there is no per-variable axis to weight or fill NaNs on.

Extends: ObjectiveFunction

See also: DesignFunction — field-level documentation.

parse_variable_value(value: float | list | tuple | ndarray[tuple[Any, ...], dtype[_ScalarT]], variable_name: str) float

Parse a variable value from model output.