Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<p align="center">
<img src="https://github.com/msallermann/chemfit/blob/next/logo/chemfit_logo.svg?raw=true" width="400"/>
<img src="https://github.com/msallermann/chemfit/blob/next/logo/chemfit_logo_portable.svg?raw=true" width="400"/>
</p>

# About

ChemFit is a Python package for concurrent force-field parameter optimization. It can be used with ASE calculators and external executables.

# Documentation

Please check the **documentation** for details [here](https://chemfit.readthedocs.io).


# Installation

From PyPi:
Expand All @@ -21,18 +26,14 @@ git clone git@github.com:MSallermann/chemfit.git
pip install chemfit
```

# Documentation

Please check the **documentation** for details [here](https://chemfit.readthedocs.io/en/latest/).

# Citation

If you find ChemFit useful and happen to use it in any academic context, please use this reference to cite it:

```
@misc{sallermann2026chemfitconcurrentframeworkmodel,
title={ChemFit: A concurrent framework for model parametrization},
author={Moritz Sallermann and Amrita Goswami and Hannes Jónsson and Elvar Ö. Jónsson and Jorge R. Espinosa},
@misc{sallermann2026chemfitframeworkautomatedhighdimensional,
title={ChemFit: A framework for automated high-dimensional model parameter optimization},
author={Moritz Sallermann and Amrita Goswami and Rosana Collepardo-Guevara and Alberto Ocana and Hannes Jónsson and Elvar Ö. Jónsson and Jorge R. Espinosa},
year={2026},
eprint={2603.11769},
archivePrefix={arXiv},
Expand Down
48 changes: 23 additions & 25 deletions docs/src/images/chemfit_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
140 changes: 0 additions & 140 deletions docs/src/usage/abstract_interface.rst

This file was deleted.

1 change: 1 addition & 0 deletions docs/src/usage/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ independent contributions.
-------------------------

.. _concepts_parallel_eval:

Independent terms and parallelism
==================================

Expand Down
109 changes: 109 additions & 0 deletions docs/src/usage/fitter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ ChemFit currently supports two optimization backends:
1. :py:meth:`~chemfit.fitter.Fitter.fit_scipy`
2. :py:meth:`~chemfit.fitter.Fitter.fit_nevergrad`

Custom optimizers can be connected through the user-driven
``init``/``ask``/``tell`` interface.

Both operate on the same parameter-dictionary interface.

----------------------------------
Expand Down Expand Up @@ -251,6 +254,108 @@ ask/tell interface.
optimizer_str="NgIohTuned",
)

----------------------------------
User-supplied ask/tell interface
----------------------------------

An optimizer can be integrated without a dedicated backend. The user owns the
loop while ChemFit evaluates parameter dictionaries, maintains contexts and
dispatches its callbacks.

.. code-block:: python

optimizer = MyOptimizer(initial_params, bounds)
fitter.init()

for _ in range(100):
params = optimizer.ask()
loss = fitter.ask(params)
optimizer.tell(params, loss)
fitter.tell() # dispatch registered progress callbacks

opt_params = fitter.finish(optimizer.recommendation())

``fitter.ask`` also accepts a list of candidates and evaluates it in parallel
when ``fitter.init`` is configured with ``num_workers`` and an executor. If no
recommendation is passed to ``finish``, ChemFit returns the best parameters it
actually evaluated.

A complete custom-loop example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The optimizer only needs to propose parameter dictionaries and accept their
losses. For example, this small optimizer searches a predefined set of points:

.. code-block:: python

class CandidateSearch:
def __init__(self, candidates):
self.candidates = iter(candidates)
self.observations = []

def ask(self):
return next(self.candidates)

def tell(self, params, loss):
self.observations.append((loss, params))

def recommendation(self):
return min(self.observations, key=lambda item: item[0])[1]


def objective(params):
return (params["x"] - 2.0) ** 2


fitter = Fitter(objective, initial_params={"x": 0.0})
optimizer = CandidateSearch([{"x": 0.0}, {"x": 2.0}, {"x": 4.0}])

fitter.register_callback(print_progress, n_steps=1)
fitter.init()

for _ in range(3):
params = optimizer.ask()
loss = fitter.ask(params)
optimizer.tell(params, loss)
fitter.tell()

opt_params = fitter.finish(optimizer.recommendation())

Calling ``fitter.ask`` applies the same objective wrapping, invalid-value
handling and context bookkeeping as the built-in SciPy and Nevergrad fitting
methods. Calling ``fitter.tell`` marks the end of an optimizer step and invokes
callbacks registered for that step. ``fitter.finish`` runs the usual post-fit
checks.

User-driven parallel batches
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Candidates can also be evaluated in batches while the user retains control of
the optimizer loop:

.. code-block:: python

from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(4) as executor:
fitter.init(num_workers=4, executor=executor)

for _ in range(25):
candidates = [optimizer.ask() for _ in range(4)]
losses = fitter.ask(candidates)

for params, loss in zip(candidates, losses):
optimizer.tell(params, loss)

fitter.tell()

opt_params = fitter.finish(optimizer.recommendation())

One :class:`~chemfit.fitter.FitterEvaluateContext` is maintained per worker.
The losses returned by ``fitter.ask`` have the same order as the candidate
list. If no executor is supplied, ``fitter.init(num_workers=...)`` creates and
later shuts down a thread pool automatically.

----------------------------------
Parallel Nevergrad execution
----------------------------------
Expand All @@ -264,6 +369,10 @@ Parallel evaluation is supported via ``num_workers``:
num_workers=4,
)

The evaluation budget is exact. When it is not divisible by ``num_workers``,
the final batch contains only the remaining candidates. For example,
``budget=10`` with four workers evaluates batches of four, four and two.

Each worker uses its own
:py:class:`~chemfit.fitter.FitterEvaluateContext`.

Expand Down
Loading
Loading