3 Copyright (C) 2006-2007 Michel Van den Bergh
5 This program is free software; you can redistribute
6 it and/or modify it under the terms of the
7 GNU General Public License as published by
8 the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it
12 will be useful, but WITHOUT ANY WARRANTY;
13 without even the implied warranty of MERCHANTABILITY
14 or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details at
16 http://www.gnu.org/copyleft/gpl.html
19 /* The latest version of this package can be found at
20 http://www.bertold.org/levin
26 0.0->0.1 Some suggestions by Richard Fateman incorporated
27 0.1->0.2 Now much faster. Zero entries are allowed.
28 0.2->0.2.1 Disable bflevin_u_sum for series with non rational terms
29 0.2->0.3 Argument treatment somewhat more robust. Added GPL header.
30 0.3->0.4 Extra checks on arguments.
31 Variable lower bound for levin_u_sum is treated correctly.
32 0.4->0.5 Got rid of ugly hacks and simply renamed parameters to "non-obvious"
33 (-)__ names (see discussion on list). This simulates lexical scoping
34 as long as the user does not use such names.
35 0.5->0.6 Faster again (for series with rational terms)!
36 "levin" can now compute progagation of rounding errors.
37 "bflevin_u_sum" now works for series with non rational terms.
38 0.6->0.7 Propagation of rounding errors is computed in lower precision.
40 Added disclaimer about optimization.
43 /* Semantic note: all functions evaluate all their arguments.
44 levin_u_sum and bflevin_u_sum perform one round of ev(-) on their
45 first argument. This is to make (bf)levin_u_sum('(...),...) behave as
46 expected. Without this, some regression tests fail. Since we simulate
47 lexical scope using "non-obvious" parameter names, the use of ev seems
48 ok. I.e. in normal situations there is no risk of ev evaluating things
52 /* Disclaimer: The following code is a proof of concept. It works
53 reliably but is highly non optimized with regard to speed and memory
54 usage. The necessary optimizations are quite straight forward however.
57 levin_options["debug"]:false; /* when true gives some extra output; should
58 really be a loglevel */
59 levin_options["min_terms"]:5; /* bflevin_u_sum starts with 5 terms */
60 levin_options["max_terms"]:640; /* bflevin_u_sum tries up to 5*2^7 terms
62 levin_options["min_precision"]:16; /* bflevin_u_sum will start with this
64 levin_options["max_precision"]:1000; /* bflevin_u_sum will not increase the
65 precision up above this value*/
67 /* see: "Mathematical Properties of a
68 New Levin-Type Sequence Transformation"
69 by Ernst Joachim Weniger
71 arXiv:math-ph/0306063 */
73 /* s : array with partial sums of a series
74 k+1 : number of partial sums to use
75 n : index of first partial sum to be used;
76 in other words, we use: s[n],...,s[n+k]
77 beta : translation parameter; this parameter can be used to avoid division
78 by zero; for accurate error estimates it currently has to be
80 omega : an array of remainder estimates;
81 depending on omega one gets a different type of levin
82 transform; omega should not contain zero entries
83 or we get a division by zero
84 mode : levin_numeric or levin_algebraic, for levin_numeric the return
85 value is a list [result,variance]. For levin_algebraic the
86 return value is just the result.
87 s__variance : in numeric mode an array with variances for the elements
89 omega__variance : in numeric more an array with variances for the elements
92 Note 1 : All variances are assumed to be in units of 10^(-2*fpprec).
94 Note 2 : The computation of propagation of variance is not quite
95 mathematically correct since we are assuming that the errors on the
96 s[j] and omega[j] are all independent. However my tests indicate that this
97 method is reasonably accurate. Note that the correct computation of
98 propagation of variance is O(N^2).
102 levin(s__,k__,n__,beta__,omega__,mode__,s__variance__,omega__variance__):=
103 block([i__,j__,l__,numerator__,denominator__,result__,result__variance__,
104 coeffs__,numerator__variance__,denominator__variance__
107 coeffs__[l__]:=((-1)^l__)*binomial(k__,l__)*(beta__+n__+l__)^(k__-1),
108 /* the following sums will be rewritten recursively */
109 numerator__:sum(coeffs__[j__]*
111 s__[n__+j__]/omega__[n__+j__]
114 denominator__:sum(coeffs__[j__]*
119 result__:errcatch(numerator__/denominator__),
120 if result__=[] then (
122 "*** levin has zero denominator ***"
126 result__:result__[1],
127 if mode__=levin_numeric then (
128 block([fpprec:16], /* start lower precision block */
130 /* the following formulas are not optimized yet, they are written with an
131 emphasis on readability, the sums will be rewritten recursively */
133 numerator__variance__:sum(coeffs__[j__]^2*
135 (1/omega__[n__+j__])^2 * s__variance__[n__+j__] +
136 (s__[n__+j__]/omega__[n__+j__]^2)^2 * omega__variance__[n__+j__] ),
138 denominator__variance__:sum(coeffs__[j__]^2*
140 (1/omega__[n__+j__]^2)^2 * omega__variance__[n__+j__]
144 ( (1/denominator__^2) * numerator__variance__ +
145 (numerator__/denominator__^2)^2 * denominator__variance__
147 ) /* end lower precision block */
149 if mode__=levin_algebraic then
152 return([result__,result__variance__])
159 /* Here we delete zero entries and compute the partial sums. We also
160 set omega such that we get the levin_u transform. In addition we
161 set beta=0 and n=1. Perhaps we should try several different values
162 for beta in case division by zero occurs. */
164 levin_u(a__,terms__,[mode__]):=block([i__,j__,l__,n__,beta__,last__,
165 omega__,t__,b__,t__variance__,omega__variance__
167 local(omega__,t__,b__,t__variance__,omega__variance__),
170 if mode__#[] then mode__:mode__[1] else mode__:levin_algebraic,
173 for i__:0 thru terms__-1 do (
174 if sign(abs(subvar(a__,i__)))#zero then (
175 b__[j__]:subvar(a__,i__),
178 if levin_options["debug"] then print("omitting zero term with index ",i)
183 if mode__=levin_algebraic then
184 return(0) /* best effort */
186 return([bfloat(0),bfloat(0)])
189 if mode__=levin_algebraic then
190 return(b__[0]) /* best effort */
192 return([bfloat(b__[0]),bfloat(0)])
194 if mode__=levin_algebraic then (
195 omega__[l__]:=l__*b__[l__],
196 t__[l__]:=t__[l__-1]+b__[l__],
198 omega__variance__[l__]:=0,
199 t__variance__[l__]:=0
201 omega__[l__]:=l__* bfloat(b__[l__]),
202 t__[l__]:=t__[l__-1]+ bfloat(b__[l__]),
204 block([fpprec:16], /* start lower precision block */
205 omega__variance__[l__]:=l__^2*b__[l__]^2,
206 t__variance__[l__]:=t__variance__[l__-1]+(abs(bfloat(b__[l__])))^2,
208 ) /* end lower precision block */
210 levin(t__,last__-n__,n__,beta__,omega__,mode__,
211 t__variance__,omega__variance__)
214 levin_u_complex(a__,terms__,[mode__]):=block([n__,re__,im__,
215 re_result__,im_result__],
217 if mode__#[] then mode__:mode__[1] else mode__:levin_algebraic,
218 if mode__=levin_algebraic then (
219 return(levin_u(a__,terms__,mode__))
221 /* im__[n__]:=imagpart(arrayapply(a__,[n__])), This give bindstack overflow.
223 re__[n__]:=realpart(arrayapply(a__,[n__])), */
224 for n__:0 thru terms__-1 do (
225 im__[n__]:imagpart(arrayapply(a__,[n__])),
226 re__[n__]:realpart(arrayapply(a__,[n__]))
228 re_result__:levin_u(re__,terms__,mode__),
229 im_result__:levin_u(im__,terms__,mode__),
230 return([re_result__[1]+%i*im_result__[1],re_result__[2]+im_result__[2]])
241 levin_check_lvalue(e__):=(symbolp(e__) or (subvarp(e__) and levin_check_lvalue(part(e__,0)))) and not stringp(e__);
243 /* User friendly version.
247 levin_u_sum(1/n^2,n,1,10) = ---------- = 1.644934081345832
250 sum(1/n^2,n,1,10) = ------- = 1.549767731166541
253 sum(1/n^2,n,1,inf) = %pi^2/6 = 1.644934066848226
255 If mode=levin_numeric then a floating point error estimate is
256 returned (the square root of the variance). It is *not* an estimate
257 for the error of the result.
261 levin_u_sum(a__,index_var__,start__,terms__,[mode__]):=
262 block([n__,c__,result__,ratprint:ratprint,
267 if mode__#[] then mode__:mode__[1]
268 else mode__:levin_algebraic,
269 if not(integerp(terms__)) or not(constantp(terms__)) or terms__<0 then (
271 "*** levin_u_sum needs a positive constant integral number of terms ***"
273 return(levin_u_sum_failed)
275 if not(levin_check_lvalue(index_var__)) then (
277 "**** ",index_var," is not an lvalue ***"
279 return(levin_u_sum_failed)
281 result__:for n__:0 thru terms__-1 do (
282 c__[n__]:subst(start__+n__,index_var__,a__),
283 c__[n__]:ev(c__[n__]), /* remove one round of quoting */
284 /* this is the plot2d convention */
285 if mode__=levin_numeric and not(constantp(c__[n__])) then (
287 "*** levin_u_sum needs constant terms in numeric mode. ***"
289 return(levin_u_sum_failed)
292 if result__=levin_u_sum_failed then return(levin_u_sum_failed),
293 result__:levin_u_complex(c__,terms__,mode__),
294 if mode__=levin_algebraic then
297 return([result__[1],bfloat(sqrt(result__[2])*10^(-fpprec))])
300 /* Numerical version. This function makes a crude attempt to compute
301 an indefinite sum numerically with precision given by fpprec.
302 If you suspect something is wrong put levin_options["debug"]:true.
305 bflevin_u_sum(a__,index_var__,start__):=block([fpprec:fpprec,fpprec_save__,
306 precision_overshoot_high__, precision_overshoot_low__,
307 non_constant__, state__, terms__, new__, error__,
308 actual_precision__, approx__, approx_old__,
309 result__,increasing_accuracy__,increasing_terms__,comparing__,
310 rational__,ratprint:ratprint,float2bf:float2bf,c__],
312 log10__(x):=bfloat(log(x)/log(10)),
315 if not(levin_check_lvalue(index_var__)) then (
317 "**** ",index_var__," is not an lvalue ***"
319 return(bflevin_u_sum_failed)
321 fpprec_save__:fpprec,
322 precision_overshoot_high__:fix((3/2)*fpprec_save__), /* I know this is much
323 too high in algebraic mode */
324 precision_overshoot_low__:min(precision_overshoot_high__-1,fpprec_save__+6),
325 fpprec:min(levin_options["max_precision"],max(levin_options["min_precision"],precision_overshoot_high__)),
326 terms__:levin_options["min_terms"],
327 non_constant__:false,
328 state__:increasing_accuracy__,
331 for n__:0 thru terms__-1 do ( /* perhaps we may need to recompute things
332 as the user may have used bfloat and the
333 precision might have been increased */
334 c__[n__]:subst(start__+n__,index_var__,a__),
335 c__[n__]:ev(c__[n__]), /* remove one round of quoting */
336 /* this is the plot2d convention */
337 if not(constantp(c__[n__])) then (
339 "*** bflevin_u_sum does not work for series with non constant terms. ***"
344 if not(ratnump(c__[n__])) then rational__:false
346 if non_constant__ then return(bflevin_u_sum_failed),
348 approx__:bfloat(levin_u(c__,terms__,levin_algebraic)),
349 if not constantp(approx__) then return (approx__),
350 actual_precision__:fpprec
352 new__:levin_u_complex(c__,terms__,levin_numeric),
353 if not constantp(new__) then return (new__),
355 error__:sqrt(new__[2]),
356 if equal(approx__,bfloat(0)) then actual_precision__:fpprec,
357 /* assume that a zero is accurate, what else can we do? */
358 actual_precision__:-fix((log10__(error__/abs(approx__))))+fpprec
360 if levin_options["debug"] then print (
361 "==============================","
363 precision= ",fpprec,"
365 rational= ",rational__,"
367 fp_accuracy= ",actual_precision__
369 if state__=increasing_accuracy__ then (
370 if actual_precision__>=precision_overshoot_low__ then (
374 fpprec:fpprec+precision_overshoot_high__-actual_precision__,
375 if fpprec> levin_options["max_precision"] then (
377 "*** required precision too high ***"
379 return(bflevin_u_sum_failed)
382 ) else if state__=increasing_terms__ then (
383 approx_old__:approx__,
384 if 2*terms__>levin_options["max_terms"] then (
386 "*** bflevin_u_sum did not obtain the required precision using ",terms__," terms ***"
388 return(bflevin_u_sum_failed)
391 state__:increasing_accuracy__
392 ) else if state__=comparing__ then (
393 if constantp(approx_old__) then (
394 if equal(approx_old__,bfloat(0)) and equal(approx__,bfloat(0))
395 then return(bfloat(0)),
396 if levin_options["debug"] then
397 print("difference= ",approx_old__-approx__),
398 if abs(approx_old__-approx__)/
399 max(abs(approx_old__),abs(approx__))
400 <10^(-fpprec_save__-1) then return(approx__)
403 state__:increasing_terms__
406 fpprec:fpprec_save__,