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
@@ -0,0 +1,102 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
# Adds abstract algebra concepts to Ruby.
|
4
|
+
#
|
5
|
+
module Algebra
|
6
|
+
# A Monoid requires:
|
7
|
+
#
|
8
|
+
# Closed and associative addition: #add method
|
9
|
+
# Additive identity element: #additive_identity
|
10
|
+
#
|
11
|
+
module Monoid
|
12
|
+
def + summand; add summand end
|
13
|
+
def zero; additive_identity end
|
14
|
+
end
|
15
|
+
|
16
|
+
# A group is a monoid with additive inverse.
|
17
|
+
#
|
18
|
+
# additive inversion: #additive_inverse
|
19
|
+
#
|
20
|
+
module Group
|
21
|
+
include Monoid
|
22
|
+
def -@; additive_inverse end
|
23
|
+
def - subtrahend; add subtrahend.additive_inverse end
|
24
|
+
end
|
25
|
+
|
26
|
+
# A group that fulfills the condition of commutativity
|
27
|
+
#
|
28
|
+
# ( a.add b == b.add a ).
|
29
|
+
#
|
30
|
+
module AbelianGroup
|
31
|
+
include Group
|
32
|
+
end
|
33
|
+
|
34
|
+
# A ring is a commutative group with multiplication.
|
35
|
+
#
|
36
|
+
# multiplication: #multiply (associative, distributive)
|
37
|
+
# multiplicative identity element: #multiplicative_identity
|
38
|
+
#
|
39
|
+
module Ring
|
40
|
+
include AbelianGroup
|
41
|
+
def * multiplicand; multiply multiplicand end
|
42
|
+
def one; multiplicative_identity end
|
43
|
+
end
|
44
|
+
|
45
|
+
# A field is a ring that can do division.
|
46
|
+
#
|
47
|
+
module Field
|
48
|
+
include Ring
|
49
|
+
def inverse; multiplicative_inverse end
|
50
|
+
def / divisor; multiply divisor.multiplicative_inverse end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Patching Integer with Algebra::Ring compliance methods.
|
55
|
+
#
|
56
|
+
class << Integer
|
57
|
+
def additive_identity; 0 end
|
58
|
+
alias zero additive_identity
|
59
|
+
def add( other ); self + other end
|
60
|
+
def additive_inverse; -self end
|
61
|
+
def multiply( other ); self * other end
|
62
|
+
def multiplicative_identity; 1 end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Patching Float with Algebra::Field compliance methods.
|
66
|
+
#
|
67
|
+
class << Float
|
68
|
+
def additive_identity; 0.0 end
|
69
|
+
alias zero additive_identity
|
70
|
+
def add( other ); self + other end
|
71
|
+
def additive_inverse; -self end
|
72
|
+
def multiply( other ); self * other end
|
73
|
+
def multiplicative_identity; 1.0 end
|
74
|
+
alias one multiplicative_identity
|
75
|
+
def multiplicative_inverse; 1.0 / self end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Patching Rational with Algebra::Field compliance methods.
|
79
|
+
#
|
80
|
+
class << Rational
|
81
|
+
def additive_identity; Rational 0, 1 end
|
82
|
+
alias zero additive_identity
|
83
|
+
def add( other ); self + other end
|
84
|
+
def additive_inverse; -self end
|
85
|
+
def multiply( other ); self * other end
|
86
|
+
def multiplicative_identity; Rational 1, 1 end
|
87
|
+
alias one multiplicative_identity
|
88
|
+
def multiplicative_inverse; Rational( 1, 1 ) / self end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Patching Complex with #zero method.
|
92
|
+
#
|
93
|
+
class << Complex
|
94
|
+
def additive_identity; Complex 0.0, 0.0 end
|
95
|
+
alias zero additive_identity
|
96
|
+
def add( other ); self + other end
|
97
|
+
def additive_inverse; -self end
|
98
|
+
def multiply( other ); self * other end
|
99
|
+
def multiplicative_identity; Complex 1, 0 end
|
100
|
+
alias one multiplicative_identity
|
101
|
+
def multiplicative_inverse; Complex( 1, 0 ) / self end
|
102
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
# Composition of quantities.
|
4
|
+
#
|
5
|
+
class SY::Composition < Hash
|
6
|
+
# Simplification rules for quantity combinations.
|
7
|
+
#
|
8
|
+
SR = SIMPLIFICATION_RULES = []
|
9
|
+
|
10
|
+
# SY::Amount and SY::Amount.relative are disposable:
|
11
|
+
SR << -> ꜧ {
|
12
|
+
ꜧ.reject! { |qnt, _| qnt == SY::Amount || qnt == SY::Amount.relative }
|
13
|
+
}
|
14
|
+
# Relative quantities of the composition are absolutized:
|
15
|
+
SR << -> ꜧ {
|
16
|
+
ꜧ.select { |qnt, _| qnt.relative? }.each { |qnt, exp|
|
17
|
+
ꜧ.delete qnt
|
18
|
+
ꜧ.update qnt.absolute => exp
|
19
|
+
}
|
20
|
+
}
|
21
|
+
# Any quantities with exponent zero can be deleted:
|
22
|
+
SR << -> ꜧ {
|
23
|
+
ꜧ.reject! { |_, exp| exp == 0 }
|
24
|
+
}
|
25
|
+
#
|
26
|
+
SR << -> ꜧ {
|
27
|
+
begin
|
28
|
+
q1, q2, q3 = SY::MoleAmount, SY::LitreVolume, SY::Molarity
|
29
|
+
rescue NameError
|
30
|
+
return ꜧ
|
31
|
+
end
|
32
|
+
# transform = -> e1, e2 {
|
33
|
+
# return e1, e2 unless e1 != 0 && e2 != 0 && ( e1 <=> 0 ) != ( e2 <=> 0 )
|
34
|
+
# n = e1 <=> 0
|
35
|
+
# return e1 - n, e2 + n
|
36
|
+
# }
|
37
|
+
# e1 = ꜧ.delete q1
|
38
|
+
# e2 = ꜧ.delete q2
|
39
|
+
# ꜧ.merge! q1 => e1, q2 => e2
|
40
|
+
# else
|
41
|
+
# ꜧ.update q1 => e1, q2 => e2
|
42
|
+
# end
|
43
|
+
e1 = ꜧ.delete q1
|
44
|
+
e2 = ꜧ.delete q2
|
45
|
+
if e1 && e2 && e1 > 0 && e2 < 0 then
|
46
|
+
e1 -= 1
|
47
|
+
e2 += 1
|
48
|
+
e3 = ꜧ.delete q3
|
49
|
+
ꜧ.update q3 => ( e3 ? e3 + 1 : 1 )
|
50
|
+
end
|
51
|
+
ꜧ.update q1 => e1 if e1 && e1 != 0
|
52
|
+
ꜧ.update q2 => e2 if e2 && e2 != 0
|
53
|
+
return ꜧ
|
54
|
+
}
|
55
|
+
|
56
|
+
class << self
|
57
|
+
def singular quantity
|
58
|
+
self[ SY.Quantity( quantity ) => 1 ]
|
59
|
+
end
|
60
|
+
|
61
|
+
def empty
|
62
|
+
self[]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Cache for quantity construction.
|
67
|
+
#
|
68
|
+
QUANTITY_TABLE = Hash.new { |ꜧ, args|
|
69
|
+
if args.keys.include? [:name] || args.keys.include?( :ɴ ) then
|
70
|
+
ɴ = args.delete( :name ) || args.delete( :ɴ ) # won't cache name
|
71
|
+
ꜧ[args].tap { |ɪ| ɪ.name = ɴ } # recursion
|
72
|
+
elsif args.keys.include? :mapping then
|
73
|
+
ᴍ = args.delete( :mapping ) # won't cache mapping
|
74
|
+
ꜧ[args].tap { |ɪ| ɪ.set_mapping ᴍ } # recursion
|
75
|
+
elsif args.keys.include? :relative then
|
76
|
+
ʀ = args.delete( :relative ) ? true : false # won't cache :relative
|
77
|
+
ꜧ[args].send ʀ ? :relative : :absolute # recursion
|
78
|
+
else
|
79
|
+
cᴍ = SY::Composition[ args ].simplify
|
80
|
+
ꜧ[args] = if cᴍ != args then ꜧ[ cᴍ ] # recursion while #simplify
|
81
|
+
else x = cᴍ.expand # we'll try to #expand now
|
82
|
+
if x != cᴍ then ꜧ[ x ] # recursion while #expand
|
83
|
+
else
|
84
|
+
if x.empty? then # use std. ∅ quantity
|
85
|
+
SY.Dimension( :∅ ).standard_quantity
|
86
|
+
elsif x.singular? then
|
87
|
+
x.first[0] # unwrap the quantity
|
88
|
+
else # create new quantity
|
89
|
+
SY::Quantity.new composition: x
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
}
|
95
|
+
|
96
|
+
# Singular compositions consist of only one quantity.
|
97
|
+
#
|
98
|
+
def singular?
|
99
|
+
size == 1 && first[1] == 1
|
100
|
+
end
|
101
|
+
|
102
|
+
# Atomic compositions are singular compositions, whose quantity dimension
|
103
|
+
# is a base dimension.
|
104
|
+
#
|
105
|
+
def atomic?
|
106
|
+
singular? && first[0].dimension.base?
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns a new instance with same hash.
|
110
|
+
#
|
111
|
+
def +@
|
112
|
+
self.class[ self ]
|
113
|
+
end
|
114
|
+
|
115
|
+
# Negates hash exponents.
|
116
|
+
#
|
117
|
+
def -@
|
118
|
+
self.class[ self.with_values do |v| -v end ]
|
119
|
+
end
|
120
|
+
|
121
|
+
# Merges two compositions.
|
122
|
+
#
|
123
|
+
def + other
|
124
|
+
self.class[ self.merge( other ) { |_, v1, v2| v1 + v2 } ]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Subtracts two compositions.
|
128
|
+
#
|
129
|
+
def - other
|
130
|
+
self + -other
|
131
|
+
end
|
132
|
+
|
133
|
+
# Multiplication by a number.
|
134
|
+
#
|
135
|
+
def * number
|
136
|
+
self.class[ self.with_values do |v| v * number end ]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Division by a number.
|
140
|
+
#
|
141
|
+
def / number
|
142
|
+
self.class[ self.with_values do |val|
|
143
|
+
raise TErr, "Compositions with rational exponents " +
|
144
|
+
"not implemented!" if val % number != 0
|
145
|
+
val / number
|
146
|
+
end ]
|
147
|
+
end
|
148
|
+
|
149
|
+
# Simplifies a quantity hash by applying simplification rules.
|
150
|
+
#
|
151
|
+
def simplify
|
152
|
+
ꜧ = self.to_hash
|
153
|
+
puts "simplifying #{ꜧ}" if SY::DEBUG
|
154
|
+
SIMPLIFICATION_RULES.each { |rule| rule.( ꜧ ) }
|
155
|
+
self.class[ ꜧ ].tap { |_| puts "result is #{_}" if SY::DEBUG }
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns the quantity appropriate to this composition.
|
159
|
+
#
|
160
|
+
def to_quantity args={}
|
161
|
+
# All the work is delegated to the quantity table:
|
162
|
+
QUANTITY_TABLE[ args.merge( self ) ]
|
163
|
+
end
|
164
|
+
|
165
|
+
# Directly (without attempts to simplify) creates a new quantity from self.
|
166
|
+
# If self is empty or singular, SY::Amount, resp. the singular quantity
|
167
|
+
# in question is returned.
|
168
|
+
#
|
169
|
+
def new_quantity args={}
|
170
|
+
SY::Quantity.new args.merge( composition: self )
|
171
|
+
end
|
172
|
+
|
173
|
+
# Dimension of a quantity composition is the sum of its dimensions.
|
174
|
+
#
|
175
|
+
def dimension
|
176
|
+
map { |qnt, exp| qnt.dimension * exp }.reduce SY::Dimension.zero, :+
|
177
|
+
end
|
178
|
+
|
179
|
+
# Infers the mapping of the composition's quantity. (Mapping
|
180
|
+
# defines mapping of a quantity to the standard quantity of its dimension.)
|
181
|
+
#
|
182
|
+
def infer_mapping
|
183
|
+
puts "#infer_mapping; hash is #{self}" if SY::DEBUG
|
184
|
+
map do |qnt, exp|
|
185
|
+
qnt.standardish? ? SY::Mapping.identity :
|
186
|
+
qnt.mapping_to( qnt.standard ) ** exp
|
187
|
+
end.reduce( SY::Mapping.identity, :* )
|
188
|
+
end
|
189
|
+
|
190
|
+
# Simple composition is one that is either empty or singular.
|
191
|
+
#
|
192
|
+
def simple?
|
193
|
+
empty? or singular?
|
194
|
+
end
|
195
|
+
|
196
|
+
# Whether it is possible to expand this
|
197
|
+
def irreducible?
|
198
|
+
all? { |qnt, exp| qnt.irreducible? }
|
199
|
+
end
|
200
|
+
|
201
|
+
# Try to simplify the composition by decomposing its quantities.
|
202
|
+
#
|
203
|
+
def expand
|
204
|
+
return self if irreducible?
|
205
|
+
puts "#expand: #{self} not irreducible" if SY::DEBUG
|
206
|
+
self.class[ reduce( self.class.empty ) { |cᴍ, pair|
|
207
|
+
qnt, exp = pair
|
208
|
+
puts "#expand: qnt: #{qnt}, exp: #{exp}" if SY::DEBUG
|
209
|
+
puts "cᴍ is #{cᴍ}" if SY::DEBUG
|
210
|
+
( cᴍ + if qnt.irreducible? then
|
211
|
+
self.class.singular( qnt ) * exp
|
212
|
+
else
|
213
|
+
qnt.composition * exp
|
214
|
+
end.tap { |x| puts "Adding #{x}." if SY::DEBUG }
|
215
|
+
).tap { |x| puts "Result is #{x}." if SY::DEBUG }
|
216
|
+
} ]
|
217
|
+
.tap{ |rslt| puts "#expand: result is #{rslt}" if SY::DEBUG }
|
218
|
+
end
|
219
|
+
end # class SY::Composition
|
data/lib/sy/dimension.rb
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
# This class represents physical dimension of a metrological quantity.
|
4
|
+
#
|
5
|
+
class SY::Dimension
|
6
|
+
# Let's set up an instance variable (of the class) that will hold standard
|
7
|
+
# quantities of selected dimensions:
|
8
|
+
#
|
9
|
+
@standard_quantities ||= Hash.new { |ꜧ, missing_key|
|
10
|
+
if missing_key.is_a? SY::Dimension then
|
11
|
+
# Create a new quantity of that dimension:
|
12
|
+
ꜧ[missing_key] = SY::Quantity.of missing_key
|
13
|
+
else
|
14
|
+
# Otherwise, let SY.Dimension constructor judge:
|
15
|
+
ꜧ[ SY.Dimension missing_key ]
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
class << self
|
20
|
+
alias __new__ new
|
21
|
+
attr_reader :standard_quantities
|
22
|
+
|
23
|
+
# The #new constructor of SY::Dimension has been changed, so that the
|
24
|
+
# same instance is returned, if that dimension has already been created.
|
25
|
+
#
|
26
|
+
def new dim={}
|
27
|
+
ꜧ = case dim
|
28
|
+
when Hash then dim
|
29
|
+
when self then return dim # it is a dimension instance
|
30
|
+
else # we'll treat dimension_specification as SPS
|
31
|
+
SY::BASE_DIMENSIONS.parse_sps( dim )
|
32
|
+
end
|
33
|
+
# Zeros by default:
|
34
|
+
ꜧ.default! Hash[ SY::BASE_DIMENSIONS.base_symbols.map { |ß| [ß, 0] } ]
|
35
|
+
# Now we can see whether the instance of this dimension already exists.
|
36
|
+
return instances.find { |inst| inst.to_hash == ꜧ } ||
|
37
|
+
__new__( ꜧ ).tap { |inst| instances << inst }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Presents class-owned instances (array).
|
41
|
+
#
|
42
|
+
def instances
|
43
|
+
return @instances ||= []
|
44
|
+
end
|
45
|
+
|
46
|
+
# Base dimension constructor. Base dimension symbol is expeced as argument.
|
47
|
+
#
|
48
|
+
def base symbol
|
49
|
+
raise AErr, "Unknown base dimension: #{symbol}" unless
|
50
|
+
SY::BASE_DIMENSIONS.base_symbols.include? symbol
|
51
|
+
return new( symbol => 1 )
|
52
|
+
end
|
53
|
+
alias basic base
|
54
|
+
|
55
|
+
# Constructor for zero dimension ("dimensionless").
|
56
|
+
#
|
57
|
+
def zero; new end
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_accessor *SY::BASE_DIMENSIONS.base_symbols
|
61
|
+
|
62
|
+
# Dimension can be initialized either by supplying a hash
|
63
|
+
# (such as Dimension.new L: 1, T: -2) or by supplying a SPS
|
64
|
+
# (superscripted product string), such as Dimension.new( "L.T⁻²" ).
|
65
|
+
# It is also possible to supply a Dimension instance, which will
|
66
|
+
# result in a new Dimension instance equal to the supplied one.
|
67
|
+
#
|
68
|
+
def initialize hash
|
69
|
+
SY::BASE_DIMENSIONS.base_symbols.each { |ß|
|
70
|
+
instance_variable_set "@#{ß}", hash[ß]
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
# #[] method provides access to the dimension components, such as
|
75
|
+
# d = Dimension.new( L: 1, T: -2 ), d[:T] #=> -2
|
76
|
+
#
|
77
|
+
def [] ß
|
78
|
+
return send ß if SY::BASE_DIMENSIONS.letters.include? ß
|
79
|
+
raise AErr, "Unknown basic dimension: #{ß}"
|
80
|
+
end
|
81
|
+
|
82
|
+
#Two dimensions are equal, if their exponents are equal.
|
83
|
+
#
|
84
|
+
def == other
|
85
|
+
to_a == other.to_a
|
86
|
+
end
|
87
|
+
|
88
|
+
# Dimension arithmetic: addition. (+, -, *, /).
|
89
|
+
#
|
90
|
+
def + other
|
91
|
+
ç.new Hash[ SY::BASE_DIMENSIONS.base_symbols.map do |l|
|
92
|
+
[ l, self.send( l ) + other.send( l ) ]
|
93
|
+
end ]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Dimension arithmetic: subtraction.
|
97
|
+
#
|
98
|
+
def - other
|
99
|
+
ç.new Hash[ SY::BASE_DIMENSIONS.base_symbols.map do |l|
|
100
|
+
[ l, self.send( l ) - other.send( l ) ]
|
101
|
+
end ]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Dimension arithmetic: multiplication by a number.
|
105
|
+
#
|
106
|
+
def * number
|
107
|
+
ç.new Hash[ SY::BASE_DIMENSIONS.base_symbols.map do |l|
|
108
|
+
[ l, self.send( l ) * number ]
|
109
|
+
end ]
|
110
|
+
end
|
111
|
+
|
112
|
+
# Dimension arithmetic: division by a number.
|
113
|
+
#
|
114
|
+
def / number
|
115
|
+
ç.new Hash[ SY::BASE_DIMENSIONS.base_symbols.map do |l|
|
116
|
+
exp = send l
|
117
|
+
raise TErr, "Dimensions with rational exponents " +
|
118
|
+
"not implemented!" if exp % number != 0
|
119
|
+
[ l, exp / number ]
|
120
|
+
end ]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Conversion to an array (of exponents of in the order of the
|
124
|
+
# basic dimension letters).
|
125
|
+
#
|
126
|
+
def to_a
|
127
|
+
SY::BASE_DIMENSIONS.base_symbols.map { |l| self.send l }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Conversion to a hash (eg. { L: 1, M: 0, T: -2, Q: 0, Θ: 0 } ).⁻³
|
131
|
+
#
|
132
|
+
def to_hash
|
133
|
+
SY::BASE_DIMENSIONS.base_symbols.each_with_object Hash.new do |l, ꜧ|
|
134
|
+
ꜧ[ l ] = self.send( l )
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# True if the dimension is zero ("dimensionless"), otherwise false.
|
139
|
+
#
|
140
|
+
def zero?
|
141
|
+
to_a.all? { |e| e.zero? }
|
142
|
+
end
|
143
|
+
|
144
|
+
# True if the dimension is basic, otherwise false.
|
145
|
+
#
|
146
|
+
def base?
|
147
|
+
to_a.count( 1 ) == 1 &&
|
148
|
+
to_a.count( 0 ) == SY::BASE_DIMENSIONS.base_symbols.size - 1
|
149
|
+
end
|
150
|
+
alias basic? base?
|
151
|
+
|
152
|
+
# Converts the dimension into its SPS.
|
153
|
+
#
|
154
|
+
def to_s
|
155
|
+
sps = SY::SPS.( SY::BASE_DIMENSIONS.base_symbols, to_a )
|
156
|
+
return sps == "" ? "∅" : sps
|
157
|
+
end
|
158
|
+
|
159
|
+
# Produces the inspect string of the dimension.
|
160
|
+
#
|
161
|
+
def inspect
|
162
|
+
"#<Dimension: #{self} >"
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns dimension's standard quantity.
|
166
|
+
#
|
167
|
+
def standard_quantity
|
168
|
+
self.class.standard_quantities[ self ]
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns default quantity composition for this dimension.
|
172
|
+
#
|
173
|
+
def to_composition
|
174
|
+
SY::Composition[ SY::BASE_DIMENSIONS.base_symbols
|
175
|
+
.map { |l| self.class.base l }
|
176
|
+
.map { |dim| self.class.standard_quantities[ dim ] }
|
177
|
+
.map { |qnt| qnt.absolute }
|
178
|
+
.zip( to_a ).delete_if { |qnt, exp| exp.zero? } ]
|
179
|
+
end
|
180
|
+
|
181
|
+
delegate :standard_unit, to: :standard_quantity
|
182
|
+
end # class SY::Dimension
|