Search code examples
delphipascallinear-equation

Seidel method in Pascal


I need to implement Seidel method in Pascal. I tried this code but it gives the wrong answer. I don't understand what the mistake is. This is what the procedure for finding roots looks like:

procedure Seidel(n: Integer; var x: vector; a: matrix; e: Real);
var k, i, j, z: integer;
s: Real;
begin
  for k := 1 to 100 do
    begin
      z := k;
      for i := 1 to n do
        begin
          s := a[i, n + 1];
          for j := 1 to n do s := s - a[i, j] * x[j];
          s := s / a[i, i];
          x[i] := x[i] + s;
          if abs(s) > e then z := 0
        end;
      if z <> 0 then Break;
    end;
end;

Procedure for variable 'a'

procedure ReadA;
var i, j: integer;
begin
  for i := 1 to m do
    for j := 1 to m + 1 do
      a[i, j] := StrToFloat(Form1.StringGrid1.Cells[j, i])
end;

This is how StringGrid looks like: "Корни Х" - "Roots X"

When you click on the "Расчёт" (calculate) button, the answers are different, and after repeated clicking, the "Floating point overflow" error appears.


Solution

  • The mistakes are

    • using no comments
    • using more than 2 single-letter variable names
    • using anti-patterns: a counting loop (for loop) should be used only if you can predict the exact number of iterations. Break does/should not belong to your standard repertoire, I even consider it a variant of spaghetti code. There are very few exceptions to this rule, but here you it’s better to stick to using a conditional loop (while … do or repeat … until).
    • omitting begin … end frames (for branches and loops) during development, when your program evidently is not finished yet

    To be fair, the Seidel method can be confusing. On the other hand, Pascal is, provided a sufficient language proficiency, pretty well-suited for such a task.

    I actually had to program that task myself in order to possibly understand why your procedure does not produce the right result. The following program uses some Extended Pascal (ISO 10206) features like schemata and type inquiries. You will need an EP-compliant compiler for that, such as the GPC (GNU Pascal Compiler). AFAIK, Delphi does not support those features, but it should be an easy task to resolve any deficiencies.

    Considering all aforementioned “mistakes” you arrive at the following solution.

    program seidel(output);
    
    type
        naturalNumber = 1..maxInt value 1;
    

    All naturalNumber values below are initialized with 1 unless otherwise specified. This is an EP extension.

        linearSystem(
                coefficientCount: naturalNumber;
                equationCount: naturalNumber
            ) = record
                coefficient: array[1..equationCount, 1..coefficientCount] of real;
                result: array[1..coefficientCount] of real;
                solution: array[1..equationCount] of real;
            end;
    

    Of course you may structure that data type differently depending on your main usage scenario.

    {
        Approximates the solution of the passed linearSystem
        using the Gauss-Seidel method.
        
        system.solution should contain an estimate of the/a solution.
    }
    procedure approximateSolution(var system: linearSystem);
        { Returns `true` if any element along the main diagonal is zero. }
        { NB: There is a chance of false negatives. }
        function mainDiagonalNonZero: Boolean;
        var
            product: real value 1.0;
            n: naturalNumber;
        begin
            { Take the product of all elements along the main diagonal. }
            { If any element is zero, the entire product is zero. }
            for n := 1 to system.coefficientCount do
            begin
                product := product * system.coefficient[n, n];
            end;
            
            mainDiagonalNonZero := product <> 0.0;
        end;
    

    This function mainDiagonalNonZero serves as a reminder that you can “nest” routines in routines. Although it is only called once below, it cleans up your source code a bit if you structure units of code like that.

    type
        { This is more readable than using plain integer values. }
        relativeOrder = (previous, next);
    var
        approximation: array[relativeOrder] of type of system.solution;
    

    Note, that approximation is declared in front of getNextApproximationResidual, so both this function and the main block of approximateSolution can access the same vectors.

        { Calculates the next approximation vector. }
        function getNextApproximationResidual: real;
        var
            { used for both, identifying the equation and a coefficient }
            n: naturalNumber;
            { used for identifying one term, i.e. coefficient × solution }
            term: 0..maxInt;
            { denotes a current error of this new/next approximation }
            residual: real;
            { denotes the largest error }
            residualMaximum: real value 0.0;
            { for simplicity, you could use `approximation[next, n]` instead }
            sum: real;
        begin
            for n := 1 to system.equationCount do
            begin
                sum := 0.0;
                
                for term := 1 to n - 1 do
                begin
                    sum := sum + system.coefficient[n, term] * approximation[next, term];
                end;
                
                { term = n is skipped, because that's what we're calculating }
                
                for term := n + 1 to system.equationCount do
                begin
                    sum := sum + system.coefficient[n, term] * approximation[previous, term];
                end;
    

    Here it becomes apparent, that your implementation does not contain two for loops. It does not iterate over all terms.

                sum := system.result[n] - sum;
                { everything times the reciprocal of coefficient[n, n] }
                approximation[next, n] := sum / system.coefficient[n, n];
                
                { finally, check for larger error }
                residual := abs(approximation[next, n] - approximation[previous, n]);
                if residual > residualMaximum then
                begin
                    residualMaximum := residual;
                end;
            end;
            
            getNextApproximationResidual := residualMaximum;
        end;
    

    I have outsourced this function getNextApproximationResidual so I could write a nicer abort condition in the loop below.

    const
        { Perform at most this many approximations before giving up. }
        limit = 1337;
        { If the approximation improved less than this value, }
        { we consider the approximation satisfactory enough. }
        errorThreshold = 8 * epsReal;
    var
        iteration: naturalNumber;
    begin
        if system.coefficientCount <> system.equationCount then
        begin
            writeLn('Error: Gauss-Seidel method only works  ',
                'on a _square_ system of linear equations.');
            halt;
        end;
        
        { Values in the main diagonal later appear as divisors, }
        { that means they must be non-zero. }
        if not mainDiagonalNonZero then
        begin
            writeLn('Error: supplied linear system contains ',
                'at least one zero along main diagonal.');
            halt;
        end;
    

    Do not trust user input. Before we calculate anything, ensure the system meets some basic requirements. halt (without any parameters) is an EP extension. Some compilers’ halt also accept an integer parameter to communicate the error condition to the OS.

        { Take system.solution as a first approximation. }
        approximation[next] := system.solution;
        
        repeat
        begin
            iteration := iteration + 1;
            { approximation[next] is overwritten by `getNextApproximationError` }
            approximation[previous] := approximation[next];
        end
        until (getNextApproximationResidual < errorThreshold) or_else (iteration >= limit);
    

    The or_else operator is an EP extension. It explicitly denotes “lazy/short-cut evaluation”. Here it wasn’t necessary, but I like it nevertheless.

        { Emit a warning if the previous loop terminated }
        { because of reaching the maximum number of iterations. }
        if iteration >= limit then
        begin
            writeLn('Note: Maximum number of iterations reached. ',
                'Approximation may be significantly off, ',
                'or it does not converge.');
        end;
        
        { Finally copy back our best approximation. }
        system.solution := approximation[next];
    end;
    

    I used the following for testing purposes. protected (EP) corresponds to const in Delphi (I guess).

    { Suitable for printing a small linear system. }
    procedure print(protected system: linearSystem);
    const
        totalWidth = 8;
        fractionWidth = 3;
        times = ' × ';
        plus = ' + ';
    var
        equation, term: naturalNumber;
    begin
        for equation := 1 to system.equationCount do
        begin
            write(system.coefficient[equation, 1]:totalWidth:fractionWidth,
                times,
                system.solution[1]:totalWidth:fractionWidth);
            for term := 2 to system.coefficientCount do
            begin
                write(plus,
                    system.coefficient[equation, term]:totalWidth:fractionWidth,
                    times,
                    system.solution[term]:totalWidth:fractionWidth);
            end;
            
            writeLn('⩰  ':8, system.result[equation]:totalWidth:fractionWidth);
        end;
    end;
    

    The following example system of linear equations was taken from Wikipedia, so I “knew” the correct result:

    { === MAIN ============================================================= }
    var
        example: linearSystem(2, 2);
    begin
        with example do
        begin
            { first equation }
            coefficient[1, 1] :=  16.0;
            coefficient[1, 2] :=   3.0;
            result[1] := 11.0;
            
            { second equation }
            coefficient[2, 1] :=   7.0;
            coefficient[2, 2] := -11.0;
            result[2] := 13.0;
            
            { used as an estimate }
            solution[1] := 1.0;
            solution[2] := 1.0;
        end;
        
        approximateSolution(example);
        
        print(example);
    end.