Ticket #3813: trac_3813_rebase.patch

File trac_3813_rebase.patch, 14.5 kB (added by anakha, 5 months ago)

Rebase of the patch against 3.1.1. Includes all prior patches.

  • a/sage/plot/plot.py

    old new  
    35833583 
    35843584    PLOT OPTIONS: 
    35853585    The plot options are 
    3586         plot_points -- the number of points to initially plot before 
    3587                        doing adaptive refinement 
    3588         plot_division -- the maximum number of subdivisions to 
    3589                          introduce in adaptive refinement. 
    3590         max_bend      -- parameter that affects adaptive refinement 
     3586        plot_points -- (default: 200) the number of points to initially plot 
     3587                       before the adaptive refinement. 
     3588        adaptive_recursion -- (default: 5) how many levels of recursion to go  
     3589                              before giving up when doing adaptive refinement. 
     3590                              Setting this to 0 disables adaptive refinement. 
     3591        adaptive_tolerance -- (default: 0.01) how large a difference should be 
     3592                              before the adaptive refinement code considers it 
     3593                              significant.  Use a smaller value for smoother 
     3594                              plots and a larger value for coarser plots.  In 
     3595                              general, adjust adaptive_recursion before 
     3596                              adaptive_tolerance, and consult the code of 
     3597                              adaptive_refinement for the details. 
     3598 
    35913599        xmin -- starting x value 
    35923600        xmax -- ending x value 
    35933601        color -- an rgb-tuple (r,g,b) with each of r,g,b between 0 and 1, or 
     
    36303638        Graphics object consisting of 1 graphics primitive 
    36313639        sage: len(P)     # number of graphics primitives 
    36323640        1 
    3633         sage: len(P[0])  # how many points were computed 
    3634         200 
     3641        sage: len(P[0])  # how many points were computed (random) 
     3642        224 
    36353643        sage: P          # render 
    36363644         
    36373645        sage: P = plot(sin, (0,10), plot_points=10); print P 
    36383646        Graphics object consisting of 1 graphics primitive 
    36393647        sage: len(P[0])  # random output 
    3640         80 
     3648        32 
    36413649        sage: P          # render 
    36423650 
    36433651    We plot with randomize=False, which makes the initial sample  
    36443652    points evenly spaced (hence always the same).  Adaptive plotting  
    3645     might insert other points, however, unless plot_division=0.  
    3646         sage: p=plot(lambda x: 1, (x,0,3), plot_points=4, randomize=False, plot_division=0) 
     3653    might insert other points, however, unless adaptive_recursion=0.  
     3654        sage: p=plot(lambda x: 1, (x,0,3), plot_points=4, randomize=False, adaptive_recursion=0) 
    36473655        sage: list(p[0]) 
    36483656        [(0.0, 1.0), (1.0, 1.0), (2.0, 1.0), (3.0, 1.0)] 
    36493657 
     
    36573665        sage: plot([sin(n*x) for n in [1..4]], (0, pi)) 
    36583666 
    36593667 
    3660     The function $\sin(1/x)$ wiggles wildly near $0$, so the 
    3661     first plot below won't look perfect.  Sage adapts to this 
    3662     and plots extra points near the origin. 
     3668    The function $\sin(1/x)$ wiggles wildly near $0$.  Sage adapts 
     3669    to this and plots extra points near the origin. 
    36633670        sage: plot(sin(1/x), (x, -1, 1)) 
    3664  
    3665     With the \code{plot_points} option you can increase the number 
    3666     of sample points, to obtain a more accurate plot.  
    3667         sage: plot(sin(1/x), (x, -1, 1), plot_points=1000) 
    36683671 
    36693672    Note that the independent variable may be omitted if there is no 
    36703673    ambiguity: 
    3671         sage: plot(sin(1/x), (-1, 1), plot_points=1000) 
     3674        sage: plot(sin(1/x), (-1, 1)) 
     3675 
     3676    The algorithm used to insert extra points is actually pretty simple. On 
     3677    the picture drawn by the lines below: 
     3678        sage: p = plot(x^2, (-0.5, 1.4)) + line([(0,0), (1,1)], rgbcolor='green') 
     3679        sage: p += line([(0.5, 0.5), (0.5, 0.5^2)], rgbcolor='purple') 
     3680        sage: p += point(((0, 0), (0.5, 0.5), (0.5, 0.5^2), (1, 1)), rgbcolor='red', pointsize=20) 
     3681        sage: p += text('A', (-0.05, 0.1), rgbcolor='red') 
     3682        sage: p += text('B', (1.01, 1.1), rgbcolor='red') 
     3683        sage: p += text('C', (0.48, 0.57), rgbcolor='red') 
     3684        sage: p += text('D', (0.53, 0.18), rgbcolor='red') 
     3685        sage: p.show(axes=False, xmin=-0.5, xmax=1.4, ymin=0, ymax=2) 
     3686     
     3687    You have the function (in blue) and its approximation (in green) passing 
     3688    through the points A and B. The algorithm finds the midpoint C of AB and 
     3689    computes the distance between C and D. The point D is added to the curve if 
     3690    it exceeds the (nonzero) adaptive_tolerance threshold. If D is added to 
     3691    the curve, then the algorithm is applied recursively to the points A and D, 
     3692    and D and B. It is repeated adaptive_recursion times (10, by default). 
    36723693 
    36733694    The actual sample points are slightly randomized, so the above 
    36743695    plots may look slightly different each time you draw them. 
     
    36983719        sage: set_verbose(0) 
    36993720             
    37003721    To plot the negative real cube root, use something like the following. 
    3701         sage: plot(lambda x : RR(x).nth_root(3), (x,-1, 1)
     3722        sage: plot(lambda x : RR(x).nth_root(3), (x,-1, 1)
    37023723 
    37033724    TESTS: 
    37043725    We do not randomize the endpoints: 
     
    37123733    def _reset(self): 
    37133734        o = self.options 
    37143735        o['plot_points'] = 200 
    3715         o['plot_division'] = 1000  
    3716         o['max_bend'] = 0.1        
    3717         o['rgbcolor'] = (0,0,1)    
    3718  
    3719     def __repr__(self): 
    3720         """ 
     3736        o['adaptive_tolerance'] = 0.01 
     3737        o['adaptive_recursion'] = 5 
     3738        o['rgbcolor'] = (0,0,1) 
     3739 
     3740    def __repr__(self): 
     3741        r""" 
    37213742        Returns a string representation of this PlotFactory object. 
    37223743 
    37233744        TESTS: 
     
    37923813        plot_points = int(options['plot_points']) 
    37933814        del options['plot_points'] 
    37943815        x, data = var_and_list_of_values(xrange, plot_points) 
    3795         data = list(data) 
    37963816        xmin = data[0] 
    37973817        xmax = data[-1] 
    37983818 
     
    38013821        if isinstance(funcs, (list, tuple)) and not parametric: 
    38023822            return reduce(operator.add, (plot(f, (xmin, xmax), polar=polar, **kwds) for f in funcs)) 
    38033823 
    3804         if len(data) >= 2: 
    3805             delta = data[1]-data[0] 
    3806         else: 
    3807             delta = 0 
     3824        delta = float(xmin-xmax) / plot_points 
    38083825 
    38093826        random = current_randstate().python_random().random 
    38103827        exceptions = 0; msg='' 
     
    38133830            xi = data[i] 
    38143831            # Slightly randomize the interior sample points if 
    38153832            # randomize is true 
    3816             if i > 0 and i < plot_points-1: 
    3817                 if randomize: 
    3818                     xi += delta*random() 
    3819                 if xi > xmax: 
    3820                     xi = xmax 
    3821             elif i == plot_points-1:  
    3822                 xi = xmax  # guarantee that we get the last point. 
    3823                  
     3833            if randomize and i > 0 and i < plot_points-1: 
     3834                xi += delta*(random() - 0.5) 
     3835             
    38243836            try: 
    38253837                data[i] = (float(xi), float(f(xi))) 
     3838                # Only NaN is not equal to NaN 
     3839                if data[i][1] != data[i][1]: 
     3840                    raise ValueError, "nan result" 
    38263841            except (ZeroDivisionError, TypeError, ValueError, OverflowError), msg: 
    38273842                sage.misc.misc.verbose("%s\nUnable to compute f(%s)"%(msg, x),1) 
    3828                 exceptions += 1 
    3829                 exception_indices.append(i) 
    3830  
    3831             if str(data[i][1]) in ['nan', 'NaN']: 
    3832                 sage.misc.misc.verbose("%s\nUnable to compute f(%s)"%(msg, x),1) 
    3833                 exceptions += 1 
    3834                 exception_indices.append(i) 
    3835  
     3843                if i == 0: 
     3844                    for j in range(1, 99): 
     3845                        xj = xi + delta*j/100.0 
     3846                        try: 
     3847                            data[i] = (float(xj), float(f(xj))) 
     3848                            # nan != nan 
     3849                            if data[i][1] != data[i][1]: 
     3850                                continue 
     3851                            break 
     3852                        except (ZeroDivisionError, TypeError, ValueError, OverflowError), msg: 
     3853                            pass 
     3854                    else: 
     3855                        exceptions += 1 
     3856                        exception_indices.append(i) 
     3857                elif i == plot_points-1: 
     3858                    for j in range(1, 99): 
     3859                        xj = xi - delta*j/100.0 
     3860                        try: 
     3861                            data[i] = (float(xj), float(f(xj))) 
     3862                            # nan != nan 
     3863                            if data[i][1] != data[i][1]: 
     3864                                continue 
     3865                            break 
     3866                        except (ZeroDivisionError, TypeError, ValueError, OverflowError), msg: 
     3867                            pass 
     3868                    else: 
     3869                        exceptions += 1 
     3870                        exception_indices.append(i) 
     3871                else: 
     3872                    exceptions += 1 
     3873                    exception_indices.append(i) 
     3874         
    38363875        data = [data[i] for i in range(len(data)) if i not in exception_indices] 
    3837              
     3876         
     3877        if 'plot_division' in options: 
     3878            # turn off adaptive refinement if it was requested with the old method, ignore otherwise 
     3879            if options['plot_division'] == 0: 
     3880                options['adaptive_recursion'] = 0 
     3881            del options['plot_division'] 
     3882            import warnings 
     3883            warnings.warn("plot_division is deprecated. See adaptive_recursion in the documentation for plot()", DeprecationWarning, stacklevel=3) 
    38383884        # adaptive refinement 
    38393885        i, j = 0, 0 
    3840         max_bend = float(options['max_bend']) 
    3841         del options['max_bend'] 
    3842         plot_division = int(options['plot_division']) 
    3843         del options['plot_division'] 
     3886        adaptive_tolerance = delta * float(options['adaptive_tolerance']) 
     3887        del options['adaptive_tolerance'] 
     3888        adaptive_recursion = int(options['adaptive_recursion']) 
     3889        del options['adaptive_recursion'] 
     3890         
    38443891        while i < len(data) - 1: 
    3845             if abs(data[i+1][1] - data[i][1]) > max_bend: 
    3846                 x = float((data[i+1][0] + data[i][0])/2) 
    3847                 try: 
    3848                     y = float(f(x)) 
    3849                     data.insert(i+1, (x, y)) 
    3850                 except (ZeroDivisionError, TypeError, ValueError), msg: 
    3851                     sage.misc.misc.verbose("%s\nUnable to compute f(%s)"%(msg, x),1) 
    3852                     exceptions += 1 
    3853                 j += 1 
    3854                 if j > plot_division: 
    3855                     break 
    3856             else: 
     3892            for p in adaptive_refinement(f, data[i], data[i+1],  
     3893                                         adaptive_tolerance=adaptive_tolerance, 
     3894                                         adaptive_recursion=adaptive_recursion): 
     3895                data.insert(i+1, p) 
    38573896                i += 1 
    3858  
     3897            i += 1 
     3898         
    38593899        if (len(data) == 0 and exceptions > 0) or exceptions > 10: 
    38603900            sage.misc.misc.verbose("WARNING: When plotting, failed to evaluate function at %s points."%exceptions, level=0) 
    38613901            sage.misc.misc.verbose("Last error message: '%s'"%msg, level=0) 
     
    46814721                'ymin':ymin, 'ymax':ymax} 
    46824722    else: 
    46834723        return xmin, xmax, ymin, ymax 
     4724 
     4725def adaptive_refinement(f, p1, p2, adaptive_tolerance=0.01, adaptive_recursion=5, level=0): 
     4726    r""" 
     4727    The adaptive refinement algorithm for plotting a function f. See the 
     4728    docstring for plot or PlotFactory for a description of the algorithm. 
     4729 
     4730    INPUT: 
     4731        f -- a function of one variable 
     4732        p1, p2 -- two points to refine between 
     4733        adaptive_recursion -- (default: 5) how many levels of recursion to go  
     4734                              before giving up when doing adaptive refinement. 
     4735                              Setting this to 0 disables adaptive refinement. 
     4736        adaptive_tolerance -- (default: 0.01) how large a difference should be 
     4737                              before the adaptive refinement code considers  
     4738                              it significant.  See the documentation for 
     4739                              plot() for more information. 
     4740     
     4741    OUTPUT: 
     4742        list -- a list of points to insert between p1 and p2 to get 
     4743                a better linear approximation between them 
     4744 
     4745    TESTS: 
     4746        sage: from sage.plot.plot import adaptive_refinement 
     4747        sage: adaptive_refinement(sin, (0,0), (pi,0), adaptive_tolerance=0.01, adaptive_recursion=0) 
     4748        [] 
     4749        sage: adaptive_refinement(sin, (0,0), (pi,0), adaptive_tolerance=0.01) 
     4750        [(0.125000000000000*pi, 0.38268343236508978), (0.187500000000000*pi, 0.55557023301960218), (0.250000000000000*pi, 0.707106781186547...), (0.312500000000000*pi, 0.83146961230254524), (0.375000000000000*pi, 0.92387953251128674), (0.437500000000000*pi, 0.98078528040323043), (0.500000000000000*pi, 1.0), (0.562500000000000*pi, 0.98078528040323043), (0.625000000000000*pi, 0.92387953251128674), (0.687500000000000*pi, 0.83146961230254546), (0.750000000000000*pi, 0.70710678118654757), (0.812500000000000*pi, 0.55557023301960218), (0.875000000000000*pi, 0.38268343236508989)] 
     4751 
     4752    This shows that lowering adaptive_tolerance and raising 
     4753    adaptive_recursion both increase the number of subdivision points: 
     4754 
     4755        sage: x = var('x') 
     4756        sage: f = sin(1/x) 
     4757        sage: n1 = len(adaptive_refinement(f, (0,0), (pi,0), adaptive_tolerance=0.01)); n1 
     4758        79 
     4759        sage: n2 = len(adaptive_refinement(f, (0,0), (pi,0), adaptive_recursion=5, adaptive_tolerance=0.01)); n2 
     4760        15 
     4761        sage: n3 = len(adaptive_refinement(f, (0,0), (pi,0), adaptive_tolerance=0.005)); n3 
     4762        88 
     4763    """ 
     4764    if level >= adaptive_recursion: 
     4765        return [] 
     4766    x = (p1[0] + p2[0])/2.0 
     4767    try: 
     4768        y = float(f(x)) 
     4769    except (ZeroDivisionError, TypeError, ValueError, OverflowError), msg: 
     4770        sage.misc.misc.verbose("%s\nUnable to compute f(%s)"%(msg, x), 1) 
     4771        # give up for this branch 
     4772        return [] 
     4773 
     4774    # this distance calculation is not perfect.  
     4775    if abs((p1[1] + p2[1])/2.0 - y) > adaptive_tolerance: 
     4776        return adaptive_refinement(f, p1, (x, y), 
     4777                    adaptive_tolerance=adaptive_tolerance, 
     4778                    adaptive_recursion=adaptive_recursion, 
     4779                    level=level+1) \ 
     4780                    + [(x, y)] + \ 
     4781            adaptive_refinement(f, (x, y), p2, 
     4782                    adaptive_tolerance=adaptive_tolerance, 
     4783                    adaptive_recursion=adaptive_recursion, 
     4784                    level=level+1) 
     4785    else: 
     4786        return []