Skip to content
Open
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
5 changes: 5 additions & 0 deletions .github/agents/new-quantity.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ When asked to create a new quantity, follow these steps:
2. **Create or verify the dimension interface** in the appropriate dimensions file.
3. **Create the quantity struct** in `source/Atmoos.Quantities/Quantities/`.
4. **Add cross-quantity operators** in the appropriate file under `source/Atmoos.Quantities/Physics/`.
5. **Classify nearby worktree changes correctly**: if a modified file (for example docs/readme listings) reflects quantities added in the same change-set, treat it as a related change, not unrelated noise.

Always adhere to the coding conventions defined in `.github/copilot-instructions.md`.

## Change Classification Rule

Before deciding that an existing modification is unrelated, check whether it is a companion update caused by the quantity work (for example, supported-quantities documentation updates). If it reflects the same newly introduced quantity types, keep it and classify it as related.

---

## Quantity Categories
Expand Down
8 changes: 6 additions & 2 deletions .github/agents/new-unit.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public readonly struct {Name} : ISiUnit, I{Dimension}

### 2. Metric Unit (`IMetricUnit`)

**When**: The unit is accepted by the SI system but is not itself an SI unit. Metric units support SI metric prefixes (`Kilo`, `Mega`, `Milli`, etc.).
**When**: The unit is accepted by the SI system but is not itself an SI unit. This includes all units listed in the SI Brochure as ["non-SI units accepted for use with SI"](https://en.wikipedia.org/wiki/International_System_of_Units#Non-SI_units_accepted_for_use_with_SI) (e.g. minute, hour, day, degree, arcminute, arcsecond, astronomical unit, hectare, litre, tonne, dalton, electronvolt). Metric units support SI metric prefixes (`Kilo`, `Mega`, `Milli`, etc.).

**Key property**: Must implement `ITransform` via `ToSi(Transformation)`. Support metric prefixes by default.

Expand All @@ -80,7 +80,7 @@ public readonly struct {Name} : IMetricUnit, I{Dimension}
}
```

**Examples**: `Hour`, `Minute`, `Gram`, `Tonne`, `Day`, `Week`, `Are`, `Bar`, `AstronomicalUnit`, `Ångström`, `HorsePower`, `Bit`, `Byte`
**Examples**: `Hour`, `Minute`, `Gram`, `Tonne`, `Day`, `Week`, `Are`, `Bar`, `AstronomicalUnit`, `Ångström`, `Degree`, `HorsePower`, `Bit`, `Byte`

---

Expand Down Expand Up @@ -304,6 +304,10 @@ public static Transformation ToSi(Transformation self) => self.FusedMultiplyAdd(
3. **Chained conversions**: Use `DerivedFrom<T>()` to build on existing unit conversions.
4. **Keep conversions exact**: Use integer arithmetic or exact decimal fractions where possible.
5. **Internal constants**: Use `internal const` fields for conversion factors that may be reused.
6. **Preserve numerator/denominator separation**: The `Polynomial` type stores nominator and denominator separately. Structure conversions so that irrational or large factors remain in the numerator and integer divisors remain in the denominator, rather than pre-computing their quotient as a single `Double`. This yields higher precision in round-trip conversions.
- ✅ `Math.PI * self.RootedIn<Radian>() / 180` — stores π as nominator, 180 as denominator.
- ❌ `Math.PI / 180 * self.RootedIn<Radian>()` — pre-computes π÷180 into a single `Double`, losing precision.
- Note: cross-unit conversions that chain two irrational factors (e.g. Turn → Gradian, both via π) may still exhibit IEEE 754 limits and require `MediumPrecision` in test assertions.

---

Expand Down
16 changes: 16 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@

---

# Domain Knowledge

This repository is a C# library for physical quantities and units of measurement. It provides a type-safe, extensible, and performant framework for representing and manipulating physical quantities in code.

Each C# project has a `readme.md` file that describes the domain knowledge and design principles of the project. This file should be used as a reference when generating code for the project to ensure that the generated code aligns with the project's goals and design.

- [Quantities](../source/Atmoos.Quantities/readme.md)
- exhaustive lists all quantities
- also lists units that are predefined in the library
- [Units](../source/Atmoos.Quantities.Units/readme.md)
- lists all other units
- Serialization
- [System.Text.Json](../source/Atmoos.Quantities.Serialization/Text.Json/readme.md)
- [Newtonsoft](../source/Atmoos.Quantities.Serialization/Newtonsoft/readme.md)


# Code Style Guidelines

This document describes the coding conventions and style guidelines for the Atmoos.Quantities project.
Expand Down
132 changes: 132 additions & 0 deletions .github/memory/Units.ai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Units — How They Work

## Directory Layout

```
Atmoos.Quantities.Units/
├── Si/ # SI base units (Metre, Second, Kilogram, Ampere, Kelvin, Mole, Candela)
│ ├── Derived/ # SI derived units (Newton, Joule, Watt, Hertz, Pascal, Volt, Ohm, Coulomb, Radian, …)
│ └── Metric/ # Metric (accepted by SI but not themselves SI): Minute, Hour, Day, Week, Litre, Gram, Ångström, Tonne, Bar, Are, Stere, HorsePower, Lambda, …
│ └── UnitsOfInformation/
├── Imperial/ # British imperial: Length/{Foot,Inch,Mile}, Mass/Pound, Volume/Pint, Temperature/Fahrenheit, Force/PoundForce, Power/HorsePower, Area/…
└── NonStandard/ # Units without a system: Angle/{Degree,Gradian,Turn}, Length/{NauticalMile,LightYear}, Mass/{Pfund,Zentner}, Area/Morgen, Pressure/{StandardAtmosphere,Torr}, Velocity/Knot, ElectricCharge/, Temperature/…
```

Tests live in `Atmoos.Quantities.Units.Test/` — one test class per quantity dimension, e.g. `LengthTest.cs`, `ForceTest.cs`, `AngleTest.ai.cs`.

## Marker Interfaces (in Atmoos.Quantities/Units/)

| Interface | Extends | Meaning |
|---|---|---|
| `IUnit` | `IRepresentable` | Root marker; every unit has a `static String Representation`. |
| `ISiUnit` | `IUnit` | SI base & derived units. No `ToSi` needed (identity). |
| `IMetricUnit` | `IUnit, ITransform` | Metric units accepted by SI. Must implement `static Transformation ToSi(Transformation)`. |
| `IImperialUnit` | `IUnit, ITransform` | Imperial units. Must implement `ToSi`. |
| `INonStandardUnit` | `IUnit, ITransform` | Units without a system. Must implement `ToSi`. |
| `ISystemInject<TDimension>` | — | For units that inject a basis dimension (e.g. `Hertz` injects `ITime`). |
| `IInvertible<TDimension>` | `ISystemInject<TDimension>` | Marker for inverse-dimension units (e.g. `Hertz` is `IInvertible<ITime>`). |
| `IPowerOf<TDimension>` | `ISystemInject<TDimension>` | Marker for power-dimension units. |

## Creating a New Unit

### SI Derived (identity to SI) — e.g. Newton, Coulomb, Radian

```csharp
using Atmoos.Quantities.Dimensions;

namespace Atmoos.Quantities.Units.Si.Derived;

public readonly struct Newton : ISiUnit, IForce
{
public static String Representation => "N";
}
```

No `ToSi` method — `ISiUnit` has identity transformation.

### Metric (non-identity) — e.g. Minute, Hour

```csharp
public readonly struct Minute : IMetricUnit, ITime
{
public static Transformation ToSi(Transformation self) => 60 * self.RootedIn<Second>();
public static String Representation => "min";
}
```

`RootedIn<TSi>()` documents which SI unit the conversion is relative to (it is a no-op that returns `self`).

### Imperial — e.g. Foot

```csharp
public readonly struct Foot : IImperialUnit, ILength
{
public static Transformation ToSi(Transformation self) => 3048 * self.RootedIn<Metre>() / 1e4;
public static String Representation => "ft";
}
```

### NonStandard — e.g. Knot (compound derived)

```csharp
public readonly struct Knot : INonStandardUnit, IVelocity
{
public static Transformation ToSi(Transformation self) => self.DerivedFrom<NauticalMile>() / ValueOf<Hour>();
public static String Representation => "kn";
}
```

`DerivedFrom<T>()` chains through another unit's `ToSi`. `ValueOf<T>()` evaluates a unit's SI polynomial to a scalar.

### Inverse-dimension units — e.g. Hertz

```csharp
public readonly struct Hertz : ISiUnit, IFrequency, IInvertible<ITime>
{
static T ISystemInject<ITime>.Inject<T>(ISystems<ITime, T> basis) => basis.Si<Second>();
public static String Representation => "Hz";
}
```

## Systems.cs — Unit Registration & Caching

`Systems` is the static entry-point users call to obtain `Scalar<TUnit>` references:

- `Si<TUnit>()` / `Si<TPrefix, TUnit>()` — SI units, with optional metric prefix.
- `Metric<TUnit>()` / `Metric<TPrefix, TUnit>()` — Metric units.
- `Binary<TPrefix, TUnit>()` — Binary-prefixed metric units.
- `Imperial<TUnit>()` — Imperial units.
- `NonStandard<TUnit>()` — Non-standard units.
- `Square<TUnit>(…)` / `Cubic<TUnit>(…)` — Power helpers.

All are backed by a file-scoped `Cache<TUnit, TMeasure>` that lazily creates and caches a `Scalar<TUnit>`.

## Dimension Interfaces (in Atmoos.Quantities/Dimensions/)

- **Base**: `ITime`, `ILength`, `IMass`, `IElectricCurrent`, `ITemperature`, `IAmountOfSubstance`, `ILuminousIntensity` — each extends `ILinear<TSelf>, IBaseQuantity`.
- **Derived**: `IArea`, `IVolume`, `IVelocity`, `IAcceleration`, `IForce`, `IPower`, `IEnergy`, `IFrequency`, `IPressure`, `IDensity`, `IVolumetricFlowRate`, `IMassFlowRate`, `IMomentum`, `IImpulse`, `ISpecificEnergy` — each extends `IDerivedQuantity` with appropriate product/power composition.
- **Electrical**: `IElectricPotential`, `IElectricalResistance`, `IElectricCharge`, `IAmountOfInformation`, `IInformationRate`.
- **Dimensionless**: `IAngle` (via `IDimensionless<IAngle>`).

A unit struct implements **both** a system interface (`ISiUnit`, `IMetricUnit`, etc.) **and** a dimension interface (`ILength`, `IForce`, etc.).

## Transformation Helpers (Extensions.cs)

| Helper | Purpose |
|---|---|
| `self.RootedIn<TSi>()` | Documents the SI reference unit (no-op, returns self). |
| `self.DerivedFrom<TBasis>()` | Chains through `TBasis.ToSi(self)` for derived conversions. |
| `ValueOf<T>(exponent)` | Evaluates `T.ToSi` polynomial to a `Double` scalar, raised to `exponent`. |

## Test Patterns (Units.Test)

- `FormattingMatches(v => Quantity.Of(v, measure), "symbol")` — verifies `ToString()` output.
- `PrecisionIsBounded(expected, actual)` — bounded floating-point comparison.
- `.Matches(expected)` — approximate equality assertion.
- `.Equal(expected)` — exact equality via `Assert.Equal`.
- `Usings.cs` globally imports: `Atmoos.Quantities.Physics`, `Atmoos.Quantities.Prefixes`, `Atmoos.Quantities.Units.Imperial.Length`, `Atmoos.Quantities.Units.Si`, `Systems`, `Convenience`, `Traits`.

## AI Transparency for Units

- Fully AI-generated files use `.ai.` infix: `Coulomb.ai.cs`, `AngleTest.ai.cs`.
- AI-generated types carry `[Ai(Model = "…", Version = "…", Variant = "…")]`.
Loading
Loading