/** * @file * @brief Root finding for sbasis functions. *//* * Authors: * Nathan Hurst * JF Barraud * Copyright 2006-2007 Authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * */ /* * It is more efficient to find roots of f(t) = c_0, c_1, ... all at once, rather than iterating. * * Todo/think about: * 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, find the median of the half and recurse. in essence we are implementing quicksort on a continuous function * the gsl poly roots finder is faster than bernstein too, but we don't use it for 3 reasons: a) it requires conversion to poly, which is numerically unstable b) it requires gsl (which is currently not a dependency, and would bring in a whole slew of unrelated stuff) c) it finds all roots, even complex ones. We don't want to accidentally treat a nearly real root as a real root From memory gsl poly roots was about 10 times faster than bernstein in the case where all the roots are in [0,1] for polys of order 5. I spent some time working out whether eigenvalue root finding could be done directly in sbasis space, but the maths was too hard for me. -- njh jfbarraud: eigenvalue root finding could be done directly in sbasis space ? njh: I don't know, I think it should. You would make a matrix whose characteristic polynomial was correct, but do it by putting the sbasis terms in the right spots in the matrix. normal eigenvalue root finding makes a matrix that is a diagonal + a row along the top. This matrix has the property that its characteristic poly is just the poly whose coefficients are along the top row. Now an sbasis function is a linear combination of the poly coeffs. So it seems to me that you should be able to put the sbasis coeffs directly into a matrix in the right spots so that the characteristic poly is the sbasis. You'll still have problems b) and c). We might be able to lift an eigenvalue solver and include that directly into 2geom. Eigenvalues also allow you to find intersections of multiple curves but require solving n*m x n*m matrices. **/ #include #include #include <2geom/sbasis.h> #include <2geom/sbasis-to-bezier.h> #include <2geom/solver.h> using namespace std; namespace Geom{ /** Find the smallest interval that bounds a \param a sbasis function \returns interval */ #ifdef USE_SBASIS_OF OptInterval bounds_exact(SBasisOf const &a) { Interval result = Interval(a.at0(), a.at1()); SBasisOf df = derivative(a); vectorextrema = roots(df); for (unsigned i=0; iextrema = roots(df); for (double i : extrema){ result.expandTo(a(i)); } return result; } #endif /** Find a small interval that bounds a \param a sbasis function \returns interval */ // I have no idea how this works, some clever bounding argument by jfb. #ifdef USE_SBASIS_OF OptInterval bounds_fast(const SBasisOf &sb, int order) { #else OptInterval bounds_fast(const SBasis &sb, int order) { #endif Interval res(0,0); // an empty sbasis is 0. for(int j = sb.size()-1; j>=order; j--) { double a=sb[j][0]; double b=sb[j][1]; double v, t = 0; v = res.min(); if (v<0) t = ((b-a)/v+1)*0.5; if (v>=0 || t<0 || t>1) { res.setMin(std::min(a,b)); } else { res.setMin(lerp(t, a+v*t, b)); } v = res.max(); if (v>0) t = ((b-a)/v+1)*0.5; if (v<=0 || t<0 || t>1) { res.setMax(std::max(a,b)); }else{ res.setMax(lerp(t, a+v*t, b)); } } if (order>0) res*=std::pow(.25,order); return res; } /** 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 &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]; double t = 0; if (lo<0) t = ((b-a)/lo+1)*0.5; if (lo>=0 || tt1) { lo = std::min(a*(1-t0)+b*t0+lo*t0*(1-t0),a*(1-t1)+b*t1+lo*t1*(1-t1)); }else{ lo = lerp(t, a+lo*t, b); } if (hi>0) t = ((b-a)/hi+1)*0.5; if (hi<=0 || tt1) { hi = std::max(a*(1-t0)+b*t0+hi*t0*(1-t0),a*(1-t1)+b*t1+hi*t1*(1-t1)); }else{ hi = lerp(t, a+hi*t, b); } } Interval res = Interval(lo,hi); if (order>0) res*=std::pow(.25,order); return res; } //-- multi_roots ------------------------------------ // goal: solve f(t)=c for several c at once. /* algo: -compute f at both ends of the given segment [a,b]. -compute bounds m 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 const &levels, std::vector > &roots, double htol, double vtol, double a, double fa, double b, double fb){ if (f.isZero(0)){ int idx; idx=upper_level(levels,0,vtol); if (idx<(int)levels.size()&&fabs(levels.at(idx))<=vtol){ roots[idx].push_back(a); roots[idx].push_back(b); } return; } ////useful? // if (f.size()==1){ // int idxa=upper_level(levels,fa); // int idxb=upper_level(levels,fb); // if (fa==fb){ // if (fa==levels[idxa]){ // roots[a]=idxa; // roots[b]=idxa; // } // return; // } // int idx_min=std::min(idxa,idxb); // int idx_max=std::max(idxa,idxb); // if (idx_max==levels.size()) idx_max-=1; // for(int i=idx_min;i<=idx_max; i++){ // double t=a+(b-a)*(levels[i]-fa)/(fb-fa); // if(a no root there. tb_hi=tb_lo=a-1;//default values => no root there. if (idxa<(int)levels.size() && fabs(fa-levels.at(idxa))0 && idxa<(int)levels.size()) ta_hi=a+(levels.at(idxa )-fa)/bs.max(); if (bs.min()<0 && idxa>0) ta_lo=a+(levels.at(idxa-1)-fa)/bs.min(); } if (idxb<(int)levels.size() && fabs(fb-levels.at(idxb))0 && idxb>0) tb_lo=b+(levels.at(idxb-1)-fb)/bs.max(); } double t0,t1; 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. if (fabs(t1-t0) > multi_roots(SBasis const &f, std::vector const &levels, double htol, double vtol, double a, double b){ std::vector > roots(levels.size(), std::vector()); SBasis df=derivative(f); multi_roots_internal(f,df,levels,roots,htol,vtol,a,f(a),b,f(b)); return(roots); } static bool compareIntervalMin( Interval I, Interval J ){ return I.min()= x static unsigned upper_level(vector const &levels, double x ){ return( lower_bound( levels.begin(), levels.end(), Interval(x,x), compareIntervalMax) - levels.begin() ); } static std::vector fuseContiguous(std::vector const &sets, double tol=0.){ std::vector result; if (sets.empty() ) return result; result.push_back( sets.front() ); for (unsigned i=1; i < sets.size(); i++ ){ if ( result.back().max() + tol >= sets[i].min() ){ result.back().unionWith( sets[i] ); }else{ result.push_back( sets[i] ); } } return result; } /** level_sets internal method. * algorithm: (~adaptation of Newton method versus 'accroissements finis') -compute f at both ends of the given segment [a,b]. -compute bounds m const &levels, std::vector > &solsets, double a, double fa, double b, double fb, double tol=1e-5){ if (f.isZero(0)){ unsigned idx; idx=upper_level( levels, 0. ); if (idxtb_hi double tb_lo; // f remains above next level for t>tb_lo ta_hi=ta_lo=b+1;//default values => no root there. tb_hi=tb_lo=a-1;//default values => no root there. //--- if f(a) belongs to a level.------- if ( idxa < levels.size() && levels[idxa].contains( fa ) ){ //find the first time when we may exit this level. ta_lo = a + ( levels[idxa].min() - fa)/bs.min(); ta_hi = a + ( levels[idxa].max() - fa)/bs.max(); if ( ta_lo < a || ta_lo > b ) ta_lo = b; if ( ta_hi < a || ta_hi > b ) ta_hi = b; //move to that time for the next iteration. solsets[idxa].push_back( Interval( a, std::min( ta_lo, ta_hi ) ) ); }else{ //--- if f(b) does not belong to a level.------- if ( idxa == 0 ){ ta_lo = b; }else{ ta_lo = a + ( levels[idxa-1].max() - fa)/bs.min(); if ( ta_lo < a ) ta_lo = b; } if ( idxa == levels.size() ){ ta_hi = b; }else{ ta_hi = a + ( levels[idxa].min() - fa)/bs.max(); if ( ta_hi < a ) ta_hi = b; } } //--- if f(b) belongs to a level.------- if (idxb b || tb_lo < a ) tb_lo = a; if ( tb_hi > b || tb_hi < a ) tb_hi = a; //move to that time for the next iteration. solsets[idxb].push_back( Interval( std::max( tb_lo, tb_hi ), b) ); }else{ //--- if f(b) does not belong to a level.------- if ( idxb == 0 ){ tb_lo = a; }else{ tb_lo = b + ( levels[idxb-1].max() - fb)/bs.max(); if ( tb_lo > b ) tb_lo = a; } if ( idxb == levels.size() ){ tb_hi = a; }else{ tb_hi = b + ( levels[idxb].min() - fb)/bs.min(); if ( tb_hi > b ) tb_hi = a; } if ( bs.min() < 0 && idxb < levels.size() ) tb_hi = b + ( levels[idxb ].min() - fb ) / bs.min(); if ( bs.max() > 0 && idxb > 0 ) tb_lo = b + ( levels[idxb-1].max() - fb ) / bs.max(); } //let [t0,t1] be the next interval where to search. double t0=std::min(ta_hi,ta_lo); double t1=std::max(tb_hi,tb_lo); if (t0>=t1) return;//no root here. //if the interval is smaller than our resolution: //pretend f simultaneously meets all the levels between f(t0) and f(t1)... if ( t1 - t0 <= tol ){ Interval f_t0t1 ( f(t0), f(t1) ); unsigned idxmin = std::min(idxa, idxb); unsigned idxmax = std::max(idxa, idxb); //push [t0,t1] into all crossed level. Cheat to avoid overlapping intervals on different levels? if ( idxmax > idxmin ){ for (unsigned idx = idxmin; idx < idxmax; idx++){ solsets[idx].push_back( Interval( t0, t1 ) ); } } if ( idxmax < levels.size() && f_t0t1.intersects( levels[idxmax] ) ){ solsets[idxmax].push_back( Interval( t0, t1 ) ); } return; } //To make sure we finally exit the level jump at least by tol: t0 = std::min( std::max( t0, a + tol ), b ); t1 = std::max( std::min( t1, b - tol ), a ); double t =(t0+t1)/2; double ft=f(t); level_sets_internal( f, df, levels, solsets, t0, f(t0), t, ft ); level_sets_internal( f, df, levels, solsets, t, ft, t1, f(t1) ); } std::vector > level_sets(SBasis const &f, std::vector const &levels, double a, double b, double tol){ std::vector > solsets(levels.size(), std::vector()); SBasis df=derivative(f); level_sets_internal(f,df,levels,solsets,a,f(a),b,f(b),tol); // Fuse overlapping intervals... for (auto & solset : solsets){ if ( solset.size() == 0 ) continue; std::sort( solset.begin(), solset.end(), compareIntervalMin ); solset = fuseContiguous( solset, tol ); } return solsets; } std::vector level_set (SBasis const &f, double level, double vtol, double a, double b, double tol){ Interval fat_level( level - vtol, level + vtol ); return level_set(f, fat_level, a, b, tol); } std::vector level_set (SBasis const &f, Interval const &level, double a, double b, double tol){ std::vector levels(1,level); return level_sets(f,levels, a, b, tol).front() ; } std::vector > level_sets (SBasis const &f, std::vector const &levels, double vtol, double a, double b, double tol){ std::vector fat_levels( levels.size(), Interval()); for (unsigned i = 0; i < levels.size(); i++){ fat_levels[i] = Interval( levels[i]-vtol, levels[i]+vtol); } return level_sets(f, fat_levels, a, b, tol); } //------------------------------------- //------------------------------------- void subdiv_sbasis(SBasis const & s, std::vector & roots, double left, double right) { 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]); roots.push_back(left*(1-t) + t*right); return; } double middle = (left + right)/2; subdiv_sbasis(compose(s, Linear(0, 0.5)), roots, left, middle); subdiv_sbasis(compose(s, Linear(0.5, 1.)), roots, middle, right); } // It is faster to use the bernstein root finder for small degree polynomials (<100?. std::vector roots1(SBasis const & s) { std::vector 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; } std::vector roots1(SBasis const & s, Interval const ivl) { std::vector res; double d = s[0][0] - s[0][1]; if(d != 0) { double r = s[0][0] / d; if(ivl.contains(r)) res.push_back(r); } return res; } /** Find all t s.t s(t) = 0 \param a sbasis function \see Bezier::roots \returns vector of zeros (roots) */ std::vector roots(SBasis const & s) { switch(s.size()) { case 0: assert(false); return std::vector(); case 1: return roots1(s); default: { Bezier bz; sbasis_to_bezier(bz, s); return bz.roots(); } } } std::vector roots(SBasis const & s, Interval const ivl) { switch(s.size()) { case 0: assert(false); return std::vector(); case 1: return roots1(s, ivl); default: { Bezier bz; sbasis_to_bezier(bz, s); return bz.roots(ivl); } } } }; /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :