madskirkfoged / engineeringunits Goto Github PK
View Code? Open in Web Editor NEWWorking with units made easy with automatic unit-check and converting between units
License: MIT License
Working with units made easy with automatic unit-check and converting between units
License: MIT License
Hi,
I didn't find the specific heat Cp [J.kg.K], did i miss something ? If no, is there a way to extend or derive this new unit as power/(surface*temperature) ?
Best regards.
I would like to start a discussion about how we can optimize for better performance.
Feel free to chip in!
Analyse where the CPU is spending much of the time?
--> Can we write better code that fixes the heavy CPU using functions?
Could we implement new strategies?
--> fx. If two units are Set as SI we could ship Unitchecks and other parts of the system
Hello, thanks for providing such a wonderful piece of software.
Ive been writing code based on your modules and for some reason when i use xunit, there happens to be unrepeatable wrong unit exceptions which come. It means like once every twenty or thirty test runs, a wrong unit exception comes up.
When i rerun the tests, it disaappears and the tests pass.
It's about 200+ tests, and i strongly suspect it's because xunit does things in parallel, and also because some classes use static operators eg, addition and subtraction.
I strongly suspect a race condition as several classes or test cases are accessing static operators at the same time. And therefore it's not really thread safe.
Were there any of such cases before?
And what are some possible fixes we can work towards?
Edit: I'll also be checking in case my code (or what i'm building off - spicesharp) isn't thread safe. But tbh i have never used static methods in my code and all the exceptions and errors i've gotten were from the EngineeringUnits package tho.
Currently, constructing with a double.NaN value (e.g. Pressure.FromSI(double.NaN)
) results in a value of Infinity.
Looking through the source code I found that this is done in BaseUnit
:
if (IsValueOverDecimalMax(value))
{
Inf = true;
return;
}
where
private static bool IsValueOverDecimalMax(double value)
{
return double.IsInfinity(value) || value > 7.9228162514264338E+28 || value < -7.9228162514264338E+28 || double.IsNaN(value);
}
I imagine this was implemented due to internally saving the value as decimal
, where decimal does not support NaN
.
But from what I can see it could be possible to handle this in exactly the same way as infinity, but side by side. I.e.
if (double.IsNaN(value))
{
IsNaN = true;
return;
}
if (double.IsInfinity(value) || value > 7.9228162514264338E+28 || value < -7.9228162514264338E+28)
{
Inf = true;
return;
}
and then also checking for IsNaN
when getting a value as double, everywhere you currently already check for Inf
.
Hopefully this would not have too much of an impact on performance.
Ideally, to avoid having to use XXX.FromSI(double.NaN)
, there could also be a static property .NaN
similar to .Zero
.
The application where this would be of help to me is for plotting, where NaN
is an empty point and values of Infinity
causes issues and results in exceptions. I can of course get around it in my application by transforming every Infinity
value back to NaN
before passing to the plotting tool, so this is not super crucial, but might be a nice addition if easy.
Might be useful to some.
E.g. defined as
/// <summary>
/// Ideal gas constant
/// </summary>
public static BaseUnit IdealGasConstant
{
get
{
UnitSystem unit = MolarEntropyUnit.JoulePerMoleKelvin.Unit;
return new BaseUnit(8.31446261815324, unit);
}
}
I.e. Volume / Mass, e.g. m^3/kg.
Another one I ran into and implemented myself. Source code for reference:
SpecificVolume.zip
Currently
var p = Pressure.FromBar(10);
// var minusP = -p; // Does not compile
var minusP = p * -1; // Workaround
I noticed you are using this workaround yourself, e.g. in Extensions.Abs
, so I'm pretty sure I didn't miss something this time.
Would probably need to define something along the lines of (following your way of defining the regular operator-
)
public class UnknownUnit
{
// ...
public static UnknownUnit operator -(UnknownUnit right)
{
return -right?.BaseUnit;
}
}
and
public class BaseUnit
{
// ...
public static UnknownUnit operator -(BaseUnit right)
{
if ((object)right == null)
{
return null;
}
try
{
if (right.Unit.IsSIUnit())
{
return new UnknownUnit(-right.NEWValue, right.Unit);
}
return new UnknownUnit(-right.ConvertValueInto(right), right.Unit);
}
catch (OverflowException)
{
return new UnknownUnit(double.PositiveInfinity, -right.Unit);
}
}
}
What do you think?
I.e. Power / (Volume * Temperature), e.g. W/m^3.K
My own implementation for reference:
VolumetricHeatTransferCoefficient.zip
I have a vector and matrix class that I had written for UnitsNet that I am trying to convert over to EngineeringUnits and having some trouble.
I have included the whole class, but there are two basic areas I am having trouble understanding how to implement. Since this is a generic class, I am having trouble initializing the values of the array to a value. I am also having trouble assigning the values of the EngineeringUnits overloaded operators back to . I get
Cannot implicitly convert type 'EngineeringUnits.UnknownUnit' to 'T'. An explicit conversion exists (are you missing a cast?)
using System;
using System.Globalization;
using UnitsNet;
namespace Tidal.Support.DataStructures
{
public class UVector<T> : ICloneable where T : IQuantity, new()
{
private T[] _vector;
public UVector(int nDim)
{
GetVectorSize = nDim;
_vector = new T[nDim];
for (var i = 0; i < nDim; i++) _vector[i] = Zero();
}
public UVector(T[] vector)
{
GetVectorSize = vector.Length;
_vector = vector;
}
public T this[int i]
{
get
{
if (i < 0 || i > GetVectorSize) throw new ArgumentException("Requested vector index is out of range.");
return _vector[i];
}
set => _vector[i] = value;
}
public int GetVectorSize { get; }
object ICloneable.Clone()
{
return Clone();
}
private T Zero()
{
QuantityValue v = 0.0;
var value = Quantity.FromQuantityInfo(_vector[0].QuantityInfo, v);
return (T) value;
}
public UVector<T> Clone()
{
var v = new UVector<T>(_vector);
v._vector = (T[]) _vector.Clone();
return v;
}
public UVector<T> SwapVectorEntries(int m, int n)
{
var temp = _vector[m];
_vector[m] = _vector[n];
_vector[n] = temp;
return new UVector<T>(_vector);
}
public override string ToString()
{
var str = "(";
for (var i = 0; i < GetVectorSize - 1; i++) str += _vector[i].ToString(CultureInfo.InvariantCulture) + ", ";
str += _vector[GetVectorSize - 1].ToString(CultureInfo.InvariantCulture) + ")";
return str;
}
public override bool Equals(object obj)
{
return obj is UVector<T> && Equals((UVector<T>) obj);
}
public bool Equals(UVector<T> v)
{
return _vector == v._vector;
}
public override int GetHashCode()
{
return _vector.GetHashCode();
}
public static bool operator ==(UVector<T> v1, UVector<T> v2)
{
return v1.Equals(v2);
}
public static bool operator !=(UVector<T> v1, UVector<T> v2)
{
return !v1.Equals(v2);
}
public static UVector<T> operator +(UVector<T> v)
{
return v;
}
public static UVector<T> operator +(UVector<T> v1, UVector<T> v2)
{
var result = new UVector<T>(v1.GetVectorSize);
var dim = v1[0].Dimensions;
QuantityValue val = 0.0;
var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
for (var i = 0; i < v1.GetVectorSize; i++)
{
val = v1[i].As(a) + v2[i].As(a);
result[i] = (T) Quantity.From(val, a);
}
return result;
}
public static UVector<T> operator -(UVector<T> v1)
{
var result = new UVector<T>(v1.GetVectorSize);
var dim = v1[0].Dimensions;
QuantityValue val = 0.0;
var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
for (var i = 0; i < v1.GetVectorSize; i++)
{
val = -v1[i].As(a);
result[i] = (T) Quantity.From(val, a);
}
return result;
}
public static UVector<T> operator -(UVector<T> v1, UVector<T> v2)
{
var result = new UVector<T>(v1.GetVectorSize);
var dim = v1[0].Dimensions;
QuantityValue val = 0.0;
var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
for (var i = 0; i < v1.GetVectorSize; i++)
{
val = v1[i].As(a) - v2[i].As(a);
result[i] = (T) Quantity.From(val, a);
}
return result;
}
public static UVector<T> operator *(UVector<T> v1, double d)
{
var result = new UVector<T>(v1.GetVectorSize);
var dim = v1[0].Dimensions;
QuantityValue val = 0.0;
var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
for (var i = 0; i < v1.GetVectorSize; i++)
{
val = v1[i].As(a) * d;
result[i] = (T) Quantity.From(val, a);
}
return result;
}
public static UVector<T> operator *(double d, UVector<T> v)
{
return v * d;
}
public static UVector<T> operator /(UVector<T> v1, double d)
{
var result = new UVector<T>(v1.GetVectorSize);
var dim = v1[0].Dimensions;
QuantityValue val = 0.0;
var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
for (var i = 0; i < v1.GetVectorSize; i++)
{
val = v1[i].As(a) / d;
result[i] = (T) Quantity.From(val, a);
}
return result;
}
public static UVector<T> operator /(double d, UVector<T> v)
{
throw new Exception("Matrix dimensions must agree.");
}
public static T DotProduct(UVector<T> v1, UVector<T> v2)
{
var dim = v1[0].Dimensions;
QuantityValue val = 0.0;
var temp = 0.0;
var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
for (var i = 0; i < v1.GetVectorSize; i++) temp += v1[i].As(a) * v2[i].As(a);
val = temp;
return (T) Quantity.From(val, a);
}
/// <summary>
/// ///////////////////////
/// </summary>
/// <returns></returns>
public T GetNorm()
{
var unit = GetNormSquare();
var dim = _vector[0].Dimensions;
var a = _vector[0].QuantityInfo.BaseUnitInfo.Value;
return (T) Quantity.From(Math.Sqrt(unit.As(a)), a);
}
/// <summary>
/// need GetNormSquare to be private as units are not consistent.
/// </summary>
/// <returns></returns>
private T GetNormSquare()
{
var dim = _vector[0].Dimensions;
QuantityValue result = 0.0;
var temp = 0.0;
var a = _vector[0].QuantityInfo.BaseUnitInfo.Value;
// double result = 0.0;
for (var i = 0; i < GetVectorSize; i++) temp += _vector[i].As(a) * _vector[i].As(a);
result = temp;
return (T) Quantity.From(result, a);
;
}
public void Normalize()
{
var norm = GetNorm();
var a = _vector[0].QuantityInfo.BaseUnitInfo.Value;
if (norm.As(a) == 0) throw new Exception("Tried to normalize a vector with a norm of zero.");
for (var i = 0; i < GetVectorSize; i++) _vector[i] = (T) Quantity.From(_vector[i].As(a) / norm.As(a), a);
}
public UVector<T> GetUnitVector()
{
var result = new UVector<T>(_vector);
result.Normalize();
return result;
}
public RVector ToUnit(Enum unit)
{
var result = new RVector(GetVectorSize);
for (var i = 0; i < GetVectorSize; i++) result[i] = _vector[i].As(unit);
return result;
}
public static UVector<T> CrossProduct(UVector<T> v1, UVector<T> v2)
{
if (v1.GetVectorSize != 3) throw new Exception("Vector v1 must be 3 dimensional.");
if (v2.GetVectorSize != 3) throw new Exception("Vector v2 must be 3 dimensional.");
var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
var result = new UVector<T>(3);
result[0] = (T) Quantity.From(v1[1].As(a) * v2[2].As(a) - v1[2].As(a) * v2[1].As(a), a);
result[1] = (T) Quantity.From(v1[2].As(a) * v2[0].As(a) - v1[0].As(a) * v2[2].As(a), a);
result[2] = (T) Quantity.From(v1[0].As(a) * v2[1].As(a) - v1[1].As(a) * v2[0].As(a), a);
return result;
}
}
}
Need to add JERK, it is the same as Acceleration but time is cubed. This is used in motion profiling and anywhere there is a linear change in Acceleration and Parabolic change in Velocity.
Molar flow (i.e. AmountOfSubstance
/ Duration
e.g. mol/s) is a unit that would be of interest for me.
Specifically, I ran into needing to compute MassFlow
/ MolarMass
, which I then noticed I could not cast to anything appropriate.
Thanks for the amazing work!
An engineering example of this is the ratio of specific heats (Gamma). This should be a double. UnitsNet results in the correct output of the following
var Cp = SpecificEntropy.FromKilojoulesPerKilogramKelvin(1.00);
var CV = SpecificEntropy.FromKilojoulesPerKilogramKelvin(0.718);
double gamma = Cp / Cv;
In EngineeringUnits this results in an implicit conversion error.
Test code:
var p1 = Pressure.FromBar(10);
var p2 = Pressure.FromBar(20);
Console.WriteLine($"{nameof(p1)}: {p1}"); // prints "p1: 10 bar"
Console.WriteLine($"{nameof(p2)}: {p2}"); // prints "p2: 20 bar"
Console.WriteLine($"p1.Minimum(p2): {p1.Minimum(p2)}"); // prints "p1.Minimum(p2): 20 bar"
Console.WriteLine($"p2.Minimum(p1): {p2.Minimum(p1)}"); // prints "p2.Minimum(p1): 20 bar"
Console.WriteLine($"p1.Maximum(p2): {p1.Maximum(p2)}"); // prints "p1.Maximum(p2): 10 bar"
Console.WriteLine($"p2.Maximum(p1): {p2.Maximum(p1)}"); // prints "p2.Maximum(p1): 10 bar"
This is a placeholder for this Task started in #19.
How should we create custom units?
Does any of the code need to change for making it easier to create custom units?
I am using sharpfluids when i noticed this bug
This is piece of code that i can isolate
static void Main(string[] args)
{
var temperature = Temperature.FromDegreesCelsius(20);
var pressure = Pressure.FromKilopascals(100);
Console.WriteLine("Pressure: {0} \nTemperature: {1}", pressure.ToUnit(EngineeringUnits.Units.PressureUnit.Kilopascal), temperature.ToUnit(EngineeringUnits.Units.TemperatureUnit.DegreeCelsius));
}
Output :
Pressure: 100 kPa
Temperature: 293,2 K
Expected behavior:
Temperature in Celsius
Observed behavior:
Temperature in Kelvin
It would be nice if EngineeringUnits would support thermal resistivity unit (K⋅m/W) that would be helpful.
For some unknown reason my IT department has not granted me permission to publish my package to Nuget at this time. (I didn't even know I needed permission until a few days ago!)
Nonetheless the source code for my application is visible: https://github.com/jccockre/constraints
It seems to automatically show up in EngineeringUnits under "Used by," which is cool.
Currently the BaseUnit will return itself when asked to raise itself to the zeroth power.
Real numbers raised to the zeroth power are defined to be one, and dimensional quantities to the zeroth power are dimensionless.
I have a fix including unit tests but getting 403 error when trying to submit a pull request; maybe I need you to grant me permissions or maybe I don't know how to use Git.
Temperature? temperature = GetTemperatureFromMethodThatCouldReturnNull();
if(temperature == null)
{
...
}
The null comparison in the if statement throws because it uses the overloaded ==
operator for BaseUnit vs BaseUnit which calls:
if (left.Unit != right.Unit)
When right is null such as the comparison above this throws a NullReferenceException.
Hello, thanks for making this great package,
I'm trying to make custom units for heat capacity using BaseUnit Class
I find that when i do:
Console.WriteLine(EnergyUnit.Kilojoule/AmountOfSubstanceUnit.Kilomole)
Console.WriteLine(EnergyUnit.SI/AmountOfSubstanceUNit.SI)
They are not equivalent.
I'm making a video guide on how to use your unit package, but the bug is at
https://youtu.be/R_2DvEKXpac
12:00 approx onwards.
I will stick to SI for now, but could you check this out? Thank you so much!
The access to the caches of the multiplied and divided units is not protected from concurrent access.
I had sporadic errors when running unit tests involving unit calculations/unit initializations. I reduced the problem to the followinf unit test which fails with a high probability:
[Fact]// (Skip = "race condition demonstration")]
[Exploratory]
public void ParallelAccess2()
{
var unit1 = LengthUnit.Meter;
var unit2 = DurationUnit.Millisecond;
Func<UnitSystem> dividedUnit = () =>
{
var unit3 = unit1.Unit / unit2.Unit;
return unit3;
};
List<Task> tasks = new List<Task>();
for (int i = 1; i < 400; ++i)
{
tasks.Add(Task.Run(dividedUnit));
}
Task.WaitAll(tasks.ToArray());
}
Observed behavior
System.ArgumentException
An item with the same key has already been added. Key: 1100879750
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
at EngineeringUnits.UnitSystem.Divide(UnitSystem a, UnitSystem b)
at EngineeringUnits.UnitSystem.op_Division(UnitSystem left, UnitSystem right)
at EngineeringUnitsTests.<>c__DisplayClass6_0.<ParallelAccess2>b__0()
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
I had another test with random units which showed some different errors, e.g.
System.InvalidOperationException
Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
at System.Collections.Generic.Dictionary`2.FindValue(TKey key)
at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
at EngineeringUnits.UnitSystem.Divide(UnitSystem a, UnitSystem b)
at EngineeringUnits.UnitSystem.op_Division(UnitSystem left, UnitSystem right)
at EngineeringUnitsTests.<>c__DisplayClass5_0.<ParallelAccess>b__5()
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
Expected behavior:
No exception when doing unit calculations in different threads.
I'm working on a project that is using EngineeringUnits for internal representation of physical units. So far it has worked as expected, but I've come across an issue where the method
BaseUnit.GetValueAs(UnitSystem to)
returns an incorrect value when the base unit is using the same UnitSystem as the method argument. I managed to pin it down to some particular sequence of instructions - here is a minimal XUnit test to reporduce the issue:
[Fact]
public void Test()
{
var unit = EngineeringUnits.Units.MassUnit.Gram.Unit;
var valueWithUnit = new BaseUnit(1, unit);
//var before = valueWithUnit.GetValueAs(unit);
for (int i = 0; i < 2; i++)
{
var siUnit = unit.GetSIUnitsystem();
var convertedValue = valueWithUnit.GetValueAs(siUnit);
var value = new UnknownUnit(convertedValue, unit);
}
var after = valueWithUnit.GetValueAs(unit);
Assert.Equal(1, after);
}
Expectated behavior: The result of GetValueAs has to be 1, because converting 1 gram to grams should yield 1 gram.
Actual behavior: The result of GetValueAs is 0.001
This test seems to work for any dimension other than mass, but I haven't verified it for every single one. Interestingly, if you execute the commented line before the for-loop, it works as expected again...
My suspicion is that it has to do with the fact that for mass units, the SI base unit is kilogram - that might explain why the returned value is off by a factor of 1000 for mass units, but not for other units. But the details are beyond my understanding, like for example I don't understand why this issue only materializes when the loop is executed more than once...
Some help on this issue and a bugfix (if this is indeed unexpected behavior) would be much appreciated.
Creating a Issue based in @mschmitz2's excellent suggestion in #19 to add cost.
It has been on my list for some time now so lets give it a go!
I have added following (still subject to change..)
Cost f1 = new Cost(10, CostUnit.USDollar); //10 $
Energy e1 = new Energy(10, EnergyUnit.KilowattHour); //10 kWh
EnergyCost test = f1 / e1; //1 $/kWh
Any other name we should consider for the unit representing $£€?
Price
Cost
Currency
Name when combining it with other units:
EnergyCost
CostOfEnergy
EnergySpecificCost
EnergyPrice
What do you guys think?
Also, we need to figure out how to convert between the different Currencies.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.