sy 2.0.2 → 2.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sy.rb +8 -4
- data/lib/sy/absolute_magnitude.rb +10 -11
- data/lib/sy/composition.rb +23 -10
- data/lib/sy/dimension.rb +0 -1
- data/lib/sy/expressible_in_units.rb +0 -1
- data/lib/sy/fixed_assets_of_the_module.rb +0 -1
- data/lib/sy/imperial.rb +2 -0
- data/lib/sy/magnitude.rb +21 -21
- data/lib/sy/mapping.rb +103 -50
- data/lib/sy/measure.rb +135 -0
- data/lib/sy/quantity.rb +112 -107
- data/lib/sy/signed_magnitude.rb +5 -5
- data/lib/sy/unit.rb +12 -18
- data/lib/sy/version.rb +1 -1
- data/test/sy_test.rb +232 -232
- metadata +3 -2
data/lib/sy/measure.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Represents a certain way, that a quantity <em>measures</em> another quantity
|
3
|
+
# (reference quantity). Instance has two attributes:
|
4
|
+
#
|
5
|
+
# * @r - read closure, for converting from the measured reference quantity.
|
6
|
+
# * @w - write closure, for converting back to the reference quantity.
|
7
|
+
#
|
8
|
+
# Convenience methods #read and #write facilitate their use.
|
9
|
+
#
|
10
|
+
class SY::Measure
|
11
|
+
class << self
|
12
|
+
# Identity measure.
|
13
|
+
#
|
14
|
+
def identity
|
15
|
+
simple_scale 1
|
16
|
+
end
|
17
|
+
|
18
|
+
# Simple scaling measure. (Eg. pounds vs kilograms)
|
19
|
+
#
|
20
|
+
def simple_scale scale
|
21
|
+
new( ratio: scale )
|
22
|
+
end
|
23
|
+
|
24
|
+
# Simple offset measure. (Such as °C)
|
25
|
+
#
|
26
|
+
def simple_offset offset
|
27
|
+
new( r: lambda { |ref_amnt| ref_amnt - offset },
|
28
|
+
w: lambda { |amnt| amnt + offset } )
|
29
|
+
end
|
30
|
+
|
31
|
+
# Linear (scaled offset) measure. Expects a hash with 2 points demonstrating
|
32
|
+
# the relationship: { ref_amount_1 => amount_1, ref_amount_2 => amount_2 }.
|
33
|
+
# (Example: Fahrenheit degrees vs. Kelvins.)
|
34
|
+
#
|
35
|
+
def linear hsh
|
36
|
+
ref_amnt_1, amnt_1, ref_amnt_2, amnt_2 = hsh.to_a.flatten
|
37
|
+
scale = ( ref_amnt_2 - ref_amnt_1 ) / ( amnt_2 - amnt_1 )
|
38
|
+
new( r: lambda { |ref_amnt| amnt_1 + ( ref_amnt - ref_amnt_1 ) / scale },
|
39
|
+
w: lambda { |amnt| ref_amnt_1 + ( amnt - amnt_1 ) * scale } )
|
40
|
+
end
|
41
|
+
|
42
|
+
# Logarithmic.
|
43
|
+
#
|
44
|
+
def logarithmic base=Math::E
|
45
|
+
new( r: lambda { |ref_amnt| Math.log ref_amnt, base },
|
46
|
+
w: lambda { |amnt| base ** amnt } )
|
47
|
+
end
|
48
|
+
|
49
|
+
# Negative logarithmic.
|
50
|
+
#
|
51
|
+
def negative_logarithmic base=Math::E
|
52
|
+
new( r: lambda { |ref_amnt| -Math.log( ref_amnt, base ) },
|
53
|
+
w: lambda { |amnt| base ** ( -amnt ) } )
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :r, :w, :ratio
|
58
|
+
|
59
|
+
# The constructor expects :r and :w arguments for read and write closure.
|
60
|
+
#
|
61
|
+
def initialize( ratio: nil, r: nil, w: nil )
|
62
|
+
if ratio.nil?
|
63
|
+
fail TypeError, ":r and :w arguments must both be closures if ratio " +
|
64
|
+
"not given!" unless r.is_a?( Proc ) && w.is_a?( Proc )
|
65
|
+
@ratio, @r, @w = nil, r, w
|
66
|
+
else
|
67
|
+
fail ArgumentError, ":r or :w must not be given if :ratio given!" if r || w
|
68
|
+
@ratio = ratio
|
69
|
+
@r = lambda { |ref_amnt| ref_amnt / ratio }
|
70
|
+
@w = lambda { |amnt| amnt * ratio }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Convenience method to read a magnitude of a reference quantity.
|
75
|
+
#
|
76
|
+
def read magnitude_of_reference_quantity, quantity
|
77
|
+
quantity.magnitude r.( magnitude_of_reference_quantity.amount )
|
78
|
+
end
|
79
|
+
|
80
|
+
# Convenience method to convert a magnitude back to the reference quantity.
|
81
|
+
#
|
82
|
+
def write magnitude, reference_quantity
|
83
|
+
reference_quantity.magnitude w.( magnitude.amount )
|
84
|
+
end
|
85
|
+
|
86
|
+
# Inverse measure.
|
87
|
+
#
|
88
|
+
def inverse
|
89
|
+
if ratio.nil? then
|
90
|
+
self.class.new( r: w, w: r ) # swap closures
|
91
|
+
else
|
92
|
+
self.class.new( ratio: 1 / ratio )
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Measure composition (like f * g function composition).
|
97
|
+
#
|
98
|
+
def * other
|
99
|
+
if ratio.nil? then
|
100
|
+
r1, r2, w1, w2 = r, other.r, w, other.w
|
101
|
+
self.class.new( r: lambda { |ref_amnt| r1.( r2.( ref_amnt ) ) },
|
102
|
+
w: lambda { |amnt| w2.( w1.( amnt ) ) } )
|
103
|
+
else
|
104
|
+
self.class.new( ratio: ratio * other.ratio )
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Measure composition with inverse of another measure.
|
109
|
+
#
|
110
|
+
def / other
|
111
|
+
self * other.inverse
|
112
|
+
end
|
113
|
+
|
114
|
+
# Measure power.
|
115
|
+
#
|
116
|
+
def ** n
|
117
|
+
if ratio.nil? then
|
118
|
+
r_closure, w_closure = r, w
|
119
|
+
self.class.new( r: lambda { |ref_amnt|
|
120
|
+
n.times.inject ref_amnt do |m, _| r_closure.( m ) end
|
121
|
+
},
|
122
|
+
w: lambda { |amnt|
|
123
|
+
n.times.inject amnt do |m, _| w_closure.( m ) end
|
124
|
+
} )
|
125
|
+
else
|
126
|
+
self.class.new( ratio: ratio ** n )
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
|
132
|
+
def []( *args )
|
133
|
+
send *args
|
134
|
+
end
|
135
|
+
end # class SY::Measure
|
data/lib/sy/quantity.rb
CHANGED
@@ -12,7 +12,7 @@ class SY::Quantity
|
|
12
12
|
RELATIVE_QUANTITY_NAME_SUFFIX = "±"
|
13
13
|
|
14
14
|
attr_reader :MagnitudeModule, :Magnitude, :Unit
|
15
|
-
attr_reader :dimension, :composition
|
15
|
+
attr_reader :dimension, :composition
|
16
16
|
|
17
17
|
class << self
|
18
18
|
# Dimension-based quantity constructor. Examples:
|
@@ -23,10 +23,10 @@ class SY::Quantity
|
|
23
23
|
ꜧ = args.extract_options!
|
24
24
|
dim = case args.size
|
25
25
|
when 0 then
|
26
|
-
ꜧ.must_have :dimension
|
26
|
+
ꜧ.must_have :dimension
|
27
27
|
ꜧ.delete :dimension
|
28
28
|
else args.shift end
|
29
|
-
args << ꜧ.merge!(
|
29
|
+
args << ꜧ.merge!( of: SY::Dimension.new( dim ) )
|
30
30
|
return new( *args ).protect!
|
31
31
|
end
|
32
32
|
|
@@ -36,14 +36,10 @@ class SY::Quantity
|
|
36
36
|
# <tt>Quantity.standard of: "L.T⁻²"
|
37
37
|
# (Both should give Acceleration as their result.)
|
38
38
|
#
|
39
|
-
def standard
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
ꜧ.must_have :dimension, syn!: :of
|
44
|
-
ꜧ.delete :dimension
|
45
|
-
else args.shift end
|
46
|
-
return SY.Dimension( dim ).standard_quantity
|
39
|
+
def standard( of: nil )
|
40
|
+
fail ArgumentError, "Dimension (:of argument) must be given!" if of.nil?
|
41
|
+
puts "Constructing standard quantity of #{of} dimension" if SY::DEBUG
|
42
|
+
return SY.Dimension( of ).standard_quantity
|
47
43
|
end
|
48
44
|
|
49
45
|
# Dimensionless quantity constructor alias.
|
@@ -52,7 +48,7 @@ class SY::Quantity
|
|
52
48
|
ꜧ = args.extract_options!
|
53
49
|
raise TErr, "Dimension not zero!" unless ꜧ[:dimension].zero? if
|
54
50
|
ꜧ.has? :dimension, syn!: :of
|
55
|
-
new( *( args << ꜧ.merge!(
|
51
|
+
new( *( args << ꜧ.merge!( of: SY::Dimension.zero ) ) ).protect!
|
56
52
|
end
|
57
53
|
alias :zero :dimensionless
|
58
54
|
end
|
@@ -60,21 +56,26 @@ class SY::Quantity
|
|
60
56
|
# Standard constructor of a metrological quantity. A quantity may have
|
61
57
|
# a name and a dimension.
|
62
58
|
#
|
63
|
-
def initialize
|
64
|
-
puts "Quantity init #{
|
65
|
-
@relative =
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
dim = args[:dimension] || args[:of]
|
70
|
-
@dimension = SY.Dimension( dim )
|
59
|
+
def initialize( relative: nil, composition: nil, of: nil, measure: nil, amount: nil, **nn )
|
60
|
+
puts "Quantity init relative: #{relative}, composition: #{composition}, measure: #{measure}, #{nn}" if SY::DEBUG
|
61
|
+
@relative = relative
|
62
|
+
if composition.nil? then
|
63
|
+
puts "Composition not given, dimension expected." if SY::DEBUG
|
64
|
+
@dimension = SY.Dimension( of )
|
71
65
|
else
|
72
|
-
puts "Composition received (#{
|
73
|
-
@composition = SY::Composition[
|
66
|
+
puts "Composition received (#{composition})." if SY::DEBUG
|
67
|
+
@composition = SY::Composition[ composition ]
|
74
68
|
@dimension = @composition.dimension
|
75
69
|
end
|
76
|
-
|
77
|
-
|
70
|
+
@measure = measure.is_a?( SY::Measure ) ? measure :
|
71
|
+
if measure.nil? then
|
72
|
+
if amount.nil? then nil else
|
73
|
+
SY::Measure.simple_scale( amount )
|
74
|
+
end
|
75
|
+
else
|
76
|
+
fail ArgumentError, ":amount and :measure shouldn't be both supplied" unless amount.nil?
|
77
|
+
SY::Measure.simple_scale( measure )
|
78
|
+
end
|
78
79
|
puts "Composition of the initialized instance is #{composition}." if SY::DEBUG
|
79
80
|
end
|
80
81
|
|
@@ -132,56 +133,52 @@ class SY::Quantity
|
|
132
133
|
"match the dimension" do |comp| comp.dimension == dimension end
|
133
134
|
end
|
134
135
|
|
135
|
-
# Acts as
|
136
|
+
# Acts as setter of measure (of the pertinent standard quantity).
|
136
137
|
#
|
137
|
-
def
|
138
|
-
@
|
138
|
+
def set_measure measure
|
139
|
+
@measure = if measure.is_a?( SY::Measure ) then
|
140
|
+
measure
|
141
|
+
else
|
142
|
+
SY::Measure.simple_scale( measure )
|
143
|
+
end
|
139
144
|
end
|
140
145
|
|
141
|
-
|
142
|
-
|
143
|
-
|
146
|
+
# Converts magnitude of another quantity to a magnitude of this quantity.
|
147
|
+
#
|
148
|
+
def read magnitude_of_other_quantity
|
149
|
+
other_quantity = magnitude_of_other_quantity.quantity
|
150
|
+
other_amount = magnitude_of_other_quantity.amount
|
151
|
+
magnitude measure( of: other_quantity ).r.( other_amount )
|
144
152
|
end
|
145
153
|
|
146
|
-
|
147
|
-
|
154
|
+
# Converts an amount of this quantity to a magnitude of other quantity.
|
155
|
+
#
|
156
|
+
def write amount_of_this_quantity, other_quantity
|
157
|
+
measure( of: other_quantity )
|
158
|
+
.write( magnitude( amount_of_this_quantity ), other_quantity )
|
148
159
|
end
|
149
160
|
|
150
|
-
#
|
161
|
+
# Creates a measure of a specified other quantity. If no :of is specified,
|
162
|
+
# simply acts as a getter of @measure attribute.
|
151
163
|
#
|
152
|
-
def
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
raise SY::DimensionError, "#{self} vs. #{
|
157
|
-
if standardish?
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
puts "Neither #{self} nor its colleague has @mapping defined" if SY::DEBUG
|
171
|
-
puts "Will ask #{self}.composition to infer the mapping" if SY::DEBUG
|
172
|
-
composition.infer_mapping
|
173
|
-
end
|
174
|
-
rescue NoMethodError
|
175
|
-
raise SY::QuantityError,"Mapping from #{self} to #{q2} cannot be inferred!"
|
176
|
-
end
|
177
|
-
if q2.standardish? then
|
178
|
-
puts "#{q2} is standardish, obtained mapping can be returned directly." if SY::DEBUG
|
179
|
-
return m1
|
180
|
-
else
|
181
|
-
puts "#{q2} not standardish, obtained mapping maps only to #{standard}, and " +
|
182
|
-
"therefrom, composition with mapping from #{standard} to #{q2} will be needed" if SY::DEBUG
|
183
|
-
return m1 * standard.mapping_to( q2 )
|
184
|
-
end
|
164
|
+
def measure( of: nil )
|
165
|
+
return @measure if of.nil? # act as simple getter if :of not specified
|
166
|
+
puts "#{self.inspect} asked about measure of #{of}" if SY::DEBUG
|
167
|
+
return SY::Measure.identity if of == self or of == colleague
|
168
|
+
raise SY::DimensionError, "#{self} vs. #{of}!" unless same_dimension? of
|
169
|
+
return of.measure( of: of.standard ).inverse if standardish?
|
170
|
+
m = begin
|
171
|
+
puts "composition is #{composition}, class #{composition.class}" if SY::DEBUG
|
172
|
+
measure ||
|
173
|
+
colleague.measure ||
|
174
|
+
composition.infer_measure
|
175
|
+
rescue NoMethodError
|
176
|
+
fail SY::QuantityError, "Measure of #{of} by #{self} impossible!"
|
177
|
+
end
|
178
|
+
return m if of.standardish?
|
179
|
+
puts "#{of} not standardish, obtained measure relates to #{standard}, and " +
|
180
|
+
"it will have to be extended to #{of}." if SY::DEBUG
|
181
|
+
return m * standard.measure( of: of )
|
185
182
|
end
|
186
183
|
|
187
184
|
# Is the quantity relative?
|
@@ -216,9 +213,9 @@ class SY::Quantity
|
|
216
213
|
same_dimension? q2
|
217
214
|
raise SY::QuantityError, "#{self} an #{q2} are both " +
|
218
215
|
"{relative? ? 'relative' : 'absolute'}!" if relative? == q2.relative?
|
219
|
-
if
|
220
|
-
raise SY::QuantityError, "
|
221
|
-
|
216
|
+
if measure && q2.measure then
|
217
|
+
raise SY::QuantityError, "Measure mismatch: #{self}, #{q2}!" unless
|
218
|
+
measure == q2.measure
|
222
219
|
end
|
223
220
|
@colleague = q2
|
224
221
|
q2.instance_variable_set :@colleague, self
|
@@ -244,31 +241,38 @@ class SY::Quantity
|
|
244
241
|
|
245
242
|
# Constructs a new absolute magnitude of this quantity.
|
246
243
|
#
|
247
|
-
def magnitude
|
248
|
-
Magnitude().new
|
244
|
+
def magnitude amount
|
245
|
+
Magnitude().new of: self, amount: amount
|
249
246
|
end
|
250
247
|
|
251
248
|
# Constructs a new unit of this quantity.
|
252
249
|
#
|
253
|
-
def unit
|
254
|
-
Unit().new(
|
250
|
+
def unit **nn
|
251
|
+
Unit().new( nn.update( of: self ) ).tap { |u| ( units << u ).uniq! }
|
255
252
|
end
|
256
253
|
|
257
|
-
# Constructor of a new standard unit (replacing
|
258
|
-
# For standard units, amount is implicitly 1. So :amount
|
259
|
-
#
|
254
|
+
# Constructor of a new standard unit (replacing current @standard_unit).
|
255
|
+
# For standard units, amount is implicitly 1. So :amount argument here has
|
256
|
+
# different meaning – it sets the measure of the quantity. Measure can also
|
257
|
+
# be specified more explicitly by :measure named argument.
|
260
258
|
#
|
261
|
-
def new_standard_unit
|
262
|
-
explain_amount_of_standard_units if
|
259
|
+
def new_standard_unit( amount: nil, measure: nil, **nn )
|
260
|
+
explain_amount_of_standard_units if amount.is_a? Numeric # n00b help
|
263
261
|
# For standard units, amount has special meaning of setting up mapping.
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
262
|
+
if measure then
|
263
|
+
raise ArgumentError, "When :measure is specified, :amount must not be " +
|
264
|
+
"expliticly specified." unless amount.nil?
|
265
|
+
raise TypeError, ":measure argument must be a SY::Measure!" unless
|
266
|
+
measure.is_a? SY::Measure
|
267
|
+
set_measure( measure )
|
268
|
+
else
|
269
|
+
set_measure( SY::Measure.simple_scale( amount.nil? ? 1 : amount.amount ) )
|
270
|
+
end
|
268
271
|
# Replace @standard_unit with the newly constructed unit.
|
269
272
|
Unit().instance_variable_set( :@standard,
|
270
|
-
|
271
|
-
|
273
|
+
unit( **nn ).tap do |u|
|
274
|
+
( units.unshift u ).uniq!
|
275
|
+
end )
|
272
276
|
end
|
273
277
|
|
274
278
|
# Quantity multiplication.
|
@@ -311,6 +315,8 @@ class SY::Quantity
|
|
311
315
|
# Returns the standard quantity for this quantity's dimension.
|
312
316
|
#
|
313
317
|
def standard
|
318
|
+
puts "Dimension of this quantity is #{dimension}" if SY::DEBUG
|
319
|
+
puts "Its standard quantity is #{dimension.standard_quantity}" if SY::DEBUG
|
314
320
|
dimension.standard_quantity
|
315
321
|
end
|
316
322
|
|
@@ -363,7 +369,7 @@ class SY::Quantity
|
|
363
369
|
#
|
364
370
|
def MagnitudeModule
|
365
371
|
@MagnitudeModule ||= if absolute? then
|
366
|
-
Module.new { include SY::Magnitude }
|
372
|
+
Module.new { include ::SY::Magnitude }
|
367
373
|
else
|
368
374
|
absolute.MagnitudeModule
|
369
375
|
end
|
@@ -372,26 +378,25 @@ class SY::Quantity
|
|
372
378
|
# Parametrized magnitude class.
|
373
379
|
#
|
374
380
|
def Magnitude
|
375
|
-
|
376
|
-
mmod = MagnitudeModule()
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
381
|
+
@Magnitude or
|
382
|
+
( mmod = MagnitudeModule()
|
383
|
+
mixin = relative? ? ::SY::SignedMagnitude : ::SY::AbsoluteMagnitude
|
384
|
+
qnt_ɴ_λ = -> { name ? "#{name}@%s" : "#<Quantity:#{object_id}@%s>" }
|
385
|
+
|
386
|
+
@Magnitude = Class.new do
|
387
|
+
include mmod
|
388
|
+
include mixin
|
389
|
+
|
390
|
+
singleton_class.class_exec do
|
391
|
+
define_method :zero do # Costructor of zero magnitudes
|
392
|
+
new amount: 0
|
393
|
+
end
|
394
|
+
|
395
|
+
define_method :to_s do # Customized #to_s. It must be a proc,
|
396
|
+
qnt_ɴ_λ.call % "Magnitude" # since the quantity owning @Magnitude
|
397
|
+
end # might not be named yet as of now.
|
387
398
|
end
|
388
|
-
|
389
|
-
define_method :to_s do # Customized #to_s. It must be a proc,
|
390
|
-
qnt_ɴ_λ.call % "Magnitude" # since the quantity owning @Magnitude
|
391
|
-
end # might not be named yet as of now.
|
392
|
-
end
|
393
|
-
end
|
394
|
-
end
|
399
|
+
end )
|
395
400
|
end
|
396
401
|
|
397
402
|
# Parametrized unit class.
|
@@ -405,12 +410,12 @@ class SY::Quantity
|
|
405
410
|
include SY::Unit
|
406
411
|
|
407
412
|
singleton_class.class_exec do
|
408
|
-
define_method :standard do |
|
409
|
-
@standard ||= new
|
413
|
+
define_method :standard do |**nn| # Customized #standard.
|
414
|
+
@standard ||= new **nn.update( of: qnt )
|
410
415
|
end
|
411
416
|
|
412
417
|
define_method :to_s do # Customized #to_s. (Same consideration
|
413
|
-
ɴλ.call % "Unit"
|
418
|
+
ɴλ.call % "Unit" # as for @Magnitude applies.)
|
414
419
|
end
|
415
420
|
end
|
416
421
|
end
|