sy 1.0.0
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 +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
|