mhuggins / ruby-measurement

Simple Ruby gem for calculating and converting measurements.
MIT License
46 stars 21 forks source link

Compound unit support? #10

Open woahdae opened 8 years ago

woahdae commented 8 years ago

I'm working on a sideproject that needs a lot of custom conversions, which is why I like ruby-measurement over ruby-units; the latter's code takes quite a lot of staring to grok, and ultimately the conversions are hard-coded, which I found odd (but maybe I don't know the problemspace well enough?).

In any case, ruby-measurement's code is easy to read and defining custom conversions is a no-brainer.

However, most of the units and conversions I'm working with are compound, like converting pounds/acre to oz/sq ft. For now I'll just define custom units and conversions for all the variations I'm using (not too onerous), but is there planned support for compound units that's more clever than brute-force defining every possibility?

mhuggins commented 8 years ago

That sounds like a solid use case for defining custom units & conversions. I think there are too many potential compound units to try to include them in the gem itself.

mhuggins commented 8 years ago

As a quick example:

Measurement.define(:"lbs/ac") do |unit|
  unit.alias :"lbs./ac.", :"pounds/acre"
  unit.convert_to(:"oz/sq ft") { |value| value / 2722.5 }
end

Measurement.define(:"oz/sq ft") do |unit|
  unit.alias :"oz/sq. ft.", :"oz/sq ft."
  unit.convert_to(:"lbs/ac") { |value| value * 2722.5 }
end
woahdae commented 8 years ago

Here's what it currently looks like, and I think you can see where official support for understanding numerators and denominators would be nice:

Measurement.define('acre') do |unit|
  unit.alias 'ac'
  unit.convert_to('sq ft') { |value| value * 43560}
end

Measurement.define('lb/ac') do |unit|
  unit.alias "lb / ac", "lbs/ac", 'lb/acre', 'lbs/acre', 'lb / acre', 'lbs / acre', 'lbs / acre', "lbs / ac", "lb per acre", "lbs per acre"
  unit.convert_to('lb/sq ft') {|value| value / 43560}
  unit.convert_to('oz/sq ft') {|value| value / 43560 / 16}
  unit.convert_to('ppm') {|value| value / 2}
end

Measurement.define('lb/sq ft') do |unit|
  unit.alias "lb / sq ft", "lbs/sq ft", "lbs / sq ft", "lb per square foot", "lbs per square foot"
  unit.convert_to('lb/ac') {|value| value * 43560}
  unit.convert_to('oz/sq ft') {|value| value * 16}
  unit.convert_to('ppm') {|value| value / 2 * 43560}
end

Measurement.define('oz/sq ft') do |unit|
  unit.alias "oz / sq ft", "ounces/sq ft", "oz per square foot", "ounces per square foot"
  unit.convert_to('lb/ac') {|value| value * 43560 / 16}
  unit.convert_to('lb/sq ft') {|value| value / 16}
  unit.convert_to('ppm') {|value| value * 43560 / 16 / 2}
end

Measurement.define('ppm') do |unit|
  unit.convert_to('lb/ac') {|value| value * 2}
  unit.convert_to('lb/sq ft') {|value| value * 2 / 43560}
  unit.convert_to('oz/sq ft') {|value| value * 2 / 43560 / 16}
end

And I just covered a few of the common aliases. The number of aliases for a compound unit is numerator aliases * denominator aliases * [space variations around '/'] * 2 ["per"], if you want to support compound unit aliases.

Plus, clearly, for many conversions I'm using a base unit as part of the calculation, and it's at least error-prone to enumerate all the definitions like this. If you knew the relationship between the numerators and denominators separately, you could just define the conversion of an acre to square feet (for example), and we already know the rest.

Finally, 'ppm' is a great example of a sticking point, but also a strength of your library. 'ppm' is a compound unit translating to pounds-per-acre just for my problemspace. But, I think someone could come up with a way of defining that conversion and having the rest fall out. It might need knowledge of unit categories, for example, defining 'ppm' -> 'lb/sq ft' lets it be known that ppm converts to weight/volume. So, that's a tough one.

woahdae commented 8 years ago

Oh, and of course, multiplying compound units needs official support (or hacks on my end). Ex: Measurement.parse("10 lbs/acre") * Measurement.parse("2 acres") should yield 20 lbs (or even more useful, Measurement.parse("10 lbs/acre") * Measurement.parse("1 sq ft") should yield 0.000229568 lbs), but the concept of "converting" lbs/acre to lbs doesn't really make sense, right?

I might need to end up writing a lot of custom conversion logic for my problemspace anyways, so maybe I'll have more insight on how to model this at some later point.

mhuggins commented 8 years ago

The math part of your argument makes sense. I think multiplying compound units and getting a new unit would be a great feature. It's a big undertaking though, and with my focus on other things at the moment, I don't see it being a feature that I get to unfortunately. With that said, it's something that I would be happy to support if you or someone else felt like contributing to the gem!