Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ it is not unique, so there would be nothing to return. `Magnitude()` and `Distan
that came out of a square root is non-negative by construction, and it keeps the guard off a call
that belongs in a hot loop.

Every vector form also gets the per-unit surface the scalars have: one `From{Unit}(T x, T y, T z)`
factory per entry in the dimension's `availableUnits`, named by the unit's singular lemma exactly as
the scalar factories are, and an `In(unit)` reader. A vector form reaches its units through the
dimension, so an overload gets them too — `Position3D<T>.FromKilometer(6778, 0, 0)` is the call that
used to be a hand-written ×1000 at every construction site. The factories convert each component
through the same `Values<T>` holder the scalar ones read, so a `decimal` or `PreciseNumber` vector
converts at its own precision rather than through `double`.

Two things the vector surface does differently, both deliberate. It carries **no `Vector0Guards`**:
components are signed by construction, a position with a negative X is ordinary, and the V0
non-negativity rule must not leak into the vectors. And `In(unit)` returns a **tuple of components**,
`(T X, T Y, T Z)`, not the vector type — it cannot return the vector type, because the result is no
longer in base units and a `Position3D` that is not in base units would be a lie in the type system.
A dimension with an offset unit gets no vector unit surface at all; see SEM010.

All generated types are generic over a numeric storage type: `where T : struct, INumber<T>`.

### Resolved design decisions
Expand Down Expand Up @@ -483,6 +498,7 @@ var converted = sourceString.As<SourceType, TargetType>();
- **SEM007** — a metadata file could not be parsed. Replaces the base generator's `CONV001` in category `SourceGenerator`, and covers the path that used to swallow the exception, where a malformed `units.json` silently produced factories with no scale factor.
- **SEM008** — a relationship's declared result does not follow from the dimensions of its operands, or its value is signed and the declared result is a magnitude. The check comes from `Semantics.Vocabulary`, shared with the C++ projection; before that this side checked the names (SEM001) and the forms (SEM003) and then emitted the operator, so `Sensitivity * Pressure -> ElectricPotential` shipped as a working C# operator computing the wrong physics — which is what found that bug, and it is now fixed. **No operator is generated** for a refused relationship, in any of the directions C# spells a product in — that followed from making the vocabulary drive emission rather than only check it, and the removal is documented in `docs/migration-guide-5.0.md`. Suppressed in `Semantics.Quantities.csproj` because ktsu.Sdk builds warnings as errors and the four below are outstanding; `UnkeepableRelationshipTests` pins the set, and asserts that none of them is in the compiled surface, so a fifth fails there rather than disappearing into the suppression.
- **SEM009**: a factor's `value` in `conversions.json` is neither a decimal literal nor a fraction of two with a non-zero denominator, or a `double` cannot hold it (a literal, operand, or quotient beyond its range, or a non-zero value that rounds to zero). An error, and no constant is generated for it, because every unit using the factor would otherwise fail to compile far from the metadata line that caused it, or convert with a wrong factor.
- **SEM010**: a dimension declares both a vector form and a unit converting with an additive offset. Adding 273.15 to each component of a displacement is not a unit change, so the whole per-unit surface — every `From{Unit}` factory and the `In(unit)` reader — is withheld from that dimension's vector types rather than emitted quietly wrong. Withheld as a whole rather than per-unit, because `In` takes the dimension's `I{Dimension}Unit` and would accept the offset unit at runtime even if only its factory were skipped. The scalar forms are unaffected: the offset is correct for a V0 or V1. Defensive — no dimension declaring a vector form has an offset unit today, and `TheRealMetadataReportsNothingUnexpected` is what keeps that true.
- Descriptors are allocated from `SemanticsDiagnostics`, which is the one place to add a new one. `AnalyzerReleaseTrackingTests` fails if the identifier is missing from `AnalyzerReleases.Unshipped.md`, so RS2008 no longer surfaces only after a push.
- See `docs/physics-generator.md` for the full schema and an end-to-end "add a dimension" walk-through.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ namespace ktsu.Semantics.Quantities;
/// <param name="other">The vector to measure the distance to.</param>
public global::ktsu.Semantics.Quantities.AccelerationMagnitude<T> DistanceTo(Acceleration2D<T> other) => global::ktsu.Semantics.Quantities.AccelerationMagnitude<T>.Create(Distance(other));

/// <summary>Creates a <see cref="Acceleration2D{T}"/> from components in MeterPerSecondSquared.</summary>
/// <param name="x">The X component, in MeterPerSecondSquared.</param>
/// <param name="y">The Y component, in MeterPerSecondSquared.</param>
/// <returns>A new <see cref="Acceleration2D{T}"/> storing the SI-base equivalent.</returns>
public static Acceleration2D<T> FromMeterPerSecondSquared(T x, T y) => new() { X = x, Y = y };

/// <summary>Creates a <see cref="Acceleration2D{T}"/> from components in StandardGravity.</summary>
/// <param name="x">The X component, in StandardGravity.</param>
/// <param name="y">The Y component, in StandardGravity.</param>
/// <returns>A new <see cref="Acceleration2D{T}"/> storing the SI-base equivalent.</returns>
public static Acceleration2D<T> FromStandardGravity(T x, T y) => new() { X = (x * Units.ConversionConstants.Values<T>.StandardGravityToMeterPerSecondSquared), Y = (y * Units.ConversionConstants.Values<T>.StandardGravityToMeterPerSecondSquared) };

/// <summary>Converts this vector's SI-base components to <paramref name="unit"/>.</summary>
/// <remarks>Returns bare components rather than a <see cref="Acceleration2D{T}"/>, because a vector that is not in base units cannot be one.</remarks>
/// <param name="unit">The dimensionally-compatible target unit.</param>
/// <returns>The components expressed in <paramref name="unit"/>.</returns>
public (T X, T Y) In(global::ktsu.Semantics.Quantities.IAccelerationUnit unit) => (unit.FromBase(X), unit.FromBase(Y));

/// <summary>Calculates the length of the vector.</summary>
public T Length()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ namespace ktsu.Semantics.Quantities;
/// <param name="other">The vector to measure the distance to.</param>
public global::ktsu.Semantics.Quantities.AccelerationMagnitude<T> DistanceTo(Acceleration3D<T> other) => global::ktsu.Semantics.Quantities.AccelerationMagnitude<T>.Create(Distance(other));

/// <summary>Creates a <see cref="Acceleration3D{T}"/> from components in MeterPerSecondSquared.</summary>
/// <param name="x">The X component, in MeterPerSecondSquared.</param>
/// <param name="y">The Y component, in MeterPerSecondSquared.</param>
/// <param name="z">The Z component, in MeterPerSecondSquared.</param>
/// <returns>A new <see cref="Acceleration3D{T}"/> storing the SI-base equivalent.</returns>
public static Acceleration3D<T> FromMeterPerSecondSquared(T x, T y, T z) => new() { X = x, Y = y, Z = z };

/// <summary>Creates a <see cref="Acceleration3D{T}"/> from components in StandardGravity.</summary>
/// <param name="x">The X component, in StandardGravity.</param>
/// <param name="y">The Y component, in StandardGravity.</param>
/// <param name="z">The Z component, in StandardGravity.</param>
/// <returns>A new <see cref="Acceleration3D{T}"/> storing the SI-base equivalent.</returns>
public static Acceleration3D<T> FromStandardGravity(T x, T y, T z) => new() { X = (x * Units.ConversionConstants.Values<T>.StandardGravityToMeterPerSecondSquared), Y = (y * Units.ConversionConstants.Values<T>.StandardGravityToMeterPerSecondSquared), Z = (z * Units.ConversionConstants.Values<T>.StandardGravityToMeterPerSecondSquared) };

/// <summary>Converts this vector's SI-base components to <paramref name="unit"/>.</summary>
/// <remarks>Returns bare components rather than a <see cref="Acceleration3D{T}"/>, because a vector that is not in base units cannot be one.</remarks>
/// <param name="unit">The dimensionally-compatible target unit.</param>
/// <returns>The components expressed in <paramref name="unit"/>.</returns>
public (T X, T Y, T Z) In(global::ktsu.Semantics.Quantities.IAccelerationUnit unit) => (unit.FromBase(X), unit.FromBase(Y), unit.FromBase(Z));

/// <summary>Calculates the length of the vector.</summary>
public T Length()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@ namespace ktsu.Semantics.Quantities;
/// <param name="other">The vector to measure the distance to.</param>
public global::ktsu.Semantics.Quantities.AccelerationMagnitude<T> DistanceTo(Acceleration4D<T> other) => global::ktsu.Semantics.Quantities.AccelerationMagnitude<T>.Create(Distance(other));

/// <summary>Creates a <see cref="Acceleration4D{T}"/> from components in MeterPerSecondSquared.</summary>
/// <param name="x">The X component, in MeterPerSecondSquared.</param>
/// <param name="y">The Y component, in MeterPerSecondSquared.</param>
/// <param name="z">The Z component, in MeterPerSecondSquared.</param>
/// <param name="w">The W component, in MeterPerSecondSquared.</param>
/// <returns>A new <see cref="Acceleration4D{T}"/> storing the SI-base equivalent.</returns>
public static Acceleration4D<T> FromMeterPerSecondSquared(T x, T y, T z, T w) => new() { X = x, Y = y, Z = z, W = w };

/// <summary>Creates a <see cref="Acceleration4D{T}"/> from components in StandardGravity.</summary>
/// <param name="x">The X component, in StandardGravity.</param>
/// <param name="y">The Y component, in StandardGravity.</param>
/// <param name="z">The Z component, in StandardGravity.</param>
/// <param name="w">The W component, in StandardGravity.</param>
/// <returns>A new <see cref="Acceleration4D{T}"/> storing the SI-base equivalent.</returns>
public static Acceleration4D<T> FromStandardGravity(T x, T y, T z, T w) => new() { X = (x * Units.ConversionConstants.Values<T>.StandardGravityToMeterPerSecondSquared), Y = (y * Units.ConversionConstants.Values<T>.StandardGravityToMeterPerSecondSquared), Z = (z * Units.ConversionConstants.Values<T>.StandardGravityToMeterPerSecondSquared), W = (w * Units.ConversionConstants.Values<T>.StandardGravityToMeterPerSecondSquared) };

/// <summary>Converts this vector's SI-base components to <paramref name="unit"/>.</summary>
/// <remarks>Returns bare components rather than a <see cref="Acceleration4D{T}"/>, because a vector that is not in base units cannot be one.</remarks>
/// <param name="unit">The dimensionally-compatible target unit.</param>
/// <returns>The components expressed in <paramref name="unit"/>.</returns>
public (T X, T Y, T Z, T W) In(global::ktsu.Semantics.Quantities.IAccelerationUnit unit) => (unit.FromBase(X), unit.FromBase(Y), unit.FromBase(Z), unit.FromBase(W));

/// <summary>Calculates the length of the vector.</summary>
public T Length()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ namespace ktsu.Semantics.Quantities;
/// <param name="other">The vector to measure the distance to.</param>
public global::ktsu.Semantics.Quantities.AngularAccelerationMagnitude<T> DistanceTo(AngularAcceleration3D<T> other) => global::ktsu.Semantics.Quantities.AngularAccelerationMagnitude<T>.Create(Distance(other));

/// <summary>Creates a <see cref="AngularAcceleration3D{T}"/> from components in RadianPerSecondSquared.</summary>
/// <param name="x">The X component, in RadianPerSecondSquared.</param>
/// <param name="y">The Y component, in RadianPerSecondSquared.</param>
/// <param name="z">The Z component, in RadianPerSecondSquared.</param>
/// <returns>A new <see cref="AngularAcceleration3D{T}"/> storing the SI-base equivalent.</returns>
public static AngularAcceleration3D<T> FromRadianPerSecondSquared(T x, T y, T z) => new() { X = x, Y = y, Z = z };

/// <summary>Converts this vector's SI-base components to <paramref name="unit"/>.</summary>
/// <remarks>Returns bare components rather than a <see cref="AngularAcceleration3D{T}"/>, because a vector that is not in base units cannot be one.</remarks>
/// <param name="unit">The dimensionally-compatible target unit.</param>
/// <returns>The components expressed in <paramref name="unit"/>.</returns>
public (T X, T Y, T Z) In(global::ktsu.Semantics.Quantities.IAngularAccelerationUnit unit) => (unit.FromBase(X), unit.FromBase(Y), unit.FromBase(Z));

/// <summary>Calculates the length of the vector.</summary>
public T Length()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,47 @@ namespace ktsu.Semantics.Quantities;
/// <param name="other">The vector to measure the distance to.</param>
public global::ktsu.Semantics.Quantities.Angle<T> DistanceTo(AngularDisplacement3D<T> other) => global::ktsu.Semantics.Quantities.Angle<T>.Create(Distance(other));

/// <summary>Creates a <see cref="AngularDisplacement3D{T}"/> from components in Radian.</summary>
/// <param name="x">The X component, in Radian.</param>
/// <param name="y">The Y component, in Radian.</param>
/// <param name="z">The Z component, in Radian.</param>
/// <returns>A new <see cref="AngularDisplacement3D{T}"/> storing the SI-base equivalent.</returns>
public static AngularDisplacement3D<T> FromRadian(T x, T y, T z) => new() { X = x, Y = y, Z = z };

/// <summary>Creates a <see cref="AngularDisplacement3D{T}"/> from components in Degree.</summary>
/// <param name="x">The X component, in Degree.</param>
/// <param name="y">The Y component, in Degree.</param>
/// <param name="z">The Z component, in Degree.</param>
/// <returns>A new <see cref="AngularDisplacement3D{T}"/> storing the SI-base equivalent.</returns>
public static AngularDisplacement3D<T> FromDegree(T x, T y, T z) => new() { X = (x * Units.ConversionConstants.Values<T>.DegreeToRadians), Y = (y * Units.ConversionConstants.Values<T>.DegreeToRadians), Z = (z * Units.ConversionConstants.Values<T>.DegreeToRadians) };

/// <summary>Creates a <see cref="AngularDisplacement3D{T}"/> from components in Gradian.</summary>
/// <param name="x">The X component, in Gradian.</param>
/// <param name="y">The Y component, in Gradian.</param>
/// <param name="z">The Z component, in Gradian.</param>
/// <returns>A new <see cref="AngularDisplacement3D{T}"/> storing the SI-base equivalent.</returns>
public static AngularDisplacement3D<T> FromGradian(T x, T y, T z) => new() { X = (x * Units.ConversionConstants.Values<T>.GradianToRadians), Y = (y * Units.ConversionConstants.Values<T>.GradianToRadians), Z = (z * Units.ConversionConstants.Values<T>.GradianToRadians) };

/// <summary>Creates a <see cref="AngularDisplacement3D{T}"/> from components in Revolution.</summary>
/// <param name="x">The X component, in Revolution.</param>
/// <param name="y">The Y component, in Revolution.</param>
/// <param name="z">The Z component, in Revolution.</param>
/// <returns>A new <see cref="AngularDisplacement3D{T}"/> storing the SI-base equivalent.</returns>
public static AngularDisplacement3D<T> FromRevolution(T x, T y, T z) => new() { X = (x * Units.ConversionConstants.Values<T>.RevolutionToRadians), Y = (y * Units.ConversionConstants.Values<T>.RevolutionToRadians), Z = (z * Units.ConversionConstants.Values<T>.RevolutionToRadians) };

/// <summary>Creates a <see cref="AngularDisplacement3D{T}"/> from components in Milliradian.</summary>
/// <param name="x">The X component, in Milliradian.</param>
/// <param name="y">The Y component, in Milliradian.</param>
/// <param name="z">The Z component, in Milliradian.</param>
/// <returns>A new <see cref="AngularDisplacement3D{T}"/> storing the SI-base equivalent.</returns>
public static AngularDisplacement3D<T> FromMilliradian(T x, T y, T z) => new() { X = (x * MetricMagnitudes.Values<T>.Milli), Y = (y * MetricMagnitudes.Values<T>.Milli), Z = (z * MetricMagnitudes.Values<T>.Milli) };

/// <summary>Converts this vector's SI-base components to <paramref name="unit"/>.</summary>
/// <remarks>Returns bare components rather than a <see cref="AngularDisplacement3D{T}"/>, because a vector that is not in base units cannot be one.</remarks>
/// <param name="unit">The dimensionally-compatible target unit.</param>
/// <returns>The components expressed in <paramref name="unit"/>.</returns>
public (T X, T Y, T Z) In(global::ktsu.Semantics.Quantities.IAngularDisplacementUnit unit) => (unit.FromBase(X), unit.FromBase(Y), unit.FromBase(Z));

/// <summary>Calculates the length of the vector.</summary>
public T Length()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ namespace ktsu.Semantics.Quantities;
/// <param name="other">The vector to measure the distance to.</param>
public global::ktsu.Semantics.Quantities.AngularJerkMagnitude<T> DistanceTo(AngularJerk3D<T> other) => global::ktsu.Semantics.Quantities.AngularJerkMagnitude<T>.Create(Distance(other));

/// <summary>Creates a <see cref="AngularJerk3D{T}"/> from components in RadianPerSecondCubed.</summary>
/// <param name="x">The X component, in RadianPerSecondCubed.</param>
/// <param name="y">The Y component, in RadianPerSecondCubed.</param>
/// <param name="z">The Z component, in RadianPerSecondCubed.</param>
/// <returns>A new <see cref="AngularJerk3D{T}"/> storing the SI-base equivalent.</returns>
public static AngularJerk3D<T> FromRadianPerSecondCubed(T x, T y, T z) => new() { X = x, Y = y, Z = z };

/// <summary>Converts this vector's SI-base components to <paramref name="unit"/>.</summary>
/// <remarks>Returns bare components rather than a <see cref="AngularJerk3D{T}"/>, because a vector that is not in base units cannot be one.</remarks>
/// <param name="unit">The dimensionally-compatible target unit.</param>
/// <returns>The components expressed in <paramref name="unit"/>.</returns>
public (T X, T Y, T Z) In(global::ktsu.Semantics.Quantities.IAngularJerkUnit unit) => (unit.FromBase(X), unit.FromBase(Y), unit.FromBase(Z));

/// <summary>Calculates the length of the vector.</summary>
public T Length()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ namespace ktsu.Semantics.Quantities;
/// <param name="other">The vector to measure the distance to.</param>
public global::ktsu.Semantics.Quantities.AngularMomentumMagnitude<T> DistanceTo(AngularMomentum3D<T> other) => global::ktsu.Semantics.Quantities.AngularMomentumMagnitude<T>.Create(Distance(other));

/// <summary>Creates a <see cref="AngularMomentum3D{T}"/> from components in KilogramMeterSquaredPerSecond.</summary>
/// <param name="x">The X component, in KilogramMeterSquaredPerSecond.</param>
/// <param name="y">The Y component, in KilogramMeterSquaredPerSecond.</param>
/// <param name="z">The Z component, in KilogramMeterSquaredPerSecond.</param>
/// <returns>A new <see cref="AngularMomentum3D{T}"/> storing the SI-base equivalent.</returns>
public static AngularMomentum3D<T> FromKilogramMeterSquaredPerSecond(T x, T y, T z) => new() { X = x, Y = y, Z = z };

/// <summary>Converts this vector's SI-base components to <paramref name="unit"/>.</summary>
/// <remarks>Returns bare components rather than a <see cref="AngularMomentum3D{T}"/>, because a vector that is not in base units cannot be one.</remarks>
/// <param name="unit">The dimensionally-compatible target unit.</param>
/// <returns>The components expressed in <paramref name="unit"/>.</returns>
public (T X, T Y, T Z) In(global::ktsu.Semantics.Quantities.IAngularMomentumUnit unit) => (unit.FromBase(X), unit.FromBase(Y), unit.FromBase(Z));

/// <summary>Calculates the length of the vector.</summary>
public T Length()
{
Expand Down
Loading
Loading