From 208ffe2b51a41d2d9db8a586fbd6cfb654d67a6d Mon Sep 17 00:00:00 2001 From: rolo Date: Fri, 28 Nov 2025 14:46:02 +0100 Subject: [PATCH] Make required properties abstract --- popt/loop/ensemble_base.py | 7 +-- popt/loop/optimize.py | 42 +++++++++++---- popt/misc_tools/optim_tools.py | 4 +- popt/update_schemes/enopt.py | 29 +++++++++-- popt/update_schemes/genopt.py | 25 +++++++-- popt/update_schemes/linesearch.py | 80 ++++++++++++++++++----------- popt/update_schemes/smcopt.py | 25 +++++++-- popt/update_schemes/trust_region.py | 62 ++++++++++++++-------- 8 files changed, 194 insertions(+), 80 deletions(-) diff --git a/popt/loop/ensemble_base.py b/popt/loop/ensemble_base.py index e5363a2a..b3dd3fc8 100644 --- a/popt/loop/ensemble_base.py +++ b/popt/loop/ensemble_base.py @@ -155,20 +155,21 @@ def function(self, x, *args, **kwargs): self._invert_scale_state() # ensure that state is in [lb,ub] self._set_multilevel_state(self.state, x) # set multilevel state if applicable run_success = self.calc_prediction(save_prediction=self.save_prediction) # calculate flow data - self._set_multilevel_state(self.state, x) # For some reason this has to be done again after calc_prediction - self._scale_state() # scale back to [0, 1] + self._set_multilevel_state(self.state, x) # toggle back after calc_prediction # Evaluate the objective function if run_success: func_values = self.obj_func( self.pred_data, input_dict=self.sim.input_dict, - true_order=self.sim.true_order, + true_order=self.sim.true_order, + state=self.state, # pass state for possible use in objective function **kwargs ) else: func_values = np.inf # the simulations have crashed + self._scale_state() # scale back to [0, 1] if len(x.shape) == 1: self.state_func_values = func_values else: self.ens_func_values = func_values diff --git a/popt/loop/optimize.py b/popt/loop/optimize.py index eae2bd7f..b2c4a82a 100644 --- a/popt/loop/optimize.py +++ b/popt/loop/optimize.py @@ -4,6 +4,7 @@ import logging import time import pickle +from abc import ABC, abstractmethod # Internal imports import popt.misc_tools.optim_tools as ot @@ -20,7 +21,7 @@ logger.addHandler(console_handler) -class Optimize: +class Optimize(ABC): """ Class for ensemble optimization algorithms. These are classified by calculating the sensitivity or gradient using ensemble instead of classical derivatives. The loop is else as a classic optimization loop: a state (or control @@ -102,12 +103,8 @@ def __init__(self, **options): self.epf_iteration = 0 # Initialize variables (set in subclasses) - # TODO: these variables should be abstract properties that subclasses are forced to define self.options = None - self.mean_state = None self.obj_func_values = None - self.fun = None # objective function - self.obj_func_tol = None # objective tolerance limit # Initialize number of function and jacobi evaluations self.nfev = 0 @@ -115,6 +112,27 @@ def __init__(self, **options): self.msg = 'Convergence was met :)' + # Abstract function that subclasses are forced to define + @abstractmethod + def fun(self, x, *args, **kwargs): # objective function + pass + + # Abstract properties that subclasses are forced to define + @property + @abstractmethod + def xk(self): # current state + pass + + @property + @abstractmethod + def ftol(self): # function tolerance + pass + + @ftol.setter + @abstractmethod + def ftol(self, value): # setter for function tolerance + pass + def run_loop(self): """ This is the main optimization loop. @@ -138,7 +156,7 @@ def run_loop(self): epf_not_converged = True previous_state = None if self.epf: - previous_state = self.mean_state + previous_state = self.xk logger.info(f' -----> EPF-EnOpt: {self.epf_iteration}, {self.epf["r"]} (outer iteration, penalty factor)') # print epf info while epf_not_converged: # outer loop using epf @@ -178,14 +196,14 @@ def run_loop(self): if self.epf_iteration > self.epf['max_epf_iter']: # max epf_iterations set to 10 logger.info(f' -----> EPF-EnOpt: maximum epf iterations reached') # print epf info break - p = np.abs(previous_state-self.mean_state) / (np.abs(previous_state) + 1.0e-9) + p = np.abs(previous_state-self.xk) / (np.abs(previous_state) + 1.0e-9) conv_crit = self.epf['conv_crit'] if np.any(p > conv_crit): epf_not_converged = True - previous_state = self.mean_state + previous_state = self.xk self.epf['r'] *= self.epf['r_factor'] # increase penalty factor - self.obj_func_tol *= self.epf['tol_factor'] # decrease tolerance - self.obj_func_values = self.fun(self.mean_state, epf = self.epf) + self.ftol *= self.epf['tol_factor'] # decrease tolerance + self.obj_func_values = self.fun(self.xk, epf = self.epf) self.iteration = 0 self.epf_iteration += 1 optimize_result = ot.get_optimize_result(self) @@ -196,9 +214,10 @@ def run_loop(self): logger.info(f' -----> EPF-EnOpt: {self.epf_iteration}, {r} (outer iteration, penalty factor)') # print epf info else: logger.info(f' -----> EPF-EnOpt: converged, no variables changed more than {conv_crit*100} %') # print epf info - final_obj_no_penalty = str(round(float(np.mean(self.fun(self.mean_state))),4)) + final_obj_no_penalty = str(round(float(np.mean(self.fun(self.xk))),4)) logger.info(f' -----> EPF-EnOpt: objective value without penalty = {final_obj_no_penalty}') # print epf info + def save(self): """ We use pickle to dump all the information we have in 'self'. Can be used, e.g., if some error has occurred. @@ -218,6 +237,7 @@ def load(self): # Save in 'self' self.__dict__.update(tmp_load) + @abstractmethod def calc_update(self): """ This is an empty dummy function. Actual functionality must be defined by the subclasses. diff --git a/popt/misc_tools/optim_tools.py b/popt/misc_tools/optim_tools.py index afe19083..ffc256de 100644 --- a/popt/misc_tools/optim_tools.py +++ b/popt/misc_tools/optim_tools.py @@ -334,7 +334,7 @@ def get_optimize_result(obj): """ # Initialize dictionary of variables to save - save_dict = OptimizeResult({'success': True, 'x': obj.mean_state, 'fun': np.mean(obj.obj_func_values), + save_dict = OptimizeResult({'success': True, 'x': obj.xk, 'fun': np.mean(obj.fk), 'nit': obj.iteration, 'nfev': obj.nfev, 'njev': obj.njev}) if hasattr(obj, 'epf') and obj.epf: save_dict['epf_iteration'] = obj.epf_iteration @@ -349,7 +349,7 @@ def get_optimize_result(obj): # Loop over variables to store in save list for save_typ in savedata: - if 'mean_state' in save_typ: + if 'xk' in save_typ: continue # mean_state is alwaysed saved as 'x' if save_typ in locals(): save_dict[save_typ] = eval('{}'.format(save_typ)) diff --git a/popt/update_schemes/enopt.py b/popt/update_schemes/enopt.py index d71111eb..e982d932 100644 --- a/popt/update_schemes/enopt.py +++ b/popt/update_schemes/enopt.py @@ -88,7 +88,7 @@ def __set__variable(var_name=None, defalut=None): # Set input as class variables self.options = options # options - self.fun = fun # objective function + self._fun = fun # objective function self.cov = args[0] # initial covariance self.jac = jac # gradient function self.hess = hess # hessian function @@ -114,7 +114,7 @@ def __set__variable(var_name=None, defalut=None): # Calculate objective function of startpoint if not self.restart: self.start_time = time.perf_counter() - self.obj_func_values = self.fun(self.mean_state, epf=self.epf) + self.obj_func_values = self._fun(self.mean_state, epf=self.epf) self.nfev += 1 self.optimize_result = ot.get_optimize_result(self) ot.save_optimize_results(self.optimize_result) @@ -142,6 +142,25 @@ def __set__variable(var_name=None, defalut=None): # The EnOpt class self-ignites, and it is possible to send the EnOpt class as a callale method to scipy.minimize self.run_loop() # run_loop resides in the Optimization class (super) + def fun(self, x, *args, **kwargs): + return self._fun(x, *args, **kwargs) + + @property + def xk(self): + return self.mean_state + + @property + def fk(self): + return self.obj_func_values + + @property + def ftol(self): + return self.obj_func_tol + + @ftol.setter + def ftol(self, value): + self.obj_func_tol = value + def calc_update(self): """ Update using steepest descent method with ensemble gradients @@ -152,7 +171,7 @@ def calc_update(self): success = False resampling_iter = 0 - while improvement is False: # resampling loop + while not improvement: # resampling loop # Shrink covariance each time we try resampling shrink = self.cov_factor ** resampling_iter @@ -179,14 +198,14 @@ def calc_update(self): # Initialize for this step alpha_iter = 0 - while improvement is False: # backtracking loop + while not improvement: # backtracking loop new_state, new_step = self.optimizer.apply_update(self.mean_state, gradient, hessian=hessian, iter=self.iteration) new_state = ot.clip_state(new_state, self.bounds) # Calculate new objective function - new_func_values = self.fun(new_state, epf=self.epf) + new_func_values = self._fun(new_state, epf=self.epf) self.nfev += 1 if np.mean(self.obj_func_values) - np.mean(new_func_values) > self.obj_func_tol: diff --git a/popt/update_schemes/genopt.py b/popt/update_schemes/genopt.py index 408baa70..1af0c755 100644 --- a/popt/update_schemes/genopt.py +++ b/popt/update_schemes/genopt.py @@ -54,7 +54,7 @@ def __set__variable(var_name=None, defalut=None): # Set input as class variables self.options = options # options - self.fun = fun # objective function + self.function = fun # objective function self.jac = jac # gradient function self.jac_mut = jac_mut # mutation function self.corr_adapt = corr_adapt # correlation adaption function @@ -82,7 +82,7 @@ def __set__variable(var_name=None, defalut=None): # Calculate objective function of startpoint if not self.restart: self.start_time = time.perf_counter() - self.obj_func_values = self.fun(self.mean_state) + self.obj_func_values = self.function(self.mean_state) self.nfev += 1 self.optimize_result = ot.get_optimize_result(self) ot.save_optimize_results(self.optimize_result) @@ -110,6 +110,25 @@ def __set__variable(var_name=None, defalut=None): # The GenOpt class self-ignites, and it is possible to send the EnOpt class as a callale method to scipy.minimize self.run_loop() # run_loop resides in the Optimization class (super) + def fun(self, x, *args, **kwargs): + return self.function(x, *args, **kwargs) + + @property + def xk(self): + return self.mean_state + + @property + def fk(self): + return self.obj_func_values + + @property + def ftol(self): + return self.obj_func_tol + + @ftol.setter + def ftol(self, value): + self.obj_func_tol = value + def calc_update(self): """ Update using steepest descent method with ensemble gradients @@ -148,7 +167,7 @@ def calc_update(self): new_state = ot.clip_state(new_state, self.bounds) # Calculate new objective function - new_func_values = self.fun(new_state) + new_func_values = self.function(new_state) self.nfev += 1 if np.mean(self.obj_func_values) - np.mean(new_func_values) > self.obj_func_tol: diff --git a/popt/update_schemes/linesearch.py b/popt/update_schemes/linesearch.py index 35e065bd..21f6f02f 100644 --- a/popt/update_schemes/linesearch.py +++ b/popt/update_schemes/linesearch.py @@ -184,8 +184,8 @@ def __init__(self, fun, x, jac, method='GD', hess=None, args=(), bounds=None, c super(LineSearchClass, self).__init__(**options) # Set input as class variables - self.function = fun - self.xk = x + self._xk = x + self.function = fun self.jacobian = jac self.method = method self.hessian = hess @@ -229,7 +229,7 @@ def __init__(self, fun, x, jac, method='GD', hess=None, args=(), bounds=None, c # set tolerance for convergence self.xtol = options.get('xtol', 1e-8) # tolerance for control vector - self.ftol = options.get('ftol', 1e-4) # relative tolerance for function value + self._ftol = options.get('ftol', 1e-4) # relative tolerance for function value self.gtol = options.get('gtol', 1e-5) # tolerance for inf-norm of jacobian # Check method @@ -245,13 +245,13 @@ def __init__(self, fun, x, jac, method='GD', hess=None, args=(), bounds=None, c self.start_time = time.perf_counter() # Check for initial callable values - self.fk = options.get('fun0', None) + self._fk = options.get('fun0', None) self.jk = options.get('jac0', None) self.Hk = options.get('hess0', None) - if self.fk is None: self.fk = self._fun(self.xk) - if self.jk is None: self.jk = self._jac(self.xk) - if self.Hk is None: self.Hk = self._hess(self.xk) + if self._fk is None: self._fk = self._fun(self._xk) + if self.jk is None: self.jk = self._jac(self._xk) + if self.Hk is None: self.Hk = self._hess(self._xk) # Check for initial inverse hessian for the BFGS method if self.method == 'BFGS': @@ -273,28 +273,46 @@ def __init__(self, fun, x, jac, method='GD', hess=None, args=(), bounds=None, c self.logger.info('\nSPECIFIED OPTIONS:\n'+pprint.pformat(OptimizeResult(self.options))) self.logger.info('') self.logger.info(f' {"iter.":<10} {fun_xk_symbol:<15} {jac_inf_symbol:<15} {"step-size":<15}') - self.logger.info(f' {self.iteration:<10} {self.fk:<15.4e} {la.norm(self.jk, np.inf):<15.4e} {0:<15.4e}') + self.logger.info(f' {self.iteration:<10} {self._fk:<15.4e} {la.norm(self.jk, np.inf):<15.4e} {0:<15.4e}') self.logger.info('') - self.run_loop() + self.run_loop() + def fun(self, x, *args, **kwargs): + return self.function(x, *args, **kwargs) + + @property + def xk(self): + return self._xk + + @property + def fk(self): + return self._fk + + @property + def ftol(self): + return self._ftol + + @ftol.setter + def ftol(self, value): + self._ftol = value def _fun(self, x): self.nfev += 1 x = ot.clip_state(x, self.bounds) # ensure bounds are respected if self.args is None: - f = np.mean(self.function(x, **self.epf)) + f = np.mean(self.function(x, epf = self.epf)) else: - f = np.mean(self.function(x, *self.args, **self.epf)) + f = np.mean(self.function(x, *self.args, epf = self.epf)) return f def _jac(self, x): self.njev += 1 x = ot.clip_state(x, self.bounds) # ensure bounds are respected if self.args is None: - g = self.jacobian(x) + g = self.jacobian(x, epf=self.epf) else: - g = self.jacobian(x, *self.args) + g = self.jacobian(x, *self.args, epf=self.epf) # project gradient onto the feasible set if self.bounds is not None: @@ -322,11 +340,11 @@ def calc_update(self, iter_resamp=0): # If in resampling mode, compute jacobian # Else, jacobian from in __init__ or from latest line_search is used if self.jk is None: - self.jk = self._jac(self.xk) + self.jk = self._jac(self._xk) # Compute hessian if (self.iteration != 1) or (iter_resamp > 0): - self.Hk = self._hess(self.xk) + self.Hk = self._hess(self._xk) # Check normalization if self.normalize: @@ -340,15 +358,15 @@ def calc_update(self, iter_resamp=0): if self.method == 'BFGS': pk = - np.matmul(self.Hk_inv, self.jk) if self.method == 'Newton-CG': - pk = newton_cg(self.jk, Hk=self.Hk, xk=self.xk, jac=self._jac, logger=self.logger.info) + pk = newton_cg(self.jk, Hk=self.Hk, xk=self._xk, jac=self._jac, logger=self.logger.info) # porject search direction onto the feasible set if self.bounds is not None: - pk = self._project_pk(pk, self.xk) + pk = self._project_pk(pk, self._xk) # Set step_size if self.bounds is not None: - self.step_size_max = self._set_max_step_size(pk, self.xk) + self.step_size_max = self._set_max_step_size(pk, self._xk) self.lskwargs['amax'] = self.step_size_max step_size = self._set_step_size(pk, self.step_size_max) @@ -357,22 +375,22 @@ def calc_update(self, iter_resamp=0): if self.lskwargs['method'] == 0: ls_res = line_search_backtracking( step_size=step_size, - xk=self.xk, + xk=self._xk, pk=pk, fun=self._fun, jac=self._jac, - fk=self.fk, + fk=self._fk, jk=self.jk, **self.lskwargs ) else: ls_res = line_search( step_size=step_size, - xk=self.xk, + xk=self._xk, pk=pk, fun=self._fun, jac=self._jac, - fk=self.fk, + fk=self._fk, jk=self.jk, **self.lskwargs ) @@ -381,16 +399,16 @@ def calc_update(self, iter_resamp=0): if not (step_size is None): # Save old values - x_old = self.xk + x_old = self._xk j_old = self.jk - f_old = self.fk + f_old = self._fk # Update control x_new = ot.clip_state(x_old + step_size*pk, self.bounds) # Update state - self.xk = x_new - self.fk = f_new + self._xk = x_new + self._fk = f_new self.jk = j_new # Update old fun, jac and pk values @@ -421,7 +439,7 @@ def calc_update(self, iter_resamp=0): if self.logger is not None: self.logger.info('') self.logger.info(f' {"iter.":<10} {fun_xk_symbol:<15} {jac_inf_symbol:<15} {"step-size":<15}') - self.logger.info(f' {self.iteration:<10} {self.fk:<15.4e} {la.norm(self.jk, np.inf):<15.4e} {step_size:<15.4e}') + self.logger.info(f' {self.iteration:<10} {self._fk:<15.4e} {la.norm(self.jk, np.inf):<15.4e} {step_size:<15.4e}') self.logger.info('') # Check for convergence @@ -430,7 +448,7 @@ def calc_update(self, iter_resamp=0): self.logger.info(self.msg) success = False return success - if (np.abs(self.fk - f_old) < self.ftol * np.abs(f_old)): + if (np.abs(self._fk - f_old) < self._ftol * np.abs(f_old)): self.msg = 'Convergence criteria met: |f(x+dx) - f(x)| < ftol * |f(x)|' self.logger.info(self.msg) success = False @@ -473,8 +491,8 @@ def get_intermediate_results(self): # Define default results results = { - 'fun': self.fk, - 'x': self.xk, + 'fun': self._fk, + 'x': self._xk, 'jac': self.jk, 'nfev': self.nfev, 'njev': self.njev, @@ -518,7 +536,7 @@ def _set_step_size(self, pk, amax): else: if (self.step_size_adapt == 1) and (np.dot(pk, self.jk) != 0): - alpha = 2*(self.fk - self.f_old)/np.dot(pk, self.jk) + alpha = 2*(self._fk - self.f_old)/np.dot(pk, self.jk) elif (self.step_size_adapt == 2) and (np.dot(pk, self.jk) == 0): slope_old = np.dot(self.p_old, self.j_old) slope_new = np.dot(pk, self.jk) diff --git a/popt/update_schemes/smcopt.py b/popt/update_schemes/smcopt.py index f0944fe8..336d4246 100644 --- a/popt/update_schemes/smcopt.py +++ b/popt/update_schemes/smcopt.py @@ -60,7 +60,7 @@ def __set__variable(var_name=None, defalut=None): # Set input as class variables self.options = options # options - self.fun = fun # objective function + self.function = fun # objective function self.sens = sens # gradient function self.bounds = bounds # parameter bounds self.mean_state = x # initial mean state @@ -80,7 +80,7 @@ def __set__variable(var_name=None, defalut=None): # Calculate objective function of startpoint if not self.restart: self.start_time = time.perf_counter() - self.obj_func_values = self.fun(self.mean_state) + self.obj_func_values = self.function(self.mean_state) self.best_func = np.mean(self.obj_func_values) self.nfev += 1 self.optimize_result = ot.get_optimize_result(self) @@ -98,6 +98,25 @@ def __set__variable(var_name=None, defalut=None): # The SmcOpt class self-ignites self.run_loop() # run_loop resides in the Optimization class (super) + def fun(self, x, *args, **kwargs): + return self.function(x, *args, **kwargs) + + @property + def xk(self): + return self._xk + + @property + def fk(self): + return self.obj_func_values + + @property + def ftol(self): + return self.obj_func_tol + + @ftol.setter + def ftol(self, value): + self.obj_func_tol = value + def calc_update(self,): """ Update using sequential monte carlo method @@ -128,7 +147,7 @@ def calc_update(self,): new_state = ot.clip_state(new_state, self.bounds) # Calculate new objective function - new_func_values = self.fun(new_state) + new_func_values = self.function(new_state) self.nfev += 1 if np.mean(self.obj_func_values) - np.mean(new_func_values) > self.obj_func_tol or \ diff --git a/popt/update_schemes/trust_region.py b/popt/update_schemes/trust_region.py index b23f6b6e..d3afe85f 100644 --- a/popt/update_schemes/trust_region.py +++ b/popt/update_schemes/trust_region.py @@ -141,7 +141,7 @@ def __init__(self, fun, x, jac, hess, method='iterative', args=(), bounds=None, # Set class attributes self.function = fun - self.xk = x + self._xk = x self.jacobian = jac self.hessian = hess self.method = method @@ -188,13 +188,13 @@ def __init__(self, fun, x, jac, hess, method='iterative', args=(), bounds=None, self.start_time = time.perf_counter() # Check for initial callable values - self.fk = options.get('fun0', None) + self._fk = options.get('fun0', None) self.jk = options.get('jac0', None) self.Hk = options.get('hess0', None) - if self.fk is None: self.fk = self._fun(self.xk) - if self.jk is None: self.jk = self._jac(self.xk) - if self.Hk is None: self.Hk = self._hess(self.xk) + if self._fk is None: self._fk = self._fun(self._xk) + if self.jk is None: self.jk = self._jac(self._xk) + if self.Hk is None: self.Hk = self._hess(self._xk) # Initial results self.optimize_result = self.update_results() @@ -204,12 +204,30 @@ def __init__(self, fun, x, jac, hess, method='iterative', args=(), bounds=None, self._log(f' ====== Running optimization - Trust Region ======') self._log('\n'+pprint.pformat(OptimizeResult(self.options))) self._log(f' {"iter.":<10} {"fun":<15} {"tr-radius":<15} {"rho":<15}') - self._log(f' {self.iteration:<10} {self.fk:<15.4e} {self.trust_radius:<15.4e} {self.rho:<15.4e}') + self._log(f' {self.iteration:<10} {self._fk:<15.4e} {self.trust_radius:<15.4e} {self.rho:<15.4e}') self._log('') # Run the optimization self.run_loop() - + + def fun(self, x, *args, **kwargs): + return self.function(x, *args, **kwargs) + + @property + def xk(self): + return self._xk + + @property + def fk(self): + return self._fk + + @property + def ftol(self): + return self.obj_func_tol + + @ftol.setter + def ftol(self, value): + self.obj_func_tol = value def _fun(self, x): self.nfev += 1 @@ -256,12 +274,12 @@ def solve_subproblem(self, g, B, delta): """ # Define quadratic model - quad = lambda p: self.fk + np.dot(g,p) + np.dot(p,np.dot(B,p))/2 + quad = lambda p: self._fk + np.dot(g,p) + np.dot(p,np.dot(B,p))/2 if self.method == 'iterative': subproblem = IterativeSubproblem( - x=self.xk, + x=self._xk, fun=quad, jac=lambda _: g, hess=lambda _: B, @@ -270,7 +288,7 @@ def solve_subproblem(self, g, B, delta): elif self.method == 'CG-Steihaug': subproblem = CGSteihaugSubproblem( - x=self.xk, + x=self._xk, fun=quad, jac=lambda _: g, hess=lambda _: B, @@ -296,22 +314,22 @@ def calc_update(self, inner_iter=0): if self.bounds is not None: lb = np.array(self.bounds)[:, 0] ub = np.array(self.bounds)[:, 1] - sk = np.clip(sk, lb - self.xk, ub - self.xk) + sk = np.clip(sk, lb - self._xk, ub - self._xk) # Calculate the actual function value - xk_new = self.xk + sk + xk_new = self._xk + sk fun_new = self._fun(xk_new) # Calculate rho - actual_reduction = self.fk - fun_new + actual_reduction = self._fk - fun_new predicted_reduction = - np.dot(self.jk, sk) - np.dot(sk, np.dot(self.Hk, sk))/2 self.rho = actual_reduction/predicted_reduction if self.rho > self.rho_tol: # Update the control - self.xk = xk_new - self.fk = fun_new + self._xk = xk_new + self._fk = fun_new # Save Results self.optimize_result = self.update_results() @@ -321,7 +339,7 @@ def calc_update(self, inner_iter=0): # Write logging info self._log('') self._log(f' {"iter.":<10} {"fun":<15} {"tr-radius":<15} {"rho":<15}') - self._log(f' {self.iteration:<10} {self.fk:<15.4e} {self.trust_radius:<15.4e} {self.rho:<15.4e}') + self._log(f' {self.iteration:<10} {self._fk:<15.4e} {self.trust_radius:<15.4e} {self.rho:<15.4e}') self._log('') # Call the callback function @@ -354,8 +372,8 @@ def calc_update(self, inner_iter=0): success = False else: # Calculate the jacobian and hessian - self.jk = self._jac(self.xk) - self.Hk = self._hess(self.xk) + self.jk = self._jac(self._xk) + self.Hk = self._hess(self._xk) # Update iteration self.iteration += 1 @@ -378,8 +396,8 @@ def calc_update(self, inner_iter=0): # Check for resampling of Jac and Hess if self.resample: self._log('Resampling gradient and hessian') - self.jk = self._jac(self.xk) - self.Hk = self._hess(self.xk) + self.jk = self._jac(self._xk) + self.Hk = self._hess(self._xk) # Recursivly call function success = self.calc_update(inner_iter=inner_iter+1) @@ -391,8 +409,8 @@ def calc_update(self, inner_iter=0): def update_results(self): res = { - 'fun': self.fk, - 'x': self.xk, + 'fun': self._fk, + 'x': self._xk, 'jac': self.jk, 'hess': self.Hk, 'nfev': self.nfev,