////
Copyright (c) 2024 The C++ Alliance, Inc. (https://cppalliance.org)

Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

Official repository: https://github.com/boostorg/website-v2-docs
////
= Aeronautical Engineering
:navtitle: Aeronautical Engineering

There are some specific challenges in the design and building of aircraft, that are less pronounced in other engineering disciples. These challenges include the size of the development teams, all working on one project, the strong presence of regulation and certification, and the unglamorous but mission-critical need for numerical correctness, stability, and traceability. Aerospace software lives for decades, must behave identically across platforms, and is scrutinized by certification authorities who care far more about _bounded error_ than raw speed.

This topic examines the pressure on software engineers when working in the aeronautical design space: a space where `sqrt(x*x + y*y)` might not be acceptable, whereas `boost::math::hypot(x, y);` works.

[square]
* <<Aeronautical Design Fundamentals>>
* <<Libraries>>
* <<Demonstrating Range Failure>>
* <<Demonstrating Underflow>>
* <<Demonstrating Order-Dependent Drift>>
* <<Next Steps>>
* <<Footnotes>>
* <<See Also>>

== Aeronautical Design Fundamentals

Boost Libraries have a number of components that can play important roles in building a co-operative (distributed) design system. These libraries won't give you a full “collaboration system” out of the box, but they provide powerful building blocks for networking, concurrency, data exchange, task distribution, serialization, and scripting integration — all foundational pieces of a distributed CAD/PDM/PLM system.

A cooperative aircraft design system must support:

. Distributed teams (multiple time zones, intermittent connectivity)

. Strong data integrity and traceability (certification pressure)

. Concurrent edits on large models (geometry, constraints, metadata)

. Heavy computation (FEA, CFD, optimization)

. Modular extensibility (tools evolve over decades)

image:aerospace-design.png[Aircraft design blueprint]

The modelling service component owns the "truth" of geometry and constraints. Conflict resolution is a well known challenge - engineer A moves an aircraft rib, whilst engineer B instead, increases the rib thickness. Something like this may well need to be flagged for human resolution

When building a system for use anywhere in the aerospace industry, it is a good idea to stick to these core principles:

[#footnote1-location]
. Command determinism - No hidden randomness and no time-based logic without injected clocks.

. Immutable inputs - Commands are value objects and model state is derived, not mutated ad hoc.

. Controlled time - Simulated time replaces wall-clock time.

. Replay-first mindset - Tests are replays and live execution is just “replay at real-time speed”.

. Time-Travel Debugging - Step forward _and_ backward through events.

. Section Cuts (an aerospace favorite) - Auto-generate _spanwise cuts_ link:#footnote1[(1)], diff cross-sections, and plot profiles.

*A deterministic simulation of engineering intent is what exactly what certification bodies care about.*

Design and simulation are a big part of the story. Then comes testing.

In aerospace terms: _if you can't replay it, you can't certify it_. An aircraft test harness must answer questions like:

* Can we replay every design change exactly?

* Are distributed services deterministic?

* Does concurrency produce identical results across runs?

* Can we detect regressions in geometry, constraints, and analysis?

* Can we reproduce a failure years later?

This goes far beyond unit tests.

One of Boost's quiet superpowers is numerical correctness without fanfare - the standard library often prioritizes _simplicity_ whereas Boost prioritizes _correctness_. Numerical errors are design flaws, not rounding accidents!

Note:: Acronyms are dense in aerospace. FEA is _Finite Element Analysis_ (essentially breaking complex structures into small elements for testing and behavior). CFD is _Computational Fluid Dynamics_  (for example, modelling turbulence). PLM is _Product Lifecycle Management_ (tracking changes, metadata, milestones) but can also mean _Part Load Management_. PDM is _Product Data Management_, managing exactly what data exists and which version is authoritative, but it can also mean _Power Distribution Module_. Context is mandatory - the unofficial rule is "if someone doesn't ask for clarification, they probably misunderstand"!

== Libraries

* boost:math[] : Provides carefully implemented, well-documented algorithms for elementary and special functions with known accuracy characteristics, correct handling of edge cases, and predictable behavior across compilers and architectures. Functions like `hypot`, robust inverse trig, stable polynomial evaluation, and well-behaved probability distributions eliminate entire classes of silent numerical failures that are notoriously hard to reproduce and even harder to certify. Equally important, this library supports a verification-first engineering culture. Its scale-aware comparisons, error bounds, and compatibility with multi-precision backends allow engineers to create reference implementations and numerical “gold standards” alongside high-performance code. This is exactly what certification demands: the ability to demonstrate not only that results are fast, but that they are correct _within defined tolerances_, repeatable years later, and defensible under audit. The library quietly delivers numerical trust, and avoid such things as _floating-point drift_ and _order-dependent drift_.

boost:multiprecision[] : For when extremely large or small numbers need to be calculated, with great precision.

image:aerospace-gear.png[Aircraft gear blueprint]

* boost:accumulators[] : This provides numerically stable statistics, preventing _summation_drift_ and an answer to _never sum large datasets naïvely_.

* boost:asio[] : A distributed design system must support robust, scalable communication — real-time updates, distributed computation requests, and client-server synchronization.

* boost:serialization[] and boost:interprocess[] :Save/load complex design models, and useful if modules on the same machine need fast shared data access.

* boost:geometry[], boost:polygon[], and boost:graph[] : These libs work well with design and CAD-related math, 2D polygon manipulation, and representing complex relationships (dependency graphs, design hierarchies).

* boost:thread[], boost:lockfree[], and boost:fiber[] : All to provide UI responsiveness with background computations.

* boost:signals2[] : Useful for local publish/subscribe patterns within a module.

* boost:program_options[] and boost:property_tree[] : Useful for server/service configuration and for storing structured metadata.

Note:: The code in this tutorial was written and tested using Microsoft Visual Studio (Visual C++ 2022, Console App project) with Boost version 1.88.0.

== Demonstrating Range Failure

In order to show the effects of unmanaged numerical ranges, we'll calculate the hypotenuse of a simple right-angled triangle. The two sides of the triangle are of equal length, so the result _should be_ the square root of 2 (1.4142...) times the length of one of the sides. This is true if the sides of the triangle are both 1, or both 1 zillion.

The naive version tries to calculate the hypotenuse using standard math.

[source,cpp]
----
#include <cmath>
#include <iostream>

double naive_hypot(double x, double y)
{
    return std::sqrt(x * x + y * y);
}

int main()
{
    double x = 1e154;
    double y = 1e154;

    double result = naive_hypot(x, y);

    std::cout << "Naive hypot result: " << result << "\n";

    if (std::isinf(result))
        std::cout << "Result overflowed to infinity\n";
}

----

If you run this code, you will probably get:

[source,text]
----
Naive hypot result: inf
Result overflowed to infinity
----

You should also have noticed that you did not get an error, warning, or exception. So, what happened?

The answer is the square of the huge values overflowed.

Now try using boost::math[].

[source,cpp]
----
#include <boost/math/special_functions/hypot.hpp>
#include <iostream>

int main()
{
    double x = 1e154;
    double y = 1e154;

    double result = boost::math::hypot(x, y);

    std::cout << "Boost hypot result: " << result << "\n";
} 
----

If you run this version, you should get the correct result:

[source,text]
----
Boost hypot result: 1.41421e+154
----

boost:math[] uses scaling algorithms so that errors are avoided in the edge cases of handling very large values, or for that matter very tiny values.

The difference between `sqrt(x*x + y*y)` and `boost::math::hypot(x, y)` is not performance or convenience — it is whether intermediate values are allowed to destroy correctness.

== Demonstrating Underflow

Underflow is the quiet sibling of overflow and much closer in spirit to _drift_. Underflow occurs when a result is too small to be represented as a normal floating-point number and is rounded to zero.

The following code shows repeated division, eventually resulting in underflow:

[source,cpp]
----
#include <iostream>
#include <iomanip>

int main()
{
    double x = 1.0;

    std::cout << std::setprecision(17);

    for (int i = 0; i < 1100; ++i)
    {
        x /= 2.0;

        if (x == 0.0)
        {
            std::cout << "Underflow to zero at iteration " << i << "\n";
            break;
        }
    }

    std::cout << "Final value: " << x << "\n";
}
----

Run this code:

[source,text]
----
Underflow to zero at iteration 1074
Final value: 0
----

The `double` does have around 308 bits of range (and 52 bits of mantissa), but eventually precision collapses. boost:math[] can improve numerical stability, but it cannot extend the representable range of a floating-point type. If the value is important, then we add boost:multiprecision[] to our toolset:

[source,cpp]
----
#include <boost/multiprecision/cpp_bin_float.hpp>
#include <iostream>
#include <iomanip>

using highp = boost::multiprecision::cpp_bin_float_100;

int main()
{
    highp x = 1.0;

    for (int i = 0; i < 1100; ++i)
        x /= 2;

    std::cout << std::scientific << std::setprecision(50);
    std::cout << "Value after division: " << x << "\n";
}
----

Run this:

[source,text]
----
Value after division: 7.36215182902286267543686617714496511764913503250964e-332
----

[#footnote2-location]
Aeronautical models need to cope with situations like long-duration decay processes, _residual convergence_ link:#footnote2[(2)], damping terms, energy dissipation, and very small forces accumulated over time. The key concept here is that *zero is physically wrong!*

For a second example, let's examine where underflow destroys a physical invariant. In the following code the super small but non-zero velocity value is lost:

[source,cpp]
----
#include <iostream>
#include <iomanip>

int main()
{
    double velocity = 1e-300;   // Very small, but non-zero
    double dt = 1e-300;         // Time step

    double displacement = velocity * dt;

    std::cout << std::setprecision(17);
    std::cout << "Displacement: " << displacement << "\n";

    if (displacement == 0.0)
        std::cout << "Underflow: motion lost\n";
}
----

Run the code:

[source,text]
----
Displacement: 0
Underflow: motion lost
----

Again we look to boost:multiprecision[] to maintain the significance of a tiny value:

[source,cpp]
----
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>

using highp = boost::multiprecision::cpp_dec_float_50;

int main()
{
    highp velocity = highp("1e-300");
    highp dt       = highp("1e-300");

    highp displacement = velocity * dt;

    std::cout << displacement << "\n";
}
----

Run this and you get the correct answer:

[source,text]
----
1e-600
----

In physical models, if motion exists, it should be represented. 

For the aerospace engineer underflow is dangerous because it is so quiet that incorrect results can look plausible, zero is, after all, a valid number.

== Demonstrating Order-Dependent Drift

Order-dependent drift is one of the most misunderstood floating-point hazards, and it shows up constantly in aerospace (summations of forces, energies, residuals, loads).

Order-dependent drift occurs because floating-point addition is not associative: `(a + b) + c` does not equal `a + (b + c)` when one, or more, value is very large and other values are very small. Physically, the small forces matter. Numerically, they may disappear.

In the following example, the small forces are silently discarded:

[source,cpp]
----
#include <vector>
#include <iostream>
#include <iomanip>

double naive_sum(const std::vector<double>& values)
{
    double sum = 0.0;
    for (double v : values)
        sum += v;
    return sum;
}

int main()
{
    std::vector<double> forces;

    forces.push_back(1e20);          // Large load
    for (int i = 0; i < 1'000'000; ++i)
        forces.push_back(1.0);       // Small loads

    double result = naive_sum(forces);

    std::cout << std::setprecision(17);
    std::cout << "Naive sum: " << result << "\n";
}
----

Run this and you get the answer:

[source,text]
----
Naive sum: 1e+20
----

Boost provides numerically stable accumulation algorithms via boost:accumulators[]. In the following example, small values are preserved and order no longer matters.

[#footnote3-location]
_Kahan summation_ link:#footnote3[(3)] keeps a compensation term that tracks lost low-order bits:

[source,cpp]
----
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/sum_kahan.hpp>
#include <iostream>
#include <iomanip>


using namespace boost::accumulators;

int main()
{
    accumulator_set<double, features<tag::sum_kahan>> acc;

    acc(1e20);
    for (int i = 0; i < 1'000'000; ++i)
        acc(1.0);

    std::cout << std::setprecision(17);
    std::cout << "Boost Kahan sum: " << sum(acc) << "\n";
}
----

Run this and you get the correct answer:

[source,text]
----
Boost Kahan sum: 1.00000000000001e+20
----

Order-dependent drift is not randomness — it is determinism you didn't control.

Note:: boost:accumulators[] and boost:math[] are known for compiling cleanly _only_ once every required header is present and all parameters are present and correct. Otherwise these libs can generate a lot of errors.


== Next Steps

Wrap your mind around the high level architecture, think federated services, not a monolith:

[source,text]
----
┌────────────┐     ┌────────────┐
│ CAD Client │     │ Analysis   │
│            │     │ Client     │
└─────┬──────┘     └─────┬──────┘
      │                  │
      ├───────┬──────────┤
              ▼
     ┌───────────────────────┐
     │ Collaboration Gateway │
     │(Boost.Asio/Beast/Json)│
     └──────────┬────────────┘
                │
 ┌──────────────┼──────────────────┐
 │              │                  │
 ▼              ▼                  ▼
Model Service  Compute Service   Metadata Service
(Geometry)     (FEA/CFD)         (PLM / audit)

----

And consider your test harness/certification architecture right from the start:

[source,text]
----
┌──────────────────────────────┐
│ Test Runner (CLI)            │
│ (Boost.ProgramOptions)       │
└────────────┬─────────────────┘
             ▼
┌──────────────────────────────┐
│ Scenario Loader              │
│ (Boost.Filesystem)           │
└────────────┬─────────────────┘
             ▼
┌──────────────────────────────┐
│ Event Replayer               │
│ (Boost.Serialization)        │
└────────────┬─────────────────┘
             ▼
┌──────────────────────────────┐
│ Virtual Services             │
│ (Model / Compute / Metadata) │
└────────────┬─────────────────┘
             ▼
┌──────────────────────────────┐
│ Verifier                     │
│ (Boost.Test / Geometry)      │
└──────────────────────────────┘
----

Start with correctness in mind, and continue along that path.

== Footnotes

[#footnote1]
link:#footnote1-location[(1)]
A spanwise cut is a cross-section taken along the span of a wing or lifting surface, from the wing root (near the fuselage) toward the wing tip. It is used to examine how  properties vary across the width of the wingspan, rather than along the flight direction. Nothing is even across the span of a wing, and spanwise cuts are used to measure pressure coefficients, bending stress, fatigue hot spots, baseline verses modified wing geometry, and loads of various sorts.

[#footnote2]
link:#footnote2-location[(2)]
Residual convergence is about measuring how wrong your current solution still is, rather than how much the solution (the result) itself is changing. Residuals are a measure of the errors, and if the residuals flatten out or oscillate, something is wrong. Even if your solver converges, a non-shrinking residual means you might be converging to the wrong answer.

[#footnote3]
link:#footnote3-location[(3)]
Kahan summation is a technique for adding lots of floating-point numbers while losing far less precision than `sum += x`. This technique keeps a small “error accumulator” that tracks the bits lost to rounding — and feeds them back in on the next addition. In simple terms, you don't get more precision than `double` allows, but you stop throwing it away. In aerospace, bio-tech, finance, and simulation code, it's one of those “once you see it, you never un-see it” techniques.

== See Also

* https://www.boost.org/doc/libs/latest/libs/libraries.htm#Algorithms[Category: Algorithms]
* https://www.boost.org/doc/libs/latest/libs/libraries.htm#Concurrent[Category: Concurrent Programming]
* https://www.boost.org/doc/libs/latest/libs/libraries.htm#IO[Category: Input/Output]
* https://www.boost.org/doc/libs/latest/libs/libraries.htm#Math[Category: Math and numerics]



