Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2a44e47
Added log barrier cost and old factory
ttchalakov Jun 21, 2022
8bf8213
Added log barrier cost and old factory
ttchalakov Jun 21, 2022
f0b2946
Merge remote-tracking branch 'origin/soft_water_robot_devel' into sof…
dohunnim Jun 21, 2022
8994cc4
fix_hyperparameter error fixed
dohunnim Jul 19, 2022
1b932a6
minor bug fix in bounds transformer and controller
dohunnim Jul 20, 2022
8e6cfda
fix_hyperparameter error fix
dohunnim Jul 20, 2022
d616734
Changing cost factory to transformer in Controller_Tuning example
dohunnim Jul 20, 2022
a796e51
get_traj implemented for RoundedOptimizer
dohunnim Jul 22, 2022
dfaf603
Fix string formatting bug
dohunnim Jul 22, 2022
1f6dae0
Fix bug converting namedtuple to dict
dohunnim Jul 22, 2022
9fe51a2
Merge remote-tracking branch 'origin/soft_water_robot_devel' into sof…
dohunnim Jul 22, 2022
ab9e925
Minor bug fixes
dohunnim Jul 27, 2022
036fb72
Cost minor syntax error fixed. ThresholdCost goal convention fixed
dohunnim Aug 3, 2022
c5cc148
Barrier Cost changed from factory to transformer
dohunnim Aug 3, 2022
a7b57e0
Barrier cost transformer added to __init__.py
dohunnim Aug 3, 2022
ef2a93b
ILQR stop gap solution
dohunnim Aug 3, 2022
a61459c
Barrier Cost lower bound fixed
dohunnim Aug 8, 2022
153861a
Fixed goal setter syntax
dohunnim Aug 8, 2022
50a19d2
Barrier Cost Transformer tunable
dohunnim Aug 8, 2022
b4b09ec
Controls weren't clipping when multi-stepping
dohunnim Aug 8, 2022
a7b2bcf
REVISIT THIS: Solution to fix hyperparameters in twostage tuning?
dohunnim Aug 8, 2022
6597981
Twostage tuning fixes
dohunnim Aug 8, 2022
7d71418
Changing default metric to ControlPerformanceMetric()
dohunnim Aug 8, 2022
8f4787b
New Barrier Transformer API
dohunnim Aug 8, 2022
d1fd354
Costs fixed to work with barriers
dohunnim Aug 9, 2022
9bf63e4
Parallel Control Evaluator
dohunnim Aug 9, 2022
6b2c4ab
BarrierCostTransformer works directly with Configs and not separate dict
dohunnim Aug 12, 2022
d89aa64
Added ability to change default_value and log when setting hyper_bounds
dohunnim Aug 12, 2022
e339ef2
change hyperparameter log scale setting
dohunnim Aug 12, 2022
aabc895
max_iter fixed in stop gap solution
dohunnim Aug 14, 2022
c1f8d33
Auto stash before merge of "soft_water_robot_devel" and "origin/soft_…
dohunnim Aug 14, 2022
49301f1
ILQR faster again, saves step duration simulation
dohunnim Aug 16, 2022
5410432
Fix conversion of ControlEvaluationTrial to dict
dohunnim Aug 16, 2022
93595f9
Merge of "soft_water_robot_devel" and "origin/soft_water_robot_devel"
dohunnim Aug 16, 2022
905ef84
Timeout feature added to ILQR
dohunnim Aug 17, 2022
825209c
Merge of "soft_water_robot_devel" and "origin/soft_water_robot_devel"
dohunnim Aug 17, 2022
1149260
Removing debugging stuff
dohunnim Aug 18, 2022
17b4403
Remove unrolling w * since it's not compatible with python <3.8
dohunnim Aug 18, 2022
3977ba5
preparing 0.2-dev pull request
dohunnim Aug 18, 2022
a3e2f14
OCP needs to be transformed when being set
dohunnim Aug 18, 2022
40bff3f
Fixing ParallelEvaluator
dohunnim Aug 19, 2022
50850b6
Minor change to Barrier Cost Transformer
dohunnim Aug 19, 2022
9cad9a1
Timeout enabled
dohunnim Aug 22, 2022
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
9 changes: 9 additions & 0 deletions autompc/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ def set_ocp(self, ocp):
"""
self.ocp = ocp

if len(self.ocp_transformers) == 1:
self.ocp_transformer = self.ocp_transformers[0]
if self.ocp_transformer:
self.transformed_ocp = self.ocp_transformer(self.ocp)
else:
Expand Down Expand Up @@ -453,6 +455,12 @@ def build(self, trajs : List[Trajectory] = None) -> None:
self.ocp_transformer.train(trajs)
else:
raise ControllerStateError("Specified OCP transformer requires learning from trajectories.")

if self.ocp_transformer:
self.transformed_ocp = self.ocp_transformer(self.ocp)
else:
self.transformed_ocp = self.ocp
self.optimizer.set_ocp(self.transformed_ocp)

self.reset()

Expand Down Expand Up @@ -481,6 +489,7 @@ def reset_history(self) -> None:
from influencing current model predictions.
"""
self.model_state = None
self.last_control = None

def reset_optimizer(self) -> None:
"""
Expand Down
1 change: 1 addition & 0 deletions autompc/costs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .quad_cost import QuadCost
from .thresh_cost import ThresholdCost, BoxThresholdCost
from .barrier_cost import LogBarrierCost
from .cost import Cost
187 changes: 187 additions & 0 deletions autompc/costs/barrier_cost.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# Created by Teodor Tchalakov, (ttcha2@illinois.edu)

import numpy as np
import numpy.linalg as la

from .cost import Cost

class LogBarrierCost(Cost):
def __init__(self, system, boundedStates):
"""
Create barrier cost that approximates an inequality constraint.
Function does not exist outside the limit.
where : - b * ln ( a - x ) for upper limit
- b * ln ( a + x ) for lower limit
Parameters
----------
system : System
Robot system object.
boundedState : dict
Dictionary of { "observation/control name" : (limit, scale, upper)}
observation/control (x) : String
Observation/control name for which limit is specified.
limit (a) : double
limit value a that barrier is placed at.
scale (b) : double
Positive scalar to magnify the cost function.
scale: (0, inf)
upper : boolean
True if the limit is an upper limit.
False if the limit is a lower limit.
"""
super().__init__(system)
self.obsConfiguration = []
self.ctrlsConfiguration = []

for variable in boundedStates.keys():
config = boundedStates[variable]
# Check that scale is positive
if(config[1] < 0):
raise ValueError(f"{variable}'s log barrier must be positive, was {config[1]}")
elif(variable in system.observations):
self.obsConfiguration.append([variable, config])
elif(variable in system.controls):
self.ctrlsConfiguration.append([variable, config])
else:
raise ValueError(f"Variable {variable} is not in the given system")

# Configs
self._is_quad = False
self._is_convex = True
self._is_diff = True
self._is_twice_diff = True
self._has_goal = False

def incremental(self, obs, control):
return self.eval_obs_cost(obs) + self.eval_ctrl_cost(control)

def incremental_diff(self, obs, control):
return self.incremental(obs, control), self.eval_obs_cost_diff(obs), self.eval_ctrl_cost_diff(control)

def incremental_hess(self, obs, control): # TODO: Tuple unpacking only supported for python>=3.8
hess_obs_ctrl = np.zeros((self.system.obs_dim, self.system.ctrl_dim))
return self.incremental(obs, control), self.eval_obs_cost_diff(obs), self.eval_ctrl_cost_diff(control), self.eval_obs_cost_hess(obs), hess_obs_ctrl, self.eval_ctrl_cost_hess(control)

def terminal(self, obs):
return 0

def terminal_diff(self, obs):
return 0, 0

def terminal_hess(self, obs):
return 0, 0, 0

def __add__(self, rhs):
if isinstance(rhs, LogBarrierCost):
if (self.goal is None and rhs.goal is None) or np.all(self.goal == rhs.goal):
return LogBarrierCost(self.system, self.boundedStates+rhs.boundedStates)
return Cost.__add__(self, rhs)

def __mul__(self, rhs):
if not isinstance(rhs, (float, int)):
raise ValueError("* only supports product with numbers")
new_cost = LogBarrierCost(self.system, self.boundedStates)
return new_cost


#Cost Function:
# b = scale
# - b * ln ( a - x ) upper limit x < a
# - b * ln ( a + x ) lower limit x > a
def eval_obs_cost(self, obs):
sum = 0
for boundedObs in self.obsConfiguration:
variable, config = boundedObs
index = self.system.observations.index(variable)
lower, upper, scale = config
if lower > -np.inf:
if lower >= obs[index]:
sum += np.inf
else:
sum = sum + -scale * np.log(-lower + obs[index])
if upper < np.inf:
if obs[index] >= upper:
sum += np.inf
else:
sum = sum + -scale * np.log(upper - obs[index])
return sum

#Jacobian:
# b / (a - x) upper limit
# -b / (-a + x) lower limit
def eval_obs_cost_diff(self, obs):
jacobian = np.zeros(self.system.obs_dim)
for boundedObs in self.obsConfiguration:
variable, config = boundedObs
index = self.system.observations.index(variable)
lower, upper, scale = config
if lower > -np.inf:
if lower >= obs[index]:
jacobian[index] += -np.inf
else:
jacobian[index] += -scale / (-lower + obs[index])
if upper < np.inf:
if obs[index] >= upper:
jacobian[index] += np.inf
else:
jacobian[index] += scale / (upper - obs[index])

return jacobian

#Hessian:
# b / (a - x)^2 upper limit
# b / (-a + x)^2 lower limit
def eval_obs_cost_hess(self, obs):
hessian = np.zeros((self.system.obs_dim, self.system.obs_dim))
for boundedObs in self.obsConfiguration:
variable, config = boundedObs
index = self.system.observations.index(variable)
lower, upper, scale = config
if lower > -np.inf:
if lower >= obs[index]:
hessian[index][index] += np.inf
else:
hessian[index][index] += scale / ((lower - obs[index])**2)
if upper < np.inf:
if obs[index] >= upper:
hessian[index][index] += np.inf
else:
hessian[index][index] += scale / ((upper - obs[index])**2)

return hessian

def eval_ctrl_cost(self, ctrl):
sum = 0
for boundedCtrl in self.ctrlsConfiguration:
variable, config = boundedCtrl
index = self.system.controls.index(variable)
lower, upper, scale = config
if lower > -np.inf:
sum = sum + -scale * np.log(-lower + ctrl[index])
if upper < np.inf:
sum = sum + -scale * np.log(upper - ctrl[index])
return sum

def eval_ctrl_cost_diff(self, ctrl):
jacobian = np.zeros(self.system.ctrl_dim)
for boundedCtrl in self.ctrlsConfiguration:
variable, config = boundedCtrl
index = self.system.controls.index(variable)
lower, upper, scale = config
if lower > -np.inf:
jacobian[index] += -scale / (-lower + ctrl[index])
if upper < np.inf:
jacobian[index] += scale / (upper - ctrl[index])
return jacobian

def eval_ctrl_cost_hess(self, ctrl):
hessian = np.zeros((self.system.ctrl_dim, self.system.ctrl_dim))
for boundedCtrl in self.ctrlsConfiguration:
variable, config = boundedCtrl
index = self.system.controls.index(variable)
lower, upper, scale = config
if lower > -np.inf:
hessian[index][index] += scale / ((lower - ctrl[index])**2)
if upper < np.inf:
hessian[index][index] += scale / ((upper - ctrl[index])**2)
return hessian
23 changes: 20 additions & 3 deletions autompc/costs/cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ def goal(self) -> Optional[np.ndarray]:
return np.copy(self.properties['goal'])

@goal.setter
def goal(self, goal):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To use @goal.setter property decorator , the function name must be the same as the attribute's name. To be consistent with the coding style of other files, I kept the original Java-style setter function in lines 195-197

"""Sets the cost's goal state. (Note: not all costs actually act to
drive the system toward a goal).
"""
self.properties['goal'] = np.copy(goal)

def set_goal(self,goal):
"""Sets the cost's goal state. (Note: not all costs actually act to
drive the system toward a goal).
Expand All @@ -201,12 +207,12 @@ def __add__(self, other):
def __mul__(self, rhs):
if not isinstance(rhs,(int,float)):
raise ValueError("Can only multiply by a float")
return MulCost(self.system, [self, rhs])
return MulCost(self.system, self, rhs)

def __rmul__(self, lhs):
if not isinstance(lhs,(int,float)):
raise ValueError("Can only multiply by a float")
return MulCost(self.system, [self, lhs])
return MulCost(self.system, self, lhs)


class SumCost(Cost):
Expand Down Expand Up @@ -279,6 +285,11 @@ def goal(self):
return super().goal

@goal.setter
def goal(self, goal):
super().goal=goal
for cost in self.costs:
cost.goal = goal

def set_goal(self,goal):
super().set_goal(goal)
for cost in self.costs:
Expand Down Expand Up @@ -354,9 +365,15 @@ def goal(self):
return super().goal

@goal.setter
def goal(self, goal):
super().goal=goal
for cost in self.costs:
cost.goal = goal

def set_goal(self,goal):
super().set_goal(goal)
self._cost.goal = goal
for cost in self.costs:
cost.goal = goal

def __mul__(self, scale):
if not isinstance(scale,(float,int)):
Expand Down
3 changes: 2 additions & 1 deletion autompc/costs/quad_cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ def incremental_hess(self, obs, control):
obst = obs
QQt = (self._Q + self._Q.T)
RRt = (self._R + self._R)
return obst.T @ self._Q @ obst + control.T @ self._R @control, QQt @ obst, RRt @ control, QQt, None, RRt
hess_obs_ctrl = np.zeros((self.system.obs_dim, self.system.ctrl_dim))
return obst.T @ self._Q @ obst + control.T @ self._R @control, QQt @ obst, RRt @ control, QQt, hess_obs_ctrl, RRt
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning None will create issues


def terminal(self, obs):
try:
Expand Down
33 changes: 13 additions & 20 deletions autompc/costs/thresh_cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,21 @@ def __init__(self, system, goal, threshold, obs_range=None, observations=None):
"""
Create threshold cost. Returns 1 for every time steps
where :math:`||x - x_\\textrm{goal}||_\\infty > \\textrm{threshold}`.
The check is performed only over the observation dimensions from
obs_range[0] to obs_range[1].


The norm is performed only over the observation dimensions from
obs_range[0] to obs_range[1], or the observations named in
`observations`.
Parameters
----------
system : System
Robot system object

goal : Numpy array
Goal position

Goal position. Can either be length system.obs_dim or
# of observations in
obs_range : (int, int)
First (inclusive and last (exclusive) index of observations
for which goal is specified. If neither this field nor
observations is set, default is full observation range.

observations : [str]
List of observation names for which goal is specified.
Supersedes obs_range when present.
Expand All @@ -39,18 +38,15 @@ def __init__(self, system, goal, threshold, obs_range=None, observations=None):
self._obs_idxs = [system.observations.index(obs) for obs in observations]
if self._obs_idxs is None:
self._obs_idxs = list(range(0, system.obs_dim))
self.set_goal(goal)

def set_goal(self, goal):
if len(goal) < self.system.obs_dim:
self._goal = np.zeros(self.system.obs_dim)
self._goal[self._obs_idxs] = goal
else:
self._goal = np.copy(goal)
full_goal = np.zeros(self.system.obs_dim)
full_goal[self._obs_idxs] = goal
goal = full_goal
self.set_goal(goal)

def incremental(self, obs, ctrl):
if (la.norm(obs[self._obs_idxs] - self._goal[self._obs_idxs], np.inf)
> self._threshold):
max_dist_to_goal = la.norm(obs[self._obs_idxs] - self.goal[self._obs_idxs], np.inf)
if (max_dist_to_goal > self._threshold or np.isnan(max_dist_to_goal)):
return 1.0
else:
return 0.0
Expand All @@ -64,16 +60,13 @@ def __init__(self, system, limits, goal=None):
"""
Create Box threshold cost. Returns 1 for every time steps
where observation is outisde of limits.

Paramters
---------
system : System
System cost is computed for

limits : numpy array of shape (system.obs_dim, 2)
Upper and lower limits. Use +np.inf or -np.inf
to allow certain dimensions unbounded.

goal : numpy array of size system.obs_dim
Goal state. Not used directly for computing cost, but
may be used by downstream cost factories.
Expand All @@ -91,4 +84,4 @@ def incremental(self, obs, ctrl):
return 0.0

def terminal(self, obs):
return 0.0
return 0.0
4 changes: 2 additions & 2 deletions autompc/costs/zero_cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def incremental_diff(self, obs, ctrl):
return 0.0,np.zeros(len(obs)),np.zeros(len(ctrl))

def incremental_hess(self, obs, ctrl):
return 0.0,np.zeros(len(obs)),np.zeros(len(ctrl)),np.zeros(len(obs),len(obs)),None,np.zeros(len(ctrl),len(ctrl))
return 0.0,np.zeros(len(obs)),np.zeros(len(ctrl)),np.zeros((len(obs),len(obs))),np.zeros((len(obs),len(ctrl))),np.zeros((len(ctrl),len(ctrl)))

def terminal(self, obs) -> float:
return 0.0
Expand All @@ -29,5 +29,5 @@ def terminal_diff(self, obs):
return 0.0,np.zeros(len(obs))

def terminal_hess(self, obs):
return 0.0,np.zeros(len(obs)),np.zeros(len(obs),len(obs))
return 0.0,np.zeros(len(obs)),np.zeros((len(obs),len(obs)))

1 change: 1 addition & 0 deletions autompc/ocp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
from .quad_cost_transformer import QuadCostTransformer
from .gauss_reg_transformer import GaussRegTransformer
from .bounds_transformer import KeepBoundsTransformer,DeleteBoundsTransformer
from .barrier_cost_transformer import LogBarrierCostTransformer
Loading