diff --git a/CMakeLists.txt b/CMakeLists.txt index 4961ed0bc28eb8e4aa6b533af4f2a00e87cac3c4..263bfb4013fcab1c4f9c46c3a332541137f19638 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,6 +225,8 @@ include_directories(src/algebra src/analytics src/common src/common/io src/equat add_subdirectory(examples) add_subdirectory(tutorials) +add_subdirectory(unitTests/correctness) + #---------------------------------- # diff --git a/examples/distributed/HelmholtzCheckerboard/CMakeLists.txt b/examples/distributed/HelmholtzCheckerboard/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3e8d28b40a80596978df92599f594c10f009d6e8 --- /dev/null +++ b/examples/distributed/HelmholtzCheckerboard/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +project(tuto CXX) + +find_package(MPI) +if(MPI_FOUND) + list(APPEND EXTRA_INCS ${MPI_CXX_INCLUDE_PATH}) + list(APPEND EXTRA_LIBS ${MPI_CXX_LIBRARIES}) + set(CMAKE_C_COMPILER ${MPI_C_COMPILER}) + set(CMAKE_CXX_COMPILER ${MPI_CXX_COMPILER}) +endif() + + + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../example.cmake) + +add_executable(tuto main.cpp ${EXTRA_INCS}) +target_link_libraries(tuto ${EXTRA_LIBS}) diff --git a/examples/distributed/HelmholtzCheckerboard/main.cpp b/examples/distributed/HelmholtzCheckerboard/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..666068f379a2877131014ba765849ecaf5a3ddf1 --- /dev/null +++ b/examples/distributed/HelmholtzCheckerboard/main.cpp @@ -0,0 +1,595 @@ +#include <PetscInterface.h> +#include <algorithm> +#include <chrono> +#include <gmsh.h> +#include <gmshfem/CSVio.h> +#include <gmshfem/DistributedField.h> +#include <gmshfem/Formulation.h> +#include <gmshfem/GmshFem.h> +#include <gmshfem/MatrixFactory.h> +#include <gmshfem/Solver.h> +#include <gmshfem/SolverMonitor.h> +#include <gmshfem/VectorFactory.h> +#include <thread> + +using namespace gmshfem; +using namespace gmshfem::common; +using namespace gmshfem::problem; +using namespace gmshfem::domain; +using namespace gmshfem::field; +using namespace gmshfem::function; +using namespace gmshfem::post; +using namespace gmshfem::equation; +using namespace gmshfem::msg; + + +class MonitorL2Error : public gmshfem::system::AbstractMonitor +{ + private: + Domain dom; + DistributedField< std::complex< double >, Form::Form0 > &field; + DistributedField< std::complex< double >, Form::Form0 > &UnknownField; + double normSol; + Formulation< std::complex< double > > &formulation; + + public: + MonitorL2Error(Domain dom, DistributedField< std::complex< double >, Form::Form0 > &field, DistributedField< std::complex< double >, Form::Form0 > &UnknownField, Formulation< std::complex< double > > &form) : + dom(dom), field(field), UnknownField(UnknownField), formulation(form) + { + // COMPUTE SOL NORM, REDUCE, IN CALLBACK: set values into field + normSol = gmshfem::common::GmshFem::reductionSum(real(integrate((field)*conj(field), dom, "Gauss6"))); + if(gmshfem::common::GmshFem::getMPIRank() == 0) + msg::info << "Initalize L2 monitor with " << normSol << msg::endl; + } + + virtual PetscErrorCode Monitor(KSP ksp, PetscInt n, PetscReal rnorm) + { + auto MPI_Rank = gmshfem::common::GmshFem::getMPIRank(); + + Vec x; + VecType t; + PetscInt size; + KSPBuildSolution(ksp, nullptr, &x); + VecGetType(x, &t); + std::vector< std::complex< double > > values; + + values.resize(formulation.getNumberOfUnknownDof()); + formulation.getDistributedContext()->readScatteredData(values, x); + formulation.setSolutionIntoFields(values); + + double normErr = gmshfem::common::GmshFem::reductionSum(real(integrate((field - UnknownField) * conj(field - UnknownField), dom, "Gauss6"))); + + + if(MPI_Rank == 0) + msg::info << "Monitor called " << n << " and rnorm " << rnorm << ". Rel L² error is: " << sqrt(normErr / normSol) << msg::endl; + + return PETSC_SUCCESS; + } +}; + +void genMesh(double L, double H, unsigned nx, unsigned ny, double lc, unsigned overlapSize, std::vector< std::pair< double, double > > sources) +{ + namespace geo = gmsh::model::geo; + std::vector< int > sourcePoints, sourceDoms; + CSVio out_source_domains("source_subdomains"); + + + gmsh::model::add("genMesh"); + double dx = L / nx; + double dy = H / ny; + double delta = lc * overlapSize; + if(delta > dx || delta > dy) + throw Exception("Overlap too big!"); + + + // points[i][j] with i the x axis and j the y axis. Each subdomain has 4 points in a direction, but offset of 3 + // because of sharing + std::vector< std::vector< int > > points(1 + 3 * nx, std::vector< int >(1 + 3 * ny)); + + // All points + for(unsigned i = 0; i < nx; ++i) { + for(unsigned j = 0; j < ny; ++j) { + double x = i * dx; + double y = j * dy; + points[3 * i][3 * j] = gmsh::model::geo::addPoint(x, y, 0., lc); + points[3 * i + 1][3 * j] = gmsh::model::geo::addPoint(x + delta, y, 0., lc); + points[3 * i + 2][3 * j] = gmsh::model::geo::addPoint(x + dx - delta, y, 0., lc); + + points[3 * i][3 * j + 1] = gmsh::model::geo::addPoint(x, y + delta, 0., lc); + points[3 * i + 1][3 * j + 1] = gmsh::model::geo::addPoint(x + delta, y + delta, 0., lc); + points[3 * i + 2][3 * j + 1] = gmsh::model::geo::addPoint(x + dx - delta, y + delta, 0., lc); + + points[3 * i][3 * j + 2] = gmsh::model::geo::addPoint(x, y + dy - delta, 0., lc); + points[3 * i + 1][3 * j + 2] = gmsh::model::geo::addPoint(x + delta, y + dy - delta, 0., lc); + points[3 * i + 2][3 * j + 2] = gmsh::model::geo::addPoint(x + dx - delta, y + dy - delta, 0., lc); + + if(j == (ny - 1)) { + points[3 * i][3 * j + 3] = gmsh::model::geo::addPoint(x, y + dy, 0., lc); + points[3 * i + 1][3 * j + 3] = gmsh::model::geo::addPoint(x + delta, y + dy, 0., lc); + points[3 * i + 2][3 * j + 3] = gmsh::model::geo::addPoint(x + dx - delta, y + dy, 0., lc); + } + + if(i == (nx - 1)) { + points[3 * i + 3][3 * j] = gmsh::model::geo::addPoint(x + dx, y, 0., lc); + points[3 * i + 3][3 * j + 1] = gmsh::model::geo::addPoint(x + dx, y + delta, 0., lc); + points[3 * i + 3][3 * j + 2] = gmsh::model::geo::addPoint(x + dx, y + dy - delta, 0., lc); + if(j == (ny - 1)) { + points[3 * i + 3][3 * j + 3] = gmsh::model::geo::addPoint(x + dx, y + dy, 0., lc); + } + } + } + } + + // There are 24 lines on each subdomain. Some are duplicated! + struct SubdomainLines { + // All ids in order [bottom, right, up, left] + // Lines are always pointing to the right or upward + int inner[4]; // Inne square + int outer[4]; // Interior parts (corner excluded) of outer square + int cornerLines[16]; // 4 * cornerId + edgeId, + }; + + std::vector< std::vector< SubdomainLines > > lines(nx, std::vector< SubdomainLines >(ny)); + + + // All lines + for(unsigned i = 0; i < nx; ++i) { + for(unsigned j = 0; j < ny; ++j) { + auto &sub = lines[i][j]; + sub.inner[0] = geo::addLine(points[3 * i + 1][3 * j + 1], points[3 * i + 2][3 * j + 1]); + sub.inner[1] = geo::addLine(points[3 * i + 2][3 * j + 1], points[3 * i + 2][3 * j + 2]); + sub.inner[2] = geo::addLine(points[3 * i + 1][3 * j + 2], points[3 * i + 2][3 * j + 2]); + sub.inner[3] = geo::addLine(points[3 * i + 1][3 * j + 1], points[3 * i + 1][3 * j + 2]); + + // Always create bottom boundary, give it to previous subdomain + sub.outer[0] = geo::addLine(points[3 * i + 1][3 * j], points[3 * i + 2][3 * j]); + if(j > 0) + lines[i][j - 1].outer[2] = sub.outer[0]; + + // If I'm at the end, I create the last ones, otherwise I'll read them. + if(i == (nx - 1)) + sub.outer[1] = geo::addLine(points[3 * i + 3][3 * j + 1], points[3 * i + 3][3 * j + 2]); + if(j == (ny - 1)) + sub.outer[2] = geo::addLine(points[3 * i + 1][3 * j + 3], points[3 * i + 2][3 * j + 3]); + + sub.outer[3] = geo::addLine(points[3 * i][3 * j + 1], points[3 * i][3 * j + 2]); + if(i > 0) + lines[i - 1][j].outer[1] = sub.outer[3]; + + + // Always define the inner corners + + // Bottom left: right and up parts + sub.cornerLines[1] = geo::addLine(points[3 * i + 1][3 * j], points[3 * i + 1][3 * j + 1]); + sub.cornerLines[2] = geo::addLine(points[3 * i][3 * j + 1], points[3 * i + 1][3 * j + 1]); + + // Bottom right: up and left parts + sub.cornerLines[4 + 2] = geo::addLine(points[3 * i + 2][3 * j + 1], points[3 * i + 3][3 * j + 1]); + sub.cornerLines[4 + 3] = geo::addLine(points[3 * i + 2][3 * j], points[3 * i + 2][3 * j + 1]); + + // top right: down and left parts + sub.cornerLines[8 + 0] = geo::addLine(points[3 * i + 2][3 * j + 2], points[3 * i + 3][3 * j + 2]); + sub.cornerLines[8 + 3] = geo::addLine(points[3 * i + 2][3 * j + 2], points[3 * i + 2][3 * j + 3]); + + // top left: down and right parts + sub.cornerLines[12 + 0] = geo::addLine(points[3 * i][3 * j + 2], points[3 * i + 1][3 * j + 2]); + sub.cornerLines[12 + 1] = geo::addLine(points[3 * i + 1][3 * j + 2], points[3 * i + 1][3 * j + 3]); + + // Shared parts of corners + // Left: always create, share if i > 0 + sub.cornerLines[3] = geo::addLine(points[3 * i][3 * j + 0], points[3 * i][3 * j + 1]); + sub.cornerLines[12 + 3] = geo::addLine(points[3 * i][3 * j + 2], points[3 * i][3 * j + 3]); + if(i > 0) { + lines[i - 1][j].cornerLines[4 + 1] = sub.cornerLines[3]; + lines[i - 1][j].cornerLines[8 + 1] = sub.cornerLines[12 + 3]; + } + + // Bottom: always create + sub.cornerLines[0] = geo::addLine(points[3 * i][3 * j + 0], points[3 * i + 1][3 * j + 0]); + sub.cornerLines[4] = geo::addLine(points[3 * i + 2][3 * j + 0], points[3 * i + 3][3 * j + 0]); + if(j > 0) { + lines[i][j - 1].cornerLines[12 + 2] = sub.cornerLines[0]; + lines[i][j - 1].cornerLines[8 + 2] = sub.cornerLines[4]; + } + + // IF i'm the last col, add the right + if(i == (nx - 1)) { + sub.cornerLines[4 + 1] = geo::addLine(points[3 * i + 3][3 * j + 0], points[3 * i + 3][3 * j + 1]); + sub.cornerLines[8 + 1] = geo::addLine(points[3 * i + 3][3 * j + 2], points[3 * i + 3][3 * j + 3]); + } + + if(j == (ny - 1)) { + sub.cornerLines[12 + 2] = geo::addLine(points[3 * i + 0][3 * j + 3], points[3 * i + 1][3 * j + 3]); + sub.cornerLines[8 + 2] = geo::addLine(points[3 * i + 2][3 * j + 3], points[3 * i + 3][3 * j + 3]); + } + } + } + + // Make the surfaces + struct SubdomaineFaces { + int inner; + int corner[4]; + int edges[4]; + }; + + std::vector< std::vector< SubdomaineFaces > > surfaces(nx, std::vector< SubdomaineFaces >(ny)); + + for(unsigned i = 0; i < nx; ++i) { + for(unsigned j = 0; j < ny; ++j) { + auto &theseLines = lines[i][j]; + int loop = geo::addCurveLoop({theseLines.inner[0], theseLines.inner[1], -theseLines.inner[2], -theseLines.inner[3]}); + surfaces[i][j].inner = geo::addPlaneSurface({loop}); + + // "Edges" + loop = geo::addCurveLoop({theseLines.outer[0], theseLines.cornerLines[7], -theseLines.inner[0], -theseLines.cornerLines[1]}); + surfaces[i][j].edges[0] = geo::addPlaneSurface({loop}); + + loop = geo::addCurveLoop({-theseLines.inner[1], theseLines.cornerLines[6], theseLines.outer[1], -theseLines.cornerLines[8]}); + surfaces[i][j].edges[1] = geo::addPlaneSurface({loop}); + + loop = geo::addCurveLoop({theseLines.inner[2], theseLines.cornerLines[11], -theseLines.outer[2], -theseLines.cornerLines[13]}); + surfaces[i][j].edges[2] = geo::addPlaneSurface({loop}); + + loop = geo::addCurveLoop({theseLines.inner[3], -theseLines.cornerLines[12], -theseLines.outer[3], theseLines.cornerLines[2]}); + surfaces[i][j].edges[3] = geo::addPlaneSurface({loop}); + + // "Corners" + for(int offset = 0; offset < 16; offset += 4) { + loop = geo::addCurveLoop({theseLines.cornerLines[offset + 0], theseLines.cornerLines[offset + 1], -theseLines.cornerLines[offset + 2], -theseLines.cornerLines[offset + 3]}); + surfaces[i][j].corner[offset / 4] = geo::addPlaneSurface({loop}); + } + } + } + + // Add source + for(auto &pos : sources) { + double x = pos.first; + double y = pos.second; + int iSrc = static_cast< int >(x / dx); + int jSrc = static_cast< int >(y / dy); + int srcDom = iSrc + jSrc * nx; + sourceDoms.push_back(srcDom); + sourcePoints.push_back(geo::addPoint(pos.first, pos.second, 0, lc)); + + out_source_domains << srcDom << csv::endl; + + gmsh::model::geo::synchronize(); + + + double xloc = x - iSrc * dx; + double yloc = y - jSrc * dy; + + auto point = sourcePoints.back(); + if(xloc > delta && xloc < (dx - delta)) { + // Inner + if(yloc > delta && yloc < (dy - delta)) { + gmsh::model::mesh::embed(0, {point}, 2, surfaces[iSrc][jSrc].inner); + } + else if(yloc < delta) { + // S edge + gmsh::model::mesh::embed(0, {point}, 2, surfaces[iSrc][jSrc].edges[0]); + } + else if(yloc > (dy - delta)) { + // N edge + gmsh::model::mesh::embed(0, {point}, 2, surfaces[iSrc][jSrc].edges[2]); + } + else { + throw common::Exception("Bad source position"); + } + } + else if(xloc < delta) { + // W edge + if(yloc > delta && yloc < (dy - delta)) { + gmsh::model::mesh::embed(0, {point}, 2, surfaces[iSrc][jSrc].edges[3]); + } + else if(yloc < delta) { + // SW corner + gmsh::model::mesh::embed(0, {point}, 2, surfaces[iSrc][jSrc].corner[0]); + } + else if(yloc > (dy - delta)) { + // NW corner + gmsh::model::mesh::embed(0, {point}, 2, surfaces[iSrc][jSrc].corner[3]); + } + else { + throw common::Exception("Bad source position"); + } + } + else if(xloc > (dx - delta)) { + // E edge + if(yloc > delta && yloc < (dy - delta)) { + gmsh::model::mesh::embed(0, {point}, 2, surfaces[iSrc][jSrc].edges[1]); + } + else if(yloc < delta) { + // SE corner + gmsh::model::mesh::embed(0, {point}, 2, surfaces[iSrc][jSrc].corner[1]); + } + else if(yloc > (dy - delta)) { + // NE corner + gmsh::model::mesh::embed(0, {point}, 2, surfaces[iSrc][jSrc].corner[2]); + } + else { + throw common::Exception("Bad source position"); + } + } + } + + //int src = geo::addPoint(xsrc, ysrc, 0, lc); + //int iSrc = nx / 2; + //int jSrc = ny / 2; + + //gmsh::model::geo::synchronize(); + //gmsh::model::mesh::embed(0, {src}, 2, surfaces[0][0].inner); + + gmsh::model::mesh::generate(); + //gmsh::write("full_mesh.msh"); + + + for(unsigned i = 0; i < nx; ++i) { + for(unsigned j = 0; j < ny; ++j) { + //gmsh::model::removePhysicalGroups(); + + std::vector< int > inner, overlap, boundary; + // Fill interior, overlap and boundary + inner.push_back(surfaces[i][j].inner); + for(int k = 0; k < 4; ++k) { + inner.push_back(surfaces[i][j].corner[k]); + inner.push_back(surfaces[i][j].edges[k]); + } + + if(i > 0) { + overlap.push_back(surfaces[i - 1][j].edges[1]); + overlap.push_back(surfaces[i - 1][j].corner[1]); + overlap.push_back(surfaces[i - 1][j].corner[2]); + + // Left bnd + boundary.push_back(lines[i - 1][j].inner[1]); + boundary.push_back(lines[i - 1][j].cornerLines[4 + 3]); + boundary.push_back(lines[i - 1][j].cornerLines[8 + 3]); + } + else { + boundary.push_back(lines[i][j].outer[3]); + boundary.push_back(lines[i][j].cornerLines[3]); + boundary.push_back(lines[i][j].cornerLines[12 + 3]); + } + + if(j > 0) { + overlap.push_back(surfaces[i][j - 1].edges[2]); + overlap.push_back(surfaces[i][j - 1].corner[2]); + overlap.push_back(surfaces[i][j - 1].corner[3]); + + // Bottom bnd + boundary.push_back(lines[i][j - 1].inner[2]); + boundary.push_back(lines[i][j - 1].cornerLines[8 + 0]); + boundary.push_back(lines[i][j - 1].cornerLines[12 + 0]); + } + else { + boundary.push_back(lines[i][j].outer[0]); + boundary.push_back(lines[i][j].cornerLines[0]); + boundary.push_back(lines[i][j].cornerLines[4]); + } + + if(i != (nx - 1)) { + overlap.push_back(surfaces[i + 1][j].edges[3]); + overlap.push_back(surfaces[i + 1][j].corner[3]); + overlap.push_back(surfaces[i + 1][j].corner[0]); + + // Right bnd + boundary.push_back(lines[i + 1][j].inner[3]); + boundary.push_back(lines[i + 1][j].cornerLines[4 + 1]); + boundary.push_back(lines[i + 1][j].cornerLines[8 + 1]); + } + else { + boundary.push_back(lines[i][j].outer[1]); + boundary.push_back(lines[i][j].cornerLines[4 + 1]); + boundary.push_back(lines[i][j].cornerLines[8 + 1]); + } + + if(j != (ny - 1)) { + overlap.push_back(surfaces[i][j + 1].edges[0]); + overlap.push_back(surfaces[i][j + 1].corner[1]); + overlap.push_back(surfaces[i][j + 1].corner[0]); + + // top bnd + boundary.push_back(lines[i][j + 1].inner[0]); + boundary.push_back(lines[i][j + 1].cornerLines[2]); + boundary.push_back(lines[i][j + 1].cornerLines[4 + 2]); + } + else { + // top bnd + boundary.push_back(lines[i][j].outer[2]); + boundary.push_back(lines[i][j].cornerLines[8 + 2]); + boundary.push_back(lines[i][j].cornerLines[12 + 2]); + } + gmsh::model::geo::synchronize(); + + /* + Corner of overlaps + */ + if(i != 0 && j != 0) { + // SW corner + overlap.push_back(surfaces[i - 1][j - 1].corner[2]); + } + if(i != 0 && j != (ny - 1)) { + // NW corner + overlap.push_back(surfaces[i - 1][j + 1].corner[1]); + } + if(i != (nx - 1) && j != 0) { + // SE corner + overlap.push_back(surfaces[i + 1][j - 1].corner[3]); + } + if(i != (nx - 1) && j != (ny - 1)) { + // NE corner + overlap.push_back(surfaces[i + 1][j + 1].corner[0]); + } + + gmsh::model::addPhysicalGroup(2, inner, 1 + 100 * (i + j * nx)); + gmsh::model::addPhysicalGroup(2, overlap, 2 + 100 * (i + j * nx)); + gmsh::model::addPhysicalGroup(1, boundary, 3 + 100 * (i + j * nx)); + + /*gmsh::model::addPhysicalGroup(2, {3}, 2, "overlap"); + gmsh::model::addPhysicalGroup(1, {l110}, 1, "gammaLat"); + gmsh::model::addPhysicalGroup(1, {l89, l910}, 2, "gammaTop"); + gmsh::model::addPhysicalGroup(1, {l12, l23}, 3, "gammaBottom"); + gmsh::model::addPhysicalGroup(0, {srcLoc}, 1, "src");*/ + for(int srcIdx = 0; srcIdx < sourceDoms.size(); ++srcIdx) { + if(i + nx * j == sourceDoms[srcIdx]) + gmsh::model::addPhysicalGroup(0, {sourcePoints[srcIdx]}, srcIdx + 1); + } + + gmsh::model::geo::synchronize(); + + //gmsh::write("mesh_" + std::to_string(i) + "_" + std::to_string(j) + ".msh"); + } + } + + gmsh::write("full_mesh.msh"); +} +/* +void solveFullProblem(unsigned nCells, unsigned order, double k) +{ + gmsh::open("full_mesh.msh"); + Domain omega("omega"); + Domain gamma("gamma"); + Domain src("src"); + + Formulation< std::complex< double > > formulation("formulation_full"); + Field< std::complex< double >, Form::Form0 > v("v", omega, FunctionSpaceTypeForm0::HierarchicalH1, order); + + formulation.integral(grad(dof(v)), grad(tf(v)), v.domain(), "Gauss6"); + formulation.integral(-k * k * dof(v), tf(v), v.domain(), "Gauss6"); + formulation.integral(std::complex< double >(0, 1) * k * dof(v), tf(v), gamma, "Gauss6"); + formulation.integral(-1, tf(v), src, "Gauss6"); + + formulation.pre(); + formulation.assemble(); + formulation.solve(false); + save(v, omega, "v_full"); +}*/ + +int main(int argc, char **argv) +{ + using namespace std::chrono_literals; + + GmshFem gmshFem(argc, argv); + unsigned int MPI_Rank = gmshFem.getMPIRank(); + unsigned MPI_Size = gmshFem.getMPISize(); + + int numSolsToSave = 1; + gmshFem.userDefinedParameter(numSolsToSave, "numSolsToSave"); + + int order = 1; + gmshFem.userDefinedParameter(order, "order"); + unsigned nx{2}, ny{1}; + + //if(MPI_Size != 2) + // throw Exception("Must run with 2 ranks! (Got " + std::to_string(MPI_Size) + ")"); + gmshFem.userDefinedParameter(nx, "nx"); + gmshFem.userDefinedParameter(ny, "ny"); + int overlapSize = 1; + gmshFem.userDefinedParameter(overlapSize, "overlapSize"); + + double wavelength = 0.05; + gmshFem.userDefinedParameter(wavelength, "wavelength"); + + double k = 2 * 3.1415 / wavelength; + + int iDom = MPI_Rank % nx; + int jDom = MPI_Rank / nx; + + + /**** + * Gmsh part + ****/ + std::vector<double> x_src = {0.17, 0.22, 0.27, 0.32, 0.37, 0.42, 0.47, 0.52, 0.57, 0.62, 0.67, 0.72, 0.77, 0.82, 0.87}; + std::vector< std::pair< double, double > > sources; + for (double x: x_src) + { + sources.push_back({x, 0.8}); + sources.push_back({x, 0.7}); + sources.push_back({x, 0.1}); + } + + if(MPI_Rank == 0) { + genMesh(1, 1, nx, ny, wavelength / 10, overlapSize, sources); + } + gmshFem.BarrierMPI(); + + std::vector< int > srcDomains, sourcesOwned; + CSVio out_source_domains("source_subdomains", ';', OpeningMode::Reading); + int tmp; + while((out_source_domains >> tmp).isEOF() == false) { + srcDomains.push_back(tmp); + if(tmp == MPI_Rank) + sourcesOwned.push_back(srcDomains.size() - 1); + if(MPI_Rank == 0) + msg::debug << "Source " << srcDomains.size() - 1 << " goes in " << tmp << msg::endl; + } + + + + //gmsh::open("mesh_" + std::to_string(iDom) + "_" + std::to_string(jDom) + ".msh"); + gmsh::open("full_mesh.msh"); + gmsh::model::mesh::createEdges(); + gmsh::model::mesh::createFaces(); + + + Formulation< std::complex< double > > formulation("formulation_" + std::to_string(MPI_Rank)); + Domain omega(2, 1 + 100 * (iDom + jDom * nx)); + Domain overlap(2, 2 + 100 * (iDom + jDom * nx)); + Domain boundary(1, 3 + 100 * (iDom + jDom * nx)); + + DistributedField< std::complex< double >, Form::Form0 > v("v", omega, overlap, FunctionSpaceTypeForm0::HierarchicalH1, order); + + formulation.integral(grad(dof(v)), grad(tf(v)), v.domain(), "Gauss6"); + std::complex< double > k2Complex(k * k, -k * k * 0.0); + std::complex< double > im(0., 1.0); + formulation.integral(-k2Complex * dof(v), tf(v), v.domain(), "Gauss6"); + formulation.integral(im * k * dof(v), tf(v), boundary, "Gauss6"); + for(int srcIdx = 0; srcIdx < srcDomains.size(); ++srcIdx) { + formulation.setCurrentRHS(srcIdx); + + if(std::find(sourcesOwned.begin(), sourcesOwned.end(), srcIdx) != sourcesOwned.end()) { + Domain thisSrc(0, srcIdx + 1); + formulation.integral(-1, tf(v), thisSrc, "Gauss6"); + } + } + + formulation.pre(); + formulation.preDistributed(); + formulation.assemble(); + formulation.solveAllDistributed(system::DISTRIBUTED_SOLVE_TYPE::ORAS, false); + + for (unsigned kSol = 0; kSol < formulation.numRHS(); ++kSol) { + formulation.loadSolution(kSol); + if (kSol < numSolsToSave) + save(v, omega, "v_rhs_" + std::to_string(kSol) + "_rank_" + std::to_string(MPI_Rank)); + } + + // Error analysis on first RHS + formulation.loadSolution(0); + decltype(v) v_backup = v; + std::unique_ptr< system::AbstractMonitor > monitor = std::make_unique< MonitorL2Error >(omega, v_backup, v, formulation); + formulation.setMonitor(std::move(monitor)); + formulation.solveDistributed(system::DISTRIBUTED_SOLVE_TYPE::ORAS, true); + + + save(v, omega, "v_" + std::to_string(MPI_Rank)); + save(v - v_backup, omega, "delta_" + std::to_string(MPI_Rank)); + auto vnorm = real(integrate((v)*conj(v), omega, "Gauss6")); + auto error1 = real(integrate((v - v_backup) * conj(v - v_backup), omega, "Gauss6")); + + msg::print << "L2 absolute error in domain " << MPI_Rank << " : " << error1 << " and sol has norm " << vnorm << " so rel error is " << error1 / vnorm << msg::endl; + double v_tot = gmshFem.reductionSum(vnorm); + double err_tot = gmshFem.reductionSum(error1); + if(MPI_Rank == 0) + msg::print << "L2 absolute error in domain (total)): " << err_tot << " and sol has norm " << v_tot << " so rel error is " << err_tot / v_tot << msg::endl; + + + //save(v - solution, omega, "err_" + std::to_string(MPI_Rank)); + /* + if(error1 > 1e-2) { + throw common::Exception("Error above tolerance : " + std::to_string(error1)); + }*/ + + return 0; +} diff --git a/examples/distributed/Poisson2DRobin/CMakeLists.txt b/examples/distributed/Poisson2DRobin/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..af8f060b31fe272fe3efb2f6f7cb71f56422b74a --- /dev/null +++ b/examples/distributed/Poisson2DRobin/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +project(tuto CXX) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../example.cmake) + +add_executable(tuto main.cpp ${EXTRA_INCS}) +target_link_libraries(tuto ${EXTRA_LIBS}) diff --git a/examples/distributed/Poisson2DRobin/main.cpp b/examples/distributed/Poisson2DRobin/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c088e954ff79d878c26628c28c1d9699c1c216c0 --- /dev/null +++ b/examples/distributed/Poisson2DRobin/main.cpp @@ -0,0 +1,437 @@ +#include <algorithm> +#include <chrono> +#include <gmsh.h> +#include <gmshfem/DistributedField.h> +#include <gmshfem/Formulation.h> +#include <gmshfem/GmshFem.h> +#include <gmshfem/MatrixFactory.h> +#include <gmshfem/VectorFactory.h> +#include <thread> + +using namespace gmshfem; +using namespace gmshfem::common; +using namespace gmshfem::problem; +using namespace gmshfem::domain; +using namespace gmshfem::field; +using namespace gmshfem::function; +using namespace gmshfem::post; +using namespace gmshfem::equation; +using namespace gmshfem::msg; + + +void genMesh(double L, double H, unsigned nx, unsigned ny, double lc, unsigned overlapSize) +{ + namespace geo = gmsh::model::geo; + + + gmsh::model::add("genMesh"); + double dx = L / nx; + double dy = H / ny; + double delta = lc * overlapSize; + if(delta > dx || delta > dy) + throw Exception("Overlap too big!"); + + + // points[i][j] with i the x axis and j the y axis. Each subdomain has 4 points in a direction, but offset of 3 + // because of sharing + std::vector< std::vector< int > > points(1 + 3 * nx, std::vector< int >(1 + 3 * ny)); + + // All points + for(unsigned i = 0; i < nx; ++i) { + for(unsigned j = 0; j < ny; ++j) { + double x = i * dx; + double y = j * dy; + points[3 * i][3 * j] = gmsh::model::geo::addPoint(x, y, 0., lc); + points[3 * i + 1][3 * j] = gmsh::model::geo::addPoint(x + delta, y, 0., lc); + points[3 * i + 2][3 * j] = gmsh::model::geo::addPoint(x + dx - delta, y, 0., lc); + + points[3 * i][3 * j + 1] = gmsh::model::geo::addPoint(x, y + delta, 0., lc); + points[3 * i + 1][3 * j + 1] = gmsh::model::geo::addPoint(x + delta, y + delta, 0., lc); + points[3 * i + 2][3 * j + 1] = gmsh::model::geo::addPoint(x + dx - delta, y + delta, 0., lc); + + points[3 * i][3 * j + 2] = gmsh::model::geo::addPoint(x, y + dy - delta, 0., lc); + points[3 * i + 1][3 * j + 2] = gmsh::model::geo::addPoint(x + delta, y + dy - delta, 0., lc); + points[3 * i + 2][3 * j + 2] = gmsh::model::geo::addPoint(x + dx - delta, y + dy - delta, 0., lc); + + if(j == (ny - 1)) { + points[3 * i][3 * j + 3] = gmsh::model::geo::addPoint(x, y + dy, 0., lc); + points[3 * i + 1][3 * j + 3] = gmsh::model::geo::addPoint(x + delta, y + dy, 0., lc); + points[3 * i + 2][3 * j + 3] = gmsh::model::geo::addPoint(x + dx - delta, y + dy, 0., lc); + } + + if(i == (nx - 1)) { + points[3 * i + 3][3 * j] = gmsh::model::geo::addPoint(x + dx, y, 0., lc); + points[3 * i + 3][3 * j + 1] = gmsh::model::geo::addPoint(x + dx, y + delta, 0., lc); + points[3 * i + 3][3 * j + 2] = gmsh::model::geo::addPoint(x + dx, y + dy - delta, 0., lc); + if(j == (ny - 1)) { + points[3 * i + 3][3 * j + 3] = gmsh::model::geo::addPoint(x + dx, y + dy, 0., lc); + } + } + } + } + + // There are 24 lines on each subdomain. Some are duplicated! + struct SubdomainLines { + // All ids in order [bottom, right, up, left] + // Lines are always pointing to the right or upward + int inner[4]; // Inne square + int outer[4]; // Interior parts (corner excluded) of outer square + int cornerLines[16]; // 4 * cornerId + edgeId, + }; + + std::vector< std::vector< SubdomainLines > > lines(nx, std::vector< SubdomainLines >(ny)); + + + // All lines + for(unsigned i = 0; i < nx; ++i) { + for(unsigned j = 0; j < ny; ++j) { + auto &sub = lines[i][j]; + sub.inner[0] = geo::addLine(points[3 * i + 1][3 * j + 1], points[3 * i + 2][3 * j + 1]); + sub.inner[1] = geo::addLine(points[3 * i + 2][3 * j + 1], points[3 * i + 2][3 * j + 2]); + sub.inner[2] = geo::addLine(points[3 * i + 1][3 * j + 2], points[3 * i + 2][3 * j + 2]); + sub.inner[3] = geo::addLine(points[3 * i + 1][3 * j + 1], points[3 * i + 1][3 * j + 2]); + + // Always create bottom boundary, give it to previous subdomain + sub.outer[0] = geo::addLine(points[3 * i + 1][3 * j], points[3 * i + 2][3 * j]); + if(j > 0) + lines[i][j - 1].outer[2] = sub.outer[0]; + + // If I'm at the end, I create the last ones, otherwise I'll read them. + if(i == (nx - 1)) + sub.outer[1] = geo::addLine(points[3 * i + 3][3 * j + 1], points[3 * i + 3][3 * j + 2]); + if(j == (ny - 1)) + sub.outer[2] = geo::addLine(points[3 * i + 1][3 * j + 3], points[3 * i + 2][3 * j + 3]); + + sub.outer[3] = geo::addLine(points[3 * i][3 * j + 1], points[3 * i][3 * j + 2]); + if(i > 0) + lines[i - 1][j].outer[1] = sub.outer[3]; + + + // Always define the inner corners + + // Bottom left: right and up parts + sub.cornerLines[1] = geo::addLine(points[3 * i + 1][3 * j], points[3 * i + 1][3 * j + 1]); + sub.cornerLines[2] = geo::addLine(points[3 * i][3 * j + 1], points[3 * i + 1][3 * j + 1]); + + // Bottom right: up and left parts + sub.cornerLines[4 + 2] = geo::addLine(points[3 * i + 2][3 * j + 1], points[3 * i + 3][3 * j + 1]); + sub.cornerLines[4 + 3] = geo::addLine(points[3 * i + 2][3 * j], points[3 * i + 2][3 * j + 1]); + + // top right: down and left parts + sub.cornerLines[8 + 0] = geo::addLine(points[3 * i + 2][3 * j + 2], points[3 * i + 3][3 * j + 2]); + sub.cornerLines[8 + 3] = geo::addLine(points[3 * i + 2][3 * j + 2], points[3 * i + 2][3 * j + 3]); + + // top left: down and right parts + sub.cornerLines[12 + 0] = geo::addLine(points[3 * i][3 * j + 2], points[3 * i + 1][3 * j + 2]); + sub.cornerLines[12 + 1] = geo::addLine(points[3 * i + 1][3 * j + 2], points[3 * i + 1][3 * j + 3]); + + // Shared parts of corners + // Left: always create, share if i > 0 + sub.cornerLines[3] = geo::addLine(points[3 * i][3 * j + 0], points[3 * i][3 * j + 1]); + sub.cornerLines[12 + 3] = geo::addLine(points[3 * i][3 * j + 2], points[3 * i][3 * j + 3]); + if(i > 0) { + lines[i - 1][j].cornerLines[4 + 1] = sub.cornerLines[3]; + lines[i - 1][j].cornerLines[8 + 1] = sub.cornerLines[12 + 3]; + } + + // Bottom: always create + sub.cornerLines[0] = geo::addLine(points[3 * i][3 * j + 0], points[3 * i + 1][3 * j + 0]); + sub.cornerLines[4] = geo::addLine(points[3 * i + 2][3 * j + 0], points[3 * i + 3][3 * j + 0]); + if(j > 0) { + lines[i][j - 1].cornerLines[12 + 2] = sub.cornerLines[0]; + lines[i][j - 1].cornerLines[8 + 2] = sub.cornerLines[4]; + } + + // IF i'm the last col, add the right + if(i == (nx - 1)) { + sub.cornerLines[4 + 1] = geo::addLine(points[3 * i + 3][3 * j + 0], points[3 * i + 3][3 * j + 1]); + sub.cornerLines[8 + 1] = geo::addLine(points[3 * i + 3][3 * j + 2], points[3 * i + 3][3 * j + 3]); + } + + if(j == (ny - 1)) { + sub.cornerLines[12 + 2] = geo::addLine(points[3 * i + 0][3 * j + 3], points[3 * i + 1][3 * j + 3]); + sub.cornerLines[8 + 2] = geo::addLine(points[3 * i + 2][3 * j + 3], points[3 * i + 3][3 * j + 3]); + } + } + } + + // Make the surfaces + struct SubdomaineFaces { + int inner; + int corner[4]; + int edges[4]; + }; + + std::vector< std::vector< SubdomaineFaces > > surfaces(nx, std::vector< SubdomaineFaces >(ny)); + + for(unsigned i = 0; i < nx; ++i) { + for(unsigned j = 0; j < ny; ++j) { + auto &theseLines = lines[i][j]; + int loop = geo::addCurveLoop({theseLines.inner[0], theseLines.inner[1], -theseLines.inner[2], -theseLines.inner[3]}); + surfaces[i][j].inner = geo::addPlaneSurface({loop}); + + // "Edges" + loop = geo::addCurveLoop({theseLines.outer[0], theseLines.cornerLines[7], -theseLines.inner[0], -theseLines.cornerLines[1]}); + surfaces[i][j].edges[0] = geo::addPlaneSurface({loop}); + + loop = geo::addCurveLoop({-theseLines.inner[1], theseLines.cornerLines[6], theseLines.outer[1], -theseLines.cornerLines[8]}); + surfaces[i][j].edges[1] = geo::addPlaneSurface({loop}); + + loop = geo::addCurveLoop({theseLines.inner[2], theseLines.cornerLines[11], -theseLines.outer[2], -theseLines.cornerLines[13]}); + surfaces[i][j].edges[2] = geo::addPlaneSurface({loop}); + + loop = geo::addCurveLoop({theseLines.inner[3], -theseLines.cornerLines[12], -theseLines.outer[3], theseLines.cornerLines[2]}); + surfaces[i][j].edges[3] = geo::addPlaneSurface({loop}); + + // "Corners" + for(int offset = 0; offset < 16; offset += 4) { + loop = geo::addCurveLoop({theseLines.cornerLines[offset + 0], theseLines.cornerLines[offset + 1], -theseLines.cornerLines[offset + 2], -theseLines.cornerLines[offset + 3]}); + surfaces[i][j].corner[offset / 4] = geo::addPlaneSurface({loop}); + } + } + } + + + gmsh::model::geo::synchronize(); + + gmsh::model::mesh::generate(); + //gmsh::write("full_mesh.msh"); + + + for(unsigned i = 0; i < nx; ++i) { + for(unsigned j = 0; j < ny; ++j) { + //gmsh::model::removePhysicalGroups(); + + std::vector< int > inner, overlap, boundary, coupling; + // Fill interior, overlap and boundary + inner.push_back(surfaces[i][j].inner); + for(int k = 0; k < 4; ++k) { + inner.push_back(surfaces[i][j].corner[k]); + inner.push_back(surfaces[i][j].edges[k]); + } + + if(i > 0) { + overlap.push_back(surfaces[i - 1][j].edges[1]); + overlap.push_back(surfaces[i - 1][j].corner[1]); + overlap.push_back(surfaces[i - 1][j].corner[2]); + + // Left bnd + coupling.push_back(lines[i - 1][j].inner[1]); + coupling.push_back(lines[i - 1][j].cornerLines[4 + 3]); + coupling.push_back(lines[i - 1][j].cornerLines[8 + 3]); + + if(j != 0) + coupling.push_back(lines[i - 1][j - 1].cornerLines[8 + 3]); + if(j != (ny - 1)) + coupling.push_back(lines[i - 1][j + 1].cornerLines[4 + 3]); + } + else { + boundary.push_back(lines[i][j].outer[3]); + boundary.push_back(lines[i][j].cornerLines[3]); + boundary.push_back(lines[i][j].cornerLines[12 + 3]); + + if(j != 0) + boundary.push_back(lines[i][j - 1].cornerLines[15]); + if(j != (ny - 1)) + boundary.push_back(lines[i][j + 1].cornerLines[3]); + } + + if(j > 0) { + overlap.push_back(surfaces[i][j - 1].edges[2]); + overlap.push_back(surfaces[i][j - 1].corner[2]); + overlap.push_back(surfaces[i][j - 1].corner[3]); + + // Bottom bnd + coupling.push_back(lines[i][j - 1].inner[2]); + coupling.push_back(lines[i][j - 1].cornerLines[8 + 0]); + coupling.push_back(lines[i][j - 1].cornerLines[12 + 0]); + + if(i != 0) + coupling.push_back(lines[i - 1][j - 1].cornerLines[8]); + if(i != (nx - 1)) + coupling.push_back(lines[i + 1][j - 1].cornerLines[12]); + } + else { + boundary.push_back(lines[i][j].outer[0]); + boundary.push_back(lines[i][j].cornerLines[0]); + boundary.push_back(lines[i][j].cornerLines[4]); + + if(i != 0) + boundary.push_back(lines[i - 1][j].cornerLines[4]); + if(i != (nx - 1)) + boundary.push_back(lines[i + 1][j].cornerLines[0]); + } + + if(i != (nx - 1)) { + overlap.push_back(surfaces[i + 1][j].edges[3]); + overlap.push_back(surfaces[i + 1][j].corner[3]); + overlap.push_back(surfaces[i + 1][j].corner[0]); + + // Right bnd + coupling.push_back(lines[i + 1][j].inner[3]); + coupling.push_back(lines[i + 1][j].cornerLines[1]); + coupling.push_back(lines[i + 1][j].cornerLines[12 + 1]); + + if(j != 0) + coupling.push_back(lines[i + 1][j - 1].cornerLines[12 + 1]); + if(j != (ny - 1)) + coupling.push_back(lines[i + 1][j + 1].cornerLines[1]); + } + else { + boundary.push_back(lines[i][j].outer[1]); + boundary.push_back(lines[i][j].cornerLines[4 + 1]); + boundary.push_back(lines[i][j].cornerLines[8 + 1]); + + if(j != 0) + boundary.push_back(lines[i][j - 1].cornerLines[8 + 1]); + if(j != (ny - 1)) + boundary.push_back(lines[i][j + 1].cornerLines[4 + 1]); + } + + if(j != (ny - 1)) { + overlap.push_back(surfaces[i][j + 1].edges[0]); + overlap.push_back(surfaces[i][j + 1].corner[1]); + overlap.push_back(surfaces[i][j + 1].corner[0]); + + // top bnd + coupling.push_back(lines[i][j + 1].inner[0]); + coupling.push_back(lines[i][j + 1].cornerLines[2]); + coupling.push_back(lines[i][j + 1].cornerLines[4 + 2]); + + if(i != 0) + coupling.push_back(lines[i - 1][j + 1].cornerLines[4 + 2]); + if(i != (nx - 1)) + coupling.push_back(lines[i + 1][j + 1].cornerLines[2]); + } + else { + // top bnd + boundary.push_back(lines[i][j].outer[2]); + boundary.push_back(lines[i][j].cornerLines[8 + 2]); + boundary.push_back(lines[i][j].cornerLines[12 + 2]); + + if(i != 0) + boundary.push_back(lines[i - 1][j].cornerLines[10]); + if(i != (nx - 1)) + boundary.push_back(lines[i + 1][j].cornerLines[14]); + } + + /* + Corner of overlaps + */ + if(i != 0 && j != 0) { + // SW corner + overlap.push_back(surfaces[i - 1][j - 1].corner[2]); + } + if(i != 0 && j != (ny - 1)) { + // NW corner + overlap.push_back(surfaces[i - 1][j + 1].corner[1]); + } + if(i != (nx - 1) && j != 0) { + // SE corner + overlap.push_back(surfaces[i + 1][j - 1].corner[3]); + } + if(i != (nx - 1) && j != (ny - 1)) { + // NE corner + overlap.push_back(surfaces[i + 1][j + 1].corner[0]); + } + + gmsh::model::geo::synchronize(); + + gmsh::model::addPhysicalGroup(2, inner, 1 + 100 * (i + j * nx)); + gmsh::model::addPhysicalGroup(2, overlap, 2 + 100 * (i + j * nx)); + gmsh::model::addPhysicalGroup(1, coupling, 3 + 100 * (i + j * nx)); + gmsh::model::addPhysicalGroup(1, boundary, 4 + 100 * (i + j * nx)); + + gmsh::model::geo::synchronize(); + + //gmsh::write("mesh_" + std::to_string(i) + "_" + std::to_string(j) + ".msh"); + } + } + + gmsh::write("full_mesh.msh"); +} + +int main(int argc, char **argv) +{ + using namespace std::chrono_literals; + + GmshFem gmshFem(argc, argv); + unsigned int MPI_Rank = gmshFem.getMPIRank(); + unsigned MPI_Size = gmshFem.getMPISize(); + + + int order = 1; + gmshFem.userDefinedParameter(order, "order"); + unsigned nx{2}, ny{1}; + gmshFem.userDefinedParameter(nx, "nx"); + gmshFem.userDefinedParameter(ny, "ny"); + if(MPI_Size != nx * ny) + throw Exception("Must run with same number of subdomains and ranks! (Got " + std::to_string(MPI_Size) + ")"); + + int overlapSize = 1; + gmshFem.userDefinedParameter(overlapSize, "overlapSize"); + + double h = 0.01; + gmshFem.userDefinedParameter(h, "h"); + + int iDom = MPI_Rank % nx; + int jDom = MPI_Rank / nx; + + + /**** + * Gmsh part + ****/ + if(MPI_Rank == 0) { + genMesh(1, 1, nx, ny, h, overlapSize); + } + gmshFem.BarrierMPI(); + + //gmsh::open("mesh_" + std::to_string(iDom) + "_" + std::to_string(jDom) + ".msh"); + gmsh::open("full_mesh.msh"); + gmsh::model::mesh::createEdges(); + gmsh::model::mesh::createFaces(); + + auto analytical = 1 + x< std::complex< double > >() + 2 * x< std::complex< double > >() * y< std::complex< double > >() + pow(y< std::complex< double > >(), 3); + + Formulation< std::complex< double > > formulation("formulation_" + std::to_string(MPI_Rank)); + Domain omega(2, 1 + 100 * (iDom + jDom * nx)); + Domain overlap(2, 2 + 100 * (iDom + jDom * nx)); + Domain coupling(1, 3 + 100 * (iDom + jDom * nx)); + Domain boundary(1, 4 + 100 * (iDom + jDom * nx)); + + DistributedField< std::complex< double >, Form::Form0 > v("v", omega, overlap, FunctionSpaceTypeForm0::HierarchicalH1, order); + + // "Convection" coefficient + double alpha = 1; + gmshFem.userDefinedParameter(alpha, "alpha"); + + formulation.integral(grad(dof(v)), grad(tf(v)), v.domain(), "Gauss6"); + formulation.integral(alpha * dof(v), tf(v), coupling, "Gauss6"); + formulation.integral(6. * y< std::complex< double > >(), (tf(v)), v.domain(), "Gauss6"); + + v.addConstraint(boundary, analytical); + + formulation.pre(); + formulation.preDistributed(); + formulation.assemble(); + formulation.solveDistributed(system::DISTRIBUTED_SOLVE_TYPE::ORAS, false); + + + auto errorLoc = real(integrate((v - analytical) * conj(v - analytical), omega, "Gauss6")); + auto solNormLoc = real(integrate((analytical) * conj(analytical), omega, "Gauss6")); + double l2_rel_error = gmshFem.reductionSum(errorLoc) / gmshFem.reductionSum(solNormLoc); + + if(MPI_Rank == 0) + msg::print << "L2 relative error in the whole domain: " << l2_rel_error << msg::endl; + save(v, omega, "v_" + std::to_string(MPI_Rank)); + save(analytical, omega, "sol_" + std::to_string(MPI_Rank)); + + double tol = 1e-2; + if(order > 1) + tol = 1e-4; + if(l2_rel_error > 1e-2) { + throw common::Exception("Error above tolerance : " + std::to_string(tol)); + } + + return 0; +} diff --git a/src/field/DistributedField.cpp b/src/field/DistributedField.cpp index dc3068525834c9bf1e56a454087bcccdf12e317c..94d4c029e02210ce1852e92de5aa223815f451a2 100644 --- a/src/field/DistributedField.cpp +++ b/src/field/DistributedField.cpp @@ -121,7 +121,7 @@ namespace gmshfem::field std::vector< ShareDofInfo > local, global; local.reserve(_sharedDofs.size()); - + unsigned rank = gmshfem::common::GmshFem::getMPIRank(); unsigned commSize = gmshfem::common::GmshFem::getMPISize(); @@ -236,7 +236,6 @@ namespace gmshfem::field _toSend.clear(); _toRead.clear(); - msg::debug << "On rank " << rank << " indexToDof has size: " << _indexToDof.size() << msg::endl; MPI_Barrier(MPI_COMM_WORLD); @@ -354,7 +353,6 @@ namespace gmshfem::field void DistributedField< T_Scalar, T_Form >::_computeToSend() { // Send all owned interfaces and inner dofs that are read by someone else - std::vector< DofGlobalIndex > local, global; local.reserve(_sharedDofs.size()); diff --git a/src/problem/Formulation.cpp b/src/problem/Formulation.cpp index 14b7dea98066e6fb99ee57f353bf88fe3418cbe4..0e141d2d1876edfef26e4018af6d8c3863e62f1a 100644 --- a/src/problem/Formulation.cpp +++ b/src/problem/Formulation.cpp @@ -219,9 +219,28 @@ namespace gmshfem::problem void Formulation< T_Scalar >::setCurrentRHS(unsigned idx) { _currentRHS = idx; + auto oldNumRHS = _numRHS; _numRHS = std::max(idx + 1, _numRHS); } + template< class T_Scalar > + void Formulation< T_Scalar >::forceRHSCount(unsigned idx) + { + _numRHS = idx; + _multiB.clear(); + _multiB.resize(numRHS()); + for(auto &bi : _multiB) { + bi.init(_dofs.nbrUnknownDofs()); + } + } + + template< class T_Scalar > + void Formulation< T_Scalar >::setMonitor(std::unique_ptr<system::AbstractMonitor> &&ptr) + { + _solver->setMonitor(std::move(ptr)); + } + + // Bilinear terms template< class T_Scalar > unsigned int Formulation< T_Scalar >::integral(const equation::EquationInterface< T_Scalar, Degree::Degree0, field::Form::Form0 > &equationLhs, const equation::EquationInterface< T_Scalar, Degree::Degree0, field::Form::Form0 > &equationRhs, const domain::GeometricObject &domain, const std::string &integrationType, const term::ProductType productType) @@ -1272,12 +1291,14 @@ namespace gmshfem::problem msg::info << "Assembling " << _name << "..." << msg::endl; if(numRHS() != _multiB.size()) { - _multiB.clear(); _multiB.resize(numRHS()); } for(auto &bi : _multiB) { - bi.init(_dofs.nbrUnknownDofs()); + // If previous RHS have the good size, don't touch them + if(bi.size() != _dofs.nbrUnknownDofs()) + bi.init(_dofs.nbrUnknownDofs()); } + s_setLinkedIndices(_dofs.nbrLinkedDofs(), _unknownFields, _A, _multiB); @@ -1607,6 +1628,85 @@ namespace gmshfem::problem return time; } + template< class T_Scalar > + common::Timer Formulation< T_Scalar >::solveDistributed(system::DISTRIBUTED_SOLVE_TYPE type, const bool reusePreconditioner) + { + if (gmshfem::common::GmshFem::getMPIRank() == 0) + msg::info << "Solving distributed system " << _name << "..." << msg::endl; + + common::Timer time; + time.tick(); + + std::vector< T_Scalar > values, valuesReordered(_dofs.nbrUnknownDofs(), 0); + + try { + if (!_distributedContext) + throw common::Exception("Missing distributed context"); + _solver->solveDistributed(values, type, *_distributedContext, reusePreconditioner); + } + catch(const std::exception &exc) { + msg::error << "Unable to solve the system" << msg::endl; + msg::error << exc.what() << msg::endl; + msg::info << "Solving aborted" << msg::endl; + time.tock(); + return time; + } + + // Copy all dofs + if(values.size() != _dofs.nbrUnknownDofs()) { + msg::error << "An error occurred while solving the system. Expected a vector of size " << _dofs.nbrUnknownDofs() << " but got " << values.size() << "." << msg::endl; + msg::info << "Solving aborted" << msg::endl; + time.tock(); + return time; + } + + setSolutionIntoFields((values)); + + time.tock(); + if(gmshfem::common::GmshFem::getMPIRank() == 0) + msg::info << "Done solving in " << time << "s" << msg::endl; + return time; + } + + template< class T_Scalar > + common::Timer Formulation< T_Scalar >::solveAllDistributed(system::DISTRIBUTED_SOLVE_TYPE solveType, const bool reusePreconditioner) + { + if(gmshfem::common::GmshFem::getMPIRank() == 0) + + msg::info << "Solving distributed system" << _name << " with " << _numRHS << " right-hand sides..." << msg::endl; + + common::Timer time; + time.tick(); + + _solutions.resize(_numRHS); + + try { + if (!_distributedContext) + throw common::Exception("Missing distributed context"); + _solver->solveAllDistributed(_solutions, solveType, *_distributedContext, reusePreconditioner); + } + catch(const std::exception &exc) { + msg::error << "Unable to solve the system" << msg::endl; + msg::error << exc.what() << msg::endl; + msg::info << "Solving aborted" << msg::endl; + time.tock(); + return time; + } + + if(_solutions.back().size() != _dofs.nbrUnknownDofs()) { + msg::error << "An error occurred while solving the system. Solution size is " << _solutions.back().size() << " but expected " << _dofs.nbrUnknownDofs() << msg::endl; + msg::info << "Solving aborted" << msg::endl; + time.tock(); + return time; + } + + + time.tock(); + if(gmshfem::common::GmshFem::getMPIRank() == 0) + msg::info << "Done solving in " << time << "s" << msg::endl; + return time; + } + template< class T_Scalar > void Formulation< T_Scalar >::setSolutionIntoFields(const std::vector< T_Scalar > &solution) { @@ -1629,6 +1729,7 @@ namespace gmshfem::problem } } + template< class T_Scalar > const system::DistributedContext< T_Scalar >* Formulation< T_Scalar >::getDistributedContext() const { @@ -2049,6 +2150,12 @@ namespace gmshfem::problem s_getRHSBlock(vector, tfTags, &_multiB.at(0)); } + template< class T_Scalar > + const std::vector< std::vector< T_Scalar > > &Formulation< T_Scalar >::getSolutionsVector() const + { + return _solutions; + } + template< class T_Scalar > void Formulation< T_Scalar >::setRHS(const algebra::Vector< T_Scalar > &vector) { diff --git a/src/problem/Formulation.h b/src/problem/Formulation.h index 736e81552f4d55f9ee1bf0e0bb364211d963e79e..4e5de7b34cb501dc531ece00235613a529b4cfc9 100644 --- a/src/problem/Formulation.h +++ b/src/problem/Formulation.h @@ -108,6 +108,10 @@ namespace gmshfem::problem // Multi RHS interface unsigned numRHS() const; void setCurrentRHS(unsigned idx); + void forceRHSCount(unsigned idx); + + void setMonitor(std::unique_ptr<system::AbstractMonitor>&& ptr); + void disableSolverShellOptions(); // Bilinear term unsigned int integral(const equation::EquationInterface< T_Scalar, Degree::Degree0, field::Form::Form0 > &equationLhs, const equation::EquationInterface< T_Scalar, Degree::Degree0, field::Form::Form0 > &equationRhs, const domain::GeometricObject &domain, const std::string &integrationType, const term::ProductType productType = term::ProductType::Hermitian); diff --git a/src/system/MatrixFactory.cpp b/src/system/MatrixFactory.cpp index 626c5009f664a746f7598d581f229d94703de6ee..a4ae130d988b1604f95557a5715c7eeec4864cc5 100644 --- a/src/system/MatrixFactory.cpp +++ b/src/system/MatrixFactory.cpp @@ -8,13 +8,13 @@ #include "CSVio.h" #include "Dof.h" #include "Exception.h" +#include "GmshFem.h" #include "KahanSum.h" #include "Message.h" #include "PPMio.h" #include "PetscInterface.h" #include "gmshfemDefines.h" #include "instantiate.h" - namespace gmshfem::system { @@ -569,6 +569,111 @@ namespace gmshfem::system } return matPetsc; } + +#ifdef HAVE_MPI + + template< class T_Scalar > + Mat MatrixFactory< T_Scalar >::getPetscDistributed(const DistributedContext<T_Scalar>& distributedContext) const + { + const auto& localDofs = distributedContext.localIDofOwned(); + const auto &localToGlobal = distributedContext.localToGlobal(); + unsigned nLocalRows = localDofs.size(); + + // PETSc can determine the number of columns because the matrix is square + std::vector< PetscInt > ai, aj; + ai.reserve(nLocalRows + 1); + ai.push_back(0); + std::vector< PetscScalar > values; + unsigned long long k = 0; + for(unsigned i = 0; i < nLocalRows; ++i) { + // Copy the row + unsigned long long rowToCopy = localDofs[i] - 1; //numDofs start at 1 + ai.push_back(k + _ai[rowToCopy + 1] - _ai[rowToCopy]); + for(unsigned l = 0; l < _ai[rowToCopy + 1] - _ai[rowToCopy]; ++l) { + // All entries from the row get copied, and columns are adapted + values.push_back((*_module)[_ai[rowToCopy] + l]); + aj.push_back(localToGlobal.at(_aj[_ai[rowToCopy] + l]) - 1); + ++k; + } + } + + Mat matPetsc; + msg::debug << "Distributed matrix. Local rows: " << nLocalRows << " ai.size(), aj.size(), values.size() : " << ai.size() << ',' << aj.size() << ',' << values.size() << msg::endl; + MatCreateMPIAIJWithArrays(PETSC_COMM_WORLD, nLocalRows, nLocalRows, PETSC_DETERMINE, PETSC_DETERMINE, ai.data(), aj.data(), values.data(), &matPetsc); + + return matPetsc; + } + template< class T_Scalar > + void MatrixFactory< T_Scalar >::modifyPetscORAS(Mat mat, const std::vector< unsigned long long > &localIdOfOwned, const std::vector< unsigned long long > &localIdOfNonOwned, const std::vector< unsigned long long > &localToGlobalMap, const IS row, const IS col) const + { + unsigned nLocalRows = localIdOfOwned.size(); + + auto rank = common::GmshFem::getMPIRank(); + + const PetscInt *rowsIdx; + PetscInt nRows, nrowsGlob; + ISGetIndices(row, &rowsIdx); + ISGetLocalSize(row, &nRows); + ISGetSize(row, &nrowsGlob); + + PetscInt n, m; + MatGetSize(mat, &n, &m); + + common::GmshFem::BarrierMPI(); + + MatAssemblyBegin(mat, MAT_FLUSH_ASSEMBLY); + MatAssemblyEnd(mat, MAT_FLUSH_ASSEMBLY); + + common::GmshFem::BarrierMPI(); + + /** + * TODO optimisation: + * - Sort ISSort pour qu'il soit ordonnée -> std::lower_bound etc + * - MatSetValues + */ + + for(unsigned k = 0; k < nRows; ++k) { + PetscInt globalDof = rowsIdx[k] + 1; // 1-Indexing in FEM code + auto it = std::find_if(localIdOfNonOwned.cbegin(), localIdOfNonOwned.cend(), [globalDof, &localToGlobalMap](unsigned long long loc) { return localToGlobalMap.at(loc - 1) == globalDof; }); + if(it != localIdOfNonOwned.cend()) { + auto globalIdx = it - localIdOfNonOwned.begin(); // i if dof is localIdOfNonOwned[i] + //msg::print << rank << ": Modifying global DOF " << globalDof << " which is the " << globalIdx << "th global dof ( k is " << k << ") and local ID is " << *it << msg::endl; + + // "k" is a local line to be updated, *it is the local ID of the line (in 1-indexing) + auto rowToCopy = *it - 1; + for(unsigned l = 0; l < _ai[rowToCopy + 1] - _ai[rowToCopy]; ++l) { + // All entries from the row get copied, and columns are adapted + //values.push_back((*_module)[_ai[rowToCopy] + l]); + auto val = (*_module)[_ai[rowToCopy] + l]; + auto globCol = localToGlobalMap.at(_aj[_ai[rowToCopy] + l]) - 1; + auto colPtr = std::find(rowsIdx, rowsIdx + nRows, globCol); + if(colPtr == rowsIdx + nRows) + throw common::Exception("Missing column ?"); + auto j = colPtr - rowsIdx; + //aj.push_back(localToGlobal.at(_aj[_ai[rowToCopy] + l]) - 1); + //msg::print << '[' << rank << "] changes entry " << k <<','<< j << msg::endl; + PetscScalar replaced; + //MatGetValue(mat, k, j, &replaced); + MatSetValue(mat, k, j, val, INSERT_VALUES); + + //msg::print << "Replacing in " << k << ',' << j << " value " << replaced << " by " << val << msg::endl; + } + } + else { + //msg::print << rank << ": Global dof untouched " << globalDof << msg::endl; + } + + } + + // Reassemble the matrix + MatAssemblyBegin(mat, MAT_FINAL_ASSEMBLY); + MatAssemblyEnd(mat, MAT_FINAL_ASSEMBLY); + common::GmshFem::BarrierMPI(); + + ISRestoreIndices(row, &rowsIdx); + } +#endif + #endif template< class T_Scalar > diff --git a/src/system/MatrixFactory.h b/src/system/MatrixFactory.h index ba74876a3ec38e109964b89b547bbacd20b76e39..3ab9ca90f40a85ff19290ad337133af7e6484e92 100644 --- a/src/system/MatrixFactory.h +++ b/src/system/MatrixFactory.h @@ -15,10 +15,12 @@ #include "OmpInterface.h" #include "gmshfemDefines.h" #include "scalar.h" +#include "DistributedContext.h" #include <vector> typedef struct _p_Mat *Mat; +typedef struct _p_IS *IS; namespace gmshfem::system { @@ -72,6 +74,10 @@ namespace gmshfem::system bool havePattern() const; #ifdef HAVE_PETSC Mat getPetsc() const; + #ifdef HAVE_MPI + Mat getPetscDistributed(const DistributedContext<T_Scalar>& distributedContext) const; + void modifyPetscORAS(Mat mat, const std::vector< unsigned long long > &localIdOfOwned, const std::vector< unsigned long long > &localIdOfNonOwned, const std::vector< unsigned long long >& localToGlobalMap, const IS row, const IS col) const; + #endif #endif common::Memory memory() const; diff --git a/src/system/Solver.cpp b/src/system/Solver.cpp index ae9dbe1f3e78fd13aa07588820c7b7e160a7b9ac..665b2f25e9d98917b35389ec5a3500af828df44a 100644 --- a/src/system/Solver.cpp +++ b/src/system/Solver.cpp @@ -25,6 +25,49 @@ namespace gmshfem::system { + // Code to use ASM preconditioner + template< class T_Scalar > + struct ModifySubmatrixContext { + std::vector< PetscInt > *ghostsIndices; + size_t nLocalRows; + const std::vector< unsigned long long > *localIdOfNonOwned; + const std::vector< unsigned long long > *localIdOfOwned; + const std::vector< unsigned long long > *localToGlobalMap; + MatrixFactory< T_Scalar > *A; + }; + + template< class T_Scalar > + static PetscErrorCode modifySubMatrix(PC pc, PetscInt nsub, const IS row[], const IS col[], Mat submat[], void *ctxPtr) + { + ModifySubmatrixContext< T_Scalar > *ctx = static_cast< ModifySubmatrixContext< T_Scalar > * >(ctxPtr); + if(ctx == nullptr) + return PETSC_ERR_ARG_WRONG; + + auto &ghosts = *ctx->ghostsIndices; + PetscInt m, n; + MatGetSize(submat[0], &m, &n); + MatType type; + MatGetType(submat[0], &type); + + PetscInt nRows; + ISGetSize(row[0], &nRows); + + //ISRestoreIndices(row[0], &rowsIdx); + + + ctx->A->modifyPetscORAS(submat[0], *ctx->localIdOfOwned, *ctx->localIdOfNonOwned, *ctx->localToGlobalMap, row[0], col[0]); + + return PETSC_SUCCESS; + }; + + template< class T_Scalar > + void Solver< T_Scalar >::setMonitor(std::unique_ptr< system::AbstractMonitor > &&ptr) + { + _customMonitor = std::move(ptr); + } + + + template< class T_Scalar > Solver< T_Scalar >::Solver(MatrixFactory< T_Scalar > *const A, std::vector< VectorFactory< T_Scalar > > &b) : @@ -127,6 +170,11 @@ namespace gmshfem::system // use direct sparse solver by default KSPSetType(_ksp, "preonly"); PCSetType(pc, (_isCholesky< T_Scalar >(_A->getOptions().symmetric(), _A->getOptions().hermitian()) ? "cholesky" : "lu")); + + PetscViewerAndFormat *vf; + //PetscViewerAndFormatCreate(PETSC_VIEWER_STDOUT_(PETSC_COMM_WORLD), PETSC_VIEWER_DEFAULT, &vf); + //KSPMonitorSet(_ksp, (PetscErrorCode(*)(KSP, PetscInt, PetscReal, void *))KSPMonitorResidual, vf, (PetscErrorCode(*)(void **))PetscViewerAndFormatDestroy); + #if defined(PETSC_HAVE_MUMPS) PCFactorSetMatSolverType(pc, "mumps"); #elif defined(PETSC_HAVE_MKL_PARDISO) @@ -181,6 +229,210 @@ namespace gmshfem::system } } + template< class T_Scalar > + void Solver< T_Scalar >::solveDistributed(std::vector< T_Scalar > &values, system::DISTRIBUTED_SOLVE_TYPE solveType, const DistributedContext<T_Scalar>& distributedContext, const bool reusePreconditioner) + { + // Unpack distributed Context + const std::vector< unsigned long long > &localIdOfOwned = distributedContext.localIDofOwned(); + const std::vector< unsigned long long > &localIdOfNonOwned = distributedContext.localIDofNonOwned(); + const std::vector< unsigned long long > &localToGlobalMap = distributedContext.localToGlobal(); + + //******************* + // Solve : + // A x = b for rhs index 0 + //******************* + try { +#ifdef HAVE_PETSC + if(_A->getModule()->name() != "A" && _A->getModule()->name() != "AFrequency") { + throw common::Exception("A separately assembled system can not be solved"); + } + + PC pc; + KSPSetReusePreconditioner(_kspDistributed, reusePreconditioner ? PETSC_TRUE : PETSC_FALSE); + + Mat A, B; + KSPGetOperators(_kspDistributed, &A, &B); + PetscInt m, n; + MatGetSize(A, &m, &n); + if((scalar::IsComplex< T_Scalar >::value == true && scalar::IsComplex< PetscScalar >::value == false ? 2 * _A->size() : _A->size()) != unsigned(m)) { + KSPSetOperators(_kspDistributed, NULL, NULL); + } + + if((_A->size() != static_cast< unsigned long long >(m) || _A->size() != static_cast< unsigned long long >(n)) || !reusePreconditioner) { + Mat mat = _A->getPetscDistributed(distributedContext); + KSPSetOperators(_kspDistributed, mat, mat); + MatDestroy(&mat); + } + KSPGetPC(_kspDistributed, &pc); + + std::string directSolver = "petsc"; +#if defined(PETSC_HAVE_MUMPS) + msg::debug << "Using MUMPS." << msg::endl; + directSolver = "mumps"; +#elif defined(PETSC_HAVE_MKL_PARDISO) + directSolver = "mkl_pardiso"; +#elif defined(PETSC_HAVE_UMFPACK) || defined(PETSC_HAVE_SUITESPARSE) + directSolver = "umfpack"; +#endif + + + bool useDDM = false; + switch(solveType) + { + case DISTRIBUTED_SOLVE_TYPE::DIRECT: + KSPSetType(_kspDistributed, "preonly"); + PCSetType(pc, "lu"); + PCFactorSetMatSolverType(pc, directSolver.c_str()); + break; + case DISTRIBUTED_SOLVE_TYPE::RAS: + KSPSetType(_kspDistributed, "gmres"); + PCSetType(pc, "asm"); + useDDM = true; + break; + case DISTRIBUTED_SOLVE_TYPE::ORAS: + KSPSetType(_kspDistributed, "gmres"); + PCSetType(pc, "asm"); + useDDM = true; + break; + + } + //KSPSetType(_kspDistributed, "gmres"); + //PCSetType(pc, "asm"); + //IS * loc, *glob; + //PetscInt nsub; + + //KSPSetType(_kspDistributed, "preonly"); + //PCSetType(pc, (_isCholesky< T_Scalar >(_A->getOptions().symmetric(), _A->getOptions().hermitian()) ? "cholesky" : "lu")); + + PetscViewerAndFormat *vf; + PetscViewerAndFormatCreate(PETSC_VIEWER_STDOUT_(PETSC_COMM_WORLD), PETSC_VIEWER_DEFAULT, &vf); + // Montior res + KSPMonitorSet(_kspDistributed, (PetscErrorCode(*)(KSP, PetscInt, PetscReal, void *))KSPMonitorResidual, vf, (PetscErrorCode(*)(void **))PetscViewerAndFormatDestroy); + if (_customMonitor) { + KSPMonitorSet(_kspDistributed, [](KSP ksp, PetscInt it, PetscReal rn, void * ctx) + {system::AbstractMonitor* monitor= static_cast<system::AbstractMonitor*> (ctx); return monitor->Monitor(ksp, it, rn);}, _customMonitor.get(), nullptr); + } + + KSPAppendOptionsPrefix(_kspDistributed, "fem_"); + KSPSetFromOptions(_kspDistributed); + + Vec sol; + Vec b = _b.at(0).getPetscDistributed(distributedContext); + VecScale(b, -1.0); + + // Create ghosted vector + std::vector< PetscInt > ghostsIndices; + for(auto sharedLocalIdx : localIdOfNonOwned) { + ghostsIndices.push_back(localToGlobalMap[sharedLocalIdx - 1] - 1); + } + VecCreateGhost(PETSC_COMM_WORLD, localIdOfOwned.size(), PETSC_DETERMINE, ghostsIndices.size(), ghostsIndices.data(), &sol); + msg::debug << "Local vector with ghosts : " << localIdOfOwned.size() << " owned DOFs and " << ghostsIndices.size() << " ghosts." << msg::endl; + PetscInt low, high; + VecGetOwnershipRange(sol, &low, &high); + PetscInt size; + VecGetLocalSize(sol, &size); + std::vector<PetscInt> isdata(localIdOfOwned.size() + ghostsIndices.size()); + for (unsigned k = 0; k < localIdOfOwned.size(); ++k){ + isdata[k] = low + k; + } + for (unsigned k = 0; k < ghostsIndices.size(); ++k){ + isdata[k + localIdOfOwned.size()] = ghostsIndices[k]; + } + + + IS mySubdomain; + if(useDDM && !reusePreconditioner) { + ISCreateGeneral(PETSC_COMM_WORLD, isdata.size(), isdata.data(), PETSC_COPY_VALUES, &mySubdomain); + PCASMSetLocalSubdomains(pc, 1, &mySubdomain, nullptr); + } + + + KSPType ksptype; + KSPGetType(_kspDistributed, &ksptype); + PCType pctype; + PCGetType(pc, &pctype); + MatSolverType stype; + PCFactorGetMatSolverType(pc, &stype); + if(common::GmshFem::getMPIRank() == 0) { + msg::info << "System size: " << size << " - " + << (ksptype ? ksptype : "none") << " " + << (pctype ? pctype : "none") << " " + << (stype ? stype : "none") << msg::endl; + } + + + ModifySubmatrixContext<T_Scalar> ctx; + ctx.ghostsIndices = &ghostsIndices; + ctx.nLocalRows = localIdOfOwned.size(); + ctx.localIdOfNonOwned = &localIdOfNonOwned; + ctx.localIdOfOwned = &localIdOfOwned; + ctx.localToGlobalMap = &localToGlobalMap; + ctx.A = _A; + + + + if (useDDM && solveType == DISTRIBUTED_SOLVE_TYPE::ORAS) + PCSetModifySubMatrices(pc, modifySubMatrix<T_Scalar>, &ctx); + + + KSPSetUp(_kspDistributed); + // SUB KSP + PetscInt nloc, firstloc; + KSP *subksp; + if(useDDM) { + PCASMGetSubKSP(pc, &nloc, &firstloc, &subksp); + msg::debug << "nloc: " << nloc << ", firstloc: " << firstloc << msg::endl; + PC subpc; + //subksp[0]; + KSPGetType(subksp[0], &ksptype); + KSPGetPC(subksp[0], &subpc); + PCSetType(subpc, PCLU); + PCGetType(subpc, &pctype); + PCFactorSetMatSolverType(subpc, "mumps"); + PCFactorGetMatSolverType(subpc, &stype); + if (gmshfem::common::GmshFem::getMPIRank() == 0) + msg::info << "Solving subdomains using : " << ksptype << ", " << (pctype ? pctype : "none") << ", " << (stype ? stype : "none") << msg::endl; + } + + + KSPSolve(_kspDistributed, b, sol); + // VISUALIZE SUB + /*PCASMGetLocalSubdomains(pc, &nsub, &loc, &glob); + msg::print << "Num subdomains:" << nsub << msg::endl; + int isSize; + ISGetLocalSize(loc[0], &isSize); + msg::print << "Local size:" << isSize << msg::endl; + */ + + // Visualize solution + VecGhostUpdateBegin(sol, INSERT_VALUES, SCATTER_FORWARD); + VecGhostUpdateEnd(sol, INSERT_VALUES, SCATTER_FORWARD); + Vec x_local; + VecGhostGetLocalForm(sol, &x_local); + + + VecGetLocalSize(x_local, &size); + + values.resize((scalar::IsComplex< T_Scalar >::value == true && scalar::IsComplex< PetscScalar >::value == false ? size / 2 : size)); + distributedContext.readScatteredData(values, sol); + //PetscScalar *array; + //VecGetArray(x_local, &array); + //PetscInterface< T_Scalar, PetscScalar > interface; + //interface(values, array, size); + //VecRestoreArray(x_local, &array); + + VecDestroy(&sol); + VecDestroy(&b); +#else + msg::error << "Unable to solve the linear system without PETSc" << msg::endl; +#endif + } + catch(const std::exception &exc) { + values.clear(); + throw; + } + } + template< class T_Scalar > void Solver< T_Scalar >::solveAll(std::vector< std::vector< T_Scalar > > &values, const bool reusePreconditioner) { @@ -303,6 +555,219 @@ namespace gmshfem::system #endif } + template< class T_Scalar > + void Solver< T_Scalar >::solveAllDistributed(std::vector< std::vector< T_Scalar > > &values, DISTRIBUTED_SOLVE_TYPE solveType, const DistributedContext<T_Scalar>& distributedContext, const bool reusePreconditioner) + { + //******************* +// Solve with MPI: +// A X = B for a thin rectangular matrix B (multi RHS) +//******************* + + // Unpack distributed Context + const std::vector< unsigned long long > &localIdOfOwned = distributedContext.localIDofOwned(); + const std::vector< unsigned long long > &localIdOfNonOwned = distributedContext.localIDofNonOwned(); + const std::vector< unsigned long long > &localToGlobalMap = distributedContext.localToGlobal(); + +#if !defined(PETSC_HAVE_HPDDM) || !defined(HAVE_PETSC) + throw gmshfem::common::Exception("Solving of multiple RHS requires PETSc with HPDDM"); +#else + + + try { + if(_A->getModule()->name() != "A" && _A->getModule()->name() != "AFrequency") { + throw common::Exception("A separately assembled system can not be solved"); + } + + PC pc; + KSPSetReusePreconditioner(_kspDistributed, reusePreconditioner ? PETSC_TRUE : PETSC_FALSE); + + Mat A, B; + KSPGetOperators(_kspDistributed, &A, &B); + PetscInt m, n, mloc, nloc; + MatGetSize(A, &m, &n); + if((scalar::IsComplex< T_Scalar >::value == true && scalar::IsComplex< PetscScalar >::value == false ? 2 * _A->size() : _A->size()) != unsigned(m)) { + KSPSetOperators(_kspDistributed, NULL, NULL); + } + + if((_A->size() != static_cast< unsigned long long >(m) || _A->size() != static_cast< unsigned long long >(n)) || !reusePreconditioner) { + + Mat mat = _A->getPetscDistributed(distributedContext); + KSPSetOperators(_kspDistributed, mat, mat); + MatGetSize(mat, &m, &n); + MatGetLocalSize(mat, &mloc, &nloc); + MatDestroy(&mat); + } + KSPGetPC(_kspDistributed, &pc); + + std::string directSolver = "petsc"; +#if defined(PETSC_HAVE_MUMPS) + msg::debug << "Using MUMPS." << msg::endl; + directSolver = "mumps"; +#elif defined(PETSC_HAVE_MKL_PARDISO) + directSolver = "mkl_pardiso"; +#elif defined(PETSC_HAVE_UMFPACK) || defined(PETSC_HAVE_SUITESPARSE) + directSolver = "umfpack"; +#endif + + + KSPSetType(_kspDistributed, KSPHPDDM); + + + bool useDDM = false; + switch(solveType) + { + case DISTRIBUTED_SOLVE_TYPE::DIRECT: + KSPHPDDMSetType(_kspDistributed, KSP_HPDDM_TYPE_PREONLY); + PCSetType(pc, "lu"); + PCFactorSetMatSolverType(pc, directSolver.c_str()); + break; + case DISTRIBUTED_SOLVE_TYPE::RAS: + KSPHPDDMSetType(_kspDistributed, KSP_HPDDM_TYPE_BGMRES); + PCSetType(pc, "asm"); + useDDM = true; + break; + case DISTRIBUTED_SOLVE_TYPE::ORAS: + KSPHPDDMSetType(_kspDistributed, KSP_HPDDM_TYPE_BGMRES); + PCSetType(pc, "asm"); + useDDM = true; + break; + + } + + + KSPAppendOptionsPrefix(_kspDistributed, "fem_"); + KSPSetFromOptions(_kspDistributed); + PetscViewerAndFormat *vf; + PetscViewerAndFormatCreate(PETSC_VIEWER_STDOUT_(PETSC_COMM_WORLD), PETSC_VIEWER_DEFAULT, &vf); + KSPMonitorSet(_kspDistributed, (PetscErrorCode(*)(KSP, PetscInt, PetscReal, void *))KSPMonitorResidual, vf, (PetscErrorCode(*)(void **))PetscViewerAndFormatDestroy); + + + // Define block matrices + unsigned nRHS = _b.size(); + Mat BlockRHS, BlockY; + BlockRHS = VectorFactory< T_Scalar >::generateDistributedBlockRHS(_b, distributedContext); + MatDuplicate(BlockRHS, MAT_DO_NOT_COPY_VALUES, &BlockY); + MatScale(BlockRHS, -1); + + + // If DDM is used, define subdomains + std::vector< PetscInt > ghostsIndices; + for(auto sharedLocalIdx : localIdOfNonOwned) { + ghostsIndices.push_back(localToGlobalMap[sharedLocalIdx - 1] - 1); + } + + PetscInt low, high; + MatGetOwnershipRange(BlockRHS, &low, &high); + PetscInt size; + //VecGetLocalSize(sol, &size); + std::vector<PetscInt> isdata(localIdOfOwned.size() + ghostsIndices.size()); + for (unsigned k = 0; k < localIdOfOwned.size(); ++k){ + isdata[k] = low + k; + } + for (unsigned k = 0; k < ghostsIndices.size(); ++k){ + isdata[k + localIdOfOwned.size()] = ghostsIndices[k]; + } + + IS mySubdomain; + if(useDDM && !reusePreconditioner) { + ISCreateGeneral(PETSC_COMM_WORLD, isdata.size(), isdata.data(), PETSC_COPY_VALUES, &mySubdomain); + PCASMSetLocalSubdomains(pc, 1, &mySubdomain, nullptr); + } + + // Setup DDM and subsolvers + ModifySubmatrixContext<T_Scalar> ctx; + ctx.ghostsIndices = &ghostsIndices; + ctx.nLocalRows = localIdOfOwned.size(); + ctx.localIdOfNonOwned = &localIdOfNonOwned; + ctx.localIdOfOwned = &localIdOfOwned; + ctx.localToGlobalMap = &localToGlobalMap; + ctx.A = _A; + + + if (useDDM && solveType == DISTRIBUTED_SOLVE_TYPE::ORAS) + PCSetModifySubMatrices(pc, modifySubMatrix<T_Scalar>, &ctx); + + KSPSetUp(_kspDistributed); + // SUB KSP + PetscInt nLocalSubdomains, firstloc; + KSP *subksp; + if(useDDM) { + PCASMGetSubKSP(pc, &nLocalSubdomains, &firstloc, &subksp); + msg::debug << "nloc: " << nLocalSubdomains << ", firstloc: " << firstloc << msg::endl; + PC subpc; + KSPType ksptype; + PCType pctype; + MatSolverType stype; + //subksp[0]; + KSPGetType(subksp[0], &ksptype); + KSPGetPC(subksp[0], &subpc); + PCSetType(subpc, PCLU); + PCGetType(subpc, &pctype); + PCFactorSetMatSolverType(subpc, "mumps"); + PCFactorGetMatSolverType(subpc, &stype); + if(gmshfem::common::GmshFem::getMPIRank() == 0) + msg::info << "Solving subdomains using : " << ksptype << ", " << (pctype ? pctype : "none") << ", " << (stype ? stype : "none") << msg::endl; + } + + + KSPType ksptype; + KSPGetType(_kspDistributed, &ksptype); + PCType pctype; + PCGetType(pc, &pctype); + MatSolverType stype; + PCFactorGetMatSolverType(pc, &stype); + if(common::GmshFem::getMPIRank() == 0) { + msg::info << "System size: " << m << " - " + << (ksptype ? ksptype : "none") << " " + << (pctype ? pctype : "none") << " " + << (stype ? stype : "none") << msg::endl; + } + + KSPMatSolve(_kspDistributed, BlockRHS, BlockY); + +#warning "Handle FEM complex + real PETSc ?" + //values.resize((scalar::IsComplex< T_Scalar >::value == true && scalar::IsComplex< PetscScalar >::value == false ? size / 2 : size)); + + + for (unsigned c = 0; c < nRHS; ++c) { + values[c].resize(mloc + ghostsIndices.size()); + Vec col; + MatDenseGetColumnVecRead(BlockY, c, &col); + distributedContext.readScatteredData(values[c], col); + MatDenseRestoreColumnVecRead(BlockY, c, &col); +} + + // Create a single Vec that contains the block solution, column major format. + /*PetscScalar *array; + MatDenseGetArray(BlockY, &array); + PetscInterface< T_Scalar, PetscScalar > interface; + //MatView(BlockY, PETSC_VIEWER_STDOUT_WORLD); + + for(unsigned iRHS = 0; iRHS < nRHS; ++iRHS) { + // Get the pointer to the beginning of the iRHS-th column. + PetscScalar *col_start = array + iRHS * mloc; + + + // TODO: have value read ghosts + values[iRHS].resize(mloc); + interface(values[iRHS], col_start, mloc); + } + + MatDenseRestoreArray(BlockY, &array);*/ + + MatDestroy(&BlockRHS); + MatDestroy(&BlockY); + + // Don't set fields to the solved system; loadSolution() should be used. + } + + catch(const std::exception &exc) { + values.clear(); + throw; + } +#endif + } + template< class T_Scalar > void Solver< T_Scalar >::eigensolve(std::vector< scalar::ComplexPrecision< T_Scalar > > &eigenvalues, std::vector< std::vector< scalar::ComplexPrecision< T_Scalar > > > &eigenvectors, const bool computeEigenvectors, const unsigned int numberOfEigenvalues, const scalar::ComplexPrecision< T_Scalar > target) { diff --git a/src/system/VectorFactory.cpp b/src/system/VectorFactory.cpp index ca568cf56e2addc839d417bec2d623c16c4f24a6..239e0dd0408403321c696f631335348cecf52e5f 100644 --- a/src/system/VectorFactory.cpp +++ b/src/system/VectorFactory.cpp @@ -47,6 +47,28 @@ namespace gmshfem::system _vec.resize(_size, 0.); } + template< class T_Scalar > + void VectorFactory< T_Scalar >::save(const std::string &name) const + { + common::CSVio file(name); + if(!file.isOpen()) { + throw common::Exception("Cannot open file " + name + ".csv"); + } + + file << csv::precision(scalar::PrecisionDigits< T_Scalar >::value) << csv::scientific; + if(scalar::IsComplex< T_Scalar >::value) { + for(auto i = 0ULL; i < _vec.size(); ++i) { + file << std::real(_vec[i]) << std::imag(_vec[i]) << csv::endl; + } + } + else { + for(auto i = 0ULL; i < _vec.size(); ++i) { + file << _vec[i] << csv::endl; + } + } + file.close(); + } + template< class T_Scalar > void VectorFactory< T_Scalar >::removeVector() { @@ -268,6 +290,60 @@ namespace gmshfem::system interface(_size, &_vec[0], &vecPetsc, _shouldBeDestroyedWithPetsc); return vecPetsc; } + +#ifdef HAVE_MPI + template< class T_Scalar > + Vec VectorFactory< T_Scalar >::getPetscDistributed(const DistributedContext<T_Scalar>& distributedContext) const + { + PetscScalar* data = static_cast<PetscScalar*>(generateRawLocalData(distributedContext)); + unsigned nLocDofs = distributedContext.localIDofOwned().size(); + + Vec vecPetsc; + VecCreateMPIWithArray(PETSC_COMM_WORLD, 1, nLocDofs, PETSC_DETERMINE, data, &vecPetsc); + _shouldBeDestroyedWithPetsc.push_back(data); + + return vecPetsc; + } + template< class T_Scalar > + void *VectorFactory< T_Scalar >::generateRawLocalData(const DistributedContext<T_Scalar>& distributedContext) const + { + const auto& localDofs = distributedContext.localIDofOwned(); + unsigned nLocalRows = localDofs.size(); + // In the distributed vector, copy the owned DOFs + //std::vector<PetscScalar> data(nLocalRows); + PetscScalar* data = (PetscScalar *)std::malloc(nLocalRows * sizeof(PetscScalar)); + for (unsigned i = 0; i < nLocalRows; ++i) { + //msg::print << "vec: " << i << ", " << localDofs[i] << ", " << localToGlobal[i] << msg::endl; + data[i] = _vec.at(localDofs[i]-1); + } + + return data; // Must be deleted by caller with std::free + } + template< class T_Scalar > + Mat VectorFactory< T_Scalar >::generateDistributedBlockRHS(const std::vector< VectorFactory< T_Scalar > > &allRHS, const DistributedContext<T_Scalar>& distributedContext) + { + unsigned nLocDofs = distributedContext.localIDofOwned().size(); + unsigned nRHS = allRHS.size(); + + Mat blockRHS; + MatCreateDense(PETSC_COMM_WORLD, nLocDofs, PETSC_DECIDE, PETSC_DECIDE, nRHS, nullptr, &blockRHS); + PetscScalar *array; + MatDenseGetArray(blockRHS, &array); + PetscInterface< T_Scalar, PetscScalar > interface; + + for(unsigned iRHS = 0; iRHS < nRHS; ++iRHS) { + // Get the pointer to the beginning of the iRHS-th column. + PetscScalar *col_start = array + iRHS * nLocDofs; + PetscScalar *colData = static_cast< PetscScalar * >(allRHS[iRHS].generateRawLocalData(distributedContext)); + std::copy(colData, colData + nLocDofs, col_start); + std::free(colData); + } + + MatDenseRestoreArray(blockRHS, &array); + + return blockRHS; + } +#endif #endif template< class T_Scalar > diff --git a/src/system/VectorFactory.h b/src/system/VectorFactory.h index 215b567eb37767c2705ee0c6a66be90462233506..e27d5013b4a8440ba78d75180b7c174b0e5fce70 100644 --- a/src/system/VectorFactory.h +++ b/src/system/VectorFactory.h @@ -40,6 +40,7 @@ namespace gmshfem::system ~VectorFactory(); void init(const unsigned long long size); + void save(const std::string& name) const; void removeVector(); void removePetscData(); @@ -56,6 +57,13 @@ namespace gmshfem::system #ifdef HAVE_PETSC Vec getPetsc() const; + #ifdef HAVE_MPI + Vec getPetscDistributed(const DistributedContext<T_Scalar>& distributedContext) const; + // Return PetscScalar* but not declared yet + void* generateRawLocalData(const DistributedContext<T_Scalar>& distributedContext) const; + + static Mat generateDistributedBlockRHS(const std::vector<VectorFactory<T_Scalar>>& allRHS, const DistributedContext<T_Scalar>& distributedContext); + #endif #endif common::Memory memory() const; unsigned long long size() const; diff --git a/unitTests/correctness/CMakeLists.txt b/unitTests/correctness/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..aad68ce18c83b60d1b323b51852be434960ba31c --- /dev/null +++ b/unitTests/correctness/CMakeLists.txt @@ -0,0 +1,39 @@ + +# List of all test cases for iteration +set(TEST_CASES + distributedPoisson1DDirichlet + distributedPoisson1DLagrangeMultiplier + distributedPoisson2DTwoRanksDirect + distributedPoisson2DORAS +) + +# Define test cases with associated ranks +set(distributedPoisson1DDirichlet_RANKS 4) +set(distributedPoisson1DLagrangeMultiplier_RANKS 2) +set(distributedPoisson2DTwoRanksDirect_RANKS 2) +set(distributedPoisson2DORAS_RANKS 4) + +foreach(TUTO IN LISTS TEST_CASES) + # Retrieve the number of ranks for this test case + set(NRANKS_VAR "${TUTO}_RANKS") + set(NRANKS "${${NRANKS_VAR}}") + + # Define test commands + set(TEST_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${TUTO}/build") + + # Ensure the directory is created + add_test(NAME "correctness:${TUTO}:mkdir" COMMAND "${CMAKE_COMMAND}" -E make_directory "${TEST_DIR}") + + # Configure the test + add_test(NAME "correctness:${TUTO}:cmake" COMMAND "${CMAKE_COMMAND}" -DCMAKE_PREFIX_PATH=${CMAKE_INSTALL_PREFIX} .. WORKING_DIRECTORY "${TEST_DIR}") + + # Build the test + add_test(NAME "correctness:${TUTO}:build" COMMAND "${CMAKE_MAKE_PROGRAM}" WORKING_DIRECTORY "${TEST_DIR}") + + # Determine if the test is MPI based on the number of ranks + if(NOT "${NRANKS}" STREQUAL "1") + add_test(NAME "correctness:${TUTO}:run" COMMAND mpirun -n ${NRANKS} ./tuto -maxThreads 1 WORKING_DIRECTORY "${TEST_DIR}") + else() + add_test(NAME "correctness:${TUTO}:run" COMMAND ./tuto -maxThreads 1 WORKING_DIRECTORY "${TEST_DIR}") + endif() +endforeach() diff --git a/unitTests/correctness/distributedPoisson1DDirichlet/CMakeLists.txt b/unitTests/correctness/distributedPoisson1DDirichlet/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3ed63f13623e9d7ed45010b2a7ffd418461a2b0c --- /dev/null +++ b/unitTests/correctness/distributedPoisson1DDirichlet/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +project(tuto CXX) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../tutorial.cmake) + +add_executable(tuto main.cpp ${EXTRA_INCS}) +target_link_libraries(tuto ${EXTRA_LIBS}) diff --git a/unitTests/correctness/distributedPoisson1DDirichlet/main.cpp b/unitTests/correctness/distributedPoisson1DDirichlet/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..66d8e5c8e95c3097bdc1837a18b866781f7da54d --- /dev/null +++ b/unitTests/correctness/distributedPoisson1DDirichlet/main.cpp @@ -0,0 +1,153 @@ +#include <algorithm> +#include <gmsh.h> +#include <gmshfem/Formulation.h> +#include <gmshfem/GmshFem.h> +#include <gmshfem/DistributedField.h> +#include <gmshfem/MatrixFactory.h> +#include <gmshfem/VectorFactory.h> +#include <chrono> +#include <thread> + +using namespace gmshfem; +using namespace gmshfem::common; +using namespace gmshfem::problem; +using namespace gmshfem::domain; +using namespace gmshfem::field; +using namespace gmshfem::function; +using namespace gmshfem::post; +using namespace gmshfem::equation; +using namespace gmshfem::msg; + +void genMesh(double lc, unsigned nDom, unsigned nOverlap) +{ + namespace geo = gmsh::model::geo; + + gmsh::model::add("genMesh"); + gmsh::option::setNumber("General.ExpertMode", 1); + double L = 2; + double domSize = L / nDom; + double delta = nOverlap * lc; + if (delta > domSize) + { + throw common::Exception("Subdomains too small!"); + } + + std::vector<int> points; + points.reserve(nDom * 3 + 1); + points.push_back(geo::addPoint(0, 0, 0, lc)); + for (unsigned iDom = 0; iDom < nDom; ++iDom) + { + double x = iDom * domSize; + points.push_back(geo::addPoint(x + delta, 0, 0, lc)); + points.push_back(geo::addPoint(x + domSize - delta, 0, 0, lc)); + points.push_back(geo::addPoint(x + domSize, 0, 0, lc)); + } + + std::vector<int> lines; + lines.reserve(3 * nDom); + for (unsigned iDom = 0; iDom < nDom; ++iDom) + { + lines.push_back(geo::addLine(points[iDom * 3], points[iDom * 3 + 1])); + lines.push_back(geo::addLine(points[iDom * 3 + 1], points[iDom * 3 + 2])); + lines.push_back(geo::addLine(points[iDom * 3 + 2], points[iDom * 3 + 3])); + } + + gmsh::model::geo::synchronize(); + gmsh::model::mesh::generate(); + + for (unsigned iDom = 0; iDom < nDom; ++iDom) + { + gmsh::model::addPhysicalGroup(1, {lines[iDom*3], lines[iDom*3+1], lines[iDom*3+2]}, 100 * iDom + 1, "omega"); + std::vector<int> overlap; + if (iDom > 0) + overlap.push_back(lines[3*iDom-1]); + if (iDom != (nDom - 1)) + overlap.push_back(lines[3*iDom+3]); + gmsh::model::addPhysicalGroup(1, {overlap}, 100 * iDom + 2, "overlap"); + if (iDom == 0) + gmsh::model::addPhysicalGroup(0, {points[0]}, iDom * 100 + 3, "boundary"); + if (iDom == (nDom - 1)) + gmsh::model::addPhysicalGroup(0, {points.back()}, iDom * 100 + 4, "boundary"); + gmsh::model::geo::synchronize(); + } + + // Everyone needs the full mesh (so far) + gmsh::model::addPhysicalGroup(1, {lines}, 1234, "omega_all"); + gmsh::write("mesh_full.msh"); + + gmsh::model::remove(); +} + +int main(int argc, char **argv) +{ + using namespace std::chrono_literals; + + GmshFem gmshFem(argc, argv); + unsigned int MPI_Rank = gmshFem.getMPIRank(); + unsigned MPI_Size = gmshFem.getMPISize(); + //if (MPI_Size != 2) + // throw Exception("Must run with 2 ranks! (Got " + std::to_string(MPI_Size) + ")"); + + int order = 1; + gmshFem.userDefinedParameter(order, "order"); + + double lc = 2.0 / MPI_Size / 3; // 3 elements per domain + gmshFem.userDefinedParameter(lc, "lc"); + + /**** + * Gmsh part + ****/ + if (MPI_Rank == 0) + genMesh(lc, MPI_Size, 1); + + std::this_thread::sleep_for(100ms); + bool volumeSrc = false; + gmshFem.userDefinedParameter(volumeSrc, "volumeSrc"); + + // -X²/2 + X has laplacian -1, is 0 on 0 and 2 on 2 + // function::ScalarFunction<std::complex<double>> analytical = -x<std::complex<double>>()*x<std::complex<double>>()/2 + 2*x<std::complex<double>>(); + auto analytical = real(-x<std::complex<double>>() * x<std::complex<double>>() / 2 + 2 * x<std::complex<double>>()); + if (!volumeSrc) + { + analytical = 1 * x<std::complex<double>>(); + } + gmsh::open("mesh_full.msh"); + gmsh::model::mesh::createEdges(); + + Formulation<std::complex<double>> formulation("formulation_" + std::to_string(MPI_Rank)); + Domain omega(1, MPI_Rank * 100 + 1); + Domain overlap(1, MPI_Rank * 100 + 2); + Domain boundary; + + DistributedField<std::complex<double>, Form::Form0> v("v", omega, overlap, FunctionSpaceTypeForm0::HierarchicalH1, order); + if (MPI_Rank == 0 || MPI_Rank == (MPI_Size - 1)) + { + int localIdx = MPI_Rank == 0 ? 3 : 4; + boundary = Domain(0, MPI_Rank * 100 + localIdx); + v.addConstraint(boundary, x<std::complex<double>>()); + } + + formulation.integral(grad(dof(v)), grad(tf(v)), v.domain(), "Gauss6"); + if (volumeSrc) + formulation.integral(-1, tf(v), v.domain(), "Gauss6"); + + formulation.pre(); + formulation.preDistributed(); + formulation.assemble(); + gmshFem.BarrierMPI(); + + formulation.solveDistributed(system::DISTRIBUTED_SOLVE_TYPE::DIRECT, false); + save(v, omega, "v_" + std::to_string(MPI_Rank)); + + auto error1 = real(integrate(pow(real(v - analytical), 2), omega, "Gauss6")) / real(integrate(pow(real(analytical), 2), omega, "Gauss6")); + msg::print << "L2 relative error in domain " << MPI_Rank << " : " << error1 << msg::endl; + save(analytical, omega, "v_truth_" + std::to_string(MPI_Rank)); + save(analytical - v, omega, "v_err_" + std::to_string(MPI_Rank)); + + if (abs(error1) > 1e-6) + { + throw common::Exception("Error above tolerance. Log10 is : " + std::to_string(log10(error1))); + } + + return 0; +} diff --git a/unitTests/correctness/distributedPoisson1DLagrangeMultiplier/CMakeLists.txt b/unitTests/correctness/distributedPoisson1DLagrangeMultiplier/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3ed63f13623e9d7ed45010b2a7ffd418461a2b0c --- /dev/null +++ b/unitTests/correctness/distributedPoisson1DLagrangeMultiplier/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +project(tuto CXX) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../tutorial.cmake) + +add_executable(tuto main.cpp ${EXTRA_INCS}) +target_link_libraries(tuto ${EXTRA_LIBS}) diff --git a/unitTests/correctness/distributedPoisson1DLagrangeMultiplier/main.cpp b/unitTests/correctness/distributedPoisson1DLagrangeMultiplier/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25fc98d1318578601d3d16786942e0ca9693840d --- /dev/null +++ b/unitTests/correctness/distributedPoisson1DLagrangeMultiplier/main.cpp @@ -0,0 +1,151 @@ +#include <algorithm> +#include <gmsh.h> +#include <gmshfem/Formulation.h> +#include <gmshfem/GmshFem.h> +#include <gmshfem/DistributedField.h> +#include <gmshfem/MatrixFactory.h> +#include <gmshfem/VectorFactory.h> +#include <chrono> +#include <thread> + +using namespace gmshfem; +using namespace gmshfem::common; +using namespace gmshfem::problem; +using namespace gmshfem::domain; +using namespace gmshfem::field; +using namespace gmshfem::function; +using namespace gmshfem::post; +using namespace gmshfem::equation; +using namespace gmshfem::msg; + +void genMesh(double lc, unsigned nDom, unsigned nOverlap) +{ + namespace geo = gmsh::model::geo; + + gmsh::model::add("genMesh"); + gmsh::option::setNumber("General.ExpertMode", 1); + double L = 2; + double domSize = L / nDom; + double delta = nOverlap * lc; + if (delta > domSize) + { + throw common::Exception("Subdomains too small!"); + } + + std::vector<int> points; + points.reserve(nDom * 3 + 1); + points.push_back(geo::addPoint(0, 0, 0, lc)); + for (unsigned iDom = 0; iDom < nDom; ++iDom) + { + double x = iDom * domSize; + points.push_back(geo::addPoint(x + delta, 0, 0, lc)); + points.push_back(geo::addPoint(x + domSize - delta, 0, 0, lc)); + points.push_back(geo::addPoint(x + domSize, 0, 0, lc)); + } + + std::vector<int> lines; + lines.reserve(3 * nDom); + for (unsigned iDom = 0; iDom < nDom; ++iDom) + { + lines.push_back(geo::addLine(points[iDom * 3], points[iDom * 3 + 1])); + lines.push_back(geo::addLine(points[iDom * 3 + 1], points[iDom * 3 + 2])); + lines.push_back(geo::addLine(points[iDom * 3 + 2], points[iDom * 3 + 3])); + } + + gmsh::model::geo::synchronize(); + gmsh::model::mesh::generate(); + + for (unsigned iDom = 0; iDom < nDom; ++iDom) + { + gmsh::model::addPhysicalGroup(1, {lines[iDom*3], lines[iDom*3+1], lines[iDom*3+2]}, 100 * iDom + 1, "omega"); + std::vector<int> overlap; + if (iDom > 0) + overlap.push_back(lines[3*iDom-1]); + if (iDom != (nDom - 1)) + overlap.push_back(lines[3*iDom+3]); + gmsh::model::addPhysicalGroup(1, {overlap}, 100 * iDom + 2, "overlap"); + if (iDom == 0) + gmsh::model::addPhysicalGroup(0, {points[0]}, iDom * 100 + 3, "boundary"); + if (iDom == (nDom - 1)) + gmsh::model::addPhysicalGroup(0, {points.back()}, iDom * 100 + 4, "boundary"); + gmsh::model::geo::synchronize(); + } + + // Everyone needs the full mesh (so far) + gmsh::model::addPhysicalGroup(1, {lines}, 1234, "omega_all"); + gmsh::write("mesh_full.msh"); + + gmsh::model::remove(); +} + +int main(int argc, char **argv) +{ + using namespace std::chrono_literals; + + GmshFem gmshFem(argc, argv); + unsigned int MPI_Rank = gmshFem.getMPIRank(); + unsigned MPI_Size = gmshFem.getMPISize(); + if (MPI_Size == 1) + throw Exception("Must run with multiple ranks! (Got " + std::to_string(MPI_Size) + ")"); + + int order = 1; + gmshFem.userDefinedParameter(order, "order"); + + double lc = 2.0 / MPI_Size / 3; // 3 elements per domain + gmshFem.userDefinedParameter(lc, "lc"); + + /**** + * Gmsh part + ****/ + if (MPI_Rank == 0) + genMesh(lc, MPI_Size, 1); + + std::this_thread::sleep_for(100ms); + + auto analytical = 1 * x<std::complex<double>>(); + gmsh::open("mesh_full.msh"); + gmsh::model::mesh::createEdges(); + + Formulation<std::complex<double>> formulation("formulation_" + std::to_string(MPI_Rank)); + Domain omega(1, MPI_Rank * 100 + 1); + Domain overlap(1, MPI_Rank * 100 + 2); + Domain boundary; + + DistributedField<std::complex<double>, Form::Form0> v("v", omega, overlap, FunctionSpaceTypeForm0::HierarchicalH1, order); + std::unique_ptr<Field<std::complex<double>, Form::Form0>> lambda_ptr; // Optional Lagrange multiplier + if (MPI_Rank == 0 || MPI_Rank == (MPI_Size - 1)) + { + int localIdx = MPI_Rank == 0 ? 3 : 4; + boundary = Domain(0, MPI_Rank * 100 + localIdx); + lambda_ptr = std::make_unique<Field<std::complex<double>, Form::Form0>>("lambda", boundary, FunctionSpaceTypeForm0::Lagrange); + // Add inhomogenuous Neumann : + formulation.integral(dof(*lambda_ptr), tf(v), boundary, "Gauss0"); + // Apply constraint + formulation.integral(dof(v), tf(*lambda_ptr), boundary, "Gauss0"); + formulation.integral(-analytical, tf(*lambda_ptr), boundary, "Gauss0"); + + } + + formulation.integral(grad(dof(v)), grad(tf(v)), v.domain(), "Gauss6"); + + + formulation.pre(); + formulation.preDistributed(); + formulation.assemble(); + gmshFem.BarrierMPI(); + + formulation.solveDistributed(system::DISTRIBUTED_SOLVE_TYPE::DIRECT, false); + save(v, omega, "v_" + std::to_string(MPI_Rank)); + + auto error1 = real(integrate(pow(real(v - analytical), 2), omega, "Gauss6")) / real(integrate(pow(real(analytical), 2), omega, "Gauss6")); + msg::print << "L2 relative error in domain " << MPI_Rank << " : " << error1 << msg::endl; + save(analytical, omega, "v_truth_" + std::to_string(MPI_Rank)); + save(analytical - v, omega, "v_err_" + std::to_string(MPI_Rank)); + + if (abs(error1) > 1e-6) + { + throw common::Exception("Error above tolerance. Log10 is : " + std::to_string(log10(error1))); + } + + return 0; +} diff --git a/unitTests/correctness/distributedPoisson2DORAS/CMakeLists.txt b/unitTests/correctness/distributedPoisson2DORAS/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3ed63f13623e9d7ed45010b2a7ffd418461a2b0c --- /dev/null +++ b/unitTests/correctness/distributedPoisson2DORAS/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) + +project(tuto CXX) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../tutorial.cmake) + +add_executable(tuto main.cpp ${EXTRA_INCS}) +target_link_libraries(tuto ${EXTRA_LIBS}) diff --git a/unitTests/correctness/distributedPoisson2DORAS/main.cpp b/unitTests/correctness/distributedPoisson2DORAS/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5923c183c0182d5ffca9bdc8ba47c28e7221ee7d --- /dev/null +++ b/unitTests/correctness/distributedPoisson2DORAS/main.cpp @@ -0,0 +1,166 @@ +#include <algorithm> +#include <gmsh.h> +#include <gmshfem/Formulation.h> +#include <gmshfem/GmshFem.h> +#include <gmshfem/DistributedField.h> +#include <gmshfem/MatrixFactory.h> +#include <gmshfem/VectorFactory.h> +#include <chrono> +#include <thread> +#include <gmshfem/CSVio.h> +using namespace gmshfem; +using namespace gmshfem::common; +using namespace gmshfem::problem; +using namespace gmshfem::domain; +using namespace gmshfem::field; +using namespace gmshfem::function; +using namespace gmshfem::post; +using namespace gmshfem::equation; +using namespace gmshfem::msg; + +void genMesh() +{ + gmsh::model::add("genMesh"); + + namespace geo = gmsh::model::geo; + + std::vector<int> points, hlines, vlines, surfaces; + std::vector<double> xs{0, 0.4, 0.5, 0.6, 1.0}; + std::vector<double> ys = xs; + for (double y : ys) + { + for (double x : xs) + points.push_back(geo::addPoint(x, y, 0)); + } + + for (unsigned j = 0; j < 5; ++j) + { + for (unsigned i = 0; i < 4; ++i) + { + hlines.push_back(geo::addLine(points[j * 5 + i], points[j * 5 + i + 1])); + gmsh::model::geo::mesh::setTransfiniteCurve(hlines.back(), 2); + + } + } + + for (unsigned j = 0; j < 4; ++j) + { + for (unsigned i = 0; i < 5; ++i) + { + vlines.push_back(geo::addLine(points[j * 5 + i], points[(j+1) * 5 + i])); + gmsh::model::geo::mesh::setTransfiniteCurve(vlines.back(), 2); + + } + } + + + for (unsigned j = 0; j < 4; ++j) + { + for (unsigned i = 0; i < 4; ++i) + { + int curve = geo::addCurveLoop({hlines[4 * j + i], vlines[5 * j + i + 1], -hlines[4 * (j + 1) + i], -vlines[5 * j + i]}); + surfaces.push_back(geo::addPlaneSurface({curve})); + geo::mesh::setTransfiniteSurface(surfaces.back()); + //geo::mesh::setRecombine(2, surfaces.back()); + } + } + + + gmsh::model::geo::synchronize(); + + bool makeFourDomains = true; + using gmsh::model::addPhysicalGroup; + if (makeFourDomains) { + // Each subdomain has an "omega" (1), an "overlap" (2), a part of the physical boundary (3) (overlap inc.), and a coupling boundary (4) + // ID: above + 100*domain index + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 2; ++j) { + int domIdx = 2*i+j; + int offset = 8*i + 2*j; + addPhysicalGroup(2, {surfaces[offset+ 0], surfaces[offset+1], surfaces[offset+4], surfaces[offset+4+1]}, 1 + 100*domIdx); + } + } + + // Define overlaps + addPhysicalGroup(2, {surfaces[2], surfaces[6], surfaces[10], surfaces[9], surfaces[8]}, 2 + 100 * 0); + addPhysicalGroup(2, {surfaces[1], surfaces[5], surfaces[9], surfaces[10], surfaces[11]}, 2 + 100 * 1); + addPhysicalGroup(2, {surfaces[4], surfaces[5], surfaces[6], surfaces[10], surfaces[14]}, 2 + 100 * 2); + addPhysicalGroup(2, {surfaces[5], surfaces[6], surfaces[7], surfaces[9], surfaces[13]}, 2 + 100 * 3); + + // Define physical boundaries (with overlap) + addPhysicalGroup(1, {hlines[0], hlines[1], hlines[2], vlines[0], vlines[5], vlines[10]}, 3 + 100 * 0); + addPhysicalGroup(1, {hlines[1], hlines[2], hlines[3], vlines[4], vlines[9], vlines[14]}, 3 + 100 * 1); + addPhysicalGroup(1, {hlines[16], hlines[17], hlines[18], vlines[5], vlines[10], vlines[15]}, 3 + 100 * 2); + addPhysicalGroup(1, {hlines[17], hlines[18], hlines[19], vlines[9], vlines[14], vlines[19]}, 3 + 100 * 3); + + // Define coupling boundaries + addPhysicalGroup(1, {hlines[12], hlines[13], hlines[14], vlines[3], vlines[8], vlines[13]}, 4 + 100 * 0); + addPhysicalGroup(1, {hlines[13], hlines[14], hlines[15], vlines[1], vlines[6], vlines[11]}, 4 + 100 * 1); + addPhysicalGroup(1, {hlines[4], hlines[5], hlines[6], vlines[8], vlines[13], vlines[18]}, 4 + 100 * 2); + addPhysicalGroup(1, {hlines[5], hlines[6], hlines[7], vlines[6], vlines[11], vlines[16]}, 4 + 100 * 3); + } + + //gmsh::model::addPhysicalGroup(1, hlines); + //gmsh::model::addPhysicalGroup(2, surfaces); + gmsh::model::mesh::generate(); + + gmsh::write("mesh.msh"); + gmsh::model::remove(); +} + +int main(int argc, char **argv) +{ + using namespace std::chrono_literals; + + GmshFem gmshFem(argc, argv); + unsigned int MPI_Rank = gmshFem.getMPIRank(); + unsigned MPI_Size = gmshFem.getMPISize(); + if (MPI_Size != 4) + throw Exception("Must run with 4 ranks! (Got " + std::to_string(MPI_Size) + ")"); + + /**** + * Gmsh part + ****/ + if (MPI_Rank == 0) + genMesh(); + gmshFem.BarrierMPI(); + + // x + 2y + 1, harmonic fct + auto analytical = real(x<std::complex<double>>() + 2 * y<std::complex<double>>() + 1); + gmsh::open("mesh.msh"); + gmsh::model::mesh::createEdges(); + gmsh::model::mesh::createFaces(); + + Formulation<std::complex<double>> formulation("formulation_" + std::to_string(MPI_Rank)); + //* + int shift = MPI_Rank * 100; + Domain omega(2, 1 + shift); + Domain overlap(2, 2 + shift); + Domain boundary(1, 3 + shift); + Domain coupling(1, 4 + shift); + + DistributedField<std::complex<double>, Form::Form0> v("v", omega, overlap, FunctionSpaceTypeForm0::HierarchicalH1, 1); + + formulation.integral(grad(dof(v)), grad(tf(v)), v.domain(), "Gauss6"); + formulation.integral(dof(v), tf(v), coupling, "Gauss6"); + + + v.addConstraint(boundary, analytical); + + formulation.pre(); + formulation.preDistributed(); + formulation.assemble(); + formulation.solveDistributed(system::DISTRIBUTED_SOLVE_TYPE::ORAS, false); + + auto error1 = real(integrate(pow(real(v - analytical), 2), omega, "Gauss6")) / real(integrate(pow(real(analytical), 2), omega, "Gauss6")); + msg::print << "L2 absolute error in domain " << MPI_Rank << " : " << error1 << msg::endl; + save(v, omega, "v_" + std::to_string(MPI_Rank)); + save(analytical, omega, "sol_" + std::to_string(MPI_Rank)); + + if (error1 > 1e-4) + { + throw common::Exception("Error above tolerance : " + std::to_string(error1)); + } + + return 0; +}