```Contributor: MARK HORRIDGE

{
From: horridge@ITS-MENZ.cc.monash.edu.au (Mark Horridge)
Sparse Solver for Borland or Turbo Pascal
*****************************************

Several people asked for a copy of my Pascal Unit to solve sparse linear
systems.  Here it is.

Unit Solspar is designed to solve sparse linear systems.
These are equation systems of the form:

A X  = R

where A is a matrix of coefficients size NxN,
R is a vector of constants size N,
and   X is a vector of variables.

The code has been in use for a number of years, being slightly adapted for
each new purpose.  This is the first attempt at a portable general purpose
version.

I expect that the code contains a few errors which are my fault - I would
be glad to hear of these.  Please check that you have not tried to solve a
singular matrix by mistake !

Below find 3 text files, separated by rows of asterisks:

Solspar.pas:
See notes at the top for instructions.
Test1.pas:
Simple example of using routines with medium size matrix
Test2.pas:
Example of using routines with N = 4, showing some possible errors
{************************************************************************}
{\$N+,E+}
unit SolSpar;
{ Unit Solspar is designed to solve sparse linear systems.
These are equation systems of the form:

A X  = R

where A is a matrix of coefficients size NxN,
R is a vector of constants size N,
and   X is a vector of variables.
(remember, in matrix A, 'rows' are equations and 'cols' are variables)

Solspar computes the vector X which satisfies the above equation.
It is designed to save time and space where A is fairly large but contains
few non-zero elements.

It is designed to work in either the real, protected-mode DOS, or Windows
versions of Borland/Turbo Pascal.

Limitations of Solspar:
It only allows one Right Hand Side (RHS) column.
It only allows one sparse structure at a time.
Not to be used with Mark and Release !
Points to watch:
Normally, the A matrix grows in size (becomes less sparse)
during the Solve phase. The biggest matrix you can store
will be to big to solve.
}
interface

{Unit consists of the following 5 Boolean Functions and 3 procedures.
Each function returns True if operation was successfully completed -
otherwise False.  If False is returned, call the procedure
GetErrorMessage to find the reason.  Finally, call the procedure
ReleaseStruc to return memory to the heap.}

function InitStruc(NumEq : Word) : Boolean;
{creates and initializes sparse matrix structure - call this first.
NumEq is number of equations/variables.}

function AddElement(ThisEqu, ThisVar : Word; ThisVal : Double) : Boolean;
{add an element to sparse matrix for equation ThisEqu and variable ThisVar;
if such an entry already exists, ThisVal will be added to existing value
You can add elements in any order, but the routine will be a bit more
efficient if you add variables (columns) to any particular equation in
ascending order, and then set the RHS}

function SetRHS(ThisEqu : Word; ThisVal : Double) : Boolean;
{Set RHS for equation ThisEqu; if RHS has already been set, ThisVal will be

function Solve1 : Boolean;
{calculate solutions; sparse matrix is destroyed}

function GetAnswer(ThisVar : Word; var ThisVal : Double) : Boolean;
{read solution for variable ThisVar - probably called for each variable in
turn}

procedure ReleaseStruc;
{releases remaining memory used by sparse matrix structure - call this
last}

procedure GetErrorMsg(var S : String; var N1, N2, N3 : Word);
{N1: error number; S: Error Description; N1, N2 : other possibly useful
numbers}

procedure showmat;      { displays small sparse matrix - not for Windows}

var
SparMemUsed    : LongInt; {no of bytes of heap used by routines}

(*
procedure GetErrorMsg is used to obtain information about any problem:

procedure GetErrorMsg(var S : String; var N1, N2, N3 : Word);
{N1: error number; S: Error Description; N1, N2 : other possibly useful
numbers}

List of possible error messages is as follows:
(remember, 'rows' are equations and 'cols' are variables)

N1        S                          N2        N3        Comment

0                                 0          0      No error: default
1  Empty Row                      Row        0
2  Row without Variables          Row        0      Equation with RHS only
3  Empty Col                      Col        0
4  Two Singles                    Row      PivotCol Two variables (the 2nd
is pivotcol) are each
mentioned only in the
same equation
5  No RHS                         Row      LastCol
6  Numerically Singular         PivotRow PivotStep  Columns (or rows)
are not linearly
independent.
7  Out of Space                   0          1      called from InitStruc
7  Out of Space                   0          2      called from InitStruc
7  Out of Space                   0          3      called from AddElement
7  Out of Space                   PivotStep  4      called from Solve1
8  Cols out of Order              Row        Col
25  Initialize without releasing   0          0
26  Too many equations             NumEq      MaxEq
27  Too few equations              NumEq      0
30  Row < 1                        ThisEqu  ThisVar  called from AddElement
31  Col < 1                        ThisEqu  ThisVar  called from AddElement
32  Row > Neq                      ThisEqu    Neq    called from AddElement
33  Col > Neq                      ThisVar    Neq    called from AddElement
41  VarNo < 1                      ThisVar    0      called from AddElement
42  VarNo > Neq                    ThisVar    Neq    called from AddElement
*)

{  Hopefully, this is as far as you need to read in order to use the unit.
There are a few comments in the code which you can look at for interest.
Direct Methods for Sparse Matrices
Duff, Erisman and Reid         Cambridge University Press  }

implementation

const
DBug           = False;
{due to Borland's "constant folding" , Debug statements cost nothing with
Dbug false}
Msg            = False;
ParaSize       = 16;  {for paragraph alignment}
MaxSize        = 65520; {largest variable size allowed by Turbo Pascal}
MaxUsable      = MaxSize-ParaSize; {allows for paragraph alignment of data}
MaxEq          = (MaxUsable div SizeOf(Double))-1; {-1 for 0-based arrays}
{based on above, MaxEq = 8187}
Uvalue         = 0.1; {number 0Nil, returns a block at OrigP of (Size+ParaSize) bytes to the
system heap.  Failure will cause a runtime error.  If this occurs, it is
most likely due to an error in this unit!  }
begin
if Msg then WriteLn('Entering FreeMemParaAlign');
if (OrigP = nil) then Exit; {already freed or never allocated, we presume}
FreeMem(OrigP, Size+ParaSize); {failure will cause runtime error}
Dec(SparMemUsed, Size+ParaSize);
OrigP := nil;       {to indicate now freed}
end;                  {FreeMemParaAlign}

function FrePrime : Boolean;
type                  {used only for typecasting}
PtrRec         = record OfsWord, SegWord : Word; end;
var
NewBlock, OrigBlock : PBlock;
NewRec         : PHeapRec;
Ofset, Count   : Word;
P              : PElmRec;
begin
if Msg then WriteLn('Entering FrePrime');
FrePrime := False;

{add new node to list of blocks}
OldHeapError := HeapError; HeapError := @HeapFunc;
NewRec := nil;
New(NewRec); Inc(SparMemUsed, SizeOf(HeapRec));
HeapError := OldHeapError;
if (NewRec = nil) then Exit;
NewRec^.NextRec := BlockList;
NewRec^.BlockPtr := nil;
BlockList := NewRec;

{get new block}
if not GetMemParaAlign(Pointer(NewBlock), Pointer(OrigBlock),
SizeOf(TBlock)) then Exit;
NewRec^.BlockPtr := OrigBlock;
{fill new block with linked nodes}
P := PElmRec(NewBlock);
Ofset := PtrRec(P).OfsWord;
for Count := 1 to ElmsPerBlock do begin
Inc(Ofset, ElmSize);
PtrRec(P).OfsWord := Ofset;
NewBlock^[Count].Next := P;
end;
Inc(FreeCount, ElmsPerBlock);

NewBlock^[ElmsPerBlock].Next := FreePtr; {point end of new block to
FreePtr}
FreePtr := PElmRec(NewBlock); {point FreePtr at start of new block}

FrePrime := True;
end;                  {FrePrime}

procedure SetErrorMsg(S : String; N1, N2, N3 : Word);
begin
Reason := S; ErrNo1 := N1; ErrNo2 := N2; ErrNo3 := N3;
end;                  {SetErrorMsg}

procedure GetErrorMsg(var S : String; var N1, N2, N3 : Word);
begin
S := Reason; N1 := ErrNo1; N2 := ErrNo2; N3 := ErrNo3;
end;                  {GetErrorMsg}

procedure FillStruct(var Dest; Count : Word; var Filler; FillerSize : Word);
{-Fill memory starting at Dest with Count instances of Filler}
inline(
\$58/\$5B/\$5A/\$59/\$5F/\$07/\$E3/\$11/\$FC/\$1E/\$8E/\$DA/
\$89/\$CA/\$89/\$DE/\$89/\$C1/\$F2/\$A4/\$89/\$D1/\$E2/\$F4/\$1F);

function InitStruc(NumEq : Word) : Boolean;
var
Col            : Word;
label Fail;
begin
if Msg then WriteLn('Entering InitStruc');
InitStruc := False;
SetErrorMsg('', 0, 0, 0);
SparMemUsed := 0;

OrigColCount := nil;
OrigFirstElm := nil;
OrigLastElm := nil;
OrigNextActiveRow := nil;
OrigPivRow := nil;
OrigRowCount := nil;

if Initialized then begin
SetErrorMsg('Initialize without releasing ', 25, 0, 0);
Exit;
end;
Initialized := True;
if (NumEq > MaxEq) then begin
SetErrorMsg('Too many equations ', 26, NumEq, MaxEq);
Exit;
end;
if (NumEq < 1) then begin
SetErrorMsg('Too few equations ', 27, NumEq, 0);
Exit;
end;

Neq := NumEq;
if not GetMemParaAlign(Pointer(FirstElm), Pointer(OrigFirstElm),
(1+NumEq)*SizeOf(PElmRec)) then begin
SetErrorMsg('Out of Space', 7, 0, 1);
Exit;
end;
if not GetMemParaAlign(Pointer(LastElm), Pointer(OrigLastElm),
(1+NumEq)*SizeOf(PElmRec)) then begin
SetErrorMsg('Out of Space', 7, 0, 2);
Exit;
end;

BlockList := nil;
FreePtr := nil;
Col := 0;
FreeCount := 0;
FillStruct(FirstElm^, 1+Neq, FreePtr, SizeOf(FreePtr));
FillStruct(LastElm^, 1+Neq, FreePtr, SizeOf(FreePtr));
InitStruc := True;
end;                  {InitStruc}

procedure ReleaseStruc;
var
OrigBlock      : PBlock;
NextPtr        : PHeapRec;
begin
if Msg then WriteLn('Entering ReleaseStruc');
Initialized := False;

{some of these may have been released already}
FreeMemParaAlign(Pointer(OrigColCount), (1+Neq)*SizeOf(Integer));
FreeMemParaAlign(Pointer(OrigFirstElm), (1+Neq)*SizeOf(PElmRec));
FreeMemParaAlign(Pointer(OrigLastElm), (1+Neq)*SizeOf(PElmRec));
FreeMemParaAlign(Pointer(OrigNextActiveRow), (1+Neq)*SizeOf(Integer));
FreeMemParaAlign(Pointer(OrigPivRow), (1+Neq)*SizeOf(Word));
FreeMemParaAlign(Pointer(OrigRowCount), (1+Neq)*SizeOf(Word));

{get rid of user heap}
while (BlockList <> nil) do begin
OrigBlock := BlockList^.BlockPtr;
if (OrigBlock <> nil) then begin
FreeMemParaAlign(Pointer(OrigBlock), SizeOf(TBlock));
if Msg then WriteLn('releasing a block');
end;
NextPtr := BlockList^.NextRec;
Dispose(BlockList); Dec(SparMemUsed, SizeOf(HeapRec));
BlockList := NextPtr;
end;                {while}

Initialized := False;
end;                  {ReleaseStruc}

function AddElement(ThisEqu, ThisVar : Word; ThisVal : Double) : Boolean;
var
PrevPtr, ElmPtr, NewPtr : PElmRec;
begin                 {PivRow[Row] points to last element }
if (ThisEqu < 1) then begin SetErrorMsg('Row < 1', 30, ThisEqu, ThisVar);
Exit; end;
if (ThisVar < 1) then begin SetErrorMsg('Col < 1', 31, ThisEqu, ThisVar);
Exit; end;
if (ThisEqu > Neq) then begin SetErrorMsg('Row > Neq', 32, ThisEqu, Neq);
Exit; end;
if (ThisVar > (1+Neq)) then begin SetErrorMsg('Col > Neq', 33, ThisVar,
Neq); Exit; end;
if ThisVal = 0.0 then begin AddElement := True; Exit; end;
if (FreeCount < Neq) then begin
if not FrePrime then begin
SetErrorMsg('Out of Space', 7, 0, 3); Exit;
end;
end;
NewPtr := FreePtr; FreePtr := FreePtr^.Next; Dec(FreeCount);

NewPtr^.Value := ThisVal;
NewPtr^.Column := ThisVar;
NewPtr^.Next := nil;

if FirstElm^[ThisEqu] = nil then begin
FirstElm^[ThisEqu] := NewPtr;
LastElm^[ThisEqu] := NewPtr;
end
else if (LastElm^[ThisEqu]^.Column < ThisVar) then begin
LastElm^[ThisEqu]^.Next := NewPtr;
LastElm^[ThisEqu] := NewPtr;
end
else begin          {insertion sort}
ElmPtr := FirstElm^[ThisEqu];
PrevPtr := nil;
while (ElmPtr^.Column < ThisVar) do begin
PrevPtr := ElmPtr;
ElmPtr := ElmPtr^.Next;
end;
if (ElmPtr^.Column = ThisVar) then begin
ElmPtr^.Value := ElmPtr^.Value+ThisVal;
NewPtr^.Next := FreePtr; FreePtr := NewPtr; Inc(FreeCount);
end
else {ElmPtr^.Column > ThisVar} begin
if PrevPtr = nil then FirstElm^[ThisEqu] := NewPtr
else PrevPtr^.Next := NewPtr;
NewPtr^.Next := ElmPtr;
end;
end;

function SetRHS(ThisEqu : Word; ThisVal : Double) : Boolean;
begin
end;                  {SetRHS}

function GetAnswer(ThisVar : Word; var ThisVal : Double) : Boolean;
{should fail if solve not called}
begin
if ((ThisVar > 0) and (ThisVar <= Neq)) then begin
end
else begin
if (ThisVar < 1) then SetErrorMsg('VarNo < 1', 41, ThisVar, 0);
if (ThisVar > Neq) then SetErrorMsg('VarNo > Neq', 42, ThisVar, Neq);
end;                {else}

procedure showmat;
var
Row, Col, c, LastCol : Word;
ElmPtr         : PElmRec;
begin
for Row := 1 to Neq do begin
ElmPtr := FirstElm^[Row];
LastCol := 0;
while ElmPtr <> nil do begin
Col := ElmPtr^.Column;
for c := (LastCol+1) to (Col-1) do Write('nil':6);
Write(ElmPtr^.Value:6:2);
LastCol := Col;
ElmPtr := ElmPtr^.Next;
end;
for c := (LastCol+1) to (Neq+1) do Write('nil':6);
WriteLn;
end;                {for row}
end;                  {showmat}

var
SumTerm        : Extended;
Factor         : Extended;
RHS            : Extended;
Biggest        : Extended;
Coeff          : Extended;
PivotValue     : Extended;
BestPtr        : PElmRec;
PrevPtr        : PElmRec;
Next_Pivot     : PElmRec;
Next_Tar       : PElmRec;
NewPtr         : PElmRec;
ElmPtr         : PElmRec;
MinRowCount    : Word;
NextTarCol     : Word;
NumToFind      : Word;
Col, LastCol   : Word;
SingleCount    : Word;
PivotStep      : Word;
PivotCol       : Word;
NextPivotCol   : Word;
LastRow        : Word;
PrevRow, NextRow : Word;
PivotRow       : Word;
Row            : Word;
NextActiveRow  : PIntArr;
ColCount       : PIntArr;
RowCount       : PWordArr;
PivRow         : PWordArr;
function Solve1 : Boolean;

label Go_on, AsBigAs, NextOne;
label TwoSingles, NumericallySingular, NoVars, EmptyRow, EmptyCol,
OutOfSpace, ColsOutofOrder, NoRHS;
begin                 {solve1}
if Msg then WriteLn('Entering Solve1');
PivotStep := 0;
FreeMemParaAlign(Pointer(OrigLastElm), (1+Neq)*SizeOf(PElmRec));

if not GetMemParaAlign(Pointer(NextActiveRow), Pointer(OrigNextActiveRow),
(1+Neq)*SizeOf(Integer)) then goto OutOfSpace;
if not GetMemParaAlign(Pointer(ColCount), Pointer(OrigColCount),
(1+Neq)*SizeOf(Integer)) then goto OutOfSpace;
if not GetMemParaAlign(Pointer(RowCount), Pointer(OrigRowCount),
(1+Neq)*SizeOf(Word)) then goto OutOfSpace;
if not GetMemParaAlign(Pointer(PivRow), Pointer(OrigPivRow),
(1+Neq)*SizeOf(Word)) then goto OutOfSpace;

Col := 0;           {set vectors to zero}
FillStruct(RowCount^, 1+Neq, Col, SizeOf(Word));
FillStruct(PivRow^, 1+Neq, Col, SizeOf(Word));
FillStruct(ColCount^, 1+Neq, Col, SizeOf(Word));

Solve1 := False;
if Msg then WriteLn('about to set up row and column counts');
for Row := 1 to Neq do begin
ElmPtr := FirstElm^[Row];
if (ElmPtr = nil) then goto EmptyRow;
LastCol := 0;
while ElmPtr <> nil do begin
Col := ElmPtr^.Column;
if DBug then ElmPtr^.CheckRow := Row;
if (Col <= LastCol) then goto ColsOutofOrder
else LastCol := Col;
Inc(RowCount^[Row]);
if DBug then Assert(Col > 0, '#2133');
if DBug then Assert(Col <= (1+Neq), '#2134');
if (Col <= Neq) then Inc(ColCount^[Col]);
ElmPtr := ElmPtr^.Next;
end;
if (LastCol <> (1+Neq)) then goto NoRHS;
end;                {for row}

if Msg then WriteLn('about to complete setup');
Row := 0;
while (Row < Neq) do begin
NextActiveRow^[Row] := Row+1;
Inc(Row);
if ColCount^[Row] = 0 then goto EmptyCol;
if RowCount^[Row] = 1 then goto NoVars;
end;                {for Row:=1 to neq}
NextActiveRow^[Neq] := 0;
{end setup}

repeat              {pivot on variables which are mentioned only once}
PivotCol := 0;
PrevRow := 0;
Row := NextActiveRow^;
while Row <> 0 do begin
NextRow := NextActiveRow^[Row];
if DBug then Assert(Row > 0, '#8033');
if DBug then Assert(Row <= Neq, '#9033');
SingleCount := 0;
ElmPtr := FirstElm^[Row]; if DBug then Assert(ElmPtr <> nil, '#34');
if DBug then Assert(ElmPtr^.Column <= Neq, '#77');
Col := ElmPtr^.Column;
while (Col <= Neq) do begin
if (ColCount^[Col] = 1) then begin
PivotCol := Col;
Inc(SingleCount);
end;
ElmPtr := ElmPtr^.Next; if DBug then Assert(ElmPtr <> nil, '#35');
Col := ElmPtr^.Column;
end;
if (SingleCount > 1) then goto TwoSingles
else if (SingleCount = 1) then begin
Inc(PivotStep);
PivotRow := Row;
ElmPtr := FirstElm^[PivotRow]; if DBug then Assert(ElmPtr <> nil,
'#34');
if DBug then Assert(ElmPtr^.Column <= Neq, '#77');
Col := ElmPtr^.Column;
while (Col <= Neq) do begin
if DBug then Assert(ColCount^[Col] > 0, '#4177');
Dec(ColCount^[Col]);
ElmPtr := ElmPtr^.Next; if DBug then Assert(ElmPtr <> nil, '#35');
Col := ElmPtr^.Column;
end;
PivRow^[PivotStep] := PivotRow;
NextActiveRow^[PrevRow] := NextActiveRow^[PivotRow];
NextActiveRow^[PivotRow] := -1; {useful ?}
RowCount^[PivotRow] := PivotCol; {change of meaning}
ColCount^[PivotCol] := -1; {mark as done}
end             {if (SingleCount=1) }
else {no Singles} PrevRow := Row;
Row := NextRow;
end;

until (PivotCol = 0);

{*************main loop}
while PivotStep < Neq do begin
Inc(PivotStep);
if Msg then WriteLn('starting step ', PivotStep);

{ Find shortest row (PivotRow) and the preceding active row (LastRow)  }
MinRowCount := MaxInt;
PrevRow := 0; LastRow := 0;
Row := NextActiveRow^;
if DBug then Assert(Row <> 0, '#33');
while Row <> 0 do begin
if RowCount^[Row] < MinRowCount then begin
MinRowCount := RowCount^[Row];
PivotRow := Row;

LastRow := PrevRow;
end;
PrevRow := Row;
Row := NextActiveRow^[Row];
end;

if Msg then WriteLn('Pivotrow: ', PivotRow, ' Rowcount ', MinRowCount);

Biggest := -1;
ElmPtr := FirstElm^[PivotRow]; if DBug then Assert(ElmPtr <> nil, '#34');
if DBug then Assert(ElmPtr^.Column <= Neq, '#77');
while (ElmPtr^.Column <= Neq) do begin
if (Abs(ElmPtr^.Value) > Biggest) then Biggest := Abs(ElmPtr^.Value);
ElmPtr := ElmPtr^.Next; if DBug then Assert(ElmPtr <> nil, '#35');
end;
if DBug then Assert(Biggest >= 0, '#45');
if Biggest = 0 then goto NumericallySingular;

if Msg then WriteLn('Biggest was :', Biggest);

Biggest := Biggest*Uvalue;
BestPtr := nil;
ElmPtr := FirstElm^[PivotRow]; if DBug then Assert(ElmPtr <> nil, '#36');
while (ElmPtr^.Column <= Neq) do begin
Col := ElmPtr^.Column;
Dec(ColCount^[Col]);
if (Abs(ElmPtr^.Value) >= Biggest) then begin
{if this Pivot were chosen}
BestPtr := ElmPtr;
end;
end;            {if (....>=UValue)}
ElmPtr := ElmPtr^.Next;
if DBug then Assert(ElmPtr <> nil, '#37');
end;
if DBug then Assert(BestPtr <> nil, '#38');

PivotCol := BestPtr^.Column;
PivotValue := BestPtr^.Value;
{Mark Pivot Row as inactive}
NextActiveRow^[LastRow] := NextActiveRow^[PivotRow];

{Answer Values for use by backsub}
PivRow^[PivotStep] := PivotRow;
{note change of meaning}
NextActiveRow^[PivotRow] := -1; {useful ?}
RowCount^[PivotRow] := PivotCol; {change of meaning}
NumToFind := ColCount^[PivotCol];
ColCount^[PivotCol] := -1; {mark as done}

if Msg then Write('Start Pivot, ');
if Msg then Write(' Pivotrow: ', PivotRow, ' Rowcount ', MinRowCount);
if Msg then WriteLn(' PivotCol: ', PivotCol, ' ColCount ', NumToFind);

Row := NextActiveRow^;
while ((Row <> 0) and (NumToFind > 0)) do begin
if (FreeCount < Neq) then if not FrePrime then goto OutOfSpace;

{preload PrevPtr so that: PrevPtr^.Next :=  FirstElm^[Row]}
ElmPtr := FirstElm^[Row];

{the goto's in this section are intended to ease transition to assembly
language}

Go_on:  if (ElmPtr^.Column >= PivotCol) then goto AsBigAs;
PrevPtr := ElmPtr;
ElmPtr := ElmPtr^.Next; {not got to it yet}
if DBug then Assert(ElmPtr <> nil, '#55');
if (ElmPtr^.Column >= PivotCol) then goto AsBigAs;
PrevPtr := ElmPtr;
ElmPtr := ElmPtr^.Next; {not got to it yet}
if DBug then Assert(ElmPtr <> nil, '#55');

goto Go_on;
AsBigAs: if (ElmPtr^.Column <> PivotCol) then goto NextOne;

{current row contains pivot col}
if Msg then WriteLn('Altering Row ', Row);
Factor := ElmPtr^.Value/PivotValue;
Dec(NumToFind);

{ DELETE pivot col in current row}
Dec(RowCount^[Row]);
PrevPtr^.Next := ElmPtr^.Next;
ElmPtr^.Next := FreePtr; FreePtr := ElmPtr; Inc(FreeCount);

Next_Pivot := FirstElm^[PivotRow];
if DBug then Assert(Next_Pivot <> nil, '#333');
PrevPtr := Addr(FirstElm^[Row-1]); {PrevPtr^.Next :=  FirstElm^[Row]}
Next_Tar := FirstElm^[Row];
if DBug then Assert(Next_Tar <> nil, '#334');
NextTarCol := Next_Tar^.Column;
while Next_Pivot <> nil do begin
NextPivotCol := Next_Pivot^.Column;
if (NextPivotCol = PivotCol) then goto OutCase;

while NextTarCol < NextPivotCol do begin
PrevPtr := Next_Tar;
Next_Tar := Next_Tar^.Next; if DBug then Assert(Next_Tar <> nil,
'#99');
NextTarCol := Next_Tar^.Column
end;
if NextTarCol = NextPivotCol then goto AdjustElm;

InsertElm: {NextTarCol > NextPivotCol}
{element in pivot row but not in current row: add in new element}
if DBug then Assert(NextTarCol > NextPivotCol, '#69');
if DBug then Assert(FreePtr <> nil, '#89');
NewPtr := FreePtr;
NewPtr^.Value := -Factor*Next_Pivot^.Value; {up for copro}
FreePtr := FreePtr^.Next; Dec(FreeCount);
PrevPtr^.Next := NewPtr;
PrevPtr := NewPtr;
NewPtr^.Column := NextPivotCol;
if DBug then NewPtr^.CheckRow := Row;
NewPtr^.Next := Next_Tar;
Inc(ColCount^[NextPivotCol]);
Inc(RowCount^[Row]);
goto OutCase;

AdjustElm: if DBug then Assert(NextTarCol = NextPivotCol, '#67');
Next_Tar^.Value := Next_Tar^.Value-Factor*Next_Pivot^.Value;
PrevPtr := Next_Tar;
Next_Tar := Next_Tar^.Next;
if (Next_Tar <> nil) then
NextTarCol := Next_Tar^.Column
else NextTarCol := 2+Neq; {sentinel value}

OutCase:
Next_Pivot := Next_Pivot^.Next; {move along pivot row}
end;            {while Next_Pivot <>  Nil}

NextOne:
Row := NextActiveRow^[Row];
end;              {while row}
if DBug then Assert(NumToFind = 0, '#66');
end;                {main loop}

{release un-needed vectors}
FreeMemParaAlign(Pointer(OrigColCount), (1+Neq)*SizeOf(Integer));
FreeMemParaAlign(Pointer(OrigNextActiveRow), (1+Neq)*SizeOf(Integer));

(1+Neq)*SizeOf(Double))
then goto OutOfSpace;
if DBug then for Row := 1 to Neq do Answer^[Row] := -99;

PivotStep := 1+Neq;
while (PivotStep > 1) do begin
Dec(PivotStep);
Row := PivRow^[PivotStep];
PivotCol := RowCount^[Row]; {note change of meaning}
SumTerm := 0.0;
Coeff := 0.0;
ElmPtr := FirstElm^[Row];
if DBug then Assert(ElmPtr <> nil, '#188');
while ElmPtr <> nil do begin
Col := ElmPtr^.Column;
if (Col = PivotCol) then
Coeff := ElmPtr^.Value
else if (Col <= Neq) then begin
if DBug then Assert(Answer^[Col] <> -99, '#177');
end
else
RHS := ElmPtr^.Value;

ElmPtr := ElmPtr^.Next;
end;              {until (elmptr=Nil)}
if DBug then Assert(Answer^[PivotCol] = -99, '#77');
end;                {for PivotRow:=neq downto 1}

FreeMemParaAlign(Pointer(OrigRowCount), (1+Neq)*SizeOf(Word));
FreeMemParaAlign(Pointer(OrigPivRow), (1+Neq)*SizeOf(Word));

Solve1 := True; Exit; {normal}

EmptyRow: SetErrorMsg('Empty Row', 1, Row, 0); Exit;
NoVars: SetErrorMsg('Row without Variables', 2, Row, 0); Exit;
EmptyCol: SetErrorMsg('Empty Col', 3, Col, 0); Exit;
TwoSingles: SetErrorMsg('Two Singles', 4, Row, PivotCol); Exit;
NoRHS: SetErrorMsg('No RHS', 5, Row, LastCol); Exit;
NumericallySingular: SetErrorMsg('Numerically Singular', 6, PivotRow,
PivotStep); Exit;
OutOfSpace: SetErrorMsg('Out of Space', 7, PivotStep, 4); Exit;
ColsOutofOrder: SetErrorMsg('Cols out of Order', 8, Row, Col); Exit;

end;                  {Solve1}

end.
{************************************************************************}
{\$N+,E+}
uses SolSpar;
var
Reason         : String;
ErrNo1, ErrNo2, ErrNo3 : Word;
Col, Row, N    : Integer;
Total, Value   : Double;
label Fail;
begin
WriteLn('Initial Heap Size ', MemAvail);
N := 1500;            {size of matrix}
if not InitStruc(N) then goto Fail;

{constructs a 'band' matrix with column border and solves it}

{for testing purposes, the RHS is set up so that variable(n)
has value n. The 'Total' variable is part of this. In normal use
the RHS would be set to some other value}

for Row := 1 to N do begin
Total := 0.0;

Value := 0.1+Random;
Col := Row;
if not AddElement(Row, Col, Value) then goto Fail;
Total := Total+Col*Value;

Col := Row-1;
if (Col > 0) then begin
Value := 0.1+Random;
if not AddElement(Row, Col, Value) then goto Fail;
Total := Total+Col*Value;
end;

Col := Row+1;
if (Col <= N) then begin
Value := 0.1+Random;
if not AddElement(Row, Col, Value) then goto Fail;
Total := Total+Col*Value;
end;

Col := N;
Value := 0.1+Random;
if not AddElement(Row, Col, Value) then goto Fail;
Total := Total+Col*Value;

if not SetRHS(Row, Total) then goto Fail;
end;                  {for Row:= 1 TO N}
WriteLn(' Memory Used To Store Matrix ', SparMemUsed);

if not Solve1 then goto Fail;
for Row := 1 to N do if ((Row mod 100) = 0) then begin
if not GetAnswer(Row, Value) then goto Fail;
WriteLn(Row:3, Value:15:5);
end;
WriteLn(' Max Memory Used To Solve Matrix ', SparMemUsed);
ReleaseStruc;
WriteLn(' Memory Used after ReleaseStruc ', SparMemUsed);
WriteLn('  Final Heap Size ', MemAvail);

Halt;

Fail:
GetErrorMsg(Reason, ErrNo1, ErrNo2, ErrNo3);
WriteLn('Failed:  Error ', ErrNo1:0, ' ', Reason, ' ', ' ', ErrNo2:3, ' ',
ErrNo3:3);
ReleaseStruc;
WriteLn(' Memory Used after ReleaseStruc ', SparMemUsed);
WriteLn('  Final Heap Size ', MemAvail);
end.
{************************************************************************}
{\$N+,E+}
uses SolSpar;
var
Reason         : String;
ErrNo1, ErrNo2, ErrNo3 : Word;
Example, Col, Row, N : Integer;
Total, Value   : Double;
label Fail, EndProg;
begin
N := 4;               {size of matrix}

{Shows how error messages work. Compile and run
test2 >tem
off DOS command line.  Then compare file 'tem' with code below}

{for testing purposes, the RHS is set up so that variable(n)
has value n. The 'Total' variable is part of this. In normal use
the RHS would be set to some other value}

for Example := 1 to 8 do begin
WriteLn('Example ', Example);
if not InitStruc(N) then goto Fail;

if not AddElement(1, 1, 0.50) then goto Fail;
if not AddElement(1, 3, 2.00) then goto Fail;
if not SetRHS(1, 6.50) then goto Fail;

if (Example <> 7) then
if not AddElement(2, 2, 0.20) then goto Fail;
if not AddElement(2, 4, 5.00) then goto Fail;
if not SetRHS(2, 20.40) then goto Fail;

if not AddElement(3, 3, 1.00) then goto Fail;
if not AddElement(3, 1, 0.75) then goto Fail;
if not SetRHS(3, 3.75) then goto Fail;

if (Example = 1) or (Example = 7) then begin
if not AddElement(4, 4, 6.00) then goto Fail;
if not AddElement(4, 1, 2.00) then goto Fail;
if not SetRHS(4, 26.00) then goto Fail;
end
else if (Example = 2) then begin {4th row is multiple of second}
if not AddElement(4, 2, 0.40) then goto Fail;
if not AddElement(4, 4, 10.0) then goto Fail;
if not SetRHS(4, 26.00) then goto Fail;
end
else if (Example = 3) then begin {variables 2 and 4 each appear only in row
2}
if not AddElement(4, 3, 1.00) then goto Fail;
if not AddElement(4, 1, 0.75) then goto Fail;
if not SetRHS(4, 26.00) then goto Fail;
end
else if (Example = 4) then begin {no vars in row 4}
if not SetRHS(4, 26.00) then goto Fail;
end
else if (Example = 5) then begin    {no rhs}
if not AddElement(4, 3, 1.00) then goto Fail;
if not AddElement(4, 1, 0.75) then goto Fail;
end
else if (Example = 6) then begin   {no row 4}
end;
showmat;
if not Solve1 then begin
GetErrorMsg(Reason, ErrNo1, ErrNo2, ErrNo3);
WriteLn('Failed:  Error ', ErrNo1:0, ' ', Reason, ' ', ' ', ErrNo2:3, '
', ErrNo3:3);
end
else begin
Write('Solved: ');
for Row := 1 to N do if GetAnswer(Row, Value)
then Write('  X(', Row, ')=', Value:0:3)
else goto Fail;
end;
WriteLn;

if (Example < 7) then ReleaseStruc;
end;                  {for example}

Halt;

Fail:
GetErrorMsg(Reason, ErrNo1, ErrNo2, ErrNo3);
WriteLn('Failed:  Error ', ErrNo1:0, ' ', Reason, ' ', ' ', ErrNo2:3, ' ',
ErrNo3:3);
end.
```