Sign in Join
Wiki copytemporary

NumLib Documentation: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Introduction
22
33NumLib is a collection of routines for numerical mathematics. It was written by C.J.J.M. van Ginneken, W.J.P.M. Kortsmit, and L. Reij at the Technical University of Eindhoven (Netherlands) and donated to the Free Pascal project. Its beginnings date back to 1965. Originally written in Algol 60, ported to poorly formatted Pascal with strange procedure names, it is hard stuff, in particular because an official documentation is not readily available (well... -- Dutch documentation files in TeX can be found at [<http://www.stack.nl/~marcov/numlib/>](http://www.stack.nl/~marcov/numlib/)).
44
55But after all, this library contains a series of very useful routines grouped into several units:
66
77- Calculation of special functions (unit *spe*)
88- Solution of systems of linear equations (unit *sle*)
99- Finding roots of equations (unit *roo*)
1010- Fitting routines (unit *ipf*)
1111- Basic operations with matrices and vectors (unit *omv*)
1212- Matrix inversion (unit *inv*)
1313- Calculation of determinants (unit *det*)
1414- Calculation of eigenvalues (unit *eig*)
1515
1616Therefore, it is the intention of this wiki article to create some useful documentation. Each routine described here was tested and its description will be accompanied by a working code example.
1717
1818> **Note:** This page heavily makes use of mathematical formulas written in LaTeX. In order to see these formulas in the browser JavaScript must be enabled.
1919
2020### Naming convention of routines
2121
2222Would you imagine that a procedure named `spears` calculates the arcsin? The original NumLib routines limit the procedure/function names to 6 characters (maybe a limitation of Algol?). The first three characters always are identical with the name of the unit in which they are implemented. This leaves only three characters to differentiate between the routines...
2323
2424Routines later added to the original library are given more conventional names, such as `normaldist` for calculation of the normal distribution.
2525
2626## Data types and declarations (unit *typ*)
2727
2828### Basic floating point type, ArbFloat
2929
3030In NumLib, a new type `ArbFloat` is defined which is used as floating point type throughout the entire library. This allows to switch between IEEE Double (64bit) and Extended(80bit) (though big endian state is unknown at present).
3131
3232At the time of this writing the `$DEFINE ArbExtended` is enabled, and therefore `ArbFloat` is identical to the standard type `extended`. In order to change the floating point type define or undefine `ArbExtended` and adapt the machineconstants to the type selected.
3333
3434### Basic integer type, ArbInt
3535
3636Because in plain FPC mode the type Integer is 16-bits (for TP compatibility), and in Delphi 32-bits, all integers were changed to `ArbInt`. The basic idea is the same as with `ArbFloat`.
3737
3838### Vectors
3939
4040NumLib often passes **vectors** to procedures as one `ArbFloat` plus some integer value. This `ArbFloat` value is the first value of an array containing the vector, the integer is the count of elements used from this array. Internally an array type is mapped over the first value in order to access the other array elements. This way both 0- and 1-based arrays as well as static and dynamic arrays can be passed to the functions and procedures of NumLib.
4141
4242``` pascal
4343procedure DoSomething(var AVector: ArbFloat; n: Integer);
4444type
4545 TLocalVector = array[0..MaxElements] of ArbFloat;
4646var
4747 i: Integer;
4848 x: TLocalVector absolute AVector;
4949begin
5050 for i:=0 to n-1 do
5151 // do something with x[i]
5252end;
5353
5454var
5555 x: array[1..100]; // Note: This array begins with index 1!
5656...
5757 DoSomething(x[1], 100);
5858```
5959
6060### Matrices
6161
6262A matrix is a two-dimensional arrangement of numbers in *m* rows and *n* columns.
6363
6464
6565$`{A} = \left[
6666 \begin{array}{cccc}
6767 a_{11} & a_{12} & a_{13} & \ldots & a_{1n}\\
6868 a_{21} & a_{22} & a_{23} & \ldots & a_{2n}\\
6969 \vdots & \vdots & \vdots & \ddots & \vdots\\
7070 a_{m1} & a_{m2} & a_{m3} & \ldots & a_{mn}
7171 \end{array}
7272 \right]`$
7373
7474The matrix elements *a<sub>ij</sub>* are addressed by row and column indexes, *i* and *j*, respectively. In mathematics, the index values usually start with 1 (*i* = 1..*m*, *j* = 1..*n*) while in computing the first index is preferred to be 0 (*i* = 0...*m*-1, *j* = 0...*n*-1).
7575
7676#### Generic matrices
7777
7878In this type of matrix, no assumption is made on symmetry and shape of the data arrangement.
7979
8080A constant (real) matrix can be declared in Pascal as a two-dimensional array of floating point numbers: `A: array[1..m, 1..n] of ArbFloat` (or `A: array[0..m-1, 0..n-1] of ArbFloat` in 0-based index notation). Similarly, a constant complex matrix can be declared as an `array[1..m, 1..n] of complex`.
8181
8282When a NumLib procedure needs a matrix the first (top left) matrix element must be passed as a var parameter, along with the number of rows and columns. It is even possible to allocate larger arrays as needed, and in this case the allocated row length (or number of columns) must be specified.
8383
8484Alternatively a matrix also can be specified as a one-dimensional array. This works because all data are found within the same memory block. The array index, `k` must be converted to the 2D matrix indexes, `i, j`:
8585
8686``` pascal
8787 k := i * n + j <==> i = k div n, j := k mod n {for 0-based array and matrix indexes}
8888 k := (i-1) * n + j <==> i := (k-1) div n + 1, j := (k-1) mod n + 1 {for 1-based array and matrix indexes}
8989```
9090
9191Using a one-dimensional array is also the correct way to dynamically allocate memory for a matrix. The first idea of using an `array of array of ArbFloat` will not work because this does not allocate a single memory block. Instead, individual memory blocks will be allocated for the inner array (rows), and these pointers will be stored in the outer array - this way of storing 2D data is not compatible with the requirements of NumLib.
9292
9393Here is a simple example how to create, populate and read a matrix dynamically
9494
9595``` math
9696A = \left[
9797 \begin{array}{ccc}
9898 1 & 2 & 3 \\
9999 4 & 5 & 6
100100 \end{array}
101101 \right]
102102```
103103
104104``` pascal
105105var
106106 A: array of Arbfloat; // Note: the index in a dynamic array is 0-based.
107107 ij: Integer;
108108 i, j: Integer;
109109 p: ^ArbFloat;
110110 m, n: Integer;
111111begin
112112 m := 2;
113113 n := 3;
114114 SetLength(A, m, n);
115115 A[0] := 1; A[1] := 2; A[2] := 3;
116116 A[3] := 4; A[4] := 5; A[5] := 6;
117117
118118 for i := 0 to m-1 do begin
119119 for j := 0 to n-1 do begin
120120 ij := i * n + j;
121121 Write(A[ij]);
122122 end;
123123 WriteLn;
124124 end;
125125
126126 // or
127127 p := @A[0]; // pointer to first array element
128128 for i:=0 to m - 1 do begin
129129 for j := 0 to n - 1 do begin
130130 WriteLn(p^);
131131 inc(p); // advance pointer to next array element
132132 end;
133133 WriteLn;
134134 end;
135135```
136136
137137#### Symmetric matrices
138138
139139The elements of a symmetric matrix are mirrored at the main diagonal, i.e. *a<sub>ij</sub>* = *a<sub>ji</sub>*. In other words: a symmetric matrix is equal to its transpose: *A* = *A*<sup>T</sup>.
140140
141141NumLib contains some routine optimized for such matrices. They require the matrix being stored like a generic matrix in a rectangular m x n array or a linear m*n array. However, only the half of the matrix below the diagonal must be populated (but it can be, of course).
142142
143143#### Symmetric positive definite matrices
144144
145145A matrix *A* is positive definite if the product **d**<sup>T</sup> *A* **d** is a positive number for any non-zero vector **d**. A matrix is symmetric positive definite if, additionally, *A*<sup>T</sup> = *A*.
146146
147147This type of matrices has some interesting properties (determinant is never 0, eigenvalues are real and positive). Therefore NumLib offers specialized routines for many matrix problems with symmetric positive matrices which are superior to those for general matrices.
148148
149149The matrix is stored in the conventional way like a rectangular 2D array of *m* rows and *n* columns or a linear 1D array with *m***n* elements.
150150
151151#### Band matrices
152152
153153A *n* x *n* band matrix has zero elements everywhere except for a band along the main diagonal characterized by a left and right bandwidth (*l* and *r*, respectively).
154154
155155The NumLib procedures for band matrices assume that the matrix stores only the elements of the band. It is expected the the matrix is passed as a 1D array. Starting at the top/left corner point of the matrix (element *A*<sub>11</sub>) the diagonal elements are collected by running along the rows of the matrix, from left to right and from top to bottom. In total, there will be *n* (*l*+1+*r*) - (*r*(*r*+1) + *l*(*l*+1)) div 2 elements. The calling procedure must allocate a 1D array of at least this size for the diagonal elements. Note that a band matrix cannot be passed as a 2D array.
156156
157157As an example, suppose this 7 x 7 matrix with left bandwidth 2 and right bandwidth 1:
158158
159159$`A = \begin{bmatrix}
160160 A_{11} & A_{12} & 0 & 0 & 0 & 0 & 0 \\
161161 A_{21} & A_{22} & A_{23} & 0 & 0 & 0 & 0 \\
162162 A_{31} & A_{32} & A_{33} & A_{34} & 0 & 0 & 0 \\
163163 0 & A_{42} & A_{43} & A_{44} & A_{45} & 0 & 0 \\
164164 0 & 0 & A_{53} & A_{54} & A_{55} & A_{56} & 0 \\
165165 0 & 0 & 0 & A_{64} & A_{65} & A_{66} & A_{67} \\
166166 0 & 0 & 0 & 0 & A_{75} & A_{76} & A_{77}
167167 \end{bmatrix}`$
168168
169169The 1D data array to be passed to the NumLib routines consists of these elements
170170
171171``` pascal
172172const
173173 arr: array[1..24] of ArbFloat = (
174174 A11, A22,
175175 A21, A22, A23,
176176 A31, A32, A33, A34,
177177 A42, A43, A44, A45,
178178 A53, A54, A55, A56,
179179 A64, A65, A66, A67,
180180 A75, A76, A77
181181 );
182182```
183183
184184#### Symmetric band matrices
185185
186186In a symmetric matrix, the matrix elements can be mirrored at the main diagonal of the matrix, i.e. *A*<sub>ij</sub> = *A*<sub>ji</sub>. All elements out side a band of width w to the left and right of the main diagonal are zero.
187187
188188Like with a generic band matrix, NumLib assumes that the only the elements of the diagonals are passed as a 1D array to the procedures dedicated to this matrix type. Since in a symmetric band matrix the left and right band widths are equal it is assumed that only the left band and the main diagonal are stored. Again the relevant diagonal elements are collected by running across the rows of the matrix from left to right and from top to bottom, stopping in each row at the main diagonal. In total, there will be *n* (*w*+1) - *w*(*w*+1) div 2 elements in the data array.
189189
190190As an example consider this 7 x 7 symmetric band matrix with band width 2:
191191
192192$`A = \begin{bmatrix}
193193 A_{11} & A_{21} & A_{31} & 0 & 0 & 0 & 0 \\
194194 A_{21} & A_{22} & A_{32} & A_{42} & 0 & 0 & 0 \\
195195 A_{31} & A_{32} & A_{33} & A_{43} & A_{53} & 0 & 0 \\
196196 0 & A_{42} & A_{43} & A_{44} & A_{54} & A_{64} & 0 \\
197197 0 & 0 & A_{53} & A_{54} & A_{55} & A_{65} & A_{75} \\
198198 0 & 0 & 0 & A_{64} & A_{65} & A_{66} & A_{76} \\
199199 0 & 0 & 0 & 0 & A_{75} & A_{76} & A_{77}
200200 \end{bmatrix}`$
201201
202202The 1D array for the NumLib procedures is
203203
204204``` pascal
205205const
206206 arr: array[1..18] of ArbFloat = (
207207 A11,
208208 A21, A22,
209209 A31, A32, A33,
210210 A42, A43, A44,
211211 A53, A54, A55,
212212 A64, A65, A66,
213213 A75, A76, A77
214214 );
215215```
216216
217217#### Tridiagonal matrices
218218
219219A tridiagonal matrix has zero elements everywhere except for the main diagonal and the diagonal immediately above and below it. This way the matrix can be stored more efficiently than a generic matrix because all the empty elements can be dropped. The elements of an n x n square tridiagonal matrix can be stored as three vectors
220220
221221- an array `l` with `n-1` elements for the subdiagonal
222222- an array `d` with `n` elements for the main diagonal, and
223223- an array `u` with `n-1` elements for the superdiagonal
224224
225225
226226$`A=
227227 \begin{bmatrix}
228228 d[0] & u[0] & & \dots & 0 \\
229229 l[1] & d[1] & u[1] & & \\
230230 & l[2] & d[2] & u[2] & \\
231231 \vdots & & \ddots & \ddots & \ddots \\
232232 & & l[n-1] & d[n-1] & u[n-1] \\
233233 0 & & & l[n] & d[n]
234234 \end{bmatrix}`$
235235
236236#### Symmetric tridiagonal matrices
237237
238238In these tridiagonal matrices, the subdiagonal and superdiagonal elements are equal. Consequently, NumLib requests only two, instead of three, one-dimensional arrays:
239239
240240- an array `d` with `n` elements for the main diagonal, and
241241- an array `c` with `n-1` elements for the superdiagonal and subdiagonal
242242
243243
244244$`A=
245245 \begin{bmatrix}
246246 d[0] & c[1] & & \dots & 0 \\
247247 c[1] & d[1] & c[2] & & \\
248248 & c[2] & d[2] & c[3] & \\
249249 \vdots & & \ddots & \ddots & \ddots \\
250250 & & c[n-1] & d[n-1] & c[n] \\
251251 0 & & & c[n] & d[n]
252252 \end{bmatrix}`$
253253
254254### Complex numbers
255255
256256NumLib defines the type `complex` as a Turbo Pascal `object`. Real and imaginary parts are denoted as `xreal` and `imag`, respectively. Methods for basic operations are provided:
257257
258258``` pascal
259259type
260260 complex = object
261261 xreal, imag : ArbFloat;
262262 procedure Init(r, i: ArbFloat); // Initializes the number with real and imaginary parts r and i, resp
263263 procedure Add(c: complex); // Adds another complex number
264264 procedure Sub(c: complex); // Subtracts a complex number
265265 function Inp(z:complex):ArbFloat; // Calculates the inner product (a.Re * b.Re + a.Im * b.Im)
266266 procedure Conjugate; // Conjugates the value (i.e. negates the imaginary part)
267267 procedure Scale(s: ArbFloat); // Multiplies real and imaginary part by the real value s
268268 function Norm: ArbFloat; // Calculates the "norm", i.e. sqr(Re) + sqr(Im)
269269 function Size: ArbFloat; // Size = sqrt(Norm)
270270 function Re: ArbFloat; // Returns the real part of the complex number
271271 procedure Unary; // Negates real and imaginary parts
272272 function Im: ArbFloat; // Returns the imaginary part of the complex number
273273 function Arg: ArbFloat; // Calculates the phase phi of the complex number z = x + y i = r exp(i phi)
274274 procedure MinC(c: complex); // Converts itself to the minimum of real and imaginary parts of itself and another value, c
275275 procedure MaxC(c: complex); // Converts itself to the maximum of real and imaginary ports of itself and another value, c
276276 procedure TransF(var t: complex);
277277 end;
278278```
279279
280280The real and imaginary parts of a complex number can also be retrieved by these stand-alone functions:
281281
282282``` pascal
283283 function Re(c: complex): ArbFloat;
284284 function Im(c: complex): ArbFloat;
285285```
286286
287287## Basic operations with matrices and vectors (unit *omv*)
288288
289289### Inner product of two vectors
290290
291291The result of the inner product of two vectors **a** = \[a<sub>1</sub>, a<sub>2</sub>, ... a<sub>n</sub>\] and **b** = \[b<sub>1</sub>, b<sub>2</sub>, ... b<sub>n</sub>\] (also called "dot product" or "scalar product") is a scalar, i.e. a single number. It is calculated as the sum of the element-by-element products of the vectors. Both vectors must have the same lengths.
292292
293293
294294$`\mathbf{a} \cdot \mathbf{b} = \sum_{i=1}^n a_i b_i = a_1 b_1 + a_2 b_2 + \dots + a_n b_n`$
295295
296296NumLib provides the function `omvinp` for calculation of the inner product:
297297
298298``` pascal
299299function omvinp(var a, b: ArbFloat; n: ArbInt): ArbFloat;
300300```
301301
302302- `a` and `b` are the first elements of 1-dimensional arrays representing the vectors **a** and **b**, respectively.
303303- `n` defines the dimension of the vectors (count of array elements). Both vectors must have the same number of elements.
304304
305305**Example**
306306
307307Calculate the dot product of the vectors **a** = \[0, 1, 2, 2, -1\] and **b** = \[3, -1, -2, 2, -1\].
308308
309309``` pascal
310310program dot_product;
311311
312312uses
313313 typ, omv;
314314
315315const
316316 n = 5;
317317 a: array[0..n-1] of ArbFloat = (0, 1, 2, 2, -1);
318318 b: array[0..n-1] of Arbfloat = (3, -1, -2, 2, -1);
319319
320320var
321321 ab: ArbFloat;
322322 i: Integer;
323323
324324begin
325325 ab := omvinp(a[0], b[0], n);
326326
327327 Write('Vector a = [');
328328 for i:= Low(a) to High(a) do
329329 Write(a[i]:5:0);
330330 WriteLn(' ]');
331331
332332 Write('Vector b = [');
333333 for i:= Low(b) to High(b) do
334334 Write(b[i]:5:0);
335335 WriteLn(' ]');
336336
337337 Write('Dot product a b = ');
338338 WriteLn(ab:0:0);
339339end.
340340```
341341
342342### Product of two matrices
343343
344344The product of a m x n matrix A and a n x p matrix B is a m x p matrix C in which the elements are calculated as the inner product of the row vectors of A and the column vectors of B (see [here](https://en.wikipedia.org/wiki/Matrix_multiplication) for more details):
345345
346346
347347$`C_{ij} = \sum_{k=0}^n A_{ik} B_{kj}`$
348348
349349The NumLib function to perform this multipication is `omvmmm` ("mmm" = **m**ultiply **m**atrix by **m**atrix):
350350
351351``` pascal
352352procedure omvmmm(var a: ArbFloat; m, n, rwa: ArbInt;
353353 var b: ArbFloat; p, rwb: ArbInt;
354354 var c: ArbFloat; rwc: ArbInt);
355355```
356356
357357- `a` is the first element of the first input matrix *A*. The array will not be changed.
358358 - `m` specifies the number of rows (or: column length) of matrix *A*.
359359 - `n` specifies the number or columns (or: row length) of matrix *A*.
360360 - `rwa` is the allocated row length of matrix *A*. This means that the array can be allocated larger than actually needed. Of course, `rwa` cannot be smaller than `n`.
361361
362362- `b` is the first element of the second input matrix *B*. Again, this array will not be changed.
363363 - `p` specifies the number or columns (or: row length) of matrix *B*. The number of rows is already defined by the number of columns of matrix *A*, `n`.
364364 - `rwb` is the allocated row length of matrix *B*. `rwb` cannot be smaller than `p`.
365365
366366- `c` is the first element of the output matrix *C*. After the calculation, this and the following array elements will contain the elements of the product matrix.
367367 - The size of the output matrix is `m` rows x `p` columns as set up by the input matrices *A* and *B*.
368368 - `rwc` is the allocated row length of matrix *C*. `rwc` cannot be smaller than `p`.
369369
370370**Example**
371371
372372Calculate the product of the matrices
373373
374374
375375$`A = \begin{bmatrix}
376376 3 & \ 5 & 4 & 1 & -4 \\
377377 -2 & \ 3 & 4 & 1 & 0 \\
378378 0 & \ 1 & -1 & -2 & 5
379379 \end{bmatrix}
380380 \ \ \ \ \
381381 B = \begin{bmatrix}
382382 3 & 4 & -2 & 0 \\
383383 0 & -2 & 2 & 1 \\
384384 -1 & 0 & 3 & 4 \\
385385 -2 & -3 & 0 & 1 \\
386386 1 & 3 & 0 & -1
387387 \end{bmatrix}`$
388388
389389``` pascal
390390program matrix_multiplication;
391391
392392uses
393393 typ, omv;
394394
395395const
396396 m = 3;
397397 n = 5;
398398 p = 4;
399399
400400 A: array[1..m, 1..n] of ArbFloat = (
401401 ( 3, 5, 4, 1, -4),
402402 (-2, 3, 4, 1, 0),
403403 ( 0, 1, -1, -2, 5)
404404 );
405405 B: array[1..n, 1..p] of ArbFloat = (
406406 ( 3, 4, -2, 0),
407407 ( 0, -2, 2, 1),
408408 (-1, 0, 3, 4),
409409 (-2, -3, 0, 1),
410410 ( 1, 3, 0, -1)
411411 );
412412
413413var
414414 C: array[1..m, 1..p] of ArbFloat;
415415 i, j: Integer;
416416
417417begin
418418 // Calculate product
419419 omvmmm(
420420 A[1,1], m, n, n,
421421 B[1,1], p, p,
422422 C[1,1], p
423423 );
424424
425425 // Print A
426426 WriteLn('A = ');
427427 for i:= 1 to m do begin
428428 for j := 1 to n do
429429 Write(A[i, j]:10:0);
430430 WriteLn;
431431 end;
432432 WriteLn;
433433
434434 // Print B
435435 WriteLn('B = ');
436436 for i:= 1 to n do begin
437437 for j := 1 to p do
438438 Write(B[i, j]:10:0);
439439 WriteLn;
440440 end;
441441 WriteLn;
442442
443443 // Print C
444444 WriteLn('C = A B = ');
445445 for i:= 1 to m do begin
446446 for j := 1 to p do
447447 Write(C[i, j]:10:0);
448448 WriteLn;
449449 end;
450450end.
451451```
452452
453453### Product of a matrix with a vector
454454
455455A matrix *A* of size m x n multiplied with a vector **b** of length n yields another vector of length m. The elements of the result vector **c** are the dot products of the matrix rows with **b**. Note that the length of vector **b** must be equal to the number of columns of matrix *A*.
456456
457457
458458$`\begin{align*}
459459 \mathbf{c} = A\ \mathbf{b}=
460460 \left[
461461 \begin{array}{cccc}
462462 a_{11} & a_{12} & a_{13} & \ldots & a_{1n}\\
463463 a_{21} & a_{22} & a_{23} & \ldots & a_{2n}\\
464464 \vdots & \vdots & \vdots & \ddots & \vdots\\
465465 a_{m1} & a_{m2} & a_{m3} & \ldots & a_{mn}
466466 \end{array}
467467 \right]
468468 \ \
469469 \left[
470470 \begin{array}{c}
471471 b_1\\
472472 b_2\\
473473 b_3\\
474474 \vdots\\
475475 b_n
476476 \end{array}
477477 \right]
478478 =
479479 \left[
480480 \begin{array}{c}
481481 a_{11}b_1+a_{12}b_2 + a_{13}b_3 + \cdots + a_{1n} b_n\\
482482 a_{21}b_1+a_{22}b_2 + a_{23}b_3 + \cdots + a_{2n} b_n\\
483483 \vdots\\
484484 a_{m1}b_1+a_{m2}b_2 + a_{m3}b_3 + \cdots + a_{mn} b_n\\
485485 \end{array}
486486 \right].
487487\end{align*}`$
488488
489489NumLib's procedure for this task is called `omvmmv` ("mmv" = **m**ultiply a **m**atrix with a **v**ector):
490490
491491``` pascal
492492procedure omvmmv(var a: ArbFloat; m, n, rwidth: ArbInt; var b, c: ArbFloat);
493493```
494494
495495**Example**
496496
497497Calculate the product
498498
499499
500500$`\begin{align*}
501501 \mathbf{c} = A\ \mathbf{b}=
502502 \left[
503503 \begin{array}{cccc}
504504 3 & 5 & 4 & 1 & -4\\
505505 -2 & 3 & 4 & 1 & 0\\
506506 0 & 1 & -1 & -2 & 5
507507 \end{array}
508508 \right]
509509 \ \
510510 \left[
511511 \begin{array}{c}
512512 3\\
513513 0\\
514514 -1\\
515515 -2\\
516516 1
517517 \end{array}
518518 \right]
519519\end{align*}`$
520520
521521``` pascal
522522program matrix_vector_multiplication;
523523
524524uses
525525 typ, omv;
526526
527527const
528528 m = 3;
529529 n = 5;
530530 p = 4;
531531
532532 A: array[1..m, 1..n] of ArbFloat = (
533533 ( 3, 5, 4, 1, -4),
534534 (-2, 3, 4, 1, 0),
535535 ( 0, 1, -1, -2, 5)
536536 );
537537 b: array[1..n] of ArbFloat = (
538538 3, 0, -1, -2, 1
539539 );
540540
541541var
542542 c: array[1..m] of ArbFloat;
543543 i, j: Integer;
544544
545545begin
546546 // Calculate product c = A b
547547 omvmmv(
548548 A[1,1], m, n, n,
549549 b[1],
550550 c[1]
551551 );
552552
553553 // Print A
554554 WriteLn('A = ');
555555 for i:= 1 to m do begin
556556 for j := 1 to n do
557557 Write(A[i, j]:10:0);
558558 WriteLn;
559559 end;
560560 WriteLn;
561561
562562 // Print vector b
563563 WriteLn('b = ');
564564 for i:= 1 to n do
565565 Write(b[i]:10:0);
566566 WriteLn;
567567 WriteLn;
568568
569569 // Print result vector c
570570 WriteLn('c = A b = ');
571571 for i:= 1 to m do
572572 Write(c[i]:10:0);
573573 WriteLn;
574574end.
575575```
576576
577577### Transpose matrix
578578
579579The [transpose matrix](https://en.wikipedia.org/wiki/Transpose) *A<sup>T</sup>* of matrix *A* is obtained by flipping rows and columns:
580580
581581
582582$`\begin{align*}
583583 {A} = \left[
584584 \begin{array}{cccc}
585585 a_{11} & a_{12} & a_{13} & \ldots & a_{1n}\\
586586 a_{21} & a_{22} & a_{23} & \ldots & a_{2n}\\
587587 \vdots & \vdots & \vdots & \ddots & \vdots\\
588588 a_{m1} & a_{m2} & a_{m3} & \ldots & a_{mn}
589589 \end{array}
590590 \right]
591591 \ \ \
592592 {A^T} =
593593 \left[
594594 \begin{array}{cccc}
595595 a_{11} & a_{21} & \ldots & a_{m1}\\
596596 a_{12} & a_{22} & \ldots & a_{m2}\\
597597 a_{13} & a_{23} & \ldots & a_{m3} \\
598598 \vdots & \vdots & \ddots & \vdots \\
599599 a_{1n} & a_{2n} & \ldots & a{mn}
600600 \end{array}
601601 \right]
602602\end{align*}`$
603603
604604Use the procedure `omvtrm` to perform this operation with NumLib:
605605
606606``` pascal
607607procedure omvtrm(
608608 var a: ArbFloat; m, n, rwa: ArbInt;
609609 var c: ArbFloat; rwc: ArbInt
610610);
611611```
612612
613613- `a` denotes the first element of the input matrix *A*. The elements of this array are not changed by the procedure.
614614 - `m` is the number of rows of matrix *A*.
615615 - `n` is the number of columns of matrix *A*.
616616 - `rwa` is the number of allocated columns of *A*. This way the array of *A* can be larger than acutally needed.
617617- `c` is the first element of the transposed matrix *A<sup>T</sup>*. It has `n` rows and `m` columns.
618618 - `rwc` is the number of allocated columns for the transposed matrix.
619619
620620**Example**
621621
622622Obtain the transpose of the 2x4 matrix
623623
624624
625625$`{A} = \left[
626626 \begin{array}{cccc}
627627 1 & 2 & 3 & 4 \\
628628 11 & 22 & 33 & 44
629629 \end{array}
630630 \right]`$
631631
632632``` pascal
633633program transpose;
634634
635635uses
636636 typ, omv;
637637
638638const
639639 m = 2;
640640 n = 4;
641641
642642 A: array[1..m, 1..n] of ArbFloat = (
643643 ( 1, 2, 3, 4),
644644 (11, 22, 33, 44)
645645 );
646646
647647var
648648 C: array[1..n, 1..m] of ArbFloat;
649649 i, j: Integer;
650650
651651begin
652652 // Transpose A
653653 omvtrm(
654654 A[1,1], m, n, n,
655655 C[1,1], m
656656 );
657657
658658 // Print A
659659 WriteLn('A = ');
660660 for i:= 1 to m do begin
661661 for j := 1 to n do
662662 Write(A[i, j]:10:0);
663663 WriteLn;
664664 end;
665665 WriteLn;
666666
667667 // Print C
668668 WriteLn('AT = ');
669669 for i:= 1 to n do begin
670670 for j := 1 to m do
671671 Write(C[i, j]:10:0);
672672 WriteLn;
673673 end;
674674end.
675675```
676676
677677### Norm of vectors and matrices
678678
679679A norm is a function which assigns a positive "length" to a [vector **a**](https://en.wikipedia.org/wiki/Norm_(mathematics)) or a [matrix *M*](https://en.wikipedia.org/wiki/Matrix_norm).
680680
681681In case of vectors, NumLib supports these norms:
682682
683683- The **1-norm** of a vector is defined as the sum of the absolute values of the vector components. It is also called "Taxicab" or "Manhattan" norm because it corresponds to the distance that a taxis has to drive in a rectangular grid of streets.
684684
685685
686686$`\|a\|_1 = \sum_{i=1}^n |{a_i}|`$
687687
688688- The **2-norm** (also: Euclidian norm) corresponds to the distance of point **a** from the origin.
689689
690690
691691$`\|a\|_2 = \sqrt{\sum_{i=1}^n {a_i}^2}`$
692692
693693- The **maximum infinite norm** of a vector is the maximum absolute value of its components.
694694
695695
696696$`\|a\|_\infty = \max({a_1}, {a_2}, ... {a_n})`$
697697
698698The norms of matrices, as implemented by NumLib, are usually defined by row or column vectors:
699699
700700- The **1-norm** of a matrix is the maximum of the absolute column sums:
701701
702702
703703$`\|M\|_1 = \max_{1 \le j \le {n}} \sum_{i=1}^m|M_{ij}|`$
704704
705705- The **maximum infinite norm** of a matrix is the maximum of the absolute row sum.
706706
707707
708708$`\|M\|_\infty = \max_{1 \le i \le\ m} \sum_{j=1}^n |M_{ij}|`$
709709
710710- Only the **Frobenius norm** is calculated by individual matrix elements. It corresponds to the 2-norm of vectors, only the sum runs over all matrix elements:
711711
712712
713713$`\|M\|_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n {M_{ij}}^2}`$
714714
715715These are the NumLib procedure for calculation of norms:
716716
717717``` pascal
718718function omvn1v(var a: ArbFloat; n: ArbInt): ArbFloat; // 1-norm of a vector
719719function omvn1m(var a: ArbFloat; m, n, rwidth: ArbInt): ArbFloat; // 1-norm of a matrix
720720function omvn2v(var a: ArbFloat; n: ArbInt): ArbFloat; // 2-norm of a vector
721721function omvnfm(Var a: ArbFloat; m, n, rwidth: ArbInt): ArbFloat; // Frobenius norm of a matrix
722722function omvnmv(var a: ArbFloat; n: ArbInt): ArbFloat; // Maximum infinite norm of a vector
723723function omvnmm(var a: ArbFloat; m, n, rwidth: ArbInt): ArbFloat; // Maximum infinite norm of a matrix
724724```
725725
726726- `a` is the first element of the vector or matrix of which the norm is to be calculated
727727- `m`, in case of a matrix norm, is the number of matrix rows.
728728- `n` is the number of vector elements in case of a vector norm, or the number of columns in case of a matrix norm.
729729- `rwidth`, in case of a matrix norm, is the allocated count of columns. This way a larger matrix can be allocated than actually needed.
730730
731731**Example**
732732
733733``` pascal
734734program norm;
735735
736736uses
737737 typ, omv;
738738
739739const
740740 n = 2;
741741 m = 4;
742742 a: array[0..m-1] of ArbFloat = (0, 1, 2, -3);
743743 b: array[0..m-1, 0..n-1] of Arbfloat = ((3, -1), (-2, 2), (0, -1), (2, 1));
744744
745745var
746746 i, j: Integer;
747747
748748begin
749749 Write('Vector a = [');
750750 for i:= Low(a) to High(a) do
751751 Write(a[i]:5:0);
752752 WriteLn(' ]');
753753 WriteLn(' 1-norm of vector a: ', omvn1v(a[0], m):0:3);
754754 WriteLn(' 2-norm of vector a: ', omvn2v(a[0], m):0:3);
755755 WriteLn(' maximum norm of vector a: ', omvnmv(a[0], m):0:3);
756756 WriteLn;
757757
758758 WriteLn('Matrix b = ');
759759 for i:= 0 to m-1 do begin
760760 for j := 0 to n-1 do
761761 Write(b[i,j]:5:0);
762762 WriteLn;
763763 end;
764764 WriteLn(' 1-norm of matrix b: ', omvn1m(b[0, 0], m, n, n):0:3);
765765 WriteLn('Forbenius norm of matrix b: ', omvnfm(b[0, 0], m, n, n):0:3);
766766 WriteLn(' maximum norm of matrix b: ', omvnmm(b[0, 0], m, n, n):0:3);
767767
768768 ReadLn;
769769end.
770770```
771771
772772## Determinant of a matrix (unit *det*)
773773
774774Unit *det* provides a series of routines for calculation of the [determinant](https://en.wikipedia.org/wiki/Determinant) of a square matrix. Besides a procedure for general matrices there are also routines optimized for special matrix types (tridiagonal, symmetric).
775775
776776In order to avoid possible overflows and underflows a factor may be split off during calculation, and the determinant is returned as a floating point number *f* times a multiplier *8<sup>k</sup>*. In many cases, however, *k = 0*, and the determinant simply is equal to *f*.
777777
778778### Determinant of a standard matrix
779779
780780The term "standard matrix" means here a matrix with is stored in the standard NumLib storage as described here, i.e. the matrix is stored as a 2D or 1D array of real numbers. The 2D array has at least *n* rows and *n* columns, the 1D array is dimensioned to contain at least *n*<sup>2</sup> elements.
781781
782782Without making any assumptions on the properties of the matrix the determinant can be calculated by the procedure `detgen`. More efficient algorithms can be used, however, if the matrix has these particular properties:
783783
784784- symmetric matrix --\> use procedure `detgsy`.
785785- symmetric positive definite matrix --\> use procedure `getgpd`.
786786
787787``` pascal
788788procedure detgen(n, rwidth: ArbInt; var a, f: ArbFloat; var k, term: ArbInt); // --> generic matrix
789789procedure detgsy(n, rwidth: ArbInt; var a, f: ArbFloat; var k, term: ArbInt); // --> symmetric matrix
790790procedure detgpd(n, rwidth: ArbInt; var a, f: ArbFloat; var k, term: ArbInt); // --> symmetric positive definite matrix
791791```
792792
793793- `n` determines the number of rows and columns of the input matrix (it must be square matrix).
794794- `rwidth` specifies the allocated row length of the input matrix. This takes care of the fact that matrices can be allocated larger than actually needed.
795795- `a` is the first (i.e. top/left) element of the input matrix. The entire matrix must be contained within the same allocated memory block.
796796- `f` and `k` return the determinant of the matrix. The determinant is given by *f * 8<sup>k</sup>*. Usually *k = 0*, and the determinant simply is equal to *f*.
797797
798798**Example**
799799
800800Calculate the determinant of the 4x4 matrix
801801
802802
803803$`A=
804804 \left[
805805 \begin{array}{rrrr}
806806 4 & 2 & 4 & 1 \\
807807 30 & 20 & 45 & 12 \\
808808 20 & 15 & 36 & 10 \\
809809 35 & 28 & 70 & 20
810810 \end{array}
811811 \right]`$
812812
813813The matrix is positive definite because all elements are positive and the product **b**<sup>T</sup> *A* **b** cannot become zero or negative for any non-zero vector **b**. While the routine `detgpd` is the best for this matrix type the code below contains also commented-out calls to the other routines.
814814
815815``` pascal
816816program determinant_spd_matrix; // "spd" = symmetric positive definite
817817
818818uses
819819 math, typ, det;
820820
821821const
822822 n = 4;
823823
824824const
825825 a: array[1..n, 1..n] of ArbFloat = (
826826 ( 4, 2, 4, 1)
827827 (30, 20, 45, 12)
828828 (20, 15, 36, 10)
829829 (35, 28, 70, 20)
830830 );
831831
832832var
833833 f: ArbFloat;
834834 k: Integer;
835835 term: Integer = 0;
836836 i, j: Integer;
837837
838838begin
839839 // write input matrix
840840 WriteLn('A = ');
841841 for i := 1 to n do begin
842842 for j := 1 to n do
843843 Write(a[i, j]:10:0);
844844 WriteLn;
845845 end;
846846 WriteLn;
847847
848848 // Calculate determinant of symmetric positive definite matrix
849849 detgpd(n, n, a[1, 1], f, k, term);
850850
851851 // or uncomment to use one of the other methods
852852 // detgen(n, n, a[1, 1], f, k, term);
853853 // detgsy(n, n, a[1, 1], f, k, term);
854854
855855 if term = 1 then
856856 WriteLn('Determinant of A = ', f * intpower(8, k):0:9, ' (k = ', k, ')')
857857 else
858858 WriteLn('Incorrect input');
859859end.
860860```
861861
862862### Determinant of a band matrix
863863
864864If a matrix has the shape of a band matrix then its determinant is best calculated by using the procedure `detgba`
865865
866866``` pascal
867867procedure detgba(n, l, r: ArbInt; var a, f: ArbFloat; var k, term:ArbInt);
868868```
869869
870870- `n` denotes the number of columns and rows of the matrix (it must be square).
871871- `l` identifies the left bandwidth, i.e. how many diagonals the band extends to the left, or below, the main diagonal.
872872- `r` identifies the right bandwidth, i.e. how many diagons the band extends to the right, or above, the main diagonsl.
873873- `a` specifies the first element of a 1D array which contains the elements of the diagonal band, see this. This array contains only the band elements and is obtained by running across the rows of the band matrix from left to right and top to bottom, starting at element *A*<sub>11</sub>. It must be dimensioned to contain at least `n*(l+1+r) - (r*(r+1)+l*(l+1)) div 2` elements.
874874- `f` and `k` return the determinant of the matrix. The determinant is given by *f* * 8<sup>*k*</sup>. Usually *k = 0*, and the determinant simply is equal to *f*.
875875
876876**Example**
877877
878878Calculate the determinant of the 5x5 band matrix with left bandwidth *l* = 2 and right bandwidth *r* = 1
879879
880880``` math
881881A = \begin{bmatrix}
882882 1 & 2 & 0 & 0 & 0 \\
883883 2 & 1 & 1 & 0 & 0 \\
884884 1 & 3 & 0 & 1 & 0 \\
885885 0 & 1 & 1 & 1 & 1 \\
886886 0 & 0 & 1 & 1 & 1
887887 \end{bmatrix}
888888```
889889
890890``` pascal
891891program determinant_band_matrix;
892892
893893uses
894894 math, typ, det;
895895
896896const
897897 n = 5;
898898 L = 2;
899899 R = 1;
900900 nelements = n*(L+1+R) - (R*(R+1) + L*(L+1)) div 2;
901901
902902const
903903 // Diagonal elements arranged in rows, elements outside of the band omitted!
904904 a: array[1..nelements] of Arbfloat = (
905905 1, 2,
906906 2, 1, 1,
907907 1, 3, 0, 1,
908908 1, 1, 1, 1,
909909 1, 1, 1
910910 );
911911var
912912 f: ArbFloat;
913913 term: Integer = 0;
914914 i, i1, i2, j, k: Integer;
915915
916916begin
917917 // Write diagonal elements
918918 WriteLn('n = ', n);
919919 Writeln('Left band width L = ', L);
920920 WriteLn('Right band width R = ', R);
921921 Write('Diagonal elements of A = ', a[1]:0:0);
922922 for k := 2 to nelements do
923923 Write(a[k]:5:0);
924924 WriteLn;
925925 WriteLn;
926926
927927 // write reconstructed band input matrix (not needed for calculation)
928928 WriteLn('Reconstructed A = ');
929929 k := 1;
930930 for j := 1 to n do begin
931931 i1 := j - L;
932932 i2 := i1 + L + R;
933933 for i:=1 to i1-1 do
934934 Write(0.0:5:0);
935935 for i := i1 to i2 do
936936 if (i >= 1) and (i <= n) then begin
937937 Write(a[k]:5:0);
938938 inc(k);
939939 end;
940940 for i:= i2 + 1 to n do
941941 if i <= n then
942942 Write(0.0:5:0);
943943 WriteLn;
944944 end;
945945 WriteLn;
946946
947947 // Calculate determinant
948948 detgba(n, l, r, a[1], f, k, term);
949949 if term = 1 then
950950 WriteLn('Determinant of A = ', f * intpower(8, k):0:9, ' (k = ', k, ')')
951951 else
952952 WriteLn('Incorrect input');
953953end.
954954```
955955
956956### Determinant of a symmetric positive definite band matrix
957957
958958If a matrix has the shape of a band matrix and is symmetric positive definite then `detgpb` is the dedicated routine for calculation of its determinant.
959959
960960``` pascal
961961procedure detgpb(n, w: ArbInt; var a, f: ArbFloat; var k, term:ArbInt);
962962```
963963
964964- `n` denotes the number of columns and rows of the matrix (it must be square).
965965- `w` identifies the bandwidth, i.e. how many diagonals the band extends to the left or to the right of the main diagonal. Left and right bandwidth must be equal.
966966- `a` specifies the first element of a 1D array which contains the elements of the diagonal band, see this. This array contains only the left band elements and the elements of the main diagonal. It is obtained by running across the rows of the band matrix from left to right and top to bottom, but stopping at the main diagonal - the right band is ignored since it is equal to the left band. The array must be dimensioned to contain at least `n*(w+1) - (w*(w+1)) div 2` elements.
967967- `f` and `k` return the determinant of the matrix. The determinant is given by *f* * 8<sup>*k*</sup>. Usually *k = 0*, and the determinant simply is equal to *f*.
968968
969969**Example**
970970
971971Calculate the determinant of the 7x7 symmetric band matrix with bandwidth *w* = 2 $`A= \begin{bmatrix}
972972 5 & -4 & 1 & & & & \\
973973 -4 & 6 & -4 & 1 & & & \\
974974 1 & -4 & 6 & -4 & 1 & & \\
975975 & 1 & -4 & 6 & -4 & 1 & \\
976976 & & 1 & -4 & 6 & -4 & 1 \\
977977 & & & 1 & -4 & 6 & -4 \\
978978 & & & & 1 & -4 & 5
979979 \end{bmatrix}`$
980980
981981``` pascal
982982program determinant_spd_band_matrix; // "spd" = symmetric positive definite
983983
984984uses
985985 math, typ, det;
986986
987987const
988988 n = 7;
989989 w = 2;
990990 nelements = n * (w + 1) - (w * (w + 1)) div 2;
991991
992992const
993993 // Band elements arragned in rows, elements outside of the band omitted!
994994 a: array[1..nelements] of Arbfloat = (
995995 5,
996996 -4, 6,
997997 1, -4, 6,
998998 1, -4, 6,
999999 1, -4, 6,
10001000 1, -4, 6,
10011001 1, -4, 5
10021002 );
10031003
10041004 function MatrixIndexToArrayIndex(i, j, n, w: Integer): Integer;
10051005
10061006 function DiagElementIndex(i: Integer): Integer;
10071007 var
10081008 k: Integer;
10091009 begin
10101010 Result := 1;
10111011 if i = 1 then
10121012 exit;
10131013 // Rows truncated at left
10141014 for k := 2 to w do begin
10151015 Result := Result + k;
10161016 if k = i then
10171017 exit;
10181018 end;
10191019 // full rows and those truncated at right
10201020 for k := w+1 to n do begin
10211021 Result := Result + w + 1;
10221022 if k = i then
10231023 exit;
10241024 end;
10251025 Result := n;
10261026 end;
10271027
10281028 var
10291029 d: Integer;
10301030 begin
10311031 if j > i then begin
10321032 Result := MatrixIndexToArrayIndex(j, i, n, w);
10331033 exit;
10341034 end;
10351035
10361036 Result := DiagElementIndex(i);
10371037 if (i = j) then
10381038 exit;
10391039
10401040 d := i - j;
10411041 if d > w then
10421042 Result := -1
10431043 else begin
10441044 dec(Result, d);
10451045 if (Result < 1) then
10461046 Result := -1
10471047 end;
10481048 end;
10491049
10501050var
10511051 i, j, k: Integer;
10521052 f: ArbFloat;
10531053 term: Integer = 0;
10541054begin
10551055 // Write diagonal elements
10561056 WriteLn('n = ', n);
10571057 Writeln('(One-sided) band width W = ', w);
10581058 Write('Diagonal elements of A = ', a[1]:0:0);
10591059 for k := 2 to nelements do
10601060 Write(a[k]:3:0);
10611061 WriteLn;
10621062 WriteLn;
10631063
10641064 // write reconstructed band input matrix (not needed for calculation)
10651065 WriteLn('Reconstructed A = ');
10661066 k := 1;
10671067 for i := 1 to n do begin
10681068 for j := 1 to n do begin
10691069 k := MatrixIndexToArrayIndex(i, j, n, w);
10701070 if k = -1 then
10711071 Write(0.0:3:0)
10721072 else
10731073 Write(a[k]:3:0);
10741074 end;
10751075 WriteLn;
10761076 end;
10771077 WriteLn;
10781078
10791079 // Calculate determinant
10801080 detgpb(n, w, a[1], f, k, term);
10811081 if term = 1 then
10821082 WriteLn('Determinant of A = ', f * intpower(8, k):0:9, ' (k = ', k, ')')
10831083 else
10841084 WriteLn('Incorrect input');
10851085end.
10861086```
10871087
10881088### Determinant of a tridiagonal matrix
10891089
10901090Calculation of the determinant of a tridiagonal matrix is done by the procedure `detgtr`:
10911091
10921092``` pascal
10931093procedure detgtr(n: ArbInt; var l, d, u, f: ArbFloat; var k, term:ArbInt);
10941094```
10951095
10961096- `n` denotes the number of columns and rows of the matrix (it must be square).
10971097- `l` specifies the first element in the subdiagonal of the matrix. This 1D array must be dimensioned to at least `n-1` elements.
10981098- `d` specifies the first element along the main diagonal of the matrix. This 1D array must be dimensioned to contain at least `n` elements.
10991099- `u` specifies the first element along the superdiagonal of the matrix. The array must be dimensioned to have at least `n-1` elements.
11001100- `f` and `k` return the determinant of the matrix. The determinant is given by *f * 8<sup>k</sup>*. Usually *k = 0*, and the determinant simply is equal to *f*.
11011101
11021102**Example** Calculate the determinant of the matrix
11031103
11041104
11051105$`A=
11061106 \left(
11071107 \begin{array}{rrrr}
11081108 1 & 1 & & \\
11091109 2 & 1 & 1 & \\
11101110 & 1 & 1 & 0 \\
11111111 & & 1 & 2
11121112 \end{array}
11131113 \right)`$
11141114
11151115``` pascal
11161116program determinant_tridiagonal_matrix;
11171117
11181118{$mode objfpc}{$H+}
11191119
11201120uses
11211121 math, typ, det;
11221122
11231123const
11241124 n = 4;
11251125
11261126const
11271127 u: array[0..n-2] of ArbFloat = (-1, -1, -1 ); // superdiagonal
11281128 d: array[0..n-1] of ArbFloat = ( 2, 2, 2, 2); // main diagonal
11291129 l: array[1..n-1] of ArbFloat = ( -1, -1, -1); // subdiagonal
11301130
11311131var
11321132 f: ArbFloat;
11331133 k: Integer;
11341134 term: Integer = 0;
11351135 i, j: Integer;
11361136
11371137begin
11381138 // write input matrix
11391139 WriteLn('A = ');
11401140 for i := 0 to n-1 do begin
11411141 for j := 0 to i-2 do
11421142 Write(0.0:5:0);
11431143 if i > 0 then
11441144 Write(l[i]:5:0);
11451145 Write(d[i]:5:0);
11461146 if i < n-1 then
11471147 Write(u[i]:5:0);
11481148 for j:=i+2 to n-1 do
11491149 Write(0.0:5:0);
11501150 WriteLn;
11511151 end;
11521152 WriteLn;
11531153
11541154 // Calculate determinant
11551155 detgtr(n, l[1], d[0], u[0], f, k, term);
11561156 if term = 1 then
11571157 WriteLn('Determinant of A = ', f * intpower(8, k):0:9, ' (k = ', k, ')')
11581158 else
11591159 WriteLn('Incorrect input');
11601160
11611161 ReadLn;
11621162
11631163end.
11641164```
11651165
11661166## Inverse of a matrix (unit *inv*)
11671167
11681168Suppose a square matrix *A*. Then the matrix *A*<sup>-1</sup> is the inverse of *A* if the product *A*<sup>-1</sup> *A* is the identity matrix *I* (all elements zero, except for the diagonal elements which are 1).
11691169
11701170The inverse matrix exists if the determinant of *A* is not zero.
11711171
11721172
11731173$`A^{-1} A = I =
11741174 \begin{bmatrix}
11751175 1 & & 0 \\
11761176 & \ddots & \\
11771177 0 & & 1
11781178 \end{bmatrix}`$
11791179
11801180NumLib provides various routines for calculation of the inverse matrix. Which algorithm is used depends on the properties of the matrix *A*. All routines discussed here have in common that the matrix is stored in the standard NumLib way, i.e. as a 2D array of real numbers with *n* rows and *m* columns, or as a 1D array with *n m* elements.
11811181
11821182No special requirements are made for the matrix passed to `invgen`, except that it is square. The calculation in this routine is based on [LU decomposition with partial pivoting](https://en.wikipedia.org/wiki/LU_decomposition). If the matrix is symmetric, it can be processed by `invgsy` which uses a reduction to tridiagonal form internally. A symmetric positive definite matrix can be passed to `invgpd` where the inverse is calculated by Cholesky decomposition.
11831183
11841184``` pascal
11851185procedure invgen(n, rwidth: ArbInt; var ai: ArbFloat; var term: ArbInt); // --> generic matrix
11861186procedure invgsy(n, rwidth: ArbInt; var ai: ArbFloat; var term: ArbInt); // --> symmetric matrix
11871187procedure invgpd(n, rwidth: ArbInt; var ai: ArbFloat; var term: ArbInt); // --> symmetric positive definite matrix
11881188```
11891189
11901190- `n` indicates the size of the matrix.
11911191- `rwidth` is length of the rows of the two-dimensional array which holds the matrix. Normally `n = rwidth`, but the matrix could have been declared larger than actually needed; in this case `rwidth` refers to the declared size of the data array.
11921192- `ai` is the first (top-left) element of the matrix in the data array. After the calculation the input data are **overwritten by the elements of the inverse matrix** in case of successful completion (`term = 1`); in other cases, the matrix elements are undefined.
11931193- `term` provides an error code:
11941194 - 1 - successful completion, the elements of the inverse matrix can be found at the same place as the input matrix.
11951195 - 2 - the inverse could not be calculated because the input matrix is (almost) singular.
11961196 - 3 - incorrect input data, `n < 1`.
11971197
11981198**Example**
11991199
12001200Calculate the inverse of the symmetric matrix $`A =
12011201 \left[
12021202 \begin{array}{rrrr}
12031203 5 & 7 & 6 & 5 \\
12041204 7 & 10 & 8 & 7 \\
12051205 6 & 8 & 10 & 9 \\
12061206 5 & 7 & 9 & 10
12071207 \end{array}
12081208 \right]`$ This matrix is symmetric and positive definite (the product **b**<sup>T</sup> *A* **b** with any vector **b** = \[b<sub>1</sub>, b<sub>2</sub>, b<sub>3</sub>\]<sup>T</sup> cannot become zero or negative since all elements of this matrix are positive). Therefore, `invgpd` is best-suited for this task although the other routines can be used as well (uncomment their calls below).
12091209
12101210``` pascal
12111211program inverse_matrix;
12121212
12131213uses
12141214 typ, inv, omv;
12151215
12161216const
12171217 n = 4;
12181218 D = 5;
12191219
12201220var
12211221 // a is the input matrix to be inverted
12221222 // Note that this is matrix must be symmetric positive definite.
12231223 a: array[1..n, 1..n] of ArbFloat = (
12241224 (5, 7, 6, 5),
12251225 (7, 10, 8, 7),
12261226 (6, 8, 10, 9),
12271227 (5, 7, 9, 10)
12281228 );
12291229 b: array[1..n, 1..n] of Arbfloat;
12301230 c: array[1..n, 1..n] of ArbFloat;
12311231 term: Integer = 0;
12321232 i, j: Integer;
12331233
12341234begin
12351235 // write input matrix
12361236 WriteLn('a = ');
12371237 for i := 1 to n do begin
12381238 for j := 1 to n do
12391239 Write(a[i, j]:10:D);
12401240 WriteLn;
12411241 end;
12421242 WriteLn;
12431243
12441244 // Store input matrix because it will be overwritten by the inverse of a
12451245 for i := 1 to n do
12461246 for j := 1 to n do
12471247 b[i, j] := a[i, j];
12481248
12491249 // Calculate inverse -- uncomment the procedure to be used.
12501250 //invgen(n, n, a[1, 1], term);
12511251 //invgsy(n, n, a[1, 1], term);
12521252 invgpd(n, n, a[1, 1], term);
12531253
12541254 // write inverse
12551255 WriteLn('a^(-1) = ');
12561256 for i := 1 to n do begin
12571257 for j := 1 to n do
12581258 Write(a[i, j]:10:D);
12591259 WriteLn;
12601260 end;
12611261 WriteLn;
12621262
12631263 // Check validity of result by multiplying inverse with saved input matrix
12641264 omvmmm(a[1, 1], n, n, n,
12651265 b[1, 1], n, n,
12661266 c[1, 1], n);
12671267
12681268 // write inverse
12691269 WriteLn('a^(-1) x a = ');
12701270 for i := 1 to n do begin
12711271 for j := 1 to n do
12721272 Write(c[i, j]:10:D);
12731273 WriteLn;
12741274 end;
12751275end.
12761276```
12771277
12781278## Solving systems of linear equations (unit *sle*)
12791279
12801280A [system of linear equations](https://en.wikipedia.org/wiki/System_of_linear_equations) (or linear system) is a collection of two or more linear equations involving the same set of variables, *x<sub>1</sub> ... x<sub>n</sub>*.
12811281
12821282
12831283$`\begin{cases}
12841284 a_{11} x_1 + a_{12} x_2 + \dots + a_{1n} x_n = b_1 \\
12851285 a_{21} x_1 + a_{22} x_2 + \dots + a_{2n} x_n = b_n \\
12861286 \ \ \ \vdots \\
12871287 a_{m1} x_1 + a_{m2} x_2 + \dots + a_{mn} x_n = b_m
12881288 \end{cases}`$
12891289
12901290In order to find a solution which satisfies all equations simultaneously the system of equations is usually converted to a matrix equation *A* **x** = **b**, where
12911291
12921292
12931293$`A = \left [ \begin{array}{ccc}
12941294 a_{11} & a_{12} & \dots & a_{1n} \\
12951295 a_{21} & a_{22} & \dots & a_{2n} \\
12961296 \vdots & \vdots & \ddots & \vdots \\
12971297 a_{m1} & a_{m2} & \dots & a_{mn}
12981298 \end{array}
12991299 \right ],
13001300 \
13011301 \mathbf{x} = \left [ \begin{array}{c}
13021302 x_1 \\
13031303 x_2 \\
13041304 \vdots \\
13051305 x_n
13061306 \end{array}
13071307 \right ],
13081308 \
13091309 \mathbf{b} = \left [ \begin{array}{c}
13101310 b_1 \\
13111311 b_2 \\
13121312 \vdots \\
13131313 b_m
13141314 \end{array}
13151315 \right ]`$
13161316
13171317NumLib offers several procedures to solve the matrix equation depending on the properties of the matrix.
13181318
13191319### Square matrices with standard storage
13201320
13211321Here we describe several routines in which the matrix of the equation system is square, i.e. there are as many equations as unknowns. This matrix is assumed to be passed to the procedures in the standard NumLib way as the first element of a 2D or 1D array. The 2D array must be dimensioned to contain at least *n* rows and *n* columns, the 1D array must contain at least *n*<sup>2</sup> elements.
13221322
13231323The most general procedure is `slegen` and applied to any square matrix *A*. The system is solved using the Gauss elimination procedure with partial pivoting.
13241324
13251325If the matrix *A* is symmetric then the reduction to tridiagonal form generally is more stable. This is done in routine `slegsy`.
13261326
13271327If *A*, furthermore, is a symmetric positive definite matrix, then the Cholesky method is best-suited. This is applied in routine `slegpd`.
13281328
13291329``` pascal
13301330procedure slegen(n, rwidth: ArbInt; var a, b, x, ca: ArbFloat; var term: ArbInt); // generic matrix
13311331procedure slegsy(n, rwidth: ArbInt; var a, b, x, ca: ArbFloat; var term: ArbInt); // symmetric matrix
13321332procedure slegpd(n, rwidth: ArbInt; var a, b, x, ca: ArbFloat; var term: ArbInt); // symmetric positive definite matrix
13331333```
13341334
13351335- `n` is the number of unknown variables. It must be the same as the number of columns and rows of the coefficent matrix.
13361336- `rwidth` identifies the allocated number of columns of the coefficient matrix. This is useful if the matrix is allocated larger than needed. It is required that `n <= rwidth`.
13371337- `a` is the first (i.e. top left) element of the coefficient matrix *A*; the other elements must follow within the same allocated memory block. The matrix will not be changed during the calculation.
13381338- `b` is the first element of the array containing the constant vector **b**. The array length at least must be equal to `n`. The vector will not be changed during the calculation.
13391339- `x` is the first element of the array to receive the solution vector **x**. It must be allocated to contain at least `n` values.
13401340- `ca` is a parameter to describe the accuracy of the solution.
13411341- `term` returns an error code:
13421342 - 1 - successful completion, the solution vector **x** is valid
13431343 - 2 - the solution could not have been determined because the matrix is (almost) singular.
13441344 - 3 - error in input values: `n < 1`.
13451345
13461346**Example**
13471347
13481348Solve this system of linear equations:
13491349
13501350
13511351$`\begin{cases}
13521352 5 x_1 + 7 x_2 + 6 x_3 + 5 x_4 = 57 \\
13531353 7 x_1 + 10 x_2 + 8 x_3 + 7 x_4 = 79 \\
13541354 6 x_1 + 8 x_2 + 10 x_3 + 9 x_4 = 88 \\
13551355 5 x_1 + 7 x_2 + 9 x_3 + 10 x_4 = 86
13561356 \end{cases}
13571357 \qquad \Rightarrow \qquad
13581358 A=
13591359 \left[
13601360 \begin{array}{rrrr}
13611361 5 & 7 & 6 & 5 \\
13621362 7 & 10 & 8 & 7 \\
13631363 6 & 8 & 10 & 9 \\
13641364 5 & 7 & 9 & 10
13651365 \end{array}
13661366 \right]
13671367 , \;
13681368 \mathbf{b} =
13691369 \left[
13701370 \begin{array}{r}
13711371 57 \\
13721372 79 \\
13731373 88 \\
13741374 86
13751375 \end{array}
13761376 \right]`$
13771377
13781378This matrix is symmetric positive definite. Therefore, all routines presented here can be applied. Uncomment the requested line in the code below. As a test the result vector **x** is multiplied to matrix *A*, and the result must equal the constant vector **b**.
13791379
13801380``` pascal
13811381program solve_sys_lin_eq;
13821382
13831383uses
13841384 typ, sle, omv;
13851385
13861386const
13871387 n = 4;
13881388 A: array[1..n, 1..n] of ArbFloat = (
13891389 ( 5, 7, 6, 5),
13901390 ( 7, 10, 8, 7),
13911391 ( 6, 8, 10, 9),
13921392 ( 5, 7, 9, 10)
13931393 );
13941394 b: array[1..n] of ArbFloat = (
13951395 57, 79, 88, 86
13961396 );
13971397
13981398var
13991399 x: array[1..n] of ArbFloat;
14001400 b_test: array[1..n] of ArbFloat;
14011401 ca: ArbFloat;
14021402 i, j: Integer;
14031403 term: Integer;
14041404
14051405begin
14061406 WriteLn('Solve matrix system A x = b');
14071407 WriteLn;
14081408
14091409 // Print A
14101410 WriteLn('Matrix A = ');
14111411 for i:= 1 to n do begin
14121412 for j := 1 to n do
14131413 Write(A[i, j]:10:0);
14141414 WriteLn;
14151415 end;
14161416 WriteLn;
14171417
14181418 // Print b
14191419 WriteLn('Vector b = ');
14201420 for i:= 1 to n do
14211421 Write(b[i]:10:0);
14221422 WriteLn;
14231423 WriteLn;
14241424
14251425 // Solve - select one of these methods
14261426 slegen(n, n, A[1, 1], b[1], x[1], ca, term);
14271427// slegsy(n, n, A[1, 1], b[1], x[1], ca, term);
14281428// slegpd(n, n, A[1, 1], b[1], x[1], ca, term);
14291429
14301430 if term = 1 then begin
14311431 WriteLn('Solution vector x = ');
14321432 for i:= 1 to n do
14331433 Write(x[i]:10:0);
14341434 WriteLn;
14351435 WriteLn;
14361436
14371437 omvmmv(A[1,1], n, n, n, x[1], b_test[1]);
14381438
14391439 WriteLn('Check result: A x = (must be equal to b)');
14401440 for i:= 1 to n do
14411441 Write(b_test[i]:10:0);
14421442 WriteLn;
14431443 end
14441444 else
14451445 WriteLn('Error');
14461446
14471447 ReadLn;
14481448
14491449end.
14501450```
14511451
14521452### Band matrix
14531453
14541454A special solution, `slegba`, is implemented for a band matrix, i.e. a matrix in which all elements are zero outside a band of width *l* below and of width *r* above the main diagonal.
14551455
14561456``` pascal
14571457procedure slegba(n, l, r: ArbInt; var a, b, x, ca: ArbFloat; var term:ArbInt);
14581458```
14591459
14601460- `n` denotes the number of columns and rows of the matrix (it must be square).
14611461- `l` identifies the left bandwidth, i.e. the number of diagonals the band extends below (or to the left of) the main diagonal.
14621462- `r` identifies the right bandwidth, i.e. the number of diagonals the band extends abofe (or to the right of) the main diagonsl.
14631463- `a` specifies the first element of a 1D array which contains the elements of the diagonal band, see this. This array contains only the band elements and is obtained by running across the rows of the band matrix from left to right and top to bottom, starting at element *A*<sub>11</sub>. It must be dimensioned to contain at least `n*(l+1+r) - (r*(r+1)+l*(l+1)) div 2` elements. Note that a 2D array cannot be used for this routine.
14641464- `b` is the first element of the array containing the constant vector **b**. The array length at least must be equal to `n`. The vector will not be changed during the calculation.
14651465- `x` is the first element of the array to receive the solution vector **x**. It must be allocated to contain at least `n` values.
14661466- `ca` is a parameter to describe the accuracy of the solution.
14671467- `term` returns an error code:
14681468 - 1 - successful completion, the solution vector **x** is valid
14691469 - 2 - the solution could not have been determined because the matrix is (almost) singular.
14701470 - 3 - error in input values: `n < 1`, `l < 0`, `l >= n`, `r < 0`, or `r >= n`
14711471
14721472**Example**
14731473
14741474Solve this system of linear equations:
14751475
14761476
14771477$`\begin{array}{ccccccc}
14781478 5 x_1 & - & 4 x_2 & + & x_3 & & & & & & & & & = 0 \\
14791479 -4 x_1 & + & 6 x_2 & - & 4 x_3 & + & x_4 & & & & & & & = 0 \\
14801480 x_1 & - & 4 x_2 & + & 6 x_3 & - & 4 x_4 & & & & & & & = 0 \\
14811481 & & x_2 & - & 4 x_3 & + & 6 x_4 & - & 4 x_5 & & & & & = 1 \\
14821482 & & & & x_3 & - & 4 x_4 & + & 6 x_5 & - & 4 x_6 & + & x_7 & = 0 \\
14831483 & & & & & & x_4 & - & 4 x_5 & + & 6 x_6 & - & 4 x_7 & = 0 \\
14841484 & & & & & & & & x_5 & - & 4 x_6 & + & 5 x_7 & = 0 \\
14851485 \end{array}
14861486 \qquad \Rightarrow \qquad
14871487 A=
14881488 \left[
14891489 \begin{array}{rrrr}
14901490 5 & -4 & 1 & 0 & 0 & 0 & 0 \\
14911491 -4 & 6 & -4 & 1 & 0 & 0 & 0 \\
14921492 1 & -4 & 6 & -4 & 1 & 0 & 0 \\
14931493 0 & 1 & -4 & 6 & -4 & 1 & 0 \\
14941494 0 & 0 & 1 & -4 & 6 & -4 & 1 \\
14951495 0 & 0 & 0 & 1 & -4 & 6 & -4 \\
14961496 0 & 0 & 0 & 0 & 1 & -4 & 5
14971497 \end{array}
14981498 \right]
14991499 , \;
15001500 \mathbf{b} =
15011501 \left[
15021502 \begin{array}{r}
15031503 0 \\
15041504 0 \\
15051505 0 \\
15061506 1 \\
15071507 0 \\
15081508 0 \\
15091509 0
15101510 \end{array}
15111511 \right]`$
15121512
15131513``` pascal
15141514program solve_band;
15151515
15161516uses
15171517 typ, sle, omv;
15181518
15191519const
15201520 n = 7;
15211521 L = 2;
15221522 R = 2;
15231523 nelements = n * (L + 1 + R) - (L * (L + 1) + R * (R + 1)) div 2;
15241524
15251525 // Band elements arranged in rows, elements outside of the band omitted!
15261526 a: array[1..nelements] of Arbfloat = (
15271527 5, -4, 1,
15281528 -4, 6, -4, 1,
15291529 1, -4, 6, -4, 1,
15301530 1, -4, 6, -4, 1,
15311531 1, -4, 6, -4, 1,
15321532 1, -4, 6, -4,
15331533 1, -4, 5
15341534 );
15351535 b: array[1..n] of ArbFloat = (
15361536 0, 0, 0, 1, 0, 0, 0
15371537 );
15381538
15391539var
15401540 x: array[1..n] of ArbFloat;
15411541 a_test: array[1..n, 1..n] of ArbFloat;
15421542 b_test: array[1..n] of ArbFloat;
15431543 ca: ArbFloat;
15441544 i, j, k: Integer;
15451545 term: Integer;
15461546
15471547begin
15481548 WriteLn('Solve matrix system A x = b where A is a band matrix');
15491549 WriteLn;
15501550
15511551 // Write diagonal elements
15521552 WriteLn('Matrix A:');
15531553 WriteLn(' n = ', n);
15541554 Writeln(' Left band width L = ', L);
15551555 WriteLn(' Right band width R = ', R);
15561556 WriteLn(' Diagonal elements of A = ');
15571557 for k := 1 to nelements do
15581558 Write(a[k]:5:0);
15591559 WriteLn;
15601560 WriteLn;
15611561
15621562 // Solve
15631563 slegba(n, l, r, A[1], b[1], x[1], ca, term);
15641564
15651565 if term = 1 then begin
15661566 // To test the result we multiply A with the solution vector x and check
15671567 // whether the result is equal to b.
15681568 // Since mutliplication of a matrix stored like a band matrix is quite
15691569 // cumbersome we copy the band matrix into a standard matrix.
15701570 FillChar(a_test, SizeOf(a_test), 0);
15711571 k := 1;
15721572 for i:=1 to L do
15731573 for j := i-L to i+R do
15741574 if (j >= 1) and (j <= n) then begin
15751575 a_test[i,j] := A[k];
15761576 inc(k);
15771577 end;
15781578
15791579 for i:= L+1 to n-R do
15801580 for j := i-L to i+R do begin
15811581 a_test[i,j] := A[k];
15821582 inc(k);
15831583 end;
15841584 for i := n-R+1 to n do
15851585 for j := i-L to i+R do
15861586 if j <= n then begin
15871587 a_test[i,j] := A[k];
15881588 inc(k);
15891589 end;
15901590
15911591 // Print A
15921592 WriteLn('Matrix A =');
15931593 for i:=1 to n do begin
15941594 for j:=1 to n do
15951595 Write(a_test[i,j]:5:0);
15961596 WriteLn;
15971597 end;
15981598 WriteLn;
15991599
16001600 // Print b
16011601 WriteLn('Vector b = ');
16021602 for i:= 1 to n do
16031603 Write(b[i]:10:0);
16041604 WriteLn;
16051605 WriteLn;
16061606
16071607 // Print solution
16081608 WriteLn('Solution vector x = ');
16091609 for i:= 1 to n do
16101610 Write(x[i]:10:3);
16111611 WriteLn;
16121612 WriteLn;
16131613
16141614 omvmmv(a_test[1,1], n, n, n, x[1], b_test[1]);
16151615
16161616 WriteLn('Check result: A x = (must be equal to b)');
16171617 for i:= 1 to n do
16181618 Write(b_test[i]:10:0);
16191619 WriteLn;
16201620 end
16211621 else
16221622 WriteLn('Error');
16231623end.
16241624```
16251625
16261626### Symmetric positive definite band matrix
16271627
16281628The optimized solution method for a symmetric positive band matrix is called `slegpb`.
16291629
16301630``` pascal
16311631procedure slegpb(n, w: ArbInt; var a, b, x, ca: ArbFloat; var term: ArbInt);
16321632```
16331633
16341634- `n` denotes the number of columns and rows of the matrix (it must be square).
16351635- `w` identifies the (one-sided) bandwidth, i.e. the number of diagonals the band extends below (or to the left of) the main diagonal. This is the same value as the number of diagonals the band extends above (or to the right of) the main diagonsl.
16361636- `a` specifies the first element of a 1D array which contains the elements of the diagonal band, see this. This array contains only the band elements and is obtained by running across the rows of the band matrix from left to right and top to bottom, starting at element *A*<sub>11</sub>, but stopping at the main diagonal. This means that the array contains only the left band and the main diagonal elements. It must be dimensioned to contain at least `n*(w+1) - (w*(w+1)) div 2` elements. Note that a 2D array cannot be used for this routine.
16371637- `b` is the first element of the array containing the constant vector **b**. The array length at least must be equal to `n`. The vector will not be changed during the calculation.
16381638- `x` is the first element of the array to receive the solution vector **x**. It must be allocated to contain at least `n` values. If `term` is not 1 then the solution array contains undefined data.
16391639- `ca` is a parameter to describe the accuracy of the solution.
16401640- `term` returns an error code:
16411641 - 1 - successful completion, the solution vector **x** is valid
16421642 - 2 - the solution could not have been determined because the matrix is (almost) singular.
16431643 - 3 - error in input values: `n<1`, `w<0`, or `w>=n`
16441644
16451645**Example**
16461646
16471647The matrix of the band matrix example is symmetrical positive definite. Therefore, procedure `slegpb` can be applied as well:
16481648
16491649``` pascal
16501650program solve_spd; // "spd" = symmetric positive definite
16511651
16521652uses
16531653 typ, sle, omv;
16541654
16551655const
16561656 n = 7;
16571657 w = 2;
16581658 nelements = n * (w + 1) - (w * (w + 1)) div 2;
16591659
16601660 // Band elements arranged in rows, elements outside of the band as well as right diagonals omitted!
16611661 a: array[1..nelements] of Arbfloat = (
16621662 5,
16631663 -4, 6,
16641664 1, -4, 6,
16651665 1, -4, 6,
16661666 1, -4, 6,
16671667 1, -4, 6,
16681668 1, -4, 5
16691669 );
16701670 b: array[1..n] of ArbFloat = (
16711671 0, 0, 0, 1, 0, 0, 0
16721672 );
16731673
16741674var
16751675 x: array[1..n] of ArbFloat;
16761676 a_test: array[1..n, 1..n] of ArbFloat;
16771677 b_test: array[1..n] of ArbFloat;
16781678 ca: ArbFloat;
16791679 i, j, k: Integer;
16801680 term: Integer;
16811681
16821682begin
16831683 WriteLn('Solve matrix system A x = b where A is a symmetric positive definite band matrix');
16841684 WriteLn;
16851685
16861686 // Write diagonal elements
16871687 WriteLn('Matrix A:');
16881688 WriteLn(' n = ', n);
16891689 Writeln(' (One-sided) band width w = ', w);
16901690 WriteLn(' Diagonal elements of A = ');
16911691 for k := 1 to nelements do
16921692 Write(a[k]:5:0);
16931693 WriteLn;
16941694 WriteLn;
16951695
16961696 // Solve
16971697 slegpb(n, w, A[1], b[1], x[1], ca, term);
16981698
16991699 if term = 1 then begin
17001700 // To test the result we multiply A with the solution vector x and check
17011701 // whether the result is equal to b.
17021702 // Since mutliplication of a matrix stored like a band matrix is quite
17031703 // cumbersome we copy the band matrix into a standard matrix.
17041704 FillChar(a_test, SizeOf(a_test), 0);
17051705 i := 1;
17061706 k := 1;
17071707 while (k <= nelements) and (i <= n) do begin
17081708 for j := i - w to i do
17091709 if (j >= 1) and (j <= n) then begin
17101710 a_test[i, j] := a[k];
17111711 a_test[j, i] := a[k];
17121712 inc(k);
17131713 end;
17141714 inc(i);
17151715 end;
17161716
17171717 // Print A
17181718 WriteLn('Matrix A =');
17191719 for i:=1 to n do begin
17201720 for j:=1 to n do
17211721 Write(a_test[i,j]:5:0);
17221722 WriteLn;
17231723 end;
17241724 WriteLn;
17251725
17261726 // Print b
17271727 WriteLn('Vector b = ');
17281728 for i:= 1 to n do
17291729 Write(b[i]:10:0);
17301730 WriteLn;
17311731 WriteLn;
17321732
17331733 // Print solution
17341734 WriteLn('Solution vector x = ');
17351735 for i:= 1 to n do
17361736 Write(x[i]:10:3);
17371737 WriteLn;
17381738 WriteLn;
17391739
17401740 omvmmv(a_test[1,1], n, n, n, x[1], b_test[1]);
17411741
17421742 WriteLn('Check result: A x = (must be equal to b)');
17431743 for i:= 1 to n do
17441744 Write(b_test[i]:10:3);
17451745 WriteLn;
17461746 end
17471747 else
17481748 WriteLn('Error');
17491749end.
17501750```
17511751
17521752### Tridiagonal matrix
17531753
17541754NumLib offers two specialized procedures for solving a system of linear equations based on a tridiagonal matrix *A*. Both rely on the memory-optimized storage of tridiagonal matrices as described here. They differ in the internal calculation algorithm and in the behavior with critical matrices.
17551755
17561756``` pascal
17571757procedure sledtr(n: ArbInt; var l, d, u, b, x: ArbFloat; var term: ArbInt);
17581758procedure slegtr(n: ArbInt; var l, d, u, b, x, ca: ArbFloat; var term: ArbInt);
17591759```
17601760
17611761- `n` is the number of unknown variables. It must be the same as the number of columns and rows of the coefficent matrix.
17621762- `l` specifies the first element in the subdiagonal of the matrix *A*. This 1D array must be dimensioned to at least `n-1` elements.
17631763- `d` specifies the first element along the main diagonal of the matrix *A*. This 1D array must be dimensioned to contain at least `n` elements.
17641764- `u` specifies the first element along the superdiagonal of the matrix *A*. The array must be dimensioned to have at least `n-1` elements.
17651765- `b` is the first element of the array containing the constant vector **b**. The array length at least must be equal to `n`. The vector will not be changed during the calculation.
17661766- `x` is the first element of the array to receive the solution vector **x**. It must be allocated to contain at least `n` values.
17671767- `ca` is a parameter to describe the accuracy of the solution.
17681768- `term` returns an error code:
17691769 - 1 - successful completion, the solution vector **x** is valid
17701770 - 2 - the solution could not have been determined because the matrix is (almost) singular.
17711771 - 3 - error in input values: `n < 1`.
17721772
17731773`sledtr` is numerically stable if matrix *A* fulfills one of these conditions:
17741774
17751775- *A* is regular (i.e. its inverse matrix exists), and *A* is columnar-diagonally dominant; this means:
17761776 - \|*d<sub>1</sub>*\| ≥ \|*l*<sub>2</sub>\|,
17771777 - \|*d<sub>i</sub>*\| ≥ \|*u*<sub>*i*-1</sub>\| + \|*l<sub>i</sub>+1\|,*i*= 2, ...,*n''-1,
17781778 - \|*d<sub>n</sub>*\| ≥ \|*u*<sub>*n*-1</sub>\|
17791779- *A* is regular, and *A* is diagonally dominant; this means:
17801780 - \|*d<sub>1</sub>*\| ≥ \|*l*<sub>2</sub>\|,
17811781 - \|*d<sub>i</sub>*\| ≥ \|*u*<sub>*i*</sub>\| + \|*l<sub>i</sub>\|,*i*= 2, ...,*n''-1,
17821782 - \|*d<sub>n</sub>*\| ≥ \|*u*<sub>*n*</sub>\|
17831783- *A* is symmetric and positive-definite.
17841784
17851785However, this procedure does not provide the parameter `ca` from which the accuracy of the determined solution can be evaluated. If this is needed the (less stable) procedure `slegtr` must be used.
17861786
17871787**Example** Solve this tridiagonal system of linear equations for *n* = 8:
17881788
17891789
17901790$`\begin{cases}
17911791 188 x_1 - 100 x_2 = 0 \\
17921792 -100 x_1 + 188 x_2 -100 x_3 = 0 \\
17931793 \vdots \\
17941794 -100 x_{n-2} + 188 x_{n-1} - 100 x_n = 0 \\
17951795 -100 x_{n-1} + 188 x_n = 0
17961796 \end{cases}
17971797 \qquad \Rightarrow \qquad
17981798 A=
17991799 \left[
18001800 \begin{array}{rrrrr}
18011801 188 & -100 & & & 0 \\
18021802 -100 & 188 & -100 & & \\
18031803 & \ddots & \ddots & \ddots & \\
18041804 & & -100 & 188 & -100 \\
18051805 & & & -100 & 188 \\
18061806 \end{array}
18071807 \right]
18081808 , \;
18091809 \mathbf{b} =
18101810 \left[
18111811 \begin{array}{r}
18121812 88 \\
18131813 -12 \\
18141814 \vdots \\
18151815 -12 \\
18161816 88
18171817 \end{array}
18181818 \right]`$
18191819
18201820``` pascal
18211821program solve_tridiag;
18221822
18231823uses
18241824 typ, sle;
18251825
18261826const
18271827 n = 8;
18281828 u: array[1..n-1] of ArbFloat = (-100, -100, -100, -100, -100, -100, -100 );
18291829 d: array[1..n] of ArbFloat = ( 188, 188, 188, 188, 188, 188, 188, 188);
18301830 l: array[2..n] of ArbFloat = ( -100, -100, -100, -100, -100, -100, -100);
18311831 b: array[1..n] of ArbFloat = ( 88, -12, -12, -12, -12, -12, -12, 88);
18321832
18331833var
18341834 A: array[1..n, 1..n] of ArbFloat;
18351835 x: array[1..n] of ArbFloat;
18361836 b_test: array[1..n] of ArbFloat;
18371837 ca: ArbFloat;
18381838 i, j: Integer;
18391839 term: Integer;
18401840
18411841begin
18421842 WriteLn('Solve tridiagonal matrix system A x = b');
18431843 WriteLn;
18441844
18451845 Write('Superdiagonal of A:':25);
18461846 for i := 1 to n-1 do
18471847 Write(u[i]:10:0);
18481848 WriteLn;
18491849
18501850 Write('Main diagonal of A:':25);
18511851 for i:= 1 to n do
18521852 Write(d[i]:10:0);
18531853 WriteLn;
18541854
18551855 Write('Subdiagonal of A:':25);
18561856 Write('':10);
18571857 for i:=2 to n do
18581858 Write(l[i]:10:0);
18591859 WriteLn;
18601860
18611861 Write('Vector b:':25);
18621862 for i:=1 to n do
18631863 Write(b[i]:10:0);
18641864 WriteLn;
18651865
18661866 // Solve
18671867 slegtr(n, l[2], d[1], u[1], b[1], x[1], ca, term);
18681868 { or:
18691869 sledtr(n, l[2], d[1], u[1], b[1], x[1], term);
18701870 }
18711871
18721872 if term = 1 then begin
18731873 Write('Solution vector x:':25);
18741874 for i:= 1 to n do
18751875 Write(x[i]:10:0);
18761876 WriteLn;
18771877
18781878 // Multiply A with x to test the result
18791879 // NumLib does not have a routine to multiply a tridiagonal matrix with a
18801880 // vector... Let's do it manually.
18811881 b_test[1] := d[1]*x[1] + u[1]*x[2];
18821882 for i := 2 to n-1 do
18831883 b_test[i] := l[i]*x[i-1] + d[i]*x[i] + u[i]*x[i+1];
18841884 b_test[n] := l[n]*x[n-1] + d[n]*x[n];
18851885
18861886 Write('Check b = A x:':25);
18871887 for i:= 1 to n do
18881888 Write(b_test[i]:10:0);
18891889 WriteLn;
18901890 end
18911891 else
18921892 WriteLn('Error');
18931893end.
18941894```
18951895
18961896### Overdetermined systems (least squares)
18971897
18981898Unlike the other routines in unit *sle* which require a square matrix *A*, `slegls` can solve linear systems described by a rectangular matrix which has more rows than colums, or speaking in terms of equations, has more equations than unknowns. Such a system generally is not solvable exactly. But an approximate solution can be found such that the sum of squared residuals of each equation, i.e. the norm $`\|\mathbf{b} - A \mathbf{x}\|_2`$ is as small as possible. The most prominent application of this technique is fitting of equations to data (regression analysis).
18991899
19001900``` pascal
19011901procedure slegls(var a: ArbFloat; m, n, rwidtha: ArbInt; var b, x: ArbFloat; var term: ArbInt);
19021902```
19031903
19041904- `a` is the first (i.e. top left) element of an array with the coefficient matrix *A*; the other elements must follow within the same allocated memory block. The array will not be changed during the calculation.
19051905- `m` is the number of rows of the matrix *A* (or: the number of equations).
19061906- `n` is the number of columns of the matrix *A* (or: the number of unknown variables). n must not be larger than m.
19071907- `rwidth` identifies the allocated number of columns of the coefficient matrix *A*. This is useful if the matrix is allocated larger than needed. It is required that `n <= rwidth`.
19081908- `b` is the first element of the array containing the constant vector **b**. The array length must correspond to the number of matrix rows `m`, but the array can be allocated to be larger.
19091909- `x` is the first element of the array to receive the solution vector **x**. The array length must correspond to the number of matrix columns `n`, but the array can be allocated to be larger.
19101910- `term` returns an error code:
19111911 - 1 - successful completion, the solution vector **x** is valid
19121912 - 2 - there is no unambiguous solution because the columns of the matrix are linearly dependant..
19131913 - 3 - error in input values: `n < 1`, or `n` \> `m`.
19141914
19151915The method is based on reduction of the array *A* to upper triangle shape through Householder transformations.
19161916
19171917**Example**
19181918
19191919Find the least-squares solution for the system *A* **x** = **b** of 4 equations and 3 unknowns with
19201920
19211921$`A=
19221922 \left(
19231923 \begin{array}{rrr}
19241924 1 & 0 & 1 \\
19251925 1 & 1 & 1 \\
19261926 0 & 1 & 0 \\
19271927 1 & 1 & 0
19281928 \end{array}
19291929 \right), \ \
19301930 b=
19311931 \left(
19321932 \begin{array}{r}
19331933 21 \\ 39 \\ 21 \\ 30
19341934 \end{array}
19351935 \right).`$
19361936
19371937``` pascal
19381938program solve_leastsquares;
19391939
19401940uses
19411941 typ, sle, omv;
19421942
19431943const
19441944 m = 4;
19451945 n = 3;
19461946
19471947 A: array[1..m, 1..n] of ArbFloat = (
19481948 (1, 0, 1),
19491949 (1, 1, 1),
19501950 (0, 1, 0),
19511951 (1, 1, 0)
19521952 );
19531953 b: array[1..m] of ArbFloat = (
19541954 21, 39, 21, 30);
19551955
19561956var
19571957 x: array[1..n] of ArbFloat;
19581958 term: ArbInt;
19591959 i, j: Integer;
19601960 b_test: array[1..m] of ArbFloat;
19611961 sum: ArbFloat;
19621962
19631963begin
19641964 WriteLn('Solve A x = b with the least-squares method');
19651965 WriteLn;
19661966
19671967 // Display input data
19681968 WriteLn('A = ');
19691969 for i := 1 to m do
19701970 begin
19711971 for j := 1 to n do
19721972 Write(A[i, j]:10:0);
19731973 WriteLn;
19741974 end;
19751975 WriteLn;
19761976 WriteLn('b = ');
19771977 for i := 1 to m do
19781978 Write(b[i]:10:0);
19791979 WriteLn;
19801980
19811981 // Calculate and show solution
19821982 slegls(a[1,1], m, n, n, b[1], x[1], term);
19831983
19841984 WriteLn;
19851985 WriteLn('Solution x = ');
19861986 for j:= 1 to n do
19871987 Write(x[j]:10:0);
19881988 WriteLn;
19891989
19901990 // Calculate and display residuals
19911991 WriteLn;
19921992 WriteLn('Residuals A x - b = ');
19931993 sum := 0;
19941994 omvmmv(a[1,1], m, n, n, x[1], b_test[1]);
19951995 for i:=1 to m do begin
19961996 Write((b_test[i] - b[i]):10:0);
19971997 sum := sum + sqr(b_test[i] - b[i]);
19981998 end;
19991999 WriteLn;
20002000
20012001 // Sum of squared residuals
20022002 WriteLn;
20032003 WriteLn('Sum of squared residuals');
20042004 WriteLn(sum:10:0);
20052005
20062006 WriteLn;
20072007 WriteLn('----------------------------------------------------------------------------');
20082008 WriteLn;
20092009
20102010 // Modify solution to show that the sum of squared residuals increases';
20112011 WriteLn('Modified solution x'' (to show that it has a larger sum of squared residuals)');
20122012 x[1] := x[1] + 1;
20132013 x[2] := x[2] - 1;
20142014 WriteLn;
20152015 for j:=1 to n do
20162016 Write(x[j]:10:0);
20172017 omvmmv(a[1,1], m, n, n, x[1], b_test[1]);
20182018 sum := 0;
20192019 for i:=1 to m do
20202020 sum := sum + sqr(b_test[i] - b[i]);
20212021 WriteLn;
20222022 WriteLn;
20232023 WriteLn('Sum of squared residuals');
20242024 WriteLn(sum:10:0);
20252025end.
20262026```
20272027
20282028## Eigenvalues and eigenvectors (unit *eig*)
20292029
20302030A vector is an [eigenvector](https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors) if it does not change its direction after a linear transformation has been applied to it. In matrix terms: if a non-zero vector **x** of dimension *n* is multiplied to a square *n*x*n* matrix *A* and the product is the same vector multiplied by a scalar *λ* then **x** is an **eigenvector**, and *λ* is called an **eigenvalue**:
20312031
20322032
20332033$`A \mathbf{x} = \lambda \mathbf{x} \qquad \Rightarrow \qquad A \mathbf{x} - \lambda \mathbf{x} = 0`$
20342034
20352035NumLib offers a variety of routines for calculation of eigenvectors and eigenvalues. They are optimized for the matrix type. Every matrix type will be supported by two or four procedures of various degree of complexity; they are identified by appended numbers 1 and 2, or 1 to 4:
20362036
20372037- The procedures with appended **1** calculate **all eigenvalues**.
20382038- The procedures with the appended **2** do the same but only for **some eigenvalues** which have an index in a specified range (*λ*<sub>k1</sub>...*λ*<sub>k2</sub> out of *λ*<sub>1</sub>...*λ*<sub>n</sub>).
20392039- The procedures with the appended **3** calculate **all eigenvalues and all eigenvectors**.
20402040- The procedures with the appended **4** do the same, but again return only **some eigenvalues and eigenvectors** having an index in the specified interval.
20412041
20422042### Matrices with general storage
20432043
20442044The routines discussed here assume that the *n* x *n* matrix for which the eigenvalues are to be calculated is stored in the conventional way as a 2D (or 1D) array of `ArbFloat` values.
20452045
20462046The NumLib method for determining the eigenvalues and eigenvectors of a generic matrix are called `eigge1` and `eigge3` ("ge" = "*generic*"). Optimized calculation schemes exists for generic symmetric matrices and symmetric positive definite matrices; the corresponding routines are called `eiggs1`, `eiggs2`, `eiggs3`, and `eiggs4` for generic symmetric matrices ("gs" = "generic symmetric")., and `eiggg1`, `eiggg2`, `eiggg3`, `eiggg4` for symmetric positive definite matrices ("gg" = ??)
20472047
20482048``` pascal
20492049// Generic matrix (without any special symmetries)
20502050procedure eigge1(var a: ArbFloat; n, rwidth: ArbInt; var lam: complex; var term: ArbInt); // all eigenvalues
20512051procedure eigge3(var a: ArbFloat; n, rwidtha: ArbInt; var lam, x: complex; rwidthx: ArbInt; var term: ArbInt); // all eigenvalues and eigenvectors
20522052
20532053// Generic symmetric matrix
20542054procedure eiggs1(var a: ArbFloat; n, rwidth: ArbInt; var lam: ArbFloat; var term: ArbInt); // all eigenvalues
20552055procedure eiggs2(var a: ArbFloat; n, rwidth, k1, k2: ArbInt; var lam: ArbFloat; var term: ArbInt); // some eigenvalues (index k1..k2)
20562056procedure eiggs3(var a: ArbFloat; n, rwidtha: ArbInt; var lam, x: ArbFloat; var term: ArbInt); // all eigenvalues and eigenvectors
20572057procedure eiggs4(var a: ArbFloat; n, rwidtha, k1, k2: ArbInt; var lam, x: ArbFloat; var term: ArbInt); // some eigenvalues and eigenvectors (index k1..k2)
20582058
20592059// Symmetric positive definite matrix
20602060procedure eiggg1(var a: ArbFloat; n, rwidth: ArbInt; var lam: ArbFloat; var term: ArbInt); // all eigenvalues
20612061procedure eiggg2(var a: ArbFloat; n, rwidth, k1, k2: ArbInt; var lam: ArbFloat; var term: ArbInt); // some eigenvalues (index k1..k2)
20622062procedure eiggg3(var a: ArbFloat; n, rwidtha: ArbInt; var lam, x: ArbFloat; var term: ArbInt); // all eigenvalues and eigenvectors
20632063procedure eiggg4(var a: ArbFloat; n, rwidtha, k1, k2: ArbInt; var lam, x: ArbFloat; var term: ArbInt); // some eigenvalues and eigenvectors (index k1..k2)
20642064```
20652065
20662066- `a` is the first element of an array containing the matrix *A* for which the eigenvalue/eigenvector has to be calculated. The array must be dimensioned to provide space for at least *n*<sup>2</sup> floating point values.
20672067- `n` specifies the size of the matrix *A*, i.e. the number of rows or columns. Note that the input matrix must be square, i.e. the number of rows and columns is equal.
20682068- `rwidth` is the allocated row length of the array `a`. It can be larger than *n* if the array is allocated larger than necessary, but normally `rwidth = n`.
20692069- `lam` is the first element of an array receiving the calculated eigenvalues. In case of a generic matrix (`eigge1`, `eigge2`) the eigenvalues can be complex; therefore, the array must be dimensioned for values of type `complex` as declared in unit *typ*. In the other cases (`eiggs1..4` or `eiggg1..4`) the eigenvalues are real, and the array must be dimensioned for datatype `ArbFloat`. Since a *n*x*n* matrix has *n* eigenvalues the array must be allocated for at least `n` values, in case of the procedures with appended 2 or 4 only `k2-k1+1` values are sufficient (see below).
20702070- `term` returns an error code:
20712071 - 1 -- successful calculation
20722072 - 2 -- calculation failed
20732073 - 3 -- error in input data: `n<1`, `k1<1`, `k1>k2`, or `k2>n`.
20742074
20752075Additionally, in case of procedure `eigge3`, `eigge4` , `eiggg3` or `eiggg4`:
20762076
20772077- `x` is the first element of a matrix to receive the calculated eigenvectors. Again, the eigenvectors of a generic matrix can have complex components. Therefore, the matrix must be declared for the datatype `complex`, and it must be large enough to hold at least *n*<sup>2</sup> values, in case of `eigge4` or `eiggg4` `n * (k2-k1+1)`. If the matrix is symmetric or positive definite, the eigenvectors are real, and the array must be declared for datatype `ArbFloat`. In any case, the eigenvectors are normalized to unit length and arranged in the columns of this matrix.
20782078- `rwidthx` denotes the allocated row length of the matrix `x`. Thus it is possible to dimension the result matrix larger than actually needed.
20792079
20802080Additionally, in case of procedures `eiggs2`, `eiggs4`, `eiggg2` and `eiggg4`:
20812081
20822082- `k1` and `k2` define the interval of indexes *k* for which the eigenvalues (*λ<sub>k</sub>*) and eigenvalues (**c<sub>k</sub>**) are to be calculated. They are integers and must be ordered such that `1<=k1<=k2<=n`.
20832083
20842084**Example**
20852085
20862086Calculate the eigenvalues and eigenvectors of the matrix
20872087
20882088
20892089$`A=
20902090 \begin{bmatrix}
20912091 8 & -1 & -5 \\
20922092 -4 & 4 & -2 \\
20932093 18 & -5 & -7
20942094 \end{bmatrix}`$
20952095
20962096``` pascal
20972097program eig_general_matrix;
20982098
20992099uses
21002100 SysUtils, math,
21012101 typ, eig;
21022102
21032103const
21042104 n = 3;
21052105 D = 3;
21062106
21072107var
21082108 // a is the input matrix
21092109 a: array[1..n, 1..n] of ArbFloat = (
21102110 ( 8, -1, -5),
21112111 (-4, 4, -2),
21122112 (18, -5, -7)
21132113 );
21142114 lambda: array[1..n] of complex;
21152115 x: array[1..n, 1..n] of complex;
21162116 term: Integer = 0;
21172117 i, j: Integer;
21182118
21192119 function ComplexToStr(z: complex; Decimals: Integer): String;
21202120 var
21212121 sgn: array[boolean] of string = ('+', '-');
21222122 begin
21232123 Result := Format('%.*f %s %.*fi', [Decimals, z.Re, sgn[z.Im < 0], Decimals, abs(z.Im)]);
21242124 end;
21252125
21262126begin
21272127 // write input matrix
21282128 WriteLn('a = ');
21292129 for i := 1 to n do begin
21302130 for j := 1 to n do
21312131 Write(a[i, j]:10:D);
21322132 WriteLn;
21332133 end;
21342134 WriteLn;
21352135
21362136 // Calculate eigenvalues/vectors
21372137 eigge3(a[1,1], n, n, lambda[1], x[1,1], n, term);
21382138
21392139 // write eigenvalues
21402140 WriteLn('Eigenvalues: lambda = ');
21412141 for i := 1 to n do
21422142 Write(ComplexToStr(lambda[i], D):25);
21432143 WriteLn;
21442144 WriteLn;
21452145
21462146 // Write eigenvectors
21472147 WriteLn('Eigenvectors (as columns): x = ');
21482148 for i := 1 to n do begin
21492149 for j := 1 to n do
21502150 Write(ComplexToStr(x[i, j], D):25);
21512151 WriteLn;
21522152 end;
21532153end.
21542154```
21552155
21562156### Symmetric band matrices
21572157
21582158NumLib provides four routines to calculate eigenvalues and eigenvectors of symmetric band matrices: `eigbs1`...`eigbs4` ("bs" = *band* and *symmetric*):
21592159
21602160``` pascal
21612161// All eigenvalues
21622162procedure eigbs1(var a: ArbFloat; n, w: ArbInt; var lam: ArbFloat; var term: ArbInt);
21632163
21642164// Some eigenvalues (index k1..k2)
21652165procedure eigbs2(var a: ArbFloat; n, w, k1, k2: ArbInt; var lam: ArbFloat; var term: ArbInt);
21662166
21672167// All eigenvalues and all eigenvectors
21682168procedure eigbs3(var a: ArbFloat; n, w: ArbInt; var lam, x: ArbFloat; rwidthx: ArbInt; var term: ArbInt);
21692169
21702170// Some eigenvalues, some eigenvectors (index k1..k2)
21712171procedure eigbs4(var a: ArbFloat; n, w, k1, k2: ArbInt; var lam, x: ArbFloat; rwidthx: ArbInt; var m2, term: ArbInt);
21722172```
21732173
21742174- `a` specifies the first element of a 1D array which contains the diagonal elements of the left band and the main diagonal. It is obtained by running across the rows of the band matrix from left to right and top to bottom, but stopping at the main diagonal - the right band is ignored since it is equal to the left band. The array must be dimensioned to contain at least `n`*(`w`+1) - (`w`*(`w`+1)) div 2 elements.
21752175- `n` denotes the number of columns and rows of the matrix (it must be square).
21762176- `w` identifies the bandwidth, i.e. how many diagonals the band extends to the left or to the right of the main diagonal. Left and right bandwidth must be equal because the matrix is symmetric.
21772177- `lam` is the first element of a real-valued array receiving the calculated eigenvalues. The eigenvalues of a symmetric matric are real values. Since an *n* x *n* matrix has *n* eigenvalues the array must be allocated for at least *n* `ArbFloat` values.
21782178- `x`, in case of routines `eigbs3` and `eigbs4`, is the first element of a matrix to receive the calculated eigenvectors. The matrix must be declared for the datatype `Arbfloat`, and it must be large enough to hold at least *n* x *n* values. The eigenvectors are normalized to unit length and are arranged in the columns of this matrix.
21792179- `rwidthx`, in case of routines `eigbs3` and `eigbs4`, denotes the allocated row length of the matrix **x**. Thus it is possible to dimension the result matrix larger than actually needed.
21802180- `k1` and `k2`, in case of procedures `eigbs2` and `eigbs4`, define the interval of indexes *k* for which the eigenvalues λ<sub>k</sub> and eigenvalues **x**<sub>k</sub> are to be calculated. They are integers and must be ordered such that `1<=k1<=k2<=n`.
21812181- `m2`, in case of routine `eigbs4`, indicates the index of the largest eigenvalue for which an eigenvector has been calculated.
21822182- `term` returns an error code:
21832183 - 1 -- successful calculation
21842184 - 2 -- calculation failed
21852185 - 3 -- error in input parameters: `n<1`, `w<0`, `w>=n`, `k1<1`, `k1>k2`, or `k2>n`.
21862186
21872187If the bandwidth `w > n/3` then it is better to calculate all eigenvalues and eigenvectors even if not all of them are needed.
21882188
21892189**Example**
21902190
21912191Calculate the eigenvalues and eigenvectors of the symmetric 7 x 7 matrix $`A=
21922192 \begin{bmatrix}
21932193 5 & -4 & 1 & & & & 0 \\
21942194 -4 & 6 & -4 & 1 & & & \\
21952195 1 & -4 & 6 & -4 & 1 & & \\
21962196 & 1 & -4 & 6 & -4 & 1 & \\
21972197 & & 1 & -4 & 6 & -4 & 1 \\
21982198 & & & 1 & -4 & 6 & -4 \\
21992199 0 & & & & 1 & -4 & 5
22002200 \end{bmatrix}`$ The eigenvalues are $`\lambda_k = 16\sin^{4}\frac{k\pi}{16}, \qquad k=1,2,\ldots,7`$
22012201
22022202``` pascal
22032203program eig_symmband_matrix;
22042204
22052205uses
22062206 SysUtils, math,
22072207 typ, eig;
22082208
22092209const
22102210 n = 7;
22112211 w = 2;
22122212 nelements = n * (w + 1) - (w * (w + 1)) div 2;
22132213 D = 3;
22142214
22152215 // a contains the elements of the left and main diagonals of the input matrix
22162216 a: array[1..nelements] of ArbFloat = (
22172217 5,
22182218 -4, 6,
22192219 1, -4, 6,
22202220 1, -4, 6,
22212221 1, -4, 6,
22222222 1, -4, 6,
22232223 1, -4, 5 );
22242224
22252225 function MatrixIndexToArrayIndex(i, j, n, w: Integer): Integer;
22262226
22272227 function DiagElementIndex(i: Integer): Integer;
22282228 var
22292229 k: Integer;
22302230 begin
22312231 Result := 1;
22322232 if i = 1 then
22332233 exit;
22342234 // Rows truncated at left
22352235 for k := 2 to w do begin
22362236 Result := Result + k;
22372237 if k = i then
22382238 exit;
22392239 end;
22402240 // full rows and those truncated at right
22412241 for k := w+1 to n do begin
22422242 Result := Result + w + 1;
22432243 if k = i then
22442244 exit;
22452245 end;
22462246 Result := n;
22472247 end;
22482248
22492249 var
22502250 d: Integer;
22512251 begin
22522252 if j > i then begin
22532253 Result := MatrixIndexToArrayIndex(j, i, n, w);
22542254 exit;
22552255 end;
22562256
22572257 Result := DiagElementIndex(i);
22582258 if (i = j) then
22592259 exit;
22602260
22612261 d := i - j;
22622262 if d > w then
22632263 Result := -1
22642264 else begin
22652265 dec(Result, d);
22662266 if (Result < 1) then
22672267 Result := -1
22682268 end;
22692269 end;
22702270
22712271var
22722272 lambda: array[1..n] of ArbFloat;
22732273 x: array[1..n, 1..n] of ArbFloat;
22742274 term: Integer = 0;
22752275 i, j, k: Integer;
22762276
22772277begin
22782278 // Write diagonal elements
22792279 WriteLn('n = ', n);
22802280 Writeln('(One-sided) band width w = ', w);
22812281 Write('Diagonal elements of A = ', a[1]:0:0);
22822282 for k := 2 to nelements do
22832283 Write(a[k]:3:0);
22842284 WriteLn;
22852285 WriteLn;
22862286
22872287 // write reconstructed band input matrix (not needed for calculation)
22882288 WriteLn('Reconstructed A = ');
22892289 k := 1;
22902290 for i := 1 to n do begin
22912291 for j := 1 to n do begin
22922292 k := MatrixIndexToArrayIndex(i, j, n, w);
22932293 if k = -1 then
22942294 Write(0.0:3:0)
22952295 else
22962296 Write(a[k]:3:0);
22972297 end;
22982298 WriteLn;
22992299 end;
23002300 WriteLn;
23012301
23022302 // Calculate eigenvalues/vectors
23032303 eigbs3(a[1], n, w, lambda[1], x[1,1], n, term);
23042304 if term <> 1 then begin
23052305 WriteLn('term = ', term, ' --> ERROR');
23062306 halt;
23072307 end;
23082308
23092309 // Write expected results of eigenvalues
23102310 WriteLn('Expected eigenvalues:');
23112311 for i := 1 to n do
23122312 Write(16 * intpower(sin(i*pi/16), 4):15:D);
23132313 WriteLn;
23142314 WriteLn;
23152315
23162316 // write eigenvalues
23172317 WriteLn('Calculated eigenvalues: lambda = ');
23182318 for i := 1 to n do
23192319 Write(lambda[i]:15:D);
23202320 WriteLn;
23212321 WriteLn;
23222322
23232323 // Write eigenvectors
23242324 WriteLn('Eigenvectors (as columns): x = ');
23252325 for i := 1 to n do begin
23262326 for j := 1 to n do
23272327 Write(x[i, j]:15:D);
23282328 WriteLn;
23292329 end;
23302330end.
23312331```
23322332
23332333### Symmetric tridiagonal matrices
23342334
23352335This type of matrices is treated best by the routines `eigts1`..`eigts4`:
23362336
23372337``` pascal
23382338// All eigenvalues
23392339procedure eigts1(var d, cd: ArbFloat; n: ArbInt; var lam: ArbFloat; var term: ArbInt);
23402340
23412341// Some eigenvalues (with indices k1..k2)
23422342procedure eigts2(var d, cd: ArbFloat; n, k1, k2: ArbInt; var lam: ArbFloat; var term: ArbInt);
23432343
23442344// All eigenvalues and all eigenvectors
23452345procedure eigts3(var d, cd: ArbFloat; n: ArbInt; var lam, x: ArbFloat; rwidth: ArbInt; var term: ArbInt);
23462346
23472347// Some eigenvalues and eigenvectors (with indices k1..k2)
23482348procedure eigts4(var d, cd: ArbFloat; n, k1, k2: ArbInt; var lam, x: ArbFloat; rwidth: ArbInt; var m2, term: ArbInt);
23492349```
23502350
23512351- `d` specifies the first element along the main diagonal of the matrix. This 1D array must be dimensioned to contain at least `n` elements.
23522352- `c` specifies the first element in the subdiagonal of the matrix. This 1D array must be dimensioned to at least `n-1` elements.
23532353- `n` denotes the number of columns or rows of the matrix (it must be square).
23542354- `k1` and `k2` define the interval of indexes *k* for which the eigenvalues (*λ<sub>k</sub>*) and eigenvalues (**c<sub>k</sub>**) are to be calculated. They are integers and must be ordered such that `1<=k1<=k2<=n`.
23552355- `lam` is the first element of an array receiving the calculated eigenvalues. Since there are *n* real eigenvalues the array must be dimensioned to contain at least *n* values of type `ArbFloat`. In case of the routines with appended 2 and 4, it is sufficient to provide space only for `k2 - k1 + 1` values.
23562356- `x` is the first element of an array to receive the calculated eigenvectors. Since the eigenvectors are real the array must be prepared for data type `ArbFloat`. In case of routine `eigts3`, the array must be dimensioned for at least *n*<sup>2</sup> elements, in case of routine `eigts4` it must provide space for at least `n * (k2-k1+1)` values. The eigenvectors are normalized to unit length and arranged in the columns of this matrix.
23572357- `rwidthx` denotes the allocated row length of the matrix `x`. Thus it is possible to dimension the result matrix larger than actually needed.
23582358- `term` returns an error code:
23592359 - 1 -- successful calculation
23602360 - 2 -- calculation failed
23612361 - 3 -- error in input data: `n<1`, `k1<1`, `k1>k2`, or `k2>n`.
23622362
23632363**Example**
23642364
23652365Calculate eigenvalues and eigenvectors of the matrix $`A=
23662366 \begin{bmatrix}
23672367 1 & 1 & 0 & 0 \\
23682368 1 & 1 & 2 & 0 \\
23692369 0 & 2 & 1 & 1 \\
23702370 0 & 0 & 1 & 1
23712371 \end{bmatrix}`$ The expected eigenvalues are $`-\sqrt{2},\ 2-\sqrt{2},\ \sqrt{2},\ 2+\sqrt{2}`$
23722372
23732373``` pascal
23742374program eig_symmtridiag_matrix;
23752375
23762376uses
23772377 typ, eig;
23782378
23792379const
23802380 n = 4;
23812381
23822382 // a contains the elements of the main diagonal of the input matrix
23832383 d: array[1..n] of ArbFloat = (1, 1, 1, 1);
23842384 // c contains the elements of the subdiagonal
23852385 c: array[2..n] of ArbFloat = (1, 2, 1);
23862386
23872387var
23882388 lambda: array[1..n] of ArbFloat;
23892389 x: array[1..n, 1..n] of ArbFloat;
23902390 term: Integer = 0;
23912391 i, j, k: Integer;
23922392
23932393begin
23942394 // Write diagonals elements
23952395 WriteLn('n = ', n);
23962396 Write('Elements of main diagonal = ', d[1]:0:0);
23972397 for k := 2 to n do
23982398 Write(d[k]:3:0);
23992399 WriteLn;
24002400 Write('Elements of subdiagonal = ', ' ':3, c[2]:0:0);
24012401 for k := 3 to n do
24022402 Write(c[k]:3:0);
24032403 WriteLn;
24042404 WriteLn;
24052405
24062406 // write reconstructed band input matrix (not needed for calculation)
24072407 WriteLn('Reconstructed A = ');
24082408 for i := 1 to n do begin
24092409 for j := 1 to n do begin
24102410 if j = i then
24112411 Write(d[i]:3:0)
24122412 else if (j = i-1) then
24132413 Write(c[i]:3:0)
24142414 else if (j = i+1) then
24152415 Write(c[i+1]:3:0)
24162416 else
24172417 Write(0.0:3:0);
24182418 end;
24192419 WriteLn;
24202420 end;
24212421 WriteLn;
24222422
24232423 // Calculate eigenvalues/vectors
24242424 eigts3(d[1], c[2], n, lambda[1], x[1,1], n, term);
24252425 if term <> 1 then begin
24262426 WriteLn('term = ', term, ' --> ERROR');
24272427 halt;
24282428 end;
24292429
24302430 // Write expected results of eigenvalues
24312431 WriteLn('Expected eigenvalues:');
24322432 Write(-sqrt(2):15:3, 2-sqrt(2):15:3, sqrt(2):15:3, 2+sqrt(2):15:3);
24332433 WriteLn;
24342434 WriteLn;
24352435
24362436 // write eigenvalues
24372437 WriteLn('Calculated eigenvalues: lambda = ');
24382438 for i := 1 to n do
24392439 Write(lambda[i]:15:3);
24402440 WriteLn;
24412441 WriteLn;
24422442
24432443 // Write eigenvectors
24442444 WriteLn('Eigenvectors (as columns): x = ');
24452445 for i := 1 to n do begin
24462446 for j := 1 to n do
24472447 Write(x[i, j]:15:3);
24482448 WriteLn;
24492449 end;
24502450end.
24512451```
24522452
24532453## Finding the roots of a function (unit *roo*)
24542454
24552455The roots are the *x* values at which a function *f(x)* is zero.
24562456
24572457### Roots of a polynomial
24582458
24592459A polynomial of degree *n*
24602460
24612461``` math
24622462z^n + a_1 z^{n-1} + a_2 z^{n-2} + ... + a_{n-1} z + a_n = 0
24632463```
24642464
24652465always has *n*, not necessarily distinct, complex solutions. The datatype `complex` has been described in section complex numbers. These roots can be calculated by the function `roopol`:
24662466
24672467``` pascal
24682468procedure roopol(var a: ArbFloat; n: ArbInt; var z: complex; var k, term: ArbInt);
24692469```
24702470
24712471- `a` is the first element of an array containing the polynomial coefficients. Note that it is assumed that the polynomial has been normalized such that the coefficient of the highest-degree term is 1; this coefficient is not contained in the array. Therefore, the array must be dimensioned for at least *n* values. Since only real polynomials are handled here the array elements must be of datatype `ArbFloat`. The polynomial coefficients must be ordered from highest to lowest degree. Note that the data organization of the array is **different** from other polynomial routines in this library.
24722472- `n` is the degree of the polynomial. Must be a positive integer.
24732473- `z` is the first element in an array of `complex` values which returns the roots of the polynomial. The array must be dimensioned to contain at least *n* values. The returned values are undefined if an error had occurred (`term` \<\> 1).
24742474- `k` returns how many roots were found. Always must be equal to *n*, otherwise an error has occured.
24752475- `term` returns an error code:
24762476 - 1 -- sucessful completion, the result array contains valid data.
24772477 - 2 -- Not all roots could be detected, i.e. `k < n`.
24782478 - 3 -- Error in input data: `n < 1`.
24792479
24802480**Example**
24812481
24822482Calculate the roots of the polynomial *z*<sup>5</sup> + 3 *z*<sup>4</sup> + 4 *z*<sup>3</sup> - 8 *z*<sup>2</sup>. The expected zero points are:
24832483
24842484``` math
24852485z_1=0,\ z_2=0,\ z_3=1,\ z_4=-2+2i,\ z_5=-2-2i
24862486```
24872487
24882488``` pascal
24892489program polynomial;
24902490
24912491uses
24922492 SysUtils, typ, roo;
24932493
24942494const
24952495 n = 5;
24962496 a: array[1..n] of ArbFloat = (3, 4, -8, 0, 0);
24972497
24982498var
24992499 z: array[1..n] of complex;
25002500 k: ArbInt;
25012501 term: ArbInt;
25022502 i: Integer;
25032503 c: complex;
25042504
25052505 function ComplexToStr(z: complex; Decimals: Integer): string;
25062506 const
25072507 SIGN: array[boolean] of string = ('+', '-');
25082508 begin
25092509 Result := Format('%.*f %s %.*f i', [Decimals, z.re, SIGN[z.im <0], Decimals, abs(z.im)]);
25102510 end;
25112511
25122512begin
25132513 // Solve equation
25142514 roopol(a[1], n, z[1], k, term);
25152515
25162516 if term = 1 then begin
25172517 // Display results
25182518 WriteLn('Results of procedure roopol:');
25192519 for i:=1 to n do
25202520 WriteLn(' Solution #', i, ': ', ComplexToStr(z[i], 6):20);
25212521 WriteLn;
25222522
25232523 // Display expected results
25242524 Writeln('Expected results:');
25252525 c.Init(0, 0); // z1 = 0
25262526 WriteLn(' Solution #1: ', complexToStr(c, 6):20);
25272527 c.Init(0, 0); // z2 = 0
25282528 WriteLn(' Solution #2: ', complexToStr(c, 6):20);
25292529 c.Init(1, 0); // z3 = 1
25302530 WriteLn(' Solution #3: ', complexToStr(c, 6):20);
25312531 c.Init(-2, +2); // z4 = -2 + 2 i
25322532 WriteLn(' Solution #4: ', complexToStr(c, 6):20);
25332533 c.Init(-2, -2); // z4 = -2 - 2 i
25342534 WriteLn(' Solution #5: ', complexToStr(c, 6):20);
25352535 end else
25362536 WriteLn('ERROR');
25372537end.
25382538```
25392539
25402540### Roots of the quadratic equation
25412541
25422542The quadratic equation
25432543
25442544
25452545$`{z}^2 + {p} {z} + {q} = 0`$
25462546
25472547is a special polynomal of degree 2. It always has two, not necessarily different, complex roots. These solutions can be determined by the procedure `rooqua`.
25482548
25492549``` pascal
25502550procedure rooqua(p, q: ArbFloat; var z1, z2: complex);
25512551```
25522552
25532553- `p` and `q` are the (real) coefficients of the quadratic equation
25542554- `z1` and `z2` return the two complex roots. See unit *typ* for the declaration of the type `complex`
25552555
25562556Note that `rooqua` assumes that the coefficient of the quadratic term has been normalized to be 1.
25572557
25582558**Example**
25592559
25602560Determine the roots of the equation *z<sup>2</sup> + 2 z + 5 = 0*.
25612561
25622562``` pascal
25632563program quadratic_equ;
25642564
25652565uses
25662566 SysUtils, typ, roo;
25672567
25682568var
25692569 z1, z2: complex;
25702570
25712571const
25722572 SIGN: array[boolean] of string = ('+', '-');
25732573
25742574begin
25752575 rooqua(2, 5, z1, z2);
25762576
25772577 WriteLn(Format('1st solution: %g %s %g i', [z1.re, SIGN[z1.im < 0], abs(z1.im)]));
25782578 WriteLn(Format('2nd solution: %g %s %g i', [z2.re, SIGN[z2.im < 0], abs(z2.im)]));
25792579end.
25802580```
25812581
25822582### Roots of the binomial equation
25832583
25842584The equation
25852585
25862586``` math
25872587z^n = a
25882588```
25892589
25902590is another special polynomial in which all terms except for the highest- and lowest-order terms have zero coefficients. It has *n* complex solutions which can be calculated by the procedure `roobin`:
25912591
25922592``` pascal
25932593procedure roobin(n: ArbInt; a: complex; var z: complex; var term: ArbInt);
25942594```
25952595
25962596- `n` is the exponent in the binomial equation used. *n* must be a positive integer.
25972597- `a` is the constant term *a* at the right side of the binomial equation. It is expected to be a complex value (see section on complex numbers).
25982598- `z` is the first element of an array of data type `complex` to receive the results of the procedure. It must be dimensioned for at least *n* `complex` values.
25992599- `term` returns an error code:
26002600 - 1 -- successful termination
26012601 - 2 -- error in input data: `n < 1`
26022602
26032603**Example**
26042604
26052605Calculate the roots of the equation
26062606
26072607``` math
26082608z^4 = -1
26092609```
26102610
26112611The exact solutions are
26122612
26132613``` math
26142614z_1 = \frac{1}{2} \sqrt{2} (1+i) ,\ z_2 = \frac{1}{2} \sqrt{2} (1-i) ,\ z_3= \frac{1}{2} \sqrt{2} (-1+i) ,\ z_4= \frac{1}{2} \sqrt{2} (-1-i)
26152615```
26162616
26172617``` pascal
26182618program binomial;
26192619
26202620uses
26212621 SysUtils, typ, roo;
26222622
26232623const
26242624 n = 4;
26252625
26262626var
26272627 z: array[1..n] of complex;
26282628 term: ArbInt;
26292629 i: Integer;
26302630 a: complex;
26312631 c: complex;
26322632
26332633 function ComplexToStr(z: complex; Decimals: Integer): string;
26342634 const
26352635 SIGN: array[boolean] of string = ('+', '-');
26362636 begin
26372637 Result := Format('%.*g %s %.*g i', [Decimals, z.re, SIGN[z.im < 0], Decimals, abs(z.im)]);
26382638 end;
26392639
26402640begin
26412641 // Prepare constant term as a complex value
26422642 a.Init(-1, 0);
26432643
26442644 // Solve equation
26452645 roobin(n, a, z[1], term);
26462646
26472647 if term = 1 then begin
26482648 // Display results
26492649 WriteLn('Results of procedure roobin:');
26502650 for i:=1 to n do
26512651 WriteLn(' Solution #', i, ': ', ComplexToStr(z[i], 6):20);
26522652 WriteLn;
26532653
26542654 // Display expected results
26552655 Writeln('Expected results:');
26562656 c.Init(1, 1);
26572657 c.Scale(0.5*sqrt(2));
26582658 WriteLn(' Solution #1: ', complexToStr(c, 6):20);
26592659 c.Init(1, -1);
26602660 c.Scale(0.5*sqrt(2));
26612661 WriteLn(' Solution #2: ', complexToStr(c, 6):20);
26622662 c.Init(-1, 1);
26632663 c.Scale(0.5*sqrt(2));
26642664 WriteLn(' Solution #3: ', complexToStr(c, 6):20);
26652665 c.Init(-1, -1);
26662666 c.Scale(0.5*sqrt(2));
26672667 WriteLn(' Solution #4: ', complexToStr(c, 6):20);
26682668 end else
26692669 WriteLn('ERROR');
26702670end.
26712671```
26722672
26732673### Bisection
26742674
26752675In the [bisection method](https://en.wikipedia.org/wiki/Bisection_method) two *x* values *a* and *b* are estimated to be around the expected root such that the function values have opposite signs at *a* and *b*. The center point of the interval is determined, and the subinterval for which the function has opposite signs at its endpoints is selected for a new iteration. The process ends when a given precision, i.e. interval length, is achieved.
26762676
26772677In NumLib, this approach is supported by the procedure `roof1r`:
26782678
26792679``` pascal
26802680procedure roof1r(f: rfunc1r; a, b, ae, re: ArbFloat; var x: ArbFloat; var term: ArbInt);
26812681```
26822682
26832683- `f` is the function for which the root is to be determined. It must be a function of one floating point argument (type `ArbFloat`). The type of the function, `rfunc1r`, is declared in unit `typ`.
26842684- `a` and `b` are the endpoints of the test interval. The root must be located between these two values, i. e. the function values *f(a)* and *f(b)* must have different signs.
26852685- `ae` and `re` determine the absolute and relative precision, respectively, with which the root will be determined. `re` is relative to the maximum of *abs(a)* and *abs(b)*. Note that precision and speed are conflicting issues. Highest accuracy is achieved if `ae` is given as `MachEps` (see unit `typ`). Both parameters must not be negative.
26862686- `x` returns the value of the found root.
26872687- `term` returns whether the process has been successful:
26882688 - 1 - successful termination, a zero point has been found with absolute accuracy `ae` or relative accuracy `re`
26892689 - 2 - the required accuracy of the root could not be reached; However, the value of x is called the "best achievable" approach
26902690 - 3 - error in input parameters: `ae` \< 0 or `re` \< 0, or *f(a)***f(b)* \> 0
26912691
26922692**Example**
26932693
26942694The following program determines the square root of 2. This is the *x* value at which the function *f(x)* = *x*^2 - 2 is zero. Since *f(1)* = 1^2 - 2 = -1 \< 0 and *f(2)* = 2^2 - 2 = 2 \> 0 we can assume *a* and *b* to be 1 and 2, respectively.
26952695
26962696``` pascal
26972697program bisection_demo;
26982698
26992699uses
27002700 typ, roo;
27012701
27022702function f(x: ArbFloat): ArbFloat;
27032703begin
27042704 Result := x*x - 2;
27052705end;
27062706
27072707var
27082708 x: ArbFloat = 0.0;
27092709 term : ArbInt;
27102710
27112711begin
27122712 roof1r(@f, 1.0, 2.0, 1e-9, 0, x, term);
27132713 WriteLn('Bisection result ', x);
27142714 WriteLn('sqrt(2) ', sqrt(2.0));
27152715end.
27162716```
27172717
27182718### Roots of a system of nonlinear equations
27192719
27202720Procedure `roofnr` finds the roots of a system of (nonlinear) equations:
27212721
27222722
27232723$`f_{i}(x_1,x_2,\ldots,x_n)=0, \; i=1,2,\ldots,n`$
27242724
27252725``` pascal
27262726procedure roofnr(f: roofnrfunc; n: ArbInt; var x, residu: ArbFloat; ra: ArbFloat; var term: ArbInt);
27272727```
27282728
27292729- `f` contains the address of the procedure that calculates the function values *f*<sub>*i*</sub>(*x*<sub>1</sub>, *x*<sub>2</sub>, ..., *x*<sub>*n*</sub>). The type of the function, `roofnrfunc`, is declared in unit *typ* to be
27302730
27312731``` pascal
27322732 type
27332733 roofnrfunc = procedure(var x, fx: ArbFloat; var deff: boolean);
27342734
27352735```
27362736
27372737:* Here, `x` is the first element of an array of at least *n* `ArbFloat` values which provides the x<sub>j</sub> values at which the functions are to be calculated.
27382738
27392739:* `fx` is the first element of another array of *n* `ArbFloat` values to receive the result of each function *f*<sub>*i*</sub>. Suppose, we want to solve the following system of equations
27402740
27412741
27422742$`\begin{array}{l}
27432743 f_1(x_1, x_2) = x_1^2 + x_2^2 - 2 = 0 \\
27442744 f_2(x_1, x_2) = -(x_1 - 1)^2 + x_2 = 0
27452745\end{array}`$
27462746
27472747
27482748Then the function to be used as `f` can be written like this:
27492749
27502750``` pascal
27512751 procedure func(var x1, f1x: real; var deff: boolean);
27522752 var
27532753 x: array[1..2] of real absolute x1;
27542754 f: array[1..2] of real absolute f1x;
27552755 begin
27562756 f[1] := sqr(x[1]) + sqr(x[2]) - 2;
27572757 f[2] := -sqr(x[1] - 1) + x[2];
27582758 end;
27592759```
27602760
27612761:* The boolean parameter `deff` provides a way to stop the root finding process if some condition is met. The above example has two solutions, namely the intersection of a circle with a parabola. If only the intersection with `x[1] >= 1` is requested then the process can be stopped as soon as a value `x[1] < 1` is reached by setting `deff` to `false`:
27622762
27632763``` pascal
27642764 procedure func(var x1, f1x: real; var deff: boolean); far;
27652765 var
27662766 x: array[1..2] of real absolute x1;
27672767 f: array[1..2] of real absolute f1x;
27682768 begin
27692769 deff := x[1] >= 1;
27702770 if deff then begin
27712771 f[1] := sqr(x[1]) + sqr(x[2]) - 2;
27722772 f[2] := -sqr(x[1] - 1) + x[2];
27732773 end;
27742774 end;
27752775```
27762776
27772777- `n` is the number of equations or independent x values (*x*<sub>i</sub>) in the system. Both must be the same.
27782778- `x` is the first element of an array of at least *n* `ArbFloat` values which serves for both input and output. On **input** the array must contain estimates of the roots to be used as starting points of the iterative calculation. On **output**, the array is overwritten with the zero points found. Inspect the returned value of `term` to decide whether the output array contains a valid solution.
27792779- `residu` is the 2-norm of the vector of residuals in the calculated solution
27802780
27812781
27822782$`\|f\|_2 = \sqrt{\Sigma_{i=1}^{n} f_i^2}`$
27832783
27842784- `ra` contains the relative accuracy with which the desired solution is to be calculated. Recommended values: 10<sup>-3</sup>, 10<sup>-5</sup>, 10<sup>-8</sup> if `ArbFloat` is `single`, `real`, or `double`.
27852785- `term` returns an error code:
27862786 - 1 -- successfull completion, a solution has been found with the desired accuracy
27872787 - 2 -- the desired accuracy could not be reached, the returned values of *x* are the best ones achievable.
27882788 - 3 -- Incorrect input data: `n < 0`, or `re < 0`
27892789 - 4 -- The calculation process has been stopped because an internal limit for the count of function calls has been reached. Retry with other starting values for x.
27902790 - 5 -- Not enough progess, there may be no solution at all. If the solution is close to 0 choose another starting value.
27912791 - 6 -- The procedure wants to calculate a value outside the range defined by `deff`.
27922792
27932793**Example**
27942794
27952795Calculate a solutions of the equation system
27962796
27972797
27982798$`\begin{array}{l}
27992799 f_1(x_1, x_2) = x_1^2 + x_2^2 - 2 = 0 \\
28002800 f_2(x_1, x_2) = -(x_1 - 1)^2 + x_2 = 0
28012801\end{array}`$
28022802
28032803``` pascal
28042804program nonlinsystem;
28052805
28062806uses
28072807 SysUtils, typ, roo;
28082808
28092809const
28102810 n = 2;
28112811 ra = 1e-10;
28122812
28132813var
28142814 x: array[1..n] of ArbFloat;
28152815 f_check: array[1..n] of ArbFloat;
28162816 residu: ArbFloat;
28172817 i: Integer;
28182818 term: Integer;
28192819 deff: Boolean;
28202820
28212821 procedure funcs(var x0, fx: ArbFloat; var deff: boolean);
28222822 var
28232823 xloc: array[1..n] of ArbFloat absolute x0;
28242824 f: array[1..n] of ArbFloat absolute fx;
28252825 x, y, z: ArbFloat;
28262826 begin
28272827 x := xloc[1];
28282828 y := xloc[2];
28292829 f[1] := sqr(x) + sqr(y) - 2;
28302830 f[2] := -sqr(x - 1) + y;
28312831 end;
28322832
28332833begin
28342834 // Initial guessed values
28352835 x[1] := 0;
28362836 x[2] := 0;
28372837
28382838 // Solve equation
28392839 roofnr(@funcs, n, x[1], residu, ra, term);
28402840
28412841 WriteLn('term = ', term);
28422842 WriteLn;
28432843
28442844 if term in [1, 2] then begin
28452845 // Display results
28462846 WriteLn('Results found by procedure roofnr:');
28472847 for i:=1 to n do
28482848 WriteLn('Solution #' + IntToStr(i)+': ':20, x[i]:0:6);
28492849 WriteLn;
28502850
28512851 WriteLn('Norm of residuals: ', residu:0:15);
28522852 WriteLn;
28532853
28542854 // Test functions:
28552855 f(x[1], f_check[1], deff);
28562856 WriteLn('Check if the function values really are 0 at the found roots:');
28572857 for i := 1 to n do
28582858 WriteLn('Function #' + IntToStr(i) + ': ':20, f_check[i]:0:15);
28592859 end else
28602860 WriteLn('ERROR');
28612861
28622862 ReadLn;
28632863end.
28642864```
28652865
28662866## Numerical integration of a function (unit *int*)
28672867
28682868The NumLib function `int1fr` calculates the [integral](https://en.wikipedia.org/wiki/Integral) of a given function between limits `a` and `b` with a specified absolute accuracy `ae`:
28692869
28702870``` pascal
28712871procedure int1fr(f: rfunc1r; a, b, ae: ArbFloat; var integral, err: ArbFloat; var term: ArbInt);
28722872```
28732873
28742874- `f` points to the function to be integrated. It must be a function of a single real variable (type `ArbFloat`) and return an `Arbfloat`. See the type `rfunc1r` declared in unit *typ*.
28752875- `a, b` are the limits of integration. `a` and/or `b` can attain the values `+/-Infinity` for integrating over an infinite interval. The order of `a` and `b` is handled in the mathematically correct sense.
28762876- `ae` determines the absolute accuracy requested.
28772877- `integreal` returns the value of the integral. It is only valid if `term = 1`.
28782878- `err` returns the achieved accuracy if the specified accuracy could not be reached. `term` has the value 2 in this case.
28792879- `term` returns an error code:
28802880 - 1 - successful termination, the integral could be calculated with absolute accuracy `ae`.
28812881 - 2 - the requested accuracy could not be reached. But the integral is approximated within the accuracy `err`.
28822882 - 3 - incorrect input data: `ae < 0`, or `a = b = infinity`, or `a = b = -infinity`
28832883 - 4 - the integral could not be calculated: divergence, or too-slow convergence.
28842884
28852885**Example**
28862886
28872887Calculate the integral
28882888
28892889
28902890$`\int_a^b \frac 1 {x^2} \mathrm{d}x`$
28912891
28922892for several integration limits `a` and `b`. Since the function diverges at *x = 0* the interval from *a* to *b* must not contain this point. The analytic result is $`-1/{b} + 1/{a}`$
28932893
28942894``` pascal
28952895program integrate;
28962896
28972897uses
28982898 SysUtils, typ, int;
28992899
29002900function recipx2(x: ArbFloat): ArbFloat;
29012901begin
29022902 Result := 1.0 / sqr(x);
29032903end;
29042904
29052905function integral_recipx2(a, b: ArbFloat): Arbfloat;
29062906begin
29072907 if a = 0 then
29082908 a := Infinity
29092909 else if a = Infinity then
29102910 a := 0.0
29112911 else
29122912 a := -1/a;
29132913 if b = 0 then
29142914 b := Infinity
29152915 else if b = Infinity then
29162916 b := 0.0
29172917 else
29182918 b := -1/b;
29192919 Result := b - a;
29202920end;
29212921
29222922procedure Execute(a, b: ArbFloat);
29232923var
29242924 err: ArbFloat = 0.0;
29252925 term: ArbInt = 0;
29262926 integral: ArbFloat = 0.0;
29272927begin
29282928 try
29292929 int1fr(@recipx2, a, b, 1e-9, integral, err, term);
29302930 except
29312931 term := 4;
29322932 end;
29332933 Write(' The integral from ' + FloatToStr(a) + ' to ' + FloatToStr(b));
29342934 case term of
29352935 1: WriteLn(' is ', integral:0:9, ', exected: ', integral_recipx2(a, b):0:9);
29362936 2: WriteLn(' is ', integral:0:9, ', error: ', err:0:9, ', exected: ', integral_recipx2(a, b):0:9);
29372937 3: WriteLn(' cannot be calculated: Error in input data');
29382938 4: WriteLn(' cannot be calculated: Divergent, or calculation converges too slowly.');
29392939 end;
29402940end;
29412941
29422942begin
29432943 WriteLn('Integral of f(x) = 1/x^2');
29442944 Execute(1.0, 2.0);
29452945 Execute(1.0, 1.0);
29462946 Execute(2.0, 1.0);
29472947 Execute(1.0, Infinity);
29482948 Execute(-Infinity, -1.0);
29492949 Execute(0.0, Infinity);
29502950 // NOTE: The next line will raise an exception if run in the IDE. This will not happen outside the IDE.
29512951 // Integrate(-1.0, Infinity);
29522952end.
29532953```
29542954
29552955## Ordinary differential equations (unit *ode*)
29562956
29572957### Solving a single first-order differential equation
29582958
29592959The routine `odeiv1` solves an initial value problem described by a first-order differential equation of the form $`\begin{cases}
29602960 y' = f(x, y), \qquad x \in [a, b]\\
29612961 y(a) = \alpha
29622962 \end{cases}`$ with given *a*, *b*, *f* and initial condition *y*(*a*) = α.
29632963
29642964``` pascal
29652965procedure odeiv1(f: rfunc2r; a, ya: ArbFloat; var b, yb: ArbFloat; ae: ArbFloat; var term: ArbInt);
29662966```
29672967
29682968- `f` is the funtion to be processed. It depends on two real variables and returns a real value, as declared in unit *typ* as `type rfunc2r = function(x, y: ArbFloat): ArbFloat`.
29692969- `a` is the *x* value of the starting point of the interval in which the solution is determined.
29702970- `ya` is the value *α* of the function at the starting point *a* (initial value).
29712971- `b` is the *x* value of the end point of the interval in which the solution is determined. The case `a > b` is allowed. If, after the calculation, the error code `term` is 2 then the interval has been changed, and `b` contains the new endpoint to which the solution could be calculated with the desired accuracy, `ae`. In all other cases, `b` is unchanged.
29722972- `yb` returns the result of the calculation if `term` \< 3. It is the value of the function at the end point *b* of the interval. If `term = 3` then the result is undefined.
29732973- `ae` defines the absolute accuracy with which the value *y*(*b*) must be determined.
29742974- `term` returns an error code:
29752975 - 1 - successful completion
29762976 - 2 - the solution could not reach the point *b* with the required accuracy. However, `yb` is a sufficiently accurate approximation of y at the delivered `b`.
29772977 - 3 - input error: `ae <= 0`
29782978
29792979The algorithm is adaptive and is based on an [explicit one-step Runge-Kutta method](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods) of order five.
29802980
29812981The procedure is not suitable for a rigid differential comparison.
29822982
29832983The accuracy can not be guaranteed in all cases. In so-called unstable problems, e.g. small variations in *y*(*a*) give large variations in *y*(*b*), the error may be significantly larger than what the result of the procedure suggests. Call the procedure with different values of `ae` to investigate this case.
29842984
29852985If one wants to solve the initial value problem for a number of points, e.g. from *x* = 0 to *x* = 1 with step size 0.1, then it is advisable to "integrate" and thus not to restart at *x* = 0 with every new step.
29862986
29872987**Example**
29882988
29892989Solve the differential equation *y"* = -10 (y - *x*<sup>2</sup>) with the initial condition *y*(0) = 0 for *x* = 0, 0.5, ..., 5. Compare with the exact solution *y*(*x*) = -0.02 exp(-10 *x*) + *x*<sup>2</sup> - 0.2 *x* + 0.02
29902990
29912991``` pascal
29922992program solve_ode;
29932993
29942994uses
29952995 typ, ode;
29962996
29972997 function f(x, y: ArbFloat): Arbfloat;
29982998 begin
29992999 Result := -10 * (y - sqr(x));
30003000 end;
30013001
30023002 function exact(x: real): real; far;
30033003 begin
30043004 Result := -0.02*exp(-10*x) + sqr(x) - 0.2*x + 0.02;
30053005 end;
30063006
30073007const
30083008 d = 0.5; // interval length
30093009 ae = 1e-5;
30103010 n = 10;
30113011
30123012var
30133013 a, b: ArbFloat;
30143014 ya, yb: ArbFloat;
30153015 term: ArbInt;
30163016 i: Arbint;
30173017
30183018begin
30193019 // Set initial conditions
30203020 a := 0.0;
30213021 b := a + d;
30223022 ya := 0.0;
30233023
30243024 WriteLn('x':12, 'y':12, 'exact':12, 'error code':17);
30253025 WriteLn;
30263026 WriteLn(a:12:5, ya:12:5, exact(a):12:5, '-':17);
30273027
30283028 for i := 1 to n do begin
30293029 odeiv1(@f, a, ya, b, yb, ae, term);
30303030 WriteLn(b:12:5, yb:12:5, exact(b):12:5, term:17);
30313031 a := b;
30323032 ya := yb;
30333033 b := b + d;
30343034 end;
30353035end.
30363036```
30373037
30383038### Solving a system of first-order differential equations
30393039
30403040An initial value problem described by system of first-order differential equations can be solved by the procedure `odeiv2`: $`\begin{cases}
30413041 \mathbf{y}' = \mathbf{f}(x, \mathbf{y}), \qquad x \in [a, b]\\
30423042 \mathbf{y}(a) = \mathbf{\alpha}
30433043 \end{cases}`$
30443044
30453045where
30463046
30473047- ***y*** is an unknown n vector
30483048
30493049
30503050
30513051*y*: \[*a*, *b*\] --\> **R**<sup>n</sup>
30523052
30533053or: *y* = \[*y<sub>1</sub>*(*x*), *y<sub>2</sub>*(*x*), ..., *y<sub>n</sub>*(*x*)\]
30543054
30553055- ***f*** is a vector function
30563056
30573057
30583058
30593059***f***: \[*a*, *b*\] --\> **R**<sup>n</sup>
30603060
30613061or: ***f***(*x*, *y*) = \[*f*<sub>1</sub>(*x*,*y*), f<sub>2</sub>(*x*,*y*), ..., f<sub>n</sub>(*x*,*y*)\]
30623062
30633063- *a*, *b*, and the initial conditions ***α*** = ***y***(*a*) = \[*y<sub>1</sub>*(*a*), *y<sub>2</sub>*(*a*), ... *y<sub>n</sub>*(*a*)\] are given.
30643064
30653065The algorithm is based on an [explicit one-step Runge-Kutta method](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods) of order five with variable step size.
30663066
30673067``` pascal
30683068procedure odeiv2(f: oderk1n; a: ArbFloat; var ya, b, yb: ArbFloat; n: ArbInt; ae: ArbFloat; var term: ArbInt);
30693069```
30703070
30713071- The parameter `f` contains the name of the procedure that calculates the function values ***f<sub>i</sub>***(*x*, *y<sub>i</sub>*). The declaration of the procedure must match the `type oderk1n = procedure(x: ArbFloat; var y, fxy: ArbFloat)` declared in unit *typ*. Here `x` is the coordinate in the interval \[*a*, *b*\], and `y` is the first value of an array of *y* values to be used in each equation of the ODE system. The function result is returned in another array which is specified by its first value `fxy`. Both arrays must be dimensioned to provide at least *n* elements.
30723072- `a` is the begin of the calculation interval.
30733073- `ya` is the first element of an array containing the initial conditions for each ODE of the system, i.e. the function values *y*<sub>i</sub>(*a*) = *α<sub>i</sub>*. The array must be allocated to containt at least *n* elements.
30743074- `b` is the end of the calculation interval. The case `a > b` is allowed. If after the calculation `term` is 2 then the interval has been changed, and `b` contains the new endpoint to which the solution could have been calculated with the desired accuracy, `ae`. In all other cases, `b` is unchanged.
30753075- `yb` is the first element of an array which receives the results of the calculation. It must be dimensioned to contain at least n elements.
30763076- `n` denotes the number of equations in the system.
30773077- `ae` specifies the absolute accuracy with which the solution must be calculated.
30783078- `term` is an error code:
30793079 - 1 - successful completion.
30803080 - 2 - the solution could not reach the point *b* with the required accuracy. However, the values in the `yb` array are sufficiently accurate approximations of the *y* values at the delivered `b`.
30813081 - 3 - input error: `n < 1`, or `ae <= 0`.
30823082
30833083**Example**
30843084
30853085Integrate the following initial value problem between *x* = 0 and *x* = 1 with step size 0.1: $`\begin{cases}
30863086 y'_{1}=2xy_{1}+y_{2} \\
30873087 y'_{2}=-y_{1}+2xy_{2}\\
30883088 y_1(0)=0,\ y_2(0)=1
30893089\end{cases}`$
30903090
30913091The exact solutions are $`\begin{cases}
30923092 y_1(x)=\exp({x^2}) \sin(x) \\
30933093 y_2(x)=\exp({x^2}) \cos(x)
30943094\end{cases}`$
30953095
30963096``` pascal
30973097program solve_ode_sys;
30983098
30993099uses
31003100 typ, ode;
31013101
31023102const
31033103 ae = 1e-5;
31043104
31053105 procedure f(x: ArbFloat; var y, fxy: ArbFloat);
31063106 var
31073107 _y: array[1..2] of ArbFloat absolute y;
31083108 _fxy: array[1..2] of ArbFloat absolute fxy;
31093109 begin
31103110 _fxy[1] := 2*x*_y[1] + _y[2];
31113111 _fxy[2] := -_y[1] + 2*x*_y[2]
31123112 end;
31133113
31143114 function exact1(x: ArbFloat): ArbFloat;
31153115 begin
31163116 Result := exp(x*x) * sin(x);
31173117 end;
31183118
31193119 function exact2(x: ArbFloat): ArbFloat;
31203120 begin
31213121 Result := exp(x*x) * cos(x);
31223122 end;
31233123
31243124var
31253125 a, b, d: ArbFloat;
31263126 ya: array[1..2] of ArbFloat;
31273127 yb: array[1..2] of ArbFloat;
31283128 term: ArbInt;
31293129 i: Arbint;
31303130 n: ArbInt;
31313131
31323132begin
31333133 // Set initial conditions
31343134 a := 0.0;
31353135 b := 0.1;
31363136 d := b - a;
31373137 ya[1] := 0.0;
31383138 ya[2] := 1.0;
31393139 n := 10;
31403140
31413141 WriteLn('x':12, 'y[1]':12, 'y[2]':12, 'exact[1]':12, 'exact[2]':12, 'error code':17);
31423142 WriteLn;
31433143 WriteLn(a:12:5, ya[1]:12:5, ya[2]:12:5, exact1(a):12:5, exact2(a):12:5, '-':17);
31443144
31453145 for i := 1 to n do begin
31463146 odeiv2(@f, a, ya[1], b, yb[1], 2, ae, term);
31473147 WriteLn(b:12:5, yb[1]:12:5, yb[2]:12:5, exact1(b):12:5, exact2(b):12:5, term:17);
31483148 a := b;
31493149 ya[1] := yb[1];
31503150 ya[2] := yb[2];
31513151 b := b + d;
31523152 end;
31533153end.
31543154```
31553155
31563156## Interpolation and fitting (unit *ipf*)
31573157
31583158Unit *ipf* contains routines for
31593159
31603160- fitting a set of data points with a polynomial,
31613161- interpolating or fitting a set of data points with a natural cubic spline. A [spline](https://en.wikipedia.org/wiki/Spline_(mathematics)) is a piecewise defined function of polynomials which has a high degree of smoothness at the connection points of the polynomials ("knots"). In case of a *natural cubic* spline, the polynomials have degree 3 and their second derivative is zero at the first and last knot.
31623162
31633163Fitting means: determine an approximating smooth function such that the deviations from the data points are at minimum.\
31643164Interpolation means: determine a function that runs exactly through the data points.
31653165
31663166Use of polynomials or splines is recommended unless the data are known to belong to a known function in which there are still some unknown parameters, for example, the data are measurements of the function ''f(x) = a + b e<sup>-c x</sup>.
31673167
31683168In the most common case, the data contain measurement errors and appear to follow the shape of a "simple" function. Therefore, it is recommended to first try to fit with a low-degree polynomial, e.g. not higher than degree 5. If this is not successful or if the shape of the function is more complicated try to fit with a spline.
31693169
31703170If the data contain no (or very little) measurement noise, it is almost always wise to interpolate the data with a spline.
31713171
31723172### Fitting with a polynomial
31733173
31743174For this purpose, the routine `ipfpol` can be applied. It uses given data points (*x<sub>i</sub>*, *y<sub>i</sub>*), *i* = 1, ..., *m*, and *n* given coefficients *b<sub>0</sub>*, ..., *b<sub>n</sub>* to calculate a polynomial $`\operatorname{p}(x) = b_0 + b_1 x + \dots + b_n x^n`$
31753175
31763176In the sense of the least squares fitting, the coefficients *b<sub>0</sub>* ... *b<sub>n</sub>* are adjusted to minimize the expression
31773177
31783178$`\sum_{i=1}^m {(\operatorname{p}(x) - y_i)^2}`$
31793179
31803180``` pascal
31813181procedure ipfpol(m, n: ArbInt; var x, y, b: ArbFloat; var term: ArbInt);
31823182```
31833183
31843184- `m` denotes the number of data points. It is required that `m ≥ 1`
31853185- `n` is the degree of the polynomial to be fitted. It is required that `n > 0`.
31863186- `x` is the first element of a one-dimensional array containing the *x* values of the data points. The array must be allocated to contain at least `m` values.
31873187- `y` is the first element of a one-dimensional array containing the *y* values of the data points. The array must be dimensioned to contain at least `m` values.
31883188- `b` is the first element of a one-dimenisonal array in which the determined polynomial coefficients are returned if `term` is 1. The array must have been dimensioned to provide space for at least `n + 1` values. The returned coefficients are ordered from lowest to highest degree.
31893189- `term` returns an error code:
31903190 - 1 - successful completion, the array `b` contains valid coefficients
31913191 - 3 - input error: `n < 0`, or `m < 1`.
31923192
31933193After the coefficients *b<sub>0</sub>*, ..., *b<sub>n</sub>* once have been determined the value of p(*x*) can be calculated for any *x* using the procedure `spepol` from the unit *spe*.
31943194
31953195> **Note:** With this routine, of course, an interpolating polynomial can also be determined by equaling *n* to *m* - 1.
31963196
31973197**Example**
31983198
31993199Fit a polynomial of degree 2 to these data points: $`\begin{array}{ccc}
32003200 \hline
32013201 i & x_i & y_i \\
32023202 \hline
32033203
32043204 1 & 0.00 & 1.26 \\
32053205 2 & 0.08 & 1.37 \\
32063206 3 & 0.22 & 1.72 \\
32073207 4 & 0.33 & 2.08 \\
32083208 5 & 0.46 & 2.31 \\
32093209 6 & 0.52 & 2.64 \\
32103210 7 & 0.67 & 3.12 \\
32113211 8 & 0.81 & 3.48
32123212 \end{array}`$ ![](/wiki-images/a/ae/Numlib_ipfpol.png)
32133213
32143214``` pascal
32153215program polynomial_fit;
32163216
32173217uses
32183218 SysUtils, StrUtils, typ, ipf, spe;
32193219
32203220const
32213221 m = 8;
32223222 n = 2;
32233223 x: array[1..m] of Arbfloat = (
32243224 0.00, 0.08, 0.22, 0.33, 0.46, 0.52, 0.67, 0.81
32253225 );
32263226 y: array[1..m] of Arbfloat = (
32273227 1.26, 1.37, 1.72, 2.08, 2.31, 2.64, 3.12, 3.48
32283228 );
32293229
32303230var
32313231 b: array[0..n] of ArbFloat;
32323232 i: Integer;
32333233 term: ArbInt;
32343234 xint, yint: ArbFloat;
32353235begin
32363236 WriteLn('Fitting a polynomial of degree 2: p(x) = b[0] + b[1] x + b[2] x^2');
32373237 WriteLn;
32383238
32393239 WriteLn('Data points');
32403240 WriteLn('i':10, 'xi':10, 'yi':10);
32413241 for i:=1 to m do
32423242 WriteLn(i:10, x[i]:10:2, y[i]:10:2);
32433243 WriteLn;
32443244
32453245 // Execute fit
32463246 ipfpol(m, n, x[1], y[1], b[0], term);
32473247
32483248 // Display fitted polynomial coefficients
32493249 WriteLn('Fitted coefficients b = ');
32503250 WriteLn(b[0]:10:6, b[1]:10:6, b[2]:10:6);
32513251 WriteLn;
32523252
32533253 // Interpolate and display fitted polynomial for some x values
32543254 WriteLn('Some interpolated data');
32553255 WriteLn('':10, 'x':10, 'y':10);
32563256 for i:= 1 to 5 do begin
32573257 xint := 0.2*i;
32583258 yint := spepol(xint, b[0], n);
32593259 WriteLn('':10, xint:10:2, yint:10:2);
32603260 end;
32613261end.
32623262```
32633263
32643264### Interpolation with a natural cubic spline
32653265
32663266The routine `ipfisn` can be used for this purpose. It calculates the parameters of the spline. Once these parameters are known, the value of the spline can be calculated at any point using the `ipfspn` procedure.
32673267
32683268Assume that (*x<sub>i</sub>*, y<sub>i</sub>*),*i*= 0, ...,*n' are the given data points with *x<sub>0</sub>* \< *x<sub>1</sub>* \< ... \< *x<sub>n</sub>*. Then assume a function *g(x)* which is twice continuously differentiable (i.e., the first and second derivatives exist and are continuous) and which interpolates the data (i.e., *g(x<sub>i</sub> = y<sub>i</sub>*). Imagine *g(x)* as the shape of a thin flexible bar. Then the bending energy (in first approximation) is proportional to
32693269
32703270$`\int_{-\infty}^{\infty} g''(x)^2 dx`$
32713271
32723272The interpolating natural spline *s* can be understood as the function *g* which minimizes this integral and therefore the bending energy.
32733273
32743274The spline *s* has the following properties:
32753275
32763276- *s* is a cubic polynomial on the interval \[*x<sub>i</sub>*, *x<sub>i+1</sub>*\]
32773277- it is twice continuously differentiable on (-∞, ∞)
32783278- *s"(x<sub>0</sub>)* = *s"(x<sub>n</sub>)* = 0
32793279- on (-∞, *x<sub>0</sub>*) and (*x<sub>n</sub>, ∞)* *s* is linear.
32803280
32813281``` pascal
32823282procedure ipfisn(n: ArbInt; var x, y, d2s: ArbFloat; var term: ArbInt);
32833283```
32843284
32853285- Calculates the second derivatives of the splines at the knots *x<sub>i</sub>*.
32863286- `n` is the index of the last data point, i.e. there are `n+1` data points.
32873287- `x` is the first element of the array with the x coordinates of the data points. It must be dimensioned to contain at last `n+1` values. The standard declaration would be `var x: array[0..n] of ArbFloat`. The data points must be in strictly ascending order, i.e. there must not be any data points for which *x<sub>i</sub>* \>= *x<sub>i+1</sub>*.
32883288- `y` is the first element of the array with the y coordinates of the data points. It must be dimensioned to contain at last `n+1` values. The standard declaration would be `var y: array[0..n] of ArbFloat`.
32893289- `d2s` is the first element of the array which receives the second derivatives of the splines at the knots if the returned error code `term` is 1. Note that the array does not contain values for the first and last data point because they are automatically set to 0 (*natural* splines). Therefore the array must be dimensioned to contain at least `n-1` elements. The standard declaration would be: `var d2s: array[1..n-1] of ArbFloat`
32903290- `term` returns an error code:
32913291 - 1 - successful completion, the array beginning at `d2s` contains valid data
32923292 - 2 - calculation of the second derivatives was not successful
32933293 - 3 - incorrect input parameters: *n* ≤ 1, or *x<sub>i</sub>* ≥ *x<sub>i+1</sub>*
32943294
32953295``` pascal
32963296function ipfspn(n: ArbInt; var x, y, d2s: ArbFloat; t: ArbFloat; var term: ArbInt): ArbFloat;
32973297```
32983298
32993299- Calculates the value of the spline function at any coordinate *t*. The calculation uses the second derivatives determined by `ipnisn`.
33003300- `n`, `x`, `y`, `d2s` have the same meaning as above
33013301- `term` returns an error code:
33023302 - 1 - successful completion
33033303 - 3 - The calculation could not be executed because *n* ≤ 1.
33043304
33053305**Example**
33063306
33073307Interpolate a natural cubic spline through the following data points: $`\begin{array}{ccc}
33083308 \hline
33093309 i & x_i & y_i \\
33103310 \hline
33113311
33123312 0 & 0.00 & 0.980 \\
33133313 1 & 0.09 & 0.927 \\
33143314 2 & 0.22 & 0.732 \\
33153315 3 & 0.34 & 0.542 \\
33163316 4 & 0.47 & 0.385 \\
33173317 5 & 0.58 & 0.292 \\
33183318 6 & 0.65 & 0.248 \\
33193319 7 & 0.80 & 0.179 \\
33203320 8 & 0.93 & 0.139
33213321 \end{array}`$ ![](/wiki-images/e/e6/Numlib_ipfisn.png)
33223322
33233323``` pascal
33243324program spline_interpolation;
33253325
33263326uses
33273327 typ, ipf;
33283328
33293329const
33303330 n = 8;
33313331 x: array[0..n] of Arbfloat = (
33323332 0.00, 0.09, 0.22, 0.34, 0.47, 0.58, 0.65, 0.80, 0.93
33333333 );
33343334 y: array[0..n] of Arbfloat = (
33353335 0.990, 0.927, 0.734, 0.542, 0.388, 0.292, 0.248, 0.179, 0.139
33363336 );
33373337
33383338var
33393339 s: array[0..n] of ArbFloat;
33403340 d2s: array[1..n-1] of ArbFloat;
33413341 i: Integer;
33423342 term: ArbInt;
33433343 xint, yint: ArbFloat;
33443344begin
33453345 WriteLn('Interpolation with a natural cubic spline');
33463346 WriteLn;
33473347
33483348 WriteLn('Data points');
33493349 WriteLn('i':10, 'xi':10, 'yi':10);
33503350 for i:=0 to n do
33513351 WriteLn(i:10, x[i]:10:2, y[i]:10:3);
33523352 WriteLn;
33533353
33543354 // Interpolation
33553355 ipfisn(n, x[0], y[0], d2s[1], term);
33563356
33573357 // Display 2nd derivatives of splines at xi
33583358 WriteLn('s"(xi)');
33593359 Write(0.0:10:6); // 2nd derivative of 1st point is 0
33603360 for i:=1 to n-1 do
33613361 Write(d2s[i]:10:6);
33623362 WriteLn(0.0:10:6); // 2nd derivative of last point is 0
33633363 WriteLn;
33643364
33653365 // Interpolate for some x values
33663366 WriteLn('Some interpolated data');
33673367 WriteLn('':10, 'x':10, 'y':10);
33683368 for i:= 1 to 5 do begin
33693369 xint := 0.2 * i;
33703370 yint := ipfspn(n, x[0], y[0], d2s[1], xint, term);
33713371 WriteLn('':10, xint:10:2, yint:10:2);
33723372 end;
33733373
33743374end.
33753375```
33763376
33773377### Fitting with a natural cubic spline
33783378
33793379The routine `ipffsn` can be used for this purpose. This routine calculates the spline-fit parameters. Once these parameters are known the value of the spline can be calculated at any point using the procedure `ipfspn`.
33803380
33813381Following the model with the bending energy in the previous chapter the calculated fit minimizes
33823382
33833383$`\int_{-\infty}^{\infty} g''(x)^2 dx + \lambda \sum_{i=0}^n (g(x_i) - y_i)^2`$
33843384
33853385where the parameter λ must be determined. It is a measure of the compromise between the "smoothness" of the fit - which is measured by the integral (the bending energy) - and the deviation of the fit to the given data - which is measured by the sum of squares. The solution is again a natural cubic spline.
33863386
33873387> **Warning:** The procedure `ipffsn` is buggy and crashes. Therefore, it is not documented at the moment.
33883388
33893389## Special functions (unit *spe*)
33903390
33913391### Evaluation of a polynomial
33923392
33933393[Horner's scheme](https://en.wikipedia.org/wiki/Horner%27s_method) provides an efficient method for evaluating a [polynomial](https://en.wikipedia.org/wiki/Polynomial) at a specific x value:
33943394
33953395
33963396$`\operatorname{p}(x) = a_0 + a_1 x + a_2 x^2 + ... + a_n x^n \ \Rightarrow \ \operatorname{p}(x) = a_0 + x (a_1 + x (a_2 + \dots + x (a_{n-1} + x a_n)))`$
33973397
33983398This method is applied in NumLib's procedure `spepol`:
33993399
34003400``` pascal
34013401function spepol(x: ArbFloat; var a: ArbFloat; n: ArbInt): ArbFloat;
34023402```
34033403
34043404- `x` is the argument at which the polynomial is evaluated.
34053405- `a` is the first element of an array containing the polynomial coefficients a<sub>0</sub>, a<sub>1</sub>, a<sub>2</sub>, ... a<sub>n</sub>. The coefficient must be ordered from lowest to highest degree.
34063406- `n` is the degree of the polynomial, i.e. there are `n+1` elements in the array.
34073407
34083408**Example**
34093409
34103410Evaluate the polynomial
34113411
34123412
34133413$`\operatorname{p}(x) = 2 x^3 - 6 x^2 + 2 x - 1`$
34143414
34153415at x = 3. Note that the terms are not in the correct order for `spepol` and rearrange them to
34163416
34173417
34183418$`\operatorname{p}(x) = -1 + 2 x - 6 x^2 + 2 x^3`$
34193419
34203420``` pascal
34213421program polynomial;
34223422
34233423uses
34243424 typ, spe;
34253425
34263426const
34273427 n = 3;
34283428 a: array[0..n] of ArbFloat = (-1, 2, -6, 2);
34293429
34303430var
34313431 x, y: ArbFloat;
34323432begin
34333433 x := 3.0;
34343434 y := spepol(x, a[0], n);
34353435 WriteLn(y:0:6);
34363436end.
34373437```
34383438
34393439### Error function
34403440
34413441![](/wiki-images/2/20/Numlib_erf_erfc.png) The [error function *erf(x)*](https://en.wikipedia.org/wiki/Error_function) and its complement, the [complementary error function *erfc(x)*](https://en.wikipedia.org/wiki/Error_function#Complementary_error_function), are the lower and upper integrals of the Gaussian function, normalized to unity:
34423442
34433443
34443444$`\operatorname{erf}(x) = \frac {2} {\sqrt{\pi}} \int_0^x e^{-{t}^2} dt`$
34453445
34463446
34473447$`\operatorname{erfc}(x) = \frac {2} {\sqrt{\pi}} \int_x^\infty e^{-{t}^2} dt = {1} - \operatorname{erf}(x)`$
34483448
34493449Both functions can be calculated by the NumLib functions `speerf` and `speefc`, respectively
34503450
34513451``` pascal
34523452function speerf(x: ArbFloat): ArbFloat; // --> erf(x)
34533453function speefc(x: ArbFloat): ArbFloat; // --> erfc(x)
34543454```
34553455
34563456**Example**
34573457
34583458A table of some values of the error function and the complementary error function is calculated in the following demo project:
34593459
34603460``` pascal
34613461program erf_Table;
34623462
34633463uses
34643464 SysUtils, StrUtils, typ, spe;
34653465
34663466const
34673467 Wx = 7;
34683468 Wy = 20;
34693469 D = 6;
34703470
34713471var
34723472 i: Integer;
34733473 x: ArbFloat;
34743474 fs: TFormatSettings;
34753475begin
34763476 fs := DefaultFormatSettings;
34773477 fs.DecimalSeparator := '.';
34783478 WriteLn('x':Wx, 'erf(x)':Wy+1, 'erfc(x)':Wy+1);
34793479 WriteLn;
34803480 for i:= 0 to 20 do begin
34813481 x := 0.1*i;
34823482 WriteLn(Format('%*.1f %*.*f %*.*f', [
34833483 Wx, x,
34843484 Wy, D, speerf(x),
34853485 Wy, D, speefc(x)
34863486 ], fs));
34873487 end;
34883488end.
34893489```
34903490
34913491### Normal distribution
34923492
34933493![](/wiki-images/7/7b/Numlib_normaldist.png) The (cumulative) [normal distribution](https://en.wikipedia.org/wiki/Normal_distribution) is the integral of the Gaussian function with zero mean and unit standard deviation, i.e.
34943494
34953495$`\operatorname{N}(x) = \frac{1}{\sqrt{2\pi}} \int_{-\infty}^{x} \operatorname{exp}(\frac{t^2}{2}) dt
34963496 = \frac{1}{2}[1 + \operatorname{erf} (\frac{x}{\sqrt{2}})]`$
34973497
34983498Suppose a set of random floating point numbers which are distributed according to this formula. Then *N(x)* measures the probability that a number from this set is smaller than *x*.
34993499
35003500In NumLib, the **normal distribution** is calculated by the function `normaldist`:
35013501
35023502``` pascal
35033503function normaldist(x: ArbFloat): ArbFloat;
35043504```
35053505
35063506- `x` can take any value between -∞ and +∞.
35073507- The function result is between 0 and 1.
35083508
35093509The **inverse normal distribution** of y returns the x value for which the normal distribution *N(x)* has the value y.
35103510
35113511``` pascal
35123512function invnormaldist(y: ArbFloat): ArbFloat;
35133513```
35143514
35153515- `y` is allowed only between 0 and 1.
35163516- The function can take any value between -∞ and +∞.
35173517
35183518> **Note:** The functions `normaldist` and `invnormaldist` are not available in fpc 3.0.x and older.
35193519
35203520**Example**
35213521
35223522Calculate the probabilities for *x* = -10, -8, ..., 12 for a normal distribution with mean *μ* = 2 and standard deviation *σ* = 5. In order to reduce this distribution to the standard normal distribution implemented by NumLib, we introduce a transformation *x<sub>r</sub>* = (*x* - *μ*)/*σ*.
35233523
35243524``` pascal
35253525program normdist;
35263526
35273527uses
35283528 typ, spe;
35293529
35303530const
35313531 mu = 2.0;
35323532 sigma = 5.0;
35333533 x: array[0..12] of ArbFloat = (-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14);
35343534
35353535var
35363536 xr, y: ArbFloat;
35373537 i: Integer;
35383538begin
35393539 WriteLn('Normal distribution with mean ', mu:0:1, ' and standard deviation ', sigma:0:1);
35403540 WriteLn;
35413541 WriteLn('x':20, 'y':20);
35423542
35433543 for i:= 0 to High(x) do begin
35443544 xr := (x[i] - mu) / sigma;
35453545 y := normaldist(xr);
35463546 WriteLn(x[i]:20:1, y:20:6);
35473547 end;
35483548end.
35493549```
35503550
35513551### Gamma function
35523552
35533553![](/wiki-images/8/8d/Numlib_gamma.png) The [gamma function](https://en.wikipedia.org/wiki/Gamma_function) is needed by many probability functions. It is defined by the integral
35543554
35553555
35563556$`\Gamma({x}) = \int_0^{\infty}t^{x-1} e^{-t} dt`$
35573557
35583558NumLib provides two functions for its calculation:
35593559
35603560``` pascal
35613561function spegam(x: ArbFloat): ArbFloat;
35623562function spelga(x: ArbFloat): ArbFloat;
35633563```
35643564
35653565The first one, `spegam`, calculates the function directly. But since the gamma function grows rapidly for even not-too large arguments this calculation very easily overflows.
35663566
35673567The second function, `spelga` calculates the natural logarithm of the gamma function which is more suitable to combinatorial calculations where multiplying and dividing the large gamma values can be avoided by adding or subtracting their logarithms.
35683568
35693569**Example**
35703570
35713571The following demo project prints a table of some values of the gamma function and of its logarithm. Note that `spegam(x)` overflows above about *x = 170* for data type `extended`.
35723572
35733573``` pascal
35743574program Gamma_Table;
35753575
35763576uses
35773577 SysUtils, StrUtils, typ, spe;
35783578
35793579const
35803580 VALUES: array[0..2] of ArbFloat = (1, 2, 5);
35813581 Wx = 7;
35823582 Wy = 30;
35833583 Wln = 20;
35843584
35853585var
35863586 i: Integer;
35873587 x: ArbFloat;
35883588 magnitude: ArbFloat;
35893589begin
35903590 WriteLn('x':Wx, 'Gamma(x)':Wy, 'ln(Gamma(x))':Wln);
35913591 WriteLn;
35923592 magnitude := 1E-3;
35933593 while magnitude <= 1000 do begin
35943594 for i := 0 to High(VALUES) do begin
35953595 x := VALUES[i] * magnitude;
35963596 Write(PadLeft(FloatToStr(x), Wx));
35973597 if x <= 170 then // Extended overflow above 170
35983598 Write(FormatFloat('0.000', spegam(x)):Wy)
35993599 else
36003600 Write('overflow':Wy);
36013601 WriteLn(spelga(x):Wln:3);
36023602 if abs(x-1000) < 1E-6 then
36033603 break;
36043604 end;
36053605 magnitude := magnitude * 10;
36063606 end;
36073607end.
36083608```
36093609
36103610### Incomplete gamma function
36113611
36123612![](/wiki-images/8/81/Numlib_gammapq.png) The lower and upper [incomplete gamma functions](https://en.wikipedia.org/wiki/Incomplete_Gamma_function) are defined by
36133613
36143614
36153615$`\operatorname{P}({s},{x}) = \frac{1}{\Gamma({s})} \int_0^{x}t^{s-1} e^{-t} dt`$
36163616
36173617
36183618$`\operatorname{Q}({s},{x}) = \frac{1}{\Gamma({s})} \int_{x}^{\infty}t^{s-1} e^{-t} dt = 1 - \operatorname{P}({s}, {x})`$
36193619
36203620``` pascal
36213621function gammap(s, x: ArbFloat): ArbFloat;
36223622function gammaq(s, x: ArbFloat): ArbFloat;
36233623```
36243624
36253625- The parameter `s` must be positive: *s* \> 0
36263626- The argument `x` must not be negative: *x* ≥ 0
36273627- The function results are in the interval \[0, 1\].
36283628
36293629> **Note:** The functions `gammap` and `gammaq` are not available in fpc v3.0.x and older.
36303630
36313631**Example**
36323632
36333633The following project prints out a table of `gammap` and `gammaq` values:
36343634
36353635``` pascal
36363636program IncompleteGamma_Table;
36373637
36383638uses
36393639 SysUtils, StrUtils, typ, spe;
36403640
36413641const
36423642 s = 1.25;
36433643
36443644 Wx = 7;
36453645 Wy = 20;
36463646 D = 6;
36473647
36483648var
36493649 i: Integer;
36503650 x: ArbFloat;
36513651 fs: TFormatSettings;
36523652begin
36533653 fs := DefaultFormatSettings;
36543654 fs.DecimalSeparator := '.';
36553655 WriteLn('x':Wx, 'GammaP('+FloatToStr(s,fs)+', x)':Wy+1, 'GammaQ('+FloatToStr(s,fs)+', x)':Wy+1);
36563656 WriteLn;
36573657 for i := 0 to 20 do begin
36583658 x := 0.5*i;
36593659 WriteLn(Format('%*.1f %*.*f %*.*f', [
36603660 Wx, x,
36613661 Wy, D, gammap(s, x),
36623662 Wy, D, gammaq(s, x)
36633663 ], fs));
36643664 end;
36653665end.
36663666```
36673667
36683668### Beta function
36693669
36703670![](/wiki-images/3/32/Numlib_beta.png) The [Beta function](https://en.wikipedia.org/wiki/Beta_function) is another basis of important functions used in statistics. It is defined as
36713671
36723672
36733673$`\operatorname{B}(a, b) = \frac{\Gamma(a+b)} = \int_0^1{t^{x-1} (1-t)^{y-1} dt}`$
36743674
36753675The NumLib routine for its calculation is
36763676
36773677``` pascal
36783678function beta(a, b: ArbFloat): ArbFloat;
36793679```
36803680
36813681- The arguments `a` and `b` must be positive: *`a`*` > 0` and *`b`*` > 1`.
36823682- The calculation is based on the logarithms of the gamma function and therefore does not suffer from the overflow issues of the gamma function.
36833683
36843684> **Note:** The function `beta` is not available in fpc v3.0.x or older.
36853685
36863686### Incomplete beta function
36873687
36883688![](/wiki-images/3/3a/Numlib_betai.png) The (regularized) [incomplete beta function](https://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function) is defined by
36893689
36903690
36913691$`\operatorname{I}_x(a,b) = \frac {1}{\operatorname{B}(ab)} \int_0^x{t^{x-1} (1-t)^{y-1} dt}`$
36923692
36933693``` pascal
36943694function betai(a, b, x: ArbFloat): ArbFloat;
36953695```
36963696
36973697- The arguments `a` and `b` must be positive: *`a`*` > 0` and *`b`*` > 1`.
36983698- `x` is only allowed to be in the range 0 ≤ *x* ≤ 1.
36993699- The function result is in the interval \[0, 1\].
37003700
37013701The inverse incomplete beta function determines the argument x for which the passed parameter is equal to *`I`<sub>`x`</sub>`(a, b)`*:
37023702
37033703``` pascal
37043704function invbetai(a, b, y: ArbFloat): ArbFloat;
37053705```
37063706
37073707- The arguments `a` and `b` must be positive: *`a`*` > 0` and *`b`*` > 1`.
37083708- `y` is only allowed to be in the range 0 ≤ *x* ≤ 1.
37093709- The function result is in the interval \[0, 1\].
37103710
37113711> **Note:** The functions `betai` and `invbetai` are not available in fpc v3.0.x and older.
37123712
37133713**Example**
37143714
37153715Calculate the incomplete beta function for some values of x, a, b, and apply the result to the inverse incomplete beta function to demonstrate the validity of the calculations.
37163716
37173717``` pascal
37183718program incomplete_beta_func;
37193719
37203720uses
37213721 SysUtils, typ, spe;
37223722
37233723 procedure TestBetai(a, b, x: ArbFloat);
37243724 var
37253725 xc, y: ArbFloat;
37263726 begin
37273727 y := betai(a, b, x);
37283728 xc := invbetai(a, b, y, macheps);
37293729 WriteLn(' x = ', x:0:6, ' ---> y = ', y:0:9, ' ---> x = ', xc:0:6);
37303730 end;
37313731
37323732begin
37333733 WriteLn('Incomplete beta function and its inverse');
37343734 WriteLn;
37353735 WriteLn(' y = betai(1, 3, x) x = invbetai(1, 3, y)');
37363736 TestBetaI(1, 3, 0.001);
37373737 TestBetaI(1, 3, 0.01);
37383738 TestBetaI(1, 3, 0.1);
37393739 TestBetaI(1, 3, 0.2);
37403740 TestBetaI(1, 3, 0.9);
37413741 TestBetaI(1, 3, 0.99);
37423742 TestBetaI(1, 3, 0.999);
37433743 WriteLn;
37443744
37453745 WriteLn(' y = betai(1, 1, x) x = invbetai(1, 1, y)');
37463746 TestBetaI(1, 1, 0.001);
37473747 TestBetaI(1, 1, 0.01);
37483748 TestBetaI(1, 1, 0.1);
37493749 TestBetaI(1, 1, 0.2);
37503750 TestBetaI(1, 1, 0.9);
37513751 TestBetaI(1, 1, 0.99);
37523752 TestBetaI(1, 1, 0.999);
37533753 WriteLn;
37543754
37553755 WriteLn(' y = betai(3, 1, x) x = invbetai(3, 1, y)');
37563756 TestBetaI(3, 1, 0.001);
37573757 TestBetaI(3, 1, 0.01);
37583758 TestBetaI(3, 1, 0.1);
37593759 TestBetaI(3, 1, 0.2);
37603760 TestBetaI(3, 1, 0.9);
37613761 TestBetaI(3, 1, 0.99);
37623762 TestBetaI(3, 1, 0.999);
37633763 WriteLn;
37643764end.
37653765```
37663766
37673767### Bessel functions
37683768
37693769The [Bessel functions](https://en.wikipedia.org/wiki/Bessel_function) are solutions of the Bessel differential equation:
37703770
37713771
37723772$`{x}^2 y'' + x y' + ({x}^2 - \alpha^2) {y} = 0`$
37733773
37743774The **Bessel functions of the first kind**, or **cylindrical harmonics**, *J<sub>α</sub>(x)*, are the solutions which are not singular at the origin, in contrast to the **Bessel functions of the second kind**, *Y<sub>α</sub>(x)*. *Y<sub>α</sub>(x)* is only defined for x \> 0.
37753775
37763776**Example**
37773777
37783778- Calculate the probability that random normal-distributed numbers are within 1, 2, and 3 standard deviations around the mean.
37793779- Calculate the maximum deviation of normal-distributed deviates (in units of standard deviations) from the mean at probabilities 0.90, 0.95, 0.98, 0.99 and 0.999.
37803780
37813781![](/wiki-images/0/0c/Numlib_bessel_1st.png) ![](/wiki-images/d/de/Numlib_bessel_2nd.png)
37823782
37833783The solutions for a purely imaginary argument are called **modified Bessel functions of the first kind**, *I<sub>α</sub>(x)*, and **modified Bessel function of the second kind**, *K<sub>α</sub>(x)*. *K<sub>α</sub>(x)* is only defined for x \> 0.
37843784
37853785![](/wiki-images/b/b6/Numlib_mod_bessel_1st.png) ![](/wiki-images/a/ae/Numlib_mod_bessel_2nd.png)
37863786
37873787NumLib implements only the solutions for the parameters α = 0 and α = 1:
37883788
37893789``` pascal
37903790function spebj0(x: ArbFloat): ArbFloat; // Bessel function of the first kind, J0 (alpha = 0)
37913791function spebj1(x: ArbFloat): ArbFloat; // Bessel function of the first kind, J1 (alpha = 1)
37923792
37933793function speby0(x: ArbFloat): ArbFloat; // Bessel function of the second kind, Y0 (alpha = 0)
37943794function speby1(x: ArbFloat): ArbFloat; // Bessel function of the second kind, Y1 (alpha = 1)
37953795
37963796function spebi0(x: ArbFloat): ArbFloat; // modified Bessel function of the first kind, I0 (alpha = 0)
37973797function spebi1(x: ArbFloat): ArbFloat; // modified Bessel function of the first kind, I1 (alpha = 1)
37983798
37993799function spebk0(x: ArbFloat): ArbFloat; // modified Bessel function of the second kind, K0 (alpha = 0)
38003800function spebk1(x: ArbFloat): ArbFloat; // modified Bessel function of the second kind, K1 (alpha = 1)
38013801```
38023802
38033803### Other functions
38043804
38053805The unit *spe* contains some other functions which will not be documented here because they are already available in th FPC's standard unit *math*.
38063806
3807-<thead>
3808-
3809-in <em>math</em></th>
3810-
3811-</thead>
3812-<tbody>
3813-
3814-</tbody>
3807+| Function | Equivalent function · in *math* | Description |
3808+|---|---|---|
3809+| **`function`**` speent(x: ArbFloat): LongInt;` | `floor(x)` | Entier function, calculates first integer smaller than or equal to `x` |
3810+| **`function`**` spemax(a, b: Arbfloat): ArbFloat;` | `max(a, b)` | Maximum of two floating point values |
3811+| **`function`**` spepow(a, b: ArbFloat): ArbFloat;` | `power(a, b)` | Calculates *ab* |
3812+| **`function`**` spesgn(x: ArbFloat): ArbInt;` | `sign(x)` | Returns the sign of `x` (`-1` for `x < 0`, `0` for `x = 0`, `+1` for `x > 0`) |
3813+| **`function`**` spears(x: ArbFloat): ArbFloat;` | `arcsin(x)` | Inverse function of `sin(x)` |
3814+| **`function`**` spearc(x: ArbFloat): ArbFloat;` | `arccos(x)` | Inverse function of `cos(x)` |
3815+| **`function`**` spesih(x: ArbFloat): ArbFloat;` | `sinh(x)` | Hyperbolic sine |
3816+| **`function`**` specoh(x: ArbFloat): ArbFloat;` | `cosh(x)` | Hyperbolic cosine |
3817+| **`function`**` spetah(x: ArbFloat): ArbFloat;` | `tanh(x)` | Hyperbolic tangent |
3818+| **`function`**` speash(x: ArbFloat): ArbFloat;` | `arcsinh(x)` | Inverse of the hyperbolic sine |
3819+| **`function`**` speach(x: ArbFloat): ArbFloat;` | `arccosh(x)` | Inverse of the hyperbolic cosine |
3820+| **`function`**` speath(x: ArbFloat): ArbFloat;` | `arctanh(x)` | Inverse of the hyperbolic tangent |
38153821
38163822---
38173823*From the Free Pascal wiki, page "NumLib Documentation", as it stood on 2024-02-14 after a change by Wp, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
38183824Category: Mathematics
38193825Category: Statistical algorithms

Back to history