@@ -212,3 +212,127 @@ function compute_structure_acceleration!(dv, system, eachparticle,
212212
213213 return v
214214end
215+
216+ """
217+ ThrustCalculator(system::TotalLagrangianSPHSystem, semi;
218+ direction,
219+ eachparticle=eachparticle(system))
220+
221+ Functor that computes the instantaneous hydrodynamic force exerted by the fluid on a
222+ [`TotalLagrangianSPHSystem`](@ref), projected onto `direction`.
223+ It can be passed as a custom quantity to [`PostprocessCallback`](@ref).
224+
225+ For a fixed fluid-interacting structure in a channel flow, choose `direction` as the
226+ direction of useful force and multiply the recorded thrust by the corresponding reference
227+ speed to obtain useful mechanical power.
228+
229+ !!! warning "Experimental implementation"
230+ This is an experimental feature and may change in future releases.
231+
232+ # Arguments
233+ - `system`: The [`TotalLagrangianSPHSystem`](@ref) whose hydrodynamic force should be
234+ monitored.
235+ - `semi`: The [`Semidiscretization`](@ref) that contains `system`.
236+
237+ # Keywords
238+ - `direction`: Direction onto which the hydrodynamic force is projected. The vector is
239+ normalized internally.
240+ - `eachparticle=eachparticle(system)`: Iterator selecting which particles contribute.
241+
242+ # Examples
243+ ```jldoctest; output = false, setup = :(system = TotalLagrangianSPHSystem(RectangularShape(0.1, (3, 4), (0.1, 0.0), density=1.0); smoothing_kernel=WendlandC2Kernel{2}(), smoothing_length=1.0, young_modulus=1.0, poisson_ratio=1.0); semi = (; systems=(system,), parallelization_backend=SerialBackend()))
244+ # Create a thrust calculator in x-direction
245+ thrust_calculator = ThrustCalculator(system, semi; direction=SVector(1.0, 0.0))
246+
247+ # After postprocessing, retrieve the latest thrust value
248+ thrust = calculated_thrust(thrust_calculator)
249+
250+ # output
251+ 0.0
252+ ```
253+ """
254+ mutable struct ThrustCalculator{ELTYPE, DV, EP, D}
255+ thrust :: ELTYPE
256+ system_index :: Int
257+ dv :: DV
258+ eachparticle :: EP
259+ direction :: D
260+ end
261+
262+ # This should dispatch on `TotalLagrangianSPHSystem`, but this name is not yet defined
263+ # due to the include order.
264+ function ThrustCalculator (system:: AbstractStructureSystem , semi;
265+ direction, eachparticle= eachparticle (system))
266+ ELTYPE = eltype (system)
267+ system_index = system_indices (system, semi)
268+
269+ # Check vector length before converting to `SVector` to avoid extremely long
270+ # compile times when accidentally passing large vectors.
271+ if length (direction) != ndims (system)
272+ throw (ArgumentError (" length of `direction` must match the number of dimensions" ))
273+ end
274+ direction_ = SVector (Tuple (direction))
275+ if iszero (direction_)
276+ throw (ArgumentError (" `direction` must not be zero" ))
277+ end
278+ direction_ = normalize (direction_)
279+
280+ # Allocate buffer to write hydrodynamic accelerations for all particles.
281+ # `PostprocessCallback` transfers data to the CPU before calling custom quantities,
282+ # so this can be a regular `Array` even when the simulation is running on a GPU.
283+ dv = Array {ELTYPE, 2} (undef, (v_nvariables (system), nparticles (system)))
284+
285+ return ThrustCalculator (zero (ELTYPE), system_index, dv, eachparticle, direction_)
286+ end
287+
288+ function reset! (calculator:: ThrustCalculator )
289+ calculator. thrust = zero (calculator. thrust)
290+
291+ return calculator
292+ end
293+
294+ """
295+ calculated_thrust(calculator::ThrustCalculator)
296+
297+ Get the latest projected hydrodynamic force from a [`ThrustCalculator`](@ref).
298+ """
299+ function calculated_thrust (calculator:: ThrustCalculator )
300+ return calculator. thrust
301+ end
302+
303+ function (calculator:: ThrustCalculator )(system, dv_ode, du_ode, v_ode, u_ode, semi, t)
304+ if system_indices (system, semi) != calculator. system_index
305+ return nothing
306+ end
307+
308+ thrust = update_thrust! (system, calculator. eachparticle, calculator. direction,
309+ calculator. dv, v_ode, u_ode, semi, t)
310+
311+ calculator. thrust = thrust
312+
313+ return calculator. thrust
314+ end
315+
316+ function update_thrust! (system, eachparticle, direction, dv, v_ode, u_ode, semi, t)
317+ # Note that the systems and NHS have already been updated by the
318+ # `PostprocessCallback` before calling this function.
319+ @trixi_timeit timer () " calculate thrust" begin
320+ compute_structure_acceleration! (dv, system, eachparticle, true ,
321+ v_ode, u_ode, semi, t)
322+
323+ return projected_force (dv, system, eachparticle, direction)
324+ end
325+ end
326+
327+ function projected_force (dv, system, eachparticle, direction)
328+ force = zero (eltype (system))
329+
330+ # Note that this is a reduction, so we cannot use `@threaded` here.
331+ for particle in eachparticle
332+ dv_particle = extract_svector (dv, system, particle)
333+ force_particle = system. mass[particle] * dv_particle
334+ force += dot (force_particle, direction)
335+ end
336+
337+ return force
338+ end
0 commit comments