FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
PanelLoadLaw.h
1#pragma once
2
14#include "LocalThroughFlow.h"
15#include "PanelLoadTypes.h"
16#include "ScreenKF2012.h"
17#include "ScreenMF2022.h"
18#include "TwineCrossFlow.h"
19
20#include <fhsim/ISimObjectLogger.h>
21#include <fhsim/simobject/ISimObjectCreator.h>
22#include <cmath>
23#include <limits>
24#include <sstream>
25#include <stdexcept>
26#include <string>
27#include <variant>
28
29namespace hydrodynamics
30{
31
32namespace panel_load_law_detail
33{
35template<class T>
36PanelLoad<T> ZeroLoad(unsigned flags)
37{
38 PanelLoad<T> load {};
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);
43 load.flags = flags;
44 return load;
45}
46} // namespace panel_load_law_detail
47
49using PanelLoadLaw = std::variant<LocalThroughFlow, ScreenKF2012, ScreenMF2022, TwineCrossFlow>;
50
67template<class T>
68PanelLoad<T> Evaluate(const PanelLoadLaw& law, const PanelGeometry<T>& panel, const Netting<T>& netting,
69 const Fluid& fluid, const FlowSample<T>& flow)
70{
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);
76}
77
78namespace panel_load_law_detail
79{
80
82inline bool AcceptsAtPanel(const LocalThroughFlow& /*law*/)
83{
84 return false;
85}
86
88inline bool AcceptsAtPanel(const ScreenKF2012& law)
89{
90 return !law.useInduction;
91}
92
94inline bool AcceptsAtPanel(const ScreenMF2022& /*law*/)
95{
97}
98
100inline bool AcceptsAtPanel(const TwineCrossFlow& /*law*/)
101{
102 return true;
103}
104
106inline std::string FormatValue(double value)
107{
108 std::ostringstream text;
109 text << value;
110 return text.str();
111}
112
117[[noreturn]] inline void RejectParameter(ISimObjectCreator* creator, const std::string& parameter, const std::string& message)
118{
119 creator->ReportParameterError(parameter, message);
120 throw std::invalid_argument(message);
121}
122
142inline void ReadHarmonics(ISimObjectCreator* creator, const std::string& model, double& a3, double& b4)
143{
144 creator->GetDoubleParam("HydroA3", &a3, a3);
145 creator->GetDoubleParam("HydroB4", &b4, b4);
146
147 if (!std::isfinite(a3)) {
148 RejectParameter(creator, "HydroA3", "HydroA3 = " + FormatValue(a3) + " is not finite for HydroModel " + model + ".");
149 }
150 if (!std::isfinite(b4)) {
151 RejectParameter(creator, "HydroB4", "HydroB4 = " + FormatValue(b4) + " is not finite for HydroModel " + model + ".");
152 }
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);
159 }
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);
168 }
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);
175 }
176}
177
185inline TwineCrossFlow ReadTwineCrossFlow(ISimObjectCreator* creator)
186{
187 TwineCrossFlow law;
188 creator->GetDoubleParam("Ct_nominal", &law.ct, law.ct);
189 creator->GetDoubleParam("CnKnots_nominal", &law.cnKnots, law.cnKnots);
190 return law;
191}
192
193} // namespace panel_load_law_detail
194
205inline bool Accepts(const PanelLoadLaw& law, FlowReference reference)
206{
207 if (reference == FlowReference::FreeStream) {
208 return true;
209 }
210 return std::visit([](const auto& heldLaw) { return panel_load_law_detail::AcceptsAtPanel(heldLaw); }, law);
211}
212
222inline Fluid ReadFluid(ISimObjectCreator* creator)
223{
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.");
230 };
231 rejectUnlessPositive("Rho", fluid.rho);
232 rejectUnlessPositive("Nu", fluid.nu);
233 return fluid;
234}
235
254inline PanelLoadLaw ReadPanelLoadLaw(ISimObjectCreator* creator)
255{
256 std::string model;
257 creator->GetStringParam("HydroModel", model, "LocalThroughFlow");
258
259 if (model == "LocalThroughFlow") {
260 LocalThroughFlow law;
261 panel_load_law_detail::ReadHarmonics(creator, model, law.a3, law.b4);
262 return law;
263 }
264 if (model == "ScreenKF2012") {
265 ScreenKF2012 law;
266 panel_load_law_detail::ReadHarmonics(creator, model, law.a3, law.b4);
267 creator->GetBoolParam("HydroInduction", &law.useInduction, law.useInduction);
268 return law;
269 }
270 if (model == "ScreenMF2022") {
271 return ScreenMF2022 {};
272 }
273 if (model == "TwineCrossFlow") {
274 return panel_load_law_detail::ReadTwineCrossFlow(creator);
275 }
276
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);
280}
281
295inline bool WarnIfOutsideFitRange(const PanelLoadLaw& law, double solidity, ISimObjectLogger& logger)
296{
297 if (!std::holds_alternative<ScreenMF2022>(law) || ScreenMF2022::InValidSolidityRange(solidity)) {
298 return false;
299 }
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).";
305 }
306 logger.LogParameterInfo("HydroModel", message);
307 return true;
308}
309
310} // namespace hydrodynamics
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