|
| 1 | +# Flow past a circular cylinder (vortex street), Tafuni et al. (2018). |
| 2 | +# Other literature using this validation: |
| 3 | +# Vacandio et al. (2013), Marrone et al. (2013), Calhoun (2002), Liu et al. (1998) |
| 4 | + |
| 5 | +using TrixiParticles |
| 6 | +using OrdinaryDiffEq |
| 7 | + |
| 8 | +# ========================================================================================== |
| 9 | +# ==== Resolution |
| 10 | +factor_d = 0.08 # Resolution in the paper is `0.01` (5M particles) |
| 11 | + |
| 12 | +const cylinder_diameter = 0.1 |
| 13 | +particle_spacing = factor_d * cylinder_diameter |
| 14 | + |
| 15 | +# Make sure that the kernel support of fluid particles at a boundary is always fully sampled |
| 16 | +boundary_layers = 4 |
| 17 | + |
| 18 | +# Make sure that the kernel support of fluid particles at an open boundary is always |
| 19 | +# fully sampled. |
| 20 | +# Note: Due to the dynamics at the inlets and outlets of open boundaries, |
| 21 | +# it is recommended to use `open_boundary_layers > boundary_layers` |
| 22 | +open_boundary_layers = 8 |
| 23 | + |
| 24 | +# ========================================================================================== |
| 25 | +# ==== Experiment Setup |
| 26 | +tspan = (0.0, 1.5) |
| 27 | + |
| 28 | +# Boundary geometry and initial fluid particle positions |
| 29 | +domain_size = (25 * cylinder_diameter, 20 * cylinder_diameter) |
| 30 | + |
| 31 | +flow_direction = [1.0, 0.0] |
| 32 | +reynolds_number = 200 |
| 33 | +const prescribed_velocity = 1.0 |
| 34 | +const fluid_density = 1000.0 |
| 35 | +sound_speed = 10 * prescribed_velocity |
| 36 | + |
| 37 | +boundary_size = (domain_size[1] + 2 * particle_spacing * open_boundary_layers, |
| 38 | + domain_size[2]) |
| 39 | + |
| 40 | +pipe = RectangularTank(particle_spacing, domain_size, boundary_size, fluid_density, |
| 41 | + n_layers=boundary_layers, velocity=[prescribed_velocity, 0.0], |
| 42 | + faces=(false, false, true, true)) |
| 43 | + |
| 44 | +# Shift pipe walls in negative x-direction for the inflow |
| 45 | +pipe.boundary.coordinates[1, :] .-= particle_spacing * open_boundary_layers |
| 46 | + |
| 47 | +n_buffer_particles = 10 * pipe.n_particles_per_dimension[2]^(ndims(pipe.fluid) - 1) |
| 48 | + |
| 49 | +cylinder_center = (5 * cylinder_diameter, domain_size[2] / 2) |
| 50 | +cylinder = SphereShape(particle_spacing, cylinder_diameter / 2, |
| 51 | + cylinder_center, fluid_density, sphere_type=RoundSphere()) |
| 52 | + |
| 53 | +fluid = setdiff(pipe.fluid, cylinder) |
| 54 | + |
| 55 | +# ========================================================================================== |
| 56 | +# ==== Fluid |
| 57 | +wcsph = true |
| 58 | + |
| 59 | +h_factor = 1.5 |
| 60 | +smoothing_length = h_factor * particle_spacing |
| 61 | +smoothing_kernel = WendlandC2Kernel{2}() |
| 62 | + |
| 63 | +fluid_density_calculator = ContinuityDensity() |
| 64 | + |
| 65 | +kinematic_viscosity = prescribed_velocity * cylinder_diameter / reynolds_number |
| 66 | + |
| 67 | +state_equation = StateEquationCole(; sound_speed, reference_density=fluid_density, |
| 68 | + exponent=7) |
| 69 | +viscosity = ViscosityAdami(nu=kinematic_viscosity) |
| 70 | + |
| 71 | +if wcsph |
| 72 | + density_diffusion = DensityDiffusionMolteniColagrossi(delta=0.1) |
| 73 | + |
| 74 | + fluid_system = WeaklyCompressibleSPHSystem(fluid, fluid_density_calculator, |
| 75 | + state_equation, smoothing_kernel, |
| 76 | + density_diffusion=density_diffusion, |
| 77 | + smoothing_length, viscosity=viscosity, |
| 78 | + pressure_acceleration=tensile_instability_control, |
| 79 | + buffer_size=n_buffer_particles) |
| 80 | +else |
| 81 | + state_equation = nothing |
| 82 | + fluid_system = EntropicallyDampedSPHSystem(fluid, smoothing_kernel, smoothing_length, |
| 83 | + sound_speed, viscosity=viscosity, |
| 84 | + density_calculator=fluid_density_calculator, |
| 85 | + buffer_size=n_buffer_particles) |
| 86 | +end |
| 87 | + |
| 88 | +# ========================================================================================== |
| 89 | +# ==== Open Boundary |
| 90 | +function velocity_function2d(pos, t) |
| 91 | + return SVector(prescribed_velocity, 0.0) |
| 92 | +end |
| 93 | + |
| 94 | +open_boundary_model = BoundaryModelTafuni() |
| 95 | + |
| 96 | +boundary_type_in = InFlow() |
| 97 | +plane_in = ([0.0, 0.0], [0.0, domain_size[2]]) |
| 98 | +inflow = BoundaryZone(; plane=plane_in, plane_normal=flow_direction, open_boundary_layers, |
| 99 | + density=fluid_density, particle_spacing, |
| 100 | + boundary_type=boundary_type_in) |
| 101 | + |
| 102 | +reference_velocity_in = velocity_function2d |
| 103 | +# At the inlet, neither pressure nor density are prescribed; instead, |
| 104 | +# these values are extrapolated from the fluid domain |
| 105 | +reference_pressure_in = nothing |
| 106 | +reference_density_in = nothing |
| 107 | +open_boundary_in = OpenBoundarySPHSystem(inflow; fluid_system, |
| 108 | + boundary_model=open_boundary_model, |
| 109 | + buffer_size=n_buffer_particles, |
| 110 | + reference_density=reference_density_in, |
| 111 | + reference_pressure=reference_pressure_in, |
| 112 | + reference_velocity=reference_velocity_in) |
| 113 | + |
| 114 | +boundary_type_out = OutFlow() |
| 115 | + |
| 116 | +plane_out = ([pipe.fluid_size[1], 0.0], [pipe.fluid_size[1], domain_size[2]]) |
| 117 | +outflow = BoundaryZone(; plane=plane_out, plane_normal=(-flow_direction), |
| 118 | + open_boundary_layers, density=fluid_density, particle_spacing, |
| 119 | + boundary_type=boundary_type_out) |
| 120 | + |
| 121 | +# At the outlet, we allow the flow to exit freely without imposing any boundary conditions |
| 122 | +reference_velocity_out = nothing |
| 123 | +reference_pressure_out = nothing |
| 124 | +reference_density_out = nothing |
| 125 | +open_boundary_out = OpenBoundarySPHSystem(outflow; fluid_system, |
| 126 | + boundary_model=open_boundary_model, |
| 127 | + buffer_size=n_buffer_particles, |
| 128 | + reference_density=reference_density_out, |
| 129 | + reference_pressure=reference_pressure_out, |
| 130 | + reference_velocity=reference_velocity_out) |
| 131 | +# ========================================================================================== |
| 132 | +# ==== Boundary |
| 133 | +boundary_model = BoundaryModelDummyParticles(pipe.boundary.density, pipe.boundary.mass, |
| 134 | + AdamiPressureExtrapolation(), |
| 135 | + state_equation=state_equation, |
| 136 | + smoothing_kernel, smoothing_length) |
| 137 | + |
| 138 | +boundary_system_wall = BoundarySPHSystem(pipe.boundary, boundary_model) |
| 139 | + |
| 140 | +boundary_model_cylinder = BoundaryModelDummyParticles(cylinder.density, cylinder.mass, |
| 141 | + AdamiPressureExtrapolation(), |
| 142 | + state_equation=state_equation, |
| 143 | + viscosity=viscosity, |
| 144 | + smoothing_kernel, smoothing_length) |
| 145 | + |
| 146 | +boundary_system_cylinder = BoundarySPHSystem(cylinder, boundary_model_cylinder) |
| 147 | + |
| 148 | +# ========================================================================================== |
| 149 | +# ==== Postprocessing |
| 150 | +circle = SphereShape(particle_spacing, (cylinder_diameter + particle_spacing) / 2, |
| 151 | + cylinder_center, fluid_density, n_layers=1, |
| 152 | + sphere_type=RoundSphere()) |
| 153 | + |
| 154 | +# Points for pressure interpolation, located at the wall interface |
| 155 | +const data_points = copy(circle.coordinates) |
| 156 | +const center = SVector(cylinder_center) |
| 157 | + |
| 158 | +calculate_lift_force(system, v_ode, u_ode, semi, t) = nothing |
| 159 | +function calculate_lift_force(system::TrixiParticles.FluidSystem, v_ode, u_ode, semi, t) |
| 160 | + force = zero(SVector{ndims(system), eltype(system)}) |
| 161 | + |
| 162 | + values = interpolate_points(data_points, semi, system, v_ode, u_ode; cut_off_bnd=false, |
| 163 | + clip_negative_pressure=false) |
| 164 | + pressure = Array(values.pressure) |
| 165 | + |
| 166 | + for i in axes(data_points, 2) |
| 167 | + point = TrixiParticles.current_coords(data_points, system, i) |
| 168 | + |
| 169 | + # F = ∑ -p_i * A_i * n_i |
| 170 | + force -= pressure[i] * particle_spacing .* |
| 171 | + TrixiParticles.normalize(point - center) |
| 172 | + end |
| 173 | + |
| 174 | + return 2 * force[2] / (fluid_density * prescribed_velocity^2 * cylinder_diameter) |
| 175 | +end |
| 176 | + |
| 177 | +calculate_drag_force(system, v_ode, u_ode, semi, t) = nothing |
| 178 | +function calculate_drag_force(system::TrixiParticles.FluidSystem, v_ode, u_ode, semi, t) |
| 179 | + force = zero(SVector{ndims(system), eltype(system)}) |
| 180 | + |
| 181 | + values = interpolate_points(data_points, semi, system, v_ode, u_ode; cut_off_bnd=false, |
| 182 | + clip_negative_pressure=false) |
| 183 | + pressure = Array(values.pressure) |
| 184 | + |
| 185 | + for i in axes(data_points, 2) |
| 186 | + point = TrixiParticles.current_coords(data_points, system, i) |
| 187 | + |
| 188 | + # F = ∑ -p_i * A_i * n_i |
| 189 | + force -= pressure[i] * particle_spacing .* |
| 190 | + TrixiParticles.normalize(point - center) |
| 191 | + end |
| 192 | + |
| 193 | + return 2 * force[1] / (fluid_density * prescribed_velocity^2 * cylinder_diameter) |
| 194 | +end |
| 195 | + |
| 196 | +# ========================================================================================== |
| 197 | +# ==== Simulation |
| 198 | +min_corner = minimum(pipe.boundary.coordinates .- 5 * particle_spacing, dims=2) |
| 199 | +max_corner = maximum(pipe.boundary.coordinates .+ 5 * particle_spacing, dims=2) |
| 200 | +cell_list = FullGridCellList(; min_corner, max_corner) |
| 201 | + |
| 202 | +neighborhood_search = GridNeighborhoodSearch{2}(; cell_list, |
| 203 | + update_strategy=ParallelUpdate()) |
| 204 | + |
| 205 | +semi = Semidiscretization(fluid_system, open_boundary_in, open_boundary_out, |
| 206 | + boundary_system_wall, boundary_system_cylinder; |
| 207 | + neighborhood_search=neighborhood_search, |
| 208 | + parallelization_backend=PolyesterBackend()) |
| 209 | + |
| 210 | +ode = semidiscretize(semi, tspan) |
| 211 | + |
| 212 | +info_callback = InfoCallback(interval=100) |
| 213 | + |
| 214 | +output_directory = "out" |
| 215 | + |
| 216 | +saving_callback = SolutionSavingCallback(; dt=0.02, prefix="", output_directory) |
| 217 | + |
| 218 | +pp_callback = PostprocessCallback(; dt=0.02, |
| 219 | + f_l=calculate_lift_force, f_d=calculate_drag_force, |
| 220 | + output_directory, filename="resulting_force", |
| 221 | + write_csv=true, write_file_interval=10) |
| 222 | + |
| 223 | +extra_callback = nothing |
| 224 | + |
| 225 | +callbacks = CallbackSet(info_callback, UpdateCallback(), saving_callback, |
| 226 | + ParticleShiftingCallback(), # To obtain a near-uniform particle distribution in the wake |
| 227 | + pp_callback, extra_callback) |
| 228 | + |
| 229 | +sol = solve(ode, RDPK3SpFSAL35(), |
| 230 | + abstol=1e-6, # Default abstol is 1e-6 (may need to be tuned to prevent boundary penetration) |
| 231 | + reltol=1e-4, # Default reltol is 1e-3 (may need to be tuned to prevent boundary penetration) |
| 232 | + dtmax=1e-2, # Limit stepsize to prevent crashing |
| 233 | + save_everystep=false, callback=callbacks); |
0 commit comments