Skip to content

Commit dcbe41c

Browse files
committed
Added growing mesh case
1 parent fd64e0c commit dcbe41c

File tree

8 files changed

+267
-0
lines changed

8 files changed

+267
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ build/
3333
.venv/
3434
__pycache__/
3535
*.pyc
36+
*.egg-info
3637

3738
# Rust
3839
Cargo.lock

growing-mesh/A/run.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/sh
2+
set -e -u
3+
4+
python3 -m venv .venv
5+
. .venv/bin/activate
6+
pip install ../solver-python
7+
8+
if [ $# -eq 0 ]; then
9+
growing A
10+
else
11+
mpirun -n "$@" growing A
12+
fi

growing-mesh/B/run.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/sh
2+
set -e -u
3+
4+
python3 -m venv .venv
5+
. .venv/bin/activate
6+
pip install ../solver-python
7+
8+
if [ $# -eq 0 ]; then
9+
growing B
10+
else
11+
mpirun -n "$@" growing B
12+
fi

growing-mesh/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: Growing Mesh
3+
permalink: tutorials-growing-mesh.html
4+
keywords: python, remeshing
5+
summary: The growing mesh case is a showcase example of two solvers which grow their mesh at predefined points in time.
6+
---
7+
8+
{% note %}
9+
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).
10+
{% endnote %}
11+
12+
## Setup
13+
14+
The problem consists of a unit-square uniformly discretized by 768 x 768 nodes.
15+
Running in parallel is only allowed for 1, 4, 9, or 16 ranks.
16+
The unit square is partitioned equally among the ranks of a solver.
17+
18+
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.
19+
20+
## Configuration
21+
22+
preCICE configuration (image generated using the [precice-config-visualizer](https://precice.org/tooling-config-visualization.html)):
23+
24+
![preCICE configuration visualization](images/tutorials-growing-mesh-precice-config.png)
25+
26+
## Available solvers
27+
28+
There are two solvers that define the same mesh:
29+
30+
- A who runs first
31+
- B who runs second
32+
33+
## Running the Simulation
34+
35+
Pass the amount of ranks to the run script of the solvers.
36+
Not passing a number, runs the simulation on a single rank.
37+
To run both on a two rank each, use:
38+
39+
```bash
40+
cd A
41+
./run.sh 2
42+
```
43+
44+
and
45+
46+
```bash
47+
cd B
48+
./run.sh 2
49+
```
76.5 KB
Loading

growing-mesh/precice-config.xml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<precice-configuration experimental="True" allow-remeshing="True">
3+
<log>
4+
<sink
5+
filter="%Severity% >= debug and %Rank% = 0"
6+
format="---[precice] %ColorizedSeverity% %Message%"
7+
enabled="true" />
8+
</log>
9+
10+
<profiling mode="all" />
11+
12+
<data:scalar name="Data-A" />
13+
<data:scalar name="Data-B" />
14+
15+
<mesh name="A-Mesh" dimensions="3">
16+
<use-data name="Data-A" />
17+
<use-data name="Data-B" />
18+
</mesh>
19+
20+
<mesh name="B-Mesh" dimensions="3">
21+
<use-data name="Data-A" />
22+
<use-data name="Data-B" />
23+
</mesh>
24+
25+
<participant name="A">
26+
<provide-mesh name="A-Mesh" />
27+
<write-data name="Data-A" mesh="A-Mesh" />
28+
<read-data name="Data-B" mesh="A-Mesh" />
29+
<receive-mesh name="B-Mesh" from="B" />
30+
<mapping:nearest-neighbor direction="read" from="B-Mesh" to="A-Mesh" constraint="consistent" />
31+
</participant>
32+
33+
<participant name="B">
34+
<provide-mesh name="B-Mesh" />
35+
<read-data name="Data-A" mesh="B-Mesh" />
36+
<write-data name="Data-B" mesh="B-Mesh" />
37+
<receive-mesh name="A-Mesh" from="A" />
38+
<mapping:nearest-neighbor direction="read" from="A-Mesh" to="B-Mesh" constraint="consistent" />
39+
</participant>
40+
41+
<m2n:sockets acceptor="A" connector="B" exchange-directory=".." />
42+
43+
<coupling-scheme:parallel-explicit>
44+
<time-window-size value="1" />
45+
<max-time-windows value="12" />
46+
<participants first="A" second="B" />
47+
<exchange data="Data-A" mesh="A-Mesh" from="A" to="B" />
48+
<exchange data="Data-B" mesh="B-Mesh" from="B" to="A" />
49+
</coupling-scheme:parallel-explicit>
50+
</precice-configuration>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[project]
2+
name = "growing"
3+
version = "0"
4+
dependencies = [
5+
"numpy",
6+
"pyprecice @ git+https://github.com/precice/python-bindings.git@develop",
7+
"mpi4py"
8+
]
9+
10+
[project.scripts]
11+
growing = "solver:main"
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/bin/python3
2+
3+
import precice
4+
import numpy as np
5+
import math
6+
import sys
7+
from mpi4py import MPI
8+
9+
import argparse
10+
11+
def split(num):
12+
for a in range(math.isqrt(num), 0, -1):
13+
if num % a == 0:
14+
return a, num // a
15+
16+
17+
def main():
18+
parser = argparse.ArgumentParser()
19+
parser.add_argument("participant", choices=["A", "B"])
20+
parser.add_argument("--config", "-c", default="../precice-config.xml")
21+
parser.add_argument("--no-remesh", dest="remesh", action="store_false")
22+
parser.add_argument("--nx", type=int, default=512)
23+
parser.add_argument("--ny", type=int, default=512)
24+
args = parser.parse_args()
25+
26+
participant_name = args.participant
27+
remote_name = "A" if participant_name == "B" else "B"
28+
29+
# x is partitioned per rank and doesn't change
30+
nx = args.nx
31+
ny = args.ny
32+
x = 0.0, 1.0
33+
y = 0.0, 1.0
34+
35+
# y grows over time
36+
newNodesPerEvent = 2
37+
eventFrequency = 3 # time windows
38+
dz = 0.1
39+
40+
# Handle partitioning
41+
world = MPI.COMM_WORLD
42+
size: int = world.size
43+
rank: int = world.rank
44+
xr, yr = split(size)
45+
46+
assert nx % xr == 0, f"Cannot split {nx=} by {xr=} ranks"
47+
assert ny % yr == 0, f"Cannot split {ny=} by {yr=} ranks"
48+
49+
pnx = nx // xr
50+
pny = ny // yr
51+
52+
px = rank % xr
53+
py = rank // xr
54+
print(f"Rank {rank}/{size} has partition ({px}, {py})/({xr}, {yr})")
55+
if rank == 0:
56+
print(f"Each of {size} partitions has node size {pnx}x{pny} = {pnx*pny} for a total of {nx*ny} nodes on the base")
57+
58+
59+
def getMesh(nz):
60+
basex = np.linspace(0, 1, nx)[px*pnx:(px+1)*pnx]
61+
basey = np.linspace(0, 1, ny)[py*pny:(py+1)*pny]
62+
z = np.array(range(nz)) * dz
63+
return np.stack(np.meshgrid(basex, basey, z, indexing="ij"), axis=-1).reshape(-1, 3)
64+
65+
def requiresEvent(tw):
66+
return tw % eventFrequency == 0
67+
68+
assert not requiresEvent(eventFrequency - 1)
69+
assert requiresEvent(eventFrequency)
70+
assert not requiresEvent(eventFrequency + 1)
71+
72+
def eventsAt(tw):
73+
# First event block at tw=0, second at eventFrequency
74+
return 1 + math.floor(tw / eventFrequency)
75+
76+
assert eventsAt(0) == 1
77+
assert eventsAt(eventFrequency - 1) == 1
78+
assert eventsAt(eventFrequency) == 2
79+
assert eventsAt(eventFrequency + 1) == 2
80+
81+
def getMeshAtTimeWindow(tw):
82+
znodes = eventsAt(tw) * newNodesPerEvent
83+
return getMesh(znodes)
84+
85+
def dataAtTimeWindow(tw):
86+
znodes = eventsAt(tw) * newNodesPerEvent
87+
total = pnx * pny * znodes
88+
return np.full(total, tw)
89+
90+
participant = precice.Participant(participant_name, args.config, rank, size)
91+
92+
mesh_name = participant_name + "-Mesh"
93+
read_data_name = "Data-" + remote_name
94+
write_data_name = "Data-" + participant_name
95+
96+
coords = getMeshAtTimeWindow(0)
97+
vertex_ids = participant.set_mesh_vertices(mesh_name, coords)
98+
participant.initialize()
99+
100+
tw = 1
101+
while participant.is_coupling_ongoing():
102+
dt = participant.get_max_time_step_size()
103+
104+
data = participant.read_data(mesh_name, read_data_name, vertex_ids, dt)
105+
if rank == 0:
106+
print(f"{participant_name}: data: {data[0]} * {len(data)}")
107+
108+
if args.remesh and requiresEvent(tw):
109+
oldCount = len(coords)
110+
coords = getMeshAtTimeWindow(tw)
111+
if rank == 0:
112+
print(
113+
f"{participant_name}: Event grows local mesh from {oldCount} to {
114+
len(coords)} and global mesh from {
115+
oldCount *
116+
size} to {
117+
len(coords) *
118+
size}")
119+
participant.reset_mesh(mesh_name)
120+
vertex_ids = participant.set_mesh_vertices(mesh_name, coords)
121+
122+
participant.write_data(mesh_name, write_data_name, vertex_ids, dataAtTimeWindow(tw))
123+
124+
participant.advance(dt)
125+
tw += 1
126+
127+
if __name__ == "__main__":
128+
try:
129+
main()
130+
except Exception as e:
131+
print(e)
132+
sys.exit(1)

0 commit comments

Comments
 (0)