14#include "LocalThroughFlow.h"
15#include "PanelLoadTypes.h"
16#include "ScreenKF2012.h"
17#include "ScreenMF2022.h"
18#include "TwineCrossFlow.h"
20#include <fhsim/ISimObjectLogger.h>
21#include <fhsim/simobject/ISimObjectCreator.h>
29namespace hydrodynamics
32namespace panel_load_law_detail
36PanelLoad<T> ZeroLoad(
unsigned flags)
39 for (
int k = 0; k < 3; k++)
40 load.force[k] = T(0.0);
41 load.drag = load.lift = load.theta = load.reynolds = T(0.0);
42 load.momentumDeficit = load.throughFlowSpeed = load.localCd = T(0.0);
49using PanelLoadLaw = std::variant<LocalThroughFlow, ScreenKF2012, ScreenMF2022, TwineCrossFlow>;
68PanelLoad<T> Evaluate(
const PanelLoadLaw& law,
const PanelGeometry<T>& panel,
const Netting<T>& netting,
69 const Fluid& fluid,
const FlowSample<T>& flow)
71 const T* u = flow.relativeVelocity;
72 const T speedSq = u[0] * u[0] + u[1] * u[1] + u[2] * u[2];
73 if (!(speedSq < std::numeric_limits<double>::infinity()))
74 return panel_load_law_detail::ZeroLoad<T>(kPanelFlagFlowNotFinite);
75 return std::visit([&](
const auto& heldLaw) {
return Evaluate(heldLaw, panel, netting, fluid, flow); }, law);
78namespace panel_load_law_detail
82inline bool AcceptsAtPanel(
const LocalThroughFlow& )
88inline bool AcceptsAtPanel(
const ScreenKF2012& law)
90 return !law.useInduction;
94inline bool AcceptsAtPanel(
const ScreenMF2022& )
100inline bool AcceptsAtPanel(
const TwineCrossFlow& )
106inline std::string FormatValue(
double value)
108 std::ostringstream text;
117[[noreturn]]
inline void RejectParameter(ISimObjectCreator* creator,
const std::string& parameter,
const std::string& message)
119 creator->ReportParameterError(parameter, message);
120 throw std::invalid_argument(message);
142inline void ReadHarmonics(ISimObjectCreator* creator,
const std::string& model,
double& a3,
double& b4)
144 creator->GetDoubleParam(
"HydroA3", &a3, a3);
145 creator->GetDoubleParam(
"HydroB4", &b4, b4);
147 if (!std::isfinite(a3)) {
148 RejectParameter(creator,
"HydroA3",
"HydroA3 = " + FormatValue(a3) +
" is not finite for HydroModel " + model +
".");
150 if (!std::isfinite(b4)) {
151 RejectParameter(creator,
"HydroB4",
"HydroB4 = " + FormatValue(b4) +
" is not finite for HydroModel " + model +
".");
153 if (a3 < kf2012::kMinA3) {
154 const std::string problem =
"HydroA3 = " + FormatValue(a3) +
" is below " + FormatValue(kf2012::kMinA3)
155 +
" for HydroModel " + model +
".";
156 const std::string reason =
" The slope of C_D(theta) / cd against cos(theta) at normal incidence, 1 + 8 a3,"
157 " is then negative, so C_D is no longer largest at normal incidence (KF2012 p. 223).";
158 RejectParameter(creator,
"HydroA3", problem + reason);
160 if (a3 > kf2012::kMaxA3) {
161 const std::string problem =
"HydroA3 = " + FormatValue(a3) +
" is above " + FormatValue(kf2012::kMaxA3)
162 +
" for HydroModel " + model +
".";
163 const std::string reason =
" The drag factor cos(theta)(1 - 4 a3 + 4 a3 cos^2 theta) of KF2012 Eq. 14 then"
164 " turns negative at grazing angles, theta above acos(sqrt((4 a3 - 1)/(4 a3))),"
165 " as on trawl panels nearly along the tow or cage side panels at 75 to 90 deg"
166 " to the current: the panel is pushed upstream and its wake speeds the flow up.";
167 RejectParameter(creator,
"HydroA3", problem + reason);
169 if (std::abs(b4) > kf2012::kMaxAbsB4) {
170 const std::string problem =
"HydroB4 = " + FormatValue(b4) +
" is outside -" + FormatValue(kf2012::kMaxAbsB4)
171 +
" to " + FormatValue(kf2012::kMaxAbsB4) +
" for HydroModel " + model +
".";
172 const std::string reason =
" The lift factor 2 b2 + 4 b4 cos(2 theta) of KF2012 Eq. 14 (b2 = 1) then changes"
173 " sign near 90 deg (b4 > 0.5) or near 0 deg (b4 < -0.5), so the lift reverses there.";
174 RejectParameter(creator,
"HydroB4", problem + reason);
185inline TwineCrossFlow ReadTwineCrossFlow(ISimObjectCreator* creator)
188 creator->GetDoubleParam(
"Ct_nominal", &law.ct, law.ct);
189 creator->GetDoubleParam(
"CnKnots_nominal", &law.cnKnots, law.cnKnots);
205inline bool Accepts(
const PanelLoadLaw& law, FlowReference reference)
207 if (reference == FlowReference::FreeStream) {
210 return std::visit([](
const auto& heldLaw) {
return panel_load_law_detail::AcceptsAtPanel(heldLaw); }, law);
222inline Fluid ReadFluid(ISimObjectCreator* creator)
224 Fluid fluid {1025.0, 1.19e-6};
225 creator->GetDoubleParam(
"Rho", &fluid.rho, fluid.rho);
226 creator->GetDoubleParam(
"Nu", &fluid.nu, fluid.nu);
227 const auto rejectUnlessPositive = [creator](
const std::string& parameter,
double value) {
228 if (!(std::isfinite(value) && value > 0.0))
229 panel_load_law_detail::RejectParameter(creator, parameter, parameter +
" = " + panel_load_law_detail::FormatValue(value) +
" must be finite and positive.");
231 rejectUnlessPositive(
"Rho", fluid.rho);
232 rejectUnlessPositive(
"Nu", fluid.nu);
254inline PanelLoadLaw ReadPanelLoadLaw(ISimObjectCreator* creator)
257 creator->GetStringParam(
"HydroModel", model,
"LocalThroughFlow");
259 if (model ==
"LocalThroughFlow") {
260 LocalThroughFlow law;
261 panel_load_law_detail::ReadHarmonics(creator, model, law.a3, law.b4);
264 if (model ==
"ScreenKF2012") {
266 panel_load_law_detail::ReadHarmonics(creator, model, law.a3, law.b4);
267 creator->GetBoolParam(
"HydroInduction", &law.useInduction, law.useInduction);
270 if (model ==
"ScreenMF2022") {
271 return ScreenMF2022 {};
273 if (model ==
"TwineCrossFlow") {
274 return panel_load_law_detail::ReadTwineCrossFlow(creator);
277 const std::string message =
"Unknown HydroModel \"" + model +
"\"; expected LocalThroughFlow, ScreenKF2012, ScreenMF2022 or TwineCrossFlow.";
278 creator->ReportParameterError(
"HydroModel", message);
279 throw std::invalid_argument(message);
295inline bool WarnIfOutsideFitRange(
const PanelLoadLaw& law,
double solidity, ISimObjectLogger& logger)
300 std::string message =
"Warning: ScreenMF2022 was fitted for solidities 0.18 to 0.36 (MF2022 Eq. 10); the solidity " + std::to_string(solidity) +
" is outside that range, so its coefficients are extrapolated.";
301 if (solidity < screen_mf2022::kMinSolidity) {
302 message +=
" Below " + std::to_string(screen_mf2022::kMinSolidity) +
" the angle fit of Eq. 10 gives a negative drag coefficient for some flow angles, so the solidity is clamped to " + std::to_string(screen_mf2022::kMinSolidity) +
".";
303 }
else if (solidity > screen_mf2022::kMaxSolidity) {
304 message +=
" Above " + std::to_string(screen_mf2022::kMaxSolidity) +
" the solidity is clamped to " + std::to_string(screen_mf2022::kMaxSolidity) +
", so the coefficients are evaluated there, not extrapolated further (MARE-0147).";
306 logger.LogParameterInfo(
"HydroModel", message);
static constexpr bool acceptsAtPanel
MF2022's coefficients include the panel's own induction.
Definition ScreenMF2022.h:77
static constexpr bool InValidSolidityRange(double solidity)
Definition ScreenMF2022.h:87