sy 2.0.2 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
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, :mapping
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, syn!: :of
26
+ ꜧ.must_have :dimension
27
27
  ꜧ.delete :dimension
28
28
  else args.shift end
29
- args << ꜧ.merge!( dimension: SY::Dimension.new( dim ) )
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 *args
40
- = args.extract_options!
41
- dim = case args.size
42
- when 0 then
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!( dimension: SY::Dimension.zero ) ) ).protect!
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 args
64
- puts "Quantity init #{args}" if SY::DEBUG
65
- @relative = args[:relative]
66
- comp = args[:composition]
67
- if comp.nil? then # composition not given
68
- puts "Composition not received, dimension expected." if SY::DEBUG
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 (#{comp})." if SY::DEBUG
73
- @composition = SY::Composition[ comp ]
66
+ puts "Composition received (#{composition})." if SY::DEBUG
67
+ @composition = SY::Composition[ composition ]
74
68
  @dimension = @composition.dimension
75
69
  end
76
- rel = args[:mapping] || args[:ratio]
77
- @mapping = SY::Mapping.new( rel ) if rel
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 mapping setter.
136
+ # Acts as setter of measure (of the pertinent standard quantity).
136
137
  #
137
- def set_mapping mapping
138
- @mapping = SY::Mapping.new( mapping )
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
- def import magnitude2
142
- quantity2, amount2 = magnitude2.quantity, magnitude2.amount
143
- magnitude mapping_to( quantity2 ).im.( amount2 )
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
- def export amount1, quantity2
147
- mapping_to( quantity2 ).export( magnitude( amount1 ), quantity2 )
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
- # Asks for a mapping of this quantity to another quantity.
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 mapping_to( q2 )
153
- puts "#{self.inspect} asked about mapping to #{q2}" if SY::DEBUG
154
- return SY::Mapping.identity if q2 == self or q2 == colleague
155
- puts "this mapping is not an identity" if SY::DEBUG
156
- raise SY::DimensionError, "#{self} vs. #{q2}!" unless same_dimension? q2
157
- if standardish? then
158
- puts "#{self} is a standardish quantity, will invert the #{q2} mapping" if SY::DEBUG
159
- return q2.mapping_to( self ).inverse
160
- end
161
- puts "#{self} is not a standardish quantity" if SY::DEBUG
162
- m1 = begin
163
- if @mapping then
164
- puts "#{self} has @mapping defined" if SY::DEBUG
165
- @mapping
166
- elsif colleague.mapping then
167
- puts "#{colleague} has @mapping defined" if SY::DEBUG
168
- colleague.mapping
169
- else
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 mapping && q2.mapping then
220
- raise SY::QuantityError, "Mapping mismatch: #{self}, #{q2}!" unless
221
- mapping == q2.mapping
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 arg
248
- Magnitude().new quantity: self, amount: arg
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 args={}
254
- Unit().new( args.merge( quantity: self ) ).tap { |u| ( units << u ).uniq! }
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 the current @standard_unit).
258
- # For standard units, amount is implicitly 1. So :amount name argument, when
259
- # supplied, has a different meaning – sets the mapping of its quantity.
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 args={}
262
- explain_amount_of_standard_units if args[:amount].is_a? Numeric # n00b help
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
- args.may_have( :mapping, syn!: :amount )
265
- = args.delete( :mapping )
266
- set_mapping( ᴍ.amount ) if ᴍ
267
- args.update amount: 1 # substitute amount 1 as required for standard units
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
- unit( args )
271
- .tap { |u| ( units.unshift u ).uniq! } )
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
- if @Magnitude then @Magnitude else
376
- mmod = MagnitudeModule()
377
- mixin = relative? ? SY::SignedMagnitude : SY::AbsoluteMagnitude
378
- qnt_ɴ_λ = -> { name ? "#{name}@%s" : "#<Quantity:#{object_id}@%s>" }
379
-
380
- @Magnitude = Class.new do
381
- include mmod
382
- include mixin
383
-
384
- singleton_class.class_exec do
385
- define_method :zero do # Costructor of zero magnitudes
386
- new amount: 0
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 |args={}| # Customized #standard.
409
- @standard ||= new args.merge( quantity: qnt )
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" # as for @Magnitude applies.)
418
+ ɴλ.call % "Unit" # as for @Magnitude applies.)
414
419
  end
415
420
  end
416
421
  end