phys-units 0.9.2 → 0.9.3

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.
@@ -0,0 +1,10 @@
1
+ require "phys/units"
2
+
3
+ # Compatibility to Quanty, the former name.
4
+ # Activated by:
5
+ # requie "phys/units/quanty"
6
+ class Quanty < Phys::Quantity
7
+ end
8
+
9
+ def Quanty(*arg); Phys::Quantity(*a)
10
+ end
@@ -9,61 +9,110 @@
9
9
 
10
10
  module Phys
11
11
 
12
+ # Phys::Unit is a class to represent Physical Unit of Measure.
13
+ # It must have:
14
+ # * *Factor* of the unit. Conversion factor to +dimension+,
15
+ # i.e., its base units.
16
+ # * *Dimension* of the unit.
17
+ # Dimension is a hash table with base units and dimension values.
18
+ # Example:
19
+ # Phys::Unit["N"].dimension #=> {"kg"=>1, "m"=>1, "s"=>-2}
20
+ #== Usage
21
+ # require "phys/units"
22
+ # Q = Phys::Quantity
23
+ # U = Phys::Unit
24
+ #
25
+ # U["miles"] / U["hr"] #=> #<Phys::Unit 0.44704,{"m"=>1, "s"=>-1}>
26
+ # U["hr"] + U["30 min"] #=> #<Phys::Unit 5400,{"s"=>1}>
27
+ # U["(m/s)"]**2 #=> #<Phys::Unit 1,{"m"=>2, "s"=>-2}>
28
+ #
29
+ # case Q[1,"miles/hr"]
30
+ # when U["m"]
31
+ # "length"
32
+ # when U["s"]
33
+ # "time"
34
+ # when U["m/s"]
35
+ # "velocity"
36
+ # else
37
+ # "other"
38
+ # end #=> "velocity"
12
39
  class Unit
13
40
 
41
+ # @visibility private
14
42
  LIST = {}
43
+ # @visibility private
15
44
  PREFIX = {}
16
45
 
46
+ # @visibility private
17
47
  def self.prefix_regex
18
48
  @@prefix_regex
19
49
  end
20
50
 
21
- def initialize(arg,expr=nil,offset=nil)
51
+ # Initialize a new unit.
52
+ # @overload initialize(factor,dimension=nil)
53
+ # @param [Numeric] factor Unit conversion factor.
54
+ # @param [Hash] dimension Dimension hash.
55
+ # @overload initialize(expr,name=nil)
56
+ # @param [String] expr Unit string to be parsed later.
57
+ # @param [String] name Name of this unit.
58
+ # @overload initialize(unit,name=nil)
59
+ # @param [Phys::Unit] unit Copy contents from the argument.
60
+ # @param [String] name Name of this unit.
61
+ # @raise [TypeError] if invalit arg types.
62
+ #
63
+ def initialize(arg,extr=nil)
22
64
  case arg
23
65
  when Numeric
24
66
  arg = Rational(arg) if Integer===arg
25
67
  @factor = arg
26
- alloc_dim(expr)
68
+ alloc_dim(extr)
27
69
  when Phys::Unit
28
- replace(arg)
70
+ @factor = arg.factor
71
+ alloc_dim arg.dim
72
+ @name = extr
29
73
  when String
30
- @name = arg
31
- if expr.kind_of? Phys::Unit
32
- @factor = expr.factor
33
- @offset = expr.offset
34
- alloc_dim expr.dim
35
- else
36
- @expr = expr
37
- end
74
+ @expr = arg
75
+ @name = extr
38
76
  else
39
77
  raise TypeError,"invalid argument : #{arg.inspect}"
40
78
  end
41
79
  end
42
80
 
43
- attr_reader :name, :offset, :expr
81
+ # Unit expression to be parsed.
82
+ # @return [String, NilClass]
83
+ attr_reader :expr
84
+
85
+ # @visibility private
86
+ attr_reader :offset
87
+ # @visibility private
88
+ attr_reader :name
44
89
 
45
- def dim
90
+ # Dimension hash.
91
+ # @example
92
+ # Phys::Unit["N"].dimension #=> {"kg"=>1, "m"=>1, "s"=>-2}
93
+ # @return [Hash]
94
+ def dimension
46
95
  use_dimension
47
96
  @dim
48
97
  end
49
- alias dimension dim
98
+ alias dim dimension
50
99
 
100
+ # Conversion factor except the dimension-value.
101
+ # @return [Numeric]
51
102
  def factor
52
103
  use_dimension
53
104
  @factor
54
105
  end
55
106
 
107
+ # Dimension value. Returns PI number for pi dimension,
108
+ # otherwise returns one. see BaseUnit.
109
+ # @return [Numeric]
56
110
  def dimension_value
57
111
  1
58
112
  end
59
113
 
60
- def replace(x)
61
- @name = x.name.dup if x.name
62
- @factor = x.factor
63
- @offset = x.offset
64
- alloc_dim x.dim
65
- end
66
-
114
+ # (internal use)
115
+ # @visibility private
67
116
  def alloc_dim(hash=nil)
68
117
  case hash
69
118
  when Hash
@@ -74,32 +123,40 @@ module Phys
74
123
  @dim.default = 0
75
124
  end
76
125
 
126
+ # (internal use)
127
+ # Parse @expr string if it has not been parsed yet.
128
+ # This function must be called before access to @dim or @factor.
129
+ # @return [nil]
130
+ # @raise [UnitError] if unit parse error.
131
+ # @visibility private
77
132
  def use_dimension
78
133
  return if @dim && @factor
79
134
  if @expr && @dim.nil?
80
- puts "unit='#{@name}', parsing '#{@expr}'..." if Unit.debug
135
+ #puts "unit='#{@name}', parsing '#{@expr}'..." if Unit.debug
81
136
  unit = Unit.parse(@expr)
82
137
  case unit
83
138
  when Unit
84
139
  @dim = unit.dim
85
140
  @factor = unit.factor
86
141
  if @dim.nil? || @factor.nil?
87
- raise UnitParseError,"parse error : #{unit.inspect}"
142
+ raise UnitError,"parse error : #{unit.inspect}"
88
143
  end
89
144
  when Numeric
90
145
  @factor = unit
91
146
  alloc_dim
92
147
  else
93
- raise UnitParseError,"parse error : #{self.inspect}"
148
+ raise UnitError,"parse error : #{self.inspect}"
94
149
  end
95
150
  else
96
- raise UnitParseError,"undefined unit?: #{self.inspect}"
151
+ raise UnitError,"undefined unit?: #{self.inspect}"
97
152
  end
98
153
  end
99
154
 
155
+ # Inspect string.
156
+ # @return [String]
100
157
  def inspect
101
158
  a = [Utils.num_inspect(@factor), @dim.inspect]
102
- a << "@name="+@name.inspect if @name
159
+ #a << "@name="+@name.inspect if @name
103
160
  a << "@expr="+@expr.inspect if @expr
104
161
  a << "@offset="+@offset.inspect if @offset
105
162
  a << "@dimensionless=true" if @dimensionless
@@ -110,6 +167,8 @@ module Phys
110
167
  "#<#{self.class} #{s}>"
111
168
  end
112
169
 
170
+ # Make unit string from dimension.
171
+ # @return [String]
113
172
  def unit_string
114
173
  use_dimension
115
174
  a = []
@@ -125,8 +184,8 @@ module Phys
125
184
  end
126
185
  alias string_form unit_string
127
186
 
128
- # Unit conversion
129
-
187
+ # Conversion Factor to base unit.
188
+ # @return [Numeric]
130
189
  def conversion_factor
131
190
  use_dimension
132
191
  f = @factor
@@ -141,39 +200,77 @@ module Phys
141
200
  f
142
201
  end
143
202
 
203
+ # Returns true if scalar unit.
204
+ # *Scalar* means the unit does not have any dimension
205
+ # including dimensionless-dimension, and its factor is one.
206
+ # @return [Boolean]
144
207
  def scalar?
145
208
  use_dimension
146
209
  (@dim.nil? || @dim.empty?) && @factor==1
147
210
  end
148
211
 
212
+ # (internal use)
213
+ # @visibility private
149
214
  def dimensionless_deleted
150
215
  use_dimension
151
216
  hash = @dim.dup
152
217
  hash.delete_if{|k,v| LIST[k].dimensionless?}
153
218
  end
154
219
 
220
+ # (internal use)
155
221
  def dimensionless?
156
222
  use_dimension
157
223
  @dim.each_key.all?{|k| LIST[k].dimensionless?}
158
224
  end
159
225
 
160
- def same_dimension?(x)
161
- dimensionless_deleted == x.dimensionless_deleted
226
+ # (internal use)
227
+ # @visibility private
228
+ def same_dimension?(unit)
229
+ dimensionless_deleted == unit.dimensionless_deleted
162
230
  end
163
231
  alias same_dim? same_dimension?
164
232
 
233
+ # (internal use)
234
+ # @visibility private
235
+ # @raise [UnitError] if not dimensionless.
165
236
  def assert_dimensionless
166
237
  if !dimensionless?
167
- raise UnitConversionError,"Not dimensionless: #{self.inspect}"
238
+ raise UnitError,"Not dimensionless: #{self.inspect}"
168
239
  end
169
240
  end
170
241
 
171
- def assert_same_dimension(x)
172
- if !same_dimension?(x)
173
- raise UnitConversionError,"Different dimension: #{self.inspect} and #{x.inspect}"
242
+ # (internal use)
243
+ # @visibility private
244
+ # @raise [UnitError] if different dimensions.
245
+ def assert_same_dimension(unit)
246
+ if !same_dimension?(unit)
247
+ raise UnitError,"Different dimension: #{self.inspect} and #{unit.inspect}"
174
248
  end
175
249
  end
176
250
 
251
+ # Comformability of units. Returns true if unit conversion is allowed.
252
+ # @param [Object] x other object (unit or quantity or numeric or other)
253
+ # @return [Boolean]
254
+ def conformable?(x)
255
+ case x
256
+ when Unit
257
+ dimensionless_deleted == x.dimensionless_deleted
258
+ when Quantity
259
+ dimensionless_deleted == x.unit.dimensionless_deleted
260
+ when Numeric
261
+ dimensionless?
262
+ else
263
+ false
264
+ end
265
+ end
266
+ alias === conformable?
267
+ alias compatible? conformable?
268
+ alias conversion_allowed? conformable?
269
+
270
+ # Convert a quantity to this unit.
271
+ # @param [Phys::Quantity] quantity to be converted.
272
+ # @return [Phys::Quantity]
273
+ # @raise [UnitError] if unit conversion is failed.
177
274
  def convert(quantity)
178
275
  if Quantity===quantity
179
276
  assert_same_dimension(quantity.unit)
@@ -184,55 +281,75 @@ module Phys
184
281
  end
185
282
  end
186
283
 
284
+ # Convert a quantity to this unit only in scale.
285
+ # @param [Phys::Quantity] quantity to be converted.
286
+ # @return [Phys::Quantity]
287
+ # @raise [UnitError] if unit conversion is failed.
187
288
  def convert_scale(quantity)
188
289
  convert(quantity)
189
290
  end
190
291
 
191
- def convert_value_to_base_unit(x)
192
- x * conversion_factor
193
- end
194
-
195
- def convert_value_from_base_unit(x)
196
- x / conversion_factor
292
+ # Convert from a value in this unit to a value in base unit.
293
+ # @param [Numeric] value
294
+ # @return [Numeric]
295
+ def convert_value_to_base_unit(value)
296
+ value * conversion_factor
197
297
  end
198
298
 
199
- def convert_to_numeric(x)
200
- assert_dimensionless
201
- x * conversion_factor
299
+ # Convert from a value in base unit to a value in this unit.
300
+ # @param [Numeric] value
301
+ # @return [Numeric]
302
+ def convert_value_from_base_unit(value)
303
+ value / conversion_factor
202
304
  end
203
305
 
306
+ # Returns numeric value of this unit, i.e. conversion factor.
307
+ # Raises UnitError if not dimensionless.
308
+ # @return [Numeric]
309
+ # @raise [UnitError] if not dimensionless.
204
310
  def to_numeric
205
311
  assert_dimensionless
206
312
  conversion_factor
207
313
  end
208
314
  alias to_num to_numeric
209
315
 
210
- def convert_to_float(x)
211
- convert_to_numeric(x).to_f
212
- end
213
316
 
317
+ # Returns Base Unit excluding dimensionless-dimension.
318
+ # @return [Phys::Unit]
214
319
  def base_unit
215
320
  Unit.new(1,dimensionless_deleted)
216
321
  end
217
322
 
218
- # Unit operation
323
+ #--
219
324
 
325
+ # Return true if this unit is operable.
326
+ # @return [Boolean]
220
327
  def operable?
221
328
  true
222
329
  end
223
330
 
331
+ # Raise error if this unit is not operable.
332
+ # @return [nil]
333
+ # @visibility private
224
334
  def check_operable
225
335
  if !operable?
226
- raise UnitOperationError,"non-operable for #{inspect}"
336
+ raise UnitError,"non-operable for #{inspect}"
227
337
  end
338
+ nil
228
339
  end
229
340
 
230
- def check_operable2(x)
231
- if !(operable? && x.operable?)
232
- raise UnitOperationError,"non-operable: #{inspect} and #{x.inspect}"
341
+ # Raise error if this unit or argument is not operable.
342
+ # @return [nil]
343
+ # @visibility private
344
+ def check_operable2(unit)
345
+ if !(operable? && unit.operable?)
346
+ raise UnitError,"non-operable: #{inspect} and #{unit.inspect}"
233
347
  end
348
+ nil
234
349
  end
235
350
 
351
+ # (internal use)
352
+ # @visibility private
236
353
  def dimension_binop(other)
237
354
  x = self.dim
238
355
  y = other.dim
@@ -241,7 +358,7 @@ module Phys
241
358
  keys = x.keys | y.keys
242
359
  dims = {}
243
360
  dims.default = 0
244
- keys.each do |k|
361
+ keys.each do |k|
245
362
  v = yield( x[k]||0, y[k]||0 )
246
363
  dims[k] = v if v!=0
247
364
  end
@@ -254,6 +371,8 @@ module Phys
254
371
  end
255
372
  end
256
373
 
374
+ # (internal use)
375
+ # @visibility private
257
376
  def dimension_uop
258
377
  x = self.dim
259
378
  if Hash===x
@@ -269,6 +388,11 @@ module Phys
269
388
  end
270
389
  end
271
390
 
391
+ # Addition of units.
392
+ # Both units must be operable and conversion-allowed.
393
+ # @param [Phys::Unit, Numeric] x other unit
394
+ # @return [Phys::Unit]
395
+ # @raise [Phys::UnitError] if unit conversion is failed.
272
396
  def +(x)
273
397
  x = Unit.cast(x)
274
398
  check_operable2(x)
@@ -276,6 +400,11 @@ module Phys
276
400
  Unit.new(@factor+x.factor,@dim.dup)
277
401
  end
278
402
 
403
+ # Subtraction of units.
404
+ # Both units must be operable and conversion-allowed.
405
+ # @param [Phys::Unit, Numeric] x other unit
406
+ # @return [Phys::Unit]
407
+ # @raise [Phys::UnitError] if not conformable unit conversion is failed.
279
408
  def -(x)
280
409
  x = Unit.cast(x)
281
410
  check_operable2(x)
@@ -283,16 +412,28 @@ module Phys
283
412
  Unit.new(@factor-x.factor,@dim.dup)
284
413
  end
285
414
 
415
+ # Unary minus.
416
+ # This unit must be operable.
417
+ # @return [Phys::Unit]
418
+ # @raise [Phys::UnitError] if not operable.
286
419
  def -@
287
420
  check_operable
288
421
  use_dimension
289
422
  Unit.new(-@factor,@dim.dup)
290
423
  end
291
424
 
425
+ # Unary plus.
426
+ # Returns self.
427
+ # @return [Phys::Unit]
292
428
  def +@
293
429
  self
294
430
  end
295
431
 
432
+ # Multiplication of units.
433
+ # Both units must be operable.
434
+ # @param [Phys::Unit, Numeric] x other unit
435
+ # @return [Phys::Unit]
436
+ # @raise [Phys::UnitError] if not operable.
296
437
  def *(x)
297
438
  x = Unit.cast(x)
298
439
  if scalar?
@@ -306,10 +447,15 @@ module Phys
306
447
  Unit.new(factor,dims)
307
448
  end
308
449
 
450
+ # Division of units.
451
+ # Both units must be operable.
452
+ # @param [Phys::Unit, Numeric] x other unit
453
+ # @return [Phys::Unit]
454
+ # @raise [Phys::UnitError] if not operable.
309
455
  def /(x)
310
456
  x = Unit.cast(x)
311
457
  if scalar?
312
- return x.inv
458
+ return x.inverse
313
459
  elsif x.scalar?
314
460
  return self
315
461
  end
@@ -319,10 +465,15 @@ module Phys
319
465
  Unit.new(factor,dims)
320
466
  end
321
467
 
468
+ # Rational division of units.
469
+ # Both units must be operable.
470
+ # @param [Phys::Unit, Numeric] x other unit
471
+ # @return [Phys::Unit]
472
+ # @raise [Phys::UnitError] if not operable.
322
473
  def rdiv(x)
323
474
  x = Unit.cast(x)
324
475
  if scalar?
325
- return x.inv
476
+ return x.inverse
326
477
  elsif x.scalar?
327
478
  return self
328
479
  end
@@ -332,20 +483,32 @@ module Phys
332
483
  Unit.new(factor,dims)
333
484
  end
334
485
 
486
+ # @visibility private
335
487
  def self.rdiv(x,y)
336
488
  Unit.cast(x).rdiv(y)
337
489
  end
338
490
 
339
- def inv
491
+ # Inverse of units.
492
+ # This unit must be operable.
493
+ # @param [Phys::Unit, Numeric] unit
494
+ # @return [Phys::Unit]
495
+ # @raise [Phys::UnitError] if not operable.
496
+ def inverse
340
497
  check_operable
341
498
  dims = dimension_uop{|a| -a}
342
499
  Unit.new(Rational(1,self.factor), dims)
343
500
  end
344
501
 
345
- def self.inv(x)
346
- Unit.cast(x).inv
502
+ # @visibility private
503
+ def self.inverse(x)
504
+ Unit.cast(x).inverse
347
505
  end
348
506
 
507
+ # Exponentiation of units.
508
+ # This units must be operable.
509
+ # @param [Numeric] x numeric
510
+ # @return [Phys::Unit]
511
+ # @raise [Phys::UnitError] if not operable.
349
512
  def **(x)
350
513
  check_operable
351
514
  m = Utils.as_numeric(x)
@@ -353,18 +516,31 @@ module Phys
353
516
  Unit.new(@factor**m,dims)
354
517
  end
355
518
 
519
+ # @visibility private
356
520
  def self.func(fn, x)
357
521
  fn = 'log' if fn == 'ln'
358
522
  m = Unit.new(x).to_numeric
359
523
  Unit.new( Math.send(fn,m) )
360
524
  end
361
525
 
526
+ # Equality of units
527
+ # @param [Object] x other unit or object
528
+ # @return [Boolean]
362
529
  def ==(x)
530
+ case x
531
+ when Numeric
532
+ x = Unit.cast(x)
533
+ when Unit
534
+ else
535
+ return false
536
+ end
363
537
  use_dimension
364
- @factor == x.factor && @dim == x.dim &&
538
+ @factor == x.factor && @dim == x.dim &&
365
539
  offset == x.offset && dimension_value == x.dimension_value
366
540
  end
367
541
 
542
+ # Coerce.
543
+ # @return [Array]
368
544
  def coerce(x)
369
545
  [Unit.find_unit(x), self]
370
546
  end
@@ -372,27 +548,30 @@ module Phys
372
548
  end # Unit
373
549
 
374
550
 
551
+ # BaseUnit is a class to represent units defined by "!" in unit.dat
552
+ # including SI units.
375
553
  class BaseUnit < Unit
376
- def initialize(s,dimless=false,v=nil)
377
- case s
554
+
555
+ def self.define(name,expr,dimval=nil)
556
+ dimles = (expr == "!dimensionless")
557
+ LIST[name] = self.new(name,dimles,dimval)
558
+ end
559
+
560
+ def initialize(name,dimless=false,dimval=nil)
561
+ case name
378
562
  when String
379
- @name = s
563
+ @name = name
380
564
  @factor = 1
381
- @dim = {s=>1}
565
+ @dim = {name=>1}
382
566
  @dim.default = 0
383
567
  @dimensionless = dimless
384
- @dimension_value = v || 1
568
+ @dimension_value = dimval || 1
385
569
  else
386
570
  raise ArgumentError "BaseUnit#initialize: arg must be string: #{s}"
387
571
  end
388
572
  end
389
573
 
390
- def replace(x)
391
- super(x)
392
- @dimensionless = x.dimensionless
393
- @dimension_value = x.dimension_value
394
- end
395
-
574
+ # @visibility private
396
575
  def use_dimension
397
576
  end
398
577
 
@@ -400,6 +579,7 @@ module Phys
400
579
  @dimensionless
401
580
  end
402
581
 
582
+ # @visibility private
403
583
  def dimensionless_deleted
404
584
  if @dimensionless
405
585
  {}
@@ -408,42 +588,59 @@ module Phys
408
588
  end
409
589
  end
410
590
 
591
+ # Dimension value.
592
+ # Returns PI number for pi dimension, otherwise returns one.
593
+ # @return [Numeric]
594
+ # @example
595
+ # Phys::Unit["pi"].dimension_value #=> 3.141592653589793
411
596
  attr_reader :dimension_value
412
597
  end
413
598
 
414
599
 
600
+ # OffsetUnit is a class to represent units with offset value.
601
+ # Focused on Farenheight/Celsius temperature.
415
602
  class OffsetUnit < Unit
416
603
 
417
604
  def self.define(name,unit,offset=nil)
418
- LIST[name] = self.new(name,unit,offset)
605
+ LIST[name] = self.new(unit,name,offset)
419
606
  end
420
607
 
421
- def initialize(name,arg,offset)
422
- super(name,arg)
423
- @offset = offset
608
+ def initialize(arg,name=nil,offset=nil)
424
609
  if offset.nil?
425
610
  raise ArgumentError,"offset is not supplied"
426
611
  end
612
+ super(arg,name)
613
+ @offset = offset
427
614
  end
428
615
 
429
- def convert_value_to_base_unit(x)
430
- x * conversion_factor + @offset
431
- end
432
-
433
- def convert_value_from_base_unit(x)
434
- (x - @offset) / conversion_factor
435
- end
436
-
616
+ # Convert a quantity to this unit only in scale.
617
+ # @param [Phys::Quantity] quantity to be converted.
618
+ # @return [Phys::Quantity]
619
+ # @raise [UnitError] if unit conversion is failed.
437
620
  def convert_scale(quantity)
438
621
  if Quantity===quantity
439
- assert_same_dimension(quantity.unit)
622
+ assert_same_dimension(quantity.unit)
440
623
  v = quantity.value * quantity.unit.conversion_factor
441
624
  v = v / self.conversion_factor
442
625
  else
443
- raise TypeError,"not Quantitiy: #{quantity.inspect}"
626
+ raise UnitError,"not Quantitiy: #{quantity.inspect}"
444
627
  end
445
628
  end
446
629
 
630
+ # Convert from a value in this unit to a value in base unit.
631
+ # @param [Numeric] value
632
+ # @return [Numeric]
633
+ def convert_value_to_base_unit(value)
634
+ value * conversion_factor + @offset
635
+ end
636
+
637
+ # Convert from a value in base unit to a value in this unit.
638
+ # @param [Numeric] value
639
+ # @return [Numeric]
640
+ def convert_value_from_base_unit(value)
641
+ (value - @offset) / conversion_factor
642
+ end
643
+
447
644
  def operable?
448
645
  false
449
646
  end