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,518 @@
|
|
1
|
+
|
2
|
+
module Quantify
|
3
|
+
module Unit
|
4
|
+
class Base
|
5
|
+
|
6
|
+
extend ExtendedMethods
|
7
|
+
|
8
|
+
# Base unit class, providing most of the functionality which is inherited
|
9
|
+
# by SI and NonSI unit classes.
|
10
|
+
|
11
|
+
# Create a new instance of self (i.e. Base or an inherited class) and load
|
12
|
+
# into the system of known units. See initialize for details of options
|
13
|
+
#
|
14
|
+
def self.load(options)
|
15
|
+
unit = self.new(options)
|
16
|
+
unit.load
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.construct_and_load(unit,&block)
|
20
|
+
self.construct(unit, &block).load
|
21
|
+
end
|
22
|
+
|
23
|
+
# Mass load prefixed units. First argument is a single or array of units.
|
24
|
+
# Second argument is a single or array of prefixes. All specfied units will
|
25
|
+
# be loaded with all specified prefixes.
|
26
|
+
#
|
27
|
+
def self.prefix_and_load(prefixes,units)
|
28
|
+
[units].flatten.each do |unit|
|
29
|
+
unit = Unit.for(unit)
|
30
|
+
[prefixes].flatten.each do |prefix|
|
31
|
+
prefixed_unit = unit.with_prefix(prefix) rescue unit
|
32
|
+
prefixed_unit.load unless prefixed_unit.loaded?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Define a new unit in terms of an already instantiated compound unit. This
|
38
|
+
# unit becomes a representation of the compound - without explicitly holding
|
39
|
+
# the base units, e.g.
|
40
|
+
#
|
41
|
+
# Unit::Base.define(Unit.m**2).name #=> "square metre"
|
42
|
+
#
|
43
|
+
# Unit::Base.define(Unit**3) do |unit|
|
44
|
+
# unit.name = "metres cubed"
|
45
|
+
# end.name #=> "metres cubed"
|
46
|
+
#
|
47
|
+
def self.construct(unit,&block)
|
48
|
+
new_unit = self.new unit.to_hash
|
49
|
+
yield new_unit if block_given?
|
50
|
+
return new_unit
|
51
|
+
end
|
52
|
+
|
53
|
+
# Syntactic sugar for defining the known units, enabling the required
|
54
|
+
# associated units to be loaded at runtime, e.g.
|
55
|
+
#
|
56
|
+
# Unit::[Base|SI|NonSI].configure do |config|
|
57
|
+
#
|
58
|
+
# load :name => :metre, :physical_quantity => :length
|
59
|
+
# load :name => 'hectare', :physical_quantity => :area, :factor => 10000
|
60
|
+
# load :name => :watt, :physical_quantity => :power, :symbol => 'W'
|
61
|
+
#
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
def self.configure &block
|
65
|
+
class_eval &block if block
|
66
|
+
end
|
67
|
+
|
68
|
+
attr_accessor :name, :symbol, :label
|
69
|
+
attr_accessor :dimensions, :factor
|
70
|
+
attr_accessor :acts_as_alternative_unit, :acts_as_equivalent_unit
|
71
|
+
|
72
|
+
# Create a new Unit::Base instance.
|
73
|
+
#
|
74
|
+
# Valid options are: :name => The unit name, e.g. :kilometre
|
75
|
+
#
|
76
|
+
# :dimensions => The physical quantity represented
|
77
|
+
# by the unit (e.g. force, mass).
|
78
|
+
# This must be recognised as a member
|
79
|
+
# of the Dimensions.dimensions array
|
80
|
+
#
|
81
|
+
# :physical_quantity => Alias for :dimensions
|
82
|
+
#
|
83
|
+
# :symbol => The unit symbol, e.g. 'kg'
|
84
|
+
#
|
85
|
+
# :factor => The factor which relates the unit
|
86
|
+
# to the SI unit for the same physical
|
87
|
+
# quantity. For example the :factor for
|
88
|
+
# a foot would be 0.3048, since a foot
|
89
|
+
# = 0.3048 m (metre is the SI unit of
|
90
|
+
# length). If no factor is set, it is
|
91
|
+
# assumed to be 1 - which represents
|
92
|
+
# an SI benchmark unit.
|
93
|
+
#
|
94
|
+
# :scaling => A scaling factor, used only by NonSI
|
95
|
+
# temperature units
|
96
|
+
#
|
97
|
+
# :label => The label used by JScience for the
|
98
|
+
# unit
|
99
|
+
#
|
100
|
+
# The physical quantity option is used to locate the corresponding dimensional
|
101
|
+
# representation in the Dimensions class. This dimensions attribute is to
|
102
|
+
# provide much of the unit functionality
|
103
|
+
#
|
104
|
+
def initialize(options=nil)
|
105
|
+
if options.is_a? Hash
|
106
|
+
@name = options[:name].standardize.singularize.downcase
|
107
|
+
options[:dimensions] = options[:dimensions] || options[:physical_quantity]
|
108
|
+
if options[:dimensions].is_a? Dimensions
|
109
|
+
@dimensions = options[:dimensions]
|
110
|
+
elsif options[:dimensions].is_a? String or options[:dimensions].is_a? Symbol
|
111
|
+
@dimensions = Dimensions.for options[:dimensions]
|
112
|
+
else
|
113
|
+
raise InvalidArgumentError, "Unknown physical_quantity specified"
|
114
|
+
end
|
115
|
+
@factor = options[:factor].nil? ? 1.0 : options[:factor].to_f
|
116
|
+
@symbol = options[:symbol].nil? ? nil : options[:symbol].standardize
|
117
|
+
@label = options[:label].nil? ? nil : options[:label].to_s
|
118
|
+
@acts_as_alternative_unit = true
|
119
|
+
@acts_as_equivalent_unit = false
|
120
|
+
end
|
121
|
+
yield self if block_given?
|
122
|
+
valid?
|
123
|
+
end
|
124
|
+
|
125
|
+
# Permits a block to be used, operating on self. This is useful for modifying
|
126
|
+
# the attributes of an already instantiated unit, especially when defining
|
127
|
+
# units on the basis of operation on existing units for adding specific
|
128
|
+
# (rather than derived) names or symbols, e.g.
|
129
|
+
#
|
130
|
+
# (Unit.pound_force/(Unit.in**2)).operate do |unit|
|
131
|
+
# unit.symbol = 'psi'
|
132
|
+
# unit.label = 'psi'
|
133
|
+
# unit.name = 'pound per square inch'
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
def operate
|
137
|
+
yield self if block_given?
|
138
|
+
return self if valid?
|
139
|
+
end
|
140
|
+
|
141
|
+
# Load an initialized Unit into the system of known units.
|
142
|
+
#
|
143
|
+
# If a block is given, the unit can be operated on prior to loading, in a
|
144
|
+
# similar to way to the #operate method.
|
145
|
+
#
|
146
|
+
def load
|
147
|
+
yield self if block_given?
|
148
|
+
raise InvalidArgumentError, "A unit with the same label: #{self.name}) already exists" if loaded?
|
149
|
+
Quantify::Unit.units << self if valid?
|
150
|
+
end
|
151
|
+
|
152
|
+
# Remove from system of known units.
|
153
|
+
def unload
|
154
|
+
Unit.unload(self.label)
|
155
|
+
end
|
156
|
+
|
157
|
+
# check if an object with the same label already exists
|
158
|
+
def loaded?
|
159
|
+
Unit.units.any? { |unit| self.has_same_identity_as? unit }
|
160
|
+
end
|
161
|
+
|
162
|
+
def make_canonical
|
163
|
+
unload
|
164
|
+
load
|
165
|
+
end
|
166
|
+
|
167
|
+
def acts_as_alternative_unit=(value)
|
168
|
+
@acts_as_alternative_unit = (value == (true||false) ? value : false)
|
169
|
+
make_canonical
|
170
|
+
end
|
171
|
+
|
172
|
+
def acts_as_equivalent_unit=(value)
|
173
|
+
@acts_as_equivalent_unit = (value == (true||false) ? value : false)
|
174
|
+
make_canonical
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns the scaling factor for the unit with repsect to its SI alternative.
|
178
|
+
#
|
179
|
+
# For example the scaling factor for degrees celsius is 273.15, i.e. celsius
|
180
|
+
# is a value of 273.15 greater than kelvin (but with no multiplicative factor).
|
181
|
+
#
|
182
|
+
def scaling
|
183
|
+
@scaling || 0.0
|
184
|
+
end
|
185
|
+
|
186
|
+
def has_scaling?
|
187
|
+
scaling != 0.0
|
188
|
+
end
|
189
|
+
|
190
|
+
# Describes what the unit measures/represents. This is taken from the
|
191
|
+
# @dimensions ivar, being, ultimately an attribute of the assocaited
|
192
|
+
# Dimensions object, e.g.
|
193
|
+
#
|
194
|
+
# Unit.metre.measures #=> :length
|
195
|
+
#
|
196
|
+
# Unit.J.measures #=> :energy
|
197
|
+
#
|
198
|
+
def measures
|
199
|
+
@dimensions.describe
|
200
|
+
end
|
201
|
+
|
202
|
+
def pluralized_name
|
203
|
+
self.name.pluralize
|
204
|
+
end
|
205
|
+
|
206
|
+
# Determine if the unit represents one of the base quantities
|
207
|
+
def is_base_unit?
|
208
|
+
Dimensions::BASE_QUANTITIES.map(&:standardize).include? self.measures
|
209
|
+
end
|
210
|
+
|
211
|
+
# Determine is the unit is a derived unit - that is, a unit made up of more
|
212
|
+
# than one of the base quantities
|
213
|
+
#
|
214
|
+
def is_derived_unit?
|
215
|
+
not is_base_unit?
|
216
|
+
end
|
217
|
+
|
218
|
+
# Determine if the unit is a prefixed unit
|
219
|
+
def is_prefixed_unit?
|
220
|
+
return true if valid_prefixes.size > 0 and
|
221
|
+
self.name =~ /\A(#{valid_prefixes.map(&:name).join("|")})/
|
222
|
+
return false
|
223
|
+
end
|
224
|
+
|
225
|
+
# Determine if the unit is one of the units against which all other units
|
226
|
+
# of the same physical quantity are defined. These units are almost entirely
|
227
|
+
# equivalent to the non-prefixed, SI units, but the one exception is the
|
228
|
+
# kilogram, making this method necessary.
|
229
|
+
#
|
230
|
+
def is_benchmark_unit?
|
231
|
+
self.factor == 1.0
|
232
|
+
end
|
233
|
+
|
234
|
+
# Determine is a unit object represents an SI named unit
|
235
|
+
def is_si_unit?
|
236
|
+
self.is_a? SI
|
237
|
+
end
|
238
|
+
|
239
|
+
# Determine is a unit object represents an NonSI named unit
|
240
|
+
def is_non_si_unit?
|
241
|
+
self.is_a? NonSI
|
242
|
+
end
|
243
|
+
|
244
|
+
# Determine is a unit object represents an compound unit consisting of SI
|
245
|
+
# or non-SI named units
|
246
|
+
def is_compound_unit?
|
247
|
+
self.is_a? Compound
|
248
|
+
end
|
249
|
+
|
250
|
+
def is_dimensionless?
|
251
|
+
self.dimensions.is_dimensionless?
|
252
|
+
end
|
253
|
+
|
254
|
+
# Determine if self is the same unit as another. Similarity is based on
|
255
|
+
# representing the same physical quantity (i.e. dimensions) and the same
|
256
|
+
# factor and scaling values.
|
257
|
+
#
|
258
|
+
# Unit.metre.is_same_as? Unit.foot #=> false
|
259
|
+
#
|
260
|
+
# Unit.metre.is_same_as? Unit.gram #=> false
|
261
|
+
#
|
262
|
+
# Unit.metre.is_same_as? Unit.metre #=> true
|
263
|
+
#
|
264
|
+
# The base_units attr of Compound units are not compared. Neither are the
|
265
|
+
# names or symbols. This is because we want to recognise cases where units
|
266
|
+
# derived from operations and defined as compound units (therefore having
|
267
|
+
# compounded names and symbols) are the same as known, named units. For
|
268
|
+
# example, if we build a unit for energy using only SI units, we want to
|
269
|
+
# recognise this as a joule, rather than a kg m^2 s^-2, e.g.
|
270
|
+
#
|
271
|
+
# (Unit.kg*Unit.m*Unit.m/Unit.s/Unit.s).is_same_as? Unit.joule
|
272
|
+
#
|
273
|
+
# #=> true
|
274
|
+
#
|
275
|
+
def is_same_as?(other)
|
276
|
+
[:dimensions,:factor,:scaling].all? do |attr|
|
277
|
+
self.send(attr) == other.send(attr)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
alias :== :is_same_as?
|
282
|
+
|
283
|
+
# Check if unit has the identity as another, i.e. the same label. This is
|
284
|
+
# used to determine if a unit with the same accessors already exists in
|
285
|
+
# the module variable @@units
|
286
|
+
#
|
287
|
+
def has_same_identity_as?(other)
|
288
|
+
self.label == other.label and not self.label.nil?
|
289
|
+
end
|
290
|
+
|
291
|
+
# Determine if another unit is an alternative unit for self, i.e. do the two
|
292
|
+
# units represent the same physical quantity. This is established by compraing
|
293
|
+
# their dimensions attributes. E.g.
|
294
|
+
#
|
295
|
+
# Unit.metre.is_alternative_for? Unit.foot #=> true
|
296
|
+
#
|
297
|
+
# Unit.metre.is_alternative_for? Unit.gram #=> false
|
298
|
+
#
|
299
|
+
# Unit.metre.is_alternative_for? Unit.metre #=> true
|
300
|
+
#
|
301
|
+
def is_alternative_for?(other)
|
302
|
+
other.dimensions == self.dimensions
|
303
|
+
end
|
304
|
+
|
305
|
+
# List the alternative units for self, i.e. the other units which share
|
306
|
+
# the same dimensions.
|
307
|
+
#
|
308
|
+
# The list can be returned containing the alternative unit names, symbols
|
309
|
+
# or JScience labels by providing the required format as a symbolized
|
310
|
+
# argument.
|
311
|
+
#
|
312
|
+
# If no format is provide, the full unit objects for all alternative units
|
313
|
+
# are returned within the array
|
314
|
+
#
|
315
|
+
def alternatives(by=nil)
|
316
|
+
self.dimensions.units(nil).reject do |unit|
|
317
|
+
unit.is_same_as? self or not unit.acts_as_alternative_unit
|
318
|
+
end.map(&by)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Returns the SI unit for the same physical quantity which is represented
|
322
|
+
# by self, e.g.
|
323
|
+
#
|
324
|
+
def si_unit
|
325
|
+
self.dimensions.si_unit
|
326
|
+
end
|
327
|
+
|
328
|
+
def valid?
|
329
|
+
return true if valid_descriptors? and valid_dimensions?
|
330
|
+
raise InvalidArgumentError, "Unit definition must include a name, a symbol, a label and physical quantity"
|
331
|
+
end
|
332
|
+
|
333
|
+
def valid_descriptors?
|
334
|
+
[:name, :symbol, :label].all? do |attr|
|
335
|
+
attribute = send(attr)
|
336
|
+
attribute.is_a? String and not attribute.empty?
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def valid_dimensions?
|
341
|
+
@dimensions.is_a? Dimensions
|
342
|
+
end
|
343
|
+
|
344
|
+
# Returns an array representing the valid prefixes for the unit described
|
345
|
+
# by self
|
346
|
+
#
|
347
|
+
# If no argument is given, the array holds instances of Prefix::Base (or
|
348
|
+
# subclasses; SI, NonSI...). Alternatively only the names or symbols of each
|
349
|
+
# prefix can be returned by providing the appropriate prefix attribute as a
|
350
|
+
# symbolized argument, e.g.
|
351
|
+
#
|
352
|
+
# Unit.m.valid_prefixes #=> [ #<Quantify::Prefix: .. >,
|
353
|
+
# #<Quantify::Prefix: .. >,
|
354
|
+
# ... ]
|
355
|
+
#
|
356
|
+
# Unit.m.valid_prefixes :name #=> [ "deca", "hecto", "kilo",
|
357
|
+
# "mega", "giga", "tera"
|
358
|
+
# ... ]
|
359
|
+
#
|
360
|
+
# Unit.m.valid_prefixes :symbol #=> [ "da", "h", "k", "M", "G",
|
361
|
+
# "T", "P" ... ]
|
362
|
+
#
|
363
|
+
def valid_prefixes(by=nil)
|
364
|
+
return empty_array = [] if self.is_compound_unit?
|
365
|
+
return Unit::Prefix.si_prefixes.map(&by) if is_si_unit?
|
366
|
+
return Unit::Prefix.non_si_prefixes.map(&by) if is_non_si_unit?
|
367
|
+
end
|
368
|
+
|
369
|
+
# Multiply two units together. This results in the generation of a compound
|
370
|
+
# unit.
|
371
|
+
#
|
372
|
+
def multiply(other)
|
373
|
+
options = []
|
374
|
+
self.instance_of?(Unit::Compound) ? options += self.base_units : options << self
|
375
|
+
other.instance_of?(Unit::Compound) ? options += other.base_units : options << other
|
376
|
+
Unit::Compound.new(*options)
|
377
|
+
end
|
378
|
+
|
379
|
+
# Divide one unit by another. This results in the generation of a compound
|
380
|
+
# unit.
|
381
|
+
#
|
382
|
+
# In the event that the new unit represents a known unit, the non-compound
|
383
|
+
# representation is returned (i.e. of the SI or NonSI class).
|
384
|
+
#
|
385
|
+
def divide(other)
|
386
|
+
options = []
|
387
|
+
self.instance_of?(Unit::Compound) ? options += self.base_units : options << self
|
388
|
+
|
389
|
+
if other.instance_of? Unit::Compound
|
390
|
+
options += other.base_units.map { |base| base.index *= -1; base }
|
391
|
+
else
|
392
|
+
options << CompoundBaseUnit.new(other,-1)
|
393
|
+
end
|
394
|
+
Unit::Compound.new(*options)
|
395
|
+
end
|
396
|
+
|
397
|
+
# Raise a unit to a power. This results in the generation of a compound
|
398
|
+
# unit, e.g. m^3.
|
399
|
+
#
|
400
|
+
# In the event that the new unit represents a known unit, the non-compound
|
401
|
+
# representation is returned (i.e. of the SI or NonSI class).
|
402
|
+
#
|
403
|
+
def pow(power)
|
404
|
+
return nil if power == 0
|
405
|
+
original_unit = self.clone
|
406
|
+
if power > 0
|
407
|
+
new_unit = self.clone
|
408
|
+
(power - 1).times { new_unit *= original_unit }
|
409
|
+
elsif power < 0
|
410
|
+
new_unit = reciprocalize
|
411
|
+
((power.abs) - 1).times { new_unit /= original_unit }
|
412
|
+
end
|
413
|
+
return new_unit
|
414
|
+
end
|
415
|
+
|
416
|
+
# Return new unit representing the reciprocal of self, i.e. 1/self
|
417
|
+
def reciprocalize
|
418
|
+
Unit.unity / self
|
419
|
+
end
|
420
|
+
|
421
|
+
alias :times :multiply
|
422
|
+
alias :* :multiply
|
423
|
+
alias :/ :divide
|
424
|
+
alias :** :pow
|
425
|
+
|
426
|
+
# Apply a prefix to self. Returns new unit according to the prefixed version
|
427
|
+
# of self, complete with modified name, symbol, factor, etc..
|
428
|
+
#
|
429
|
+
def with_prefix(name_or_symbol)
|
430
|
+
if self.name =~ /\A(#{valid_prefixes(:name).join("|")})/
|
431
|
+
raise InvalidArgumentError, "Cannot add prefix where one already exists: #{self.name}"
|
432
|
+
end
|
433
|
+
|
434
|
+
prefix = Unit::Prefix.for(name_or_symbol,valid_prefixes)
|
435
|
+
|
436
|
+
unless prefix.nil?
|
437
|
+
new_unit_options = {}
|
438
|
+
new_unit_options[:name] = "#{prefix.name}#{self.name}"
|
439
|
+
new_unit_options[:symbol] = "#{prefix.symbol}#{self.symbol}"
|
440
|
+
new_unit_options[:label] = "#{prefix.symbol}#{self.label}"
|
441
|
+
new_unit_options[:factor] = prefix.factor * self.factor
|
442
|
+
new_unit_options[:physical_quantity] = self.dimensions
|
443
|
+
self.class.new(new_unit_options)
|
444
|
+
else
|
445
|
+
raise InvalidArgumentError, "Prefix unit is not known: #{prefix}"
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def with_prefixes(*prefixes)
|
450
|
+
[prefixes].map { |prefix| self.with_prefix(prefix) }
|
451
|
+
end
|
452
|
+
|
453
|
+
# Return a hash representation of self containing each unit attribute (i.e
|
454
|
+
# each instance variable)
|
455
|
+
#
|
456
|
+
def to_hash
|
457
|
+
hash = {}
|
458
|
+
self.instance_variables.each do |var|
|
459
|
+
symbol = var.gsub("@","").to_sym
|
460
|
+
hash[symbol] = send symbol
|
461
|
+
end
|
462
|
+
return hash
|
463
|
+
end
|
464
|
+
|
465
|
+
# Enables shorthand for reciprocal of a unit, e.g.
|
466
|
+
#
|
467
|
+
# unit = Unit.m
|
468
|
+
#
|
469
|
+
# (1/unit).symbol #=> "m^-1"
|
470
|
+
#
|
471
|
+
def coerce(object)
|
472
|
+
if object.kind_of? Numeric and object == 1
|
473
|
+
return Unit.unity, self
|
474
|
+
else
|
475
|
+
raise InvalidArgumentError, "Cannot coerce #{self.class} into #{object.class}"
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Clone self and explicitly clone the associated Dimensions object located
|
480
|
+
# at @dimensions.
|
481
|
+
#
|
482
|
+
# This enables full or 'deep' copies of the already initialized units to be
|
483
|
+
# retrieved and manipulated without corrupting the known unit representations.
|
484
|
+
# (self.clone makes only a shallow copy, i.e. clones attributes but not
|
485
|
+
# referenced objects)
|
486
|
+
#
|
487
|
+
def initialize_copy(source)
|
488
|
+
super
|
489
|
+
instance_variable_set("@dimensions", dimensions.clone)
|
490
|
+
if self.is_compound_unit?
|
491
|
+
instance_variable_set("@base_units", base_units.map {|base| base.clone })
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# Provides syntactic sugar for several methods. E.g.
|
496
|
+
#
|
497
|
+
# Unit.metre.to_kilo
|
498
|
+
#
|
499
|
+
# is equivalent to Unit.metre.with_prefix :kilo.
|
500
|
+
#
|
501
|
+
# Unit.m.alternatives_by_name
|
502
|
+
#
|
503
|
+
# is equaivalent to Unit.m.alternatives :name
|
504
|
+
#
|
505
|
+
def method_missing(method, *args, &block)
|
506
|
+
if method.to_s =~ /(to_)(.*)/ and prefix = Prefix.for($2.to_sym)
|
507
|
+
return self.with_prefix prefix
|
508
|
+
elsif method.to_s =~ /(alternatives_by_)(.*)/ and self.respond_to? $2.to_sym
|
509
|
+
return self.alternatives $2.to_sym
|
510
|
+
elsif method.to_s =~ /(valid_prefixes_by_)(.*)/ and Prefix::Base.instance_methods.include? $2.to_s
|
511
|
+
return self.valid_prefixes $2.to_sym
|
512
|
+
end
|
513
|
+
super
|
514
|
+
end
|
515
|
+
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
|
2
|
+
module Quantify
|
3
|
+
module Unit
|
4
|
+
class CompoundBaseUnit
|
5
|
+
|
6
|
+
# Container class for compound unit base units. Each instance is represented
|
7
|
+
# by a unit and an index, i.e. a unit raised to some power. If no index is
|
8
|
+
# present, 1 is assumed.
|
9
|
+
#
|
10
|
+
# Instances of this class can be used to initialize base units, and are the
|
11
|
+
# structures which hold base units within compound units
|
12
|
+
#
|
13
|
+
|
14
|
+
attr_accessor :unit, :index
|
15
|
+
|
16
|
+
def initialize(unit,index=1)
|
17
|
+
@unit = Unit.match(unit) || raise(InvalidUnitError, "Base unit not known: #{unit}")
|
18
|
+
raise InvalidUnitError, "Base unit cannot be compound unit" if @unit.is_a? Compound
|
19
|
+
@index = index
|
20
|
+
end
|
21
|
+
|
22
|
+
def dimensions
|
23
|
+
@unit.dimensions ** @index
|
24
|
+
end
|
25
|
+
|
26
|
+
# Only refers to the unit index, rather than the dimensions configuration
|
27
|
+
# of the actual unit
|
28
|
+
#
|
29
|
+
def is_dimensionless?
|
30
|
+
@index == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
# Absolute index as names always contain 'per' before denominator units
|
34
|
+
def name
|
35
|
+
@unit.name.to_power(@index.abs)
|
36
|
+
end
|
37
|
+
|
38
|
+
def pluralized_name
|
39
|
+
@unit.pluralized_name.to_power(@index.abs)
|
40
|
+
end
|
41
|
+
|
42
|
+
def symbol
|
43
|
+
@unit.symbol.to_s + ( @index.nil? or @index == 1 ? "" : "^#{@index}" )
|
44
|
+
end
|
45
|
+
|
46
|
+
def label
|
47
|
+
@unit.label + (@index == 1 ? "" : "^#{@index}")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Reciprocalized version of label, i.e. sign changed. This is used to make
|
51
|
+
# a denominator unit renderable in cases where there are no numerator units,
|
52
|
+
# i.e. where no '/' appears in the label
|
53
|
+
#
|
54
|
+
def reciprocalized_label
|
55
|
+
@unit.label + (@index == -1 ? "" : "^#{@index * -1}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def factor
|
59
|
+
@unit.factor ** @index
|
60
|
+
end
|
61
|
+
|
62
|
+
def is_numerator?
|
63
|
+
@index > 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def is_denominator?
|
67
|
+
@index < 0
|
68
|
+
end
|
69
|
+
|
70
|
+
def is_si_unit?
|
71
|
+
@unit.is_si_unit?
|
72
|
+
end
|
73
|
+
|
74
|
+
def is_non_si_unit?
|
75
|
+
@unit.is_non_si_unit?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Physical quantity represented by self. This refers only to the unit, rather
|
79
|
+
# than the unit together with the index. Is used to match base units with
|
80
|
+
# similar units of same physical quantity
|
81
|
+
#
|
82
|
+
def measures
|
83
|
+
@unit.dimensions.physical_quantity
|
84
|
+
end
|
85
|
+
|
86
|
+
def initialize_copy(source)
|
87
|
+
instance_variable_set("@unit", unit.clone)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|