www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - physical units checked at compile time (yet lacking IFTI capabilities)

reply Norbert Nemec <Norbert Nemec-online.de> writes:
I just wanted to try some ideas based on templates. After a long odyssee
through the details of D template programming, I finally succeeded to
arrive at a working piece of code, even though it is still a long way
from where I hoped to arrive.

I think it is interesting to share this experience for several reasons:

* It shows a promising idea how to handle and check physical dimensions
along with unit conversions at compile time.
* It gives a good example where IFTI would be crucial.
* It illustrates a point where IFTI does not work yet.
* Somebody may be able to give me further ideas about how to go on.

***********************************************

Let me explain:

The ultimate goal was to handle physical dimensions as compile time.
Basically, I would then be able to write code like:

-----
| mass m = 0.5 * ounce;
| velocity v = 1.0 * km / hour;
| energy E_kin = 0.5 * m * v*v;
| printf("E_kin = %g kcal\n",E_kin/kcal);
-----

and so on, where all the dimensions are checked at compile time and all
necessary conversions are done automatically.

I started out with the basic idea:

-------------------
| struct physical_quantity(
| 	int m, // length
| 	int s, // time
| 	int kg // mass
| 	// ignore A, K, mol and cd for the moment
| ) {
| 	double value;
| }
|
| alias physical_quantity!(1,0,0) length;
| alias physical_quantity!(0,1,0) time;
| alias physical_quantity!(0,0,1) mass;
|
| const length meter    = { value: 1.0 };
| const time   second   = { value: 1.0 };
| const mass   kilogram = { value: 1.0 };
-------------------

which could be continued like:

-------------------
| alias physical_quantity!(1,-1,0) velocity;
| alias physical_quantity!(2,-2,1) energy;
|
| const time minute = { value: 60.0 };
| const time hour = { value: 3600.0 };
| const velocity speedoflight = { value: 299792458 };
| const energy Joule = { value: 1.0 };
| const energy kcal = { value: 4185.0 };
-------------------

So far, everything worked fine. Now, I wanted to continue defining the
multiplication such that I could do, for example:

-------------------
| physical_quantity!(1,4,-2) v1 = { value: 1.0 };
| physical_quantity!(2,-4,1) v2 = { value: 1.0 };
| physical_quantity!(3,0,-1) prod = v1 * v2;
| // internally, equivalent to: prod.value = v1.value * v2.value;
-------------------

but I failed horribly trying to define this multiplication.

The most promising try so far was:

-------------------------------
| struct physical_quantity(
|     int m,
|     int s,
|     int kg
| ) {
|     double value;
|
|     const int _m = m;
|     const int _s = s;
|     const int _kg = kg;
|
|     template opMul(T) {   // line 12
|         physical_quantity!(m+T._m,s+T._s,kg+T._kg) opMul(T other) {
|             physical_quantity!(m+T._m,s+T._s,kg+T._kg) res;
|             res.value = value*other.value;
|             return res;
|         }
|     }
| };
|
| int main(char[][] args)
| {
|     physical_quantity!(1,0,0) v1; v1.value = 1.0;
|     physical_quantity!(0,1,0) v2; v2.value = 1.0;
|     physical_quantity!(1,1,0) prod = v1*v2;      // line 25
|     printf("%f\n",prod.value);
|     return 0;
| }
--------------------------------

which fails with the compiler error:

------------
| units_minimal.d(25): incompatible types for ((v1) * (v2)):
'physical_quantity' and 'physical_quantity'
| units_minimal.d(25): 'v1' is not an arithmetic type
| units_minimal.d(25): 'v2' is not an arithmetic type
| units_minimal.d(25): cannot implicitly convert expression ((v1) *
(v2)) of type physical_quantity to physical_quantity
------------

My guess was that the problem is with the operator overloading, and
indeed, replacing line 25 by
-------------
|     physical_quantity!(1,1,0) prod = v1.opMul(v2);      // line 25
-------------

changes the compiler error to:

------------
| units_minimal.d(12): template
units_minimal.physical_quantity!(1,0,0).physical_quantity.opMul(T)
templates don't have properties
| units_minimal.d(25): function expected before (), not v1 of type
physical_quantity
| units_minimal.d(25): cannot implicitly convert expression ((v1)((v2)))
of type int to physical_quantity
------------

My next guess was, that the IFTI causes the problem, so I changed lines
12 and 25 to

------------
|     template T_opMul(T) {   // line 12
------------
|     physical_quantity!(1,1,0) prod =
v1.T_opMul!(typeof(v2)).opMul(v2);      // line 25
------------

and now the program worked correctly.

Of course, in this state, the whole code is far from useful. Without
IFTM and operator overloading, the whole point of the library is moot:
allowing readable code with compile-time error checking.

Even if IFTI and operators would work, there is, of course, some way to
go for a working library, but I am confident, that this it will be
possible to go that way.

Greetings,
Norbert
Apr 23 2006
parent reply BCS <BCS_member pathlink.com> writes:
I feel your pain. A few post back I include a link to a unit.d program that does
just what you are trying to do, but a run time (with all of the costs :-p that
entails, OTOH it lets you do units dynamically) I have bean waiting for better
template support to do exactly the same project you are looking at. Until that
happens I think we're sunk.

I was looking at something like this:

struct Unit(T, int L, int M, int T)
{
T val;

Unit(T, L, M, T) opAdd(Unit(T, L, M, T) op)
{
Unit(T, L, M, T) ret = { val : val+op.val };
return ret;
}

// this will need some improvements to templates
Unit(T, L+opL, M+opM, T+opT) opMul(Unit(T,opL, opM, opT) op)
{
Unit(T, L+opL, M+opM, T+opT) ret = { val : val*op.val };
return ret;
}
..
}

Lets hope walter makes some of these possible in the near future.



In article <e2gc64$bqv$1 digitaldaemon.com>, Norbert Nemec says...
I just wanted to try some ideas based on templates. After a long odyssee
through the details of D template programming, I finally succeeded to
arrive at a working piece of code, even though it is still a long way
from where I hoped to arrive.

I think it is interesting to share this experience for several reasons:

* It shows a promising idea how to handle and check physical dimensions
along with unit conversions at compile time.
* It gives a good example where IFTI would be crucial.
* It illustrates a point where IFTI does not work yet.
* Somebody may be able to give me further ideas about how to go on.

***********************************************

Let me explain:

The ultimate goal was to handle physical dimensions as compile time.
Basically, I would then be able to write code like:

-----
| mass m = 0.5 * ounce;
| velocity v = 1.0 * km / hour;
| energy E_kin = 0.5 * m * v*v;
| printf("E_kin = %g kcal\n",E_kin/kcal);
-----

and so on, where all the dimensions are checked at compile time and all
necessary conversions are done automatically.

I started out with the basic idea:

-------------------
| struct physical_quantity(
| 	int m, // length
| 	int s, // time
| 	int kg // mass
| 	// ignore A, K, mol and cd for the moment
| ) {
| 	double value;
| }
|
| alias physical_quantity!(1,0,0) length;
| alias physical_quantity!(0,1,0) time;
| alias physical_quantity!(0,0,1) mass;
|
| const length meter    = { value: 1.0 };
| const time   second   = { value: 1.0 };
| const mass   kilogram = { value: 1.0 };
-------------------

which could be continued like:

-------------------
| alias physical_quantity!(1,-1,0) velocity;
| alias physical_quantity!(2,-2,1) energy;
|
| const time minute = { value: 60.0 };
| const time hour = { value: 3600.0 };
| const velocity speedoflight = { value: 299792458 };
| const energy Joule = { value: 1.0 };
| const energy kcal = { value: 4185.0 };
-------------------

So far, everything worked fine. Now, I wanted to continue defining the
multiplication such that I could do, for example:

-------------------
| physical_quantity!(1,4,-2) v1 = { value: 1.0 };
| physical_quantity!(2,-4,1) v2 = { value: 1.0 };
| physical_quantity!(3,0,-1) prod = v1 * v2;
| // internally, equivalent to: prod.value = v1.value * v2.value;
-------------------

but I failed horribly trying to define this multiplication.

The most promising try so far was:

-------------------------------
| struct physical_quantity(
|     int m,
|     int s,
|     int kg
| ) {
|     double value;
|
|     const int _m = m;
|     const int _s = s;
|     const int _kg = kg;
|
|     template opMul(T) {   // line 12
|         physical_quantity!(m+T._m,s+T._s,kg+T._kg) opMul(T other) {
|             physical_quantity!(m+T._m,s+T._s,kg+T._kg) res;
|             res.value = value*other.value;
|             return res;
|         }
|     }
| };
|
| int main(char[][] args)
| {
|     physical_quantity!(1,0,0) v1; v1.value = 1.0;
|     physical_quantity!(0,1,0) v2; v2.value = 1.0;
|     physical_quantity!(1,1,0) prod = v1*v2;      // line 25
|     printf("%f\n",prod.value);
|     return 0;
| }
--------------------------------

which fails with the compiler error:

------------
| units_minimal.d(25): incompatible types for ((v1) * (v2)):
'physical_quantity' and 'physical_quantity'
| units_minimal.d(25): 'v1' is not an arithmetic type
| units_minimal.d(25): 'v2' is not an arithmetic type
| units_minimal.d(25): cannot implicitly convert expression ((v1) *
(v2)) of type physical_quantity to physical_quantity
------------

My guess was that the problem is with the operator overloading, and
indeed, replacing line 25 by
-------------
|     physical_quantity!(1,1,0) prod = v1.opMul(v2);      // line 25
-------------

changes the compiler error to:

------------
| units_minimal.d(12): template
units_minimal.physical_quantity!(1,0,0).physical_quantity.opMul(T)
templates don't have properties
| units_minimal.d(25): function expected before (), not v1 of type
physical_quantity
| units_minimal.d(25): cannot implicitly convert expression ((v1)((v2)))
of type int to physical_quantity
------------

My next guess was, that the IFTI causes the problem, so I changed lines
12 and 25 to

------------
|     template T_opMul(T) {   // line 12
------------
|     physical_quantity!(1,1,0) prod =
v1.T_opMul!(typeof(v2)).opMul(v2);      // line 25
------------

and now the program worked correctly.

Of course, in this state, the whole code is far from useful. Without
IFTM and operator overloading, the whole point of the library is moot:
allowing readable code with compile-time error checking.

Even if IFTI and operators would work, there is, of course, some way to
go for a working library, but I am confident, that this it will be
possible to go that way.

Greetings,
Norbert
Apr 23 2006
parent reply Norbert Nemec <Norbert Nemec-online.de> writes:
BCS wrote:
 I feel your pain. A few post back I include a link to a unit.d program that
does
 just what you are trying to do, but a run time (with all of the costs :-p that
 entails, OTOH it lets you do units dynamically) I have bean waiting for better
 template support to do exactly the same project you are looking at. Until that
 happens I think we're sunk.
 
 I was looking at something like this:
 
 struct Unit(T, int L, int M, int T)
 {
 T val;
 
 Unit(T, L, M, T) opAdd(Unit(T, L, M, T) op)
 {
 Unit(T, L, M, T) ret = { val : val+op.val };
 return ret;
 }
 
 // this will need some improvements to templates
 Unit(T, L+opL, M+opM, T+opT) opMul(Unit(T,opL, opM, opT) op)
 {
 Unit(T, L+opL, M+opM, T+opT) ret = { val : val*op.val };
 return ret;
 }
 ..
 }
Pretty much the same thing.
 Lets hope walter makes some of these possible in the near future.
That's one of the main intentions of my detailed post. Walter clearly stated that the current IFTI mechanism is rudimentary and that he intends to improve it. Giving clear use cases may help in finding out the details of what is really needed. The difficulty is, that D templates are so fundamentally different from C++ templates, that it really is hard to notice which features are really missing in D and which things should simply be done in a different way. Coming from C++, meta-programming in D asks for a completely different way of thinking, yet it is important not to throw overboard all the experience that was gained in C++.
Apr 23 2006
parent reply Dan <Dan_member pathlink.com> writes:
I don't mean to rain on your parade, but I tend to think the units should be
under a namespace other than the global one.

I also think the unit.d should most definitely be inlined because the few dude
already wrote are a drop in the bucket compared to the full barrage of units
that we use.  There will be *alot* of code in that file that will be useless in
any given program.

Uncompiled code size doesn't matter for anything other than the amount of time
spent compiling (and scrolling).  Templates aren't needed, they just make code
more legible.

Well anyways, I'm glad it's being figured out.  I do wish it was part of phobos
though because where there are numbers, there are usually units.  Most of the
time the Powers That Be(tm) care that we don't mistake our meters for our feet.
A good unit conversion table is an important asset to have around.

Well.. thanks!
Apr 24 2006
parent Norbert Nemec <Norbert Nemec-online.de> writes:
Dan wrote:
 I don't mean to rain on your parade, but I tend to think the units should be
 under a namespace other than the global one.
Maybe. I did not even think about that, yet. Now, thinking of it, I'm not sure whether I would go for a separate namespace. Of course, they will be in a separate module, but inside that, I would rather choose full names like "meter", "liter", "second", "hour" etc. instead of abbreviations ("m", "l", "s", "h"). And with these full names, I think the namespace-pollution is not that large: only few unit names are really likely to be used for other symbols in a program (especially in a program that deals with physics)
 I also think the unit.d should most definitely be inlined because the few dude
 already wrote are a drop in the bucket compared to the full barrage of units
 that we use.  There will be *alot* of code in that file that will be useless in
 any given program.
Guess, the units should be grouped into sub-modules. If you want to use only SI units, you don't need the thousands of ancient units that can be found in history books.
 
 Uncompiled code size doesn't matter for anything other than the amount of time
 spent compiling (and scrolling).  Templates aren't needed, they just make code
 more legible.
Sorry, I don't get your point here. The templates in my proof-of-concept code are essential for the functionality and have nothing to do with legibility. Each physical dimension defines a type (like meter, squaremeter, cubicmeter, ...) the list of these possible types is infinite, so you cannot explicitely define all of them by hand. Only a limited set of types has legible names (which are defined as aliases) but also combined dimensions like m^3*s/kg^13 could occur internally and are then simply instantiated when needed. Greetings, Norbert
Apr 24 2006