Skip to content

Commit

Permalink
Adding the missing BaseUnits (#1473)
Browse files Browse the repository at this point in the history
Fixes #1463 
Fixes #1043

- removed the `UnitSystem` constructor from the Dimensionless quantities
(which was previously throwing)
- `As`/`ToUnit(UnitSystem)` for all dimensionless quantities now convert
to their `BaseUnit` (i.e. the "DecimalFraction") *
- `As/ToUnit(UnitSystem)` for all other quantities refactored using the
QuantityInfoExtensions
- added tests for the `UnitSystem` methods, skipping the tests for all
quantities that fail with `UnitSystem.SI` (with a reason)

There are only two dimensionless quantities (IMO) that don't fit the
definition:
- `RelativeHumidity`: currently has only the `Percent` unit, I think we
should add the `DecimalFraction`, setting it to be the `BaseUnit`
- `FuelEfficiency`: I think this could be defined as `"L": -2` with the
addition of the `MeterPerCubicMeter` unit (possibly setting it as its
`BaseUnit`, if we want to satisfy the `BaseUnit_HasSIBase` test)

You can look for `As_UnitSystem_ReturnsValueInDimensionlessUnit` if you
want to check the rest of the dimensionless quantities.

Regarding the removed `BaseUnits` (see `Force.json` or `Pressure.json`),
those were detected by some earlier tests I had in place, regarding the
multiplication/division operators where I used the following definition:
- A given operation between two quantities (either multiplication or
division) such as `A / B = C` is only defined if `A.Dimensions /
B.Dimensions = C.Dimensions`
- When the intersection between `A.Dimensions` and `B.Dimensions` is the
empty set, for every unit of `A` and `B` for which the `BaseUnits` is
not `Unidefined`, and every unit of `C`, having `BaseUnits` =
`A.BaseUnits union B.BaseUnits`, it must be true that `C.Value = A.Value
/ B.Value`.
- When the intersection between `A.Dimensions` and `B.Dimensions` is not
empty, and the intersecting `BaseUnits` of `A` , `B` and `C` are all the
same, then again we have the same condition, which I generally refer to
as "the conversion coefficient is 1"
- The same logic can be used to infer the unit-conversion coefficient
based off the `Dimensions` and a pair of `BaseUnits`, but special
attention needs to be taken w.r.t. the exponents (e.g. `Area` is `L2` so
the unit-conversion coefficients are squares of the ones from `Length`)
- If we want to extend this definition in the future, we should consider
introducing a way to override the "default conversion coefficient"
(`1`)..

Here are some links:
https://en.wikipedia.org/wiki/Dimensional_analysis
https://en.wikipedia.org/wiki/International_System_of_Units#Definition
https://en.wikipedia.org/wiki/Coherence_(units_of_measurement)
  • Loading branch information
lipchev authored Dec 30, 2024
1 parent cdab29e commit 090742c
Show file tree
Hide file tree
Showing 341 changed files with 17,113 additions and 8,234 deletions.
76 changes: 30 additions & 46 deletions CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,10 @@ private void GenerateInstanceConstructors()
Writer.WL($@"
_unit = unit;
}}
");
if (!_isDimensionless)
{
Writer.WL($@"
/// <summary>
/// Creates an instance of the quantity with the given numeric value in units compatible with the given <see cref=""UnitSystem""/>.
/// If multiple compatible units were found, the first match is used.
Expand All @@ -225,18 +228,11 @@ private void GenerateInstanceConstructors()
/// <exception cref=""ArgumentException"">No unit was found for the given <see cref=""UnitSystem""/>.</exception>
public {_quantity.Name}(double value, UnitSystem unitSystem)
{{
if (unitSystem is null) throw new ArgumentNullException(nameof(unitSystem));
var unitInfos = Info.GetUnitInfosFor(unitSystem.BaseUnits);
var firstUnitInfo = unitInfos.FirstOrDefault();
");

Writer.WL(@"
_value = value;");
Writer.WL($@"
_unit = firstUnitInfo?.Value ?? throw new ArgumentException(""No units were found for the given UnitSystem."", nameof(unitSystem));
_value = value;
_unit = Info.GetDefaultUnit(unitSystem);
}}
");
}
}

private void GenerateStaticProperties()
Expand Down Expand Up @@ -1000,34 +996,16 @@ public double As({_unitEnumName} unit)
}}
");

Writer.WL($@"
Writer.WL( $@"
/// <inheritdoc cref=""IQuantity.As(UnitSystem)""/>
public double As(UnitSystem unitSystem)
{{
if (unitSystem is null)
throw new ArgumentNullException(nameof(unitSystem));
var unitInfos = Info.GetUnitInfosFor(unitSystem.BaseUnits);
var firstUnitInfo = unitInfos.FirstOrDefault();
if (firstUnitInfo == null)
throw new ArgumentException(""No units were found for the given UnitSystem."", nameof(unitSystem));
return As(firstUnitInfo.Value);
return As(Info.GetDefaultUnit(unitSystem));
}}
");

Writer.WL($@"
/// <inheritdoc />
double IQuantity.As(Enum unit)
{{
if (!(unit is {_unitEnumName} typedUnit))
throw new ArgumentException($""The given unit is of type {{unit.GetType()}}. Only {{typeof({_unitEnumName})}} is supported."", nameof(unit));
return As(typedUnit);
}}
/// <summary>
/// Converts this {_quantity.Name} to another {_quantity.Name} with the unit representation <paramref name=""unit"" />.
/// </summary>
Expand Down Expand Up @@ -1123,29 +1101,33 @@ private bool TryToUnit({_unitEnumName} unit, [NotNullWhen(true)] out {_quantity.
converted = convertedOrNull.Value;
return true;
}}
");
Writer.WL($@"
/// <inheritdoc cref=""IQuantity.ToUnit(UnitSystem)""/>
public {_quantity.Name} ToUnit(UnitSystem unitSystem)
{{
return ToUnit(Info.GetDefaultUnit(unitSystem));
}}
");

/// <inheritdoc />
IQuantity IQuantity.ToUnit(Enum unit)
Writer.WL($@"
#region Explicit implementations
double IQuantity.As(Enum unit)
{{
if (!(unit is {_unitEnumName} typedUnit))
if (unit is not {_unitEnumName} typedUnit)
throw new ArgumentException($""The given unit is of type {{unit.GetType()}}. Only {{typeof({_unitEnumName})}} is supported."", nameof(unit));
return ToUnit(typedUnit, DefaultConversionFunctions);
return As(typedUnit);
}}
/// <inheritdoc cref=""IQuantity.ToUnit(UnitSystem)""/>
public {_quantity.Name} ToUnit(UnitSystem unitSystem)
/// <inheritdoc />
IQuantity IQuantity.ToUnit(Enum unit)
{{
if (unitSystem is null)
throw new ArgumentNullException(nameof(unitSystem));
var unitInfos = Info.GetUnitInfosFor(unitSystem.BaseUnits);
var firstUnitInfo = unitInfos.FirstOrDefault();
if (firstUnitInfo == null)
throw new ArgumentException(""No units were found for the given UnitSystem."", nameof(unitSystem));
if (!(unit is {_unitEnumName} typedUnit))
throw new ArgumentException($""The given unit is of type {{unit.GetType()}}. Only {{typeof({_unitEnumName})}} is supported."", nameof(unit));
return ToUnit(firstUnitInfo.Value);
return ToUnit(typedUnit, DefaultConversionFunctions);
}}
/// <inheritdoc />
Expand All @@ -1158,6 +1140,8 @@ IQuantity IQuantity.ToUnit(Enum unit)
IQuantity<{_unitEnumName}> IQuantity<{_unitEnumName}>.ToUnit(UnitSystem unitSystem) => ToUnit(unitSystem);
#endregion
#endregion
");
}

Expand Down
Loading

0 comments on commit 090742c

Please sign in to comment.