sy 2.0.2 → 2.0.4
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.
- 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
|