|
| 1 | +using TrixiParticles |
| 2 | +using OrdinaryDiffEqLowStorageRK |
| 3 | + |
| 4 | +# ========================================================================================== |
| 5 | +# 2D Periodic Poiseuille Flow with Carreau-Yasuda Viscosity |
| 6 | +# |
| 7 | +# This example simulates pressure-driven channel flow with the |
| 8 | +# `ViscosityCarreauYasuda` non-Newtonian viscosity model. The driving pressure |
| 9 | +# gradient is represented by an equivalent body acceleration. |
| 10 | +# ========================================================================================== |
| 11 | + |
| 12 | +# ========================================================================================== |
| 13 | +# ==== Resolution |
| 14 | +ny = 50 |
| 15 | + |
| 16 | +# ========================================================================================== |
| 17 | +# ==== Experiment Setup |
| 18 | +t_end_factor = 0.1 |
| 19 | +eps_factor = 1.0 |
| 20 | +sound_speed_factor = 60.0 |
| 21 | +initial_condition_mode = "analytical" |
| 22 | +power_law_index = 1.0 |
| 23 | + |
| 24 | +channel_height = 1.0 |
| 25 | +channel_length = 6.0 * channel_height |
| 26 | +particle_spacing = channel_height / ny |
| 27 | +boundary_layers = 5 |
| 28 | + |
| 29 | +fluid_density = 1000.0 |
| 30 | +nu0 = 1.0e-3 |
| 31 | +nu_inf = 0.0 |
| 32 | +lambda_exponent = 2.0 |
| 33 | +reynolds_number = 200.0 |
| 34 | + |
| 35 | +reference_velocity = reynolds_number * nu0 / channel_height |
| 36 | +pressure_gradient = 8.0 * fluid_density * reference_velocity^2 / |
| 37 | + (reynolds_number * channel_height) |
| 38 | +acceleration_x = pressure_gradient / fluid_density |
| 39 | +carreau_time_constant = channel_height / max(reference_velocity, eps()) |
| 40 | + |
| 41 | +t_end = t_end_factor * channel_height / max(reference_velocity, eps()) |
| 42 | +tspan = (0.0, t_end) |
| 43 | + |
| 44 | +if !(initial_condition_mode in ("newtonian", "analytical", "zero")) |
| 45 | + throw(ArgumentError("initial condition mode must be \"newtonian\", " * |
| 46 | + "\"analytical\", or \"zero\"")) |
| 47 | +end |
| 48 | + |
| 49 | +# ========================================================================================== |
| 50 | +# ==== Analytical Solution |
| 51 | +function linear_interpolation_clamped(x, y, interpolation_point) |
| 52 | + interpolation_point <= first(x) && return first(y) |
| 53 | + interpolation_point >= last(x) && return last(y) |
| 54 | + |
| 55 | + i = searchsortedlast(x, interpolation_point) |
| 56 | + x0, x1 = x[i], x[i + 1] |
| 57 | + y0, y1 = y[i], y[i + 1] |
| 58 | + return y0 + (y1 - y0) * (interpolation_point - x0) / (x1 - x0) |
| 59 | +end |
| 60 | + |
| 61 | +function carreau_yasuda_kinematic_viscosity(shear_rate, nu0, nu_inf, |
| 62 | + time_constant, lambda_exponent, |
| 63 | + power_law_index) |
| 64 | + return nu_inf + (nu0 - nu_inf) * |
| 65 | + (1.0 + (time_constant * shear_rate)^lambda_exponent)^((power_law_index - |
| 66 | + 1.0) / |
| 67 | + lambda_exponent) |
| 68 | +end |
| 69 | + |
| 70 | +function solve_shear_rate_from_stress(shear_stress, density, nu0, nu_inf, |
| 71 | + time_constant, lambda_exponent, |
| 72 | + power_law_index) |
| 73 | + shear_stress <= 0 && return 0.0 |
| 74 | + |
| 75 | + residual(shear_rate) = density * |
| 76 | + carreau_yasuda_kinematic_viscosity(shear_rate, nu0, |
| 77 | + nu_inf, |
| 78 | + time_constant, |
| 79 | + lambda_exponent, |
| 80 | + power_law_index) * |
| 81 | + shear_rate - shear_stress |
| 82 | + |
| 83 | + lower = 0.0 |
| 84 | + upper = 1.0 |
| 85 | + while residual(upper) < 0.0 |
| 86 | + upper *= 2.0 |
| 87 | + upper > 1.0e12 && |
| 88 | + error("failed to bracket shear-rate root for shear stress $shear_stress") |
| 89 | + end |
| 90 | + |
| 91 | + for _ in 1:120 |
| 92 | + middle = 0.5 * (lower + upper) |
| 93 | + residual_middle = residual(middle) |
| 94 | + |
| 95 | + if abs(residual_middle) <= 1.0e-12 * max(shear_stress, 1.0) |
| 96 | + return middle |
| 97 | + elseif residual_middle > 0 |
| 98 | + upper = middle |
| 99 | + else |
| 100 | + lower = middle |
| 101 | + end |
| 102 | + end |
| 103 | + |
| 104 | + return 0.5 * (lower + upper) |
| 105 | +end |
| 106 | + |
| 107 | +function analytical_ux_profile(y_positions, power_law_index, channel_height, |
| 108 | + density, nu0, nu_inf, time_constant, |
| 109 | + lambda_exponent, pressure_gradient) |
| 110 | + distances_to_centerline = sort(unique(abs.(y_positions .- 0.5 * channel_height))) |
| 111 | + shear_rates = similar(distances_to_centerline) |
| 112 | + |
| 113 | + for i in eachindex(distances_to_centerline) |
| 114 | + shear_stress = pressure_gradient * distances_to_centerline[i] |
| 115 | + shear_rates[i] = solve_shear_rate_from_stress(shear_stress, density, nu0, |
| 116 | + nu_inf, time_constant, |
| 117 | + lambda_exponent, |
| 118 | + power_law_index) |
| 119 | + end |
| 120 | + |
| 121 | + velocity_at_distance = zeros(length(distances_to_centerline)) |
| 122 | + for i in (lastindex(distances_to_centerline) - 1):-1:firstindex(distances_to_centerline) |
| 123 | + ds = distances_to_centerline[i + 1] - distances_to_centerline[i] |
| 124 | + velocity_at_distance[i] = velocity_at_distance[i + 1] + |
| 125 | + 0.5 * (shear_rates[i + 1] + shear_rates[i]) * ds |
| 126 | + end |
| 127 | + |
| 128 | + velocity = Vector{Float64}(undef, length(y_positions)) |
| 129 | + for (i, y) in pairs(y_positions) |
| 130 | + distance_to_centerline = abs(y - 0.5 * channel_height) |
| 131 | + velocity[i] = linear_interpolation_clamped(distances_to_centerline, |
| 132 | + velocity_at_distance, |
| 133 | + distance_to_centerline) |
| 134 | + end |
| 135 | + |
| 136 | + return velocity |
| 137 | +end |
| 138 | + |
| 139 | +function newtonian_ux(y, channel_height, density, nu0, pressure_gradient) |
| 140 | + return pressure_gradient / (2.0 * density * nu0) * y * (channel_height - y) |
| 141 | +end |
| 142 | + |
| 143 | +function l2_velocity_error(system::TrixiParticles.AbstractFluidSystem, |
| 144 | + dv_ode, du_ode, v_ode, u_ode, semi, t) |
| 145 | + v = TrixiParticles.wrap_v(v_ode, system, semi) |
| 146 | + u = TrixiParticles.wrap_u(u_ode, system, semi) |
| 147 | + |
| 148 | + y_positions = [TrixiParticles.current_coords(u, system, particle)[2] |
| 149 | + for particle in TrixiParticles.eachparticle(system)] |
| 150 | + analytical_velocity = analytical_ux_profile(y_positions, power_law_index, |
| 151 | + channel_height, fluid_density, nu0, |
| 152 | + nu_inf, carreau_time_constant, |
| 153 | + lambda_exponent, pressure_gradient) |
| 154 | + |
| 155 | + squared_error = 0.0 |
| 156 | + squared_reference = 0.0 |
| 157 | + for (i, particle) in enumerate(TrixiParticles.eachparticle(system)) |
| 158 | + ux = TrixiParticles.current_velocity(v, system, particle)[1] |
| 159 | + squared_error += (ux - analytical_velocity[i])^2 |
| 160 | + squared_reference += analytical_velocity[i]^2 |
| 161 | + end |
| 162 | + |
| 163 | + return sqrt(squared_error / nparticles(system)) / |
| 164 | + (sqrt(squared_reference / nparticles(system)) + eps()) |
| 165 | +end |
| 166 | +l2_velocity_error(system, dv_ode, du_ode, v_ode, u_ode, semi, t) = nothing |
| 167 | + |
| 168 | +function max_velocity_error(system::TrixiParticles.AbstractFluidSystem, |
| 169 | + dv_ode, du_ode, v_ode, u_ode, semi, t) |
| 170 | + v = TrixiParticles.wrap_v(v_ode, system, semi) |
| 171 | + u = TrixiParticles.wrap_u(u_ode, system, semi) |
| 172 | + |
| 173 | + y_positions = [TrixiParticles.current_coords(u, system, particle)[2] |
| 174 | + for particle in TrixiParticles.eachparticle(system)] |
| 175 | + analytical_velocity = analytical_ux_profile(y_positions, power_law_index, |
| 176 | + channel_height, fluid_density, nu0, |
| 177 | + nu_inf, carreau_time_constant, |
| 178 | + lambda_exponent, pressure_gradient) |
| 179 | + |
| 180 | + error = 0.0 |
| 181 | + for (i, particle) in enumerate(TrixiParticles.eachparticle(system)) |
| 182 | + ux = TrixiParticles.current_velocity(v, system, particle)[1] |
| 183 | + error = max(error, abs(ux - analytical_velocity[i])) |
| 184 | + end |
| 185 | + |
| 186 | + return error |
| 187 | +end |
| 188 | +max_velocity_error(system, dv_ode, du_ode, v_ode, u_ode, semi, t) = nothing |
| 189 | + |
| 190 | +# ========================================================================================== |
| 191 | +# ==== Initial Condition |
| 192 | +initial_velocity = if initial_condition_mode == "zero" |
| 193 | + (0.0, 0.0) |
| 194 | +elseif initial_condition_mode == "analytical" |
| 195 | + y_reference = collect(range(0.0, channel_height; length=4 * ny + 1)) |
| 196 | + ux_reference = analytical_ux_profile(y_reference, power_law_index, |
| 197 | + channel_height, fluid_density, nu0, |
| 198 | + nu_inf, carreau_time_constant, |
| 199 | + lambda_exponent, pressure_gradient) |
| 200 | + x -> (linear_interpolation_clamped(y_reference, ux_reference, x[2]), 0.0) |
| 201 | +else |
| 202 | + x -> (newtonian_ux(x[2], channel_height, fluid_density, nu0, |
| 203 | + pressure_gradient), 0.0) |
| 204 | +end |
| 205 | + |
| 206 | +# ========================================================================================== |
| 207 | +# ==== Fluid |
| 208 | +tank = RectangularTank(particle_spacing, (channel_length, channel_height), |
| 209 | + (channel_length, channel_height), fluid_density; |
| 210 | + n_layers=boundary_layers, |
| 211 | + faces=(false, false, true, true), |
| 212 | + velocity=initial_velocity, |
| 213 | + coordinates_eltype=Float64) |
| 214 | + |
| 215 | +smoothing_length = 1.2 * particle_spacing |
| 216 | +smoothing_kernel = SchoenbergCubicSplineKernel{2}() |
| 217 | + |
| 218 | +sound_speed = sound_speed_factor * reference_velocity |
| 219 | +state_equation = StateEquationCole(; sound_speed, |
| 220 | + reference_density=fluid_density, |
| 221 | + exponent=7) |
| 222 | + |
| 223 | +viscosity = ViscosityCarreauYasuda(; nu0, nu_inf, |
| 224 | + lambda=carreau_time_constant, |
| 225 | + a=lambda_exponent, |
| 226 | + n=power_law_index, |
| 227 | + epsilon=max(0.5, eps_factor) * particle_spacing) |
| 228 | + |
| 229 | +fluid_system = WeaklyCompressibleSPHSystem(tank.fluid; |
| 230 | + density_calculator=ContinuityDensity(), |
| 231 | + state_equation, |
| 232 | + smoothing_kernel, |
| 233 | + smoothing_length, |
| 234 | + acceleration=(acceleration_x, 0.0), |
| 235 | + viscosity, |
| 236 | + shifting_technique=nothing) |
| 237 | + |
| 238 | +# ========================================================================================== |
| 239 | +# ==== Boundary |
| 240 | +boundary_model = BoundaryModelDummyParticles(tank.boundary.density, |
| 241 | + tank.boundary.mass, |
| 242 | + AdamiPressureExtrapolation(), |
| 243 | + smoothing_kernel, |
| 244 | + smoothing_length; |
| 245 | + state_equation, |
| 246 | + viscosity) |
| 247 | + |
| 248 | +boundary_system = WallBoundarySystem(tank.boundary, boundary_model) |
| 249 | + |
| 250 | +# ========================================================================================== |
| 251 | +# ==== Simulation |
| 252 | +periodic_box = PeriodicBox(min_corner=[0.0, -10.0 * channel_height], |
| 253 | + max_corner=[channel_length, 10.0 * channel_height]) |
| 254 | +neighborhood_search = GridNeighborhoodSearch{2}(; periodic_box) |
| 255 | + |
| 256 | +semi = Semidiscretization(fluid_system, boundary_system; |
| 257 | + neighborhood_search, |
| 258 | + parallelization_backend=PolyesterBackend()) |
| 259 | + |
| 260 | +ode = semidiscretize(semi, tspan) |
| 261 | + |
| 262 | +n_label = replace(string(power_law_index), "." => "p") |
| 263 | +output_directory = joinpath("out_poiseuille_carreau", "n_$power_law_index") |
| 264 | +result_filename = "validation_run_poiseuille_carreau_2d_n_$(n_label)_ny_$ny" |
| 265 | + |
| 266 | +info_callback = InfoCallback(interval=200) |
| 267 | +saving_callback = SolutionSavingCallback(; dt=t_end / 20, |
| 268 | + prefix="", |
| 269 | + output_directory) |
| 270 | +pp_callback = PostprocessCallback(; dt=t_end / 20, |
| 271 | + output_directory, |
| 272 | + filename=result_filename, |
| 273 | + l2_velocity_error, |
| 274 | + max_velocity_error, |
| 275 | + write_csv=true) |
| 276 | +cfl_callback = StepsizeCallback(cfl=0.2) |
| 277 | +callbacks = CallbackSet(info_callback, saving_callback, pp_callback, |
| 278 | + cfl_callback, UpdateCallback()) |
| 279 | + |
| 280 | +sol = solve(ode, RDPK3SpFSAL35(); |
| 281 | + abstol=1.0e-7, |
| 282 | + reltol=1.0e-4, |
| 283 | + save_everystep=false, |
| 284 | + callback=callbacks, |
| 285 | + maxiters=2_000_000); |
0 commit comments