Skip to content

Commit

Permalink
Merge pull request #61 from hsalehipour/dev
Browse files Browse the repository at this point in the history
Added boundary force computation and more
  • Loading branch information
mehdiataei authored Sep 5, 2024
2 parents 74bd907 + 2a3e6c6 commit fe8c945
Show file tree
Hide file tree
Showing 28 changed files with 792 additions and 375 deletions.
30 changes: 22 additions & 8 deletions examples/cfd/flow_past_sphere_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@
from xlb.precision_policy import PrecisionPolicy
from xlb.helper import create_nse_fields, initialize_eq
from xlb.operator.stepper import IncompressibleNavierStokesStepper
from xlb.operator.boundary_condition import FullwayBounceBackBC, ZouHeBC, RegularizedBC, EquilibriumBC, DoNothingBC, ExtrapolationOutflowBC
from xlb.operator.boundary_condition import (
FullwayBounceBackBC,
HalfwayBounceBackBC,
ZouHeBC,
RegularizedBC,
EquilibriumBC,
DoNothingBC,
ExtrapolationOutflowBC,
)
from xlb.operator.macroscopic import Macroscopic
from xlb.operator.boundary_masker import IndicesBoundaryMasker
from xlb.utils import save_fields_vtk, save_image
import warp as wp
import numpy as np
import jax.numpy as jnp
import time


class FlowOverSphere:
Expand All @@ -25,7 +34,7 @@ def __init__(self, omega, grid_shape, velocity_set, backend, precision_policy):
self.velocity_set = velocity_set
self.backend = backend
self.precision_policy = precision_policy
self.grid, self.f_0, self.f_1, self.missing_mask, self.boundary_mask = create_nse_fields(grid_shape)
self.grid, self.f_0, self.f_1, self.missing_mask, self.boundary_map = create_nse_fields(grid_shape)
self.stepper = None
self.boundary_conditions = []

Expand All @@ -34,7 +43,7 @@ def __init__(self, omega, grid_shape, velocity_set, backend, precision_policy):

def _setup(self, omega):
self.setup_boundary_conditions()
self.setup_boundary_masks()
self.setup_boundary_masker()
self.initialize_fields()
self.setup_stepper(omega)

Expand Down Expand Up @@ -69,19 +78,20 @@ def setup_boundary_conditions(self):
# bc_outlet = RegularizedBC("pressure", 1.0, indices=outlet)
# bc_outlet = DoNothingBC(indices=outlet)
bc_outlet = ExtrapolationOutflowBC(indices=outlet)
bc_sphere = FullwayBounceBackBC(indices=sphere)
bc_sphere = HalfwayBounceBackBC(indices=sphere)

self.boundary_conditions = [bc_left, bc_outlet, bc_sphere, bc_walls]
# Note: it is important to add bc_walls to be after bc_outlet/bc_inlet because
# of the corner nodes. This way the corners are treated as wall and not inlet/outlet.
# TODO: how to ensure about this behind in the src code?

def setup_boundary_masks(self):
def setup_boundary_masker(self):
indices_boundary_masker = IndicesBoundaryMasker(
velocity_set=self.velocity_set,
precision_policy=self.precision_policy,
compute_backend=self.backend,
)
self.boundary_mask, self.missing_mask = indices_boundary_masker(self.boundary_conditions, self.boundary_mask, self.missing_mask, (0, 0, 0))
self.boundary_map, self.missing_mask = indices_boundary_masker(self.boundary_conditions, self.boundary_map, self.missing_mask, (0, 0, 0))

def initialize_fields(self):
self.f_0 = initialize_eq(self.f_0, self.grid, self.velocity_set, self.backend)
Expand All @@ -90,12 +100,16 @@ def setup_stepper(self, omega):
self.stepper = IncompressibleNavierStokesStepper(omega, boundary_conditions=self.boundary_conditions, collision_type="BGK")

def run(self, num_steps, post_process_interval=100):
start_time = time.time()
for i in range(num_steps):
self.f_1 = self.stepper(self.f_0, self.f_1, self.boundary_mask, self.missing_mask, i)
self.f_1 = self.stepper(self.f_0, self.f_1, self.boundary_map, self.missing_mask, i)
self.f_0, self.f_1 = self.f_1, self.f_0

if i % post_process_interval == 0 or i == num_steps - 1:
self.post_process(i)
end_time = time.time()
print(f"Completing {i} iterations. Time elapsed for 1000 LBM steps in {end_time - start_time:.6f} seconds.")
start_time = time.time()

def post_process(self, i):
# Write the results. We'll use JAX backend for the post-processing
Expand All @@ -114,7 +128,7 @@ def post_process(self, i):

fields = {"u_magnitude": u_magnitude, "u_x": u[0], "u_y": u[1], "u_z": u[2], "rho": rho[0]}

save_fields_vtk(fields, timestep=i)
# save_fields_vtk(fields, timestep=i)
save_image(fields["u_magnitude"][:, self.grid_shape[1] // 2, :], timestep=i)


Expand Down
10 changes: 5 additions & 5 deletions examples/cfd/lid_driven_cavity_2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(self, omega, grid_shape, velocity_set, backend, precision_policy):
self.velocity_set = velocity_set
self.backend = backend
self.precision_policy = precision_policy
self.grid, self.f_0, self.f_1, self.missing_mask, self.boundary_mask = create_nse_fields(grid_shape)
self.grid, self.f_0, self.f_1, self.missing_mask, self.boundary_map = create_nse_fields(grid_shape)
self.stepper = None
self.boundary_conditions = []

Expand All @@ -33,7 +33,7 @@ def __init__(self, omega, grid_shape, velocity_set, backend, precision_policy):

def _setup(self, omega):
self.setup_boundary_conditions()
self.setup_boundary_masks()
self.setup_boundary_masker()
self.initialize_fields()
self.setup_stepper(omega)

Expand All @@ -51,13 +51,13 @@ def setup_boundary_conditions(self):
bc_walls = HalfwayBounceBackBC(indices=walls)
self.boundary_conditions = [bc_top, bc_walls]

def setup_boundary_masks(self):
def setup_boundary_masker(self):
indices_boundary_masker = IndicesBoundaryMasker(
velocity_set=self.velocity_set,
precision_policy=self.precision_policy,
compute_backend=self.backend,
)
self.boundary_mask, self.missing_mask = indices_boundary_masker(self.boundary_conditions, self.boundary_mask, self.missing_mask)
self.boundary_map, self.missing_mask = indices_boundary_masker(self.boundary_conditions, self.boundary_map, self.missing_mask)

def initialize_fields(self):
self.f_0 = initialize_eq(self.f_0, self.grid, self.velocity_set, self.backend)
Expand All @@ -67,7 +67,7 @@ def setup_stepper(self, omega):

def run(self, num_steps, post_process_interval=100):
for i in range(num_steps):
self.f_1 = self.stepper(self.f_0, self.f_1, self.boundary_mask, self.missing_mask, i)
self.f_1 = self.stepper(self.f_0, self.f_1, self.boundary_map, self.missing_mask, i)
self.f_0, self.f_1 = self.f_1, self.f_0

if i % post_process_interval == 0 or i == num_steps - 1:
Expand Down
126 changes: 100 additions & 26 deletions examples/cfd/windtunnel_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@
FullwayBounceBackBC,
EquilibriumBC,
DoNothingBC,
RegularizedBC,
HalfwayBounceBackBC,
ExtrapolationOutflowBC,
)
from xlb.operator.force.momentum_transfer import MomentumTransfer
from xlb.operator.macroscopic import Macroscopic
from xlb.operator.boundary_masker import IndicesBoundaryMasker
from xlb.operator.boundary_masker import IndicesBoundaryMasker, MeshBoundaryMasker
from xlb.utils import save_fields_vtk, save_image
import warp as wp
import numpy as np
import jax.numpy as jnp
import matplotlib.pyplot as plt


class WindTunnel3D:
Expand All @@ -32,18 +36,25 @@ def __init__(self, omega, wind_speed, grid_shape, velocity_set, backend, precisi
self.velocity_set = velocity_set
self.backend = backend
self.precision_policy = precision_policy
self.grid, self.f_0, self.f_1, self.missing_mask, self.boundary_mask = create_nse_fields(grid_shape)
self.grid, self.f_0, self.f_1, self.missing_mask, self.boundary_map = create_nse_fields(grid_shape)
self.stepper = None
self.boundary_conditions = []

# Setup the simulation BC, its initial conditions, and the stepper
self._setup(omega, wind_speed)

def _setup(self, omega, wind_speed):
self.setup_boundary_conditions(wind_speed)
self.setup_boundary_masks()
self.wind_speed = wind_speed
self.omega = omega
self._setup()

# Make list to store drag coefficients
self.time_steps = []
self.drag_coefficients = []
self.lift_coefficients = []

def _setup(self):
self.setup_boundary_conditions()
self.setup_boundary_masker()
self.initialize_fields()
self.setup_stepper(omega)
self.setup_stepper()

def voxelize_stl(self, stl_filename, length_lbm_unit):
mesh = trimesh.load_mesh(stl_filename, process=False)
Expand All @@ -64,46 +75,66 @@ def define_boundary_indices(self):
for i in range(self.velocity_set.d)
]

# Load the mesh
stl_filename = "examples/cfd/stl-files/DrivAer-Notchback.stl"
grid_size_x = self.grid_shape[0]
car_length_lbm_unit = grid_size_x / 4
car_voxelized, pitch = self.voxelize_stl(stl_filename, car_length_lbm_unit)

# car_area = np.prod(car_voxelized.shape[1:])
tx, ty, _ = np.array([grid_size_x, grid_size_y, grid_size_z]) - car_voxelized.shape
shift = [tx // 4, ty // 2, 0]
car = np.argwhere(car_voxelized) + shift
car = np.array(car).T
car = [tuple(car[i]) for i in range(self.velocity_set.d)]
mesh = trimesh.load_mesh(stl_filename, process=False)
mesh_vertices = mesh.vertices

# Transform the mesh points to be located in the right position in the wind tunnel
mesh_vertices -= mesh_vertices.min(axis=0)
mesh_extents = mesh_vertices.max(axis=0)
length_phys_unit = mesh_extents.max()
length_lbm_unit = self.grid_shape[0] / 4
dx = length_phys_unit / length_lbm_unit
shift = np.array([self.grid_shape[0] * dx / 4, (self.grid_shape[1] * dx - mesh_extents[1]) / 2, 0.0])
car = mesh_vertices + shift
self.grid_spacing = dx
self.car_cross_section = np.prod(mesh_extents[1:]) / dx**2

return inlet, outlet, walls, car

def setup_boundary_conditions(self, wind_speed):
def setup_boundary_conditions(self):
inlet, outlet, walls, car = self.define_boundary_indices()
bc_left = EquilibriumBC(rho=1.0, u=(wind_speed, 0.0, 0.0), indices=inlet)
bc_left = EquilibriumBC(rho=1.0, u=(self.wind_speed, 0.0, 0.0), indices=inlet)
# bc_left = RegularizedBC('velocity', (self.wind_speed, 0.0, 0.0), indices=inlet)
bc_walls = FullwayBounceBackBC(indices=walls)
bc_do_nothing = ExtrapolationOutflowBC(indices=outlet)
bc_car = FullwayBounceBackBC(indices=car)
bc_car = HalfwayBounceBackBC(mesh_vertices=car)
# bc_car = FullwayBounceBackBC(mesh_vertices=car)
self.boundary_conditions = [bc_left, bc_do_nothing, bc_walls, bc_car]

def setup_boundary_masks(self):
def setup_boundary_masker(self):
indices_boundary_masker = IndicesBoundaryMasker(
velocity_set=self.velocity_set,
precision_policy=self.precision_policy,
compute_backend=self.backend,
)
self.boundary_mask, self.missing_mask = indices_boundary_masker(self.boundary_conditions, self.boundary_mask, self.missing_mask, (0, 0, 0))
mesh_boundary_masker = MeshBoundaryMasker(
velocity_set=self.velocity_set,
precision_policy=self.precision_policy,
compute_backend=self.backend,
)
bclist_other = self.boundary_conditions[:-1]
bc_mesh = self.boundary_conditions[-1]
dx = self.grid_spacing
origin, spacing = (0, 0, 0), (dx, dx, dx)
self.boundary_map, self.missing_mask = indices_boundary_masker(bclist_other, self.boundary_map, self.missing_mask)
self.boundary_map, self.missing_mask = mesh_boundary_masker(bc_mesh, origin, spacing, self.boundary_map, self.missing_mask)

def initialize_fields(self):
self.f_0 = initialize_eq(self.f_0, self.grid, self.velocity_set, self.backend)

def setup_stepper(self, omega):
self.stepper = IncompressibleNavierStokesStepper(omega, boundary_conditions=self.boundary_conditions, collision_type="KBC")
def setup_stepper(self):
self.stepper = IncompressibleNavierStokesStepper(self.omega, boundary_conditions=self.boundary_conditions, collision_type="KBC")

def run(self, num_steps, print_interval, post_process_interval=100):
# Setup the operator for computing surface forces at the interface of the specified BC
bc_car = self.boundary_conditions[-1]
self.momentum_transfer = MomentumTransfer(bc_car)

start_time = time.time()
for i in range(num_steps):
self.f_1 = self.stepper(self.f_0, self.f_1, self.boundary_mask, self.missing_mask, i)
self.f_1 = self.stepper(self.f_0, self.f_1, self.boundary_map, self.missing_mask, i)
self.f_0, self.f_1 = self.f_1, self.f_0

if (i + 1) % print_interval == 0:
Expand Down Expand Up @@ -133,6 +164,49 @@ def post_process(self, i):
save_fields_vtk(fields, timestep=i)
save_image(fields["u_magnitude"][:, grid_size_y // 2, :], timestep=i)

# Compute lift and drag
boundary_force = self.momentum_transfer(self.f_0, self.boundary_map, self.missing_mask)
drag = np.sqrt(boundary_force[0] ** 2 + boundary_force[1] ** 2) # xy-plane
lift = boundary_force[2]
c_d = 2.0 * drag / (self.wind_speed**2 * self.car_cross_section)
c_l = 2.0 * lift / (self.wind_speed**2 * self.car_cross_section)
self.drag_coefficients.append(c_d)
self.lift_coefficients.append(c_l)
self.time_steps.append(i)

# Save monitor plot
self.plot_drag_coefficient()
return

def plot_drag_coefficient(self):
# Compute moving average of drag coefficient, 100, 1000, 10000
drag_coefficients = np.array(self.drag_coefficients)
self.drag_coefficients_ma_10 = np.convolve(drag_coefficients, np.ones(10) / 10, mode="valid")
self.drag_coefficients_ma_100 = np.convolve(drag_coefficients, np.ones(100) / 100, mode="valid")
self.drag_coefficients_ma_1000 = np.convolve(drag_coefficients, np.ones(1000) / 1000, mode="valid")
self.drag_coefficients_ma_10000 = np.convolve(drag_coefficients, np.ones(10000) / 10000, mode="valid")
self.drag_coefficients_ma_100000 = np.convolve(drag_coefficients, np.ones(100000) / 100000, mode="valid")

# Plot drag coefficient
plt.plot(self.time_steps, drag_coefficients, label="Raw")
if len(self.time_steps) > 10:
plt.plot(self.time_steps[9:], self.drag_coefficients_ma_10, label="MA 10")
if len(self.time_steps) > 100:
plt.plot(self.time_steps[99:], self.drag_coefficients_ma_100, label="MA 100")
if len(self.time_steps) > 1000:
plt.plot(self.time_steps[999:], self.drag_coefficients_ma_1000, label="MA 1,000")
if len(self.time_steps) > 10000:
plt.plot(self.time_steps[9999:], self.drag_coefficients_ma_10000, label="MA 10,000")
if len(self.time_steps) > 100000:
plt.plot(self.time_steps[99999:], self.drag_coefficients_ma_100000, label="MA 100,000")

plt.ylim(-1.0, 1.0)
plt.legend()
plt.xlabel("Time step")
plt.ylabel("Drag coefficient")
plt.savefig("drag_coefficient_ma.png")
plt.close()


if __name__ == "__main__":
# Grid parameters
Expand Down
16 changes: 6 additions & 10 deletions examples/cfd_old_to_be_migrated/flow_past_sphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def warp_implementation(self, rho, u, vel):
u = grid.create_field(cardinality=velocity_set.d, dtype=wp.float32)
f0 = grid.create_field(cardinality=velocity_set.q, dtype=wp.float32)
f1 = grid.create_field(cardinality=velocity_set.q, dtype=wp.float32)
boundary_mask = grid.create_field(cardinality=1, dtype=wp.uint8)
boundary_map = grid.create_field(cardinality=1, dtype=wp.uint8)
missing_mask = grid.create_field(cardinality=velocity_set.q, dtype=wp.bool)

# Make operators
Expand Down Expand Up @@ -154,23 +154,19 @@ def warp_implementation(self, rho, u, vel):
indices = wp.from_numpy(indices, dtype=wp.int32)

# Set boundary conditions on the indices
boundary_mask, missing_mask = indices_boundary_masker(indices, half_way_bc.id, boundary_mask, missing_mask, (0, 0, 0))
boundary_map, missing_mask = indices_boundary_masker(indices, half_way_bc.id, boundary_map, missing_mask, (0, 0, 0))

# Set inlet bc
lower_bound = (0, 0, 0)
upper_bound = (0, nr, nr)
direction = (1, 0, 0)
boundary_mask, missing_mask = planar_boundary_masker(
lower_bound, upper_bound, direction, equilibrium_bc.id, boundary_mask, missing_mask, (0, 0, 0)
)
boundary_map, missing_mask = planar_boundary_masker(lower_bound, upper_bound, direction, equilibrium_bc.id, boundary_map, missing_mask, (0, 0, 0))

# Set outlet bc
lower_bound = (nr - 1, 0, 0)
upper_bound = (nr - 1, nr, nr)
direction = (-1, 0, 0)
boundary_mask, missing_mask = planar_boundary_masker(
lower_bound, upper_bound, direction, do_nothing_bc.id, boundary_mask, missing_mask, (0, 0, 0)
)
boundary_map, missing_mask = planar_boundary_masker(lower_bound, upper_bound, direction, do_nothing_bc.id, boundary_map, missing_mask, (0, 0, 0))

# Set initial conditions
rho, u = initializer(rho, u, vel)
Expand All @@ -185,7 +181,7 @@ def warp_implementation(self, rho, u, vel):
num_steps = 1024 * 8
start = time.time()
for _ in tqdm(range(num_steps)):
f1 = stepper(f0, f1, boundary_mask, missing_mask, _)
f1 = stepper(f0, f1, boundary_map, missing_mask, _)
f1, f0 = f0, f1
if (_ % plot_freq == 0) and (not compute_mlup):
rho, u = macroscopic(f0, rho, u)
Expand All @@ -195,7 +191,7 @@ def warp_implementation(self, rho, u, vel):
plt.imshow(u[0, :, nr // 2, :].numpy())
plt.colorbar()
plt.subplot(1, 2, 2)
plt.imshow(boundary_mask[0, :, nr // 2, :].numpy())
plt.imshow(boundary_map[0, :, nr // 2, :].numpy())
plt.colorbar()
plt.savefig(f"{save_dir}/{str(_).zfill(6)}.png")
plt.close()
Expand Down
6 changes: 3 additions & 3 deletions examples/cfd_old_to_be_migrated/taylor_green.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def run_taylor_green(backend, compute_mlup=True):
u = grid.create_field(cardinality=velocity_set.d, precision=xlb.Precision.FP32)
f0 = grid.create_field(cardinality=velocity_set.q, precision=xlb.Precision.FP32)
f1 = grid.create_field(cardinality=velocity_set.q, precision=xlb.Precision.FP32)
boundary_mask = grid.create_field(cardinality=1, precision=xlb.Precision.UINT8)
boundary_map = grid.create_field(cardinality=1, precision=xlb.Precision.UINT8)
missing_mask = grid.create_field(cardinality=velocity_set.q, precision=xlb.Precision.BOOL)

# Make operators
Expand Down Expand Up @@ -149,10 +149,10 @@ def run_taylor_green(backend, compute_mlup=True):
for _ in tqdm(range(num_steps)):
# Time step
if backend == "warp":
f1 = stepper(f0, f1, boundary_mask, missing_mask, _)
f1 = stepper(f0, f1, boundary_map, missing_mask, _)
f1, f0 = f0, f1
elif backend == "jax":
f0 = stepper(f0, boundary_mask, missing_mask, _)
f0 = stepper(f0, boundary_map, missing_mask, _)

# Plot if needed
if (_ % plot_freq == 0) and (not compute_mlup):
Expand Down
Loading

0 comments on commit fe8c945

Please sign in to comment.