diff --git a/examples/cart_pulling/base_and_ee_path_following.py b/examples/cart_pulling/base_and_ee_path_following.py new file mode 100644 index 0000000000000000000000000000000000000000..f7c1fd338071b2cb6a7e836b4fcc7576cca945f0 --- /dev/null +++ b/examples/cart_pulling/base_and_ee_path_following.py @@ -0,0 +1,56 @@ +from smc import getRobotFromArgs +from smc import getMinimalArgParser +from smc.control.optimal_control.util import get_OCP_args +from smc.control.cartesian_space import getClikArgs +from smc.control.optimal_control.croco_mpc_path_following import ( + BaseAndEEPathFollowingMPC, +) +import numpy as np + + +def path(T_w_e, i): + # 2) do T_mobile_base_ee_pos to get + # end-effector reference frame(s) + + # generate bullshit just to see it works + path_ee = [] + path_base = [] + t = i * robot.dt + for i in range(args.n_knots): + t += 0.01 + new = T_w_e.copy() + translation = 2 * np.array([np.cos(t / 20), np.sin(t / 20), 0.3]) + new.translation = translation + path_ee.append(new) + translation[2] = 0.0 + path_base.append(translation) + return path_base, path_ee + + +def get_args(): + parser = getMinimalArgParser() + parser = get_OCP_args(parser) + parser = getClikArgs(parser) # literally just for goal error + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = get_args() + robot = getRobotFromArgs(args) + x0 = np.concatenate([robot.q, robot.v]) + robot._step() + + BaseAndEEPathFollowingMPC(args, robot, path) + + print("final position:", robot.T_w_e) + + if args.real: + robot.stopRobot() + + if args.save_log: + robot._log_manager.saveLog() + robot._log_manager.plotAllControlLoops() + + if args.visualizer: + robot.killManipulatorVisualizer() diff --git a/examples/cart_pulling/cart_pulling_notes.md b/examples/cart_pulling/cart_pulling_notes.md index 95fe861a8f701e20a69c7a2dbcf069332f625155..5c6eed59576c0a5a930e2aa71b36452bd8b09864 100644 --- a/examples/cart_pulling/cart_pulling_notes.md +++ b/examples/cart_pulling/cart_pulling_notes.md @@ -20,6 +20,11 @@ we go with option 2) because option 2) is false! it does not account for orienta you could also try "freezing" the first point tbh +# should the reference for the base be just a translation or a full pose? +------------------------------------------------------------------------------- +on one level we don't care, but then again do know what it should be. so idk +- ideally just test this shit (annoying as fuck to implement but what can you do) + # the desided joint space position diff --git a/examples/cart_pulling/ee_only_path_following_from_plannner.py b/examples/cart_pulling/ee_only_path_following_from_plannner.py index e3b7f77b06fd755b7563fa5ad6c583bf6ecda658..d7aea034e81b18a48e7681c8eee405bfe64156aa 100644 --- a/examples/cart_pulling/ee_only_path_following_from_plannner.py +++ b/examples/cart_pulling/ee_only_path_following_from_plannner.py @@ -7,7 +7,7 @@ from smc.control.cartesian_space import getClikArgs from smc.path_generation.planner import starPlanner, getPlanningArgs from smc.control.optimal_control.croco_mpc_point_to_point import CrocoIKMPC from smc.control.optimal_control.croco_mpc_path_following import ( - CrocoEndEffectorPathFollowingMPC, + CrocoEEPathFollowingMPC, ) from smc.multiprocessing import ProcessManager @@ -73,7 +73,7 @@ if __name__ == "__main__": print("going to initial path position") CrocoIKMPC(args, robot, pathSE3[0]) print("initialized!") - CrocoEndEffectorPathFollowingMPC(args, robot, x0, path_planner) + CrocoEEPathFollowingMPC(args, robot, x0, path_planner) print("final position:", robot.T_w_e) diff --git a/examples/cartesian_space/clik.py b/examples/cartesian_space/clik_point_to_point.py similarity index 100% rename from examples/cartesian_space/clik.py rename to examples/cartesian_space/clik_point_to_point.py diff --git a/examples/optimal_control/path_following_mpc.py b/examples/optimal_control/path_following_mpc.py index 439352518beb563d076069f21bb3597cdd607b68..2e11a54104211e46764924edde04603c624414d5 100644 --- a/examples/optimal_control/path_following_mpc.py +++ b/examples/optimal_control/path_following_mpc.py @@ -3,7 +3,7 @@ from smc import getMinimalArgParser from smc.control.optimal_control.util import get_OCP_args from smc.control.cartesian_space import getClikArgs from smc.control.optimal_control.croco_mpc_path_following import ( - CrocoEndEffectorPathFollowingMPC, + CrocoEEPathFollowingMPC, ) import numpy as np @@ -38,7 +38,7 @@ if __name__ == "__main__": x0 = np.concatenate([robot.q, robot.v]) robot._step() - CrocoEndEffectorPathFollowingMPC(args, robot, x0, path) + CrocoEEPathFollowingMPC(args, robot, x0, path) print("final position:", robot.T_w_e) diff --git a/python/smc/control/optimal_control/croco_mpc_path_following.py b/python/smc/control/optimal_control/croco_mpc_path_following.py index 1f7fdb83313f44233a08043e21312b154c997f89..eac9b86877b47847190d09ed190b7eef73586745 100644 --- a/python/smc/control/optimal_control/croco_mpc_path_following.py +++ b/python/smc/control/optimal_control/croco_mpc_path_following.py @@ -18,7 +18,51 @@ import types from argparse import Namespace -def CrocoEndEffectorPathFollowingMPCControlLoop( +def CrocoEEPathFollowingMPCControlLoop( + args, + robot: SingleArmInterface, + ocp: CrocoOCP, + path_planner: types.FunctionType, + t, + _, +): + """ + CrocoPathFollowingMPCControlLoop + ----------------------------- + end-effector(s) follow their path(s). + + path planner can either be a function which spits out a list of path points + or an instance of ProcessManager which spits out path points + by calling ProcessManager.getData() + """ + breakFlag = False + log_item = {} + save_past_item = {} + + q = robot.q + T_w_e = robot.T_w_e + + pathSE3 = path_planner(T_w_e, t) + + x0 = np.concatenate([robot.q, robot.v]) + ocp.warmstartAndReSolve(x0, data=pathSE3) + xs = np.array(ocp.solver.xs) + us = np.array(ocp.solver.us) + vel_cmds = xs[1, robot.model.nq :] + + robot.sendVelocityCommand(vel_cmds) + + # TODO: EVIL AND HAS TO BE REMOVED FROM HERE + if args.visualizer: + if t % int(np.ceil(args.ctrl_freq / 25)) == 0: + robot.visualizer_manager.sendCommand({"frame_path": pathSE3}) + + log_item["qs"] = q.reshape((robot.model.nq,)) + log_item["dqs"] = robot.v.reshape((robot.model.nv,)) + return breakFlag, save_past_item, log_item + + +def CrocoEEPathFollowingFromPlannerMPCControlLoop( args, robot: SingleArmInterface, ocp: CrocoOCP, @@ -46,31 +90,28 @@ def CrocoEndEffectorPathFollowingMPCControlLoop( # NOTE: it's pointless to recalculate the path every time # if it's the same 2D path - if type(path_planner) == types.FunctionType: - pathSE3 = path_planner(T_w_e, t) - else: - path_planner.sendCommand(p) - data = path_planner.getData() + path_planner.sendCommand(p) + data = path_planner.getData() - if data == "done": - breakFlag = True + if data == "done": + breakFlag = True - if data == "done" or data is None: - robot.sendVelocityCommand(np.zeros(robot.model.nv)) - log_item["qs"] = q - log_item["dqs"] = robot.v - return breakFlag, save_past_item, log_item + if data == "done" or data is None: + robot.sendVelocityCommand(np.zeros(robot.model.nv)) + log_item["qs"] = q + log_item["dqs"] = robot.v + return breakFlag, save_past_item, log_item - _, path2D_untimed = data - path2D_untimed = np.array(path2D_untimed).reshape((-1, 2)) - # who cares if the velocity is right, - # it should be kinda right so that we need less ocp iterations - # and that's it - max_base_v = np.linalg.norm(robot._max_v[:2]) - path2D = path2D_timed(args, path2D_untimed, max_base_v) + _, path2D_untimed = data + path2D_untimed = np.array(path2D_untimed).reshape((-1, 2)) + # who cares if the velocity is right, + # it should be kinda right so that we need less ocp iterations + # and that's it + max_base_v = np.linalg.norm(robot._max_v[:2]) + path2D = path2D_timed(args, path2D_untimed, max_base_v) - # create a 3D reference out of the path - pathSE3 = path2D_to_SE3_fixed(path2D, args.handlebar_height) + # create a 3D reference out of the path + pathSE3 = path2D_to_SE3_fixed(path2D, args.handlebar_height) # TODO: EVIL AND HAS TO BE REMOVED FROM HERE if args.visualizer: @@ -90,11 +131,11 @@ def CrocoEndEffectorPathFollowingMPCControlLoop( return breakFlag, save_past_item, log_item -def CrocoEndEffectorPathFollowingMPC( +def CrocoEEPathFollowingMPC( args: Namespace, robot: SingleArmInterface, x0: np.ndarray, - path_planner: ProcessManager, + path_planner: ProcessManager | types.FunctionType, ): """ CrocoEndEffectorPathFollowingMPC @@ -111,9 +152,18 @@ def CrocoEndEffectorPathFollowingMPC( x0 = np.concatenate([robot.q, robot.v]) ocp.solveInitialOCP(x0) - controlLoop = partial( - CrocoEndEffectorPathFollowingMPCControlLoop, args, robot, ocp, path_planner - ) + if type(path_planner) == types.FunctionType: + controlLoop = partial( + CrocoEEPathFollowingMPCControlLoop, args, robot, ocp, path_planner + ) + else: + controlLoop = partial( + CrocoEEPathFollowingFromPlannerMPCControlLoop, + args, + robot, + ocp, + path_planner, + ) log_item = {"qs": np.zeros(robot.model.nq), "dqs": np.zeros(robot.model.nv)} save_past_item = {} loop_manager = ControlLoopManager( @@ -122,14 +172,54 @@ def CrocoEndEffectorPathFollowingMPC( loop_manager.run() -# TODO: the robot put in is a whole body robot, -# which you need to implement def BaseAndEEPathFollowingMPCControlLoop( + args, + robot, + ocp: CrocoOCP, + path_planner: types.FunctionType, + t, + _, +): + """ + CrocoPathFollowingMPCControlLoop + ----------------------------- + end-effector(s) follow their path(s). + + path planner can either be a function which spits out a list of path points + or an instance of ProcessManager which spits out path points + by calling ProcessManager.getData() + """ + breakFlag = False + log_item = {} + save_past_dict = {} + + q = robot.q + T_w_e = robot.T_w_e + path_base, path_EE = path_planner(T_w_e, t) + + x0 = np.concatenate([robot.q, robot.v]) + ocp.warmstartAndReSolve(x0, data=(path_base, path_EE)) + xs = np.array(ocp.solver.xs) + us = np.array(ocp.solver.us) + vel_cmds = xs[1, robot.model.nq :] + + robot.sendVelocityCommand(vel_cmds) + if t % int(np.ceil(args.ctrl_freq / 25)) == 0: + path_base = np.array(path_base) + robot.visualizer_manager.sendCommand({"path": path_base}) + robot.visualizer_manager.sendCommand({"frame_path": path_EE}) + + log_item["qs"] = q.reshape((robot.model.nq,)) + log_item["dqs"] = robot.v.reshape((robot.model.nv,)) + return breakFlag, save_past_dict, log_item + + +def BaseAndEEPathFollowingFromPlannerMPCControlLoop( args, robot, ocp: CrocoOCP, path_planner: ProcessManager, - iter_n, + t, past_data, ): """ @@ -227,7 +317,7 @@ def BaseAndEEPathFollowingMPCControlLoop( pathSE3_handlebar = path2D_to_SE3_fixed(path2D_handlebar_1, args.handlebar_height) if args.visualizer: - if iter_n % 20 == 0: + if t % int(np.ceil(args.ctrl_freq / 25)) == 0: robot.visualizer_manager.sendCommand({"path": path_base}) robot.visualizer_manager.sendCommand({"frame_path": pathSE3_handlebar}) @@ -244,7 +334,11 @@ def BaseAndEEPathFollowingMPCControlLoop( return breakFlag, save_past_dict, log_item -def BaseAndEEPathFollowingMPC(args, robot, path_planner): +def BaseAndEEPathFollowingMPC( + args: Namespace, + robot: SingleArmInterface, + path_planner: ProcessManager | types.FunctionType, +): """ CrocoEndEffectorPathFollowingMPC ----- @@ -258,7 +352,7 @@ def BaseAndEEPathFollowingMPC(args, robot, path_planner): ocp = BaseAndEEPathFollowingOCP(args, robot, x0) ocp.solveInitialOCP(x0) - # prepopulate past data to make base and cart be on the same path in the past + # prepopulate past data to make base and cart be on the same path in the past # (which didn't actually happen because this just started) T_w_e = robot.T_w_e p_cart = robot.q[:2] @@ -276,16 +370,24 @@ def BaseAndEEPathFollowingMPC(args, robot, path_planner): ) ) - controlLoop = partial( - BaseAndEEPathFollowingMPCControlLoop, args, robot, ocp, path_planner - ) + if type(path_planner) == types.FunctionType: + controlLoop = partial( + BaseAndEEPathFollowingMPCControlLoop, args, robot, ocp, path_planner + ) + else: + controlLoop = partial( + BaseAndEEPathFollowingFromPlannerMPCControlLoop, + args, + robot, + ocp, + path_planner, + ) log_item = {"qs": np.zeros(robot.model.nq), "dqs": np.zeros(robot.model.nv)} save_past_dict = {"path2D_untimed": T_w_e.translation[:2]} loop_manager = ControlLoopManager( robot, controlLoop, args, save_past_dict, log_item ) - loop_manager.past_data["path2D_untimed"].clear() loop_manager.past_data["path2D_untimed"].extend( path2D_handlebar[i] for i in range(args.past_window_size)