Lazarus

Programming => General => Topic started by: circular on January 22, 2023, 02:02:45 pm

Title: Type checking of units of measurement
Post by: circular on January 22, 2023, 02:02:45 pm
In another topic about type system, we talked a bit about type checking of dimensioned quantities (values in meters, time in seconds etc.).
https://forum.lazarus.freepascal.org/index.php/topic,61781.0.html

Few languages handle this natively. Units of measure are handled natively in F# and it can be implemented in Rust.
Code: F#  [Select][+][-]
  1. open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames
  2. [<Measure>] type miles
  3. [<Measure>] type hour
  4. let milesPerMeter = 0.00062137<miles/meter>
  5. let secondPerHour = 3600.0<second/hour>
  6.  
  7. let distance = 1000.0<meter> * milesPerMeter
  8. let time = 60.0<second> / secondPerHour
  9. printfn "Speed: %A mph" (distance / time)

In Rust, using the crate dimensioned:
Code: Text  [Select][+][-]
  1. extern crate dimensioned as dim;
  2. use dim::si;
  3.  
  4. fn main() {
  5.     let user_distance = 10.0 * M;
  6.     let user_time = 5.0 * s;
  7.     let user_speed = user_distance / user_time;
  8.     println!("Speed is {} m/s", user_speed);
  9. }

This is checked at compile time only so there is no overhead and it can be applied to any numeric type.

I was wondering how to do that in FreePascal, playing a little bit I made a unit to have some basic support for meters, seconds and speed.

Attached is the unit to add support for units. It can be used like this:
Code: Pascal  [Select][+][-]
  1. uses UUnits;
  2.  
  3. var
  4.   distance: TLengthInKilometer;
  5.   time: TDurationInHour;
  6.   time2: TDuration; // SI unit is [s]
  7.   speed: TSpeedInKilometerPerHour;
  8.   acceleration, gravity: TAcceleration; // SI unit is [m/s2]
  9.  
  10. begin
  11.   distance := ((10*km + 100*m) * 2).ToKilometer;
  12.   time := 2*h;
  13.   speed := distance/time;
  14.  
  15.   time2 := 10*s;
  16.   acceleration := speed/time2;
  17.  
  18.  
  19.   writeln('The distance is: ', distance.ToString);
  20.   writeln('The number of hundreds of meters is: ', distance / (100*m) );
  21.   writeln('The time is: ', time.ToString);
  22.   // casting to TDuration uses the default unit Second instead of Hour
  23.   writeln('Also: ', TDuration(time).ToString);
  24.   writeln('The speed is: ', speed.ToString);
  25.   writeln('Also: ', TSpeed(speed).ToString);
  26.   writeln;
  27.  
  28.   distance := 50*km;
  29.   // ToVerboseString display the full name of the unit
  30.   writeln('Distance to go: ', distance.ToVerboseString);
  31.   time := distance / speed;
  32.   writeln('Time to get there: ', time.ToString);
  33.   writeln('Also: ', TDuration(time).ToString);
  34.   writeln;
  35.  
  36.   writeln('The time to accelerate is: ', time2.ToString);
  37.   writeln('The acceleration is: ', acceleration.ToString);
  38.   writeln('Also: ', acceleration.ToKilometerPerHourPerSecond.ToString);
  39.   writeln;
  40.  
  41.   gravity := 9.81*(m/s2);
  42.   writeln('Gravity is: ', gravity.ToVerboseString);
  43. end.

Units are checked at compile time, so that's what we wanted. There is a tiny overhead because the compiler adds a few useless assembler instructions, but that's not very significant.

It is a bit tedious though to define all possible cases for operators. Maybe one could generate the code of UUnit, though it kind of makes sense to specify each operator that one actually want to allow.
Title: Re: Type checking of units of measurement
Post by: marcov on January 22, 2023, 02:16:41 pm
There already is convutils ?
Title: Re: Type checking of units of measurement
Post by: circular on January 22, 2023, 02:22:52 pm
It is not about converting values but about type checking at compile time.

For example, with this type checking, you cannot assign a duration to a distance, etc. Also the overhead is minimal.
Title: Re: Type checking of units of measurement
Post by: marcov on January 22, 2023, 02:46:59 pm
Looks nice. But maybe the overloading gets unwieldy. Perhaps a value in the record that gives the subtype of a dimension (any length, including units like angstrom etc)

It would save many, many overload routines even for just all the length units out there.
Title: Re: Type checking of units of measurement
Post by: Bogen85 on January 22, 2023, 06:02:12 pm
It is not about converting values but about type checking at compile time.

For example, with this type checking, you cannot assign a duration to a distance, etc. Also the overhead is minimal.

This looks good, and is the kind of enforcement I was wanting.
Title: Re: Type checking of units of measurement
Post by: circular on January 22, 2023, 07:09:04 pm
This looks good, and is the kind of enforcement I was wanting.
I am glad you like it and if it can be useful for you.  :)

Looks nice. But maybe the overloading gets unwieldy. Perhaps a value in the record that gives the subtype of a dimension (any length, including units like angstrom etc)

It would save many, many overload routines even for just all the length units out there.
Yes, the overloading could get out of hand. In this example, I presume that only two units will be used but that's clearly not always the case.

Defining the base unit in the value would not be sufficient to check for type, this needs to be done at compile time.

Another approach is to always store the value in the base unit. So when you would write for example 5*km, it will be in fact computed as 5000*m. This makes things easier but always doing this multiplication can have some effect on the precision of the value and is an additional arithmetic operation. It could be a good compromise though to reduce the amount of overloading.
Title: Re: Type checking of units of measurement
Post by: Warfley on January 22, 2023, 08:29:37 pm
I personally do like using what I call wrapper types for different semantic concepts for typechecking (e.g. using a type TEmail instead string so I don't accidentally mix an email up with for example the username), but the typecasting can make it a bit tedious sometimes.

What I was wondering, why have a type for every single quantity of basically the same unit? Just talking distances, there are all the metric units from pico to kilometers, then there are the imperials, etc. All needing to be interoperable with each other.

You could just use one unit TDistance and have converters:
Code: Pascal  [Select][+][-]
  1. type
  2.   generic TUnit<const BaseUnit: string> = record
  3.   const Base = BaseUnit;
  4.   type TSelf = specialize TUnit<BaseUnit>;
  5.   public
  6.     value: Double;
  7.     class operator +(const lhs, rhs: TSelf): TSelf;
  8.   end;
  9.  
  10.   generic TConverter<TBaseUnit> = record
  11.     type TSelf = specialize TConverter<TBaseUnit>;
  12.     public Factor: Double;
  13.     class operator *(value: Double; converter: TSelf): TBaseUnit;
  14.   end;
  15.  
  16. type TDistance = specialize TUnit<'m'>;
  17. const m: specialize TConverter<TDistance> = (Factor: 1);
  18. const km: specialize TConverter<TDistance> = (Factor: 1000);
  19. const mm: specialize TConverter<TDistance> = (Factor: 0.001);
  20.  
  21. type TTime = specialize TUnit<'s'>;
  22. const s: specialize TConverter<TDistance> = (Factor: 1);
  23. const min: specialize TConverter<TDistance> = (Factor: 60);
  24. const h: specialize TConverter<TDistance> = (Factor: 3600);
  25.  
  26. implementation
  27.  
  28. class operator TUnit.+(const lhs, rhs: TSelf): TSelf;
  29. begin
  30.   result.value := lhs.value + rhs.value;
  31. end;
  32.  
  33. class operator TConverter.*(value: Double; converter: TSelf): TBaseUnit;
  34. begin
  35.   Result.value := value * converter.Factor;
  36. end;

This still needs a lot of units for combination (such as distance by time being velocity and so on), but it is much more managable
Title: Re: Type checking of units of measurement
Post by: circular on January 22, 2023, 10:27:49 pm
That's a good idea to have this generic converter record. This way you can use the same code for the * operator. Sometimes I don't know what can be done or not with generics. For example, I am surprised that one can put a string constant (BaseUnit) as generic parameter. Thanks for showing me  :)
Title: Re: Type checking of units of measurement
Post by: Warfley on January 23, 2023, 10:20:47 am
Constants as generics is a new feature thats currently only available in trunk I think, exremely useful for some situations
Title: Re: Type checking of units of measurement
Post by: circular on January 25, 2023, 11:28:20 pm
Ok, so using what you suggest to have a variety of units, not using const generic parameter as I don't use trunk, here is what I get:
Code: Pascal  [Select][+][-]
  1. uses UUnits;
  2.  
  3. var
  4.   km_h: TSpeedUnit;
  5.   km_h_s, m_s2: TAccelerationUnit;
  6.  
  7.   distance: TLength;
  8.   time, time2: TDuration;
  9.   speed: TSpeed;
  10.   acceleration, gravity: TAcceleration;
  11.  
  12. begin
  13.   km_h := km/h;
  14.   km_h_s := km_h/s;
  15.   m_s2 := m/s2;
  16.  
  17.   distance := (10*km + 100*m) * 2;
  18.   time := 2*h;
  19.   speed := distance/time;
  20.  
  21.   time2 := 10*s;
  22.   acceleration := speed/time2;
  23.  
  24.   writeln('The distance is: ', km.Format(distance));
  25.   writeln('The number of hundreds of meters is: ', distance / (100*m) );
  26.   writeln('The time is: ', h.Format(time));
  27.   // the default time unit is the Second
  28.   writeln('Also: ', time.ToString);
  29.   writeln('The speed is: ', km_h.Format(speed));
  30.   writeln('Also: ', speed.ToString);
  31.   writeln;
  32.  
  33.   distance := 50*km;
  34.   // VerboseFormat display the full name of the unit
  35.   writeln('Distance to go: ', km.VerboseFormat(distance));
  36.   time := distance / speed;
  37.   writeln('Time to get there: ', h.Format(time));
  38.   writeln('Also: ', time.ToString);
  39.   writeln;
  40.  
  41.   writeln('The time to accelerate is: ', time2.ToString);
  42.   writeln('The acceleration is: ', acceleration.ToString);
  43.   writeln('Also: ', km_h_s.Format(acceleration));
  44.   writeln;
  45.  
  46.   gravity := 9.81*m_s2;
  47.   writeln('Gravity is: ', gravity.ToVerboseString);
  48. end.

It works. Though now, there is always a multiplication by a factor, even when it is not necessary. For example 5*m will multiply the value by 1. Also it will always convert to the base unit, which can give some a slight inaccuracy. Also one need to compute the units (km_h etc.) at the beginning. We could use the notation km/h later but it would actually recompute the unit each time.
Title: Re: Type checking of units of measurement
Post by: Warfley on January 26, 2023, 10:22:34 am
I've looked at the assembler, direct initialization (https://godbolt.org/z/TaxhMTjrW):
Code: C  [Select][+][-]
  1.         leaq    -8(%rsp),%rsp
  2.         movq    _$OUTPUT$_Ld1,%rax
  3.         movq    %rax,(%rsp)
  4.         leaq    8(%rsp),%rsp
  5.         ret

With a converter:
Code: Pascal  [Select][+][-]
  1.         leaq    -24(%rsp),%rsp
  2.         movq    m(),%rax
  3.         movq    %rax,16(%rsp)
  4.         movsd   _$OUTPUT$_Ld1,%xmm0
  5.         mulsd   16(%rsp),%xmm0
  6.         movsd   %xmm0,8(%rsp)
  7.         leaq    24(%rsp),%rsp
  8.         ret
So it's a 2 move and 1 multiplication instruction more, this is probably around 1ns overhead on a modern CPU.
This is nothign to worry about.

And if you still worry about it, you should try and compile it with LLVM, this will probably completely optmize this away (I don't have an LLVM on this machine right now so I can't test it).

So I wouldn't worry about this. Also the m/s will probably be optimized to just 1 a few extra instructions, or maybe even fully (with llvm)
Title: Re: Type checking of units of measurement
Post by: ccrause on January 26, 2023, 11:56:20 am
This is an interesting topic with a lot of promise for type safe general calculations involving dimensioned values.  Consider that there are 7 base quantities (https://en.wikipedia.org/wiki/SI_base_unit) from which all other quantities (or physical units of measure) can be derived.  If the current concept can be extended to handle arbitrary units of measure in a type safe way it would be mind blowing.  Take the following example calculation:
Code: Pascal  [Select][+][-]
  1. var
  2.   F: TForce;         // SI base units [kg.m/s/s]
  3.   m: TMass;          // SI base units [kg]
  4.   a: acceleration;   // SI base units [m/s/s]
  5. begin
  6.   m := 100;  // kg
  7.   F := 1000; // Newton, or kg.m/s/s
  8.   a := F / a;
  9. end;

A complicated mix of units which is difficult to keep track of manually, but if the compiler can ensure that the LHS and RHS base units simplify to the same (i.e. kg.m/s/s) then the expression is dimensionally consistent.  Also, converting to any derived units become relatively simple, perhaps by having a table that map between the base units and any desired derived units.

A slight complication is converting quantities that involve offsets, for example converting temperatures (K <-> °C, or R <-> °F) or gauge to absolute pressure.
Title: Re: Type checking of units of measurement
Post by: 440bx on January 26, 2023, 04:41:14 pm
This is an interesting topic with a lot of promise for type safe general calculations involving dimensioned values.  Consider that there are 7 base quantities (https://en.wikipedia.org/wiki/SI_base_unit) from which all other quantities (or physical units of measure) can be derived.  If the current concept can be extended to handle arbitrary units of measure in a type safe way it would be mind blowing. 
I agree.  It really is extremely interesting because it has the potential to be extremely useful.

Currently, using FPC (and Delphi), I see two major hurdles to getting it done right.

The first one is, the compiler does not recognize _true_ typed constants, e.g, "const SomeTime : TSECONDS = 10;" that declaration would not result in a compiler constant but a variable which is a problem because their treatment by the compiler is very different.

The other one is, in FPC, there does not seem to be a way to create "hard" new types other than using "record".  Without using "record" saying e.g, TMYTYPE = type integer; is just declaring a synonym for integer instead of declaring a new type.  IOW, TMYTYPE is still assignment compatible with "integer" which is a problem when using this or similar construction to create units of measurement.

I think that circumventing the above two problems would make a general implementation of units of measure very difficult and cumbersome to use, if possible at all.

Title: Re: Type checking of units of measurement
Post by: circular on January 27, 2023, 01:48:42 pm
A complicated mix of units which is difficult to keep track of manually, but if the compiler can ensure that the LHS and RHS base units simplify to the same (i.e. kg.m/s/s) then the expression is dimensionally consistent.  Also, converting to any derived units become relatively simple, perhaps by having a table that map between the base units and any desired derived units.
Basically, the compiler would need to have some counters that can be added and subtracted, that can be attached to a type. For example, s, s2 and 3 would be derived from the same generic type with a const parameter 1, 2 and 3.
Code: Pascal  [Select][+][-]
  1. type
  2.   TUnit<const Symbol: string; const Name: string; const Exponent: integer> = class
  3.     class operator *(
  4.       const ALeft: specialize TUnit<Symbol, Name, ExponentLeft>;
  5.       const ARight: specialize TUnit<Symbol, Name, ExponentRight>):
  6.       specialize TUnit<Symbol, Name, ExponentLeft+ExponentRight>;  
  7.   end;
  8.  
  9. class operator TUnit.*(
  10.   const ALeft: specialize TUnit<Symbol, Name, ExponentLeft>;
  11.   const ARight: specialize TUnit<Symbol, Name, ExponentRight>):
  12.   specialize TUnit<Symbol, Name, ExponentLeft+ExponentRight>;
  13. begin
  14.   result := nil; // don't need an actual instance
  15. end;  
  16.  
Title: Re: Type checking of units of measurement
Post by: avra on January 27, 2023, 03:12:46 pm
Nice work. Potentially very useful. When polished I would like to see it as FPC package available out of the box.

Although I fully understand the reasoning for uunits.pas naming, pascal developers have strong relation to unit term so to avoid confusion maybe something like dimunits.pas or dimensions.pas would be better. What do you think?
Title: Re: Type checking of units of measurement
Post by: circular on January 28, 2023, 05:25:31 pm
Thanks.

I agree that "UUnit" is not very beautiful. I will use "Dimensioned" instead.

Continuing my experiments, I found how to make type-safe units with factors. So that km and h are factored units, not quite the same as m and s.

I also added a better formatting of numbers.

Code: Pascal  [Select][+][-]
  1. program unitsOfMeasurement;
  2.  
  3. uses Dimensioned, Math;
  4.  
  5. var
  6.   distance: TKilometers;
  7.   time: THours;
  8.   time2: TDuration; // SI unit is [s]
  9.   speed: TKilometersPerHour;
  10.   acceleration, gravity: TAcceleration; // SI unit is [m/s2]
  11.   km_h_s: TAcceleration;
  12.   side, side2: TLength;
  13.   surface: TArea;
  14.   mass: TKilograms;
  15.   scoop: TMass;
  16.  
  17. begin
  18.   distance.Assign((10*km + 100*m) * 2);
  19.   time := THours.From(120*mn);
  20.   speed := distance/time;
  21.  
  22.   time2 := 110*s mod (100*s);
  23.   acceleration := speed/time2;
  24.  
  25.   writeln('The distance is: ', distance.ToString);
  26.   writeln('The number of hundreds of meters is: ', FormatValue(distance / (100*m)) );
  27.   writeln('The time is: ', time.ToString);
  28.   writeln('Also: ', TMinutes.From(time).ToString);
  29.   writeln('The speed is: ', speed.ToString);
  30.   writeln('Also: ', speed.ToBase.ToString);
  31.   writeln;
  32.  
  33.   distance := TKilometers.From(50*km + 0*m + 5*mm);
  34.   // ToVerboseString display the full name of the unit
  35.   writeln('Distance to go: ', distance.ToVerboseString);
  36.   time := distance / speed;
  37.   writeln('Time to get there: ', time.ToString);
  38.   writeln('Also: ', time.ToBase.ToString);
  39.   writeln;
  40.  
  41.   writeln('The time to accelerate is: ', time2.ToString);
  42.   writeln('The acceleration is: ', acceleration.ToString);
  43.   km_h_s := 1*(km/h)/(1*s);
  44.   writeln('Also: ', FormatValue(acceleration/km_h_s), ' km/h/s');
  45.   writeln;
  46.  
  47.   gravity := 9.81*(m/s2);
  48.   writeln('Gravity is: ', gravity.ToVerboseString);
  49.   writeln('s2 = s*s? ', s2 = s*(s2/s));
  50.   writeln('m/s2*s = m/s? ', m/s2*s = m/s);
  51.   writeln;
  52.  
  53.   side := 10*m;
  54.   surface := side*side;
  55.   writeln('The surface of a ', side.ToString,' by ', side.ToString,
  56.     ' square is: ', surface.ToString);
  57.   side2 := 5*m;
  58.   writeln('It is the same surface as a rectangle of ', side2.ToString,
  59.     ' by ', (surface/side2).ToString);
  60.  
  61.   mass := 3*kg;
  62.   scoop := 100*g;
  63.   writeln('From ', mass.ToString,', I can make ', FormatValue(mass/scoop), ' scoops of ', scoop.ToString);
  64. end.

Here is the output
Code: [Select]
The distance is: 20.2 km
The number of hundreds of meters is: 202
The time is: 2 h
Also: 120 min
The speed is: 10.1 km/h
Also: 2.80555555555556 m/s

Distance to go: 50 kilometers
Time to get there: 4.95049554455446 h
Also: 17822 s

The time to accelerate is: 10 s
The acceleration is: 0.280555555555556 m/s2
Also: 1.01 km/h/s

Gravity is: 9.81 meters per second squared
s2 = s*s? TRUE
m/s2*s = m/s? TRUE

The surface of a 10 m by 10 m square is: 100 m2
It is the same surface as a rectangle of 5 m by 20 m
From 3 kg, I can make 30 scoops of 100 g

I did not find how to fit some operators into classes to use generics in order to define the same operation in one go for all units:
Code: Pascal  [Select][+][-]
  1. // making dimensioned values
  2. operator *(const AValue: double; const {%H-}TheUnit: TMeterPerSecond): TSpeed; inline;
  3. operator *(const AValue: double; const {%H-}TheUnit: TKilometerPerHour): TKilometersPerHour; inline;
  4.  
  5. // combining units
  6. operator /(const {%H-}m: TMeter; const {%H-}s: TSecond): TMeterPerSecond; inline;
  7. operator *(const {%H-}m_s: TMeterPerSecond; const {%H-}s: TSecond): TMeter; inline;
  8. operator /(const {%H-}km: TKilometer; const {%H-}h: THour): TKilometerPerHour; inline;
  9. operator *(const {%H-}km_h: TKilometerPerHour; const {%H-}h: THour): TKilometer; inline;
  10.  
  11. // combining dimensioned quantities
  12. operator /(const ALength: TLength; const ADuration: TDuration): TSpeed; inline;
  13. operator /(const ALength: TKilometers; const ADuration: THours): TKilometersPerHour; inline;
Title: Re: Type checking of units of measurement
Post by: Bogen85 on January 28, 2023, 06:06:21 pm
Nice work. Potentially very useful. When polished I would like to see it as FPC package available out of the box.

Likewise!
Title: Re: Type checking of units of measurement
Post by: VisualLab on January 29, 2023, 12:03:52 am
Code: Pascal  [Select][+][-]
  1. program unitsOfMeasurement;
  2.  
  3. uses Dimensioned, Math;
  4.  
  5. var
  6.   distance: TKilometers;
  7.   time: THours;
  8.   time2: TDuration; // SI unit is [s]
  9.   speed: TKilometersPerHour;
  10.   acceleration, gravity: TAcceleration; // SI unit is [m/s2]
  11.   km_h_s: TAcceleration;
  12.   side, side2: TLength;
  13.   surface: TArea;
  14.   mass: TKilograms;
  15.   scoop: TMass;
  16.  
  17. ...

It's all very interesting. But the concept of mixing physical units with physical quantities is not clear to me. In the variables section, both TKilometers (physical unit) and TLength (physical quantity) or TKilograms (physical unit) and TMass (physical quantity) are declared. Separate units and quantities will force the code to multiply the quantity by the unit. So maybe it would be better:
Title: Re: Type checking of units of measurement
Post by: circular on January 29, 2023, 12:47:56 am
I understand the confusion.

TKilometers is the quantity whereas TKilometer is the unit.

The names TLength, TMass were in the beginning to show that they are the reference unit, with factor 1, instead of Kilometer having a factor of 1000.

Though as it becomes almost transparent with automatic conversions, it would make sense to replace TLength by TMeters, TMass by TGrams etc. This is all fine until we get to more complicated units like TAcceleration, that would be TMetersPerSecond2 or TMetersPerSecondSquared. And there are much more complicated units.

I thought as well about separating the multiplier. It is possible, though it may require to add some brackets that may not be intuitive. Also it would be a bit longer. For example, "m" cannot be used for the factor milli because it is already taken by the meter.

Code: Pascal  [Select][+][-]
  1.   d := 6*(milli*m); // this gives mm unit

Note that I prefer not multiply by the factor until it is necessary, to avoid loss of precision. So kilo*g would actually build factored unit, not exactly the same as 1000*g.

As I continue to experiment, it becomes clear that to reduce the number of operator definitions, I would need either:
- generic operators: similar to generic functions, but with operator, that could be applied to the various types that match the specializations.
- inheritance of class operators that refer to "self" type

The second one seems far fetched, it would require record inheritance and reinterpretation of signatures and implementing the "self" type.

So probably the most plausible would be the first one: generic operators. I don't know if this is something considered on trunk.
Title: Re: Type checking of units of measurement
Post by: avra on January 29, 2023, 05:00:34 am
Long time ago I didn't like constant ugly usage of FreeRTOS macro to convert ms to systicks:
Code: C  [Select][+][-]
  1. static const TickType_t xInterruptFrequency = pdMS_TO_TICKS(250)
so I defined my own macro:
Code: C  [Select][+][-]
  1. #define msec * ( TickType_t ) configTICK_RATE_HZ / ( TickType_t ) 1000
to be able to use it like this:
Code: Pascal  [Select][+][-]
  1. static const TickType_t xInterruptFrequency = 500 msec;
which is much more to my liking.

So, to add some spice to this discussion here is a simple trick to achieve that in pascal:

Code: Pascal  [Select][+][-]
  1. {$macro on}
  2. {$define m  := * 1}
  3. {$define mm := / 100}
  4. {$define km := * 1000}
  5. ...
  6. procedure TForm1.Button1Click(Sender: TObject);
  7. begin
  8.   Caption := '5m = ' + IntToStr(5m) + ', 5mm = ' + FloatToStr(5mm) + ', 5km + 200m = ' + (5km + 200m).ToString;
  9.   // 5m = 5, 5mm = 0.05, 5km + 200m = 5200
  10. end;

As a side effect, 5m looks more natural then 5*m  :D
Title: Re: Type checking of units of measurement
Post by: circular on January 29, 2023, 06:32:39 am
Thanks for that  :D

Well it still entails a multiplication, so a loss of precision. For example:
Code: Pascal  [Select][+][-]
  1. writeln((100/1000 + 200/1000)*1000, ' =? ', (100*mm + 200*mm).Value);
Will output
Code: [Select]
3.0000000000000006E+002 =?  3.0000000000000000E+002
Though one could define "mm" as "millimeter" instead and add your macro to multiply.

It is more beautiful but then it prevents the use of the unit as itself. For example:
Code: [Select]
speed := 10*(m/s);
Title: Re: Type checking of units of measurement
Post by: bytebites on January 29, 2023, 07:42:17 am
1 m = 1000 mm
Title: Re: Type checking of units of measurement
Post by: MarkMLl on January 29, 2023, 09:36:48 am
I agree that "UUnit" is not very beautiful. I will use "Dimensioned" instead.

Careful there, it's fairly common to use "dimension" to refer to the rank of an array.

Apart from that I agree, good work.

I think one of the earliest implementations of this sort of thing was in Boost, I don't know my way around that but presumably https://www.boost.org/doc/libs/1_81_0/doc/html/boost_units.html applies.

MarkMLl
Title: Re: Type checking of units of measurement
Post by: avra on January 29, 2023, 04:05:12 pm
1 m = 1000 mm
Right on the spot!  :D
I had in mind cm but completely failed at the keyboard  :) %) ::)
Title: Re: Type checking of units of measurement
Post by: avra on January 29, 2023, 04:13:34 pm
I agree that "UUnit" is not very beautiful. I will use "Dimensioned" instead.
I am not English native, but dim or dimensions still sound better to me over dimensioned. I guess si or metrics could be even better names, but since imperial units would probably end up there too - it might not be that great idea. Anyway, it's not that important and your contribution will be most welcome under any name. I already see it useful when publishing measurements with appropriate unit names under some MQTT or OPC server.
Title: Re: Type checking of units of measurement
Post by: avra on January 29, 2023, 04:42:37 pm
As I continue to experiment, it becomes clear that to reduce the number of operator definitions, I would need either:
- generic operators: similar to generic functions, but with operator, that could be applied to the various types that match the specializations.
- inheritance of class operators that refer to "self" type

You could also take a look at syshelp.inc and how it uses single syshelpo.inc with different macro parameters to avoid code repetition and typing errors:
Code: Pascal  [Select][+][-]
  1. { ---------------------------------------------------------------------
  2.   TByteHelper
  3.   ---------------------------------------------------------------------}
  4.  
  5. {$define TORDINALHELPER:=TByteHelper}
  6. {$define TORDINALTYPE:=Byte}
  7. {$define TORDINALBITINDEX:=TByteBitIndex}
  8. {$define TORDINALNIBBLEINDEX:=TByteNibbleIndex}
  9. {$define TORDINALOVERLAY:=TByteOverlay}
  10. {$define TORDINALTYPESIZE1}
  11. {$i syshelpo.inc}
  12. {$undef TORDINALTYPESIZE1}
  13.  
  14. { ---------------------------------------------------------------------
  15.   TShortintHelper
  16.   ---------------------------------------------------------------------}
  17.  
  18. {$define TORDINALHELPER:=TShortIntHelper}
  19. {$define TORDINALTYPE:=ShortInt}
  20. {$define TORDINALBITINDEX:=TShortIntBitIndex}
  21. {$define TORDINALNIBBLEINDEX:=TShortIntNibbleIndex}
  22. {$define TORDINALOVERLAY:=TShortIntOverlay}
  23. {$define TORDINALTYPESIZE1}
  24. {$i syshelpo.inc}
  25. {$undef TORDINALTYPESIZE1}

I exploited that idea a lot when adding bit helpers functionality.
Title: Re: Type checking of units of measurement
Post by: 440bx on January 29, 2023, 05:13:19 pm
I agree that "UUnit" is not very beautiful. I will use "Dimensioned" instead.
How about "uom" (for "units of measure") ? short and unlikely to be confused with something else and, it does not presume being metric or imperial.

Title: Re: Type checking of units of measurement
Post by: circular on January 29, 2023, 10:03:04 pm
You could also take a look at syshelp.inc and how it uses single syshelpo.inc with different macro parameters to avoid code repetition and typing errors:
Interesting. Indeed using $define could solve my repetition problems.  :)

How about "uom" (for "units of measure") ? short and unlikely to be confused with something else and, it does not presume being metric or imperial.
I don't know, it is original but not easy to remember, it sounds a bit mysterious unless you remember the acronym.
Title: Re: Type checking of units of measurement
Post by: circular on January 30, 2023, 01:57:24 am
Ok so using $define it was possible to factor the repeated code.

Also I made the syntax simpler when converting to unit. For example to get hours from minutes you can write h.From(120*mn).

Adding cubic meters revealed a new problem, which is mutual dependency of record types. With classes one can do a forward declaration, but that doesn't work with records. So I am not sure how to define m3/m2.

Another problem was kind of solved, regarding acceleration, by defining as a ratio of ratio (m/s)/s instead of m/s2 (which still exists as an alias). So I could add support for km/h/s that can be computed from an acceleration with (km/h/s).From(1*(m/s2))

Code: Pascal  [Select][+][-]
  1. uses Dimensioned;
  2.  
  3. var
  4.   side: TLength;
  5.   volume: TVolume;
  6.  
  7. begin
  8.   volume := 2*m3;
  9.   side := 1*m;
  10.   writeln('A rectangular cuboid of of volume ', volume.ToString,
  11.     ' with a height ', side.ToString);
  12.   writeln('has an horizontal face of ', (volume/side).ToString);
  13. end.
Title: Re: Type checking of units of measurement
Post by: 440bx on January 30, 2023, 02:19:20 am
How about "uom" (for "units of measure") ? short and unlikely to be confused with something else and, it does not presume being metric or imperial.
I don't know, it is original but not easy to remember, it sounds a bit mysterious unless you remember the acronym.
You're right, it has its pros and cons. 

The premise is that if the use of the unit becomes common then "uom" will be easy to "remember".
Title: Re: Type checking of units of measurement
Post by: circular on January 30, 2023, 09:47:51 pm
Ok so I thought about the name.

As units are small, they are likely to collide with local variables. So it is better to have a short and easily identifiable name for the unit.

"Dim" is the best I can see for that, easy to read, so that would go nicely in the code if needed. For example:
Code: Pascal  [Select][+][-]
  1.  side := 100*Dim.cm;

I created a GitHub repository called DimPas.
https://github.com/circular17/DimPas

Taking into account the remark about the confusion between TDuration and THours for example, I changed the names to units only. So TDuration is now TSeconds.

If you're interested by this project I will gladly give you access to the repository.
Title: Re: Type checking of units of measurement
Post by: avra on January 31, 2023, 01:09:34 am
I created a GitHub repository called DimPas.
https://github.com/circular17/DimPas
Sorry, GPL3 is a show stopper for me since I create closed source a lot. That fact also switched my point of view so now I would like to see it as a package in FPC only if license changes to something more liberal - at least FPC modified LGPL.
Title: Re: Type checking of units of measurement
Post by: Bogen85 on January 31, 2023, 01:31:06 am
I created a GitHub repository called DimPas.
https://github.com/circular17/DimPas
Sorry, GPL3 is a show stopper for me since I create closed source a lot. That fact also switched my point of view so now I would like to see it as a package in FPC only if license changes to something more liberal - at least FPC modified LGPL.

That is a show stopper for me as well. I would also like to see this as a package in FPC.
Title: Re: Type checking of units of measurement
Post by: circular on January 31, 2023, 10:35:37 am
Oh yes I forgot about linking. I modified the licenses to LGPL3+link exception.

By the way it is not a show for me, but a community project. Thanks for the precious input I've received so far.
Title: Re: Type checking of units of measurement
Post by: avra on February 01, 2023, 11:58:41 am
I modified the licenses to LGPL3+link exception.
Thanks!  :D

However, provided license LGPL3 on it's own (as given in https://github.com/circular17/DimPas/blob/main/LICENSE) does not include linking exception, according to https://tldrlegal.com/license/gnu-lesser-general-public-license-v3-(lgpl-3):
Quote
If you distribute this library in an executable, you must make the source available for 3 years.

If you want to explicitly allow linking exception, then this might be one of the ways:
https://wiki.lazarus.freepascal.org/FPC_modified_LGPL
Title: Re: Type checking of units of measurement
Post by: circular on February 01, 2023, 02:01:10 pm
Yes, that's what I meant, I've put a LICENSE.modifiedLGPL file.
Title: Re: Type checking of units of measurement
Post by: avra on February 01, 2023, 06:19:02 pm
I've put a LICENSE.modifiedLGPL file.
Thanks again!  :D 8-) :D
Title: Re: Type checking of units of measurement
Post by: circular on February 01, 2023, 10:13:49 pm
You're welcome.  :)

Continuing my experiments, I've added reciprocal units, unit products, degC, degF, Hz, rad, deg, deg/s, deg/s2, N, C, lx, Sv, kat.

I will have less time available, so feel free to look at the code and propose improvements, new unit combinations etc.

Here is how the units are structured. The principle is to define base units (meter, gram, second), then combine them as ratio or product. For example, m/s (speed) is a ratio and A.s (coulomb) is a product.

Then units can be combined again. (m/s)/s = m/s2 is the speed. kg.(m/s2) is the newton.

The way a unit is defined define operators that can be applied to it. For example, a speed (m/s) can be multiplied by a time (s) which gives a length (m).

It is needed to define explicitly an operator to combine units. For the speed, it is the "/" operator on meters and seconds.

For complicated units, such as the newton N, which is defined as kg.((m/s)/s), the predefined operators are to divide it by a mass or an acceleration. Not to multiply it by a second, for example, even though the unit is equivalent to (kg.(m/s))/s. If such operator is useful, then it would make sense to add it to the Dim unit.
Title: Re: Type checking of units of measurement
Post by: qk on February 01, 2023, 10:59:07 pm
Nice work Circular !

For each units combination in needed to have specific operators for mul and div, I have correctly understood ?

Thanks.
Title: Re: Type checking of units of measurement
Post by: circular on February 01, 2023, 11:33:56 pm
Thanks  :)

Most operators are defined by the generic classes, so that there is no need to define them specifically for one unit combination. But there is a need for at least one explicit operator in order to build the new combination of units and of quantities.

Here is an example for defining rad/s:
Code: Pascal  [Select][+][-]
  1. type
  2.   // not necessary but useful when using rad/s in a new combination like (rad/s)/s
  3.   TRadianPerSecond = specialize TRatioUnit<TRadian, TSecond>;
  4.  
  5.   // needed for rad/s
  6.   TRadianPerSecondIdentifier = specialize TRatioUnitIdentifier<TRadian, TSecond>;
  7.   TRadiansPerSecond = specialize TRatioDimensionedQuantity<TRadian, TSecond>;
  8.  
  9. // combining units
  10. operator /(const {%H-}rad: TRadianIdentifier; const {%H-}s: TSecondIdentifier): TRadianPerSecondIdentifier; inline;
  11.  
  12. // combining dimensioned quantities
  13. operator /(const AAngle: TRadians; const ADuration: TSeconds): TRadiansPerSecond; inline;
  14.  
  15. implementation
  16.  
  17. operator /(const rad: TRadianIdentifier; const s: TSecondIdentifier): TRadianPerSecondIdentifier;
  18. begin end;
  19.  
  20. operator /(const AAngle: TRadians; const ADuration: TSeconds): TRadiansPerSecond;
  21. begin
  22.  result.Value:= AAngle.Value / ADuration.Value;
  23. end;

Title: Re: Type checking of units of measurement
Post by: qk on February 02, 2023, 12:00:52 am
Thanks!

I'm playing with your old code (first approach) writing a tool for creating dim.pas unit automatically.
I can confirm you that a lot of operators are needed.

Your last code is too complex for me.
Regars
Title: Re: Type checking of units of measurement
Post by: circular on February 02, 2023, 12:50:36 am
Cool. That's a lot of operators indeed  :D

I realize I had not defined the comparisons operators. I've added them.
https://github.com/circular17/DimPas/commit/b421924200f8f4e266add7f0f21bc58f26270275

I see you've done the factored squared/cubed quantities (km2, km3 etc.) which is a good idea. This is something that could be added to my version.

I think the multiplication of units in your code isn't correct regarding the factor
Code: Pascal  [Select][+][-]
  1. operator *(const ALeft: TLengthUnit; const ARight: TLengthUnit): TAreaUnit;
  2. begin
  3.   result.Symbol := 'm2';
  4.   result.Factor := 1;
  5. end;

The factor will be always 1 but for you could multiply kilometers for example. That's one of the reason why in the end I did not use variables to define the factors but class functions with a different type each time.
Title: Re: Type checking of units of measurement
Post by: qk on February 03, 2023, 11:40:54 pm
Hi Circular,

I'm trying to create TSquareMillimeter unit
Code: Pascal  [Select][+][-]
  1.   TSquareMillimeter = specialize TUnitSquared<TMilliMeter>;
  2.   TSquareMillimeters = specialize TSquaredDimensionedQuantity<TMilliMeter>;
  3.   TSquareMillimeterIdentifier = specialize TUnitSquaredIdentifier<TMilliMeter>;  
  4.  
but this code doesn't work.

When I try to multiply mm x mm the error is : Operator is not overloaded: "TFactoredDimensionedQuantity$2$crcAAEB45C2" * "TFactoredDimensionedQuantity$2$crcAAEB45C2"

Is it necessary to add a new operator *?
Title: Re: Type checking of units of measurement
Post by: circular on February 04, 2023, 12:51:57 pm
Hi qk,

TMillimeter is a factored unit and squaring is not implemented for that.

I am adding that to the Dim unit, I will let you know when I'm finished.
Title: Re: Type checking of units of measurement
Post by: circular on February 04, 2023, 01:32:55 pm
Ok I'm done. By the way, I've added strict type checking for factored types, to avoid any possible mistake when defining derived types. So here's how you would define square millimeters:

Code: Pascal  [Select][+][-]
  1. type
  2.   TMillimeter = specialize TMilliUnit<TMeter>;
  3.   TSquareMillimeter = specialize TFactoredUnitSquared<TMillimeter>;
  4.   TSquareMillimeters = specialize TFactoredSquaredDimensionedQuantity<TMeter, TMillimeter>;
  5.   TSquareMillimeterIdentifier = specialize TFactoredUnitSquaredIdentifier<TMeter, TMillimeter>;
  6.  
  7. var
  8.   mm2: TSquareMillimeterIdentifier;

Note: you don't need to define any operators for square units. For cubic units, you would need to define what is the cubed unit divided by the squared unit like:
Code: Pascal  [Select][+][-]
  1. // combining units
  2. operator/(const m3: TCubicMeterIdentifier; const m2: TSquareMeterIdentifier): TMeterIdentifier;
  3. begin end;
  4.  
  5. // combining dimensioned quantities
  6. operator/(const AVolume: TCubicMeters; const ASurface: TSquareMeters): TMeters;
  7. begin
  8.   result.Value := AVolume.Value / ASurface.Value;
  9. end;

EDIT: I've refactored the code of dim.pas by putting the includes next to where they are used, it makes more sense this way.
https://github.com/circular17/DimPas/blob/main/dim.pas
Title: Re: Type checking of units of measurement
Post by: qk on February 04, 2023, 03:51:29 pm
Thanks circular!

Today I've compiled a real application using the huge dim.pas unit, that with more than five hundred operators.
At the moment the main problem is that the compilation in slowly.

Now I'm restarting with your dim.pas but there are some others unit to add, I will let you know.
Title: Re: Type checking of units of measurement
Post by: circular on February 04, 2023, 05:07:51 pm
:)

Ok, I will gladly merge the other units you add.
Title: Re: Type checking of units of measurement
Post by: qk on February 04, 2023, 06:42:31 pm
Hi Circular,

TSquaredMillimeters, TCubicMillimeter ... and others added, but now I have a doubt about the approatch to the problem.

I think that working with the units (TMeter, TMillimeter, TInches ...) requires more operators or code that working with physical quantities (such as TLength, TArea ...).

I've tried to calculate speed using TMillimeter at numerator instead of TKilometer, but before it's needed to create a new class (such as TMillimeterPerHour, TCentimeterPerHour);

If for each combinations of unit (or multiplies) it's needed to write a new class and some operators, I think that there is too much code to write to have a usable unit Dim.pas.

Am I making a mistake? Thanks
Title: Re: Type checking of units of measurement
Post by: circular on February 04, 2023, 08:39:30 pm
I've applied your changes (add cm2, cm3, mm2, mm3). What you did is correct, there is no mistake.

I have some doubts as well about the maintainability.

Some things can be simplified. For example, the squared/cubed unit type could not be defined.

We could as well not define TMillimeterIdentifier and instead have a simple syntax to build it. For example "3*centi(m)" would be like "3*cm". This doesn't work yet though with the current version of the compiler. Maybe in trunk. We could do "m.centi" but I find it confusing.

Something that could work could be for exponents. For example it is possible to define that "km._2" or "km.sqr" would be the same as km2.

We can gain some space by putting the identifier definition directly on the var line.

Regarding the operators, we could consider unlikely that someone is going to write cm3/cm2 in the code, as it can obviously be replace by cm. I thought it would be nice that the unit would understood by the compiler, but that's not so important. So we can probably remove the additional operator / on units. It will anyway even become problematic if we add exponent 4 for example.

The operator on quantities also could be omitted. I wanted avoid any premultiplication by the unit factor, but one could argue that it doesn't matter much when dividing a volume by a surface. So one can go around this operator by converting to the base unit, for example "(3*km3).ToBase / (2*km2)" instead of "(3*km3) / (2*km2)"

So for example to define centimeters along with its exponents, that could be:
Code: Pascal  [Select][+][-]
  1. type
  2.   TCentimeter = specialize TCentiUnit<TMeter>;
  3.   TCentimeters = specialize TFactoredDimensionedQuantity<TMeter, TCentimeter>;
  4.   TSquareCentimeters = specialize TFactoredSquaredDimensionedQuantity<TMeter, TCentimeter>;
  5.   TCubicCentimeters = specialize TFactoredCubedDimensionedQuantity<TMeter, TCentimeter>;
  6.  
  7. var
  8.   cm: specialize TFactoredUnitIdentifier<TMeter, TCentimeter>;

That could be used like:
Code: Pascal  [Select][+][-]
  1. var
  2.   surface: TSquareCentimeters;
  3. begin
  4.   surface := 5*cm._2;

What do you think?
Title: Re: Type checking of units of measurement
Post by: qk on February 04, 2023, 11:46:47 pm
About then proposal "3*centi(m)", "km._2" or "km.sqrt" I find it confusing too.

I'm reading your old posts to better understand the reason for moving from uunit.pas to dim.pas.
I have found two main reasons:

1) There is always a multiplication by a factor, even when it is not necessary (5*m, 10*s ...)

If this is true only when assign a value the first time then I think that it isn't a important problem.
How many people use m and how many people use others (mm cm, dm, foot, inches...) ? 

2) It's needed to define km_h ...

Yes, this is a little cons. km_h isn't intuitive to use.
There are other reasons ?

For my application, an usable dim.pas unit must have capability to manage and combine:

mm, mm2, mm3, mm4
Nm, N/m, N/mm2 (MPa)
kg/dm3
Hz


plus some extra units for calculation (1/mm, 1/mm2, 1/mm3, mm2/s2).
This is minimum requirement, but I want to extend it to english measurement system.

Consider that I have modified some formulas because, for example, in some calculations
the mm4 are exceeded... or there were new units not supported.
The situations can be very different, it' depend on branch, engineering, physics.

I hope that this description can help you to understand which is the better way.
At the moment the game is 1-0 for uunit.pas.  :o

Regards
Title: Re: Type checking of units of measurement
Post by: circular on February 05, 2023, 12:31:31 am
Maybe "km.Squared" and we could have as well this function to square a quantity, for example (2*km).Squared = 4*km.Squared.

It is not that bad, and one can define their own unit in they program if they use them a lot.

I suppose you are ok with the other simplifications proposed. I've applied them. I guess it may be more doable now.

1) It is a bit more than that. Either we multiply once at the beginning but it means it is always automatically converted to the base unit, so we need to reconvert it back to the unit we want to display. This premultiplication has some effect on accuracy as well.

2) Indeed

About the other reasons, a big one was the number of operators to define. In the latest version, the number of operators defined for each new unit is considerably smaller. In the old versions I was getting a lot of operators, and in the new versions there are few even though I've added more units.

So for me it is not 1 - 0. I wouldn't say it is 0 - 1 either.
Title: Re: Type checking of units of measurement
Post by: qk on February 05, 2023, 09:31:06 am
Ok! I have tried to add mm4. Please check the code, I'm not a PRO.
Title: Re: Type checking of units of measurement
Post by: circular on February 05, 2023, 11:03:21 am
Thanks. I've merged your changes.

About operator overloading, inside a class, it can be defined only if TSelf appears in the parameters * and /. The compiler does not complain but it will be ignored otherwise. That's why if we want m3/m2 then it needs to be defined outside of the meter records.

I am wondering about the name for the fourth power. The name "quarticed" does not seem to be used.

https://www.reddit.com/r/askscience/comments/3rchs6/if_2_is_squared_and_3_is_cubed_what_is_4_called/

Some people suggest "cubic -> cubed" so "quartic -> quarted". Others suggest "tesseracted" as a tesseract is a 4-dimensional cube. But those are not used either.

We can avoid this problem by calling things: square, cubic, quartic, quintic etc. Also the names can be made more consistent, sometimes I've put the cubed sometimes before sometimes after. So I will fix that.
Title: Re: Type checking of units of measurement
Post by: qk on February 05, 2023, 11:27:54 am
Quote
About operator overloading, inside a class, it can be defined only if TSelf appears in the parameters * and /. The compiler does not complain but it will be ignored otherwise. That's why if we want m3/m2 then it needs to be defined outside of the meter records.

Thank for explanation.

Quote
We can avoid this problem by calling things: square, cubic, quartic, quintic etc. Also the names can be made more consistent, sometimes I've put the cubed sometimes before sometimes after. So I will fix that.

I agree and I will wait your changer before start with N/mm.

Edit: with Github, is there a way to propose a change without applying it? I prefer that you apply the changes after checking. So we would avoid exchanging files on the forum.
Title: Re: Type checking of units of measurement
Post by: circular on February 05, 2023, 12:25:16 pm
You're welcome.

I've applied the name changes. Also simplified how powers are defined. For example:
Code: Pascal  [Select][+][-]
  1. type
  2.   generic TSquareUnit<BaseU: TUnit> = {$DEFINE POWER_UNIT_INTF}{$i dim.pas}
  3.   generic TFactoredSquareUnit<BaseU: TFactoredUnit> = {$DEFINE FACTORED_POWER_UNIT_INTF}{$i dim.pas}
  4. ...
  5. { TSquareUnit }
  6.  
  7. class function TSquareUnit.Exponent: integer;
  8. begin
  9.   result := 2;
  10. end;
  11.  
  12. { TFactoredSquareUnit }
  13.  
  14. class function TFactoredSquareUnit.Exponent: integer;
  15. begin
  16.   result := 2;
  17. end;

Indeed, you can propose changes via GitHub. For that you need a GitHub account, and then in my repository, do a fork. The fork button is at the top of the page.

Then checkout your forked repository as any other Git repository, and commit changes there. Then go in my GitHub repository page and do a pull request.
Title: Re: Type checking of units of measurement
Post by: qk on February 05, 2023, 12:30:07 pm
ok thanks, I will try with fork.

I have an other question.
For creating N/m class I think to use TRatioUnit<TNewton, TMeter>, It's right ?

And for creating N/mm ? Millimeters are factored but I can't find TRightFactoredUnitRatio or something like that.

Can you better explain me when to use a class instead of another ?
Title: Re: Type checking of units of measurement
Post by: circular on February 05, 2023, 01:36:04 pm
Ok.  :)
 
Indeed that can be a ratio of N and m. In fact here there is a choice we can make. It could be also a ratio of kg and s2 because N/m = kg/s2. So it depends on what is the most common use of this unit.

If you define as N/m, it could make sense to make an operator / that applies to kg and s2 that would also create a quantity in N/m. I don't know if that makes sense from a physical point of view. Then maybe an operator * so that if we multiply a quantity in N/m by a quantity in s2 that would give a quantity in kg.

Or that the operator operator / would give a quantiy in kg/s2 and define this unit as well. Then this could be linked by adding some equivalence like for the litre:
Code: Pascal  [Select][+][-]
  1. // dimension equivalence
  2. operator:=(const AVolume: TLitres): TCubicMeters;
  3. operator:=(const AVolume: TLitres): TCubicDecimeters;
Title: Re: Type checking of units of measurement
Post by: qk on February 05, 2023, 02:21:46 pm
kg/s2 is an acceleration, how fast I will get fat  :D
It can be a case to implement.  :)

I think is more common to multiply N/m (stifness) for m for having N (force).

Now, TNewton isn't a TUnit and we can't define N/m as TUnitRatio. How to fix it ?

Regards
Title: Re: Type checking of units of measurement
Post by: circular on February 05, 2023, 02:39:31 pm
kg/s2 is an acceleration, how fast I will get fat  :D
:D

Quote
I think is more common to multiply N/m (stifness) for m for having N (force).

Now, TNewton isn't a TUnit and we can't define N/m as TUnitRatio. How to fix it ?
TNewton is a TFactoredUnit because it is uses kg instead of g. So I suppose you can combine it with TMeter with TFactoredNumeratorUnit.

Ah but then we need to define the base unit for TNewton, I suppose. That would be TMillinewton maybe? EDIT: No in fact that would be TKilogramMeterPerSecondSquared.

If that's too complicated we might want to define two kilograms unit. One being the factored unit from the gram, and one begin the SI base unit.
Title: Re: Type checking of units of measurement
Post by: qk on February 05, 2023, 03:43:44 pm
Quote
TNewton is a TFactoredUnit because it is uses kg instead of g. So I suppose you can combine it with TMeter with TFactoredNumeratorUnit.
... but TFactoredNumeratorUnitIdentifier and TFactoredNumeratorUnitQuantity need TUnit not TFactoredUnit.

Quote
If that's too complicated we might want to define two kilograms unit. One being the factored unit from the gram, and one begin the SI base unit.

and then can a user combine units derived from these the two kg units ?
 :'(
Title: Re: Type checking of units of measurement
Post by: circular on February 05, 2023, 05:49:22 pm
... but TFactoredNumeratorUnitIdentifier and TFactoredNumeratorUnitQuantity need TUnit not TFactoredUnit.
That's because unfactored versions of the units come first as generic parameters. But anyway let's simplify.

Quote
and then can a user combine units derived from these the two kg units ?
 :'(
Yes. Basically the "kg" identifier will still be the one derived from the gram, allowing to do conversions to g, mg... But when combined, it will become the new TBaseKilogram unit.

I've added some implicit conversions to do that.

So now, TNewton is much simpler:
Code: Pascal  [Select][+][-]
  1. type
  2.   TNewton = specialize TUnitProduct<TBaseKilogram, TMeterPerSecondSquared>;
  3.   TNewtonIdentifier = specialize TUnitProductIdentifier<TBaseKilogram, TMeterPerSecondSquared>;
  4.   TNewtons = specialize TDimensionedQuantityProduct<TBaseKilogram, TMeterPerSecondSquared>;
  5.  
  6. // combining units
  7. operator *(const {%H-}kg: TKilogramIdentifier; const {%H-}m_s2: TMeterPerSecondSquaredIdentifier): TNewtonIdentifier; inline;
  8. operator /(const {%H-}kg: TKilogramMeterIdentifier; const {%H-}s2: TSquareSecondIdentifier): TNewtonIdentifier; inline;
  9.  
  10. // combining dimensioned quantities  
  11. operator *(const AWeight: TGrams; const AAcceleration: TMetersPerSecondSquared): TNewtons; inline;
  12. operator *(const AAcceleration: TMetersPerSecondSquared; const AWeight: TGrams): TNewtons; inline;
  13. operator *(const AWeight: TBaseKilograms; const AAcceleration: TMetersPerSecondSquared): TNewtons; inline;
  14. operator *(const AAcceleration: TMetersPerSecondSquared; const AWeight: TBaseKilograms): TNewtons; inline;

The operator to combine TBaseKilograms is not strictly necessary but it can help prevent useless conversion of kg to g and then back to base kg.
Title: Re: Type checking of units of measurement
Post by: qk on February 05, 2023, 11:33:00 pm
Pull request done!  :)

Regards.
Title: Re: Type checking of units of measurement
Post by: avra on February 06, 2023, 12:31:21 am
... the "kg" identifier will still be the one derived from the gram...
Huh, shouldn't it be the other way around (g derived from kg)? kg (not g) is base SI unit:
https://www.nist.gov/pml/owm/metric-si/si-units
https://www.nist.gov/sites/default/files/styles/2800_x_2800_limit/public/images/2021/08/23/NIST.SP_.1247.png
Title: Re: Type checking of units of measurement
Post by: circular on February 06, 2023, 07:53:05 am
Yes and no. If "kg" is the base unit, then the "k" is just part of its name. But we want to be able to convert from kg to g to mg etc. So it is better if kg is the kilo-unit of the gram.

But when combining kg with other units to make a special unit like newton, volt, ohm, etc. we don't want this unit to be a kilo-unit. So then the base unit is the "kg". And from the new unit, we can make a kilo-unit, for example kN.

So there are two base units of mass, in the sense of non-factored units.

A similar thing happens with cubic meters and litres.
1 L = 1 dm3 = 0.001 m3
1 kL = 1 m3

We want to be able to have cL, etc. and this cannot be done with SI base unit. Indeed what we can only do by factors of 1000 due to the cube:
1 cm3 = 0.001 dm3 = 0.001 L = 1 mL
Title: Re: Type checking of units of measurement
Post by: circular on February 12, 2023, 10:29:06 am
Some update about the topic: new units were added, it was not obvious but we did it.
https://github.com/circular17/DimPas/pull/1#issuecomment-1426892616

Base units
Time: ms, s, s2, mn, h, day
Length: mm..mm4, cm..cm4, m..m4, km..km4, L
Weight: mg, g, kg, ton
Mole: mol
Ampere: A, A2
Temperature: K, ºC, ºF
Candela: cd

Special units
Frequency: Hz = s-1
Angle: rad, deg
Force: N = kg*m/s2, kN
Charge: C = A*s
Pressure: Pa, kPa, MPa
Energy: J = N*m
Illuminance: lux = cd/m2
Equivalent dose: Sv = m2/s2
Catalytic activity: kat = mol/s

Derived units
Speed: m/s, km/h, rad/s
Acceleration: m/s2, km/h/s, rad/s2
Stiffness: N/m
Density: g/m3, kg/m3

Any help adding more units is welcome. Latest version:
https://github.com/circular17/DimPas/blob/main/dim.pas
Title: Re: Type checking of units of measurement
Post by: avra on February 13, 2023, 12:35:02 am
What I see missing are micro and nano (um, nm, us, ns)
Title: Re: Type checking of units of measurement
Post by: dseligo on February 13, 2023, 01:00:50 am
What I see missing are micro and nano (um, nm, us, ns)

And pico if electrical units will be included (needed for Farad, pF).
Title: Re: Type checking of units of measurement
Post by: circular on February 13, 2023, 06:18:47 am
Indeed, well I invite you to have a look at the code and see if you can guess how to add that.
Title: Re: Type checking of units of measurement
Post by: circular on February 19, 2023, 08:03:49 pm
I've added micro, nano and pico.

qk added Siemes, Weber, Tesla and Henry.

marcov, Bogen85, Warfley, ccrause, 440bx, avra, VisualLab you seemed interested by such package. I would say most of the obstacles have been dealt with.

Do you have an actual use of the package, suggestions or contributions to propose?
Title: Re: Type checking of units of measurement
Post by: 440bx on February 20, 2023, 01:07:22 am
marcov, Bogen85, Warfley, ccrause, 440bx, avra, VisualLab you seemed interested by such package. I would say most of the obstacles have been dealt with.

Do you have an actual use of the package, suggestions or contributions to propose?
I'm not, at the moment, working on a physics problem, because of this I currently don't need to deal with units but, it's quite likely that I will sometime in the future.  At that time, I will take your unit out for a "walk" ;)

The biggest problem I'm going to face is getting used to the fact that I can use units.  IOW, I'm completely used to using numbers and being responsible for using them in a way that is dimensionally sensible.
Title: Re: Type checking of units of measurement
Post by: circular on February 26, 2023, 12:08:23 pm
I will be happy to know how it goes when you go for a "walk" with my unit.

There seem to be a bug with the generics, at least on the version of FPC that I am using. Can someone with trunk tell me if the bug still happens?

The problem is the following. I have commented out line 153 of Dim.pas (and its implementation on line 2269)
Code: Pascal  [Select][+][-]
  1. class operator /(const ASelf: TSelf; const AQuantity1: TQuantity1): TQuantity2;

When uncommented, the compiler comlains on the next line that:
Quote
dim.pas(154,20) Error: Function is already declared Public/Forward "operator /(const TDimensionedQuantityProduct$2;const TDimensionedQuantityProduct$2.TDimensionedQuantity$1$crc92C318DE):<record type>; Static;
But the type of the parameter is not the same (TQuantity1 and TQuantity2).

A similar problem happens on line 386 with:
Code: Pascal  [Select][+][-]
  1. class operator /(const {%H-}TheUnit: TSelf; const {%H-}Unit1: TIdentifier1): TIdentifier2;

Maybe I am missing something?
Title: Re: Type checking of units of measurement
Post by: qk on May 13, 2023, 02:50:49 pm
I confirm you that there is the same problem with trunk version.

Code: Pascal  [Select][+][-]
  1. dim.pas(154,20) Error: Function is already declared Public/Forward "operator /(const TQuantityProduct$2;const TQuantityProduct$2.TQuantity$1$crc013BEC18_crcA104356F):TQuantityProduct$2.TQuantity$1$crc013BEC18_crcA104356F; Static;"

Regards

EDIT: The result appears corresponding to the documentation:

Quote
Each specialization of a generic class with the same types as parameters is a new, distinct type, but these types are assignment compatible if the template types used to specialize them are equal.

Reference: https://www.freepascal.org/docs-html/ref/refse57.html (https://www.freepascal.org/docs-html/ref/refse57.html)
Title: Re: Type checking of units of measurement
Post by: circular on May 13, 2023, 09:11:45 pm
Yes, but in this case, the parameters are not the same.

I suspect the type of the second parameter TQuantityProduct$2.TQuantity$1$crc013BEC18_crcA104356F does not contain enough information and thus is confused with another one.
Title: Re: Type checking of units of measurement
Post by: circular on May 14, 2023, 02:24:32 pm
Problem solved because now I use macros to define operators.  :)

I've refactored the code to make it more readable and put stuff in different files.
TinyPortal © 2005-2018