diff --git a/config/walk_parameters.yaml b/config/walk_parameters.yaml index 0ec3b6671519f4b67e92ffdb6913181aab2b3a77..3e67324ee3711136faf748579873e70ae4c4ee84 100644 --- a/config/walk_parameters.yaml +++ b/config/walk_parameters.yaml @@ -13,7 +13,7 @@ robot: predefined_vel: true # If we are using a predefined reference velocity (True) or a joystick (False) N_SIMULATION: 5000 # Number of simulated wbc time steps enable_corba_viewer: false # Enable/disable Corba Viewer - enable_multiprocessing: false # Enable/disable running the MPC in another process in parallel of the main loop + enable_multiprocessing: true # Enable/disable running the MPC in another process in parallel of the main loop perfect_estimator: true # Enable/disable perfect estimator by using data directly from PyBullet # General control parameters @@ -25,9 +25,9 @@ robot: dt_mpc: 0.001 # Time step of the model predictive control type_MPC: 3 # Which MPC solver you want to use: 0 for OSQP MPC, 1, 2, 3 for Crocoddyl MPCs # Kp_main: [0.0, 0.0, 0.0] # Proportional gains for the PD+ - Kp_main: [1, 1, 1] # Proportional gains for the PD+ + Kp_main: [3, 3, 3] # Proportional gains for the PD+ # Kd_main: [0., 0., 0.] # Derivative gains for the PD+ - Kd_main: [1, 1, 1] # Derivative gains for the PD+ + Kd_main: [0.3, 0.3, 0.3] # Derivative gains for the PD+ # Kff_main: 0.0 # Feedforward torques multiplier for the PD+ Kff_main: 1.0 # Feedforward torques multiplier for the PD+ diff --git a/python/quadruped_reactive_walking/Controller.py b/python/quadruped_reactive_walking/Controller.py index 47c0614edf1b1e90f76dc7d0e03e84f414fe5e4e..992d8a11c82b8b2341fd285c7fe1d5e00164f856 100644 --- a/python/quadruped_reactive_walking/Controller.py +++ b/python/quadruped_reactive_walking/Controller.py @@ -82,7 +82,7 @@ class Controller: self.us_init = None print("No initial guess\n") - self.compute(device) + # self.compute(device) def compute(self, device, qc=None): """ @@ -118,16 +118,17 @@ class Controller: if not self.error: self.mpc_result = self.mpc.get_latest_result() + ### ONLY IF YOU WANT TO STORE THE FIRST SOLUTION TO WARM-START THE INITIAL Problem ### # if not self.initialized: # np.save(open('/tmp/init_guess.npy', "wb"), {"xs": self.mpc_result.xs, "us": self.mpc_result.us} ) # print("Initial guess saved") - # self.result.P = np.array(self.params.Kp_main.tolist() * 4) - self.result.P = np.array([5] * 3 + [1] * 3 + [5] * 6) - # self.result.D = np.array(self.params.Kd_main.tolist() * 4) - self.result.D = np.array([0.3] * 3 + [0.01] * 3 + [0.3] * 6) - tauFF = self.mpc_result.u[0] + self.result.P = np.array(self.params.Kp_main.tolist() * 4) + # self.result.P = np.array([3] * 3 + [3] * 3 + [3] * 6) + self.result.D = np.array(self.params.Kd_main.tolist() * 4) + # self.result.D = np.array([0.3] * 3 + [0.01] * 3 + [0.3] * 6) + tauFF = self.mpc_result.us[0] self.result.FF = self.params.Kff_main * np.array( [0] * 3 + list(tauFF) + [0] * 6 ) @@ -135,15 +136,15 @@ class Controller: # Keep only the actuated joints and set the other to default values self.mpc_result.q = np.array([self.pd.q0] * (self.pd.T + 1))[:, 7:19] self.mpc_result.v = np.array([self.pd.v0] * (self.pd.T + 1))[:, 6:] - self.mpc_result.q[:, 3:6] = np.array(self.mpc_result.x)[:, : self.pd.nq] - self.mpc_result.v[:, 3:6] = np.array(self.mpc_result.x)[:, self.pd.nq :] + self.mpc_result.q[:, 3:6] = np.array(self.mpc_result.xs)[:, : self.pd.nq] + self.mpc_result.v[:, 3:6] = np.array(self.mpc_result.xs)[:, self.pd.nq :] self.result.q_des = self.mpc_result.q[1] self.result.v_des = self.mpc_result.v[1] self.result.tau_ff = np.zeros(12) - self.xs_init = self.mpc_result.x[1:] + [self.mpc_result.x[-1]] - self.us_init = self.mpc_result.u[1:] + [self.mpc_result.u[-1]] + self.xs_init = self.mpc_result.xs[1:] + [self.mpc_result.xs[-1]] + self.us_init = self.mpc_result.us[1:] + [self.mpc_result.us[-1]] t_send = time.time() self.t_send = t_send - t_mpc diff --git a/python/quadruped_reactive_walking/WB_MPC/CrocoddylOCP.py b/python/quadruped_reactive_walking/WB_MPC/CrocoddylOCP.py index 97409d1aa8b2e7649c6ff90e71c32935f3f2bec1..550e5503f68c29b9e40f1b55ea392b18593e3fec 100644 --- a/python/quadruped_reactive_walking/WB_MPC/CrocoddylOCP.py +++ b/python/quadruped_reactive_walking/WB_MPC/CrocoddylOCP.py @@ -126,7 +126,7 @@ class OCP: return ( self.ddp.xs.tolist().copy(), self.ddp.us.tolist().copy(), - self.ddp.K.copy(), + self.ddp.K.tolist().copy(), self.t_ddp, ) diff --git a/python/quadruped_reactive_walking/WB_MPC_Wrapper.py b/python/quadruped_reactive_walking/WB_MPC_Wrapper.py index a2ad642859be78423e5f60c1e8e1bbb9360ea011..6a2f9e6cafb7f3cd75c7dbc5c8fb9fc3bf90650c 100644 --- a/python/quadruped_reactive_walking/WB_MPC_Wrapper.py +++ b/python/quadruped_reactive_walking/WB_MPC_Wrapper.py @@ -13,9 +13,9 @@ import quadruped_reactive_walking as qrw class Result: def __init__(self, pd): - self.xs = np.zeros((pd.T + 1, pd.nx)) - self.us = np.zeros((pd.T, pd.nu)) - self.K = None + self.xs = list(np.zeros((pd.T + 1, pd.nx))) + self.us = list(np.zeros((pd.T, pd.nu))) + self.K = list(np.zeros([pd.T, pd.nu, pd.nx])) self.solving_duration = 0.0 @@ -39,11 +39,12 @@ class MPC_Wrapper: self.running = Value("b", True) self.in_k = Value("i", 0) self.in_x0 = Array("d", [0] * pd.nx) - self.in_xs = Array("d", [0] * pd.T + 1 * pd.nx) - self.in_us = Array("d", [0] * pd.T * pd.nu) - self.out_xs = Array("d", [0] * pd.T + 1 * pd.nx) - self.out_us = Array("d", [0] * pd.T * pd.nu) - self.out_k = Array("d", [0] * 0 * 0) + self.in_warm_start = Value("b", False) + self.in_xs = Array("d", [0] * ((pd.T + 1) * pd.nx)) + self.in_us = Array("d", [0] * (pd.T * pd.nu)) + self.out_xs = Array("d", [0] * ((pd.T + 1) * pd.nx)) + self.out_us = Array("d", [0] * (pd.T * pd.nu)) + self.out_k = Array("d", [0] * (pd.T * pd.nu * pd.nx)) self.out_solving_time = Value("d", 0.0) else: self.ocp = OCP(pd, target) @@ -58,7 +59,6 @@ class MPC_Wrapper: Args: k (int): Number of inv dynamics iterations since the start of the simulation """ - if self.multiprocessing: self.run_MPC_asynchronous(k, x0, xs, us) else: @@ -101,6 +101,7 @@ class MPC_Wrapper: Run the MPC (asynchronous version) """ if k == 0: + self.last_available_result.xs = [x0 for _ in range (self.pd.T + 1)] p = Process(target=self.MPC_asynchronous) p.start() @@ -116,13 +117,13 @@ class MPC_Wrapper: self.new_data.value = False - k, x0, xs, us = self.decompress_dataIn(self.dataIn) + k, x0, xs, us = self.decompress_dataIn() if k == 0: loop_ocp = OCP(self.pd, self.target) loop_ocp.solve(x0, xs, us) - xs, us, K, solving_time = loop_ocp.get_latest_result() + xs, us, K, solving_time = loop_ocp.get_results() self.compress_dataOut(xs, us, K, solving_time) self.new_result.value = True @@ -146,13 +147,20 @@ class MPC_Wrapper: with self.in_k.get_lock(): self.in_k.value = k with self.in_x0.get_lock(): - np.frombuffer(self.in_x0).reshape(self.pd.nx)[:] = x0 + np.frombuffer(self.in_x0.get_obj()).reshape(self.pd.nx)[:] = x0 + + if xs is None or us is None: + self.in_warm_start.value = False + return + with self.in_xs.get_lock(): - np.frombuffer(self.in_xs).reshape((self.pd.T + 1, self.pd.nx))[:, :] = xs + np.frombuffer(self.in_xs.get_obj()).reshape((self.pd.T + 1, self.pd.nx))[ + :, : + ] = np.array(xs) with self.in_us.get_lock(): - np.frombuffer(self.in_us).reshape((self.pd.T, self.pd.nu))[:, :] = us - - return k, x0, xs, us + np.frombuffer(self.in_us.get_obj()).reshape((self.pd.T, self.pd.nu))[ + :, : + ] = np.array(us) def decompress_dataIn(self): """ @@ -160,13 +168,21 @@ class MPC_Wrapper: retrieve data from the main control loop in the asynchronous MPC """ with self.in_k.get_lock(): - k = self.dataIn.k + k = self.in_k.value with self.in_x0.get_lock(): - x0 = np.frombuffer(self.in_x0).reshape(self.pd.nx) + x0 = np.frombuffer(self.in_x0.get_obj()).reshape(self.pd.nx) + + if not self.in_warm_start.value: + return k, x0, None, None + with self.in_xs.get_lock(): - xs = np.frombuffer(self.in_xs).reshape((self.pd.T + 1, self.pd.nx)) + xs = list( + np.frombuffer(self.in_xs.get_obj()).reshape((self.pd.T + 1, self.pd.nx)) + ) with self.in_us.get_lock(): - us = np.frombuffer(self.in_us).reshape((self.pd.T, self.pd.nu)) + us = list( + np.frombuffer(self.in_us.get_obj()).reshape((self.pd.T, self.pd.nu)) + ) return k, x0, xs, us @@ -176,22 +192,35 @@ class MPC_Wrapper: retrieve data in the main control loop from the asynchronous MPC """ with self.out_xs.get_lock(): - np.frombuffer(self.out_xs).reshape((self.pd.T + 1, self.pd.nx))[:, :] = xs + np.frombuffer(self.out_xs.get_obj()).reshape((self.pd.T + 1, self.pd.nx))[ + :, : + ] = np.array(xs) with self.out_us.get_lock(): - np.frombuffer(self.out_us).reshape((self.pd.T, self.pd.nu))[:, :] = us + np.frombuffer(self.out_us.get_obj()).reshape((self.pd.T, self.pd.nu))[ + :, : + ] = np.array(us) with self.out_k.get_lock(): - np.frombuffer(self.out_k).reshape((0, 0))[:, :] = K + np.frombuffer(self.out_k.get_obj()).reshape( + [self.pd.T, self.pd.nu, self.pd.nx] + )[:, :, :] = np.array(K) self.out_solving_time.value = solving_time + def decompress_dataOut(self): """ Return the result of the asynchronous MPC (desired contact forces) that is stored in the shared memory """ - xs = np.frombuffer(self.out_xs).reshape((self.pd.T + 1, self.pd.nx)) - us = np.frombuffer(self.out_us).reshape((self.pd.T, self.pd.nu)) - K = np.frombuffer(self.out_k).reshape((0, 0)) - solving_time = self.solving_time.value + xs = list( + np.frombuffer(self.out_xs.get_obj()).reshape((self.pd.T + 1, self.pd.nx)) + ) + us = list(np.frombuffer(self.out_us.get_obj()).reshape((self.pd.T, self.pd.nu))) + K = list( + np.frombuffer(self.out_k.get_obj()).reshape( + [self.pd.T, self.pd.nu, self.pd.nx] + ) + ) + solving_time = self.out_solving_time.value return xs, us, K, solving_time diff --git a/python/quadruped_reactive_walking/main_solo12_control.py b/python/quadruped_reactive_walking/main_solo12_control.py index 654e65832b72a2adf0c4c5f46240433af6c58513..2aaddc49d55daf1de9b4c07e1f79bc738ad27604 100644 --- a/python/quadruped_reactive_walking/main_solo12_control.py +++ b/python/quadruped_reactive_walking/main_solo12_control.py @@ -209,12 +209,11 @@ def control_loop(): cnt += 1 # **************************************************************** - finished = t >= t_max damp_control(device, 12) if params.enable_multiprocessing: print("Stopping parallel process MPC") - controller.mpc_wrapper.stop_parallel_loop() + controller.mpc.stop_parallel_loop() # **************************************************************** diff --git a/python/quadruped_reactive_walking/tools/LoggerControl.py b/python/quadruped_reactive_walking/tools/LoggerControl.py index 79794913905440d570fb4f84b435fd985780fa4d..75bbc4e05fa6faf5b7547f49c4309ac326731045 100644 --- a/python/quadruped_reactive_walking/tools/LoggerControl.py +++ b/python/quadruped_reactive_walking/tools/LoggerControl.py @@ -102,7 +102,7 @@ class LoggerControl: # Controller timings: MPC time, ... self.target[self.i] = controller.point_target - self.t_mpc[self.i] = controller.mpc.mpc_result.solver_time + self.t_mpc[self.i] = controller.t_mpc self.t_send[self.i] = controller.t_send self.t_loop[self.i] = controller.t_loop self.t_measures[self.i] = controller.t_measures @@ -115,11 +115,11 @@ class LoggerControl: self.t_mpc[self.i] = controller.t_mpc self.t_send[self.i] = controller.t_send self.t_loop[self.i] = controller.t_loop - + + self.t_ocp_ddp[self.i] = controller.mpc_result.solving_duration if not self.multiprocess_mpc: self.t_ocp_update[self.i] = controller.mpc.ocp.t_update self.t_ocp_warm_start[self.i] = controller.mpc.ocp.t_warm_start - self.t_ocp_ddp[self.i] = controller.mpc.ocp.t_ddp self.t_ocp_solve[self.i] = controller.mpc.ocp.t_solve self.t_ocp_update_FK[self.i] = controller.mpc.ocp.t_FK @@ -249,9 +249,10 @@ class LoggerControl: plt.plot(t_range, self.t_mpc, "g+") plt.plot(t_range, self.t_send, "b+") plt.plot(t_range, self.t_loop, "+", color="violet") + plt.plot(t_range, self.t_ocp_ddp, "+", color="royalblue") plt.axhline(y=0.001, color="grey", linestyle=":", lw=1.0) plt.axhline(y=0.01, color="grey", linestyle=":", lw=1.0) - lgd = ["Measures", "MPC", "Send", "Whole-loop"] + lgd = ["Measures", "MPC", "Send", "Whole-loop", "MPC solve"] plt.legend(lgd) plt.xlabel("Time [s]") plt.ylabel("Time [s]")