From 56376ca29e0de9f0b6c9c89fbb0a82f7ffbf456d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 13:20:46 +0000 Subject: [PATCH] Add KilometerPerSecond, the unit orbital state vectors are quoted in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #274. The Velocity dimension carried five units and none of them was km/s, so a library that can express a car's speed five ways could not express a satellite's without a factor of a thousand spelled by hand at the call site. Declared with "magnitude": "Kilo" rather than the new conversion constant the issue proposed. A kilometre per second is exactly a metric prefix on the dimension's base unit, which is the mechanism Kilonewton, Kilopascal, Kilojoule, Kilowatt and Megawatt already use — and the one Kilometer itself uses. The generator emits value * MetricMagnitudes.Values.Kilo, so this adds no constant for SEM009 to check and reuses a value the suite already covers. The issue was wrong about the vector forms, and the generated diff is what caught it. It claimed Velocity2D/3D/4D have no From* factories at all; they do, componentwise, and FromMeterPerSecond was already among them. So this also yields Velocity3D.FromKilometerPerSecond(x, y, z) beside the Position3D.FromKilometer the tests already used for an orbit — which is the actual shape of a published state vector and the most useful thing here. Tests go in StorageConversionTests, so they run over double, decimal and PreciseNumber. The scalar test cross-checks against KilometerPerHour at 3600 to 1, which ties the new unit to a neighbour defined by a different mechanism (the repeating factor 5/18) rather than only to itself. UnitFactorConsistencyTests picks the new unit up by reflection. Mutating Kilo to Mega fails both new tests on all three storage types. The alias props cannot change, and that is provable rather than observed: Generate-AliasProps.ps1 scans only QuantitiesGenerator and LogarithmicScalesGenerator and derives alias names from filenames, and this change adds no new .g.cs file — all fourteen it touches already existed. 1329 pass, 8 skipped (Windows-only). A local build with the SonarCloud analyzers reports nothing in any file this touches. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Dn4fyZp3kQhh2199arTU6p --- .../Airspeed.g.cs | 8 +++++ .../FlowSpeed.g.cs | 8 +++++ .../GroundSpeed.g.cs | 8 +++++ .../GroupVelocity.g.cs | 8 +++++ .../PhaseVelocity.g.cs | 8 +++++ .../SoundSpeed.g.cs | 8 +++++ .../Speed.g.cs | 8 +++++ .../Velocity1D.g.cs | 7 ++++ .../Velocity2D.g.cs | 6 ++++ .../Velocity3D.g.cs | 7 ++++ .../Velocity4D.g.cs | 8 +++++ .../WindSpeed.g.cs | 8 +++++ .../WindVelocity3D.g.cs | 7 ++++ .../Units.g.cs | 36 +++++++++++++++++++ .../Metadata/dimensions.json | 2 +- .../Metadata/units.json | 7 ++++ .../Quantities/StorageConversionTests.cs | 36 +++++++++++++++++++ 17 files changed, 179 insertions(+), 1 deletion(-) diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Airspeed.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Airspeed.g.cs index 2ad923a..6755b9f 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Airspeed.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Airspeed.g.cs @@ -102,6 +102,14 @@ namespace ktsu.Semantics.Quantities; /// Thrown when the resulting magnitude would be negative. public static Airspeed FromMeterPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + /// + /// Creates a new Airspeed from a value in KilometerPerSecond. + /// + /// The value in KilometerPerSecond. + /// A new Airspeed instance. + /// Thrown when the resulting magnitude would be negative. + public static Airspeed FromKilometerPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative((value * MetricMagnitudes.Values.Kilo), nameof(value))); + /// /// Creates a new Airspeed from a value in KilometerPerHour. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/FlowSpeed.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/FlowSpeed.g.cs index b0f357c..44a6869 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/FlowSpeed.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/FlowSpeed.g.cs @@ -102,6 +102,14 @@ namespace ktsu.Semantics.Quantities; /// Thrown when the resulting magnitude would be negative. public static FlowSpeed FromMeterPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + /// + /// Creates a new FlowSpeed from a value in KilometerPerSecond. + /// + /// The value in KilometerPerSecond. + /// A new FlowSpeed instance. + /// Thrown when the resulting magnitude would be negative. + public static FlowSpeed FromKilometerPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative((value * MetricMagnitudes.Values.Kilo), nameof(value))); + /// /// Creates a new FlowSpeed from a value in KilometerPerHour. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GroundSpeed.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GroundSpeed.g.cs index ce45a50..c5b18b6 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GroundSpeed.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GroundSpeed.g.cs @@ -102,6 +102,14 @@ namespace ktsu.Semantics.Quantities; /// Thrown when the resulting magnitude would be negative. public static GroundSpeed FromMeterPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + /// + /// Creates a new GroundSpeed from a value in KilometerPerSecond. + /// + /// The value in KilometerPerSecond. + /// A new GroundSpeed instance. + /// Thrown when the resulting magnitude would be negative. + public static GroundSpeed FromKilometerPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative((value * MetricMagnitudes.Values.Kilo), nameof(value))); + /// /// Creates a new GroundSpeed from a value in KilometerPerHour. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GroupVelocity.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GroupVelocity.g.cs index c457d4b..38846d8 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GroupVelocity.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GroupVelocity.g.cs @@ -102,6 +102,14 @@ namespace ktsu.Semantics.Quantities; /// Thrown when the resulting magnitude would be negative. public static GroupVelocity FromMeterPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + /// + /// Creates a new GroupVelocity from a value in KilometerPerSecond. + /// + /// The value in KilometerPerSecond. + /// A new GroupVelocity instance. + /// Thrown when the resulting magnitude would be negative. + public static GroupVelocity FromKilometerPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative((value * MetricMagnitudes.Values.Kilo), nameof(value))); + /// /// Creates a new GroupVelocity from a value in KilometerPerHour. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/PhaseVelocity.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/PhaseVelocity.g.cs index 51b1440..beb534c 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/PhaseVelocity.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/PhaseVelocity.g.cs @@ -102,6 +102,14 @@ namespace ktsu.Semantics.Quantities; /// Thrown when the resulting magnitude would be negative. public static PhaseVelocity FromMeterPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + /// + /// Creates a new PhaseVelocity from a value in KilometerPerSecond. + /// + /// The value in KilometerPerSecond. + /// A new PhaseVelocity instance. + /// Thrown when the resulting magnitude would be negative. + public static PhaseVelocity FromKilometerPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative((value * MetricMagnitudes.Values.Kilo), nameof(value))); + /// /// Creates a new PhaseVelocity from a value in KilometerPerHour. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SoundSpeed.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SoundSpeed.g.cs index 77aff08..4152f24 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SoundSpeed.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SoundSpeed.g.cs @@ -102,6 +102,14 @@ namespace ktsu.Semantics.Quantities; /// Thrown when the resulting magnitude would be negative. public static SoundSpeed FromMeterPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + /// + /// Creates a new SoundSpeed from a value in KilometerPerSecond. + /// + /// The value in KilometerPerSecond. + /// A new SoundSpeed instance. + /// Thrown when the resulting magnitude would be negative. + public static SoundSpeed FromKilometerPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative((value * MetricMagnitudes.Values.Kilo), nameof(value))); + /// /// Creates a new SoundSpeed from a value in KilometerPerHour. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Speed.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Speed.g.cs index ea19b3b..337e614 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Speed.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Speed.g.cs @@ -101,6 +101,14 @@ namespace ktsu.Semantics.Quantities; /// Thrown when the resulting magnitude would be negative. public static Speed FromMeterPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + /// + /// Creates a new from a value in KilometerPerSecond. + /// + /// The value in KilometerPerSecond. + /// A new instance. + /// Thrown when the resulting magnitude would be negative. + public static Speed FromKilometerPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative((value * MetricMagnitudes.Values.Kilo), nameof(value))); + /// /// Creates a new from a value in KilometerPerHour. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity1D.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity1D.g.cs index a1b023d..6125ef8 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity1D.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity1D.g.cs @@ -104,6 +104,13 @@ namespace ktsu.Semantics.Quantities; /// A new instance. public static Velocity1D FromMeterPerSecond(T value) => Create(value); + /// + /// Creates a new from a value in KilometerPerSecond. + /// + /// The value in KilometerPerSecond. + /// A new instance. + public static Velocity1D FromKilometerPerSecond(T value) => Create((value * MetricMagnitudes.Values.Kilo)); + /// /// Creates a new from a value in KilometerPerHour. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity2D.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity2D.g.cs index ab99cac..8356e7b 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity2D.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity2D.g.cs @@ -47,6 +47,12 @@ namespace ktsu.Semantics.Quantities; /// A new storing the SI-base equivalent. public static Velocity2D FromMeterPerSecond(T x, T y) => new() { X = x, Y = y }; + /// Creates a from components in KilometerPerSecond. + /// The X component, in KilometerPerSecond. + /// The Y component, in KilometerPerSecond. + /// A new storing the SI-base equivalent. + public static Velocity2D FromKilometerPerSecond(T x, T y) => new() { X = (x * MetricMagnitudes.Values.Kilo), Y = (y * MetricMagnitudes.Values.Kilo) }; + /// Creates a from components in KilometerPerHour. /// The X component, in KilometerPerHour. /// The Y component, in KilometerPerHour. diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity3D.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity3D.g.cs index c34e97f..ab87d05 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity3D.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity3D.g.cs @@ -54,6 +54,13 @@ namespace ktsu.Semantics.Quantities; /// A new storing the SI-base equivalent. public static Velocity3D FromMeterPerSecond(T x, T y, T z) => new() { X = x, Y = y, Z = z }; + /// Creates a from components in KilometerPerSecond. + /// The X component, in KilometerPerSecond. + /// The Y component, in KilometerPerSecond. + /// The Z component, in KilometerPerSecond. + /// A new storing the SI-base equivalent. + public static Velocity3D FromKilometerPerSecond(T x, T y, T z) => new() { X = (x * MetricMagnitudes.Values.Kilo), Y = (y * MetricMagnitudes.Values.Kilo), Z = (z * MetricMagnitudes.Values.Kilo) }; + /// Creates a from components in KilometerPerHour. /// The X component, in KilometerPerHour. /// The Y component, in KilometerPerHour. diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity4D.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity4D.g.cs index dc31821..a783426 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity4D.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Velocity4D.g.cs @@ -61,6 +61,14 @@ namespace ktsu.Semantics.Quantities; /// A new storing the SI-base equivalent. public static Velocity4D FromMeterPerSecond(T x, T y, T z, T w) => new() { X = x, Y = y, Z = z, W = w }; + /// Creates a from components in KilometerPerSecond. + /// The X component, in KilometerPerSecond. + /// The Y component, in KilometerPerSecond. + /// The Z component, in KilometerPerSecond. + /// The W component, in KilometerPerSecond. + /// A new storing the SI-base equivalent. + public static Velocity4D FromKilometerPerSecond(T x, T y, T z, T w) => new() { X = (x * MetricMagnitudes.Values.Kilo), Y = (y * MetricMagnitudes.Values.Kilo), Z = (z * MetricMagnitudes.Values.Kilo), W = (w * MetricMagnitudes.Values.Kilo) }; + /// Creates a from components in KilometerPerHour. /// The X component, in KilometerPerHour. /// The Y component, in KilometerPerHour. diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/WindSpeed.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/WindSpeed.g.cs index 780d280..33cc705 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/WindSpeed.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/WindSpeed.g.cs @@ -102,6 +102,14 @@ namespace ktsu.Semantics.Quantities; /// Thrown when the resulting magnitude would be negative. public static WindSpeed FromMeterPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + /// + /// Creates a new WindSpeed from a value in KilometerPerSecond. + /// + /// The value in KilometerPerSecond. + /// A new WindSpeed instance. + /// Thrown when the resulting magnitude would be negative. + public static WindSpeed FromKilometerPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative((value * MetricMagnitudes.Values.Kilo), nameof(value))); + /// /// Creates a new WindSpeed from a value in KilometerPerHour. /// diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/WindVelocity3D.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/WindVelocity3D.g.cs index d3e125b..c78fd59 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/WindVelocity3D.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/WindVelocity3D.g.cs @@ -54,6 +54,13 @@ namespace ktsu.Semantics.Quantities; /// A new storing the SI-base equivalent. public static WindVelocity3D FromMeterPerSecond(T x, T y, T z) => new() { X = x, Y = y, Z = z }; + /// Creates a from components in KilometerPerSecond. + /// The X component, in KilometerPerSecond. + /// The Y component, in KilometerPerSecond. + /// The Z component, in KilometerPerSecond. + /// A new storing the SI-base equivalent. + public static WindVelocity3D FromKilometerPerSecond(T x, T y, T z) => new() { X = (x * MetricMagnitudes.Values.Kilo), Y = (y * MetricMagnitudes.Values.Kilo), Z = (z * MetricMagnitudes.Values.Kilo) }; + /// Creates a from components in KilometerPerHour. /// The X component, in KilometerPerHour. /// The Y component, in KilometerPerHour. diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs index 3c2ed75..911ec21 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs @@ -2283,6 +2283,39 @@ public KilometerPerHour() { } T IUnit.ToBaseOffsetAs() => T.Zero; } +/// +/// Kilometers per second - 1000 meters per second; the unit orbital state vectors are quoted in. +/// +public sealed record KilometerPerSecond : IUnit, IVelocityUnit +{ + /// Gets the full name of the unit. + public string Name => "KilometerPerSecond"; + + /// Gets the symbol/abbreviation of the unit. + public string Symbol => "km/s"; + + /// Gets the unit system this unit belongs to. + public UnitSystem System => UnitSystem.SIDerived; + + /// Gets the physical dimension this unit measures. + public DimensionInfo Dimension => PhysicalDimensions.Velocity; + + /// Gets the multiplication factor used in the to-base affine conversion. + public double ToBaseFactor => MetricMagnitudes.Kilo; + + /// Gets the additive offset used in the to-base affine conversion. + public double ToBaseOffset => 0d; + + /// Initializes a new instance of the unit. + public KilometerPerSecond() { } + + /// + T IUnit.ToBaseFactorAs() => MetricMagnitudes.Values.Kilo; + + /// + T IUnit.ToBaseOffsetAs() => T.Zero; +} + /// /// Miles per hour - Imperial unit of velocity. /// @@ -6594,6 +6627,9 @@ public static class Units /// Singleton KilometerPerHour instance. public static readonly KilometerPerHour KilometerPerHour = new KilometerPerHour(); + /// Singleton KilometerPerSecond instance. + public static readonly KilometerPerSecond KilometerPerSecond = new KilometerPerSecond(); + /// Singleton Kilomole instance. public static readonly Kilomole Kilomole = new Kilomole(); diff --git a/Semantics.SourceGenerators/Metadata/dimensions.json b/Semantics.SourceGenerators/Metadata/dimensions.json index 65388ad..9425541 100644 --- a/Semantics.SourceGenerators/Metadata/dimensions.json +++ b/Semantics.SourceGenerators/Metadata/dimensions.json @@ -259,7 +259,7 @@ "symbol": "L T⁻¹", "dimensionalFormula": { "length": 1, "time": -1 }, "availableUnits": [ - "MeterPerSecond", "KilometerPerHour", "MilePerHour", "FootPerSecond", "Knot" + "MeterPerSecond", "KilometerPerSecond", "KilometerPerHour", "MilePerHour", "FootPerSecond", "Knot" ], "quantities": { "vector0": { diff --git a/Semantics.SourceGenerators/Metadata/units.json b/Semantics.SourceGenerators/Metadata/units.json index c437aa0..e3550a6 100644 --- a/Semantics.SourceGenerators/Metadata/units.json +++ b/Semantics.SourceGenerators/Metadata/units.json @@ -480,6 +480,13 @@ "system": "SIDerived", "conversionFactor": "KilometerPerHourToMeterPerSecond" }, + { + "name": "KilometerPerSecond", + "symbol": "km/s", + "description": "Kilometers per second - 1000 meters per second; the unit orbital state vectors are quoted in.", + "system": "SIDerived", + "magnitude": "Kilo" + }, { "name": "MilePerHour", "symbol": "mph", diff --git a/Semantics.Test/Quantities/StorageConversionTests.cs b/Semantics.Test/Quantities/StorageConversionTests.cs index c767778..16b4bb8 100644 --- a/Semantics.Test/Quantities/StorageConversionTests.cs +++ b/Semantics.Test/Quantities/StorageConversionTests.cs @@ -81,6 +81,42 @@ public void KilometersPerHourConvertAndRoundTrip() AssertValue("10", speed.In(Units.MeterPerSecond), terminates: false); } + [TestMethod] + public void AKilometerPerSecondIsExactlyAThousandMetersPerSecond() + { + Speed speed = Speed.FromKilometerPerSecond(Of("7.66")); + + AssertValue("7660", speed.Value, terminates: true); + AssertValue("7.66", speed.In(Units.KilometerPerSecond), terminates: true); + + // Against a neighbour defined by a different mechanism: kilometers per hour carries the + // repeating factor 5/18, so agreeing with it at 3600 to 1 ties the new unit to something + // other than its own definition. Non-terminating because that factor is. + AssertValue("3600", Speed.FromKilometerPerSecond(T.One).In(Units.KilometerPerHour), terminates: false); + } + + /// + /// The whole of what KilometerPerSecond is for: an orbital state is published in + /// kilometers and kilometers per second together, and until this unit existed the second half + /// had to be multiplied by a thousand at the call site — in a library whose point is that units + /// are not spelled by hand. + /// + [TestMethod] + public void AStateVectorIsBuiltFromItsPublishedUnitsWithNoArithmeticAtTheCallSite() + { + Position3D position = Position3D.FromKilometer(Of("6778"), Of("-1.5"), T.Zero); + Velocity3D velocity = Velocity3D.FromKilometerPerSecond(Of("0.5"), Of("7.5"), Of("-1.25")); + + AssertValue("6778000", position.X, terminates: true); + AssertValue("500", velocity.X, terminates: true); + AssertValue("7500", velocity.Y, terminates: true); + AssertValue("-1250", velocity.Z, terminates: true); + + // Componentwise, and signed, exactly as the position factory beside it is — the vector + // forms carry no Vector0 guard, so a retrograde component is ordinary. + Assert.AreEqual(Speed.FromKilometerPerSecond(Of("7.5")).Value, velocity.Y); + } + [TestMethod] public void AKnotIsTheRepeatingFractionOfAMeterPerSecond() {