Skip to content

Commit

Permalink
Merge branch 'master' into car-in-garage-new
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter230655 committed Jan 28, 2025
2 parents 7ed8318 + e08a42d commit 90dcea1
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 7 deletions.
4 changes: 2 additions & 2 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
version: 2
build:
os: "ubuntu-22.04"
os: "ubuntu-24.04"
tools:
python: "mambaforge-22.9"
python: "miniconda-latest"
sphinx:
configuration: docs/conf.py
formats: all
Expand Down
5 changes: 3 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ The required dependencies are as follows:
- numpy >= 1.19.0
- python 3.9-3.13
- scipy >= 1.5.0
- setuptools
- sympy >= 1.6.0

The optional dependencies are as follows:
Expand Down Expand Up @@ -136,7 +137,7 @@ set the ``LD_LIBRARY_PATH`` so that you can link to Ipopt when installing

Once Ipopt is installed and accessible, install conda then create an environment::

$ conda create -n opty-custom -c conda-forge cython numpy pip scipy sympy
$ conda create -n opty-custom -c conda-forge cython numpy pip scipy setuptools sympy
$ source activate opty-custom
(opty-custom)$ pip install cyipopt # this will compile cyipopt against the available ipopt
(opty-custom)$ pip install opty
Expand All @@ -145,7 +146,7 @@ If you want to develop opty, create a conda environment with all of the
dependencies installed::

$ conda config --add channels conda-forge
$ conda create -n opty-dev python sympy numpy scipy cython ipopt cyipopt matplotlib pytables pydy pandas pytest sphinx sphinx-gallery numpydoc
$ conda create -n opty-dev python sympy numpy scipy cython ipopt cyipopt matplotlib pytables pydy pandas pytest setuptools sphinx sphinx-gallery numpydoc
$ source activate opty-dev

Next download the opty source files and install with::
Expand Down
1 change: 1 addition & 0 deletions opty-dev-env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies:
- pytest-cov
- python
- scipy >=1.5.0
- setuptools
- sphinx
- sphinx-gallery
- sympy >=1.13 # version required for the docs
123 changes: 121 additions & 2 deletions opty/direct_collocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ class Problem(cyipopt.Problem):
eomn2, ... eomnN,
c1, ..., co]
The attributes may be accessed as follows: ``Problem_instance.collocator.name_of_attribute``
"""

INF = 10e19
Expand Down Expand Up @@ -340,7 +342,7 @@ def jacobian(self, free):
Returns
=======
jac_vals : ndarray, shape((2*n + q + r + s)*(n*(N - 1) + o), )
jac_vals : ndarray, shape((2*n + q + r + s)*(n*(N - 1)) + o, )
Non-zero Jacobian values in triplet format.
"""
Expand Down Expand Up @@ -597,13 +599,126 @@ def plot_objective_value(self):

return ax

def parse_free(self, free):
"""Parses the free parameters vector and returns it's components.
Parameters
==========
free : ndarray, shape(n*N + q*N + r + s)
The free parameters of the system.
Returns
=======
states : ndarray, shape(n, N)
The array of n states through N time steps.
specified_values : ndarray, shape(q, N) or shape(N,), or None
The array of q specified inputs through N time steps.
constant_values : ndarray, shape(r,)
The array of r constants.
time_interval : float
The time between collocation nodes. Only returned if
``variable_duration`` is ``True``.
Notes
=====
- N : number of collocation nodes
- n : number of unknown state trajectories
- q : number of unknown input trajectories
- r : number of unknown parameters
- s : number of unknown time intervals (s=1 if ``variable duration`` is
``True`` else s=0)
"""

n = self.collocator.num_states
N = self.collocator.num_collocation_nodes
q = self.collocator.num_unknown_input_trajectories
variable_duration = self.collocator._variable_duration

return parse_free(free, n, q, N, variable_duration)

class ConstraintCollocator(object):
"""This class is responsible for generating the constraint function and the
sparse Jacobian of the constraint function using direct collocation methods
for a non-linear programming problem where the essential constraints are
defined from the equations of motion of the system.
Attributes
==========
current_discrete_state_symbols : n-tuple
The symbols for the current discrete states.
current_discrete_specified_symbols : q-tuple
The symbols for the current discrete specified inputs.
discrete_eom : sympy.Matrix, shape(n, 1)
Discretized equations of motion. Depending on the integration method
used.
eom: sympy.Matrix, shape(n, 1)
The equations of motion used.
input_trajectories : tuple
known_input_trajectories + unknown_input_trajectories.
instance_constraints : o-tuple
The instance constraints used in the optimization.
integration_method : str
The integration method used.
known_parameters : tuple
The symbols of the known parameters in the problem.
known_parameter_map : dict
A mapping of known parameters to their values.
known_trajectory_map : dict
A mapping of known trajectories to their values.
known_trajectory_symbols : (m-q)-tuple
The known trajectory symbols.
next_discrete_specified_symbols : q-tuple
The symbols for the next discrete specified inputs.
next_discrete_state_symbols : n-tuple
The symbols for the next discrete states.
node_time_interval : float or sympy.Symbol
The time interval between the collocation nodes. float if the interval
is fixed, ``sympy.Symbol`` if the interval is variable.
num_collocation_nodes : int
Number of times spaced evenly between the initial and final time of
the optimization = N.
num_constraints : int
The number of constraints = (num_collection_nodes-1)*num_states +
len(instance_constraints).
num_free : int
Number of variables to be optimized = n*N + q*N + r + s.
num_input_trajectories : int
The number of input trajectories = len(input_trajectories).
num_instance_constraints : int
The number of instance constraints = len(instance_constraints).
num_known_trajectories : int
The number of known trajectories = len(known_trajectory_symbols).
num_parameters : int
The number of parameters = len(parameters).
num_states : int
The number of states = len(state_symbols) = n.
num_unknown_input_trajectories : int
The number of unknown input trajectories =
len(unknown_input_trajectories).
num_unknown_parameters : int
The number of unknown parameters = r.
parameters : tuple
known_parameters + unknown_parameters.
parallel : bool
Whether to use parallel processing or not.
previous_discrete_state_symbols : n-tuple
The symbols for the previous discrete states.
show_compile_output : bool
Whether to show the compile output or not.
state_derivative_symbols : n-tuple
symbols for the time derivatives of the states.
time_symbol : sympy.Symbol
The symbol used to represent time, usually `t`.
tmp_dir
The temporary directory used to store files generated.
unknown_input_trajectories : q-tuple
The unknown input trajectories symbols.
unknown_parameters : r-tuple
The unknown parameters in the problem, in the sequence in which they
appear in the solution of the optimization.
Notes
=====
Expand All @@ -618,8 +733,12 @@ class ConstraintCollocator(object):
- nN + qN + r + s : number of free variables
- n(N - 1) + o : number of constraints
"""
Some of the attributes are explained in more detail under Parameters below.
It is best to treat ``ConstraintCollocator`` as immutable, changing
attributes after initialization will inevitably fail.
"""
def __init__(self, equations_of_motion, state_symbols,
num_collocation_nodes, node_time_interval,
known_parameter_map={}, known_trajectory_map={},
Expand Down
123 changes: 122 additions & 1 deletion opty/tests/test_direct_collocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pytest import raises

from ..direct_collocation import Problem, ConstraintCollocator
from ..utils import create_objective_function, sort_sympy
from ..utils import create_objective_function, sort_sympy, parse_free


def test_pendulum():
Expand Down Expand Up @@ -1566,3 +1566,124 @@ def test_for_algebraic_eoms():
)

assert excinfo.type is ValueError


def test_prob_parse_free():
"""
Test for parse_free method of Problem class.
===========================================
This test whether the parse_free method of the Problem class works as
the parse_free in utils.
**States**
- :math:`x_1, x_2, ux_1, ux_2` : state variables
**Control**
- :math:`u_1, u_2` : control variable
"""

t = mech.dynamicsymbols._t

x1, x2, ux1, ux2 = mech.dynamicsymbols('x1 x2 ux1 ux2')
u1, u2 = mech.dynamicsymbols('u1 u2')
h = sym.symbols('h')
a, b = sym.symbols('a b')

# equations of motion.
# (No meaning, just for testing)
eom = sym.Matrix([
-x1.diff(t) + ux1,
-x2.diff(t) + ux2,
-ux1.diff(t) + a*u1,
-ux2.diff(t) + b*u2,
])

# Set up and Solve the Optimization Problem
num_nodes = 11
t0, tf = 0.0, 0.9
state_symbols = (x1, x2, ux1, ux2)
control_symbols = (u1, u2)

interval_value = (tf - t0)/(num_nodes - 1)
times = np.linspace(t0, tf, num_nodes)

# Specify the symbolic instance constraints, as per the example.
instance_constraints = (
x1.func(t0) - 1.0,
x2.func(t0) + 1.0,
)

# Specify the objective function and form the gradient.

def obj(free):
return sum([free[i]**2 for i in range(2*num_nodes)])

def obj_grad(free):
grad = np.zeros_like(free)
grad[:2*num_nodes] = 2*free[:2*num_nodes]
return grad

# Create the optimization problem and set any options.
prob = Problem(
obj,
obj_grad,
eom,
state_symbols,
num_nodes,
interval_value,
instance_constraints=instance_constraints,
)

# Give some estimates for the trajectory.
initial_guess = np.random.rand(prob.num_free)
initial_guess1 = initial_guess

# check whether same results.
statesu, controlsu, constantsu = parse_free(initial_guess1,
len(state_symbols), len(control_symbols), num_nodes)

states, controls, constants = prob.parse_free(initial_guess)
np.testing.assert_allclose(states, statesu)
np.testing.assert_allclose(controls, controlsu)
np.testing.assert_allclose(constants, constantsu)

# test with variable interval_value
interval_value = h
t0, tf = 0.0, (num_nodes - 1)*interval_value
def obj(free):
return sum([free[i]**2 for i in range(2*num_nodes)])

def obj_grad(free):
grad = np.zeros_like(free)
grad[:2*num_nodes] = 2*free[:2*num_nodes]
return grad

# Create the optimization problem and set any options.
prob = Problem(
obj,
obj_grad,
eom,
state_symbols,
num_nodes,
interval_value,
instance_constraints=instance_constraints,
)

# Give some estimates for the trajectory.
initial_guess = np.random.rand(prob.num_free)
initial_guess1 = initial_guess

# check whether same results.
statesu, controlsu, constantsu, timeu = parse_free(initial_guess1,
len(state_symbols), len(control_symbols),
num_nodes, variable_duration=True)

states, controls, constants, times = prob.parse_free(initial_guess)
np.testing.assert_allclose(states, statesu)
np.testing.assert_allclose(controls, controlsu)
np.testing.assert_allclose(constants, constantsu)
np.testing.assert_allclose(timeu, times)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
'cython>=0.29.19',
'numpy>=1.19.0',
'scipy>=1.5.0',
'setuptools', # provides distutils for Python >=3.13
'sympy>=1.6.0',
],
extras_require={
Expand Down

0 comments on commit 90dcea1

Please sign in to comment.