diff --git a/python/ur_simple_control/__pycache__/managers.cpython-312.pyc b/python/ur_simple_control/__pycache__/managers.cpython-312.pyc index cbec686b753e00fb945018a65c3a29abc042fc33..e8f0cf9924bbd4c123fcdc9cf58e2b3c93cdaf56 100644 Binary files a/python/ur_simple_control/__pycache__/managers.cpython-312.pyc and b/python/ur_simple_control/__pycache__/managers.cpython-312.pyc differ diff --git a/python/ur_simple_control/basics/__pycache__/basics.cpython-312.pyc b/python/ur_simple_control/basics/__pycache__/basics.cpython-312.pyc index e00e5fe4b51234988fa9300a62849b129253b847..82aa642f4eee74e77dad389ccccdaa2651892031 100644 Binary files a/python/ur_simple_control/basics/__pycache__/basics.cpython-312.pyc and b/python/ur_simple_control/basics/__pycache__/basics.cpython-312.pyc differ diff --git a/python/ur_simple_control/basics/basics.py b/python/ur_simple_control/basics/basics.py index 444ea98d9c02d295ecbeafa3279dd2f119ea304b..47977c2bfe829a356d1256c1170df6910239d235 100644 --- a/python/ur_simple_control/basics/basics.py +++ b/python/ur_simple_control/basics/basics.py @@ -63,7 +63,7 @@ def moveJP(args, robot, q_desired): print("MoveJP done: convergence achieved, reached destionation!") -def jointTrajFollowingPDControlLoop(robot: RobotManager, reference, i, past_data): +def jointTrajFollowingPDControlLoop(stop_at_final : bool, robot: RobotManager, reference, i, past_data): breakFlag = False save_past_dict = {} log_item = {} @@ -73,10 +73,21 @@ def jointTrajFollowingPDControlLoop(robot: RobotManager, reference, i, past_data # which is pretty much true t = i * robot.dt # take the future (next) one + # TODO: turn this into interpolation t_index = int(np.ceil(t / reference['dt'])) + # TODO: move to a different function later + if t_index >= len(reference['qs']): + t_index = len(reference['qs']) - 1 + if stop_at_final: + reference['vs'][t_index] = np.zeros(len(reference['vs'][t_index])) # TODO: check if that's really the last - if t_index == len(reference['qs']) - 1: + #if t_index == len(reference['qs']) - 1: + # breakFlag = True + + # TODO NOTE TODO TODO: remove/change + if (t_index == len(reference['qs']) - 1) and \ + (np.linalg.norm(q - reference['qs'][-1]) < 1e-2): breakFlag = True error_q = reference['qs'][t_index] - q @@ -91,6 +102,8 @@ def jointTrajFollowingPDControlLoop(robot: RobotManager, reference, i, past_data log_item['error_q'] = error_q log_item['error_v'] = error_v + log_item['q'] = q + log_item['v'] = v log_item['reference_q'] = reference['qs'][t_index] log_item['reference_v'] = reference['vs'][t_index] @@ -98,10 +111,12 @@ def jointTrajFollowingPDControlLoop(robot: RobotManager, reference, i, past_data def jointTrajFollowingPD(args, robot, reference): # we're not using any past data or logging, hence the empty arguments - controlLoop = partial(jointTrajFollowingPDControlLoop, robot, reference) + controlLoop = partial(jointTrajFollowingPDControlLoop, args.stop_at_final, robot, reference) log_item = { 'error_q' : np.zeros(robot.model.nq), 'error_v' : np.zeros(robot.model.nv), + 'q' : np.zeros(robot.model.nq), + 'v' : np.zeros(robot.model.nv), 'reference_q' : np.zeros(robot.model.nq), 'reference_v' : np.zeros(robot.model.nv) } diff --git a/python/ur_simple_control/managers.py b/python/ur_simple_control/managers.py index 340d36e4ab4175af420bf3c439c96d7e98964ca1..3e4cbf9b0c26d7698bf4e363376a2526967d6205 100644 --- a/python/ur_simple_control/managers.py +++ b/python/ur_simple_control/managers.py @@ -452,7 +452,8 @@ class RobotManager: self.dt = 1 / self.update_rate # you better not give me crazy stuff # and i'm not clipping it, you're fixing it - assert args.acceleration <= 1.7 and args.acceleration > 0.0 + self.MAX_ACCELERATION = 1.7 + assert args.acceleration <= self.MAX_ACCELERATION and args.acceleration > 0.0 # this is the number passed to speedj self.acceleration = args.acceleration # NOTE: this is evil and everything only works if it's set to 1 @@ -816,13 +817,13 @@ class RobotManager: else: # this one takes all 8 elements of qd since we're still in pinocchio # this is ugly, todo: fix - if len(qd) == 6: - qd = qd_cmd.reshape((6,)) - qd = list(qd) - qd.append(0.0) - qd.append(0.0) - qd = np.array(qd) - self.v_q = qd + qd = qd[:6] + qd = qd_cmd.reshape((6,)) + qd = list(qd) + qd.append(0.0) + qd.append(0.0) + qd = np.array(qd) + self.v_q = qd self.q = pin.integrate(self.model, self.q, qd * self.dt) def openGripper(self): diff --git a/python/ur_simple_control/optimal_control/optimal_control.py b/python/ur_simple_control/optimal_control/optimal_control.py index c8daeb8d424934a5f3602a2d0557460245301e49..e3fb969279a98988ac8ad84fff12e0db0b278096 100644 --- a/python/ur_simple_control/optimal_control/optimal_control.py +++ b/python/ur_simple_control/optimal_control/optimal_control.py @@ -4,6 +4,9 @@ import crocoddyl from ur_simple_control.managers import getMinimalArgParser, ControlLoopManager, RobotManager import argparse from ur_simple_control.basics.basics import jointTrajFollowingPD +from ur_simple_control.visualize.visualize import plotFromDict +import example_robot_data +import time def get_OCP_args(parser : argparse.ArgumentParser): @@ -11,18 +14,31 @@ def get_OCP_args(parser : argparse.ArgumentParser): help="time length between state-control-pair decision variables") parser.add_argument("--n-knots", type=int, default=100, \ help="number of state-control-pair decision variables") + parser.add_argument("--stop-at-final", type=int, default=True, \ + help="the trajectory generated by the OCP will be followed. it might not have finally velocity 0. \ + if this argument is set to true, the final velocity will be set to 0 (there will be overshoot).") return parser # TODO: use fddp and incorporate torque (i.e. velocity constraints) # --> those will correspond to max_qd and acceleration attributes in robotmanager def IKOCP(args, robot : RobotManager, goal : pin.SE3): + # create torque bounds which correspond to percentage + # of maximum allowed acceleration + # NOTE: idk if this makes any sense, but let's go for it + #print(robot.model.effortLimit) + #exit() + #robot.model.effortLimit = robot.model.effortLimit * (args.acceleration / robot.MAX_ACCELERATION) + #robot.model.effortLimit = robot.model.effortLimit * 0.01 + #robot.data = robot.model.createData() # starting state x0 = np.concatenate([robot.getQ(), robot.getQd()]) state = crocoddyl.StateMultibody(robot.model) # command input IS torque actuation = crocoddyl.ActuationModelFull(state) - # we will be summing 3 different costs + + # we will be summing 4 different costs + # first 3 are for tracking, state and control regulation runningCostModel = crocoddyl.CostModelSum(state) terminalCostModel = crocoddyl.CostModelSum(state) uResidual = crocoddyl.ResidualModelControl(state, state.nv) @@ -43,24 +59,56 @@ def IKOCP(args, robot : RobotManager, goal : pin.SE3): # cost 3) x residual (overall amount of movement) xResidual = crocoddyl.ResidualModelState(state, x0, state.nv) # put these costs into the running costs - runningCostModel.addCost("gripperPose", goalTrackingCost, 1) - runningCostModel.addCost("xReg", xRegCost, 1e-1) - runningCostModel.addCost("uReg", uRegCost, 1e-1) + runningCostModel.addCost("gripperPose", goalTrackingCost, 1e2) + runningCostModel.addCost("xReg", xRegCost, 1e-3) + runningCostModel.addCost("uReg", uRegCost, 1e-3) # and add the terminal cost, which is the distance to the goal # NOTE: shouldn't there be a final velocity = 0 here? - terminalCostModel.addCost("gripperPose", goalTrackingCost, 1e3) + terminalCostModel.addCost("gripperPose", goalTrackingCost, 1e2) + terminalCostModel.addCost("uReg", uRegCost, 1e3) + + # the 4th cost is for defining bounds via costs + # NOTE: could have gotten the same info from state.lb and state.ub. + # the first state is unlimited there idk what that means really, + # but the arm's base isn't doing a full rotation anyway, let alone 2 or more + xlb = np.concatenate([ + robot.model.lowerPositionLimit, + -1 * robot.model.velocityLimit]) + xub = np.concatenate([ + robot.model.upperPositionLimit, + robot.model.velocityLimit]) + bounds = crocoddyl.ActivationBounds(xlb, xub, 1.0) + xLimitResidual = crocoddyl.ResidualModelState(state, x0, actuation.nu) + xLimitActivation = crocoddyl.ActivationModelQuadraticBarrier(bounds) + limitCost = crocoddyl.CostModelResidual(state, xLimitActivation, xLimitResidual) + runningCostModel.addCost("limitCost", limitCost, 1e3) + terminalCostModel.addCost("limitCost", limitCost, 1e3) + # NOTE: i have no idea how to incorporate this into anything + # we need to add state constraints for things to make sense + # NOTE!!!: there are no hard inequality constraints on states + # in what's implemented in crocoddyl. + # INSTEAD, you define quadratic barier-type costs for that. + # there are box constraints on control input though, + # but i'm pretty sure i'm not using those correctly. + #x_constraint_lower = x0.copy() + #x_constraint_upper = x0.copy() + #x_constraint_lower[:robot.model.nq] = -2 * np.pi + #x_constraint_upper[:robot.model.nq] = 2 * np.pi + #x_constraint_lower[robot.model.nq:] = -1 * robot.max_qd + #x_constraint_upper[robot.model.nq:] = robot.max_qd + #state_constraint = crocoddyl.ConstraintModelResidual(state, xResidual, x_constraint_lower, x_constraint_upper) # Next, we need to create an action model for running and terminal knots. The # forward dynamics (computed using ABA) are implemented # inside DifferentialActionModelFreeFwdDynamics. runningModel = crocoddyl.IntegratedActionModelEuler( - crocoddyl.DifferentialActionModelFreeFwdDynamics( + crocoddyl.DifferentialActionModelFreeInvDynamics( state, actuation, runningCostModel ), args.ocp_dt, ) terminalModel = crocoddyl.IntegratedActionModelEuler( - crocoddyl.DifferentialActionModelFreeFwdDynamics( + crocoddyl.DifferentialActionModelFreeInvDynamics( state, actuation, terminalCostModel ), 0.0, @@ -69,25 +117,49 @@ def IKOCP(args, robot : RobotManager, goal : pin.SE3): # now we define the problem problem = crocoddyl.ShootingProblem(x0, [runningModel] * args.n_knots, terminalModel) # and the solver - solver = crocoddyl.SolverFDDP(problem) + solver = crocoddyl.SolverBoxFDDP(problem) + #solver = crocoddyl.SolverIpopt(problem) + # TODO: remove / place elsewhere once it works + solver.setCallbacks([crocoddyl.CallbackVerbose(), crocoddyl.CallbackLogger()]) # run solve - solver.solve() + # NOTE: there are some possible parameters here that I'm not using + xs = [x0] * (solver.problem.T + 1) + us = solver.problem.quasiStatic([x0] * solver.problem.T) + solver.solve(xs, us, 5000, False, 1e-9) + #solver.solve() # get reference (state trajectory) # we aren't using controls because we only have velocity inputs xs = np.array(solver.xs) qs = xs[:, :robot.model.nq] vs = xs[:, robot.model.nq:] reference = {'qs' : qs, 'vs' : vs, 'dt' : args.ocp_dt} - return reference + return reference, solver if __name__ == "__main__": parser = getMinimalArgParser() parser = get_OCP_args(parser) args = parser.parse_args() + ex_robot = example_robot_data.load("ur5_gripper") robot = RobotManager(args) + # TODO: remove once things work + robot.max_qd = 3.14 + robot.q = pin.randomConfiguration(robot.model) goal = pin.SE3.Random() goal.translation = np.random.random(3) * 0.4 print("set goal:", goal) - reference = IKOCP(args, robot, goal) + reference, solver = IKOCP(args, robot, goal) + # we only work within -pi - pi (or 0-2pi idk anymore) + #reference['qs'] = reference['qs'] % (2*np.pi) - np.pi + log = solver.getCallbacks()[1] + crocoddyl.plotOCSolution(log.xs, log.us, figIndex=1, show=True) + crocoddyl.plotConvergence(log.costs, log.pregs, log.dregs, log.grads, log.stops, log.steps, figIndex=2) jointTrajFollowingPD(args, robot, reference) + reference.pop('dt') + plotFromDict(reference, args.n_knots + 1, args) print("achieved result", robot.getT_w_e()) + display = crocoddyl.MeshcatDisplay(ex_robot) + display.rate = -1 + display.freq = 1 + while True: + display.displayFromSolver(solver) + time.sleep(1.0)