Skip to content

Commit 434f06e

Browse files
committed
Added throw NaN error for restart geometry steps
1 parent 8c87685 commit 434f06e

File tree

6 files changed

+33
-7
lines changed

6 files changed

+33
-7
lines changed

dfols/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from __future__ import absolute_import, division, print_function, unicode_literals
4040

4141
# DFO-LS version
42-
__version__ = '1.6.3'
42+
__version__ = '1.6.4'
4343

4444
# Main solver & exit flags
4545
from .solver import *

dfols/controller.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -596,11 +596,11 @@ def trust_region_step(self, params, criticality_measure=1e-2):
596596

597597
return d, gopt, H, gnew, crvmin
598598

599-
def geometry_step(self, knew, adelt, number_of_samples, params):
599+
def geometry_step(self, knew, adelt, number_of_samples, params, throw_error_on_nans=False):
600600
if self.do_logging:
601601
module_logger.debug("Running geometry-fixing step")
602602
try:
603-
c, g = self.model.lagrange_gradient(knew)
603+
c, g = self.model.lagrange_gradient(knew, throw_error_on_nans=throw_error_on_nans)
604604
# c = 1.0 if knew == self.model.kopt else 0.0 # based at xopt, just like d
605605
if self.model.projections:
606606
# Solve problem: use projection onto arbitrary constraints, and ||xnew-xopt|| <= adelt
@@ -707,7 +707,7 @@ def choose_point_to_replace(self, d, skip_kopt=True):
707707
exit_info = None
708708

709709
try:
710-
cs, gs = self.model.lagrange_gradient(k=None) # find all Lagrange polynomials for k in range(self.model.npt())
710+
cs, gs = self.model.lagrange_gradient(k=None, throw_error_on_nans=False) # find all Lagrange polynomials for k in range(self.model.npt())
711711
except LA.LinAlgError:
712712
exit_info = ExitInformation(EXIT_LINALG_ERROR, "Singular matrix when choosing point to replace")
713713
return knew, exit_info
@@ -872,9 +872,16 @@ def soft_restart(self, number_of_samples, nruns_so_far, params, x_in_abs_coords_
872872
# Determine which point to update (knew)
873873
knew = closest_points[i]
874874

875+
throw_error_on_nans = params("restarts.throw_error_on_nans")
876+
# If throwing error on NaNs, can safely skip this on the first iteration, since we always force moving the base point
877+
# So, the first two evaluations from a restart will always be the same (third may change depending on if
878+
# second evaluation becomes the new base/current iterate or not)
879+
if params("restarts.soft.move_xk") and i == 0:
880+
throw_error_on_nans = False
881+
875882
# Using adelt=delta in fix_geometry (adelt determines the ball to max lagrange poly in altmov)
876883
# [Only reason actual 'delta' is needed in fix_geometry is for calling nsamples()]
877-
exit_info = self.geometry_step(knew, self.delta, number_of_samples, params)
884+
exit_info = self.geometry_step(knew, self.delta, number_of_samples, params, throw_error_on_nans=throw_error_on_nans)
878885
if exit_info is not None:
879886
return exit_info
880887

dfols/model.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,9 @@ def build_full_model(self):
413413
H = 2.0 * np.dot(J.T, J)
414414
return g, H
415415

416-
def lagrange_gradient(self, k=None, factorise_first=True):
416+
def lagrange_gradient(self, k=None, factorise_first=True, throw_error_on_nans=False):
417+
# throw_error_on_nans: throw an error if the *actual* rhs (i.e. objective values) have nans
418+
# (needed for optclim with soft restarts)
417419
if factorise_first:
418420
self.factorise_geom_system()
419421

@@ -423,6 +425,14 @@ def lagrange_gradient(self, k=None, factorise_first=True):
423425
rhs[k] = 1.0
424426
else:
425427
rhs = np.eye(self.npt()) # find all Lagrange polynomials
428+
429+
if throw_error_on_nans:
430+
fval_row_idx = np.arange(self.npt()) # indices of all rows
431+
if np.any(np.isnan(self.fval_v[fval_row_idx, :])): # check objective values
432+
if self.do_logging:
433+
module_logger.warning("model.lagrange_gradient: NaNs encountered in objective evaluations, raising error")
434+
raise np.linalg.LinAlgError("NaN encountered in objective evaluations during Lagrange polynomial construction")
435+
426436
soln = self.solve_geom_system(rhs)
427437

428438
if k is not None:

dfols/params.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def __init__(self, n, npt, maxfun, objfun_has_noise=False):
7777
self.params["regression.momentum_extra_steps"] = False # use momentum (False) or geometry (True) to do steps
7878
# Restarts
7979
self.params["restarts.use_restarts"] = True if objfun_has_noise else False
80+
self.params["restarts.throw_error_on_nans"] = False # throw numpy.linalg.LinAlgError if building a Lagrange polynomial with nan objective values?
8081
self.params["restarts.max_unsuccessful_restarts"] = 10
8182
self.params["restarts.rhoend_scale"] = 1.0 # how much to decrease rhoend by after each restart
8283
self.params["restarts.use_soft_restarts"] = True
@@ -211,6 +212,8 @@ def param_type(self, key, npt):
211212
type_str, nonetype_ok, lower, upper = 'bool', False, None, None
212213
elif key == "restarts.use_restarts":
213214
type_str, nonetype_ok, lower, upper = 'bool', False, None, None
215+
elif key == "restarts.throw_error_on_nans":
216+
type_str, nonetype_ok, lower, upper = 'bool', False, None, None
214217
elif key == "restarts.max_unsuccessful_restarts":
215218
type_str, nonetype_ok, lower, upper = 'int', False, 0, None
216219
elif key == "restarts.rhoend_scale":

docs/advanced.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ Regression Model Management
6767

6868
Multiple Restarts
6969
-----------------
70-
* :code:`restarts.use_restarts` - Whether to do restarts when :math:`\rho_k` reaches :math:`\rho_{end}`, or (optionally) when all points are within noise level of :math:`f(x_k)`. Default is :code:`False` for smooth problems or :code:`True` for noisy problems.
70+
* :code:`restarts.use_restarts` - Whether to do restarts when :math:`\rho_k` reaches :math:`\rho_{end}`, or (optionally) when all points are within noise level of :math:`f(x_k)`. Default is :code:`False` for smooth problems or :code:`True` for noisy problems.
71+
* :code:`restarts.throw_error_on_nans` - whether or not to throw :code:`numpy.linalg.LinAlgError` if trying to interpolate to NaN objective values. If :code:`False`, DFO-LS should terminate gracefully with an error flag. Default is :code:`False`.
7172
* :code:`restarts.max_unsuccessful_restarts` - Maximum number of consecutive unsuccessful restarts allowed (i.e.~restarts which did not reduce the objective further). Default is 10.
7273
* :code:`restarts.rhoend_scale` - Factor to reduce :math:`\rho_{end}` by with each restart. Default is 1.
7374
* :code:`restarts.use_soft_restarts` - Whether to use soft or hard restarts. Default is :code:`True`.

docs/history.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,8 @@ Version 1.6.2 (21 Jan 2026)
102102
Version 1.6.3 (16 Mar 2026)
103103
-------------------------
104104
* Allow parameter :code:`init.run_in_parallel=True` when :code:`init.random_initial_directions=False`
105+
106+
Version 1.6.4 (23 Mar 2026)
107+
-------------------------
108+
* Add new parameter :code:`restarts.throw_error_on_nans` to customize handling of NaN objective values during restarts
109+

0 commit comments

Comments
 (0)