def knitro_context_manager() -> Iterator[Tuple[Any, Any]]: """Import Knitro and initialize its context.""" try: import knitro except OSError as exception: if 'Win32' in repr(exception): raise EnvironmentError("Make sure both Knitro and Python are 32- or 64-bit.") from exception raise # modify Knitro to work with numpy import knitroNumPy knitro.KTR_array_handler._cIntArray = knitroNumPy._cIntArray knitro.KTR_array_handler._cDoubleArray = knitroNumPy._cDoubleArray knitro.KTR_array_handler._userArray = knitroNumPy._userArray knitro.KTR_array_handler._userToCArray = knitroNumPy._userToCArray knitro.KTR_array_handler._cToUserArray = knitroNumPy._cToUserArray # create the Knitro context and attempt to free it if anything goes wrong knitro_context = None try: knitro_context = knitro.KTR_new() if not knitro_context: raise OSError( "Failed to find a Knitro license. Make sure that Knitro is properly installed. You may have to create " "the environment variable ARTELYS_LICENSE and set it to the location of the directory with the license " "file." ) yield knitro, knitro_context finally: try: knitro.KTR_free(knitro_context) except Exception: pass
def knitro_optimizer(initial_values, bounds, objective_function, iteration_callback, compute_gradient, **knitro_options): """Optimize with Knitro.""" try: import knitro except OSError as exception: if 'Win32' in repr(exception): raise EnvironmentError("Make sure both Knitro and Python are 32- or 64-bit.") from exception raise # modify Knitro to work with numpy import knitroNumPy knitro.KTR_array_handler._cIntArray = knitroNumPy._cIntArray knitro.KTR_array_handler._cDoubleArray = knitroNumPy._cDoubleArray knitro.KTR_array_handler._userArray = knitroNumPy._userArray knitro.KTR_array_handler._userToCArray = knitroNumPy._userToCArray knitro.KTR_array_handler._cToUserArray = knitroNumPy._cToUserArray # create the Knitro context and attempt to free it if anything goes wrong knitro_context = None try: knitro_context = knitro.KTR_new() if not knitro_context: raise RuntimeError( "Failed to find a Knitro license. Make sure that Knitro is properly installed. You may have to create " "the environment variable ARTELYS_LICENSE and set it to the location of the directory with the license " "file." ) # define a function that handles requests to compute either the objective or its gradient (which are cached for # when the next request is for the same values) and calls the iteration callback when there's a new iteration def combined_callback(*args): request_code, values, objective_store, gradient_store = (args[i] for i in [0, 5, 7, 9]) # call the iteration callback if this is a new iteration iterations = knitro.KTR_get_number_iters(knitro_context) while combined_callback.iterations < iterations: iteration_callback() combined_callback.iterations += 1 # compute the objective or used cached values if combined_callback.cache is not None and np.array_equal(values, combined_callback.cache[0]): combined = combined_callback.cache[1] else: combined = objective_function(values) combined_callback.cache = (values.copy(), combined) # extract the objective and its gradient objective, gradient = combined if compute_gradient else (combined, None) # handle request codes if request_code == knitro.KTR_RC_EVALFC: objective_store[:] = objective return knitro.KTR_RC_BEGINEND if request_code == knitro.KTR_RC_EVALGA: gradient_store[:] = gradient return knitro.KTR_RC_BEGINEND return knitro.KTR_RC_CALLBACK_ERR # initialize an empty cache and the counter combined_callback.cache = None combined_callback.iterations = 0 # configure Knitro callbacks callback_mapping = { knitro.KTR_set_func_callback: combined_callback, knitro.KTR_set_grad_callback: combined_callback } for set_callback, callback in callback_mapping.items(): code = set_callback(knitro_context, callback) if code != 0: raise RuntimeError(f"Encountered error code {code} when registering {set_callback.__name__}.") # configure Knitro parameters for key, value in knitro_options.items(): set_parameter = knitro.KTR_set_param_by_name if isinstance(value, str): set_parameter = knitro.KTR_set_char_param_by_name code = set_parameter(knitro_context, key, value) if code != 0: raise RuntimeError(f"Encountered error code {code} when configuring '{key}'.") # initialize the problem bounds = bounds or [None] * initial_values.size with warnings.catch_warnings(): warnings.simplefilter('ignore') code = knitro.KTR_init_problem( kc=knitro_context, n=initial_values.size, xInitial=initial_values, lambdaInitial=None, objGoal=knitro.KTR_OBJGOAL_MINIMIZE, objType=knitro.KTR_OBJTYPE_GENERAL, xLoBnds=np.array([b[0] if np.isfinite(b[0]) else -knitro.KTR_INFBOUND for b in bounds]), xUpBnds=np.array([b[1] if np.isfinite(b[1]) else +knitro.KTR_INFBOUND for b in bounds]), cType=None, cLoBnds=None, cUpBnds=None, jacIndexVars=None, jacIndexCons=None, hessIndexRows=None, hessIndexCols=None ) if code != 0: raise RuntimeError(f"Encountered error code {code} when initializing the Knitro problem solver.") # solve the problem values_store = np.zeros_like(initial_values) with warnings.catch_warnings(): warnings.simplefilter('ignore') return_code = knitro.KTR_solve( kc=knitro_context, x=values_store, lambda_=np.zeros_like(initial_values), evalStatus=0, obj=np.array([0]), c=None, objGrad=None, jac=None, hess=None, hessVector=None, userParams=None ) # Knitro was only successful if its return code was 0 (final solution satisfies the termination conditions for # verifying optimality) or between -100 and -199 (a feasible approximate solution was found) return values_store, return_code > -200 finally: try: knitro.KTR_free(knitro_context) except: pass