Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog-entries/677.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added a new Partitioned Pipe Multiscale tutorial including 1D–3D, 3D–1D, 1D–1D, and 3D–3D coupling configurations using OpenFOAM, Nutils, and preCICE [#677](https://github.com/precice/tutorials/pull/677)
254 changes: 254 additions & 0 deletions partitioned-pipe-multiscale/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
---
title: Partitioned Pipe — Geometric Axial Multiscale
keywords: OpenFOAM, Nutils, preCICE, geometric multiscale, fluid
summary: The Partitioned Pipe — Geometric Axial Multiscale tutorial couples a 1D pipe model with a 3D CFD pipe using preCICE.
---

Comment thread
franiqui marked this conversation as resolved.
{% note %}
Get the [case files of this tutorial](https://github.com/precice/tutorials/tree/develop/partitioned-pipe-multiscale), as continuously rendered here, or see the [latest released version](https://github.com/precice/tutorials/tree/master/partitioned-pipe-multiscale) (if there is already one). Read how in the [tutorials introduction](https://precice.org/tutorials.html).
{% endnote %}

## Setup

We solve a simple **partitioned pipe problem** using a 1D–3D coupling approach.
In this tutorial, the computational domain is split into two coupled regions: a 1D pipe section and a 3D pipe section.
The coupling is performed using **preCICE**.

In addition to the 1D–3D setup, this tutorial also includes configurations for **1D–1D** and **3D–3D** coupling.
These variants can be beneficial for validation studies, solver comparisons, or for investigating the influence of model dimensionality.

In the following, $\mathrm{1D}$ denotes the reduced-order domain (e.g., a Nutils solver) and $\mathrm{3D}$ denotes the full 3D CFD domain (e.g., OpenFOAM).

The problem consists of a straight pipe of length $L = 40\,\mathrm{m}$ and diameter $D = 10\,\mathrm{m}$. We partition the domain at $z_c = 20\,\mathrm{m}$, where the coupling interface is located. The pipe axis is aligned with the z-axis.
The **1D domain** solves the flow equations using Nutils, while the **3D domain** is solved using OpenFOAM.
Both solvers are coupled via preCICE by exchanging the **pressure** and **axial velocity** at the interface.

Two coupling directions are possible:

- **1D → 3D**: The 1D solver provides the interface velocity to the 3D solver, which responds with pressure.
- **3D → 1D**: The 3D solver provides the velocity, and the 1D solver returns the pressure.

The global outlet (end of the rightmost domain) is set to $p_{\mathrm{out}} = 0\,\mathrm{Pa}$.
Comment thread
franiqui marked this conversation as resolved.
Outdated

For the **3D → 1D** coupling, the 3D inlet velocity is prescribed as a **parabolic (Poiseuille)** profile with a bulk velocity of $u_{\mathrm{in}} = 0.1\,\mathrm{m/s}$
implemented using a `codedFixedValue` boundary condition. This ensures a physically realistic velocity distribution consistent with the 1D model.

For the **1D → 3D** coupling, the inlet velocity is set to $u_{\mathrm{in}} = 0.1\,\mathrm{m/s}$.

## Configuration

preCICE configuration for the 1D-3D simulation (image generated using the [precice-config-visualizer](https://precice.org/tooling-config-visualization.html)):

![preCICE configuration visualization 1D-3D](images/tutorials-partitioned-pipe-multiscale-1d3d-config.png)

preCICE configuration for the 3D-1D simulation:

![preCICE configuration visualization 3D-1D](images/tutorials-partitioned-pipe-multiscale-3d1d-config.png)

## Available solvers

- OpenFOAM (**pimpleFoam**). An incompressible/transient OpenFOAM solver. See the [OpenFOAM adapter documentation](https://precice.org/adapter-openfoam-overview.html).
- Nutils. A Python-based finite element framework. For more information, see the [Nutils adapter documentation](https://precice.org/adapter-nutils.html)

## Running the Simulation

First, select which coupling you want to run. This sets the correct `precice-config.xml` symlink:

```bash
# Choose one configuration
./setcase.sh 1d3d
# or
./setcase.sh 3d1d
# or
./setcase.sh 1d1d
# or
./setcase.sh 3d3d
```

Open **two terminals** and start the corresponding participants for your chosen setup.

### Example A — 1D → 3D coupling

Terminal 1:

```bash
cd fluid1d-left
Comment thread
franiqui marked this conversation as resolved.
Outdated
./run.sh
```

Terminal 2:

```bash
cd fluid3d-right
./run.sh
```

### Example B — 3D → 1D coupling

Terminal 1:

```bash
cd fluid3d-left
./run.sh
```

Terminal 2:

```bash
cd fluid1d-right
./run.sh
```

> Tip: If you switch coupling direction later, rerun `./setcase.sh` with the other option before launching the participants.

## Visualization

The output of the coupled simulation is written into the folders `fluid1d-left`, `fluid1d-right`, `fluid3d-left`, and `fluid3d-right`, depending on which coupling direction (`1d3d` or `3d1d`) you selected.

### 3D domain (OpenFOAM)

For the 3D participant, all simulation results are stored in the time directories inside the respective case folder (e.g., `fluid3d-right/`).
You can visualize the flow field and pressure distribution using **ParaView** by opening the case file:

```bash
paraview fluid3d-right/fluid3d-right.foam
```

or, for the left domain if applicable:

```bash
paraview fluid3d-left/fluid3d-left.foam
```

Typical fields to inspect include:

- `p` – pressure
- `U` – velocity

We also record pressure and velocity at fixed points each time step using the OpenFOAM `probes` function object.

**Probe setup (excerpt):**

```c
#includeEtc "caseDicts/postProcessing/probes/probes.cfg"

fields (p U);
probeLocations
(
(0 0 20)
(0 0 40)
);
// For the left 3D domain use instead:
// probeLocations ((0 0 0) (0 0 20));
```

**Output location:**

- `fluid3d-right/postProcessing/probes/0/p`
- `fluid3d-right/postProcessing/probes/0/U`

In addition to point probes, the 3D participant samples the **axial pressure distribution** along the pipe centerline using `sampleDict`. This provides the spatial pressure variation along the pipe and allows direct comparison with the 1D solution at the latest time step.

The tutorial includes a `sampleDict` in the 3D cases:

- `fluid3d-left/system/sampleDict`
- `fluid3d-right/system/sampleDict`

**sampleDict (excerpt):**

```cpp
FoamFile
{
version 2.0;
format ascii;
class dictionary;
location "system";
object sampleDict;
}

type sets;
libs ("libsampling.so");

setFormat raw;

sets
(
centerline
{
type uniform;
axis z;
start (0 0 0);
end (0 0 20);
nPoints 200;
}
);

fields (p);
```

The sampling is executed automatically during the run.

**Output location:**

```text
postProcessing/sampleDict/<latestTime>/centerline_p.xy
```

The file contains two columns:

1. axial coordinate `z`
2. pressure `p`

### 1D domain (Nutils)

The 1D solver writes a `watchpoint.txt` with semicolon-separated time series:
Comment thread
franiqui marked this conversation as resolved.
Outdated

```text
time; p_in; u_in; p_out; u_out; p_mid; u_mid
```

where:

- `p_in`, `u_in` → pressure and velocity at the inlet of the 1D domain
- `p_out`, `u_out` → pressure and velocity at the outlet of the 1D domain
- `p_mid`, `u_mid` → pressure and velocity at the midpoint of the 1D domain

The 1D solver also writes a `final_fields.txt` with space-separated values:

```text
x u p
```

They correspond to the axial position, velocity and pressure at the last time-step, i.e., at \(t = 5\,\mathrm{s}\).

### Plotting axial pressure distribution (optional)

To reproduce the axial pressure distribution shown in the figure below, a helper script is provided:

```bash
cd visualization-scripts
python plot-pressure-distribution.py
```

The script reads the centerline sampling data from the 3D participant (`centerline_p.xy`) and the `final_fields.txt` output from the 1D solver, and combines them to plot the pressure variation along the coupled pipe.

By default, the figures are saved to:

```text
images/pressure_distribution_*.png
```

You can select which coupling configurations to plot by editing the `PLOT_CASES` variable inside the script.

The script assumes that the simulation has been executed and that the sampling and solver output files are available.

### Example visualization

![Pressure distribution along the main axis in the 3D-1D Coupled Pipe](images/tutorials-partitioned-pipe-multiscale-3d1d-pressure-distribution.png)

**Pressure along the pipe centerline.** The pressure decreases nearly linearly from **≈12.8 Pa** at the 3D inlet to **0 Pa** at the 1D outlet, consistent with steady, laminar Poiseuille flow. The 3D (0–20 m) and 1D (20–40 m) sections connect smoothly at the coupling interface.

![Velocity at the 3D coupling interface in the 3D-1D Coupled Pipe](images/tutorials-partitioned-pipe-multiscale-3d1d-velocityProfileInterface3d.png)

**Parabolic velocity profile at the 3D outlet / coupling interface (z = 20 m).**
The profile is Poiseuille-like with a bulk velocity of **0.1 m/s**; consequently the **centerline velocity is ≈ 0.2 m/s** (≈ 2 × bulk) and vanishes at the wall (no-slip). This is the velocity state at the interface used for coupling to the 1D domain.
11 changes: 11 additions & 0 deletions partitioned-pipe-multiscale/clean-tutorial.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env sh
set -e -u

# shellcheck disable=SC1091
. ../tools/cleaning-tools.sh

clean_tutorial .
clean_precice_logs .
rm -fv ./*.log
rm -fv ./*.vtu

8 changes: 8 additions & 0 deletions partitioned-pipe-multiscale/fluid1d-left/clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env sh
set -e -u

. ../../tools/cleaning-tools.sh

rm -f ./results/Fluid1D_*
clean_precice_logs .
clean_case_logs .
5 changes: 5 additions & 0 deletions partitioned-pipe-multiscale/fluid1d-left/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
setuptools # required by nutils
nutils==7
numpy >1, <2
pyprecice~=3.0
matplotlib
13 changes: 13 additions & 0 deletions partitioned-pipe-multiscale/fluid1d-left/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e -u

. ../../tools/log.sh
exec > >(tee --append "$LOGFILE") 2>&1

python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt && pip freeze > pip-installed-packages.log

NUTILS_RICHOUTPUT=no python3 ../solver-fluid1d/Fluid1D.py side=Left

close_log
8 changes: 8 additions & 0 deletions partitioned-pipe-multiscale/fluid1d-right/clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env sh
set -e -u

. ../../tools/cleaning-tools.sh

rm -f ./results/Fluid1D_*
clean_precice_logs .
clean_case_logs .
5 changes: 5 additions & 0 deletions partitioned-pipe-multiscale/fluid1d-right/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
setuptools # required by nutils
nutils==7
numpy >1, <2
pyprecice~=3.0
matplotlib
13 changes: 13 additions & 0 deletions partitioned-pipe-multiscale/fluid1d-right/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e -u

. ../../tools/log.sh
exec > >(tee --append "$LOGFILE") 2>&1

python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt && pip freeze > pip-installed-packages.log

NUTILS_RICHOUTPUT=no python3 ../solver-fluid1d/Fluid1D.py side=Right

close_log
61 changes: 61 additions & 0 deletions partitioned-pipe-multiscale/fluid3d-left/0/U
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
FoamFile
{
version 2.0;
format ascii;
class volVectorField;
location "0";
object U;
}

dimensions [0 1 -1 0 0 0 0];

internalField uniform (0 0 0);

boundaryField
{
inlet
{
type codedFixedValue;
value uniform (0 0 0); // dummy fallback
name parabolicInlet;

code
#{
// User parameters — edit these:
const scalar Uinlet = 0.1; // m/s (set this)
const scalar R = 5.0; // m (pipe radius)
const vector axis = vector(0,0,1); // flow axis (unit!)
const vector ctr = vector(0,0,0); // inlet center (global coords)

// Ensure axis is unit length
const vector e = axis/mag(axis);

// Face centres on this patch
const vectorField& Cf = patch().Cf();

// Reference to the patch field to write into
vectorField& Up = *this;

forAll(Cf, i)
{
const vector x = Cf[i];
const vector rvec = x - ctr;
// radial component: subtract axial projection
const vector rperp = rvec - (rvec & e)*e;
const scalar r = mag(rperp);

scalar u = 2 * Uinlet * max(0.0, 1.0 - sqr(r/R)); // clip outside R
Up[i] = u * e;
}
#};
}
outlet
{
type fixedGradient;
gradient uniform (0 0 0);
}
fixedWalls
{
type noSlip;
}
}
Loading