Closed sequc82 closed 4 years ago
Hi there, thanks for doing all the research on the existing issues and creating a summary here.
I don't know this domain at all and I currently don't what exactly is lacking in Units.NET or what needs to be added or changed. My time is also very limited these days with family and other projects, so if you could create a short bullet list of what concrete changes you'd like to see in Units.NET, or maybe you need some help from me to decide how to fit a new concept into Units.NET, then it's a lot easier for me to comment on it and help move this forward.
Should we add some new quantities? New units? Should some quantities be renamed/repurposed because they are wrong/ambiguous? Should we add some wrapper types that help with conversions by adding extra information that our current quantities don't provide?
Things like that. The more specific/concrete the better. Pseudocode examples of intended usage is also very nice.
I see I commented in #713 that my understanding was that maybe a wrapper type with some more context/information is needed to solve this.
Best, Andreas
I suppose it's also worth noting, this applies only to gas flow rather than liquid flow.
MassFlow
than VolumeFlow
StandardFlow
or NormalFlow
MassFlow
using gas specific gravity (or Density
) as a parameter input, or conversion from VolumeFlow
using temperature, pressure, and gas density (via specific gravity, relative humidity, etc).ActualCubicFootPerHour
and StandardCubicFootPerHour
to clarify CubicFootPerHour
I'm still trying to familiarize myself with the architecture of UnitsNet. I assume every conversion across quantities requires a wrapper? I would expect that conversions to a StandardFlowUnit
could be handled within StandardFlow
, but would conversions from a StandardFlowUnit
require a wrapper for both MassFlow
and VolumeFlow
?
So I suppose I'm proposing creating instances of StandardFlow
in the forms below:
from MassFlow
new StandardFlow(MassFlow massFlow, double gasSpecificGravity)
new StandardFlow(MassFlow massFlow, Density gasDensity)
or from VolumeFlow
(for air)
new StandardFlow(VolumeFlow volumeFlow, Temperature fluidTemperature, ReferencePressure fluidPressure, ReferencePressure saturationPressure, double relativeHumidity, ReferencePressure standardPressure, Temperature standardTemperature)
We would see more varieties to the constructor from VolumeFlow
rather than MassFlow
to account for fluid density at different temperatures and pressures.
I assume every conversion across quantities requires a wrapper?
It depends on what you mean. The architecture is currently designed to use a code generator to read JSON files like Length.json and output C# code with quantity types like Length
and Mass
. Quantities represent a value and unit and has properties and methods to convert between units of the same quantity.
We sometimes extend the generated code to allow for converting to other quantities, such as Mass m = Mass.FromGravitationalForce(myForce);
or Area a = myLength1 * myLength2;
.
Extensions like that are manually added to partial files like Mass.extra.cs and Length.extra.cs.
So if I understood you right, there is typically no wrapper type involved in cross-quantity conversions, only partial files that add extra methods and members to the generated quantity types.
So in your suggestion, if you are generating the StandardFlow
type based off a JSON file like all the other quantities, then you could add a StandardFlow.extra.cs
file with
public partial struct StandardFlow
{
/* extra methods, properties etc here */
}
If you are NOT generating the code for StandardFlow
, but rather implementing it by hand, then you can do whatever you want.
I'm slowly getting a better grip on this, but it would still help a lot for my understanding if you could show a couple of concrete, real-life examples of how you would like to use this in your day to day work:
@angularsen, Thank you for the clarification of the architecture.
Based on that, I suspect the best way to avoid code duplication would be to build upon VolumeFlow
.
StandardFlow
to another would use the same conversion rates as VolumeFlow
.
VolumeFlow
conversion also requires the physical properties of the gas (density, molar mass, compressibility, etc.) to be considered.VolumeFlow
would require Pressure
and Temperature
references, I suspect a wrapper for VolumeFlow
would be needed, similar to ReferencePressure
in #726.
VolumeFlow
.IncompressibleVolumeFlow
and CompressibleVolumeFlow
to inherit VolumeFlow
instead?Because temperature, pressure, and fluid are held constant, a Standard, or Normal flow rate can be treated as MassFlow
.
If I understand correctly, then an extension to MassFlow
would be the most appropriate way to handle conversion from StandardFlow
?
In my line of work:
PoundPerHour
to SCFH (air), and from SCFH (air) to SCFH (natural gas). VolumeFlow = Area * Velocity
, but I can see other use cases for that arithmetic.As I continue in detail on this topic, I start to wonder the same question of scope you mentioned previously in #713. Considering at the highest level though, VolumeFlow
really just breaks down into compressible and incompressible flow, I can also see it being general enough to be within scope of the UnitsNet library.
My head is exploding a bit on level of detail here :D It's great, just a lot to take in. We have some use cases now, but we still need to reduce this into some concrete changes to Units.NET.
would it be worth creating a layer of abstraction for something like IncompressibleVolumeFlow and CompressibleVolumeFlow to inherit VolumeFlow instead?
With struct
quantity types we can unfortunately not inherit.
These sound to me like wrapper types that add extra information about the volume flows and you can add methods to the wrappers to convert between compressible and incompressible, which would change the internal VolumeFlow
value accordingly.
Let's try to get a bit more concrete. I find that easier to discuss. Help me refine and formulate this.
Since a standard flow needs additional information beyond VolumeFlow
such as pressure and temperature, we can't reuse code generator and need to write manual wrapper types.
StandardFlow
and NormalFlow
wrapper types to represent volumetric flows at certain temperatures and pressure. Questions:
enum
value? IUPAC, ISO2533, DIN1342 is also mentioned.See code example below.
Do we need to support parsing strings like "1 SCF/HR"
or "Sm³/hr"
?
If so, what are all the units and abbreviations we would like to parse?
Some examples here: https://www.traditionaloven.com/conversions_of_measures/gas_flows_converter_tool.html
Without code generator, we need to manually add support for parsing these.
As for ToString(), we could do it easy and add suffix "(standard)" or "(standard ISO)" behind the VolumeFlow.ToString()
, to get something like 1 m³/h (standard ISO)
. It's better than nothing, but maybe not what you want.
If we add Parse() support, then it should be able to parse whatever ToString() outputs.
A single wrapper type to hold information for Standard/Normal flow rates, with methods to convert to/from MassFlow and VolumeFlow.
// Not sure about this name, trying find something that represents a flow at a certain condition, maybe FlowWithReferenceCondition or something
public abstract class ReferenceFlow
{
public ReferenceFlow(MassFlow mf, Pressure p, Temperature t, ReferenceFlowCondition c)
{
/* assign properties here */
}
public VolumeFlow VolumeFlow { get; }
public Pressure BasePressure { get; }
public Temperature BaseTemperature { get; }
// Arguably we don't need this if we use inheritance and separate types for each condition and can match on type instead, but I like having both
public ReferenceFlowCondition ReferenceFlowCondition { get; }
public MassFlow ToMassFlow() {}
public VolumeFlow ToVolumeFlow() {}
}
public enum ReferenceFlowCondition
{
StandardUS,
StandardISO,
StandardAGA,
Normal,
Custom
}
// Each standard as its own type, inheriting from base type
public class StandardFlowUS : ReferenceFlow
{
public StandardFlowUS(/*args*/) : this(/*pass on args to ReferenceFlow ctor*/) {}
}
public class StandardFlowISO : ReferenceFlow
{
public StandardFlowISO(/*args*/) : this(/*pass on args to ReferenceFlow ctor*/) {}
}
public class StandardFlowAGA : ReferenceFlow
{
public StandardFlowAGA(/*args*/) : this(/*pass on args to ReferenceFlow ctor*/) {}
}
public class NormalFlow : ReferenceFlow
{
public NormalFlow(/*args*/) : this(/*pass on args to ReferenceFlow ctor*/) {}
}
// The idea is to support custom conditions beyond the standard ones, I don't know if this will ever be useful though?
public class CustomReferenceFlow : ReferenceFlow
{
public CustomReferenceFlow(/*args*/) : this(/*pass on args to ReferenceFlow ctor*/) {}
}
// MassFlow.extra.cs
public static StandardFlowUS ToStandardFlowUS(this MassFlow mf, /* other args */) {}
public static StandardFlowISO ToStandardFlowISO(this MassFlow mf, /* other args */) {}
public static StandardFlowAGA ToStandardFlowAGA(this MassFlow mf, /* other args */) {}
public static NormalFlow ToNormalFlow(this MassFlow mf, /* other args */) {}
// VolumeFlow.extra.cs
public static StandardFlowUS ToStandardFlowUS(this VolumeFlow vf, /* other args */) {}
public static StandardFlowISO ToStandardFlowISO(this VolumeFlow vf, /* other args */) {}
public static StandardFlowAGA ToStandardFlowAGA(this VolumeFlow vf, /* other args */) {}
public static NormalFlow ToNormalFlow(this VolumeFlow vf, /* other args */) {}
Let me know what you think of this direction.
Adding @bitbonk, @lipchev, @MarcDrexler to the discussion, since they have been involved in previous related issues.
This has been a long stream of consciousness...
I keep forgetting about the struct
vs class
debate.
I now think its best to describe standard/normal flows as VolumeFlow
types, but can be converted to MassFlow
types when accounting for fluid density (through Standard Temperature and Pressure).
While thinking through this some more, I realize we can simplify this to two separate problems:
MassFlow
to VolumeFlow
(nor can we convert volume flows of different conditions).UnitSystem
type to define BaseUnits
, we do not have a type to define Standard Temperature and Pressure.Standard Temperature and Pressure
StandardFlow
and NormalFlow
, along with US, ISO, AGA, etc., are still the same type, but with different values for Pressure
, Temperature
and Relative Humidity.
What if we created a EnvironmentalReference
type, which stored the Pressure
, Temperature
and Relative Humidity for a given measurement? We could create a Dictionary
of EnvironmentalReferences
to store the values of known standard/normal conditions (US, ISO, AGA, etc.) to reference for use. At that, I suppose we could still rely on an enum
value to index the Dictionary
.
Note: I realized when thinking about STP, that the development of ReferencePressure
currently assigns DefaultAtmosphericPressure
as 1 atm
. There is an overload to the ReferencePressure
constructor to accept a different atmospheric Pressure
, but it may be worth referencing an EnvironmentalReference.Pressure
value instead.
A couple conversion thoughts:
VolumeFlow
Conversions:
VolumeFlow
units are the same, so for example, SCFH to SCFM uses the same conversion constant as CFH to CFM. Can we wrap VolumeFlow
in a way that allows us to reuse the existing code generator conversions?VolumeFlow
to MassFlow
, gas flow requires some information about the physical properties of the gas flowed. Should we include direct conversions of VolumeFlow
between gases? MassFlow
Conversions:
MassFlow
. We could rely on the basic conversion of MassFlow
as a fuction of VolumeFlow
and Density
, which is universal to compressible and incompressible flow.
Pressure
, Temperature
, and the individual gas constant, R for the particular gas.
MolarMass
or Specific Gravity (unitless).Pressure
and Temperature
) is also required.Parsing and ToString()
You make a good point of supporting strings. While I haven't seen 1 SCF/HR
before (typ. SCFH), I have seen Sm3/hr
and Nm3/hr
before.
As we can see above, with Nm3/hr
, it could easily be mistaken for Newtons
rather than Normal
. Especially when you consider the number of standards to define EnvironmentalReference
, I would think a prefix/suffix sort of idea could be acceptable to maintain clarity. Perhaps in cases where the flow condition is not at a particular standard, the same suffix could instead specify the flow conditions in detail to get something like 1 m3/h (at 300 K, and 1 atm)
.
I would suggest additionally including the fluid in the suffix when deviating from the standard specification, such as 1 m3/h (dry nitrogen, standard ISO)
, or to the same degree, 1 m3/h (air, at 300 K, 1 atm, and 20% R.H.)
.
For example
public enum StandardReference
{
StandardUS,
StandardISO,
StandardAGA,
Normal,
Custom
}
public struct EnvironmentalReference
{
public EnvironmentalReference(Pressure p, Temperature t, double RelativeHumidity)
{
/*Assign properties here*/
}
public static Dictionary<StandardReference, EnvironmentalReference> StandardReferences { get; } = new Dictionary<StandardReference, EnvironmentalReference>()
{
{StandardReference.StandardUS, new EnvironmentalReference(Pressure.FromPoundsForcePerSquareInch(14.696), Temperature.FromDegreesFahrenheit(60), 0)},
/*Define subsequent StandardReferences here*/
};
{
public struct ReferenceVolumeFlow
{
public ReferenceVolumeFlow(VolumeFlow q, EnvironmentalReference e, MolarMass m)
{
/*Assign properties here*/
/*Calculate density here*/
}
public ReferenceVolumeFlow(VolumeFlow q, Pressure p, Temperature t, MolarMass m)
{
/*Assign properties here*/
/*Calculate density here*/
}
public ReferenceVolumeFlow(VolumeFlow q, Pressure p, Temperature t, MolarMass m, CompressibilityFactor z)
{
/*Assign properties here*/
/*Calculate density here*/
}
public ReferenceVolumeFlow(VolumeFlow q, Density d)
{
/*Assign density here*/
}
public ReferenceVolumeFlow ToCondition (Pressure p, Temperature t)
{
/*pass on args to ReferenceVolumeFlow ctor*/
}
public ReferenceVolumeFlow ToCondition (EnvironmentalReference e)
{
/*pass on args to ReferenceVolumeFlow ctor*/
}
public ReferenceVolumeFlow ToFluid (MolarMass m, CompressibilityFactor z)
{
/*pass on args to ReferenceVolumeFlow ctor*/
}
public MassFlow ToMassFlow (MassFlowUnit u)
{
/*Calculate from properties*/
/*Pass on args to MassFlow ctor*/
}
/*Other overloads*/
{
//MassFlow.Extra.cs
public static ReferenceVolumeFlow ToReferenceVolumeFlow(this MassFlow mf, /* other args */) {}
//VolumeFlow.Extra.cs
public static ReferenceVolumeFlow ToReferenceVolumeFlow(this VolumeFlow vf, /* other args */) {}
Can we wrap VolumeFlow in a way that allows us to reuse the existing code generator conversions?
Not easy as of today, to my knowledge. I don't want to resort to manually keeping any hard coded delegate methods in the wrapper type in sync whenever conversion methods change in MassFlow
or VolumeFlow
. We could maybe extend the code generator to support this use case.
To convert VolumeFlow to MassFlow, gas flow requires some information about the physical properties of the gas flowed. Should we include direct conversions of VolumeFlow between gases?
This would be trivial to add with some extension methods that take the relevant arguments to convert between those two, as detailed earlier. To distinguish between ideal and real gases, I'd suggest including that in the name of the conversion methods, maybe like this ToMassFlowForIdealGas(this VolumeFlow idealGasFlow, /*other args*/)
.
As we can see above, with Nm3/hr, it could easily be mistaken for Newtons rather than Normal.
Yes, but parsing is mutually exclusive to each quantity type, so for VolumeFlow.Parse("1 Nm3/hr")
it should work as long as this abbreviation is unique and unambiguous for all volume flow units. It will not conflict with Force.Parse()
and other quantities.
specially when you consider the number of standards to define EnvironmentalReference, I would think a prefix/suffix sort of idea could be acceptable to maintain clarity.
Good, that is easier to do.
Perhaps in cases where the flow condition is not at a particular standard, the same suffix could instead specify the flow conditions in detail to get something like 1 m3/h (at 300 K, and 1 atm).
Good suggestion, I like it.
I would suggest additionally including the fluid in the suffix when deviating from the standard specification, such as 1 m3/h (dry nitrogen, standard ISO), or to the same degree, 1 m3/h (air, at 300 K, 1 atm, and 20% R.H.).
OK, if that makes sense to your domain and usage, then sure. It would require specifying this when constructing the wrapper quantity type.
Great example, some comments:
dry nitrogen
StandardReference
enum it was constructed with, neither as part of EnvironmentalReference
where maybe it belongsJust as a side note: While it is nice to have conversions built in between the different flow quantities, for us it would be enough just to have the standard flow quantity (including the unit sccm, displayable as "sccm"). We will hardly ever have the need for such conversions.
I suspect that if the conversions between the different flow quantities is not built in, it would still be possible to do it manually, right?
@bitbonk, Does that mean the only functionality you need is around parsing and ToString(), including shorthand strings like SCCM? The only problem I foresee with shorthand strings like SCCM, SCFM, SCFH, etc. is the ambiguity of STP for example, SCFM wiki. I haven't yet figured out a good way to avoid the ambiguity.
@angularsen
To distinguish between ideal and real gases, I'd suggest including that in the name of the conversion methods, maybe like this
ToMassFlowForIdealGas(this VolumeFlow idealGasFlow, /*other args*/)
.
- ReferenceVolumeFlow does not currently
- hold any information about whether it represents an ideal gas flow, real gas flow or liquid flow. Should that be included?
I like the suggestion for distinguishing between ideal and real conversion methods. I'm not sure if we would need to store information on ideal, real or liquid, since the relevant conversions would be to or from MassFlow
, and would be identified by the conversion method. What do you think?
- hold information about the fluid, like
dry nitrogen
That is a good point. I referenced it when discussing string parsing, and jumped to its properties, like MolarMass
or CompressibilityFactor
, but didn't connect the two. Would it be worth creating a struct
, enum
, and Dictionary
like EnvironmentalReference
, where common fluids are stored in the Dictionary
, but the struct
allows for creating unique fluids?
More nitpicky on my end, but it slipped my mind that compressibility factor is actually a calculated value from gas Density
(at a given Temperature
and ReferencePressure
) and MolarMass
, so the stored physical properties would not need to include compressibility factor.
For example
public enum CommonFluid
{
DryNitrogen,
NaturalGas,
LiquidPropane,
DryAir,
Water
}
public struct Fluid
{
public Fluid(string Name, MolarMass m, Density d, ReferencePressure p, Temperature t, /*Any other relevant physical properties*/ )
{
/*Assign properties here*/
}
public static Dictionary<CommonFluid, Fluid> CommonFluid{ get; } = new Dictionary<CommonFluid, Fluid>()
{
{CommonFluid.DryNitrogen, new Fluid("Dry Nitrogen, MolarMass.FromGramPerMole(28.013), Density.FromGramPerLiter(1.126), new ReferencePressure(Pressure.FromPoundsForcePerSquareInch(14.696)), Temperature.FromDegreesFahrenheit(60))} ,
/*Define subsequent CommonFluids here*/
};
{
- hold information about StandardReference enum it was constructed with, neither as part of EnvironmentalReference where maybe it belongs
I agree this one needs to be addressed. I would think EnvironmentalReference
would be similar to UnitSystem
, with the STP conditions being similar to BaseUnits
, and the Dictionary
of StandardReference
would be similar to UnitSystem.SI
if it stored a collection of UnitSystem
instead of only SI.
I think storage at that level deals with the issue I recognized in my last comment about ReferencePressure
as well.
Note: I realized when thinking about STP, that the development of
ReferencePressure
currently assignsDefaultAtmosphericPressure
as1 atm
. There is an overload to theReferencePressure
constructor to accept a different atmosphericPressure
, but it may be worth referencing anEnvironmentalReference.Pressure
value instead.
Would you be able to clarify the storage of those objects, and share your thoughts on this idea?
I think I've mentioned this before, but I still wonder if the notion of a "reference scale" is not something that's fundamentally missing from our quantities. I guess you could put all of the conversion code in the *.extra.cs if you had access to the reference scale as part of the quantity. I mean we already have some implicit one dimensional scale: f(x)->x for [double.MinValue. double.MaxValue]. Give it some type/struct, a decent name and put as the default value for the construction of normal quantities- all arithmetic operations operating on the same scale are preserved- operations on different scales are either not supported or use some special mapping function from one scale to another. Custom scale classes are created per quantity, are strongly typed and hold whatever necessary information. We add a property to the json schema that associates the quantity with the special type- and the generator uses it to create a field of the class type. So, what other quantities could benefit from such an addition? Temperature, Pressure, Density? Also note the reduction of dimensions for the VolumeFlowReferenceScale with the possible addition of a DensityReferenceScale (to the Density unit).
Hmm, here's one final thought(possibly out of scope): you know how we loose precision with units that are far away from the base unit- what if some basic quantity, like Length had multiple possible scales- e.g. Macro, Normal, Micro- and the scales corresponded to some mapping function like f(x)->10e6 * x that is applied on the conversion coefficient- then adding for instance two very small lengths with different units, but in the same scale should preserve some of the precision of the result.
@sequc82
Does that mean the only functionality you need is around parsing and ToString(), including shorthand strings like SCCM?
We would like to have the standard flow quantity that has parsing and ToString() for SCCM and that can convert between different units within that quantity (eg. from SCCM to SCM). Conversion to other quantitites is nice to have but not required. So I would say the answer to your question is yes.
This is great! 😀 People who know the domain and will be using this stuff needs to move this design forward. I can assist in how to best fit it into UnitsNet code base and its overall architecture, but you guys need to figure out how it best solves your needs.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
I created a PR for the simple version without complex conversions proposed by @bitbonk.
First-time contributor here!
It looks like this feature idea has been addressed to some degree already in #212 and #713.
It is already clear that standard and normal flow rates are missing in UnitsNet. Units such as SCCM (standard cubic centimeters/min), SCFM (standard cubic feet/min), SCFH (standard cubic feet/hour), Nm3/h (normal cubic meters/hour), etc. are best described as the type MassFlowUnit rather than VolumeFlowUnit. This is due to the gas flow being constrained to Normal or Standard pressure and temperature conditions as previously mentioned in #212. As mentioned in #713, these units are typically used with mass flow meters and controllers.
Conversions from standard and normal flow rates to more conventional mass flow rates are then just constant conversions like any other with the exception that the specific gravity of the gas must also be accounted for.
For example, lb/h gas to SCFH gas: SCFH gas = lb/h*13.1/S.G. S.G.: Gas Specific Gravity (unitless)
Converting from mass flow in SCFH to volume flow in ACFH (actual cubic feet per hour) of course is more complex as has been discovered in #212 and #713.
How can I help?
Some references: Fisher Control Valve Handbook Chapter 15: Conversions and Equivalents Wikipedia: SCFH The Engineering Toolbox: Gas Specific Gravity