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

Modifications and improvements to the base functionalities #9

Merged
merged 57 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
126bbb8
Added possibility to specify simple dynamics.
S-Dafarra Jul 17, 2023
4c7ef03
Avoid to add the integrators in the main namespace
S-Dafarra Jul 24, 2023
5d1f932
Added liecasadi and adam-robotics dependencies
S-Dafarra Jul 18, 2023
910d007
Updated gitignore to not consider VSCode files
S-Dafarra Jul 26, 2023
5100160
Stylistic improvements to multiple shooting solver
S-Dafarra Jul 27, 2023
720c5ee
Added functionality to create a symbolic structure in multiple shooti…
S-Dafarra Jul 28, 2023
1f5d12c
Removed top_level_index when flattening variables
S-Dafarra Jul 28, 2023
0c758a4
Output the symbolic structure when creating a ocp
S-Dafarra Jul 28, 2023
77d1cdb
Fixed style issues in dynamics.py
S-Dafarra Jul 28, 2023
aaa4d8f
Allowed specifying the dynamics using MX.sym
S-Dafarra Jul 28, 2023
36f3bac
Added possibility to add expression over horizon
S-Dafarra Jul 30, 2023
c7e07ab
Added possibility to set dt from cs.MX
S-Dafarra Jul 30, 2023
48792b5
Added test for add_expression_to_horizon
S-Dafarra Jul 30, 2023
2394f8f
Fixed some stylistic issues in opti_solver
S-Dafarra Aug 1, 2023
4ef9d42
The maximum number of step for an expression can be 1
S-Dafarra Aug 2, 2023
83d5d72
Added possibility to have scalar optimization objects
S-Dafarra Aug 4, 2023
58df128
Added default_composite_field function
S-Dafarra Aug 7, 2023
f35f94c
Added possibility to set initial conditions from symbolic structure
S-Dafarra Aug 7, 2023
2bae503
Added names to expressions
S-Dafarra Aug 8, 2023
9718953
Added one missing typehint in dynamics.py
S-Dafarra Aug 9, 2023
1253c74
Added definition of CompositeType
S-Dafarra Aug 9, 2023
5cbada9
Added utility function "initial" to retrieve the first element of a v…
S-Dafarra Aug 9, 2023
2c02b53
Added function to get the last element of a given variable
S-Dafarra Aug 9, 2023
943e2da
The initial guess is set automatically when setting the structure
S-Dafarra Aug 11, 2023
b3e4ddb
Added possibility to set casadi options to opti in planner
S-Dafarra Aug 11, 2023
36476ed
Allow changing the opti inner solver in the post_init
S-Dafarra Aug 16, 2023
2179034
Added idyntree as a dependency
S-Dafarra Aug 16, 2023
5b7d763
Avoiding setting the initial guess when calling generate_optimization…
S-Dafarra Aug 16, 2023
6ea8f58
Fix of extend_structure_to_horizon when the horizon is not specified
S-Dafarra Aug 16, 2023
7bd5b59
opti_solver converts list of floats to vectors automatically
S-Dafarra Aug 17, 2023
004570b
Fixed use of dt generator in multiple shooting solver
S-Dafarra Aug 17, 2023
671407a
Added OverridableParameter and OverridableVariable.
S-Dafarra Sep 12, 2023
740c12d
Added workaround for https://github.com/ami-iit/hippopt/issues/8
S-Dafarra Sep 12, 2023
ee06bbb
Use different workaround for python crash
S-Dafarra Sep 13, 2023
ca3ab20
Removed workaround for https://github.com/ami-iit/hippopt/issues/8
S-Dafarra Sep 22, 2023
65bc457
Allow to set guess also from Casadi.DM
S-Dafarra Oct 10, 2023
f33c056
Added message specifying which parameters have not been set
S-Dafarra Oct 10, 2023
d210764
Fixed setting of guesses for lists in aggregated variables.
S-Dafarra Oct 10, 2023
f79df9e
Catching exceptions when getting the solution from opti
S-Dafarra Oct 10, 2023
9e69233
Added use of logging in opti_solver
S-Dafarra Oct 10, 2023
dc908ea
Using OptiSolver instead of opti_solver for logging
S-Dafarra Oct 13, 2023
cc55dd4
Specified set of dependencies needed for visualizationr
S-Dafarra Oct 13, 2023
d682e1a
Improved type hinting of the Output in Problem
S-Dafarra Oct 16, 2023
7541c25
Avoid setting guess when the input shape is null
S-Dafarra Oct 16, 2023
4eae76c
Allow lists even when the input number is an integer
S-Dafarra Oct 17, 2023
addc7f7
Updated dependencies in CI
S-Dafarra Oct 18, 2023
c3b7fb0
Added matplotlib dependencyy
S-Dafarra Oct 30, 2023
857a640
Added possibility to transform an OptimizationObject to list and MX
S-Dafarra Nov 23, 2023
c592a3a
Improved error message in Opti solver
S-Dafarra Nov 29, 2023
7a6ea9c
Initial modifications to OptiSolver to have callbacks
S-Dafarra Jan 5, 2024
f82c88c
Initial implementation of opti_callback
S-Dafarra Jan 5, 2024
d72494d
Using weakref in opti callback and save also the cost values and dual…
S-Dafarra Jan 8, 2024
62ef421
Allowed combining callback criteria using or/and
S-Dafarra Jan 8, 2024
f50cec7
Added possibility to set callback in opti to have intermediate soluti…
S-Dafarra Jan 8, 2024
fd83021
Allow avoiding saving the cost values and constraint multipliers to s…
S-Dafarra Jan 9, 2024
a4e4c71
Saving parameters only once in the opti callback
S-Dafarra Jan 10, 2024
535ce01
Removed pinning of metis
S-Dafarra Jan 15, 2024
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
6 changes: 5 additions & 1 deletion .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ jobs:
with:
miniforge-variant: Mambaforge
miniforge-version: latest
channels: conda-forge,robotology
channel-priority: true

- name: Dependencies
shell: bash -l {0}
run: |
mamba install python=${{ matrix.python }} casadi pytest
mamba install python=${{ matrix.python }} casadi pytest liecasadi adam-robotics idyntree meshcat-python ffmpeg-python matplotlib
mamba install metis=5.1.0 # Reminder for https://github.com/conda-forge/conda-forge-pinning-feedstock/pull/4857#issuecomment-1699470569
S-Dafarra marked this conversation as resolved.
Show resolved Hide resolved
mamba list

- name: Install
shell: bash -l {0}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,6 @@ dmypy.json

# Pycharm files
.idea/

# VSCode files
.vscode/
13 changes: 13 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,22 @@ style =
isort
testing=
pytest
robot_planning=
liecasadi
adam-robotics
turnkey_planners=
idyntree
visualization=
ffmpeg-python
idyntree
meshcat-python
matplotlib
all =
%(style)s
%(testing)s
%(robot_planning)s
%(turnkey_planners)s
%(visualization)s

[options.packages.find]
where = src
20 changes: 12 additions & 8 deletions src/hippopt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
from . import base, integrators
import hippopt.base.opti_callback as opti_callback

from .base.dynamics import Dynamics, TypedDynamics, dot
from .base.multiple_shooting_solver import MultipleShootingSolver
from .base.opti_solver import OptiFailure, OptiSolver
from .base.optimal_control_problem import OptimalControlProblem
from .base.optimal_control_problem import (
OptimalControlProblem,
OptimalControlProblemInstance,
)
from .base.optimization_object import (
CompositeType,
OptimizationObject,
StorageType,
TimeExpansion,
TOptimizationObject,
default_composite_field,
default_storage_field,
default_storage_metadata,
time_varying_metadata,
)
from .base.optimization_problem import OptimizationProblem
from .base.optimization_problem import OptimizationProblem, OptimizationProblemInstance
from .base.optimization_solver import SolutionNotAvailableException
from .base.parameter import Parameter, TParameter
from .base.problem import ExpressionType, ProblemNotSolvedException
from .base.parameter import OverridableParameter, Parameter, TParameter
from .base.problem import ExpressionType, Output, ProblemNotSolvedException
from .base.single_step_integrator import (
SingleStepIntegrator,
TSingleStepIntegrator,
step,
)
from .base.variable import TVariable, Variable
from .integrators.forward_euler import ForwardEuler
from .integrators.implicit_trapezoid import ImplicitTrapezoid
from .base.variable import OverridableVariable, TVariable, Variable
1 change: 1 addition & 0 deletions src/hippopt/base/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from . import (
dynamics,
multiple_shooting_solver,
opti_callback,
opti_solver,
optimal_control_problem,
optimal_control_solver,
Expand Down
128 changes: 104 additions & 24 deletions src/hippopt/base/dynamics.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,45 @@

@dataclasses.dataclass
class DynamicsRHS:
_f: cs.Function = dataclasses.field(default=None)
_f: cs.Function | list[str] = dataclasses.field(default=None)
_names_map: dict[str, str] = dataclasses.field(default=None)
_names_map_inv: dict[str, str] = dataclasses.field(default=None)
f: dataclasses.InitVar[cs.Function] = None
f: dataclasses.InitVar[cs.Function | str | list[str] | cs.MX] = None
names_map_in: dataclasses.InitVar[dict[str, str]] = None

def __post_init__(self, f: cs.Function, names_map_in: dict[str, str]):
def __post_init__(
self, f: cs.Function | str | list[str] | cs.MX, names_map_in: dict[str, str]
):
"""
Create the DynamicsRHS object
:param f: The CasADi function describing the dynamics. The output order should match the list provided
in the dot function.
:param names_map_in: A dict describing how to switch from the input names to those used in the function.
The key is the name provided by the user, while the value is the input name expected by the function.
It is also possible to specify labels for nested variables using ".", e.g. "a.b" corresponds
to the variable "b" within "a".
:param f: The CasADi function describing the dynamics. The output order should
match the list provided in the dot function. As an alternative, if the
dynamics is trivial (e.g. dot(x) = y), it is possible to pass directly the name
of the variable in the right-hand-side, or the list of variables in case the
left-hand-side is a list.
:param names_map_in: A dict describing how to switch from the input names to
those used in the function.
The key is the name provided by the user, while the value is the input name
expected by the function.
Refer to the specific optimal control problem for a specification of the
label convention.
This is valid only for the keys.
If time is an input, its label needs to be provided using the "dot" function.
:return: Nothing
"""
self._f = f
if isinstance(f, str):
self._f = [f]
elif isinstance(f, list) or isinstance(f, cs.Function):
self._f = f
elif isinstance(f, cs.MX):
inputs = cs.symvar(f)
input_names = [el.name() for el in inputs]
self._f = cs.Function(
"dynamics_rhs", inputs, [f], input_names, ["dynamics_rhs_output"]
)
else:
raise ValueError("Unsupported input f")

self._names_map = names_map_in if names_map_in else {}
self._names_map_inv = {v: k for k, v in self._names_map.items()} # inverse dict

Expand All @@ -48,10 +67,19 @@ def evaluate(
key = name if name not in self._names_map else self._names_map[name]
input_dict[key] = variables[name]

if isinstance(self._f, list):
return input_dict

assert isinstance(self._f, cs.Function)
return self._f(**input_dict)

def input_names(self) -> list[str]:
function_inputs = self._f.name_in()
if isinstance(self._f, list):
function_inputs = self._f
else:
assert isinstance(self._f, cs.Function)
function_inputs = self._f.name_in()

output = []
for el in function_inputs:
output_name = self._names_map_inv[el] if el in self._names_map_inv else el
Expand All @@ -60,43 +88,95 @@ def input_names(self) -> list[str]:
return output

def outputs(self) -> list[str]:
if isinstance(self._f, list):
return self._f

assert isinstance(self._f, cs.Function)
return self._f.name_out()


@dataclasses.dataclass
class DynamicsLHS:
_x: list[str] = dataclasses.field(default=None)
x: dataclasses.InitVar[list[str] | str] = None
_t_label: str = "t"
t_label: dataclasses.InitVar[str] = None
_t_label: str | cs.MX = "t"
t_label: dataclasses.InitVar[str | cs.MX] = None

def __post_init__(self, x: list[str] | str, t_label: str):
def __post_init__(
self, x: str | list[str] | cs.MX | list[cs.MX], t_label: str | cs.MX = None
):
"""
Constructs the DynamicsLHS object
:param x: List of variable names on the left hand side of dot{x} = f(y).
The list can contain empty strings if some output of f needs to be discarded. If one output
needs to be mapped to a nested item, use "." as separator, e.g. "a.b"
:param t_label: The label of the time variable. Default "t"
The list can contain empty strings if some output of f needs to be discarded.
Refer to the specific optimal control problem for a specification of the
label convention.
The input can also be of type cs.MX. This allows using the symbolic
structure provided by the optimal control solver. The input cannot be an
expression. Also in the case, the input can be a list too, and can contain
None if some outputs of f have to be discarded.
:param t_label: The label of the time variable. Default "t". It can also be a
cs.MX. In this case, its name will be used
:return: Nothing
"""
self._x = x if isinstance(x, list) else [x]
self._t_label = t_label if isinstance(t_label, str) else "t"

def equal(self, f: cs.Function, names_map: dict[str, str] = None) -> TDynamics:
def input_to_string(
input_value: str | cs.MX, default_string: str = None
) -> str:
if isinstance(input_value, str):
return input_value

if input_value is None:
return ""

if not isinstance(input_value, cs.MX):
if default_string is not None:
return default_string

raise ValueError("The input can be only a string, a cs.MX, or None.")

if not input_value.is_symbolic():
raise ValueError("The input MX has to be symbolic.")
return input_value.name()

if isinstance(x, list):
self._x = [input_to_string(el) for el in x]
else:
self._x = [input_to_string(x)]

self._t_label = input_to_string(input_value=t_label, default_string="t")

def equal(
self, f: cs.Function | str | list[str] | cs.MX, names_map: dict[str, str] = None
) -> TDynamics:
rhs = DynamicsRHS(f=f, names_map_in=names_map)
if len(rhs.outputs()) != len(self._x):
raise ValueError(
"The number of outputs of the dynamics function does not match the specified number of state variables."
"The number of outputs of the dynamics function does not match"
" the specified number of state variables."
)
return TypedDynamics(lhs=self, rhs=rhs)

def __eq__(
self, other: cs.Function | tuple[cs.Function, dict[str, str]]
self,
other: cs.Function
| str
| list[str]
| cs.MX
| tuple[cs.Function, dict[str, str]]
| tuple[str, dict[str, str]]
| tuple[list[str], dict[str, str]]
| tuple[cs.MX, dict[str, str]],
) -> TDynamics:
if isinstance(other, tuple):
return self.equal(f=other[0], names_map=other[1])

assert isinstance(other, cs.Function)
assert (
isinstance(other, cs.Function)
or isinstance(other, str)
or isinstance(other, list)
or isinstance(other, cs.MX)
)
return self.equal(f=other)

def state_variables(self) -> list[str]:
Expand All @@ -106,7 +186,7 @@ def time_label(self) -> str:
return self._t_label


def dot(x: str | list[str], t: str = "t") -> TDynamicsLHS:
def dot(x: str | list[str] | cs.MX | list[cs.MX], t: str | cs.MX = "t") -> TDynamicsLHS:
return DynamicsLHS(x=x, t_label=t)


Expand Down
Loading
Loading