Skip to content

Commit e6ef73e

Browse files
committed
Added basic infrastructure for 3D RBC
1 parent 42ddf1a commit e6ef73e

File tree

8 files changed

+629
-5
lines changed

8 files changed

+629
-5
lines changed

pySDC/implementations/problem_classes/RayleighBenard3D.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,3 +462,20 @@ def get_frequency_spectrum(self, u):
462462
spectrum[..., index_global] += _spectrum[..., index_local]
463463

464464
return xp.array(unique_k_all), spectrum
465+
466+
def get_vertical_profiles(self, u, components):
467+
if self.spectral_space:
468+
u_hat = u.copy()
469+
else:
470+
u_hat = self.transform(u)
471+
472+
_u_hat = self.axes[-1].itransform(u_hat, axes=(-1,))
473+
474+
avgs = {}
475+
for c in components:
476+
i = self.index(c)
477+
avg = self.xp.ascontiguousarray(_u_hat[i, 0, 0, :].real) / self.axes[0].N / self.axes[1].N
478+
self.comm.Bcast(avg, root=0)
479+
avgs[c] = avg
480+
481+
return avgs

pySDC/implementations/sweeper_classes/Runge_Kutta.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,15 @@ class IMEXEuler(RungeKuttaIMEX):
510510
matrix_explicit = ForwardEuler.matrix
511511

512512

513+
class IMEXEulerStifflyAccurate(RungeKuttaIMEX):
514+
nodes = np.array([0, 1])
515+
weights = np.array([0, 1])
516+
weights_explicit = np.array([1, 0])
517+
518+
matrix = np.array([[0, 0], [0, 1]])
519+
matrix_explicit = np.array([[0, 0], [1, 0]])
520+
521+
513522
class CrankNicolson(RungeKutta):
514523
"""
515524
Implicit Runge-Kutta method of second order, A-stable.
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
from pySDC.projects.GPU.configs.base_config import get_config
2+
from pySDC.projects.GPU.run_experiment import parse_args
3+
from pySDC.helpers.fieldsIO import FieldsIO
4+
import matplotlib.pyplot as plt
5+
from tqdm import tqdm
6+
from mpi4py import MPI
7+
import numpy as np
8+
import pickle
9+
import os
10+
11+
# global configs
12+
BASE_PATH = 'data/RBC_time_averaged'
13+
14+
15+
# prepare problem instance
16+
args = parse_args()
17+
comm = MPI.COMM_WORLD
18+
config = get_config(args)
19+
desc = config.get_description(**args)
20+
P = desc['problem_class'](
21+
**{
22+
**desc['problem_params'],
23+
'spectral_space': False,
24+
'comm': comm,
25+
'Dirichlet_recombination': False,
26+
'left_preconditioner': False,
27+
}
28+
)
29+
P.setUpFieldsIO()
30+
zInt = P.axes[-1].get_integration_weights()
31+
xp = P.xp
32+
33+
# prepare paths
34+
os.makedirs(BASE_PATH, exist_ok=True)
35+
fname = config.get_file_name()
36+
start = fname.index('data')
37+
path = f'{BASE_PATH}/{fname[start + 5:-6]}.pickle'
38+
39+
# open simulation data
40+
data = FieldsIO.fromFile(fname)
41+
42+
# prepare arrays to store data in
43+
Nu = {
44+
'V': [],
45+
'b': [],
46+
't': [],
47+
'thermal': [],
48+
'kinetic': [],
49+
}
50+
t = []
51+
T = []
52+
profiles = {key: [] for key in ['T', 'u', 'v', 'w']}
53+
rms_profiles = {key: [] for key in profiles.keys()}
54+
spectrum = []
55+
spectrum_all = []
56+
57+
58+
# try to load time averaged values
59+
u_mean_profile = P.u_exact()
60+
if os.path.isfile(path):
61+
with open(path, 'rb') as file:
62+
avg_data = pickle.load(file)
63+
if comm.rank == 0:
64+
print(f'Read data from file {path!r}')
65+
for key in profiles.keys():
66+
if f'profile_{key}' in avg_data.keys():
67+
u_mean_profile[P.index(key)] = avg_data[f'profile_{key}'][P.local_slice(False)[-1]]
68+
elif comm.rank == 0:
69+
print('No mean profiles available yet. Please rerun script after completion to get correct RMS profiles')
70+
71+
# prepare progress bar
72+
indeces = range(args['restart_idx'], data.nFields)
73+
if P.comm.rank == 0:
74+
indeces = tqdm(indeces)
75+
76+
# loop through all data points and compute stuff
77+
for i in indeces:
78+
_t, u = data.readField(i)
79+
80+
# Nusselt numbers
81+
_Nu = P.compute_Nusselt_numbers(u)
82+
if any(me > 1e3 for me in _Nu.values()):
83+
continue
84+
85+
for key in Nu.keys():
86+
Nu[key].append(_Nu[key])
87+
88+
t.append(_t)
89+
90+
# profiles
91+
_profiles = P.get_vertical_profiles(u, list(profiles.keys()))
92+
_rms_profiles = P.get_vertical_profiles((u - u_mean_profile) ** 2, list(profiles.keys()))
93+
for key in profiles.keys():
94+
profiles[key].append(_profiles[key])
95+
rms_profiles[key].append(_rms_profiles[key])
96+
97+
# spectrum
98+
k, s = P.get_frequency_spectrum(u)
99+
s_mean = zInt @ P.axes[-1].transform(s[0], axes=(0,))
100+
spectrum.append(s_mean)
101+
spectrum_all.append(s)
102+
103+
104+
# make a plot of the results
105+
t = xp.array(t)
106+
z = P.axes[-1].get_1dgrid()
107+
108+
if config.converged == 0:
109+
print('Warning: no convergence has been set for this configuration!')
110+
111+
fig, axs = plt.subplots(1, 4, figsize=(18, 4))
112+
for key in Nu.keys():
113+
axs[0].plot(t, Nu[key], label=f'$Nu_{{{key}}}$')
114+
if config.converged > 0:
115+
axs[0].axvline(config.converged, color='black')
116+
axs[0].set_ylabel('$Nu$')
117+
axs[0].set_xlabel('$t$')
118+
axs[0].legend(frameon=False)
119+
120+
# compute differences in Nusselt numbers
121+
avg_Nu = {}
122+
std_Nu = {}
123+
for key in Nu.keys():
124+
_Nu = [Nu[key][i] for i in range(len(Nu[key])) if t[i] > config.converged]
125+
avg_Nu[key] = xp.mean(_Nu)
126+
std_Nu[key] = xp.std(_Nu)
127+
128+
rel_error = {
129+
key: abs(avg_Nu[key] - avg_Nu['V']) / avg_Nu['V']
130+
for key in [
131+
't',
132+
'b',
133+
'thermal',
134+
'kinetic',
135+
]
136+
}
137+
if comm.rank == 0:
138+
print(
139+
f'With Ra={P.Rayleigh:.0e} got Nu={avg_Nu["V"]:.2f}+-{std_Nu["V"]:.2f} with errors: Top {rel_error["t"]:.2e}, bottom: {rel_error["b"]:.2e}, thermal: {rel_error["thermal"]:.2e}, kinetic: {rel_error["kinetic"]:.2e}'
140+
)
141+
142+
143+
# compute average profiles
144+
avg_profiles = {}
145+
for key, values in profiles.items():
146+
values_from_convergence = [values[i] for i in range(len(values)) if t[i] >= config.converged]
147+
148+
avg_profiles[key] = xp.mean(values_from_convergence, axis=0)
149+
150+
avg_rms_profiles = {}
151+
for key, values in rms_profiles.items():
152+
values_from_convergence = [values[i] for i in range(len(values)) if t[i] >= config.converged]
153+
avg_rms_profiles[key] = xp.sqrt(xp.mean(values_from_convergence, axis=0))
154+
155+
156+
# average T
157+
avg_T = avg_profiles['T']
158+
axs[1].axvline(0.5, color='black')
159+
axs[1].plot(avg_T, z)
160+
axs[1].set_xlabel('$T$')
161+
axs[1].set_ylabel('$z$')
162+
163+
# rms profiles
164+
avg_T = avg_rms_profiles['T']
165+
max_idx = xp.argmax(avg_T)
166+
res_in_boundary_layer = max_idx if max_idx < len(z) / 2 else len(z) - max_idx
167+
boundary_layer = z[max_idx] if max_idx > len(z) / 2 else P.axes[-1].L - z[max_idx]
168+
if comm.rank == 0:
169+
print(f'Thermal boundary layer of thickness {boundary_layer:.2f} is resolved with {res_in_boundary_layer} points')
170+
axs[2].axhline(z[max_idx], color='black')
171+
axs[2].plot(avg_T, z)
172+
axs[2].scatter(avg_T, z)
173+
axs[2].set_xlabel(r'$T_\text{rms}$')
174+
axs[2].set_ylabel('$z$')
175+
176+
# spectrum
177+
_s = xp.array(spectrum)
178+
avg_spectrum = xp.mean(_s[t >= config.converged], axis=0)
179+
axs[3].loglog(k[avg_spectrum > 1e-15], avg_spectrum[avg_spectrum > 1e-15])
180+
axs[3].set_xlabel('$k$')
181+
axs[3].set_ylabel(r'$\|\hat{u}_x\|$')
182+
183+
if P.comm.rank == 0:
184+
write_data = {
185+
't': t,
186+
'Nu': Nu,
187+
'avg_Nu': avg_Nu,
188+
'std_Nu': std_Nu,
189+
'z': P.axes[-1].get_1dgrid(),
190+
'k': k,
191+
'spectrum': spectrum_all,
192+
'avg_spectrum': avg_spectrum,
193+
'boundary_layer_thickness': boundary_layer,
194+
'res_in_boundary_layer': res_in_boundary_layer,
195+
}
196+
for key, values in avg_profiles.items():
197+
write_data[f'profile_{key}'] = values
198+
for key, values in avg_rms_profiles.items():
199+
write_data[f'rms_profile_{key}'] = values
200+
201+
with open(path, 'wb') as file:
202+
pickle.dump(write_data, file)
203+
print(f'Wrote data to file {path!r}')
204+
205+
fig.tight_layout()
206+
fig.savefig(f'{BASE_PATH}/{fname[start + 5:-6]}.pdf')
207+
plt.show()
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import pickle
2+
import matplotlib.pyplot as plt
3+
import numpy as np
4+
from scipy import integrate
5+
from pySDC.helpers.plot_helper import figsize_by_journal, setup_mpl
6+
7+
setup_mpl()
8+
9+
10+
def get_pySDC_data(Ra, RK=False, res=-1, dt=-1, config_name='RBC3DG4'):
11+
assert type(Ra) == str
12+
13+
base_path = 'data/RBC_time_averaged'
14+
15+
if RK:
16+
config_name = f'{config_name}RK'
17+
18+
path = f'{base_path}/{config_name}Ra{Ra}-res{res}-dt{dt:.0e}.pickle'
19+
with open(path, 'rb') as file:
20+
data = pickle.load(file)
21+
22+
return data
23+
24+
25+
def interpolate_NuV_to_reference_times(data, reference_data, order=12):
26+
from qmat.lagrange import getSparseInterpolationMatrix
27+
28+
t_in = np.array(data['t'])
29+
t_out = np.array([me for me in reference_data['t'] if me <= max(t_in)])
30+
31+
interpolation_matrix = getSparseInterpolationMatrix(t_in, t_out, order=order)
32+
return interpolation_matrix @ t_in, interpolation_matrix @ data['Nu']['V']
33+
34+
35+
def plot_Nu(Ra, res, dts, config_name, ref, ax, title): # pragma: no cover
36+
ax.plot(ref['t'], ref['Nu']['V'], color='black', ls='--')
37+
ax.set_title(title)
38+
Nu_ref = np.array(ref['Nu']['V'])
39+
40+
for dt in dts:
41+
data = get_pySDC_data(Ra=Ra, res=res, dt=dt, config_name=config_name)
42+
t_i, Nu_i = interpolate_NuV_to_reference_times(data, ref)
43+
ax.plot(t_i, Nu_i, label=rf'$\Delta t={{{dt}}}$')
44+
45+
error = np.abs(Nu_ref[: Nu_i.shape[0]] - Nu_i) / np.abs(Nu_ref[: Nu_i.shape[0]])
46+
47+
# compute mean Nu
48+
mask = np.logical_and(t_i >= 100, t_i <= 200)
49+
Nu_mean = np.mean(Nu_i[mask])
50+
Nu_std = np.std(Nu_i[mask])
51+
52+
last_line = ax.get_lines()[-1]
53+
if any(error > 1e-2):
54+
deviates = min(t_i[error > 1e-2])
55+
ax.axvline(deviates, color=last_line.get_color(), ls=':')
56+
print(f'{title} dt={dt} Nu={Nu_mean:.3f}+={Nu_std:.3f}, deviates more than 1% from t={deviates:.2f}')
57+
else:
58+
print(f'{title} dt={dt} Nu={Nu_mean:.3f}+={Nu_std:.3f}')
59+
ax.legend(frameon=True, loc='upper left')
60+
61+
62+
def plot_Nu_over_time_Ra1e5(): # pragma: no cover
63+
Nu_fig, Nu_axs = plt.subplots(4, 1, sharex=True, sharey=True, figsize=figsize_by_journal('Nature_CS', 1, 1.4))
64+
65+
Ra = '1e5'
66+
res = 32
67+
68+
ref_data = get_pySDC_data(Ra, res=res, dt=0.01, config_name='RBC3DG4R4')
69+
70+
_Nu_axs = {'SDC 3': Nu_axs[1], 'SDC': Nu_axs[0], 'RK': Nu_axs[2], 'Euler': Nu_axs[3]}
71+
72+
plot_Nu(
73+
'1e5',
74+
32,
75+
[
76+
0.06,
77+
0.04,
78+
0.02,
79+
],
80+
'RBC3DG4R4SDC34',
81+
ref_data,
82+
Nu_axs[0],
83+
'SDC34',
84+
)
85+
plot_Nu('1e5', 32, [0.06, 0.05, 0.02, 0.01], 'RBC3DG4R4SDC23', ref_data, Nu_axs[1], 'SDC23')
86+
plot_Nu('1e5', 32, [0.05, 0.04, 0.02, 0.01, 0.005], 'RBC3DG4R4RK', ref_data, Nu_axs[2], 'RK443')
87+
plot_Nu('1e5', 32, [0.02, 0.01, 0.005], 'RBC3DG4R4Euler', ref_data, Nu_axs[3], 'RK111')
88+
89+
Nu_axs[-1].set_xlabel('$t$')
90+
Nu_axs[-1].set_ylabel('$Nu$')
91+
92+
Nu_fig.tight_layout()
93+
Nu_fig.savefig('./plots/Nu_over_time_Ra1e5.pdf', bbox_inches='tight')
94+
95+
96+
if __name__ == '__main__':
97+
98+
plot_Nu_over_time_Ra1e5()
99+
100+
plt.show()

0 commit comments

Comments
 (0)