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

Switch to side-by-side view

--- a
+++ b/arm_model/projection.py
@@ -0,0 +1,225 @@
+import sympy as sp
+import numpy as np
+from logger import Logger
+from util import to_np_mat
+from analysis import construct_muscle_space_inequality
+from scipy.optimize import minimize
+
+# ------------------------------------------------------------------------
+# Task Space
+# ------------------------------------------------------------------------
+
+
+class TaskSpace:
+    """Task space EoM of a given task.
+
+    Assuming the following joint space convention:
+
+    M qddot + f = tau
+
+    task projection is given by:
+
+    ft = Lt (xddot - JDotQDot) + JtBarT f
+
+    Lt = (R M^{-1} R^T)^{-1}
+
+    JtBarT = Lt R M^{-1}
+
+    NtT = (I - JtT JtBarT)
+
+    and the resultant task space forces are given by:
+
+    tau = JtT ft + NtT f
+
+    """
+
+    def __init__(self, model, xt):
+        """ Constructor.
+
+        Parameters
+        ----------
+
+        model: model
+        xt: [d x 1] task positions
+
+        """
+        self.logger = Logger('TaskSpace')
+        self.model = model
+        self.xt = xt
+        self.__construct_task_space_variables()
+
+    def __construct_task_space_variables(self):
+        """Calculate task Jacobian Jt and JtDot * qDot
+
+        """
+        self.Jt = self.xt.jacobian(self.model.Q())
+        self.JtT = self.Jt.transpose()
+        JtDot = sp.diff(self.Jt, self.model.t)
+        self.JtDotQDot = JtDot * sp.Matrix(self.model.U())
+        # the derivative of a matrix with a vector is a rank 3 tensor (3D
+        # array), [dM/dq1, dM/dq2, ...]
+        self.JtTDq = sp.derive_by_array(self.JtT, self.model.Q())
+
+    def calculate_force(self, xddot, pose):
+        """Calculate task forces.
+
+        For a given end effector acceleration compute the joint space
+        torques:
+
+        ft = Lt (xddot - JDotQDot) + JtBarT f
+
+        tau = JtT ft + NtT f
+
+        Parameters
+        ----------
+
+        xddot: [2 x 1] a sympy Matrix containing the desired accelerations in 2D
+        pose: system constants, coordinates and speeds as dictionary
+
+        Returns
+        -------
+
+        (tau, ft): required torque and task forces to track the desired
+        acceleration
+
+        """
+        M = to_np_mat(self.model.M.subs(pose))
+        f = to_np_mat(self.model.f.subs(pose))
+        Jt = to_np_mat(self.Jt.subs(pose))
+        JtDotQDot = to_np_mat(self.JtDotQDot.subs(pose))
+        JtT = to_np_mat(self.JtT.subs(pose))
+
+        MInv = np.linalg.inv(M)
+        LtInv = Jt * MInv * JtT
+        Lt = np.linalg.pinv(LtInv)
+        JtBarT = Lt * Jt * MInv
+        NtT = np.asmatrix(np.eye(len(JtT))) - JtT * JtBarT
+
+        ft = Lt * (xddot - JtDotQDot) + JtBarT * f
+
+        return JtT * ft + 0 * NtT * f, ft
+
+    def x(self, pose):
+        """For a given pose (q) evaluate the task position.
+
+        Parameters
+        ----------
+
+        pose: dictionary of model parameters and q's
+
+        Returns
+        -------
+
+        x: sympy Matrix
+
+        """
+        return self.xt.subs(pose)
+
+    def u(self, pose, qdot):
+        """For a given pose (q, u) evaluate the task velocity.
+
+        Parameters
+        ----------
+
+        pose: dictionary of model parameters, q's and u's
+        qdot: an array of u(t)
+
+        Returns
+        ------
+
+        u: sympy Matrix
+
+        """
+        return self.Jt.subs(pose) * sp.Matrix(qdot)
+
+
+# ------------------------------------------------------------------------
+# Muscle Space
+# ------------------------------------------------------------------------
+
+
+class MuscleSpace:
+    """ Muscle space EoM.
+    """
+
+    def __init__(self, model, use_optimization=False):
+        """ Constructor
+
+        Parameters
+        ----------
+
+        model: model
+
+        """
+        self.logger = Logger('MuscleSpace')
+        self.model = model
+        self.use_optimization = use_optimization
+        self.Fmax = to_np_mat(self.model.Fmax)
+        self.x_max = np.max(self.Fmax)
+
+    def calculate_force(self, lmdd, pose):
+        """Calculate muscle forces.
+
+        For a given muscle length acceleration compuyte the muscle space EoM of
+        motion and required muscle force to track the goal acceleration.
+
+        fm_par = -Lm (lmdd - RDotQDot) - RBarT f
+
+        tau = -R^T fm_par
+
+        Parameters
+        ----------
+
+        lmdd: [m x 1] a sympy Matrix containing the desired muscle length
+        accelerations
+
+        pose: dictionary
+
+        Returns
+        -------
+
+        (tau, fm_par + fm_perp) required torque to track the desired acceleration
+
+        """
+
+        M = to_np_mat(self.model.M.subs(pose))
+        f = to_np_mat(self.model.f.subs(pose))
+        R = to_np_mat(self.model.R.subs(pose))
+        RT = R.transpose()
+        RDotQDot = to_np_mat(self.model.RDotQDot.subs(pose))
+
+        MInv = np.linalg.inv(M)
+        LmInv = R * MInv * RT
+        Lm = np.linalg.pinv(LmInv)
+        RBarT = np.linalg.pinv(RT)
+        NR = np.asmatrix(np.eye(len(RBarT)) - RBarT * RT)
+
+        fm_par = -Lm * (lmdd - RDotQDot) - RBarT * f
+
+        # Ensure fm_par > 0 not required for simulation, but for muscle analysis
+        # otherwise muscle forces will be negative. Since RT * NR = 0 the null
+        # space term does not affect the resultant torques.
+        m = fm_par.shape[0]
+        fm_0 = np.zeros((m, 1))
+        if self.use_optimization:
+            Z, B = construct_muscle_space_inequality(NR, fm_par, self.Fmax)
+
+            def objective(x):
+                return np.sum(x**2)
+
+            def inequality_constraint(x):
+                return np.array(B - Z * (x.reshape(-1, 1))).reshape(-1,)
+
+            x0 = np.zeros(m)
+            bounds = tuple([(-self.x_max, self.x_max) for i in range(0, m)])
+            constraints = ({'type': 'ineq', 'fun': inequality_constraint})
+            sol = minimize(objective, x0,  method='SLSQP',
+                           bounds=bounds,
+                           constraints=constraints)
+            fm_0 = sol.x.reshape(-1, 1)
+            if sol.success == False:
+                raise RuntimeError('Some muscles are too week for this action')
+
+        fm_perp = NR * fm_0
+
+        return -RT * fm_par, fm_par + fm_perp