|
22 | 22 | import numpy as np
|
23 | 23 | import jax.numpy as jnp
|
24 | 24 | import matplotlib.pyplot as plt
|
| 25 | +from functools import partial |
| 26 | +from jax import jit |
25 | 27 |
|
26 | 28 |
|
27 | 29 | class WindTunnel3D:
|
@@ -55,8 +57,7 @@ def _setup(self):
|
55 | 57 | # NOTE: it is important to initialize fields before setup_boundary_masker is called because f_0 or f_1 might be used to store BC information
|
56 | 58 | self.initialize_fields()
|
57 | 59 | self.setup_boundary_conditions()
|
58 |
| - self.setup_boundary_masker() |
59 |
| - self.setup_stepper() |
| 60 | + self.setup_stepper(self.omega) |
60 | 61 |
|
61 | 62 | def voxelize_stl(self, stl_filename, length_lbm_unit):
|
62 | 63 | mesh = trimesh.load_mesh(stl_filename, process=False)
|
@@ -85,57 +86,77 @@ def define_boundary_indices(self):
|
85 | 86 | length_phys_unit = mesh_extents.max()
|
86 | 87 | length_lbm_unit = self.grid_shape[0] / 4
|
87 | 88 | dx = length_phys_unit / length_lbm_unit
|
88 |
| - shift = np.array([self.grid_shape[0] * dx / 4, (self.grid_shape[1] * dx - mesh_extents[1]) / 2, 0.0]) |
| 89 | + mesh_vertices = mesh_vertices / dx |
| 90 | + shift = np.array([self.grid_shape[0] / 4, (self.grid_shape[1] - mesh_extents[1] / dx) / 2, 0.0]) |
89 | 91 | car = mesh_vertices + shift
|
90 |
| - self.grid_spacing = dx |
91 | 92 | self.car_cross_section = np.prod(mesh_extents[1:]) / dx**2
|
92 | 93 |
|
93 | 94 | return inlet, outlet, walls, car
|
94 | 95 |
|
95 | 96 | def setup_boundary_conditions(self):
|
96 | 97 | inlet, outlet, walls, car = self.define_boundary_indices()
|
97 |
| - bc_left = EquilibriumBC(rho=1.0, u=(self.wind_speed, 0.0, 0.0), indices=inlet) |
98 |
| - # bc_left = RegularizedBC('velocity', (self.wind_speed, 0.0, 0.0), indices=inlet) |
| 98 | + bc_left = RegularizedBC("velocity", profile=self.bc_profile(), indices=inlet) |
99 | 99 | bc_walls = FullwayBounceBackBC(indices=walls)
|
100 | 100 | bc_do_nothing = ExtrapolationOutflowBC(indices=outlet)
|
101 |
| - # bc_car = HalfwayBounceBackBC(mesh_vertices=car) |
102 |
| - bc_car = GradsApproximationBC(mesh_vertices=car) |
103 |
| - # bc_car = FullwayBounceBackBC(mesh_vertices=car) |
| 101 | + bc_car = FullwayBounceBackBC(mesh_vertices=car) |
104 | 102 | self.boundary_conditions = [bc_walls, bc_left, bc_do_nothing, bc_car]
|
105 | 103 |
|
106 |
| - def setup_boundary_masker(self): |
107 |
| - # check boundary condition list for duplicate indices before creating bc mask |
108 |
| - check_bc_overlaps(self.boundary_conditions, self.velocity_set.d, self.backend) |
109 |
| - |
110 |
| - indices_boundary_masker = IndicesBoundaryMasker( |
111 |
| - velocity_set=self.velocity_set, |
112 |
| - precision_policy=self.precision_policy, |
113 |
| - compute_backend=self.backend, |
114 |
| - ) |
115 |
| - # mesh_boundary_masker = MeshBoundaryMasker( |
116 |
| - # velocity_set=self.velocity_set, |
117 |
| - # precision_policy=self.precision_policy, |
118 |
| - # compute_backend=self.backend, |
119 |
| - # ) |
120 |
| - mesh_distance_boundary_masker = MeshDistanceBoundaryMasker( |
121 |
| - velocity_set=self.velocity_set, |
122 |
| - precision_policy=self.precision_policy, |
123 |
| - compute_backend=self.backend, |
124 |
| - ) |
125 |
| - bclist_other = self.boundary_conditions[:-1] |
126 |
| - bc_mesh = self.boundary_conditions[-1] |
127 |
| - dx = self.grid_spacing |
128 |
| - origin, spacing = (0, 0, 0), (dx, dx, dx) |
129 |
| - self.bc_mask, self.missing_mask = indices_boundary_masker(bclist_other, self.bc_mask, self.missing_mask) |
130 |
| - # self.bc_mask, self.missing_mask = mesh_boundary_masker(bc_mesh, origin, spacing, self.bc_mask, self.missing_mask) |
131 |
| - self.bc_mask, self.missing_mask, self.f_1 = mesh_distance_boundary_masker(bc_mesh, origin, spacing, self.bc_mask, self.missing_mask, self.f_1) |
132 |
| - |
133 | 104 | def initialize_fields(self):
|
134 | 105 | self.f_0 = initialize_eq(self.f_0, self.grid, self.velocity_set, self.precision_policy, self.backend)
|
135 | 106 | self.f_1 = initialize_eq(self.f_1, self.grid, self.velocity_set, self.precision_policy, self.backend)
|
136 | 107 |
|
137 |
| - def setup_stepper(self): |
138 |
| - self.stepper = IncompressibleNavierStokesStepper(self.omega, boundary_conditions=self.boundary_conditions, collision_type="KBC") |
| 108 | + def bc_profile(self): |
| 109 | + u_max = self.wind_speed |
| 110 | + # Get the grid dimensions for the y and z directions |
| 111 | + H_y = float(self.grid_shape[1] - 1) # Height in y direction |
| 112 | + H_z = float(self.grid_shape[2] - 1) # Height in z direction |
| 113 | + |
| 114 | + @wp.func |
| 115 | + def bc_profile_warp(index: wp.vec3i): |
| 116 | + # Poiseuille flow profile: parabolic velocity distribution |
| 117 | + y = self.precision_policy.store_precision.wp_dtype(index[1]) |
| 118 | + z = self.precision_policy.store_precision.wp_dtype(index[2]) |
| 119 | + |
| 120 | + # Calculate normalized distance from center |
| 121 | + y_center = y - (H_y / 2.0) |
| 122 | + z_center = z - (H_z / 2.0) |
| 123 | + r_squared = (2.0 * y_center / H_y) ** 2.0 + (2.0 * z_center / H_z) ** 2.0 |
| 124 | + |
| 125 | + # Parabolic profile: u = u_max * (1 - r²) |
| 126 | + return wp.vec(u_max * wp.max(0.0, 1.0 - r_squared), 0.0, 0.0, 0.0, 0.0, length=5) |
| 127 | + |
| 128 | + def bc_profile_jax(): |
| 129 | + y = jnp.arange(self.grid_shape[1]) |
| 130 | + z = jnp.arange(self.grid_shape[2]) |
| 131 | + Y, Z = jnp.meshgrid(y, z, indexing="ij") |
| 132 | + |
| 133 | + # Calculate normalized distance from center |
| 134 | + y_center = Y - (H_y / 2.0) |
| 135 | + z_center = Z - (H_z / 2.0) |
| 136 | + r_squared = (2.0 * y_center / H_y) ** 2.0 + (2.0 * z_center / H_z) ** 2.0 |
| 137 | + |
| 138 | + # Parabolic profile for x velocity, zero for y and z |
| 139 | + u_x = u_max * jnp.maximum(0.0, 1.0 - r_squared) |
| 140 | + u_y = jnp.zeros_like(u_x) |
| 141 | + u_z = jnp.zeros_like(u_x) |
| 142 | + |
| 143 | + return jnp.stack([u_x, u_y, u_z]) |
| 144 | + |
| 145 | + if self.backend == ComputeBackend.JAX: |
| 146 | + return bc_profile_jax |
| 147 | + elif self.backend == ComputeBackend.WARP: |
| 148 | + return bc_profile_warp |
| 149 | + |
| 150 | + def setup_stepper(self, omega): |
| 151 | + self.stepper, self.f_0, self.f_1, self.bc_mask, self.missing_mask = IncompressibleNavierStokesStepper( |
| 152 | + f_0=self.f_0, |
| 153 | + f_1=self.f_1, |
| 154 | + bc_mask=self.bc_mask, |
| 155 | + missing_mask=self.missing_mask, |
| 156 | + omega=omega, |
| 157 | + boundary_conditions=self.boundary_conditions, |
| 158 | + collision_type="BGK", |
| 159 | + ) |
139 | 160 |
|
140 | 161 | def run(self, num_steps, print_interval, post_process_interval=100):
|
141 | 162 | # Setup the operator for computing surface forces at the interface of the specified BC
|
@@ -236,8 +257,7 @@ def plot_drag_coefficient(self):
|
236 | 257 | print_interval = 1000
|
237 | 258 |
|
238 | 259 | # Set up Reynolds number and deduce relaxation time (omega)
|
239 |
| - # Re = 50000.0 |
240 |
| - Re = 500000000000.0 |
| 260 | + Re = 5000.0 |
241 | 261 | clength = grid_size_x - 1
|
242 | 262 | visc = wind_speed * clength / Re
|
243 | 263 | omega = 1.0 / (3.0 * visc + 0.5)
|
|
0 commit comments