Concepts¶
This page introduces the core concepts behind ChemFit and explains how objective functions are structured and evaluated.
The goal is to provide a mental model that helps you design your own objective functions and understand how ChemFit executes them.
Objective functions as compositions¶
In ChemFit, an objective function is built from small, composable pieces.
The basic building blocks are:
A
QuantityComputer, which computes intermediate quantities from a parameter dictionary.A loss function, which maps these quantities to a scalar value.
These two are combined into an ObjectiveFunctor.
More complex objectives are constructed by combining multiple such terms using
CombinedObjectiveFunction.
Conceptually, this looks like:
parameters
↓
QuantityComputer
↓
ObjectiveFunctor (loss)
↓
CombinedObjectiveFunction
↓
total loss
This structure allows complex objectives to be built from many simple, independent contributions.
Note
For advanced use-cases, combined objective functions can compute their loss
from the quantities of individual child terms, instead of simple scalar losses.
This is achieved by supplying a custom Aggregator.
Independent terms and parallelism¶
A key design principle of ChemFit is that objective terms are evaluated independently.
When using CombinedObjectiveFunction,
each term is evaluated in isolation and contributes a scalar value to the final loss.
This enables parallel evaluation:
Different terms can be evaluated concurrently
No shared mutable state is required
The same objective definition works for serial, threaded, or MPI execution
As a result, parallelization is built into the structure of the objective function, rather than being added on top.
The execution model is defined by the objective function itself. Parallel execution is provided by wrapper implementations, which evaluate the same objective structure using different execution backends (e.g. threads or MPI).
Evaluation contexts¶
ChemFit uses an EvaluateContext
to store all per-evaluation state.
A context is created for each evaluation and passed through the computation.
It serves several purposes:
storing input parameters
recording computed quantities
storing the final loss
collecting metadata during evaluation
Each evaluation has its own context, which avoids shared mutable state and makes parallel execution safe.
In simple use cases, you do not need to interact with the context explicitly. However, it becomes useful when you want to inspect results or record additional information.
Example¶
The context can be used to record additional information during evaluation:
from chemfit.wrap_funcs import to_quantity_computer
from chemfit.abstract_objective_function import EvaluateContext
@to_quantity_computer(pass_ctx=True)
def computer(params, ctx):
value = params["x"] ** 2
ctx.meta["x_squared"] = value
return {"x2": value}
ctx = EvaluateContext()
computer({"x": 3.0}, ctx)
print(ctx.quantities) # {'x2': 9.0}
print(ctx.meta) # {'x_squared': 9.0}
Parent and child contexts¶
When combining multiple objective terms, ChemFit creates a hierarchy of contexts.
The top-level evaluation uses a parent context
Each objective term is evaluated in its own child context
Conceptually:
parent context
├── child context (term 1)
├── child context (term 2)
└── ...
Each child context stores the results of its corresponding term.
After evaluation, relevant information from child contexts can be collected into the parent context as metadata.
This structure mirrors the composition of the objective function and is key to enabling parallel execution.
Metadata¶
Each context contains a meta dictionary that can be used to store
additional information during evaluation.
Typical uses include:
recording intermediate results
storing diagnostic information
tracking which terms contributed to the final loss
Metadata is propagated upward from child contexts to the parent context, allowing you to inspect the full evaluation after it completes.
In addition, QuantityComputer
instances can define static metadata via their static_meta_data attribute.
This information is automatically merged into the context’s meta dictionary
during evaluation, allowing components to annotate results with descriptive or
structural information.
Parallel evaluations require separate contexts¶
An EvaluateContext stores
the state of exactly one evaluation.
This means that if the same objective function is evaluated for multiple parameter dictionaries in parallel, each evaluation must use its own context.
Conceptually:
objective(params_1, ctx_1)
objective(params_2, ctx_2)
objective(params_3, ctx_3)
Using one context per evaluation avoids shared mutable state and makes parallel evaluation safe.
This is distinct from the child-context mechanism used inside
CombinedObjectiveFunction,
where one parent context owns multiple child contexts for the terms of a
single objective evaluation.
Design principles¶
ChemFit is built around a few key ideas:
Composition over monoliths Complex objectives are assembled from small, independent pieces.
Explicit data flow All evaluation state is passed through contexts, avoiding hidden global state.
Separation of concerns Quantity computation and loss evaluation are kept separate.
These principles make ChemFit flexible, extensible, and well-suited for large-scale fitting problems.