diff --git a/tutorials/helmholtz/free_space/CMakeLists.txt b/tutorials/helmholtz/free_space/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7481f3ad58dcd475585824c7f40fddf332e191dc --- /dev/null +++ b/tutorials/helmholtz/free_space/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/tutorials/helmholtz/free_space/main.cpp b/tutorials/helmholtz/free_space/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d264370c84d078a7eb46a8bf6b4c79705d9fd86a --- /dev/null +++ b/tutorials/helmholtz/free_space/main.cpp @@ -0,0 +1,429 @@ +#include "gmsh.h" + +#include <gmshddm/Formulation.h> +#include <gmshddm/GmshDdm.h> +#include <gmshddm/MPIInterface.h> +#include <gmshfem/Message.h> + +using namespace gmshddm; +using namespace gmshddm::common; +using namespace gmshddm::domain; +using namespace gmshddm::problem; +using namespace gmshddm::field; +using namespace gmshddm::mpi; + +using namespace gmshfem; +using namespace gmshfem::domain; +using namespace gmshfem::equation; +using namespace gmshfem::problem; +using namespace gmshfem::field; +using namespace gmshfem::function; +using namespace gmshfem::post; + +#include <cstring> +#include <fstream> +#include <sstream> + +static std::string freqToStr(double f) { + char buffer[100]; + sprintf(buffer, "%.2f", f); + return std::string(buffer); +} + +struct sourcePosition{ + double x; + double y; + sourcePosition(double x, double y) : x(x), y(y) {} +}; + +std::vector<sourcePosition> readSources(const std::string& path) { + std::ifstream inFile(path); + std::vector<sourcePosition> positions; + + if (!inFile.is_open()) { + throw std::runtime_error("Unable to open file: " + path); + } + + std::string line; + int lineNumber = 0; + while (std::getline(inFile, line)) { + lineNumber++; + std::istringstream lineStream(line); + double x, y; + char delimiter; + + if (lineStream >> x >> delimiter >> y && delimiter == ';') { + positions.push_back({x, y}); + } else { + throw std::runtime_error("Invalid line format in file: " + path + " at line " + std::to_string(lineNumber)); + } + } + + inFile.close(); + return positions; +} + + +/** + * Returns ID of the domains containing the source +*/ +std::vector<int> mesh(double L, double H, double x0, double y0, const std::vector<sourcePosition>& sourcePositions, unsigned nX = 2, unsigned nY = 2, double lc = 0.1) { + gmsh::model::add("freeSpace"); + // Disable warnings for large meshes + gmsh::option::setNumber("General.ExpertMode", 1); + + namespace gmodel = gmsh::model; + + for(unsigned i = 0; i < sourcePositions.size(); ++i) { + double x_src = sourcePositions[i].x; + double y_src = sourcePositions[i].y; + if(x_src < x0 || y_src < y0 || x_src > (x0 + L) || y_src > (y0 + H)) { + throw gmshfem::common::Exception("Invalid source location."); + } + } + + std::vector<std::vector<int>> points(nX + 1); + std::vector<std::vector<int>> edgesH(nX); + std::vector<std::vector<int>> edgesV(nX+1); + std::vector<std::vector<int>> curvedLoops(nX); + // Indices from 0 to N (N+1 points) + for (unsigned i = 0; i <= nX; ++i) + points[i].resize(nY + 1); + + const double dx = L / nX; + const double dy = H / nY; + + // Create all points + for (unsigned i = 0; i <= nX; ++i) { + for (unsigned j = 0; j <= nY; ++j) { + points[i][j] = gmodel::geo::addPoint(x0 + i * dx, y0 + j * dy, 0.0, lc); + } + } + + // Create all horizontal edges + for (unsigned i = 0; i < nX; ++i) { + edgesH[i].resize(nY+1); + for (unsigned j = 0; j <= nY; ++j) { + edgesH[i][j] = gmodel::geo::addLine(points.at(i).at(j), points.at(i+1).at(j)); + } + } + + // Create all vertical edges + for (unsigned i = 0; i <= nX; ++i) { + edgesV[i].resize(nY); + for (unsigned j = 0; j < nY; ++j) { + edgesV[i][j] = gmodel::geo::addLine(points.at(i).at(j), points.at(i).at(j+1)); + } + } + + // Create all surfaces + for (unsigned i = 0; i < nX; ++i) { + curvedLoops[i].resize(nY); + for (unsigned j = 0; j < nY; ++j) { + curvedLoops[i][j] = gmodel::geo::addCurveLoop({edgesH.at(i).at(j), edgesV.at(i+1).at(j), -edgesH.at(i).at(j+1), -edgesV.at(i).at(j)}); + gmodel::geo::addPlaneSurface({curvedLoops[i][j]}, curvedLoops[i][j]); // Same indices + } + } + + // Create sources. TODO: optimize + std::vector<int> sources; + std::vector<int> sourcesDomains; + for (const auto& pos : sourcePositions) { + int source = gmodel::geo::addPoint(pos.x, pos.y, 0, lc); + int i_source = int(pos.x / dx); + int j_source = int(pos.y / dy); + gmodel::geo::synchronize(); + gmodel::mesh::embed(0, {source}, 2, curvedLoops[i_source][j_source]); + sources.push_back(source); + sourcesDomains.push_back(i_source + j_source * nX); + } + + gmodel::geo::synchronize(); + + /* Define physical entities */ + + + + // Domains + std::vector<int> omegaAll; + for (unsigned i = 0; i < nX; ++i) { + for (unsigned j = 0; j < nY; ++j) { + int omegaIJ = gmodel::addPhysicalGroup(2, {curvedLoops[i][j]}, -1); + omegaAll.push_back(omegaIJ); + gmodel::setPhysicalName(2, omegaIJ, "omega_" + std::to_string(i) + "_" + std::to_string(j)); + } + } + + int omegaAllIdx = gmodel::addPhysicalGroup(2, omegaAll, -1); + gmodel::setPhysicalName(2, omegaAllIdx, "omega"); + + // Outer boundary + std::vector<int> gammaOut; + for (unsigned i = 0; i < nX; ++i) + { + gammaOut.push_back(edgesH[i][0]); + gammaOut.push_back(edgesH[i][nY]); + } + + for (unsigned j = 0; j < nY; ++j) + { + gammaOut.push_back(edgesV[0][j]); + gammaOut.push_back(edgesV[nX][j]); + } + + int gammaOutIdx = gmodel::addPhysicalGroup(1, gammaOut, -1); + gmodel::setPhysicalName(1, gammaOutIdx, "gammaOut"); + + // All eges + // H-edges + for (unsigned i = 0; i < nX; ++i) { + for (unsigned j = 0; j <= nY; ++j) { + int HIJ = gmodel::addPhysicalGroup(1, {edgesH[i][j]}, -1); + gmodel::setPhysicalName(1, HIJ, "edge_h_" + std::to_string(i) + "_" + std::to_string(j)); + } + } + + // V-edges + for (unsigned i = 0; i <= nX; ++i) { + for (unsigned j = 0; j < nY; ++j) { + int VIJ = gmodel::addPhysicalGroup(1, {edgesV[i][j]}, -1); + gmodel::setPhysicalName(1, VIJ, "edge_v_" + std::to_string(i) + "_" + std::to_string(j)); + } + } + + // Points + for (unsigned i = 0; i <= nX; ++i) { + for (unsigned j = 0; j <= nY; ++j) { + int VIJ = gmodel::addPhysicalGroup(1, {points[i][j]}, -1); + gmodel::setPhysicalName(1, VIJ, "point_" + std::to_string(i) + "_" + std::to_string(j)); + } + } + + // Src + for (unsigned srcId = 0; srcId < sources.size(); ++srcId) { + int srcEntity = gmodel::addPhysicalGroup(0, {sources[srcId]}); + gmodel::setPhysicalName(0, srcEntity, "source_" + std::to_string(srcId)); + + } + + gmodel::geo::synchronize(); + gmodel::mesh::generate(); + + return sourcesDomains; + +} + +int main(int argc, char **argv) +{ + GmshDdm gmshDdm(argc, argv); + constexpr std::complex<double> im(0, 1); + constexpr double pi = 3.14159265359; + + unsigned rank = gmshddm::mpi::getMPIRank(); + + int order = 2; + gmshDdm.userDefinedParameter(order, "order"); + double f = 2; + gmshDdm.userDefinedParameter(f, "f"); + + double lc = (1. / f) / 10; + + + gmshDdm.userDefinedParameter(lc, "lc"); + + std::string gauss = "Gauss10"; + gmshDdm.userDefinedParameter(gauss, "gauss"); + + // Output parameters + bool mustSolve{true}; + gmshDdm.userDefinedParameter(mustSolve, "noSolve"); + + + auto nX = 2; + auto nY = 2; + gmshDdm.userDefinedParameter(nX, "nX"); + gmshDdm.userDefinedParameter(nY, "nY"); + + double L{1}, H{1}; + gmshDdm.userDefinedParameter(L, "L"); + gmshDdm.userDefinedParameter(H, "H"); + + + + bool save_matrix = false; + gmshDdm.userDefinedParameter(save_matrix, "save_iteration_matrix"); + + // TODO : Weird behavior for negative y-axis -> Unsupported x0, y0 + std::vector<sourcePosition> sources; + std::string sourcePath = "../srcs.csv"; + gmshDdm.userDefinedParameter(sourcePath, "sourcePath"); + sources = readSources(sourcePath); + auto d_source = mesh(L, H, 0.0, 0, sources, nX, nY, lc); + + + // Configure wavenumber. If no model is given, assume homogenuous velocity of 1 + // (i.e. k = 2 * pi * f) + // If a model is given, it is assumed to be a squared slowness. + gmshfem::function::Function<std::complex<double>, gmshfem::Degree::Degree0> k; + + std::string model_path = ""; + std::string model_name; + gmshDdm.userDefinedParameter(model_path, "model_path"); + if (model_path != "") { + model_name = model_path.substr(0, model_path.size() - 4); + gmsh::merge(model_path); + std::vector<int> tags; + gmsh::view::getTags(tags); + + auto view = tags.back(); + ScalarFunction<std::complex<double>> model = probeScalarView<std::complex<double>>(view); + // k = omega * slowness + k = 2 * pi * f * sqrt(model); + } + else { + // Unit velocity + k = 2 * pi * f; + model_name = "free_space"; + } + + gmshDdm.userDefinedParameter(model_name, "model_name"); + msg::info << "Model name is " << model_name << gmshfem::msg::endl; + + + auto nDom = nX * nY; + // Define domain + Subdomain omega(nDom); + Subdomain gammaOut(nDom); + Interface sigma(nDom); + + #define OMEGA(i,j) "omega_" + std::to_string((i)) + "_" + std::to_string((j)) + #define POINT(i,j) "point_" + std::to_string((i)) + "_" + std::to_string((j)) + #define EDGE_H(i,j) "edge_h_" + std::to_string((i)) + "_" + std::to_string((j)) + #define EDGE_V(i,j) "edge_v_" + std::to_string((i)) + "_" + std::to_string((j)) + + for(int i = 0; i < nX; ++i) { + for (int j = 0; j < nY; ++j) { + int d = i + nX * j; + omega(d) = Domain(OMEGA(i,j)); + + // Left border / interface + if(i == 0) { + gammaOut(d) |= Domain(EDGE_V(i, j)); + } + else { + sigma(d, d - 1) = Domain(EDGE_V(i, j)); + } + // Right border / interface + if(i == nX - 1) { + gammaOut(d) |= Domain(EDGE_V(i + 1, j)); + } + else { + sigma(d, d + 1) = Domain(EDGE_V(i + 1, j)); + } + // Bottom border / interface + if(j == 0) { + gammaOut(d) |= Domain(EDGE_H(i, j)); + } + else { + sigma(d, d - nX) = Domain(EDGE_H(i, j)); + } + // Top border / interface + if(j == nY - 1) { + gammaOut(d) |= Domain(EDGE_H(i, j + 1)); + } + else { + sigma(d, d + nX) = Domain(EDGE_H(i, j + 1)); + } + + + } + } + + // Define topology + std::vector< std::vector< unsigned int > > topology(nDom); + for(unsigned int i = 0; i < nX; ++i) { + for (unsigned int j = 0; j < nY; ++j) { + unsigned int d = i + nX * j; + if (i != 0) topology[d].push_back(d-1); + if (i != nX-1) topology[d].push_back(d+1); + if (j != 0) topology[d].push_back(d-nX); + if (j != nY-1) topology[d].push_back(d+nX); + } + } + + + // Create DDM formulation + gmshddm::problem::Formulation< std::complex< double > > formulation("Helmholtz", topology); + + // Create fields + SubdomainField< std::complex< double >, Form::Form0 > u("u", omega | gammaOut, FunctionSpaceTypeForm0::HierarchicalH1, order); + InterfaceField< std::complex< double >, Form::Form0 > g("g", sigma, FunctionSpaceTypeForm0::HierarchicalH1, order); + // Tell to the formulation that g is field that have to be exchanged between subdomains + formulation.addInterfaceField(g); + + // Add terms common to all RHS + for(unsigned int i = 0; i < nDom; ++i) { + // VOLUME TERMS + formulation(i).integral(grad(dof(u(i))), grad(tf(u(i))), omega(i), gauss); + formulation(i).integral(-k * k * dof(u(i)), tf(u(i)), omega(i), gauss); + formulation(i).integral(-im * k * dof(u(i)), tf(u(i)), gammaOut(i), gauss); + + // Artificial source + for(unsigned int jj = 0; jj < topology[i].size(); ++jj) { + const unsigned int j = topology[i][jj]; + formulation(i).integral(-im * k * dof(u(i)), tf(u(i)), sigma(i, j), gauss); + formulation(i).integral(formulation.artificialSource(-g(j, i)), tf(u(i)), sigma(i, j), gauss); + } + + // INTERFACE TERMS + for(unsigned int jj = 0; jj < topology[i].size(); ++jj) { + const unsigned int j = topology[i][jj]; + formulation(i, j).integral(dof(g(i, j)), tf(g(i, j)), sigma(i, j), gauss); + formulation(i, j).integral(formulation.artificialSource(g(j, i)), tf(g(i, j)), sigma(i, j), gauss); + formulation(i, j).integral(2. * im * k * u(i), tf(g(i, j)), sigma(i, j), gauss); + } + } + + // Add all sources + formulation.setShotNumber(sources.size()); + for(unsigned isrc = 0; isrc < sources.size(); ++isrc) { + formulation.numberedPhysicalSourceTerm(isrc, formulation(d_source[isrc]).integral(formulation.physicalSource(-1), tf(u(d_source[isrc])), Domain("source_" + std::to_string(isrc)), gauss)); + } + + formulation.pre(false); + + + for(unsigned isrc = 0; isrc < sources.size(); ++isrc) { + formulation.disableAllShots(); + formulation.enableShot(isrc); + + // Solve the DDM formulation + formulation.assemble(); + + if(mustSolve) { + int iterMax = 1000; + std::string solver = "gmres"; + gmshDdm.userDefinedParameter(solver, "solver"); + gmshDdm.userDefinedParameter(iterMax, "iter"); + formulation.solve(solver, 1e-6, iterMax); + save(u.buildUnifiedField(), u.getLocalDomain(), "u_rhs_" + std::to_string(isrc) + "_rank_" + std::to_string(rank)); + } + + + std::string suffix = model_name + "_" + freqToStr(f) + "_" + std::to_string(nX) + "_" + std::to_string(nY); + std::string mat_name{"matrix_" + suffix}, vec_name{"rhs_" + std::to_string(isrc) + "_" + suffix}; + if(save_matrix) { + // Export all RHS but only one matrix + if(isrc == 0) { + auto mat = formulation.computeMatrix(); + msg::info << "nnz = " << mat.numberOfNonZero() << ", symmetric = " << mat.isSymmetric() << ", hermitian = " << mat.isHermitian() << msg::endl; + mat.save(mat_name); + } + + formulation.getRHS().save(vec_name); + } + } + + + return 0; +} diff --git a/tutorials/helmholtz/free_space/srcs.csv b/tutorials/helmholtz/free_space/srcs.csv new file mode 100644 index 0000000000000000000000000000000000000000..91a47d7151446cf9b29949ee28cff8bee33bb800 --- /dev/null +++ b/tutorials/helmholtz/free_space/srcs.csv @@ -0,0 +1,4 @@ +0.4;0.4 +0.4;0.6 +0.6;0.4 +0.6;0.6