OpenRadioss 2025.1.11
OpenRadioss project
Loading...
Searching...
No Matches
precice_coupling_adapter.cpp
Go to the documentation of this file.
1//Copyright> OpenRadioss
2//Copyright> Copyright (C) 1986-2025 Altair Engineering Inc.
3//Copyright>
4//Copyright> This program is free software: you can redistribute it and/or modify
5//Copyright> it under the terms of the GNU Affero General Public License as published by
6//Copyright> the Free Software Foundation, either version 3 of the License, or
7//Copyright> (at your option) any later version.
8//Copyright>
9//Copyright> This program is distributed in the hope that it will be useful,
10//Copyright> but WITHOUT ANY WARRANTY; without even the implied warranty of
11//Copyright> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12//Copyright> GNU Affero General Public License for more details.
13//Copyright>
14//Copyright> You should have received a copy of the GNU Affero General Public License
15//Copyright> along with this program. If not, see <https://www.gnu.org/licenses/>.
16//Copyright>
17//Copyright>
18//Copyright> Commercial Alternative: Altair Radioss Software
19//Copyright>
20//Copyright> As an alternative to this open-source version, Altair also offers Altair Radioss
21//Copyright> software under a commercial license. Contact Altair to discuss further if the
22//Copyright> commercial version may interest you: https://www.altair.com/radioss/.
24#include <algorithm>
25#include <fstream>
26#include <iostream>
27#include <memory>
28#include <sstream>
29#include <stdexcept>
30#include <string>
31#include <vector>
32
33#ifdef WITH_PRECICE
34
35PreciceCouplingAdapter::PreciceCouplingAdapter()
36 : active_(false)
37 , maxTimeStepSize_(0.0)
38 , precice_(nullptr)
39{
40}
41
42PreciceCouplingAdapter::~PreciceCouplingAdapter() {
43 if (active_) {
44 finalize();
45 }
46}
47
48bool PreciceCouplingAdapter::configure(const std::string& configFile) {
49 // Expected format of the configuration file:
50 // /PRECICE/PARTICIPANT_NAME/SolverOne
51 // /PRECICE/CONFIG_FILE/precice-config.xml
52 // /PRECICE/MESH_NAME/MeshOne
53 // /PRECICE/READ/DISPLACEMENTS
54 // /PRECICE/WRITE/FORCES
55 // /PRECICE/GRNOD/grnodID
56 std::ifstream file(configFile);
57 if (!file.is_open()) {
58 std::cout << "No " << configFile << " file found in the current directory" << std::endl;
59 std::cout << "preCICE will not be active" << std::endl;
60 active_ = false;
61 return false;
62 }
63
64 std::string line;
65 while (std::getline(file, line)) {
66 // Remove leading/trailing whitespace
67 line.erase(0, line.find_first_not_of(" \t"));
68 line.erase(line.find_last_not_of(" \t") + 1);
69
70 if (line.empty() || line[0] == '#') continue;
71
72 // Split by '/' and expect format: /PRECICE/KEY/VALUE
73 std::vector<std::string> parts;
74 std::istringstream iss(line);
75 std::string part;
76
77 while (std::getline(iss, part, '/')) {
78 if (!part.empty()) { // Skip empty parts (from leading '/')
79 parts.push_back(part);
80 }
81 }
82
83 // Expected format: parts[0] = "PRECICE", parts[1] = "KEY", parts[2] = "VALUE"
84 if (parts.size() >= 3 && parts[0] == "PRECICE") {
85 const auto& key = parts[1];
86 auto value = parts[2];
87
88 // Handle cases where VALUE might contain additional path segments
89 if (parts.size() > 3) {
90 for (size_t i = 3; i < parts.size(); ++i) {
91 value += "/" + parts[i];
92 }
93 }
94
95 if (key == "PARTICIPANT_NAME") {
96 participantName_ = value;
97 } else if (key == "CONFIG_FILE") {
98 configFile_ = value;
99 } else if (key == "MESH_NAME") {
100 meshName_ = value;
101 } else if (key == "READ") {
102 const auto dataType = stringToDataType(value);
103 if (dataType == DataType::NOTHING) {
104 std::cout << "Warning: Unknown data type '" << value << "' in read configuration." << std::endl;
105 continue;
106 }
107 readData_[static_cast<size_t>(dataType)].isActive = true;
108 if (DataType::POSITIONS == dataType) {
109 readData_[static_cast<size_t>(dataType)].mode = Mode::REPLACE;
110 } else if (DataType::FORCES == dataType) {
111 readData_[static_cast<size_t>(dataType)].mode = Mode::ADD;
112 } else if (DataType::DISPLACEMENTS == dataType) {
113 readData_[static_cast<size_t>(dataType)].mode = Mode::REPLACE;
114 }
115 } else if (key == "WRITE") {
116 const auto dataType = stringToDataType(value);
117 if (dataType == DataType::NOTHING) {
118 std::cout << "Warning: Unknown data type '" << value << "' in write configuration." << std::endl;
119 continue;
120 }
121 writeData_[static_cast<size_t>(dataType)].isActive = true;
122 } else if (key == "GRNOD") {
123 try {
124 setGroupNodeId(std::stoi(value));
125 } catch (const std::exception& e) {
126 std::cout << "Warning: Invalid GRNOD value '" << value << "': " << e.what() << std::endl;
127 }
128 }
129 } else {
130 std::cout << "Warning: Ignoring malformed line: " << line << std::endl;
131 }
132 }
133
134 active_ = true;
135 return true;
136}
137
138void PreciceCouplingAdapter::setNodes(const std::vector<int>& nodeIds) {
139 couplingNodeIds_ = nodeIds;
140
141 // Allocate buffers for 3D data
142 constexpr auto dimensions = getDimensions();
143 const auto bufferSize = couplingNodeIds_.size() * dimensions;
144 vertexIds_.resize(couplingNodeIds_.size());
145
146 // Loop over readData_
147 for (auto& data : readData_) {
148 if (data.isActive) {
149 data.buffer.resize(bufferSize);
150 }
151 }
152
153 // Loop over writeData_
154 for (auto& data : writeData_) {
155 if (data.isActive) {
156 data.buffer.resize(bufferSize);
157 }
158 }
159}
160
161bool PreciceCouplingAdapter::initialize(const double* coordinates, int totalNodes, int mpiRank, int mpiSize) {
162 if (!active_) return false;
163 if (!coordinates) {
164 std::cerr << "Error: coordinates pointer is null" << std::endl;
165 return false;
166 }
167 if (totalNodes <= 0) {
168 std::cerr << "Error: totalNodes must be positive, got " << totalNodes << std::endl;
169 return false;
170 }
171
172 try {
173 // Create preCICE participant
174 precice_ = std::make_unique<precice::Participant>(participantName_, configFile_, mpiRank, mpiSize);
175
176 // Set up mesh vertices
177 std::vector<double> meshVertices;
178 meshVertices.reserve(couplingNodeIds_.size() * 3);
179
180 for (const auto nodeId : couplingNodeIds_) {
181 if (!isNodeIdValid(nodeId, totalNodes)) {
182 std::cerr << "Error: Node ID " << nodeId << " out of bounds [1, " << totalNodes << "]" << std::endl;
183 return false;
184 }
185 // Convert from 1-based Fortran indexing to 0-based C++ indexing
186 const auto idx = nodeId - 1;
187 constexpr auto dimensions = getDimensions();
188 meshVertices.push_back(coordinates[idx * dimensions]);
189 meshVertices.push_back(coordinates[idx * dimensions + 1]);
190 meshVertices.push_back(coordinates[idx * dimensions + 2]);
191 }
192
193 // Set mesh vertices
194 precice_->setMeshVertices(meshName_, meshVertices, vertexIds_);
195
196 // Check if initial data is required
197 if (precice_->requiresInitialData()) {
198 for (size_t i = 0; i < writeData_.size(); ++i) {
199 if (writeData_[i].isActive) {
200 const auto& dataName = dataTypeToString(static_cast<DataType>(i));
201 precice_->writeData(meshName_, dataName, vertexIds_, writeData_[i].buffer);
202 }
203 }
204 }
205
206 // Initialize preCICE
207 precice_->initialize();
208 maxTimeStepSize_ = precice_->getMaxTimeStepSize();
209
210 } catch (const std::exception& e) {
211 std::cerr << "Error initializing preCICE: " << e.what() << std::endl;
212 return false;
213 }
214
215 return true;
216}
217
218void PreciceCouplingAdapter::writeData(const double* values, int totalNodes, double dt, int dataType) {
219 if (!active_) return;
220 if (!precice_) return;
221 if (!precice_->isCouplingOngoing()) return;
222 if (!values) {
223 std::cerr << "Error: values pointer is null in writeData" << std::endl;
224 return;
225 }
226
227 const auto& writeDataName = dataTypeToString(static_cast<DataType>(dataType));
228 if (!writeData_[static_cast<size_t>(dataType)].isActive) {
229 return;
230 }
231
232 // Extract data for coupling nodes
233 extractNodeData(values, totalNodes, dataType);
234 precice_->writeData(meshName_, writeDataName, vertexIds_, writeData_[dataType].buffer);
235}
236
237void PreciceCouplingAdapter::readData(double* values, int totalNodes, double dt, int dataType) {
238 if (!active_) return;
239 if (!precice_) return;
240 if (!precice_->isCouplingOngoing()) return;
241 if (!values) {
242 std::cerr << "Error: values pointer is null in readData" << std::endl;
243 return;
244 }
245
246 const auto& readDataName = dataTypeToString(static_cast<DataType>(dataType));
247 if (!readData_[static_cast<size_t>(dataType)].isActive) {
248 return;
249 }
250
251 const auto maxTimeStepSize = precice_->getMaxTimeStepSize();
252 const auto cdt = std::min(dt, maxTimeStepSize);
253
254 // Read data from preCICE
255 precice_->readData(meshName_, readDataName, vertexIds_, cdt, readData_[dataType].buffer);
256
257 // Inject data into global arrays
258 injectNodeData(values, totalNodes, dataType);
259}
260
261void PreciceCouplingAdapter::advance(double& dt) {
262 if (!active_) return;
263 if (!precice_) return;
264 if (!precice_->isCouplingOngoing()) return;
265
266 const auto dtToUse = std::min(dt, maxTimeStepSize_);
267
268 // Advance preCICE
269 precice_->advance(dtToUse);
270
271 // Update max time step size
272 maxTimeStepSize_ = precice_->getMaxTimeStepSize();
273 dt = dtToUse;
274}
275
276bool PreciceCouplingAdapter::isCouplingOngoing() const {
277 if (!active_) return false;
278 if (!precice_) return false;
279 return precice_->isCouplingOngoing();
280}
281
282bool PreciceCouplingAdapter::requiresWritingCheckpoint() const {
283 if (!active_) return false;
284 if (!precice_) return false;
285 return precice_->requiresWritingCheckpoint();
286}
287
288bool PreciceCouplingAdapter::requiresReadingCheckpoint() const {
289 if (!active_) return false;
290 if (!precice_) return false;
291 return precice_->requiresReadingCheckpoint();
292}
293
294void PreciceCouplingAdapter::finalize() {
295 if (!active_) return;
296
297 if (precice_) {
298 precice_->finalize();
299 precice_.reset();
300 }
301
302 // Clear all data structures
303 couplingNodeIds_.clear();
304 vertexIds_.clear();
305 for (auto& data : readData_) {
306 data.buffer.clear();
307 data.isActive = false;
308 }
309 for (auto& data : writeData_) {
310 data.buffer.clear();
311 data.isActive = false;
312 }
313
314 active_ = false;
315}
316
317bool PreciceCouplingAdapter::isActive() const {
318 return active_;
319}
320
321double PreciceCouplingAdapter::getMaxTimeStepSize() const {
322 return maxTimeStepSize_;
323}
324
325int PreciceCouplingAdapter::getNumberOfCouplingNodes() const {
326 return static_cast<int>(couplingNodeIds_.size());
327}
328
329void PreciceCouplingAdapter::extractNodeData(const double* globalValues, int totalNodes, int dataType) {
330 if (!writeData_[dataType].isActive) {
331 return;
332 }
333
334 constexpr auto dimensions = getDimensions();
335 for (size_t i = 0; i < couplingNodeIds_.size(); ++i) {
336 const auto nodeId = couplingNodeIds_[i];
337 if (!isNodeIdValid(nodeId, totalNodes)) {
338 std::cerr << "Error: Node ID " << nodeId << " out of bounds in extractNodeData" << std::endl;
339 continue;
340 }
341 const auto idx = nodeId - 1; // Convert to 0-based indexing
342 writeData_[dataType].buffer[i * dimensions] = globalValues[idx * dimensions];
343 writeData_[dataType].buffer[i * dimensions + 1] = globalValues[idx * dimensions + 1];
344 writeData_[dataType].buffer[i * dimensions + 2] = globalValues[idx * dimensions + 2];
345 }
346}
347
348void PreciceCouplingAdapter::injectNodeData(double* globalValues, int totalNodes, int dataType) {
349 if (!readData_[dataType].isActive) {
350 return;
351 }
352 if (readData_[dataType].mode == Mode::ADD) {
353 for (size_t i = 0; i < couplingNodeIds_.size(); ++i) {
354 int nodeId = couplingNodeIds_[i] - 1; // Convert to 0-based indexing
355 globalValues[nodeId * 3] += readData_[dataType].buffer[i * 3];
356 globalValues[nodeId * 3 + 1] += readData_[dataType].buffer[i * 3 + 1];
357 globalValues[nodeId * 3 + 2] += readData_[dataType].buffer[i * 3 + 2];
358 }
359 } else if (readData_[dataType].mode == Mode::REPLACE) {
360 for (size_t i = 0; i < couplingNodeIds_.size(); ++i) {
361 int nodeId = couplingNodeIds_[i] - 1; // Convert to 0-based indexing
362 globalValues[nodeId * 3] = readData_[dataType].buffer[i * 3];
363 globalValues[nodeId * 3 + 1] = readData_[dataType].buffer[i * 3 + 1];
364 globalValues[nodeId * 3 + 2] = readData_[dataType].buffer[i * 3 + 2];
365 }
366 } else {
367 std::cout << "Warning: Unknown mode for data type " << dataTypeToString(static_cast<DataType>(dataType))
368 << ", skipping injection." << std::endl;
369 }
370}
371
372#endif