mp-units icon indicating copy to clipboard operation
mp-units copied to clipboard

cast from unknown dimension to known dimension when mixing systems

Open mikeford3 opened this issue 4 years ago • 11 comments

When new units are created from multiplying or dividing when using several systems unknown_dimension are the result. It would be an enhancement if the library could recognise when there is a dimension defined for the unknown_dimension and use this instead, or if quantity_cast could be used to enforce this. For example: si::length<si::metre> * fps::length<fps::inch> should give si::area godbolt example

mikeford3 avatar Jun 27 '20 12:06 mikeford3

I do not think it is a good idea to choose the system of the first argument. From maths, we know that a * b should give the same result as b * a. It would not be the case here...

However, it should work with the explicit cast:

auto area_si_1 = units::quantity_cast<si::dim_area>(area_unk0);
auto area_si_2 = units::quantity_cast<si::area<si::square_metre>>(area_unk0);

It seems that we have a bug as it does not work with the above now.

mpusz avatar Jun 27 '20 17:06 mpusz

//####################################
  auto area_unk0 = len_fps * len_si; // ERROR
//#####################################

I believe the above should fail to compile (maybe a static_assert

 static_assert(get_measurement_system(qa) == get_measurement_system(qb),
   "Only binary operations on quantities in the same measurement system area allowed"
 );

Only binary operations on quantities in the same measurement system should be allowed, because base_units and hence what that number in the rep actually means are unique to a measurement system.

The following should work ( as @mpusz has said I think )

  auto area_si = explicit_cast<si_system>(len_fps) * len_si; // ok
  auto area_fps = len_fps * explicit_cast<fps_system>(len_si) // ok

The implementaion of that would require some heavy lifting, based on a set of conversion factors being provided that convert base_units from one system to another something like :

  quantity<target_system> explicit_cast(quantity<source_system> q)
  {
     multiplier = q.conversion_factor; 
     for each base_quantity in dimension of q{
         if same base_quantity is not in target_system  then FAIL ( e.g no electric_current base_quantity in fps) 
         }else{
          multiplier * = get_conversion_factor<base_quantity,target_system,source_system>() 
              * get_exp of base_quantity in q ;
         }
     }
     return q<target_system>(q.count() * multiplier);
  }

Alternatively, and maybe better you could select one system as a central point to convert in and out of, so each other source system only needs to convert in to that (the si being the obvious hub). The target system then converts out from that hub system

kwikius avatar Jun 27 '20 21:06 kwikius

I am sorry to disagree here.

It is up to the user that defines a system to determine if he/she wants inter-system conversions or not. In many cases, it is really helpful to mix and reuse systems and their units. If someone will define the base units of a new system as a scaled version of base units of another system for the same dimensions and uses the same equations to define those dimensions (unlike i.e. some implementations of natural units) than the conversions should be allowed and should work as I stated above. This is why we have equivalent_dim type trait and use it instead of is_same in many places.

For sure we do not want to introduce a dedicated type for a system. The only library that does this is Boost.Units and the author claims that was a mistake (no flexibility, interop, etc).

The following should work ( as @mpusz has said I think )

auto area_si = explicit_cast<si_system>(len_fps) * len_si; // ok
auto area_fps = len_fps * explicit_cast<fps_system>(len_si) // ok

This is not what I said ;-) My example was allowing casting an unknown derived dimension (with base dimensions and their base units from several systems) to a dimension in a concrete system of units.

If the user wants an isolated system than the base units of such systems should be standalone (not scaled versions of another system). In such a case there will be no possibility to do the inter-systems conversions.

mpusz avatar Jun 28 '20 06:06 mpusz

I am sorry to disagree here.

No problem :) The discussion here has prompted me to get back to work on my old physical quantities library. I may even steal some things from mpusz/units that I like ;-)

It is up to the user that defines a system to determine if he/she wants inter-system conversions or not.

I don't have a problem with that , if user wants it, it is just some sort of cast...

length<systemy> y = length<systemx>{};

... but that doesn't seem to be the issue . Here we have a binary operation on 2 quantities in different systems:

si::length<si::metre> * fps::length<fps::inch>

We know the dimension of the result . What we don't know is the measurement system of the result. As you said initially, there is no reason to favour lhs or rhs measurement system, so the solution is to do the cast as above, before the binary op. Simple ;-)

In many cases, it is really helpful to mix and reuse systems and their units. If someone will define the base units of a new system as a scaled version of base units of another system for the same dimensions and uses the same equations to define those dimensions (unlike i.e. some implementations of natural units) than the conversions should be allowed and should work as I stated above. This is why we have equivalent_dim type trait and use it instead of is_same in many places.

Both fps and si use the same length base_quantity , so in the above the dimension is length ^ 2 in both fps and si systems isnt it ?

For sure we do not want to introduce a dedicated type for a system.

Do you mean a type representing the measurement system here or something else ?

The only library that does this is Boost.Units and the author claims that was a mistake (no flexibility, interop, etc).

Boost.Units is simply a poor units library. Just because Boost.Units does something (else) badly, doesn't make it bad in the entire universe ;-)

The following should work ( as @mpusz has said I think )

auto area_si = explicit_cast<si_system>(len_fps) * len_si; // ok
auto area_fps = len_fps * explicit_cast<fps_system>(len_si) // ok

This is not what I said ;-) My example was allowing casting an unknown derived dimension (with base dimensions and their base units from several systems) to a dimension in a concrete system of units.

Why is the dimension unknown ? surely it is a length ^2 ?

If the user wants an isolated system than the base units of such systems should be standalone (not scaled versions of another system). In such a case there will be no possibility to do the inter-systems conversions.

If the base_quantities of a quantity are available in both systems, then you can provide conversions for each base quantity from one sytem to the other and hence convert the quantity , as I laid out above. Sure you can say that you dont want to do it, but it is not impossible

kwikius avatar Jun 28 '20 08:06 kwikius

Both fps and si use the same length base_quantity , so in the above the dimension is length ^ 2 in both fps and si systems isnt it ?

Not really. Here come the changes I introduced in 0.5.0 design:

si::dim_length -> base_dimension<"L", si::metre>
fps::dim_length -> base_dimension<"L", fps::foot>

and

https://github.com/mpusz/units/blob/d2c66e79f52fae759d749db05ec9316e0f12c919/src/include/units/physical/fps/length.h#L29-L32

As a result both systems are using different types of base dimension for length. However, both of them use the same identifier (which means the physical dimension is the same) and the base units of them are convertible between systems (fps::foot is defined as a scaled unit of si::metre). In such a case the library should allow conversions between systems.

Do you mean a type representing the measurement system here or something else ?

Yes. We should not introduce an identifier for the system.

Why is the dimension unknown ? surely it is a length ^2 ?

It is a derived_dimension<exp<si::dim_length, 1>, exp<fps::dim_length, 1>> and should be comparable and explicitly assignable to the dim_area. I just have to fix the equivalent_dim type trait which currently allows mixing base units of different systems as long as they are for different dimensions (i.e. si::dim_length and fps::dim_mass) but does not take into account dimensions with the same identifier with base units from different systems.

Thanks to the above we are always able to print and convert units correctly. The example provided by @mikeford3 will print:

as unknown_dimension:           4 [25/3 × 10⁻²] m ⋅ ft
as unknown_coherent dimension:  0.333333 m ⋅ ft

mpusz avatar Jun 28 '20 12:06 mpusz

Both fps and si use the same length base_quantity , so in the above the dimension is length ^ 2 in both fps and si systems isnt it ?

Not really. Here come the changes I introduced in 0.5.0 design:

Not really. Both fps and si use the same length base_quantity

si::dim_length -> base_dimension<"L", si::metre>
fps::dim_length -> base_dimension<"L", fps::foot>

I think this is generally agreed to be called a base unit , A combination of a base_quantity with a measurement of a physical phenomena which is defined in a system of measurement

https://github.com/kwikius/pqs/wiki/type_template-base_unit

and

https://github.com/mpusz/units/blob/d2c66e79f52fae759d749db05ec9316e0f12c919/src/include/units/physical/fps/length.h#L29-L32

As a result both systems are using different types of base dimension for length. However, both of them use the same identifier (which means the physical dimension is the same) and the base units of them are convertible between systems (fps::foot is defined as a scaled unit of si::metre). In such a case the library should allow conversions between systems.

I find this hard to parse ! Removing the dependence of a dimension on a measurement system would make life easier I think

So the fps system isn't really a system in its own right, but rather just a set of conversion factors from the si system in that case

Do you mean a type representing the measurement system here or something else ?

Yes. We should not introduce an identifier for the system.

Again, it is tricky to understand what you mean here. We need to know what system we are working in surely?

Why is the dimension unknown ? surely it is a length ^2 ?

It is a derived_dimension<exp<si::dim_length, 1>, exp<fps::dim_length, 1>> and should be comparable and explicitly assignable to the dim_area. I just have to fix the equivalent_dim type trait which currently allows mixing base units of different systems as long as they are for different dimensions (i.e. si::dim_length and fps::dim_mass) but does not take into account dimensions with the same identifier with base units from different systems.

Again I find this hard to parse .

kwikius avatar Jun 28 '20 18:06 kwikius

I think this is generally agreed to be called a base unit , A combination of a base_quantity with a measurement of a physical phenomena which is defined in a system of measurement

All of the official SI terms can be found here: https://mpusz.github.io/units/glossary.html. However, physical units library is more than "just" SI. Surely SI is the most popular one, but I got frequent requests to support others too (i.e. Walter Brown often refers to a system where length and time are measured in seconds because of c == 1). Natural Units is an another example here. If a system allows using the same unit for different quantities/dimensions making dimensions depend on units is the only way to do it (in contrary to a typical approach where units depend on dimensions).

Removing the dependence of a dimension on a measurement system would make life easier I think

I agree and this where I started too (0.4.0). However, I did not find the way to support all other use cases we support right now with such a design, so in 0.5.0 I had to reorder the dependencies.

Frankly speaking, the dependence of dimensions on units was the major issue when I tried to remove the downcasting facility (I couldn't easily define a Length concept that will work cross-systems). I will have to come back to it at some point but still, I do not have a good way to solve it.

So the fps system isn't really a system in its own right, but rather just a set of conversion factors from the si system in that case

Correct. We mentioned this some time ago when you were rising up a necessity of using system coherent units for calculations to not overflow ratio. If I am not wrong I also mentioned that probably we should put all such subsystems in the si namespace. So the FPS will end up in si::fps like the Benri library is doing. What is you opinion on this?

Of course, someone may define an FPS system as a standalone one where a foot will just be 1. It will make the ratios smaller (but still not ideal as FPS does not use exponent to create prefixes or other units). Also, someone may define a metre in terms of foot and even put it into fps::si namespace and system.

What I mean by the above is that we provide a generic tool to define custom systems. I believe that making it right is the most important task we have to do before the standardization starts. I can even imagine standardizing only the interface without the SI implementation in the first C++ version if we will not be sure that it is done right (i.e. we still have concerns about angle).

We need to know what system we are working in surely?

Well, not really. Following @mikeford3's example, if you do si::length<si::metre> * fps::length<fps::inch> what system you end up with? Or if you define a coffee/water/sugar system and decide to reuse si::time and si::length/volume in it. what is the system you are in? I believe that systems should be open (not closed). This is probably against the math but from engineering point of view, it is probably a proper way of doing it. I believe that systems should be easy to extend, it should be easy to create a new system reusing pieces of another system, or even it should be possible to mix quantities from different systems. It is up to the user to define what is his/her intent here and we provide tools that make all of those options possible.

mpusz avatar Jun 29 '20 07:06 mpusz

I do not think it is a good idea to choose the system of the first argument. From maths, we know that a * b should give the same result as b * a.

On second thoughts, yes. That would potentially involve a lot of unnecessary work to convert between systems and could still require an explicit conversion to the correct type.

Correct. We mentioned this some time ago when you were rising up a necessity of using system coherent units for calculations to not overflow ratio. If I am not wrong I also mentioned that probably we should put all such subsystems in the si namespace. So the FPS will end up in si::fps like the Benri library is doing. What is you opinion on this?

I have been wondering about the best way to handle the units of a system that are common across systems. For time cgs & fps just bring si::second etc into the system which allows the user only #include fps/time.h or cgs/time.h, without having to mix systems. But this isn't a particularly neat solution if we're going to have header files to capture si::absorbed_dose, si::angles, si::angular_velocity etc.

si::fps and si::cgs (and fps::si) seem a good idea, allowing the explicit selection of a 'master' system for common units and to convert through.

mikeford3 avatar Jun 29 '20 13:06 mikeford3

I think we are all in agreement that there should be different measurement sytems, but currently everything seems to depend on the si system.

On a simpler level, there are units that are intrinsic to a measurement system such as the si base_units and other units that are really just scaling factors from units in other systems and there is nothing wrong with having (say) ft represented as a base unit in the fps system and as a scaling factor in the si system or kilogram represented as a foreign unit in the fps system. This is good because each measurement system can deal with the unit according to its own semantics.

Rather than talk about it, I decided to try to just implement it, and maybe I should try it in mpusz/units, but, anyway I made a start on a fps system. (Also as you can see trying out a different way to specify units, based on mpusz/units way of doing it , but using runtime operators. I also grabbed the mpusz/units fixed_string for the name of the unit.):

https://github.com/kwikius/pqs/blob/master/src/include/pqs/imperial/units/length_unit.hpp#L10

Here foot as a base unit of the system , so 1 ft has a numeric value of 1. Units yard and mile are 'incoherent units' in si units terms . I think implementing other systems might be fun. Logically, if you go back to earlier systems they are simpler and things are more human in scale.

Also on a lighter note maybe would be fun to implement : https://en.wikipedia.org/wiki/List_of_humorous_units_of_measurement

kwikius avatar Jun 30 '20 03:06 kwikius

there should be different measurement sytems, but currently everything seems to depend on the si system.

Well, it is because we chose to implement it that way based on our requirements (SI is the most important one so have to be supported, @mikeford3 required interoperability of FPS and SI). However, other implementations are also possible if someone needs them.

there is nothing wrong with having (say) ft represented as a base unit in the fps system and as a scaling factor in the si system

This is exactly what we have right now.

Is there a need to implement FPS as a standalone system? I think that interoperability with SI is a benefit and according to the user's requirements rather than a problem?

mpusz avatar Jun 30 '20 09:06 mpusz

there should be different measurement sytems, but currently everything seems to depend on the si system.

Well, it is because we chose to implement it that way based on our requirements (SI is the most important one so have to be supported, @mikeford3 required interoperability of FPS and SI). However, other implementations are also possible if someone needs them.

there is nothing wrong with having (say) ft represented as a base unit in the fps system and as a scaling factor in the si system

This is exactly what we have right now.

Is there a need to implement FPS as a standalone system? I think that interoperability with SI is a benefit and according to the user's requirements rather than a problem?

I think it would be a useful feature to provide a way to allow users to customise operations on quantities.

For example if the user wishes

  • to implement a different multiply semantic. https://github.com/mpusz/units/pull/55
  • To make operations generic. for example to have compatibility with std::chronos types https://github.com/mpusz/units/pull/130
  • to force a hard error if trying to do an op on quantities in different systems

One way to do that is to customise the measurement system.

In my current scheme, The unit of the quantity has a measurement_system type tag. https://github.com/kwikius/pqs/wiki/concept-unit#requires Now (for example) operator* (Qa,Qb) calls op_multiply<Qa.measurement_system_tag, Qb.measurement_system.tag>( Qa, Qb)

Thus we can use the measurement_system tag to customise quite easily per operation or defer to some other system's operations

So goes the theory anyway. I will have to try implementing it now to see how well it works out in practise.

kwikius avatar Jun 30 '20 11:06 kwikius

No longer an issue in V2.

mpusz avatar Jun 15 '23 06:06 mpusz