Files
unison/src/core/model/length.h

1001 lines
27 KiB
C++

/*
* Copyright (c) 2019 Lawrence Livermore National Laboratory
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Mathew Bielejeski <bielejeski1@llnl.gov>
*/
#ifndef NS3_LENGTH_H_
#define NS3_LENGTH_H_
#include "attribute-helper.h"
#include "attribute.h"
#ifdef HAVE_BOOST
#include <boost/units/quantity.hpp>
#include <boost/units/systems/si.hpp>
#endif
#include <istream>
#include <limits>
#include <optional>
#include <ostream>
#include <string>
/**
* @file
* @ingroup length
* Declaration of ns3::Length class
*/
/**
* ns3 namespace
*/
namespace ns3
{
/**
* @ingroup core
* @defgroup length Length
*
* Management of lengths in real world units.
*
*/
/**
* @ingroup length
*
* Represents a length in meters
*
* The purpose of this class is to replace the use of raw numbers (ints, doubles)
* that have implicit lengths with a class that represents lengths with an explicit
* unit. Using raw values with implicit units can lead to bugs when a value is
* assumed to represent one unit but actually represents another. For example,
* assuming a value represents a length in meters when it in fact represents a
* length in kilometers.
*
* The Length class prevents this confusion by storing values internally as
* doubles representing lengths in Meters and providing conversion functions
* to convert to/from other length units (kilometers, miles, etc.).
*
* ## Supported Units ##
*
* Conversion to and from meters is supported for the following units:
*
* ### Metric ###
* - nanometer
* - micrometer
* - millimeter
* - centimeter
* - meter
* - kilometer
* - nautical mile
*
* ### US Customary ###
* - inch
* - foot
* - yard
* - mile
*
* ## Construction ##
*
* Length objects can be constructed in a number of different ways
*
* ### String Constructor ###
*
* The string constructor parses strings with the format \<number\>\<unit\> or
* \<number\> \<unit\> and creates the equivalent Length value in meters.
* \<unit\> can be the full name of the unit (nanometer, kilometer, foot, etc.) or the
* abbreviated name (nm, km, ft, etc.).
*
* @code
* //construct lengths from strings
* Length foot ("1foot");
* Length cm ("1cm");
* Length mile ("1 mile");
* Length km ("1 km");
*
* //nautical mile is special because it is two words
* Length nautmile ("1 nautical mile");
* Length nmi ("1 nmi");
* @endcode
*
* ### Quantity Constructor ###
*
* Quantity is a class that describes a value and a unit type. For example, meter
* is a unit while 5 meters is a quantity.
* The Length constructor takes the quantity instance and converts it to the
* equivalent value in meters.
*
* @code
* //construct a Length representing 2 kilometers
* Length::Quantity q (2, Length::Unit::Kilometer);
* Length km(q);
* @endcode
*
* ### Value/Unit Constructor
*
* Two constructors are provided which take a value and a unit as separate
* parameters. The difference between the two constructors is that one takes
* the unit as a string and the other takes the unit as a Length::Unit value.
* An assertion is triggered if the string does not map to a valid
* Length::Unit
*
* @code
* //These two constructors are equivalent
* Length l1 (1, "cm");
* Length l2 (1, Length::Unit::Centimeter);
* @endcode
*
* ### boost::units
*
* If the boost::units library is discovered during ns-3 configuration an
* additional constructor is enabled which allows constructing Length objects
* from boost::unit quantities.
*
* @code
* //construct length from a boost::units quantity
* boost::units::quantity<boost::units::si::length> q
* = 5 * boost::units::si::meter;
* Length meters (q);
* @endcode
*
* ## Arithmetic Operations ##
*
* The following arithmetic operations are supported:
*
* ### Addition ###
*
* Addition is between two Length instances
*
* @code
* std::cout << Length(1, Length::Unit::Meter) + Length (2, Length::Unit::Meter);
* // output: "3 m"
* @endcode
*
* ### Subtraction ###
*
* Subtraction is between two Length instances
*
* @code
* std::cout << Length(3, Length::Unit::Meter) - Length (2, Length::Unit::Meter);
* // output: "1 m"
* @endcode
*
* ### Multiplication ###
*
* Multiplication is only supported between a Length and a unitless scalar value
*
* @code
* std::cout << Length(1, Length::Unit::Meter) * 5; // output: "5 m"
*
* std::cout << 5 * Length (1, Length::Unit::Meter); // output: "5 m"
* @endcode
*
*
* ### Division ###
*
* Division can be between a Length and a scalar or two Length objects.
*
* Division between a Length and a scalar returns a new Length.
*
* Division between two Length objects returns a unitless value.
*
* @code
* std::cout << Length(5, Length::Unit::Meter) / 5; // output: "1 m"
*
* std::cout << Length (5, Length::Unit::Meter) / Length (5, Length::Unit::Meter);
* // output: 1
* @endcode
*
* ## Comparison Operations ##
*
* All the usual arithmetic comparison operations (=, !=, <, <=, >, >=) are supported.
* There are two forms of comparison operators: free function and member function.
* The free functions define =, !=, <, <=, >, >= and perform exact comparisons of
* the underlying double values. The Length class provides comparison
* operator functions (IsEqual, IsLess, IsGreater, etc.) which accept an
* additional tolerance value to control how much the underlying double values
* must match when performing the comparison.
*
* @code
* //check for exact match
* Length l (5, Length::Unit::Meter);
*
* bool match = (l == l); // match is true
*
* Length v1 (0.02, Length::Unit::Meter);
* Length v2 (0.022, Length::Unit::Meter);
*
* match = (v1 == v2); // match is false
*
* double tolerance = 0.01;
* bool mostly_match = v1.IsEqual (v2, tolerance); // mostly_match is true
* @endcode
*
* ## Serialization ##
*
* The Length class supports serialization using the << and >> operators.
* By default the output serialization is in meters. Use Length::As to output
* the Length value in a different unit.
*
* @code
* Length m(5, Length::Unit::Meter);
*
* std::cout << m << ", "
* << m.As(Length::Unit::Kilometer) << ", "
* << m.As(Length::Unit::Foot);
* //output: 5 m, 0.005 km, 16.4042 ft
* @endcode
*/
class Length
{
public:
/**
* Units of length in various measurement systems that are supported by the
* Length class
*/
enum Unit : uint16_t
{
// Metric Units
Nanometer = 1, //!< 1e<sup>-9</sup> meters
Micrometer, //!< 1e<sup>-6</sup> meters
Millimeter, //!< 1e<sup>-3</sup> meters
Centimeter, //!< 1e<sup>-2</sup> meters
Meter, //!< Base length unit in metric system
Kilometer, //!< 1e<sup>3</sup> meters
NauticalMile, //!< 1,852 meters
// US Customary Units
Inch, //!< 1/12 of a foot
Foot, //!< Base length unit in US customary system
Yard, //!< 3 feet
Mile //!< 5,280 feet
};
/**
* An immutable class which represents a value in a specific length unit
*/
class Quantity
{
public:
/**
* Constructor
*
* @param value Length value
* @param unit Length unit of the value
*/
Quantity(double value, Length::Unit unit)
: m_value(value),
m_unit(unit)
{
}
/**
* Copy Constructor
*/
Quantity(const Quantity&) = default;
/**
* Move Constructor
*/
Quantity(Quantity&&) = default;
/**
* Destructor
*/
~Quantity() = default;
/**
* Copy Assignment Operator
* @param [in] other The source to copy from.
* @returns this.
*/
Quantity& operator=(const Quantity& other) = default;
/**
* Move Assignment Operator
* @param [in] other The source to move from.
* @returns this.
*/
Quantity& operator=(Quantity&& other) = default;
/**
* The value of the quantity
*
* @return The value of this quantity
*/
double Value() const
{
return m_value;
}
/**
* The unit of the quantity
*
* @return The unit of this quantity
*/
Length::Unit Unit() const
{
return m_unit;
}
private:
double m_value; //!< Value of the length
Length::Unit m_unit; //!< unit of length of the value
};
/**
* Default tolerance value used for the member comparison functions (IsEqual,
* IsLess, etc.)
*
* The default tolerance is set to epsilon which is defined as the difference
* between 1.0 and the next value that can be represented by a double.
*/
static constexpr double DEFAULT_TOLERANCE = std::numeric_limits<double>::epsilon();
/**
* Attempt to construct a Length object from a value and a unit string
*
* \p unit can either be the full name of the unit (meter, kilometer, mile, etc.)
* or the symbol of the unit (m, km, mi, etc.)
*
* This function will return false if \p unit does not map to a known type.
*
* @param value Numeric value of the new length
* @param unit Unit that the value represents
*
* @return A std::optional object containing the Length object constructed from
* the given value and unit, if the attempt to construct the Length object was
* successful.
*/
static std::optional<Length> TryParse(double value, const std::string& unit);
/**
* Default Constructor
*
* Initialize with a value of 0 meters.
*/
Length();
/**
* String Constructor
*
* Parses \p text and initializes the value with the parsed result.
*
* The expected format of \p text is \<number\> \<unit\> or \<number\>\<unit\>
*
* @param text Serialized length value
*/
Length(const std::string& text);
/**
* Construct a Length object from a value and a unit string
*
* \p unit can either be the full name of the unit (meter, kilometer, mile, etc.)
* or the symbol of the unit (m, km, mi, etc.)
*
* @warning NS_FATAL_ERROR is called if \p unit is not a valid unit string.
* @warning Use Length::TryParse to parse potentially bad values without terminating.
*
* @param value Numeric value of the new length
* @param unit Unit that the value represents
*/
Length(double value, const std::string& unit);
/**
* Construct a Length object from a value and a unit
*
* @warning NS_FATAL_ERROR is called if \p unit is not valid.
* @warning Use Length::TryParse to parse potentially bad values without terminating.
*
* @param value Numeric value of the new length
* @param unit Length unit of the value
*/
Length(double value, Length::Unit unit);
/**
* Construct a Length object from a Quantity
*
* @param quantity Quantity representing a length value and unit
*/
Length(Quantity quantity);
#ifdef HAVE_BOOST_UNITS
/**
* Construct a Length object from a boost::units::quantity
*
* @note The boost::units:quantity must contain a unit that derives from
* the length dimension. Passing a quantity with a Unit that is not a length
* unit will result in a compile time error
*
* @tparam U A boost::units length unit
* @tparam T Numeric data type of the quantity value
*
* @param quantity A boost::units length quantity
*/
template <class U, class T>
explicit Length(boost::units::quantity<U, T> quantity);
#endif
/**
* Copy Constructor
*
* Initialize an object with the value from \p other.
*
* @param other Length object to copy
*/
Length(const Length& other) = default;
/**
* Move Constructor
*
* Initialize an object with the value from \p other.
*
* After the move completes, \p other is left in an undefined but
* usable state.
*
* @param other Length object to move
*/
Length(Length&& other) = default;
/**
* Destructor
*/
~Length() = default;
/**
* Copy Assignment operator
*
* Replace the current value with the value from \p other
*
* @param other Length object to copy
*
* @return Reference to the updated object
*/
Length& operator=(const Length& other) = default;
/**
* Move Assignment operator
*
* Replace the current value with the value from \p other
* After the move, \p other is left in an undefined but valid state
*
* @param other Length object to move
*
* @return Reference to the updated object
*/
Length& operator=(Length&& other) = default;
/**
* Assignment operator
*
* Replace the current value with the value from \p q
*
* @param q Quantity holding the value to assign
*
* @return Reference to the updated object
*/
Length& operator=(const Length::Quantity& q);
/**
* Check if \p other is equal in value to this instance.
*
* @param other Value to compare against
* @param tolerance Smallest difference allowed between the two
* values to still be considered equal
*
* @return true if the absolute difference between lengths
* is less than or equal to \p tolerance
*/
bool IsEqual(const Length& other, double tolerance = DEFAULT_TOLERANCE) const;
/**
* Check if \p other is not equal in value to this instance.
*
* @param other Value to compare against
* @param tolerance Smallest difference allowed between the two
* values to still be considered equal
*
* @return true if the absolute difference between lengths
* is greater than \p tolerance
*/
bool IsNotEqual(const Length& other, double tolerance = DEFAULT_TOLERANCE) const;
/**
* Check if \p other is greater in value than this instance.
*
* @param other Value to compare against
* @param tolerance Smallest difference allowed between the two
* values to still be considered equal
*
* @return true if the values are not equal and \p other is
* greater in value
*/
bool IsLess(const Length& other, double tolerance = DEFAULT_TOLERANCE) const;
/**
* Check if \p other is greater or equal in value than this instance.
*
* @param other Value to compare against
* @param tolerance Smallest difference allowed between the two
* values to still be considered equal
*
* Equivalent to:
* @code
* IsEqual(other, tolerance) || IsLess(other, tolerance)
* @endcode
*
* @return true if the values are equal or \p other is
* greater in value
*/
bool IsLessOrEqual(const Length& other, double tolerance = DEFAULT_TOLERANCE) const;
/**
* Check if \p other is less in value than this instance.
*
* @param other Value to compare against
* @param tolerance Smallest difference allowed between the two
* values to still be considered equal
*
* Equivalent to:
* @code
* !(IsLessOrEqual(other, tolerance))
* @endcode
*
* @return true if the values are not equal and \p other is
* less in value
*/
bool IsGreater(const Length& other, double tolerance = DEFAULT_TOLERANCE) const;
/**
* Check if \p other is equal or less in value than this instance.
*
* @param other Value to compare against
* @param tolerance Smallest difference allowed between the two
* values to still be considered equal
*
* Equivalent to:
* @code
* !IsLess(other, tolerance)
* @endcode
*
* @return true if the values are equal or \p other is
* less in value
*/
bool IsGreaterOrEqual(const Length& other, double tolerance = DEFAULT_TOLERANCE) const;
/**
* Swap values with another object
*
* Swap the current value with the value in \p other.
*
* Equivalent to:
* @code
* Length temp(*this);
* *this = other;
* other = temp;
* @endcode
*
* @param other Length object to swap
*/
void swap(Length& other);
/**
* Current length value
*
* Equivalent to:
* @code
* As (Length::Unit::Meter).Value ()
* @endcode
* @return The current value, in meters
*/
double GetDouble() const;
/**
* Create a Quantity in a specific unit from a Length
*
* Converts the current length value to the equivalent value specified by
* \p unit and returns a Quantity object with the converted value and unit
*
* @param unit The desired unit of the returned Quantity
*
* @return A quantity representing the length in the requested unit
*/
Quantity As(Unit unit) const;
private:
double m_value; //!< Length in meters
// end of class Length
};
ATTRIBUTE_HELPER_HEADER(Length);
/**
* @ingroup length
* @brief Return the symbol of the supplied unit
*
* The symbol of the unit is the shortened form of the unit name and is usually
* two or three characters long
*
* @param unit The unit to symbolize
*
* @return String containing the symbol of \p unit
*/
std::string ToSymbol(Length::Unit unit);
/**
* @ingroup length
* @brief Return the name of the supplied unit
*
* The value returned by this function is the common name of \p unit. The output
* is always lowercase.
*
* If \p plural is true, then the plural form of the common name is returned
* instead.
*
* @param unit The unit to name
* @param plural Boolean indicating if the returned string should contain the
* plural form of the name
*
* @return String containing the full name of \p unit
*/
std::string ToName(Length::Unit unit, bool plural = false);
/**
* @ingroup length
* @brief Find the equivalent Length::Unit for a unit string
*
* The string value can be a symbol or name (plural or singular).
*
* The string comparison ignores case so strings like "NanoMeter", "centiMeter",
* "METER" will all match the correct unit.
*
* Leading and trailing whitespace are trimmed from the string before searching
* for a match.
*
* @param unitString String containing the symbol or name of a length unit
*
* @return A std::optional object containing a Length::Unit if a match for the
* string could be found
*/
std::optional<Length::Unit> FromString(std::string unitString);
/**
* @ingroup length
* @brief Write a length value to an output stream.
*
* The output of the length is in meters.
*
* Equivalent to:
* @code
* stream << l.As (Meter);
* @endcode
*
* @param stream Output stream
* @param l Length value to write to the stream
*
* @return Reference to the output stream
*/
std::ostream& operator<<(std::ostream& stream, const Length& l);
/**
* @ingroup length
* @brief Write a Quantity to an output stream.
*
* The data written to the output stream will have the format \<value\> \<symbol\>
*
* Equivalent to:
* @code
* stream << q.Value () << ' ' << ToSymbol (q.Unit());
* @endcode
*
* @param stream Output stream
* @param q Quantity to write to the output stream
*
* @return Reference to the output stream
*/
std::ostream& operator<<(std::ostream& stream, const Length::Quantity& q);
/**
* @ingroup length
* @brief Write a Length::Unit to an output stream.
*
* Writes the name of \p unit to the output stream
*
* Equivalent to:
* @code
* stream << ToName (unit);
* @endcode
*
* @param stream Output stream
* @param unit Length unit to output
*
* @return Reference to the output stream
*/
std::ostream& operator<<(std::ostream& stream, Length::Unit unit);
/**
* @ingroup length
* @brief Read a length value from an input stream.
*
* The expected format of the input is \<number\> \<unit\>
* or \<number\>\<unit\>
* This function calls NS_ABORT if the input stream does not contain
* a valid length string
*
* @param stream Input stream
* @param l Object where the deserialized value will be stored
*
* @return Reference to the input stream
*/
std::istream& operator>>(std::istream& stream, Length& l);
/**
* @ingroup length
* @brief Compare two length objects for equality.
*
* Equivalent to:
* @code
* left.IsEqual(right, 0);
* @endcode
*
* @param left Left length object
* @param right Right length object
*
* @return true if \p left and \p right have the same value
*/
bool operator==(const Length& left, const Length& right);
/**
* @ingroup length
* @brief Compare two length objects for inequality.
*
* Equivalent to:
* @code
* left.IsNotEqual(right, 0);
* @endcode
*
* @param left Left length object
* @param right Right length object
*
* @return true if \p left and \p right do not have the same value
*/
bool operator!=(const Length& left, const Length& right);
/**
* @ingroup length
* @brief Check if \p left has a value less than \p right
*
* Equivalent to:
* @code
* left.IsLess(right, 0);
* @endcode
*
* @param left Left length object
* @param right Right length object
*
* @return true if \p left is less than \p right
*/
bool operator<(const Length& left, const Length& right);
/**
* @ingroup length
* @brief Check if \p left has a value less than or equal to \p right
*
* Equivalent to:
* @code
* left.IsLessOrEqual(right, 0);
* @endcode
*
* @param left Left length object
* @param right Right length object
*
* @return true if \p left is less than or equal to \p right
*/
bool operator<=(const Length& left, const Length& right);
/**
* @ingroup length
* @brief Check if \p left has a value greater than \p right
*
* Equivalent to:
* @code
* left.IsGreater(right, 0);
* @endcode
*
* @param left Left length object
* @param right Right length object
*
* @return true if \p left is greater than \p right
*/
bool operator>(const Length& left, const Length& right);
/**
* @ingroup length
* @brief Check if \p left has a value greater than or equal to \p right
*
* Equivalent to:
* @code
* left.IsGreaterOrEqual(right, 0);
* @endcode
*
* @param left Left length object
* @param right Right length object
*
* @return true if \p left is greater than or equal to \p right
*/
bool operator>=(const Length& left, const Length& right);
/**
* @ingroup length
* @brief Add two length values together.
*
* Adds the values of \p left to \p right and returns a new
* Length object containing the result.
*
* @param left A Length object
* @param right A Length object
*
* @return A newly constructed Length object containing the
* result of `left + right`.
*/
Length operator+(const Length& left, const Length& right);
/**
* @ingroup length
* @brief Subtract two length values.
*
* Subtracts the value of \p right from \p left and returns a
* new Length object containing the result.
*
* @param left A Length object
* @param right A Length object
*
* @return A newly constructed Length object containing the
* result of `left - right`.
*/
Length operator-(const Length& left, const Length& right);
/**
* @ingroup length
* @brief Multiply a length value by a scalar
*
* Multiplies the value \p l by \p scalar and returns a new
* Length object containing the result.
*
* @param l The Length object
* @param scalar Multiplication factor
*
* @return A newly constructed Length object containing the result
* of `l * scalar`.
*/
Length operator*(double scalar, const Length& l);
/**
* @ingroup length
* @brief Multiply a length value by a scalar
*
* Multiplies the value \p l by \p scalar and returns a new
* Length object containing the result.
*
* @param l The Length object
* @param scalar Multiplication factor
*
* @return A newly constructed Length object containing the result
* of `l * scalar`.
*/
Length operator*(const Length& l, double scalar);
/**
* @ingroup length
* @brief Divide a length value by a scalar
*
* Divides the value \p left by \p scalar and returns a new
* Length object containing the result.
*
* \p scalar must contain a non zero value.
* NS_FATAL_ERROR is called if \p scalar is zero
*
* @param left Length value
* @param scalar Multiplication factor
*
* @return A newly constructed Length object containing the result
* of `left / scalar`.
*/
Length operator/(const Length& left, double scalar);
/**
* @ingroup length
* @brief Divide a length value by another length value
*
* Divides the value \p numerator by the value \p denominator and
* returns a scalar value containing the result.
*
* The return value will be NaN if \p denominator is 0.
*
* @param numerator The top value of the division
* @param denominator The bottom value of the division
*
* @return A scalar value that is the result of `numerator / denominator` or
* NaN if \p denominator is 0.
*/
double operator/(const Length& numerator, const Length& denominator);
/**
* @ingroup length
* @brief Calculate how many times \p numerator can be split into \p denominator
* sized pieces.
*
* If the result of `numerator / denominator` is not a whole number, the
* result is rounded toward zero to the nearest whole number.
* The amount remaining after the division can be retrieved by passing a pointer
* to a Length in \p remainder. The remainder will be less than \p denominator
* and have the same sign as \p numerator.
*
* NS_FATAL_ERROR is called if \p denominator is 0.
*
* @param numerator The value to split
* @param denominator The length of each split
* @param remainder Location to store the remainder
*
* @return The number of times \p numerator can be split into \p denominator
* sized pieces, rounded down to the nearest whole number
*/
int64_t Div(const Length& numerator, const Length& denominator, Length* remainder = nullptr);
/**
* @ingroup length
* @brief Calculate the amount remaining after dividing two lengths
*
* The returned value will be less than \p denominator and have the same sign as
* \p numerator.
*
* NS_FATAL_ERROR is called if \p denominator is 0.
*
* @param numerator The value to split
* @param denominator The length of each split
*
* @return The amount remaining after splitting \p numerator into \p denominator
* sized pieces.
*/
Length Mod(const Length& numerator, const Length& denominator);
/**
* Construct a length from a value in the indicated unit.
* @{
* @ingroup length
* @param value The numerical value.
* @returns Length object.
*/
Length NanoMeters(double value);
Length MicroMeters(double value);
Length MilliMeters(double value);
Length CentiMeters(double value);
Length Meters(double value);
Length KiloMeters(double value);
Length NauticalMiles(double value);
Length Inches(double value);
Length Feet(double value);
Length Yards(double value);
Length Miles(double value);
/**@}*/
#ifdef HAVE_BOOST_UNITS
template <class U, class T>
Length::Length(boost::units::quantity<U, T> quantity)
: m_value(0)
{
namespace bu = boost::units;
using BoostMeters = bu::quantity<bu::si::length, double>;
// convert value to meters
m_value = static_cast<BoostMeters>(quantity).value();
}
#endif
} // namespace ns3
#endif /* NS3_LENGTH_H_ */