Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get_current_iterate and get_current_violations methods to Problem class #182

Merged
merged 47 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
81971d0
start function to get iterate
Robbybp Feb 10, 2023
c224638
implement get_current_iterate and get_current_violations methods
Robbybp Feb 10, 2023
f356322
fix typo in definition instance fixture
Robbybp Feb 10, 2023
7c82b25
tests for get_current_iterate and get_current_violations
Robbybp Feb 10, 2023
bf7de5c
TODO comments
Robbybp Feb 10, 2023
dfa93c4
update tolerance in primal infeas test
Robbybp Feb 10, 2023
60fcb04
Merge branch 'master' of https://github.com/mechmotum/cyipopt into ge…
Robbybp Feb 12, 2023
444f0bc
check ipopt version before calling GetIpoptCurrentIterate
Robbybp Feb 13, 2023
5b32906
check Ipopt version at build time
Robbybp Feb 14, 2023
f2a0acf
update pre/post-solve tests so they work with latest Ipopt branch
Robbybp Feb 14, 2023
77efa0d
add flag to degect whether we are in a call to IpoptSolve
Robbybp Feb 14, 2023
0ad62e8
remove outdated comment
Robbybp Feb 14, 2023
fd4356f
update return types from get_current_* to dicts
Robbybp Feb 14, 2023
d4c7a7c
turn off bound relaxation and set atol for (final) intermediate prima…
Robbybp Feb 15, 2023
9752fb9
Merge branch 'get-iterate' of https://github.com/robbybp/cyipopt into…
Robbybp Feb 15, 2023
7cf21e1
install libarchive
Robbybp Feb 16, 2023
4c0f4f0
Merge branch 'master' into get-iterate
Robbybp Feb 18, 2023
02bd357
get_current_iterate docstring
Robbybp Feb 18, 2023
e91ceff
docstring for get_current_violations
Robbybp Feb 18, 2023
e74299b
return none from get_current_* if values are not loaded
Robbybp Feb 19, 2023
5101bbd
Merge branch 'get-iterate' of https://github.com/robbybp/cyipopt into…
Robbybp Feb 19, 2023
5d08fed
update key in violation dict
Robbybp Feb 19, 2023
f0dba5e
update docstrings
Robbybp Feb 19, 2023
2aa23ab
update docstring
Robbybp Feb 19, 2023
29393b2
prototype support for 12-arg intermediate callback
Robbybp Feb 19, 2023
18d3812
Merge pull request #1 from Robbybp/cb-signature
Robbybp Feb 27, 2023
55d833d
update get_current_violations test to not rely on special attribute o…
Robbybp Feb 27, 2023
f502cee
add test using subclass of Problem
Robbybp Feb 27, 2023
ad973f5
document optional problem argument in intermediate callback
Robbybp Mar 17, 2023
7e5923f
raise error if intermediate call signature is not something we expect
Robbybp Mar 17, 2023
8573c67
add test callback with variable number of arguments
Robbybp Mar 17, 2023
c1f1b63
clarify logic and change name of flag when deciding which callback to…
Robbybp Mar 17, 2023
f93733a
add tests for exceptions with different invalid intermediate call sig…
Robbybp Mar 17, 2023
ff88aea
tutorial section using get_current_* by subclassing cyipopt.Problem
Robbybp Apr 9, 2023
a19b114
remove unnecessary punctuation
Robbybp Apr 10, 2023
54bd031
Merge branch 'master' of https://github.com/mechmotum/cyipopt into ge…
Robbybp Apr 25, 2023
abef99c
Merge branch 'get-iterate' of https://github.com/robbybp/cyipopt into…
Robbybp Apr 25, 2023
01a776b
remove code that was necessary to support and test a 12-argument call…
Robbybp Apr 25, 2023
b7046e7
update get_current_* section of docs
Robbybp Apr 25, 2023
2a24f09
pin scipy to 1.10.0
Robbybp Apr 27, 2023
de52b8e
Update .github/workflows/tests.yml
moorepants Apr 28, 2023
cc4efc0
Update .github/workflows/tests.yml
moorepants Apr 28, 2023
27ebaf4
Update .github/workflows/tests.yml
moorepants Apr 28, 2023
6614eaf
Update .github/workflows/tests.yml
moorepants Apr 28, 2023
aba6c97
Update .github/workflows/tests.yml
moorepants Apr 28, 2023
0a1af02
Update .github/workflows/tests.yml
moorepants Apr 28, 2023
f78615d
Drop Python 3.7 tests in CI.
moorepants Apr 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ jobs:
# Ipopt needed different libfortrans.
if: (matrix.ipopt-version != '3.12' && matrix.python-version != '3.11') || (matrix.ipopt-version != '3.12' && matrix.python-version != '3.10' && matrix.os != 'macos-latest')
run: |
mamba install -q -y -c conda-forge cython>=0.26 "ipopt=${{ matrix.ipopt-version }}" numpy>=1.15 pkg-config>=0.29.2 setuptools>=39.0 pytest>=3.3.2 scipy>=0.19.0
mamba install -q -y -c conda-forge cython>=0.26 "ipopt=${{ matrix.ipopt-version }}" numpy>=1.15 pkg-config>=0.29.2 setuptools>=39.0 pytest>=3.3.2 "scipy<1.10.0"
mamba list
pytest
101 changes: 101 additions & 0 deletions cyipopt/cython/ipopt.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,78 @@ Copyright (C) 2017-2023 cyipopt developers
License: EPL 2.0
"""

cdef extern from "IpoptConfig.h":

int IPOPT_VERSION_MAJOR

int IPOPT_VERSION_MINOR

int IPOPT_VERSION_RELEASE


cdef extern from "IpStdCInterface.h":
"""
#define VERSION_LT_3_14_0\
(IPOPT_VERSION_MAJOR < 3\
|| (IPOPT_VERSION_MAJOR == 3 && IPOPT_VERSION_MINOR < 14))

#if VERSION_LT_3_14_0
// If not defined, define dummy versions of these functions
Bool GetIpoptCurrentIterate(
IpoptProblem ipopt_problem,
Bool scaled,
Index n,
Number* x,
Number* z_L,
Number* z_U,
Index m,
Number* g,
Number* lambd
){
return 0;
}
Bool GetIpoptCurrentViolations(
IpoptProblem ipopt_problem,
Bool scaled,
Index n,
Number* x_L_violation,
Number* x_U_violation,
Number* compl_x_L,
Number* compl_x_U,
Number* grad_lag_x,
Index m,
Number* nlp_constraint_violation,
Number* compl_g
){
return 0;
}
#define _ip_get_iter(\
problem, scaled, n, x, z_L, z_U, m, g, lambd\
)\
GetIpoptCurrentIterate(\
problem, scaled, n, x, z_L, z_U, m, g, lambd\
)
#define _ip_get_viol(\
problem, scaled, n, xL, xU, complxL, complxU, glx, m, cviol, complg\
)\
GetIpoptCurrentViolations(\
problem, scaled, n, xL, xU, complxL, complxU, glx, m, cviol, complg\
)
#else
#define _ip_get_iter(\
problem, scaled, n, x, z_L, z_U, m, g, lambd\
)\
GetIpoptCurrentIterate(\
problem, scaled, n, x, z_L, z_U, m, g, lambd\
)
#define _ip_get_viol(\
problem, scaled, n, xL, xU, complxL, complxU, glx, m, cviol, complg\
)\
GetIpoptCurrentViolations(\
problem, scaled, n, xL, xU, complxL, complxU, glx, m, cviol, complg\
)
#endif
"""

ctypedef double Number

Expand Down Expand Up @@ -167,3 +238,33 @@ cdef extern from "IpStdCInterface.h":
Number* mult_x_U,
UserDataPtr user_data
)

# Wrapper around GetIpoptCurrentIterate with a dummy implementation in
# case it is not defined (i.e. Ipopt < 3.14.0)
Bool CyGetCurrentIterate "_ip_get_iter" (
IpoptProblem ipopt_problem,
Bool scaled,
Index n,
Number* x,
Number* z_L,
Number* z_U,
Index m,
Number* g,
Number* lambd
)

# Wrapper around GetIpoptCurrentViolations with a dummy implementation in
# case it is not defined (i.e. Ipopt < 3.14.0)
Bool CyGetCurrentViolations "_ip_get_viol" (
IpoptProblem ipopt_problem,
Bool scaled,
Index n,
Number* x_L_violation,
Number* x_U_violation,
Number* compl_x_L,
Number* compl_x_U,
Number* grad_lag_x,
Index m,
Number* nlp_constraint_violation,
Number* compl_g
)
196 changes: 195 additions & 1 deletion cyipopt/cython/ipopt_wrapper.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ License: EPL 2.0
import logging
import sys
import warnings
import inspect

import numpy as np
cimport numpy as np

from cyipopt.utils import deprecated_warning, generate_deprecation_warning_msg
from ipopt cimport *

__all__ = ["set_logging_level", "setLoggingLevel", "Problem", "problem"]
__all__ = [
"set_logging_level", "setLoggingLevel", "Problem", "problem", "IPOPT_VERSION"
]

IPOPT_VERSION = (IPOPT_VERSION_MAJOR, IPOPT_VERSION_MINOR, IPOPT_VERSION_RELEASE)

DTYPEi = np.int32
ctypedef np.int32_t DTYPEi_t
Expand Down Expand Up @@ -275,6 +280,7 @@ cdef class Problem:
cdef public Index __m

cdef public object __exception
cdef Bool __in_ipopt_solve

def __init__(self, n, m, problem_obj=None, lb=None, ub=None, cl=None,
cu=None):
Expand Down Expand Up @@ -433,6 +439,10 @@ cdef class Problem:

self.__exception = None

# This flag is necessary to prevent segfaults in Ipopt <=3.14.11 due
# to the lack of guard for __nlp->tnlp being NULL or undefined.
self.__in_ipopt_solve = False

def __dealloc__(self):
if self.__nlp != NULL:
FreeIpoptProblem(self.__nlp)
Expand Down Expand Up @@ -628,6 +638,9 @@ cdef class Problem:

cdef Number obj_val = 0

# Set flag that we are in a solve, so __nlp->tnlp references (e.g. in
# get_current_iterate) are valid.
self.__in_ipopt_solve = True
stat = IpoptSolve(self.__nlp,
<Number*>np_x.data,
<Number*>g.data,
Expand All @@ -637,6 +650,8 @@ cdef class Problem:
<Number*>mult_x_U.data,
<UserDataPtr>self
)
# Unset flag
self.__in_ipopt_solve = False

if self.__exception:
raise self.__exception[0], self.__exception[1], self.__exception[2]
Expand All @@ -654,6 +669,185 @@ cdef class Problem:

return np_x, info

def get_current_iterate(self, scaled=False):
"""Return the current iterate vectors during an Ipopt solve

The iterate contains vectors for primal variables, bound multipliers,
constraint function values, and constraint multipliers. Here, the
constraints are treated as a single function rather than separating
equality and inequality constraints. This method can only be called
during an intermediate callback.

**Only supports Ipopt >=3.14.0**

Parameters
----------
scaled: Bool
Whether the scaled iterate vectors should be returned

Returns
-------
dict or None
A dict containing the iterate vector with keys ``"x"``,
``"mult_x_L"``, ``"mult_x_U"``, ``"g"``, and ``"mult_g"``.
If iterate vectors cannot be obtained, ``None`` is returned.

"""
# Check that we are using an Ipopt version that supports this
# functionality
major, minor, release = IPOPT_VERSION
if major < 3 or (major == 3 and minor < 14):
raise RuntimeError(
"get_current_iterate only supports Ipopt version >=3.14.0"
" CyIpopt is compiled with version %s.%s.%s"
% (major, minor, release)
)
# Check that we are in a solve. This is necessary to prevent segfaults
# pre-Ipopt 3.14.12
if not self.__in_ipopt_solve:
raise RuntimeError(
"get_current_iterate can only be called during a call to solve,"
" e.g. in an intermediate callback."
)
# Allocate arrays to hold the current iterate
cdef np.ndarray[DTYPEd_t, ndim=1] np_x
cdef np.ndarray[DTYPEd_t, ndim=1] np_mult_x_L
cdef np.ndarray[DTYPEd_t, ndim=1] np_mult_x_U
cdef np.ndarray[DTYPEd_t, ndim=1] np_g
cdef np.ndarray[DTYPEd_t, ndim=1] np_mult_g
np_x = np.zeros((self.__n,), dtype=DTYPEd).flatten()
np_mult_x_L = np.zeros((self.__n,), dtype=DTYPEd).flatten()
np_mult_x_U = np.zeros((self.__n,), dtype=DTYPEd).flatten()
np_g = np.zeros((self.__m,), dtype=DTYPEd).flatten()
np_mult_g = np.zeros((self.__m,), dtype=DTYPEd).flatten()

# Cast to C data types
x = <Number*>np_x.data
mult_x_L = <Number*>np_mult_x_L.data
mult_x_U = <Number*>np_mult_x_U.data
g = <Number*>np_g.data
mult_g = <Number*>np_mult_g.data

successful = CyGetCurrentIterate(
self.__nlp,
scaled,
self.__n,
x,
mult_x_L,
mult_x_U,
self.__m,
g,
mult_g,
)
if successful:
# Return values to user
return {
"x": np_x,
"mult_x_L": np_mult_x_L,
"mult_x_U": np_mult_x_U,
"g": np_g,
"mult_g": np_mult_g,
}
else:
# This happens if this method is called during IpoptSolve,
# but outside of an intermediate callback.
return None

def get_current_violations(self, scaled=False):
"""Return the current violation vectors during an Ipopt solve

Violations returned are primal variable bound violations, bound
complementarities, the gradient of the Lagrangian, constraint
violation, and constraint complementarity. Here, the constraints
are treated as a single function rather than separating equality
and inequality constraints. This method can only be called during
an intermediate callback.

**Only supports Ipopt >=3.14.0**

Parameters
----------
scaled: Bool
Whether to scale the returned violations

Returns
-------
dict or None
A dict containing the violation vector with keys
``"x_L_violation"``, ``"x_U_violation"``, ``"compl_x_L"``,
``"compl_x_U"``, ``"grad_lag_x"``, ``"g_violation"``,
and ``"compl_g"``. If violation vectors cannot be obtained,
``None`` is returned.

"""
major, minor, release = IPOPT_VERSION
if major < 3 or (major == 3 and minor < 14):
raise RuntimeError(
"get_current_violations only supports Ipopt version >=3.14.0"
" CyIpopt is compiled with version %s.%s.%s"
% (major, minor, release)
)
# Check that we are in a solve. This is necessary to prevent segfaults
# pre-Ipopt 3.14.12
if not self.__in_ipopt_solve:
raise RuntimeError(
"get_current_violations can only be called during a call to solve,"
" e.g. in an intermediate callback."
)
# Allocate arrays to hold current violations
cdef np.ndarray[DTYPEd_t, ndim=1] np_x_L_viol
cdef np.ndarray[DTYPEd_t, ndim=1] np_x_U_viol
cdef np.ndarray[DTYPEd_t, ndim=1] np_compl_x_L
cdef np.ndarray[DTYPEd_t, ndim=1] np_compl_x_U
cdef np.ndarray[DTYPEd_t, ndim=1] np_grad_lag_x
cdef np.ndarray[DTYPEd_t, ndim=1] np_g_viol
cdef np.ndarray[DTYPEd_t, ndim=1] np_compl_g
np_x_L_viol = np.zeros((self.__n,), dtype=DTYPEd).flatten()
np_x_U_viol = np.zeros((self.__n,), dtype=DTYPEd).flatten()
np_compl_x_L = np.zeros((self.__n,), dtype=DTYPEd).flatten()
np_compl_x_U = np.zeros((self.__n,), dtype=DTYPEd).flatten()
np_grad_lag_x = np.zeros((self.__n,), dtype=DTYPEd).flatten()
np_g_viol = np.zeros((self.__m,), dtype=DTYPEd).flatten()
np_compl_g = np.zeros((self.__m,), dtype=DTYPEd).flatten()

# Cast to C data types
x_L_viol = <Number*>np_x_L_viol.data
x_U_viol = <Number*>np_x_U_viol.data
compl_x_L = <Number*>np_compl_x_L.data
compl_x_U = <Number*>np_compl_x_U.data
grad_lag_x = <Number*>np_grad_lag_x.data
g_viol = <Number*>np_g_viol.data
compl_g = <Number*>np_compl_g.data

successful = CyGetCurrentViolations(
self.__nlp,
scaled,
self.__n,
x_L_viol,
x_U_viol,
compl_x_L,
compl_x_U,
grad_lag_x,
self.__m,
g_viol,
compl_g,
)
if successful:
# Return values to the user
return {
"x_L_violation": np_x_L_viol,
"x_U_violation": np_x_U_viol,
"compl_x_L": np_compl_x_L,
"compl_x_U": np_compl_x_U,
"grad_lag_x": np_grad_lag_x,
"g_violation": np_g_viol,
"compl_g": np_compl_g,
}
else:
# This happens if this method is called during IpoptSolve,
# but outside of an intermediate callback.
return None


#
# Callback functions
Expand Down
6 changes: 3 additions & 3 deletions cyipopt/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def intermediate(*args):


@pytest.fixture()
def hs071_defintion_instance_fixture(hs071_objective_fixture,
def hs071_definition_instance_fixture(hs071_objective_fixture,
hs071_gradient_fixture,
hs071_constraints_fixture,
hs071_jacobian_fixture,
Expand Down Expand Up @@ -175,15 +175,15 @@ def hs071_constraint_upper_bounds_fixture():


@pytest.fixture()
def hs071_problem_instance_fixture(hs071_defintion_instance_fixture,
def hs071_problem_instance_fixture(hs071_definition_instance_fixture,
hs071_initial_guess_fixture,
hs071_variable_lower_bounds_fixture,
hs071_variable_upper_bounds_fixture,
hs071_constraint_lower_bounds_fixture,
hs071_constraint_upper_bounds_fixture,
):
"""Return a default cyipopt.Problem instance of the hs071 test problem."""
problem_definition = hs071_defintion_instance_fixture
problem_definition = hs071_definition_instance_fixture
x0 = hs071_initial_guess_fixture
lb = hs071_variable_lower_bounds_fixture
ub = hs071_variable_upper_bounds_fixture
Expand Down
Loading