quantity 0.0.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ require 'quantity'
2
+ require 'quantity/systems/si'
3
+ # this could just as well be 'imperial', had to pick something.
4
+ require 'quantity/systems/us'
5
+ require 'quantity/systems/information'
6
+ require 'quantity/systems/enumerable'
7
+ # other isn't done yet
8
+ #require 'quantity/systems/other'
@@ -0,0 +1,269 @@
1
+ class Quantity
2
+ # A Dimension is a measurable something. It is often
3
+ # called a 'base quantity'.
4
+ #
5
+ # There are 8 base physical dimensions: Length, Mass,
6
+ # Current, Mass, Time, Temperature, Substance, and Luminosity.
7
+ # There are additionally a number of other useful dimensions,
8
+ # such as Enumerable items (think of 'dozen' as a unit of
9
+ # measurement).
10
+ #
11
+ # From these base dimensions all other measurement dimensions
12
+ # can be constructed. For example, speed is Length / Time.
13
+ # Such dimensions are called compound dimensions.
14
+ #
15
+ class Dimension
16
+ ### Class-level methods/vars
17
+ # all known dimensions
18
+ @@dimensions = {}
19
+
20
+ # The dimension for a given symbol, dimension, or string description
21
+ # of a compound dimension
22
+ # @param [String Symbol Dimension]
23
+ # @return [Dimension]
24
+ def self.for(to)
25
+ case to
26
+ when Dimension
27
+ if @@dimensions[to.name]
28
+ @@dimensions[to.name]
29
+ else
30
+ add_alias to, to.name
31
+ to
32
+ end
33
+ when Symbol
34
+ if @@dimensions.has_key?(to)
35
+ @@dimensions[to]
36
+ else
37
+ # it's possible we have a non-normalized form, such as mass*length
38
+ # instead of length * mass
39
+ @@dimensions[string_form(parse_string_form(to)).to_sym]
40
+ end
41
+ when Array
42
+ @@dimensions[string_form(to).to_sym]
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ # DSL component to add a new dimension. Dimension name, reference unit,
49
+ # and aliases. This should be the only way that dimensions are added.
50
+ # @param [Symbol] name
51
+ # @param [[Symbol String]] *aliases
52
+ def self.add_dimension(name, *aliases)
53
+ dim = nil
54
+ if name.is_a?(Dimension)
55
+ dim = name
56
+ name.name = aliases.first if aliases.first
57
+ else
58
+ dim = self.for(name) || self.new({ :name => aliases.first , :description => name})
59
+ self.add_alias(dim,name)
60
+ end
61
+ unless (dim.class == Dimension)
62
+ dim.name = dim.class.name.downcase.split(/::/).last.to_sym
63
+ self.add_alias(dim,dim.name)
64
+ end
65
+ self.add_alias(dim,*aliases)
66
+ dim
67
+ end
68
+
69
+ # Register a dimension to the known list, with the given aliases
70
+ # @param [Dimension]
71
+ # @param [[*names]]
72
+ def self.add_alias(dimension, *names)
73
+ names.each do |name|
74
+ raise ArgumentError, "Invalid dimension alias: #{name}" unless (name.to_s =~ /^(\^|\/)/).nil?
75
+ @@dimensions[name] = dimension
76
+ end
77
+ end
78
+
79
+ # All known dimensions
80
+ # @return [[Dimension]]
81
+ def self.all_dimensions
82
+ @@dimensions.values.uniq
83
+ end
84
+
85
+ # Reset the known dimensions. Generally only used for testing.
86
+ def self.__reset!
87
+ @@dimensions = {}
88
+ end
89
+
90
+ DimensionComponent = Struct.new(:dimension, :power)
91
+ DimensionComponent.class_eval do
92
+ def inspect
93
+ "#{dimension.inspect}^#{power}"
94
+ end
95
+ end
96
+
97
+ attr_reader :numerators, :denominators
98
+ attr_accessor :name
99
+ ### Instance-level methods/vars
100
+
101
+ # A new dimension
102
+ # @param [Hash] options
103
+ # @return Dimension
104
+ def initialize(opts)
105
+ if (opts[:description])
106
+ (@numerators,@denominators) = Dimension.parse_string_form(opts[:description])
107
+ elsif (opts[:numerators])
108
+ @numerators = opts[:numerators]
109
+ @denominators = opts[:denominators]
110
+ else
111
+ raise ArgumentError, "Invalid options for dimension constructors"
112
+ end
113
+ raise ArgumentError, "Dimensions require a numerator" unless @numerators.first.dimension
114
+ @name = opts[:name] || string_form.to_sym
115
+ Dimension.add_alias(self,@name)
116
+ Dimension.add_alias(self,string_form.to_sym)
117
+ end
118
+
119
+ def to_s
120
+ @name.to_s
121
+ end
122
+
123
+ # Dimensional multiplication
124
+ # @param [Dimension] other
125
+ # @return [Dimension]
126
+ def *(other)
127
+ raise ArgumentError, "Cannot multiply #{self} and #{other.class}" unless other.is_a?(Dimension)
128
+ (new_n, new_d) = Dimension.reduce(@numerators + other.numerators, @denominators + other.denominators)
129
+ existing = Dimension.for([new_n,new_d])
130
+ existing.nil? ? Dimension.new({:numerators => new_n, :denominators => new_d}) : existing
131
+ end
132
+
133
+ # Dimensional division
134
+ # @param [Dimension] other
135
+ # @return [Dimension]
136
+ def /(other)
137
+ raise ArgumentError, "Cannot divide #{self} by #{other.class}" unless other.is_a?(Dimension)
138
+ (new_n, new_d) = Dimension.reduce(@numerators + other.denominators, @denominators + other.numerators)
139
+ existing = Dimension.for([new_n,new_d])
140
+ existing.nil? ? Dimension.new({:numerators => new_n, :denominators => new_d}) : existing
141
+ end
142
+
143
+ # Dimensional exponentiation
144
+ # @param [Numeric] other
145
+ # @return [Dimension]
146
+ def **(other)
147
+ raise ArgumentError, "Dimensions can only be raised to whole powers" unless other.is_a?(Fixnum) && other > 0
148
+ other == 1 ? self : self * self**(other-1)
149
+ end
150
+
151
+ # Whether or not this is a compound representation of a base dimension
152
+ # @return [Boolean]
153
+ def is_base?
154
+ @denominators.size == 0 && @numerators.size == 1 && @numerators.first.power == 1
155
+ end
156
+
157
+ # Spaceship operator for comparable.
158
+ # @param [Any] other
159
+ # @return [-1 0 1]
160
+ def <=>(other)
161
+ if other.is_a?(Dimension)
162
+ if self.is_base? && other.is_base?
163
+ name.to_s <=> other.name.to_s
164
+ elsif other.is_base?
165
+ 1
166
+ else
167
+ string_form <=> other.string_form
168
+ end
169
+ else
170
+ nil
171
+ end
172
+ end
173
+
174
+ # Returns a developer-friendly representation of this value.
175
+ #
176
+ # The string will be of the format `#<Quantity::Dimension::0x12345678(...)>`,
177
+ # where `...` is the string returned by #to_s.
178
+ #
179
+ # @return [String]
180
+ def inspect
181
+ sprintf("#<%s:%#0x %s (%s)>", self.class.name, object_id, string_form, @name)
182
+ end
183
+
184
+
185
+ # Returns a vaguely human-readable and parsable description of this dimension
186
+ # @return [String]
187
+ def string_form
188
+ Dimension.string_form(@numerators,@denominators)
189
+ end
190
+
191
+ # A vaguely human-readable, vaguely machine-readable string description of
192
+ # a set of numerators and denominators.
193
+ # @private
194
+ # @ param [[DimensionComponent],[DimensionComponent]]
195
+ # @return [String]
196
+ def self.string_form(numerators, denominators = nil)
197
+ # We sometimes get [numerators,denominators],nil
198
+ (numerators,denominators) = numerators if (numerators.first.is_a?(Array))
199
+ string = ""
200
+ string_thunk = lambda do | array |
201
+ array.each_with_index do | component, n |
202
+ string << component.dimension.to_s
203
+ (string << '^' << component.power.to_s) if component.power.to_i > 1
204
+ string << '*' if n < array.size - 1
205
+ end
206
+ end
207
+ string_thunk.call(numerators)
208
+ string << "/" if denominators && denominators.size > 0
209
+ string_thunk.call(denominators) if denominators
210
+ string
211
+ end
212
+
213
+ # Parse the output of string_form into numerators and denominators
214
+ # @param [String] string
215
+ # @return [[DimensionComponent],[DimensionComponent]]
216
+ # @private
217
+ def self.parse_string_form(serialized)
218
+ parse_thunk = lambda do | string |
219
+ components = []
220
+ if !string.nil?
221
+ string.split(/\*/).each do | component |
222
+ (dimension, power) = component.split(/\^/)
223
+ components << DimensionComponent.new(dimension.to_sym,power.nil? ? 1 : power.to_i)
224
+ end
225
+ end
226
+ components
227
+ end
228
+ (top, bottom) = serialized.to_s.split(/\//)
229
+ Dimension.reduce(parse_thunk.call(top), parse_thunk.call(bottom))
230
+ end
231
+
232
+ # Returns numerators and denominators that represent the reduced form of the given
233
+ # numerators and denominators
234
+ # @return [[Array],[Array]]
235
+ # @private
236
+ def self.reduce(numerators,denominators)
237
+ new_numerators = reduce_multiplied_units(numerators)
238
+ new_denominators = reduce_multiplied_units(denominators)
239
+
240
+ new_numerators.each_with_index do | comp, i |
241
+ new_denominators.each_with_index do | dcomp, j |
242
+ if dcomp.dimension == comp.dimension
243
+ diff = [dcomp.power,comp.power].max - (dcomp.power - comp.power).abs
244
+ dcomp.power -= diff
245
+ comp.power -= diff
246
+ new_numerators.delete_at(i) if comp.power <= 0
247
+ new_denominators.delete_at(j) if dcomp.power <= 0
248
+ end
249
+ end
250
+ end
251
+ [new_numerators, new_denominators]
252
+ end
253
+
254
+ # Reduce an array of units to its most compact, sorted form
255
+ # @param [[DimensionComponent]]
256
+ # @return [[DimensionComponent]]
257
+ # @private
258
+ def self.reduce_multiplied_units(array)
259
+ new = {}
260
+ array.each do | item |
261
+ new[item.dimension] = DimensionComponent.new(item.dimension,0) unless new[item.dimension]
262
+ new[item.dimension].power += item.power
263
+ end
264
+ new.values.sort { |a,b| a.dimension.to_s <=> b.dimension.to_s }
265
+ end
266
+
267
+
268
+ end
269
+ end
@@ -0,0 +1,54 @@
1
+ #
2
+ #
3
+ class Quantity
4
+ #
5
+ # This module attempts to enumerate all of simple, base dimensions.
6
+ # This includes all of the base SI dimensions and some others
7
+ #
8
+ class Dimension
9
+
10
+ class Length < Quantity::Dimension ; end
11
+ length = Length.add_dimension :length, :width, :distance
12
+
13
+ class Time < Quantity::Dimension ; end
14
+ time = Time.add_dimension :time
15
+
16
+ class Mass < Quantity::Dimension ; end
17
+ mass = Mass.add_dimension :mass
18
+
19
+ class Current < Quantity::Dimension ; end
20
+ current = Current.add_dimension :current
21
+
22
+ class Luminosity < Quantity::Dimension ; end
23
+ luminosity = Luminosity.add_dimension :luminosity
24
+
25
+ class Substance < Quantity::Dimension ; end
26
+ substance = Substance.add_dimension :substance
27
+
28
+ class Temperature < Quantity::Dimension ; end
29
+ temp = Temperature.add_dimension :temperature
30
+
31
+ area = add_dimension length**2, :area
32
+
33
+ speed = add_dimension length / time, :speed, :velocity
34
+
35
+ accel = add_dimension speed / time, :acceleration
36
+
37
+ force = add_dimension mass * accel, :force
38
+
39
+ volume = add_dimension length**3, :volume
40
+
41
+ class Information < Quantity::Dimension ; end
42
+ information = Information.add_dimension :information, :data
43
+
44
+ # Quantity is the base dimension for the quantity of enumerable objects.
45
+ # Units are things like '2 dozen'.
46
+ class Quantity < Quantity::Dimension ; end
47
+ information = Quantity.add_dimension :quantity, :items, :enumerables
48
+
49
+ # Hardly a scientific base measurement, but it comes up a lot
50
+ class Currency < Dimension ; end
51
+ currency = Currency.add_dimension :money
52
+
53
+ end
54
+ end
@@ -0,0 +1,12 @@
1
+ # a few countable quantities
2
+
3
+ class Quantity
4
+ class Unit
5
+
6
+ add_unit :dozen, :quantity, 12, :dozens
7
+ add_unit :couple, :quantity, 2
8
+ add_unit :score, :quantity, 20, :scores
9
+
10
+ end
11
+ end
12
+
@@ -0,0 +1,10 @@
1
+ # Imperial versions of british/american customary units
2
+ class Quantity::Unit
3
+ add_unit :foot, :length, 304.8, :ft, :feet
4
+ add_unit :inch, :length, 25.4, :in, :inches
5
+ add_unit :yard, :length, 914.4, :yd, :yards
6
+ add_unit :mile, :length, 1_609_344, :miles
7
+
8
+ add_unit :pound, :mass, 453592.37, :pounds, :lb, :lbs
9
+ add_unit :ounce, :mass, 28349.5231, :ounces, :oz
10
+ end
@@ -0,0 +1,30 @@
1
+ class Quantity
2
+ class Unit
3
+ ##
4
+ # @see http://en.wikipedia.org/wiki/Mebibyte
5
+ # @see http://en.wikipedia.org/wiki/Units_of_information
6
+
7
+ add_unit :bit, :data, 1, :bits
8
+ add_unit :nibble, :data, 4, :nibbles, :nybble, :nybbles
9
+ add_unit :byte, :data, 8, :bytes
10
+
11
+ add_unit :kilobyte, :data, 8 * 1000, :kb, :kilobytes
12
+ add_unit :megabyte, :data, 8 * (1000**2), :mb, :megabytes
13
+ add_unit :gigabyte, :data, 8 * (1000**3), :gb, :gigabytes
14
+ add_unit :terabyte, :data, 8 * (1000**4), :tb, :terabytes
15
+ add_unit :petabyte, :data, 8 * (1000**5), :pb, :petabytes
16
+ add_unit :exabyte, :data, 8 * (1000**6), :exabytes
17
+ add_unit :zettabyte, :data, 8 * (1000**7), :zettabytes
18
+ add_unit :yottabyte, :data, 8 * (1000**8), :yottabytes
19
+
20
+ add_unit :kibibyte, :data, 8 * 1024, :kibibytes, :KiB, :kib
21
+ add_unit :mebibyte, :data, 8 * (1024**2), :mebibytes, :MiB, :mib
22
+ add_unit :gibibyte, :data, 8 * (1024**3), :gibibytes, :GiB, :gib
23
+ add_unit :tebibyte, :data, 8 * (1024**4), :tebibytes, :TiB, :tib
24
+ add_unit :pebibyte, :data, 8 * (1024**5), :pebibytes, :PiB, :pib
25
+ add_unit :exbibyte, :data, 8 * (1024**6), :exbibytes, :EiB, :eib
26
+ add_unit :zebibyte, :data, 8 * (1024**7), :zebibytes, :ZiB, :zib
27
+ add_unit :yobibyte, :data, 8 * (1024**8), :yobibytes, :YiB, :yib
28
+
29
+ end
30
+ end
@@ -0,0 +1,105 @@
1
+ require 'quantity/dimension/base'
2
+
3
+ # SI units for Length, Mass, Luminosity, Current, Substance,
4
+ # Temperature, and Time. Units from yocto- to yotta- are supplied.
5
+ #
6
+ # Also supplied:
7
+ # * Ångstroms are supplied for Length. (use angstrom or angstroms)
8
+ # * Tonnes (Metric) are supplied for mass.
9
+ # * cc's for volume
10
+ #
11
+ # Volume (liters) is also part of this, since it follows the same pattern,
12
+ # even though the SI considers it a derived unit.
13
+ #
14
+ # The 'reference' unit is milli-. Units larger than milli-
15
+ # constructed via Fixnums/Bignums, such as 2.meters, will be stored with
16
+ # Fixnum / Bignum accuracy. Smaller items, such as 35.femtometers, will
17
+ # be stored with rationals or floats. Generally speaking, you shouldn't
18
+ # have to worry about this--use the numbers, and it will Do The Right Thing.
19
+ # Do remember that you may need to do a .to_f before dividing if that's
20
+ # what you want.
21
+ #
22
+ # @see http://physics.nist.gov/cuu/Units/units.html
23
+ # @see http://physics.nist.gov/cuu/Units/current.html
24
+ # @see http://physics.nist.gov/cuu/Units/prefixes.html
25
+ class Quantity
26
+ class Unit
27
+
28
+ prefixes = {}
29
+ units = {}
30
+ aliases = {}
31
+
32
+ prefixes['yotta'] = 10 ** 27
33
+ prefixes['zetta'] = 10 ** 24
34
+ prefixes['exa'] = 10 ** 21
35
+ prefixes['peta'] = 10 ** 18
36
+ prefixes['tera'] = 10 ** 15
37
+ prefixes['giga'] = 10 ** 12
38
+ prefixes['mega'] = 10 ** 9
39
+ prefixes['kilo'] = 10 ** 6
40
+ prefixes['hecto'] = 10 ** 5
41
+ prefixes['deca'] = 10 ** 4
42
+ prefixes[''] = 10 ** 3
43
+ prefixes['deci'] = 10 ** 2
44
+ prefixes['centi'] = 10
45
+ # milli is the reference point for SI-measured units
46
+ prefixes['milli'] = 1
47
+ prefixes['micro'] = 10 ** -3
48
+ prefixes['nano'] = 10 ** -6
49
+ prefixes['pico'] = 10 ** -9
50
+ prefixes['femto'] = 10 ** -12
51
+ prefixes['atto'] = 10 ** -15
52
+ prefixes['zepto'] = 10 ** -18
53
+ prefixes['yocto'] = 10 ** -21
54
+
55
+ units['meter'] = :length
56
+ units['gram'] = :mass
57
+ units['second'] = :time
58
+ units['kelvin'] = :temperature
59
+ units['candela'] = :luminosity
60
+ units['ampere'] = :current
61
+ units['mole'] = :substance
62
+ # liter is a special cased, handled separately below
63
+
64
+ aliases['ampere'] = ['amp', 'amps', 'A']
65
+ aliases['liter'] = ['litre', 'litres']
66
+ aliases['candela'] = ['cd']
67
+ aliases['mole'] = ['mol']
68
+ aliases['kelvin'] = ['K']
69
+
70
+ units.each do | unit, dimension |
71
+ prefixes.each do | prefix, value |
72
+ add_unit "#{prefix + unit}".to_sym, dimension, value, "#{prefix + unit}s".to_sym
73
+ if aliases[unit]
74
+ aliases[unit].each do | unit_alias |
75
+ add_alias "#{prefix + unit}".to_sym, "#{prefix + unit_alias}".to_sym
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ add_alias :kilometer, :km
82
+ add_alias :centimeter, :cm
83
+ add_alias :meter, :m
84
+ add_alias :nanometer, :nm
85
+ add_alias :millimeter, :mm
86
+ add_alias :millisecond, :ms
87
+ add_unit :angstrom, :length, 10 ** -7, :angstroms
88
+
89
+ add_alias :kilogram, :kg
90
+ add_alias :gram, :g
91
+ add_alias :milligram, :mg
92
+ add_alias :megagram, :tonne, :tonnes
93
+
94
+
95
+ prefixes.each do | prefix, value |
96
+ add_unit "#{prefix}liter".to_sym, :volume, value * 1000, "#{prefix}liters".to_sym
97
+ (aliases['liter']).each do | unit_alias |
98
+ add_alias "#{prefix}liter".to_sym, "#{prefix + unit_alias}".to_sym
99
+ end
100
+ end
101
+ add_alias :liter, :l
102
+ add_alias :milliliter, :ml
103
+
104
+ end
105
+ end