File: nummethods.mpi

package info (click to toggle)
mathpiper 0.0.svn2556-2
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 7,416 kB
  • ctags: 2,729
  • sloc: java: 21,643; xml: 751; sh: 105; makefile: 5
file content (379 lines) | stat: -rw-r--r-- 14,594 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
/// coded by Serge Winitzki. See essays documentation for algorithms.

//////////////////////////////////////////////////
/// Numerical method: AGM sequence
//////////////////////////////////////////////////

/// compute the AGM sequence up to a given precision
AG'Mean(a, b, eps) :=
[
	Local(a1, b1);
	If(InVerboseMode(), Echo("AG'Mean: Info: at prec. ", BuiltinPrecisionGet()));
	// AGM main loop
	While(Abs(a-b)>=eps)
	[
		a1 := DivideN(a+b, 2);
		b1 := SqrtN(MultiplyN(a, b));	// avoid Sqrt() which uses N() inside it
		a := a1;
		b := b1;
	];
	DivideN(a+b, 2);
];
//UnFence(AG'Mean, 3);

//////////////////////////////////////////////////
/// Numerical method: Taylor series, rectangular summation
//////////////////////////////////////////////////

/// Fast summation of Taylor series using a rectangular scheme.
/// SumTaylorNum(x, nth'term'func, n'terms) = Sum(k, 0, n'terms, nth'term'func(k)*x^k)
/// Note that sufficient precision must be preset to avoid roundoff errors (these methods do not modify precision).
/// The only reason to try making these functions HoldArg is to make sure that the closures nth'term'func and next'term'factor are passed intact. But it's probably not desired in most cases because a closure might contain parameters that should be evaluated.

/// The short form is used when only the nth term is known but no simple relation between a term and the next term.
/// The long form is used when there is a simple relation between consecutive terms. In that case, the n'th term function is not needed, only the 0th term value.

/// SumTaylorNum0 is summing the terms with direct methods (Horner's scheme or simple summation). SumTaylorNum1 is for the rectangular method.

/// nth'term'func and next'term'func must be functions applicable to one argument.

/// interface
SumTaylorNum0(_x, _nth'term'func, _n'terms) <-- SumTaylorNum0(x, nth'term'func, {}, n'terms);

SumTaylorNum1(_x, _nth'term'func, _n'terms) <-- SumTaylorNum1(x, nth'term'func, {}, n'terms);

/// interface
SumTaylorNum(_x, _nth'term'func, _n'terms) <--
If(
	n'terms >= 30,	// threshold for calculation with next'term'factor
	// use the rectangular algorithm for large enough number of terms
	SumTaylorNum1(x, nth'term'func, n'terms),
	SumTaylorNum0(x, nth'term'func, n'terms)
);

SumTaylorNum(_x, _nth'term'func, _next'term'factor, _n'terms) <--
If(
	n'terms >= 5,	// threshold for calculation with next'term'factor
	SumTaylorNum1(x, nth'term'func, next'term'factor, n'terms),
	SumTaylorNum0(x, nth'term'func, next'term'factor, n'terms)
);
//HoldArgNr(SumTaylorNum, 3, 2);

/// straightforward algorithms for a small number of terms
1# SumTaylorNum0(_x, _nth'term'func, {}, _n'terms) <--
[
	Local(sum, k);
  N([
    // use Horner scheme starting from the last term
    x:=Eval(x);
    sum := 0;
    For(k:=n'terms, k>=0, k--)
      sum := AddN(sum*x, nth'term'func @ k);
  ]);
	sum;
];

//HoldArgNr(SumTaylorNum0, 3, 2);

2# SumTaylorNum0(_x, _nth'term'func, _next'term'factor, _n'terms) <--
[
	Local(sum, k, term, delta);
  N([
    x:=Eval(x);	// x must be floating-point
    If (IsConstant(nth'term'func),
      term := nth'term'func,
      term := (nth'term'func @ {0}),
    );
    sum := term;	// sum must be floating-point
  ]);
  NonN([
    delta := 1;
    For(k:=1, k<=n'terms And delta != 0, k++)
    [
      term := MultiplyNum(term, next'term'factor @ {k}, x);	// want to keep exact fractions here, but the result is floating-point
      delta := sum;
      sum := sum + term;	// term must be floating-point
      delta := Abs(sum-delta);	// check for underflow
    ];
  ]);
	sum;
];

/// interface
SumTaylorNum0(_x, _nth'term'func, _n'terms) <-- SumTaylorNum0(x, nth'term'func, {}, n'terms);

//HoldArgNr(SumTaylorNum0, 4, 2);
//HoldArgNr(SumTaylorNum0, 4, 3);

/// this is to be used when a simple relation between a term and the next term is known.
/// next'term'factor must be a function applicable to one argument, so that if term = nth'term'func(k-1), then nth'term'func(k) = term / next'term'factor(k). (This is optimized for Taylor series of elementary functions.) In this case, nth'term'func is either a number, value of the 0th term, or a function.
/// A special case: when next'term'factor is an empty list; then we act as if there is no next'term'factor available.
/// In this case, nth'term'func must be a function applicable to one argument.
/// Need IntLog(n'terms, 10) + 1 guard digits due to accumulated roundoff error.
SumTaylorNum1(x, nth'term'func, next'term'factor, n'terms) :=
[
	// need Sqrt(n'terms/2) units of storage (rows) and Sqrt(n'terms*2) columns. Let's underestimate the storage.
	Local(sum, rows, cols, rows'tmp, last'power, i, j, x'power, term'tmp);
  N([ // want to keep exact fractions
    x:=Eval(x);	// x must be floating-point
    rows := IntNthRoot(n'terms+1, 2);
    cols := Div(n'terms+rows, rows);	// now: rows*cols >= n'terms+1
    Check(rows>1 And cols>1, "SumTaylorNum1: Internal error: number of Taylor sum terms must be at least 4");
    rows'tmp := ArrayCreate(rows, 0);
    x'power := x ^ rows;	// do not use PowerN b/c x might be complex
    // initialize partial sums (array rows'tmp) - the 0th column (i:=0)
    // prepare term'tmp for the first element
    // if we are using next'term'factor, then term'tmp is x^(rows*i)*a[rows*i]
    // if we are not using it, then term'tmp is x^(rows*i)
    If(
      next'term'factor = {},
      term'tmp := 1,
  //		term'tmp := (nth'term'func @ 0)	// floating-point
      If (IsConstant(nth'term'func),
        term'tmp := nth'term'func,
        term'tmp := (nth'term'func @ {0}),
      )
    );
  ]);
  NonN([ // want to keep exact fractions below
    // do horizontal summation using term'tmp to get the first element
    For(i:=0, i<cols, i++)
    [
      // add i'th term to each row
      For(j:=0, j<rows And (i<cols-1 Or i*rows+j<=n'terms), j++)	// do this unless we are beyond the last term in the last column
      [
        // if we are using next'term'factor, then term'tmp is x^(rows*i)*a[rows*i]
        // if we are not using it, then term'tmp is x^(rows*i)
        If(
          next'term'factor = {},	// no next'term'factor, so nth'term'func must be given
          [
            rows'tmp[j+1] := rows'tmp[j+1] + MultiplyNum(term'tmp, nth'term'func @ {i*rows+j});
          ],
          [
            rows'tmp[j+1] := rows'tmp[j+1] + term'tmp;	// floating-point
            term'tmp := MultiplyNum(term'tmp, next'term'factor @ {i*rows+j+1});	// arguments may be rational but the result is floating-point
          ]
        );
      ];
      // update term'tmp for the next column
      term'tmp := term'tmp*x'power;	// both floating-point
    ];
    // do vertical summation using Horner's scheme
    // now x'power = x^cols
    For([j:=rows; sum:=0;], j>0, j--)
      sum := sum*x + rows'tmp[j];
  ]);
	sum;
];

//HoldArgNr(SumTaylorNum, 4, 2);
//HoldArgNr(SumTaylorNum, 4, 3);

/* 
Examples:
In> SumTaylorNum(1,{{k}, 1/k!},{{k}, 1/k}, 10 )
Out> 2.7182818006;
In> SumTaylorNum(1,{{k},1/k!}, 10 )
Out> 2.7182818007;
*/

//////////////////////////////////////////////////
/// Numerical method: multiply floats by rationals
//////////////////////////////////////////////////

/// aux function: optimized numerical multiplication. Use MultiplyN() and DivideN().
/// optimization consists of multiplying or dividing by integers if one of the arguments is a rational number. This is presumably always better than floating-point calculations, except if we use Rationalize() on everything.
/// note that currently this is not a big optimization b/c of slow arithmetic but it already helps for rational numbers under InNumericMode() returns True and it will help even more when faster math is done

Function() MultiplyNum(x, y, ...);
Function() MultiplyNum(x);

10 # MultiplyNum(x_IsList)_(Length(x)>1) <-- MultiplyNum(Head(x), Tail(x));

10 # MultiplyNum(x_IsRational, y_IsRationalOrNumber) <--
[
	If(
        Type(y) = "/",  // IsRational(y), changed by Nobbi before redefinition of IsRational
		DivideN(Numer(x)*Numer(y), Denom(x)*Denom(y)),
		// y is floating-point
		// avoid multiplication or division by 1
		If(
			Numer(x)=1,
			DivideN(y, Denom(x)),
			If(
				Denom(x)=1,
				MultiplyN(y, Numer(x)),
				DivideN(MultiplyN(y, Numer(x)), Denom(x))
			)
		)
	);
];

20 # MultiplyNum(x_IsNumber, y_IsRational) <-- MultiplyNum(y, x);

25 # MultiplyNum(x_IsNumber, y_IsNumber) <-- MultiplyN(x,y);

30 # MultiplyNum(Complex(r_IsNumber, i_IsNumber), y_IsRationalOrNumber) <-- Complex(MultiplyNum(r, y), MultiplyNum(i, y));

35 # MultiplyNum(y_IsNumber, Complex(r_IsNumber, i_IsRationalOrNumber)) <-- MultiplyNum(Complex(r, i), y);

40 # MultiplyNum(Complex(r1_IsNumber, i1_IsNumber), Complex(r2_IsNumber, i2_IsNumber)) <-- Complex(MultiplyNum(r1,r2)-MultiplyNum(i1,i2), MultiplyNum(r1,i2)+MultiplyNum(i1,r2));

/// more than 2 operands
30 # MultiplyNum(x_IsRationalOrNumber, y_IsNumericList)_(Length(y)>1) <-- MultiplyNum(MultiplyNum(x, Head(y)), Tail(y));
40 # MultiplyNum(x_IsRationalOrNumber, y_IsNumericList)_(Length(y)=1) <-- MultiplyNum(x, Head(y));

//////////////////////////////////////////////////
/// Numerical method: Newton-like superconvergent iteration
//////////////////////////////////////////////////

// Newton's method, generalized, with precision control and diagnostics

/// auxiliary utility: compute the number of common decimal digits of x and y (using relative precision)
Common'digits(x,y) :=
[
	Local(diff);
	diff := Abs(x-y);
	If(
		diff=0,
		Infinity,
		// use approximation Ln(2)/Ln(10) > 351/1166
		Div(IntLog(FloorN(DivideN(Max(Abs(x), Abs(y)), diff)), 2)*351, 1166)
	); 	// this many decimal digits in common
];

///interface
NewtonNum(_func, _x0) <-- NewtonNum(func, x0, 5);	// default prec0
NewtonNum(_func, _x0, _prec0) <-- NewtonNum(func, x0, prec0, 2);

// func is the function to iterate, i.e. x' = func(x).
// prec0 is the initial precision necessary to get convergence started.
// order is the order of convergence of the given sequence (e.g. 2 or 3).
// x0 must be close enough so that x1 has a few common digits with x0 after at most 5 iterations.
NewtonNum(_func, _x'init, _prec0, _order) <--
[
	Check(prec0>=4, "NewtonNum: Error: initial precision must be at least 4");
	Check(IsInteger(order) And order>1, "NewtonNum: Error: convergence order must be an integer and at least 2");
	Local(x0, x1, prec, exact'digits, int'part, initial'tries);
  N([
    x0 := x'init;
    prec := BuiltinPrecisionGet();
    int'part := IntLog(Ceil(Abs(x0)), 10);	// how many extra digits for numbers like 100.2223
    // int'part must be set to 0 if we have true floating-point semantics of BuiltinPrecisionSet()
    BuiltinPrecisionSet(2+prec0-int'part);	// 2 guard digits
    x1 := (func @ x0);	// let's run one more iteration by hand
    // first, we get prec0 exact digits
    exact'digits := 0;
    initial'tries := 5;	// stop the loop the the initial value is not good
    While(exact'digits*order < prec0 And initial'tries>0)
    [
      initial'tries--;
      x0 := x1;
      x1 := (func @ x0);
      exact'digits := Common'digits(x0, x1);
  //		If(InVerboseMode(), Echo("NewtonNum: Info: got", exact'digits, "exact digits at prec. ", BuiltinPrecisionGet()));
    ];
    // need to check that the initial precision is achieved
    If(
      Assert("value", {"NewtonNum: Error: need a more accurate initial value than", x'init})
        exact'digits >= 1,
    [
    exact'digits :=Min(exact'digits, prec0+2);
    // run until get prec/order exact digits
    int'part := IntLog(Ceil(Abs(x1)), 10);	// how many extra digits for numbers like 100.2223
    While(exact'digits*order <= prec)
    [
      exact'digits := exact'digits*order;
      BuiltinPrecisionSet(2+Min(exact'digits, Div(prec,order)+1)-int'part);
      x0 := x1;
      x1 := (func @ x0);
  //		If(InVerboseMode(), Echo("NewtonNum: Info: got", Common'digits(x0, x1), "exact digits at prec. ", BuiltinPrecisionGet()));
    ];
    // last iteration by hand
    BuiltinPrecisionSet(2+prec);
    x1 := RoundTo( (func @ x1), prec);
    ],
    // did not get a good initial value, so return what we were given
    x1 := x'init
    );
    BuiltinPrecisionSet(prec);
  ]);
	x1;
];

/*
example: logarithm function using cubically convergent Newton iteration for
Exp(x/2)-a*Exp(-x/2)=0:

x' := x - 2 * (Exp(x)-a) / (Exp(x)+a)

LN(x_IsNumber)_(x>1 ) <--
	LocalSymbols(y)
[
// initial guess is obtained as Ln(x^2)/Ln(2) * (Ln(2)/2)
	NewtonNum({{y},4*x/(Exp(y)+x)-2+y}, N(794/2291*IntLog(Floor(x*x),2),5), 10, 3);
];
/**/

//////////////////////////////////////////////////
/// Numerical method: integer powers by binary reduction
//////////////////////////////////////////////////

/// generalized integer Power function using the classic binary method.
5 # IntPowerNum(_x, 0, _func, _unity) <-- unity;
10 # IntPowerNum(_x, n_IsInteger, _func, _unity) <--
[
	// use binary method
	Local(result);
	// unity might be of non-scalar type, avoid assignment
	While(n > 0)
	[
		If(
			(n&1) = 1,
			If(
				IsBound(result), // if result is already assigned
				result := Apply(func, {result,x}),
				result := x, // avoid multiplication
			)
		);
		x := Apply(func, {x,x});
		n := n>>1;
	];
	result;
];

//////////////////////////////////////////////////
/// Numerical method: binary splitting technique for simple series
//////////////////////////////////////////////////

/// Binary splitting for series of the form
/// S(m,n) = Sum(k,m,n, a(k)/b(k)*(p(0)*...*p(k))/(q(0)*...*q(k)))


/// High-level interface routine
BinSplitNum(m,n,a,b,p,q) := BinSplitFinal(BinSplitData(m,n,a,b,p,q));

/// Low-level routine: compute the floating-point answer from P, Q, B, T data
BinSplitFinal({_P,_Q,_B,_T}) <-- DivideN(T, MultiplyN(B, Q));

/// Low-level routine: combine two binary-split intermediate results
BinSplitCombine({_P1, _Q1, _B1, _T1}, {_P2, _Q2, _B2, _T2}) <-- {P1*P2, Q1*Q2, B1*B2, B1*P1*T2+B2*Q2*T1};

/// Low-level routine: compute the list of four integers P, Q, B, T. (T=BQS)
/// Input: m, n and four functions a,b,p,q of one integer argument.

// base of recursion
10 # BinSplitData(_m, _n, _a, _b, _p, _q)_(m>n) <-- {1,1,1,0};

10 # BinSplitData(_m, _n, _a, _b, _p, _q)_(m=n) <-- {p@m, q@m, b@m, (a@m)*(p@m)};

10 # BinSplitData(_m, _n, _a, _b, _p, _q)_(m+1=n) <-- {(p@m)*(p@n), (q@m)*(q@n), (b@m)*(b@n), (p@m)*((a@m)*(b@n)*(q@n)+(a@n)*(b@m)*(p@n))};

// could implement some more cases of recursion base, to improve speed

// main recursion step
20 # BinSplitData(_m, _n, _a, _b, _p, _q) <--
[
	BinSplitCombine(BinSplitData(m,(m+n)>>1, a,b,p,q), BinSplitData(1+((m+n)>>1),n, a,b,p,q));
];