[ANN] Ruby Units

K

Kevin Olbrich

Announcing......

Ruby Units

Version: 0.1.0
Kevin C. Olbrich, Ph.D.
http://www.sciwerks.com

Project page: http://rubyforge.org/projects/ruby-units

==Introduction
Many technical applications make use of specialized calculations at some
point. Frequently, these calculations require unit conversions to
ensure accurate results. Needless to say, this is a pain to properly
keep track of, and is prone to numerous errors.

==Solution
The 'Ruby units' gem is designed so simplify the handling of units for
scientific calculations. The units of each quantity are specified when
a Unit object is created and the Unit class will handle all subsequent
conversions and manipulations to ensure an accurate result.

==Installation:
This package may be installed using:
gem install ruby-units

==Usage:
unit = Unit.new("1") # constant only
unit = Unit.new("mm") # unit only (defaults to a value of 1)
unit = Unit.new("1 mm") # create a simple unit
unit = Unit.new("1 mm/s") # a compound unit
unit = Unit.new("1 mm s^-1") # in exponent notation
unit = Unit.new("1 kg*m^2/s^2") # complex unit
unit = Unit.new("1 kg m^2 s^-2") # complex unit
unit = Unit("1 mm") # shorthand
unit = "1 mm".to_unit # convert string object
unit = object.to_unit # convert any object using object.to_s

==Rules:
1. only 1 quantity per unit (with 2 exceptions... 6'5" and '8 lbs 8 oz')
2. use SI notation when possible
3. avoid using spaces in unit names

==Unit compatability:
Many methods require that the units of two operands are compatible.
Compatible units are those that can be easily converted into each other,
such as 'meters' and 'feet'.

unit1 =~ unit2 #=> true if units are compatible

==Unit Math:
Unit#+():: Add. only works if units are compatible
Unit#-():: Subtract. only works if units are compatible
Unit#*():: Multiply.
Unit#/():: Divide.
Unit#**():: Exponentiate. Exponent must be an integer, can be
positive, negative, or zero
Unit#inverse:: Returns 1/unit
Unit#abs:: Returns absolute value of the unit quantity. Strips off
the units
Unit#ceil:: rounds quantity to next highest integer
Unit#floor:: rounds quantity down to next lower integer
Unit#round:: rounds quantity to nearest integer
Unit#to_int:: returns the quantity as an integer

Unit will coerce other objects into a Unit if used in a formula. This
means that ..

Unit("1 mm") + "2 mm" == Unit("3 mm")

This will work as expected so long as you start the formula with a Unit
object.

==Conversions & comparisons

Units can be converted to other units in a couple of ways.

unit1 = unit >> "ft" # => convert to 'feet'
unit >>= "ft" # => convert and overwrite original object
unit3 = unit1 + unit2 # => resulting object will have the units of unit1
unit3 = unit1 - unit2 # => resulting object will have the units of unit1

unit1 <=> unit2 # => does comparison on quantities in base
units, throws an exception if not compatible
unit1 === unit2 # => true if units and quantity are the same,
even if 'equivalent' by <=>

==Text Output
Units will display themselves nicely based on the preferred abbreviation
for the units and prefixes.
Since Unit implements a Unit#to_s, all that is needed in most cases is:
"#{Unit.new('1 mm')}" #=> "1 mm"

The to_s also accepts some options.
Unit.new('1.5 mm').to_s("%0.2f") # => "1.50 mm". Enter any valid
format string
Unit.new('1.5 mm').to_s("in") # => converts to inches before printing
Unit.new("2 m").to_s:)ft) #=> returns 6'7"
Unit.new("100 kg").to_s:)lbs) #=> returns 220 lbs, 7 oz

Feedback is welcome, as are feature suggestions.

This release covers most SI, imperial, and customary units.

_Kevin
www.sciwerks.com
 
T

Trans

Kevin said:
The concept isn't new, but I think this approach has the fewest
limitations and is the most general.

Besides, it was a good learning experince.

Nice work. Though, you may be interested in Facets' units.rb, written
by Peter Vanbroekhoven. It is about as complete as I can imagine it can
get.

http://facets.rubyforge.org

I think the currency conversion functionality is "off line" though. I
need to fix that. It wuld be great if you took a look, maybe you would
like to join the Facets team and work in innovations of your system. I
have for sometime been thinking of releasing parts of Facets in
"bite-sized" portions, staring in fact with units.rb, but I have bigger
plans for distribution, so I've put it off.

T.
 
P

Peter Vanbroekhoven

Nice work. Though, you may be interested in Facets' units.rb, written
by Peter Vanbroekhoven. It is about as complete as I can imagine it can
get.

http://facets.rubyforge.org

I think the currency conversion functionality is "off line" though. I
need to fix that. It wuld be great if you took a look, maybe you would
like to join the Facets team and work in innovations of your system. I
have for sometime been thinking of releasing parts of Facets in
"bite-sized" portions, staring in fact with units.rb, but I have bigger
plans for distribution, so I've put it off.

If someone plans to work on units.rb, it might be interesting to know
I have a newer version lying around, but I haven't gotten around to
completing it or thoroughly testing it. Trans, I'll send it to you,
and you can arrange its further completion at your leisure.
 
K

Kevin Olbrich

Nice work. Though, you may be interested in Facets' units.rb, written
by Peter Vanbroekhoven. It is about as complete as I can imagine it can
get.

http://facets.rubyforge.org

I think the currency conversion functionality is "off line" though. I
need to fix that. It wuld be great if you took a look, maybe you would
like to join the Facets team and work in innovations of your system. I
have for sometime been thinking of releasing parts of Facets in
"bite-sized" portions, staring in fact with units.rb, but I have bigger
plans for distribution, so I've put it off.

T.

Hey, amazing all the interesting stuff that pops up when you know where
to look for it.

After a 15 minute look, I can see some issues with the current version
of Units in facets.

1. It doesn't implement Comparable.
1.cm < 2.m #=> throws an exception in Facets
"1 cm".unit < "2 m".unit #=> returns true in ruby-units

2. The binary prefixes are incorrect (at least in the RDoc). They
should be like 'kibi = 2**10' and 'mebi = 2**20'.

3. Facet Units doesn't seem to do temperature conversions. Maybe it
does, but I can't find any documentation on it.

4. I haven't found a simple way to convert a string into a Unit using
Facets. One reason that ruby-units does this is because I wanted to
make it easy for users to provide units as input and then parse them.
The facets Units seem to work fine if you know which units you want to
use when you write your program.

5. ruby-units is much more flexible in terms of what units it recognizes
with the default configuration.
1.attoparsec/microfortnight.to(inch/s) # => fails in Facets
"1 attoparsec/microfortnight".unit("in/s") #=> "1.0043269330917 in/s"
in ruby-units

6. Facets seems to get in trouble when the name of a unit collides with
a ruby keyword
1.m.to(in) #=> causes a parse error in Facets
This isn't an issue in ruby-units since all the units are specified as
strings.

7. ruby-units does not do dynamic currency conversions right now.

8. Facet Units does not seem to implement may Numeric methods like (abs,
ceil, floor, to_i, round, etc..). This works as you would expect with
ruby-units.

9. Facet Units seems to be unable to simplify units in some cases..
(1.m * 1.m) / (1.cm * 1.cm) #=> 1 m**2/cm**2
I can't figure out how to convert this result to a float.

In ruby-units, the equivalent would be.
("1 m".unit * "1 m".unit / ("1 cm".unit * "1 cm".unit)).to_base.to_f
#=> 10000.0

Disclaimer: Some of these observations may be erroneous due to my
unfamiliarity with this package. Feel free to correct them.

I'd be happy to work with Facets to improve it's handling of units.

_Kevin
www.sciwerks.com
 
P

Peter Vanbroekhoven

Hey, amazing all the interesting stuff that pops up when you know where
to look for it.

After a 15 minute look, I can see some issues with the current version
of Units in facets.

1. It doesn't implement Comparable.
1.cm < 2.m #=> throws an exception in Facets
"1 cm".unit < "2 m".unit #=> returns true in ruby-units

2. The binary prefixes are incorrect (at least in the RDoc). They
should be like 'kibi = 2**10' and 'mebi = 2**20'.

I have no problem adding these. However, personally, I don't like
kibibyte and prefer kilobyte, and I expect I'm not alone. Facets'
units.rb is flexible enough to allow kibi = 2**10 and kilo = 10**3
(following the IEC convention,
http://en.wikipedia.org/wiki/Binary_prefix) next to kilo = 2**10. The
only issue is which to make the default.
3. Facet Units doesn't seem to do temperature conversions. Maybe it
does, but I can't find any documentation on it.

It doesn't as temperature conversions are ill-defined. As you know,
both absolute temperatures and temperature differences have the same
unit. Any operation on two temperatures expressed in different units
cannot be correct for absolute temperatures and temperature
differences. This is an inherent problem, so we had three choices: do
no conversions, use the conversion for absolute temperatures, or use
the conversion for temperature differences. We chose the first of
these three. We're open to additional ideas on this.
4. I haven't found a simple way to convert a string into a Unit using
Facets. One reason that ruby-units does this is because I wanted to
make it easy for users to provide units as input and then parse them.
The facets Units seem to work fine if you know which units you want to
use when you write your program.

I remember implementing this, so it should be present in the version I
sent Trans.
5. ruby-units is much more flexible in terms of what units it recognizes
with the default configuration.
1.attoparsec/microfortnight.to(inch/s) # => fails in Facets
"1 attoparsec/microfortnight".unit("in/s") #=> "1.0043269330917 in/s"
in ruby-units

6. Facets seems to get in trouble when the name of a unit collides with
a ruby keyword
1.m.to(in) #=> causes a parse error in Facets
This isn't an issue in ruby-units since all the units are specified as
strings.

This is indeed a bit of a problem. Again, if I remember correctly, the
new version should handle strings as well, though maybe/probably not
for every operation.
7. ruby-units does not do dynamic currency conversions right now.

8. Facet Units does not seem to implement may Numeric methods like (abs,
ceil, floor, to_i, round, etc..). This works as you would expect with
ruby-units.

9. Facet Units seems to be unable to simplify units in some cases..
(1.m * 1.m) / (1.cm * 1.cm) #=> 1 m**2/cm**2
I can't figure out how to convert this result to a float.

In ruby-units, the equivalent would be.
("1 m".unit * "1 m".unit / ("1 cm".unit * "1 cm".unit)).to_base.to_f
#=> 10000.0

I remember that this is by design, although I don't remember the
actual reason behind this design decision. I think it was because this
automatic conversion may be unwanted because the conversion introduces
rounding errors in some cases, and it is better to postpone the
conversion until the programmer explicitly requests this. This is done
using #to, but that doesn't work for unitless quantities as in your
example above (unless you do .to(m/m) or so), so we need a to_f method
indeed.
Disclaimer: Some of these observations may be erroneous due to my
unfamiliarity with this package. Feel free to correct them.

I have a disclaimer of my own: it is not a finished library, and all
of the issues above that I haven't addressed were planned to be added
at some point. I have been focussing my attention on getting the base
functionality right. Your observations above shouldn't be hard to add.
I'd be happy to work with Facets to improve it's handling of units.

I'll work on addressing your observations above this evening, and I'll
send the new code to Trans and you. Thanks for your interest, and your
help.

Peter
 
K

Kevin Olbrich

I have no problem adding these. However, personally, I don't like
kibibyte and prefer kilobyte, and I expect I'm not alone. Facets'
units.rb is flexible enough to allow kibi = 2**10 and kilo = 10**3
(following the IEC convention,
http://en.wikipedia.org/wiki/Binary_prefix) next to kilo = 2**10. The
only issue is which to make the default.

My only suggestion is to name the binary prefixes correctly, and allow
people to use the SI ones as well. I've tried to follow the standards
when possible, even if I don't like Mebibits.
It doesn't as temperature conversions are ill-defined. As you know,
both absolute temperatures and temperature differences have the same
unit. Any operation on two temperatures expressed in different units
cannot be correct for absolute temperatures and temperature
differences. This is an inherent problem, so we had three choices: do
no conversions, use the conversion for absolute temperatures, or use
the conversion for temperature differences. We chose the first of
these three. We're open to additional ideas on this.

My solution to this was to scale the temperature units by size (as a
differential) except in the case where the conversion was from one
temperature unit to another. In that case, it does the correct conversion.

When temperatures are used in compound units, they never represent an
absolute temperature value. If you do the absolute conversion when they
are the only units there, then you can do proper temperature math.

Although it appears that my solution has it's own problems. You can add/
subtract temperatures and the get the new absolute value, but you may
want the differential value instead. I'll have to think about that one
a bit more.


_Kevin
www.sciwerks.com
 
C

Calamitas

Peter Vanbroekhoven wrote:

/ ...


No, actually, temperature conversions are perfectly well-defined.
Unfortunately, absolute temperature conversions require two mathematical
operations instead of one (a multiplier and an offset), which sets them
apart from nearly all other conversions. It even sets them apart from
temperature differential conversions.


That is because converting a temperature differential is not the same as
converting a temperature. But each of the operations is clearly defined.


There is a third choice -- convert temperatures and temperature
differentials separately. Name one absolute temperatures, and the other
delta-t or something similar.

It is basically a Calculus issue. You may know that taking a differential
(or a derivative) involves the loss of information, and when the converse
operation (integration, a term only loosely applicable here) is performed,
a constant (usually unknown) is included to remind the practitioner about
the lost information. In the same way, moving from a temperature to a
differential of temperature loses information, so the reciprocal operation
(difference to absolute) must be carefully handled (when it can be
performed at all).

Someone versed in Calculus wouldn't think of mixing functions and their
derivatives without being vigilant that they represent different things.
The temperature conversion problem should be looked on in the same way.


Well, this is just something to think about. I've written a lot of unit
conversion routines over the years and I haven't considered handling
temperatures and temperature differentials separately, but it's an obvious
solution.

We have considered this option too, but because I started by saying
that both temperatures and temperature differentials have the same
units, I didn't arrive at that option in my reasoning. If I remember
correctly, we discarded this option because it was a bit weird for a
simple units library. This actually goes beyond the units part, it is
about interpretation. It's like you say, it's a calculus-y issue (I
think it's actually an algebra issue as it is not differentiation, but
that's not important), and we felt that that belongs in a
calculus/algebra/scientific library. So let me refine my statement
above: conversions of temperatures consisting only of a value and a
temperature unit are ill-defined. More information is needed, and we
felt back then that it should not be part of a units library. Of
course, this means we can do no conversion, so I'm considering
changing my opinion on this.
I just noticed something. The Linux utility "units" handles both temperature
and temperature differences, and they are distinguished by syntax:

$ units "tempC(0)" tempF # absolute temperature
32

$ units "32 degF" degC # temperature difference
* 17.777778
/ 0.05625

So it seems it has been done.

We considered something like this too, but what bothers me is that
tempC, tempF, degC and degF are no actual units.

I think this is an issue with the people who started this library both
being perfectionists. None of the solutions we could think of, are
perfect.

I'll see if I can get a tempC-degC like scheme to work, and then we
can still decide this afterwards.

Peter
 
A

ara.t.howard

We considered something like this too, but what bothers me is that
tempC, tempF, degC and degF are no actual units.

I think this is an issue with the people who started this library both
being perfectionists. None of the solutions we could think of, are
perfect.

I'll see if I can get a tempC-degC like scheme to work, and then we
can still decide this afterwards.

http://www.gnu.org/software/units/manual/units.html
http://www.gnu.org/software/units/manual/units.html#tempconvert

http://narray.rubyforge.org/quanty/quanty-en.html

regards.

-a
 
K

Kevin Olbrich

We have considered this option too, but because I started by saying
that both temperatures and temperature differentials have the same
units, I didn't arrive at that option in my reasoning. If I remember
correctly, we discarded this option because it was a bit weird for a
simple units library. This actually goes beyond the units part, it is
about interpretation. It's like you say, it's a calculus-y issue (I
think it's actually an algebra issue as it is not differentiation, but
that's not important), and we felt that that belongs in a
calculus/algebra/scientific library. So let me refine my statement
above: conversions of temperatures consisting only of a value and a
temperature unit are ill-defined. More information is needed, and we
felt back then that it should not be part of a units library. Of
course, this means we can do no conversion, so I'm considering
changing my opinion on this.


We considered something like this too, but what bothers me is that
tempC, tempF, degC and degF are no actual units.

I think this is an issue with the people who started this library both
being perfectionists. None of the solutions we could think of, are
perfect.

I'll see if I can get a tempC-degC like scheme to work, and then we
can still decide this afterwards.

Peter

After thinking about this a bit, here are a few things that might be handy.

1. Temperature is not a unit, it's a property that is measured in degrees.
2. That measurement is always relative to a fixed value, so really all
temperature values are differences
3. The number of real calculations in which you would want to use the
absolute temperature instead of some delta of temperature is exceedingly
small.

Consequently, the approach I am currently taking is to
* represent all temperatures as differences.
* provide a helper function that does the conversion from one
temperature scale to another for those times when you really need it.
* document the heck out of this

So far it seems to work pretty well


_Kevin
www.sciwerks.com
 
C

Calamitas

I'll work on addressing your observations above this evening, and I'll
send the new code to Trans and you. Thanks for your interest, and your
help.

I've sent my code to Trans. If your interested, please contact him.

I've not addressed the temperature problem as I'm still not very happy
with it. My current opinion is that maybe temperature conversion
should be done by separate methods, and only Kelvin should be allowed
in the whole units machinery. As Kelvin has the absolute zero
temperature as reference point, there are no problems in its use.
Besides, we were taught in science class to first convert all
temperatures to Kelvin, then do all calculations, and then convert
temperatures back if necessary, so this restriction should pose few
problems (for me at least).

Peter
 
H

Hal Fulton

Paul said:
Calamitas wrote:




If you only have Kelvin and no other temperature units, no conversions can
be made.

Since he says "in the machinery," I think he means internally it would
all be in Kelvin.

I'm assuming every measure has a "default" unit (e.g., meter) and that
all conversions go there first.

Maybe not -- but that's how I'd do it. Convert feet to furlongs by first
converting feet to meters and then meters to furlongs. That way there
are fewer conversion factors required. Of course, significant digits
become a problem, but I have to assume the library user worries about
that.


Hal
 
K

Kevin Olbrich

Since he says "in the machinery," I think he means internally it would
all be in Kelvin.

I'm assuming every measure has a "default" unit (e.g., meter) and that
all conversions go there first.

Maybe not -- but that's how I'd do it. Convert feet to furlongs by first
converting feet to meters and then meters to furlongs. That way there
are fewer conversion factors required. Of course, significant digits
become a problem, but I have to assume the library user worries about
that.


Hal

Yes, you need to do a lot of conversion to base units to make things
work nicely.

BTW...

def test_ideal_gas_law
p = Unit "100 kPa"
v = Unit "1 m^3"
n = Unit "1 mole"
r = Unit "8.31451 J/mol*degK"
t = ((p*v)/(n*r)).to("tempF")
assert_in_delta 21189.2,t.to_f, 0.1
end

p t.to_s #=> '21189.2 degF'

This test passes.

Note the use if the 'tempF' unit for conversion. This unit doesn't
really exist in ruby-units, it just tells the conversion routine to do
the proper math to convert the internal differential temperature units
into an absolute one.

This seems like an acceptable compromise to me.

_Kevin
www.sciwerks.com
 
C

Calamitas

If you only have Kelvin and no other temperature units, no conversions can
be made.


Rankine also has absolute zero as its reference point. Rankine = Kelvin *
1.8.


Except, unless I have misunderstood you, you won't have any other
temperature units.

I think that you may indeed have misunderstood. What I meant is that
I'd allow all temperature units in explicit conversion (using #to, or
possibly some extra methods), but not in other manipulatons of
quantities (+, *, etc) where only Kelvin would be allowed, and
possibly Rankine too.

As the library works now, quantities like 1 m/s can be converted to
other units by explicit request from the programmer or implicitly when
adding/subtracting/comparing two quantities. You cannot do this with
absolute temperatures, because that would lead to things like:

2 K / 2s = - 271.15 C / 2s = -135.575 C/s
= 1 K / s = -272.15 C/s

So absolute temperatures can only be converted if not combined with
another unit. Note that it does make sense to combine absolute
temperatures with other units as quite some physical equations involve
absolute temperatures (for instance the ideal gas law), though only
when using Kelvins.

So to keep things simple, I'd only allow conversions between
temperatures by themselves (both absolute and differential), and only
allow Kelvin/Rankine in other operations. I could allow Fahrenheit and
Celsius as well, with the understanding that they have to be
differentials, or be relative to absolute zero. However, this adds
some extra conceptual complexity, and remembering from science class
that conversion of all temperatures to Kelvin (or Rankine) is good
practice, it seems better to just disallow Fahrenheit and Celsius from
anything but simple temperature conversions. As this would make those
two a major special case, they wouldn't fit into the existing
machinery, and I'm still contemplating whether or not to integrate it
into this machinery, or to keep it separate.

I hope this makes clear what I meant, and why.

Peter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top