Skip to content

Commit

Permalink
Enable inversion of measures and frequency as first such quantity (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
atmoos committed Apr 9, 2024
2 parents e4ac45e + 5f94715 commit 77920bb
Show file tree
Hide file tree
Showing 37 changed files with 356 additions and 163 deletions.
1 change: 0 additions & 1 deletion source/Quantities.Benchmark/Quantities.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Quantities\Quantities.csproj" />
<ProjectReference Include="..\Quantities.Units\Quantities.Units.csproj" />
<ProjectReference Include="..\Quantities.Serialization\Text.Json\Quantities.Serialization.Text.Json.csproj" />
<ProjectReference Include="..\Quantities.Serialization\Newtonsoft\Quantities.Serialization.Newtonsoft.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Quantities.Units\Quantities.Units.csproj" />
<ProjectReference Include="..\Newtonsoft\Quantities.Serialization.Newtonsoft.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Atmoos.Quantities.Units" Version="1.1.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Quantities.Units\Quantities.Units.csproj" />
<ProjectReference Include="..\Text.Json\Quantities.Serialization.Text.Json.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Atmoos.Quantities.Units" Version="1.1.0" />
</ItemGroup>

</Project>
8 changes: 4 additions & 4 deletions source/Quantities.Test.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
<!-- Test info common to all test projects within the quantities project -->

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
3 changes: 1 addition & 2 deletions source/Quantities.Test/Dimensions/DimensionUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Quantities.Dimensions;
using Quantities.Dimensions;
using Xunit.Sdk;

namespace Quantities.Test.Dimensions;
Expand Down
2 changes: 1 addition & 1 deletion source/Quantities.Test/ElectricPotentialTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void PowerLawInPrefixedUnits()
_ = ElectricPotential.Of(15, Si<Kilo, Volt>());
ElectricCurrent ampere = ElectricCurrent.Of(600, Si<Ampere>());

// ToDo: Implement rounding based on value!
// ToDo #5: Implement rounding based on value!
ElectricPotential expected = ElectricPotential.Of(15, Si<Kilo, Volt>());

ElectricPotential potential = watts / ampere;
Expand Down
12 changes: 8 additions & 4 deletions source/Quantities.Test/LengthTest.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using Quantities.Dimensions;
using Quantities.Units.Si.Metric;

using static Quantities.Test.Convenience;
using Quantities.Units.Si.Metric;

namespace Quantities.Test;

Expand All @@ -10,6 +7,13 @@ public sealed class LengthTest
private const Double miles_in_kilometre = 1.609344d;
private const Double kilometre_in_miles = 1d / miles_in_kilometre;

[Fact]
public void MetreToString() => FormattingMatches(v => Length.Of(v, Si<Metre>()), "m");
[Fact]
public void KiloMetreToString() => FormattingMatches(v => Length.Of(v, Si<Kilo, Metre>()), "km");
[Fact]
public void MileToString() => FormattingMatches(v => Length.Of(v, Imperial<Mile>()), "mi");

[Fact]
public void MetreToKilometre()
{
Expand Down
4 changes: 2 additions & 2 deletions source/Quantities.Test/Measures/Expect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ internal static class Expect<TResult>
where TResult : IMeasure
{
private static readonly Measure expected = Measure.Of<TResult>();
public static Polynomial IsProductOf<TLeft, TRight>()
public static Polynomial ToBeProductOf<TLeft, TRight>()
where TLeft : IMeasure
where TRight : IMeasure
{
Expand All @@ -18,7 +18,7 @@ public static Polynomial IsProductOf<TLeft, TRight>()
Assert.Same(expected, (Measure)actual);
return (Polynomial)actual;
}
public static Polynomial IsQuotientOf<TNumerator, TDenominator>()
public static Polynomial ToBeQuotientOf<TNumerator, TDenominator>()
where TNumerator : IMeasure
where TDenominator : IMeasure
{
Expand Down
14 changes: 7 additions & 7 deletions source/Quantities.Test/Measures/MeasureDivisionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,45 @@ public class MeasureDivisionTest
[Fact]
public void IdentityPerAnyIsInverseAny()
{
var conversion = Expect<Quotient<Identity, Si<Micro, Second>>>.IsQuotientOf<Identity, Si<Micro, Second>>();
var conversion = Expect<Quotient<Identity, Si<Micro, Second>>>.ToBeQuotientOf<Identity, Si<Micro, Second>>();
Assert.Equal(One / Of<Micro>(), conversion);
}
[Fact]
public void AnyPerIdentityIsAny()
{
var conversion = Expect<Si<Pico, Second>>.IsQuotientOf<Si<Pico, Second>, Identity>();
var conversion = Expect<Si<Pico, Second>>.ToBeQuotientOf<Si<Pico, Second>, Identity>();
Assert.Equal(One, conversion);
}
[Fact]
public void ScalarPerOtherScalarIsQuotient()
{
var conversion = Expect<Quotient<Si<Metre>, Si<Second>>>.IsQuotientOf<Si<Metre>, Si<Second>>();
var conversion = Expect<Quotient<Si<Metre>, Si<Second>>>.ToBeQuotientOf<Si<Metre>, Si<Second>>();
Assert.Equal(One, conversion);
}
[Fact]
public void ScalarPerSameScalarIsIdentity()
{
var conversion = Expect<Identity>.IsQuotientOf<Metric<Hour>, Si<Kilo, Second>>();
var conversion = Expect<Identity>.ToBeQuotientOf<Metric<Hour>, Si<Kilo, Second>>();
Assert.Equal((Of<Hour>() / Of<Kilo>()).Simplify(), conversion);
}

[Fact]
public void SquareScalarPerSimilarScalarIsScalar()
{
var conversion = Expect<Si<Kilo, Metre>>.IsQuotientOf<Power<Square, Si<Kilo, Metre>>, Imperial<Yard>>();
var conversion = Expect<Si<Kilo, Metre>>.ToBeQuotientOf<Power<Square, Si<Kilo, Metre>>, Imperial<Yard>>();
Assert.Equal((Of<Kilo>() / Of<Yard>()).Simplify(), conversion);
}
[Fact]
public void CubicScalarPerSimilarScalarIsSquare()
{
var conversion = Expect<Power<Square, Si<Kilo, Metre>>>.IsQuotientOf<Power<Cubic, Si<Kilo, Metre>>, Imperial<Foot>>();
var conversion = Expect<Power<Square, Si<Kilo, Metre>>>.ToBeQuotientOf<Power<Cubic, Si<Kilo, Metre>>, Imperial<Foot>>();
Assert.Equal((Of<Kilo>() / Of<Foot>()).Simplify(), conversion);
}

[Fact]
public void CubicScalarPerSquareScalarIsScalar()
{
var conversion = Expect<Si<Metre>>.IsQuotientOf<Power<Cubic, Si<Metre>>, Power<Square, Si<Metre>>>();
var conversion = Expect<Si<Metre>>.ToBeQuotientOf<Power<Cubic, Si<Metre>>, Power<Square, Si<Metre>>>();
Assert.Equal(One, conversion);
}
}
10 changes: 5 additions & 5 deletions source/Quantities.Test/Measures/MeasureMultiplicationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,32 @@ public class MeasureMultiplicationTest
[Fact]
public void IdentityTimesAnyIsAny()
{
var conversion = Expect<Si<Micro, Second>>.IsProductOf<Identity, Si<Micro, Second>>();
var conversion = Expect<Si<Micro, Second>>.ToBeProductOf<Identity, Si<Micro, Second>>();
Assert.Equal(One, conversion);
}
[Fact]
public void AnyTimesIdentityIsAny()
{
var conversion = Expect<Si<Pico, Second>>.IsProductOf<Si<Pico, Second>, Identity>();
var conversion = Expect<Si<Pico, Second>>.ToBeProductOf<Si<Pico, Second>, Identity>();
Assert.Equal(One, conversion);
}
[Fact]
public void ScalarTimesOtherScalarIsProduct()
{
var conversion = Expect<Product<Si<Metre>, Si<Second>>>.IsProductOf<Si<Metre>, Si<Second>>();
var conversion = Expect<Product<Si<Metre>, Si<Second>>>.ToBeProductOf<Si<Metre>, Si<Second>>();
Assert.Equal(One, conversion);
}

[Fact]
public void ScalarTimesSimilarScalarIsSquareScalar()
{
var conversion = Expect<Power<Square, Si<Metre>>>.IsProductOf<Si<Metre>, Imperial<Yard>>();
var conversion = Expect<Power<Square, Si<Metre>>>.ToBeProductOf<Si<Metre>, Imperial<Yard>>();
Assert.Equal(Of<Yard>(), conversion);
}
[Fact]
public void ScalarTimesSquareSimilarScalarIsCubicScalar()
{
var conversion = Expect<Power<Cubic, Metric<Hour>>>.IsProductOf<Metric<Hour>, Power<Square, Metric<Day>>>();
var conversion = Expect<Power<Cubic, Metric<Hour>>>.ToBeProductOf<Metric<Hour>, Power<Square, Metric<Day>>>();
Assert.Equal((Of<Day>().Pow(2) / Of<Hour>()).Simplify(), conversion);
}
}
1 change: 0 additions & 1 deletion source/Quantities.Test/Quantities.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Quantities\Quantities.csproj" />
<ProjectReference Include="..\Quantities.Units\Quantities.Units.csproj" />
</ItemGroup>

Expand Down
11 changes: 11 additions & 0 deletions source/Quantities.Test/VolumeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ namespace Quantities.Test;

public sealed class VolumeTest
{
[Fact]
public void CubicMetreToString() => FormattingMatches(v => Volume.Of(v, Cubic(Si<Metre>())), "");
[Fact]
public void CubicKiloMetreToString() => FormattingMatches(v => Volume.Of(v, Cubic(Si<Kilo, Metre>())), "km³");
[Fact]
public void MilliLitreToString() => FormattingMatches(v => Volume.Of(v, Metric<Milli, Litre>()), "mℓ");
[Fact]
public void CubicFootToString() => FormattingMatches(v => Volume.Of(v, Cubic(Imperial<Foot>())), "ft³");
[Fact]
public void PintToString() => FormattingMatches(v => Volume.Of(v, Imperial<Pint>()), "pt");

[Fact]
public void AddCubicMetres()
{
Expand Down
21 changes: 15 additions & 6 deletions source/Quantities/Core/IFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static abstract TQuantity Of<TLeft, TRight>(in Double value, in Product<T
where TRight : TRightDimension, IUnit;
}

public interface ISquare<out TQuantity, in TDimension, in TLinear> : IAlias<TQuantity, TDimension, TLinear>
public interface ISquare<out TQuantity, in TDimension, in TLinear> : IPowerOf<TQuantity, TDimension, TLinear>
where TQuantity : ISquare<TQuantity, TDimension, TLinear>, TDimension
where TDimension : ISquare<TLinear>
where TLinear : IDimension, ILinear
Expand All @@ -55,7 +55,7 @@ public interface ISquare<out TQuantity, in TDimension, in TLinear> : IAlias<TQua
public static abstract TQuantity Of<TUnit>(in Double value, in Square<TUnit> measure) where TUnit : TLinear, IUnit;
}

public interface ICubic<out TQuantity, in TDimension, in TLinear> : IAlias<TQuantity, TDimension, TLinear>
public interface ICubic<out TQuantity, in TDimension, in TLinear> : IPowerOf<TQuantity, TDimension, TLinear>
where TQuantity : ICubic<TQuantity, TDimension, TLinear>, TDimension
where TDimension : ICubic<TLinear>
where TLinear : IDimension, ILinear
Expand All @@ -64,11 +64,20 @@ public interface ICubic<out TQuantity, in TDimension, in TLinear> : IAlias<TQuan
public static abstract TQuantity Of<TUnit>(in Double value, in Cubic<TUnit> measure) where TUnit : TLinear, IUnit;
}

public interface IAlias<out TQuantity, in TDimension, in TLinear>
where TQuantity : IAlias<TQuantity, TDimension, TLinear>, TDimension
public interface IInvertible<out TQuantity, in TDimension, in TInverse>
where TQuantity : IInvertible<TQuantity, TDimension, TInverse>, TDimension
where TDimension : IDimension, IInverse<TInverse>, ILinear
where TInverse : IDimension, ILinear
{
public TQuantity To<TUnit>(in Scalar<TUnit> other) where TUnit : TDimension, IInvertible<TInverse>, IUnit;
public static abstract TQuantity Of<TUnit>(in Double value, in Scalar<TUnit> measure) where TUnit : TDimension, IInvertible<TInverse>, IUnit;
}

public interface IPowerOf<out TQuantity, in TDimension, in TLinear>
where TQuantity : IPowerOf<TQuantity, TDimension, TLinear>, TDimension
where TDimension : IDimension
where TLinear : IDimension, ILinear
{
public TQuantity To<TUnit>(in Scalar<TUnit> alias) where TUnit : TDimension, IAlias<TLinear>, IUnit;
public static abstract TQuantity Of<TUnit>(in Double value, in Scalar<TUnit> alias) where TUnit : TDimension, IAlias<TLinear>, IUnit;
public TQuantity To<TUnit>(in Scalar<TUnit> alias) where TUnit : TDimension, IPowerOf<TLinear>, IUnit;
public static abstract TQuantity Of<TUnit>(in Double value, in Scalar<TUnit> alias) where TUnit : TDimension, IPowerOf<TLinear>, IUnit;
}
1 change: 1 addition & 0 deletions source/Quantities/Core/IMeasure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal interface IMeasure : IRepresentable, ISerialize
{
public static abstract Dimension D { get; }
public static abstract Polynomial Poly { get; }
public static abstract Result Inverse { get; }
public static abstract Result Multiply<TMeasure>() where TMeasure : IMeasure;
public static abstract Result Divide<TMeasure>() where TMeasure : IMeasure;
}
Expand Down
2 changes: 2 additions & 0 deletions source/Quantities/Core/Measure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal abstract class Measure
private readonly Polynomial conversion;
private Measure(in Polynomial conversion) => this.conversion = conversion;
public Double Project(Measure other, in Double value) => this.conversion / other.conversion * value;
public abstract Result Invert();
public abstract Result Multiply(Measure other);
protected abstract Result Multiply<TMeasure>() where TMeasure : IMeasure;
public abstract Result Divide(Measure other);
Expand All @@ -19,6 +20,7 @@ private sealed class Impl<TMeasure> : Measure
where TMeasure : IMeasure
{
public Impl() : base(TMeasure.Poly) { }
public override Result Invert() => TMeasure.Inverse;
public override Result Multiply(Measure other) => other.Multiply<TMeasure>();
public override Result Divide(Measure other) => other.Divide<TMeasure>();
public override void Serialize(IWriter writer) => TMeasure.Write(writer);
Expand Down
16 changes: 10 additions & 6 deletions source/Quantities/Core/Quantity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ namespace Quantities.Core;
{
private readonly Double value;
private readonly Measure measure;
internal Quantity(in Double value, in Measure map) => (this.measure, this.value) = (map, value);
internal Quantity(in Double value, in Measure measure) => (this.measure, this.value) = (measure, value);
public Quantity Project(in Measure other) => ReferenceEquals(this.measure, other)
? this : new Quantity(this.measure.Project(other, in this.value), in other);
private Double Project(in Quantity other) => ReferenceEquals(this.measure, other.measure)
? other.value : other.measure.Project(this.measure, in other.value);
public Double Ratio(in Quantity right)
{
var rightValue = Project(in right);
Double rightValue = Project(in right);
return this.value / rightValue;
}
public void Write(IWriter writer)
Expand All @@ -37,7 +37,7 @@ public Boolean Equals(Quantity other)
{
const Double min = 1d - 2e-15;
const Double max = 1d + 2e-15;
var projectedOther = Project(in other);
Double projectedOther = Project(in other);
if (projectedOther == 0d) {
return this.value == 0d;
}
Expand All @@ -58,7 +58,6 @@ public static Quantity Of<TMeasure>(in Double value)
public static Boolean operator >=(Quantity left, Quantity right) => left.value >= left.Project(in right);
public static Boolean operator <(Quantity left, Quantity right) => left.value < left.Project(in right);
public static Boolean operator <=(Quantity left, Quantity right) => left.value <= left.Project(in right);

public static Quantity operator *(Quantity left, Quantity right)
{
Result product = left.measure.Multiply(right.measure);
Expand All @@ -73,14 +72,19 @@ public static Quantity Of<TMeasure>(in Double value)
public static Quantity operator *(Double scalar, Quantity right) => new(scalar * right.value, in right.measure);
public static Quantity operator *(Quantity left, Double scalar) => new(scalar * left.value, in left.measure);
public static Quantity operator /(Quantity left, Double scalar) => new(left.value / scalar, in left.measure);
public static Quantity operator /(Double scalar, Quantity right)
{
Result inverse = right.measure.Invert();
return new(inverse * scalar / right.value, inverse);
}
public static Quantity operator +(Quantity left, Quantity right)
{
var rightValue = left.Project(in right);
Double rightValue = left.Project(in right);
return new(left.value + rightValue, in left.measure);
}
public static Quantity operator -(Quantity left, Quantity right)
{
var rightValue = left.Project(in right);
Double rightValue = left.Project(in right);
return new(left.value - rightValue, in left.measure);
}
public static implicit operator Double(Quantity self) => self.value;
Expand Down
10 changes: 4 additions & 6 deletions source/Quantities/Creation/Creators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

namespace Quantities.Creation;

internal delegate Measure MeasureSelector(Factory factory);

public readonly struct Scalar<TUnit>
where TUnit : IUnit, IDimension
{
Expand All @@ -14,13 +16,9 @@ public Product<TUnit, TRight> Times<TRight>(in Scalar<TRight> rightTerm)
public Quotient<TUnit, TDenominator> Per<TDenominator>(in Scalar<TDenominator> denominator)
where TDenominator : IUnit, IDimension => new(denominator.factory.Inject(this.factory.Quotient));
internal Quantity Create(in Double value) => new(in value, this.factory.Create());
internal Quantity Create<TAlias, TLinear>(in Double value)
where TAlias : TUnit, IAlias<TLinear>
where TLinear : ILinear, IDimension => new(in value, this.factory.AliasTo<TAlias, TLinear>());
internal Quantity Create(in Double value, MeasureSelector selectMeasure) => new(in value, selectMeasure(this.factory));
internal Quantity Transform(in Quantity other) => other.Project(this.factory.Create());
internal Quantity Transform<TAlias, TLinear>(in Quantity other)
where TAlias : TUnit, IAlias<TLinear>
where TLinear : ILinear, IDimension => other.Project(this.factory.AliasTo<TAlias, TLinear>());
internal Quantity Transform(in Quantity other, MeasureSelector selectMeasure) => other.Project(selectMeasure(this.factory));
}

public readonly struct Product<TLeft, TRight>
Expand Down
Loading

0 comments on commit 77920bb

Please sign in to comment.