Code

Extensions. Compressed+media export improvements (see Bug #386664, Gather Resources...
[inkscape.git] / src / 2geom / sbasis-roots.cpp
index 52d3ef6a9dcc960526d5fba87a88b4e488f28bc8..95fd0cf3ba8e45c19da4b878537ad7f84ea5576e 100644 (file)
@@ -8,7 +8,7 @@
  *  multi-roots using bernstein method, one approach would be:
     sort c
     take median and find roots of that
-    whenever a segment lies entirely on one side of the median, 
+    whenever a segment lies entirely on one side of the median,
     find the median of the half and recurse.
 
     in essence we are implementing quicksort on a continuous function
@@ -44,15 +44,32 @@ also allow you to find intersections of multiple curves but require solving n*m
 #include <cmath>
 #include <map>
 
-#include "sbasis.h"
-#include "sbasis-to-bezier.h"
-#include "solver.h"
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/solver.h>
 
 using namespace std;
 
 namespace Geom{
 
-Interval bounds_exact(SBasis const &a) {
+/** Find the smallest interval that bounds a
+ \param a sbasis function
+ \returns inteval
+
+*/
+
+#ifdef USE_SBASIS_OF
+OptInterval bounds_exact(SBasisOf<double> const &a) {
+    Interval result = Interval(a.at0(), a.at1());
+    SBasisOf<double> df = derivative(a);
+    vector<double>extrema = roots(df);
+    for (unsigned i=0; i<extrema.size(); i++){
+        result.extendTo(a(extrema[i]));
+    }
+    return result;
+}
+#else
+OptInterval bounds_exact(SBasis const &a) {
     Interval result = Interval(a.at0(), a.at1());
     SBasis df = derivative(a);
     vector<double>extrema = roots(df);
@@ -61,9 +78,21 @@ Interval bounds_exact(SBasis const &a) {
     }
     return result;
 }
+#endif
+
+/** Find a small interval that bounds a
+ \param a sbasis function
+ \returns inteval
+
+*/
+// I have no idea how this works, some clever bounding argument by jfb.
+#ifdef USE_SBASIS_OF
+OptInterval bounds_fast(const SBasisOf<double> &sb, int order) {
+#else
+OptInterval bounds_fast(const SBasis &sb, int order) {
+#endif
+    Interval res(0,0); // an empty sbasis is 0.
 
-Interval bounds_fast(const SBasis &sb, int order) {
-    Interval res;
     for(int j = sb.size()-1; j>=order; j--) {
         double a=sb[j][0];
         double b=sb[j][1];
@@ -89,8 +118,19 @@ Interval bounds_fast(const SBasis &sb, int order) {
     return res;
 }
 
-Interval bounds_local(const SBasis &sb, const Interval &i, int order) {
-    double t0=i.min(), t1=i.max(), lo=0., hi=0.;
+/** Find a small interval that bounds a(t) for t in i to order order
+ \param sb sbasis function
+ \param i domain interval
+ \param order number of terms
+ \return interval
+
+*/
+#ifdef USE_SBASIS_OF
+OptInterval bounds_local(const SBasisOf<double> &sb, const OptInterval &i, int order) {
+#else
+OptInterval bounds_local(const SBasis &sb, const OptInterval &i, int order) {
+#endif
+    double t0=i->min(), t1=i->max(), lo=0., hi=0.;
     for(int j = sb.size()-1; j>=order; j--) {
         double a=sb[j][0];
         double b=sb[j][1];
@@ -124,7 +164,7 @@ Interval bounds_local(const SBasis &sb, const Interval &i, int order) {
          going from f(a)  up  to C with slope M takes at least time (C-f(a))/M
          From this we conclude there are no roots before a'=a+min((f(a)-c)/m,(C-f(a))/M).
          Do the same for b: compute some b' such that there are no roots in (b',b].
-         -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b']. 
+         -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b'].
   unfortunately, extra care is needed about rounding errors, and also to avoid the repetition of roots,
   making things tricky and unpleasant...
 */
@@ -135,8 +175,13 @@ static int upper_level(vector<double> const &levels,double x,double tol=0.){
     return(upper_bound(levels.begin(),levels.end(),x-tol)-levels.begin());
 }
 
+#ifdef USE_SBASIS_OF
 static void multi_roots_internal(SBasis const &f,
                                 SBasis const &df,
+#else
+static void multi_roots_internal(SBasis const &f,
+                                SBasis const &df,
+#endif
                                 std::vector<double> const &levels,
                                 std::vector<std::vector<double> > &roots,
                                 double htol,
@@ -145,7 +190,7 @@ static void multi_roots_internal(SBasis const &f,
                                 double fa,
                                 double b,
                                 double fb){
-    
+
     if (f.size()==0){
         int idx;
         idx=upper_level(levels,0,vtol);
@@ -155,7 +200,7 @@ static void multi_roots_internal(SBasis const &f,
         }
         return;
     }
-////usefull? 
+////usefull?
 //     if (f.size()==1){
 //         int idxa=upper_level(levels,fa);
 //         int idxb=upper_level(levels,fb);
@@ -186,11 +231,11 @@ static void multi_roots_internal(SBasis const &f,
         }
         return;
     }
-    
+
     int idxa=upper_level(levels,fa,vtol);
     int idxb=upper_level(levels,fb,vtol);
 
-    Interval bs = bounds_local(df,Interval(a,b));
+    Interval bs = *bounds_local(df,Interval(a,b));
 
     //first times when a level (higher or lower) can be reached from a or b.
     double ta_hi,tb_hi,ta_lo,tb_lo;
@@ -217,9 +262,9 @@ static void multi_roots_internal(SBasis const &f,
         if (bs.max()>0 && idxb>0)
             tb_lo=b+(levels.at(idxb-1)-fb)/bs.max();
     }
-    
+
     double t0,t1;
-    t0=std::min(ta_hi,ta_lo);    
+    t0=std::min(ta_hi,ta_lo);
     t1=std::max(tb_hi,tb_lo);
     //hum, rounding errors frighten me! so I add this +tol...
     if (t0>t1+htol) return;//no root here.
@@ -244,6 +289,29 @@ static void multi_roots_internal(SBasis const &f,
     }
 }
 
+/** Solve f(t)=c for several c at once.
+    \param f sbasis function
+    \param levels vector of 'y' values
+    \param htol, vtol 
+    \param a, b left and right bounds
+    \returns a vector of vectors, one for each y giving roots
+
+Effectively computes:
+results = roots(f(y_i)) for all y_i
+
+* algo: -compute f at both ends of the given segment [a,b].
+         -compute bounds m<df(t)<M for df on the segment.
+         let c and C be the levels below and above f(a):
+         going from f(a) down to c with slope m takes at least time (f(a)-c)/m
+         going from f(a)  up  to C with slope M takes at least time (C-f(a))/M
+         From this we conclude there are no roots before a'=a+min((f(a)-c)/m,(C-f(a))/M).
+         Do the same for b: compute some b' such that there are no roots in (b',b].
+         -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b'].
+  unfortunately, extra care is needed about rounding errors, and also to avoid the repetition of roots,
+  making things tricky and unpleasant...
+
+TODO: Make sure the code is "rounding-errors proof" and take care about repetition of roots!
+*/
 std::vector<std::vector<double> > multi_roots(SBasis const &f,
                                       std::vector<double> const &levels,
                                       double htol,
@@ -254,69 +322,18 @@ std::vector<std::vector<double> > multi_roots(SBasis const &f,
     std::vector<std::vector<double> > roots(levels.size(), std::vector<double>());
 
     SBasis df=derivative(f);
-    multi_roots_internal(f,df,levels,roots,htol,vtol,a,f(a),b,f(b));  
+    multi_roots_internal(f,df,levels,roots,htol,vtol,a,f(a),b,f(b));
 
     return(roots);
 }
 //-------------------------------------
 
-#if 0
-double Laguerre_internal(SBasis const & p,
-                         double x0,
-                         double tol,
-                         bool & quad_root) {
-    double a = 2*tol;
-    double xk = x0;
-    double n = p.size();
-    quad_root = false;
-    while(a > tol) {
-        //std::cout << "xk = " << xk << std::endl;
-        Linear b = p.back();
-        Linear d(0), f(0);
-        double err = fabs(b);
-        double abx = fabs(xk);
-        for(int j = p.size()-2; j >= 0; j--) {
-            f = xk*f + d;
-            d = xk*d + b;
-            b = xk*b + p[j];
-            err = fabs(b) + abx*err;
-        }
-        
-        err *= 1e-7; // magic epsilon for convergence, should be computed from tol
-        
-        double px = b;
-        if(fabs(b) < err)
-            return xk;
-        //if(std::norm(px) < tol*tol)
-        //    return xk;
-        double G = d / px;
-        double H = G*G - f / px;
-        
-        //std::cout << "G = " << G << "H = " << H;
-        double radicand = (n - 1)*(n*H-G*G);
-        //assert(radicand.real() > 0);
-        if(radicand < 0)
-            quad_root = true;
-        //std::cout << "radicand = " << radicand << std::endl;
-        if(G.real() < 0) // here we try to maximise the denominator avoiding cancellation
-            a = - std::sqrt(radicand);
-        else
-            a = std::sqrt(radicand);
-        //std::cout << "a = " << a << std::endl;
-        a = n / (a + G);
-        //std::cout << "a = " << a << std::endl;
-        xk -= a;
-    }
-    //std::cout << "xk = " << xk << std::endl;
-    return xk;
-}
-#endif
 
 void subdiv_sbasis(SBasis const & s,
-                   std::vector<double> & roots, 
+                   std::vector<double> & roots,
                    double left, double right) {
-    Interval bs = bounds_fast(s);
-    if(bs.min() > 0 || bs.max() < 0)
+    OptInterval bs = bounds_fast(s);
+    if(!bs || bs->min() > 0 || bs->max() < 0)
         return; // no roots here
     if(s.tailError(1) < 1e-7) {
         double t = s[0][0] / (s[0][0] - s[0][1]);
@@ -330,10 +347,35 @@ void subdiv_sbasis(SBasis const & s,
 
 // It is faster to use the bernstein root finder for small degree polynomials (<100?.
 
+std::vector<double> roots1(SBasis const & s) {
+    std::vector<double> res;
+    double d = s[0][0] - s[0][1];
+    if(d != 0) {
+        double r = s[0][0] / d;
+        if(0 <= r && r <= 1)
+            res.push_back(r);
+    }
+    return res;
+}
+
+/** Find all t s.t s(t) = 0
+ \param a sbasis function
+ \returns vector of zeros (roots)
+
+*/
 std::vector<double> roots(SBasis const & s) {
-    if(s.size() == 0) return std::vector<double>();
-    
-    return sbasis_to_bezier(s).roots();
+    switch(s.size()) {
+        case 0:
+            return std::vector<double>();
+        case 1:
+            return roots1(s);
+        default:
+        {
+            Bezier bz;
+            sbasis_to_bezier(bz, s);
+            return bz.roots();
+        }
+    }
 }
 
 };
@@ -347,4 +389,4 @@ std::vector<double> roots(SBasis const & s) {
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :