Add GravitationalParameter, SpecificEnergy and SpecificAngularMomentum [minor] - #262
Conversation
…m [minor] 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<X> convention rather than the issue's Meter Cubed<X> 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLTGLrPaSc3E9APdgUq6wn
|
| Check | Result |
|---|---|
| Test on ubuntu-latest | ✅ |
| Test on macos-latest | ✅ |
| Test on windows-latest | ✅ |
| Generated files up to date | ✅ |
| CodeQL | ✅ |
| Analyze (csharp) ×2, Analyze (actions) | ✅ |
The Generated files up to date pass is the one worth calling out: it rebuilds and re-runs Generate-AliasProps.ps1, so it independently confirms the committed generated source and the 228 alias entries match the metadata in this PR.
(Edited to correct the commit SHA — an earlier version of this comment cited ce49ffa, which is not a commit on this branch. The head is 35b2e8c; the check results above are unchanged.)
Generated by Claude Code
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLTGLrPaSc3E9APdgUq6wn
…orbital-dimensions
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLTGLrPaSc3E9APdgUq6wn
|



Fixes #240
Adds the three dimensions orbital mechanics cannot be written without.
dimensions.jsongoes from 72 entries over 63 exponent vectors to 75 over 64 — onlyGravitationalParameterbrings a vector of its own.GravitationalParameterSpecificEnergyAbsorbedDose,EquivalentDoseSpecificAngularMomentumKinematicViscosityThe two decisions a reviewer should not reverse
SpecificEnergy's base is a signedvector1, not avector0. This is the one that must not be got backwards, and it will look inconsistent with its neighbours, which arevector0. Specific orbital energy is ε = −μ/2a, negative for every bound orbit. Avector0base runsVector0Guards.EnsureNonNegativeand would throwArgumentExceptionon the entirely ordinary case of a satellite in orbit — a type that fails its own assertion on every real input. The generated code confirms the split:SpecificEnergy<T>.FromJoulePerKilogram→Create(value), no guardSpecificEnergyMagnitude<T>.FromJoulePerKilogram→Create(Vector0Guards.EnsureNonNegative(...))A
vector0form sits alongside it, carryingSpecificKineticEnergy, which genuinely is non-negative.cross(Displacement3D, Velocity3D)is declared onLength, notVelocity. h = r × v, not v × r, and the exponents cannot tell the two apart because a cross product and its negation have identical dimensions — SEM008 passes either way. Declaring it onLengthis what puts the operands in that order. This is exactly how theForce-vs-Lengthdeclaration of torque went wrong before.How each formula was checked
Following #216's method — two witnesses rather than intuition — each formula was checked against the units it carries and the relationships it participates in, not against a recollection of the physics:
GravitationalParameter / Area → Acceleration— L³T⁻² − L² = L T⁻², and the test asserts μ_Earth / R_Earth² lands on ~9.82 m/s²GravitationalParameter / Length → SpecificEnergy— L³T⁻² − L = L²T⁻²cross(Length, Velocity) → SpecificAngularMomentum— L + L T⁻¹ = L² T⁻¹All three were emitted; the build reports no SEM008.
Evidence the tests are load-bearing
The sign assertion was verified two ways rather than assumed:
+5.0→−5.0onh.Z) failed exactly 1 of 1290 tests — so the assertion pins a real value, and the emitted operator really is r × v.Velocity— the mistake the torque relationship once made — producederror CS1503: cannot convert from 'Velocity3D<double>' to 'Displacement3D<double>', with no SEM008 diagnostic, confirming the exponents cannot catch it and only the declaration site and the test can.Worth recording from (2): for a cross product, declaring it on the wrong dimension does not silently invert an answer — only the declared direction is emitted, so the call site stops compiling. It is the sign of an existing call that nothing but a test can check.
Two deviations from the issue, both deliberate
MeterCubedPerSecondSquared/KilometerCubedPerSecondSquared. The repo's convention is aCubic<X>prefix —CubicMeter,CubicMeterPerSecond,CubicCentimeter,WattPerCubicMeter— so these areCubicMeterPerSecondSquaredandCubicKilometerPerSecondSquared.physicalConstraints: { minExclusive: "0" }onGravitationalParameteris not set. The issue called it "arguably right", butPhysicalConstraintsis declared only onOverloadDefinition, not onVectorFormDefinition, so it cannot be placed on a base without a model change. Thevector0base already carries the non-negativity guard; only the upgrade to strictly-positive is unavailable. Left as a separate question rather than widening this PR.SquareMeterPerSecondis reused forSpecificAngularMomentumrather than duplicated, making it the second unit claimed by two dimensions afterSquareMeter. Both collide on identical exponents, so theIUnit.Dimensionanswer differs in name only — the ruleCLAUDE.mdalready documents.Verification
Run locally on the committed tree, mirroring
verify-generated.yml:dotnet build Semantics.Quantities -c Release→ succeeded, 0 warnings, no SEM diagnosticspwsh scripts/Generate-AliasProps.ps1→ 220 → 228 aliases across all four storage packages, matching the 8 new types exactlygit diff --exit-codeafter rebuild + regenerate → clean, so theverify-generatedgate passesSemantics.Test→ 1290 total, 0 failed (1282 passed, 8 skipped Windows-only); 7 of these are newSemantics.Cpp.Test→ 43 total, 0 failed, so the sharedSemantics.Vocabularycheck is happy on both projectionsDocumentation
CLAUDE.mdupdated: the counts (75-over-64, 220 classes — 153 magnitudes, 29 signed scalars, 38 vectors), the angle-axis figure (62 → 64), the second twice-claimed unit, the two new collisions as the clearest illustration of why the nominal layer exists, and the h = r × v declaration rule beside the torque one. The before-figures in that file were reproduced exactly by the counting script before changing them, which is what makes the new ones trustworthy.🤖 Generated with Claude Code
https://claude.ai/code/session_01HLTGLrPaSc3E9APdgUq6wn
Generated by Claude Code