diff --git a/README.md b/README.md index df5105d..741a819 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,18 @@ Speed vesselSpeed = Speed.FromKnots(8); A nautical mile is exactly 1852 m, and a knot is one nautical mile per hour. Nautical helpers do not generate SI-prefixed variants. +Add `DimensionalQuantities.Extensions.CgsEmag` for practical CGS mechanical, electromagnetic (EMU), and magnetic conversions. Import its namespace to use helpers such as: + +```csharp +using DimensionalQuantities.Extensions.CgsEmag; + +MagneticFluxDensity field = MagneticFluxDensity.FromGauss(10_000); +MagneticFieldStrength strength = MagneticFieldStrength.FromOersteds(1); +Energy work = Energy.FromErgs(1_000_000); +``` + +It includes dynes, ergs, baryes, gals, the common `ab` electrical units, maxwells, gauss and oersteds. These are named conversions into coherent SI quantities; the package does not model a separate CGS electromagnetic dimension system or generate SI-prefixed variants. See [NIST's CGS unit definitions](https://www.nist.gov/pml/special-publication-811/nist-guide-si-chapter-5-units-outside-si) for the distinction between EMU, ESU and Gaussian systems. + ```csharp Voltage voltage = Voltage.FromVolts(12); Current current = Current.FromAmps(3); diff --git a/src/DimensionalQuantities.Extensions.CgsEmag.Tests/CgsEmagConversionsTests.cs b/src/DimensionalQuantities.Extensions.CgsEmag.Tests/CgsEmagConversionsTests.cs new file mode 100644 index 0000000..ae9df6b --- /dev/null +++ b/src/DimensionalQuantities.Extensions.CgsEmag.Tests/CgsEmagConversionsTests.cs @@ -0,0 +1,83 @@ +using System.Reflection; +using DimensionalQuantities.Extensions.CgsEmag; +using Xunit; + +namespace DimensionalQuantities.Extensions.CgsEmag.Tests; + +public sealed class CgsEmagConversionsTests +{ + [Fact] + public void MechanicalUnitsConvertToCoherentSi() + { + Assert.Multiple( + () => Assert.Equal(1e-5, Force.FromDynes(1).AsSiBaseUnit()), + () => Assert.Equal(1e-7, Energy.FromErgs(1).AsSiBaseUnit()), + () => Assert.Equal(0.1, Pressure.FromBaryes(1).AsSiBaseUnit()), + () => Assert.Equal(0.01, Acceleration.FromGals(1).AsSiBaseUnit())); + } + + [Fact] + public void ElectromagneticUnitsConvertToCoherentSi() + { + Assert.Multiple( + () => Assert.Equal(10, Current.FromAbamperes(1).AsSiBaseUnit()), + () => Assert.Equal(10, Current.FromBiots(1).AsSiBaseUnit()), + () => Assert.Equal(10, ElectricCharge.FromAbcoulombs(1).AsSiBaseUnit()), + () => Assert.Equal(1e-8, Voltage.FromAbvolts(1).AsSiBaseUnit()), + () => Assert.Equal(1e-9, Resistance.FromAbohms(1).AsSiBaseUnit()), + () => Assert.Equal(1e-9, Inductance.FromAbhenries(1).AsSiBaseUnit()), + () => Assert.Equal(1e9, Capacitance.FromAbfarads(1).AsSiBaseUnit()), + () => Assert.Equal(1e-8, MagneticFlux.FromMaxwells(1).AsSiBaseUnit()), + () => Assert.Equal(1e-4, MagneticFluxDensity.FromGauss(1).AsSiBaseUnit())); + + double oersted = MagneticFieldStrength.FromOersteds(1).AsSiBaseUnit(); + Assert.InRange(oersted, 1000 / (4 * Math.PI) - 1e-12, 1000 / (4 * Math.PI) + 1e-12); + } + + [Fact] + public void AccessorsReturnOriginalCgsMagnitude() + { + Assert.Multiple( + () => Assert.Equal(1, Force.FromSiBaseUnit(1e-5).AsDynes()), + () => Assert.Equal(1, Energy.FromSiBaseUnit(1e-7).AsErgs()), + () => Assert.Equal(1, Pressure.FromSiBaseUnit(0.1).AsBaryes()), + () => Assert.Equal(1, Acceleration.FromSiBaseUnit(0.01).AsGals()), + () => Assert.Equal(1, Current.FromSiBaseUnit(10).AsAbamperes()), + () => Assert.Equal(1, Current.FromSiBaseUnit(10).AsBiots()), + () => Assert.Equal(1, ElectricCharge.FromSiBaseUnit(10).AsAbcoulombs()), + () => Assert.Equal(1, Voltage.FromSiBaseUnit(1e-8).AsAbvolts()), + () => Assert.Equal(1, Resistance.FromSiBaseUnit(1e-9).AsAbohms()), + () => Assert.Equal(1, Inductance.FromSiBaseUnit(1e-9).AsAbhenries()), + () => Assert.Equal(1, Capacitance.FromSiBaseUnit(1e9).AsAbfarads()), + () => Assert.Equal(1, MagneticFlux.FromSiBaseUnit(1e-8).AsMaxwells()), + () => Assert.Equal(1, MagneticFluxDensity.FromSiBaseUnit(1e-4).AsGauss())); + + double oersted = MagneticFieldStrength.FromSiBaseUnit(1000 / (4 * Math.PI)).AsOersteds(); + Assert.InRange(oersted, 1 - 1e-14, 1 + 1e-14); + } + + [Fact] + public void SharedDimensionAliasesUseTheConversions() + { + Assert.Multiple( + () => Assert.Equal(1e-7, Torque.FromErgs(1).AsSiBaseUnit()), + () => Assert.Equal(0.1, Stress.FromBaryes(1).AsSiBaseUnit()), + () => Assert.Equal(0.1, YoungsModulus.FromBaryes(1).AsSiBaseUnit()), + () => Assert.Equal(1e-8, FluxLinkage.FromMaxwells(1).AsSiBaseUnit()), + () => Assert.Equal(1e-4, RemanentFluxDensity.FromGauss(1).AsSiBaseUnit())); + } + + [Fact] + public void ProviderDoesNotGeneratePrefixConversions() + { + string[] names = Assembly.Load("DimensionalQuantities.Extensions.CgsEmag").GetExportedTypes() + .Where(type => type.Namespace == "DimensionalQuantities.Extensions.CgsEmag") + .SelectMany(type => type.GetMethods()).Select(method => method.Name).ToArray(); + + Assert.Multiple( + () => Assert.DoesNotContain("FromPrefixedUnits", names), + () => Assert.DoesNotContain("AsPrefixedUnits", names), + () => Assert.DoesNotContain("FromKiloGauss", names), + () => Assert.DoesNotContain("AsKiloGauss", names)); + } +} diff --git a/src/DimensionalQuantities.Extensions.CgsEmag.Tests/DimensionalQuantities.Extensions.CgsEmag.Tests.csproj b/src/DimensionalQuantities.Extensions.CgsEmag.Tests/DimensionalQuantities.Extensions.CgsEmag.Tests.csproj new file mode 100644 index 0000000..f919681 --- /dev/null +++ b/src/DimensionalQuantities.Extensions.CgsEmag.Tests/DimensionalQuantities.Extensions.CgsEmag.Tests.csproj @@ -0,0 +1,19 @@ + + + net11.0 + false + true + Exe + true + + + + + + + + + + + diff --git a/src/DimensionalQuantities.Extensions.CgsEmag/CgsEmagSymbols.cs b/src/DimensionalQuantities.Extensions.CgsEmag/CgsEmagSymbols.cs new file mode 100644 index 0000000..7c761ca --- /dev/null +++ b/src/DimensionalQuantities.Extensions.CgsEmag/CgsEmagSymbols.cs @@ -0,0 +1,35 @@ +using DimensionalQuantities.Units.Declarations; + +namespace DimensionalQuantities.Extensions.CgsEmag; + +internal static class CgsEmagSymbols +{ + [UnitSymbol("Dyne", "Dynes", "dyn", Scale = 1e-5)] + internal sealed class Dyne { } + [UnitSymbol("Erg", "Ergs", "erg", Scale = 1e-7)] + internal sealed class Erg { } + [UnitSymbol("Barye", "Baryes", "Ba", Scale = 0.1)] + internal sealed class Barye { } + [UnitSymbol("Gal", "Gals", "Gal", Scale = 0.01)] + internal sealed class Gal { } + [UnitSymbol("Abampere", "Abamperes", "abA", Scale = 10)] + internal sealed class Abampere { } + [UnitSymbol("Biot", "Biots", "Bi", Scale = 10)] + internal sealed class Biot { } + [UnitSymbol("Abcoulomb", "Abcoulombs", "abC", Scale = 10)] + internal sealed class Abcoulomb { } + [UnitSymbol("Abvolt", "Abvolts", "abV", Scale = 1e-8)] + internal sealed class Abvolt { } + [UnitSymbol("Abohm", "Abohms", "abΩ", Scale = 1e-9)] + internal sealed class Abohm { } + [UnitSymbol("Abhenry", "Abhenries", "abH", Scale = 1e-9)] + internal sealed class Abhenry { } + [UnitSymbol("Abfarad", "Abfarads", "abF", Scale = 1e9)] + internal sealed class Abfarad { } + [UnitSymbol("Maxwell", "Maxwells", "Mx", Scale = 1e-8)] + internal sealed class Maxwell { } + [UnitSymbol("Gauss", "Gauss", "G", Scale = 1e-4)] + internal sealed class Gauss { } + [UnitSymbol("Oersted", "Oersteds", "Oe", Scale = 79.57747154594767)] + internal sealed class Oersted { } +} diff --git a/src/DimensionalQuantities.Extensions.CgsEmag/CgsEmagUnits.cs b/src/DimensionalQuantities.Extensions.CgsEmag/CgsEmagUnits.cs new file mode 100644 index 0000000..cd40fe0 --- /dev/null +++ b/src/DimensionalQuantities.Extensions.CgsEmag/CgsEmagUnits.cs @@ -0,0 +1,69 @@ +using DimensionalQuantities.Units.Declarations; + +namespace DimensionalQuantities.Extensions.CgsEmag; + +[UnitProvider] +internal static class CgsEmagUnits +{ + [QuantityUnit(typeof(Force))] + [UnitFactor(typeof(CgsEmagSymbols.Dyne))] + private sealed class ForceUnit { } + + [QuantityUnit(typeof(Energy))] + [QuantityUnit(typeof(Torque))] + [UnitFactor(typeof(CgsEmagSymbols.Erg))] + private sealed class EnergyUnit { } + + [QuantityUnit(typeof(Pressure))] + [QuantityUnit(typeof(Stress))] + [QuantityUnit(typeof(YoungsModulus))] + [UnitFactor(typeof(CgsEmagSymbols.Barye))] + private sealed class PressureUnit { } + + [QuantityUnit(typeof(Acceleration))] + [UnitFactor(typeof(CgsEmagSymbols.Gal))] + private sealed class AccelerationUnit { } + + [QuantityUnit(typeof(Current))] + [UnitFactor(typeof(CgsEmagSymbols.Abampere))] + [AdditionalUnit(typeof(BiotUnit))] + private sealed class CurrentUnit { } + + [UnitFactor(typeof(CgsEmagSymbols.Biot))] + private sealed class BiotUnit { } + + [QuantityUnit(typeof(ElectricCharge))] + [UnitFactor(typeof(CgsEmagSymbols.Abcoulomb))] + private sealed class ChargeUnit { } + + [QuantityUnit(typeof(Voltage))] + [UnitFactor(typeof(CgsEmagSymbols.Abvolt))] + private sealed class VoltageUnit { } + + [QuantityUnit(typeof(Resistance))] + [UnitFactor(typeof(CgsEmagSymbols.Abohm))] + private sealed class ResistanceUnit { } + + [QuantityUnit(typeof(Inductance))] + [UnitFactor(typeof(CgsEmagSymbols.Abhenry))] + private sealed class InductanceUnit { } + + [QuantityUnit(typeof(Capacitance))] + [UnitFactor(typeof(CgsEmagSymbols.Abfarad))] + private sealed class CapacitanceUnit { } + + [QuantityUnit(typeof(MagneticFlux))] + [QuantityUnit(typeof(FluxLinkage))] + [UnitFactor(typeof(CgsEmagSymbols.Maxwell))] + private sealed class MagneticFluxUnit { } + + [QuantityUnit(typeof(MagneticFluxDensity))] + [QuantityUnit(typeof(RemanentFluxDensity))] + [UnitFactor(typeof(CgsEmagSymbols.Gauss))] + private sealed class MagneticFluxDensityUnit { } + + [QuantityUnit(typeof(MagneticFieldStrength))] + [QuantityUnit(typeof(CoerciveFieldStrength))] + [UnitFactor(typeof(CgsEmagSymbols.Oersted))] + private sealed class MagneticFieldStrengthUnit { } +} diff --git a/src/DimensionalQuantities.Extensions.CgsEmag/DimensionalQuantities.Extensions.CgsEmag.csproj b/src/DimensionalQuantities.Extensions.CgsEmag/DimensionalQuantities.Extensions.CgsEmag.csproj new file mode 100644 index 0000000..02e4ef8 --- /dev/null +++ b/src/DimensionalQuantities.Extensions.CgsEmag/DimensionalQuantities.Extensions.CgsEmag.csproj @@ -0,0 +1,35 @@ + + + netstandard2.0 + GoSim + MIT + README.md + https://github.com/GoSimulate/DimensionalQuantities + Named CGS electromagnetic and mechanical conversions for dimensional quantities. + git + true + $(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/generated + true + snupkg + true + true + true + portable + + + + + + + + + + + <_ProjectReferencesWithVersions Update="@(_ProjectReferencesWithVersions)" + Condition="'%(_ProjectReferencesWithVersions.Filename)' == 'DimensionalQuantities'"> + [$(PackageVersion)] + + + + diff --git a/src/DimensionalQuantities.IntegrationTests/Packaging/PackagedGeneratorTests.cs b/src/DimensionalQuantities.IntegrationTests/Packaging/PackagedGeneratorTests.cs index 77a5551..a49d900 100644 --- a/src/DimensionalQuantities.IntegrationTests/Packaging/PackagedGeneratorTests.cs +++ b/src/DimensionalQuantities.IntegrationTests/Packaging/PackagedGeneratorTests.cs @@ -124,6 +124,33 @@ public static class Consumer () => Assert.Equal(1852d / 3600d, InvokeDouble(type, "ConvertSpeed"))); } + [Fact] + public void CgsEmagPackageProvidesConversionsThroughCoreDependency() + { + using PackageRepository repository = PackageRepository.Create(_scratch, new Uri("https://api.nuget.org/v3/index.json")); + PackCore(repository); + Package cgsEmag = PackSourceProject(repository, "DimensionalQuantities.Extensions.CgsEmag"); + ProjectCreator consumer = CreateProject("CgsEmagConsumer", """ + using DimensionalQuantities.Extensions.CgsEmag; + + public static class Consumer + { + public static double ConvertFluxDensity() => MagneticFluxDensity.FromGauss(10_000).AsSiBaseUnit(); + public static double ConvertCurrent() => Current.FromAbamperes(1).AsSiBaseUnit(); + public static double ConvertCharge() => ElectricCharge.FromAbcoulombs(1).AsSiBaseUnit(); + } + """, cgsEmag); + Build(consumer); + + using PackageAssemblyContext context = new(OutputDirectory("CgsEmagConsumer")); + Type type = context.LoadFromAssemblyPath(Path.Combine(OutputDirectory("CgsEmagConsumer"), "CgsEmagConsumer.dll")) + .GetType("Consumer", throwOnError: true)!; + Assert.Multiple( + () => Assert.Equal(1, InvokeDouble(type, "ConvertFluxDensity")), + () => Assert.Equal(10, InvokeDouble(type, "ConvertCurrent")), + () => Assert.Equal(10, InvokeDouble(type, "ConvertCharge"))); + } + [Fact] public void MetricNamedConversionsRequireNamespaceImport() { diff --git a/src/DimensionalQuantities.IntegrationTests/Packaging/SymbolPackageTests.cs b/src/DimensionalQuantities.IntegrationTests/Packaging/SymbolPackageTests.cs index 5bfeef4..03ce449 100644 --- a/src/DimensionalQuantities.IntegrationTests/Packaging/SymbolPackageTests.cs +++ b/src/DimensionalQuantities.IntegrationTests/Packaging/SymbolPackageTests.cs @@ -53,6 +53,7 @@ public void CoreSymbolPackageMatchesRuntimeAndAnalyzerAssemblies() [InlineData("Metric")] [InlineData("Imperial")] [InlineData("Nautical")] + [InlineData("CgsEmag")] public void ExtensionSymbolPackageMatchesAssemblyAndEmbedsGeneratedSource(string extension) { string projectName = "DimensionalQuantities.Extensions." + extension; diff --git a/src/DimensionalQuantities.slnx b/src/DimensionalQuantities.slnx index 75ae68d..2c6c5ff 100644 --- a/src/DimensionalQuantities.slnx +++ b/src/DimensionalQuantities.slnx @@ -15,6 +15,10 @@ + + + + diff --git a/src/DimensionalQuantities/Units/EngineeringQuantityAliases.cs b/src/DimensionalQuantities/Units/EngineeringQuantityAliases.cs index 1679397..1233bbd 100644 --- a/src/DimensionalQuantities/Units/EngineeringQuantityAliases.cs +++ b/src/DimensionalQuantities/Units/EngineeringQuantityAliases.cs @@ -9,6 +9,7 @@ internal static class EngineeringQuantityAliases { new(Length: 2), ["Area"] }, { new(Length: 3), ["Volume"] }, { new(Length: 1, Time: -1), ["Speed"] }, + { new(Length: 1, Time: -2), ["Acceleration"] }, { new(Time: -1), ["Frequency", "AngularVelocity"] }, { new(Length: 1, Mass: 1, Time: -2), ["Force"] }, { new(Length: -3, Mass: 1), ["Density"] }, @@ -18,7 +19,9 @@ internal static class EngineeringQuantityAliases { new(Length: 2, Time: -3), ["SpecificPower", "SpecificPowerLoss"] }, { new(Temperature: -1), ["TemperatureCoefficient"] }, { new(Length: -1, Mass: 1, Time: -3), ["PowerDensity", "VolumetricPowerLoss"] }, + { new(Time: 1, ElectricCurrent: 1), ["ElectricCharge"] }, { new(Length: 2, Mass: 1, Time: -3, ElectricCurrent: -1), ["Voltage"] }, + { new(Length: -2, Mass: -1, Time: 4, ElectricCurrent: 2), ["Capacitance"] }, { new(Length: 2, Mass: 1, Time: -3, ElectricCurrent: -2), ["Resistance"] }, { new(Length: 2, Mass: 1, Time: -2, ElectricCurrent: -2), ["Inductance"] }, { new(Length: 2, Mass: 1, Time: -2, ElectricCurrent: -1), ["MagneticFlux", "FluxLinkage"] },