-
Notifications
You must be signed in to change notification settings - Fork 385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ILinearQuantity, ILogarithmicQuantity and IAffineQuantity interfaces #1461
Comments
You'd probably wonder about the /// <inheritdoc cref="IQuantity" />
/// <remarks>
/// This is a specialization of <see cref="IQuantity" /> that is used (internally) for constraining certain
/// methods, without having to include the unit type as additional generic parameter.
/// </remarks>
/// <typeparam name="TQuantity"></typeparam>
public interface IQuantityInstance<out TQuantity> : IQuantity
where TQuantity : IQuantity It currently sits between the public interface IQuantity<TSelf, TUnitType> : IQuantityInstance<TSelf>, IQuantity<TUnitType>
where TSelf : IQuantity<TSelf, TUnitType>
where TUnitType : struct, Enum |
Before I get to the
/// <summary>
/// Sums a sequence of linear quantities, such as Mass and Length.
/// </summary>
/// <typeparam name="TQuantity">The type of the linear quantity.</typeparam>
/// <param name="quantities">The collection of linear quantities to sum.</param>
/// <returns>
/// The sum of the linear quantities. If the sequence is empty, returns zero in the base unit.
/// In all other cases, the result will have the unit of the first element in the collection.
/// </returns>
public static TQuantity Sum<TQuantity>(this IEnumerable<TQuantity> quantities)
where TQuantity : ILinearQuantity<TQuantity> /// <summary>
/// Computes the sum of a sequence of quantities by applying a specified selector function to each element of the
/// sequence.
/// </summary>
/// <typeparam name="TSource">The type of the elements of the source sequence.</typeparam>
/// <typeparam name="TQuantity">The type of the quantity elements in the source sequence.</typeparam>
/// <param name="source">A sequence of quantities to calculate the sum of.</param>
/// <param name="selector">A function to transform each element of the source sequence into a quantity.</param>
/// <returns>
/// The sum of the projected quantities. If the sequence is empty, returns zero in the base unit; otherwise,
/// returns the sum in the unit of the first element.
/// </returns>
/// <exception cref="ArgumentNullException">Thrown if the source or selector is null.</exception>
public static TQuantity Sum<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector)
where TQuantity : ILinearQuantity<TQuantity>
{
return source.Select(selector).Sum();
} /// <summary>
/// Sums a sequence of linear quantities, such as Mass and Length.
/// </summary>
/// <typeparam name="TQuantity">The type of the linear quantity.</typeparam>
/// <typeparam name="TUnit">The unit type of the linear quantity.</typeparam>
/// <param name="quantities">The collection of linear quantities to sum.</param>
/// <param name="unit">The unit in which to express the sum.</param>
/// <returns>The sum of the linear quantities in the specified unit.</returns>
/// <exception cref="ArgumentNullException">Thrown when the sequence is null.</exception>
/// <remarks>
/// This method is slightly more performant than the alternative <see cref="Sum{TQuantity}(IEnumerable{TQuantity})" />
/// when most of the quantities in the sequence are expected to be in the target unit.
/// </remarks>
public static TQuantity Sum<TQuantity, TUnit>(this IEnumerable<TQuantity> quantities, TUnit unit)
where TQuantity : ILinearQuantity<TQuantity>, IQuantity<TQuantity, TUnit>
where TUnit : struct, Enum /// <summary>
/// Computes the sum of the sequence of <typeparamref name="TQuantity" /> values that are obtained by invoking a
/// transform function on each element of the input sequence.
/// </summary>
/// <typeparam name="TSource">The type of the elements of the source sequence.</typeparam>
/// <typeparam name="TQuantity">The type of the quantity elements in the source sequence.</typeparam>
/// <typeparam name="TUnit">The type of the unit of the quantities.</typeparam>
/// <param name="source">A sequence of quantities to calculate the sum of.</param>
/// <param name="selector">A function to transform each element of the source sequence into a quantity.</param>
/// <param name="targetUnit">The desired unit type for the resulting quantity</param>
/// <returns>The sum of the projected quantities in the specified unit.</returns>
/// <exception cref="ArgumentNullException">Thrown if the source or selector is null.</exception>
public static TQuantity Sum<TSource, TQuantity, TUnit>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, TUnit targetUnit)
where TQuantity : ILinearQuantity<TQuantity>, IQuantity<TQuantity, TUnit>
where TUnit : struct, Enum
{
return source.Select(selector).Sum(targetUnit);
} It's basically the same thing for the /// <summary>
/// Calculates the arithmetic average of a sequence of linear quantities, such as Mass and Length.
/// </summary>
/// <typeparam name="TQuantity">The type of the linear quantity.</typeparam>
/// <param name="quantities">The sequence of linear quantities to average.</param>
/// <returns>The average of the linear quantities, using the unit of the first element in the sequence.</returns>
/// <exception cref="ArgumentNullException">Thrown when the sequence is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when the sequence is empty.</exception>
public static TQuantity Average<TQuantity>(this IEnumerable<TQuantity> quantities)
where TQuantity : ILinearQuantity<TQuantity>
{
return quantities.ArithmeticMean();
}
/// <summary>
/// Computes the arithmetic average of a sequence of quantities, such as Mass and Length, by applying a specified
/// selector
/// function to each element of the elements in the sequence.
/// </summary>
/// <typeparam name="TSource">The type of the elements of the source sequence.</typeparam>
/// <typeparam name="TQuantity">The type of the quantity elements in the source sequence.</typeparam>
/// <param name="source">A sequence of quantities to calculate the average of.</param>
/// <param name="selector">A function to transform each element of the source sequence into a quantity.</param>
/// <returns>The average of the projected quantities in the unit of the first element in the sequence.</returns>
/// <exception cref="ArgumentNullException">Thrown when the sequence is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when the sequence is empty.</exception>
public static TQuantity Average<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector)
where TQuantity : ILinearQuantity<TQuantity>
{
return source.Select(selector).Average();
}
/// <summary>
/// Calculates the average of a sequence of linear quantities, such as Mass and Length.
/// </summary>
/// <typeparam name="TQuantity">The type of the linear quantity.</typeparam>
/// <typeparam name="TUnit">The unit type of the linear quantity.</typeparam>
/// <param name="quantities">The sequence of linear quantities to average.</param>
/// <param name="targetUnit">The unit in which to express the average.</param>
/// <returns>The average of the linear quantities in the specified unit.</returns>
/// <exception cref="InvalidOperationException">Thrown when the sequence is empty.</exception>
/// <remarks>
/// This method is slightly more performant than the alternative
/// <see cref="Average{TQuantity}(IEnumerable{TQuantity})" />
/// when most of the quantities in the sequence are expected to be in the target unit.
/// </remarks>
public static TQuantity Average<TQuantity, TUnit>(this IEnumerable<TQuantity> quantities, TUnit targetUnit)
where TQuantity : ILinearQuantity<TQuantity>, IQuantity<TQuantity, TUnit>
where TUnit : struct, Enum
{
return quantities.ArithmeticMean(targetUnit);
}
/// <summary>
/// Computes the average of the sequence of <typeparamref name="TQuantity" /> values, such as Mass and Length, that are
/// obtained by invoking a transform function on each element of the input sequence.
/// </summary>
/// <typeparam name="TSource">The type of the elements of the source sequence.</typeparam>
/// <typeparam name="TQuantity">The type of the quantity elements in the source sequence.</typeparam>
/// <typeparam name="TUnit">The type of the unit of the quantities.</typeparam>
/// <param name="source">A sequence of quantities to calculate the average of.</param>
/// <param name="selector">A function to transform each element of the source sequence into a quantity.</param>
/// <param name="targetUnit">The desired unit type for the resulting quantity.</param>
/// <returns>The average of the projected quantities in the specified unit.</returns>
/// <exception cref="InvalidOperationException">Thrown when the sequence is empty.</exception>
public static TQuantity Average<TSource, TQuantity, TUnit>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, TUnit targetUnit)
where TQuantity : ILinearQuantity<TQuantity>, IQuantity<TQuantity, TUnit>
where TUnit : struct, Enum
{
return source.Select(selector).Average(targetUnit);
} All of these extensions take precedence over the extensions in the The other extensions would apply for "custom quantities that implement the PS They also replace the same extensions from the |
I've also got the rest of the overloads but in order to reduce the clutter, I'm going to just focus on the main ones: /// <summary>
/// Sums a sequence of logarithmic quantities, such as PowerRatio and AmplitudeRatio.
/// </summary>
/// <typeparam name="TQuantity">The type of the logarithmic quantity.</typeparam>
/// <param name="quantities">The sequence of logarithmic quantities to sum.</param>
/// <param name="significantDigits">The number of significant digits to retain in the result. Default is 15.</param>
/// <returns>The sum of the logarithmic quantities in the unit of the first element in the sequence.</returns>
/// <exception cref="InvalidOperationException">Thrown when the sequence is empty.</exception>
/// <remarks>
/// When the sequence is not empty, each quantity is converted to linear space (in the unit of the first element),
/// summed, and then the result is converted back to logarithmic space using the same unit.
/// </remarks>
public static TQuantity Sum<TQuantity>(this IEnumerable<TQuantity> quantities, int significantDigits = 15)
where TQuantity : ILogarithmicQuantity<TQuantity> Notice that we're throwing an Another thing to pay attention to is the use of the Frankly, I wasn't even sure if it makes sense to have this one - but the AI had some convincing arguments (which I no longer recall) - so I kept it. /// <summary>
/// Computes the arithmetic mean of a sequence of logarithmic quantities, such as PowerRatio and AmplitudeRatio.
/// </summary>
/// <typeparam name="TQuantity">The type of the logarithmic quantity.</typeparam>
/// <param name="quantities">The sequence of logarithmic quantities to average.</param>
/// <param name="significantDigits">The number of significant digits to retain in the result. Default is 15.</param>
/// <returns>The average of the logarithmic quantities in the unit of the first element in the sequence.</returns>
/// <exception cref="InvalidOperationException">Thrown when the sequence is empty.</exception>
/// <remarks>
/// When the sequence is not empty, each quantity is converted to linear space (in the unit of the first element),
/// averaged, and then the result is converted back to logarithmic space using the same unit.
/// </remarks>
public static TQuantity ArithmeticMean<TQuantity>(this IEnumerable<TQuantity> quantities, int significantDigits = 15)
where TQuantity : ILogarithmicQuantity<TQuantity> /// <summary>
/// Computes the geometric mean of a sequence of logarithmic quantities, such as PowerRatio and AmplitudeRatio.
/// </summary>
/// <typeparam name="TQuantity">The type of the logarithmic quantity.</typeparam>
/// <param name="quantities">The sequence of logarithmic quantities to average.</param>
/// <param name="accuracy">The number of decimal places of accuracy for the square root calculation. Default is 15.</param>
/// <returns>The geometric mean of the logarithmic quantities in the unit of the first element in the sequence.</returns>
/// <exception cref="InvalidOperationException">Thrown when the sequence is empty.</exception>
/// <remarks>
/// When the sequence is not empty, calculates the n-th root of the product of the quantities, which for the
/// logarithmic quantities is equal to the sum the values, converted in unit of the first element.
/// </remarks>
public static TQuantity GeometricMean<TQuantity>(this IEnumerable<TQuantity> quantities, int accuracy = 15)
where TQuantity : ILogarithmicQuantity<TQuantity> Again, the AI insisted that both the arithmetic and the geometric means make sense in certain situations, but I cannot confirm or dispute that claim.. |
/// <summary>
/// Calculates the average of a collection of <see cref="Temperature" /> values.
/// </summary>
/// <param name="temperatures">The collection of <see cref="Temperature" /> values to average.</param>
/// <returns>The average <see cref="Temperature" />, in the unit of the first element in the sequence.</returns>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="temperatures" /> collection is null.</exception>
/// <exception cref="InvalidOperationException">Thrown if the <paramref name="temperatures" /> collection is empty.</exception>
public static Temperature Average(this IEnumerable<Temperature> temperatures)
{
return temperatures.ArithmeticMean();
}
/// <summary>
/// Calculates the average of a collection of <see cref="Temperature" /> values.
/// </summary>
/// <param name="temperatures">The collection of <see cref="Temperature" /> values to average.</param>
/// <param name="unit">The unit in which to express the average.</param>
/// <returns>The average <see cref="Temperature" />, in the specified unit.</returns>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="temperatures" /> collection is null.</exception>
/// <exception cref="InvalidOperationException">Thrown if the <paramref name="temperatures" /> collection is empty.</exception>
/// <remarks>
/// This method is slightly more performant than the alternative
/// <see cref="Average(System.Collections.Generic.IEnumerable{UnitsNet.Temperature})" />
/// when most of the quantities in the collection are expected to be in the target unit.
/// </remarks>
public static Temperature Average(this IEnumerable<Temperature> temperatures, TemperatureUnit unit)
{
return temperatures.ArithmeticMean(unit);
} |
Ok lastly, I'm going to try to be short w.r.t. the "Equals with tolerance" - as we know, this is currently the recommended way of comparing all quantities due to the possible rounding error incurred by the unit conversions etc.
/// <inheritdoc cref="EqualsAbsolute{TQuantity,TOther,TTolerance}" />
public static bool Equals<TQuantity, TOther, TTolerance>(this TQuantity quantity, TOther? other, TTolerance tolerance)
where TQuantity : ILinearQuantity<TQuantity>
where TOther : IQuantityInstance<TQuantity>
where TTolerance : IQuantityInstance<TQuantity>
{
return other != null && quantity.EqualsAbsolute(other, tolerance);
}
/// <inheritdoc cref="EqualsAbsolute{TQuantity,TOther,TTolerance}" />
/// <exception cref="ArgumentException">
/// Thrown when the <paramref name="tolerance" /> is not of the same type as the
/// <paramref name="quantity" />.
/// </exception>
public static bool Equals<TQuantity>(this TQuantity quantity, IQuantity? other, IQuantity tolerance)
where TQuantity : ILinearQuantity<TQuantity> Ok, so I'm going to stop here for now, if you agree with the overall picture so far- I can create a separate issue regarding the modifications to the Equals (with tolerance) part of the interfaces.. (or of course I can keep going here if you prefer 😆 ).. |
A lot to unwrap here.
That's all I got time for right now. Generally though, I think the changes look good 👍 |
Better to continue discussion for each change's pull request, instead of in a mega thread. |
The downside here is that the
That's just what the comment says- in reality I'm keeping it mostly in order to avoid the breaking change.
I think we should keep it for now, even if only to avoid the breaking change (for the extended quantities).
The default is 15 significant digits - which is the safe maximum precision that avoids the rounding error (when converting a
I'm pretty sure that the calculations for the Technically there is also the possibility to calculate the RootN with 500 digits of precision but that's probably a more extreme scenario
Technically we can but:
Note that unfortunately I won't be able to bring these changes in without introducing the What do you think about the |
Ok, that's not 100% accurate - I think I could introduce them with as a standalone PR, however the standard extensions (without the |
Equals as extension method is OK I think, as long is it's in the Regarding |
Following my comments on the subject in #1200 here is the proposed modification to the
IQuantity
interfaces:ILinearQuantity
- in v6 Wishlist #1200 I used the nameIVectorQuantity
but after some reflection I decided that the wordLinear
is a better fit (in fact, if we look at the conversion expressions- they are all of the formax
, without an offset)NET7_OR_GREATER
I introduce the staticLogarithmicScalingFactor
, however innetstandard
this needs to either be an instance member ( 😞 ) or alternatively I could introduce an extra interface for it'sQuantityInfo
, overriding the property to something of the sortnew ILogarithmicQuantityInfo<TQuantity> QuantityInfo { get; }
IAffineQuantity
: there is only one of these- theTemperature
with it'sTOffset
being theTemperatureDelta
(also note that the conversion functions here are of the formax + b
)The text was updated successfully, but these errors were encountered: