Files
lazarus-ccr/applications/lazstats/source/units/matrixunit.pas
2021-01-03 23:50:51 +00:00

1257 lines
27 KiB
ObjectPascal

{ A general-purpose matrix library }
unit MatrixUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils,
Globals;
type
TDblVector = DblDyneVec; // array of double
TDblMatrix = DblDyneMat; // array of array of double
TDblCube = DblDyneCube; // array of array of array of double
TDblQuad = DblDyneQuad; // array of array of array of array of double
TIntVector = IntDyneVec; // array of integer
TIntMatrix = IntDyneMat; // array of array of integer
TIntCube = IntDyneCube; // array of array of array of integer
TIntQuad = IntDyneQuad; // array of array of array of array of integer
EMatrix = class(Exception);
// Vectors
operator + (A, B: TDblVector): TDblVector;
operator + (A: TDblVector; b: double): TDblVector;
operator - (A, B: TDblVector): TDblVector;
operator - (A: TDblVector; b: double): TDblVector;
operator * (A, B: TDblVector): Double;
operator * (A: TDblVector; b: Double): TDblVector;
operator * (a: Double; B: TDblVector): TDblVector;
function VecCreate(ALength: Integer; ADefault: Double = 0.0): TDblVector;
procedure VecCheck(A, B: TDblVector; out n: Integer);
function VecCopy(A: TDblVector): TDblVector;
function VecMultiply(A, B: TDblVector): TDblVector;
function VecOnes(n: Integer): TDblVector;
procedure VecSize(A: TDblVector; out n: Integer);
procedure VecMaxMin(const AData: TDblVector;
out AMax, AMin: Double);
function VecMean(const AData: TDblVector): Double;
procedure VecMeanStdDev(const AData: TDblVector;
out AMean, AStdDev: Double);
procedure VecMeanVarStdDev(const AData: TDblVector;
out AMean, AVariance, AStdDev: Double);
procedure VecMeanVarStdDevSS(const AData: TDblVector;
out AMean, AVariance, AStdDev, ASumOfSquares: Double);
procedure VecSumSS(const AData: TDblVector;
out Sum, SS: Double);
function VecHistogram(const AData: TDblVector; AMin, AMax: Double;
N: Integer): TDblVector;
function VecMedian(const AData: TDblVector): Double;
// Matrices
{ NOTE: Indices follow math convention:
- 1st index is the row index, i.e. runs vertically
- 2nd index is the col index, i.e. runs horizontally
All indices are 0-based. }
operator + (A, B: TDblMatrix): TDblMatrix;
operator - (A, B: TDblMatrix): TDblMatrix;
operator * (A, B: TDblMatrix): TDblMatrix;
operator * (A: TDblMatrix; v: TDblVector): TDblVector;
function MatCreate(n, m: Integer; ADefault: Double = 0.0): TDblMatrix;
function MatAppendColVector(A: TDblMatrix; v: TDblVector): TDblMatrix;
procedure MatCheck(A: TDblMatrix);
procedure MatCheckSquare(A: TDblMatrix; out n: Integer);
procedure MatColDelete(A: TDblMatrix; ACol: Integer);
procedure MatColMeanVarStdDev(A: TDblMatrix; out AMeans, AVariances, AStdDevs: TDblVector);
function MatColMeans(A: TDblMatrix): TDblVector;
function MatColVector(A: TDblMatrix; AColIndex: Integer): TDblVector;
function MatCopy(A: TDblMatrix): TDblMatrix;
function MatDeterminant(A: TDblMatrix): double;
function MatEqualSize(A, B: TDblMatrix): Boolean;
procedure MatExchangeElements(A: TDblMatrix; i1, j1, i2, j2: Integer);
procedure MatExchangeRows(A: TDblMatrix; i1, i2: Integer);
function MatInverse(A: TDblMatrix): TDblMatrix;
function MatIsSquare(A: TDblMatrix; out n: Integer): Boolean;
function MatNumCols(A: TDblMatrix): Integer;
function MatNumRows(A: TDblMatrix): Integer;
function MatRowMeans(A: TDblMatrix): TDblVector;
function MatRowVector(A: TDblMatrix; ARowIndex: Integer): TDblVector;
procedure MatSize(A: TDblMatrix; out n, m: Integer);
procedure MatSize(A: TIntMatrix; out n, m: Integer);
function MatTransposed(A: TDblMatrix): TDblMatrix;
function SubMatrix(A: TDblMatrix; i1,j1, i2,j2: Integer): TDblMatrix;
// Sorting
procedure Exchange(var a, b: Double); overload;
procedure Exchange(var a, b: Integer); overload;
procedure Exchange(var a, b: String); overload;
procedure SortOnX(X: TDblVector; Y: TDblVector = nil; Z: TDblVector = nil);
procedure SortOnX(X: TDblVector; Y: TDblMatrix);
procedure QuickSortOnX(X: TDblVector; Y: TDblVector = nil; Z: TDblVector = nil); // not 100% tested...
type
TLUSolver = class
private
FLU: TDblMatrix; // LU-decomposed matrix
FIndex: array of integer; // records permutations
d: Double; // odd or even row permuations
procedure LUDecompose;
public
constructor Create(A: TDblMatrix);
// function CreateL: TDblMatrix;
// function CreateU: TDblMatrix;
procedure Solve(b: TDblVector);
property LU: TDblMatrix read FLU;
end;
function IntVecCreate(n: Integer; ADefault: Integer = 0): TIntVector;
function IntMatCreate(n, m: Integer; ADefault: Integer = 0): TIntMatrix;
function CubeCreate(n1, n2, n3: Integer; ADefault: Double = 0.0): TDblCube;
function IntCubeCreate(n1, n2, n3: Integer; ADefault: Integer = 0): TIntCube;
function QuadCreate(n1, n2, n3, n4: Integer; ADefault: Double = 0.0): TDblQuad;
function IntQuadCreate(n1, n2, n3, n4: Integer; ADefault: Integer = 0): TIntQuad;
function CheckFactorCodes(AFactorName: String; const ACodes: TDblVector;
out AErrMsg: String): Boolean;
implementation
uses
Math;
operator + (A, B: TDblVector): TDblVector;
var
i, n: Integer;
begin
Result := nil;
VecCheck(A, B, n);
SetLength(Result, n);
for i := 0 to n-1 do
Result[i] := A[i] + B[i];
end;
operator + (A: TDblVector; b: double): TDblVector;
var
i, n: Integer;
begin
Result := nil;
n := Length(A);
Setlength(Result, n);
for i := 0 to n-1 do
Result[i] := A[i] + b;
end;
operator - (A, B: TDblVector): TDblVector;
var
i, n: Integer;
begin
Result := nil;
VecCheck(A, B, n);
SetLength(Result, n);
for i := 0 to n-1 do
Result[i] := A[i] - B[i];
end;
operator - (A: TDblVector; b: double): TDblVector;
var
i, n: Integer;
begin
Result := nil;
n := Length(A);
Setlength(Result, n);
for i := 0 to n-1 do
Result[i] := A[i] - b;
end;
// Vector dot product
operator * (A, B: TDblVector): Double;
var
i, n: Integer;
begin
VecCheck(A, B, n);
Result := 0;
for i := 0 to n-1 do
Result := Result + A[i] * B[i];
end;
// Multiplication of a vector by a scalar
operator * (A: TDblVector; b: Double): TDblVector;
var
i, n: Integer;
begin
Result := nil;
n := Length(A);
SetLength(Result, n);
for i := 0 to n-1 do
Result[i] := A[i] * b;
end;
operator * (a: Double; B: TDblVector): TDblVector;
var
i, n: Integer;
begin
Result := nil;
n := Length(B);
SetLength(Result, n);
for i := 0 to n-1 do
Result[i] := a * B[i];
end;
function VecCreate(ALength: Integer; ADefault: Double = 0.0): TDblVector;
var
i: Integer;
begin
Result := nil;
SetLength(Result, ALength);
for i := 0 to High(Result) do
Result[i] := ADefault;
end;
procedure VecCheck(A, B: TDblVector; out n: Integer);
var
na, nb: Integer;
begin
na := Length(A);
nb := Length(B);
if na <> nb then
raise EMatrix.Create('Dimension error.')
else
n := na;
end;
function VecCopy(A: TDblVector): TDblVector;
var
i: Integer;
begin
Result := nil;
SetLength(Result, Length(A));
for i := 0 to High(A) do Result[i] := A[i];
end;
function VecMultiply(A, B: TDblVector): TDblVector;
var
i, n: Integer;
begin
Result := nil;
VecCheck(A, B, n);
SetLength(Result, n);
for i := 0 to n-1 do
Result[i] := A[i] * B[i];
end;
function VecOnes(n: Integer): TDblVector;
var
i: Integer;
begin
Result := nil;
SetLength(Result, n);
for i := 0 to n-1 do Result[i] := 1;
end;
procedure VecSize(A: TDblVector; out n: Integer);
begin
n := Length(A);
end;
{===============================================================================
Statistical vector operations
===============================================================================}
procedure VecMaxMin(const AData: TDblVector; out AMax, AMin: Double);
var
i: Integer;
begin
AMin := Infinity;
AMax := -Infinity;
for i := Low(AData) to High(AData) do
begin
if AData[i] < AMin then AMin := AData[i];
if AData[i] > AMax then AMax := AData[i];
end;
end;
function VecMean(const AData: TDblVector): Double;
var
i, n: Integer;
begin
Result := 0;
n := Length(AData);
if n > 0 then
begin
for i := 0 to n-1 do
Result := Result + AData[i];
Result := Result / n;
end else
Result := NaN;
end;
procedure VecMeanStdDev(const AData: TDblVector; out AMean, AStdDev: Double);
var
variance: Double;
begin
VecMeanVarStdDev(AData, AMean, variance, AStdDev);
end;
procedure VecMeanVarStdDev(const AData: TDblVector;
out AMean, AVariance, AStdDev: Double);
var
sum, ss: Double;
n: Integer;
begin
AMean := NaN;
AVariance := NaN;
AStdDev := NaN;
n := Length(AData);
if n = 0 then
exit;
VecSumSS(AData, sum, ss);
AMean := sum / n;
if n = 1 then
exit;
AVariance := (ss - sqr(AMean) * n) / (n - 1);
AStdDev := sqrt(AVariance);
end;
procedure VecMeanVarStdDevSS(const AData: TDblVector;
out AMean, AVariance, AStdDev, ASumOfSquares: Double);
var
sum: Double;
n: Integer;
begin
AMean := NaN;
AVariance := NaN;
AStdDev := NaN;
n := Length(AData);
if n = 0 then
exit;
VecSumSS(AData, sum, ASumOfSquares);
AMean := sum / n;
if n = 1 then
exit;
AVariance := (ASumOfSquares - sqr(sum) / n) / (n - 1);
AStdDev := sqrt(AVariance);
end;
procedure VecSumSS(const AData: TDblVector; out Sum, SS: Double);
var
i: Integer;
begin
Sum := 0;
SS := 0;
for i := Low(AData) to High(AData) do
begin
Sum := Sum + AData[i];
SS := SS + sqr(AData[i]);
end;
end;
function VecHistogram(const AData: TDblVector; AMin, AMax: Double;
N: Integer): TDblVector;
var
i, j: Integer;
factor: Double;
begin
Result := nil;
SetLength(Result, N);
for j := 0 to N-1 do Result[j] := 0;
factor := N / (AMax - AMin);
for i := 0 to High(AData) do
begin
j := trunc((AData[i] - AMin) * factor);
if j <= 0 then j := 0;
if j >= N then j := N-1;
Result[j] := Result[j] + 1;
end;
end;
function VecMedian(const AData: TDblVector): Double;
var
N, midPt: integer;
begin
SortOnX(AData);
N := Length(AData);
midPt := N div 2;
if odd(N) then
Result := AData[midPt] // odd no. of values
else
Result := (AData[midPt-1] + AData[midPt]) / 2; // even no. of values
end;
{===============================================================================
Matrix operators
===============================================================================}
operator + (A, B: TDblMatrix): TDblMatrix;
var
n, m, i, j: Integer;
begin
n := Length(A);
m := Length(A[0]);
if (n <> Length(B)) or (m <> Length(B[0])) then
raise EMatrix.Create('Matrix subtraction: dimension error');
Result := nil;
SetLength(Result, n,m);
for i := 0 to n-1 do
for j := 0 to m-1 do
Result[i, j] := A[i, j] + B[i, j];
end;
operator - (A, B: TDblMatrix): TDblMatrix;
var
n, m, i, j: Integer;
begin
n := Length(A);
m := Length(A[0]);
if (n <> Length(B)) or (m <> Length(B[0])) then
raise EMatrix.Create('Matrix subtraction: dimension error');
Result := nil;
SetLength(Result, n,m);
for i := 0 to n-1 do
for j := 0 to m-1 do
Result[i, j] := A[i, j] - B[i, j];
end;
{ Product of two matrices }
operator * (A, B: TDblMatrix): TDblMatrix;
var
na, ma, nb, mb, i, j, k: Integer;
sum: Double;
begin
MatSize(A, na, ma);
MatSize(B, nb, mb);
if ma <> nb then
raise EMatrix.Create('Matrix product: dimension error');
Result := nil;
SetLength(Result, na,mb);
for i := 0 to na-1 do
for j := 0 to mb-1 do begin
sum := 0;
for k := 0 to mA - 1 do
sum := sum + A[i, k] * B[k, j];
Result[i, j] := sum;
end;
end;
{ Product of an n x m matrix with an m vector }
operator * (A: TDblMatrix; v: TDblVector): TDblVector;
var
na, ma, nv, i, j: Integer;
sum: Double;
begin
MatSize(A, na, ma);
VecSize(v, nv);
if ma <> nv then
raise EMatrix.Create('Dimension error.');
Result := nil;
SetLength(Result, na);
for i := 0 to na-1 do
begin
sum := 0;
for j := 0 to ma-1 do
sum := sum + A[i, j] * v[j];
Result[i] := sum;
end;
end;
function MatCreate(n, m: Integer; ADefault: Double = 0.0): TDblMatrix;
var
i, j: Integer;
begin
Result := nil;
SetLength(Result, n,m);
for i := 0 to n-1 do
for j := 0 to m-1 do
Result[i, j] := ADefault;
end;
{ Adds the elements of the vector v to the rows of the matrix A, i.e. the number
of columns increases by 1 }
function MatAppendColVector(A: TDblMatrix; v: TDblVector): TDblMatrix;
var
i, j, n, m, nv: Integer;
begin
MatSize(A, n, m);
nv := Length(v);
if n <> nv then
raise EMatrix.Create('Dimension error.');
Result := nil;
SetLength(Result, n, m+1);
for i := 0 to n-1 do
begin
for j := 0 to m-1 do
Result[i, j] := A[i, j];
Result[i, m] := v[i];
end;
end;
{ Checks whether the matrix A has been initialized (i.e. by calling SetLength).
Raises an Exception otherwise. }
procedure MatCheck(A: TDblMatrix);
begin
if (A = nil) and (Length(A) = 0) then
raise EMatrix.Create('Matrix not created.');
end;
procedure MatCheckSquare(A: TDblMatrix; out n: Integer);
begin
if not MatIsSquare(A, n) then
raise EMatrix.Create('Matrix is not square.');
end;
procedure MatColDelete(A: TDblMatrix; ACol: Integer);
var
n, m, i, j: Integer;
begin
MatSize(A, n,m);
if (ACol < 0) or (ACol >= m) then
raise EMatrix.Create('MatColDelete: illegal column index.');
for i := 0 to n - 1 do begin
for j := 0 to m - 2 do
if j >= ACol then
A[i, j] := A[i, j+1];
SetLength(A[i], m-1);
end;
end;
procedure MatColMeanVarStdDev(A: TDblMatrix; out AMeans, AVariances, AStdDevs: TDblVector);
var
n, m, i, j: Integer;
s, ss: Double;
begin
AMeans := nil;
AVariances := nil;
AStdDevs := nil;
MatSize(A, n, m);
SetLength(AMeans, m);
SetLength(AVariances, m);
SetLength(AStdDevs, m);
for j := 0 to m-1 do
begin
s := 0;
ss := 0;
for i := 0 to n-1 do
begin
s := s + A[i, j];
ss := ss + sqr(A[i, j]);
end;
AMeans[j] := s / n;
AVariances[j] := (ss - sqr(AMeans[j]) * n) / (n - 1);
AStdDevs[j] := sqrt(AVariances[j]);
end;
end;
function MatColMeans(A: TDblMatrix): TDblVector;
var
i, j, n, m: Integer;
sum: Double;
begin
Result := nil;
MatSize(A, n, m);
SetLength(Result, m);
for j := 0 to m-1 do
begin
sum := 0;
for i := 0 to n-1 do
sum := sum + A[i, j];
Result[j] := sum / n;
end;
end;
function MatColVector(A: TDblMatrix; AColIndex: Integer): TDblVector;
var
i, n, m: Integer;
begin
Result := nil;
MatSize(A, n,m);
SetLength(Result, n);
for i := 0 to n-1 do
Result[i] := A[i, AColIndex];
end;
function MatCopy(A: TDblMatrix): TDblMatrix;
var
n, m, i, j: Integer;
begin
Result := nil;
MatSize(A, n,m);
SetLength(Result, n, m);
for i := 0 to n-1 do
for j := 0 to m-1 do
Result[i, j] := A[i, j];
end;
function MatDeterminant(A: TDblMatrix): double;
var
i, n: integer;
S: TLUSolver;
d: Double;
begin
Result := NaN;
MatCheck(A);
MatCheckSquare(A, n);
S := TLUSolver.Create(A);
try
d := S.d;
for i := 0 to n-1 do
d := d * S.LU[i, i];
Result := d;
finally
S.Free;
end;
end;
function MatEqualSize(A, B: TDblMatrix): Boolean;
var
na, ma, nb, mb: Integer;
begin
MatSize(A, na, ma);
MatSize(B, nb, mb);
Result := (na = ma) and (nb = mb);
end;
{ Exchanges the elements (i1,j1) and (i2,j2) of the matrix A }
procedure MatExchangeElements(A: TDblMatrix; i1,j1, i2,j2: Integer);
var
tmp: Double;
begin
tmp := A[i1, j1];
A[i1, j1] := A[i2, j2];
A[i2, j2] := tmp;
end;
{ Exchanges the rows i1 and i2 of matrix A }
procedure MatExchangeRows(A: TDblMatrix; i1, i2: Integer);
var
j, n, m: integer;
begin
MatSize(A, n,m);
for j := 0 to m-1 do
MatExchangeElements(A, i1,j, i2,j);
end;
function MatInverse(A: TDblMatrix): TDblMatrix;
var
i, j, n: integer;
S: TLUSolver;
v: TDblVector = nil;
begin
Result := nil;
MatCheck(A);
MatCheckSquare(A, n);
SetLength(Result, n,n);
SetLength(v, n);
S := TLUSolver.Create(A);
try
for j := 0 to n-1 do
begin
for i := 0 to n-1 do
v[i] := 0.0;
v[j] := 1.0;
S.Solve(v);
for i := 0 to n-1 do Result[i, j] := v[i];
end;
finally
S.Free;
end;
end;
function MatIsSquare(A: TDblMatrix; out n: Integer): Boolean;
var
m: Integer;
begin
MatSize(A, n, m);
Result := (n = m);
end;
function MatNumCols(A: TDblMatrix): Integer;
var
n: Integer;
begin
MatSize(A, n, Result);
end;
function MatNumRows(A: TDblMatrix): Integer;
var
m: Integer;
begin
MatSize(A, Result, m);
end;
function MatRowMeans(A: TDblMatrix): TDblVector;
var
i, j, n, m: Integer;
sum: Double;
begin
Result := nil;
MatSize(A, n,m);
SetLength(Result, n);
for i := 0 to n-1 do
begin
sum := 0;
for j := 0 to m-1 do
sum := sum + A[i, j];
Result[i] := sum / m;
end;
end;
function MatRowVector(A: TDblMatrix; ARowIndex: Integer): TDblVector;
var
j, n, m: Integer;
begin
Result := nil;
MatSize(A, n,m);
SetLength(Result, m);
for j := 0 to m-1 do
Result[j] := A[ARowIndex, j];
end;
procedure MatSize(A: TDblMatrix; out n, m: Integer);
begin
n := Length(A);
m := Length(A[0]);
end;
procedure MatSize(A: TIntMatrix; out n, m: Integer);
begin
n := Length(A);
m := Length(A[0]);
end;
function MatTransposed(A: TDblMatrix): TDblMatrix;
var
n, m, i, j: Integer;
begin
Result := nil;
MatCheck(A);
MatSize(A, n, m);
SetLength(Result, m, n);
for i := 0 to n-1 do
for j := 0 to m-1 do
Result[j, i] := A[i, j];
end;
function SubMatrix(A: TDblMatrix; i1,j1, i2,j2: Integer): TDblMatrix;
var
i, j, n, m: Integer;
begin
Result := nil;
MatSize(A, n,m);
i1 := EnsureRange(i1, 0, n);
i2 := EnsureRange(i2, 0, n);
j1 := EnsureRange(j1, 0, m);
j2 := EnsureRange(j2, 0, m);
if i1 > i2 then Exchange(i1, i2);
if j1 > j2 then Exchange(j1, j2);
SetLength(Result, i2-i1+1, j2-j1+1);
for i := i1 to i2 do
for j := j1 to j2 do
Result[i-i1, j-j1] := A[i, j];
end;
{===============================================================================
TLUSolver
--------------------------------------------------------------------------------
Solves the linear system of equations, A x = b by means of LU decomposition.
Matrix A is passed when the solver is created and it is decomposed in
upper and lower triangular matrices, A = L * U where L has non-zero elements
only below, and U only above the diagonal.
The equation system is is solved by means of the insertion method (Solve).
Ref:
Press et al, Numerical Recipies in Pascal
===============================================================================}
constructor TLUSolver.Create(A: TDblMatrix);
var
n: Integer;
begin
MatCheckSquare(A, n);
FLU := MatCopy(A);
LUDecompose;
end;
(*
{ A has been decomposed into the product of two matrices, L*U. L and U are
saved in a minimal way in the single matrix FLU.
CreateL creates a new matrix and copies the elements of L to the correct
position in the result. The product of the two matrices created by CreateL
and CreateU, is A. }
function TLUSolver.CreateL: TDblMatrix;
var
n, m, i, j: integer;
begin
MatrixSize(FLU, n,m);
SetLength(Result, n, m);
for i := 0 to n-1 do begin
for j := 0 to i do begin
if (i > j) then
Result[i, j] := FLU[i,j]
else
if (i = j) then
Result[i, j] := 1.0;
end;
end;
for i := 0 to n - 1 do MatExchangeRows(Result, i, FIndex[i]);
end;
{ A has been decomposed into the product of two matrices, L*U. L and U are
saved in a minimal way in the single matrix FLU.
CreateU creates a new matrix and copies the elements of U to the correct
position in the result. The product of the two matrices created by CreateL
and CreateU, is A. }
function TLUSolver.CreateU: TDblMatrix;
var
n, m, i, j: integer;
begin
MatSize(FLU, n,m);
SetLength(Result, n, m);
for i := 0 to n-1 do
for j := i to m-1 do
if (i <= j) then Result[i,j] := FLU[i,j];
end;
*)
{ Executes the LU decompositon. Main procedure of the solver. }
procedure TLUSolver.LUDecompose;
const
tiny = 1.0e-20;
var
k, j, imax, i: integer;
sum, dum, big: float;
vv: TDblVector = nil;
n : integer;
begin
n := MatNumRows(FLU);
SetLength(vv, n);
SetLength(FIndex, n);
d := 1.0;
for i := 0 to n-1 do // Scaling information
begin
big := 0.0;
for j := 0 to n-1 do
begin
dum := abs(FLU[i, j]);
if dum > big then big := dum;
end;
if big = 0.0 then
raise EMatrix.Create('Matrix is singular.');
vv[i] := 1.0 / big;
end;
for j := 0 to n-1 do begin
for i := 0 to j-1 do begin
sum := FLU[i, j];
for k := 0 to i-1 do
sum := sum - FLU[i, k] * FLU[k, j];
FLU[i, j] := sum;
end;
big := 0.0;
for i := j to n-1 do begin
sum := FLU[i,j];
for k := 0 to j-1 do
sum := sum - FLU[i, k] * FLU[k, j];
FLU[i, j] := sum;
dum := vv[i] * abs(sum);
if dum > big then begin
big := dum;
imax := i;
end;
end;
if j <> imax then begin
for k := 0 to n-1 do begin
dum := FLU[imax, k];
FLU[imax, k] := FLU[j, k];
FLU[j, k] := dum;
end;
d := -d;
vv[imax] := vv[j];
end;
FIndex[j] := imax;
if FLU[j, j]=0.0 then FLU[j, j] := tiny;
if j <> n-1 then
begin
dum := 1.0 / FLU[j, j];
for i := succ(j) to n-1 do
FLU[i, j] := FLU[i, j] * dum;
end;
end;
end;
{ Solves the equation system A*x = b for x and returns the solution in b.
A already had been LU-decomposed when the solver was created. This means
that the method can be reused again and again for different b vectors.
NOTE: the input b is overwritten by the calculation! }
procedure TLUSolver.Solve(b: TDblVector);
var
nB, n, m: integer;
i, ii, ip, j: integer;
sum: Double;
begin
nB := Length(b);
MatSize(FLU, n,m);
if nB <> n then
raise EMatrix.Create('TLUSolver: Dimension error.');
ii := -1;
for i := 0 to n-1 do
begin
ip := FIndex[i];
sum := b[ip];
b[ip] := b[i];
if ii <> -1 then
for j := ii to pred(i) do
sum := sum - FLU[i, j] * b[j]
else
if (sum <> 0) then
ii := i;
b[i] := sum;
end;
for i := n-1 downto 0 do
begin
sum := b[i];
for j := succ(i) to n-1 do
sum := sum - FLU[i, j] * b[j];
b[i] := sum / FLU[i,i];
end;
end;
{===============================================================================
Sorting
===============================================================================}
procedure Exchange(var a, b: Double);
var
tmp: Double;
begin
tmp := a;
a := b;
b := tmp;
end;
procedure Exchange(var a, b: Integer);
var
tmp: Integer;
begin
tmp := a;
a := b;
b := tmp;
end;
procedure Exchange(var a, b: String);
var
tmp: String;
begin
tmp := a;
a := b;
b := tmp;
end;
procedure SortOnX(X: DblDyneVec; Y: DblDyneVec = nil; Z: DblDyneVec = nil);
var
i, j, N: Integer;
begin
N := Length(X);
if (Y <> nil) and (N <> Length(Y)) then
raise Exception.Create('[SortOnX] Arrays must have the same length.');
if (Z <> nil) and (N <> Length(Z)) then
raise Exception.Create('[SortOnX] Arrays must have the same length.');
for i := 0 to N - 2 do
begin
for j := i + 1 to N - 1 do
begin
if X[i] > X[j] then //swap
begin
Exchange(X[i], X[j]);
if Y <> nil then
Exchange(Y[i], Y[j]);
if Z <> nil then
Exchange(Z[i], Z[j]);
end;
end;
end;
end;
// NOTE: The matrix Y is transposed relative to the typical usage in LazStats
procedure SortOnX(X: DblDyneVec; Y: DblDyneMat);
var
i, j, k, N, Ny: Integer;
begin
N := Length(X);
if N <> Length(Y[0]) then
raise Exception.Create('[SortOnX] Arrays X and Y (2nd index) must have the same length');
Ny := Length(Y);
for i := 0 to N-2 do
begin
for j := i+1 to N-1 do
if X[i] > X[j] then
begin
Exchange(X[i], X[j]);
for k := 0 to Ny-1 do
Exchange(Y[k, i], Y[k, j]);
end;
end;
end;
procedure QuickSortOnX(X: DblDyneVec; Y: DblDyneVec = nil; Z: DblDyneVec = nil);
procedure DoQuickSort(L, R: Integer);
var
I,J: Integer;
P: Integer;
begin
repeat
I := L;
J := R;
P := (L+R) div 2;
repeat
while CompareValue(X[P], X[I]) > 0 do inc(I);
while CompareValue(X[P], X[J]) < 0 do dec(J);
if I <= J then begin
if I <> J then begin
Exchange(X[I], X[J]);
if Y <> nil then
Exchange(Y[I], Y[J]);
if Z <> nil then
Exchange(Z[I], Z[J]);
end;
if P = I then
P := J
else if P = J then
P := I;
inc(I);
dec(J);
end;
until I > J;
if L < J then
DoQuickSort(L, J);
L := I;
until I >= R;
end;
begin
DoQuickSort(0, High(X));
end;
{ Checks whether the codes provided are integers and whether the codes are
consecutive, i.e. without gaps. }
function CheckFactorCodes(AFactorName: String; const ACodes: TDblVector;
out AErrMsg: String): Boolean;
const
EPS = 1E-9;
NonIntegerError = 'Factor "%s" contains non-integer values.';
NonConsecutiveError = 'Factor "%s" does not contain consecutive codes.';
var
values: DblDyneVec;
i, prev, curr: Integer;
begin
Result := false;
values := VecCopy(ACodes);
SortOnX(values);
if abs(values[0] - trunc(values[0])) > EPS then
begin
AErrMsg := Format(NonIntegerError, [AFactorName]);
exit;
end;
prev := round(values[0]);
for i := 1 to High(values) do
begin
if abs(values[i] - trunc(values[i])) > EPS then
begin
AErrMsg := Format(NonIntegerError, [AFactorName]);
exit;
end;
curr := round(values[i]);
if curr - prev > 1 then
begin
AErrMsg := Format(NonConsecutiveError, [AFactorName]);
exit;
end;
prev := curr;
end;
Result := true;
AErrMsg := '';
end;
function IntVecCreate(n: Integer; ADefault: Integer = 0): TIntVector;
var
i: Integer;
begin
Result := nil;
SetLength(Result, n);
for i := 0 to High(Result) do
Result[i] := ADefault;
end;
function IntMatCreate(n, m: Integer; ADefault: Integer = 0): TIntMatrix;
var
i, j: Integer;
begin
Result := nil;
SetLength(Result, n,m);
for i := 0 to n-1 do
for j := 0 to m-1 do
Result[i, j] := ADefault;
end;
function CubeCreate(n1, n2, n3: Integer; ADefault: Double = 0.0): TDblCube;
var
i,j,k: Integer;
begin
Result := nil;
SetLength(Result, n1, n2, n3);
for i := 0 to n1 - 1 do
for j := 0 to n2 - 1 do
for k := 0 to n3 - 1 do
Result[i, j, k] := ADefault;
end;
function IntCubeCreate(n1, n2, n3: Integer; ADefault: Integer = 0): TIntCube;
var
i, j, k: Integer;
begin
Result := nil;
SetLength(Result, n1, n2, n3);
for i := 0 to n1 - 1 do
for j := 0 to n2 - 1 do
for k := 0 to n3 - 1 do
Result[i, j, k] := ADefault;
end;
function QuadCreate(n1, n2, n3, n4: Integer; ADefault: Double = 0.0): TDblQuad;
var
i, j, k, l: Integer;
begin
Result := nil;
SetLength(Result, n1, n2, n3, n4);
for i := 0 to n1 - 1 do
for j := 0 to n2 - 1 do
for k := 0 to n3 - 1 do
for l := 0 to n4 - 1 do
Result[i, j, k, l] := ADefault;
end;
function IntQuadCreate(n1, n2, n3, n4: Integer; ADefault: Integer = 0): TIntQuad;
var
i, j, k, l: Integer;
begin
Result := nil;
SetLength(Result, n1, n2, n3, 4);
for i := 0 to n1 - 1 do
for j := 0 to n2 - 1 do
for k := 0 to n3 - 1 do
for l := 0 to n4 - 1 do
Result[i, j, k, l] := ADefault;
end;
end.