forked from tsai/mathnet-numerics
committed by
GitHub
20 changed files with 1934 additions and 3 deletions
@ -0,0 +1,152 @@ |
|||||
|
#include "wrapper_common.h" |
||||
|
#include "dss.h" |
||||
|
|
||||
|
#if __cplusplus |
||||
|
extern "C" { |
||||
|
#endif |
||||
|
|
||||
|
// Notes: zero-based indexing is used for rowIdx[] and colPtr[].
|
||||
|
|
||||
|
DLLEXPORT dss_int s_dss_solve(const dss_int matrixStructure, const dss_int matrixType, const dss_int systemType, |
||||
|
const dss_int nRows, const dss_int nCols, const dss_int nnz, const dss_int rowIdx[], const dss_int colPtr[], const float values[], |
||||
|
const dss_int nRhs, const float rhsValues[], float solValues[]) |
||||
|
{ |
||||
|
_MKL_DSS_HANDLE_t handle; |
||||
|
dss_int error; |
||||
|
|
||||
|
dss_int opt = MKL_DSS_MSG_LVL_WARNING + MKL_DSS_TERM_LVL_ERROR + MKL_DSS_ZERO_BASED_INDEXING + MKL_DSS_AUTO_ORDER; |
||||
|
opt += systemType; |
||||
|
|
||||
|
// Initialize the solver
|
||||
|
error = dss_create(handle, opt); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Define the non-zero structure of the matrix
|
||||
|
error = dss_define_structure(handle, matrixStructure, rowIdx, nRows, nCols, colPtr, nnz); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Reorder the matrix
|
||||
|
error = dss_reorder(handle, opt, 0); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Factor the matrix
|
||||
|
error = dss_factor_real(handle, matrixType, values); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Get the solution vector
|
||||
|
error = dss_solve_real(handle, opt, rhsValues, nRhs, solValues); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Deallocate solver storage
|
||||
|
error = dss_delete(handle, opt); |
||||
|
return error; |
||||
|
} |
||||
|
|
||||
|
DLLEXPORT dss_int d_dss_solve(const dss_int matrixStructure, const dss_int matrixType, const dss_int systemType, |
||||
|
const dss_int nRows, const dss_int nCols, const dss_int nnz, const dss_int rowIdx[], const dss_int colPtr[], const double values[], |
||||
|
const dss_int nRhs, const double rhsValues[], double solValues[]) |
||||
|
{ |
||||
|
_MKL_DSS_HANDLE_t handle; |
||||
|
dss_int error; |
||||
|
|
||||
|
dss_int opt = MKL_DSS_MSG_LVL_WARNING + MKL_DSS_TERM_LVL_ERROR + MKL_DSS_ZERO_BASED_INDEXING + MKL_DSS_AUTO_ORDER; |
||||
|
opt += systemType; |
||||
|
|
||||
|
// Initialize the solver
|
||||
|
error = dss_create(handle, opt); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Define the non-zero structure of the matrix
|
||||
|
error = dss_define_structure(handle, matrixStructure, rowIdx, nRows, nCols, colPtr, nnz); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Reorder the matrix
|
||||
|
error = dss_reorder(handle, opt, 0); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Factor the matrix
|
||||
|
error = dss_factor_real(handle, matrixType, values); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Get the solution vector
|
||||
|
error = dss_solve_real(handle, opt, rhsValues, nRhs, solValues); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Deallocate solver storage
|
||||
|
error = dss_delete(handle, opt); |
||||
|
return error; |
||||
|
} |
||||
|
|
||||
|
DLLEXPORT dss_int c_dss_solve(const dss_int matrixStructure, const dss_int matrixType, const dss_int systemType, |
||||
|
const dss_int nRows, const dss_int nCols, const dss_int nnz, const dss_int rowIdx[], const dss_int colPtr[], const dss_complex_float values[], |
||||
|
const dss_int nRhs, const dss_complex_float rhsValues[], dss_complex_float solValues[]) |
||||
|
{ |
||||
|
_MKL_DSS_HANDLE_t handle; |
||||
|
dss_int error; |
||||
|
|
||||
|
dss_int opt = MKL_DSS_MSG_LVL_WARNING + MKL_DSS_TERM_LVL_ERROR + MKL_DSS_ZERO_BASED_INDEXING + MKL_DSS_AUTO_ORDER; |
||||
|
opt += systemType; |
||||
|
|
||||
|
// Initialize the solver
|
||||
|
error = dss_create(handle, opt); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Define the non-zero structure of the matrix
|
||||
|
error = dss_define_structure(handle, matrixStructure, rowIdx, nRows, nCols, colPtr, nnz); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Reorder the matrix
|
||||
|
error = dss_reorder(handle, opt, 0); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Factor the matrix
|
||||
|
error = dss_factor_complex(handle, matrixType, values); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Get the solution vector
|
||||
|
error = dss_solve_real(handle, opt, rhsValues, nRhs, solValues); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Deallocate solver storage
|
||||
|
error = dss_delete(handle, opt); |
||||
|
return error; |
||||
|
} |
||||
|
|
||||
|
DLLEXPORT dss_int z_dss_solve(const dss_int matrixStructure, const dss_int matrixType, const dss_int systemType, |
||||
|
const dss_int nRows, const dss_int nCols, const dss_int nnz, const dss_int rowIdx[], const dss_int colPtr[], const dss_complex_double values[], |
||||
|
const dss_int nRhs, const dss_complex_double rhsValues[], dss_complex_double solValues[]) |
||||
|
{ |
||||
|
_MKL_DSS_HANDLE_t handle; |
||||
|
dss_int error; |
||||
|
|
||||
|
dss_int opt = MKL_DSS_MSG_LVL_WARNING + MKL_DSS_TERM_LVL_ERROR + MKL_DSS_ZERO_BASED_INDEXING + MKL_DSS_AUTO_ORDER; |
||||
|
opt += systemType; |
||||
|
|
||||
|
// Initialize the solver
|
||||
|
error = dss_create(handle, opt); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Define the non-zero structure of the matrix
|
||||
|
error = dss_define_structure(handle, matrixStructure, rowIdx, nRows, nCols, colPtr, nnz); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Reorder the matrix
|
||||
|
error = dss_reorder(handle, opt, 0); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Factor the matrix
|
||||
|
error = dss_factor_complex(handle, matrixType, values); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Get the solution vector
|
||||
|
error = dss_solve_real(handle, opt, rhsValues, nRhs, solValues); |
||||
|
if (error != MKL_DSS_SUCCESS) return error; |
||||
|
|
||||
|
// Deallocate solver storage
|
||||
|
error = dss_delete(handle, opt); |
||||
|
return error; |
||||
|
} |
||||
|
|
||||
|
#if __cplusplus |
||||
|
} |
||||
|
#endif |
||||
@ -0,0 +1,9 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include "mkl_dss.h" |
||||
|
#include "mkl_types.h" |
||||
|
|
||||
|
#define dss_int MKL_INT |
||||
|
#define dss_complex_float MKL_Complex8 |
||||
|
#define dss_complex_double MKL_Complex16 |
||||
|
|
||||
@ -0,0 +1,585 @@ |
|||||
|
using MathNet.Numerics.LinearAlgebra; |
||||
|
using MathNet.Numerics.LinearAlgebra.Double; |
||||
|
using MathNet.Numerics.LinearAlgebra.Storage; |
||||
|
using MathNet.Numerics.Providers.SparseSolver; |
||||
|
using NUnit.Framework; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace MathNet.Numerics.UnitTests.Providers.SparseSolver.Double |
||||
|
{ |
||||
|
|
||||
|
#if NATIVE
|
||||
|
#if MKL
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Base class for sparse solver provider tests.
|
||||
|
/// </summary>
|
||||
|
[TestFixture, Category("SparseSolverProvider")] |
||||
|
public class SparseSolverProviderTests |
||||
|
{ |
||||
|
readonly double[] _b4 = { 1.0, 2.0, 3.0, 4.0}; |
||||
|
readonly double[] _b5 = { 1.0, 2.0, 3.0, 4.0, 5.0 }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Test matrix to use.
|
||||
|
/// </summary>
|
||||
|
readonly IDictionary<string, SparseMatrix> _matrices = new Dictionary<string, SparseMatrix> |
||||
|
{ |
||||
|
{"SymmetricPositiveDefinite5x5", (SparseMatrix)Matrix<double>.Build.SparseOfColumnArrays(new [] {9.0, 1.5, 6.0, 0.75, 3.0}, new [] {1.5, 0.5, 0.0, 0.0, 0.0}, new [] {6.0, 0.0, 12.0, 0.0, 0.0 }, new [] { 0.75, 0.0, 0.0, 0.625, 0.0}, new [] {3.0, 0.0, 0.0, 0.0, 16.0})}, |
||||
|
{"Triangle5x5", (SparseMatrix)Matrix<double>.Build.SparseOfColumnArrays(new [] {1.0, 0.0, 0.0, 0.0, 0.0}, new [] {5.0, 2.0, 0.0, 0.0, 0.0}, new [] {0.0, 8.0, 3.0, 0.0, 0.0 }, new [] { 0.0, 0.0, 9.0, 4.0, 0.0}, new [] {0.0, 0.0, 0.0, 10.0, 5.0})}, |
||||
|
{"Square4x4", (SparseMatrix)Matrix<double>.Build.SparseOfColumnArrays(new [] {1.0, 1.0, 1.0, 2.0 },new [] {2.0, 0.0, 0.0, 2.0 },new [] {0.0, 0.0, 2.0, 1.0 },new [] {4.0, 1.0, 1.0, 0.0 })}, |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can solve Ax=b using direct sparse solver.
|
||||
|
/// </summary>
|
||||
|
[Test] |
||||
|
public void CanSolveSymmetricPositiveDefiniteMatrix() |
||||
|
{ |
||||
|
var A = _matrices["SymmetricPositiveDefinite5x5"].UpperTriangle(); |
||||
|
|
||||
|
var csr = A.Storage as SparseCompressedRowMatrixStorage<double>; |
||||
|
csr.PopulateExplicitZerosOnDiagonal(); |
||||
|
|
||||
|
var rowCount = csr.RowCount; |
||||
|
var columnCount = csr.ColumnCount; |
||||
|
var valueCount = csr.ValueCount; |
||||
|
var values = csr.Values; |
||||
|
var rowPointers = csr.RowPointers; |
||||
|
var columnIndices = csr.ColumnIndices; |
||||
|
|
||||
|
var xactual = new double[rowCount]; |
||||
|
|
||||
|
var error = SparseSolverControl.Provider.Solve(DssMatrixStructure.Symmetric, DssMatrixType.PositiveDefinite, DssSystemType.DontTranspose, |
||||
|
rowCount, columnCount, valueCount, rowPointers, columnIndices, values, |
||||
|
1, _b5, xactual); |
||||
|
|
||||
|
Assert.That(error, Is.EqualTo(DssStatus.MKL_DSS_SUCCESS)); |
||||
|
|
||||
|
var xtrue = new double[] { -979.0 / 3.0, 983.0, 1961.0 / 12.0, 398.0, 123.0 / 2.0 }; |
||||
|
|
||||
|
for (int i = 0; i < xtrue.Length; i++) |
||||
|
AssertHelpers.AlmostEqualRelative(xtrue[i], xactual[i], 12); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can solve Ax=b using direct sparse solver.
|
||||
|
/// </summary>
|
||||
|
[Test] |
||||
|
public void CanSolveUpperTriangularMatrix() |
||||
|
{ |
||||
|
var A = _matrices["Triangle5x5"].UpperTriangle(); |
||||
|
|
||||
|
var csr = A.Storage as SparseCompressedRowMatrixStorage<double>; |
||||
|
csr.PopulateExplicitZerosOnDiagonal(); |
||||
|
|
||||
|
var rowCount = csr.RowCount; |
||||
|
var columnCount = csr.ColumnCount; |
||||
|
var valueCount = csr.ValueCount; |
||||
|
var values = csr.Values; |
||||
|
var rowPointers = csr.RowPointers; |
||||
|
var columnIndices = csr.ColumnIndices; |
||||
|
|
||||
|
var xactual = new double[rowCount]; |
||||
|
|
||||
|
var error = SparseSolverControl.Provider.Solve(DssMatrixStructure.Nonsymmetric, DssMatrixType.Indefinite, DssSystemType.DontTranspose, |
||||
|
rowCount, columnCount, valueCount, rowPointers, columnIndices, values, |
||||
|
1, _b5, xactual); |
||||
|
|
||||
|
Assert.That(error, Is.EqualTo(DssStatus.MKL_DSS_SUCCESS)); |
||||
|
|
||||
|
var xtrue = new double[] { 106.0, -21.0, 5.5, -1.5, 1.0 }; |
||||
|
|
||||
|
for (int i = 0; i < xtrue.Length; i++) |
||||
|
AssertHelpers.AlmostEqualRelative(xtrue[i], xactual[i], 13); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can solve Ax=b using direct sparse solver.
|
||||
|
/// </summary>
|
||||
|
[Test] |
||||
|
public void CanSolveSquareMatrix() |
||||
|
{ |
||||
|
var A = _matrices["Square4x4"]; |
||||
|
|
||||
|
var csr = A.Storage as SparseCompressedRowMatrixStorage<double>; |
||||
|
csr.PopulateExplicitZerosOnDiagonal(); |
||||
|
|
||||
|
var rowCount = csr.RowCount; |
||||
|
var columnCount = csr.ColumnCount; |
||||
|
var valueCount = csr.ValueCount; |
||||
|
var values = csr.Values; |
||||
|
var rowPointers = csr.RowPointers; |
||||
|
var columnIndices = csr.ColumnIndices; |
||||
|
|
||||
|
var xactual = new double[rowCount]; |
||||
|
|
||||
|
var error = SparseSolverControl.Provider.Solve(DssMatrixStructure.Nonsymmetric, DssMatrixType.Indefinite, DssSystemType.DontTranspose, |
||||
|
rowCount, columnCount, valueCount, rowPointers, columnIndices, values, |
||||
|
1, _b4, xactual); |
||||
|
|
||||
|
Assert.That(error, Is.EqualTo(DssStatus.MKL_DSS_SUCCESS)); |
||||
|
|
||||
|
var xtrue = new double[] { 2.1, -0.35, 0.5, -0.1 }; |
||||
|
|
||||
|
for (int i = 0; i < xtrue.Length; i++) |
||||
|
AssertHelpers.AlmostEqualRelative(xtrue[i], xactual[i], 10); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can inverse A by using AX = I.
|
||||
|
/// </summary>
|
||||
|
[Test] |
||||
|
public void CanInverseSquareMatrix() |
||||
|
{ |
||||
|
var A = _matrices["SymmetricPositiveDefinite5x5"]; |
||||
|
var Atr = A.UpperTriangle(); |
||||
|
|
||||
|
var csr = Atr.Storage as SparseCompressedRowMatrixStorage<double>; |
||||
|
csr.PopulateExplicitZerosOnDiagonal(); |
||||
|
|
||||
|
var rowCount = csr.RowCount; |
||||
|
var columnCount = csr.ColumnCount; |
||||
|
var valueCount = csr.ValueCount; |
||||
|
var values = csr.Values; |
||||
|
var rowPointers = csr.RowPointers; |
||||
|
var columnIndices = csr.ColumnIndices; |
||||
|
|
||||
|
var Identity = Matrix<double>.Build.DenseIdentity(columnCount); |
||||
|
var b = Identity.ToColumnMajorArray(); |
||||
|
var Xactual = new double[rowCount * columnCount]; |
||||
|
|
||||
|
var error = SparseSolverControl.Provider.Solve(DssMatrixStructure.Symmetric, DssMatrixType.PositiveDefinite, DssSystemType.DontTranspose, |
||||
|
rowCount, columnCount, valueCount, rowPointers, columnIndices, values, |
||||
|
Identity.ColumnCount, b, Xactual); |
||||
|
|
||||
|
Assert.That(error, Is.EqualTo(DssStatus.MKL_DSS_SUCCESS)); |
||||
|
|
||||
|
var Ainverse_actual = Matrix<double>.Build.SparseOfColumnMajor(rowCount, columnCount, Xactual); |
||||
|
|
||||
|
var Ainverse_expected = Matrix<double>.Build.DenseOfColumnArrays( |
||||
|
new[] { 80.0/3.0, -80.0, -40.0/3.0, -32.0, -5.0 }, |
||||
|
new[] { -80.0, 242.0, 40.0, 96.0, 15.0 }, |
||||
|
new[] { -40.0/3.0, 40.0, 6.75, 16.0, 2.5 }, |
||||
|
new[] { -32.0, 96.0, 16.0, 40.0, 6.0 }, |
||||
|
new[] { -5.0, 15.0, 2.5, 6.0, 1.0}); |
||||
|
|
||||
|
for (int i = 0; i < Ainverse_actual.RowCount; i++) |
||||
|
for (int j = 0; j < Ainverse_actual.ColumnCount; j++) |
||||
|
AssertHelpers.AlmostEqualRelative(Ainverse_actual[i, j], Ainverse_expected[i, j], 10); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Can solve 1D boundary problem.
|
||||
|
/// </summary>
|
||||
|
[TestCase(4, 8001, 10, 1000)] |
||||
|
[TestCase(40, 8001, 0.1, 100)] |
||||
|
[TestCase(400, 8001, 0.001, 10)] |
||||
|
[TestCase(4000, 8001, 0.00001, 1)] |
||||
|
public void CanSolvePoissonEquation(int elementCount, int interpolationCount, double errV, double errE) |
||||
|
{ |
||||
|
var domain = new Domain(elementCount, 0.08); |
||||
|
domain.DefineBoundaries(1.0, 0.0); |
||||
|
domain.DefineProblem(); |
||||
|
domain.SolveProblem(); |
||||
|
|
||||
|
var xGrid = Generate.LinearSpaced(interpolationCount, 0.0, domain.Length); |
||||
|
|
||||
|
var interpolated = domain.InterpolateAt(xGrid); |
||||
|
var Vactual = interpolated.Item1; // interpolated electric potential
|
||||
|
var Eactual = interpolated.Item2; // interpolated electric field
|
||||
|
|
||||
|
var exact = domain.GetExactSolution(xGrid); |
||||
|
var Vexpected = exact.Item1; // expected electric potential
|
||||
|
var Eexpected = exact.Item2; // expected electric field
|
||||
|
|
||||
|
var Vdiff = Vector<double>.Build.Dense(Vactual.Length, (i) => Vexpected[i] - Vactual[i]); |
||||
|
var Vnorm2 = Vdiff.L2Norm(); |
||||
|
Assert.LessOrEqual(Vnorm2, errV); |
||||
|
|
||||
|
var Ediff = Vector<double>.Build.Dense(Eactual.Length, (i) => Eexpected[i] - Eactual[i]); |
||||
|
var Enorm2 = Ediff.L2Norm(); |
||||
|
Assert.LessOrEqual(Enorm2, errE); |
||||
|
} |
||||
|
|
||||
|
#region Finite element method to solve Poisson's equation
|
||||
|
|
||||
|
class Node |
||||
|
{ |
||||
|
public int ID; |
||||
|
public double X; |
||||
|
public double PrimaryValue; // electric potential at the node
|
||||
|
public double SecondaryValue; // electric field at the node
|
||||
|
|
||||
|
public Node(double x) |
||||
|
{ |
||||
|
X = x; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class Element // Linear element
|
||||
|
{ |
||||
|
public int ID; |
||||
|
public Node[] Nodes; |
||||
|
public double Alpha; |
||||
|
public double Gamma; |
||||
|
public Matrix<double> Kmatrix; |
||||
|
public Vector<double> Rhs; |
||||
|
|
||||
|
public Element(Node node1, Node node2, double alpha, double gamma) |
||||
|
{ |
||||
|
Nodes = new[] { node1, node2 }; |
||||
|
Alpha = alpha; |
||||
|
Gamma = gamma; |
||||
|
} |
||||
|
|
||||
|
public void ComputeMatrices() |
||||
|
{ |
||||
|
// The master equation:
|
||||
|
// ∇(α∇V) + γ = 0
|
||||
|
//
|
||||
|
// Let's define a transformation from u to x
|
||||
|
// x = a * u + b
|
||||
|
//
|
||||
|
// for Node1, u = -1 gives -a + b = x1
|
||||
|
// for Node2, u = +1 gives a + b = x2
|
||||
|
//
|
||||
|
// So,
|
||||
|
// x = (x2 - x1)/2*u + (x2 + x1)/2
|
||||
|
// u = 2*(x - x1)/(x2 - x1) - 1
|
||||
|
//
|
||||
|
// This gives
|
||||
|
// dx = l/2*du where l = x2 - x1 = x21 is length of the line
|
||||
|
//
|
||||
|
// Interpolation function, Ni(u) = ai + bi*u for i = 1, 2
|
||||
|
// N1(-1) = a1 - b1 = 1
|
||||
|
// N1(+1) = a1 + b1 = 0 -> a1 = 1/2, b1 = -1/2
|
||||
|
// N2(-1) = a2 - b2 = 0
|
||||
|
// N2(+1) = a2 + b2 = 1 -> a2 = 1/2, b2 = 1/2
|
||||
|
// so,
|
||||
|
// N1 = (1 - u) / 2
|
||||
|
// N2 = (1 + u) / 2
|
||||
|
//
|
||||
|
// Using the chain rule of differentiation,
|
||||
|
// ∂Ni(x)/∂u = ∂Ni/∂x ∂x/∂u
|
||||
|
//
|
||||
|
// Here,
|
||||
|
// J = ∂x/∂u = x21/2 = l/2
|
||||
|
//
|
||||
|
// |J| = l/2 where l is the length of the line
|
||||
|
//
|
||||
|
// Therefore, we can get
|
||||
|
// ∂N1/∂x = 2/l ∂N1/∂u = - 1/l
|
||||
|
// ∂N2/∂x = 2/l ∂N2/∂u = 1/l
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
// By using V(u) = Σ Vi*Ni(u) and ω = Ni for i = 1, 2
|
||||
|
// the weak form of the master equation is given as
|
||||
|
//
|
||||
|
// ∫ ω [d/dx(α dV/dx) + γ] dx = 0
|
||||
|
//
|
||||
|
// K v = f + p
|
||||
|
//
|
||||
|
// where
|
||||
|
// Kij = ∫ (dNi/dx) α (dNj/dx) dx
|
||||
|
// fi = ∫ Ni γ dx
|
||||
|
// pi = -Ni(x2)D(x2) + Ni(x1)D(x1) where D(x) = -α dV(x)/dx
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
// Kij = (l/2) ∫ α (∂Ni/∂x) (∂Nj/∂x) du, where α = const.
|
||||
|
// = α*(∂Ni/∂x)*(∂Nj/∂x)*l
|
||||
|
// K11 = α/l
|
||||
|
// K12 = M21 = - α/l
|
||||
|
// K22 = α/l
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
// fi = γ*(l/2) ∫ Ni du, where γ = const.
|
||||
|
// = γ*(l/2)
|
||||
|
// f1 = γ*l/2
|
||||
|
// f2 = γ*l/2
|
||||
|
//-----------------------------------------------------------------------------
|
||||
|
// p1 = -N1(x2)D(x2) + N1(x1)D(x1) = D(x1) = D1
|
||||
|
// p2 = -N2(x2)D(x2) + N2(x1)D(x1) = -D(x2) = -D2
|
||||
|
// For a sufficiently large number of elements in the domain, we can ignore p.
|
||||
|
|
||||
|
var x21 = Nodes[1].X - Nodes[0].X; // element length
|
||||
|
|
||||
|
Kmatrix = Matrix<double>.Build.Dense(2, 2); |
||||
|
Kmatrix[0, 0] = Alpha / x21; |
||||
|
Kmatrix[0, 1] = -Alpha / x21; |
||||
|
Kmatrix[1, 0] = -Alpha / x21; |
||||
|
Kmatrix[1, 1] = Alpha / x21; |
||||
|
|
||||
|
Rhs = Vector<double>.Build.Dense(2); |
||||
|
Rhs[0] = Gamma * x21 / 2.0; |
||||
|
Rhs[1] = Gamma * x21 / 2.0; |
||||
|
} |
||||
|
|
||||
|
public bool Contains(Node point) |
||||
|
{ |
||||
|
// A point P can be described with a line A-B
|
||||
|
// P = A + s*(B - A)
|
||||
|
// s = PA/BA
|
||||
|
// if (s >= 0 && s <= 1), then P is inside the line A-B.
|
||||
|
|
||||
|
var s = (point.X - Nodes[0].X) / (Nodes[1].X - Nodes[0].X); |
||||
|
return s >= 0.0 && (1.0 - s) >= 0.0; |
||||
|
} |
||||
|
|
||||
|
public void InterpolateAt(Node point) |
||||
|
{ |
||||
|
// V(u) can be described with interpolation functions
|
||||
|
// V(u) = V1*N1(u) + V2*N2(u)
|
||||
|
// where
|
||||
|
// N1 = (1 - u) / 2
|
||||
|
// N2 = (1 + u) / 2
|
||||
|
// The transformation from x to u,
|
||||
|
// u = 2*(x - x1)/(x2 - x1) - 1
|
||||
|
// gives
|
||||
|
// V(x) = V1*(x2 - x)/(x2 - x1) + V2*(x - x1)/(x2 - x1)
|
||||
|
|
||||
|
var V1 = Nodes[0].PrimaryValue; |
||||
|
var V2 = Nodes[1].PrimaryValue; |
||||
|
|
||||
|
var x21 = Nodes[1].X - Nodes[0].X; |
||||
|
var xp1 = point.X - Nodes[0].X; |
||||
|
var u = 2.0 * xp1 / x21 - 1.0; |
||||
|
|
||||
|
var Vx = V1 * (1 - u) * 0.5 + V2 * (1 + u) * 0.5; |
||||
|
|
||||
|
// Electric field,
|
||||
|
// E = -∇V
|
||||
|
// where
|
||||
|
// ∇V = [ (∂/∂x) ∑ViNi ]
|
||||
|
// = [ (∂/∂x)(V1*N1 + V2*N2) ]
|
||||
|
// = [ V1*(∂N1/∂x) + V2*(∂N2/∂x) ]
|
||||
|
//
|
||||
|
// The derivatives of Ni are
|
||||
|
// ∂N1/∂x = 2/l ∂N1/∂u = - 1/l where l = x21
|
||||
|
// ∂N2/∂x = 2/l ∂N2/∂u = 1/l
|
||||
|
//
|
||||
|
// Therefore,
|
||||
|
// E = (V1 - V2) / x21
|
||||
|
|
||||
|
var Ex = (V1 - V2) / x21; |
||||
|
|
||||
|
point.PrimaryValue = Vx; |
||||
|
point.SecondaryValue = Ex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class Domain |
||||
|
{ |
||||
|
// Length of the domain in [m]
|
||||
|
public double Length; |
||||
|
|
||||
|
// Dielectric constant of the domain
|
||||
|
public double Permittivity; |
||||
|
|
||||
|
// Charge density in [C/m^3]
|
||||
|
public double ChargeDensity; |
||||
|
|
||||
|
// Boundary conditions
|
||||
|
public double VoltageAtLeft; |
||||
|
public double VoltageAtRight; |
||||
|
public List<Tuple<Node, double>> Boundaries; |
||||
|
|
||||
|
public Node[] Nodes; |
||||
|
public Element[] Elements; |
||||
|
|
||||
|
public Matrix<double> Kmatrix; |
||||
|
public Vector<double> Rhs; |
||||
|
|
||||
|
public Domain(int elementCount = 4, double length = 0.08, double relativePermittivity = 1, double chargeDensity = 1E-8) |
||||
|
{ |
||||
|
Length = length; |
||||
|
|
||||
|
Permittivity = Constants.ElectricPermittivity * relativePermittivity; |
||||
|
ChargeDensity = chargeDensity; |
||||
|
|
||||
|
// Create nodes and elements
|
||||
|
Nodes = new Node[elementCount + 1]; |
||||
|
Elements = new Element[elementCount]; |
||||
|
|
||||
|
var dx = length / (double)elementCount; |
||||
|
|
||||
|
// nodes: 0 1 2 3 ... n+1
|
||||
|
// elements: 0 1 2 ... n
|
||||
|
// +---+---+---+ ... ---+
|
||||
|
// x axis: 0 length
|
||||
|
|
||||
|
for (int i = 0; i < Nodes.Length; i++) |
||||
|
{ |
||||
|
Nodes[i] = new Node(dx * i) { ID = i }; |
||||
|
} |
||||
|
for (int i = 0; i < Elements.Length; i++) |
||||
|
{ |
||||
|
Elements[i] = new Element(Nodes[i], Nodes[i + 1], Permittivity, -ChargeDensity) { ID = i }; |
||||
|
} |
||||
|
|
||||
|
// Initialization of the global K matrix and right-hand side vector
|
||||
|
// We know the K matrix is a symmetric matrix, so we will only handle the upper triangular parts.
|
||||
|
Kmatrix = Matrix<double>.Build.Sparse(Nodes.Length, Nodes.Length); |
||||
|
Rhs = Vector<double>.Build.Dense(Nodes.Length); |
||||
|
} |
||||
|
|
||||
|
public void DefineBoundaries(double Vleft, double Vright) |
||||
|
{ |
||||
|
VoltageAtLeft = Vleft; |
||||
|
VoltageAtRight = Vright; |
||||
|
|
||||
|
// Dirichlet boundary conditions
|
||||
|
Boundaries = new List<Tuple<Node, double>>(); |
||||
|
foreach (var node in Nodes) |
||||
|
{ |
||||
|
if (node.X == 0d) |
||||
|
{ |
||||
|
Boundaries.Add(new Tuple<Node, double>(node, Vleft)); |
||||
|
} |
||||
|
else if (node.X == Length) |
||||
|
{ |
||||
|
Boundaries.Add(new Tuple<Node, double>(node, Vright)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void DefineProblem() |
||||
|
{ |
||||
|
// Form the element matrices and assemble to the global matrix
|
||||
|
foreach (var element in Elements) |
||||
|
{ |
||||
|
element.ComputeMatrices(); |
||||
|
|
||||
|
// Assemble element matrix into the global K matrix
|
||||
|
for (int i = 0; i < element.Nodes.Length; i++) |
||||
|
{ |
||||
|
var row = element.Nodes[i].ID; |
||||
|
for (int j = 0; j < element.Nodes.Length; j++) |
||||
|
{ |
||||
|
var col = element.Nodes[j].ID; |
||||
|
if (row <= col) // only upper triangular parts are handled
|
||||
|
{ |
||||
|
Kmatrix[row, col] += element.Kmatrix[i, j]; |
||||
|
} |
||||
|
} |
||||
|
Rhs[row] += element.Rhs[i]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Imposition of Dirichlet boundary conditions
|
||||
|
//
|
||||
|
// If xn is given, i.e. x2 = b0, then the linear equations,
|
||||
|
// [ K11 K12 K13 K14 ][ x1 ] = [ b1 ]
|
||||
|
// [ K21 K22 K23 K24 ][ x2 ] [ b2 ]
|
||||
|
// [ K31 K32 K33 K34 ][ x3 ] [ b3 ]
|
||||
|
// [ K41 K42 K43 K44 ][ x4 ] [ b4 ]
|
||||
|
// can be changed to
|
||||
|
// [ K11 0 K13 K14 ][ x1 ] = [ b1 - K12*b0 ]
|
||||
|
// [ 0 1 0 0 ][ x2 ] [ b0 ]
|
||||
|
// [ K31 0 K33 K34 ][ x3 ] [ b3 - K32*b0 ]
|
||||
|
// [ K41 0 K43 K44 ][ x4 ] [ b4 - K42*b0 ]
|
||||
|
|
||||
|
foreach (var boundary in Boundaries) |
||||
|
{ |
||||
|
var node = boundary.Item1; |
||||
|
var i = node.ID; |
||||
|
var val = boundary.Item2; |
||||
|
|
||||
|
for (int j = 0; j < Nodes.Length; j++) |
||||
|
{ |
||||
|
if (Nodes[j].ID != i) |
||||
|
{ |
||||
|
Rhs[j] -= (j <= i) |
||||
|
? Kmatrix[j, i] * val |
||||
|
: Kmatrix[i, j] * val; // Kmatrix has only upper triangular parts
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var storage = Kmatrix.Storage as SparseCompressedRowMatrixStorage<double>; |
||||
|
storage.MapIndexedInplace( |
||||
|
(row, col, x) => (row == i && col == i) |
||||
|
? 1d |
||||
|
: (row == i) || (col == i) |
||||
|
? 0d |
||||
|
: x, |
||||
|
Zeros.AllowSkip); |
||||
|
|
||||
|
Rhs[i] = val; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void SolveProblem() |
||||
|
{ |
||||
|
// Note that Kmatrix is actually an upper triangular, but considered as a symmetric.
|
||||
|
var storage = Kmatrix.Storage as SparseCompressedRowMatrixStorage<double>; |
||||
|
var rowCount = storage.RowCount; |
||||
|
var columnCount = storage.ColumnCount; |
||||
|
var valueCount = storage.ValueCount; |
||||
|
var values = storage.Values; |
||||
|
var rowPointers = storage.RowPointers; |
||||
|
var columnIndices = storage.ColumnIndices; |
||||
|
|
||||
|
var rhs = Rhs.ToArray(); |
||||
|
var solution = new double[rowCount]; |
||||
|
|
||||
|
SparseSolverControl.Provider.Solve(DssMatrixStructure.Symmetric, DssMatrixType.Indefinite, DssSystemType.DontTranspose, |
||||
|
rowCount, columnCount, valueCount, rowPointers, columnIndices, values, |
||||
|
1, rhs, solution); |
||||
|
|
||||
|
for (int i = 0; i < solution.Length; i++) |
||||
|
{ |
||||
|
Nodes[i].PrimaryValue = solution[i]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Tuple<double[], double[]> InterpolateAt(double[] xGrid) |
||||
|
{ |
||||
|
var Vactual = new double[xGrid.Length]; |
||||
|
var Eactual = new double[xGrid.Length]; |
||||
|
|
||||
|
for (int i = 0; i < xGrid.Length; i++) |
||||
|
{ |
||||
|
var point = new Node(xGrid[i]); |
||||
|
for (int j = 0; j < Elements.Length; j++) |
||||
|
{ |
||||
|
if (Elements[j].Contains(point)) |
||||
|
{ |
||||
|
Elements[j].InterpolateAt(point); |
||||
|
Vactual[i] = point.PrimaryValue; |
||||
|
Eactual[i] = point.SecondaryValue; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return new Tuple<double[], double[]>(Vactual, Eactual); |
||||
|
} |
||||
|
|
||||
|
public Tuple<double[], double[]> GetExactSolution(double[] xGrid) |
||||
|
{ |
||||
|
// Poisson's equation: ∇(ε∇V) = ρ
|
||||
|
// Solution:
|
||||
|
// V(x) = ρ/ε/2*x^2 - (ρ/ε/2*d + (Va - Vb)/d)*x + Va, where d = length
|
||||
|
// E(x) = -∇V = ρ/ε*x - (ρ/ε/2*d + (Va - Vb)/d)
|
||||
|
|
||||
|
double[] Vexact = new double[xGrid.Length]; // electric potentials
|
||||
|
double[] Eexact = new double[xGrid.Length]; // electric fields
|
||||
|
var factor = ChargeDensity / Permittivity * 0.5; // ρ/ε/2
|
||||
|
|
||||
|
for (int i = 0; i < xGrid.Length; i++) |
||||
|
{ |
||||
|
var x = xGrid[i]; |
||||
|
Vexact[i] = factor * x * x - factor * Length * x - (VoltageAtLeft - VoltageAtRight) / Length * x + VoltageAtLeft; |
||||
|
Eexact[i] = -2d * factor * x + factor * Length + (VoltageAtLeft - VoltageAtRight) / Length; |
||||
|
} |
||||
|
|
||||
|
return new Tuple<double[], double[]>(Vexact, Eexact); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
} |
||||
|
|
||||
|
#endif
|
||||
|
#endif
|
||||
|
|
||||
|
} |
||||
|
|
||||
@ -0,0 +1,115 @@ |
|||||
|
using Complex = System.Numerics.Complex; |
||||
|
|
||||
|
namespace MathNet.Numerics.Providers.SparseSolver |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Structure option.
|
||||
|
/// </summary>
|
||||
|
public enum DssMatrixStructure : int |
||||
|
{ |
||||
|
Symmetric = 536870976, |
||||
|
SymmetricStructure = 536871040, |
||||
|
Nonsymmetric = 536871104, |
||||
|
SymmetricComplex = 536871168, |
||||
|
SymmetricStructureComplex = 536871232, |
||||
|
NonsymmetricComplex = 536871296, |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Factorization option.
|
||||
|
/// </summary>
|
||||
|
public enum DssMatrixType : int |
||||
|
{ |
||||
|
PositiveDefinite = 134217792, |
||||
|
Indefinite = 134217856, |
||||
|
HermitianPositiveDefinite = 134217920, |
||||
|
HermitianIndefinite = 134217984 |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Solver step's substitution.
|
||||
|
/// </summary>
|
||||
|
public enum DssSystemType : int |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Solve a system, Ax = b.
|
||||
|
/// </summary>
|
||||
|
DontTranspose = 0, |
||||
|
/// <summary>
|
||||
|
/// Solve a transposed system, A'x = b
|
||||
|
/// </summary>
|
||||
|
Transpose = 262144, |
||||
|
/// <summary>
|
||||
|
/// Solve a conjugate transposed system, A†x = b
|
||||
|
/// </summary>
|
||||
|
ConjugateTranspose = 524288, |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Status values
|
||||
|
/// </summary>
|
||||
|
public enum DssStatus : int |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The operation was successful.
|
||||
|
/// </summary>
|
||||
|
MKL_DSS_SUCCESS = 0, |
||||
|
MKL_DSS_ZERO_PIVOT = -1, |
||||
|
MKL_DSS_OUT_OF_MEMORY = -2, |
||||
|
MKL_DSS_FAILURE = -3, |
||||
|
MKL_DSS_ROW_ERR = -4, |
||||
|
MKL_DSS_COL_ERR = -5, |
||||
|
MKL_DSS_TOO_FEW_VALUES = -6, |
||||
|
MKL_DSS_TOO_MANY_VALUES = -7, |
||||
|
MKL_DSS_NOT_SQUARE = -8, |
||||
|
MKL_DSS_STATE_ERR = -9, |
||||
|
MKL_DSS_INVALID_OPTION = -10, |
||||
|
MKL_DSS_OPTION_CONFLICT = -11, |
||||
|
MKL_DSS_MSG_LVL_ERR = -12, |
||||
|
MKL_DSS_TERM_LVL_ERR = -13, |
||||
|
MKL_DSS_STRUCTURE_ERR = -14, |
||||
|
MKL_DSS_REORDER_ERR = -15, |
||||
|
MKL_DSS_VALUES_ERR = -16, |
||||
|
MKL_DSS_STATISTICS_INVALID_MATRIX = -17, |
||||
|
MKL_DSS_STATISTICS_INVALID_STATE = -18, |
||||
|
MKL_DSS_STATISTICS_INVALID_STRING = -19, |
||||
|
MKL_DSS_REORDER1_ERR = -20, |
||||
|
MKL_DSS_PREORDER_ERR = -21, |
||||
|
MKL_DSS_DIAG_ERR = -22, |
||||
|
MKL_DSS_I32BIT_ERR = -23, |
||||
|
MKL_DSS_OOC_MEM_ERR = -24, |
||||
|
MKL_DSS_OOC_OC_ERR = -25, |
||||
|
MKL_DSS_OOC_RW_ERR = -26, |
||||
|
} |
||||
|
|
||||
|
public interface ISparseSolverProvider : |
||||
|
ISparseSolverProvider<double>, |
||||
|
ISparseSolverProvider<float>, |
||||
|
ISparseSolverProvider<Complex>, |
||||
|
ISparseSolverProvider<Complex32> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Try to find out whether the provider is available, at least in principle.
|
||||
|
/// Verification may still fail if available, but it will certainly fail if unavailable.
|
||||
|
/// </summary>
|
||||
|
bool IsAvailable(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initialize and verify that the provided is indeed available. If not, fall back to alternatives like the managed provider
|
||||
|
/// </summary>
|
||||
|
void InitializeVerify(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Frees memory buffers, caches and handles allocated in or to the provider.
|
||||
|
/// Does not unload the provider itself, it is still usable afterwards.
|
||||
|
/// </summary>
|
||||
|
void FreeResources(); |
||||
|
} |
||||
|
|
||||
|
public interface ISparseSolverProvider<T> |
||||
|
where T : struct |
||||
|
{ |
||||
|
DssStatus Solve(DssMatrixStructure matrixStructure, DssMatrixType matrixType, DssSystemType systemType, int rows, int cols, int nnz, int[] rowIdx, int[] colPtr, T[] values, int nRhs, T[] rhs, T[] solution); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,68 @@ |
|||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
|
||||
|
namespace MathNet.Numerics.Providers.SparseSolver.Managed |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The managed sparse solver provider
|
||||
|
/// </summary>
|
||||
|
internal partial class ManagedSparseSolverProvider : ISparseSolverProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Try to find out whether the provider is available, at least in principle.
|
||||
|
/// Verification may still fail if available, but it will certainly fail if unavailable.
|
||||
|
/// </summary>
|
||||
|
public virtual bool IsAvailable() |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initialize and verify that the provided is indeed available. If not, fall back to alternatives like the managed provider
|
||||
|
/// </summary>
|
||||
|
public virtual void InitializeVerify() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Frees memory buffers, caches and handles allocated in or to the provider.
|
||||
|
/// Does not unload the provider itself, it is still usable afterwards.
|
||||
|
/// </summary>
|
||||
|
public virtual void FreeResources() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return "Managed"; |
||||
|
} |
||||
|
|
||||
|
public virtual DssStatus Solve(DssMatrixStructure matrixStructure, DssMatrixType matrixType, DssSystemType systemType, |
||||
|
int rowCount, int columnCount, int nonZerosCount, int[] rowPointers, int[] ColumnIndices, float[] values, |
||||
|
int nRhs, float[] rhs, float[] solution) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public virtual DssStatus Solve(DssMatrixStructure matrixStructure, DssMatrixType matrixType, DssSystemType systemType, |
||||
|
int rowCount, int columnCount, int nonZerosCount, int[] rowPointers, int[] ColumnIndices, double[] values, |
||||
|
int nRhs, double[] rhs, double[] solution) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public virtual DssStatus Solve(DssMatrixStructure matrixStructure, DssMatrixType matrixType, DssSystemType systemType, |
||||
|
int rowCount, int columnCount, int nonZerosCount, int[] rowPointers, int[] ColumnIndices, Complex32[] values, |
||||
|
int nRhs, Complex32[] rhs, Complex32[] solution) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public virtual DssStatus Solve(DssMatrixStructure matrixStructure, DssMatrixType matrixType, DssSystemType systemType, |
||||
|
int rowCount, int columnCount, int nonZerosCount, int[] rowPointers, int[] ColumnIndices, Complex[] values, |
||||
|
int nRhs, Complex[] rhs, Complex[] solution) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
#if NATIVE
|
||||
|
|
||||
|
using MathNet.Numerics.Properties; |
||||
|
using MathNet.Numerics.Providers.Common.Mkl; |
||||
|
using System; |
||||
|
using System.Security; |
||||
|
using Complex = System.Numerics.Complex; |
||||
|
|
||||
|
namespace MathNet.Numerics.Providers.SparseSolver.Mkl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Intel's Math Kernel Library (MKL) direct sparse solver provider.
|
||||
|
/// </summary>
|
||||
|
internal partial class MklSparseSolverProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Solves sparse linear systems of equations, <b>AX = B</b>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="matrixStructure">The symmetricity of the matrix. For a symmetric matrix, only upper ot lower triangular matrix is used.</param>
|
||||
|
/// <param name="matrixType">The definiteness of the matrix.</param>
|
||||
|
/// <param name="systemType">The type of the systems.</param>
|
||||
|
/// <param name="rowCount">The number of rows of matrix.</param>
|
||||
|
/// <param name="columnCount">The number of columns of matrix.</param>
|
||||
|
/// <param name="nonZerosCount">The number of non zero elements of matrix.</param>
|
||||
|
/// <param name="rowPointers">The array containing the row indices of the existing rows.</param>
|
||||
|
/// <param name="columnIndices">The array containing the column indices of the non-zero values</param>
|
||||
|
/// <param name="values">The array that contains the non-zero elements of matrix. No diagonal element can be ommitted. </param>
|
||||
|
/// <param name="nRhs">The number of columns of the right hand side matrix.</param>
|
||||
|
/// <param name="rhs">The right hand side matrix</param>
|
||||
|
/// <param name="solution">The left hand side matrix</param>
|
||||
|
/// <returns>The status of the solver.</returns>
|
||||
|
[SecuritySafeCritical] |
||||
|
public override DssStatus Solve(DssMatrixStructure matrixStructure, DssMatrixType matrixType, DssSystemType systemType, |
||||
|
int rowCount, int columnCount, int nonZerosCount, int[] rowPointers, int[] columnIndices, Complex[] values, |
||||
|
int nRhs, Complex[] rhs, Complex[] solution) |
||||
|
{ |
||||
|
if (rowCount != columnCount) |
||||
|
{ |
||||
|
throw new ArgumentNullException(Resources.ArgumentMatrixSymmetric); |
||||
|
} |
||||
|
|
||||
|
if (rowPointers == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(rowPointers)); |
||||
|
} |
||||
|
|
||||
|
if (columnIndices == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(columnIndices)); |
||||
|
} |
||||
|
|
||||
|
if (values == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(values)); |
||||
|
} |
||||
|
|
||||
|
if (rhs == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(rhs)); |
||||
|
} |
||||
|
|
||||
|
if (solution == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(solution)); |
||||
|
} |
||||
|
|
||||
|
if (rowCount * nRhs != rhs.Length) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(rhs)); |
||||
|
} |
||||
|
|
||||
|
if (columnCount * nRhs != solution.Length) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(solution)); |
||||
|
} |
||||
|
|
||||
|
var error = SafeNativeMethods.z_dss_solve((int)matrixStructure, (int)matrixType, (int)systemType, |
||||
|
rowCount, columnCount, nonZerosCount, rowPointers, columnIndices, values, |
||||
|
nRhs, rhs, solution); |
||||
|
return (DssStatus)error; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endif
|
||||
@ -0,0 +1,84 @@ |
|||||
|
#if NATIVE
|
||||
|
|
||||
|
using MathNet.Numerics.Properties; |
||||
|
using MathNet.Numerics.Providers.Common.Mkl; |
||||
|
using System; |
||||
|
using System.Security; |
||||
|
|
||||
|
namespace MathNet.Numerics.Providers.SparseSolver.Mkl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Intel's Math Kernel Library (MKL) direct sparse solver provider.
|
||||
|
/// </summary>
|
||||
|
internal partial class MklSparseSolverProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Solves sparse linear systems of equations, <b>AX = B</b>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="matrixStructure">The symmetricity of the matrix. For a symmetric matrix, only upper ot lower triangular matrix is used.</param>
|
||||
|
/// <param name="matrixType">The definiteness of the matrix.</param>
|
||||
|
/// <param name="systemType">The type of the systems.</param>
|
||||
|
/// <param name="rowCount">The number of rows of matrix.</param>
|
||||
|
/// <param name="columnCount">The number of columns of matrix.</param>
|
||||
|
/// <param name="nonZerosCount">The number of non zero elements of matrix.</param>
|
||||
|
/// <param name="rowPointers">The array containing the row indices of the existing rows.</param>
|
||||
|
/// <param name="columnIndices">The array containing the column indices of the non-zero values</param>
|
||||
|
/// <param name="values">The array that contains the non-zero elements of matrix. No diagonal element can be ommitted. </param>
|
||||
|
/// <param name="nRhs">The number of columns of the right hand side matrix.</param>
|
||||
|
/// <param name="rhs">The right hand side matrix</param>
|
||||
|
/// <param name="solution">The left hand side matrix</param>
|
||||
|
/// <returns>The status of the solver.</returns>
|
||||
|
[SecuritySafeCritical] |
||||
|
public override DssStatus Solve(DssMatrixStructure matrixStructure, DssMatrixType matrixType, DssSystemType systemType, |
||||
|
int rowCount, int columnCount, int nonZerosCount, int[] rowPointers, int[] columnIndices, Complex32[] values, |
||||
|
int nRhs, Complex32[] rhs, Complex32[] solution) |
||||
|
{ |
||||
|
if (rowCount != columnCount) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentMatrixSymmetric); |
||||
|
} |
||||
|
|
||||
|
if (rowPointers == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(rowPointers)); |
||||
|
} |
||||
|
|
||||
|
if (columnIndices == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(columnIndices)); |
||||
|
} |
||||
|
|
||||
|
if (values == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(values)); |
||||
|
} |
||||
|
|
||||
|
if (rhs == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(rhs)); |
||||
|
} |
||||
|
|
||||
|
if (solution == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(solution)); |
||||
|
} |
||||
|
|
||||
|
if (rowCount * nRhs != rhs.Length) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(rhs)); |
||||
|
} |
||||
|
|
||||
|
if (columnCount * nRhs != solution.Length) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(solution)); |
||||
|
} |
||||
|
|
||||
|
var error = SafeNativeMethods.c_dss_solve((int)matrixStructure, (int)matrixType, (int)systemType, |
||||
|
rowCount, columnCount, nonZerosCount, rowPointers, columnIndices, values, |
||||
|
nRhs, rhs, solution); |
||||
|
return (DssStatus)error; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endif
|
||||
@ -0,0 +1,84 @@ |
|||||
|
#if NATIVE
|
||||
|
|
||||
|
using MathNet.Numerics.Properties; |
||||
|
using MathNet.Numerics.Providers.Common.Mkl; |
||||
|
using System; |
||||
|
using System.Security; |
||||
|
|
||||
|
namespace MathNet.Numerics.Providers.SparseSolver.Mkl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Intel's Math Kernel Library (MKL) direct sparse solver provider.
|
||||
|
/// </summary>
|
||||
|
internal partial class MklSparseSolverProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Solves sparse linear systems of equations, <b>AX = B</b>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="matrixStructure">The symmetricity of the matrix. For a symmetric matrix, only upper ot lower triangular matrix is used.</param>
|
||||
|
/// <param name="matrixType">The definiteness of the matrix.</param>
|
||||
|
/// <param name="systemType">The type of the systems.</param>
|
||||
|
/// <param name="rowCount">The number of rows of matrix.</param>
|
||||
|
/// <param name="columnCount">The number of columns of matrix.</param>
|
||||
|
/// <param name="nonZerosCount">The number of non zero elements of matrix.</param>
|
||||
|
/// <param name="rowPointers">The array containing the row indices of the existing rows.</param>
|
||||
|
/// <param name="columnIndices">The array containing the column indices of the non-zero values</param>
|
||||
|
/// <param name="values">The array that contains the non-zero elements of matrix. No diagonal element can be ommitted. </param>
|
||||
|
/// <param name="nRhs">The number of columns of the right hand side matrix.</param>
|
||||
|
/// <param name="rhs">The right hand side matrix</param>
|
||||
|
/// <param name="solution">The left hand side matrix</param>
|
||||
|
/// <returns>The status of the solver.</returns>
|
||||
|
[SecuritySafeCritical] |
||||
|
public override DssStatus Solve(DssMatrixStructure matrixStructure, DssMatrixType matrixType, DssSystemType systemType, |
||||
|
int rowCount, int columnCount, int nonZerosCount, int[] rowPointers, int[] columnIndices, double[] values, |
||||
|
int nRhs, double[] rhs, double[] solution) |
||||
|
{ |
||||
|
if (rowCount != columnCount) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentMatrixSymmetric); |
||||
|
} |
||||
|
|
||||
|
if (rowPointers == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(rowPointers)); |
||||
|
} |
||||
|
|
||||
|
if (columnIndices == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(columnIndices)); |
||||
|
} |
||||
|
|
||||
|
if (values == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(values)); |
||||
|
} |
||||
|
|
||||
|
if (rhs == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(rhs)); |
||||
|
} |
||||
|
|
||||
|
if (solution == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(solution)); |
||||
|
} |
||||
|
|
||||
|
if (rowCount * nRhs != rhs.Length) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(rhs)); |
||||
|
} |
||||
|
|
||||
|
if (columnCount * nRhs != solution.Length) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(solution)); |
||||
|
} |
||||
|
|
||||
|
var error = SafeNativeMethods.d_dss_solve((int)matrixStructure, (int)matrixType, (int)systemType, |
||||
|
rowCount, columnCount, nonZerosCount, rowPointers, columnIndices, values, |
||||
|
nRhs, rhs, solution); |
||||
|
return (DssStatus)error; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endif
|
||||
@ -0,0 +1,83 @@ |
|||||
|
#if NATIVE
|
||||
|
|
||||
|
using MathNet.Numerics.Properties; |
||||
|
using MathNet.Numerics.Providers.Common.Mkl; |
||||
|
using System; |
||||
|
using System.Security; |
||||
|
|
||||
|
namespace MathNet.Numerics.Providers.SparseSolver.Mkl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Intel's Math Kernel Library (MKL) direct sparse solver provider.
|
||||
|
/// </summary>
|
||||
|
internal partial class MklSparseSolverProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Solves sparse linear systems of equations, <b>AX = B</b>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="matrixStructure">The symmetricity of the matrix. For a symmetric matrix, only upper ot lower triangular matrix is used.</param>
|
||||
|
/// <param name="matrixType">The definiteness of the matrix.</param>
|
||||
|
/// <param name="systemType">The type of the systems.</param>
|
||||
|
/// <param name="rowCount">The number of rows of matrix.</param>
|
||||
|
/// <param name="columnCount">The number of columns of matrix.</param>
|
||||
|
/// <param name="nonZerosCount">The number of non zero elements of matrix.</param>
|
||||
|
/// <param name="rowPointers">The array containing the row indices of the existing rows.</param>
|
||||
|
/// <param name="columnIndices">The array containing the column indices of the non-zero values</param>
|
||||
|
/// <param name="values">The array that contains the non-zero elements of matrix. No diagonal element can be ommitted. </param>
|
||||
|
/// <param name="nRhs">The number of columns of the right hand side matrix.</param>
|
||||
|
/// <param name="rhs">The right hand side matrix</param>
|
||||
|
/// <param name="solution">The left hand side matrix</param>
|
||||
|
/// <returns>The status of the solver.</returns>
|
||||
|
[SecuritySafeCritical] |
||||
|
public override DssStatus Solve(DssMatrixStructure matrixStructure, DssMatrixType matrixType, DssSystemType systemType, |
||||
|
int rowCount, int columnCount, int nonZerosCount, int[] rowPointers, int[] columnIndices, float[] values, |
||||
|
int nRhs, float[] rhs, float[] solution) |
||||
|
{ |
||||
|
if (rowCount != columnCount) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentMatrixSymmetric); |
||||
|
} |
||||
|
|
||||
|
if (rowPointers == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(rowPointers)); |
||||
|
} |
||||
|
|
||||
|
if (columnIndices == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(columnIndices)); |
||||
|
} |
||||
|
|
||||
|
if (values == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(values)); |
||||
|
} |
||||
|
|
||||
|
if (rhs == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(rhs)); |
||||
|
} |
||||
|
|
||||
|
if (solution == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(solution)); |
||||
|
} |
||||
|
|
||||
|
if (rowCount * nRhs != rhs.Length) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(rhs)); |
||||
|
} |
||||
|
|
||||
|
if (columnCount * nRhs != solution.Length) |
||||
|
{ |
||||
|
throw new ArgumentException(Resources.ArgumentArraysSameLength, nameof(solution)); |
||||
|
} |
||||
|
|
||||
|
var error = SafeNativeMethods.s_dss_solve((int)matrixStructure, (int)matrixType, (int)systemType, |
||||
|
rowCount, columnCount, nonZerosCount, rowPointers, columnIndices, values, |
||||
|
nRhs, rhs, solution); |
||||
|
return (DssStatus)error; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -0,0 +1,77 @@ |
|||||
|
#if NATIVE
|
||||
|
|
||||
|
using MathNet.Numerics.Providers.Common.Mkl; |
||||
|
using System; |
||||
|
|
||||
|
namespace MathNet.Numerics.Providers.SparseSolver.Mkl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Intel's Math Kernel Library (MKL) sparse solver provider.
|
||||
|
/// </summary>
|
||||
|
internal partial class MklSparseSolverProvider : Managed.ManagedSparseSolverProvider, IDisposable |
||||
|
{ |
||||
|
const int MinimumCompatibleRevision = 12; |
||||
|
|
||||
|
readonly string _hintPath; |
||||
|
|
||||
|
int sparseSolverMajor; |
||||
|
int sparseSolverMinor; |
||||
|
|
||||
|
/// <param name="hintPath">Hint path where to look for the native binaries</param>
|
||||
|
internal MklSparseSolverProvider(string hintPath) |
||||
|
{ |
||||
|
_hintPath = hintPath; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Try to find out whether the provider is available, at least in principle.
|
||||
|
/// Verification may still fail if available, but it will certainly fail if unavailable.
|
||||
|
/// </summary>
|
||||
|
public override bool IsAvailable() |
||||
|
{ |
||||
|
return MklProvider.IsAvailable(hintPath: _hintPath); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initialize and verify that the provided is indeed available.
|
||||
|
/// If calling this method fails, consider to fall back to alternatives like the managed provider.
|
||||
|
/// </summary>
|
||||
|
public override void InitializeVerify() |
||||
|
{ |
||||
|
int revision = MklProvider.Load(_hintPath); |
||||
|
if (revision < MinimumCompatibleRevision) |
||||
|
{ |
||||
|
throw new NotSupportedException($"MKL Native Provider revision r{revision} is too old. Consider upgrading to a newer version. Revision r{MinimumCompatibleRevision} and newer are supported."); |
||||
|
} |
||||
|
|
||||
|
sparseSolverMajor = SafeNativeMethods.query_capability((int)ProviderCapability.SparseSolverMajor); |
||||
|
sparseSolverMinor = SafeNativeMethods.query_capability((int)ProviderCapability.SparseSolverMinor); |
||||
|
if (!(sparseSolverMajor == 1 && sparseSolverMinor >= 0)) |
||||
|
{ |
||||
|
throw new NotSupportedException(string.Format("MKL Native Provider not compatible. Expecting sparse solver v1 but provider implements v{0}.", sparseSolverMajor)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Frees memory buffers, caches and handles allocated in or to the provider.
|
||||
|
/// Does not unload the provider itself, it is still usable afterwards.
|
||||
|
/// </summary>
|
||||
|
public override void FreeResources() |
||||
|
{ |
||||
|
MklProvider.FreeResources(); |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return MklProvider.Describe(); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
FreeResources(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#endif
|
||||
|
|
||||
@ -0,0 +1,165 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace MathNet.Numerics.Providers.SparseSolver |
||||
|
{ |
||||
|
public static class SparseSolverControl |
||||
|
{ |
||||
|
const string EnvVarSSProvider = "MathNetNumericsSSProvider"; |
||||
|
const string EnvVarSSProviderPath = "MathNetNumericsSSProviderPath"; |
||||
|
|
||||
|
static ISparseSolverProvider _sparseSolverProvider; |
||||
|
static readonly object StaticLock = new object(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the sparse solver provider. Consider to use UseNativeMKL or UseManaged instead.
|
||||
|
/// </summary>
|
||||
|
/// <value>The linear algebra provider.</value>
|
||||
|
public static ISparseSolverProvider Provider |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (_sparseSolverProvider == null) |
||||
|
{ |
||||
|
lock (StaticLock) |
||||
|
{ |
||||
|
if (_sparseSolverProvider == null) |
||||
|
{ |
||||
|
UseDefault(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return _sparseSolverProvider; |
||||
|
} |
||||
|
set |
||||
|
{ |
||||
|
value.InitializeVerify(); |
||||
|
|
||||
|
// only actually set if verification did not throw
|
||||
|
_sparseSolverProvider = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Optional path to try to load native provider binaries from.
|
||||
|
/// If not set, Numerics will fall back to the environment variable
|
||||
|
/// `MathNetNumericsSSProviderPath` or the default probing paths.
|
||||
|
/// </summary>
|
||||
|
public static string HintPath { get; set; } |
||||
|
|
||||
|
public static ISparseSolverProvider CreateManaged() |
||||
|
{ |
||||
|
return new Managed.ManagedSparseSolverProvider(); |
||||
|
} |
||||
|
|
||||
|
public static void UseManaged() |
||||
|
{ |
||||
|
Provider = CreateManaged(); |
||||
|
} |
||||
|
|
||||
|
#if NATIVE
|
||||
|
public static ISparseSolverProvider CreateNativeMKL() |
||||
|
{ |
||||
|
return new Mkl.MklSparseSolverProvider(GetCombinedHintPath()); |
||||
|
} |
||||
|
|
||||
|
public static void UseNativeMKL() |
||||
|
{ |
||||
|
Provider = CreateNativeMKL(); |
||||
|
} |
||||
|
|
||||
|
public static bool TryUseNativeMKL() |
||||
|
{ |
||||
|
return TryUse(CreateNativeMKL()); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Try to use a native provider, if available.
|
||||
|
/// </summary>
|
||||
|
public static bool TryUseNative() |
||||
|
{ |
||||
|
return TryUseNativeMKL(); |
||||
|
} |
||||
|
#endif
|
||||
|
|
||||
|
static bool TryUse(ISparseSolverProvider provider) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
if (!provider.IsAvailable()) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
Provider = provider; |
||||
|
return true; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
// intentionally swallow exceptions here - use the explicit variants if you're interested in why
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Use the best provider available.
|
||||
|
/// </summary>
|
||||
|
public static void UseBest() |
||||
|
{ |
||||
|
#if NATIVE
|
||||
|
if (!TryUseNative()) |
||||
|
{ |
||||
|
UseManaged(); |
||||
|
} |
||||
|
#else
|
||||
|
UseManaged(); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Use a specific provider if configured, e.g. using the
|
||||
|
/// "MathNetNumericsDSSProvider" environment variable,
|
||||
|
/// or fall back to the best provider.
|
||||
|
/// </summary>
|
||||
|
public static void UseDefault() |
||||
|
{ |
||||
|
#if NATIVE
|
||||
|
var value = Environment.GetEnvironmentVariable(EnvVarSSProvider); |
||||
|
switch (value != null ? value.ToUpperInvariant() : string.Empty) |
||||
|
{ |
||||
|
|
||||
|
case "MKL": |
||||
|
UseNativeMKL(); |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
UseBest(); |
||||
|
break; |
||||
|
} |
||||
|
#else
|
||||
|
UseBest(); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
public static void FreeResources() |
||||
|
{ |
||||
|
Provider.FreeResources(); |
||||
|
} |
||||
|
|
||||
|
static string GetCombinedHintPath() |
||||
|
{ |
||||
|
if (!String.IsNullOrEmpty(HintPath)) |
||||
|
{ |
||||
|
return HintPath; |
||||
|
} |
||||
|
|
||||
|
var value = Environment.GetEnvironmentVariable(EnvVarSSProviderPath); |
||||
|
if (!String.IsNullOrEmpty(value)) |
||||
|
{ |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue