Diff of /arm_model/controller.py [000000] .. [17a672]

Switch to side-by-side view

--- a
+++ b/arm_model/controller.py
@@ -0,0 +1,551 @@
+import numpy as np
+import sympy as sp
+from logger import Logger
+from scipy.optimize import minimize
+from util import to_np_mat, to_np_vec, sigmoid, rotate, gaussian
+from simulation import SimulationReporter
+from delay import DelayArray
+
+
+# ------------------------------------------------------------------------
+# PID
+# ------------------------------------------------------------------------
+
+
+class PD:
+    """Proportional Derivative Controller.
+
+    a = ad + Kp (xd - x) + Kd (ud - u)
+
+    """
+
+    def __init__(self, Kp, Kd):
+        """
+        Parameters
+        ----------
+
+        Kp: proportional gain
+
+        Kd: derivative gain
+
+        """
+
+        self.Kp = Kp
+        self.Kd = Kd
+        self.t = 0
+
+    def compute(self, x, u, xd, ud, ad):
+        """
+        Parameters
+        ----------
+
+        x: current position
+        u: current velocity
+        xd: desired position
+        ud: desired velocity
+        ad: desired acceleration
+
+        Returns
+        -------
+
+        the requred acceleration
+
+        """
+
+        return ad + self.Kp * (xd - x) + self.Kd * (ud - u)
+
+
+class PID:
+    """
+    An implementation of Proportional Integral Derivative controller.
+
+    e = xd - x
+    u = Kp e + Ki \int_0^t e d\tau + Kd de/dt
+
+    """
+
+    def __init__(self, Kp, Ki, Kd):
+        """
+        Parameters
+        ----------
+
+        Kp: proportional gain
+        Ki: integral gain
+        Kd: derivative gain
+
+        """
+
+        self.Kp = Kp
+        self.Ki = Ki
+        self.Kd = Kd
+        self.t = 0
+        self.error_sum = 0
+
+    def compute(self, t, x, u, xd, ud):
+        """
+        Parameters
+        ----------
+
+        x: current position
+        u: current velocity
+        xd: desired position
+        ud: desired velocity
+
+        Returns
+        -------
+
+        the requred acceleration
+
+        Note: TODO if the numerical integration goes backward in time then this
+        may cause instabilities
+
+        """
+
+        dt = np.abs(self.t - t)
+
+        error = xd - x
+        error_d = ud - u
+        self.error_sum = self.error_sum + error * dt
+        self.t = t
+
+        return self.Kp * error + self.Kd * error_d + self.Ki * self.error_sum
+
+
+# ------------------------------------------------------------------------
+# JointSpaceController
+# ------------------------------------------------------------------------
+
+
+class JointSpaceController:
+    """Simple joint space tracking controller.
+
+    """
+
+    def __init__(self, model):
+        """ Constructor.
+
+        Parameters
+        ----------
+
+        model: reference to the model
+
+        """
+        self.logger = Logger('JointSpaceController')
+        self.model = model
+        self.pid = PID(10, 2, 2)
+        self.reporter = SimulationReporter(model)
+
+    def controller(self, x, t, x0):
+        """Default simple joint space PD controller.
+
+        Parameters
+        ----------
+
+        x: state
+        t: time
+        x0: initial state
+
+        """
+        # self.logger.debug('Time: ' + str(t))
+        n = self.model.nd
+        q = np.array(x[:n])
+        u = np.array(x[n:])
+        qd = np.array(self.target(x, t, x0))
+        ud = np.zeros((n))
+        tau = np.array(self.pid.compute(t, q, u, qd, ud))
+
+        # record
+        self.reporter.t.append(t)
+        self.reporter.q.append(q)
+        self.reporter.qd.append(qd)
+        self.reporter.tau.append(tau)
+        # self.fs_reporter.record(t, q, u, tau.reshape((n, 1)))
+
+        return tau
+
+    def target(self, x, t, x0):
+        """Default joint space target function.
+
+        Parameters
+        ----------
+
+        x: state
+        t: time
+        x0: initial state
+
+        """
+        return [np.deg2rad(130), np.deg2rad(60), np.deg2rad(95)]
+
+# ------------------------------------------------------------------------
+# TaskSpaceController
+# ------------------------------------------------------------------------
+
+
+class TaskSpaceController:
+    """A task space controller that moves the task goal in a direction.
+
+    """
+
+    def __init__(self, model, task, angle=0, evaluate_muscle_forces=False):
+        """Constructor.
+
+        Parameters
+        ----------
+
+        model: Model
+            a reference to the model
+
+        task: TaskSpace
+            a reference to TaskSpace
+
+        angle: rad (default=0)
+            direction to move the task
+
+        evaluate_muscle_forces: Boolean (default=False)
+            compute muscle forces that satisfy the task constraint
+
+        """
+        self.logger = Logger('TaskSpaceController')
+        self.model = model
+        self.task = task
+        self.angle = angle
+        self.evaluate_muscle_forces = evaluate_muscle_forces
+        self.pd = PD(50, 5)
+        self.reporter = SimulationReporter(model)
+
+    def controller(self, x, t, x0):
+        """Controller function.
+
+        Parameters
+        ----------
+
+        x: state
+        t: time
+        x0: initial state
+
+        """
+        self.logger.debug('Time: ' + str(t))
+        n = self.model.nd
+        q = np.array(x[:n])
+        u = np.array(x[n:])
+        pose = self.model.model_parameters(q=q, u=u)
+
+        # task variables
+        xc = to_np_mat(self.task.x(pose))
+        uc = to_np_mat(self.task.u(pose, u))
+        xd, ud, ad = self.target(x, t, x0)
+        ad = sp.Matrix(self.pd.compute(xc, uc, xd, ud, ad))
+
+        # forces
+        tau, ft = self.task.calculate_force(ad, pose)
+        ft = to_np_vec(ft)
+        tau = to_np_vec(tau)
+
+        # solve static optimization
+        fm = None
+        if self.evaluate_muscle_forces:
+            m = self.model.md
+            R = to_np_mat(self.model.R.subs(pose))
+            RT = R.transpose()
+
+            def objective(x):
+                return np.sum(x**2)
+
+            def inequality_constraint(x):
+                return np.array(tau + RT * (x.reshape(-1, 1))).reshape(-1,)
+
+            x0 = np.zeros(m)
+            bounds = tuple([(0, self.model.Fmax[i, i]) for i in range(0, m)])
+            constraints = ({'type': 'ineq', 'fun': inequality_constraint})
+            sol = minimize(objective, x0,  method='SLSQP',
+                           bounds=bounds,
+                           constraints=constraints)
+            if sol.success is False:
+                raise Exception('Static optimization failed at: ' + t)
+
+            fm = sol.x.reshape(-1,)
+
+        # record
+        self.reporter.t.append(t)
+        self.reporter.q.append(q)
+        self.reporter.u.append(u)
+        self.reporter.x.append(np.array(xc).reshape(-1,))
+        self.reporter.xd.append(np.array(xd).reshape(-1,))
+        self.reporter.tau.append(tau)
+        self.reporter.ft.append(ft)
+        self.reporter.fm.append(fm)
+        # self.fs_reporter.record(t, q, u, tau)
+
+        return tau
+
+    def target(self, x, t, x0):
+        """ A directed sigmoid function target.
+
+        Parameters
+        ----------
+
+        x: state
+        t: time
+        x0: initial state
+
+        Returns
+        -------
+
+        (x, u, a)
+
+        """
+        pose0 = self.model.model_parameters(q=x0[:self.model.nd])
+        xt0 = self.task.x(pose0)
+
+        t0 = 1
+        A = 0.3
+        B = 4
+        o = np.asmatrix([[0], [0]])
+        xd, ud, ad = sigmoid(t, t0, A, B)
+
+        xd = rotate(o, np.asmatrix([[xd], [0]]), self.angle)
+        ud = rotate(o, np.asmatrix([[ud], [0]]), self.angle)
+        ad = rotate(o, np.asmatrix([[ad], [0]]), self.angle)
+
+        return (np.asmatrix(xt0 + xd),
+                np.asmatrix(ud),
+                np.asmatrix(ad))
+
+
+# ------------------------------------------------------------------------
+# MuscleSpaceControllerJS
+# ------------------------------------------------------------------------
+
+
+class MuscleSpaceControllerJS:
+    """This controller uses the muscle space EoM to driving a model from a
+    reference pose to a desired pose in joint space.
+
+    """
+
+    def __init__(self, model, musclespace):
+        """Constructor.
+
+        Parameters
+        ----------
+
+        model: a reference to the model
+        musclespace: a reference to MuscleSpace
+
+        """
+        self.logger = Logger('MsucleSpaceControllerJS')
+        self.model = model
+        self.musclespace = musclespace
+        self.pid = PID(10, 0, 10)
+        self.reporter = SimulationReporter(model)
+
+    def controller(self, x, t, x0):
+        """Default simple joint space PD controller.
+
+        Parameters
+        ----------
+
+        x: state
+        t: time
+        x0: initial state
+
+        """
+        self.logger.debug('Time: ' + str(t))
+        n = self.model.nd
+        m = self.model.md
+        q = np.array(x[:n])
+        u = np.array(x[n:])
+        qd = np.array(self.target(x, t, x0))
+        ud = np.zeros((n))
+        qddot = np.array(self.pid.compute(t, q, u, qd, ud))
+
+        # evalutate desired lmdd_d
+        pose = self.model.model_parameters(q=q, u=u)
+        RDotQDot = self.model.RDotQDot.subs(pose)
+        RQDDot = self.model.R.subs(pose) * sp.Matrix(qddot)
+        lmdd_d = sp.Matrix(RDotQDot + RQDDot)
+        lmd = to_np_vec(self.model.lmd.subs(pose))
+        lmd_d = to_np_vec(self.model.R.subs(pose) * sp.Matrix(u))
+
+        tau = self.musclespace.calculate_force(lmdd_d, pose)[0]
+        tau = to_np_vec(tau)
+
+        # store record
+        self.reporter.t.append(t)
+        self.reporter.q.append(q)
+        self.reporter.u.append(u)
+        self.reporter.qd.append(qd)
+        self.reporter.lmd.append(lmd)
+        self.reporter.lmd_d.append(lmd_d)
+        self.reporter.tau.append(tau)
+
+        return tau
+
+    def target(self, x, t, x0):
+        """Default joint space target function.
+
+        Parameters
+        ----------
+
+        x: state
+        t: time
+        x0: initial state
+
+        """
+        return [np.deg2rad(130), np.deg2rad(60), np.deg2rad(95)]
+
+
+# ------------------------------------------------------------------------
+# PosturalMuscleSpaceController
+# ------------------------------------------------------------------------
+
+
+class PosturalMuscleSpaceController:
+    """This is a posture controller in muscle space. The system is disturbed and a
+    muscle length controller is responsible for restoring it to its initial
+    pose.
+
+    """
+
+    def __init__(self, model, musclespace, kp, kd, delay, a, t0, sigma, gamma):
+        """ Constructor.
+
+        Parameters
+        ----------
+
+        model: a reference to ToyModel
+
+        muscle: a reference to TaskSpace
+
+        kp: proportional gain
+
+        kd: derivative gain
+
+        delay: the delay of the reflex loops
+
+        a: Gaussian aptitude (disturbance)
+
+        t0: time of application (disturbance)
+
+        sigma: outspread (disturbance)
+
+        gamma: direction of disturbance
+
+        """
+        self.logger = Logger('PosturalMsucleSpaceController')
+        self.model = model
+        self.musclespace = musclespace
+        self.pd = PD(kp, kd)  # (10, 10) -> full controller, (0, 10) -> reflex
+        self.delay = delay
+        self.a = a
+        self.t0 = t0
+        self.sigma = sigma
+        self.gamma = gamma
+        self.reporter = SimulationReporter(model)
+        self.__initialize_delay_components()
+        self.__calculate_task()
+
+    def __calculate_task(self):
+        """Calculate model's end effector Jacobian transpose.
+
+        """
+        xt = sp.Matrix(self.model.ee)
+        Jt = xt.jacobian(self.model.Q())
+        self.JtT = Jt.transpose()
+
+    def __add_in_distrurbance(self, t, tau, pose):
+        """Adds distrubance to end effector.
+
+        """
+        angle = self.gamma
+        o = np.asmatrix([[0], [0]])
+        fd = np.asmatrix([[gaussian(t, self.a, self.t0, self.sigma)], [0]])
+        fd = rotate(o, fd, angle)
+        return tau + self.JtT.subs(pose) * fd
+
+    def __initialize_delay_components(self):
+        """Initializes the delay components with the default muscle length (state(0))
+        and zero length velocities.
+
+        """
+        n = self.model.nd
+        m = self.model.md
+
+        state0 = self.model.state0
+        q = state0[:n]
+        u = state0[n:]
+        pose = self.model.model_parameters(q=q, u=u)
+        self.lm0 = self.model.lm.subs(pose)
+        self.lm0 = to_np_vec(self.lm0)
+
+        delay = np.full(m, self.delay)
+
+        self.lm_del = DelayArray(m, delay, self.lm0)
+        self.lmd_del = DelayArray(m, delay, np.zeros(m))
+
+    def controller(self, x, t, x0):
+        """ Controller.
+
+        Parameters
+        ----------
+
+        x: state
+        t: time
+        x0: initial state
+
+        """
+        self.logger.debug('Time: ' + str(t))
+        m = self.model.md
+        n = self.model.nd
+        q = np.array(x[:n])
+        u = np.array(x[n:])
+
+        # compute current muscle length and derivative
+        pose = self.model.model_parameters(q=q, u=u)
+        lm = self.model.lm.subs(pose)
+        lm = to_np_vec(lm)
+        lmd = self.model.lmd.subs(pose)
+        lmd = to_np_vec(lmd)
+
+        # compute target
+        lm_des = self.target(x, t, x0)
+        lmd_des = np.zeros(m)
+        lmdd_des = np.zeros(m)
+
+        # update delayed values and get current (must update first)
+        self.lm_del.add(t, lm)
+        self.lmd_del.add(t, lmd)
+
+        lm_del = np.array(self.lm_del.get_delayed())
+        lmd_del = np.array(self.lmd_del.get_delayed())
+
+        lmdd_des = sp.Matrix(self.pd.compute(
+            lm_del, lmd_del, lm_des, lmd_des, lmdd_des))
+
+        tau, fm = self.musclespace.calculate_force(lmdd_des, pose)
+        tau = self.__add_in_distrurbance(t, tau, pose)
+        tau = to_np_vec(tau)
+
+        # record
+        self.reporter.t.append(t)
+        self.reporter.q.append(q)
+        self.reporter.u.append(u)
+        self.reporter.lm.append(lm)
+        self.reporter.lm_d.append(lm_des)
+        self.reporter.fm.append(fm)
+        self.reporter.tau.append(tau)
+
+        return tau
+
+    def target(self, x, t, x0):
+        """Default muscle space target function.
+
+        Parameters
+        ----------
+
+        x: state
+        t: time
+        x0: initial state
+
+        """
+        return self.lm0