-
Notifications
You must be signed in to change notification settings - Fork 393
Calculation of some Units #515
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
Comments
Hi, this calculation is not yet defined. I'm not very familiar with If, however, there are other quantities of the same dimensions, then the better way would be to construct it something like this: What would work better, do you think? |
I think the operator is the way to go. Even if there were other types with the same dimensions, you'd just have to specify the left hand side to resolve the ambiguity (could not use var). |
I misspoke: we need to use out parameters to distinguish multiply/divide operations rather than overloads, because the return type can not be the only difference. For example, using the BaseDimensions to calculate the operations, Length gives the following: public partial struct Length
{
public void Multiply( Acceleration right, out SpecificEnergy result ) { result = SpecificEnergy.Zero; }
public void Multiply( Area right, out Volume result ) { result = Volume.Zero; }
public void Multiply( AreaDensity right, out LinearDensity result ) { result = LinearDensity.Zero; }
public void Divide( AreaDensity right, out SpecificVolume result ) { result = SpecificVolume.Zero; }
public void Multiply( Density right, out AreaDensity result ) { result = AreaDensity.Zero; }
public void Divide( Duration right, out Speed result ) { result = Speed.Zero; }
public void Multiply( DynamicViscosity right, out MassFlow result ) { result = MassFlow.Zero; }
public void Divide( ElectricAdmittance right, out ElectricResistivity result ) { result = ElectricResistivity.Zero; }
public void Divide( ElectricConductance right, out ElectricResistivity result ) { result = ElectricResistivity.Zero; }
public void Multiply( ElectricConductivity right, out ElectricAdmittance result ) { result = ElectricAdmittance.Zero; }
public void Multiply( ElectricConductivity right, out ElectricConductance result ) { result = ElectricConductance.Zero; }
public void Multiply( ElectricCurrentDensity right, out Magnetization result ) { result = Magnetization.Zero; }
public void Multiply( ElectricField right, out ElectricPotential result ) { result = ElectricPotential.Zero; }
public void Multiply( ElectricResistance right, out ElectricResistivity result ) { result = ElectricResistivity.Zero; }
public void Divide( ElectricResistivity right, out ElectricAdmittance result ) { result = ElectricAdmittance.Zero; }
public void Divide( ElectricResistivity right, out ElectricConductance result ) { result = ElectricConductance.Zero; }
public void Multiply( Force right, out ApparentEnergy result ) { result = ApparentEnergy.Zero; }
public void Multiply( Force right, out Energy result ) { result = Energy.Zero; }
public void Multiply( Force right, out RotationalStiffness result ) { result = RotationalStiffness.Zero; }
public void Multiply( Force right, out Torque result ) { result = Torque.Zero; }
public void Multiply( ForceChangeRate right, out ApparentPower result ) { result = ApparentPower.Zero; }
public void Multiply( ForceChangeRate right, out Power result ) { result = Power.Zero; }
public void Multiply( ForceChangeRate right, out ReactivePower result ) { result = ReactivePower.Zero; }
public void Multiply( ForcePerLength right, out Force result ) { result = Force.Zero; }
public void Multiply( ForcePerLength right, out RotationalStiffnessPerLength result ) { result = RotationalStiffnessPerLength.Zero; }
public void Multiply( Frequency right, out Speed result ) { result = Speed.Zero; }
public void Multiply( HeatFlux right, out ForceChangeRate result ) { result = ForceChangeRate.Zero; }
public void Multiply( HeatTransferCoefficient right, out ThermalConductivity result ) { result = ThermalConductivity.Zero; }
public void Multiply( Irradiance right, out ForceChangeRate result ) { result = ForceChangeRate.Zero; }
public void Multiply( Irradiation right, out Force result ) { result = Force.Zero; }
public void Multiply( Irradiation right, out RotationalStiffnessPerLength result ) { result = RotationalStiffnessPerLength.Zero; }
public void Multiply( KinematicViscosity right, out VolumeFlow result ) { result = VolumeFlow.Zero; }
public void Multiply( LapseRate right, out Temperature result ) { result = Temperature.Zero; }
public void Multiply( Length right, out Area result ) { result = Area.Zero; }
public void Multiply( LinearDensity right, out Mass result ) { result = Mass.Zero; }
public void Multiply( Magnetization right, out ElectricCurrent result ) { result = ElectricCurrent.Zero; }
public void Multiply( MassFlux right, out DynamicViscosity result ) { result = DynamicViscosity.Zero; }
public void Multiply( Permeability right, out ElectricInductance result ) { result = ElectricInductance.Zero; }
public void Multiply( Permittivity right, out Capacitance result ) { result = Capacitance.Zero; }
public void Multiply( PowerDensity right, out HeatFlux result ) { result = HeatFlux.Zero; }
public void Multiply( PowerDensity right, out Irradiance result ) { result = Irradiance.Zero; }
public void Multiply( Pressure right, out ForcePerLength result ) { result = ForcePerLength.Zero; }
public void Multiply( Pressure right, out Irradiation result ) { result = Irradiation.Zero; }
public void Multiply( PressureChangeRate right, out HeatFlux result ) { result = HeatFlux.Zero; }
public void Multiply( PressureChangeRate right, out Irradiance result ) { result = Irradiance.Zero; }
public void Multiply( RotationalAcceleration right, out Acceleration result ) { result = Acceleration.Zero; }
public void Multiply( RotationalSpeed right, out Speed result ) { result = Speed.Zero; }
public void Multiply( RotationalStiffnessPerLength right, out ApparentEnergy result ) { result = ApparentEnergy.Zero; }
public void Multiply( RotationalStiffnessPerLength right, out Energy result ) { result = Energy.Zero; }
public void Multiply( RotationalStiffnessPerLength right, out RotationalStiffness result ) { result = RotationalStiffness.Zero; }
public void Multiply( RotationalStiffnessPerLength right, out Torque result ) { result = Torque.Zero; }
public void Divide( SpecificVolume right, out AreaDensity result ) { result = AreaDensity.Zero; }
public void Multiply( SpecificWeight right, out Pressure result ) { result = Pressure.Zero; }
public void Multiply( Speed right, out KinematicViscosity result ) { result = KinematicViscosity.Zero; }
public void Divide( Speed right, out Duration result ) { result = Duration.Zero; }
public void Divide( ThermalConductivity right, out ThermalResistance result ) { result = ThermalResistance.Zero; }
public void Divide( ThermalResistance right, out ThermalConductivity result ) { result = ThermalConductivity.Zero; }
public void Multiply( Volume right, out AreaMomentOfInertia result ) { result = AreaMomentOfInertia.Zero; }
} Since there's only one Multiply that takes a Length parameter, you can use var since Area is the only valid option: var length = Length.FromMeters( 1.0 );
var length2 = Length.FromMeters( 2.0 );
length.Multiply( length2, out var area );
Console.WriteLine( area.SquareCentimeters ); However for Multiply( Force ) it could be ApparentEnergy, Energy, RotationalStiffness, or Torque. In this case you'll need to specify the out variable type to distinguish: var length = Length.FromMeters( 1.0 );
var force = Force.FromNewtons( 1.0 );
length.Multiply( force, out Torque torque );
Console.WriteLine( torque.NewtonMeters ); Does this seem acceptable? |
I have a feeling the First, there is different behavior/possibilities when using Second, having a Multiply method that specifies different results using different I think On second thought, how about we add a // Instead of this
public void Multiply( Force right, out ApparentEnergy result ) { result = ApparentEnergy.Zero; }
public void Multiply( Force right, out Energy result ) { result = Energy.Zero; }
public void Multiply( Force right, out RotationalStiffness result ) { result = RotationalStiffness.Zero; }
public void Multiply( Force right, out Torque result ) { result = Torque.Zero; }
// How about this?
public static class Test
{
public static void TestIt()
{
AreaMomentOfInertia ami = Area.FromSquareMeters(4) * Length.FromMeters(3) * Length.FromMeters(4);
}
}
public struct Length4
{
public double TesseractMeters { get; }
public Length4(double tesseractMeters)
{
TesseractMeters = tesseractMeters;
}
}
public partial struct Volume
{
public static Length4 operator *(Volume volume, Length length)
{
return new Length4(volume.CubicMeters * length.Meters);
}
}
public partial struct AreaMomentOfInertia
{
public static implicit operator AreaMomentOfInertia(Length4 length4)
{
return AreaMomentOfInertia.FromMetersToTheFourth(length4.TesseractMeters);
}
} I guess the same approach could really be used for all our quantities, now that we have the base dimensions mapped out. This way, arithmetic given similar input quantity types could still result in different output quantity types. Length meter = Length.FromMeters(1);
Force newton = Force.FromNewtons(1);
ApparentEnergy ae = meter * newton;
Energy e = meter * newton;
RotationalStiffness rs = meter * newton;
Torque t = meter * newton; What do you guys think? |
I don't like the multiply either. But there isn't any obvious solutions. I think your example would fall apart for derived units but I haven't tried. |
I'm not sure if you need to be able to know the desired result of the output inside the multiplication. However, operator overloads for interfaces are (not yet) supported by the language. 😭 But if they would, I'd provide the unit combinations through generics to keep it somewhat static typed. But then add implicit casts ( But untill the operator overloads are available, i think we'd be stuck with We'd have to find a way to generate those smartly though. IUnit<IMass, IInverse> should be automatically become a plain scalar etc. An alternative would be just IUnit (like mathjs) without generics, would simplify it a lot, but we'd loose the compiler checking. And after a given amount of levels we'd have to fall back to dynamic types anyway since the combinations would just explode. |
I have also wanted this for a long time and In my mind if UnitsNet can do math with any kind of temporary quantities it would be a huge milestone to reach! I dont have a solution but I would love to brainstorm to reach one! Ex This math is fine but we Inside the equation get a temporary unit what if we did something like this in the Temperature class:
This will return the unit
This will return My got-feeling is telling me that this can be done |
Just brainstorming with myself: Define a unit for TemporaryUnit. //Temperature class: //This covers all cases of unknown temporary Units
}
|
You don't need a temporary unit. We can multiply or divide the base dimensions to find out what the resultant quantity is (or quantities in some cases). When #576 is merged, you could look up what that quantity is from the BaseDimensions. Otherwise throw an exception. And you can always return IQuantity. Then you just have to figure out what to do with it. Strong typing would be nice, but not sure we can for the reasons above (more than one resultant unit). |
Thats where the genrics could come in i'd guess, just not sure how feasible that is yet with the current state of c#. Would need to do some experimenting first. |
I'd be very interested to see what you can come up with in some proof of concept PR. With current C# and possibly with a future C#. I think you can even build your own compiler and pull in experimental features from Roslyn pull requests, and use it with a nuget or something. Haven't tried it, but sounds awesome. |
@tmilnthorp Lets say you try to do: First it will go into: And it will go into it again: Then two times into into Where it will finally return a @angularsen |
@Mobz87 If I understand your suggestion right, and I only skimmed it once, you are not really getting static type safety.
I think this is the crux in your example. How can you with type safety ensure that this is correct? You have lost the information you/the compiler needs to figure this out. I don't see the benefit here over simply this: // Example: if we add Multiply(), Divide() and ToQuantity<T> to IQuantity
IQuantity t2 = Temperature.FromDegreesCelsius(20);
IQuantity t3 = Temperature.FromDegreesCelsius(20);
IQuantity t4 = Temperature.FromDegreesCelsius(20);
IQuantity t1 = t2.Multiply(t3).Divide(t4); // 20 Celsius
double t1Celsius = t1.As(TemperatureUnit.DegreeCelsius); // 20, throws if not compatible
Temperature t1_ = t1.ToQuantity<Temperature>(); // 20 Celsius, throws if not compatible Edit: |
Personally I would love to be able to use the operators instead of Multiply() and Divide(). They make equations harder and less 'math like' to read. In my example there is no type safety for all unknown units - with logic and typecast we will insure no errors. |
Updated to C# vNext: IQuantity t2 = Temperature.FromDegreesCelsius(20);
IQuantity t3 = Temperature.FromDegreesCelsius(20);
IQuantity t4 = Temperature.FromDegreesCelsius(20);
IQuantity t1 = t2 * t3 / t4; // 20 Celsius
double t1Celsius = t1.As(TemperatureUnit.DegreeCelsius); // 20, throws if not compatible
Temperature t1_ = t1.ToQuantity<Temperature>(); // 20 Celsius, throws if not compatible Updated to what I hope to achieve: IQuantity<Temperature> t2 = Temperature.FromDegreesCelsius(20);
IQuantity<Temperature> t3 = Temperature.FromDegreesCelsius(20);
IQuantity<Temperature> t4 = Temperature.FromDegreesCelsius(20);
IQuantity<Temperature, Temperature> t23 = t2 * t3;
IQuantity<Temperature> t1 = t23 / t4; // 20 Celsius
double t1Celsius = t1.As(TemperatureUnit.DegreeCelsius); // 20, no exceptions required
Temperature t1_ = t1; // 20 Celsius, implicit cast, no exceptions required Ofc, you can then replace all the var t2 = Temperature.FromDegreesCelsius(20);
var t3 = Temperature.FromDegreesCelsius(20);
var t4 = Temperature.FromDegreesCelsius(20);
var t23 = t2 * t3;
var t1 = t23 / t4; // 20 Celsius
double t1Celsius = t1.As(TemperatureUnit.DegreeCelsius); // 20, no exceptions required
Temperature t1_ = t1; // 20 Celsius, implicit cast, no exceptions required So far, this is only wishfull thinking, and is entirely up to the C# language team to provide us with the operator overloads for interfaces. But I'm afraid the amount of method combinations this would yield would be impossible to handle. Just a quick example to try: Coulomb's Law.
Would result in an intermediate unit of Now imagine all the methods that need to be generated to support reducing that back into For now I would indeed settle with just |
Today we can do this: Length length1 = Length.FromInches( 2.0 );
Length length2 = Length.FromMeters( 2.0 );
double multipliedValue = length1.Value * length2.As( length1.Unit );
BaseDimensions multipliedBaseDimensions = length1.Dimensions * length2.Dimensions;
QuantityInfo multipliedQuantityInfo = Quantity.Infos.Where( info => info.BaseDimensions == multipliedBaseDimensions ).First(); Here multipliedValue is 157.48031496062993. This is in inches (square inches really). And indeed multipliedQuantityInfo is a UnitsNet.QuantityInfo<UnitsNet.Units.AreaUnit> The remaining piece needed is the BaseUnits for each unit. This is also necessary for #547. In this case, we need to add something per-unit as follows for this example: "SingularName": "Inch",
"PluralName": "Inches",
"BaseUnits": {
"L": "Inch"
}, Then we can get the base units for the left-hand side of inches, and combine it with the BaseDimensions. Area would look as follows: "SingularName": "SquareInch",
"PluralName": "SquareInches",
"BaseUnits": {
"L": "Inch"
}, We would use multipliedBaseDimensions as it exists today, plus the BaseUnits on each unit to find we need Area in SquareInches. |
Thanks for the detailed thought experiment @SGStino , it helped me grasp the concept and the challenges.
I agree, for now I don't see a practical way to achieve type safety of quantity calculations. It sure is enticing and I'd love to get there, but I think C# would need something like templating or a more powerful type inference system to make it happen. Just FYI, but F# has its own native support for measures that I've heard works pretty well for these things. I have not tried it myself, but here are some examples: F# runs on .NET too, so it could possibly be a way to achieve what we describe here. Not sure what the limitations are, but I would be very interested to hear about it if anyone knows or finds out. |
What kind of sorcery is this! Attributes inside generics ... Or more accuratly, what rock have I been living under? But this is a big turn-off for what I am concerned at the moment:
Although I'm not entirely sure how correct that statement is given that it uses attributes, and that attributes are available at runtime. Perhaps no support in the library, but not that difficult. And they treat all units as separate entities, no relation between feet and meters. Conversion would require a constant in the unit meter/feet. Not that elegant 😄 Meanwhile, I'm also going to dig a little into when I find some time: And then extra libraries for the SI/non-SI implementations. |
Cool, thanks for reporting back on F#. |
Closing this due to inactivity. |
Hi! I am trying to calculate area of inertia using area ang Length like this:
but the error "operator * cannot be applied to operands fo type Volume and Length" happens.
Is there any way to do such calculation?
The text was updated successfully, but these errors were encountered: