static void ComputeErrors(Func <ConventionalDGField, ConventionalDGField, double> distFunc, IEnumerable <string> FieldsToCompare, IEnumerable <ITimestepInfo> timestepS, out double[] GridRes, out Dictionary <string, int[]> __DOFs, out Dictionary <string, double[]> Errors, out Guid[] timestepIds) { using (var tr = new FuncTrace()) { if (FieldsToCompare == null || FieldsToCompare.Count() <= 0) { throw new ArgumentException("empty list of field names."); } if (timestepS == null || timestepS.Count() < 1) { throw new ArgumentException("requiring at least two different solutions."); } // load the DG-Fields List <IEnumerable <DGField> > fields = new List <IEnumerable <DGField> >(); // 1st index: grid / 2nd index: enumeration int i = 1; foreach (var timestep in timestepS) { //Console.WriteLine("Loading timestep {0} of {1}, ({2})...", i, timestepS.Count(), timestep.ID); fields.Add(timestep.Fields); i++; //Console.WriteLine("done (Grid has {0} cells).", fields.Last().First().GridDat.CellPartitioning.TotalLength); } // sort according to grid resolution { var s = fields.OrderBy(f => f.First().GridDat.CellPartitioning.TotalLength).ToArray(); var orgfields = fields.ToArray(); fields.Clear(); fields.AddRange(s); s = null; // filter equal grids: while (fields.Count >= 2 && (fields[fields.Count - 1].First().GridDat.CellPartitioning.TotalLength == fields[fields.Count - 2].First().GridDat.CellPartitioning.TotalLength)) { fields.RemoveAt(fields.Count - 2); } // extract timestep Id's timestepIds = new Guid[fields.Count]; for (int z = 0; z < timestepIds.Length; z++) { int idx = orgfields.IndexOf(fields[z], (f1, f2) => object.ReferenceEquals(f1, f2)); timestepIds[z] = timestepS.ElementAt(idx).ID; } } // grids and resolution GridData[] gDataS = fields.Select(fc => GridHelper.ExtractGridData(fc.First().GridDat)).ToArray(); GridRes = gDataS.Take(gDataS.Length - 1).Select(gd => gd.Cells.h_minGlobal).ToArray(); // compute the errors Errors = new Dictionary <string, double[]>(); __DOFs = new Dictionary <string, int[]>(); foreach (string Identification in FieldsToCompare) { double[] L2Error = new double[gDataS.Length - 1]; int[] dof = new int[gDataS.Length - 1]; for (int iLevel = 0; iLevel < gDataS.Length - 1; iLevel++) { //Console.WriteLine("Computing L2 error of '{0}' on level {1} ...", Identification, iLevel); tr.Info(string.Format("Computing L2 error of '{0}' on level {1} ...", Identification, iLevel)); ConventionalDGField fine = (ConventionalDGField)(fields.Last().Single(fi => fi.Identification == Identification)); ConventionalDGField coarse = (ConventionalDGField)(fields.ElementAt(iLevel).Single(fi => fi.Identification == Identification)); L2Error[iLevel] = distFunc(coarse, fine); dof[iLevel] = coarse.Mapping.TotalLength; //Console.WriteLine("done (Error is {0:0.####E-00}).", L2Error[iLevel]); tr.Info(string.Format("done (Error is {0:0.####E-00}).", L2Error[iLevel])); } Errors.Add(Identification, L2Error); __DOFs.Add(Identification, dof); } } }
/// <summary> /// Approximate L2 distance between two DG fields; this also supports DG fields on different meshes, /// it could be used for convergence studies. /// </summary> /// <param name="A"></param> /// <param name="B"></param> /// <param name="IgnoreMeanValue"> /// if true, the mean value (mean over entire domain) will be subtracted - this mainly useful for comparing pressures /// </param> /// <param name="scheme"> /// a cell quadrature scheme on the coarse of the two meshes /// </param> /// <returns></returns> static public double L2Distance(this ConventionalDGField A, ConventionalDGField B, bool IgnoreMeanValue = false, CellQuadratureScheme scheme = null) { int maxDeg = Math.Max(A.Basis.Degree, B.Basis.Degree); int quadOrder = maxDeg * 3 + 3; if (A.GridDat.SpatialDimension != B.GridDat.SpatialDimension) { throw new ArgumentException("Both fields must have the same spatial dimension."); } if (object.ReferenceEquals(A.GridDat, B.GridDat) && false) { // ++++++++++++++ // equal meshes // ++++++++++++++ CellMask domain = scheme != null ? scheme.Domain : null; double errPow2 = A.L2Error(B, domain).Pow2(); if (IgnoreMeanValue) { // domain volume double Vol = 0; int J = A.GridDat.iGeomCells.NoOfLocalUpdatedCells; for (int j = 0; j < J; j++) { Vol += A.GridDat.iGeomCells.GetCellVolume(j); } Vol = Vol.MPISum(); // mean value double mean = A.GetMeanValueTotal(domain) - B.GetMeanValueTotal(domain); // Note: for a field p, we have // || p - <p> ||^2 = ||p||^2 - <p>^2*vol return(Math.Sqrt(errPow2 - mean * mean * Vol)); } else { return(Math.Sqrt(errPow2)); } } else { // ++++++++++++++++++ // different meshes // ++++++++++++++++++ DGField fine, coarse; if (A.GridDat.CellPartitioning.TotalLength > B.GridDat.CellPartitioning.TotalLength) { fine = A; coarse = B; } else { fine = B; coarse = A; } var CompQuadRule = scheme.SaveCompile(coarse.GridDat, maxDeg * 3 + 3); // use over-integration var eval = new FieldEvaluation(GridHelper.ExtractGridData(fine.GridDat)); void FineEval(MultidimensionalArray input, MultidimensionalArray output) { int L = input.GetLength(0); Debug.Assert(output.GetLength(0) == L); eval.Evaluate(1.0, new DGField[] { fine }, input, 0.0, output.ResizeShallow(L, 1)); } double errPow2 = coarse.LxError(FineEval, (double[] X, double fC, double fF) => (fC - fF).Pow2(), CompQuadRule, Quadrature_ChunkDataLimitOverride: int.MaxValue); if (IgnoreMeanValue == true) { // domain volume double Vol = 0; int J = coarse.GridDat.iGeomCells.NoOfLocalUpdatedCells; for (int j = 0; j < J; j++) { Vol += coarse.GridDat.iGeomCells.GetCellVolume(j); } Vol = Vol.MPISum(); // mean value times domain volume double meanVol = coarse.LxError(FineEval, (double[] X, double fC, double fF) => fC - fF, CompQuadRule, Quadrature_ChunkDataLimitOverride: int.MaxValue); // Note: for a field p, we have // || p - <p> ||^2 = ||p||^2 - <p>^2*vol return(Math.Sqrt(errPow2 - meanVol * meanVol / Vol)); } else { return(Math.Sqrt(errPow2)); } } }
/// <summary> /// Projects a DG field <paramref name="source"/>, which may be defined on some different mesh, /// onto the DG field <paramref name="target"/>. /// </summary> static public void ProjectFromForeignGrid(this ConventionalDGField target, double alpha, ConventionalDGField source, CellQuadratureScheme scheme = null) { using (new FuncTrace()) { Console.WriteLine(string.Format("Projecting {0} onto {1}... ", source.Identification, target.Identification)); int maxDeg = Math.Max(target.Basis.Degree, source.Basis.Degree); var CompQuadRule = scheme.SaveCompile(target.GridDat, maxDeg * 3 + 3); // use over-integration int D = target.GridDat.SpatialDimension; if (object.ReferenceEquals(source.GridDat, target.GridDat)) { // +++++++++++++++++ // equal grid branch // +++++++++++++++++ target.ProjectField(alpha, source.Evaluate, CompQuadRule); } else { // +++++++++++++++++++++ // different grid branch // +++++++++++++++++++++ if (source.GridDat.SpatialDimension != D) { throw new ArgumentException("Spatial Dimension Mismatch."); } var eval = new FieldEvaluation(GridHelper.ExtractGridData(source.GridDat)); // // Difficulty with MPI parallelism: // While the different meshes may represent the same geometrical domain, // their MPI partitioning may be different. // // Solution Approach: we circulate unlocated points among all MPI processes. // int MPIsize = target.GridDat.MpiSize; // pass 1: collect all points // ========================== MultidimensionalArray allNodes = null; void CollectPoints(MultidimensionalArray input, MultidimensionalArray output) { Debug.Assert(input.Dimension == 2); Debug.Assert(input.NoOfCols == D); if (allNodes == null) { allNodes = input.CloneAs(); } else { /* * var newNodes = MultidimensionalArray.Create(allNodes.NoOfRows + input.NoOfRows, D); * newNodes.ExtractSubArrayShallow(new[] { 0, 0 }, new[] { allNodes.NoOfRows - 1, D - 1 }) * .Acc(1.0, allNodes); * newNodes.ExtractSubArrayShallow(new[] { allNodes.NoOfRows, 0 }, new[] { newNodes.NoOfRows - 1, D - 1 }) * .Acc(1.0, allNodes); */ allNodes = allNodes.CatVert(input); } Debug.Assert(allNodes.NoOfCols == D); } target.ProjectField(alpha, CollectPoints, CompQuadRule); int L = allNodes != null?allNodes.GetLength(0) : 0; // evaluate // ======== //allNodes = MultidimensionalArray.Create(2, 2); //allNodes[0, 0] = -1.5; //allNodes[0, 1] = 2.0; //allNodes[1, 0] = -0.5; //allNodes[1, 1] = 2.0; //L = 2; var Res = L > 0 ? MultidimensionalArray.Create(L, 1) : default(MultidimensionalArray); int NoOfUnlocated = eval.EvaluateParallel(1.0, new DGField[] { source }, allNodes, 0.0, Res); int TotalNumberOfUnlocated = NoOfUnlocated.MPISum(); if (TotalNumberOfUnlocated > 0) { Console.Error.WriteLine($"WARNING: {TotalNumberOfUnlocated} unlocalized points in 'ProjectFromForeignGrid(...)'"); } // perform the real projection // =========================== int lc = 0; void ProjectionIntegrand(MultidimensionalArray input, MultidimensionalArray output) { Debug.Assert(input.Dimension == 2); Debug.Assert(input.NoOfCols == D); int LL = input.GetLength(0); output.Set(Res.ExtractSubArrayShallow(new int[] { lc, 0 }, new int[] { lc + LL - 1, -1 })); lc += LL; } target.ProjectField(alpha, ProjectionIntegrand, CompQuadRule); } } }
/// <summary> /// Addition/Subtraction of DG fields on different grids /// </summary> /// <param name="A"></param> /// <param name="scaleA"></param> /// <param name="B"></param> /// <param name="scaleB"></param> /// <returns> /// <paramref name="scaleA"/>*<paramref name="A"/> + <paramref name="scaleB"/>*<paramref name="B"/> /// </returns> static public DGField ScaledSummation(this DGField A, double scaleA, DGField B, double scaleB) { if (object.ReferenceEquals(A.GridDat, B.GridDat)) { // ++++++++++++++++++++++++++++ // both fields on the same grid // ++++++++++++++++++++++++++++ DGField sum; if (A.Basis.IsSubBasis(B.Basis)) { sum = (DGField)B.Clone(); sum.Scale(scaleB); sum.AccLaidBack(1.0, A); } else if (B.Basis.IsSubBasis(A.Basis)) { sum = (DGField)A.Clone(); sum.Scale(scaleA); sum.AccLaidBack(1.0, B); } else { throw new ApplicationException("can't add the two fields, because their basis are incompatible"); } sum.Identification = "(" + scaleA + "*" + A.Identification + "+" + scaleB + "*" + B.Identification + ")"; return(sum); } else { // ++++++++++++++++++++++++++ // fields on different grids // ++++++++++++++++++++++++++ DGField fine, coarse; double aF, aC; if (A.GridDat.CellPartitioning.TotalLength > B.GridDat.CellPartitioning.TotalLength) { fine = A; coarse = B; aF = scaleA; aC = scaleB; } else { coarse = A; fine = B; aC = scaleA; aF = scaleB; } Foundation.Grid.Classic.GridData fineGridData = GridHelper.ExtractGridData(fine.GridDat); Foundation.Grid.Classic.GridData coarseGridData = GridHelper.ExtractGridData(coarse.GridDat); DGFieldComparison.ComputeFine2CoarseMap( fineGridData, coarseGridData, out var Fine2CoarseMapS); DGField injected; if (coarse is ConventionalDGField) { ConventionalDGField _injected = new SinglePhaseField( new Basis(fine.GridDat, Math.Max(coarse.Basis.Degree, fine.Basis.Degree)), coarse.Identification); DGFieldComparison.InjectDGField(Fine2CoarseMapS, _injected, coarse as ConventionalDGField); injected = _injected; } else if (coarse is XDGField) { XDGField _injected = new XDGField( new XDGBasis((fine as XDGField).Basis.Tracker, Math.Max(coarse.Basis.Degree, fine.Basis.Degree)), coarse.Identification); DGFieldComparison.InjectXDGField(Fine2CoarseMapS, _injected, coarse as XDGField); injected = _injected; } else { throw new NotSupportedException(); } return(ScaledSummation(injected, aC, fine, aF)); } }