For coupling, we need coupling meshes. Let’s see how we can tell preCICE about our coupling mesh. For the moment, we define coupling meshes only as clouds of vertices. In Step 8, we will learn how to define mesh connectivity, so edges, triangles, and quads.
Coupling meshes and associated data fields are defined in the preCICE configuration file, which you probably already know from the tutorials. The concrete values, however, you can access with the API:
VertexID setMeshVertex(
::precice::string_view meshName,
::precice::span<const double> position);
void setMeshVertices(
precice::string_view meshName,
precice::span<const double> positions,
precice::span<VertexID> ids);
setMeshVertex
defines the coordinates of a single mesh vertex and returns a vertex ID, which you can use to refer to this vertex.setMeshVertices
defines multiple vertices at once. So, you can use this function instead of callingsetMeshVertex
multiple times. This is also good practice for performance reasons.
To write data to the coupling data structure the following API function is needed:
void Participant::writeData(
precice::string_view meshName,
precice::string_view dataName,
precice::span<const VertexID> vertices,
precice::span<const double> values)
Similarly, there is a readData
API function for reading coupling data:
void readData(
precice::string_view meshName,
precice::string_view dataName,
precice::span<const VertexID> vertices,
double relativeReadTime,
precice::span<double> values) const;
The relative read time can be anything from the current point in time (0
) to the end of the time window (getMaxTimeStepSize()
). We will talk about the additional argument relativeReadTime
in detail in the section on time interpolation.
Let’s define coupling meshes and access coupling data in our example code:
turnOnSolver(); //e.g. setup and partition mesh
precice::Participant precice("FluidSolver","precice-config.xml",rank,size); // constructor
int meshDim = precice.getMeshDimensions("FluidMesh");
int vertexSize; // number of vertices at wet surface
// determine vertexSize
std::vector<double> coords(vertexSize*dim); // coords of vertices at wet surface
// determine coordinates
std::vector<int> vertexIDs(vertexSize);
precice.setMeshVertices("FluidMesh", coords, vertexIDs);
int forcesDim = precice.getDataDimensions("FluidMesh", "Forces");
std::vector<double> forces(vertexSize*forcesDim);
int displacementsDim = precice.getDataDimensions("FluidMesh", "Displacements");
std::vector<double> displacements(vertexSize*displacementsDim);
double solverDt; // solver time step size
double preciceDt; // maximum precice time step size
double dt; // actual time step size
precice.initialize();
while (not simulationDone()){ // time loop
preciceDt = precice.getMaxTimeStepSize();
solverDt = beginTimeStep(); // e.g. compute adaptive dt
dt = min(preciceDt, solverDt);
precice.readData("FluidMesh", "Displacements", vertexIDs, dt, displacements);
setDisplacements(displacements);
solveTimeStep(dt);
computeForces(forces);
precice.writeData("FluidMesh", "Forces", vertexIDs, forces);
precice.advance(dt);
endTimeStep(); // e.g. update variables, increment time
}
precice.finalize(); // frees data structures and closes communication channels
turnOffSolver();
Did you see that your fluid solver now also needs to provide the functions computeForces
and setDisplacements
? As you are an expert in your fluid code, these functions should be easy to implement. Most probably, you already have such functionality anyway. If you are not an expert in your code try to find an expert :smirk:.
Once your adapter reaches this point, it is a good idea to test your adapter against one of the solverdummies, which then plays the role of the SolidSolver
.
You can use the following precice-config.xml
:
<?xml version="1.0"?>
<precice-configuration>
<data:vector name="Forces"/>
<data:vector name="Displacements"/>
<mesh name="FluidMesh" dimensions="3">
<use-data name="Forces"/>
<use-data name="Displacements"/>
</mesh>
<mesh name="StructureMesh" dimensions="3">
<use-data name="Forces"/>
<use-data name="Displacements"/>
</mesh>
<participant name="FluidSolver">
<provide-mesh name="FluidMesh" />
<receive-mesh name="StructureMesh" from="SolidSolver"/>
<write-data name="Forces" mesh="FluidMesh"/>
<read-data name="Displacements" mesh="FluidMesh"/>
<mapping:nearest-neighbor direction="write" from="FluidMesh"
to="StructureMesh" constraint="conservative"/>
<mapping:nearest-neighbor direction="read" from="StructureMesh"
to="FluidMesh" constraint="consistent"/>
</participant>
<participant name="SolidSolver">
<provide-mesh name="StructureMesh" />
<write-data name="Displacements" mesh="StructureMesh"/>
<read-data name="Forces" mesh="StructureMesh"/>
</participant>
<m2n:sockets from="FluidSolver" to="SolidSolver"/>
<coupling-scheme:serial-explicit>
<participants first="FluidSolver" second="SolidSolver"/>
<max-time-windows value="10" />
<time-window-size value="1.0" />
<exchange data="Forces" mesh="StructureMesh" from="FluidSolver" to="SolidSolver"/>
<exchange data="Displacements" mesh="StructureMesh" from="SolidSolver" to="FluidSolver"/>
</coupling-scheme:serial-explicit>
</precice-configuration>