Skip to content

Factorize() should clear the SparseLU workspace now that it can be refactorized. #58

Description

@Mervill

Issue

int n = 3;

// A: an MNA matrix with an ideal-voltage-source row/column
// (column 2's only structural entry is at row 0 - its own diagonal is absent):
//   [ 0.001   -0.001   1 ]
//   [-0.001   10.001   0 ]
//   [ 1        0       0 ]   <- row 2 encodes the constraint x[0] == 5
var coo = new CoordinateStorage<double>(n, n, 6);
coo.At(0, 0, 0.001);
coo.At(1, 0, -0.001);
coo.At(2, 0, 1.0);
coo.At(0, 1, -0.001);
coo.At(1, 1, 10.001);
coo.At(0, 2, 1.0);

var A = CompressedColumnStorage<double>.OfIndexed(coo, inplace: true);

double[] someNonzeroB = { 1.0, 2.0, 3.0 };
double[] B = { 0.0, 0.0049995, 5.0 };
double[] throwaway = new double[n];
double[] x = new double[n];
double[] xFresh = new double[n];

var lu = SparseLU.Create(A, ColumnOrdering.MinimumDegreeAtPlusA, 1e-8);
lu.Solve(someNonzeroB, throwaway); // leaves `temp` dirty with a real solution
lu.Refactorize(A, 1e-8);           // reuses the dirty `temp`
lu.Solve(B, x);

var luFresh = SparseLU.Create(A, ColumnOrdering.MinimumDegreeAtPlusA, 1e-8);
luFresh.Solve(B, xFresh);

Console.WriteLine($"x (via Refactorize) = [{string.Join(", ", x)}]");
Console.WriteLine($"xFresh (via Create) = [{string.Join(", ", xFresh)}]");
Console.WriteLine($"expected x[0] == 5.0 (row 2's constraint); via Refactorize got {x[0]}");

// Output:
//   x (via Refactorize) = [5.015042122304683, 0.0010013540768227859, -0.005014040768227683]
//   xFresh (via Create) = [5, 0.0009998500149985, -0.004999000149985001]
//   expected x[0] == 5.0 (row 2's constraint); via Refactorize got 5.015042122304683

Solution

SparseLU.Factorize(...) should clear the workspace:

// Workspace
var x = this.temp;
Array.Clear(x, 0, n);
var xi = new int[2 * n];

For both Double and Complex versions.

More

In a Factorize call, x is only self-cleaning for indices in the current column's elimination "reach" set and SolveSp clears those before use. The diagonal-preference pivot check, if (pinv[col] < 0 && Math.Abs(x[col]) >= a * tol), reads x[col] unconditionally, without checking whether col is actually in that reach set. When a column's own diagonal is structurally absent from A and not reachable through the partialy built L (e.g. an MNA circuit's ideal voltage-source branch column), x[col] is never written this iteration, so the read returns whatever was last left in temp[col]. At low pivot tolerances that stale value easily passes the >= a * tol check.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions