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,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
|