Skip to content
Open
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ build/
.venv/
__pycache__/
*.pyc
*.egg-info

# Rust
Cargo.lock
Expand Down
18 changes: 18 additions & 0 deletions growing-mesh/A/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/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 ../solver-python
pip freeze ../solver-python/ > ../solver-python/pip-installed-packages.log

if [ $# -eq 0 ]; then
growing A
else
mpirun -n "$@" growing A
fi

close_log
18 changes: 18 additions & 0 deletions growing-mesh/B/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/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 ../solver-python
pip freeze ../solver-python/ > ../solver-python/pip-installed-packages.log

if [ $# -eq 0 ]; then
growing B
else
mpirun -n "$@" growing B
fi

close_log
56 changes: 56 additions & 0 deletions growing-mesh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: Growing Mesh
permalink: tutorials-growing-mesh.html
keywords: python, remeshing
summary: The growing mesh case is a showcase example of two solvers which grow their mesh at predefined points in time.
---

{% note %}
Get the [case files of this tutorial](https://github.com/precice/tutorials/tree/master/growing-mesh). Read how in the [tutorials introduction](https://precice.org/tutorials.html).
{% endnote %}

## Motivation

This case is an extremely simplified version of the scenario presented by Jean-Marc Gratien during Coupled 2023 *Implementing a Comprehensive Hydromechanical Model for Sedimentary Basins by Coupling a 3D Mechanical Code to a Classic Basin Fluid Flow Code with the PreCICE Library*.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the title case (with PreCICE) here? 😬


In the original scenario a hydrogeological and a geomechanical model are coupled via preCICE using an implicit coupling schemes.
The scheme iterates until both model agree on effective stress.
At fixed points in time, geological events deposit new layers onto the mesh, effectively growing the mesh into z-direction.

This tutorial aims to implement such a growing mesh scenario without the implicit coupling.

## Setup

The problem consists of a unit-square uniformly discretized by 512 x 512 nodes.
The unit square is partitioned among the ranks of a solver according to an n x m grid, where 512 must be divisible by n and m.
The mesh starts with 2 nodes in z direction and at a given frequency, 2 nodes are added to the mesh, changing only the load per rank, not the partitioning.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original was a bit difficult for me to read, maybe this is better:

Suggested change
The mesh starts with 2 nodes in z direction and at a given frequency, 2 nodes are added to the mesh, changing only the load per rank, not the partitioning.
The mesh starts with two nodes in z direction. In regular intervals, two nodes are added to the mesh, changing only the load per rank, but not the partitioning.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"A and B" are mentioned below, but not introduced. Compromise:

Suggested change
While motivated by a setup with a hydrogeological and a geomechanical model, we name these participants A and B, as this tutorial only aims to demonstrate the any such growing mesh case scenario.

## Configuration

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

![preCICE configuration visualization](images/tutorials-growing-mesh-precice-config.png)

## Available solvers

There is a generic python solver that can be used for either A or B.

The runs scripts in participant folders `A` and `B` accept the amount of ranks as an optional parameter.

## Running the Simulation

Pass the amount of ranks to the run script of the solvers.
Not passing a number, runs the simulation on a single rank.
To run both on a two rank each, use:

```bash
cd A
./run.sh 2
```

and

```bash
cd B
./run.sh 2
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A post-processing section would be nice, hinting to what the user should see in the end. Note that there is no other figure (besides the config visualization) in this tutorial. Someone that does not yet know what "growing mesh" means would need to read and imagine a bit first, before being able to get a first overview.

An image with the mesh in two different times (potentially with partitions shown) would be helpful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this case even produce any results to show how the mesh grows?

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions growing-mesh/precice-config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" ?>
<precice-configuration experimental="True" allow-remeshing="True">
<log>
<sink
filter="%Severity% >= debug and %Rank% = 0"
format="---[precice] %ColorizedSeverity% %Message%"
enabled="true" />
</log>

<profiling mode="all" />

<data:scalar name="Data-A" />
<data:scalar name="Data-B" />

<mesh name="A-Mesh" dimensions="3">
<use-data name="Data-A" />
<use-data name="Data-B" />
</mesh>

<mesh name="B-Mesh" dimensions="3">
<use-data name="Data-A" />
<use-data name="Data-B" />
</mesh>

<participant name="A">
<provide-mesh name="A-Mesh" />
<write-data name="Data-A" mesh="A-Mesh" />
<read-data name="Data-B" mesh="A-Mesh" />
<receive-mesh name="B-Mesh" from="B" />
<mapping:nearest-neighbor direction="read" from="B-Mesh" to="A-Mesh" constraint="consistent" />
</participant>

<participant name="B">
<provide-mesh name="B-Mesh" />
<read-data name="Data-A" mesh="B-Mesh" />
<write-data name="Data-B" mesh="B-Mesh" />
<receive-mesh name="A-Mesh" from="A" />
<mapping:nearest-neighbor direction="read" from="A-Mesh" to="B-Mesh" constraint="consistent" />
</participant>

<m2n:sockets acceptor="A" connector="B" exchange-directory=".." />

<coupling-scheme:parallel-explicit>
<time-window-size value="1" />
<max-time-windows value="12" />
<participants first="A" second="B" />
<exchange data="Data-A" mesh="A-Mesh" from="A" to="B" />
<exchange data="Data-B" mesh="B-Mesh" from="B" to="A" />
</coupling-scheme:parallel-explicit>
</precice-configuration>
11 changes: 11 additions & 0 deletions growing-mesh/solver-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[project]
name = "growing"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we give a more descriptive name?
Not necessary, but If a user were to install this outside the venv, a growing binary lying around would be quite strange.

One direction could be growing-mesh-solver-python.

version = "0"
dependencies = [
"numpy",
"pyprecice @ git+https://github.com/precice/python-bindings.git@develop",
"mpi4py"
]

[project.scripts]
growing = "solver:main"
137 changes: 137 additions & 0 deletions growing-mesh/solver-python/solver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/bin/python3

import precice
import numpy as np
import math
import sys
from mpi4py import MPI

import argparse


def split(num):
for a in range(math.isqrt(num), 0, -1):
if num % a == 0:
return a, num // a


def main():
parser = argparse.ArgumentParser()
parser.add_argument("participant", choices=["A", "B"])
parser.add_argument("--config", "-c", default="../precice-config.xml")
parser.add_argument("--no-remesh", dest="remesh", action="store_false")
parser.add_argument("--nx", type=int, default=512)
parser.add_argument("--ny", type=int, default=512)
args = parser.parse_args()

participant_name = args.participant
remote_name = "A" if participant_name == "B" else "B"

# x is partitioned per rank and doesn't change
nx = args.nx
ny = args.ny
x = 0.0, 1.0
y = 0.0, 1.0

# y grows over time
newNodesPerEvent = 2
eventFrequency = 3 # time windows
dz = 0.1

# Handle partitioning
world = MPI.COMM_WORLD
size: int = world.size
rank: int = world.rank
xr, yr = split(size)

assert nx % xr == 0, f"Cannot split {nx=} by {xr=} ranks"
assert ny % yr == 0, f"Cannot split {ny=} by {yr=} ranks"

pnx = nx // xr
pny = ny // yr

px = rank % xr
py = rank // xr
print(f"Rank {rank}/{size} has partition ({px}, {py})/({xr}, {yr})")
if rank == 0:
print(
f"Each of {size} partitions has node size {pnx}x{pny} = {pnx * pny}"
"for a total of {nx * ny} nodes on the base"
)

def getMesh(nz):
basex = np.linspace(0, 1, nx)[px * pnx: (px + 1) * pnx]
basey = np.linspace(0, 1, ny)[py * pny: (py + 1) * pny]
z = np.array(range(nz)) * dz
return np.stack(np.meshgrid(basex, basey, z, indexing="ij"), axis=-1).reshape(
-1, 3
)

def requiresEvent(tw):
return tw % eventFrequency == 0

assert not requiresEvent(eventFrequency - 1)
assert requiresEvent(eventFrequency)
assert not requiresEvent(eventFrequency + 1)

def eventsAt(tw):
# First event block at tw=0, second at eventFrequency
return 1 + math.floor(tw / eventFrequency)

assert eventsAt(0) == 1
assert eventsAt(eventFrequency - 1) == 1
assert eventsAt(eventFrequency) == 2
assert eventsAt(eventFrequency + 1) == 2

def getMeshAtTimeWindow(tw):
znodes = eventsAt(tw) * newNodesPerEvent
return getMesh(znodes)

def dataAtTimeWindow(tw):
znodes = eventsAt(tw) * newNodesPerEvent
total = pnx * pny * znodes
return np.full(total, tw)

participant = precice.Participant(participant_name, args.config, rank, size)

mesh_name = participant_name + "-Mesh"
read_data_name = "Data-" + remote_name
write_data_name = "Data-" + participant_name

coords = getMeshAtTimeWindow(0)
vertex_ids = participant.set_mesh_vertices(mesh_name, coords)
participant.initialize()

tw = 1
while participant.is_coupling_ongoing():
dt = participant.get_max_time_step_size()

data = participant.read_data(mesh_name, read_data_name, vertex_ids, dt)
if rank == 0:
print(f"{participant_name}: data: {data[0]} * {len(data)}")

if args.remesh and requiresEvent(tw):
oldCount = len(coords)
coords = getMeshAtTimeWindow(tw)
if rank == 0:
print(
f"{participant_name}: Event grows local mesh from {oldCount} to {len(coords)}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed the formatting of these strings (here and in line 58). Cross-check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, while the solver messages show up in the correct order when running with two ranks, they show up separately at the end when running serially:

--[precice]  time-window 11 (max: 12), t 10, Dt 1, max-dt 1
---[precice]  Time window completed
---[precice]  Mapping "Data-B" for t=11 from "B-Mesh" to "A-Mesh"
---[precice]  time-window 12 (max: 12), t 11, Dt 1, max-dt 1
---[precice]  Reinitializing Participant
---[precice]  Close distributed communication channels
---[precice]  Setting up primary communication to coupling partner/s
---[precice]  Primary ranks are connected
---[precice]  Setting up preliminary secondary communication to coupling partner/s
---[precice]  Prepare partition for mesh A-Mesh
---[precice]  Gather mesh A-Mesh
---[precice]  Send global mesh A-Mesh
---[precice]  Receive global mesh B-Mesh
---[precice]  Setting up secondary communication to coupling partner/s
---[precice]  Secondary ranks are connected
---[precice]  Time window completed
---[precice]  Computing "nearest-neighbor" mapping from mesh "B-Mesh" to mesh "A-Mesh" in "read" direction.
---[precice]  Mapping distance min:0 max:0 avg: 0 var: 0 cnt: 2621440
---[precice]  Mapping "Data-B" for t=12 from "B-Mesh" to "A-Mesh"
---[precice]  Reached end at: final time-window: 12, final time: 12
---[precice]  Implicitly finalizing in destructor
---[precice]  Close communication channels
Rank 0/1 has partition (0, 0)/(1, 1)
Each of 1 partitions has node size 512x512 = 262144 for a total of 262144 nodes on the base
A: data: 0.0 * 524288
A: data: 1.0 * 524288
A: data: 2.0 * 524288
A: Event grows local mesh from 524288 to 1048576 and global mesh from 524288 to 1048576
A: data: 3.0 * 1048576
A: data: 4.0 * 1048576
A: data: 5.0 * 1048576
A: Event grows local mesh from 1048576 to 1572864 and global mesh from 1048576 to 1572864
A: data: 6.0 * 1572864
A: data: 7.0 * 1572864
A: data: 8.0 * 1572864
A: Event grows local mesh from 1572864 to 2097152 and global mesh from 1572864 to 2097152
A: data: 9.0 * 2097152
A: data: 10.0 * 2097152
A: data: 11.0 * 2097152
A: Event grows local mesh from 2097152 to 2621440 and global mesh from 2097152 to 2621440

"and global mesh from {oldCount * size} to {len(coords) * size}"
)
participant.reset_mesh(mesh_name)
vertex_ids = participant.set_mesh_vertices(mesh_name, coords)

participant.write_data(
mesh_name, write_data_name, vertex_ids, dataAtTimeWindow(tw)
)

participant.advance(dt)
tw += 1


if __name__ == "__main__":
try:
main()
except Exception as e:
print(e)
sys.exit(1)