Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 24 additions & 2 deletions opty/direct_collocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ class Problem(cyipopt.Problem):
eomM2, ... eomMN,
c1, ..., co]

The ``bounds`` attribute may be changed to a new dictionary after
instantiation of ``Problem`` by setting ``Problem.bounds = {..}`` but
changing ``problem.bounds[state] = (low, high)`` will have no effect.

Some attributes are only accessible from the
:py:class:`ConstraintCollocator` object associated with this ``Problem``
and may be accessed as follows::

Problem_instance.collocator.name_of_attribute

"""

INF = 10e19
Expand Down Expand Up @@ -437,8 +447,20 @@ def _generate_bound_arrays(self):
msg = 'Bound variable {} not present in free variables.'
raise ValueError(msg.format(var))

self.lower_bound = lb
self.upper_bound = ub
# NOTE : The lower and upper bounds arrays are passed into Problem and
# pointers to the data in the arrays are stored in the underlying Ipopt
# problem object. So if they already exist and the user modifies them
# by creating a new Problem.bounds dictionary update the data instead
# of created new arrays.
if hasattr(self, '_lower_bound'):
self._lower_bound[:] = lb
else:
self._lower_bound = lb

if hasattr(self, 'upper_bound'):
self._upper_bound[:] = ub
else:
self._upper_bound = ub

def objective(self, free):
"""Returns the value of the objective function given a solution to the
Expand Down
33 changes: 33 additions & 0 deletions opty/tests/test_direct_collocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,14 @@ def test_pendulum():

assert prob.collocator.num_instance_constraints == 4

# NOTE : you cannot set the bounds directly, it has to be done via setting
# the Problem.bounds to a new dictionary.
with raises(AttributeError):
prob.lower_bound = np.ones(prob.num_free)

with raises(AttributeError):
prob.upper_bound = np.ones(prob.num_free)

expected_low_con_bounds = np.zeros(prob.num_constraints)
expected_low_con_bounds[:50] = -20.0
expected_low_con_bounds[50:100] = -10.0
Expand Down Expand Up @@ -470,6 +478,31 @@ def test_problem(self):

assert prob.collocator.num_instance_constraints == 0

lb = prob.lower_bound
ub = prob.upper_bound

assert prob.collocator.num_instance_constraints == 0

prob.bounds = {
x: (-12.0, 12.0),
f: (-8.0, 8.0),
m: (-1.0, 1.0),
c: (-0.5, 0.5),
}

expected_lower = np.array([-12.0, -12.0,
-INF, -INF,
-8.0, -8.0,
-0.5, -INF, -1.0])
np.testing.assert_allclose(prob.lower_bound, expected_lower)
assert np.shares_memory(lb, prob.lower_bound)
expected_upper = np.array([12.0, 12.0,
INF, INF,
8.0, 8.0,
0.5, INF, 1.0])
np.testing.assert_allclose(prob.upper_bound, expected_upper)
assert np.shares_memory(ub, prob.upper_bound)

# run Problem again to see if the cache worked.
prob = Problem(
lambda x: 1.0,
Expand Down
Loading