OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
openswmm_api_common.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2//
3// Copyright 2026 Caleb Buahin
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
28
29#ifndef OPENSWMM_API_COMMON_HPP
30#define OPENSWMM_API_COMMON_HPP
31
32#include "SWMMEngine.hpp"
33#include "UnitConversion.hpp"
35
36#include <cstring>
37#include <algorithm>
38#include <cmath>
39
50static inline openswmm::SWMMEngine* to_engine(SWMM_Engine e) noexcept {
51 return static_cast<openswmm::SWMMEngine*>(e);
52}
53
54// ============================================================================
55// Unit conversion at the C API boundary
56// ============================================================================
57//
58// The engine stores all state in internal SWMM units (ft, cfs, ft³, ft/s, sec).
59// The public C API, like legacy swmm5.c, exposes values in the project's
60// configured *display* units (selected by [OPTIONS] FLOW_UNITS): getters return
61// display units, setters accept display units.
62//
63// getter: display = internal * UCF(q) -> to_display()
64// setter: internal = display * UCF_inv(q) -> to_internal()
65//
66// Setters multiply by the precomputed reciprocal (UCF_inv / Qcf_inv) rather than
67// dividing by UCF — no runtime division at any boundary site. The two roundings
68// can leave a written value an ulp or two off when it is read back (98 m →
69// 97.99999999999999). Setters pick the internal value whose display reads back
70// unchanged where one exists (exact_inverse); for the rest, getters snap the
71// result to the nearest 15-significant-digit decimal within two ulps
72// (snap_display), so a value written with up to 15 significant digits always
73// reads back exactly. `q` is a
74// openswmm::ucf::Quantity code (LENGTH, FLOW, VOLUME, LANDAREA, RAINFALL,
75// EVAPRATE, RAINDEPTH, ...). Templated on the context type so the include of the
76// full SimulationContext definition is deferred to the impl translation units.
77
90static inline double snap_display(double v) noexcept {
91 static constexpr double kPow10[23] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
92 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
93 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22};
94 const double a = std::fabs(v);
95 if (!(a >= 1e-8 && a < 1e15))
96 return v;
97 // Scale to a 15-digit integer; correct log10 when it lands on the wrong side of a power of ten.
98 int k = 14 - static_cast<int>(std::floor(std::log10(a)));
99 double m = std::nearbyint(a * kPow10[k]);
100 if (m >= 1e15 && k > 0)
101 m = std::nearbyint(a * kPow10[--k]);
102 else if (m < 1e14 && k < 22)
103 m = std::nearbyint(a * kPow10[++k]);
104 const double r = std::copysign(m / kPow10[k], v); // powers up to 1e22 are exact
105 const double two_ulps = 2.0 * (std::nextafter(a, HUGE_VAL) - a);
106 return std::fabs(r - v) <= two_ulps ? r : v;
107}
108
120template <class Ctx> static inline double to_display(const Ctx& ctx, int q, double v) noexcept {
121 const double f = openswmm::ucf::UCF(q, ctx.options);
122 return f == 1.0 ? v : snap_display(v * f);
123}
124
138static inline double exact_inverse(double internal, double factor, double display) noexcept {
139 if (internal * factor == display || !std::isfinite(internal))
140 return internal;
141 const double up = std::nextafter(internal, HUGE_VAL);
142 if (up * factor == display)
143 return up;
144 const double down = std::nextafter(internal, -HUGE_VAL);
145 return down * factor == display ? down : internal;
146}
147
159template <class Ctx> static inline double to_internal(const Ctx& ctx, int q, double v) noexcept {
160 return exact_inverse(v * openswmm::ucf::UCF_inv(q, ctx.options),
161 openswmm::ucf::UCF(q, ctx.options), v);
162}
163
164#define CHECK_HANDLE(e) \
165 do { \
166 if (!(e)) \
167 return SWMM_ERR_BADHANDLE; \
168 } while (0)
169#define CHECK_INDEX(cond) \
170 do { \
171 if (!(cond)) \
172 return SWMM_ERR_BADINDEX; \
173 } while (0)
174
175// ============================================================================
176// Lifecycle state guards
177// ============================================================================
178
180#define CHECK_GEOMETRY(ctx) \
181 do { \
182 if ((ctx).state != openswmm::EngineState::BUILDING && \
183 (ctx).state != openswmm::EngineState::OPENED) \
184 return SWMM_ERR_LIFECYCLE; \
185 } while (0)
186
192#define CHECK_EDITABLE(ctx) \
193 do { \
194 if ((ctx).state != openswmm::EngineState::BUILDING && \
195 (ctx).state != openswmm::EngineState::OPENED) \
196 return SWMM_ERR_LIFECYCLE; \
197 } while (0)
198
200#define CHECK_INITIAL_COND(ctx) \
201 do { \
202 if ((ctx).state != openswmm::EngineState::BUILDING && \
203 (ctx).state != openswmm::EngineState::OPENED && \
204 (ctx).state != openswmm::EngineState::INITIALIZED) \
205 return SWMM_ERR_LIFECYCLE; \
206 } while (0)
207
209#define CHECK_RUNNING(ctx) \
210 do { \
211 if ((ctx).state != openswmm::EngineState::RUNNING) \
212 return SWMM_ERR_LIFECYCLE; \
213 } while (0)
214
216#define CHECK_READABLE(ctx) \
217 do { \
218 if ((ctx).state == openswmm::EngineState::CREATED || \
219 (ctx).state == openswmm::EngineState::CLOSED || \
220 (ctx).state == openswmm::EngineState::ERROR_STATE) \
221 return SWMM_ERR_LIFECYCLE; \
222 } while (0)
223
224#endif /* OPENSWMM_API_COMMON_HPP */
Global unit conversion factors — matching legacy SWMM Ucf[]/Qcf[].
double UCF(int quantity, const SimulationOptions &opts)
Get unit conversion factor for a quantity.
Definition UnitConversion.cpp:39
double UCF_inv(int quantity, const SimulationOptions &opts)
Reciprocal of UCF — multiply display by this to get internal units.
Definition UnitConversion.cpp:53
void * SWMM_Engine
Opaque handle to an OpenSWMM Engine instance.
Definition openswmm_callbacks.h:55
OpenSWMM Engine — primary transparent C API (master header).