sy 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/lib/sy.rb +274 -0
- data/lib/sy/absolute_magnitude.rb +96 -0
- data/lib/sy/abstract_algebra.rb +102 -0
- data/lib/sy/composition.rb +219 -0
- data/lib/sy/dimension.rb +182 -0
- data/lib/sy/expressible_in_units.rb +135 -0
- data/lib/sy/fixed_assets_of_the_module.rb +287 -0
- data/lib/sy/magnitude.rb +515 -0
- data/lib/sy/mapping.rb +84 -0
- data/lib/sy/matrix.rb +69 -0
- data/lib/sy/quantity.rb +455 -0
- data/lib/sy/signed_magnitude.rb +51 -0
- data/lib/sy/unit.rb +288 -0
- data/lib/sy/version.rb +4 -0
- data/lib/sy/wildcard_zero.rb +29 -0
- data/sy.gemspec +19 -0
- data/test/sy_test.rb +385 -0
- metadata +80 -0
data/lib/sy/mapping.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
# Represents relationship of two quantities. Provides import and export
|
4
|
+
# conversion closures. Instances are immutable and have 2 attributes:
|
5
|
+
#
|
6
|
+
# * im - import closure, converting amount of quantity 1 into quantity 2
|
7
|
+
# * ex - export closure, converting amount of quantity 2 into quantity 1
|
8
|
+
#
|
9
|
+
# Convenience methods for mapping magnitudes are:
|
10
|
+
#
|
11
|
+
# * import - like im, but operates on magnitudes
|
12
|
+
# * export - like ex, but operates on magnitudes
|
13
|
+
#
|
14
|
+
class SY::Mapping
|
15
|
+
class << self
|
16
|
+
def identity
|
17
|
+
new 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :ex, :im, :ratio
|
22
|
+
|
23
|
+
# Takes either a magnitude (1 argument), or 2 named arguments :im, :ex
|
24
|
+
# speficying amount import and export closure. For a magnitude, these
|
25
|
+
# closures are constructed automatically, assuming simple ratio rule.
|
26
|
+
#
|
27
|
+
def initialize arg
|
28
|
+
case arg
|
29
|
+
when Hash then
|
30
|
+
@ex, @im = arg[:ex], arg[:im]
|
31
|
+
else
|
32
|
+
@ratio = r = arg
|
33
|
+
@ex = lambda { |amount1| amount1 * r }
|
34
|
+
@im = lambda { |amount2| amount2 / r }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def import magnitude, from_quantity
|
39
|
+
from_quantity.magnitude @im.( magnitude.amount )
|
40
|
+
end
|
41
|
+
|
42
|
+
def export magnitude, to_quantity
|
43
|
+
to_quantity.magnitude @ex.( magnitude.amount )
|
44
|
+
end
|
45
|
+
|
46
|
+
def inverse
|
47
|
+
self.class.new begin
|
48
|
+
1 / @ratio
|
49
|
+
rescue NoMethodError, TypeError
|
50
|
+
i, e = im, ex
|
51
|
+
{ im: e, ex: i } # swap closures
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def * r2 # mapping composition
|
56
|
+
ç.new begin
|
57
|
+
@ratio * r2.ratio
|
58
|
+
rescue NoMethodError, TypeError
|
59
|
+
i1, i2, e1, e2 = im, r2.im, ex, r2.ex
|
60
|
+
{ ex: lambda { |a1| e2.( e1.( a1 ) ) }, # export compose
|
61
|
+
im: lambda { |a2| i1.( i2.( a2 ) ) } } # import compose
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def / r2
|
66
|
+
self * r2.inverse
|
67
|
+
end
|
68
|
+
|
69
|
+
def ** n
|
70
|
+
ç.new begin
|
71
|
+
n == 1 ? @ratio * 1 : @ratio ** n
|
72
|
+
rescue NoMethodError, TypeError
|
73
|
+
i, e = im, ex
|
74
|
+
{ ex: lambda { |a1| n.times.reduce a1 do |m, _| e.( m ) end },
|
75
|
+
im: lambda { |a2| n.times.reduce a2 do |m, _| i.( m ) end } }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def []( *args )
|
82
|
+
send *args
|
83
|
+
end
|
84
|
+
end # class SY::Mapping
|
data/lib/sy/matrix.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'matrix'
|
4
|
+
|
5
|
+
# As a matter of fact, current version of the Matrix class (by Marc-Andre
|
6
|
+
# Lafortune) does not work with physical magnitudes. It is a feature of the
|
7
|
+
# physical magnitudes, that they do not allow themselves summed with plain
|
8
|
+
# numbers or incompatible magnitudes. But current version of Matrix class,
|
9
|
+
# upon matrix multiplication, performs needless addition of the matrix elements
|
10
|
+
# to literal numeric 0.
|
11
|
+
#
|
12
|
+
# The obvious solution is to patch Matrix class so that the needless addition
|
13
|
+
# to literal 0 is no longer performed.
|
14
|
+
#
|
15
|
+
# More systematically, abstract algebra is to be added to Ruby, and Matrix is
|
16
|
+
# to require that its elements comply with monoid, group, ring, field, depending
|
17
|
+
# on the operation one wants to do with such matrices.
|
18
|
+
#
|
19
|
+
class Matrix
|
20
|
+
# Matrix multiplication.
|
21
|
+
#
|
22
|
+
def * arg # arg is matrix or vector or number
|
23
|
+
case arg
|
24
|
+
when Numeric
|
25
|
+
rows = @rows.map { |row| row.map { |e| e * arg } }
|
26
|
+
return new_matrix rows, column_size
|
27
|
+
when Vector
|
28
|
+
arg = Matrix.column_vector arg
|
29
|
+
result = self * arg
|
30
|
+
return result.column 0
|
31
|
+
when Matrix
|
32
|
+
Matrix.Raise ErrDimensionMismatch if column_size != arg.row_size
|
33
|
+
if empty? then # if empty?, then reduce uses WILDCARD_ZERO
|
34
|
+
rows = Array.new row_size do |i|
|
35
|
+
Array.new arg.column_size do |j|
|
36
|
+
( 0...column_size ).reduce WILDCARD_ZERO do |sum, c|
|
37
|
+
sum + arg[c, j] * self[i, c]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
else # if non-empty, reduce proceeds without WILDCARD_ZERO
|
42
|
+
rows = Array.new row_size do |i|
|
43
|
+
Array.new arg.column_size do |j|
|
44
|
+
( 0...column_size ).map { |c| arg[c, j] * self[i, c] }.reduce :+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
return new_matrix( rows, arg.column_size )
|
49
|
+
when SY::Magnitude # newly added - multiplication by a magnitude
|
50
|
+
# I am not happy with this explicit switch on SY::Magnitude type here.
|
51
|
+
# Perhaps coerce should handle this?
|
52
|
+
rows = Array.new row_size do |i|
|
53
|
+
Array.new column_size do |j|
|
54
|
+
self[i, j] * arg
|
55
|
+
end
|
56
|
+
end
|
57
|
+
return self.class[ *rows ]
|
58
|
+
else
|
59
|
+
compat_1, compat_2 = arg.coerce self
|
60
|
+
return compat_1 * compat_2
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates a matrix of prescribed dimensions filled with wildcard zeros.
|
65
|
+
#
|
66
|
+
def Matrix.wildcard_zero r_count, c_count=r_count
|
67
|
+
build r_count, c_count do |r, c| WILDCARD_ZERO end
|
68
|
+
end
|
69
|
+
end
|
data/lib/sy/quantity.rb
ADDED
@@ -0,0 +1,455 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
# Quantity.
|
4
|
+
#
|
5
|
+
class SY::Quantity
|
6
|
+
include NameMagic
|
7
|
+
|
8
|
+
# name_set_closure do |name, new_instance, old_name|
|
9
|
+
# new_instance.protect!; name
|
10
|
+
# end
|
11
|
+
|
12
|
+
RELATIVE_QUANTITY_NAME_SUFFIX = "±"
|
13
|
+
|
14
|
+
attr_reader :MagnitudeModule, :Magnitude, :Unit
|
15
|
+
attr_reader :dimension, :composition, :mapping
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Dimension-based quantity constructor. Examples:
|
19
|
+
# <tt>Quantity.of Dimension.new( "L.T⁻²" )</tt>
|
20
|
+
# <tt>Quantity.of "L.T⁻²"</tt>
|
21
|
+
#
|
22
|
+
def of *args
|
23
|
+
ꜧ = args.extract_options!
|
24
|
+
dim = case args.size
|
25
|
+
when 0 then
|
26
|
+
ꜧ.must_have :dimension, syn!: :of
|
27
|
+
ꜧ.delete :dimension
|
28
|
+
else args.shift end
|
29
|
+
args << ꜧ.merge!( dimension: SY::Dimension.new( dim ) )
|
30
|
+
return new( *args ).protect!
|
31
|
+
end
|
32
|
+
|
33
|
+
# Standard quantity. Example:
|
34
|
+
# <tt>Quantity.standard of: Dimension.new( "L.T⁻²" )</tt>
|
35
|
+
# or
|
36
|
+
# <tt>Quantity.standard of: "L.T⁻²"
|
37
|
+
# (Both should give Acceleration as their result.)
|
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
|
47
|
+
end
|
48
|
+
|
49
|
+
# Dimensionless quantity constructor alias.
|
50
|
+
#
|
51
|
+
def dimensionless *args
|
52
|
+
ꜧ = args.extract_options!
|
53
|
+
raise TErr, "Dimension not zero!" unless ꜧ[:dimension].zero? if
|
54
|
+
ꜧ.has? :dimension, syn!: :of
|
55
|
+
new( *( args << ꜧ.merge!( dimension: SY::Dimension.zero ) ) ).protect!
|
56
|
+
end
|
57
|
+
alias :zero :dimensionless
|
58
|
+
end
|
59
|
+
|
60
|
+
# Standard constructor of a metrological quantity. A quantity may have
|
61
|
+
# a name and a dimension.
|
62
|
+
#
|
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 )
|
71
|
+
else
|
72
|
+
puts "Composition received (#{comp})." if SY::DEBUG
|
73
|
+
@composition = SY::Composition[ comp ]
|
74
|
+
@dimension = @composition.dimension
|
75
|
+
end
|
76
|
+
rel = args[:mapping] || args[:ratio]
|
77
|
+
@mapping = SY::Mapping.new( rel ) if rel
|
78
|
+
puts "Composition of the initialized instance is #{composition}." if SY::DEBUG
|
79
|
+
end
|
80
|
+
|
81
|
+
# Simple quantity is one with simple composition. If nontrivial composition
|
82
|
+
# is known for the colleague, it is assumed that the same composition would
|
83
|
+
# apply for this quantity, so it is not simple.
|
84
|
+
#
|
85
|
+
def simple?
|
86
|
+
cᴍ = composition
|
87
|
+
cᴍ.empty? || cᴍ.singular? && cᴍ.first[0] == self
|
88
|
+
end
|
89
|
+
|
90
|
+
# Protected quantity is not allowed to be decomposed in the process of quantity
|
91
|
+
# simplification.
|
92
|
+
#
|
93
|
+
def protected?
|
94
|
+
@protected
|
95
|
+
end
|
96
|
+
|
97
|
+
# Protects quantity from decomposition.
|
98
|
+
#
|
99
|
+
def protect!
|
100
|
+
@protected = true
|
101
|
+
@composition ||= SY::Composition.singular self
|
102
|
+
return self
|
103
|
+
end
|
104
|
+
|
105
|
+
# Unprotects quantity from decomposition.
|
106
|
+
#
|
107
|
+
def unprotect!
|
108
|
+
@protected = false
|
109
|
+
@composition = nil if @composition == SY::Composition.singular( self )
|
110
|
+
return self
|
111
|
+
end
|
112
|
+
|
113
|
+
# Irreducible quantity is one which cannot or <em>should not</em> be reduced
|
114
|
+
# to its components in the process of quantity simplification.
|
115
|
+
#
|
116
|
+
def irreducible?
|
117
|
+
simple? or protected?
|
118
|
+
end
|
119
|
+
|
120
|
+
# Creates a composition from a dimension, or acts as composition getter
|
121
|
+
# if this has already been specified.
|
122
|
+
#
|
123
|
+
def composition
|
124
|
+
@composition || dimension.to_composition
|
125
|
+
end
|
126
|
+
|
127
|
+
# Acts as composition setter (dimension must match).
|
128
|
+
#
|
129
|
+
def set_composition comp
|
130
|
+
@composition = SY::Composition[ comp ]
|
131
|
+
.aT "composition, when redefined after initialization,",
|
132
|
+
"match the dimension" do |comp| comp.dimension == dimension end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Acts as mapping setter.
|
136
|
+
#
|
137
|
+
def set_mapping mapping
|
138
|
+
@mapping = SY::Mapping.new( mapping )
|
139
|
+
end
|
140
|
+
|
141
|
+
def import magnitude2
|
142
|
+
quantity2, amount2 = magnitude2.quantity, magnitude2.amount
|
143
|
+
magnitude mapping_to( quantity2 ).im.( amount2 )
|
144
|
+
end
|
145
|
+
|
146
|
+
def export amount1, quantity2
|
147
|
+
mapping_to( quantity2 ).export( magnitude( amount1 ), quantity2 )
|
148
|
+
end
|
149
|
+
|
150
|
+
# Asks for a mapping of this quantity to another quantity.
|
151
|
+
#
|
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
|
185
|
+
end
|
186
|
+
|
187
|
+
# Is the quantity relative?
|
188
|
+
#
|
189
|
+
def relative?
|
190
|
+
@relative ? true : false
|
191
|
+
end
|
192
|
+
|
193
|
+
# Is the quantity absolute? (Opposite of #relative?)
|
194
|
+
#
|
195
|
+
def absolute?
|
196
|
+
not relative?
|
197
|
+
end
|
198
|
+
|
199
|
+
# Relative quantity related to this quantity.
|
200
|
+
#
|
201
|
+
def relative
|
202
|
+
relative? ? self : colleague
|
203
|
+
end
|
204
|
+
|
205
|
+
# For an absolute quantity, colleague is the corresponding relative quantity.
|
206
|
+
# Vice-versa, for a relative quantity, colleague is its absolute quantity.
|
207
|
+
#
|
208
|
+
def colleague
|
209
|
+
@colleague ||= construct_colleague
|
210
|
+
end
|
211
|
+
|
212
|
+
# Acts as colleague setter.
|
213
|
+
#
|
214
|
+
def set_colleague q2
|
215
|
+
raise SY::DimensionError, "Mismatch: #{self}, #{q2}!" unless
|
216
|
+
same_dimension? q2
|
217
|
+
raise SY::QuantityError, "#{self} an #{q2} are both " +
|
218
|
+
"{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
|
222
|
+
end
|
223
|
+
@colleague = q2
|
224
|
+
q2.instance_variable_set :@colleague, self
|
225
|
+
end
|
226
|
+
|
227
|
+
# Absolute quantity related to this quantity.
|
228
|
+
#
|
229
|
+
def absolute
|
230
|
+
absolute? ? self : colleague
|
231
|
+
end
|
232
|
+
|
233
|
+
# Reader of standard unit.
|
234
|
+
#
|
235
|
+
def standard_unit
|
236
|
+
Unit().standard
|
237
|
+
end
|
238
|
+
|
239
|
+
# Presents an array of units ordered as favored by this quantity.
|
240
|
+
#
|
241
|
+
def units
|
242
|
+
@units ||= []
|
243
|
+
end
|
244
|
+
|
245
|
+
# Constructs a new absolute magnitude of this quantity.
|
246
|
+
#
|
247
|
+
def magnitude arg
|
248
|
+
Magnitude().new quantity: self, amount: arg
|
249
|
+
end
|
250
|
+
|
251
|
+
# Constructs a new unit of this quantity.
|
252
|
+
#
|
253
|
+
def unit args={}
|
254
|
+
Unit().new( args.merge( quantity: self ) ).tap { |u| ( units << u ).uniq! }
|
255
|
+
end
|
256
|
+
|
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.
|
260
|
+
#
|
261
|
+
def new_standard_unit args={}
|
262
|
+
explain_amount_of_standard_units if args[:amount].is_a? Numeric # n00b help
|
263
|
+
# 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
|
268
|
+
# Replace @standard_unit with the newly constructed unit.
|
269
|
+
Unit().instance_variable_set( :@standard,
|
270
|
+
unit( args )
|
271
|
+
.tap { |u| ( units.unshift u ).uniq! } )
|
272
|
+
end
|
273
|
+
|
274
|
+
# Quantity multiplication.
|
275
|
+
#
|
276
|
+
def * q2
|
277
|
+
puts "#{self.name} * #{q2.name}" if SY::DEBUG
|
278
|
+
rel = [ self, q2 ].any? &:relative
|
279
|
+
( SY::Composition[ self => 1 ] + SY::Composition[ q2 => 1 ] )
|
280
|
+
.to_quantity relative: rel
|
281
|
+
end
|
282
|
+
|
283
|
+
# Quantity division.
|
284
|
+
#
|
285
|
+
def / q2
|
286
|
+
puts "#{self.name} / #{q2.name}" if SY::DEBUG
|
287
|
+
rel = [ self, q2 ].any? &:relative?
|
288
|
+
( SY::Composition[ self => 1 ] - SY::Composition[ q2 => 1 ] )
|
289
|
+
.to_quantity relative: rel
|
290
|
+
end
|
291
|
+
|
292
|
+
# Quantity raising to a number.
|
293
|
+
#
|
294
|
+
def ** num
|
295
|
+
puts "#{self.name} ** #{num}" if SY::DEBUG
|
296
|
+
SY::Composition[ self => num ].to_quantity relative: relative?
|
297
|
+
end
|
298
|
+
|
299
|
+
# Is the quantity dimensionless?
|
300
|
+
#
|
301
|
+
def dimensionless?
|
302
|
+
dimension.zero?
|
303
|
+
end
|
304
|
+
|
305
|
+
# Make the quantity standard for its dimension.
|
306
|
+
#
|
307
|
+
def standard!
|
308
|
+
SY::Dimension.standard_quantities[ dimension ] = self
|
309
|
+
end
|
310
|
+
|
311
|
+
# Returns the standard quantity for this quantity's dimension.
|
312
|
+
#
|
313
|
+
def standard
|
314
|
+
dimension.standard_quantity
|
315
|
+
end
|
316
|
+
|
317
|
+
# Is the dimension standard?
|
318
|
+
#
|
319
|
+
def standard?
|
320
|
+
self == standard
|
321
|
+
end
|
322
|
+
|
323
|
+
# Is the dimension or its colleague standard?
|
324
|
+
#
|
325
|
+
def standardish?
|
326
|
+
standard? || colleague.standard?
|
327
|
+
end
|
328
|
+
|
329
|
+
# A string briefly describing the quantity.
|
330
|
+
#
|
331
|
+
def to_s
|
332
|
+
name.nil? ? "[#{dimension}]" : name.to_s
|
333
|
+
end
|
334
|
+
|
335
|
+
# Inspect string.
|
336
|
+
#
|
337
|
+
def inspect
|
338
|
+
"#<Quantity: #{to_s} >"
|
339
|
+
end
|
340
|
+
|
341
|
+
def coerce other
|
342
|
+
case other
|
343
|
+
when Numeric then
|
344
|
+
return SY::Amount.relative, self
|
345
|
+
when SY::Quantity then
|
346
|
+
# By default, coercion between quantities doesn't exist. The basic
|
347
|
+
# purpose of having quantities is to avoid mutual mixing of
|
348
|
+
# incompatible magnitudes, as in "one cannot sum pears with apples".
|
349
|
+
#
|
350
|
+
if other == self then
|
351
|
+
return other, self
|
352
|
+
else
|
353
|
+
raise SY::QuantityError, "#{other} and #{self} do not mix!"
|
354
|
+
end
|
355
|
+
else
|
356
|
+
raise TErr, "#{self} cannot be coerced into a #{other.class}!"
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
protected
|
361
|
+
|
362
|
+
# Main parametrized (ie. quantity-specific) module for magnitudes.
|
363
|
+
#
|
364
|
+
def MagnitudeModule
|
365
|
+
@MagnitudeModule ||= if absolute? then
|
366
|
+
Module.new { include SY::Magnitude }
|
367
|
+
else
|
368
|
+
absolute.MagnitudeModule
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# Parametrized magnitude class.
|
373
|
+
#
|
374
|
+
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
|
387
|
+
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
|
395
|
+
end
|
396
|
+
|
397
|
+
# Parametrized unit class.
|
398
|
+
#
|
399
|
+
def Unit
|
400
|
+
@Unit ||= if relative? then absolute.Unit else
|
401
|
+
qnt = self
|
402
|
+
ɴλ = -> { name ? "#{name}@%s" : "#<Quantity:#{object_id}@%s>" }
|
403
|
+
|
404
|
+
Class.new Magnitude() do # Unit class.
|
405
|
+
include SY::Unit
|
406
|
+
|
407
|
+
singleton_class.class_exec do
|
408
|
+
define_method :standard do |args={}| # Customized #standard.
|
409
|
+
@standard ||= new args.merge( quantity: qnt )
|
410
|
+
end
|
411
|
+
|
412
|
+
define_method :to_s do # Customized #to_s. (Same consideration
|
413
|
+
ɴλ.call % "Unit" # as for @Magnitude applies.)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
private
|
421
|
+
|
422
|
+
def construct_colleague
|
423
|
+
puts "#{self}#construct_colleague" if SY::DEBUG
|
424
|
+
ɴ = name
|
425
|
+
ʀsuffix = SY::Quantity::RELATIVE_QUANTITY_NAME_SUFFIX
|
426
|
+
rel = relative?
|
427
|
+
puts "#{self} is #{rel ? 'relative' : 'absolute'}" if SY::DEBUG
|
428
|
+
# Here, it is impossible to rely on Composition::QUANTITY_TABLE –
|
429
|
+
# on the contrary, the table relies on Quantity#colleague.
|
430
|
+
constr_ɴ = ->( ɴ, ʀ ) { ç.new composition: composition, ɴ: ɴ, relative: ʀ }
|
431
|
+
constr_anon = ->( ʀ ) { ç.new composition: composition, relative: ʀ }
|
432
|
+
# enough of preliminaries
|
433
|
+
if not rel then
|
434
|
+
inst = ɴ ? constr_ɴ.( "#{ɴ}#{ʀsuffix}", true ) : constr_anon.( true )
|
435
|
+
inst.aT { relative? }
|
436
|
+
elsif ɴ.to_s.ends_with?( ʀsuffix ) && ɴ.size > ʀsuffix.size
|
437
|
+
inst = constr_ɴ.( ɴ.to_s[0..ɴ.size-ʀsuffix.size-1], false )
|
438
|
+
inst.aT { absolute? }
|
439
|
+
else inst = constr_anon.( false ).aT { absolute? } end
|
440
|
+
inst.instance_variable_set :@colleague, self
|
441
|
+
return inst
|
442
|
+
end
|
443
|
+
|
444
|
+
def same_dimension? other
|
445
|
+
dimension == other.dimension
|
446
|
+
end
|
447
|
+
|
448
|
+
def explain_amount_of_standard_units
|
449
|
+
raise TErr, "The amount of standard units is, by definition, 1. When " +
|
450
|
+
":amount named parameter fis supplied to the construtor of a " +
|
451
|
+
"standard unit, it has different meaning: It must be given as " +
|
452
|
+
"a magnitude of another quantity of the same dimension, and it " +
|
453
|
+
"establishes relationship between this and the other quantity."
|
454
|
+
end
|
455
|
+
end # class SY::Quantity
|