FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
0023 — CenterWeight requires an Environment but damps against absolute velocity, ignoring the current
ID 0023
Class BUG
Severity 3
Status blocked
Models Trawl/CenterWeight
Found 2026-09-17 test-suite pass (baseline review of FISH-0001)
Decision needed Should CenterWeight's quadratic drag act on the velocity relative to the water, so the weight is swept by current and waves like every other wetted object in the library, or is the present ground-referenced damping deliberate? If it is deliberate, the Environment requirement at src/trawl/CenterWeight.cpp:171-173 should be dropped and the header must say the weight does not feel the current. Second, may the drag reference "area" be corrected to an area (pi*D^2/4) in the same pass — it is currently the diameter, which is dimensionally not a force — or must the present magnitude be preserved for calibration reasons?

Severity 3: wrong numbers reach the user. A center weight in a 2.5 m/s current does not drift, and the drag expression is dimensionally not a force, so both the trajectory and the force magnitude are wrong in every trawl model that uses the object.

Evidence

tests/in/CenterWeight/CenterWeight_in.xml:10-11 configures a constant current of 2.5,0,0 — purely along x — and the weight starts at rest (tests/in/CenterWeight/CenterWeight_in.xml:32-35, C.Vel = "0,0,0"). The recorded baseline tests/in/CenterWeight/CenterWeight_ref.csv:3-5 nevertheless has Pos_0 == Pos_1 and Vel_0 == Vel_1 at every logged time, and the x drift after 10 s is 1.585397e-01 m — 16 cm, against 25 m of water travel:

1.000000e+01;1.585397e-01;1.585397e-01;3.991589e+01;...;1.648160e-02;1.648160e-02;4.669402e+00;...

The x/*y* symmetry is not itself the defect — it follows from the symmetric test loads, C.Force1 = C.Force2 = C.Force3 = "10,10,10" (tests/in/CenterWeight/CenterWeight_in.xml:25-27), i.e. 30 N in each axis. What the symmetry proves is that the current contributes nothing: an x-only current of 2.5 m/s cannot leave the x and y responses identical to 7 digits.

src/trawl/CenterWeight.cpp:50-56 is the reason — the drag is built from the state velocity alone, with no water velocity anywhere:

const double* const adVel = adX + m_IStateVel;
double absVel = sfh::math::Norm(adVel, 3);
double adDamping[3];
for (unsigned short i = 0; i < 3; i++) {
adDamping[i] = -0.5 * 1025 * m_radius * 2 * 1.2 * absVel * adVel[i];
}

m_environment is fetched and its absence is a hard error (src/trawl/CenterWeight.cpp:171-173), but grep -n m_environment src/trawl/CenterWeight.* returns only that acquisition and the member declaration (src/trawl/CenterWeight.h:128): GetParticleVelocity is never called. Only the seafloor resource is actually used (src/trawl/CenterWeight.cpp:60). Compare the sibling object one directory over, which does it the intended way — src/trawl/TrawlDoorBase.cpp:270-275:

m_environment->GetParticleVelocity(dT, ma_adXPos_n, m_waterVel_n);
sfh::sim::RotateToLocal(m_waterVel_n, ma_mdR, ma_adWaterVel_d);
for (int i = 0; i < 3; i++)
ma_adRelVel_d[i] = ma_adXVel_d[i] - ma_adWaterVel_d[i];

The baseline is reproduced exactly by the absolute-velocity law, which rules out any possibility that the current enters somewhere else. With Mass = 1000 and Density = 7500 (tests/in/CenterWeight/CenterWeight_in.xml:17,19): volume 0.133333, m_radius = 0.316920, m_weight = 8469.30 N (src/trawl/CenterWeight.cpp:36-38) and C = 0.5*1025*2*m_radius*1.2 = 389.812. Terminal fall speed from C*v^2 = 8469.30 + 30 is 4.669431 m/s against the recorded 4.669402, and the transverse speed from 30 = C*|v|*v_x is 1.648170e-02 against the recorded 1.648160e-02. Had the drag used the relative velocity, Vel_0 would sit near the current speed, not at 1.6 cm/s.

Two further observations about the same three lines, cheap to settle in the same pass and part of the decision above rather than separate items:

  • The reference "area" is m_radius * 2, a length. 0.5*rho*D*Cd*|v|*v has units kg/s^2, not newtons; the sphere's projected area is pi*D^2/4 = 0.315537 m^2 here versus the 0.633841 m used, so the drag is also about a factor two too large for this configuration (and wrong by a different factor for any other size). The same expression is duplicated in the Jacobian, src/trawl/CenterWeight.cpp:135, so both must change together.
  • Cd = 1.2 is hard-coded, and so is rho = 1025 — three times, at :55, :38 and :135, while the door reads its Rho from a parameter (tests/in/TrawlDoor/TrawlDoor_in.xml:22). Not worth an item on its own, but a fix pass here should not leave the water density in three literals.

The same absolute-velocity pattern appears in src/trawl/TrawlDoorBase.cpp:319-331 (AddDampingForces), on top of the door's proper relative-velocity hydrodynamics, where it is plausibly an intentional numerical damper. That is not part of this item, but the owner's answer above should say which of the two readings applies there.

Effect

A center weight is a heavy body on the ground-gear of a double trawl; in the real rig it is swept aft by the tow and by the current together with everything else. In FhSim it is the only wetted object in the trawl library that does not feel the water it is required to be submerged in:

  • Its trajectory is wrong whenever the current is non-zero. The object reaches equilibrium against a ground-referenced damper, so it hangs almost straight below its release point (16 cm in 10 s here) instead of drifting with the flow, and the error grows with the current.
  • Because the drag opposes absolute motion, a weight resting in a uniform current feels no force from that current, so it also transmits none to the sweeps and bridles connected through Force1..Force3 — the coupling a double-trawl study is built to observe.
  • The magnitude of the drag it does produce is wrong by a factor that depends on the weight's size, because the reference area is a diameter.
  • The object errors out at load time if no Environment is present (src/trawl/CenterWeight.cpp:171-173) while never reading it. A user who provides an Environment, as examples/input/Example.xml does, is entitled to conclude the weight uses it.
  • The header promises no more than "a mass object with gravitational forces and three external forces" (src/trawl/CenterWeight.h:14) and documents neither the drag nor the current, so nothing in the documentation contradicts the code — which is why this went unnoticed. The documentation half must be fixed whichever way the decision goes.

Possible fix

  1. Use the relative velocity (the expected answer). In OdeFcn, after reading the states, call m_environment->GetParticleVelocity(dT, adX + m_IStatePos, waterVel) as src/trawl/TrawlDoorBase.cpp:270 does, form relVel = vel - waterVel, and drive both absVel and the damping at src/trawl/CenterWeight.cpp:52-56 from it. No rotation is needed — the weight has no orientation, so everything stays in NED. OdeFcn is const and m_environment is a plain pointer, so this compiles unchanged. The Jacobian at :116-146 stays correct in form: d(relVel)/d(vel) = I, so the existing dDamp_dVij block is unchanged, evaluated at relVel instead of vel; the header's existing caveat about omitted terms (src/trawl/CenterWeight.h:96-97) still holds.
  2. Keep the ground-referenced damping and stop demanding an Environment. Delete src/trawl/CenterWeight.cpp:171-173, keep only the SeafloorForces lookup, and state in the header that the damping is against the ground, so the weight is a mass-spring-damper and not a hydrodynamic body. Defensible only if the object is meant as a load, not a drifting body; it leaves the trawl examples physically wrong in a documented way.
  3. Correct the reference area (pi*m_radius*m_radius for a sphere) at :55 and :135, independently of 1 or 2. Together with option 1 this changes the equilibrium velocities of every model containing a center weight.

Option 1 plus option 3 is the physically correct combination; option 1 alone still leaves the magnitude wrong, which is why both are in the decision.

Test that would prove it

tests/in/CenterWeight/ already contains the discriminating setup and only needs its loads made asymmetric. Set C.Force1 = C.Force2 = C.Force3 = "0,0,0" in tests/in/CenterWeight/CenterWeight_in.xml, keep Current.Vector = "2.5,0,0" and log Pos/Vel: today Vel_0 stays at 0 and the weight falls straight down; after option 1 Vel_0 climbs toward 2.5 m/s and Pos_0 grows by tens of metres, while Pos_1 stays zero — a difference of metres, far outside any tolerance question. Keep the existing symmetric case as a second case if its numbers are wanted, but re-record it: option 1 changes it too.

A second, sharper case for option 3: a neutrally buoyant weight (Density = "1025", so m_weight is zero) released at rest in the same current reaches a terminal velocity exactly equal to the current, whatever the drag constant, which then also checks that the relative velocity — not the absolute one — is what the damping drives to zero.

Risk

Options 1 and 3 change the trajectory of every model containing a Trawl/CenterWeight: tests/in/CenterWeight/CenterWeight_ref.csv and CenterWeight.ref must be re-recorded, and the CenterWeight_Jacobian case (tests/CenterWeight_Test.cpp:14-33) must be re-checked — its relTol of 1e-2 covers omitted input-port and seafloor terms, and the water velocity adds no new state dependence, so it should still pass, but that is an assumption to verify rather than assert. examples/input/Example.xml has no baseline yet, so nothing else needs regenerating; FISH-0019 must be settled before that example can be run at all. No state, port, parameter or input-file name changes under any option, so no input file breaks. Option 2 changes no numbers but makes the wrong physics official.