Tekkotsu Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

IKGradientSolver.cc

Go to the documentation of this file.
00001 #include "IKGradientSolver.h"
00002 #include "Shared/fmat.h"
00003 #include <cmath>
00004 #include <valarray>
00005 
00006 using namespace std;
00007 
00008 const std::string IKGradientSolver::autoRegisterIKGradientSolver = IKSolver::getRegistry().registerType<IKGradientSolver>("IKGradientSolver");
00009 const std::string IKGradientSolver::autoRegisterDefaultIKSolver = IKSolver::getRegistry().registerType<IKGradientSolver>("");
00010 
00011 bool IKGradientSolver::solve(const Point& pEff, const Rotation& oriEff, 
00012            KinematicJoint& j, const Position& pTgt, float posPri, 
00013            const Orientation& oriTgt, float oriPri) const {
00014   if (posPri==0 && oriPri==0)
00015     return true;
00016 
00017   posPri=std::max(0.f,posPri);
00018   oriPri=std::max(0.f,oriPri);
00019   
00020   KinematicJoint* firstMobileJoint = NULL;
00021   for(KinematicJoint *ancestor = &j; ancestor!=NULL; ancestor = ancestor->getParent())
00022     if (ancestor->isMobile())
00023       firstMobileJoint = ancestor;
00024   if (firstMobileJoint==NULL)
00025     return false;
00026   
00027   // choose pDist by heuristic: proportional to distance between effector and base frame
00028   fmat::Transform Tj = j.getT(*firstMobileJoint);
00029   fmat::Column<3> pEffFMJ(Tj*pEff);
00030   fmat::fmatReal pDist = pEffFMJ.norm()/10;
00031   
00032   // rotation is scale invariant, just start with 1
00033   fmat::fmatReal oriDist = 1;
00034   
00035   std::valarray<fmat::fmatReal> q(j.getDepth()+1);
00036   for(KinematicJoint * ancestor = &j; ancestor!=NULL; ancestor = ancestor->getParent())
00037     q[ancestor->getDepth()] = ancestor->getQ();
00038   std::valarray<fmat::fmatReal> dq, ndq(j.getDepth()+1);
00039   unsigned int iterCnt=0, overCnt=0;
00040   bool solved=false;
00041   while(iterCnt<MAX_ITER) {
00042     IKSolver::StepResult_t res = step(pEff,oriEff,j,pTgt,pDist,posPri,oriTgt,oriDist,oriPri,false);
00043     if (res==SUCCESS) {
00044       solved=true;
00045       break;
00046     }
00047     if (res!=PROGRESS) {
00048       std::cout << "Result " << res << std::endl;
00049       break;
00050     }
00051     for(KinematicJoint * ancestor = &j; ancestor!=NULL; ancestor = ancestor->getParent()) {
00052       ndq[ancestor->getDepth()] = ancestor->getQ() - q[ancestor->getDepth()];
00053       q[ancestor->getDepth()] = ancestor->getQ();
00054     }
00055     fmat::fmatReal qa=1;
00056     if (dq.size()==0) {
00057       dq.resize(j.getDepth()+1);
00058       dq=ndq;
00059     } else {
00060       qa = (dq*ndq).sum();
00061       std::swap(dq,ndq);
00062     }
00063     // only need abs on min: if negative, it will be more so
00064     if ( std::max(std::abs(dq.max()), std::abs(dq.min())) < QTOL ) {
00065       std::cout << "Converged" << std::endl;
00066       break; // not actually making progress... probably out of range
00067     }
00068     if (qa<0) {
00069       // this step reversed direction of previous
00070       // step, probably overshot target, so cut
00071       // distance in half for next try
00072       pDist *= 0.75f;
00073       oriDist *= 0.75f;
00074       ++overCnt;
00075     }/* else {
00076      // more gradual convergence
00077      pDist*=.9;
00078      oriDist*=.9;
00079      }*/
00080     ++iterCnt;
00081   }
00082   cout << "Iterations: " << iterCnt << "  Overshoots: " << overCnt << endl;
00083   return solved;
00084 }
00085 
00086 IKSolver::StepResult_t IKGradientSolver::step(const Point& pEff, const Rotation& oriEff,
00087                 KinematicJoint& j, const Position& pTgt, float pDist, float posPri,
00088                 const Orientation& oriTgt, float oriDist, float oriPri, 
00089                 bool successInReach) const {
00090   if ((posPri<=0 && oriPri<=0) || (pDist<=0 && oriDist<=0))
00091     return SUCCESS;
00092   
00093   posPri=std::max(0.f,posPri);
00094   oriPri=std::max(0.f,oriPri);
00095   pDist=std::max(0.f,pDist);
00096   oriDist=std::max(0.f,oriDist);
00097   
00098   const float totPri = posPri+oriPri;
00099   posPri/=totPri;
00100   oriPri/=totPri;
00101   
00102   // if we run out of joints to move, then we'll return joint-limits-hit
00103   StepResult_t result = LIMITS;
00104   
00105   // only work with joints for which qmin≠qmax (i.e. mobile joints)
00106   std::vector<int> mobile;
00107   mobile.reserve(j.getDepth()+1);
00108   for(KinematicJoint * ancestor=&j; ancestor!=NULL; ancestor=ancestor->getParent()) {
00109     if (ancestor->isMobile())
00110       mobile.push_back(ancestor->getDepth());
00111     //cout << "Joint " << ancestor->getDepth() << " @ " << ancestor->getQ() << endl;
00112     //cout << ancestor->getFullT() << endl;
00113   }
00114   std::reverse(mobile.begin(),mobile.end());
00115   
00116   // as we hit joint limits, we'll resolve with the remaining step distance:
00117   fmat::fmatReal distRemain = pDist;
00118   fmat::fmatReal angRemain = oriDist;
00119   
00120   while(mobile.size()>0) {
00121     
00122     fmat::Transform Tj = j.getFullT();
00123     fmat::Column<3> pEffBase(Tj*pEff);
00124     fmat::Quaternion qj = j.getWorldQuaternion(); //cout << "WorldQ: " << qj;
00125     Rotation oEffBase = qj*oriEff;
00126     
00127     fmat::Column<3> pErr;
00128     pTgt.computeErrorGradient(pEffBase,oEffBase,pErr);
00129     
00130     fmat::Quaternion oriErr;
00131     oriTgt.computeErrorGradient(pEffBase,oEffBase,oriErr);
00132     oriErr.normalize(); // importantly, this flips axis as needed to ensure positive angle, which is assumed later
00133     
00134     fmat::fmatReal pErrMagnitude = pErr.norm();
00135     fmat::fmatReal oriErrMagnitude = std::abs(oriErr.angle());
00136     // cout << "\nCurrent: " << oEffBase << ' ' << oEffBase.angle() << ' ' << oEffBase.axis() << endl;
00137     // cout << "Remain: " << oriErr << ' ' << oriErrMagnitude << ' ' << angRemain << ' ' << oriErrMagnitude/angRemain << endl;
00138     if (pErrMagnitude/distRemain*posPri < .001 && oriErrMagnitude/angRemain*oriPri < .001) {
00139       //cout << "Nailed it!" << endl;
00140       return SUCCESS;
00141     }
00142     if (pErrMagnitude*posPri < PTOL && oriErrMagnitude*oriPri < OTOL) {
00143       //cout << "Close enough! " << pErrMagnitude << ' ' << posPri << ' ' << PTOL << endl;
00144       return SUCCESS;
00145     }
00146     if (pErrMagnitude<distRemain && oriErrMagnitude<angRemain) {
00147       distRemain=pErrMagnitude;
00148       angRemain=oriErrMagnitude;
00149       if (successInReach)
00150   result = SUCCESS; // we'll still take a step, but we're basically there
00151     } else if (pErrMagnitude<distRemain) {
00152       distRemain=pErrMagnitude;
00153       if (oriPri==0 && successInReach)
00154   result = SUCCESS;
00155     } else if (oriErrMagnitude<angRemain) {
00156       angRemain=oriErrMagnitude;
00157       if (posPri==0 && successInReach)
00158   result = SUCCESS;
00159     }
00160     
00161     std::vector<fmat::Column<6> > J;
00162     j.getFullJacobian(pEffBase,J);
00163     //cout << "Jacobian\n"; for(unsigned int i=0; i<J.size(); ++i) std::cout << J[i] << '\n';
00164     std::vector<fmat::Column<6> > Jm(mobile.size());
00165     for(size_t i=0; i<mobile.size(); ++i)
00166       Jm[i] = J[mobile[i]];
00167     //cout << "Jacobian mobile only\n"; for(unsigned int i=0; i<Jm.size(); ++i) std::cout << Jm[i] << '\n';
00168     
00169     std::valarray<fmat::fmatReal> q(mobile.size());
00170     
00171     if (posPri>0 && distRemain>0) {
00172       // need to find pseudo-inverse of J:
00173       
00174       // use transposed jacobian as inverse (see "virtual work" -- there is some rationale why this works!)
00175       // basically we're simulating a force "pulling" on the effector
00176       
00177       // find candidate q's for mobile joints regarding positional error
00178       // multiplying the Jm transpose by the error, aka take dot product of each column with error
00179       std::valarray<fmat::fmatReal> posQ(Jm.size());
00180       for(unsigned int i=0; i<posQ.size(); ++i) {
00181   posQ[i]=fmat::dotProduct(fmat::SubVector<3>(Jm[i]),pErr);
00182       }
00183       
00184       // when joints are aligned, each tries to take the full step, so we overshoot
00185       // so normalize q's by the size of the step they would produce (according to jacobian anyway...),
00186       // and then scale by the size of the step we wanted to take
00187       fmat::Column<3> move;
00188       for(unsigned int i=0; i<posQ.size(); ++i)
00189   move+=fmat::SubVector<3>(Jm[i])*posQ[i];
00190       
00191       fmat::fmatReal moveSize = move.norm();
00192       if (moveSize > std::numeric_limits<float>::epsilon())
00193   posQ *= distRemain / moveSize;
00194       
00195       // if we're only moving to solve positional error, see if we're at a local minimum (not making progress)
00196       if (oriErrMagnitude*oriPri < OTOL) {
00197   //std::cout << "err " << pErr << ' ' << pErrMagnitude << " move " << move << ' ' << moveSize << std::endl;
00198   // if we're moving perpendicular to the target, we're done... (avoid twitching)
00199   fmat::fmatReal moveCos = fmat::dotProduct(pErr/pErrMagnitude, move/moveSize);
00200   if (moveCos < QTOL) {
00201     //std::cout << "Move cos " << moveCos << std::endl;
00202     return RANGE;
00203   }
00204   //cout << "progress angle: " << moveCos << ' ' << std::acos(moveCos) << endl;
00205   //cout << "progress percent: " << moveCos*distRemain / pErrMagnitude << endl;
00206   fmat::fmatReal posqsum=0;
00207   for(unsigned int i=0; i<posQ.size(); ++i)
00208     posqsum+=posQ[i]*posQ[i];
00209   if (std::sqrt(posqsum) < std::numeric_limits<float>::epsilon()) {
00210     //std::cout << "Move cos " << moveCos << " posqsum " << posqsum << std::endl;
00211     return RANGE;
00212   }
00213       }
00214       
00215       // combine weighted factors:
00216       posQ*=posPri; // posPri and oriPri are normalized at the top so we don't divide by (posPri+oriPri)
00217       q += posQ;
00218     }
00219     
00220     if (oriPri>0 && angRemain>0) {
00221       // orientation correction movment:
00222       std::valarray<fmat::fmatReal> oriQ(Jm.size());
00223       fmat::Column<3> oriErrV = oriErr.axis();
00224       // std::cerr << "oriErr " << oriErr << "  " << oriErr.angle() << " " << oriErrV << std::endl;
00225       for(unsigned int i=0; i<oriQ.size(); ++i) {
00226   oriQ[i] = fmat::dotProduct(fmat::SubVector<3>(Jm[i],3),oriErrV);
00227   //std::cout << oriQ[i] << ' ';
00228       }
00229       
00230       // normalization for desired magnitude of orientation movement:
00231       fmat::fmatReal rotSize=0;
00232       for(unsigned int i=0; i<oriQ.size(); ++i)
00233   rotSize+=oriQ[i]*oriQ[i];
00234       rotSize=std::sqrt(rotSize);
00235       /*for(unsigned int i=0; i<oriQ.size(); ++i)
00236   rotSize+=std::abs(oriQ[i]);*/
00237       // std::cout << " * " << oriDist << " * " << angRemain << " / " << rotSize << std::endl;
00238       if (rotSize > std::numeric_limits<float>::epsilon())
00239   oriQ *= oriDist * angRemain / rotSize;
00240       
00241       // combine weighted factors:
00242       oriQ*=oriPri; // posPri and oriPri are normalized at the top so we don't divide by (posPri+oriPri)
00243       q += oriQ; 
00244     }
00245     
00246     // check for range limits:
00247     size_t prevSize=mobile.size();
00248     KinematicJoint * ancestor=&j;
00249     for(int i=mobile.size()-1; i>=0; --i) {
00250       while(ancestor!=NULL && ancestor->getDepth()>static_cast<typeof(ancestor->getDepth())>(mobile[i]))
00251   ancestor=ancestor->getParent();
00252       q[i]+=ancestor->getQ();
00253       if (!ancestor->validQ(q[i])) {
00254   // if a joint hits the limit, fix it there (then we'll loop over again with the remaining joints in a bit)
00255   // cout << "mobile " << i << " (" << mobile[i] << ") is past limit" << endl;
00256   ancestor->tryQ(q[i]);
00257   std::vector<int>::iterator it=mobile.begin();
00258   std::advance(it,i);
00259   mobile.erase(it);
00260       }
00261     }
00262     if (mobile.size()<prevSize) {
00263       // some joint(s) hit their limit, find distance remaining and we'll try again
00264       fmat::Column<3> newpEffBase(j.getFullT()*pEff);
00265       distRemain -= (newpEffBase - pEffBase).norm();
00266       
00267       fmat::Quaternion newoEffBase = j.getWorldQuaternion()*oriEff;
00268       fmat::Quaternion angMoved = crossProduct(newoEffBase, oEffBase);
00269       // std::cout << "REMAIN: " << angMoved.angle() << " of " << angRemain << ", dist " << distRemain << std::endl;
00270       angRemain -= std::abs(angMoved.angle());
00271       // std::cout << "NOW " << angRemain << std::endl;
00272 
00273       if (angRemain<=0 && distRemain<=0)
00274   return PROGRESS;
00275       if (distRemain<std::numeric_limits<float>::epsilon())
00276   distRemain=std::numeric_limits<float>::epsilon();
00277       if (angRemain<std::numeric_limits<float>::epsilon())
00278   angRemain=std::numeric_limits<float>::epsilon();
00279     } else {
00280       // no joints hit the limit, so we're done with the step!
00281       ancestor=&j;
00282       for(int i=mobile.size()-1; i>=0; --i) {
00283   while(ancestor!=NULL && ancestor->getDepth()>static_cast<typeof(ancestor->getDepth())>(mobile[i]))
00284     ancestor=ancestor->getParent();
00285   ancestor->setQ(q[i]);
00286       }
00287       return (result==LIMITS) ? PROGRESS : result;
00288     }
00289   }
00290   return result;
00291 }
00292 
00293 /*! @file
00294  * @brief Implements IKGradientSolver, which performs gradient descent on the joints to find a solution
00295  * @author Ethan Tira-Thompson (ejt) (Creator)
00296  */

Tekkotsu v5.1CVS
Generated Sat May 4 06:32:50 2013 by Doxygen 1.6.3