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
@@ -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
|