Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add concentration units (mixtures/solutions) #646

Merged
merged 10 commits into from
Apr 21, 2019
24 changes: 2 additions & 22 deletions Common/UnitDefinitions/MassConcentration.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,7 @@
},
{
"SingularName": "GramPerDeciliter",
"PluralName": "GramsPerDeciliter",
"BaseUnits": {
"M": "Gram",
"L": "Decimeter"
},
"PluralName": "GramsPerDeciliter",
"FromUnitToBaseFunc": "x/1e-1",
"FromBaseToUnitFunc": "x*1e-1",
"Prefixes": [ "Pico", "Nano", "Micro", "Milli", "Centi", "Deci" ],
Expand All @@ -106,30 +102,14 @@
},
"FromUnitToBaseFunc": "x",
"FromBaseToUnitFunc": "x",
"Prefixes": [ "Pico", "Nano", "Micro", "Milli", "Centi", "Deci" ],
"Prefixes": [ "Pico", "Nano", "Micro", "Milli", "Centi", "Deci", "Kilo" ],
"Localization": [
{
"Culture": "en-US",
"Abbreviations": [ "g/L"]
}
]
},
{
"SingularName": "KilogramPerLiter",
"PluralName": "KilogramsPerLiter",
"BaseUnits": {
"M": "Kilogram",
"L": "Decimeter"
},
"FromUnitToBaseFunc": "x*1e3",
"FromBaseToUnitFunc": "x/1e3",
"Localization": [
{
"Culture": "en-US",
"Abbreviations": [ "kg/L" ]
}
]
},
{
"SingularName": "TonnePerCubicMillimeter",
"PluralName": "TonnesPerCubicMillimeter",
Expand Down
4 changes: 2 additions & 2 deletions Common/UnitDefinitions/MassFraction.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
]
},
{
"SingularName": "GramPerKiloGram",
"PluralName": "GramsPerKiloGram",
"SingularName": "GramPerKilogram",
"PluralName": "GramsPerKilogram",
"FromUnitToBaseFunc": "x/1e3",
"FromBaseToUnitFunc": "x*1e3",
"Prefixes": [ "Nano", "Micro", "Milli", "Centi", "Deci", "Deca", "Hecto", "Kilo" ],
Expand Down
4 changes: 2 additions & 2 deletions Common/UnitDefinitions/VolumeConcentration.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"Localization": [
{
"Culture": "en-US",
"Abbreviations": [ "l/l" ]
"Abbreviations": [ "L/L" ]
}
]
},
Expand All @@ -38,7 +38,7 @@
"Localization": [
{
"Culture": "en-US",
"Abbreviations": [ "l/ml" ]
"Abbreviations": [ "L/mL" ]
}
]
},
Expand Down
88 changes: 48 additions & 40 deletions UnitsNet.Tests/CustomCode/AmountOfSubstanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet.

using System;
using UnitsNet.Units;
using Xunit;

namespace UnitsNet.Tests.CustomCode
Expand All @@ -43,17 +44,6 @@ public class AmountOfSubstanceTests : AmountOfSubstanceTestsBase
protected override double PoundMolesInOneMole => 0.002204622621848776;
protected override double MegamolesInOneMole => 1e-6;

private static double MolarMassOfOxygen = 15.999;
private static double MolesInTenGramsOfOxygen = 0.6250390649415588;


private static double MolarMassHClInGramsPerMole = 36.46;
private static double MassOfSubstanceInGrams = 5;
private static double VolumeOfSolutionInLiters = 1.2;

private static double ExpectedMolarityMolesPerLiter = 0.1142805; // molarity = 5 / (1.2 * 36.46) = 0.114 mol/l = 0.114 M


[Fact]
public void NumberOfParticlesInOneMoleEqualsAvogadroConstant()
{
Expand All @@ -70,45 +60,63 @@ public void NumberOfParticlesInTwoMolesIsDoubleAvogadroConstant()
Assert.Equal(AmountOfSubstance.AvogadroConstant * 2, numberOfParticles);
}

[Fact]
public void TenMolesOfOxygenToMassEqualToExpected()
[Theory]
[InlineData(10, AmountOfSubstanceUnit.Mole,
KnownQuantities.MolarMassOfOxygen, MolarMassUnit.GramPerMole,
10 * KnownQuantities.MolarMassOfOxygen, MassUnit.Gram)] // 10 Moles of Oxygen weight 10 times as much as 1 Mole of Oxygen (MolarMass)
public void MassFromAmountOfSubstanceAndMolarMass(
double amountOfSubstanceValue, AmountOfSubstanceUnit amountOfSubstanceUnit,
double molarMassValue, MolarMassUnit molarMassUnit,
double expectedMass, MassUnit expectedMassUnit, double tolerence = 1e-5)
{
AmountOfSubstance tenMoles = AmountOfSubstance.FromMoles(10);
MolarMass molarMass = MolarMass.FromGramsPerMole(MolarMassOfOxygen);
AssertEx.EqualTolerance(10 * MolarMassOfOxygen, (tenMoles * molarMass).Grams, MolesTolerance);
}
AmountOfSubstance amountOfSubstance = new AmountOfSubstance(amountOfSubstanceValue, amountOfSubstanceUnit);
MolarMass molarMass = new MolarMass(molarMassValue, molarMassUnit);

Mass mass = amountOfSubstance * molarMass;

[Fact]
public void TenGramsOfOxygenToMolesEqualToExpected()
{
Mass tenGrams = Mass.FromGrams(10);
MolarMass molarMass = MolarMass.FromGramsPerMole(MolarMassOfOxygen);
AssertEx.EqualTolerance(MolesInTenGramsOfOxygen, (tenGrams / molarMass).Moles, MolesTolerance);
AssertEx.EqualTolerance(expectedMass, mass.As(expectedMassUnit), tolerence);
}

[Fact]
public void HClSolutionVolumeIsEqualToExpected()
[Theory]
[InlineData(5, MassUnit.Gram,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, this is fine, but I didn't mean that [InlineData] was my preferred method of testing. Only that the values would be inlined/inserted into the test code itself, so that you can read all the numbers that go into the calculation for each test method rather than reusing fields. [Fact] is probably more "correct" than [Theory] + [InlineData] if there is only one test case. If you have multiple test cases/inputs, then [InlineData] is a good fit.

At any rate, this works and I'm not going to bother you to change it again.

KnownQuantities.MolarMassHClInGramsPerMole, MolarMassUnit.GramPerMole,
1.2, VolumeUnit.Liter,
0.1142805, MolarityUnit.MolesPerLiter)] // molarity(HCl) = 5g / (1.2L * 36.46) = 0.114 mol/l = 0.114 M
public void MolarityFromComponentMassAndSolutionVolume(
double componentMassValue, MassUnit componentMassUnit,
double componentMolarMassValue, MolarMassUnit componentMolarMassUnit,
double solutionVolumeValue, VolumeUnit solutionVolumeUnit,
double expectedMolarityValue, MolarityUnit expectedMolarityUnit, double tolerence = 1e-5)
{
MolarMass molarMass = MolarMass.FromGramsPerMole(MolarMassHClInGramsPerMole);
Mass substanceMass = Mass.FromGrams(MassOfSubstanceInGrams);
AmountOfSubstance amountOfSubstance = substanceMass / molarMass;
Molarity molarity = Molarity.FromMolesPerLiter(ExpectedMolarityMolesPerLiter);
var componentMass = new Mass(componentMassValue, componentMassUnit);
var componentMolarMass = new MolarMass(componentMolarMassValue, componentMolarMassUnit);
var volumeSolution = new Volume(solutionVolumeValue, solutionVolumeUnit);

AmountOfSubstance amountOfSubstance = componentMass / componentMolarMass;
Molarity molarity = amountOfSubstance / volumeSolution;

Volume volumeSolution = amountOfSubstance / molarity;
AssertEx.EqualTolerance(VolumeOfSolutionInLiters, volumeSolution.Liters, MolesTolerance);
AssertEx.EqualTolerance(expectedMolarityValue, molarity.As(expectedMolarityUnit), tolerence);
}

[Fact]
public void HClSolutionMolarityIsEqualToExpected()
[Theory]
[InlineData(5, MassUnit.Gram,
KnownQuantities.MolarMassHClInGramsPerMole, MolarMassUnit.GramPerMole,
0.1142805, MolarityUnit.MolesPerLiter,
1.2, VolumeUnit.Liter)] // 1.2 L of solution required for obtaining 0.1142805 Moles/L from 5g HCl
public void VolumeSolutionFromComponentMassAndDesiredConcentration(
double componentMassValue, MassUnit componentMassUnit,
double componentMolarMassValue, MolarMassUnit componentMolarMassUnit,
double desiredMolarityValue, MolarityUnit desiredMolarityUnit,
double expectedSolutionVolumeValue, VolumeUnit expectedSolutionVolumeUnit, double tolerence = 1e-5)
{
MolarMass molarMass = MolarMass.FromGramsPerMole(MolarMassHClInGramsPerMole);
Mass substanceMass = Mass.FromGrams(MassOfSubstanceInGrams);
AmountOfSubstance amountOfSubstance = substanceMass / molarMass;
Volume volumeSolution = Volume.FromLiters(VolumeOfSolutionInLiters);
var componentMass = new Mass(componentMassValue, componentMassUnit);
var componentMolarMass = new MolarMass(componentMolarMassValue, componentMolarMassUnit);
var desiredMolarity = new Molarity(desiredMolarityValue, desiredMolarityUnit);

Molarity molarity = amountOfSubstance / volumeSolution;
AssertEx.EqualTolerance(ExpectedMolarityMolesPerLiter, molarity.MolesPerLiter, MolesTolerance);
}
AmountOfSubstance amountOfSubstance = componentMass / componentMolarMass;
Volume volumeSolution = amountOfSubstance / desiredMolarity;

AssertEx.EqualTolerance(expectedSolutionVolumeValue, volumeSolution.As(expectedSolutionVolumeUnit), tolerence);
}
}
}
2 changes: 2 additions & 0 deletions UnitsNet.Tests/CustomCode/KnownQuantities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
{
public static class KnownQuantities
{
public const double MolarMassOfOxygen = 15.9994;

public const double DensityOfEthanolInKgPerCubicMeter = 789;
public const double MolarMassOfEthanolInGramsPerMole = 46.06844;

Expand Down
80 changes: 34 additions & 46 deletions UnitsNet.Tests/CustomCode/MassConcentrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,39 +73,10 @@ public class MassConcentrationTests : MassConcentrationTestsBase
#endregion

[Theory]
[InlineData(2, MassConcentrationUnit.KilogramPerCubicMeter, 3, VolumeUnit.CubicMeter, 6, MassUnit.Kilogram)]
public static void MassConcentrationTimesVolumeEqualsMass(
double massConcValue, MassConcentrationUnit massConcUnit,
double volumeValue, VolumeUnit volumeUnit,
double expectedMassValue, MassUnit expectedMassUnit, double tolerance= 1e-5)
{
var massConcentration = new MassConcentration(massConcValue, massConcUnit);
var volume = new Volume(volumeValue, volumeUnit);

Mass mass = massConcentration * volume;
AssertEx.EqualTolerance(mass.As(expectedMassUnit), expectedMassValue, tolerance);
}

[Fact]
public static void VolumeTimesMassConcentrationEqualsMass()
{
Mass mass = Volume.FromCubicMeters(3) * MassConcentration.FromKilogramsPerCubicMeter(2);
Assert.Equal(mass, Mass.FromKilograms(6));
}

[Fact]
public void ExpectMolarityConvertedToMassConcentrationCorrectly()
{
var molarity = Molarity.FromMolesPerLiter(1.02698355);
var molarMass = MolarMass.FromGramsPerMole(58.443);

MassConcentration concentration = molarity.ToMassConcentration(molarMass); // molarity * molarMass
AssertEx.EqualTolerance(60.02, concentration.KilogramsPerCubicMeter, KilogramsPerCubicMeterTolerance);
}

[Theory]
[InlineData(60.02, MassConcentrationUnit.KilogramPerCubicMeter, 58.443, MolarMassUnit.GramPerMole, 1026.98355, MolarityUnit.MolesPerCubicMeter)]
public void ExpectMassConcentrationConvertedToMolarityCorrectly(
[InlineData(60.02, MassConcentrationUnit.KilogramPerCubicMeter,
58.443, MolarMassUnit.GramPerMole,
1026.98355, MolarityUnit.MolesPerCubicMeter)] // test from JonathanDavies626
public void MolarityFromMassConcentrationAndMolarMass(
double massConcValue, MassConcentrationUnit massConcUnit,
double molarMassValue, MolarMassUnit molarMassUnit,
double expectedMolarityValue, MolarityUnit expectedMolarityUnit, double tolerance = 1e-5)
Expand All @@ -114,34 +85,50 @@ public void ExpectMassConcentrationConvertedToMolarityCorrectly(
var molarMass = new MolarMass(molarMassValue, molarMassUnit);

Molarity molarity = massConcentration.ToMolarity(molarMass); // molarity / molarMass

AssertEx.EqualTolerance(expectedMolarityValue, molarity.As(expectedMolarityUnit), tolerance);
angularsen marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public void MassConcentrationFromVolumeConcentrationAndDensity()
[Theory]
[InlineData(10, DensityUnit.GramPerCubicMeter,
5, MassConcentrationUnit.GramPerCubicMeter,
0.5, VolumeConcentrationUnit.DecimalFraction)] // synthetic data
public void VolumeConcentrationFromMassConcentrationAndDensity(
double componentDensityValue, DensityUnit componentDensityUnit,
double massConcValue, MassConcentrationUnit masConcUnit,
double expectedVolumeConcValue, VolumeConcentrationUnit expectedVolumeConcUnit, double tolerence = 1e-5)
{
var volumeConcentration = VolumeConcentration.FromPercent(50);
var density = Density.FromGramsPerCubicMeter(10);
var density = new Density(componentDensityValue, componentDensityUnit);
var massConcentration = new MassConcentration(massConcValue, masConcUnit);

VolumeConcentration volumeConcentration = massConcentration.ToVolumeConcentration(density); // massConcentration / density;

MassConcentration massConcentration = volumeConcentration.ToMassConcentration(density); // volumeConcentration * density
AssertEx.EqualTolerance(5, massConcentration.GramsPerCubicMeter, GramsPerCubicMeterTolerance);
AssertEx.EqualTolerance(expectedVolumeConcValue, volumeConcentration.As(expectedVolumeConcUnit), tolerence);
}

[Fact]
public void MassConcentrationFromVolumeConcentrationEthanol()
[Theory]
[InlineData(2, MassConcentrationUnit.KilogramPerCubicMeter,
3, VolumeUnit.CubicMeter,
6, MassUnit.Kilogram)] // synthetic data
public static void ComponentMassFromMassConcentrationAndSolutionVolume(
double massConcValue, MassConcentrationUnit massConcUnit,
double volumeValue, VolumeUnit volumeUnit,
double expectedMassValue, MassUnit expectedMassUnit, double tolerance = 1e-5)
{
const double VolumeConcentration_0_5M_Ethanol = 29.19419518377693;
var density = Density.FromKilogramsPerCubicMeter(KnownQuantities.DensityOfEthanolInKgPerCubicMeter);
var volumeConcentration = VolumeConcentration.FromMillilitersPerLiter(VolumeConcentration_0_5M_Ethanol);
var massConcentration = new MassConcentration(massConcValue, massConcUnit);
var volume = new Volume(volumeValue, volumeUnit);

MassConcentration massConcentration = volumeConcentration.ToMassConcentration(density);
AssertEx.EqualTolerance(23.03422, massConcentration.GramsPerLiter, GramsPerCubicMeterTolerance);
Mass massComponent = massConcentration * volume;

AssertEx.EqualTolerance(expectedMassValue, massComponent.As(expectedMassUnit), tolerance);
}


[Fact(Skip = "No BaseUnit defined: see https://github.com/angularsen/UnitsNet/issues/651")]
angularsen marked this conversation as resolved.
Show resolved Hide resolved
public void DefaultSIUnitIsKgPerCubicMeter()
{
var massConcentration = new MassConcentration(1, UnitSystem.SI);

Assert.Equal(MassConcentrationUnit.KilogramPerCubicMeter, massConcentration.Unit); // MassConcentration.BaseUnit = KilogramPerCubicMeter
}

Expand All @@ -152,6 +139,7 @@ public void DefaultUnitTypeRespectedForCustomUnitSystem()
ElectricCurrentUnit.Ampere, TemperatureUnit.DegreeCelsius, AmountOfSubstanceUnit.Mole, LuminousIntensityUnit.Candela));

var massConcentration = new MassConcentration(1, customSystem);

Assert.Equal(MassConcentrationUnit.GramPerCubicMillimeter, massConcentration.Unit);
}

Expand Down
25 changes: 14 additions & 11 deletions UnitsNet.Tests/CustomCode/MassFractionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ namespace UnitsNet.Tests.CustomCode
public class MassFractionTests : MassFractionTestsBase
{
#region Unit Conversion Coefficients
protected override double KilogramsPerKiloGramInOneDecimalFraction => 1;
protected override double HectogramsPerKiloGramInOneDecimalFraction => 10;
protected override double DecagramsPerKiloGramInOneDecimalFraction => 1E2;
protected override double GramsPerKiloGramInOneDecimalFraction => 1E3;
protected override double DecigramsPerKiloGramInOneDecimalFraction => 1E4;
protected override double CentigramsPerKiloGramInOneDecimalFraction => 1E5;
protected override double MilligramsPerKiloGramInOneDecimalFraction => 1E6;
protected override double MicrogramsPerKiloGramInOneDecimalFraction => 1E9;
protected override double NanogramsPerKiloGramInOneDecimalFraction => 1E12;
protected override double KilogramsPerKilogramInOneDecimalFraction => 1;
protected override double HectogramsPerKilogramInOneDecimalFraction => 10;
protected override double DecagramsPerKilogramInOneDecimalFraction => 1E2;
protected override double GramsPerKilogramInOneDecimalFraction => 1E3;
protected override double DecigramsPerKilogramInOneDecimalFraction => 1E4;
protected override double CentigramsPerKilogramInOneDecimalFraction => 1E5;
protected override double MilligramsPerKilogramInOneDecimalFraction => 1E6;
protected override double MicrogramsPerKilogramInOneDecimalFraction => 1E9;
protected override double NanogramsPerKilogramInOneDecimalFraction => 1E12;

protected override double KilogramsPerGramInOneDecimalFraction => 1E-3;
protected override double HectogramsPerGramInOneDecimalFraction => 1E-2;
Expand Down Expand Up @@ -64,6 +64,7 @@ public void MassFractionFromMassesConstructedCorrectly()
var two_kg = Mass.FromKilograms(2);

var massFraction = MassFraction.FromMasses(one_kg, two_kg);

AssertEx.EqualTolerance(50, massFraction.Percent, PercentTolerance);
}

Expand All @@ -74,7 +75,8 @@ public void TotalMassFromMassFraction()
var massFraction = MassFraction.FromPercent(50);

var totalMass = massFraction.GetTotalMass(componentMass);
AssertEx.EqualTolerance(2, totalMass.Kilograms, KilogramsPerKiloGramTolerance);

AssertEx.EqualTolerance(2, totalMass.Kilograms, KilogramsPerKilogramTolerance);
}

[Fact]
lipchev marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -84,7 +86,8 @@ public void ComponentMassFromMassFraction()
var massFraction = MassFraction.FromPercent(50);

var componentMass = massFraction.GetComponentMass(totalMass);
AssertEx.EqualTolerance(1, componentMass.Kilograms, KilogramsPerKiloGramTolerance);

AssertEx.EqualTolerance(1, componentMass.Kilograms, KilogramsPerKilogramTolerance);
}

}
Expand Down
17 changes: 17 additions & 0 deletions UnitsNet.Tests/CustomCode/MassTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,22 @@ public void NegativeMassToStonePoundsReturnsCorrectValues()
Assert.Equal(-1.0, stonePounds.Stone);
Assert.Equal(-11.0, stonePounds.Pounds);
}

[Theory]
[InlineData(10, MassUnit.Gram,
KnownQuantities.MolarMassOfOxygen, MolarMassUnit.GramPerMole,
0.625023438378939, AmountOfSubstanceUnit.Mole)] // 10 grams Of Oxygen contain 0.625023438378939 Moles
public void AmountOfSubstanceFromMassAndMolarMass(
double massValue, MassUnit massUnit,
double molarMassValue, MolarMassUnit molarMassUnit,
double expectedAmountOfSubstanceValue, AmountOfSubstanceUnit expectedAmountOfSubstanceUnit, double tolerence = 1e-5)
{
var mass = new Mass(massValue, massUnit);
var molarMass = new MolarMass(molarMassValue, molarMassUnit);

AmountOfSubstance amountOfSubstance = mass / molarMass;

AssertEx.EqualTolerance(expectedAmountOfSubstanceValue, amountOfSubstance.As(expectedAmountOfSubstanceUnit), tolerence);
}
}
}
Loading