Different time window sizes in three-participant bi-coupling setup leading to deadlock

Dear preCICE developers,

I am currently working on a coupled setup with three participants using a bi-coupling strategy. Let me call the solvers A, B, and C. Due to the characteristics of the application, each pair (A–B, B–C, and A–C) is required to use a different time window size in the coupling.

To investigate this, I constructed a minimal example using the dummy solvers. In this simplified setup, I configured three participants (Solver A, Solver B, Solver C) and assigned different time window sizes for each pair-wise coupling (AB, BC, AC) in the precice-config.xml. However, when I run this configuration, the simulation ends up in a deadlock.

Before posting, I tried to carefully check the configuration, but I could not identify whether the issue comes from a mistake on my side or from a limitation in the current design of preCICE. Therefore, I would like to ask:

  1. Is it in principle supported in the current version of preCICE to have three participants in a bi-coupling setup, where each pair of participants uses a different time window size?

  2. If it is supposed to be supported, are there known constraints or best practices for configuring such a case in precice-config.xml to avoid deadlocks?

  3. If it is not supported by design, could you please confirm that this use case is currently outside the intended scope of preCICE? In that case, any suggestions for possible workarounds or alternative coupling strategies would be very helpful.

I am happy to provide my precice-config.xml, the dummy solver configuration, and log files if needed for debugging. Thank you very much in advance for your time and for maintaining preCICE.

Best regards,
Seongju Do

<?xml version="1.0" encoding="UTF-8" ?>
<precice-configuration>
  <log>
    <sink
      type="stream"
      output="stdout"
      filter="%Severity% > debug"
      format="preCICE:%ColorizedSeverity% %Message%"
      enabled="true" />
  </log>

  <!-- 6 data fields: bidirectional for all 3 pairs -->
  <data:scalar name="Data-AB" />
  <data:scalar name="Data-BA" />
  <data:scalar name="Data-AC" />
  <data:scalar name="Data-CA" />
  <data:scalar name="Data-BC" />
  <data:scalar name="Data-CB" />

  <!-- SolverA-Mesh: A-B and A-C data -->
  <mesh name="SolverA-Mesh" dimensions="2">
    <use-data name="Data-AB" />
    <use-data name="Data-BA" />
    <use-data name="Data-AC" />
    <use-data name="Data-CA" />
  </mesh>

  <!-- SolverB-Mesh: A-B and B-C data -->
  <mesh name="SolverB-Mesh" dimensions="2">
    <use-data name="Data-AB" />
    <use-data name="Data-BA" />
    <use-data name="Data-BC" />
    <use-data name="Data-CB" />
  </mesh>

  <!-- SolverC-Mesh: A-C and B-C data -->
  <mesh name="SolverC-Mesh" dimensions="2">
    <use-data name="Data-AC" />
    <use-data name="Data-CA" />
    <use-data name="Data-BC" />
    <use-data name="Data-CB" />
  </mesh>

  <!-- SolverA: controller (hub) -->
  <participant name="SolverA">
    <provide-mesh name="SolverA-Mesh" />
    <write-data name="Data-AB" mesh="SolverA-Mesh" />
    <write-data name="Data-AC" mesh="SolverA-Mesh" />
    <read-data  name="Data-BA" mesh="SolverA-Mesh" />
    <read-data  name="Data-CA" mesh="SolverA-Mesh" />
  </participant>

  <!-- SolverB: maps B-Mesh <-> A-Mesh for A-B data, provides B-Mesh for B-C data -->
  <participant name="SolverB">
    <provide-mesh name="SolverB-Mesh" />
    <receive-mesh name="SolverA-Mesh" from="SolverA" />
    <mapping:nearest-neighbor
      direction="write"
      from="SolverB-Mesh"
      to="SolverA-Mesh"
      constraint="consistent" />
    <mapping:nearest-neighbor
      direction="read"
      from="SolverA-Mesh"
      to="SolverB-Mesh"
      constraint="consistent" />
    <write-data name="Data-BA" mesh="SolverB-Mesh" />
    <write-data name="Data-BC" mesh="SolverB-Mesh" />
    <read-data  name="Data-AB" mesh="SolverB-Mesh" />
    <read-data  name="Data-CB" mesh="SolverB-Mesh" />
  </participant>

  <!-- SolverC: maps C-Mesh <-> A-Mesh for A-C data, maps C-Mesh <-> B-Mesh for B-C data -->
  <participant name="SolverC">
    <provide-mesh name="SolverC-Mesh" />
    <receive-mesh name="SolverA-Mesh" from="SolverA" />
    <receive-mesh name="SolverB-Mesh" from="SolverB" />
    <mapping:nearest-neighbor
      direction="write"
      from="SolverC-Mesh"
      to="SolverA-Mesh"
      constraint="consistent" />
    <mapping:nearest-neighbor
      direction="read"
      from="SolverA-Mesh"
      to="SolverC-Mesh"
      constraint="consistent" />
    <mapping:nearest-neighbor
      direction="write"
      from="SolverC-Mesh"
      to="SolverB-Mesh"
      constraint="consistent" />
    <mapping:nearest-neighbor
      direction="read"
      from="SolverB-Mesh"
      to="SolverC-Mesh"
      constraint="consistent" />
    <write-data name="Data-CA" mesh="SolverC-Mesh" />
    <write-data name="Data-CB" mesh="SolverC-Mesh" />
    <read-data  name="Data-AC" mesh="SolverC-Mesh" />
    <read-data  name="Data-BC" mesh="SolverC-Mesh" />
  </participant>

  <!-- m2n: full mesh - all pairs connected -->
  <m2n:sockets acceptor="SolverA" connector="SolverB" exchange-directory=".." />
  <m2n:sockets acceptor="SolverA" connector="SolverC" exchange-directory=".." />
  <m2n:sockets acceptor="SolverB" connector="SolverC" exchange-directory=".." />

  <!-- A <-> B -->
  <coupling-scheme:serial-explicit>
    <participants first="SolverA" second="SolverB" />
    <max-time value="10" />
    <time-window-size value="1.0" />
    <exchange data="Data-AB" mesh="SolverA-Mesh" from="SolverA" to="SolverB" />
    <exchange data="Data-BA" mesh="SolverA-Mesh" from="SolverB" to="SolverA" />
  </coupling-scheme:serial-explicit>

  <!-- A <-> C -->
  <coupling-scheme:serial-explicit>
    <participants first="SolverA" second="SolverC" />
    <max-time value="10" />
    <time-window-size value="0.5" />
    <exchange data="Data-AC" mesh="SolverA-Mesh" from="SolverA" to="SolverC" />
    <exchange data="Data-CA" mesh="SolverA-Mesh" from="SolverC" to="SolverA" />
  </coupling-scheme:serial-explicit>

  <!-- B <-> C (direct) -->
  <coupling-scheme:serial-explicit>
    <participants first="SolverB" second="SolverC" />
    <max-time value="10" />
    <time-window-size value="0.3" />
    <exchange data="Data-BC" mesh="SolverB-Mesh" from="SolverB" to="SolverC" />
    <exchange data="Data-CB" mesh="SolverB-Mesh" from="SolverC" to="SolverB" />
  </coupling-scheme:serial-explicit>

</precice-configuration>

SolverA_20260415_134408.log (12.4 KB)

SolverC_20260415_134408.log (5.7 KB)

SolverB_20260415_134408.log (5.0 KB)

Hi @SeongJu_Do,

We don’t currently have any integration tests with a compositional coupling scheme with different time window sizes, and I am not sure which combinations might make sense. The publication explaining the concept also does not cover such a scenario, from what I understand. Note that it is not only that you have different time window sizes, but also a closed circle (which seems to be configured correctly).

From the logs, I see that SolverA performs multiple time windows and stops at:

preCICE:e[0m partner: SolverB, time-window 1, t 0.48 (max: 10), Dt 1, max-dt 0.52; partner: SolverC, time-window 1, t 0.48 (max: 10), Dt 0.5, max-dt 0.020000000000000018

This seems to be the time that it needs data from C, via the A-C coupling.

Meanwhile, SolverB and SolverC are stuck in initialization. SolverB:

preCICE:e[0m Setting up primary communication to coupling partner/s
preCICE:e[0m Primary ranks are connected
preCICE:e[0m Setting up preliminary secondary communication to coupling partner/s
preCICE:e[0m Receive global mesh SolverA-Mesh
preCICE:e[0m Prepare partition for mesh SolverB-Mesh
preCICE:e[0m Gather mesh SolverB-Mesh
preCICE:e[0m Send global mesh SolverB-Mesh
preCICE:e[0m Setting up secondary communication to coupling partner/s
preCICE:e[0m Secondary ranks are connected
preCICE:e[0m Computing "nearest-neighbor" mapping from mesh "SolverB-Mesh" to mesh "SolverA-Mesh" in "write" direction.
preCICE:e[0m Mapping distance min:0 max:0.00857143 avg: 0.00482897 var: 8.87417e-06 cnt: 71
preCICE:e[0m Mapping "Data-BA" for t=0 from "SolverB-Mesh" to "SolverA-Mesh" (skipped zero sample)

SolverC:

preCICE:e[0m Setting up primary communication to coupling partner/s
preCICE:e[0m Primary ranks are connected
preCICE:e[0m Setting up preliminary secondary communication to coupling partner/s
preCICE:e[0m Receive global mesh SolverA-Mesh
preCICE:e[0m Receive global mesh SolverB-Mesh
preCICE:e[0m Prepare partition for mesh SolverC-Mesh
preCICE:e[0m Setting up secondary communication to coupling partner/s
preCICE:e[0m Secondary ranks are connected
preCICE:e[0m Computing "nearest-neighbor" mapping from mesh "SolverC-Mesh" to mesh "SolverA-Mesh" in "write" direction.
preCICE:e[0m Mapping distance min:0 max:0.00714286 avg: 0.00402414 var: 6.16262e-06 cnt: 71
preCICE:e[0m Computing "nearest-neighbor" mapping from mesh "SolverC-Mesh" to mesh "SolverB-Mesh" in "write" direction.
preCICE:e[0m Mapping distance min:0 max:0.00666667 avg: 0.00392157 var: 6.40779e-06 cnt: 51
preCICE:e[0m Mapping "Data-CA" for t=0 from "SolverC-Mesh" to "SolverA-Mesh" (skipped zero sample)
preCICE:e[0m Mapping "Data-CB" for t=0 from "SolverC-Mesh" to "SolverB-Mesh" (skipped zero sample)

A-C is a serial coupling, with SolverA as first, SolverC as second. At t=0.5, SolverA has to wait for SolverC. However, SolverC also waits for SolverB, which does not yet have the information it needs from SolverA, since it is not yet t=1.0.

I am not sure if any permutation of this setup could work (e.g., different order), but I would assume not. The different schemes are executed in parallel to each other. Making the schemes themselves parallel would not help, since at some point, some participant will need information that is not yet available.

Independently of what preCICE does, what would you expect, algorithm-wise?

Very interesting scenario, in any case. We definitely have a lot of blind spots to document in the multi-coupling configuration page.

Hi @SeongJu_Do :waving_hand:

Compositional coupling schemes can, in general, lead to deadlocks. There is indeed no fixed list or categorization of what preCICE supports and what it does not.

There is another integration test here:

Is it in principle supported in the current version of preCICE to have three participants in a bi-coupling setup, where each pair of participants uses a different time window size?

Yes, but not arbitrary cases.

If it is supposed to be supported, are there known constraints or best practices for configuring such a case in precice-config.xml to avoid deadlocks?

One best practice is explained here:

If it is not supported by design, could you please confirm that this use case is currently outside the intended scope of preCICE? In that case, any suggestions for possible workarounds or alternative coupling strategies would be very helpful.

My guess is that the case above runs if only parallel-explicit coupling schemes are used.

Why do you need different time window sizes in the first place? Wouldn’t different time step sizes under a fixed time window size suffice?

I wanted to write a research proposal on such multi-rate cases this year. Any use cases would be helpful in motivating the research. I would be interested in finding out more about your case. Do you have any further description? Also privately if you prefer.

Benjamin