LCOV - code coverage report
Current view: top level - issm-precice - issm.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 85.4 % 260 222
Test Date: 2026-08-10 06:53:29 Functions: 89.8 % 49 44

            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/confreader.hpp"
       7              : #include "issm-precice/logging.hpp"
       8              : #include "issm-precice/math.hpp"
       9              : #include "issm-precice/timer.hpp"
      10              : #include "shared/shared.h"
      11              : #include "classes/classes.h"
      12              : #include "cores/cores.h"
      13              : #include <algorithm>
      14              : #include <cmath>
      15              : #include <iterator>
      16              : #include <limits>
      17              : #include <ranges>
      18              : #include <span>
      19              : #include <type_traits>
      20              : 
      21              : namespace stdr = std::ranges;
      22              : namespace stdv = std::views;
      23              : 
      24              : namespace ipc
      25              : {
      26        45923 : const char* issm_input_name(definitions input_id)
      27              : {
      28        45923 :     return EnumToStringx(input_id);
      29              : }
      30              : 
      31            0 : definitions issm_input_id(const std::string& input_name)
      32              : {
      33            0 :     return definitions(StringToEnumx(input_name.c_str()));
      34              : }
      35              : 
      36              : namespace
      37              : {
      38              : 
      39              : /**
      40              :  * Turn a ISSM DataSet into a C++ range.
      41              :  * Cast the void ptrs stored by the DataSet into the specified type.
      42              :  * @tparam Type of objects stored by the DataSet.
      43              :  * @param ds An ISSM DataSet representing a collection of abstract objects.
      44              :  * @returns Random access view of the range of objects in the DataSet as T*.
      45              :  */
      46              : template<class T, class DS, class = std::enable_if_t<std::is_base_of_v<DataSet, std::decay_t<DS>>>>
      47       108977 : auto as_range(DS&& ds)
      48              : {
      49      2353513 :     return ds.objects | std::views::transform([](auto& o) { return static_cast<T*>(o); });
      50              : }
      51              : 
      52              : /**
      53              :  * Get an Issm parameter.
      54              :  */
      55              : template<class T>
      56          343 : T get_parameter(const ::FemModel& model, ::definitions par)
      57              : {
      58              :     T value;
      59          343 :     const_cast<::FemModel&>(model).parameters->FindParam(&value, par);
      60          343 :     return value;
      61              : }
      62              : 
      63              : /**
      64              :  * Get an Issm enum parameter.
      65              :  */
      66              : template<>
      67          318 : ::definitions get_parameter<::definitions>(const ::FemModel& model, ::definitions par)
      68              : {
      69              :     int value;
      70          318 :     const_cast<::FemModel&>(model).parameters->FindParam(&value, par);
      71          318 :     return (::definitions)value;
      72              : }
      73              : 
      74              : /**
      75              :  * Set an Issm parameter.
      76              :  */
      77              : template<class T>
      78          490 : void set_parameter(::FemModel& model, ::definitions par, T value)
      79              : {
      80          490 :     model.parameters->SetParam(value, par);
      81          490 : }
      82              : 
      83              : } // namespace
      84              : 
      85          118 : FemModelPtr make_fem_model(const std::filesystem::path& root_path, const std::string& model_name, MPI_Comm comm)
      86              : {
      87          118 :     IPC_LOG_INFO_0(comm, "Creating ISSM Model using {} / {}", root_path.string(), model_name);
      88              : 
      89              :     //ISSM requires non-const char*, but doesn't modify, so cast is safe.
      90          118 :     char* args[] = {
      91              :         const_cast<char*>(""),
      92              :         const_cast<char*>("TransientSolution"),
      93          118 :         const_cast<char*>(root_path.c_str()),
      94          118 :         const_cast<char*>(model_name.c_str())};
      95          118 :     FemModel* model = new FemModel{4, args, comm};
      96          236 :     return FemModelPtr(model);
      97              : }
      98              : 
      99              : /**
     100              : * Deleter that deletes an Element if it was spawned temporarily.
     101              : * Elements are spawned e.g. as a the base triangle element of a prism element.
     102              : * Otherwise the Element is still required by the Model and not deleted.
     103              : */
     104              : struct SpawnedElementDeleter
     105              : {
     106        65600 :     void operator()(::Element* element)
     107              :     {
     108        65600 :         if (element->IsSpawnedElement())
     109              :         {
     110        42400 :             element->DeleteMaterials();
     111        42400 :             delete element;
     112              :         }
     113        65600 :     }
     114              : };
     115              : 
     116          133 : std::pair<Mesh, Mesh> make_mesh(const Mesh2dConfig& mesh2d, const ::FemModel& model)
     117              : {
     118          133 :     auto layer = mesh2d.layer;
     119          133 :     if (layer != 0 && layer != -1)
     120              :     {
     121            0 :         throw std::runtime_error("Only coupling with base (0) and surface (-1) layers implemented.");
     122              :     }
     123              : 
     124       192800 :     auto is_on_layer = [layer](auto&& el)
     125              :     {
     126       192800 :         return (layer == 0 && el->IsOnBase()) || (layer == -1 && el->IsOnSurface());
     127          133 :     };
     128          133 :     auto layer_elements = as_range<::Element>(*model.elements) //
     129          266 :                           | stdv::filter(is_on_layer);
     130              : 
     131          133 :     Mesh connected_mesh;
     132          133 :     Mesh unique_mesh;
     133          133 :     const size_t INVALID_IDX = std::numeric_limits<size_t>::max(); 
     134          133 :     auto vertex_index_map = std::vector<size_t>(model.vertices->Size(), INVALID_IDX);
     135        65733 :     for (auto&& element : layer_elements)
     136              :     {
     137              :         int element_idx;
     138        65600 :         model.elements->GetObjectById(&element_idx, element->Id());
     139              : 
     140        65600 :         connected_mesh.elements.push_back(Element{.id = (size_t)element_idx});
     141              : 
     142              :         auto layer_element = std::unique_ptr<::Element, SpawnedElementDeleter>(
     143        65600 :             layer == 0 ? element->SpawnBasalElement() : element->SpawnTopElement());
     144        65600 :         assert(layer_element->GetNumberOfVertices() == 3 && "Non triangle element.");
     145       262400 :         for (int i = 0; i < layer_element->GetNumberOfVertices(); ++i)
     146              :         {
     147       196800 :             auto issm_vertex = layer_element->vertices[i];
     148              :             int vertex_idx;
     149       196800 :             model.vertices->GetObjectById(&vertex_idx, issm_vertex->Id());
     150       196800 :             if (vertex_index_map[vertex_idx] == INVALID_IDX)
     151              :             {
     152              :                 auto vertex = Vertex{
     153        37318 :                     .id = size_t(vertex_idx),
     154        37318 :                     .coordinates = {issm_vertex->x, issm_vertex->y, issm_vertex->z},
     155        37318 :                     .element_id = (size_t)element_idx
     156        37318 :                 };
     157        37318 :                 connected_mesh.vertices.push_back(vertex);
     158        37318 :                 if (!issm_vertex->clone)
     159              :                 {
     160        36162 :                     unique_mesh.vertices.push_back(vertex);
     161              :                 }
     162        37318 :                 IPC_LOG_TRACE("Vertex {} [{}, {}]", issm_vertex->id, issm_vertex->x, issm_vertex->y);
     163        37318 :                 vertex_index_map[vertex_idx] = connected_mesh.vertices.size() - 1;
     164              :             }
     165       196800 :             connected_mesh.elements.back().vertices[i] = vertex_index_map[vertex_idx];
     166              :         }
     167        65600 :     }
     168              : 
     169          133 :     IPC_LOG_TRACE("Use {} vertices of {} elements.", connected_mesh.vertices.size(), connected_mesh.elements.size());
     170              : 
     171          266 :     return {connected_mesh, unique_mesh};
     172          133 : }
     173              : 
     174            0 : std::pair<Mesh, Mesh> make_mesh(const Mesh3dConfig& /* config */, const ::FemModel& /* model */)
     175              : {
     176            0 :     throw std::runtime_error("3D Mesh not implemented.");
     177              : }
     178              : 
     179          118 : Issm::Issm(const std::filesystem::path& root_path, const std::string& model_name, const MeshConfig& mesh, MPI_Comm comm)
     180          118 :     : m_model(make_fem_model(root_path, model_name, comm))
     181          118 :     , m_coupling_mesh_config(mesh)
     182          236 :     , m_initialized_stress_balance(false)
     183              : {
     184              :     //check for compatible mesh type
     185          118 :     auto domain_type = get_parameter<::definitions>(*m_model, DomainTypeEnum);
     186          118 :     if (domain_type == Domain2DverticalEnum)
     187              :     {
     188            0 :         throw std::runtime_error("Coupling for vertical domains not implemented.");
     189              :     }
     190              : 
     191              :     //time steps are controlled by precice
     192              :     //store the start time as the current time so it can be used in advance().
     193              :     //ISSM only sets the current time when simulation begins.
     194          118 :     auto start_time = get_parameter<double>(*m_model, TimesteppingStartTimeEnum);
     195          118 :     set_parameter(*m_model, TimeEnum, start_time);
     196              : 
     197              : #ifdef ISSM_PRECICE_FEATURE_CONFIGURE_OUTPUT
     198              :     // don't save results at "final" time step
     199              :     // when coupled, there is a "final" time step at the end of every coupling window
     200          118 :     set_parameter(*m_model, SaveFinalResultsEnum, false);
     201              : #endif
     202              : 
     203          118 :     if (get_parameter<::definitions>(*m_model, TimesteppingTypeEnum) == FixedTimesteppingEnum)
     204              :     {
     205              :         // save the time step set in the model setup;
     206              :         // we need it during solve and it may be overridden during simulation
     207          117 :         m_initial_dt = get_parameter<double>(*m_model, TimesteppingTimeStepEnum);
     208              :     }
     209              :     else
     210              :     {
     211              :         // time step not available if adaptive time stepping
     212            1 :         m_initial_dt = std::numeric_limits<double>::signaling_NaN();
     213              :     }
     214              : 
     215              :     //initialize meshes
     216          118 :     std::tie(m_connected_mesh, m_unique_mesh) =
     217          354 :         std::visit([&](auto&& mesh_type) { return make_mesh(mesh_type, *m_model); }, m_coupling_mesh_config.type);
     218          118 : }
     219              : 
     220          122 : IIssm::~IIssm()
     221              : {
     222          122 : }
     223              : 
     224          102 : Mesh Issm::get_vertices(bool with_connectivity) const
     225              : {
     226          102 :     return with_connectivity ? m_connected_mesh : m_unique_mesh;
     227              : }
     228              : 
     229           15 : Mesh Issm::get_vertices(const MeshConfig& config, bool with_connectivity) const
     230              : {
     231           15 :     auto [connected_mesh, unique_mesh] =
     232           30 :         std::visit([this](auto& mesh_type) { return make_mesh(mesh_type, *this->m_model); }, config.type);
     233           30 :     return with_connectivity ? connected_mesh : unique_mesh;
     234           15 : }
     235              : 
     236         2878 : auto get_analysis(definitions input)
     237              : {
     238         2878 :     switch (input)
     239              :     {
     240         2878 :         case VxEnum:
     241              :         case VyEnum:
     242         2878 :             return StressbalanceAnalysisEnum;
     243            0 :         default:
     244            0 :             throw std::runtime_error(fmt::format("Constraint coupling not implemented for {}.", EnumToStringx(input)));
     245              :     }
     246              : }
     247              : 
     248         4299 : auto get_constraint_dof_idx(definitions input)
     249              : {
     250         4299 :     switch (input)
     251              :     {
     252         1433 :         case VxEnum:
     253         1433 :             return 0;
     254         2866 :         case VyEnum:
     255         2866 :             return 1;
     256            0 :         default:
     257            0 :             return 0;
     258              :     }
     259              : }
     260              : 
     261           36 : std::vector<double> Issm::synchronize_ghosts(const std::vector<Vertex>& vertices, std::span<const double> values) const
     262              : {
     263              :     static_assert(std::numeric_limits<double>::has_quiet_NaN);
     264              :     auto all_values =
     265           36 :         std::vector<double>(m_model->vertices->NumberOfVerticesLocalAll(), std::numeric_limits<double>::quiet_NaN());
     266           36 :     auto issm_vertices = as_range<::Vertex>(*m_model->vertices);
     267           36 :     assert(all_values.size() == issm_vertices.size());
     268         5464 :     for (size_t vtx_idx = 0; vtx_idx < vertices.size(); ++vtx_idx)
     269              :     {
     270         5428 :         auto issm_vertex_idx = vertices[vtx_idx].id;
     271         5428 :         all_values[issm_vertices[issm_vertex_idx]->Lid()] = values[vtx_idx];
     272              :     }
     273           36 :     m_model->SyncLocalVectorWithClonesVertices(all_values.data());
     274           72 :     return all_values;
     275            0 : }
     276              : 
     277           12 : void Issm::set_constraints(definitions input, const std::vector<Vertex>& vertices, std::span<const double> values)
     278              : {
     279           12 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Setting ISSM constraints for {}", issm_input_name(input));
     280              : 
     281           12 :     m_model->SetCurrentConfiguration(get_analysis(input));
     282              : 
     283              :     // TODO: this condition might be relaxed if we use coupling meshes based on nodes instead of vertices.
     284              :     static int allowed_fe_types[] = {P1Enum, P1P1Enum};
     285           24 :     if (stdr::find(allowed_fe_types, as_range<::Element>(*m_model->elements)[0]->element_type) == std::end(allowed_fe_types))
     286              :     {
     287            0 :         throw std::runtime_error("Constraint coupling only implemented for P1 elements (nodes == vertices).");
     288              :     }
     289              : 
     290              :     // set constraints
     291              :     // !! loop over ALL vertices of the model, vertices in the coupling mesh do not include ghosts/clones !!
     292           12 :     auto all_vertex_values = synchronize_ghosts(vertices, values);
     293        14342 :     for (size_t vtx_idx = 0; vtx_idx < all_vertex_values.size(); ++vtx_idx)
     294              :     {
     295        14330 :         auto vertex = as_range<::Vertex>(*m_model->vertices)[vtx_idx];
     296        14330 :         if (std::isnan(all_vertex_values[vertex->Lid()]))
     297              :         {
     298        11464 :             continue; // constraint not set
     299              :         }
     300              : 
     301              :         // find constraint on this vertex
     302              :         // This can probably be optimised since the constraint should have a similar order as the vertices
     303         2866 :         auto static_constraints = as_range<::Constraint>(*m_model->constraints) |
     304      1880498 :                                   stdv::transform([](auto cons) { return dynamic_cast<SpcStatic*>(cons); }) |
     305       937383 :                                   stdv::filter([](auto cons) { return cons != nullptr; });
     306         2866 :         auto constraint = stdr::find_if(
     307              :             static_constraints,
     308       931651 :             [&](auto static_constraint)
     309              :             {
     310       935950 :                 return static_constraint->GetNodeId() == vertex->Sid() + 1 &&
     311       935950 :                        static_constraint->GetDof() == get_constraint_dof_idx(input);
     312              :             });
     313         2866 :         if (constraint == end(static_constraints))
     314              :         {
     315            0 :             throw std::runtime_error(fmt::format(
     316              :                 "Constraint not found for vertex {}, Sid {}, Value {}.",
     317              :                 vtx_idx,
     318            0 :                 vertex->Sid(),
     319            0 :                 all_vertex_values[vertex->Lid()]));
     320              :         }
     321              :         // set constraint
     322         8598 :         **constraint = SpcStatic(
     323         2866 :             (**constraint).Id(),
     324              :             (**constraint).GetNodeId(),
     325              :             (**constraint).GetDof(),
     326         2866 :             all_vertex_values[vertex->Lid()],
     327         5732 :             get_analysis(input));
     328              :     }
     329           12 : }
     330              : 
     331           62 : void Issm::set_input(definitions input, const std::vector<Vertex>& vertices, std::span<const double> values)
     332              : {
     333           62 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Setting ISSM input values for {}", issm_input_name(input));
     334              : 
     335           62 :     if (vertices.size() < m_connected_mesh.vertices.size())
     336              :     {
     337              :         // input array does not include ghosts
     338           24 :         auto all_vertex_values = synchronize_ghosts(vertices, values);
     339              :         
     340              :         // set value for each vertex.
     341              :         // Could probably be made more efficient by setting all values at the same time but
     342              :         // making this work for many different types of meshes is probably not worth it.
     343         3078 :         for (size_t i = 0; i < m_connected_mesh.vertices.size(); ++i)
     344              :         {
     345         3054 :             auto issm_vertex = as_range<::Vertex>(*m_model->vertices)[m_connected_mesh.vertices[i].id];
     346         3054 :             auto issm_element = as_range<::Element>(*m_model->elements)[m_connected_mesh.vertices[i].element_id];
     347         3054 :             IPC_LOG_TRACE(
     348              :                 "Set value for {} at vertex local {} global {} ghost {} [{} {}] = {}",
     349              :                 issm_input_name(input),
     350              :                 issm_vertex->lid,
     351              :                 issm_vertex->sid,
     352              :                 issm_vertex->clone ? 1 : 0,
     353              :                 issm_vertex->x,
     354              :                 issm_vertex->y,
     355              :                 values[i]);
     356         3054 :             issm_element->SetElementInput(
     357         3054 :                 m_model->inputs,
     358              :                 1,
     359              :                 &issm_vertex->lid,
     360         3054 :                 const_cast<double*>(&all_vertex_values[issm_vertex->Lid()]),
     361              :                 input);
     362              :         }
     363           24 :     }
     364              :     else
     365              :     {
     366              :         // input array include ghosts, set directly
     367              :         // set value for each vertex.
     368              :         // Could probably be made more efficient by setting all values at the same time but
     369              :         // making this work for many different types of meshes is probably not worth it.
     370        11776 :         for (size_t i = 0; i < vertices.size(); ++i)
     371              :         {
     372        11738 :             auto issm_vertex = as_range<::Vertex>(*m_model->vertices)[vertices[i].id];
     373        11738 :             auto issm_element = as_range<::Element>(*m_model->elements)[vertices[i].element_id];
     374        11738 :             IPC_LOG_TRACE(
     375              :                 "Set value for {} at vertex local {} global {} ghost {} [{} {}] = {}",
     376              :                 issm_input_name(input),
     377              :                 issm_vertex->lid,
     378              :                 issm_vertex->sid,
     379              :                 issm_vertex->clone ? 1 : 0,
     380              :                 issm_vertex->x,
     381              :                 issm_vertex->y,
     382              :                 values[i]);
     383              :             issm_element
     384        11738 :                 ->SetElementInput(m_model->inputs, 1, &issm_vertex->lid, const_cast<double*>(&values[i]), input);
     385              :         }
     386              :     }
     387           62 : }
     388              : 
     389          125 : void Issm::get_input(definitions input, const std::vector<Vertex>& vertices, std::span<double> values) const
     390              : {
     391          125 :     assert(values.size() == vertices.size());
     392              : 
     393          125 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Get ISSM input values for {}", issm_input_name(input));
     394              : 
     395              :     //get value for each vertex. 
     396              :     //Could probably be made more efficient by setting all values at the same time but
     397              :     //making this work for many different types of meshes is probably not worth it.
     398        31008 :     auto get_value = [&](auto& v)
     399              :     {
     400        31008 :         auto vertex = as_range<::Vertex>(*m_model->vertices)[v.id];
     401        31008 :         auto element = as_range<::Element>(*m_model->elements)[v.element_id];
     402        31008 :         IPC_LOG_TRACE("Get value of at vertex {} [{} {}]", vertex->lid, vertex->x, vertex->y);
     403              :         double value;
     404        31008 :         if (element->GetInput(input))
     405              :         {
     406        31008 :             element->GetInputValue(&value, vertex, input);
     407              :         }
     408              :         else
     409              :         {
     410            0 :             if (input == VelEnum)
     411              :             {
     412              :                 //Vel doesn't exist yet during initialization
     413              :                 double vx, vy, vz;
     414            0 :                 element->GetInputValue(&vx, vertex, VxEnum);
     415            0 :                 element->GetInputValue(&vy, vertex, VyEnum);
     416            0 :                 element->GetInputValue(&vz, vertex, VzEnum);
     417            0 :                 value = sqrt(vx * vx + vy * vy + vz * vz);
     418              :             }
     419              :             else
     420              :             {
     421            0 :                 IPC_LOG_ERROR("Input {} not found.", issm_input_name(input));
     422            0 :                 throw std::runtime_error("Input not found.");
     423              :             }
     424              :         }
     425        31008 :         IPC_LOG_TRACE("Got value for {} at vertex local {} global {} ghost {} [{} {}] = {}", issm_input_name(input), vertex->lid, vertex->sid, vertex->clone ? 1 : 0, vertex->x, vertex->y, value);
     426              : 
     427        31008 :         return value;
     428          125 :     };
     429          125 :     std::ranges::transform(vertices, values.begin(), get_value);
     430          125 : }
     431              : 
     432            3 : void Issm::extrude(definitions input, const std::vector<Vertex>& /*vertices*/)
     433              : {
     434            3 :     if (auto p_mesh2d = get_if<Mesh2dConfig>(&m_coupling_mesh_config.type); p_mesh2d)
     435              :     {
     436            3 :         IPC_LOG_INFO_0(IssmComm::GetComm(), "Extruding ISSM input {}", issm_input_name(input));
     437            3 :         set_parameter(*m_model, InputToExtrudeEnum, input);
     438            3 :         if (p_mesh2d->layer == 0)
     439              :         {
     440            2 :             extrudefrombase_core(m_model.get());
     441              :         }
     442            1 :         else if (p_mesh2d->layer == -1)
     443              :         {
     444            1 :             extrudefromtop_core(m_model.get());
     445              :         }
     446              :         else
     447              :         {
     448            0 :             throw std::runtime_error("Extruding from layer other than base or surface not implemented.");
     449              :         }
     450              :     }
     451            3 : }
     452              : 
     453            5 : definitions depth_average_input(definitions input)
     454              : {
     455            5 :     switch (input)
     456              :     {
     457            2 :     case VxEnum: return VxAverageEnum;
     458            2 :     case VyEnum: return VyAverageEnum;
     459            0 :     case WaterfractionDrainageEnum: return WaterfractionDrainageIntegratedEnum;
     460            0 :     case MaterialsRheologyBEnum: return MaterialsRheologyBbarEnum;
     461            0 :     case DamageDEnum: return DamageDbarEnum;
     462            0 :     case DamageDOldEnum: return DamageDbarOldEnum;
     463            0 :     case MaterialsRheologyEEnum: return MaterialsRheologyEbarEnum;
     464            0 :     case MaterialsRheologyEsEnum: return MaterialsRheologyEsbarEnum;
     465            0 :     case MaterialsRheologyEcEnum: return MaterialsRheologyEcbarEnum;
     466            1 :     default: throw std::domain_error("Depth averaging not implemented for this input.");
     467              :     }
     468              : }
     469              : 
     470            3 : definitions Issm::depth_average(definitions input, const std::vector<Vertex>& /*vertices*/)
     471              : {
     472            3 :     auto average_input = depth_average_input(input);
     473            2 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Depth averaging ISSM input {} into {}", issm_input_name(input), issm_input_name(average_input));
     474            2 :     if (auto p_mesh2d = get_if<Mesh2dConfig>(&m_coupling_mesh_config.type); !p_mesh2d || p_mesh2d->layer != 0)
     475              :     {
     476            0 :         IPC_LOG_WARN_0(IssmComm::GetComm(), "Values of depth averaged input {} only valid at base layer.", issm_input_name(average_input));
     477              :     }
     478              : 
     479            2 :     set_parameter(*m_model, InputToDepthaverageInEnum, input);
     480            2 :     set_parameter(*m_model, InputToDepthaverageOutEnum, average_input);
     481            2 :     depthaverage_core(m_model.get());
     482            2 :     return average_input;
     483              : }
     484              : 
     485            3 : void Issm::initialize(definitions input)
     486              : {
     487              : 
     488            3 :     switch (input)
     489              :     {
     490            0 :     case MaskIceLevelsetEnum:
     491              :     case MaskOceanLevelsetEnum:
     492              :     case ThicknessEnum:
     493              :     case BaseEnum:
     494              :     case SurfaceEnum:
     495              :     case BedEnum:
     496              :         //need no initialization, must be initialized in a valid setup
     497            0 :         break;
     498            3 :     case VxEnum:
     499              :     case VyEnum:
     500              :     case VzEnum:
     501              :     case VelEnum:
     502              :     case PressureEnum:
     503            3 :         if (!m_initialized_stress_balance)
     504              :         {
     505            3 :             stressbalance_core(m_model.get());
     506            3 :             m_model->results->clear(); //don't store results
     507            3 :             m_initialized_stress_balance = true;
     508              :         }
     509            3 :         break;
     510            0 :     default:
     511            0 :         IPC_LOG_WARN_0(
     512              :             IssmComm::GetComm(), 
     513              :             "Initializing coupling data of {}. "
     514              :             "Make sure this field has a valid initial value as the adapter can not compute it. "
     515              :             "A procedure to compute the initial value can be added to ipc::Issm::initialize(). ",
     516              :             issm_input_name(input));
     517            0 :         break;
     518              :     }
     519            3 : }
     520              : 
     521            7 : double Issm::get_max_time_step() const
     522              : {
     523              : #ifdef ISSM_PRECICE_FEATURE_SUBCYCLING
     524              :     int ts_type;
     525            7 :     m_model->parameters->FindParam(&ts_type, TimesteppingTypeEnum);
     526              :     
     527            7 :     if (ts_type == AdaptiveTimesteppingEnum)
     528              :     {
     529              :         // determine adaptive step size
     530              :         double adapt_dt;
     531            1 :         m_model->TimeAdaptx(&adapt_dt);
     532            1 :         return adapt_dt;
     533              :     }
     534            6 :     else if (ts_type == FixedTimesteppingEnum)
     535              :     {
     536              :         // use either the saved initial dt or override dt 
     537              :         // don't use the time step that is currently set in ISSM, since it may have been overridden, 
     538              :         // e.g., to match the exact final time of the last coupling window.
     539            6 :         return m_dt_override > 0 ? m_dt_override : m_initial_dt;
     540              :     }
     541              :     else
     542              :     {
     543            0 :         throw std::runtime_error("Unknown time stepping scheme.");
     544              :     }
     545              : #else
     546              :     return std::numeric_limits<double>::infinity();
     547              : #endif
     548              : }
     549              : 
     550           82 : void Issm::solve(double dt)
     551              : {
     552           82 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Advance ISSM by {}", dt);
     553              : 
     554              :     double t;
     555           82 :     m_model->parameters->FindParam(&t, TimeEnum);
     556              : 
     557              :     // set the time step if not using adaptive time steps.
     558              :     // must be set before every solve because it may have been overridden internally, 
     559              :     // e.g., to match the final time exactly.
     560           82 :     if (get_parameter<::definitions>(*m_model, TimesteppingTypeEnum) == FixedTimesteppingEnum)
     561              :     {
     562           81 :         set_parameter(*m_model, TimesteppingTimeStepEnum, m_dt_override > 0.0 ? m_dt_override : m_initial_dt);
     563              :     }
     564              :     
     565              :     // set or disable output frequency
     566           82 :     if (m_output_frequency_override > 0)
     567              :     {
     568            1 :         set_parameter(*m_model, SettingsOutputFrequencyEnum, m_output_frequency_override);
     569              :     } 
     570           81 :     else if (m_output_frequency_override == 0)
     571              :     {
     572              :         // disable output
     573            1 :         set_parameter(*m_model, SettingsOutputFrequencyEnum, std::numeric_limits<int>::max());
     574              : #ifndef ISSM_PRECICE_FEATURE_CONFIGURE_OUTPUT
     575              :         IPC_LOG_WARN_0(IssmComm::GetComm(), "Disabling output entirely requires ISSM version after 2026.2.");
     576              : #endif
     577              :     }
     578              : 
     579           82 :     set_parameter(*m_model, TimesteppingStartTimeEnum, t);
     580           82 :     auto final_t = t + dt;
     581           82 :     set_parameter(*m_model, TimesteppingFinalTimeEnum, final_t);
     582              :     
     583           82 :     IPC_LOG_INFO_0(IssmComm::GetComm(), "Run ISSM from t = {} to {}", t, t + dt);
     584              : 
     585           82 :     m_model->Solve();
     586              : 
     587              :     // check that ISSM has hit the exact final time.
     588              :     // there is a bug in issm that makes it overshoot.
     589              :     // workaround: set the time step so the coupling window
     590              :     // is an integer multiple of the time step.
     591              :     // bug should be fixed in ISSM version 2026.1
     592           82 :     auto new_t = get_parameter<double>(*m_model, TimeEnum);
     593           82 :     if (!fp_equal(new_t, final_t, 0, 1e-10))
     594              :     {
     595            0 :         IPC_LOG_WARN_0(
     596              :             IssmComm::GetComm(), 
     597              :             "ISSM overshot the expected final time by {} s. Check time step settings.", 
     598              :             new_t - final_t);
     599              :     }
     600           82 : }
     601              : 
     602            6 : void Issm::set_time_step_override(double dt)
     603              : {
     604            6 :     m_dt_override = dt;
     605            6 : }
     606              : 
     607            2 : void Issm::set_output_frequency_override(int f)
     608              : {
     609            2 :     m_output_frequency_override = f;
     610            2 : }
     611              : 
     612           13 : double Issm::get_time() const
     613              : {
     614           13 :     return get_parameter<double>(*m_model, TimeEnum);
     615              : }
     616              : 
     617           13 : int Issm::get_step() const
     618              : {
     619           13 :     return get_parameter<int>(*m_model, StepEnum);
     620              : }
     621              : 
     622              : } // namespace ipc
        

Generated by: LCOV version 2.0-1