LCOV - code coverage report
Current view: top level - issm-precice - issm.cpp (source / functions) Hit Total Coverage
Test: issm-precice-test-coverage.info Lines: 152 182 83.5 %
Date: 2025-07-12 23:40:24 Functions: 31 35 88.6 %

          Line data    Source code
       1             : // SPDX-FileCopyrightText: 2024 Daniel Abele <daniel.abele@dlr.de>
       2             : //
       3             : // SPDX-License-Identifier: BSD-3-Clause
       4             : 
       5             : #include "issm-precice/issm.hpp"
       6             : #include "issm-precice/logging.hpp"
       7             : #include "issm-precice/timer.hpp"
       8             : #include "shared/shared.h"
       9             : #include "classes/classes.h"
      10             : #include "cores/cores.h"
      11             : #include <algorithm>
      12             : #include <cmath>
      13             : #include <iterator>
      14             : #include <ranges>
      15             : #include <span>
      16             : 
      17             : namespace stdr = std::ranges;
      18             : namespace stdv = std::views;
      19             : 
      20             : namespace ipc
      21             : {
      22          72 : const char* issm_input_name(definitions input_id)
      23             : {
      24          72 :     return EnumToStringx(input_id);
      25             : }
      26             : 
      27           0 : definitions issm_input_id(const std::string& input_name)
      28             : {
      29           0 :     return definitions(StringToEnumx(input_name.c_str()));
      30             : }
      31             : 
      32             : /**
      33             :  * Turn a ISSM DataSet into a C++ range.
      34             :  * Cast the void ptrs stored by the DataSet into the specified type.
      35             :  * @tparam Type of objects stored by the DataSet.
      36             :  * @param ds An ISSM DataSet representing a collection of abstract objects.
      37             :  * @returns Random access view of the range of objects in the DataSet as T*.
      38             :  */
      39             : template<class T, class DS, class = std::enable_if_t<std::is_base_of_v<DataSet, std::decay_t<DS>>>>
      40      293833 : auto as_range(DS&& ds)
      41             : {
      42     2302525 :     return ds.objects | std::views::transform([](auto& o) { return static_cast<T*>(o); });
      43             : }
      44             : 
      45          34 : FemModelPtr make_fem_model(const std::filesystem::path& root_path, const std::string& model_name, MPI_Comm comm)
      46             : {
      47          34 :     IPC_LOG_INFO_0(comm, "Creating ISSM Model using {} / {}", root_path.string(), model_name);
      48             : 
      49             :     //ISSM requires non-const char*, but doesn't modify, so cast is safe.
      50          34 :     char* args[] = {
      51             :         const_cast<char*>(""),
      52             :         const_cast<char*>("TransientSolution"),
      53          34 :         const_cast<char*>(root_path.c_str()),
      54          34 :         const_cast<char*>(model_name.c_str())};
      55          34 :     FemModel* model = new FemModel{4, args, comm};
      56          68 :     return FemModelPtr(model);
      57             : }
      58             : 
      59          34 : Issm::Issm(const std::filesystem::path& root_path, const std::string& model_name, const MeshDefinition& mesh, MPI_Comm comm)
      60          34 :     : m_model(make_fem_model(root_path, model_name, comm))
      61          34 :     , m_mesh_definition(mesh)
      62          34 :     , m_initialized_stress_balance(false)
      63             : {
      64             :     //check for compatible mesh type
      65             :     int domain_type;
      66          34 :     m_model->parameters->FindParam(&domain_type, DomainTypeEnum);
      67             : 
      68          34 :     if (domain_type == Domain2DverticalEnum)
      69             :     {
      70           0 :         throw std::runtime_error("Coupling for vertical domains not implemented.");
      71             :     }
      72             : 
      73             :     //time steps are controlled by precice
      74             :     //store the start time as the current time so it can be used in advance().
      75             :     //ISSM only sets the current time when simulation begins.
      76             :     double start_time;
      77          34 :     m_model->parameters->FindParam(&start_time, TimesteppingStartTimeEnum);
      78          34 :     m_model->parameters->SetParam(start_time, TimeEnum);
      79             : 
      80             :     // ISSM can make any step required by the coupler by making multiple time steps.
      81             :     // We currently do not support subcycling (i.e. we don't call advance() for every ISSM time step)
      82             :     // because ISSM writes one ouput step per call to FemModel::Solve, and that could be data to handle.
      83          34 :     m_max_dt = std::numeric_limits<double>::max();
      84          34 : }
      85             : 
      86             : /**
      87             : * Deleter that deletes an Element if it was spawned temporarily.
      88             : * Elements are spawned e.g. as a the base triangle element of a prism element.
      89             : * Otherwise the Element is still required by the Model. 
      90             : */
      91             : struct SpawnedElementDeleter
      92             : {
      93       32800 :     void operator()(Element* element)
      94             :     {
      95       32800 :         if (element->IsSpawnedElement())
      96             :         {
      97       25600 :             element->DeleteMaterials();
      98       25600 :             delete element;
      99             :         }
     100       32800 :     }
     101             : };
     102             : 
     103          38 : IIssm::~IIssm()
     104             : {
     105          38 : }
     106             : 
     107          30 : std::vector<Vertex> Issm::get_vertices() const
     108             : {
     109          30 :     return get_vertices(m_mesh_definition);
     110             : }
     111             : 
     112          41 : std::vector<Vertex> Issm::get_vertices(const MeshDefinition& mesh) const
     113             : {
     114          82 :     return std::visit([this](auto& mesh_type) { return this->get_vertices(mesh_type); }, mesh.type);
     115             : }
     116             : 
     117          41 : std::vector<Vertex> Issm::get_vertices(const Mesh2dDefinition& mesh2d) const
     118             : {
     119          41 :     auto layer = mesh2d.layer;
     120          41 :     if (layer != 0 && layer != -1)
     121             :     {
     122           0 :         throw std::runtime_error("Only coupling with base (0) and surface (-1) layers implemented.");
     123             :     }
     124             : 
     125      196000 :     auto is_on_layer = [layer](auto&& el)
     126             :     {
     127      109600 :         return (layer == 0 && el->IsOnBase()) || (layer == -1 && el->IsOnSurface());
     128          41 :     };
     129          41 :     auto layer_elements = as_range<Element>(*m_model->elements) //
     130          82 :                           | std::views::filter(is_on_layer);
     131             : 
     132          41 :     std::vector<Vertex> vertices;
     133       32841 :     for (auto&& element : layer_elements)
     134             :     {
     135             :         int element_idx;
     136       32800 :         m_model->elements->GetObjectById(&element_idx, element->Id());
     137             : 
     138             :         auto layer_element = std::unique_ptr<Element, SpawnedElementDeleter>(
     139       32800 :             layer == 0 ? element->SpawnBasalElement() : element->SpawnTopElement());
     140      131200 :         for (auto&& vertex : std::span(layer_element->vertices, layer_element->GetNumberOfVertices()))
     141             :         {
     142             :             int vertex_idx;
     143       98400 :             m_model->vertices->GetObjectById(&vertex_idx, vertex->Id());
     144           0 :             vertices.push_back(Vertex{
     145       98400 :                 .id = size_t(vertex_idx),
     146       98400 :                 .coordinates = {vertex->x, vertex->y, vertex->z},
     147       98400 :                 .element_id = size_t(element_idx)});
     148       98400 :             IPC_LOG_TRACE("Vertex {} [{}, {}]", vertex->id, vertex->x, vertex->y);
     149             :         }
     150       32800 :     }
     151             : 
     152          41 :     IPC_LOG_TRACE("Use {} vertices of {} elements.", vertices.size(), m_model->elements->NumberOfElements());
     153             : 
     154          82 :     return vertices;
     155           0 : }
     156             : 
     157           0 : std::vector<Vertex> Issm::get_vertices(const Mesh3dDefinition&) const
     158             : {
     159           0 :     throw std::runtime_error("3D coupling interface not implemented.");
     160             : }
     161             : 
     162        1768 : auto get_analysis(definitions input)
     163             : {
     164        1768 :     switch (input)
     165             :     {
     166        1768 :         case VxEnum:
     167             :         case VyEnum:
     168        1768 :             return StressbalanceAnalysisEnum;
     169           0 :         default:
     170           0 :             throw std::runtime_error(fmt::format("Constraint coupling not implemented for {}.", EnumToStringx(input)));
     171             :     }
     172             : }
     173             : 
     174        2646 : auto get_constraint_dof_idx(definitions input)
     175             : {
     176        2646 :     switch (input)
     177             :     {
     178         882 :         case VxEnum:
     179         882 :             return 0;
     180        1764 :         case VyEnum:
     181        1764 :             return 1;
     182           0 :         default:
     183           0 :             return 0;
     184             :     }
     185             : }
     186             : 
     187           4 : void Issm::set_constraints(definitions input, const std::vector<Vertex>& vertices, std::span<const double> values)
     188             : {
     189           4 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Setting ISSM constraints for {}", issm_input_name(input));
     190             : 
     191           4 :     m_model->SetCurrentConfiguration(get_analysis(input));
     192             : 
     193             :     // TODO: this condition might be relaxed if we use coupling meshes based on nodes instead of vertices.
     194             :     static int allowed_fe_types[] = {P1Enum, P1P1Enum};
     195           4 :     if (stdr::find(allowed_fe_types, as_range<Element>(*m_model->elements)[0]->element_type) == std::end(allowed_fe_types))
     196             :     {
     197           0 :         throw std::runtime_error("Constraint coupling only implemented for P1 elements (nodes == vertices).");
     198             :     }
     199             : 
     200             :     // get values of constraints of ghost/clone vertices. NaN means no constraint
     201             :     static_assert(std::numeric_limits<double>::has_quiet_NaN);
     202             :     auto all_vertex_values =
     203           4 :         std::vector<double>(m_model->vertices->NumberOfVerticesLocalAll(), std::numeric_limits<double>::quiet_NaN());
     204           4 :     auto vs = as_range<::Vertex>(*m_model->vertices);
     205           4 :     assert(all_vertex_values.size() == vs.size());
     206        9604 :     for (size_t vtx_idx = 0; vtx_idx < vertices.size(); ++vtx_idx)
     207             :     {
     208        9600 :         all_vertex_values[vs[vertices[vtx_idx].id]->Lid()] = values[vtx_idx];
     209             :     }
     210           4 :     m_model->SyncLocalVectorWithClonesVertices(all_vertex_values.data());
     211             : 
     212             :     // set constraints
     213             :     // !! loop over ALL vertices of the model, vertices in the coupling mesh do not include ghosts/clones !!
     214        8824 :     for (size_t vtx_idx = 0; vtx_idx < all_vertex_values.size(); ++vtx_idx)
     215             :     {
     216        8820 :         auto vertex = as_range<::Vertex>(*m_model->vertices)[vtx_idx];
     217        8820 :         if (std::isnan(all_vertex_values[vertex->Lid()]))
     218             :         {
     219        7056 :             continue; // constraint not set
     220             :         }
     221             : 
     222             :         // find constraint on this vertex
     223             :         // This can probably be optimised since the constraint should have a similar order as the vertices
     224        1764 :         auto static_constraints = as_range<::Constraint>(*m_model->constraints) |
     225     1568196 :                                   stdv::transform([](auto cons) { return dynamic_cast<SpcStatic*>(cons); }) |
     226      782334 :                                   stdv::filter([](auto cons) { return cons != nullptr; });
     227        1764 :         auto constraint = stdr::find_if(
     228             :             static_constraints,
     229      778806 :             [&](auto static_constraint)
     230             :             {
     231      781452 :                 return static_constraint->GetNodeId() == vertex->Sid() + 1 &&
     232      781452 :                        static_constraint->GetDof() == get_constraint_dof_idx(input);
     233             :             });
     234        1764 :         if (constraint == end(static_constraints))
     235             :         {
     236           0 :             throw std::runtime_error(fmt::format(
     237             :                 "Constraint not found for vertex {}, Sid {}, Value {}.",
     238             :                 vtx_idx,
     239           0 :                 vertex->Sid(),
     240           0 :                 all_vertex_values[vertex->Lid()]));
     241             :         }
     242             :         // set constraint
     243        5292 :         **constraint = SpcStatic(
     244        1764 :             (**constraint).Id(),
     245             :             (**constraint).GetNodeId(),
     246             :             (**constraint).GetDof(),
     247        1764 :             all_vertex_values[vertex->Lid()],
     248        3528 :             get_analysis(input));
     249             :     }
     250           4 : }
     251             : 
     252          16 : void Issm::set_input(definitions input, const std::vector<Vertex>& vertices, std::span<const double> values)
     253             : {
     254          16 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Setting ISSM input values for {}", issm_input_name(input));
     255             : 
     256             :     //set value for each vertex. 
     257             :     //Could probably be made more efficient by setting all values at the same time but
     258             :     //making this work for many different types of meshes is probably not worth it.
     259       38416 :     for (size_t i = 0; i < vertices.size(); ++i)
     260             :     {
     261       38400 :         auto vertex = as_range<::Vertex>(*m_model->vertices)[vertices[i].id];
     262       38400 :         auto element = as_range<Element>(*m_model->elements)[vertices[i].element_id];
     263       38400 :         IPC_LOG_TRACE("Set value at vertex {} [{} {}]", vertex->lid, vertex->x, vertex->y);
     264       38400 :         element->SetElementInput(m_model->inputs, 1, &vertex->lid, const_cast<double*>(&values[i]), input);
     265             :     }
     266          16 : }
     267             : 
     268          43 : void Issm::get_input(definitions input, const std::vector<Vertex>& vertices, std::span<double> values) const
     269             : {
     270          43 :     assert(values.size() == vertices.size());
     271             : 
     272          43 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Get ISSM input values for {}", issm_input_name(input));
     273             : 
     274             :     //get value for each vertex. 
     275             :     //Could probably be made more efficient by setting all values at the same time but
     276             :     //making this work for many different types of meshes is probably not worth it.
     277      103200 :     auto get_value = [&](auto& v)
     278             :     {
     279      103200 :         auto vertex = as_range<::Vertex>(*m_model->vertices)[v.id];
     280      103200 :         auto element = as_range<Element>(*m_model->elements)[v.element_id];
     281      103200 :         IPC_LOG_TRACE("Get value of at vertex {} [{} {}]", vertex->lid, vertex->x, vertex->y);
     282             :         double value;
     283      103200 :         element->GetInputValue(&value, vertex, input);
     284      103200 :         return value;
     285          43 :     };
     286          43 :     std::ranges::transform(vertices, values.begin(), get_value);
     287          43 : }
     288             : 
     289           3 : void Issm::extrude(definitions input, const std::vector<Vertex>& /*vertices*/)
     290             : {
     291           3 :     if (auto p_mesh2d = get_if<Mesh2dDefinition>(&m_mesh_definition.type); p_mesh2d)
     292             :     {
     293           3 :         IPC_LOG_INFO_0(IssmComm::GetComm(), "Extruding ISSM input {}", issm_input_name(input));
     294           3 :         m_model->parameters->SetParam(input, InputToExtrudeEnum);
     295           3 :         if (p_mesh2d->layer == 0)
     296             :         {
     297           2 :             extrudefrombase_core(m_model.get());
     298             :         }
     299           1 :         else if (p_mesh2d->layer == -1)
     300             :         {
     301           1 :             extrudefromtop_core(m_model.get());
     302             :         }
     303             :         else
     304             :         {
     305           0 :             throw std::runtime_error("Extruding from layer other than base or surface not implemented.");
     306             :         }
     307             :     }
     308           3 : }
     309             : 
     310           5 : definitions depth_average_input(definitions input)
     311             : {
     312           5 :     switch (input)
     313             :     {
     314           2 :     case VxEnum: return VxAverageEnum;
     315           2 :     case VyEnum: return VyAverageEnum;
     316           0 :     case WaterfractionDrainageEnum: return WaterfractionDrainageIntegratedEnum;
     317           0 :     case MaterialsRheologyBEnum: return MaterialsRheologyBbarEnum;
     318           0 :     case DamageDEnum: return DamageDbarEnum;
     319           0 :     case DamageDOldEnum: return DamageDbarOldEnum;
     320           0 :     case MaterialsRheologyEEnum: return MaterialsRheologyEbarEnum;
     321           0 :     case MaterialsRheologyEsEnum: return MaterialsRheologyEsbarEnum;
     322           0 :     case MaterialsRheologyEcEnum: return MaterialsRheologyEcbarEnum;
     323           1 :     default: throw std::domain_error("Depth averaging not implemented for this input.");
     324             :     }
     325             : }
     326             : 
     327           3 : definitions Issm::depth_average(definitions input, const std::vector<Vertex>& /*vertices*/)
     328             : {
     329           3 :     auto average_input = depth_average_input(input);
     330           2 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Depth averaging ISSM input {} into {}", issm_input_name(input), issm_input_name(average_input));
     331           2 :     if (auto p_mesh2d = get_if<Mesh2dDefinition>(&m_mesh_definition.type); !p_mesh2d || p_mesh2d->layer != 0)
     332             :     {
     333           0 :         IPC_LOG_WARN_0(IssmComm::GetComm(), "Values of depth averaged input {} only valid at base layer.", issm_input_name(average_input));
     334             :     }
     335             : 
     336           2 :     m_model->parameters->SetParam(input, InputToDepthaverageInEnum);
     337           2 :     m_model->parameters->SetParam(average_input, InputToDepthaverageOutEnum);
     338           2 :     depthaverage_core(m_model.get());
     339           2 :     return average_input;
     340             : }
     341             : 
     342           3 : void Issm::initialize(definitions input)
     343             : {
     344             : 
     345           3 :     switch (input)
     346             :     {
     347           0 :     case MaskIceLevelsetEnum:
     348             :     case MaskOceanLevelsetEnum:
     349             :     case ThicknessEnum:
     350             :     case BaseEnum:
     351             :     case SurfaceEnum:
     352             :     case BedEnum:
     353             :         //need no initialization, must be initialized in a valid setup
     354           0 :         break;
     355           3 :     case VxEnum:
     356             :     case VyEnum:
     357             :     case VzEnum:
     358             :     case VelEnum:
     359             :     case PressureEnum:
     360           3 :         if (!m_initialized_stress_balance)
     361             :         {
     362           3 :             stressbalance_core(m_model.get());
     363           3 :             m_model->results->clear(); //don't store results
     364           3 :             m_initialized_stress_balance = true;
     365             :         }
     366           3 :         break;
     367           0 :     default:
     368           0 :         IPC_LOG_WARN_0(
     369             :             IssmComm::GetComm(), 
     370             :             "Initializing coupling data of {}. "
     371             :             "Make sure this field has a valid initial value as the adapter can not compute it. "
     372             :             "A procedure to compute the initial value can be added to ipc::Issm::initialize(). ",
     373             :             issm_input_name(input));
     374           0 :         break;
     375             :     }
     376           3 : }
     377             : 
     378          19 : double Issm::get_max_time_step() const
     379             : {
     380          19 :     return m_max_dt;
     381             : }
     382             : 
     383          19 : void Issm::solve(double dt)
     384             : {
     385          19 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Advance ISSM by {}", dt);
     386             : 
     387          19 :     assert(dt < get_max_time_step() && "Time step too big.");
     388             : 
     389             :     double t;
     390          19 :     m_model->parameters->FindParam(&t, TimeEnum);
     391             : 
     392          19 :     m_model->parameters->SetParam(t, TimesteppingStartTimeEnum);
     393          19 :     m_model->parameters->SetParam(t + dt, TimesteppingFinalTimeEnum);
     394             :     
     395          19 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Run ISSM from t = {} to {}", t, t + dt);
     396             : 
     397          19 :     m_model->Solve();
     398          19 : }
     399             : 
     400             : } // namespace ipc

Generated by: LCOV version 1.14