Note:
XDGagglomeration.ipynb
.
One can directly load this into Jupyter to interactively work with the following code examples.BoSSSpad.dll
is required.
You must either set #r "BoSSSpad.dll"
to something which is appropirate for your computer
(e.g. C:\Program Files (x86)\FDY\BoSSS\bin\Release\net5.0\BoSSSpad.dll
if you installed the binary distribution),
or, if you are working with the source code, you must compile BoSSSpad
and put it side-by-side to this worksheet file
(from the original location in the repository, you can use the scripts getbossspad.sh
, resp. getbossspad.bat
).//#r "C:\experimental\public\src\L4-application\BoSSSpad\bin\Debug\net5.0\BoSSSpad.dll"
#r "BoSSSpad.dll"
using static BoSSS.Application.BoSSSpad.BoSSSshell;
Init();
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using NUnit.Framework;
using ilPSP;
using ilPSP.Utils;
using ilPSP.LinSolvers;
using BoSSS.Platform;
using BoSSS.Foundation;
using BoSSS.Foundation.Grid;
using BoSSS.Foundation.Grid.Classic;
using BoSSS.Foundation.XDG;
using BoSSS.Foundation.IO;
using BoSSS.Solution;
using BoSSS.Solution.AdvancedSolvers;
using BoSSS.Solution.Control;
using BoSSS.Solution.GridImport;
using BoSSS.Solution.Statistic;
using BoSSS.Solution.Utils;
using BoSSS.Solution.Gnuplot;
using BoSSS.Application.BoSSSpad;
using BoSSS.Application.XNSE_Solver;
In this worksheet, Scalar Advection will be used as an example; Therefore it is required to implement the respective equation components (aka. flux) for the bulk and the interface.
Below, an upwind formulation for the equation $$ \text{div}(\vec{V} c) = 0 \text{ in } \Omega = (0,2) \times (0,1) $$ with the boundary condition $$ c = c_{\text{in}} \text{ for } \vec{V} \cdot \vec{n}_{\partial \Omega} < 0 $$ for the inflow part of the boundary is implemented:
static class CommonStuff {
public static double a = 1;
public static double FlowFunc(double x) {
return a;
}
public static Vector FlowField(double[] X) {
var flowfield = new Vector(2);
flowfield.x = FlowFunc(X[1]);
flowfield.y = 1;
return flowfield;
}
}
class ScalarTransportFlux : ISpeciesFilter, IEdgeForm, IVolumeForm {
public double cin;
public ScalarTransportFlux(string spc, double _cin) {
this.cin = _cin;
this.spc = spc;
}
protected double BorderEdgeFlux(double time, double[] x, double[] normal, byte EdgeTag, double[] Uin, int jEdge) {
Vector n = normal;
var flowfield = CommonStuff.FlowField(x);
double[] Uout = new double[1];
if(flowfield * n < 0){ // inflow
Uout[0] = cin;
} else {
Uout = Uin;
}
return InnerEdgeFlux(time, x, normal, Uin, Uout, jEdge);
}
protected double InnerEdgeFlux(double time, double[] x, double[] normal, double[] Uin, double[] Uout, int jEdge) {
Vector n = normal;
var flowfield = CommonStuff.FlowField(x);
if(flowfield * n > 0)
return (flowfield * Uin[0]) * n;
else
return (flowfield * Uout[0]) * n;
}
protected void Flux(double time, double[] x, double[] U, double[] output) {
var flowfield = CommonStuff.FlowField(x);
Vector o;
o = flowfield * U[0];
output[0] = o.x;
output[1] = o.y;
}
public double InnerEdgeForm(ref CommonParams inp, double[] _uIN, double[] _uOUT, double[,] _Grad_uIN, double[,] _Grad_uOUT, double _vIN, double _vOUT, double[] _Grad_vIN, double[] _Grad_vOUT) {
return InnerEdgeFlux(inp.time, inp.X, inp.Normal, _uIN, _uOUT,inp.iEdge) * (_vIN-_vOUT);
}
public double BoundaryEdgeForm(ref CommonParamsBnd inp, double[] _uA, double[,] _Grad_uA, double _vA, double[] _Grad_vA) {
return BorderEdgeFlux(inp.time, inp.X, inp.Normal, inp.EdgeTag, _uA, inp.iEdge)*_vA;
}
public double VolumeForm(ref CommonParamsVol cpv, double[] U, double[,] GradU, double V, double[] GradV) {
double[] output= new double[2];
Flux(cpv.time, cpv.Xglobal, U, output);
return -1*(output * (Vector) GradV);
}
public IList<string> ArgumentOrdering => new string[] { "c" };
string spc="";
public string ValidSpecies => spc;
public TermActivationFlags BoundaryEdgeTerms => TermActivationFlags.UxV | TermActivationFlags.V;
public TermActivationFlags InnerEdgeTerms => TermActivationFlags.UxV;
public TermActivationFlags VolTerms => TermActivationFlags.UxGradV;
public IList<string> ParameterOrdering => null;
}
public class UpwindFlux_XDG_Interface : ILevelSetForm, ILevelSetEquationComponentCoefficient {
public int LevelSetIndex => 0;
public string PositiveSpecies => "A";
public string NegativeSpecies => "B";
public TermActivationFlags LevelSetTerms {
get {
return TermActivationFlags.UxV;
}
}
public IList<string> ArgumentOrdering {
get {
return new string[] { "c" };
}
}
public double InnerEdgeForm(ref CommonParams inp, double[] uA, double[] uB, double[,] Grad_uA, double[,] Grad_uB, double vA, double vB, double[] Grad_vA, double[] Grad_vB) {
// Flux across the interface
// Took Regular Flux
Vector n = new Vector(2); n.x = inp.Normal.x; n.y = inp.Normal.y;
var vel = CommonStuff.FlowField(inp.X);
if(vel * n > 0)
return (vel * uA[0]) * n * (vA - vB);
else
return (vel * uB[0]) * n * (vA - vB);
//throw new NotSupportedException("Has to be checked again.");
}
public void CoefficientUpdate(CoefficientSet csA, CoefficientSet csB, int[] DomainDGdeg, int TestDGdeg) {
return;
}
public IList<string> ParameterOrdering => null;
}
We discretize the domain $\Omega$ with two cells
$$
K_1 =(0,1) \times (0,1),~K_2 =(1,2) \times (0,1)
$$
and initialize a field of degree 1 for the LevelSet and a XDGField
of degree 0.
double[] xNodes = GenericBlas.Linspace(0, 2, 3);
double[] yNodes = GenericBlas.Linspace(0, 1, 2);
var grid = Grid2D.Cartesian2DGrid(xNodes, yNodes);
int dgDegree = 0;
int LsDegree = 1;
var LevelSet = new LevelSet(new Basis(grid, LsDegree), "LevelSet");
var LsTrk = new LevelSetTracker(grid.GridData, XQuadFactoryHelper.MomentFittingVariants.Saye, 1, new string[] { "A", "B" }, LevelSet);
XDGBasis basis = new XDGBasis(LsTrk, dgDegree);
XDGField c = new XDGField(basis, "c");
var mapping = c.Mapping;
The LevelSet is set as such that the 0-isocontour of the LevelSet leads to a very small cutCell.
$$ \varphi (x,t)= -x+0.9+1.1t \quad $$We set the XDGField to
$$c_0(c,t)=1 $$double x0 = 0.9;
double a = 1.0;
LevelSet.ProjectField((x, t) => -1.0*(x- x0-a*t));
LsTrk.UpdateTracker(0.0);
c.GetSpeciesShadowField("A").ProjectField((_2D)((x, t) => 1));
c.GetSpeciesShadowField("B").ProjectField((_2D)((x, t) => 1));
Here we plot the field we just initalized.
bool plot = true;
if (plot) {
var files = Directory.GetFiles(".", "*.plt");
Array.ForEach(files, file => File.Delete(file));
Tecplot("SDC_test_Agglo_" + 0, 0.0, 4, LevelSet, c);
}
Supersampling 4 requested, but limiting to 3 because higher values would very likely exceed this computers memory. Note: Higher supersampling values are supported by external plot application, or by using e.g. the Tecplot class directly. Writing output file C:\Gitlab-Runner\builds\PYSUsL3w\1\kummer\bosss-public\src\L4-application\PublicTestRunner\bin\Release\net6.0\SDC_test_Agglo_0... done.
An XDG method typically produces very small cut cells; Indeed, depending on the position of the level set, cut cells may become arbitrarily small. This typically leads to unstable methods, for both, implicit and explicit solvers.
Therefore, stabilization techniques are required; One stabilization technique is cell-agglomeration, where small cut cells are agglomerated with their largest neighbor (in the same species). Then, the XDG-problem is not solved in the original XDG-space (defined through the cut-cell mesh), but in the agglomerated XDG-space (defined through the agglomerated cut-cell-mesh).
(An alternative stabilization technique, which is not supported in BoSSS are e.g. ghost penalties.)
Here We initialize the CellAglomerator. After being initialized we can invesitgate the Volumes of the CutCells. A CutCell $K_{i,s}$ will be agglomerated (merged with another cell) if $$ \frac{\vert K_{i,s} \vert}{\vert K_{i} \vert} < agglo_{trsh}$$ In other words, if its volume divided by the Volume of the correspoding Background Cell is less than the agglomeration treshhold.
By calculating we we have:
which we can also obtain from our agglomerator.
var Agg = LsTrk.GetAgglomerator(LsTrk.SpeciesIdS.ToArray(), 6, __AgglomerationTreshold:0.1);
var CutCellVolumes = Agg.CutCellVolumes;
var CutCellVolumesA = CutCellVolumes[LsTrk.SpeciesIdS[0]].To1DArray();
var CutCellVolumesB = CutCellVolumes[LsTrk.SpeciesIdS[1]].To1DArray();
Console.WriteLine("Cell Volume K_1A: " + CutCellVolumesA[0]);
Console.WriteLine("Cell Volume K_1B: " + CutCellVolumesB[0]);
Console.WriteLine("Cell Volume K_2A: " + CutCellVolumesA[1]);
Console.WriteLine("Cell Volume K_2B: " + CutCellVolumesB[1]);
Cell Volume K_1A: 0.5999999999999999 Cell Volume K_1B: 0.9950000000000001 Cell Volume K_2A: 0.5999999999999999 Cell Volume K_2B: 0.40499999999999997
Note that the cell volumes (as well as other cell metrics) provided by the agglomerator are valid for the agglomerated mesh. These metrics will/should be used e.g. in the penalty parameters used for Symmetric Interior Penalty (SIP).
It is, however, also possible to access the non-agglomerated metrics:
var NonAggCutCellVolumesA = Agg.NonAgglomeratedMetrics.CutCellVolumes[LsTrk.SpeciesIdS[0]].To1DArray();
var NonAggCutCellVolumesB = Agg.NonAgglomeratedMetrics.CutCellVolumes[LsTrk.SpeciesIdS[1]].To1DArray();
Console.WriteLine("Non-agglomerated Cut Cell Volume K_1A: " + NonAggCutCellVolumesA[0]);
Console.WriteLine("Non-agglomerated Cut Cell Volume K_1B: " + NonAggCutCellVolumesB[0]);
Console.WriteLine("Non-agglomerated Cut Cell Volume K_2A: " + NonAggCutCellVolumesA[1]);
Console.WriteLine("Non-agglomerated Cut Cell Volume K_2B: " + NonAggCutCellVolumesB[1]);
Non-agglomerated Cut Cell Volume K_1A: 0.004999999999999939 Non-agglomerated Cut Cell Volume K_1B: 0.9950000000000001 Non-agglomerated Cut Cell Volume K_2A: 0.595 Non-agglomerated Cut Cell Volume K_2B: 0.40499999999999997
so we see that in our setting we would want to agglomerate Cut Cell $K_{2A}$ as the fraction is less then our treshhold (0.1)
Sidenote:¶
the standard way of handing length scales to an equation component involves two steps:
- Set the
XEvaluatorBase.CellLengthScales
to the appropriate length scale before evaluation of the operator, resp. matrix assembly- Implementing the
IEquationComponentCoefficient
interfcae for bulk components and theILevelSetEquationComponentCoefficient
interface for level-set components. The length scales specified (see below) are passed to theCoefficientUpdate(...)
method.
For XDG, the mass matrix is quite expensive to compute.
Furthermore, it also depends on the chosen quadrature order: E.g. a mass matrix for degree 2, computed with 4-th order rules is typically slightly different from a the one computed with a 6-th order rule. In order to compute it we need an Object called MassMatrixFactory
.
var MMF = LsTrk.GetXDGSpaceMetrics(LsTrk.SpeciesIdS.ToArray(),6).MassMatrixFactory;
Note, that in the simple setting presented here, the entries of the mass matrix are equal to the (non-agglomerated) cut-cell volumes.This is because:
var MM = MMF.GetMassMatrix(mapping);
Console.WriteLine(MM.ToStringDense())
5.000000E-003 0 0 0 0 9.950000E-001 0 0 0 0 5.950000E-001 0 0 0 0 4.050000E-001
The mass-matrix factory can also be used to obtain the inverse of the mass-matrix; these are also cached, since computing the inverse blocks is, for higher DG-orders quite expensive.
var invMM = MMF.GetMassMatrix(mapping, inverse: true);
Console.WriteLine(invMM.ToStringDense())
2.000000E+002 0 0 0 0 1.005025E+000 0 0 0 0 1.680672E+000 0 0 0 0 2.469136E+000
Definition of the operator:
double cin = 1;
var Op = new XDifferentialOperatorMk2(new string[] {"c"}, new string[] { "codom1" }, (int[] a_1, int[] b_1, int[] c_1) => 4 , new string[] { "A", "B" });
//Bulk
Op.EquationComponents["codom1"].Add(new ScalarTransportFlux("A", cin));
Op.EquationComponents["codom1"].Add(new ScalarTransportFlux("B", cin));
//Interface
Op.EquationComponents["codom1"].Add(new UpwindFlux_XDG_Interface());
Op.AgglomerationThreshold = 0.1;
Op.IsLinear = true;
Op.Commit();
Since the input field c
is constant and complies with the boundary condition, the expected residual is zero.
c.CoordinateVector
index | value |
---|---|
0 | 1 |
1 | 1 |
2 | 1 |
3 | 1 |
var Residual = new double[mapping.LocalLength];
var ev = Op.GetEvaluatorEx(mapping.Fields, null, mapping);
ev.Evaluate(1.0, 0.0, Residual);
Residual
index | value |
---|---|
0 | 0 |
1 | 0 |
2 | 7.355227538141662E-16 |
3 | -8.881784197001252E-16 |
Here we assemble the Operator Matrix and the RHS
var ev = Op.GetMatrixBuilder(mapping, null, mapping);
var OpMatrix = new BlockMsrMatrix(mapping);
var OpAffine = new double[mapping.LocalLength];
ev.ComputeMatrix(OpMatrix, OpAffine);
Console.WriteLine(OpMatrix.ToStringDense());
//OpAffine.SaveToStream(Console.Out);
1.000000E-001 0 0 0 -1.570092E-017 1.900000E+000 0 0 -1.000000E-001 0 1.100000E+000 0 0 -9.000000E-001 0 9.000000E-001
Then we can solve the implicit system:
$$Op(c)*c=-r(c)\approx \text{RHS}$$var RHS = OpAffine.CloneAs();
RHS.ScaleV(-1.0);
var Sol = OpMatrix.Solve_Direct(RHS);
Sol
index | value |
---|---|
0 | 0.9999999999999999 |
1 | 1 |
2 | 0.9999999999999993 |
3 | 1.000000000000001 |
Assert.Less(Sol.L2Dist(c.CoordinateVector), 1.0e-10, "Exact solution is wrong.");
Next we will solve the same system but in the Aggregated space. Hence, we first need to Transform our Matrix and the RHS. This is done using the method ManipulateMatrixAndRHS(...)
var AggOpMatrix = OpMatrix.CloneAs();
var AggOpAffine = OpAffine.CloneAs();
Agg.ManipulateMatrixAndRHS(AggOpMatrix, AggOpAffine, mapping, mapping);
Console.WriteLine(AggOpMatrix.ToStringDense());
AggOpAffine.SaveToStream(Console.Out);
0 0 0 0 0 1.900000E+000 -1.570092E-017 0 0 0 1.100000E+000 0 0 -9.000000E-001 0 9.000000E-001 0 -1.9000000000000017 -1.0999999999999999 0
As you can see BoSSS keeps the original size of the matrix. So as the cells being agglmerated vanish, we obtain zero-rows and zero-columns for these respective cells. In order to reobtain a uniquely solvable system we therefore need to artificially add a 1 on the diagonal, for the agglomerated cells.
AggOpMatrix[0, 0] = 1.0;
var AggRHS = AggOpAffine.CloneAs();
AggRHS.ScaleV(-1.0);
var AggSol = OpMatrix.Solve_Direct(AggRHS);
AggSol
index | value |
---|---|
0 | -0 |
1 | 1 |
2 | 0.9999999999999993 |
3 | 1.000000000000001 |
Next, the solution in the agglomerated space can be extrapolated to the original space using the Extrapolate(...)
-method .
During the extrapolation, the agglomerated coordinate will be overwritten; we illustrate this by setting the respective entry to NaN
:
AggSol[0] = double.NaN;
Agg.Extrapolate(AggSol, mapping);
AggSol
index | value |
---|---|
0 | 0.9999999999999993 |
1 | 1 |
2 | 0.9999999999999993 |
3 | 1.000000000000001 |
NUnit.Framework.Assert.Less(AggSol.L2Dist(c.CoordinateVector), 1.0e-10, "Exact solution is wrong.");
The mass matrix of the agglomerated space can also be obtained by passing the original mass matrix to the
ManipulateMatrixAndRHS(...)
method:
Agg.ManipulateMatrixAndRHS(MM, default(double[]), mapping, mapping);
Console.WriteLine(MM.ToStringDense());
0 0 0 0 0 9.950000E-001 0 0 0 0 6.000000E-001 0 0 0 0 4.050000E-001
One can verify the extrapolation by asserting that the jump norm on agglomerated edges for e.g. a random field is zero.
var aggRandom = new XDGField(basis);
aggRandom.CoordinateVector.FillRandom(0);
aggRandom.CoordinateVector[0] = double.NaN;
aggRandom.CoordinateVector.ToArray()
index | value |
---|---|
0 | NaN |
1 | 0.8173253595909687 |
2 | 0.7680226893946634 |
3 | 0.5581611914365372 |
Agg.Extrapolate(aggRandom.CoordinateVector,aggRandom.Mapping);
Next, we obtain the agglomerated edges from the Cell Agglomerator for a specific species.
First we get the cell agglomerator for the "A" species using GetAgglomerator(LsTrk.GetSpeciesId("A"))
. Then we acces to AgglomerationEdges by AggInfo.AgglomerationEdges
.
var AggEdges_SpcA = Agg.GetAgglomerator(LsTrk.GetSpeciesId("A")).AggInfo.AgglomerationEdges;
AggEdges_SpcA
index | JE | Elements | i0 | Len |
---|---|---|---|---|
0 | 7 | [ 6 ] | 6 | 1 |
Assert.Greater(AggEdges_SpcA.NoOfItemsLocally, 0, "there should be at least 1 aggomeration edge");
Then we can compute the JumpNorm on those edges by
double jmpNormAcrossAgg = aggRandom.GetSpeciesShadowField("A").JumpNorm(AggEdges_SpcA);
jmpNormAcrossAgg
Assert.Less(jmpNormAcrossAgg, 1.0e-10, "Extrapolation produced jump across agglomeration edge.");
The matrix of the restiction operation is can be accessed by:
var RestrMtx = Agg.GetRowManipulationMatrix(mapping);
RestrMtx.ToStringDense()
0 0 0 0 0 1.000000E+000 0 0 1.000000E+000 0 1.000000E+000 0 0 0 0 1.000000E+000
One can also observe that the Extrapolation (aka. Prolongation, aka. Injection) is the transpose of this:
var ExpolMtx = Agg.GetColManipulationMatrix(mapping);
ExpolMtx.ToStringDense()
0 0 1.000000E+000 0 0 1.000000E+000 0 0 0 0 1.000000E+000 0 0 0 0 1.000000E+000
Assert.IsTrue((RestrMtx - ExpolMtx.Transpose()).InfNorm() == 0.0, "Resriction and Extrapolation Matrix must be transpose");
However, even when ignoring the zero row and column, which correspond to an un-used DOF in the agglomerated domain, one can observe that the Solution Extrapolation RHS-Restriction/Projection
(RestrMtx*ExpolMtx).ToStringDense()
0 0 0 0 0 1.000000E+000 0 0 0 0 2.000000E+000 0 0 0 0 1.000000E+000
In the next section we want to focus on the projection of a solution $u(x)$ from the original space to the agglomerated. While the injection is canonoical (i.e. any member of the aggregate XDG space must also be a member of the original XDG space, due to the sub-space structure), the restriction is not.
The $L^2$-Projection can be constructed in the following way; Let $Q^T = \text{RestrMtx}$ and $Q = \text{ExpolMtx}$; for the theoretical consideration here, we ommit the zero-rows and columns.
The solution is given as a coordinate vector $\tilde{u} $ such that $u(x)= \underline{\phi} \cdot \tilde{u}.$ We now want to find a coordinatevector corresponding to projection onto the agglomerated space that is $\underline{\phi}^A \cdot \tilde{u}^A$.
The agglomerate XDG basis $ \underline{\phi}^A $ is related to the original basis $\underline{\phi}$ through the relation $$ \underline{\phi}^A = \underline{\phi} Q . $$ Furthermore, let $M = \langle \underline{\phi}^T , \underline{\phi} \rangle $ be the mass matrix of the original XDG basis.
Then the mass matrix of $\underline{\phi}^A$ is given as $$ M^A := \langle {\underline{\phi}^{A}}^T , \underline{\phi}^{A} \rangle = \langle Q^T \underline{\phi}^T , \underline{\phi} Q \rangle = Q^T \langle \underline{\phi}^T , \underline{\phi} \rangle Q = Q^T M Q. $$
Now, the member $\underline{\phi}^A \cdot \tilde{u}^A$ of the agglomerated XDG space should represent the $L^2$-Projection of $\underline{\phi} \cdot \tilde{u}$ from the original XDG space. The ansatz for the projection is $$ \langle {\underline{\phi}^{A}}^T, \underline{\phi}^A \cdot \tilde{u}^A - \underline{\phi} \cdot \tilde{u} \rangle = 0, $$ i.e. we enforce orthogonality of the approximation residual $\underline{\phi}^A \cdot \tilde{u}^A - \underline{\phi} \cdot \tilde{u}$ to all elements of $ {\underline{\phi}^{A}}^T$.
We infer: $$
\langle {\underline{\phi}^{A}}^T, \underline{\phi} \rangle \tilde{u} $$ and further $$ M^A \tilde{u}^A = Q^T M \tilde{u}. $$ Therefore, the matrix of the $L^2$-Projection in the respective bases is given as: $$ \tilde{u}^A = ( {M^A}^{-1} Q^T M ) \tilde{u}. $$ One can easily verify that the composition of extrapolation (matrix $Q$) and $L^2$-Projection is the identity: $$ ( {M^A}^{-1} Q^T M ) Q = {M^A}^{-1} {M^A} = I. $$
Right now the MultiPhaseAgglomerator
does not support this operation but the MultiGridOperator
must be used instead
The multigrid operator performes a change of basis for the DG representation;
Using e.g. the IdMass
configuration ensures an orthonormal XDG basis.
Note: the following MultigridOperator.ChangeOfBasisConfig
and will, at some point, be merged into the
IDifferentialOperator
interface.
var MgConfig = new MultigridOperator.ChangeOfBasisConfig[1][];
MgConfig[0] = new MultigridOperator.ChangeOfBasisConfig[] {
new MultigridOperator.ChangeOfBasisConfig() {
VarIndex = new int[] { 0 },
DegreeS = new int[] { c.Basis.Degree },
mode = MultigridOperator.Mode.IdMass_DropIndefinite
}
};
var MgOp = Op.GetMultigridOperator(mapping, MgConfig);
Warning: Multigrid sequence is requested, but not setup for this mesh; performing a zero-aggregation with one level; consider configuring multigrid using the `AggregationGridData[] MultigridSequence(...)` method. Agglom. for species A: 1 (0 [rnk 0] -> 1 [rnk 0], Level 0) Agglom. for species B: 0 creating sparse system for 4 DOF's ...
Then, the mass matrix is the identity:
MgOp.MassMatrix.ToStringDense()
1.000000E+000 0 0 0 1.000000E+000 0 0 0 1.000000E+000
The operator matrix can be accesd via:
MgOp.OperatorMatrix.ToStringDense()
1.909548E+000 0 -2.032067E-017 -1.417762E+000 2.222222E+000 0 0 0 1.833333E+000
Note that the MultigridOperator
also gets rid of the unused DOF's after agglomeration;
Therefore, the matrices in the example above are only 3x3 after transformation.
int L = MgOp.Mapping.LocalLength;
L
An easy-to-use driver function UniSolver.Solve(...)
allows to use the multigrid operator in order to solve the steady-state equation opsed by the IDifferentialOperator
;
var c2 = new XDGField(basis);
Op.Solve(c2.Mapping, verbose:true);
c2.CoordinateVector
Trying to solve equation (starting at 12/05/2023 02:18:59)... Agglom. for species A: 1 (0 [rnk 0] -> 1 [rnk 0], Level 0) Agglom. for species B: 0 Solver running in XDG mode. Solving for species: A, B; Using quadrature order 4. 1 multigrid levels available. Operator/PDE is linear. creating sparse system for 4 DOF's ... Species A: no of agglomerated cells: 1 Species B: no of agglomerated cells: 0 done 0.0469637 sec. System size: 4 No of blocks: 2 No of blocks in matrix: 4 min DG coordinates per cell: 1 max DG coordinates per cell: 2 Total non-zeros in matrix: 5 Approx. matrix storage (MB): 0 Setting up multigrid operator... done. (0.0251503 sec) Memory reserved|used by multi-grid operator 0.00 | 0.00 MB Running solver... 1 2.22e-16 done. (0.1165434 sec, 1 iter) Pardiso phase 11: 0.3997329 Pardiso phase 22: 0.0006469 Pardiso phase 33: 0.0002546 spmm total 0.0803599 spmm core 0.0064682 spmv total 0.0026954 spmv inner 0 spmv outer 0.0001276 Total runtime: 00:00:00.2424540 Solver SUCCESSFUL!
index | value |
---|---|
0 | 0.9999999999999993 |
1 | 1 |
2 | 0.9999999999999993 |
3 | 1.0000000000000007 |
Assert.Less(c2.CoordinateVector.L2Dist(c.CoordinateVector), 1e-12, "Solution form UniSolver is wrong");
To solve an equation manualy, using the multigrid operator, the following recipe can be used:
var RHS = OpAffine.CloneAs();
RHS.ScaleV(-1);
// the RHS must be transferred into the agglomerated XDG space, w.r.t. the cannonical basis
Agg.ManipulateMatrixAndRHS(default(MsrMatrix), RHS, mapping, mapping);
// it can then be converted to the orthonormal basis constructed in the `MultigridOperator`
var RHS_onb = new double[L];
MgOp.TransformRhsInto(RHS, RHS_onb, false);
// now, solve:
var Sol_onb = MgOp.OperatorMatrix.Solve_Direct(RHS_onb);
// transform the solution to the agglomerate XDG space:
XDGField c3 = new XDGField(basis);
MgOp.TransformSolFrom(c3.CoordinateVector, Sol_onb);
// perform the extrapolation
Agg.Extrapolate(c3.Mapping);
c3.CoordinateVector
index | value |
---|---|
0 | 0.9999999999999993 |
1 | 1 |
2 | 0.9999999999999993 |
3 | 1.0000000000000007 |
Assert.Less(c3.CoordinateVector.L2Dist(c.CoordinateVector), 1e-12, "Manual Multigrid solution is wrong");