From f55eacfa8576071be91e4d490167502eeb55a25e Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Sun, 21 Jun 2020 10:37:45 +0200 Subject: [PATCH] glide_computer example added --- example/CMakeLists.txt | 12 +- example/glide_computer.cpp | 561 ++++++++++++++++++++++++ src/include/units/bits/external/hacks.h | 8 + 3 files changed, 576 insertions(+), 5 deletions(-) create mode 100644 example/glide_computer.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 1074642c..a563a003 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -26,17 +26,15 @@ function(add_example target) endfunction() add_example(avg_speed) -add_example(hello_units) -add_example(measurement) -add_example(total_energy) -add_example(unknown_dimension) add_example(box_example) add_example(capacitor_time_curve) add_example(clcpp_response) add_example(conversion_factor) -add_example(kalman_filter-alpha_beta_filter_example2) add_example(experimental_angle) add_example(foot_pound_second) +add_example(glide_computer) +add_example(hello_units) +add_example(kalman_filter-alpha_beta_filter_example2) conan_check_testing(linear_algebra) add_example(linear_algebra) @@ -45,4 +43,8 @@ target_link_libraries(linear_algebra $,CONAN_PKG::linear_algebra,linear_algebra::linear_algebra> ) +add_example(measurement) +add_example(total_energy) +add_example(unknown_dimension) + add_subdirectory(alternative_namespaces) diff --git a/example/glide_computer.cpp b/example/glide_computer.cpp new file mode 100644 index 00000000..2501fce7 --- /dev/null +++ b/example/glide_computer.cpp @@ -0,0 +1,561 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include +#include +#include +#include +#include +#include +#include + +// An example of a really simplified tactical glide computer +// Simplifications: +// - glider 100% clean and with full factory performance (brand new painting) +// - no influence of the ballast (pilot weight, water, etc) to glider performance +// - only one point on a glider polar curve +// - no influence of bank angle (during circling) on a glider performance +// - no wind +// - constant thermals strength +// - thermals exactly where and when we need them ;-) +// - no airspaces +// - Earth is flat +// - ground level changes linearly between airports +// - no ground obstacles (i.e. mountains) to pass +// - flight path exactly on a shortest possible line to destination + +// horizontal/vertical vector +namespace { + +using namespace units; + +enum class direction { + horizontal, + vertical +}; + +template + requires Quantity || QuantityPoint +class vector { +public: + using magnitude_type = Q; + static constexpr direction dir = D; + + vector() = default; + explicit constexpr vector(Q m): magnitude_(std::move(m)) {} + + // template + // requires QuantityPoint + // explicit constexpr vector(QQ q) + // : magnitude_(std::move(q)) {} + + constexpr const Q& magnitude() const & { return magnitude_; } + constexpr Q magnitude() const && { return std::move(magnitude_); } + + template + [[nodiscard]] constexpr vector operator-() const + requires requires(QQ q) { -q; } + // requires requires { -magnitude(); } // TODO gated by gcc-9 (fixed in gcc-10) + { + return vector(-magnitude()); + } + +#if __GNUC__ >= 10 + + template + [[nodiscard]] friend constexpr auto operator<=>(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() <=> rhs.magnitude(); } + { + return lhs.magnitude() <=> rhs.magnitude(); + } + + template + [[nodiscard]] friend constexpr bool operator==(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() == rhs.magnitude(); } + { + return lhs.magnitude() == rhs.magnitude(); + } + +#else + + template + [[nodiscard]] friend constexpr auto operator==(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() == rhs.magnitude(); } + { + return lhs.magnitude() == rhs.magnitude(); + } + + template + [[nodiscard]] friend constexpr auto operator!=(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() != rhs.magnitude(); } + { + return !(lhs == rhs); + } + + template + [[nodiscard]] friend constexpr auto operator<(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() < rhs.magnitude(); } + { + return lhs.magnitude() < rhs.magnitude(); + } + + template + [[nodiscard]] friend constexpr auto operator>(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() > rhs.magnitude(); } + { + return rhs < lhs; + } + + template + [[nodiscard]] friend constexpr auto operator<=(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() <= rhs.magnitude(); } + { + return !(rhs < lhs); + } + + template + [[nodiscard]] friend constexpr auto operator>=(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() >= rhs.magnitude(); } + { + return !(lhs < rhs); + } + +#endif + + // template + // requires Quantity // TODO gated by gcc-9 (fixed in gcc-10) + template + requires Quantity + friend std::basic_ostream& operator<<(std::basic_ostream& os, const vector& v) + { + return os << v.magnitude(); + } + +private: + Q magnitude_{}; +}; + +template +[[nodiscard]] constexpr auto operator+(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() + rhs.magnitude(); } +{ + using ret_type = decltype(lhs.magnitude() + rhs.magnitude()); + return vector(lhs.magnitude() + rhs.magnitude()); +} + +template +[[nodiscard]] constexpr auto operator-(const vector& lhs, const vector& rhs) + requires requires { lhs.magnitude() - rhs.magnitude(); } +{ + using ret_type = decltype(lhs.magnitude() - rhs.magnitude()); + return vector(lhs.magnitude() - rhs.magnitude()); +} + +// template +// constexpr AUTO operator*(const vector& lhs, const vector& rhs) +// requires requires { lhs.magnitude() * rhs.magnitude(); } +// { +// using ret_type = decltype(lhs.magnitude() * rhs.magnitude()); +// return vector(lhs.magnitude() * rhs.magnitude()); +// } + +template +[[nodiscard]] constexpr double operator/(const vector& lhs, const vector& rhs) + requires equivalent_dim && + requires { lhs.magnitude() / rhs.magnitude(); } +{ + return lhs.magnitude() / rhs.magnitude(); +} + +template +inline constexpr bool is_vector = false; +template +inline constexpr bool is_vector> = true; + +} // namespace + +template +struct fmt::formatter> : formatter { + template + auto format(const vector& v, FormatContext& ctx) + { + return formatter::format(v.magnitude(), ctx); + } +}; + +// custom types +namespace { + +using namespace units::physical; + +// concepts do not scale :-( +// template +// concept QPLength = QuantityPoint && Length; + +using distance = vector, direction::horizontal>; +using height = vector, direction::vertical>; +using altitude = vector, direction::vertical>; + +using velocity = vector, direction::horizontal>; +using rate_of_climb = vector, direction::vertical>; + +using time_point = quantity_point; +using duration = si::time; + +template +std::basic_ostream& operator<<(std::basic_ostream& os, const altitude& a) +{ + return os << a.magnitude().relative() << " AMSL"; +} + +} // namespace + +template<> +struct fmt::formatter : formatter> { + template + auto format(altitude a, FormatContext& ctx) + { + formatter>::format(a.magnitude().relative(), ctx); + return format_to(ctx.out(), " AMSL"); + } +}; + +// gliders database +namespace { + +using namespace units::physical::si::literals; +using namespace units::physical::international::literals; + +struct glider { + struct polar_point { + velocity v; + rate_of_climb climb; + }; + + std::string name; + std::array polar; +}; + +auto get_gliders() +{ + const std::array gliders = { + glider{"SZD-30 Pirat", {velocity(83q_km_per_h), rate_of_climb(-0.7389q_m_per_s)}}, + glider{"SZD-51 Junior", {velocity(80q_km_per_h), rate_of_climb(-0.6349q_m_per_s)}}, + glider{"SZD-48 Jantar Std 3", {velocity(110q_km_per_h), rate_of_climb(-0.77355q_m_per_s)}}, + glider{"SZD-56 Diana", {velocity(110q_km_per_h), rate_of_climb(-0.63657q_m_per_s)}} + }; + return gliders; +} + +constexpr double glide_ratio(const glider::polar_point& polar) +{ + return polar.v / -polar.climb; +} + +template + requires std::same_as, glider> +void print(const R& gliders) +{ + std::cout << "Gliders:\n"; + std::cout << "========\n"; + for(const auto& g : gliders) { + std::cout << "- Name: " << g.name << "\n"; + std::cout << "- Polar:\n"; + for(const auto& p : g.polar) + fmt::print(" * {:%.4Q %q} @ {:%.1Q %q} -> {:.1f}\n", p.climb, p.v, glide_ratio(g.polar[0])); + std::cout << "\n"; + } +} + +} // namespace + +// weather conditions +namespace { + +struct weather { + height cloud_base; + rate_of_climb thermal_strength; +}; + +auto get_weather_conditions() +{ + const std::array weather_conditions = { + std::pair("Good", weather{height(1900q_m), rate_of_climb(4.3q_m_per_s)}), + std::pair("Medium", weather{height(1550q_m), rate_of_climb(2.8q_m_per_s)}), + std::pair("Bad", weather{height(850q_m), rate_of_climb(1.8q_m_per_s)}) + }; + return weather_conditions; +} + +template + requires std::same_as, std::pair> +void print(const R& conditions) +{ + std::cout << "Weather:\n"; + std::cout << "========\n"; + for(const auto& c : conditions) { + std::cout << "- Kind: " << c.first << "\n"; + const auto& w = c.second; + std::cout << "- Cloud base: " << fmt::format("{:%.0Q %q}", w.cloud_base) << " AGL\n"; + std::cout << "- Thermals strength: " << fmt::format("{:%.1Q %q}", w.thermal_strength) << "\n"; + std::cout << "\n"; + } +} + +} // namespace + +// task +namespace { + +struct waypoint { + std::string name; + altitude alt; +}; + +struct task { + waypoint start; + waypoint finish; + distance dist; +}; + +void print(const task& t) +{ + std::cout << "Task:\n"; + std::cout << "=====\n"; + std::cout << "- Start: " << t.start.name << " (" << fmt::format("{:%.1Q %q}", t.start.alt) << ")\n"; + std::cout << "- Finish: " << t.finish.name << " (" << fmt::format("{:%.1Q %q}", t.finish.alt) << ")\n"; + std::cout << "- Distance: " << fmt::format("{:%.1Q %q}", t.dist) << "\n"; + std::cout << "\n"; +} + +} // namespace + +// safety params +namespace { + +struct safety { + height min_agl_height; +}; + +void print(const safety& s) +{ + std::cout << "Safety:\n"; + std::cout << "=======\n"; + std::cout << "- Min AGL separation: " << fmt::format("{:%.0Q %q}", s.min_agl_height) << "\n"; + std::cout << "\n"; +} + +} // namespace + +// tow parameters +namespace { + +struct aircraft_tow { + height height_agl; + rate_of_climb performance; +}; + +void print(const aircraft_tow& tow) +{ + std::cout << "Tow:\n"; + std::cout << "====\n"; + std::cout << " - Type: aircraft\n" ; + std::cout << " - Height: " << fmt::format("{:%.0Q %q}", tow.height_agl) <<"\n"; + std::cout << " - Performance: " << fmt::format("{:%.1Q %q}", tow.performance) <<"\n"; + std::cout << "\n"; +} + +} // namespace + +// tactical flight computer basics +namespace { + +struct position { + time_point timestamp; + distance dist; + altitude alt; +}; + +constexpr altitude terrain_level_alt(const task& t, distance pos) +{ + const height alt_diff = t.finish.alt - t.start.alt; + return t.start.alt + height(alt_diff.magnitude() * (pos / t.dist)); +} + +constexpr height agl(altitude glider_alt, altitude terrain_level) +{ + return glider_alt - terrain_level; +} + +position takeoff(const task& t) +{ + const position new_pos{{}, {}, t.start.alt}; + return new_pos; +} + +void print(std::string_view phase_name, const position& pos, const position& new_pos) +{ + fmt::print("| {:<12} | {:>9%.1Q %q} (Total: {:>9%.1Q %q}) | {:>8%.1Q %q} (Total: {:>8%.1Q %q}) | {:>7%.0Q %q} ({:>6%.0Q %q}) |\n", + phase_name, + quantity_cast(new_pos.timestamp - pos.timestamp), quantity_cast(new_pos.timestamp.relative()), + new_pos.dist - pos.dist, new_pos.dist, + new_pos.alt - pos.alt, new_pos.alt); +} + +position tow(const position& pos, const aircraft_tow& at) +{ + const duration d = at.height_agl.magnitude() / at.performance.magnitude(); + const position new_pos{pos.timestamp + d, pos.dist, pos.alt + at.height_agl}; + + print("Tow", pos, new_pos); + return new_pos; +} + +position circle(const position& pos, const glider& g, const weather& w, const task& t, height& height_to_gain) +{ + const height h_agl = agl(pos.alt, terrain_level_alt(t, pos.dist)); + const height circle_height = std::min(w.cloud_base - h_agl, height_to_gain); + const rate_of_climb circling_rate = w.thermal_strength + g.polar[0].climb; + const duration d = circle_height.magnitude() / circling_rate.magnitude(); + const position new_pos{pos.timestamp + d, pos.dist, pos.alt + circle_height}; + + height_to_gain = height_to_gain - circle_height; // TODO -= + + print("Circle", pos, new_pos); + return new_pos; +} + +// Returns `x` of the intersection of a glide line and a terrain line. +// y = -x / glide_ratio + pos.alt; +// y = (finish_alt - ground_alt) / dist_to_finish * x + ground_alt + min_agl_height; +constexpr distance glide_distance(const position& pos, const glider& g, const task& t, const safety& s, altitude ground_alt) +{ + const auto dist_to_finish = t.dist - pos.dist; + return distance((ground_alt + s.min_agl_height - pos.alt).magnitude() / ((ground_alt - t.finish.alt) / dist_to_finish - 1 / glide_ratio(g.polar[0]))); +} + +position glide(const position& pos, const glider& g, const task& t, const safety& s) +{ + const auto ground_alt = terrain_level_alt(t, pos.dist); + const auto dist = glide_distance(pos, g, t, s, ground_alt); + const auto alt = ground_alt + s.min_agl_height; + const auto dist3d = sqrt(pow<2>(dist.magnitude()) + pow<2>((pos.alt - alt).magnitude())); + const duration d = dist3d / g.polar[0].v.magnitude(); + const position new_pos{pos.timestamp + d, pos.dist + dist, terrain_level_alt(t, pos.dist + dist) + s.min_agl_height}; + + print("Glide", pos, new_pos); + return new_pos; +} + +position final_glide(const position& pos, const glider& g, const task& t) +{ + const auto dist = t.dist - pos.dist; + const auto dist3d = sqrt(pow<2>(dist.magnitude()) + pow<2>((pos.alt - t.finish.alt).magnitude())); + const duration d = dist3d / g.polar[0].v.magnitude(); + const position new_pos{pos.timestamp + d, pos.dist + dist, t.finish.alt}; + + print("Final Glide", pos, new_pos); + return new_pos; +} + +void estimate(const glider& g, const weather& w, const task& t, const safety& s, const aircraft_tow& at) +{ + // ready to takeoff + position pos = takeoff(t); + + // estimate aircraft towing + pos = tow(pos, at); + + // estimate the altitude needed to reach the finish line from this place + const altitude final_glide_alt = t.finish.alt + height(t.dist.magnitude() / glide_ratio(g.polar[0])); + + // how much height we still need to gain in the thermalls to reach the destination? + height height_to_gain = final_glide_alt - pos.alt; + + do { + // glide to the next thermall + pos = glide(pos, g, t, s); + + // circle in a thermall to gain height + pos = circle(pos, g, w, t, height_to_gain); + } + while(height_to_gain > height(0q_m)); + + // final glide + pos = final_glide(pos, g, t); +} + +} // namespace + +// example +namespace { + +void example() +{ + const auto gliders = get_gliders(); + print(gliders); + + const auto weather_conditions = get_weather_conditions(); + print(weather_conditions); + + const task t = { + waypoint{"EPPR", altitude(quantity_point(16q_ft))}, + waypoint{"EPGI", altitude(quantity_point(115q_ft))}, + distance(81.7q_km) + }; + print(t); + + const safety s = {height(300q_m)}; + print(s); + + const aircraft_tow tow = {height(400q_m), rate_of_climb(1.6q_m_per_s)}; + print(tow); + + for(const auto& g : gliders) { + for(const auto& c : weather_conditions) { + std::string txt = "Scenario: Glider = " + g.name + ", Weather = " + c.first; + std::cout << txt << "\n"; + fmt::print("{0:=^{1}}\n\n", "", txt.size()); + fmt::print("| {:<12} | {:^28} | {:^26} | {:^21} |\n", "Flight phase", "Duration", "Distance", "Height"); + fmt::print("|{0:-^14}|{0:-^30}|{0:-^28}|{0:-^23}|\n", ""); + + estimate(g, c.second, t, s, tow); + + std::cout << "\n\n"; + } + } +} + +} // namespace + +int main() +{ + try { + example(); + } + catch (const std::exception& ex) { + std::cerr << "Unhandled std exception caught: " << ex.what() << '\n'; + } + catch (...) { + std::cerr << "Unhandled unknown exception caught\n"; + } +} diff --git a/src/include/units/bits/external/hacks.h b/src/include/units/bits/external/hacks.h index 5e4f451d..2d3ec9b1 100644 --- a/src/include/units/bits/external/hacks.h +++ b/src/include/units/bits/external/hacks.h @@ -45,6 +45,7 @@ #else #include +#include #endif @@ -85,6 +86,13 @@ namespace std { using concepts::totally_ordered; using concepts::totally_ordered_with; + namespace ranges { + + using ::ranges::forward_range; + using ::ranges::range_value_t; + + } + // missing in Range-v3 template concept floating_point = std::is_floating_point_v;