Esempio n. 1
0
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
Esempio n. 2
0
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