Changeset 5181ccc in sasmodels


Ignore:
Timestamp:
May 19, 2017 8:13:16 AM (7 years ago)
Author:
Paul Kienzle <pkienzle@…>
Branches:
master, core_shell_microgels, costrafo411, magnetic_model, ticket-1257-vesicle-product, ticket_1156, ticket_1265_superball, ticket_822_more_unit_tests
Children:
487e695
Parents:
f102a96
Message:

improve accuracy of bessel function J1

Files:
2 edited

Legend:

Unmodified
Added
Removed
  • explore/precision.py

    reb2946f r5181ccc  
    5757        return calculator(background=0) 
    5858 
    59     def run(self, xrange="log", diff=True): 
     59    def run(self, xrange="log", diff="relative"): 
    6060        r""" 
    6161        Compare accuracy of different methods for computing f. 
    6262 
    63         *xrange* is log=[10^-3,10^5], linear=[1,1000], zoom[1000,1010], 
    64         or neg=[-100,100]. 
    65  
    66         *diff* is False if showing function value rather than relative error. 
     63        *xrange* is:: 
     64 
     65            log:    [10^-3,10^5] 
     66            logq:   [10^-4, 10^1] 
     67            linear: [1,1000] 
     68            zoom:   [1000,1010] 
     69            neg:    [-100,100] 
     70 
     71        *diff* is "relative", "absolute" or "none" 
    6772 
    6873        *x_bits* is the precision with which the x values are specified.  The 
    6974        default 23 should reproduce the equivalent of a single precisio 
    7075        """ 
    71         linear = xrange != "log" 
     76        linear = not xrange.startswith("log") 
    7277        if xrange == "zoom": 
    7378            lin_min, lin_max, lin_steps = 1000, 1010, 2000 
    7479        elif xrange == "neg": 
    7580            lin_min, lin_max, lin_steps = -100.1, 100.1, 2000 
     81        elif xrange == "linear": 
     82            lin_min, lin_max, lin_steps = 1, 1000, 2000 
     83        elif xrange == "log": 
     84            log_min, log_max, log_steps = -3, 5, 400 
     85        elif xrange == "logq": 
     86            log_min, log_max, log_steps = -4, 1, 400 
    7687        else: 
    77             lin_min, lin_max, lin_steps = 1, 1000, 2000 
    78         lin_min = max(lin_min, self.limits[0]) 
    79         lin_max = min(lin_max, self.limits[1]) 
    80         log_min, log_max, log_steps = -3, 5, 400 
     88            raise ValueError("unknown range "+xrange) 
    8189        with mp.workprec(500): 
     90            # Note: we make sure that we are comparing apples to apples... 
     91            # The x points are set using single precision so that we are 
     92            # examining the accuracy of the transformation from x to f(x) 
     93            # rather than x to f(nearest(x)) where nearest(x) is the nearest 
     94            # value to x in the given precision. 
    8295            if linear: 
     96                lin_min = max(lin_min, self.limits[0]) 
     97                lin_max = min(lin_max, self.limits[1]) 
    8398                qrf = np.linspace(lin_min, lin_max, lin_steps, dtype='single') 
     99                #qrf = np.linspace(lin_min, lin_max, lin_steps, dtype='double') 
    84100                qr = [mp.mpf(float(v)) for v in qrf] 
    85101                #qr = mp.linspace(lin_min, lin_max, lin_steps) 
    86102            else: 
     103                log_min = np.log10(max(10**log_min, self.limits[0])) 
     104                log_max = np.log10(min(10**log_max, self.limits[1])) 
    87105                qrf = np.logspace(log_min, log_max, log_steps, dtype='single') 
     106                #qrf = np.logspace(log_min, log_max, log_steps, dtype='double') 
    88107                qr = [mp.mpf(float(v)) for v in qrf] 
    89108                #qr = [10**v for v in mp.linspace(log_min, log_max, log_steps)] 
     
    98117        pylab.suptitle(self.name + " compared to 500-bit mpmath") 
    99118 
    100     def compare(self, x, precision, target, linear=False, diff=True): 
     119    def compare(self, x, precision, target, linear=False, diff="relative"): 
    101120        r""" 
    102121        Compare the different computation methods using the given precision. 
     
    113132        plotdiff(x, target, self.call_ocl(x, precision, 0), 'OpenCL '+precision, diff=diff) 
    114133        pylab.xlabel(self.xaxis) 
    115         if diff: 
     134        if diff == "relative": 
    116135            pylab.ylabel("relative error") 
     136        elif diff == "absolute": 
     137            pylab.ylabel("absolute error") 
    117138        else: 
    118139            pylab.ylabel(self.name) 
     
    121142            pylab.xscale('linear') 
    122143 
    123 def plotdiff(x, target, actual, label, diff=True): 
     144def plotdiff(x, target, actual, label, diff): 
    124145    """ 
    125146    Plot the computed value. 
     
    127148    Use relative error if SHOW_DIFF, otherwise just plot the value directly. 
    128149    """ 
    129     if diff: 
     150    if diff == "relative": 
    130151        err = np.array([abs((t-a)/t) for t, a in zip(target, actual)], 'd') 
    131152        #err = np.clip(err, 0, 1) 
     153        pylab.loglog(x, err, '-', label=label) 
     154    elif diff == "absolute": 
     155        err = np.array([abs((t-a)) for t, a in zip(target, actual)], 'd') 
    132156        pylab.loglog(x, err, '-', label=label) 
    133157    else: 
     
    289313    qr = x * f(RADIUS)*mp.sin(theta) 
    290314    qh = x * f(LENGTH)/f(2)*mp.cos(theta) 
    291     return (f(2)*mp.j1(qr)/qr * mp.sin(qh)/qh)**f(2) 
     315    be = f(2)*mp.j1(qr)/qr 
     316    si = mp.sin(qh)/qh 
     317    background = f(0) 
     318    #background = f(1)/f(1000) 
     319    volume = mp.pi*f(RADIUS)**f(2)*f(LENGTH) 
     320    contrast = f(5) 
     321    units = f(1)/f(10000) 
     322    #return be 
     323    #return si 
     324    return units*(volume*contrast*be*si)**f(2)/volume + background 
    292325def np_cyl(x): 
    293326    f = np.float64 if x.dtype == np.float64 else np.float32 
     
    295328    qr = x * f(RADIUS)*np.sin(theta) 
    296329    qh = x * f(LENGTH)/f(2)*np.cos(theta) 
    297     return (f(2)*scipy.special.j1(qr)/qr*np.sin(qh)/qh)**f(2) 
     330    be = f(2)*scipy.special.j1(qr)/qr 
     331    si = np.sin(qh)/qh 
     332    background = f(0) 
     333    #background = f(1)/f(1000) 
     334    volume = f(np.pi)*f(RADIUS)**2*f(LENGTH) 
     335    contrast = f(5) 
     336    units = f(1)/f(10000) 
     337    #return be 
     338    #return si 
     339    return units*(volume*contrast*be*si)**f(2)/volume + background 
    298340ocl_cyl = """\ 
    299341    double THETA = %(THETA).15e*M_PI_180; 
    300342    double qr = q*%(RADIUS).15e*sin(THETA); 
    301343    double qh = q*0.5*%(LENGTH).15e*cos(THETA); 
    302     return square(sas_2J1x_x(qr)*sas_sinx_x(qh)); 
     344    double be = sas_2J1x_x(qr); 
     345    double si = sas_sinx_x(qh); 
     346    double background = 0; 
     347    //double background = 0.001; 
     348    double volume = M_PI*square(%(RADIUS).15e)*%(LENGTH).15e; 
     349    double contrast = 5.0; 
     350    double units = 1e-4; 
     351    //return be; 
     352    //return si; 
     353    return units*square(volume*contrast*be*si)/volume + background; 
    303354"""%{"LENGTH":LENGTH, "RADIUS": RADIUS, "THETA": THETA} 
    304355add_function( 
     
    428479    names = ", ".join(sorted(ALL_FUNCTIONS)) 
    429480    print("""\ 
    430 usage: precision.py [-f] [--log|--linear|--zoom|--neg] name... 
     481usage: precision.py [-f/a/r] [-x<range>] name... 
    431482where 
    432     -f indicates that the function value should be plotted rather than error, 
    433     --log indicates log stepping in [10^-3, 10^5] 
    434     --linear indicates linear stepping in [1, 1000] 
    435     --zoom indicates linear stepping in [1000, 1010] 
    436     --neg indicates linear stepping in [-100.1, 100.1] 
     483    -f indicates that the function value should be plotted, 
     484    -a indicates that the absolute error should be plotted, 
     485    -r indicates that the relative error should be plotted (default), 
     486    -x<range> indicates the steps in x, where <range> is one of the following 
     487      log indicates log stepping in [10^-3, 10^5] (default) 
     488      logq indicates log stepping in [10^-4, 10^1] 
     489      linear indicates linear stepping in [1, 1000] 
     490      zoom indicates linear stepping in [1000, 1010] 
     491      neg indicates linear stepping in [-100.1, 100.1] 
    437492and name is "all [first]" or one of: 
    438493    """+names) 
     
    441496def main(): 
    442497    import sys 
    443     diff = True 
     498    diff = "relative" 
    444499    xrange = "log" 
    445     args = sys.argv[1:] 
    446     if '-f' in args: 
    447         args.remove('-f') 
    448         diff = False 
    449     for k in "log linear zoom neg".split(): 
    450         if '--'+k in args: 
    451             args.remove('--'+k) 
    452             xrange = k 
    453     if not args: 
     500    options = [v for v in sys.argv[1:] if v.startswith('-')] 
     501    for opt in options: 
     502        if opt == '-f': 
     503            diff = "none" 
     504        elif opt == '-r': 
     505            diff = "relative" 
     506        elif opt == '-a': 
     507            diff = "absolute" 
     508        elif opt.startswith('-x'): 
     509            xrange = opt[2:] 
     510        else: 
     511            usage() 
     512 
     513    names = [v for v in sys.argv[1:] if not v.startswith('-')] 
     514    if not names: 
    454515        usage() 
    455     if args[0] == "all": 
    456         cutoff = args[1] if len(args) > 1 else "" 
    457         args = list(sorted(ALL_FUNCTIONS)) 
    458         args = [k for k in args if k >= cutoff] 
    459     if any(k not in FUNCTIONS for k in args): 
     516 
     517    if names[0] == "all": 
     518        cutoff = names[1] if len(names) > 1 else "" 
     519        names = list(sorted(ALL_FUNCTIONS)) 
     520        names = [k for k in names if k >= cutoff] 
     521    if any(k not in FUNCTIONS for k in names): 
    460522        usage() 
    461     multiple = len(args) > 1 
     523    multiple = len(names) > 1 
    462524    pylab.interactive(multiple) 
    463     for k in args: 
     525    for k in names: 
    464526        pylab.clf() 
    465527        comparator = FUNCTIONS[k] 
  • sasmodels/models/lib/sas_J1.c

    reb2946f r5181ccc  
    109109{ 
    110110 
    111     double w, z, p, q, xn, abs_x, sign_x; 
     111    double w, z, p, q, abs_x, sign_x; 
    112112 
    113113    const double Z1 = 1.46819706421238932572E1; 
    114114    const double Z2 = 4.92184563216946036703E1; 
    115     const double THPIO4 =  2.35619449019234492885; 
    116     const double SQ2OPI = 0.79788456080286535588; 
    117115 
    118116    // 2017-05-18 PAK - mathematica and mpmath use J1(-x) = -J1(x) 
     
    136134    p = polevl( z, PPJ1, 6)/polevl( z, PQJ1, 6 ); 
    137135    q = polevl( z, QPJ1, 7)/p1evl( z, QQJ1, 7 ); 
    138     xn = abs_x - THPIO4; 
    139  
    140     double sn, cn; 
    141     SINCOS(xn, sn, cn); 
    142     p = p * cn - w * q * sn; 
    143  
    144     return( sign_x * p * SQ2OPI / sqrt(abs_x) ); 
     136 
     137    // 2017-05-19 PAK improve accuracy using trig identies 
     138    // original: 
     139    //    const double THPIO4 =  2.35619449019234492885; 
     140    //    const double SQ2OPI = 0.79788456080286535588; 
     141    //    double sin_xn, cos_xn; 
     142    //    SINCOS(abs_x - THPIO4, sin_xn, cos_xn); 
     143    //    p = p * cos_xn - w * q * sin_xn; 
     144    //    return( sign_x * p * SQ2OPI / sqrt(abs_x) ); 
     145    // expanding p*cos(a - 3 pi/4) - wq sin(a - 3 pi/4) 
     146    //    [ p(sin(a) - cos(a)) + wq(sin(a) + cos(a)) / sqrt(2) 
     147    // note that sqrt(1/2) * sqrt(2/pi) = sqrt(1/pi) 
     148    const double SQRT1_PI = 0.56418958354775628; 
     149    double sin_x, cos_x; 
     150    SINCOS(abs_x, sin_x, cos_x); 
     151    p = p*(sin_x - cos_x) + w*q*(sin_x + cos_x); 
     152    return( sign_x * p * SQRT1_PI / sqrt(abs_x) ); 
    145153} 
    146154 
     
    188196 
    189197    const float Z1 = 1.46819706421238932572E1; 
    190     const float THPIO4F =  2.35619449019234492885;    /* 3*pi/4 */ 
    191198 
    192199 
     
    207214    p = w * polevl( q, MO1J1, 7); 
    208215    w = q*q; 
    209     xn = q * polevl( w, PH1J1, 7) - THPIO4F; 
    210     p = p * cos(xn + x); 
     216    // 2017-05-19 PAK improve accuracy using trig identies 
     217    // original: 
     218    //    const float THPIO4F =  2.35619449019234492885;    /* 3*pi/4 */ 
     219    //    xn = q * polevl( w, PH1J1, 7) - THPIO4F; 
     220    //    p = p * cos(xn + x); 
     221    //    return( xx < 0. ? -p : p ); 
     222    // expanding cos(a + b - 3 pi/4) is 
     223    //    [sin(a)sin(b) + sin(a)cos(b) + cos(a)sin(b)-cos(a)cos(b)] / sqrt(2) 
     224    xn = q * polevl( w, PH1J1, 7); 
     225    float cos_xn, sin_xn; 
     226    float cos_x, sin_x; 
     227    SINCOS(xn, sin_xn, cos_xn);  // about xn and 1 
     228    SINCOS(x, sin_x, cos_x); 
     229    p *= M_SQRT1_2*(sin_xn*(sin_x+cos_x) + cos_xn*(sin_x-cos_x)); 
    211230 
    212231    return( xx < 0. ? -p : p ); 
Note: See TracChangeset for help on using the changeset viewer.