The semantic quantities system is generated by Roslyn incremental generators in Semantics.SourceGenerators/, driven by metadata files in Semantics.SourceGenerators/Metadata/. This doc explains the schema and the workflow for adding or changing dimensions.
For the why (the unified vector model), see docs/strategy-unified-vector-quantities.md. For the runtime contracts (IVector0..IVector4, PhysicalQuantity<TSelf, T>), see Semantics.Quantities/.
| Generator | Output | Notes |
|---|---|---|
DimensionsGenerator |
PhysicalDimensions.g.cs |
One static record per dimension with its symbol and dimensional formula. |
UnitsGenerator |
Units.g.cs |
All declared units with their conversion factors. |
ConversionsGenerator |
ConversionConstants.g.cs |
Hard-coded conversion ratios (FeetToMeters, etc.) used by Units and operators. |
MagnitudesGenerator |
MetricMagnitudes.g.cs |
SI prefixes and their numeric magnitudes. |
PrecisionGenerator |
StorageTypes.g.cs |
The set of INumber<T> storage types the library opts into (double, float, decimal, …). |
PhysicalConstantsGenerator |
PhysicalConstants.g.cs |
PhysicalConstants.Generic.X<T>() and PhysicalConstants.Conversion.X<T>() accessors backed by PreciseNumber. |
QuantitiesGenerator |
one *.g.cs file per emitted type |
Vector0/V1/V2/V3/V4 bases, semantic overloads, factories, operators, magnitude extraction, dot/cross products. |
LogarithmicScalesGenerator |
one *.g.cs file per logarithmic scale |
Decibel levels, pitch intervals, and pH from logarithmic.json: standalone readonly partial record structs with linear-quantity conversions, log-space arithmetic, and comparisons. |
Outputs land under Semantics.Quantities/Generated/Semantics.SourceGenerators/<GeneratorName>/. Generated files are committed so that the project compiles without first running the generator.
Semantics.SourceGenerators/Metadata/dimensions.json is a single object with one key, physicalDimensions, whose value is a list of dimension entries. Each entry looks like:
| Field | Required | Meaning |
|---|---|---|
name |
yes | Stable dimension identifier; used for diagnostics and cross-references. |
symbol |
yes | Physics symbol (L, M, T, I, Θ, N, J, …). |
dimensionalFormula |
yes | Map of base dimension → exponent ({"length":1, "time":-2}). Used for dimensional-analysis equality. |
availableUnits |
yes | Names of units defined in the units metadata. The first entry is the SI base unit. The generator emits From{Unit} factories for each entry. |
quantities.vectorN.base |
yes per declared form | Base type emitted for that form (e.g. Length, Displacement1D, Force3D). |
quantities.vectorN.overloads[] |
optional | Semantic overloads of the base. Each gets its own type with implicit-widen / explicit-narrow conversions and a From(base) factory. |
relationships (on an overload) |
optional | C# expressions emitted as To{Other}() / From{Other}() instance methods. Reference Value and T.CreateChecked for type-correct constants. |
integrals / derivatives |
optional | Cross-dimensional * and / operator pairs. { "other": X, "result": Y } produces Self * X => Y and the inverse Y / X => Self. |
dotProducts / crossProducts |
optional | Vector-form-aware operators between this dimension's V≥1 forms and another dimension's V≥1 forms. |
physicalConstraints |
optional | (Planned) per-dimension floors/ceilings such as { "minValue": "0", "minValueUnit": "Kelvin" } — emitted as ArgumentException-throwing guards inside Create/From*. Tracked in issue #51. |
- Vector0: enforces non-negativity.
V0 - V0returns the same V0 ofT.Abs(a - b)(decision locked). Generator emitsMagnitude()only on V≥1 forms. - Vector1: signed scalar. Magnitude extraction returns the V0 base (
Velocity1D.Magnitude() => Speed). - Vector2/3/4: per-component-signed.
Magnitude()returns the V0 base via Euclidean norm. - Dot products: emitted on the higher-dimensional form, returning the V0 base of the result dimension. Example:
Force3D.Dot(Displacement3D) => Energy. - Cross products: emitted on V3 only (V2 cross is intentionally unsupported). Result is the V3 of the result dimension.
An overload (e.g. Width, Height, Depth on Length) emits:
- A record extending the base type but with its own identity:
record Width<T> : Length<T>. - Implicit operator widening to the base.
- Explicit operator narrowing from the base.
- A
From(base)factory. - Per-relationship
To{Other}()/From{Other}()methods ifrelationshipsis set.
Operator preservation (e.g. Width + Width => Width vs. Width + Length => Length) is generated, and follows the rule "narrowest-shared overload wins; otherwise widen to base".
- Edit
Semantics.SourceGenerators/Metadata/dimensions.json. Add the dimension entry. Make sureavailableUnitsreferences units defined in the units metadata; if you need a new unit, add it there first. - If the dimension introduces operators with existing dimensions, declare them under
integrals/derivatives/dotProducts/crossProductson one side of each pair. The generator emits forward and inverse forms automatically. - Add per-dimension constraints (
physicalConstraints) if there's a floor such as absolute zero. dotnet build. The generators run as analyzers; new files appear underSemantics.Quantities/Generated/Semantics.SourceGenerators/<GeneratorName>/.- Diff the generated files. Verify the expected
From*factories, operators, andMagnitude/Dot/Crossmethods are present. - Add tests under
Semantics.Test/Quantities/that exercise the new types and any new operator paths. - Commit the metadata change and the regenerated
*.g.csfiles together.
- Locate the dimension in
dimensions.json. - Append to its
quantities.vectorN.overloads[]:{ "name": "Heading", "description": "Direction of motion in 2D.", "relationships": { … } } - Build, diff the new
Heading.g.cs, add tests, commit.
For example, declaring Force × Distance = Work:
- Pick one side as the owner — usually the lower-rank operand. Add to
Force'sintegrals:{ "other": "Length", "result": "Energy" } - The generator emits both
Force * Length => EnergyandEnergy / Length => Force. No second declaration needed. - For vector forms, declare on
dotProductsorcrossProducts— these are emitted on the matching vector forms only.
Logarithmic quantities (decibel levels, pitch intervals, pH) don't obey linear
arithmetic, so they are not dimensions. They live in
Semantics.SourceGenerators/Metadata/logarithmic.json and are emitted by
LogarithmicScalesGenerator as standalone readonly partial record structs
around scale = multiplier · log_base(linear / reference):
{
"name": "SoundPressureLevel",
"description": "Represents a sound pressure level (SPL) in decibels…",
"displayFormat": "{0} dB SPL", // ToString template
"scalarFactory": "FromDecibels", // raw-value factory name
"arithmetic": true, // emit log-space + and -
"conversions": [
{
"linear": "SoundPressure", // generated linear counterpart
"multiplier": "20", // 20 = field, 10 = power, 1200 = cents…
"logBase": "10", // optional, defaults to 10
"reference": { "constant": "ReferenceSoundPressure" }, // or { "value": "1000" }
"fromName": "FromSoundPressure", // optional, defaults to From{Linear}
"toName": "ToSoundPressure" // optional, defaults to To{Linear}
}
]
}Each conversion generates From{Linear}({Linear}<T>) and To{Linear}()
methods; the core always gets the scalar factory, CompareTo, comparison
operators, and ToString. Bespoke members that don't fit the schema —
named constants (PH.Neutral, Semitones.Octave), cross-scale conversions
(Cents↔Semitones), raw-T conveniences (Decibels.FromAmplitude(T)) —
go in a hand-written partial next to the generated core (see
Semantics.Quantities/AudioEngineering/ and Acoustics/).
SEM005 flags missing or duplicate scale names and conversions with no linear type.
- Unknown dimension references in
integrals/derivatives/dotProducts/crossProductsare currently dropped silently; this is tracked as a generator diagnostic improvement (issue #56). Until it lands, diff the output when editing metadata to catch typos. availableUnitsorder matters: the first entry is treated as the SI base unit byUnitsGenerator.relationshipsexpressions are emitted verbatim into method bodies. UseValuefor the current quantity andT.CreateChecked(...)(not literal numerics) for constants so all storage types stay correct.- Generator output is committed. CI must catch metadata/code drift;
git statusshould be clean after a build.
| Concern | File |
|---|---|
| Quantity emission, operators, overload preservation | Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs |
| Metadata model | Semantics.SourceGenerators/Models/DimensionsMetadata.cs |
| Templates (records, classes, methods, properties) | Semantics.SourceGenerators/Templates/ |
| Runtime base + interfaces | Semantics.Quantities/PhysicalQuantity.cs, IVector0.cs..IVector4.cs |
| Generated output | Semantics.Quantities/Generated/Semantics.SourceGenerators/ |
{ "name": "Length", "symbol": "L", "dimensionalFormula": { "length": 1 }, "availableUnits": [ "Meter", "Kilometer", "Foot", "Inch", "Mile", … ], "quantities": { "vector0": { "base": "Length", "overloads": [ { "name": "Width", "description": "Horizontal extent." }, { "name": "Diameter", "description": "Distance across a circle.", "relationships": { "toRadius": "Value / T.CreateChecked(2)", "fromRadius": "Value * T.CreateChecked(2)" } } ] }, "vector1": { "base": "Displacement1D", "overloads": [ { "name": "Offset", … } ] }, "vector2": { "base": "Displacement2D" }, "vector3": { "base": "Displacement3D", "overloads": [ { "name": "Position3D", … } ] }, "vector4": { "base": "Displacement4D" } }, "integrals": [ { "other": "Length", "result": "Area" } ], "derivatives": [ ], "dotProducts": [ ], "crossProducts": [ ] }