Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added boundary force computation and more #61

Merged
merged 10 commits into from
Sep 5, 2024
Merged
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
Loading