Combined Objective Functions

The CombinedObjectiveFunction class represents a weighted sum of multiple objective functions.

It allows several independent objectives (each mapping params: dict -> float) to be combined into a single callable that can be minimized by a Fitter.

This is useful when fitting against multiple datasets, configurations, or loss metrics simultaneously.

Basic concept

Each objective function is paired with a non-negative weight. When the combined objective is called, all sub-objectives are evaluated using the same parameter dictionary, multiplied by their weights, and summed.

combined_loss(params) = sum_i ( weight_i * objective_i(params) )

Example

from chemfit.combined_objective_function import CombinedObjectiveFunction

# Define two simple objectives
def ob1(params):
    return (params["x"] - 1.0)**2

def ob2(params):
    return (params["x"] - 3.0)**2

combined = CombinedObjectiveFunction(
    objective_functions=[ob1, ob2],
    weights=[0.5, 1.0],
)

loss = combined({"x": 2.0})
print(loss)  # 0.5*(1.0)^2 + 1.0*(1.0)^2 = 1.5

Constructor

CombinedObjectiveFunction(objective_functions, weights=None)

Parameters

  • objective_functions Sequence of callables f(params: dict[str, float]) -> float. Each function represents one objective term.

  • weights (optional) Sequence of non-negative floats. If omitted, all weights default to 1.0.

Raises

  • AssertionError if len(weights) does not match the number of objectives.

  • AssertionError if any weight is negative.

Attributes

  • objective_functions List of all stored objective callables.

  • weights List of non-negative weights, aligned with the objective list.

Calling the combined objective

The combined objective is itself callable:

result = combined(params)

By default, all internal terms are included. A subset can be selected via the optional idx_slice argument:

# Evaluate only the first term
loss = combined(params, idx_slice=slice(0, 1))

Methods

n_terms()

Returns the number of objective terms.

n = combined.n_terms()  # integer

add(obj_funcs, weights=1.0)

Add one or more new objective functions (and corresponding weights) to the instance.

def ob3(params): return (params["x"] - 4.0)**2
combined.add(ob3, weights=0.2)

# Or add multiple at once
combined.add([ob1, ob2], weights=[0.5, 0.5])

Notes:

  • If weights is a single float, it is reused for each new objective.

  • All new weights must be non-negative.

  • The function returns self for method chaining.


add_flat(combined_objective_functions_list, weights=None)

Class method that merges several CombinedObjectiveFunction instances into a new, flat one. Each sub-instance’s internal weights are scaled by its associated outer weight.

combined1 = CombinedObjectiveFunction([ob1, ob2], [1.0, 2.0])
combined2 = CombinedObjectiveFunction([ob3], [0.5])

combined_flat = CombinedObjectiveFunction.add_flat(
    [combined1, combined2],
    weights=[1.0, 0.2],
)

print(combined_flat.n_terms())  # 3

This is especially useful when building composite objectives programmatically (such as combining multiple molecule or configuration terms).


get_meta_data()

Returns basic metadata about the combined objective:

meta = combined.get_meta_data()
# Example: {"n_terms": 3, "type": "CombinedObjectiveFunction"}

gather_meta_data(idx_slice=slice(None))

Collects metadata from all sub-objectives that support get_meta_data() (for example, instances of ObjectiveFunctor).

Returns a list of dictionaries or None for objectives without metadata.

meta_list = combined.gather_meta_data()
for entry in meta_list:
    print(entry)

Practical use with Fitter

The combined objective can be minimized directly using a Fitter:

from chemfit import Fitter

fitter = Fitter(objective_function=combined, initial_params={"x": 0.0})
opt_params = fitter.fit_scipy()

print(opt_params)

This pattern allows you to easily combine multiple QuantityComputerObjectiveFunction terms, each corresponding to a different dataset, configuration, or physical property.

Example (sketch):

combined = CombinedObjectiveFunction(
    [
        energy_objective,     # e.g., fit energies
        force_objective,      # e.g., fit forces
        dipole_objective,     # e.g., fit dipole moments
    ],
    weights=[1.0, 0.2, 0.5],
)

fitter = Fitter(combined, initial_params=params)
fitter.fit_nevergrad(budget=200)

Summary

  • Combines multiple objective functions into a single weighted sum.

  • Supports arbitrary callables that accept params: dict[str, float].

  • Weights can be adjusted, extended, or merged at runtime.

  • Compatible with Fitter.

  • Useful for multi-objective fitting (energies, forces, properties, etc.).

  • Provides metadata aggregation for downstream analysis.