quantify 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|