Skip to content

Interim updates#232

Open
rozyczko wants to merge 7 commits intodevelopfrom
interim_updates
Open

Interim updates#232
rozyczko wants to merge 7 commits intodevelopfrom
interim_updates

Conversation

@rozyczko
Copy link
Copy Markdown
Member

This pull request adds support for progress callbacks during fitting operations. The main goal is to allow users to receive progress updates and optionally cancel fits in progress. Added progress_callback parameter and a way to handle cancellation gracefully.

  • Added an optional progress_callback parameter to the fit methods in Fitter, MinimizerBase, MultiFitter and all minimizer subclasses, allowing users to receive iterative progress updates and cancel fits.

  • Implemented progress callback integration in LMFit: added _create_iter_callback to wrap the user callback, constructed detailed progress payloads, and enabled fit cancellation by raising a new FitCancelled exception. Ensured parameter values are restored on cancellation or error.

  • Added the FitCancelled exception class to signal user-requested cancellation, and ensured proper restoration of parameter values on cancellation or error.

Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request does not contain a valid label. Please add one of the following labels: ['[scope] bug', '[scope] enhancement', '[scope] documentation', '[scope] significant', '[scope] maintenance']

@rozyczko
Copy link
Copy Markdown
Member Author

Required for easyscience/EasyReflectometryApp#290

@rozyczko rozyczko added [scope] enhancement Adds/improves features (major.MINOR.patch) [priority] medium Normal/default priority [area] base classes Changes to or creation of new base classes labels Apr 13, 2026
@rozyczko rozyczko marked this pull request as ready for review April 17, 2026 08:01
Copy link
Copy Markdown
Contributor

@damskii9992 damskii9992 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR needs an ADR explaining the software architecture design implemented and the rationale for why this design was chosen etc.
We probably also need a tutorial showcasing how the callback might be used. Both for ourselves as a kind of documentation but also for potential users.
I have not bothered looking at the unit tests.


def _restore_parameter_values(self) -> None:
for key in self._cached_pars.keys():
self._cached_pars[key].value = self._cached_pars_vals[key][0]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably also want to restore the error/variance here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this method should be reused in the fit method of all the minimizers when fitting fails.

method: Optional[str] = None,
tolerance: Optional[float] = None,
max_evaluations: Optional[int] = None,
progress_callback: Optional[Callable[[dict], Optional[bool]]] = None,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't type hint optional parameters with Optional anymore, use | None.

history.requires(step=1)

def __call__(self, history):
self.last_step = int(history.step[0])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use the actual class implemented in Bumps? StepMonitor in fitters seems to do exactly what you want.

problem=self._problem,
iteration=int(history.step[0]),
point=np.asarray(history.point[0]),
nllf=float(history.value[0]),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nllf? What is that supposed to mean?

tolerance # tolerance for change in parameter value, could be an independent value
)
minimizer_kwargs['ftol'] = tolerance
minimizer_kwargs['xtol'] = tolerance
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably shouldn't remove the comments here. They're good to have to remember what the difference between xtol and ftol is without having to find it in the Bumps source code. ( I really hate that they don't have a documentation).

results = self._gen_fit_results(model_results, weights)
except FitError:
self._restore_parameter_values()
raise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an empty raise statement? What happens then? O.o

raise ValueError('Weights must be strictly positive and non-zero.')

if callback_every < 1:
raise ValueError('callback_every must be a positive integer.')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about floats like 1.3?

results.y_obs = self._cached_model.y
results.y_calc = self.evaluate(results.x, minimizer_parameters=results.p)
results.y_err = weights
results.n_evaluations = int(fit_results.nf)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be fit_results.nfev?

if getattr(results, name, False):
setattr(results, name, value)
results.success = not bool(fit_results.flag)
results.success = fit_results.flag == fit_results.EXIT_SUCCESS
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider raising a warning if the fit did not succeed.

chi2_val = self.chi2
reduced_val = self.reduced_chi2
if not np.isfinite(chi2_val) or not np.isfinite(reduced_val):
raise ValueError
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error should probably have some context

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[area] base classes Changes to or creation of new base classes [priority] medium Normal/default priority [scope] enhancement Adds/improves features (major.MINOR.patch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants