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

Tekkotsu v5.1CVS
Generated Fri Mar 16 05:26:41 2012 by Doxygen 1.6.3