def help(args=None): import sys, shlex # program name try: prog = sys.argv[0] except Exception: prog = getattr(sys, 'executable', 'python') # arguments if args is None: args = sys.argv[1:] elif isinstance(args, str): args = shlex.split(args) else: args = [str(a) for a in args] # import and initialize import petsc4py petsc4py.init([prog, '-help'] + args) from petsc4py import PETSc # help dispatcher COMM = PETSc.COMM_SELF if 'vec' in args: vec = PETSc.Vec().create(comm=COMM) vec.setSizes(0) vec.setFromOptions() vec.destroy() if 'mat' in args: mat = PETSc.Mat().create(comm=COMM) mat.setSizes([0, 0]) mat.setFromOptions() mat.destroy() if 'pc' in args: pc = PETSc.PC().create(comm=COMM) pc.setFromOptions() pc.destroy() if 'ksp' in args: ksp = PETSc.KSP().create(comm=COMM) ksp.setFromOptions() ksp.destroy() if 'snes' in args: snes = PETSc.SNES().create(comm=COMM) snes.setFromOptions() snes.destroy() if 'ts' in args: ts = PETSc.TS().create(comm=COMM) ts.setFromOptions() ts.destroy() if 'tao' in args: tao = PETSc.TAO().create(comm=COMM) tao.setFromOptions() tao.destroy() if 'dmda' in args: dmda = PETSc.DMDA().create(comm=COMM) dmda.setFromOptions() dmda.destroy() if 'dmplex' in args: dmplex = PETSc.DMPlex().create(comm=COMM) dmplex.setFromOptions() dmplex.destroy()
def __init__(self, problem, parameters=None, riesz_map=None, prefix=""): try: from petsc4py import PETSc except: raise Exception("Could not find petsc4py. Please install it.") try: TAO = PETSc.TAO except: raise Exception("Your petsc4py version does not support TAO. Please upgrade to petsc4py >= 3.5.") self.PETSc = PETSc # Use PETSc forms if riesz_map is not None: # Handle the case where the user supplied riesz_maps.L2(V) if hasattr(riesz_map, "assemble"): riesz_map = riesz_map.assemble() self.riesz_map = as_backend_type(riesz_map).mat() else: self.riesz_map = None if len(prefix) > 0 and prefix[-1] != "_": prefix += "_" self.prefix = prefix OptimizationSolver.__init__(self, problem, parameters) self.tao = PETSc.TAO().create(PETSc.COMM_WORLD) self.__build_app_context() self.__set_parameters() self.__build_tao()
def setUp(self): self.tao = PETSc.TAO().create(comm=self.COMM)
def tao_pounders( criterion_and_derivative, x, lower_bounds, upper_bounds, *, convergence_absolute_gradient_tolerance=CONVERGENCE_ABSOLUTE_GRADIENT_TOLERANCE, convergence_relative_gradient_tolerance=CONVERGENCE_RELATIVE_GRADIENT_TOLERANCE, convergence_scaled_gradient_tolerance=CONVERGENCE_SCALED_GRADIENT_TOLERANCE, trustregion_initial_radius=None, stopping_max_iterations=STOPPING_MAX_ITERATIONS, ): r"""Minimize a function using the POUNDERs algorithm. For details see :ref:`tao_algorithm`. """ if not IS_PETSC4PY_INSTALLED: raise NotImplementedError( "The petsc4py package is not installed and required for 'tao_pounders'. If " "you are using Linux or MacOS, install the package with 'conda install -c " "conda-forge petsc4py. The package is not available on Windows.") func = functools.partial( criterion_and_derivative, task="criterion", algorithm_info=POUNDERS_ALGO_INFO, ) x = _initialise_petsc_array(x) # We need to know the number of contributions of the criterion value to allocate the # array. n_errors = len( criterion_and_derivative.keywords["first_criterion_evaluation"] ["output"]["root_contributions"]) residuals_out = _initialise_petsc_array(n_errors) # Create the solver object. tao = PETSc.TAO().create(PETSc.COMM_WORLD) # Set the solver type. tao.setType("pounders") tao.setFromOptions() def func_tao(tao, x, resid_out): """Evaluate objective and attach result to an petsc object f. This is required to use the pounders solver from tao. Args: tao: The tao object we created for the optimization task. x (PETSc.array): Current parameter values. f: Petsc object in which we save the current function value. """ resid_out.array = func(x.array) # Set the procedure for calculating the objective. This part has to be changed if we # want more than pounders. tao.setResidual(func_tao, residuals_out) if trustregion_initial_radius is None: trustregion_initial_radius = calculate_trustregion_initial_radius(x) elif trustregion_initial_radius <= 0: raise ValueError("The initial trust region radius must be > 0.") tao.setInitialTrustRegionRadius(trustregion_initial_radius) # Add bounds. lower_bounds = _initialise_petsc_array(lower_bounds) upper_bounds = _initialise_petsc_array(upper_bounds) tao.setVariableBounds(lower_bounds, upper_bounds) # Put the starting values into the container and pass them to the optimizer. tao.setInitial(x) # Obtain tolerances for the convergence criteria. Since we can not create # scaled_gradient_tolerance manually we manually set absolute_gradient_tolerance and # or relative_gradient_tolerance to zero once a subset of these two is turned off # and scaled_gradient_tolerance is still turned on. default_gatol = (convergence_absolute_gradient_tolerance if convergence_absolute_gradient_tolerance else -1) default_gttol = (convergence_scaled_gradient_tolerance if convergence_scaled_gradient_tolerance else -1) default_grtol = (convergence_relative_gradient_tolerance if convergence_relative_gradient_tolerance else -1) # Set tolerances for default convergence tests. tao.setTolerances( gatol=default_gatol, grtol=default_grtol, gttol=default_gttol, ) # Set user defined convergence tests. Beware that specifying multiple tests could # overwrite others or lead to unclear behavior. if stopping_max_iterations is not None: tao.setConvergenceTest( functools.partial(_max_iters, stopping_max_iterations)) elif (convergence_scaled_gradient_tolerance is False and convergence_absolute_gradient_tolerance is False): tao.setConvergenceTest( functools.partial(_grtol_conv, convergence_relative_gradient_tolerance)) elif (convergence_relative_gradient_tolerance is False and convergence_scaled_gradient_tolerance is False): tao.setConvergenceTest( functools.partial(_gatol_conv, convergence_absolute_gradient_tolerance)) elif convergence_scaled_gradient_tolerance is False: tao.setConvergenceTest( functools.partial( _grtol_gatol_conv, convergence_relative_gradient_tolerance, convergence_absolute_gradient_tolerance, )) # Run the problem. tao.solve() results = _process_pounders_results(residuals_out, tao) # Destroy petsc objects for memory reasons. for obj in [tao, x, residuals_out, lower_bounds, upper_bounds]: obj.destroy() return results
def run_local_tao(user_specs, comm_queue, x0, f0, child_can_read, parent_can_read): """ Runs a PETSc/TAO local optimization run starting at ``x0``, governed by the parameters in ``user_specs``. """ assert isinstance(x0, np.ndarray) tao_comm = PETSc.COMM_SELF n, = x0.shape if f0.shape == (): m = 1 else: m, = f0.shape # Create starting point, bounds, and tao object x = PETSc.Vec().create(tao_comm) x.setSizes(n) x.setFromOptions() x.array = x0 lb = x.duplicate() ub = x.duplicate() lb.array = 0 * np.ones(n) ub.array = 1 * np.ones(n) tao = PETSc.TAO().create(tao_comm) tao.setType(user_specs['localopt_method']) if user_specs['localopt_method'] == 'pounders': f = PETSc.Vec().create(tao_comm) f.setSizes(m) f.setFromOptions() if hasattr(tao, 'setResidual'): tao.setResidual( lambda tao, x, f: tao_callback_fun_pounders( tao, x, f, comm_queue, child_can_read, parent_can_read, user_specs), f) else: tao.setSeparableObjective( lambda tao, x, f: tao_callback_fun_pounders( tao, x, f, comm_queue, child_can_read, parent_can_read, user_specs), f) delta_0 = user_specs['dist_to_bound_multiple'] * np.min( [np.min(ub.array - x.array), np.min(x.array - lb.array)]) PETSc.Options().setValue('-tao_pounders_delta', str(delta_0)) elif user_specs['localopt_method'] == 'nm': tao.setObjective(lambda tao, x: tao_callback_fun_nm( tao, x, comm_queue, child_can_read, parent_can_read, user_specs)) elif user_specs['localopt_method'] == 'blmvm': g = PETSc.Vec().create(tao_comm) g.setSizes(n) g.setFromOptions() tao.setObjectiveGradient(lambda tao, x, g: tao_callback_fun_grad( tao, x, g, comm_queue, child_can_read, parent_can_read, user_specs) ) # Set everything for tao before solving PETSc.Options().setValue('-tao_max_funcs', str(user_specs.get('run_max_eval', 1000 * n))) tao.setFromOptions() tao.setVariableBounds((lb, ub)) tao.setTolerances(grtol=user_specs.get('grtol', 1e-8), gatol=user_specs.get('gatol', 1e-8)) tao.setInitial(x) # print('[Child]: Started my optimization', flush=True) tao.solve(x) x_opt = tao.getSolution().getArray() exit_code = tao.getConvergedReason() if exit_code > 0: opt_flag = 1 else: # https://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/Tao/TaoGetConvergedReason.html print( "[APOSMM] The run started from " + str(x0) + " exited with a nonpositive reason. No point from " "this run will be ruled as a minimum! APOSMM may start a new run from some point in this run." ) opt_flag = 0 if user_specs['localopt_method'] == 'pounders': f.destroy() elif user_specs['localopt_method'] == 'blmvm': g.destroy() lb.destroy() ub.destroy() x.destroy() tao.destroy() finish_queue(x_opt, opt_flag, comm_queue, parent_can_read, user_specs)
def minimize_pounders( func, x, len_out, bounds=None, init_tr=None, max_iterations=None, gatol=1e-8, grtol=1e-8, gttol=1e-10, ): """Minimize a function using the pounders algortihm. Args: func (callable): Function that takes a 1d NumPy array and returns a 1d NumPy array. x (np.ndarray): Contains the start values of the variables of interest bounds (list or tuple of lists): Contains the bounds for the variable of interest. The first list contains the lower value for each parameter and the upper list the upper value. The object has to contain two elements of which one represents the upper and the other one the lower bound. init_tr (float): Sets the radius for the initial trust region that the optimizer employs. If none the algorithm uses 100 as initial trust region radius. max_iterations (int): Alternative Stopping criterion. If set the routine will stop after the number of specified iterations or after the step size is sufficiently small. If the variable is set the default criteria will all be ignored. gatol (int): Stop if relative norm of gradient is less than this. If set to False the algorithm will not consider gatol. grtol (int): Stop if norm of gradient is less than this. If set to False the algorithm will not consider grtol. gttol (int): Stop if norm of gradient is reduced by this factor. If set to False the algorithm will not consider grtol. Returns: out (dict): Dictionary with the following key-value pairs: - `"solution"`: solution vector as `np.ndarray`. - `"func_values"`: `np.ndarray` of value of the objective at the solution. - `"x"`: `np.ndarray` of the start values. - `"conv"`: string indicating the termination reason. - `"sol"`: `list` containing ... - current iterate as integer. - current value of the objective as float - current value of the approximated - jacobian as float - infeasability norm as float - step length as float - termination reason as int. """ if sys.platform == "win32": raise NotImplementedError( "The pounders algorithm is not available on Windows.") # We want to get containers for the func verctor and the paras. size_paras = len(x) size_objective = len_out paras, crit = _prep_args(size_paras, size_objective) # Set the start value. paras[:] = x def func_tao(tao, paras, f): """Evaluate objective and attach result to an petsc object f. This is required to use the pounders solver from tao. Args: tao: The tao object we created for the optimization task. paras (np.ndarray): 1d NumPy array of the current values at which we want to evaluate the function. f: Petsc object in which we save the current function value. """ dev = func(paras.array) # Attach to PETSc object. f.array = dev # Create the solver object. tao = PETSc.TAO().create(PETSc.COMM_WORLD) # Set the solver type. tao.setType("pounders") tao.setFromOptions() # Set the procedure for calculating the objective. This part has to be changed if we # want more than pounders. tao.setResidual(func_tao, crit) # We try to set user defined convergence tests. if init_tr is not None: tao.setInitialTrustRegionRadius(init_tr) # Change they need to be in a container # Set the variable sounds if existing if bounds is not None: low, up = _prep_args(len(x), len(x)) low.array = bounds[0] up.array = bounds[1] tao.setVariableBounds([low, up]) # Set the container over which we optimize that already contians start values tao.setInitial(paras) # Obtain tolerances for the convergence criteria. Since we can not create gttol # manually we manually set gatol and or grtol to zero once a subset of these two is # turned off and gttol is still turned on. tol_real = _get_tolerances(gttol, gatol, grtol) # Set tolerances for default convergence tests. tao.setTolerances(gatol=tol_real["gatol"], gttol=tol_real["gttol"], grtol=tol_real["grtol"]) # Set user defined convergence tests. Beware that specifiying multiple tests could # overwrite others or lead to unclear behavior. if max_iterations is not None: tao.setConvergenceTest(partial(_max_iters, max_iterations)) elif gttol is False and gatol is False: tao.setConvergenceTest(partial(_grtol_conv, grtol)) elif grtol is False and gttol is False: tao.setConvergenceTest(partial(_gatol_conv, gatol)) elif gttol is False: tao.setConvergenceTest(partial(_grtol_gatol_conv, grtol, gatol)) # Run the problem. tao.solve() # Create a dict that contains relevant information. out = {} out["x"] = paras.array out["fun"] = crit.array[-1] out["func_values"] = crit.array out["start_values"] = x out["conv"] = _translate_tao_convergence_reason(tao.getConvergedReason()) out["sol"] = tao.getSolutionStatus() # Destroy petsc objects for memory reasons. tao.destroy() paras.destroy() crit.destroy() return out
def tao_pounders( criterion_and_derivative, x, lower_bounds, upper_bounds, *, convergence_absolute_gradient_tolerance=CONVERGENCE_ABSOLUTE_GRADIENT_TOLERANCE, convergence_relative_gradient_tolerance=CONVERGENCE_RELATIVE_GRADIENT_TOLERANCE, convergence_scaled_gradient_tolerance=CONVERGENCE_SCALED_GRADIENT_TOLERANCE, trustregion_initial_radius=None, stopping_max_iterations=STOPPING_MAX_ITERATIONS, ): r"""Minimize a function using the POUNDERs algorithm. Do not call this function directly but pass its name "tao_pounders" to estimagic's maximize or minimize function as `algorithm` argument. Specify your desired arguments as a dictionary and pass them as `algo_options` to minimize or maximize. POUNDERs (:cite:`Benson2017`, :cite:`Wild2015`, `GitLab repository <https://gitlab.com/petsc/petsc/-/tree/master/src/binding/petsc4py/>`_) can be a useful tool for economists who estimate structural models using indirect inference, because unlike commonly used algorithms such as Nelder-Mead, POUNDERs is tailored for minimizing a non-linear sum of squares objective function, and therefore may require fewer iterations to arrive at a local optimum than Nelder-Mead. The criterion function :func:`func` should return a dictionary with the following fields: 1. ``"value"``: The sum of squared (potentially weighted) errors. 2. ``"root_contributions"``: An array containing the root (weighted) contributions. Scaling the problem is necessary such that bounds correspond to the unit hypercube :math:`[0, 1]^n`. For unconstrained problems, scale each parameter such that unit changes in parameters result in similar order-of-magnitude changes in the criterion value(s). POUNDERs has several convergence criteria. Let :math:`X` be the current parameter vector, :math:`X_0` the initial parameter vector, :math:`g` the gradient, and :math:`f` the criterion function. ``absolute_gradient_tolerance`` stops the optimization if the norm of the gradient falls below :math:`\epsilon`. .. math:: ||g(X)|| < \epsilon ``relative_gradient_tolerance`` stops the optimization if the norm of the gradient relative to the criterion value falls below :math:`epsilon`. .. math:: ||g(X)|| / |f(X)| < \epsilon ``scaled_gradient_tolerance`` stops the optimization if the norm of the gradient is lower than some fraction :math:`epsilon` of the norm of the gradient at the initial parameters. .. math:: ||g(X)|| / ||g(X0)|| < \epsilon Args: absolute_gradient_tolerance (float): Stop if relative norm of gradient is less than this. If set to False the algorithm will not consider absolute_gradient_tolerance. relative_gradient_tolerance (float): Stop if norm of gradient is less than this. If set to False the algorithm will not consider relative_gradient_tolerance. scaled_gradient_tolerance (float): Stop if norm of gradient is reduced by this factor. If set to False the algorithm will not consider relative_gradient_tolerance. trustregion_initial_radius (float): Initial value of the trust region radius. It must be :math:`> 0`. stopping_max_iterations (int): Alternative Stopping criterion. If set the routine will stop after the number of specified iterations or after the step size is sufficiently small. If the variable is set the default criteria will all be ignored. Returns: results (dict): Dictionary with processed optimization results. """ if not IS_PETSC4PY_INSTALLED: raise NotImplementedError( "The petsc4py package is not installed and required for 'tao_pounders'. If " "you are using Linux or MacOS, install the package with 'conda install -c " "conda-forge petsc4py. The package is not available on Windows.") func = functools.partial( criterion_and_derivative, task="criterion", algorithm_info=POUNDERS_ALGO_INFO, ) x = _initialise_petsc_array(x) # We need to know the number of contributions of the criterion value to allocate the # array. n_errors = len( criterion_and_derivative.keywords["first_criterion_evaluation"] ["output"]["root_contributions"]) residuals_out = _initialise_petsc_array(n_errors) # Create the solver object. tao = PETSc.TAO().create(PETSc.COMM_WORLD) # Set the solver type. tao.setType("pounders") tao.setFromOptions() def func_tao(tao, x, resid_out): """Evaluate objective and attach result to an petsc object f. This is required to use the pounders solver from tao. Args: tao: The tao object we created for the optimization task. x (PETSc.array): Current parameter values. f: Petsc object in which we save the current function value. """ resid_out.array = func(x.array) # Set the procedure for calculating the objective. This part has to be changed if we # want more than pounders. tao.setResidual(func_tao, residuals_out) if trustregion_initial_radius is None: trustregion_initial_radius = calculate_trustregion_initial_radius(x) elif trustregion_initial_radius <= 0: raise ValueError("The initial trust region radius must be > 0.") tao.setInitialTrustRegionRadius(trustregion_initial_radius) # Add bounds. lower_bounds = _initialise_petsc_array(lower_bounds) upper_bounds = _initialise_petsc_array(upper_bounds) tao.setVariableBounds(lower_bounds, upper_bounds) # Put the starting values into the container and pass them to the optimizer. tao.setInitial(x) # Obtain tolerances for the convergence criteria. Since we can not create # scaled_gradient_tolerance manually we manually set absolute_gradient_tolerance and # or relative_gradient_tolerance to zero once a subset of these two is turned off # and scaled_gradient_tolerance is still turned on. default_gatol = (convergence_absolute_gradient_tolerance if convergence_absolute_gradient_tolerance else -1) default_gttol = (convergence_scaled_gradient_tolerance if convergence_scaled_gradient_tolerance else -1) default_grtol = (convergence_relative_gradient_tolerance if convergence_relative_gradient_tolerance else -1) # Set tolerances for default convergence tests. tao.setTolerances( gatol=default_gatol, grtol=default_grtol, gttol=default_gttol, ) # Set user defined convergence tests. Beware that specifying multiple tests could # overwrite others or lead to unclear behavior. if stopping_max_iterations is not None: tao.setConvergenceTest( functools.partial(_max_iters, stopping_max_iterations)) elif (convergence_scaled_gradient_tolerance is False and convergence_absolute_gradient_tolerance is False): tao.setConvergenceTest( functools.partial(_grtol_conv, convergence_relative_gradient_tolerance)) elif (convergence_relative_gradient_tolerance is False and convergence_scaled_gradient_tolerance is False): tao.setConvergenceTest( functools.partial(_gatol_conv, convergence_absolute_gradient_tolerance)) elif convergence_scaled_gradient_tolerance is False: tao.setConvergenceTest( functools.partial( _grtol_gatol_conv, convergence_relative_gradient_tolerance, convergence_absolute_gradient_tolerance, )) # Run the problem. tao.solve() results = _process_pounders_results(residuals_out, tao) # Destroy petsc objects for memory reasons. for obj in [tao, x, residuals_out, lower_bounds, upper_bounds]: obj.destroy() return results
def run_local_tao(user_specs, comm_queue, x0, f0, child_can_read, parent_can_read): assert isinstance(x0, np.ndarray) tao_comm = MPI.COMM_SELF n, = x0.shape if f0.shape == (): m = 1 else: m, = f0.shape # Create starting point, bounds, and tao object x = PETSc.Vec().create(tao_comm) x.setSizes(n) x.setFromOptions() x.array = x0 lb = x.duplicate() ub = x.duplicate() lb.array = 0 * np.ones(n) ub.array = 1 * np.ones(n) tao = PETSc.TAO().create(tao_comm) tao.setType(user_specs['localopt_method']) if user_specs['localopt_method'] == 'pounders': f = PETSc.Vec().create(tao_comm) f.setSizes(m) f.setFromOptions() if hasattr(tao, 'setResidual'): tao.setResidual( lambda tao, x, f: tao_callback_fun(tao, x, f, comm_queue, child_can_read, parent_can_read, user_specs), f) else: tao.setSeparableObjective( lambda tao, x, f: tao_callback_fun(tao, x, f, comm_queue, child_can_read, parent_can_read, user_specs), f) elif user_specs['localopt_method'] == 'blmvm': g = PETSc.Vec().create(tao_comm) g.setSizes(n) g.setFromOptions() tao.setObjectiveGradient(lambda tao, x, g: tao_callback_fun_grad( tao, x, g, comm_queue, child_can_read, parent_can_read, user_specs) ) delta_0 = user_specs['dist_to_bound_multiple'] * np.min( [np.min(ub.array - x.array), np.min(x.array - lb.array)]) PETSc.Options().setValue('-tao_pounders_delta', str(delta_0)) # Set everything for tao before solving # FIXME: Hard-coding 100 as the max funcs as couldn't find any other # sensible value. PETSc.Options().setValue('-tao_max_funcs', '100') tao.setFromOptions() tao.setVariableBounds((lb, ub)) # tao.setObjectiveTolerances(fatol=user_specs['fatol'], frtol=user_specs['frtol']) # tao.setGradientTolerances(grtol=user_specs['grtol'], gatol=user_specs['gatol']) tao.setTolerances(grtol=user_specs['grtol'], gatol=user_specs['gatol']) tao.setInitial(x) # print('[Child]: Started my optimization', flush=True) tao.solve(x) x_opt = tao.getSolution().getArray() # exit_code = tao.getConvergedReason() # FIXME: Need to do something with the exit codes. # print(exit_code) # print(tao.view()) # print(x_opt) if user_specs['localopt_method'] == 'pounders': f.destroy() elif user_specs['localopt_method'] == 'blmvm': g.destroy() lb.destroy() ub.destroy() x.destroy() tao.destroy() # FIXME: Do we need to do something of the final 'x_opt'? # print('[Child]: I have converged.', flush=True) comm_queue.put(ConvergedMsg(x_opt)) parent_can_read.set()
def set_up_and_run_tao(Run_H, user_specs): """ Set up objective and runs PETSc on the comm_self communicator Declares the appropriate syntax for our special objective function to read through Run_H, sets the parameters and starting points for the run. """ tao_comm = MPI.COMM_SELF n = len(user_specs['ub']) def pounders_obj_func(tao, X, F, Run_H): F.array = look_in_history(X.array_r, Run_H, vector_return=True) return F def blmvm_obj_func(tao, X, G, Run_H): (f, grad) = look_in_history(X.array_r, Run_H) G.array = grad return f # Create starting point, bounds, and tao object x = PETSc.Vec().create(tao_comm) x.setSizes(n) x.setFromOptions() x.array = Run_H['x_on_cube'][0] lb = x.duplicate() ub = x.duplicate() lb.array = 0 * np.ones(n) ub.array = 1 * np.ones(n) tao = PETSc.TAO().create(tao_comm) tao.setType(user_specs['localopt_method']) if user_specs['localopt_method'] == 'pounders': f = PETSc.Vec().create(tao_comm) f.setSizes(len(Run_H['fvec'][0])) f.setFromOptions() delta_0 = user_specs['dist_to_bound_multiple'] * np.min( [np.min(ub.array - x.array), np.min(x.array - lb.array)]) PETSc.Options().setValue('-tao_pounders_delta', str(delta_0)) # PETSc.Options().setValue('-pounders_subsolver_tao_type','bqpip') if hasattr(tao, 'setResidual'): tao.setResidual( lambda tao, x, f: pounders_obj_func(tao, x, f, Run_H), f) else: tao.setSeparableObjective( lambda tao, x, f: pounders_obj_func(tao, x, f, Run_H), f) elif user_specs['localopt_method'] == 'blmvm': g = PETSc.Vec().create(tao_comm) g.setSizes(n) g.setFromOptions() tao.setObjectiveGradient( lambda tao, x, g: blmvm_obj_func(tao, x, g, Run_H)) # Set everything for tao before solving PETSc.Options().setValue('-tao_max_funcs', str(len(Run_H) + 1)) tao.setFromOptions() tao.setVariableBounds((lb, ub)) # tao.setObjectiveTolerances(fatol=user_specs['fatol'], frtol=user_specs['frtol']) # tao.setGradientTolerances(grtol=user_specs['grtol'], gatol=user_specs['gatol']) tao.setTolerances(grtol=user_specs['grtol'], gatol=user_specs['gatol']) tao.setInitial(x) tao.solve(x) x_opt = tao.getSolution().getArray() exit_code = tao.getConvergedReason() # print(exit_code) # print(tao.view()) # print(x_opt) if user_specs['localopt_method'] == 'pounders': f.destroy() if user_specs['localopt_method'] == 'blmvm': g.destroy() lb.destroy() ub.destroy() x.destroy() tao.destroy() return x_opt, exit_code
# create Hessian matrix H = PETSc.Mat().create(PETSc.COMM_SELF) H.setSizes([user.size, user.size]) H.setFromOptions() H.setOption(PETSc.Mat.Option.SYMMETRIC, True) H.setUp() # pass the following to command line: # $ ... -methods nm,lmvm,nls,ntr,cg,blmvm,tron # to try many methods methods = OptDB.getString('methods', '') methods = methods.split(',') for meth in methods: # create TAO Solver tao = PETSc.TAO().create(PETSc.COMM_SELF) if meth: tao.setType(meth) tao.setFromOptions() # solve the problem tao.setObjectiveGradient(user.formObjGrad) tao.setObjective(user.formObjective) tao.setGradient(user.formGradient) tao.setHessian(user.formHessian, H) #app.getKSP().getPC().setFromOptions() x.set(0) # zero initial guess #tao.setInitial(x) tao.solve(x) tao.destroy() ## # this is just for testing ## x = app.getSolution()
def minimize_pounders_np( func, x0, bounds, gatol=1e-8, grtol=1e-8, gttol=1e-10, init_tr=None, max_iterations=None, n_errors=None, ): """Minimize a function using the Pounders algorithm. Pounders can be a useful tool for economists who estimate structural models using indirect inference, because unlike commonly used algorithms such as Nelder-Mead, Pounders is tailored for minimizing a non-linear sum of squares objective function, and therefore may require fewer iterations to arrive at a local optimum than Nelder-Mead. The criterion function :func:`func` should return an array of the errors NOT an array of the squared errors or anything else. Args: func (callable): Objective function. x0 (np.ndarray): Starting values of the parameters. bounds (Tuple[np.ndarray]): A tuple containing two NumPy arrays where the first corresponds to the lower and the second to the upper bound. Unbounded parameters are represented by infinite values. The arrays have the same length as the parameter vector. gatol (float): Stop if relative norm of gradient is less than this. If set to False the algorithm will not consider gatol. Default is 1e-8. grtol (float): Stop if norm of gradient is less than this. If set to False the algorithm will not consider grtol. Default is 1e-8. gttol (float): Stop if norm of gradient is reduced by this factor. If set to False the algorithm will not consider grtol. Default is 1e-10. init_tr (float): Sets the radius for the initial trust region that the optimizer employs. If `None` the algorithm uses 100 as initial trust region radius. Default is `None`. max_iterations (int): Alternative Stopping criterion. If set the routine will stop after the number of specified iterations or after the step size is sufficiently small. If the variable is set the default criteria will all be ignored. Default is `None`. n_errors (int or None): The number of outputs of `func` are necessary to pre-allocate the results array. If the argument is ``None``, evaluate the function once. This might be undesirable during dashboard optimizations. Returns: results (dict): Dictionary with processed optimization results. .. _TAO Users Manual: https://www.mcs.anl.gov/petsc/petsc-current/docs/tao_manual.pdf .. _Solving Derivative-Free Nonlinear Least Squares Problems with POUNDERS: https://www.mcs.anl.gov/papers/P5120-0414.pdf """ if sys.platform == "win32": raise NotImplementedError("The pounders algorithm is not available on Windows.") # We need to know the dimension of the output of the criterion function. Evaluate # plain `criterion` to prevent logging. if n_errors is None: n_errors = len(func(x0)) # We want to get containers for the func vector and the paras. x0 = _initialise_petsc_array(x0) residuals_out = _initialise_petsc_array(n_errors) # Create the solver object. tao = PETSc.TAO().create(PETSc.COMM_WORLD) # Set the solver type. tao.setType("pounders") tao.setFromOptions() def func_tao(tao, x, resid_out): """Evaluate objective and attach result to an petsc object f. This is required to use the pounders solver from tao. Args: tao: The tao object we created for the optimization task. x (PETSc.array): Current parameter values. f: Petsc object in which we save the current function value. """ resid_out.array = func(x.array) # Set the procedure for calculating the objective. This part has to be changed if we # want more than pounders. tao.setResidual(func_tao, residuals_out) # We try to set user defined convergence tests. if init_tr is not None: tao.setInitialTrustRegionRadius(init_tr) # Add bounds. n_params = len(x0.array) processed_bounds = [] for bound in bounds: bound = np.full(n_params, bound) if isinstance(bound, (int, float)) else bound processed_bounds.append(_initialise_petsc_array(bound)) tao.setVariableBounds(processed_bounds) # Put the starting values into the container and pass them to the optimizer. tao.setInitial(x0) # Obtain tolerances for the convergence criteria. Since we can not create gttol # manually we manually set gatol and or grtol to zero once a subset of these two is # turned off and gttol is still turned on. default_gatol = gatol if gatol else -1 default_gttol = gttol if gttol else -1 default_grtol = grtol if grtol else -1 # Set tolerances for default convergence tests. tao.setTolerances(gatol=default_gatol, gttol=default_gttol, grtol=default_grtol) # Set user defined convergence tests. Beware that specifying multiple tests could # overwrite others or lead to unclear behavior. if max_iterations is not None: tao.setConvergenceTest(partial(_max_iters, max_iterations)) elif gttol is False and gatol is False: tao.setConvergenceTest(partial(_grtol_conv, grtol)) elif grtol is False and gttol is False: tao.setConvergenceTest(partial(_gatol_conv, gatol)) elif gttol is False: tao.setConvergenceTest(partial(_grtol_gatol_conv, grtol, gatol)) # Run the problem. tao.solve() results = _process_pounders_results(residuals_out, tao) # Destroy petsc objects for memory reasons. tao.destroy() x0.destroy() residuals_out.destroy() return results
def solve(func, x, len_out, bounds=None, init_tr=None, tol={ "gatol": 0.00000001, "grtol": 0.00000001, "gttol": 0.0000000001 }, max_iterations=None, gatol=True, grtol=True, gttol=True): """ Args: func: function that takes a 1d numpy array and returns a 1d numpy array x:np.array that contains the start values of the variables of interest bounds: list or tuple of lists containing the bounds for the variable of interest The first list contains the lower value for each param and the upper list the upper value init_tr: Sets the radius for the initial trust region that the optimizer employs. tol: Sets the tolerance for the three default stopping criteria. The routine will stop once the first is reached. One can turn off specific criteria with other args. In this case their value in this dict does not matter. max_iterations: Alternative Stopping criterion. If set the routine will stop after the number of specified iterations or after the step size is sufficiently small. If the variable is set the default criteria will all be ignored. gatol: Boolean that indicates whether the gatol should be cosnidered. Explicit description is in the documentation. grtol: Boolean that indicates whether the grtol should be cosnidered. Explicit description is in the documentation gttol: Boolean that indicates whether the gttol should be cosnidered. Explicit description is in the documentation Returns: out: dict with the following key value pairs: "solution": solution vector as np.array, "func values": np.array of value of the objective at the solution "x": np.array of the start values "conv": string indicating the termination reason "sol": list containing: current iterate as int, current value of the objective as float, current value of the approximated jacobian as float, infeasability norm as float, step length as float and termination reason as int. """ # we want to get containers for the func verctor and the paras size_paras = len(x) size_objective = len_out paras, crit = _prep_args(size_paras, size_objective) # Set the start value paras[:] = x def func_tao(tao, paras, f): """ This function takes an input, calculates the value of the objective and attaches it to an petsc object f thereafter. func_tao puts the objective in a format that the optimizer requires. Args: tao: The tao object we created for the optimization task paras: 1d np.array of the current values at which we want to evaluate the function. f: Petsc object in which we save the current function value """ dev = func(paras.array) # Attach to PETSc object f.array = dev # Create the solver object tao = PETSc.TAO().create(PETSc.COMM_WORLD) # Set the solver type tao.setType('pounders') tao.setFromOptions() # Set the procedure for calculating the objective # This part has to be changed if we want more than pounders tao.setResidual(func_tao, crit) # We try to set user defined convergence tests if init_tr is not None: tao.setInitialTrustRegionRadius(init_tr) # Change they need to be in a container # Set the variable sounds if existing if bounds is not None: low, up = _prep_args(len(x), len(x)) low.array = bounds[0] up.array = bounds[1] tao.setVariableBounds([low, up]) # Set the container over which we optimize that already contians start values tao.setInitial(paras) # Obtain tolerances for the convergence criteria # Since we can not create gttol manually we manually set gatol and or grtol to zero once a subset of these two is # turned off and gttol is still turned on tol_real = get_tolerances(tol, gatol, grtol) # Set tolerances for default convergence tests tao.setTolerances(gatol=tol_real["gatol"], gttol=tol_real["gttol"], grtol=tol_real["grtol"]) # Set user defined convergence tests. Beware that specifiying multiple tests could overwrite others or lead to # unclear behavior. if max_iterations is not None: tao.setConvergenceTest(partial(max_iters, max_iterations)) elif gttol is False and gatol is False: tao.setConvergenceTest(partial(grtol_conv, tol["grtol"])) elif gatol is False and gttol is False: tao.setConvergenceTest(partial(gatol_conv, tol["gatol"])) elif gttol is False: tao.setConvergenceTest( partial(grtol_gatol_conv, tol["grtol"], tol["gatol"])) # Run the problem tao.solve() # Create a dict that contains relevant information out = dict() out["solution"] = paras.array out["func_values"] = crit.array out["x"] = x out["conv"] = conv_reason[tao.getConvergedReason()] out["sol"] = tao.getSolutionStatus() # Destroy petsc objects for memory reasons tao.destroy() paras.destroy() crit.destroy() return out