quantify 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +20 -0
- data/Examples.rb +104 -0
- data/README +7 -0
- data/lib/quantify/config.rb +379 -0
- data/lib/quantify/core_extensions.rb +63 -0
- data/lib/quantify/dimensions.rb +523 -0
- data/lib/quantify/exception.rb +21 -0
- data/lib/quantify/inflections.rb +63 -0
- data/lib/quantify/quantify.rb +37 -0
- data/lib/quantify/quantity.rb +325 -0
- data/lib/quantify/unit/base_unit.rb +518 -0
- data/lib/quantify/unit/compound_base_unit.rb +91 -0
- data/lib/quantify/unit/compound_unit.rb +321 -0
- data/lib/quantify/unit/non_si_unit.rb +20 -0
- data/lib/quantify/unit/prefix/base_prefix.rb +42 -0
- data/lib/quantify/unit/prefix/non_si_prefix.rb +10 -0
- data/lib/quantify/unit/prefix/prefix.rb +73 -0
- data/lib/quantify/unit/prefix/si_prefix.rb +10 -0
- data/lib/quantify/unit/si_unit.rb +10 -0
- data/lib/quantify/unit/unit.rb +217 -0
- data/lib/quantify.rb +26 -0
- data/spec/dimension_spec.rb +294 -0
- data/spec/quantity_spec.rb +250 -0
- data/spec/unit_spec.rb +687 -0
- metadata +103 -0
@@ -0,0 +1,523 @@
|
|
1
|
+
#! usr/bin/ruby
|
2
|
+
|
3
|
+
module Quantify
|
4
|
+
|
5
|
+
# The Dimensions class represents specfic physical quantities in
|
6
|
+
# terms of powers of their constituent base dimensions, e.g.:
|
7
|
+
#
|
8
|
+
# area = length^2
|
9
|
+
# force = mass^1 x length^1 x time^-2
|
10
|
+
#
|
11
|
+
# Each dimension object is characterised by instance variables
|
12
|
+
# which describe the power (or index) of the respective base dimensions.
|
13
|
+
# Dimension objects can be manipulated - multiplied, divided, raised
|
14
|
+
# to powers, etc.
|
15
|
+
#
|
16
|
+
# Standard physical quantities (e.g. length, acceleration, energy)
|
17
|
+
# are loaded into the @@dimensions class variable at runtime. These
|
18
|
+
# can be accessed, used and manipulated for arbitrary dimensional uses.
|
19
|
+
#
|
20
|
+
# Instances of Dimensions are also used as the basis for defining and
|
21
|
+
# manipulating objects of the Unit::Base class.
|
22
|
+
|
23
|
+
class Dimensions
|
24
|
+
|
25
|
+
# The BASE_QUANTITIES array specifies the system of base quantities
|
26
|
+
# upon which all Dimensions objects are defined.
|
27
|
+
#
|
28
|
+
# :information, :currency, :item represent tentative additions to
|
29
|
+
# the standard set of base quantities.
|
30
|
+
#
|
31
|
+
# :item is intended to represent arbitrary 'things' for specifying
|
32
|
+
# quantities such as, for example:
|
33
|
+
#
|
34
|
+
# 'dollars per capita' (:currency => 1, :items => -1)
|
35
|
+
# 'trees per hectare' (:items => 1, :length => -2).
|
36
|
+
#
|
37
|
+
BASE_QUANTITIES = [
|
38
|
+
:mass, :length, :time, :electric_current, :temperature,
|
39
|
+
:luminous_intensity, :amount_of_substance, :information,
|
40
|
+
:currency, :item ]
|
41
|
+
|
42
|
+
# Class variable which holds in memory all defined (and 'loaded') quantities
|
43
|
+
@@dimensions = []
|
44
|
+
|
45
|
+
# Provides access the class array which holds all defined quantities
|
46
|
+
def self.dimensions
|
47
|
+
@@dimensions
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns an array of Dimensions objects representing just the base quantities,
|
51
|
+
# i.e. length, mass, time, temperature, etc.
|
52
|
+
#
|
53
|
+
def self.base_dimensions
|
54
|
+
@@dimensions.select do |dimensions|
|
55
|
+
BASE_QUANTITIES.map(&:standardize).include? dimensions.describe
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# This method allows specific, named quantities to be initialized and
|
60
|
+
# loaded into the @@dimensions array. Quantities are specified by their
|
61
|
+
# consituent base dimensions, but must also include a name/description,
|
62
|
+
# i.e. 'acceleration', :force - indicated by the :physical_quantity key -
|
63
|
+
# in order to be included in the system of known dimensions, e.g.:
|
64
|
+
#
|
65
|
+
# Dimensions.load :physical_quantity => :force,
|
66
|
+
# :length => 1,
|
67
|
+
# :mass => 1,
|
68
|
+
# :time => -2
|
69
|
+
#
|
70
|
+
# Standard quantities such as force, energy, mass, etc. should not need to
|
71
|
+
# be defined as they are included in the set of quantities already defined
|
72
|
+
# (see config.rb) and automatically loaded. These can be removed, overridden
|
73
|
+
# or configured differently if desired.
|
74
|
+
#
|
75
|
+
def self.load(options)
|
76
|
+
if options[:physical_quantity]
|
77
|
+
@@dimensions << Dimensions.new(options)
|
78
|
+
else
|
79
|
+
raise InvalidDimensionError, "Cannot load dimensions without physical quantity description"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Remove a dimension from the system of known dimensions
|
84
|
+
def self.unload(*unloaded_dimensions)
|
85
|
+
[unloaded_dimensions].flatten.each do |unloaded_dimension|
|
86
|
+
unloaded_dimension = Dimensions.for(unloaded_dimensions)
|
87
|
+
@@dimensions.delete_if { |unit| unit.physical_quantity == unloaded_dimension.physical_quantity }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns an array containing the names/descriptions of all known (loaded)
|
92
|
+
# physical quantities, e.g.:
|
93
|
+
#
|
94
|
+
# Dimensions.physical_quantities #=> [ 'acceleration',
|
95
|
+
# 'area',
|
96
|
+
# 'electric Current',
|
97
|
+
# ... ]
|
98
|
+
#
|
99
|
+
def self.physical_quantities
|
100
|
+
@@dimensions.map(&:physical_quantity)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Retrieve a known quantity - returns a Dimensions instance, which is a
|
104
|
+
# clone of the initialized instance of the specified quantity. This enables
|
105
|
+
# the object to be modified/manipulated without corrupting the representation
|
106
|
+
# of the quantity in the @@dimensions class array.
|
107
|
+
#
|
108
|
+
# The required quantity name/descriptor can be specified as a symbol or a
|
109
|
+
# string, e.g.:
|
110
|
+
#
|
111
|
+
# Dimensions.for :acceleration
|
112
|
+
# Dimensions.for 'luminous_flux'
|
113
|
+
#
|
114
|
+
# These can be shortened to, e.g. Dimensions.acceleration by virtue of the
|
115
|
+
# #method_missing class method (below)
|
116
|
+
#
|
117
|
+
def self.for(name)
|
118
|
+
return name if name.is_a? Dimensions
|
119
|
+
if name.is_a? String or name.is_a? Symbol
|
120
|
+
if quantity = @@dimensions.find do |quantity|
|
121
|
+
quantity.physical_quantity == name.standardize.downcase
|
122
|
+
end
|
123
|
+
return quantity.clone
|
124
|
+
else
|
125
|
+
raise InvalidArgumentError, "Physical quantity not known: #{name}"
|
126
|
+
end
|
127
|
+
else
|
128
|
+
raise InvalidArgumentError, "Argument must be a Symbol or String"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Syntactic sugar for defining the known quantities. This method simply
|
133
|
+
# evaluates code within the context of the Dimensions class, enabling
|
134
|
+
# the required quantities to be loaded at runtime, e.g.
|
135
|
+
#
|
136
|
+
# Dimensions.configure do
|
137
|
+
#
|
138
|
+
# load :physical_quantity => :length, :length => 1
|
139
|
+
# load :physical_quantity => :area, :length => 2
|
140
|
+
# load :physical_quantity => :power, :mass => 1, :length => 2, :time => -3
|
141
|
+
#
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
def self.configure &block
|
145
|
+
self.class_eval &block if block
|
146
|
+
end
|
147
|
+
|
148
|
+
# Provides a shorthand for retrieving known quantities, e.g.:
|
149
|
+
#
|
150
|
+
# Dimensions.force
|
151
|
+
#
|
152
|
+
# is equivalent to
|
153
|
+
#
|
154
|
+
# Dimensions.for :force
|
155
|
+
#
|
156
|
+
# Both variants return a clone of the initialized dimensional representation
|
157
|
+
# of the specified physical quantity (i.e. force).
|
158
|
+
#
|
159
|
+
def self.method_missing(method, *args, &block)
|
160
|
+
if dimensions = self.for(method)
|
161
|
+
return dimensions
|
162
|
+
end
|
163
|
+
super
|
164
|
+
end
|
165
|
+
|
166
|
+
BASE_QUANTITIES.each { |quantity| attr_reader quantity }
|
167
|
+
|
168
|
+
attr_accessor :physical_quantity
|
169
|
+
|
170
|
+
# Initialize a new Dimension object.
|
171
|
+
#
|
172
|
+
# The options argument is a hash which represents the base dimensions that
|
173
|
+
# define the physical quantity. Each key-value pair should consist of a key
|
174
|
+
# included in the BASE_QUANTITIES array, and a value which represents the
|
175
|
+
# index/power of that base quantity.
|
176
|
+
#
|
177
|
+
# In addition, a name or description of the physical quantity can be
|
178
|
+
# specified (i.e. 'acceleration', 'electric_current'). This is optional for
|
179
|
+
# creating a new Dimensions instance, but required if that object is to be
|
180
|
+
# loaded into the @@dimensions class array. e.g.:
|
181
|
+
#
|
182
|
+
# Dimensions.new :physical_quantity => :density,
|
183
|
+
# :mass => 1,
|
184
|
+
# :length => -3
|
185
|
+
#
|
186
|
+
def initialize(options={})
|
187
|
+
if options.has_key?(:physical_quantity)
|
188
|
+
@physical_quantity = options.delete(:physical_quantity).standardize.downcase
|
189
|
+
end
|
190
|
+
enumerate_base_quantities(options)
|
191
|
+
describe
|
192
|
+
end
|
193
|
+
|
194
|
+
# Load an already instantiated Dimensions object into the @@dimensions class
|
195
|
+
# array, from which it will be accessible as a universal representation of
|
196
|
+
# that physical quantity.
|
197
|
+
#
|
198
|
+
# Object must include a non-nil @physical_quantity attribute, i.e. a name or
|
199
|
+
# description of the physical quantity represented.
|
200
|
+
#
|
201
|
+
def load
|
202
|
+
if describe and not loaded?
|
203
|
+
@@dimensions << self
|
204
|
+
elsif describe
|
205
|
+
raise InvalidDimensionError, "A dimension instance with the same physical quantity already exists"
|
206
|
+
else
|
207
|
+
raise InvalidDimensionError, "Cannot load dimensions without physical quantity description"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def loaded?
|
212
|
+
Dimensions.dimensions.any? { |quantity| self.has_same_identity_as? quantity }
|
213
|
+
end
|
214
|
+
|
215
|
+
# Remove from system of known units.
|
216
|
+
def unload
|
217
|
+
Dimensions.unload(self.physical_quantity)
|
218
|
+
end
|
219
|
+
|
220
|
+
def has_same_identity_as?(other)
|
221
|
+
self.physical_quantity == other.physical_quantity and not self.physical_quantity.nil?
|
222
|
+
end
|
223
|
+
|
224
|
+
# Return a description of what physical quantity self represents. If no
|
225
|
+
# value is found in the @physical_quantity instance variable, the task is
|
226
|
+
# delegated to the #get_description method.
|
227
|
+
#
|
228
|
+
def describe
|
229
|
+
@physical_quantity or get_description
|
230
|
+
end
|
231
|
+
|
232
|
+
# Searches the system of known physical quantities (@@dimensions class
|
233
|
+
# array) looking for any which match self in terms of the configuration of
|
234
|
+
# base dimensions, i.e. an object which dimensionally represents the same
|
235
|
+
# thing.
|
236
|
+
#
|
237
|
+
# If found, the name/description of that quantity is assigned to the
|
238
|
+
# @physical_quantity attribute of self.
|
239
|
+
#
|
240
|
+
# This method is useful in cases where Dimensions instances are manipulated
|
241
|
+
# using operators (e.g. multiply, divide, power, reciprocal), resulting in
|
242
|
+
# a change to the configuration of base dimensions (perhaps as a new instance).
|
243
|
+
# This method tries to find a description of the new quantity.
|
244
|
+
#
|
245
|
+
# If none is found, self.physical_quantity is set to nil.
|
246
|
+
#
|
247
|
+
def get_description
|
248
|
+
similar = @@dimensions.find { |quantity| quantity == self }
|
249
|
+
@physical_quantity = ( similar.nil? ? nil : similar.physical_quantity )
|
250
|
+
end
|
251
|
+
|
252
|
+
# Returns an array containing the known units which represent the physical
|
253
|
+
# quantity described by self
|
254
|
+
#
|
255
|
+
# If no argument is given, the array holds instances of Unit::Base (or
|
256
|
+
# subclasses) which represent each unit. Alternatively only the names or
|
257
|
+
# symbols of each unit can be returned by providing the appropriate unit
|
258
|
+
# attribute as a symbolized argument, e.g.
|
259
|
+
#
|
260
|
+
# Dimensions.energy.units #=> [ #<Quantify::Dimensions: .. >,
|
261
|
+
# #<Quantify::Dimensions: .. >,
|
262
|
+
# ... ]
|
263
|
+
#
|
264
|
+
# Dimensions.mass.units :name #=> [ 'kilogram', 'ounce',
|
265
|
+
# 'pound', ... ]
|
266
|
+
#
|
267
|
+
# Dimensions.length.units :symbol #=> [ 'm', 'ft', 'yd', ... ]
|
268
|
+
#
|
269
|
+
def units(by=nil)
|
270
|
+
Unit.units.select { |unit| unit.dimensions == self }.map(&by)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Returns the SI unit for the physical quantity described by self.
|
274
|
+
#
|
275
|
+
# Plane/solid angle are special cases which are dimensionless units, and so
|
276
|
+
# are handled explicitly. Otherwise, the si base units for each of the base
|
277
|
+
# dimensions of self are indentified and the corresponding compound unit is
|
278
|
+
# derived. If this new unit is the same as a known (SI derived) unit, the
|
279
|
+
# known unit is returned.
|
280
|
+
#
|
281
|
+
# Dimensions.energy.units #=> #<Quantify::Dimensions: .. >
|
282
|
+
#
|
283
|
+
# Dimensions.energy.si_unit.name #=> 'joule'
|
284
|
+
#
|
285
|
+
# Dimensions.kinematic_viscosity.si_unit.name
|
286
|
+
#
|
287
|
+
# #=> 'metre squared per second'
|
288
|
+
#
|
289
|
+
def si_unit
|
290
|
+
return Unit.steridian if self.describe == 'solid angle'
|
291
|
+
return Unit.radian if self.describe == 'plane angle'
|
292
|
+
return si_base_units.inject(Unit.unity) do |compound,unit|
|
293
|
+
compound * unit
|
294
|
+
end.or_equivalent
|
295
|
+
rescue
|
296
|
+
return nil
|
297
|
+
end
|
298
|
+
|
299
|
+
# Returns an array representing the base SI units for the physical quantity
|
300
|
+
# described by self
|
301
|
+
#
|
302
|
+
# If no argument is given, the array holds instances of Unit::Base (or
|
303
|
+
# subclasses) which represent each base unit. Alternatively only the names
|
304
|
+
# or symbols of each unit can be returned by providing the appropriate unit
|
305
|
+
# attribute as a symbolized argument, e.g.
|
306
|
+
#
|
307
|
+
# Dimensions.energy.si_base_units #=> [ #<Quantify::Unit: .. >,
|
308
|
+
# #<Quantify::Unit: .. >,
|
309
|
+
# ... ]
|
310
|
+
#
|
311
|
+
# Dimensions.energy.si_base_units :name
|
312
|
+
#
|
313
|
+
# #=> [ "metre squared",
|
314
|
+
# "per second squared",
|
315
|
+
# "kilogram"] #
|
316
|
+
#
|
317
|
+
# Dimensions.force.units :symbol #=> [ "m", "s^-2", "kg"]
|
318
|
+
#
|
319
|
+
def si_base_units(by=nil)
|
320
|
+
self.to_hash.map do |dimension,index|
|
321
|
+
Unit.si_base_units.select do |unit|
|
322
|
+
unit.measures == dimension.standardize
|
323
|
+
end.first.clone ** index
|
324
|
+
end.map(&by)
|
325
|
+
end
|
326
|
+
|
327
|
+
# Compares the base quantities of two Dimensions objects and returns true if
|
328
|
+
# they are the same. This indicates that the two objects represent the same
|
329
|
+
# physical quantity (irrespective of their names - @physical_quantity - being
|
330
|
+
# similar, different, or absent.
|
331
|
+
#
|
332
|
+
def ==(other)
|
333
|
+
self.to_hash == other.to_hash
|
334
|
+
end
|
335
|
+
|
336
|
+
# Returns true if the physical quantity that self represents is known
|
337
|
+
def is_known?
|
338
|
+
describe ? true : false
|
339
|
+
end
|
340
|
+
|
341
|
+
# Returns true if self is a dimensionless quantity
|
342
|
+
def is_dimensionless?
|
343
|
+
base_quantities.empty?
|
344
|
+
end
|
345
|
+
|
346
|
+
# Returns true if self represents one of the base quantities (i.e. length,
|
347
|
+
# mass, time, etc.)
|
348
|
+
def is_base?
|
349
|
+
base_quantities.size == 1 and
|
350
|
+
self.instance_variable_get(base_quantities.first) == 1 ? true : false
|
351
|
+
end
|
352
|
+
|
353
|
+
# Method for identifying quantities which are 'specific' quantities, i.e
|
354
|
+
# quantities which represent a quantity of something *per unit mass*
|
355
|
+
#
|
356
|
+
def is_specific_quantity?
|
357
|
+
denominator_quantities == ["@mass"]
|
358
|
+
end
|
359
|
+
|
360
|
+
# Method for identifying quantities which are 'molar' quantities, i.e
|
361
|
+
# quantities which represent a quantity of something *per mole*
|
362
|
+
#
|
363
|
+
def is_molar_quantity?
|
364
|
+
denominator_quantities == ["@amount_of_substance"]
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
# Multiplies self by another Dimensions object, returning self with an
|
369
|
+
# updated configuration of dimensions. Since this is likely to have resulted
|
370
|
+
# in the representation of a different physical quantity than was originally
|
371
|
+
# represented, the #get_description method is invoked to attempt to find a
|
372
|
+
# suitable description.
|
373
|
+
#
|
374
|
+
def multiply!(other)
|
375
|
+
enumerate_base_quantities(other.to_hash)
|
376
|
+
get_description
|
377
|
+
return self
|
378
|
+
end
|
379
|
+
|
380
|
+
# Similar to #multiply! but returns a new Dimensions instance representing
|
381
|
+
# the physical quantity which results from the multiplication.
|
382
|
+
#
|
383
|
+
def multiply(other)
|
384
|
+
Dimensions.new(self.to_hash).multiply! other
|
385
|
+
end
|
386
|
+
|
387
|
+
# Similar to #multiply! but performs a division of self by the specified
|
388
|
+
# Dimensions object.
|
389
|
+
#
|
390
|
+
def divide!(other)
|
391
|
+
enumerate_base_quantities(other.reciprocalize.to_hash)
|
392
|
+
get_description
|
393
|
+
return self
|
394
|
+
end
|
395
|
+
|
396
|
+
# Similar to #divide! but returns a new Dimensions instance representing
|
397
|
+
# the physical quantity which results from the division.
|
398
|
+
#
|
399
|
+
def divide(other)
|
400
|
+
Dimensions.new(self.to_hash).divide! other
|
401
|
+
end
|
402
|
+
|
403
|
+
# Raises self to the power provided. As with multiply and divide, the
|
404
|
+
# #get_description method is invoked to attempt to find a suitable
|
405
|
+
# description for the new quantity represented.
|
406
|
+
#
|
407
|
+
def pow!(power)
|
408
|
+
make_dimensionless if power == 0
|
409
|
+
if power < 0
|
410
|
+
self.reciprocalize!
|
411
|
+
power *= -1
|
412
|
+
end
|
413
|
+
original_dimensions = self.clone
|
414
|
+
(power - 1).times { self.multiply!(original_dimensions) }
|
415
|
+
get_description
|
416
|
+
return self
|
417
|
+
end
|
418
|
+
|
419
|
+
# Similar to #pow! but returns a new Dimensions instance representing
|
420
|
+
# the physical quantity which results from the raised power.
|
421
|
+
#
|
422
|
+
def pow(power)
|
423
|
+
Dimensions.new(self.to_hash).pow!(power)
|
424
|
+
end
|
425
|
+
|
426
|
+
# Inverts self, returning a representation of 1/self. This is equivalent to
|
427
|
+
# raising to the power -1. The #get_description method is invoked to attempt
|
428
|
+
# to find a suitable description for the new quantity represented.
|
429
|
+
#
|
430
|
+
def reciprocalize!
|
431
|
+
base_quantities.each do |variable|
|
432
|
+
new_value = self.instance_variable_get(variable) * -1
|
433
|
+
self.instance_variable_set(variable, new_value)
|
434
|
+
end
|
435
|
+
get_description
|
436
|
+
return self
|
437
|
+
end
|
438
|
+
|
439
|
+
# Similar to #reciprocalize! but returns a new Dimensions instance representing
|
440
|
+
# the physical quantity which results from the inversion.
|
441
|
+
#
|
442
|
+
def reciprocalize
|
443
|
+
Dimensions.new(self.to_hash).reciprocalize!
|
444
|
+
end
|
445
|
+
|
446
|
+
alias :times :multiply
|
447
|
+
alias :* :multiply
|
448
|
+
alias :/ :divide
|
449
|
+
alias :** :pow
|
450
|
+
|
451
|
+
protected
|
452
|
+
|
453
|
+
# Returns an array containing the names of the instance variables which
|
454
|
+
# represent the base quantities of self. This enables various operations to
|
455
|
+
# be performed on these variables without touching the @physical_quantity
|
456
|
+
# variable.
|
457
|
+
#
|
458
|
+
def base_quantities
|
459
|
+
quantities = self.instance_variables
|
460
|
+
quantities.delete("@physical_quantity")
|
461
|
+
return quantities
|
462
|
+
end
|
463
|
+
|
464
|
+
# Just the base quantities which have positive indices
|
465
|
+
def numerator_quantities
|
466
|
+
base_quantities.select { |quantity| self.instance_variable_get(quantity) > 0 }
|
467
|
+
end
|
468
|
+
|
469
|
+
# Just the base quantities which have negative indices
|
470
|
+
def denominator_quantities
|
471
|
+
base_quantities.select { |quantity| self.instance_variable_get(quantity) < 0 }
|
472
|
+
end
|
473
|
+
|
474
|
+
# Returns a hash representation of the base dimensions of self. This is used
|
475
|
+
# in various operations and is useful for instantiating new objects with
|
476
|
+
# the same base dimensions.
|
477
|
+
#
|
478
|
+
def to_hash
|
479
|
+
hash = {}
|
480
|
+
base_quantities.each do |variable|
|
481
|
+
hash[variable.gsub("@","").to_sym] = self.instance_variable_get(variable)
|
482
|
+
end
|
483
|
+
return hash
|
484
|
+
end
|
485
|
+
|
486
|
+
# Method for initializing the base quantities of self.
|
487
|
+
#
|
488
|
+
# Where base quantities are already defined, the new indices are added to
|
489
|
+
# the existing ones. This represents the multiplication of base quantities
|
490
|
+
# (multiplication of similar quantities involves the addition of their
|
491
|
+
# powers).
|
492
|
+
#
|
493
|
+
# This method is therefore used in the multiplication of Dimensions objects,
|
494
|
+
# but also in divisions and raising of powers following other operations.
|
495
|
+
#
|
496
|
+
def enumerate_base_quantities(options)
|
497
|
+
options.each_pair do |base_quantity,index|
|
498
|
+
base_quantity = base_quantity.to_s.downcase.to_sym
|
499
|
+
unless index.is_a? Integer and BASE_QUANTITIES.include? base_quantity
|
500
|
+
raise InvalidDimensionError, "An invalid base quantity was specified (#{base_quantity})"
|
501
|
+
end
|
502
|
+
variable = "@#{base_quantity}"
|
503
|
+
if self.instance_variable_defined?(variable)
|
504
|
+
new_index = self.instance_variable_get(variable) + index
|
505
|
+
if new_index == 0
|
506
|
+
remove_instance_variable(variable)
|
507
|
+
else
|
508
|
+
self.instance_variable_set(variable, new_index)
|
509
|
+
end
|
510
|
+
else
|
511
|
+
self.instance_variable_set(variable, index)
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
# Make object represent a dimensionless quantity.
|
517
|
+
def make_dimensionless
|
518
|
+
self.physical_quantity = 'dimensionless'
|
519
|
+
base_quantities.each { |var| remove_instance_variable(var) }
|
520
|
+
end
|
521
|
+
|
522
|
+
end
|
523
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Quantify
|
2
|
+
|
3
|
+
class QuantityParseError < Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
class InvalidObjectError < Exception
|
7
|
+
end
|
8
|
+
|
9
|
+
class InvalidUnitError < Exception
|
10
|
+
end
|
11
|
+
|
12
|
+
class InvalidDimensionError < Exception
|
13
|
+
end
|
14
|
+
|
15
|
+
class InvalidPhysicalQuantityError < Exception
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvalidArgumentError < Exception
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
3
|
+
|
4
|
+
inflect.uncountable %w( clo hertz lux siemens )
|
5
|
+
|
6
|
+
inflect.plural /(metre)/i, '\1s'
|
7
|
+
inflect.singular /(metre)s?/i, '\1'
|
8
|
+
|
9
|
+
inflect.plural /(degree)/i, '\1s'
|
10
|
+
inflect.singular /(degree)s?/i, '\1'
|
11
|
+
|
12
|
+
inflect.plural /(barrel)/i, '\1s'
|
13
|
+
inflect.singular /(barrel)s?/i, '\1'
|
14
|
+
|
15
|
+
inflect.plural /(unit)/i, '\1s'
|
16
|
+
inflect.singular /(unit)s?/i, '\1'
|
17
|
+
|
18
|
+
inflect.plural /(ounce)/i, '\1s'
|
19
|
+
inflect.singular /(ounce)s?/i, '\1'
|
20
|
+
|
21
|
+
inflect.plural /(volt)/i, '\1s'
|
22
|
+
inflect.singular /(volt)s?/i, '\1'
|
23
|
+
|
24
|
+
inflect.plural /(foot)/i, 'feet'
|
25
|
+
inflect.singular /(feet)/i, 'foot'
|
26
|
+
|
27
|
+
inflect.plural /(gallon)/i, '\1s'
|
28
|
+
inflect.singular /(gallon)s?/i, '\1'
|
29
|
+
|
30
|
+
inflect.plural /(horsepower)/i, '\1'
|
31
|
+
inflect.singular /(horsepower)/i, '\1'
|
32
|
+
|
33
|
+
inflect.plural /(hundredweight)/i, '\1'
|
34
|
+
inflect.singular /(hundredweight)/i, '\1'
|
35
|
+
|
36
|
+
inflect.plural /(inch)/i, '\1es'
|
37
|
+
inflect.singular /(inch)(es)?/i, '\1'
|
38
|
+
|
39
|
+
inflect.plural /(league)/i, '\1s'
|
40
|
+
inflect.singular /(league)s?/i, '\1'
|
41
|
+
|
42
|
+
inflect.plural /(mass)/i, '\1es'
|
43
|
+
inflect.singular /(mass)(es)?/i, '\1'
|
44
|
+
|
45
|
+
inflect.plural /(mile)/i, '\1s'
|
46
|
+
inflect.singular /(mile)s?/i, '\1'
|
47
|
+
|
48
|
+
inflect.plural /(pound)/i, '\1s'
|
49
|
+
inflect.singular /(pound)s?/i, '\1'
|
50
|
+
|
51
|
+
inflect.plural /(ton)/i, '\1s'
|
52
|
+
inflect.singular /(ton)s?/i, '\1'
|
53
|
+
|
54
|
+
inflect.plural /(tonne)/i, '\1s'
|
55
|
+
inflect.singular /(tonne)s?/i, '\1'
|
56
|
+
|
57
|
+
inflect.plural /(stone)/i, '\1s'
|
58
|
+
inflect.singular /(stone)s?/i, '\1'
|
59
|
+
|
60
|
+
inflect.irregular 'footcandle', 'footcandles'
|
61
|
+
inflect.irregular 'kilowatt hour', 'kilowatt hours'
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Quantify
|
2
|
+
|
3
|
+
def self.configure &block
|
4
|
+
self.module_eval &block if block
|
5
|
+
end
|
6
|
+
|
7
|
+
module ExtendedMethods
|
8
|
+
|
9
|
+
# Provides syntactic sugar for accessing units via the #for method.
|
10
|
+
# Specify:
|
11
|
+
#
|
12
|
+
# Unit.degree_celsius
|
13
|
+
#
|
14
|
+
# rather than Unit.for :degree_celsius
|
15
|
+
#
|
16
|
+
def method_missing(method, *args, &block)
|
17
|
+
if method.to_s =~ /((si|non_si|compound)_)?(non_(prefixed)_)?((base|derived|benchmark)_)?units(_by_(name|symbol|label))?/
|
18
|
+
if $2 or $4 or $6
|
19
|
+
conditions = []
|
20
|
+
conditions << "unit.is_#{$2}_unit?" if $2
|
21
|
+
conditions << "!unit.is_prefixed_unit?" if $4
|
22
|
+
conditions << "unit.is_#{$6}_unit?" if $6
|
23
|
+
units = Unit.units.select { |unit| instance_eval(conditions.join(" and ")) }
|
24
|
+
else
|
25
|
+
units = Unit.units
|
26
|
+
end
|
27
|
+
return_format = ( $8 ? $8.to_sym : nil )
|
28
|
+
units.map(&return_format)
|
29
|
+
elsif unit = Unit.for(method)
|
30
|
+
return unit
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|