|
| 1 | +import typing as tp |
1 | 2 | from abc import ABC, abstractmethod |
2 | 3 |
|
| 4 | +import crocoddyl |
3 | 5 | import numpy as np |
| 6 | +import pinocchio as pin |
4 | 7 | from simple_pid import PID |
5 | 8 |
|
6 | 9 |
|
7 | 10 | class Controller(ABC): |
8 | 11 | @abstractmethod |
9 | | - def compute_command(self, position: float): |
| 12 | + def compute_command(self, state): |
10 | 13 | pass |
11 | 14 |
|
12 | | - @staticmethod |
13 | | - def build_controller(parameters: dict): |
14 | | - controller_type = parameters["controller_type"] |
15 | | - # match controller_type: |
16 | | - # case "PIDController": |
17 | | - # return PIDController(parameters) |
18 | | - # case _: |
19 | | - # raise ValueError(f"Invalid controller type: {controller_type}") |
20 | | - if controller_type == "PIDController": |
21 | | - return PIDController(parameters) |
22 | | - else: |
23 | | - raise ValueError(f"Invalid controller type: {controller_type}") |
24 | | - |
25 | 15 |
|
26 | 16 | class PIDController(Controller): |
27 | | - def __init__(self, parameters): |
28 | | - try: |
29 | | - sample_time = 1 / parameters["control_frequency"] |
30 | | - except KeyError: |
31 | | - sample_time = None |
32 | | - |
33 | | - try: |
34 | | - self.pid = PID( |
35 | | - Kp=parameters["Kp"], |
36 | | - Ki=parameters["Ki"], |
37 | | - Kd=parameters["Kd"], |
38 | | - setpoint=np.deg2rad(parameters["setpoint"]), |
39 | | - sample_time=sample_time, |
40 | | - ) |
41 | | - except KeyError: |
42 | | - raise ValueError("Invalid PID parameters") |
| 17 | + def __init__( |
| 18 | + self, |
| 19 | + dt: float = None, |
| 20 | + Kp: float = 0.0, |
| 21 | + Ki: float = 0.0, |
| 22 | + Kd: float = 0.0, |
| 23 | + setpoint: float = 0.0, |
| 24 | + ): |
| 25 | + |
| 26 | + self.pid = PID( |
| 27 | + Kp=Kp, |
| 28 | + Ki=Ki, |
| 29 | + Kd=Kd, |
| 30 | + setpoint=setpoint, |
| 31 | + sample_time=dt, |
| 32 | + ) |
43 | 33 |
|
44 | 34 | def compute_command(self, position: float): |
45 | 35 | return self.pid(position) |
| 36 | + |
| 37 | + |
| 38 | +class SwingUpController(Controller): |
| 39 | + class FurutaActuationModel(crocoddyl.ActuationModelAbstract): |
| 40 | + def __init__(self, state, nu): |
| 41 | + crocoddyl.ActuationModelAbstract.__init__(self, state, nu=nu) |
| 42 | + |
| 43 | + def calc(self, data, x, u): |
| 44 | + assert len(data.tau) == 2 |
| 45 | + # Map the control dimensions to the joint torque |
| 46 | + data.tau[0] = u |
| 47 | + data.tau[1] = 0 # Underactuated joint |
| 48 | + |
| 49 | + def calcDiff(self, data, x, u): |
| 50 | + # Specify the actuation jacobian |
| 51 | + data.dtau_du[0] = 1 |
| 52 | + data.dtau_du[1] = 0 |
| 53 | + |
| 54 | + def __init__( |
| 55 | + self, |
| 56 | + robot: pin.RobotWrapper, |
| 57 | + x_target: np.ndarray, |
| 58 | + control_freq: float = 100.0, |
| 59 | + t_final: float = 0.5, |
| 60 | + u_lim: float = 0.1, |
| 61 | + Q: np.ndarray = np.array([10, 50, 1, 1]), |
| 62 | + R: np.ndarray = np.array([0.1]), |
| 63 | + S: np.ndarray = np.array([1.0]), |
| 64 | + M: int = 10, |
| 65 | + ): |
| 66 | + self.u_lim = u_lim |
| 67 | + |
| 68 | + # Time variables |
| 69 | + dt = 1 / control_freq # Time step |
| 70 | + self.N = int(t_final * control_freq) |
| 71 | + |
| 72 | + # Instantiate robot as a pinocchio RobotWrapper |
| 73 | + self.robot = robot |
| 74 | + |
| 75 | + # State |
| 76 | + state = crocoddyl.StateMultibody(robot.model) |
| 77 | + |
| 78 | + # Actuation model |
| 79 | + nu = 1 |
| 80 | + actuation = self.FurutaActuationModel(state, nu) |
| 81 | + |
| 82 | + # State Cost |
| 83 | + state_residual = crocoddyl.ResidualModelState(state, xref=x_target, nu=nu) |
| 84 | + state_residual_activation = crocoddyl.ActivationModelWeightedQuad(Q) |
| 85 | + state_cost = crocoddyl.CostModelResidual(state, state_residual_activation, state_residual) |
| 86 | + |
| 87 | + # Control Cost |
| 88 | + control_residual = crocoddyl.ResidualModelControl(state, nu=nu) |
| 89 | + control_residual_activation = crocoddyl.ActivationModelWeightedQuad(R) |
| 90 | + control_cost = crocoddyl.CostModelResidual( |
| 91 | + state, control_residual_activation, control_residual |
| 92 | + ) |
| 93 | + |
| 94 | + # Control rate cost |
| 95 | + self.control_rate_residual = crocoddyl.ResidualModelControl(state, uref=np.array([0.0])) |
| 96 | + control_rate_residual_activation = crocoddyl.ActivationModelWeightedQuad(S) |
| 97 | + control_rate_cost = crocoddyl.CostModelResidual( |
| 98 | + state, control_rate_residual_activation, self.control_rate_residual |
| 99 | + ) |
| 100 | + |
| 101 | + self.running_models = [] |
| 102 | + for k in range(self.N): |
| 103 | + running_cost = crocoddyl.CostModelSum(state, nu=nu) |
| 104 | + running_cost.addCost("state_cost", cost=state_cost, weight=1.0) |
| 105 | + running_cost.addCost("control_cost", cost=control_cost, weight=1.0) |
| 106 | + running_cost.addCost("control_rate_cost", cost=control_rate_cost, weight=np.exp(M - k)) |
| 107 | + |
| 108 | + running_model = crocoddyl.IntegratedActionModelEuler( |
| 109 | + crocoddyl.DifferentialActionModelFreeFwdDynamics(state, actuation, running_cost), |
| 110 | + dt, |
| 111 | + ) |
| 112 | + self.running_models.append(running_model) |
| 113 | + |
| 114 | + # Terminal cost |
| 115 | + terminal_cost = crocoddyl.CostModelSum(state, nu=nu) |
| 116 | + terminal_cost.addCost("state_cost", cost=state_cost, weight=self.N) |
| 117 | + |
| 118 | + self.terminal_model = crocoddyl.IntegratedActionModelEuler( |
| 119 | + crocoddyl.DifferentialActionModelFreeFwdDynamics(state, actuation, terminal_cost), |
| 120 | + 0.0, |
| 121 | + ) |
| 122 | + |
| 123 | + def create_problem(self, state: np.ndarray): |
| 124 | + self.problem = crocoddyl.ShootingProblem(state, self.running_models, self.terminal_model) |
| 125 | + |
| 126 | + def init_solver(self): |
| 127 | + self.solver = crocoddyl.SolverFDDP(self.problem) |
| 128 | + callbacks = [] |
| 129 | + callbacks.append(crocoddyl.CallbackVerbose()) |
| 130 | + self.solver.setCallbacks(callbacks) |
| 131 | + |
| 132 | + def compute_command(self, state: np.ndarray, max_iter: int = 500, x_ws=[], u_ws=[]) -> float: |
| 133 | + self.create_problem(state) |
| 134 | + self.init_solver() |
| 135 | + self.solver.solve(x_ws, u_ws, max_iter, False, 1e-5) |
| 136 | + u = np.clip(self.solver.us[0][0], -self.u_lim, self.u_lim) |
| 137 | + return u |
| 138 | + |
| 139 | + def get_trajectoy(self) -> np.ndarray: |
| 140 | + return self.solver.xs.tolist() |
| 141 | + |
| 142 | + def get_command(self) -> np.ndarray: |
| 143 | + return self.solver.us.tolist() |
| 144 | + |
| 145 | + def get_warm_start(self) -> tp.Tuple[np.ndarray, np.ndarray]: |
| 146 | + x_ws = self.solver.xs.tolist()[1:] + [self.solver.xs[-1]] |
| 147 | + u_ws = self.solver.us.tolist()[1:] + [self.solver.us[-1]] |
| 148 | + return x_ws, u_ws |
0 commit comments