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