(* * Screws.m - a screw package for mathematica * * Richard Murray and Sudipto Sur * * Division of Engineering and Applied Science * California Institute of Technology * Pasadena, CA 91125 * * Screws.m is a Mathematica package for performing screw calculus. * It follows the treatment described in _A Mathematical Introduction to * Robotic Manipulation_, by R. M. Murray, Z. Li, and S. S. Sastry * (CRC Press, 1994). This package implements screw theory in * 3-dimensional Euclidean space (some functions work in n dimensions). * * Copyright (c) 1994, Richard Murray. * Permission is granted to copy, modify and redistribute this file, * provided that this header message is retained. * * Revision history: * * Version 1.1 (7 Dec 1991) * Initial implementation (as RobotLinks.m) * * Version 1.2 (10 Dec 1992) * Removed robot specific code, leaving screw theory operations only * Changed name to Screws.m to reflect new emphasis * Rewrote some functions to use features of Mma more effectively * * Version 1.2a (30 Jan 1995) * Modified TwistExp, TwistMagnitude and TwistAxis, 10/6/94, Jeff Wendlandt * ...Changed If[w.w != 0, ... to If[(MatchQ[w,{0,0,0}] || .., ... * If[w.w != 0,... does not work when w is a symbolic expression. * *) BeginPackage["Screws`", "Graphics`Shapes`"]; (* * Function usage * * Document all of the functions which are defined in this file. * This is the same line that appears in the printed documentation. *) AxisToSkew::usage=Skew::usage= "AxisToSkew[w] generates skew symmetric matrix given 3 vector w"; SkewToAxis::usage=UnSkew::usage= "SkewToAxis[S] extracts vector w from skewsymmetric matrix S"; SkewExp::usage= "SkewExp[w,(theta)] gives the matrix exponential of an axis w. Default value of Theta is 1."; RotationAxis::usage= "RotationAxis[R] finds the rotation axis given the rotation matrix R"; RotationQ::usage= "RotationQ[m] tests whether matrix m is a rotation matrix"; RPToHomogeneous::usage= "RPToHomogeneous[R,p] forms homogeneous matrix from rotation matrix R \ and position vector p"; TwistExp::usage= "TwistExp[xi,(Theta)] gives the matrix exponential of a twist xi. Default value of Theta is 1."; TwistToHomogeneous::usage= "TwistToHomogeneous[xi] converts xi from a 6 vector to a 4X4 matrix"; HomogeneousToTwist::usage= "HomogeneousToTwist[xi] converts xi from a 4x4 matrix to a 6 vector"; RigidOrientation::usage= "RigidOrientation[g] extracts rotation matrix R from g"; RigidPosition::usage= "RigidPosition[g] extracts position vector p from g"; RigidTwist::usage= "RigidTwist[g] extracts 6 vector xi from g"; TwistPitch::usage= "TwistPitch[xi] gives pitch of screw corresponding to a twist"; TwistAxis::usage= "TwistAxis[xi] gives axis of screw corresponding to a twist"; TwistMagnitude::usage= "TwistMagnitude[xi] gives magnitude of screw corresponding to a twist"; RigidAdjoint::usage= "RigidAdjoint[g] gives the adjoint matrix corresponding to g"; RigidInverse::usage= "RigidInverse[g] gives the inverse transformation of g"; PointToHomogeneous::usage= "PointToHomogeneous[q] gives the homogeneous representation of a point"; VectorToHomogeneous::usage= "VectorToHomogeneous[q] gives the homogeneous representation of a vector"; ScrewToTwist::usage= "ScrewToTwist[h, q, w] returns the twist coordinates of a screw"; DrawScrew::usage= "DrawScrew[q, w, h] generates a graphical description of a screw"; DrawFrame::usage= "DrawFrame[p, R] generates a graphical description of a coordinate frame"; ScrewSize::usage= "ScrewSize sets the length of a screw for DrawScrew"; AxisSize::usage= "AxisSize sets the length of an axis vector for DrawFrame"; (* Utility functions which might be useful in this context *) StackRows::usage="StackRows[Mat1,Mat2,..] stacks matrix rows together"; StackCols::usage="StackCols[Mat1,Mat2,...] stacks matrix columns together"; (* * Error messages * * Use the Mma error message facility so that we can turn off error * messages that we don't want to hear about * *) AxisToSkew::wrongD = "`1` is not a 3 vector."; SkewToAxis::notskewsymmetric = "`1` is not a skew symmetric matrix"; Screws::wrongDimensions = "`1`: Dimensions of input matrices incorrect."; Screws::notSquare = "`1`: Input matrix is not square."; Screws::notVector = "`1`: Input is not a vector."; (* Begin private section of the package *) Begin["`Private`"]; (* * Rotation matrices * * Operations on SO(n), the Lie group of rotation matrices. * *) (* Find the axis of a rotation matrix *) RotationAxis[R_] := Module[ {v, nr, nc, axis}, (* Check to make sure that our input makes sense *) If[Not[MatrixQ[R]] || ({nr, nc} = Dimensions[R]; nr != nc), Message[Screws::wrongDimensions, "RotationAxis"]; Return Null; ]; (* Construct a dummy vector to operate on *) v = Table[Unique["v"], {nc}]; (* First check for degenerate case: R = identity *) If[And @@ (R . v == v), Message[Screws::notUnique, "RotationAxis"]; axis = Table[0, {nc}]; axis[[1]] = 1; Return[axis]; ]; (* Otherwise, solve a linear equation to find the answer *) v /. Solve[R . v == v, v][[1]] /. Map[# -> 1&, v] ]; (* Generate a skew symmetric matrix from an axis *) (*! This only works in R^3 for now !*) Skew[w_] := AxisToSkew[w]; (* backwards compatibility *) AxisToSkew[omega_?VectorQ]:= Module[ {}, (* Check to make sure the dimensions are okay *) If[Not[VectorQ[omega]] || Length[omega] != 3, Message[Screws::wrongDimension]; Return Null; ]; (* Return the appropriate matrix *) {{ 0, -omega[[3]], omega[[2]]}, { omega[[3]], 0, -omega[[1]]}, {-omega[[2]], omega[[1]], 0 }} ]; (* Generate an axis from a skew symmetric matrix *) UnSkew[S_] := SkewToAxis[S]; (* for compatibility *) SkewToAxis[S_]:= Module[ {}, (* First check to make sure we have a skew symmetric matrix *) If[Not[skewQ[S]] || Dimensions[S] != {3,3}, Message[Screws::wrongDimension]; Return Null ]; (* Now extract the appropriate component *) {S[[3,2]], S[[1,3]], S[[2,1]]} ]; (* Matrix exponential for a skew symmetric matrix *) SkewExp[v_?VectorQ,theta_:1] := SkewExp[AxisToSkew[v],theta]; SkewExp[S_?skewQ,theta_:1]:= Module[ {n = Dimensions[S][[1]]}, (* Use Rodrigues's formula *) IdentityMatrix[3] + Sin[theta] S + (1 - Cos[theta]) S.S ]; (* * Homogeneous representation * * These functions convert back and forth from elements of SE(3) * and their homogeneous representations. * *) (* Convert a rotation + translation to a homogeneous matrix *) RPToHomogeneous[R_, p_] := Module[ {n}, (* Check to make sure the dimensions of the args make sense *) If[Not[VectorQ[p]] || Not[MatrixQ[R]] || (n = Length[p]; Dimensions[R] != {n, n}), Message[Screws::wrongDimensions, "RPToHomogeneous:"]; ]; (* Now put everything together into a homogeneous transformation *) StackCols[ StackRows[R, zeroMatrix[1, n]], StackRows[p, {1}] ] ]; (* Convert a point into homogeneous coordinates *) PointToHomogeneous[p_] := Block[{}, (* Check to make sure the dimensions of the args make sense *) If[Not[VectorQ[p]], Message[Screws::notVector, "PointToHomogeneous"]]; (* Now put everything together into a homogeneous vector *) StackRows[p, {1}] ]; (* Convert a vector into homogeneous coordinates *) VectorToHomogeneous[p_] := Block[{}, (* Check to make sure the dimensions of the args make sense *) If[Not[VectorQ[p]], Message[Screws::notVector, "VectorToHomogeneous"]]; (* Now put everything together into a homogeneous vector *) StackRows[p, {0}] ]; (* Extract the orientation portion from a homogeneous transformation *) RigidOrientation[g_?MatrixQ]:= Module[ {nr, nc}, (* Check to make sure that we were passed a square matrix *) If[Not[MatrixQ[g]] || ({nr, nc} = Dimensions[g]; nr != nc) || nr < 3, Message[Screws::wrongDimensions, "RigidOrientation"]; Return Null; ]; (* Extract the upper left corner *) extractSubMatrix[g, {1,nr-1}, {1,nc-1}] ]; (* Extract the orientation portion from a homogeneous transformation *) RigidPosition[g_?MatrixQ]:= Module[ {nr, nc}, (* Check to make sure that we were passed a square matrix *) If[Not[MatrixQ[g]] || ({nr, nc} = Dimensions[g]; nr != nc) || nr < 3, Message[Screws::wrongDimensions, "RigidPosition"]; Return Null; ]; (* Extract the upper left column *) Flatten[extractSubMatrix[g, {1,nr-1}, {nc}]] ]; (* * Twists * * Functions for manipulating twists and converting rigid body * transformations back and forth from twists. * *) (* Figure out the dimension of a twist [private] *) xidim[xi_?VectorQ] := Module[ {l = Length[xi], n}, (* Check the dimensions of the vector to make sure everything is okay *) n = (Sqrt[1 + 8l] - 1)/2; If[Not[IntegerQ[n]], Message[Screws::wrongDimensions, "xidim"]; Return 0; ]; n ]; (* Extract the angular portion of a twist [private] *) xitow[xi_?VectorQ] := Module[ {n = xidim[xi]}, (* Make sure that the vector had a reasonable length *) If[n == 0, Return Null]; (* Extract the angular portion of the twist *) Take[xi, -(n (n-1) / 2)] ]; (* Extract the linear portion of a twist [private] *) xitov[xi_?VectorQ] := Module[ {n = xidim[xi]}, (* Make sure that the vector had a reasonable length *) If[n == 0, Return Null]; (* Extract the linear portion of the twist *) Take[xi, n] ]; (* Check to see if a matrix is a twist matrix *) (*! Not implemented !*) TwistMatrixQ[A_] := MatrixQ[A]; (* Convert a homogeneous matrix to a twist *) (*! This only works in dimensions 2 and 3 for now !*) HomogeneousToTwist[A_] := Module[ {nr, nc}, (* Check to make sure that our input makes sense *) If[Not[MatrixQ[A]] || ({nr, nc} = Dimensions[A]; nr != nc), Message[Screws::wrongDimensions, "HomogeneousToTwist"]; Return Null; ]; (* Make sure that we have a twist and not a rigid motion *) If[A[[nr,nc]] != 0, Message[Screws::notTwistMatrix, "HomogeneousToTwist"]; Return Null; ]; (* Extract the skew part and the vector part and make a vector *) Join[ Flatten[extractSubMatrix[A, {1,nr-1}, {nc}]], SkewToAxis[ extractSubMatrix[A, {1,nr-1}, {1,nc-1}] ] ] ]; (* Convert a twist to homogeneous coordinates *) TwistToHomogeneous[xi_?VectorQ] := Module[ {w = xitow[xi], v = xitov[xi], R, p}, (* Make sure that we got a real twist *) If[w == Null || v == NULL, Return Null]; (* Now put everything together into a homogeneous transformation *) StackCols[ StackRows[AxisToSkew[w], zeroMatrix[1, Length[w]]], StackRows[v, {0}] ] ]; (* Take the exponential of a twist *) (*! This only works in dimension 3 for now !*) TwistExp[xi_?MatrixQ, theta_:1]:=TwistExp[HomogeneousToTwist[xi], theta]; TwistExp[xi_?VectorQ, theta_:1] := Module[ {w = xitow[xi], v = xitov[xi], R, p}, (* Make sure that we got a real twist *) If[w == Null || v == NULL, Return Null]; (* Use the exponential formula from MLS *) If [(MatchQ[w,{0,0,0}] || MatchQ[w, {{0},{0},{0}}]), R = IdentityMatrix[3]; p = v * theta, (* else *) R = SkewExp[w, theta]; p = (IdentityMatrix[3] - R) . (AxisToSkew[w] . v) + w (w.v) theta; ]; RPToHomogeneous[R, p] ]; (* Find the twist which generates a rigid motion *) RigidTwist[g_?MatrixQ] := Module[ {R, p, axis, v, theta}, (* Make sure the dimensions are okay *) (*! Missing !*) (* Extract the appropriate pieces of the homogeneous transformation *) R = RigidOrientation[g]; p = RigidPosition[g]; (* Now find the axis from the rotation *) w = RotationAxis[R]; theta = RotationAngle[R]; (* Split into cases depending on whether theta is zero *) If[theta == 0, theta = Magnitude[p]; v = p/Magnitude[p]; w = 0; (* else *) (* Solve a linear equation to figure out what v is *) v = LinearSolve[ (IdentityMatrix[3]-Outer[Times,w,w]) Sin[theta] + Skew[w] (1 - Cos[theta]) + Outer[Times,w,w] theta, p] ]; Flatten[{v, w}] ]; (* * Geometric attributes of twists and wrenches. * * For twists in R^3, find the attributes of that twist. * * Wrench attributes are defined by switching the role of linear * and angular portions *) (* Build a twist from a screw *) ScrewToTwist[Infinity, q_, w_] := Join[w, {0,0,0}]; ScrewToTwist[h_, q_, w_] := Join[-AxisToSkew[w] . q + h w, w] (* Find the pitch associated with a twist in R^3 *) TwistPitch[xi_?VectorQ] := Module[ {v, w}, {v, w} = Partition[xi, 3]; v . w / w.w ]; WrenchPitch[xi_?VectorQ] := Null; (* Find the axis of a twist *) TwistAxis[xi_?VectorQ] := Module[ {v, w}, {v, w} = Partition[xi, 3]; If[(MatchQ[w,{0,0,0}] || MatchQ[w, {{0},{0},{0}}]), {0, v / Sqrt[v.v]}, {AxisToSkew[w] . v / w.w, (w / w.w)}] ]; WrenchAxis[xi_?VectorQ] := Null; (* Find the magnitude of a twist *) TwistMagnitude[xi_?VectorQ] := Module[ {v, w}, {v, w} = Partition[xi, 3]; If[(MatchQ[w,{0,0,0}] || MatchQ[w, {{0},{0},{0}}]), Sqrt[v.v], Sqrt[w.w]] ]; WrenchMagnitude[xi_?VectorQ] := Null; (* Inverse matrix calculation *) (*! This only works in R^3 !*) RigidInverse[g_?MatrixQ] := Module[ {R = RigidOrientation[g], p = RigidPosition[g]}, RPToHomogeneous[Transpose[R], -Transpose[R].p] ]; (* * Adjoint calculation * * The adjoint matrix maps twist vectors to twist vectors. * *) (* Adjoint matrix calculation *) (*! This only works in R^3 !*) RigidAdjoint[g_?MatrixQ] := Module[ {R = RigidOrientation[g], p = RigidPosition[g]}, StackRows[ StackCols[R, AxisToSkew[p] . R], StackCols[zeroMatrix[3], R] ] ]; (* * Predicate (query) functions * * Define predicates to test for the various types of objects which * occur in Screw theory. * * RotationQ rotation matrix * skewQ skew symmetric [private] *) (* check to see if a matrix is a rotation matrix (any dimension) *) RotationQ[mat_] := Module[ {nr, nc, zmat}, (* First check to make sure that this is square matrix *) If[Not[MatrixQ[mat]] || ({nr, nc} = Dimensions[mat]; nr != nc), Message[Screws::notSquare]; Return[False]]; (* Check to see if R^T R = Identity *) zmat = Simplify[mat . Transpose[mat]] - IdentityMatrix[nr]; Return[ And @@ Map[TrueQ[Simplify[#] == 0]&, Flatten[zmat]]] ]; skewQ[mat_] := Module[ {nr, nc, zmat}, (* First check to make sure that this is square matrix *) If[Not[MatrixQ[mat]] || ({nr, nc} = Dimensions[mat]; nr != nc), Message[Screws::notSquare]; Return[False]]; (* Check to see if A = -A^T *) zmat = mat + Transpose[mat]; Return[ And @@ Map[TrueQ[Simplify[#] == 0]&, Flatten[zmat]]] ]; (* * Graphics functions * * DrawScrew generate a graphical representation of a screw * *) (* Define default options for screws *) Options[DrawScrew] = {ScrewSize->2} (* Draw a screw through the point q, in direction w, with pitch h *) DrawScrew[q_, w_, h_:0, opts___Rule] := Module[ {x, y, z, R, axis, tip}, (* Generate the rotation to get things to align with the tip *) z = w / Sqrt[w.w]; y = NullSpace[{z}][[1]]; y = y / Sqrt[y.y]; x = AxisToSkew[y] . z; R = Transpose[{x,y,z}]; (* Generate the arrow for the tip *) tip := TranslateShape[ Graphics3D[ Cone[ScrewSize/20, ScrewSize/20, 10] /. {Polygon[list_] :> Polygon[Map[R . #&, list]]} ], q + ScrewSize * w]; (* Generate the axis of the screw *) axis = Graphics3D[ Point[q], Thickness[0.01 * ScrewSize/2], Line[{q, q + ScrewSize * w}] ]; (* Put everything together as a list of graphics objects *) Flatten[{axis, tip} /. {opts} /. Options[DrawScrew]] ]; (* Draw a coordinate frame at a point *) Options[DrawFrame] = {AxisSize->1} (* Draw a coordinate frame at the appropriate point *) DrawFrame[p_, R_, opts___Rule] := Flatten[{Graphics3D[ Thickness[0.01], Line[{p, p + R[[1]] * AxisSize}], Line[{p, p + R[[2]] * AxisSize}], Line[{p, p + R[[3]] * AxisSize}] ]} /. {opts} /. Options[DrawFrame]]; (* * Utility functions for stacking rows, cols, + other matrix operations * * StackRows stack rows of matrices together * StackCols stack cols of matrices together * zeroMatrix create a matrix of zeros [private] * extractSubMatrix pick out pieces of a matrix [private] *) (* Stack matrix columns together *) StackCols[mats__] := Block[ {i,j}, Table[ Join[ Flatten[Table[{mats}[[j]][[i]], {j,Length[{mats}]}], 1] ], {i, Length[ {mats}[[1]] ]}] ]; (* Stack matrix rows together *) StackRows[mats__] := Join[Flatten[{mats}, 1]]; (* Create matrices of zeros *) zeroMatrix[nr_, nc_] := Table[0, {nr}, {nc}]; zeroMatrix[nr_] := zeroMatrix[nr, nr]; (* Extract a submatrix from a bigger matrix *) extractSubMatrix[A_, rows_, cols_] := Map[Take[#, cols]&, Take[A, rows]]; (* Close up the open environments *) End[]; EndPackage[]; (* End Screws.m *)