Skip to content

Commit

Permalink
Port runner to preCICE v3 (#9)
Browse files Browse the repository at this point in the history
* Start adapting runner to v3

* First working coupling with v3 for one special case

* Implement coupling for scalar data only

* Implement exchagne of preCICE vector data

* Test v3 runner with oscillator tutorial

* Add detection of preCICE version

* Revert duplication of configurations

* Merge cleaning scripts of test

* Make run scripts executable

* Update preCICE configs

* Remove support of v2

* Remove explicit data type from configuration

* Format code

---------

Co-authored-by: Leonard Willeke <st150067@stud.uni-stuttgart.de>
  • Loading branch information
uekerman and Leonard Willeke authored Feb 21, 2024
1 parent 9f17108 commit d6c605a
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 134 deletions.
122 changes: 40 additions & 82 deletions fmiprecice/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,13 @@
import json


# Define functions

def precice_read_data(interface, data_type, read_data_id, vertex_id):
"""
Reads data from preCICE. The preCICE API call depends on the data type, scalar or vector.
"""

if data_type == "scalar":
read_data = interface.read_scalar_data(read_data_id, vertex_id)
elif data_type == "vector":
read_data = interface.read_vector_data(read_data_id, vertex_id)
else:
raise Exception("Please choose data type from: scalar, vector.")

return read_data


def precice_write_data(interface, data_type, write_data_id, vertex_id, write_data):
"""
Writes data to preCICE. The preCICE API call depends on the data type, scalar or vector.
"""

if data_type == "scalar":
write_data = interface.write_scalar_data(write_data_id, vertex_id, write_data)
elif data_type == "vector":
write_data = interface.write_vector_data(write_data_id, vertex_id, write_data)
else:
raise Exception("Please choose data type from: scalar, vector.")


def main():
"""
Executes the Runner
- Load settings from json files
- Load FMU model and prepare the simulation
- Initialize the preCICE interface
- Create an object of the preCICE participant
- Run the coupled simulation
- Store the results
- Terminate FMU model and preCICE
Expand Down Expand Up @@ -167,59 +137,49 @@ def main():
solver_process_size = 1
num_vertices = 1

# Initialize interface
interface = precice.Interface(
if precice.get_version_information().decode()[0] != "3":
raise Exception("This version of the FMI Runner is only compatible with preCICE v3.")

# Create the participant
participant = precice.Participant(
precice_data["coupling_params"]["participant_name"],
precice_data["coupling_params"]["config_file_name"],
solver_process_index,
solver_process_size
)

mesh_id = interface.get_mesh_id(precice_data["coupling_params"]["mesh_name"])
dimensions = interface.get_dimensions()
mesh_name = precice_data["coupling_params"]["mesh_name"]
read_data_name = precice_data["coupling_params"]["read_data_name"]
write_data_name = precice_data["coupling_params"]["write_data_name"]

dimensions = participant.get_mesh_dimensions(mesh_name)

vertices = np.zeros((num_vertices, dimensions))
read_data = np.zeros((num_vertices, dimensions))
write_data = np.zeros((num_vertices, dimensions))

vertex_id = interface.set_mesh_vertices(mesh_id, vertices)
read_data_id = interface.get_data_id(precice_data["coupling_params"]["read_data"]["name"], mesh_id)
write_data_id = interface.get_data_id(precice_data["coupling_params"]["write_data"]["name"], mesh_id)
read_data_type = precice_data["coupling_params"]["read_data"]["type"]
write_data_type = precice_data["coupling_params"]["write_data"]["type"]

# check entries for data types
if read_data_type not in ["scalar", "vector"]:
raise Exception("Wrong data type for read data in the precice settings file. Please choose from: scalar, vector")
if write_data_type not in ["scalar", "vector"]:
raise Exception("Wrong data type for write data in the precice settings file. Please choose from: scalar, vector")

# initial value for write data
if write_data_type == "scalar":
write_data = fmu_write_data_init[0]
elif write_data_type == "vector":
write_data = np.array(fmu_write_data_init)
# Is it possible to have different data types for read and write? Eg read a scalar and write a vector. This should be possible from preCICE, but I have to implement it.

precice_dt = interface.initialize()
my_dt = precice_dt # use my_dt < precice_dt for subcycling
vertex_id = participant.set_mesh_vertices(mesh_name, vertices)

# write initial data
if interface.is_action_required(precice.action_write_initial_data()):
precice_write_data(interface, write_data_type, write_data_id, vertex_id, write_data)
interface.mark_action_fulfilled(precice.action_write_initial_data())
if participant.requires_initial_data():
write_data = np.array(fmu_write_data_init)
participant.write_data(mesh_name, write_data_name, vertex_id, write_data)

interface.initialize_data()
participant.initialize()

recorder = Recorder(fmu=fmu, modelDescription=model_description, variableNames=output_names)

t = 0

recorder.sample(t, force=False)

while interface.is_coupling_ongoing():
if interface.is_action_required(precice.action_write_iteration_checkpoint()):
while participant.is_coupling_ongoing():

# Check if model has the appropiate functionalities
if participant.requires_writing_checkpoint():

# Check if model has the appropriate functionalities
if is_fmi1:
raise Exception("Implicit coupling not possible because FMU model with FMI1 can't reset state. "
"Please update model to FMI2 or FMI3. "
Expand All @@ -234,19 +194,20 @@ def main():
state_cp = fmu.getFMUState()
t_cp = t

interface.mark_action_fulfilled(precice.action_write_iteration_checkpoint())

# Compute current time step size
dt = np.min([precice_dt, my_dt])
precice_dt = participant.get_max_time_step_size()
dt = precice_dt # FMU always does the max possible dt

# Read data from other participant
read_data = precice_read_data(interface, read_data_type, read_data_id, vertex_id)
read_data = participant.read_data(mesh_name, read_data_name, vertex_id, precice_dt)

# Convert data to list for FMU
if read_data_type == "scalar":
read_data = [read_data]
elif read_data_type == "vector":
read_data = list(read_data)
if participant.get_data_dimensions(mesh_name, read_data_name) > 1:
# why does this work with one-entry vectors? A (1,2) vector is written on a single scalar FMU variable.
# This is not correct
# The program should abort if data_type = vector and the number of entries
# in vr_read / vr_write do not match the number of elements in read_data / write_data
# preCICE aborts for write_data() with the wrong dimensions, that is ok for now
read_data = read_data[0]

# Set signals in FMU
input.apply(t)
Expand All @@ -261,31 +222,28 @@ def main():
fmu.doStep(t, dt)
result = fmu.getFloat64(vr_write)

# Convert result to double or array for preCICE
if write_data_type == "scalar":
write_data = result[0]
elif write_data_type == "vector":
# Convert result for preCICE
# Convert to array
if participant.get_data_dimensions(mesh_name, write_data_name) == 1:
write_data = np.array(result)
else:
write_data = np.array([result])

# Write data to other participant
precice_write_data(interface, write_data_type, write_data_id, vertex_id, write_data)
participant.write_data(mesh_name, write_data_name, vertex_id, write_data)

t = t + dt

precice_dt = interface.advance(dt)

if interface.is_action_required(precice.action_read_iteration_checkpoint()):
participant.advance(dt)

if participant.requires_reading_checkpoint():
fmu.setFMUState(state_cp)
t = t_cp

interface.mark_action_fulfilled(precice.action_read_iteration_checkpoint())

else:
# Save output data for completed timestep
recorder.sample(t, force=False)

interface.finalize()
participant.finalize()

# store final results
try:
Expand Down
3 changes: 0 additions & 3 deletions tests/SolverOne/clean.sh

This file was deleted.

4 changes: 2 additions & 2 deletions tests/SolverOne/precice-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"participant_name": "SolverOne",
"config_file_name": "../precice-config.xml",
"mesh_name": "MeshOne",
"write_data": {"name": "dataOne", "type": "scalar"},
"read_data": {"name": "dataTwo", "type": "scalar"}
"write_data_name": "dataOne",
"read_data_name": "dataTwo"
}
}
Empty file modified tests/SolverOne/run.sh
100644 → 100755
Empty file.
4 changes: 0 additions & 4 deletions tests/SolverTwo/clean.sh

This file was deleted.

4 changes: 2 additions & 2 deletions tests/SolverTwo/precice-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"participant_name": "SolverTwo",
"config_file_name": "../precice-config.xml",
"mesh_name": "MeshTwo",
"write_data": {"name": "dataTwo", "type": "scalar"},
"read_data": {"name": "dataOne", "type": "scalar"}
"write_data_name": "dataTwo",
"read_data_name": "dataOne"
}
}
Empty file modified tests/SolverTwo/run.sh
100644 → 100755
Empty file.
7 changes: 7 additions & 0 deletions tests/clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
rm -rfv SolverOne/precice-profiling
rm -rfv SolverTwo/precice-profiling
rm -rfv ./precice-run
rm -rfv SolverOne/output
rm -rfv SolverTwo/output
rm -rfv SolverOne/*.log
rm -rfv SolverTwo/*.log
86 changes: 45 additions & 41 deletions tests/precice-config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,49 @@
enabled="true" />
</log>

<solver-interface dimensions="2">
<data:scalar name="dataOne" />
<data:scalar name="dataTwo" />
<mesh name="MeshOne">
<use-data name="dataOne" />
<use-data name="dataTwo" />
</mesh>
<mesh name="MeshTwo">
<use-data name="dataOne" />
<use-data name="dataTwo" />
</mesh>
<participant name="SolverOne">
<use-mesh name="MeshOne" provide="yes" />
<write-data name="dataOne" mesh="MeshOne" />
<read-data name="dataTwo" mesh="MeshOne" />
</participant>
<participant name="SolverTwo">
<use-mesh name="MeshOne" from="SolverOne" />
<use-mesh name="MeshTwo" provide="yes" />
<mapping:nearest-neighbor
direction="write"
from="MeshTwo"
to="MeshOne"
constraint="conservative" />
<mapping:nearest-neighbor
direction="read"
from="MeshOne"
to="MeshTwo"
constraint="consistent" />
<write-data name="dataTwo" mesh="MeshTwo" />
<read-data name="dataOne" mesh="MeshTwo" />
</participant>
<m2n:sockets from="SolverOne" to="SolverTwo" exchange-directory=".." />
<coupling-scheme:serial-explicit>
<participants first="SolverOne" second="SolverTwo" />
<max-time-windows value="5" />
<time-window-size value="1.0" />
<exchange data="dataOne" mesh="MeshOne" from="SolverOne" to="SolverTwo" />
<exchange data="dataTwo" mesh="MeshOne" from="SolverTwo" to="SolverOne" />
</coupling-scheme:serial-explicit>
</solver-interface>
<data:scalar name="dataOne" />
<data:scalar name="dataTwo" />

<mesh name="MeshOne" dimensions="2">
<use-data name="dataOne" />
<use-data name="dataTwo" />
</mesh>

<mesh name="MeshTwo" dimensions="2">
<use-data name="dataOne" />
<use-data name="dataTwo" />
</mesh>

<participant name="SolverOne">
<provide-mesh name="MeshOne" />
<write-data name="dataOne" mesh="MeshOne" />
<read-data name="dataTwo" mesh="MeshOne" />
</participant>

<participant name="SolverTwo">
<receive-mesh name="MeshOne" from="SolverOne" />
<provide-mesh name="MeshTwo" />
<mapping:nearest-neighbor
direction="write"
from="MeshTwo"
to="MeshOne"
constraint="conservative" />
<mapping:nearest-neighbor
direction="read"
from="MeshOne"
to="MeshTwo"
constraint="consistent" />
<write-data name="dataTwo" mesh="MeshTwo" />
<read-data name="dataOne" mesh="MeshTwo" />
</participant>

<m2n:sockets acceptor="SolverOne" connector="SolverTwo" exchange-directory=".." />

<coupling-scheme:serial-explicit>
<participants first="SolverOne" second="SolverTwo" />
<max-time-windows value="5" />
<time-window-size value="1.0" />
<exchange data="dataOne" mesh="MeshOne" from="SolverOne" to="SolverTwo" />
<exchange data="dataTwo" mesh="MeshOne" from="SolverTwo" to="SolverOne" />
</coupling-scheme:serial-explicit>
</precice-configuration>

0 comments on commit d6c605a

Please sign in to comment.