Skip to content

Commit 312b670

Browse files
committed
[meshMotion] cylinderMotion initial setup
1 parent be0da51 commit 312b670

File tree

17 files changed

+870
-95
lines changed

17 files changed

+870
-95
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/sh
2+
cd "${0%/*}" || exit # Run from this directory
3+
. ${WM_PROJECT_DIR:?}/bin/tools/CleanFunctions # Tutorial clean functions
4+
#------------------------------------------------------------------------------
5+
6+
rm -rf cylinderMotionExperiment
7+
rm -f *.out *.err log*.* *.dat
8+
9+
#------------------------------------------------------------------------------
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
cd "${0%/*}" || exit # Run from this directory
3+
. ${WM_PROJECT_DIR:?}/bin/tools/CleanFunctions # Tutorial clean functions
4+
#------------------------------------------------------------------------------
5+
6+
python cylinder-motion.py
7+
8+
#------------------------------------------------------------------------------
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
#!/usr/bin/env python
2+
3+
# Parsing OpenFOAM configuration files
4+
from PyFoam.RunDictionary.ParsedParameterFile import ParsedParameterFile
5+
import os
6+
import sys
7+
import pandas as pd
8+
9+
# SmartSim
10+
from smartsim import Experiment
11+
from smartredis import Client
12+
13+
from matplotlib import pyplot as plt
14+
from matplotlib import rcParams
15+
rcParams["figure.dpi"] = 200
16+
17+
import torch
18+
import torch.nn as nn
19+
import numpy as np
20+
import io
21+
from sklearn.model_selection import train_test_split
22+
import torch.optim as optim
23+
24+
from sklearn.metrics import mean_squared_error
25+
26+
# For calling pre-processing scripts
27+
import subprocess
28+
29+
class MLP(nn.Module):
30+
def __init__(self, num_layers, layer_width, input_size, output_size, activation_fn):
31+
super(MLP, self).__init__()
32+
33+
layers = []
34+
layers.append(nn.Linear(input_size, layer_width))
35+
layers.append(activation_fn)
36+
37+
for _ in range(num_layers - 2):
38+
layers.append(nn.Linear(layer_width, layer_width))
39+
layers.append(activation_fn)
40+
41+
layers.append(nn.Linear(layer_width, output_size))
42+
self.layers = nn.Sequential(*layers)
43+
44+
def forward(self, x):
45+
return self.layers(x)
46+
47+
def sort_tensors_by_names(tensors, tensor_names):
48+
# Pair each tensor with its name and sort by the name
49+
pairs = sorted(zip(tensor_names, tensors))
50+
51+
# Extract the sorted tensors
52+
tensor_names_sorted, tensors_sorted = zip(*pairs)
53+
54+
# Convert back to list if needed
55+
tensor_names_sorted = list(tensor_names_sorted)
56+
tensors_sorted = list(tensors_sorted)
57+
58+
return tensors_sorted, tensor_names_sorted
59+
60+
def visualization_points(n_points):
61+
62+
domain_min = [-3, -3, 0]
63+
domain_max = [3, 3, 0]
64+
radius = 1
65+
66+
# Generate grid of points
67+
x = np.linspace(domain_min[0], domain_max[0], n_points)
68+
y = np.linspace(domain_min[1], domain_max[1], n_points)
69+
xx, yy = np.meshgrid(x, y)
70+
grid_points = np.column_stack((xx.ravel(), yy.ravel(), np.zeros(n_points**2)))
71+
72+
# Filter out points within the circle
73+
norm = np.linalg.norm(grid_points[:, :2], axis=1)
74+
visualization_points = grid_points[norm > radius]
75+
76+
return visualization_points
77+
78+
79+
exp = Experiment("cylinderMotionExperiment", launcher="local")
80+
81+
db = exp.create_database(port=8000, # database port
82+
interface="lo") # network interface to use
83+
exp.start(db)
84+
85+
# Connect the python client to the smartredis database
86+
client = Client(address=db.get_address()[0], cluster=False)
87+
88+
num_mpi_ranks = 4
89+
90+
of_rs = exp.create_run_settings(exe="moveDynamicMesh", exe_args="-case cylinder -parallel",
91+
run_command="mpirun",
92+
run_args={"np": f"{num_mpi_ranks}"})
93+
94+
of_model = exp.create_model(name="of_model", run_settings=of_rs)
95+
96+
try:
97+
# Pre-process: clean existing data in cylinder.
98+
res_allrun_clean = subprocess.call(['bash', 'cylinder/Allclean'])
99+
print(f'Allrun.pre in cylinder executed with return code: {res_allrun_clean}')
100+
# Pre-process: create a mesh and decompose the solution domain of cylinder
101+
# - Pre-processing does not interact with ML, so SmartSim models are not used.
102+
res_allrun_pre = subprocess.call(['bash', 'cylinder/Allrun.pre'])
103+
print(f'Allrun.pre in cylinder executed with return code: {res_allrun_pre}')
104+
105+
# Run the experiment
106+
exp.start(of_model, block=False)
107+
108+
torch.set_default_dtype(torch.float64)
109+
110+
# Initialize the model
111+
model = MLP(num_layers=3, layer_width=50, input_size=2, output_size=2, activation_fn=torch.nn.ReLU())
112+
113+
# Make sure all datasets are avaialble in the smartredis database.
114+
local_time_index = 1
115+
while True:
116+
117+
print (f"Time step {local_time_index}")
118+
119+
# Fetch datasets from SmartRedis
120+
121+
# - Poll until the points datasets are written by OpenFOAM
122+
# print (f"dataset_list_length {dataset_list_length}") # Debug info
123+
points_updated = client.poll_list_length("pointsDatasetList",
124+
num_mpi_ranks, 10, 1000);
125+
if (not points_updated):
126+
raise ValueError("Points dataset list not updated.")
127+
128+
# - Poll until the displacements datasets are written by OpenFOAM
129+
# print (f"dataset_list_length {dataset_list_length}") # Debug info
130+
displacements_updated = client.poll_list_length("displacementsDatasetList",
131+
num_mpi_ranks, 10, 1000);
132+
if (not displacements_updated):
133+
raise ValueError("Displacements dataset list not updated.")
134+
135+
# - Get the points and displacements datasets from SmartRedis
136+
points_datasets = client.get_datasets_from_list("pointsDatasetList")
137+
displacements_datasets = client.get_datasets_from_list("displacementsDatasetList")
138+
139+
# - Agglomerate all tensors from points and displacements datasets:
140+
# sort tensors by their names to ensure matching patches of same MPI ranks
141+
points = []
142+
points_names = []
143+
displacements = []
144+
displacements_names = []
145+
146+
# Agglomerate boudary points and displacements for training.
147+
# TODO(TM): for mesh motion, send points_MPI_r, displacements_MPI_r and
148+
# train the MLP directly on the tensors, there is no need to
149+
# differentiate the BCs, as values are used for the training.
150+
for points_dset, displs_dset in zip(points_datasets, displacements_datasets):
151+
points_tensor_names = points_dset.get_tensor_names()
152+
displs_tensor_names = displs_dset.get_tensor_names()
153+
for points_name,displs_name in zip(points_tensor_names,displs_tensor_names):
154+
patch_points = points_dset.get_tensor(points_name)
155+
points.append(patch_points)
156+
points_names.append(points_name)
157+
158+
patch_displs = displs_dset.get_tensor(displs_name)
159+
displacements.append(patch_displs)
160+
displacements_names.append(displs_name)
161+
162+
points, points_names = sort_tensors_by_names(points, points_names)
163+
displacements, displacements_names = sort_tensors_by_names(displacements, displacements_names)
164+
165+
# - Reshape points and displacements into [N_POINTS,SPATIAL_DIMENSION] tensors
166+
# This basically agglomerates data from OpenFOAM boundary patches into a list
167+
# of boundary points (unstructured) and a list of respective point displacements.
168+
points = torch.from_numpy(np.vstack(points))
169+
displacements = torch.from_numpy(np.vstack(displacements))
170+
171+
# TODO(TM): hardcoded x,y coordinates, make the OF client store polymesh::solutionD
172+
# and use solutionD non-zero values for sampling vector coordinates.
173+
points = points[:, :2]
174+
displacements = displacements[:, :2]
175+
176+
# Split training and validation data
177+
points_train, points_val, displ_train, displ_val = train_test_split(points, displacements,
178+
test_size=0.2, random_state=42)
179+
180+
# PYTORCH Training Loop
181+
optimizer = optim.Adam(model.parameters(), lr=1e-04)
182+
loss_func = nn.MSELoss()
183+
epochs = 10000
184+
mean_mag_displ = torch.mean(torch.norm(displ_train, dim=1))
185+
validation_rmse = []
186+
model.train()
187+
for epoch in range(epochs):
188+
# Zero the gradients
189+
optimizer.zero_grad()
190+
191+
# Forward pass on the training data
192+
displ_pred = model(points_train)
193+
194+
# Compute loss on the training data
195+
loss_train = loss_func(displ_pred, displ_train)
196+
197+
# Backward pass and optimization
198+
loss_train.backward()
199+
optimizer.step()
200+
201+
# Forward pass on the validation data, with torch.no_grad() for efficiency
202+
with torch.no_grad():
203+
displ_pred_val = model(points_val)
204+
mse_loss_val = loss_func(displ_pred_val, displ_val)
205+
rmse_loss_val = torch.sqrt(mse_loss_val)
206+
validation_rmse.append(rmse_loss_val)
207+
208+
# Visualize validation RMSE
209+
#plt.loglog()
210+
#plt.title("Validation loss RMSE")
211+
#plt.xlabel("Epochs")
212+
#plt.plot(validation_rmse)
213+
#plt.show()
214+
215+
# Store the model into SmartRedis
216+
model.eval() # TEST
217+
# Prepare a sample input
218+
example_forward_input = torch.rand(2)
219+
# Convert the PyTorch model to TorchScript
220+
model_script = torch.jit.trace(model, example_forward_input)
221+
# Save the TorchScript model to a buffer
222+
model_buffer = io.BytesIO()
223+
torch.jit.save(model_script, model_buffer)
224+
# Set the model in the SmartRedis database
225+
print("Saving model MLP")
226+
client.set_model("MLP", model_buffer.getvalue(), "TORCH", "CPU")
227+
228+
# Update the model in smartredis
229+
client.put_tensor("model_updated", np.array([0.]))
230+
231+
# Delete dataset lists for the next time step
232+
client.delete_list("pointsDatasetList")
233+
client.delete_list("displacementsDatasetList")
234+
235+
# Update time index
236+
local_time_index = local_time_index + 1
237+
238+
if client.poll_key("end_time_index", 10, 10):
239+
print ("End time reached.")
240+
break
241+
242+
except Exception as e:
243+
print("Caught an exception: ", str(e))
244+
245+
finally:
246+
exp.stop(db)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*--------------------------------*- C++ -*----------------------------------*\
2+
| ========= | |
3+
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
4+
| \\ / O peration | Version: v2206 |
5+
| \\ / A nd | Website: www.openfoam.com |
6+
| \\/ M anipulation | |
7+
\*---------------------------------------------------------------------------*/
8+
FoamFile
9+
{
10+
version 2.0;
11+
format ascii;
12+
class pointVectorField;
13+
object pointDisplacement;
14+
}
15+
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
16+
17+
dimensions [0 1 0 0 0 0 0];
18+
19+
internalField uniform (0 0 0);
20+
21+
boundaryField
22+
{
23+
24+
cylinder
25+
{
26+
type solidBodyMotionDisplacement;
27+
28+
solidBodyMotionFunction oscillatingLinearMotion;
29+
30+
oscillatingLinearMotionCoeffs
31+
{
32+
amplitude (0 1 0); // Amplitude of oscillation in x direction
33+
omega 4; // Angular frequency in rad/s
34+
phase 0; // Phase shift in radians (optional)
35+
}
36+
37+
multiMotionCoeffs
38+
{
39+
translation
40+
{
41+
solidBodyMotionFunction linearMotion;
42+
linearMotionCoeffs
43+
{
44+
velocity (1 0 0);
45+
}
46+
}
47+
rotation
48+
{
49+
solidBodyMotionFunction rotatingMotion;
50+
rotatingMotionCoeffs
51+
{
52+
origin (0 0 0);
53+
axis (0 0 1);
54+
//omega 1; // rad/s, 1rad/s=9.5rpm
55+
}
56+
}
57+
}
58+
59+
oscillatingRotatingMotionCoeffs
60+
{
61+
origin (0 0 0);
62+
axis (0 0 1);
63+
omega 1.5; // rad/s, 1rad/s=9.5rpm
64+
amplitude (0 0 30); // max amplitude (degrees)
65+
}
66+
}
67+
68+
inlet
69+
{
70+
type fixedValue;
71+
value uniform (0 0 0);
72+
}
73+
74+
outlet
75+
{
76+
type fixedValue;
77+
value uniform (0 0 0);
78+
}
79+
80+
walls
81+
{
82+
type fixedValue;
83+
value uniform (0 0 0);
84+
}
85+
86+
frontAndBack
87+
{
88+
type empty;
89+
}
90+
91+
}
92+
93+
94+
// ************************************************************************* //
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
cd "${0%/*}" || exit # Run from this directory
3+
. ${WM_PROJECT_DIR:?}/bin/tools/CleanFunctions # Tutorial clean functions
4+
#------------------------------------------------------------------------------
5+
6+
cleanCase0
7+
8+
#------------------------------------------------------------------------------
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
cd "${0%/*}" || exit # Run from this directory
3+
. ${WM_PROJECT_DIR:?}/bin/tools/RunFunctions # Tutorial run functions
4+
#------------------------------------------------------------------------------
5+
6+
runApplication mpirun -np 4 checkMesh -constant -writeAllFields -parallel
7+
8+
#------------------------------------------------------------------------------
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/sh
2+
cd "${0%/*}" || exit # Run from this directory
3+
. ${WM_PROJECT_DIR:?}/bin/tools/RunFunctions # Tutorial run functions
4+
#------------------------------------------------------------------------------
5+
6+
restore0Dir
7+
8+
runApplication blockMesh
9+
10+
runApplication decomposePar
11+
12+
#------------------------------------------------------------------------------

0 commit comments

Comments
 (0)