Skip to content

Commit

Permalink
Use 0 to N-1 for the variable time instance constraints.
Browse files Browse the repository at this point in the history
  • Loading branch information
moorepants committed May 9, 2024
1 parent 2b93b9a commit f114166
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 34 deletions.
10 changes: 5 additions & 5 deletions examples/pendulum_swing_up_variable_duration.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ def obj_grad(free):


# Specify the symbolic instance constraints, i.e. initial and end conditions
# using node numbers 1 to N.
instance_constraints = (theta(1*h),
theta(num_nodes*h) - target_angle,
omega(1*h),
omega(num_nodes*h))
# using node numbers 0 to N - 1
instance_constraints = (theta(0*h),
theta((num_nodes - 1)*h) - target_angle,
omega(0*h),
omega((num_nodes - 1)*h))

# Create an optimization problem.
prob = Problem(obj, obj_grad, eom, state_symbols, num_nodes, h,
Expand Down
43 changes: 28 additions & 15 deletions opty/direct_collocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ def __init__(self, obj, obj_grad, equations_of_motion, state_symbols,
Returns the gradient of the objective function given the free
vector.
bounds : dictionary, optional
This dictionary should contain a mapping from any of the
symbolic states, unknown trajectories, or unknown parameters to
a 2-tuple of floats, the first being the lower bound and the
second the upper bound for that free variable, e.g. ``{x(t):
(-1.0, 5.0)}``.
This dictionary should contain a mapping from any of the symbolic
states, unknown trajectories, unknown parameters, or unknown time
interval to a 2-tuple of floats, the first being the lower bound
and the second the upper bound for that free variable, e.g.
``{x(t): (-1.0, 5.0)}``.
"""

Expand Down Expand Up @@ -464,6 +464,13 @@ class ConstraintCollocator(object):
- nN + qN + r + s : number of free variables
- n(N - 1) + o : number of constraints
[x11, ... x1N,
xn1, ... xnN,
u11, ... u1N,
uq1, ... xqN,
p1, ... pp,
h]
"""

def __init__(self, equations_of_motion, state_symbols,
Expand Down Expand Up @@ -514,9 +521,9 @@ def __init__(self, equations_of_motion, state_symbols,
specified as x(0) - 5.0 and the constraint x(0) = x(5.0) would be
specified as x(0) - x(5.0). For variable duration problems you must
specify time as an integer multiple of the node time interval
symbol, for example ``x(1*h) - 5.0``. The integer must be a value
from 1 to ``num_collocation_nodes``. Unknown parameters and time
varying parameters other than the states are currently not
symbol, for example ``x(0*h) - 5.0``. The integer must be a value
from 0 to ``num_collocation_nodes - 1``. Unknown parameters and
time varying parameters other than the states are currently not
supported.
time_symbol : SymPy Symbol, optional
The symbol representating time in the equations of motion. If not
Expand Down Expand Up @@ -842,14 +849,20 @@ def determine_free_index(time_index, state):
node_map = {}
for func in self.instance_constraint_function_atoms:
if self._variable_duration:
msg = ('Instance constraint {} is not a correct integer '
'multiple of the time interval.')
try:
time_idx = int(func.args[0]/self.time_interval_symbol) - 1
except TypeError as err: # can't convert to integer
raise TypeError(msg.format(func)) from err
if func.args[0] == 0:
time_idx = 0
else:
try:
time_idx = int(func.args[0]/self.time_interval_symbol)
except TypeError as err: # can't convert to integer
msg = ('Instance constraint {} is not a correct '
'integer multiple of the time interval.')
raise TypeError(msg.format(func)) from err
if time_idx not in range(self.num_collocation_nodes):
raise ValueError(msg.format(func))
msg = ('Instance constraint {} gives an index of {} which '
'is not between 0 and {}.')
raise ValueError(msg.format(
func, time_idx, self.num_collocation_nodes - 1))
else:
time_value = func.args[0]
time_vector = np.linspace(0.0, duration, num=N)
Expand Down
29 changes: 15 additions & 14 deletions opty/tests/test_direct_collocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1161,12 +1161,13 @@ def setup_method(self):

# Additional node equality contraints.
theta, omega = sym.symbols('theta, omega', cls=sym.Function)
# If it is variable duration then use values 1 to N to specify instance
# constraints instead of time.
instance_constraints = (theta(1*h), # theta(t0)
theta(self.num_nodes*h) - sym.pi, # theta(tf)
omega(1*h), # omega(t0)
omega(self.num_nodes*h)) # omega(tf)
# If it is variable duration then use values 0 to N - 1 to specify
# instance constraints instead of time.
t0, tf = 0*h, (self.num_nodes - 1)*h
instance_constraints = (theta(t0),
theta(tf) - sym.pi,
omega(t0),
omega(tf))

self.collocator = ConstraintCollocator(
equations_of_motion=self.eom,
Expand Down Expand Up @@ -1239,10 +1240,10 @@ def test_identify_function_in_instance_constraints(self):

theta, omega = sym.symbols('theta, omega', cls=sym.Function)

expected = set((theta(1*self.interval_symbol),
theta(self.num_nodes*self.interval_symbol),
omega(1*self.interval_symbol),
omega(self.num_nodes*self.interval_symbol)))
expected = set((theta(0*self.interval_symbol),
theta((self.num_nodes - 1)*self.interval_symbol),
omega(0*self.interval_symbol),
omega((self.num_nodes - 1)*self.interval_symbol)))

assert self.collocator.instance_constraint_function_atoms == expected

Expand All @@ -1254,10 +1255,10 @@ def test_find_free_index(self):
self.collocator._find_closest_free_index()

expected = {
theta(1*self.interval_symbol): 0,
theta(self.num_nodes*self.interval_symbol): 3,
omega(1*self.interval_symbol): 4,
omega(self.num_nodes*self.interval_symbol): 7,
theta(0*self.interval_symbol): 0,
theta((self.num_nodes - 1)*self.interval_symbol): 3,
omega(0*self.interval_symbol): 4,
omega((self.num_nodes - 1)*self.interval_symbol): 7,
}

assert self.collocator.instance_constraints_free_index_map == expected
Expand Down

0 comments on commit f114166

Please sign in to comment.