-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor the processing of nonlinear constraints
- Loading branch information
1 parent
63a319c
commit 5440781
Showing
5 changed files
with
137 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2248,3 +2248,4 @@ pycutest | |
excludelist | ||
slsqp | ||
optiprofiler | ||
manylinux |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,100 +1,79 @@ | ||
import numpy as np | ||
|
||
def process_single_nl_constraint(nlc): | ||
def process_single_nl_constraint(nlc, x0): | ||
''' | ||
The Python interfaces receives the constraints as lb <= constraint(x) <= ub, | ||
but the Fortran backend expects the nonlinear constraints to be constr(x) <= 0. | ||
but the Fortran backend expects the nonlinear constraints to be constraint(x) <= 0. | ||
Thus a conversion is needed. | ||
This function will take a NonlinearConstraint and return a new function which incorporates | ||
the lower and upper bounds in such a way as to satisfy the expectation of the Fortran | ||
backend. This new function first calls the original function from the provided NonlinearConstraint | ||
and obtains the values, and then passes those values to a second function which combines | ||
them with the lower and upper bounds as appropriate. The main work here is creating that | ||
second function. | ||
In addition to the conversion, we would like to check the size of the output of the | ||
constraint function as compared to the provided lb and ub, and so we must run the | ||
constraint function here. Since we presume it is expensive to run the constraint function, | ||
we must also return the output of the constraint function to the caller, so that they may | ||
avoid running the constraint function unnecessarily. | ||
In general to convert lb <= constraint(x) <= ub to newconstraint(x) <= 0, all we need to do | ||
is define newconstraint(x) as [constraint(x) - ub, lb - constraint(x)]. There are some details | ||
regarding the type and content of lb and ub as detailed below. | ||
In order to accomplish all these goals, we first run the constraint function, then we | ||
upgrade lb/ub to vectors if they are not already (while checking their sizes in the process | ||
and raising exceptions if necessary), and then we create a function to transform the output | ||
of the constraint function to the form expected by the Fortran backend. | ||
''' | ||
|
||
# Run the constraint function and upgrade the lb/ub to the correct size, | ||
# while also checking that if they were already vectors that they had the correct | ||
# size in the first place. | ||
nlconstr0 = nlc.fun(x0) | ||
nlconstr0 = np.atleast_1d(np.array(nlconstr0, dtype=np.float64)) | ||
lb, ub = _upgrade_lb_ub_to_vectors(nlc.lb, nlc.ub, nlconstr0) | ||
|
||
The upper and lower bounds could be either scalars or vectors, and so we have 4 possible | ||
cases to take into account: | ||
# Check if any bounds contain -inf and +inf simultaneously | ||
if np.any((lb == -np.inf) & (ub == np.inf)): | ||
raise ValueError("A NonlinearConstraint was provided without specifying lower or upper bounds") | ||
|
||
1. Both lb and ub are scalars | ||
In this case we have a further 4 cases! | ||
1a. lb == -inf and ub == inf | ||
This case makes no sense since it means that there are effectively no constraints, | ||
so we raise an exception. | ||
1b. lb == -inf and ub != inf | ||
This is a one sided constraint, so we can define newconstraint(x) as constraint(x) - ub | ||
1c. lb != -inf and ub == inf | ||
This is a one sided constraint, so we can define newconstraint(x) as lb - constraint(x) | ||
1d. lb != -inf and ub != inf | ||
Since we have both constraints we define newconstraint(x) as [constraint(x) - ub, lb - constraint(x)] | ||
2. lb is a scalar and ub is a vector | ||
In this case we have a further 2 cases. | ||
2a. lb == -inf | ||
This is a one sided constraint, so we can define newconstraint(x) as [constraint(x) - ub], however we | ||
should first check if there is any inf in ub and raise an exception since this would be same case as 1a. | ||
2b. lb != -inf | ||
In this case we can have inf in ub, so newconstraint needs to check for that and omit it from the constraints. | ||
See the code for the exact method of accomplishing this. | ||
3. lb is a vector and ub is a scalar | ||
This is the same as case 2, but with lb and ub reversed. | ||
4. Both lb and ub are vectors | ||
There are no subcases here, other than checking for -np.inf in lb and np.inf in ub and removing those constraints. | ||
However we also check for any particular index where ub is inf and lb is -inf simultaneously and raise an exception | ||
if so, since again this is like case 1a. | ||
''' | ||
lb_is_scalar = not hasattr(nlc.lb, "__len__") | ||
ub_is_scalar = not hasattr(nlc.ub, "__len__") | ||
if lb_is_scalar and ub_is_scalar: | ||
if nlc.lb == -np.inf and nlc.ub == np.inf: | ||
raise ValueError("A NonlinearConstraint was provided without specifying lower or upper bounds") | ||
elif nlc.lb == -np.inf: | ||
align_constraint_values = lambda values: values - nlc.ub | ||
elif nlc.ub == np.inf: | ||
align_constraint_values = lambda values: nlc.lb - values | ||
else: | ||
align_constraint_values = lambda values: np.concatenate((values - nlc.ub, nlc.lb - values)) | ||
elif lb_is_scalar and not ub_is_scalar: | ||
if nlc.lb == -np.inf: | ||
if np.inf in nlc.ub: | ||
raise ValueError("A NonlinearConstraint was provided without specifying lower or upper bounds") | ||
align_constraint_values = lambda values: values - nlc.ub | ||
else: | ||
align_constraint_values = lambda values: np.concatenate(([vi - ub_ii for ub_ii, vi in zip(nlc.ub, values) if ub_ii < np.inf], | ||
nlc.lb - values)) | ||
elif not lb_is_scalar and ub_is_scalar: | ||
if nlc.ub == np.inf: | ||
if -np.inf in nlc.lb: | ||
raise ValueError("A NonlinearConstraint was provided without specifying lower or upper bounds") | ||
align_constraint_values = lambda values: nlc.lb - values | ||
else: | ||
align_constraint_values = lambda values: np.concatenate((values - nlc.ub, | ||
[lb_ii - vi for lb_ii, vi in zip(nlc.lb, values) if lb_ii > -np.inf])) | ||
else: | ||
if np.any((nlc.lb == -np.inf) & (nlc.ub == np.inf)): | ||
raise ValueError("A NonlinearConstraint was provided without specifying lower or upper bounds") | ||
align_constraint_values = lambda values: np.concatenate(([vi - ub_ii for ub_ii, vi in zip(nlc.ub, values) if ub_ii < np.inf], | ||
[lb_ii - vi for lb_ii, vi in zip(nlc.lb, values) if lb_ii > -np.inf])) | ||
align_constraint_values = lambda values: np.concatenate(([vi - ub_ii for ub_ii, vi in zip(ub, values) if ub_ii < np.inf], | ||
[lb_ii - vi for lb_ii, vi in zip(lb, values) if lb_ii > -np.inf])) | ||
def newconstraint(x): | ||
values = np.atleast_1d(np.array(nlc.fun(x), dtype=np.float64)) | ||
return align_constraint_values(values) | ||
return newconstraint | ||
nlconstr0 = align_constraint_values(nlconstr0) | ||
return newconstraint, nlconstr0 | ||
|
||
|
||
def process_nl_constraints(nlcs): | ||
def process_nl_constraints(nlcs, x0): | ||
functions = [] | ||
nlconstr0 = np.empty(0) | ||
for nlc in nlcs: | ||
functions.append(process_single_nl_constraint(nlc)) | ||
fun_i, nlconstr0_i = process_single_nl_constraint(nlc, x0) | ||
functions.append(fun_i) | ||
nlconstr0 = np.concatenate((nlconstr0, nlconstr0_i)) | ||
def constraint_function(x): | ||
values = np.empty(0) | ||
for fun in functions: | ||
values = np.concatenate((values, fun(x))) | ||
return values | ||
return constraint_function | ||
return constraint_function, nlconstr0 | ||
|
||
|
||
def _upgrade_lb_ub_to_vectors(lb, ub, nlconstr0): | ||
''' | ||
Make sure length of lb/ub align with length of nlconstr0 | ||
''' | ||
lb_is_scalar = not hasattr(lb, "__len__") | ||
ub_is_scalar = not hasattr(ub, "__len__") | ||
len_nlconstr0 = len(nlconstr0) | ||
if lb_is_scalar and ub_is_scalar: | ||
return np.array([lb]*len_nlconstr0, dtype=np.float64), np.array([ub]*len_nlconstr0, dtype=np.float64) | ||
elif lb_is_scalar and not ub_is_scalar: | ||
if len(ub) != len_nlconstr0: | ||
raise ValueError("The number of elements in the constraint function's output does not match the number of elements in the upper bound.") | ||
return np.array([lb]*len_nlconstr0, dtype=np.float64), np.array(ub, dtype=np.float64) | ||
elif not lb_is_scalar and ub_is_scalar: | ||
if len(lb) != len_nlconstr0: | ||
raise ValueError("The number of elements in the constraint function's output does not match the number of elements in the lower bound.") | ||
return np.array(lb, dtype=np.float64), np.array([ub]*len_nlconstr0, dtype=np.float64) | ||
else: | ||
if len(lb) != len_nlconstr0: | ||
raise ValueError("The number of elements in the constraint function's output does not match the number of elements in the lower bound.") | ||
if len(ub) != len_nlconstr0: | ||
raise ValueError("The number of elements in the constraint function's output does not match the number of elements in the upper bound.") | ||
return np.array(lb, dtype=np.float64), np.array(ub, dtype=np.float64) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters