From 35b2e8cf68e1743d114f44466c32bd8506dd65ba Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 14:35:49 +0000 Subject: [PATCH 1/3] Add GravitationalParameter, SpecificEnergy and SpecificAngularMomentum [minor] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three dimensions orbital mechanics cannot be written without. mu = GM (L³T⁻²) had no vector at all and could only be carried as a bare T; the other two are name collisions on vectors that already exist, which is the clearest illustration the library has of why the nominal layer is needed. SpecificEnergy's base is a signed vector1, not a vector0. Specific orbital energy is epsilon = -mu/2a, negative for every bound orbit, so a vector0 base would run Vector0Guards.EnsureNonNegative and throw on the ordinary case of a satellite in orbit. A vector0 form sits alongside it for specific kinetic energy, which genuinely is non-negative. cross(Displacement3D, Velocity3D) is declared on Length so the operands land as h = r x v rather than its negation. SEM008 cannot tell the two apart -- a cross product and its negation have identical dimensions -- so a test pins the sign, as the torque relationship already does. Units are named for the repo's Cubic convention rather than the issue's Meter Cubed wording: CubicMeterPerSecondSquared and CubicKilometerPerSecondSquared, beside CubicMeterPerSecond. SquareMeterPerSecond is reused for SpecificAngularMomentum, the second unit claimed by two dimensions. Fixes #240 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HLTGLrPaSc3E9APdgUq6wn --- CLAUDE.md | 29 +++- .../ktsu.Semantics.Quantities.Decimal.props | 8 + .../ktsu.Semantics.Quantities.Double.props | 8 + .../ktsu.Semantics.Quantities.Float.props | 8 + .../ktsu.Semantics.Quantities.Precise.props | 8 + .../ConversionConstants.g.cs | 9 ++ .../PhysicalDimensions.g.cs | 35 ++++- .../AccelerationMagnitude.g.cs | 6 + .../Area.g.cs | 6 + .../Displacement3D.g.cs | 6 + .../GravitationalParameter.g.cs | 139 +++++++++++++++++ .../Length.g.cs | 6 + .../SpecificAngularMomentum3D.g.cs | 108 ++++++++++++++ .../SpecificAngularMomentumMagnitude.g.cs | 119 +++++++++++++++ .../SpecificEnergy.g.cs | 121 +++++++++++++++ .../SpecificEnergyMagnitude.g.cs | 125 ++++++++++++++++ .../SpecificKineticEnergy.g.cs | 126 ++++++++++++++++ .../SpecificOrbitalEnergy.g.cs | 125 ++++++++++++++++ .../StandardGravitationalParameter.g.cs | 134 +++++++++++++++++ .../Units.g.cs | 110 +++++++++++++- .../Metadata/conversions.json | 11 ++ .../Metadata/dimensions.json | 62 +++++++- .../Metadata/units.json | 19 +++ .../OrbitalMechanicsQuantityTests.cs | 141 ++++++++++++++++++ 24 files changed, 1460 insertions(+), 9 deletions(-) create mode 100644 Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GravitationalParameter.g.cs create mode 100644 Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentum3D.g.cs create mode 100644 Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentumMagnitude.g.cs create mode 100644 Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificEnergy.g.cs create mode 100644 Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificEnergyMagnitude.g.cs create mode 100644 Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificKineticEnergy.g.cs create mode 100644 Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificOrbitalEnergy.g.cs create mode 100644 Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/StandardGravitationalParameter.g.cs create mode 100644 Semantics.Test/Quantities/OrbitalMechanicsQuantityTests.cs diff --git a/CLAUDE.md b/CLAUDE.md index 00df7496..398bca70 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,15 +89,22 @@ one driving it. Two layers, and both earn their place: rather than generated, because none of it is derived from the metadata. It is what gives a product nobody declared a type at all. - **Nominal.** One class per dimension, per vector form and per named overload — `Length`, - `Displacement3D`, `Weight`. This is what the exponents cannot do: **72 dimensions share 63 + `Displacement3D`, `Weight`. This is what the exponents cannot do: **75 dimensions share 64 exponent vectors**, so `Area` and `NuclearCrossSection`, `Torque` and `Energy`, `AbsorbedDose` - and `EquivalentDose` are each one vector between two names. 212 classes in all — 148 magnitudes, - 27 signed scalars, and 37 vectors of two to four components. + and `EquivalentDose` are each one vector between two names. 220 classes in all — 153 magnitudes, + 29 signed scalars, and 38 vectors of two to four components. + + The clearest illustration of the point is the newest: `SpecificEnergy` is `L² T⁻²`, which is + what `AbsorbedDose` and `EquivalentDose` already are — specific orbital energy and absorbed + radiation dose are both joules per kilogram, and nothing in the exponents separates the energy + of an orbit from a radiation dose. `SpecificAngularMomentum` lands on `L² T⁻¹` beside + `KinematicViscosity` the same way. Only `GravitationalParameter` brought a vector of its own, + `L³ T⁻²`, which is the 64th. **Eight axes, not the seven in `dimensionalFormula` before.** `angle` is carried by `AngularDisplacement`, `AngularVelocity`, `AngularAcceleration` and `AngularJerk`, and that is the whole of it. Without it an angle is the same type as a ratio and an angular speed the same type as -a frequency; with it, 61 distinct exponent vectors become 63. It is read by the C++ projection and +a frequency; with it, 62 distinct exponent vectors become 64. It is read by the C++ projection and carried through `DimensionInfo` on the .NET side, where `ktsu.Schema` reads it off a unit to fill the eight exponents in its C++ reflection table. @@ -108,8 +115,9 @@ has to pick one. Picking the first declared picked by file position, and `Dimens first entry in `dimensions.json`, so every angular unit reported no exponents at all: the same answer a unitless count gives, which is the conflation the axis was added to prevent. A claim that says something now beats one that says nothing. Where several say something the first still wins, -which decides the only other unit claimed twice: `SquareMeter` is `Area` and `NuclearCrossSection`, -one of the 72-over-63 collisions, so the two answers differ in name and not in exponents. +which decides the other two units claimed twice: `SquareMeter` is `Area` and `NuclearCrossSection`, +and `SquareMeterPerSecond` is `KinematicViscosity` and `SpecificAngularMomentum`. Both are +75-over-64 collisions, so in each the two answers differ in name and not in exponents. **A relationship is checked before it is emitted.** The operator is written as `Result{ lhs.value() * rhs.value() }`, so the exponents have to agree with the declared result or it @@ -163,6 +171,15 @@ negation. No exponent can catch that: a cross product and its negation have iden the only thing standing between the two is which dimension declares the relationship. A test pins the sign: a force of +10 ŷ at a lever arm of +0.5 x̂ gives +5 about z, and the other order gives −5. +Specific angular momentum is the second relationship under that rule, and it is declared the same +way for the same reason: **h = r × v**, so `cross(Displacement3D, Velocity3D)` is declared on +`Length`. SEM008 passes either declaration — the exponents of h and −h are identical — and +`OrbitalMechanicsQuantityTests` pins it the way the torque test does. Declaring it on `Velocity` +instead does not produce a wrong answer quietly; it produces no `Displacement3D.Cross(Velocity3D)` +at all, because only the declared direction is emitted, so the test stops compiling rather than +silently inverting. That is worth knowing: for a cross product the declaration site is checked by +the call site, and it is the *sign* of an existing call that nothing but a test can check. + **How the generated code is written is measured, not chosen.** See the header of `CppQuantityGenerator` — the same vocabulary written two ways measured 0.9896 and 1.4004 against bare floats on MSVC while GCC and clang folded both away, so the wrong formulation passes on three diff --git a/Semantics.Quantities.Decimal/build/ktsu.Semantics.Quantities.Decimal.props b/Semantics.Quantities.Decimal/build/ktsu.Semantics.Quantities.Decimal.props index d6cfa001..053b32a6 100644 --- a/Semantics.Quantities.Decimal/build/ktsu.Semantics.Quantities.Decimal.props +++ b/Semantics.Quantities.Decimal/build/ktsu.Semantics.Quantities.Decimal.props @@ -89,6 +89,7 @@ + @@ -180,10 +181,17 @@ + + + + + + + diff --git a/Semantics.Quantities.Double/build/ktsu.Semantics.Quantities.Double.props b/Semantics.Quantities.Double/build/ktsu.Semantics.Quantities.Double.props index 8382ba7b..f07b188e 100644 --- a/Semantics.Quantities.Double/build/ktsu.Semantics.Quantities.Double.props +++ b/Semantics.Quantities.Double/build/ktsu.Semantics.Quantities.Double.props @@ -89,6 +89,7 @@ + @@ -180,10 +181,17 @@ + + + + + + + diff --git a/Semantics.Quantities.Float/build/ktsu.Semantics.Quantities.Float.props b/Semantics.Quantities.Float/build/ktsu.Semantics.Quantities.Float.props index bfc7c607..66ab02e0 100644 --- a/Semantics.Quantities.Float/build/ktsu.Semantics.Quantities.Float.props +++ b/Semantics.Quantities.Float/build/ktsu.Semantics.Quantities.Float.props @@ -89,6 +89,7 @@ + @@ -180,10 +181,17 @@ + + + + + + + diff --git a/Semantics.Quantities.Precise/build/ktsu.Semantics.Quantities.Precise.props b/Semantics.Quantities.Precise/build/ktsu.Semantics.Quantities.Precise.props index 5769af15..13a8a9d0 100644 --- a/Semantics.Quantities.Precise/build/ktsu.Semantics.Quantities.Precise.props +++ b/Semantics.Quantities.Precise/build/ktsu.Semantics.Quantities.Precise.props @@ -89,6 +89,7 @@ + @@ -180,10 +181,17 @@ + + + + + + + diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.ConversionsGenerator/ConversionConstants.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.ConversionsGenerator/ConversionConstants.g.cs index 81fddb42..6627c778 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.ConversionsGenerator/ConversionConstants.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.ConversionsGenerator/ConversionConstants.g.cs @@ -239,6 +239,9 @@ internal static class ConversionConstants /// Standard gravity to meters per second squared: 9.80665 m/s² per g (exact by definition) internal const double StandardGravityToMeterPerSecondSquared = 9.80665d; + /// Cubic kilometer per second squared to cubic meter per second squared: 1e9 m³/s² per km³/s² (exact by definition) + internal const double CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared = 1e9d; + /// Dyne to newton conversion: 1e-5 N/dyn (exact by definition) internal const double DyneToNewtons = 1e-5d; @@ -737,6 +740,12 @@ internal static class Values /// StandardGravityToMeterPerSecondSquared parsed into , or when the is converted at each read. private static readonly T? ParsedStandardGravityToMeterPerSecondSquared = StorageLiteral.Parse("9.80665"); + /// Cubic kilometer per second squared to cubic meter per second squared: 1e9 m³/s² per km³/s² (exact by definition) + internal static T CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared => ParsedCubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared ?? T.CreateChecked(ConversionConstants.CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared); + + /// CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared parsed into , or when the is converted at each read. + private static readonly T? ParsedCubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared = StorageLiteral.Parse("1e9"); + /// Dyne to newton conversion: 1e-5 N/dyn (exact by definition) internal static T DyneToNewtons => ParsedDyneToNewtons ?? T.CreateChecked(ConversionConstants.DyneToNewtons); diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.DimensionsGenerator/PhysicalDimensions.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.DimensionsGenerator/PhysicalDimensions.g.cs index e6e85bff..de61456f 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.DimensionsGenerator/PhysicalDimensions.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.DimensionsGenerator/PhysicalDimensions.g.cs @@ -110,6 +110,9 @@ public static class PhysicalDimensions /// Physical dimension: Frequency public static readonly DimensionInfo Frequency = new("Frequency", "T⁻¹", new Dictionary { ["time"] = -1 }, new List { "Frequency", "SamplingRate", "ClockSpeed", "Bandwidth", "Pitch" }); + /// Physical dimension: GravitationalParameter + public static readonly DimensionInfo GravitationalParameter = new("GravitationalParameter", "L³ T⁻²", new Dictionary { ["length"] = 3, ["time"] = -2 }, new List { "GravitationalParameter", "StandardGravitationalParameter" }); + /// Physical dimension: HeatTransferCoefficient public static readonly DimensionInfo HeatTransferCoefficient = new("HeatTransferCoefficient", "M T⁻³ Θ⁻¹", new Dictionary { ["mass"] = 1, ["time"] = -3, ["temperature"] = -1 }, new List { "HeatTransferCoefficient" }); @@ -200,6 +203,12 @@ public static class PhysicalDimensions /// Physical dimension: Snap public static readonly DimensionInfo Snap = new("Snap", "L T⁻⁴", new Dictionary { ["length"] = 1, ["time"] = -4 }, new List { "SnapMagnitude", "Snap1D", "Snap2D", "Snap3D", "Snap4D" }); + /// Physical dimension: SpecificAngularMomentum + public static readonly DimensionInfo SpecificAngularMomentum = new("SpecificAngularMomentum", "L² T⁻¹", new Dictionary { ["length"] = 2, ["time"] = -1 }, new List { "SpecificAngularMomentumMagnitude", "SpecificAngularMomentum3D" }); + + /// Physical dimension: SpecificEnergy + public static readonly DimensionInfo SpecificEnergy = new("SpecificEnergy", "L² T⁻²", new Dictionary { ["length"] = 2, ["time"] = -2 }, new List { "SpecificEnergyMagnitude", "SpecificKineticEnergy", "SpecificEnergy", "SpecificOrbitalEnergy" }); + /// Physical dimension: SpecificHeat public static readonly DimensionInfo SpecificHeat = new("SpecificHeat", "L² T⁻² Θ⁻¹", new Dictionary { ["length"] = 2, ["time"] = -2, ["temperature"] = -1 }, new List { "SpecificHeat", "SpecificEntropy" }); @@ -234,7 +243,7 @@ public static class PhysicalDimensions public static readonly DimensionInfo VolumetricFlowRate = new("VolumetricFlowRate", "L³ T⁻¹", new Dictionary { ["length"] = 3, ["time"] = -1 }, new List { "VolumetricFlowRate" }); /// Gets a frozen collection of all standard physical dimensions. - public static IReadOnlySet All = new HashSet([ AbsorbedDose, Acceleration, AcousticImpedance, AmountOfSubstance, AngularAcceleration, AngularDisplacement, AngularJerk, AngularMomentum, AngularVelocity, Area, CatalyticActivity, Concentration, Density, Dimensionless, DynamicViscosity, ElectricCapacitance, ElectricCharge, ElectricConductance, ElectricConductivity, ElectricCurrent, ElectricField, ElectricFlux, ElectricPotential, ElectricPowerDensity, ElectricResistance, Energy, Entropy, EquivalentDose, Exposure, Force, Frequency, HeatTransferCoefficient, Illuminance, Inductance, Irradiance, Jerk, KinematicViscosity, Length, Loudness, Luminance, LuminousFlux, LuminousIntensity, MagneticFlux, MagneticFluxDensity, Mass, MassFlowRate, MolarEnergy, MolarMass, MomentOfInertia, Momentum, NuclearCrossSection, OpticalPower, Permittivity, Power, Pressure, RadioactiveActivity, RateConstant, ReactionRate, Sensitivity, Sharpness, Snap, SpecificHeat, SurfaceTension, Temperature, ThermalConductivity, ThermalExpansion, ThermalResistance, Time, Torque, Velocity, Volume, VolumetricFlowRate ]); + public static IReadOnlySet All = new HashSet([ AbsorbedDose, Acceleration, AcousticImpedance, AmountOfSubstance, AngularAcceleration, AngularDisplacement, AngularJerk, AngularMomentum, AngularVelocity, Area, CatalyticActivity, Concentration, Density, Dimensionless, DynamicViscosity, ElectricCapacitance, ElectricCharge, ElectricConductance, ElectricConductivity, ElectricCurrent, ElectricField, ElectricFlux, ElectricPotential, ElectricPowerDensity, ElectricResistance, Energy, Entropy, EquivalentDose, Exposure, Force, Frequency, GravitationalParameter, HeatTransferCoefficient, Illuminance, Inductance, Irradiance, Jerk, KinematicViscosity, Length, Loudness, Luminance, LuminousFlux, LuminousIntensity, MagneticFlux, MagneticFluxDensity, Mass, MassFlowRate, MolarEnergy, MolarMass, MomentOfInertia, Momentum, NuclearCrossSection, OpticalPower, Permittivity, Power, Pressure, RadioactiveActivity, RateConstant, ReactionRate, Sensitivity, Sharpness, Snap, SpecificAngularMomentum, SpecificEnergy, SpecificHeat, SurfaceTension, Temperature, ThermalConductivity, ThermalExpansion, ThermalResistance, Time, Torque, Velocity, Volume, VolumetricFlowRate ]); } /// @@ -485,6 +494,14 @@ public interface IFrequencyUnit : IUnit { } +/// +/// Marker interface implemented by every unit of the GravitationalParameter dimension. +/// Generated quantities use this to make In(...) dimensionally type-safe at compile time. +/// +public interface IGravitationalParameterUnit : IUnit +{ +} + /// /// Marker interface implemented by every unit of the HeatTransferCoefficient dimension. /// Generated quantities use this to make In(...) dimensionally type-safe at compile time. @@ -725,6 +742,22 @@ public interface ISnapUnit : IUnit { } +/// +/// Marker interface implemented by every unit of the SpecificAngularMomentum dimension. +/// Generated quantities use this to make In(...) dimensionally type-safe at compile time. +/// +public interface ISpecificAngularMomentumUnit : IUnit +{ +} + +/// +/// Marker interface implemented by every unit of the SpecificEnergy dimension. +/// Generated quantities use this to make In(...) dimensionally type-safe at compile time. +/// +public interface ISpecificEnergyUnit : IUnit +{ +} + /// /// Marker interface implemented by every unit of the SpecificHeat dimension. /// Generated quantities use this to make In(...) dimensionally type-safe at compile time. diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/AccelerationMagnitude.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/AccelerationMagnitude.g.cs index c855e549..1f328f07 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/AccelerationMagnitude.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/AccelerationMagnitude.g.cs @@ -147,5 +147,11 @@ namespace ktsu.Semantics.Quantities; /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] public static Duration operator /(AccelerationMagnitude left, JerkMagnitude right) => Duration.Create(left.Quantity / right.Quantity); + + /// + /// Multiplies AccelerationMagnitude by Area to produce GravitationalParameter. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator *(AccelerationMagnitude left, Area right) => GravitationalParameter.Create(left.Quantity * right.Quantity); } diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Area.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Area.g.cs index 0bca1df8..cabd7936 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Area.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Area.g.cs @@ -219,5 +219,11 @@ namespace ktsu.Semantics.Quantities; /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] public static LuminousIntensity operator *(Area left, Luminance right) => LuminousIntensity.Create(left.Quantity * right.Quantity); + + /// + /// Multiplies Area by AccelerationMagnitude to produce GravitationalParameter. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator *(Area left, AccelerationMagnitude right) => GravitationalParameter.Create(left.Quantity * right.Quantity); } diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Displacement3D.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Displacement3D.g.cs index d15dadbf..f2847242 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Displacement3D.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Displacement3D.g.cs @@ -114,4 +114,10 @@ public Torque3D Cross(Force3D other) return new() { X = (Y * other.Z) - (Z * other.Y), Y = (Z * other.X) - (X * other.Z), Z = (X * other.Y) - (Y * other.X) }; } + /// Typed cross product: Displacement3D x Velocity3D = SpecificAngularMomentum3D. + public SpecificAngularMomentum3D Cross(Velocity3D other) + { + return new() { X = (Y * other.Z) - (Z * other.Y), Y = (Z * other.X) - (X * other.Z), Z = (X * other.Y) - (Y * other.X) }; + } + }; diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GravitationalParameter.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GravitationalParameter.g.cs new file mode 100644 index 00000000..9030728f --- /dev/null +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/GravitationalParameter.g.cs @@ -0,0 +1,139 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors +// + +namespace ktsu.Semantics.Quantities; + +using System.Numerics; + +/// +/// Magnitude (Vector0) quantity for the GravitationalParameter dimension. +/// +/// The numeric storage type. +public readonly partial record struct GravitationalParameter : IVector0, T>, IPhysicalQuantity, T> + where T : struct, INumber +{ + /// Gets the value stored in this quantity (in the dimension's SI base unit). + public T Value => Quantity; + + /// Gets whether this quantity is finite and not NaN. + public bool IsPhysicallyValid => PhysicalQuantityCore.IsPhysicallyValid(Quantity); + + /// Gets a quantity with value zero. + public static GravitationalParameter Zero => Create(T.Zero); + + /// Gets the physical dimension this quantity belongs to. + public DimensionInfo Dimension => PhysicalDimensions.GravitationalParameter; + + /// Gets the stored value, in the dimension's SI base unit. + public T Quantity { get; init; } + + /// + /// Creates a quantity holding , in the SI base unit. + /// + /// The value in the SI base unit. + /// A new quantity holding . + public static GravitationalParameter Create(T value) => new() { Quantity = value }; + + /// + /// Compares this quantity to another of the same physical dimension. + /// + /// The quantity to compare against. + /// A negative number, zero or a positive number as this sorts before, with, or after . + /// When the two do not share a dimension. + public int CompareTo(IPhysicalQuantity other) => PhysicalQuantityCore.Compare, T>(this, other); + + /// + /// Reports whether this quantity shares a dimension and a value with . + /// + /// The quantity to compare against. + /// when both dimension and value match. + public bool Equals(IPhysicalQuantity other) => PhysicalQuantityCore.AreEqual, T>(this, other); + + /// Adds two values. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator +(GravitationalParameter left, GravitationalParameter right) => Create(left.Quantity + right.Quantity); + + /// Negates a . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator -(GravitationalParameter value) => Create(-value.Quantity); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator *(GravitationalParameter left, T right) => Create(left.Quantity * right); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator *(T left, GravitationalParameter right) => Create(left * right.Quantity); + + /// Divides a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator /(GravitationalParameter left, T right) => T.IsZero(right) ? throw new System.DivideByZeroException("Cannot divide by zero.") : Create(left.Quantity / right); + + /// Divides one by another, giving the bare ratio. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static T operator /(GravitationalParameter left, GravitationalParameter right) => T.IsZero(right.Quantity) ? throw new System.DivideByZeroException("Cannot divide by zero.") : left.Quantity / right.Quantity; + + /// Reports whether the left value sorts before the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <(GravitationalParameter left, GravitationalParameter right) => left.Quantity < right.Quantity; + + /// Reports whether the left value sorts before or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <=(GravitationalParameter left, GravitationalParameter right) => left.Quantity <= right.Quantity; + + /// Reports whether the left value sorts after the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >(GravitationalParameter left, GravitationalParameter right) => left.Quantity > right.Quantity; + + /// Reports whether the left value sorts after or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >=(GravitationalParameter left, GravitationalParameter right) => left.Quantity >= right.Quantity; + + /// Returns the stored value as text. + /// The value in the SI base unit, rendered by . + public override string ToString() => Quantity.ToString() ?? string.Empty; + + /// + /// Creates a new from a value in CubicMeterPerSecondSquared. + /// + /// The value in CubicMeterPerSecondSquared. + /// A new instance. + /// Thrown when the resulting magnitude would be negative. + public static GravitationalParameter FromCubicMeterPerSecondSquared(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + + /// + /// Creates a new from a value in CubicKilometerPerSecondSquared. + /// + /// The value in CubicKilometerPerSecondSquared. + /// A new instance. + /// Thrown when the resulting magnitude would be negative. + public static GravitationalParameter FromCubicKilometerPerSecondSquared(T value) => Create(Vector0Guards.EnsureNonNegative((value * Units.ConversionConstants.Values.CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared), nameof(value))); + + /// + /// Converts this quantity's SI-base value to the value in . + /// Cross-dimension calls (e.g. passing a non-GravitationalParameter unit) fail at compile time. + /// + /// The dimensionally-compatible target unit. + /// The value expressed in . + public T In(global::ktsu.Semantics.Quantities.IGravitationalParameterUnit unit) => unit.FromBase(Value); + + /// + /// Subtracts two GravitationalParameter values, returning the absolute difference as a non-negative GravitationalParameter. + /// Magnitude subtraction stays a magnitude (per the unified-vector model). + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator -(GravitationalParameter left, GravitationalParameter right) => Create(T.Abs(left.Quantity - right.Quantity)); + + /// + /// Divides GravitationalParameter by Area to produce AccelerationMagnitude. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static AccelerationMagnitude operator /(GravitationalParameter left, Area right) => AccelerationMagnitude.Create(left.Quantity / right.Quantity); + + /// + /// Divides GravitationalParameter by Length to produce SpecificEnergyMagnitude. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergyMagnitude operator /(GravitationalParameter left, Length right) => SpecificEnergyMagnitude.Create(left.Quantity / right.Quantity); +} + diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Length.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Length.g.cs index 8bf38137..d89396dd 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Length.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/Length.g.cs @@ -257,5 +257,11 @@ namespace ktsu.Semantics.Quantities; /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] public static ForceMagnitude operator *(Length left, SurfaceTension right) => ForceMagnitude.Create(left.Quantity * right.Quantity); + + /// + /// Multiplies Length by SpecificEnergyMagnitude to produce GravitationalParameter. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator *(Length left, SpecificEnergyMagnitude right) => GravitationalParameter.Create(left.Quantity * right.Quantity); } diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentum3D.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentum3D.g.cs new file mode 100644 index 00000000..a9be8110 --- /dev/null +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentum3D.g.cs @@ -0,0 +1,108 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors +// + +#pragma warning disable IDE0040 // Accessibility modifiers required +#pragma warning disable CA2225 // Operator overloads have named alternates + +namespace ktsu.Semantics.Quantities; + +using System; +using System.Numerics; + +/// +/// 3D vector representation of SpecificAngularMomentum. +/// +/// The numeric component type. +public readonly partial record struct SpecificAngularMomentum3D : IVector3, T> + where T : struct, INumber +{ + /// Gets the X component. + public T X { get; init; } + + /// Gets the Y component. + public T Y { get; init; } + + /// Gets the Z component. + public T Z { get; init; } + + /// Gets a vector with all components set to zero. + public static SpecificAngularMomentum3D Zero => new() { X = T.Zero, Y = T.Zero, Z = T.Zero }; + + /// Gets a vector with all components set to one. + public static SpecificAngularMomentum3D One => new() { X = T.One, Y = T.One, Z = T.One }; + + /// Gets the unit vector for the X-axis. + public static SpecificAngularMomentum3D UnitX => new() { X = T.One, Y = T.Zero, Z = T.Zero }; + + /// Gets the unit vector for the Y-axis. + public static SpecificAngularMomentum3D UnitY => new() { X = T.Zero, Y = T.One, Z = T.Zero }; + + /// Gets the unit vector for the Z-axis. + public static SpecificAngularMomentum3D UnitZ => new() { X = T.Zero, Y = T.Zero, Z = T.One }; + + /// Gets the magnitude as a . + public SpecificAngularMomentumMagnitude Magnitude() => SpecificAngularMomentumMagnitude.Create(Length()); + + /// Calculates the length of the vector. + public T Length() + { + T sum = (X * X) + (Y * Y) + (Z * Z); + return StorageMath.Sqrt(sum); + } + + /// Calculates the squared length of the vector. + public T LengthSquared() => (X * X) + (Y * Y) + (Z * Z); + + /// Calculates the dot product of two vectors. + public T Dot(SpecificAngularMomentum3D other) => (X * other.X) + (Y * other.Y) + (Z * other.Z); + + /// Calculates the cross product of two vectors. + public SpecificAngularMomentum3D Cross(SpecificAngularMomentum3D other) + { + return new() { X = (Y * other.Z) - (Z * other.Y), Y = (Z * other.X) - (X * other.Z), Z = (X * other.Y) - (Y * other.X) }; + } + + /// Calculates the distance between two vectors. + public T Distance(SpecificAngularMomentum3D other) + { + T dX = X - other.X; + T dY = Y - other.Y; + T dZ = Z - other.Z; + T sum = (dX * dX) + (dY * dY) + (dZ * dZ); + return StorageMath.Sqrt(sum); + } + + /// Calculates the squared distance between two vectors. + public T DistanceSquared(SpecificAngularMomentum3D other) + { + T dX = X - other.X; + T dY = Y - other.Y; + T dZ = Z - other.Z; + return (dX * dX) + (dY * dY) + (dZ * dZ); + } + + /// Returns a normalized version of the vector. + public SpecificAngularMomentum3D Normalize() + { + T len = Length(); + return new() { X = X / len, Y = Y / len, Z = Z / len }; + } + + /// Adds two vectors. + public static SpecificAngularMomentum3D operator +(SpecificAngularMomentum3D left, SpecificAngularMomentum3D right) => new() { X = left.X + right.X, Y = left.Y + right.Y, Z = left.Z + right.Z }; + + /// Subtracts two vectors. + public static SpecificAngularMomentum3D operator -(SpecificAngularMomentum3D left, SpecificAngularMomentum3D right) => new() { X = left.X - right.X, Y = left.Y - right.Y, Z = left.Z - right.Z }; + + /// Multiplies a vector by a scalar. + public static SpecificAngularMomentum3D operator *(SpecificAngularMomentum3D vector, T scalar) => new() { X = vector.X * scalar, Y = vector.Y * scalar, Z = vector.Z * scalar }; + + /// Multiplies a scalar by a vector. + public static SpecificAngularMomentum3D operator *(T scalar, SpecificAngularMomentum3D vector) => new() { X = scalar * vector.X, Y = scalar * vector.Y, Z = scalar * vector.Z }; + + /// Divides a vector by a scalar. + public static SpecificAngularMomentum3D operator /(SpecificAngularMomentum3D vector, T scalar) => new() { X = vector.X / scalar, Y = vector.Y / scalar, Z = vector.Z / scalar }; + + /// Negates a vector. + public static SpecificAngularMomentum3D operator -(SpecificAngularMomentum3D vector) => new() { X = -vector.X, Y = -vector.Y, Z = -vector.Z }; +}; diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentumMagnitude.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentumMagnitude.g.cs new file mode 100644 index 00000000..28996b07 --- /dev/null +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentumMagnitude.g.cs @@ -0,0 +1,119 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors +// + +namespace ktsu.Semantics.Quantities; + +using System.Numerics; + +/// +/// Magnitude (Vector0) quantity for the SpecificAngularMomentum dimension. +/// +/// The numeric storage type. +public readonly partial record struct SpecificAngularMomentumMagnitude : IVector0, T>, IPhysicalQuantity, T> + where T : struct, INumber +{ + /// Gets the value stored in this quantity (in the dimension's SI base unit). + public T Value => Quantity; + + /// Gets whether this quantity is finite and not NaN. + public bool IsPhysicallyValid => PhysicalQuantityCore.IsPhysicallyValid(Quantity); + + /// Gets a quantity with value zero. + public static SpecificAngularMomentumMagnitude Zero => Create(T.Zero); + + /// Gets the physical dimension this quantity belongs to. + public DimensionInfo Dimension => PhysicalDimensions.SpecificAngularMomentum; + + /// Gets the stored value, in the dimension's SI base unit. + public T Quantity { get; init; } + + /// + /// Creates a quantity holding , in the SI base unit. + /// + /// The value in the SI base unit. + /// A new quantity holding . + public static SpecificAngularMomentumMagnitude Create(T value) => new() { Quantity = value }; + + /// + /// Compares this quantity to another of the same physical dimension. + /// + /// The quantity to compare against. + /// A negative number, zero or a positive number as this sorts before, with, or after . + /// When the two do not share a dimension. + public int CompareTo(IPhysicalQuantity other) => PhysicalQuantityCore.Compare, T>(this, other); + + /// + /// Reports whether this quantity shares a dimension and a value with . + /// + /// The quantity to compare against. + /// when both dimension and value match. + public bool Equals(IPhysicalQuantity other) => PhysicalQuantityCore.AreEqual, T>(this, other); + + /// Adds two values. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificAngularMomentumMagnitude operator +(SpecificAngularMomentumMagnitude left, SpecificAngularMomentumMagnitude right) => Create(left.Quantity + right.Quantity); + + /// Negates a . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificAngularMomentumMagnitude operator -(SpecificAngularMomentumMagnitude value) => Create(-value.Quantity); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificAngularMomentumMagnitude operator *(SpecificAngularMomentumMagnitude left, T right) => Create(left.Quantity * right); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificAngularMomentumMagnitude operator *(T left, SpecificAngularMomentumMagnitude right) => Create(left * right.Quantity); + + /// Divides a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificAngularMomentumMagnitude operator /(SpecificAngularMomentumMagnitude left, T right) => T.IsZero(right) ? throw new System.DivideByZeroException("Cannot divide by zero.") : Create(left.Quantity / right); + + /// Divides one by another, giving the bare ratio. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static T operator /(SpecificAngularMomentumMagnitude left, SpecificAngularMomentumMagnitude right) => T.IsZero(right.Quantity) ? throw new System.DivideByZeroException("Cannot divide by zero.") : left.Quantity / right.Quantity; + + /// Reports whether the left value sorts before the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <(SpecificAngularMomentumMagnitude left, SpecificAngularMomentumMagnitude right) => left.Quantity < right.Quantity; + + /// Reports whether the left value sorts before or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <=(SpecificAngularMomentumMagnitude left, SpecificAngularMomentumMagnitude right) => left.Quantity <= right.Quantity; + + /// Reports whether the left value sorts after the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >(SpecificAngularMomentumMagnitude left, SpecificAngularMomentumMagnitude right) => left.Quantity > right.Quantity; + + /// Reports whether the left value sorts after or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >=(SpecificAngularMomentumMagnitude left, SpecificAngularMomentumMagnitude right) => left.Quantity >= right.Quantity; + + /// Returns the stored value as text. + /// The value in the SI base unit, rendered by . + public override string ToString() => Quantity.ToString() ?? string.Empty; + + /// + /// Creates a new from a value in SquareMeterPerSecond. + /// + /// The value in SquareMeterPerSecond. + /// A new instance. + /// Thrown when the resulting magnitude would be negative. + public static SpecificAngularMomentumMagnitude FromSquareMeterPerSecond(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + + /// + /// Converts this quantity's SI-base value to the value in . + /// Cross-dimension calls (e.g. passing a non-SpecificAngularMomentum unit) fail at compile time. + /// + /// The dimensionally-compatible target unit. + /// The value expressed in . + public T In(global::ktsu.Semantics.Quantities.ISpecificAngularMomentumUnit unit) => unit.FromBase(Value); + + /// + /// Subtracts two SpecificAngularMomentumMagnitude values, returning the absolute difference as a non-negative SpecificAngularMomentumMagnitude. + /// Magnitude subtraction stays a magnitude (per the unified-vector model). + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificAngularMomentumMagnitude operator -(SpecificAngularMomentumMagnitude left, SpecificAngularMomentumMagnitude right) => Create(T.Abs(left.Quantity - right.Quantity)); +} + diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificEnergy.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificEnergy.g.cs new file mode 100644 index 00000000..d1460d92 --- /dev/null +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificEnergy.g.cs @@ -0,0 +1,121 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors +// + +namespace ktsu.Semantics.Quantities; + +using System.Numerics; + +/// +/// Signed one-dimensional (Vector1) quantity for the SpecificEnergy dimension. +/// +/// The numeric storage type. +public readonly partial record struct SpecificEnergy : IVector1, T>, IPhysicalQuantity, T> + where T : struct, INumber +{ + /// Gets the value stored in this quantity (in the dimension's SI base unit). + public T Value => Quantity; + + /// Gets whether this quantity is finite and not NaN. + public bool IsPhysicallyValid => PhysicalQuantityCore.IsPhysicallyValid(Quantity); + + /// Gets a quantity with value zero. + public static SpecificEnergy Zero => Create(T.Zero); + + /// Gets the physical dimension this quantity belongs to. + public DimensionInfo Dimension => PhysicalDimensions.SpecificEnergy; + + /// Gets the stored value, in the dimension's SI base unit. + public T Quantity { get; init; } + + /// + /// Creates a quantity holding , in the SI base unit. + /// + /// The value in the SI base unit. + /// A new quantity holding . + public static SpecificEnergy Create(T value) => new() { Quantity = value }; + + /// + /// Compares this quantity to another of the same physical dimension. + /// + /// The quantity to compare against. + /// A negative number, zero or a positive number as this sorts before, with, or after . + /// When the two do not share a dimension. + public int CompareTo(IPhysicalQuantity other) => PhysicalQuantityCore.Compare, T>(this, other); + + /// + /// Reports whether this quantity shares a dimension and a value with . + /// + /// The quantity to compare against. + /// when both dimension and value match. + public bool Equals(IPhysicalQuantity other) => PhysicalQuantityCore.AreEqual, T>(this, other); + + /// Adds two values. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergy operator +(SpecificEnergy left, SpecificEnergy right) => Create(left.Quantity + right.Quantity); + + /// Subtracts one from another. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergy operator -(SpecificEnergy left, SpecificEnergy right) => Create(left.Quantity - right.Quantity); + + /// Negates a . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergy operator -(SpecificEnergy value) => Create(-value.Quantity); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergy operator *(SpecificEnergy left, T right) => Create(left.Quantity * right); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergy operator *(T left, SpecificEnergy right) => Create(left * right.Quantity); + + /// Divides a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergy operator /(SpecificEnergy left, T right) => T.IsZero(right) ? throw new System.DivideByZeroException("Cannot divide by zero.") : Create(left.Quantity / right); + + /// Divides one by another, giving the bare ratio. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static T operator /(SpecificEnergy left, SpecificEnergy right) => T.IsZero(right.Quantity) ? throw new System.DivideByZeroException("Cannot divide by zero.") : left.Quantity / right.Quantity; + + /// Reports whether the left value sorts before the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <(SpecificEnergy left, SpecificEnergy right) => left.Quantity < right.Quantity; + + /// Reports whether the left value sorts before or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <=(SpecificEnergy left, SpecificEnergy right) => left.Quantity <= right.Quantity; + + /// Reports whether the left value sorts after the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >(SpecificEnergy left, SpecificEnergy right) => left.Quantity > right.Quantity; + + /// Reports whether the left value sorts after or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >=(SpecificEnergy left, SpecificEnergy right) => left.Quantity >= right.Quantity; + + /// Returns the stored value as text. + /// The value in the SI base unit, rendered by . + public override string ToString() => Quantity.ToString() ?? string.Empty; + + /// + /// Creates a new from a value in JoulePerKilogram. + /// + /// The value in JoulePerKilogram. + /// A new instance. + public static SpecificEnergy FromJoulePerKilogram(T value) => Create(value); + + /// + /// Converts this quantity's SI-base value to the value in . + /// Cross-dimension calls (e.g. passing a non-SpecificEnergy unit) fail at compile time. + /// + /// The dimensionally-compatible target unit. + /// The value expressed in . + public T In(global::ktsu.Semantics.Quantities.ISpecificEnergyUnit unit) => unit.FromBase(Value); + + /// + /// Gets the magnitude of this quantity as a . + /// + /// The non-negative magnitude. + public SpecificEnergyMagnitude Magnitude() => SpecificEnergyMagnitude.Create(T.Abs(Value)); +} + diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificEnergyMagnitude.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificEnergyMagnitude.g.cs new file mode 100644 index 00000000..ab34e4b5 --- /dev/null +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificEnergyMagnitude.g.cs @@ -0,0 +1,125 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors +// + +namespace ktsu.Semantics.Quantities; + +using System.Numerics; + +/// +/// Magnitude (Vector0) quantity for the SpecificEnergy dimension. +/// +/// The numeric storage type. +public readonly partial record struct SpecificEnergyMagnitude : IVector0, T>, IPhysicalQuantity, T> + where T : struct, INumber +{ + /// Gets the value stored in this quantity (in the dimension's SI base unit). + public T Value => Quantity; + + /// Gets whether this quantity is finite and not NaN. + public bool IsPhysicallyValid => PhysicalQuantityCore.IsPhysicallyValid(Quantity); + + /// Gets a quantity with value zero. + public static SpecificEnergyMagnitude Zero => Create(T.Zero); + + /// Gets the physical dimension this quantity belongs to. + public DimensionInfo Dimension => PhysicalDimensions.SpecificEnergy; + + /// Gets the stored value, in the dimension's SI base unit. + public T Quantity { get; init; } + + /// + /// Creates a quantity holding , in the SI base unit. + /// + /// The value in the SI base unit. + /// A new quantity holding . + public static SpecificEnergyMagnitude Create(T value) => new() { Quantity = value }; + + /// + /// Compares this quantity to another of the same physical dimension. + /// + /// The quantity to compare against. + /// A negative number, zero or a positive number as this sorts before, with, or after . + /// When the two do not share a dimension. + public int CompareTo(IPhysicalQuantity other) => PhysicalQuantityCore.Compare, T>(this, other); + + /// + /// Reports whether this quantity shares a dimension and a value with . + /// + /// The quantity to compare against. + /// when both dimension and value match. + public bool Equals(IPhysicalQuantity other) => PhysicalQuantityCore.AreEqual, T>(this, other); + + /// Adds two values. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergyMagnitude operator +(SpecificEnergyMagnitude left, SpecificEnergyMagnitude right) => Create(left.Quantity + right.Quantity); + + /// Negates a . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergyMagnitude operator -(SpecificEnergyMagnitude value) => Create(-value.Quantity); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergyMagnitude operator *(SpecificEnergyMagnitude left, T right) => Create(left.Quantity * right); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergyMagnitude operator *(T left, SpecificEnergyMagnitude right) => Create(left * right.Quantity); + + /// Divides a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergyMagnitude operator /(SpecificEnergyMagnitude left, T right) => T.IsZero(right) ? throw new System.DivideByZeroException("Cannot divide by zero.") : Create(left.Quantity / right); + + /// Divides one by another, giving the bare ratio. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static T operator /(SpecificEnergyMagnitude left, SpecificEnergyMagnitude right) => T.IsZero(right.Quantity) ? throw new System.DivideByZeroException("Cannot divide by zero.") : left.Quantity / right.Quantity; + + /// Reports whether the left value sorts before the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <(SpecificEnergyMagnitude left, SpecificEnergyMagnitude right) => left.Quantity < right.Quantity; + + /// Reports whether the left value sorts before or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <=(SpecificEnergyMagnitude left, SpecificEnergyMagnitude right) => left.Quantity <= right.Quantity; + + /// Reports whether the left value sorts after the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >(SpecificEnergyMagnitude left, SpecificEnergyMagnitude right) => left.Quantity > right.Quantity; + + /// Reports whether the left value sorts after or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >=(SpecificEnergyMagnitude left, SpecificEnergyMagnitude right) => left.Quantity >= right.Quantity; + + /// Returns the stored value as text. + /// The value in the SI base unit, rendered by . + public override string ToString() => Quantity.ToString() ?? string.Empty; + + /// + /// Creates a new from a value in JoulePerKilogram. + /// + /// The value in JoulePerKilogram. + /// A new instance. + /// Thrown when the resulting magnitude would be negative. + public static SpecificEnergyMagnitude FromJoulePerKilogram(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + + /// + /// Converts this quantity's SI-base value to the value in . + /// Cross-dimension calls (e.g. passing a non-SpecificEnergy unit) fail at compile time. + /// + /// The dimensionally-compatible target unit. + /// The value expressed in . + public T In(global::ktsu.Semantics.Quantities.ISpecificEnergyUnit unit) => unit.FromBase(Value); + + /// + /// Subtracts two SpecificEnergyMagnitude values, returning the absolute difference as a non-negative SpecificEnergyMagnitude. + /// Magnitude subtraction stays a magnitude (per the unified-vector model). + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificEnergyMagnitude operator -(SpecificEnergyMagnitude left, SpecificEnergyMagnitude right) => Create(T.Abs(left.Quantity - right.Quantity)); + + /// + /// Multiplies SpecificEnergyMagnitude by Length to produce GravitationalParameter. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static GravitationalParameter operator *(SpecificEnergyMagnitude left, Length right) => GravitationalParameter.Create(left.Quantity * right.Quantity); +} + diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificKineticEnergy.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificKineticEnergy.g.cs new file mode 100644 index 00000000..b3dbc35a --- /dev/null +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificKineticEnergy.g.cs @@ -0,0 +1,126 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors +// + +namespace ktsu.Semantics.Quantities; + +using System.Numerics; + +/// +/// Kinetic energy per unit mass, which is never negative. +/// Semantic overload of . +/// +/// The numeric storage type. +public readonly partial record struct SpecificKineticEnergy : IVector0, T>, IPhysicalQuantity, T> + where T : struct, INumber +{ + /// Gets the value stored in this quantity (in the dimension's SI base unit). + public T Value => Quantity; + + /// Gets whether this quantity is finite and not NaN. + public bool IsPhysicallyValid => PhysicalQuantityCore.IsPhysicallyValid(Quantity); + + /// Gets a quantity with value zero. + public static SpecificKineticEnergy Zero => Create(T.Zero); + + /// Gets the physical dimension this quantity belongs to. + public DimensionInfo Dimension => PhysicalDimensions.SpecificEnergy; + + /// Gets the stored value, in the dimension's SI base unit. + public T Quantity { get; init; } + + /// + /// Creates a quantity holding , in the SI base unit. + /// + /// The value in the SI base unit. + /// A new quantity holding . + public static SpecificKineticEnergy Create(T value) => new() { Quantity = value }; + + /// + /// Compares this quantity to another of the same physical dimension. + /// + /// The quantity to compare against. + /// A negative number, zero or a positive number as this sorts before, with, or after . + /// When the two do not share a dimension. + public int CompareTo(IPhysicalQuantity other) => PhysicalQuantityCore.Compare, T>(this, other); + + /// + /// Reports whether this quantity shares a dimension and a value with . + /// + /// The quantity to compare against. + /// when both dimension and value match. + public bool Equals(IPhysicalQuantity other) => PhysicalQuantityCore.AreEqual, T>(this, other); + + /// Adds two values. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificKineticEnergy operator +(SpecificKineticEnergy left, SpecificKineticEnergy right) => Create(left.Quantity + right.Quantity); + + /// Negates a . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificKineticEnergy operator -(SpecificKineticEnergy value) => Create(-value.Quantity); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificKineticEnergy operator *(SpecificKineticEnergy left, T right) => Create(left.Quantity * right); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificKineticEnergy operator *(T left, SpecificKineticEnergy right) => Create(left * right.Quantity); + + /// Divides a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificKineticEnergy operator /(SpecificKineticEnergy left, T right) => T.IsZero(right) ? throw new System.DivideByZeroException("Cannot divide by zero.") : Create(left.Quantity / right); + + /// Divides one by another, giving the bare ratio. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static T operator /(SpecificKineticEnergy left, SpecificKineticEnergy right) => T.IsZero(right.Quantity) ? throw new System.DivideByZeroException("Cannot divide by zero.") : left.Quantity / right.Quantity; + + /// Reports whether the left value sorts before the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <(SpecificKineticEnergy left, SpecificKineticEnergy right) => left.Quantity < right.Quantity; + + /// Reports whether the left value sorts before or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <=(SpecificKineticEnergy left, SpecificKineticEnergy right) => left.Quantity <= right.Quantity; + + /// Reports whether the left value sorts after the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >(SpecificKineticEnergy left, SpecificKineticEnergy right) => left.Quantity > right.Quantity; + + /// Reports whether the left value sorts after or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >=(SpecificKineticEnergy left, SpecificKineticEnergy right) => left.Quantity >= right.Quantity; + + /// Returns the stored value as text. + /// The value in the SI base unit, rendered by . + public override string ToString() => Quantity.ToString() ?? string.Empty; + + /// + /// Creates a new SpecificKineticEnergy from a value in JoulePerKilogram. + /// + /// The value in JoulePerKilogram. + /// A new SpecificKineticEnergy instance. + /// Thrown when the resulting magnitude would be negative. + public static SpecificKineticEnergy FromJoulePerKilogram(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + + /// + /// Converts this quantity's SI-base value to the value in . + /// Cross-dimension calls (e.g. passing a non-SpecificEnergy unit) fail at compile time. + /// + /// The dimensionally-compatible target unit. + /// The value expressed in . + public T In(global::ktsu.Semantics.Quantities.ISpecificEnergyUnit unit) => unit.FromBase(Value); + + /// Implicit conversion to SpecificEnergyMagnitude. + public static implicit operator SpecificEnergyMagnitude(SpecificKineticEnergy value) => SpecificEnergyMagnitude.Create(value.Value); + + /// Explicit conversion from SpecificEnergyMagnitude. + public static explicit operator SpecificKineticEnergy(SpecificEnergyMagnitude value) => Create(value.Value); + + /// Creates a SpecificKineticEnergy from a SpecificEnergyMagnitude value. + public static SpecificKineticEnergy From(SpecificEnergyMagnitude value) => Create(value.Value); + + /// Subtracts two SpecificKineticEnergy values, returning the absolute difference as a non-negative SpecificKineticEnergy. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificKineticEnergy operator -(SpecificKineticEnergy left, SpecificKineticEnergy right) => Create(T.Abs(left.Quantity - right.Quantity)); +} + diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificOrbitalEnergy.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificOrbitalEnergy.g.cs new file mode 100644 index 00000000..f711c707 --- /dev/null +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificOrbitalEnergy.g.cs @@ -0,0 +1,125 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors +// + +namespace ktsu.Semantics.Quantities; + +using System.Numerics; + +/// +/// Orbital energy per unit reduced mass, negative for every bound orbit. +/// Semantic overload of . +/// +/// The numeric storage type. +public readonly partial record struct SpecificOrbitalEnergy : IVector1, T>, IPhysicalQuantity, T> + where T : struct, INumber +{ + /// Gets the value stored in this quantity (in the dimension's SI base unit). + public T Value => Quantity; + + /// Gets whether this quantity is finite and not NaN. + public bool IsPhysicallyValid => PhysicalQuantityCore.IsPhysicallyValid(Quantity); + + /// Gets a quantity with value zero. + public static SpecificOrbitalEnergy Zero => Create(T.Zero); + + /// Gets the physical dimension this quantity belongs to. + public DimensionInfo Dimension => PhysicalDimensions.SpecificEnergy; + + /// Gets the stored value, in the dimension's SI base unit. + public T Quantity { get; init; } + + /// + /// Creates a quantity holding , in the SI base unit. + /// + /// The value in the SI base unit. + /// A new quantity holding . + public static SpecificOrbitalEnergy Create(T value) => new() { Quantity = value }; + + /// + /// Compares this quantity to another of the same physical dimension. + /// + /// The quantity to compare against. + /// A negative number, zero or a positive number as this sorts before, with, or after . + /// When the two do not share a dimension. + public int CompareTo(IPhysicalQuantity other) => PhysicalQuantityCore.Compare, T>(this, other); + + /// + /// Reports whether this quantity shares a dimension and a value with . + /// + /// The quantity to compare against. + /// when both dimension and value match. + public bool Equals(IPhysicalQuantity other) => PhysicalQuantityCore.AreEqual, T>(this, other); + + /// Adds two values. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificOrbitalEnergy operator +(SpecificOrbitalEnergy left, SpecificOrbitalEnergy right) => Create(left.Quantity + right.Quantity); + + /// Subtracts one from another. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificOrbitalEnergy operator -(SpecificOrbitalEnergy left, SpecificOrbitalEnergy right) => Create(left.Quantity - right.Quantity); + + /// Negates a . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificOrbitalEnergy operator -(SpecificOrbitalEnergy value) => Create(-value.Quantity); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificOrbitalEnergy operator *(SpecificOrbitalEnergy left, T right) => Create(left.Quantity * right); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificOrbitalEnergy operator *(T left, SpecificOrbitalEnergy right) => Create(left * right.Quantity); + + /// Divides a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static SpecificOrbitalEnergy operator /(SpecificOrbitalEnergy left, T right) => T.IsZero(right) ? throw new System.DivideByZeroException("Cannot divide by zero.") : Create(left.Quantity / right); + + /// Divides one by another, giving the bare ratio. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static T operator /(SpecificOrbitalEnergy left, SpecificOrbitalEnergy right) => T.IsZero(right.Quantity) ? throw new System.DivideByZeroException("Cannot divide by zero.") : left.Quantity / right.Quantity; + + /// Reports whether the left value sorts before the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <(SpecificOrbitalEnergy left, SpecificOrbitalEnergy right) => left.Quantity < right.Quantity; + + /// Reports whether the left value sorts before or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <=(SpecificOrbitalEnergy left, SpecificOrbitalEnergy right) => left.Quantity <= right.Quantity; + + /// Reports whether the left value sorts after the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >(SpecificOrbitalEnergy left, SpecificOrbitalEnergy right) => left.Quantity > right.Quantity; + + /// Reports whether the left value sorts after or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >=(SpecificOrbitalEnergy left, SpecificOrbitalEnergy right) => left.Quantity >= right.Quantity; + + /// Returns the stored value as text. + /// The value in the SI base unit, rendered by . + public override string ToString() => Quantity.ToString() ?? string.Empty; + + /// + /// Creates a new SpecificOrbitalEnergy from a value in JoulePerKilogram. + /// + /// The value in JoulePerKilogram. + /// A new SpecificOrbitalEnergy instance. + public static SpecificOrbitalEnergy FromJoulePerKilogram(T value) => Create(value); + + /// + /// Converts this quantity's SI-base value to the value in . + /// Cross-dimension calls (e.g. passing a non-SpecificEnergy unit) fail at compile time. + /// + /// The dimensionally-compatible target unit. + /// The value expressed in . + public T In(global::ktsu.Semantics.Quantities.ISpecificEnergyUnit unit) => unit.FromBase(Value); + + /// Implicit conversion to SpecificEnergy. + public static implicit operator SpecificEnergy(SpecificOrbitalEnergy value) => SpecificEnergy.Create(value.Value); + + /// Explicit conversion from SpecificEnergy. + public static explicit operator SpecificOrbitalEnergy(SpecificEnergy value) => Create(value.Value); + + /// Creates a SpecificOrbitalEnergy from a SpecificEnergy value. + public static SpecificOrbitalEnergy From(SpecificEnergy value) => Create(value.Value); +} + diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/StandardGravitationalParameter.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/StandardGravitationalParameter.g.cs new file mode 100644 index 00000000..d520aaf3 --- /dev/null +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/StandardGravitationalParameter.g.cs @@ -0,0 +1,134 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors +// + +namespace ktsu.Semantics.Quantities; + +using System.Numerics; + +/// +/// Gravitational constant times a body's mass, mu = GM. +/// Semantic overload of . +/// +/// The numeric storage type. +public readonly partial record struct StandardGravitationalParameter : IVector0, T>, IPhysicalQuantity, T> + where T : struct, INumber +{ + /// Gets the value stored in this quantity (in the dimension's SI base unit). + public T Value => Quantity; + + /// Gets whether this quantity is finite and not NaN. + public bool IsPhysicallyValid => PhysicalQuantityCore.IsPhysicallyValid(Quantity); + + /// Gets a quantity with value zero. + public static StandardGravitationalParameter Zero => Create(T.Zero); + + /// Gets the physical dimension this quantity belongs to. + public DimensionInfo Dimension => PhysicalDimensions.GravitationalParameter; + + /// Gets the stored value, in the dimension's SI base unit. + public T Quantity { get; init; } + + /// + /// Creates a quantity holding , in the SI base unit. + /// + /// The value in the SI base unit. + /// A new quantity holding . + public static StandardGravitationalParameter Create(T value) => new() { Quantity = value }; + + /// + /// Compares this quantity to another of the same physical dimension. + /// + /// The quantity to compare against. + /// A negative number, zero or a positive number as this sorts before, with, or after . + /// When the two do not share a dimension. + public int CompareTo(IPhysicalQuantity other) => PhysicalQuantityCore.Compare, T>(this, other); + + /// + /// Reports whether this quantity shares a dimension and a value with . + /// + /// The quantity to compare against. + /// when both dimension and value match. + public bool Equals(IPhysicalQuantity other) => PhysicalQuantityCore.AreEqual, T>(this, other); + + /// Adds two values. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static StandardGravitationalParameter operator +(StandardGravitationalParameter left, StandardGravitationalParameter right) => Create(left.Quantity + right.Quantity); + + /// Negates a . + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static StandardGravitationalParameter operator -(StandardGravitationalParameter value) => Create(-value.Quantity); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static StandardGravitationalParameter operator *(StandardGravitationalParameter left, T right) => Create(left.Quantity * right); + + /// Scales a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static StandardGravitationalParameter operator *(T left, StandardGravitationalParameter right) => Create(left * right.Quantity); + + /// Divides a by a bare number. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static StandardGravitationalParameter operator /(StandardGravitationalParameter left, T right) => T.IsZero(right) ? throw new System.DivideByZeroException("Cannot divide by zero.") : Create(left.Quantity / right); + + /// Divides one by another, giving the bare ratio. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static T operator /(StandardGravitationalParameter left, StandardGravitationalParameter right) => T.IsZero(right.Quantity) ? throw new System.DivideByZeroException("Cannot divide by zero.") : left.Quantity / right.Quantity; + + /// Reports whether the left value sorts before the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <(StandardGravitationalParameter left, StandardGravitationalParameter right) => left.Quantity < right.Quantity; + + /// Reports whether the left value sorts before or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator <=(StandardGravitationalParameter left, StandardGravitationalParameter right) => left.Quantity <= right.Quantity; + + /// Reports whether the left value sorts after the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >(StandardGravitationalParameter left, StandardGravitationalParameter right) => left.Quantity > right.Quantity; + + /// Reports whether the left value sorts after or with the right. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static bool operator >=(StandardGravitationalParameter left, StandardGravitationalParameter right) => left.Quantity >= right.Quantity; + + /// Returns the stored value as text. + /// The value in the SI base unit, rendered by . + public override string ToString() => Quantity.ToString() ?? string.Empty; + + /// + /// Creates a new StandardGravitationalParameter from a value in CubicMeterPerSecondSquared. + /// + /// The value in CubicMeterPerSecondSquared. + /// A new StandardGravitationalParameter instance. + /// Thrown when the resulting magnitude would be negative. + public static StandardGravitationalParameter FromCubicMeterPerSecondSquared(T value) => Create(Vector0Guards.EnsureNonNegative(value, nameof(value))); + + /// + /// Creates a new StandardGravitationalParameter from a value in CubicKilometerPerSecondSquared. + /// + /// The value in CubicKilometerPerSecondSquared. + /// A new StandardGravitationalParameter instance. + /// Thrown when the resulting magnitude would be negative. + public static StandardGravitationalParameter FromCubicKilometerPerSecondSquared(T value) => Create(Vector0Guards.EnsureNonNegative((value * Units.ConversionConstants.Values.CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared), nameof(value))); + + /// + /// Converts this quantity's SI-base value to the value in . + /// Cross-dimension calls (e.g. passing a non-GravitationalParameter unit) fail at compile time. + /// + /// The dimensionally-compatible target unit. + /// The value expressed in . + public T In(global::ktsu.Semantics.Quantities.IGravitationalParameterUnit unit) => unit.FromBase(Value); + + /// Implicit conversion to GravitationalParameter. + public static implicit operator GravitationalParameter(StandardGravitationalParameter value) => GravitationalParameter.Create(value.Value); + + /// Explicit conversion from GravitationalParameter. + public static explicit operator StandardGravitationalParameter(GravitationalParameter value) => Create(value.Value); + + /// Creates a StandardGravitationalParameter from a GravitationalParameter value. + public static StandardGravitationalParameter From(GravitationalParameter value) => Create(value.Value); + + /// Subtracts two StandardGravitationalParameter values, returning the absolute difference as a non-negative StandardGravitationalParameter. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Physics quantity operator")] + public static StandardGravitationalParameter operator -(StandardGravitationalParameter left, StandardGravitationalParameter right) => Create(T.Abs(left.Quantity - right.Quantity)); +} + 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 6b496edf..3c2ed75b 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 @@ -2052,6 +2052,105 @@ public MeterPerSecondSquared() { } T IUnit.ToBaseOffsetAs() => T.Zero; } +/// +/// Cubic meter per second squared - SI derived unit of standard gravitational parameter. +/// +public sealed record CubicMeterPerSecondSquared : IUnit, IGravitationalParameterUnit +{ + /// Gets the full name of the unit. + public string Name => "CubicMeterPerSecondSquared"; + + /// Gets the symbol/abbreviation of the unit. + public string Symbol => "m³/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.GravitationalParameter; + + /// Gets the multiplication factor used in the to-base affine conversion. + public double ToBaseFactor => 1d; + + /// Gets the additive offset used in the to-base affine conversion. + public double ToBaseOffset => 0d; + + /// Initializes a new instance of the unit. + public CubicMeterPerSecondSquared() { } + + /// + T IUnit.ToBaseFactorAs() => T.One; + + /// + T IUnit.ToBaseOffsetAs() => T.Zero; +} + +/// +/// Cubic kilometer per second squared - 1e9 m³/s², the unit published values of mu are quoted in. +/// +public sealed record CubicKilometerPerSecondSquared : IUnit, IGravitationalParameterUnit +{ + /// Gets the full name of the unit. + public string Name => "CubicKilometerPerSecondSquared"; + + /// 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.GravitationalParameter; + + /// Gets the multiplication factor used in the to-base affine conversion. + public double ToBaseFactor => CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared; + + /// Gets the additive offset used in the to-base affine conversion. + public double ToBaseOffset => 0d; + + /// Initializes a new instance of the unit. + public CubicKilometerPerSecondSquared() { } + + /// + T IUnit.ToBaseFactorAs() => ConversionConstants.Values.CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared; + + /// + T IUnit.ToBaseOffsetAs() => T.Zero; +} + +/// +/// Joule per kilogram - SI derived unit of specific energy. +/// +public sealed record JoulePerKilogram : IUnit, ISpecificEnergyUnit +{ + /// Gets the full name of the unit. + public string Name => "JoulePerKilogram"; + + /// Gets the symbol/abbreviation of the unit. + public string Symbol => "J/kg"; + + /// Gets the unit system this unit belongs to. + public UnitSystem System => UnitSystem.SIDerived; + + /// Gets the physical dimension this unit measures. + public DimensionInfo Dimension => PhysicalDimensions.SpecificEnergy; + + /// Gets the multiplication factor used in the to-base affine conversion. + public double ToBaseFactor => 1d; + + /// Gets the additive offset used in the to-base affine conversion. + public double ToBaseOffset => 0d; + + /// Initializes a new instance of the unit. + public JoulePerKilogram() { } + + /// + T IUnit.ToBaseFactorAs() => T.One; + + /// + T IUnit.ToBaseOffsetAs() => T.Zero; +} + /// /// Bar - Metric unit of pressure. /// @@ -5520,7 +5619,7 @@ public Micromolar() { } /// /// Square meter per second - SI derived unit of kinematic viscosity. /// -public sealed record SquareMeterPerSecond : IUnit, IKinematicViscosityUnit +public sealed record SquareMeterPerSecond : IUnit, IKinematicViscosityUnit, ISpecificAngularMomentumUnit { /// Gets the full name of the unit. public string Name => "SquareMeterPerSecond"; @@ -6318,12 +6417,18 @@ public static class Units /// Singleton CubicInch instance. public static readonly CubicInch CubicInch = new CubicInch(); + /// Singleton CubicKilometerPerSecondSquared instance. + public static readonly CubicKilometerPerSecondSquared CubicKilometerPerSecondSquared = new CubicKilometerPerSecondSquared(); + /// Singleton CubicMeter instance. public static readonly CubicMeter CubicMeter = new CubicMeter(); /// Singleton CubicMeterPerSecond instance. public static readonly CubicMeterPerSecond CubicMeterPerSecond = new CubicMeterPerSecond(); + /// Singleton CubicMeterPerSecondSquared instance. + public static readonly CubicMeterPerSecondSquared CubicMeterPerSecondSquared = new CubicMeterPerSecondSquared(); + /// Singleton Curie instance. public static readonly Curie Curie = new Curie(); @@ -6429,6 +6534,9 @@ public static class Units /// Singleton JoulePerKelvin instance. public static readonly JoulePerKelvin JoulePerKelvin = new JoulePerKelvin(); + /// Singleton JoulePerKilogram instance. + public static readonly JoulePerKilogram JoulePerKilogram = new JoulePerKilogram(); + /// Singleton JoulePerKilogramKelvin instance. public static readonly JoulePerKilogramKelvin JoulePerKilogramKelvin = new JoulePerKilogramKelvin(); diff --git a/Semantics.SourceGenerators/Metadata/conversions.json b/Semantics.SourceGenerators/Metadata/conversions.json index 280bc0ed..808cb0b5 100644 --- a/Semantics.SourceGenerators/Metadata/conversions.json +++ b/Semantics.SourceGenerators/Metadata/conversions.json @@ -482,6 +482,17 @@ } ] }, + { + "category": "GravitationalParameter", + "description": "Gravitational parameter unit conversion factors", + "factors": [ + { + "name": "CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared", + "description": "Cubic kilometer per second squared to cubic meter per second squared: 1e9 m³/s² per km³/s² (exact by definition)", + "value": "1e9" + } + ] + }, { "category": "Force", "description": "Force unit conversion factors", diff --git a/Semantics.SourceGenerators/Metadata/dimensions.json b/Semantics.SourceGenerators/Metadata/dimensions.json index 35968258..65388adf 100644 --- a/Semantics.SourceGenerators/Metadata/dimensions.json +++ b/Semantics.SourceGenerators/Metadata/dimensions.json @@ -84,7 +84,8 @@ "derivatives": [], "dotProducts": [], "crossProducts": [ - { "other": "Force", "result": "Torque", "forms": [3] } + { "other": "Force", "result": "Torque", "forms": [3] }, + { "other": "Velocity", "result": "SpecificAngularMomentum", "forms": [3] } ] }, { @@ -1372,6 +1373,65 @@ "derivatives": [], "dotProducts": [], "crossProducts": [] + }, + { + "name": "GravitationalParameter", + "symbol": "L³ T⁻²", + "dimensionalFormula": { "length": 3, "time": -2 }, + "availableUnits": ["CubicMeterPerSecondSquared", "CubicKilometerPerSecondSquared"], + "quantities": { + "vector0": { + "base": "GravitationalParameter", + "overloads": [ + { "name": "StandardGravitationalParameter", "description": "Gravitational constant times a body's mass, mu = GM." } + ] + } + }, + "integrals": [], + "derivatives": [ + { "other": "Area", "result": "Acceleration" }, + { "other": "Length", "result": "SpecificEnergy" } + ], + "dotProducts": [], + "crossProducts": [] + }, + { + "name": "SpecificEnergy", + "symbol": "L² T⁻²", + "dimensionalFormula": { "length": 2, "time": -2 }, + "availableUnits": ["JoulePerKilogram"], + "quantities": { + "vector0": { + "base": "SpecificEnergyMagnitude", + "overloads": [ + { "name": "SpecificKineticEnergy", "description": "Kinetic energy per unit mass, which is never negative." } + ] + }, + "vector1": { + "base": "SpecificEnergy", + "overloads": [ + { "name": "SpecificOrbitalEnergy", "description": "Orbital energy per unit reduced mass, negative for every bound orbit." } + ] + } + }, + "integrals": [], + "derivatives": [], + "dotProducts": [], + "crossProducts": [] + }, + { + "name": "SpecificAngularMomentum", + "symbol": "L² T⁻¹", + "dimensionalFormula": { "length": 2, "time": -1 }, + "availableUnits": ["SquareMeterPerSecond"], + "quantities": { + "vector0": { "base": "SpecificAngularMomentumMagnitude" }, + "vector3": { "base": "SpecificAngularMomentum3D" } + }, + "integrals": [], + "derivatives": [], + "dotProducts": [], + "crossProducts": [] } ] } diff --git a/Semantics.SourceGenerators/Metadata/units.json b/Semantics.SourceGenerators/Metadata/units.json index 02607a6c..c437aa0b 100644 --- a/Semantics.SourceGenerators/Metadata/units.json +++ b/Semantics.SourceGenerators/Metadata/units.json @@ -433,6 +433,25 @@ "description": "Meters per second squared - SI derived unit of acceleration.", "system": "SIDerived" }, + { + "name": "CubicMeterPerSecondSquared", + "symbol": "m³/s²", + "description": "Cubic meter per second squared - SI derived unit of standard gravitational parameter.", + "system": "SIDerived" + }, + { + "name": "CubicKilometerPerSecondSquared", + "symbol": "km³/s²", + "description": "Cubic kilometer per second squared - 1e9 m³/s², the unit published values of mu are quoted in.", + "system": "SIDerived", + "conversionFactor": "CubicKilometerPerSecondSquaredToCubicMeterPerSecondSquared" + }, + { + "name": "JoulePerKilogram", + "symbol": "J/kg", + "description": "Joule per kilogram - SI derived unit of specific energy.", + "system": "SIDerived" + }, { "name": "Bar", "symbol": "bar", diff --git a/Semantics.Test/Quantities/OrbitalMechanicsQuantityTests.cs b/Semantics.Test/Quantities/OrbitalMechanicsQuantityTests.cs new file mode 100644 index 00000000..e8b60392 --- /dev/null +++ b/Semantics.Test/Quantities/OrbitalMechanicsQuantityTests.cs @@ -0,0 +1,141 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors + +namespace ktsu.Semantics.Test.Quantities; + +using ktsu.Semantics.Quantities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +/// +/// Covers the three dimensions orbital mechanics cannot be written without — +/// GravitationalParameter (L³T⁻²), SpecificEnergy (L²T⁻²) and +/// SpecificAngularMomentum (L²T⁻¹) — and the relationships declared on them. Issue #240. +/// +/// +/// Two of the three are name collisions on exponent vectors that already exist: +/// SpecificEnergy shares L²T⁻² with AbsorbedDose and EquivalentDose, and +/// SpecificAngularMomentum shares L²T⁻¹ with KinematicViscosity. SEM008 checks the +/// exponents, so what it cannot check is what these tests are for: the sign of a cross product, and +/// whether a form can hold a negative value at all. +/// +[TestClass] +public sealed class OrbitalMechanicsQuantityTests +{ + /// Standard gravitational parameter of the Earth, in m³/s² (IERS 2010). + private const double EarthMu = 3.986004418e14; + + /// Mean equatorial radius of the Earth, in metres. + private const double EarthRadius = 6.371e6; + + // ------------------------------------------------ GravitationalParameter + + /// + /// g = mu / r². At the Earth's surface this lands on the familiar ~9.82 m/s², which is the + /// check that the L³T⁻² exponents and the m³/s² unit describe the same quantity. + /// + [TestMethod] + public void GravitationalParameter_Over_Area_Is_An_Acceleration() + { + GravitationalParameter mu = GravitationalParameter.FromCubicMeterPerSecondSquared(EarthMu); + Area rSquared = Area.FromSquareMeter(EarthRadius * EarthRadius); + + AccelerationMagnitude g = mu / rSquared; + + Assert.AreEqual(EarthMu / (EarthRadius * EarthRadius), g.Value, 1e-9); + Assert.AreEqual(9.82, g.Value, 0.01); + } + + /// + /// mu / r is a specific energy — the magnitude form, because mu and r are both positive. + /// + [TestMethod] + public void GravitationalParameter_Over_Length_Is_A_SpecificEnergy() + { + GravitationalParameter mu = GravitationalParameter.FromCubicMeterPerSecondSquared(EarthMu); + Length r = Length.FromMeter(EarthRadius); + + SpecificEnergyMagnitude energy = mu / r; + + Assert.AreEqual(EarthMu / EarthRadius, energy.Value, 1e-3); + } + + /// + /// Published values of mu are quoted in km³/s², so the conversion is the one a caller actually + /// reaches for. The factor is 1e9 exactly, not 1e3. + /// + [TestMethod] + public void GravitationalParameter_Converts_From_CubicKilometerPerSecondSquared() + { + GravitationalParameter mu = GravitationalParameter.FromCubicKilometerPerSecondSquared(398600.4418); + + Assert.AreEqual(EarthMu, mu.Value, 1.0); + } + + // ------------------------------------------------------- SpecificEnergy + + /// + /// The reason SpecificEnergy's base is a signed vector1 and not a vector0: specific + /// orbital energy is epsilon = -mu / 2a, which is negative for every bound orbit. A vector0 + /// base would run Vector0Guards.EnsureNonNegative and throw on the ordinary case of a + /// satellite in orbit — a type that fails its own assertion on every real input. + /// + [TestMethod] + public void SpecificOrbitalEnergy_Holds_The_Negative_Value_Every_Bound_Orbit_Has() + { + // A 400 km circular orbit: a = R + 400 km. + double semiMajorAxis = EarthRadius + 400e3; + double expected = -EarthMu / (2.0 * semiMajorAxis); + + SpecificOrbitalEnergy epsilon = SpecificOrbitalEnergy.FromJoulePerKilogram(expected); + + Assert.IsTrue(epsilon.Value < 0.0, "A bound orbit has negative specific orbital energy."); + Assert.AreEqual(expected, epsilon.Value, 1e-3); + } + + /// + /// The vector0 form alongside it keeps the non-negativity guard, which is what makes it the + /// right form for specific kinetic energy. + /// + [TestMethod] + public void SpecificKineticEnergy_Rejects_A_Negative_Value() + { + _ = Assert.ThrowsExactly( + () => SpecificKineticEnergy.FromJoulePerKilogram(-1.0)); + } + + // ----------------------------------------------- SpecificAngularMomentum + + /// + /// h = r × v, not v × r. The exponents cannot tell the two apart — a cross product and its + /// negation have identical dimensions — so the only thing standing between correct and + /// silently negated is that the relationship is declared on Length, which is what puts + /// the operands in that order. This is the same hazard the torque declaration got wrong. + /// + [TestMethod] + public void Displacement3D_Cross_Velocity3D_Has_The_Sign_Of_r_Cross_v() + { + Displacement3D r = new() { X = 0.5, Y = 0.0, Z = 0.0 }; + Velocity3D v = new() { X = 0.0, Y = 10.0, Z = 0.0 }; + + SpecificAngularMomentum3D h = r.Cross(v); + + // +0.5 x̂ crossed with +10 ŷ points along +ẑ. The other order gives -5, which is what this + // would assert if the relationship were declared on Velocity instead of Length. + Assert.AreEqual(0.0, h.X, 1e-10); + Assert.AreEqual(0.0, h.Y, 1e-10); + Assert.AreEqual(5.0, h.Z, 1e-10); + } + + /// + /// The direction of h is what encodes the orbital plane, so the magnitude form has to be + /// reachable from the vector form rather than replacing it. + /// + [TestMethod] + public void SpecificAngularMomentum3D_Magnitude_Is_The_Magnitude_Form() + { + SpecificAngularMomentum3D h = new() { X = 3.0, Y = 4.0, Z = 0.0 }; + + SpecificAngularMomentumMagnitude magnitude = h.Magnitude(); + + Assert.AreEqual(5.0, magnitude.Value, 1e-10); + } +} From c17ab147a953cc900a946ee8a1841424e64e51ae Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 14:54:41 +0000 Subject: [PATCH 2/3] Use Assert.IsLessThan for the negative-energy assertion [patch] SonarCloud flagged MSTEST0037 on the new test: Assert.IsTrue(x < 0.0) hides the compared values from the failure message. Assert.IsLessThan reports them. Argument order verified rather than assumed -- IsLessThan(upperBound, value) asserts value < upperBound, confirmed by flipping the expected energy positive and watching this test, and only this test, fail. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HLTGLrPaSc3E9APdgUq6wn --- Semantics.Test/Quantities/OrbitalMechanicsQuantityTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Semantics.Test/Quantities/OrbitalMechanicsQuantityTests.cs b/Semantics.Test/Quantities/OrbitalMechanicsQuantityTests.cs index e8b60392..1d6d7311 100644 --- a/Semantics.Test/Quantities/OrbitalMechanicsQuantityTests.cs +++ b/Semantics.Test/Quantities/OrbitalMechanicsQuantityTests.cs @@ -87,7 +87,7 @@ public void SpecificOrbitalEnergy_Holds_The_Negative_Value_Every_Bound_Orbit_Has SpecificOrbitalEnergy epsilon = SpecificOrbitalEnergy.FromJoulePerKilogram(expected); - Assert.IsTrue(epsilon.Value < 0.0, "A bound orbit has negative specific orbital energy."); + Assert.IsLessThan(0.0, epsilon.Value, "A bound orbit has negative specific orbital energy."); Assert.AreEqual(expected, epsilon.Value, 1e-3); } From d7d5f6d6b4b49fa0043c3a02f21b60fb59d222c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 00:21:50 +0000 Subject: [PATCH 3/3] Regenerate SpecificAngularMomentum3D after merging main [patch] Merging main brought in #263, which adds DistanceTo() to every vector form and fully qualifies the magnitude return type. SpecificAngularMomentum3D is new on this branch, so it was written by the pre-#263 generator and the merge had no way to know it needed reemitting. Regenerated with the repo's tooling rather than hand-edited. The two changes compose as intended: the new vector form now carries the same typed Magnitude() and DistanceTo() as every other. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HLTGLrPaSc3E9APdgUq6wn --- .../SpecificAngularMomentum3D.g.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentum3D.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentum3D.g.cs index a9be8110..483a3c15 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentum3D.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.QuantitiesGenerator/SpecificAngularMomentum3D.g.cs @@ -40,8 +40,12 @@ namespace ktsu.Semantics.Quantities; /// Gets the unit vector for the Z-axis. public static SpecificAngularMomentum3D UnitZ => new() { X = T.Zero, Y = T.Zero, Z = T.One }; - /// Gets the magnitude as a . - public SpecificAngularMomentumMagnitude Magnitude() => SpecificAngularMomentumMagnitude.Create(Length()); + /// Gets the magnitude of this vector as a . + public global::ktsu.Semantics.Quantities.SpecificAngularMomentumMagnitude Magnitude() => global::ktsu.Semantics.Quantities.SpecificAngularMomentumMagnitude.Create(Length()); + + /// Gets the distance to another vector as a . + /// The vector to measure the distance to. + public global::ktsu.Semantics.Quantities.SpecificAngularMomentumMagnitude DistanceTo(SpecificAngularMomentum3D other) => global::ktsu.Semantics.Quantities.SpecificAngularMomentumMagnitude.Create(Distance(other)); /// Calculates the length of the vector. public T Length()