forked from Autodesk/XLB
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsimulation_manager.py
More file actions
217 lines (187 loc) · 8.42 KB
/
simulation_manager.py
File metadata and controls
217 lines (187 loc) · 8.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import neon
import warp as wp
from xlb.operator.stepper import MultiresIncompressibleNavierStokesStepper
from xlb.operator.macroscopic import MultiresMacroscopic
from xlb.mres_perf_optimization_type import MresPerfOptimizationType
class MultiresSimulationManager(MultiresIncompressibleNavierStokesStepper):
"""
A simulation manager for multiresolution simulations using the Neon backend in XLB.
"""
def __init__(
self,
omega_finest,
grid,
boundary_conditions=[],
collision_type="BGK",
forcing_scheme="exact_difference",
force_vector=None,
initializer=None,
mres_perf_opt: MresPerfOptimizationType = MresPerfOptimizationType.NAIVE_COLLIDE_STREAM,
):
super().__init__(grid, boundary_conditions, collision_type, forcing_scheme, force_vector)
self.initializer = initializer
self.count_levels = grid.count_levels
self.omega_list = [self.compute_omega(omega_finest, level) for level in range(self.count_levels)]
self.mres_perf_opt = mres_perf_opt
# Create fields
self.rho = grid.create_field(cardinality=1, dtype=self.precision_policy.store_precision)
self.u = grid.create_field(cardinality=3, dtype=self.precision_policy.store_precision)
self.coalescence_factor = grid.create_field(cardinality=self.velocity_set.q, dtype=self.precision_policy.store_precision)
for level in range(self.count_levels):
self.u.fill_run(level, 0.0, 0)
self.rho.fill_run(level, 1.0, 0)
self.coalescence_factor.fill_run(level, 0.0, 0)
# Prepare fields
self.f_0, self.f_1, self.bc_mask, self.missing_mask = self.prepare_fields(self.rho, self.u, self.initializer)
self.prepare_coalescence_count(coalescence_factor=self.coalescence_factor, bc_mask=self.bc_mask)
self.iteration_idx = -1
self.macro = MultiresMacroscopic(
compute_backend=self.compute_backend,
precision_policy=self.precision_policy,
velocity_set=self.velocity_set,
)
# Construct the stepper skeleton
self._construct_stepper_skeleton()
def compute_omega(self, omega_finest, level):
"""
Compute the relaxation parameter omega at a given grid level based on the finest level omega.
We select a refinement ratio of 2 where a coarse cell at level L is uniformly divided into 2^d cells
where d is the dimension. to arrive at level L - 1, or in other words ∆x_{L-1} = ∆x_L/2.
For neighboring cells that interface two grid levels, a maximum jump in grid level of ∆L = 1 is
allowed. Due to acoustic scaling which requires the speed of sound cs to remain constant across various grid levels,
∆tL ∝ ∆xL and hence ∆t_{L-1} = ∆t_{L}/2. In addition, the fluid viscosity \nu must also remain constant on each
grid level which leads to the following relationship for the relaxation parameter omega at grid level L base
on the finest grid level omega_finest.
Args:
omega_finest: Relaxation parameter at the finest grid level.
level: Current grid level (0-indexed, with 0 being the finest level).
Returns:
Relaxation parameter omega at the specified grid level.
"""
omega0 = omega_finest
return 2 ** (level + 1) * omega0 / ((2**level - 1.0) * omega0 + 2.0)
def export_macroscopic(self, fname_prefix):
print(f"exporting macroscopic: #levels {self.count_levels}")
self.macro(self.f_0, self.bc_mask, self.rho, self.u, streamId=0)
wp.synchronize()
self.u.update_host(0)
wp.synchronize()
self.u.export_vti(f"{fname_prefix}{self.iteration_idx}.vti", "u")
print("DONE exporting macroscopic")
return
def step(self):
self.iteration_idx = self.iteration_idx + 1
self.sk.run()
# Construct the stepper skeleton
def _construct_stepper_skeleton(self):
self.app = []
def recursion_reference(level, app):
if level < 0:
return
# Compute omega at the current level
omega = self.omega_list[level]
print(f"RECURSION down to level {level}")
print(f"RECURSION Level {level}, COLLIDE")
self.add_to_app(
app=app,
op_name="collide_coarse",
mres_level=level,
f_0=self.f_0,
f_1=self.f_1,
bc_mask=self.bc_mask,
missing_mask=self.missing_mask,
omega=omega,
timestep=0,
)
recursion_reference(level - 1, app)
recursion_reference(level - 1, app)
# Important: swapping of f_0 and f_1 is done here
print(f"RECURSION Level {level}, stream_coarse_step_ABC")
self.add_to_app(
app=app,
op_name="stream_coarse_step_ABC",
mres_level=level,
f_0=self.f_1,
f_1=self.f_0,
bc_mask=self.bc_mask,
missing_mask=self.missing_mask,
omega=self.coalescence_factor,
timestep=0,
)
def recursion_fused_finest(level, app):
if level < 0:
return
# Compute omega at the current level
omega = self.omega_list[level]
if level == 0:
print(f"RECURSION down to the finest level {level}")
print(f"RECURSION Level {level}, Fused STREAM and COLLIDE")
self.add_to_app(
app=app,
op_name="finest_fused_pull",
mres_level=level,
f_0=self.f_0,
f_1=self.f_1,
bc_mask=self.bc_mask,
missing_mask=self.missing_mask,
omega=omega,
timestep=0,
is_f1_the_explosion_src_field=True,
)
self.add_to_app(
app=app,
op_name="finest_fused_pull",
mres_level=level,
f_0=self.f_1,
f_1=self.f_0,
bc_mask=self.bc_mask,
missing_mask=self.missing_mask,
omega=omega,
timestep=0,
is_f1_the_explosion_src_field=False,
)
return
print(f"RECURSION down to level {level}")
print(f"RECURSION Level {level}, COLLIDE")
self.add_to_app(
app=app,
op_name="collide_coarse",
mres_level=level,
f_0=self.f_0,
f_1=self.f_1,
bc_mask=self.bc_mask,
missing_mask=self.missing_mask,
omega=omega,
timestep=0,
)
# 1. Accumulation is read from f_0 in the streaming step, where f_0=self.f_1.
# so is_self_f1_the_coalescence_dst_field is True
# 2. Explision data is the output from the corser collide, which is f_1=self.f_1.
# so is_self_f1_the_explosion_src_field is True
if level - 1 == 0:
recursion_fused_finest(level - 1, app)
else:
recursion_fused_finest(level - 1, app)
recursion_fused_finest(level - 1, app)
# Important: swapping of f_0 and f_1 is done here
print(f"RECURSION Level {level}, stream_coarse_step_ABC")
self.add_to_app(
app=app,
op_name="stream_coarse_step_ABC",
mres_level=level,
f_0=self.f_1,
f_1=self.f_0,
bc_mask=self.bc_mask,
missing_mask=self.missing_mask,
omega=self.coalescence_factor,
timestep=0,
)
if self.mres_perf_opt == MresPerfOptimizationType.NAIVE_COLLIDE_STREAM:
recursion_reference(self.count_levels - 1, app=self.app)
elif self.mres_perf_opt == MresPerfOptimizationType.FUSION_AT_FINEST:
recursion_fused_finest(self.count_levels - 1, app=self.app)
else:
raise ValueError(f"Unknown optimization level: {self.opt_level}")
bk = self.grid.get_neon_backend()
self.sk = neon.Skeleton(backend=bk)
self.sk.sequence("mres_nse_stepper", self.app)