sy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ #encoding: utf-8
2
+
3
+ # Qualities specific to relative magnitudes.
4
+ #
5
+ module SY::SignedMagnitude
6
+ # Relative magnitude constructor takes :quantity (alias :of) named argument,
7
+ # and :amount named argument, where :amount is allowed to be negative.
8
+ #
9
+ def initialize args={}
10
+ @quantity = args[:quantity] || args[:of]
11
+ amnt = args[:amount]
12
+ @amount = case amnt
13
+ when Numeric then amnt
14
+ when nil then 1
15
+ else
16
+ begin
17
+ amnt.amount
18
+ rescue NameError, NoMethodError
19
+ amnt
20
+ end
21
+ end
22
+ end
23
+
24
+ # Addition.
25
+ #
26
+ def + m2
27
+ return magnitude( amount + m2.amount ) if quantity == m2.quantity
28
+ return quantity.absolute.magnitude( amount + m2.amount ) if
29
+ quantity.absolute == m2.quantity
30
+ compat_1, compat_2 = m2.coerce self
31
+ return compat_1 + compat_2
32
+ end
33
+
34
+ # Subtraction.
35
+ #
36
+ def - m2
37
+ return magnitude( amount - m2.amount ) if m2.quantity == quantity.relative
38
+ return quantity.relative.magnitude( amount - m2.amount ) if
39
+ quantity == m2.quantity
40
+ compat_1, compat_2 = m2.coerce self
41
+ return compat_1 - compat_2
42
+ end
43
+
44
+ private
45
+
46
+ # String describing this class.
47
+ #
48
+ def çς
49
+ "±Magnitude"
50
+ end
51
+ end # module SY::SignedMagnitudeMixin
data/lib/sy/unit.rb ADDED
@@ -0,0 +1,288 @@
1
+ #encoding: utf-8
2
+
3
+ # This class represents a unit of measurement – a predefined magnitude
4
+ # of a metrological quantity.
5
+ #
6
+ module SY::Unit
7
+ def self.pre_included target
8
+ class << target
9
+ # Overriding this method from NameMagic mixin makes sure that all Unit
10
+ # subclasses have the same namespace in Unit class, rather then each
11
+ # parametrized subclass its own.
12
+ #
13
+ def namespace
14
+ SY::Unit
15
+ end
16
+
17
+ # Tweaking instance accessor from NameMagic, to make it accept unit
18
+ # abbreviations, and unit names regardless of capitalization
19
+ #
20
+ def instance arg
21
+ begin
22
+ super # let's first try the original method
23
+ rescue NameError # if we fail...
24
+ begin # second in order, let's try whether it's an abbreviation
25
+ super instances.find { |inst|
26
+ inst.abbreviation.to_s == arg.to_s if inst.abbreviation
27
+ }
28
+ rescue NameError, TypeError
29
+ begin # finally, let's try upcase if we have all-downcase arg
30
+ super arg.to_s.upcase
31
+ rescue NameError # if not, tough luck
32
+ raise NameError, "Unknown unit symbol: #{which}"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end # class << target
38
+ end # def self.pre_included
39
+
40
+ def self.included target
41
+ target.class_exec do
42
+ # Let's set up the naming hook for NameMagic:
43
+ name_set_closure do |name, new_instance, old_name|
44
+ ɴ = name.to_s
45
+ up, down = ɴ.upcase, ɴ.downcase
46
+ unless ɴ == up || ɴ = down
47
+ raise NameError, "Unit must be either all-upper or all-lower case!"
48
+ end
49
+ conflicter = SY::PREFIX_TABLE.full_prefixes
50
+ .find { |prefix| down.starts_with? prefix unless prefix.empty? }
51
+ raise NameError, "Name #{ɴ} starts with #{conflicter}- prefix" unless
52
+ down == 'kilogram' if conflicter
53
+ up.to_sym
54
+ end
55
+
56
+ # name_get_closure { |name| name.to_s.downcase.to_sym }
57
+
58
+ # Using eval, we'll now define all the prefix methods on the target, such
59
+ # as #mili, #micro, #kilo, #mega, etc. These are defined only for units, to which
60
+ # they represent multiplication by the factor of the prefix (side effect
61
+ # of such multiplication is conversion to a normal magnitude). However,
62
+ # the Unit class offers the opportunity for these prefix methods to cause
63
+ # <em>reframing</em> into a quantity specified by #quantity_by_prefix
64
+ # instance method. (This instance method normally returns the unit's own
65
+ # quantity unchanged, but can and should be overriden for those unit,
66
+ # which have area-specific prefix use.)
67
+ #
68
+ SY::PREFIX_TABLE.full_prefixes.each do |prefix|
69
+ unless prefix.empty?
70
+ define_method prefix do
71
+ SY::Quantity.instance( quantity_by_prefix( prefix ) )
72
+ .magnitude( self * SY::PREFIX_TABLE.to_factor( prefix ) )
73
+ end
74
+ end
75
+ end
76
+ end # module_exec
77
+ end # def self.included
78
+
79
+ include NameMagic
80
+
81
+ class << self
82
+ # Constructor of units of a given quantity.
83
+ #
84
+ def of *args
85
+ ꜧ = args.extract_options!
86
+ qnt = case args.size
87
+ when 0 then
88
+ ꜧ.must_have( :quantity, syn!: :of )
89
+ ꜧ.delete :quantity
90
+ when 1 then args.shift
91
+ else
92
+ raise AErr, "Too many ordered arguments!"
93
+ end
94
+ return qnt.unit *( ꜧ.empty? ? args : args << ꜧ )
95
+ end
96
+
97
+ # Standard unit constructor. In absence of other named arguments, standard
98
+ # unit of the specified quantity is merely retrieved. If other named
99
+ # arguments than :quantity (alias :of) are supplied, they are forwarded to
100
+ # Quantity#new_standard_unit method, that resets the standard unit of the
101
+ # specified quantity. Note that :amount for standard units, if supplied, has
102
+ # special meaning of setting the relationship of that quantity.
103
+ #
104
+ def standard args={}
105
+ args.must_have :quantity, syn!: :of
106
+ qnt = SY::Quantity.instance( args.delete :quantity )
107
+ if args.empty? then
108
+ qnt.standard_unit
109
+ else
110
+ qnt.new_standard_unit( args )
111
+ end
112
+ end
113
+
114
+ # Unit abbreviations as a hash of abbreviation => unit pairs.
115
+ #
116
+ def abbreviations
117
+ ii = instances
118
+ Hash[ ii.map( &:short ).zip( ii ).select { |short, _| ! short.nil? } ]
119
+ end
120
+
121
+ # Full list of known unit names and unit abbreviations.
122
+ #
123
+ def known_symbols
124
+ instance_names + abbreviations.keys
125
+ end
126
+
127
+ # Parses an SPS, curring it with known unit names and abbreviations,
128
+ # and all known full and short prefixes.
129
+ #
130
+ def parse_sps_using_all_prefixes sps
131
+ puts "Unit about to sps parse (#{sps})" if SY::DEBUG
132
+ SY::PREFIX_TABLE.parse_sps( sps, known_symbols )
133
+ end
134
+ end # class << self
135
+
136
+ # Unlike ordinary magnitudes, units can have names and abbreviations.
137
+ #
138
+ attr_reader :abbreviation
139
+ alias :short :abbreviation
140
+
141
+ # Unit abbreviation setter.
142
+ #
143
+ def abbreviation= unit_abbreviation
144
+ @abbreviation = unit_abbreviation.to_sym
145
+ end
146
+
147
+ # Unit abbreviation setter (alias for #abbreviation=).
148
+ #
149
+ def short= unit_abbreviation
150
+ @abbreviation = unit_abbreviation.to_sym
151
+ end
152
+
153
+ # Unit name. While named units are typically introduced as constants in
154
+ # all-upper case, their names are then presented in all-lower case.
155
+ #
156
+ def name
157
+ ɴ = super
158
+ return ɴ ? ɴ.to_s.downcase.to_sym : nil
159
+ end
160
+
161
+ # Constructor of units provides support for one additional named argument:
162
+ # :abbreviation, alias :short. (This is in addition to :name, alias :ɴ named
163
+ # argument provided by NameMagic.) As a general rule, only named units unit
164
+ # should be given abbreviations. In choosing unit names and abbreviations,
165
+ # ambiguity with regard to standard prefixes and abbreviations thereof should
166
+ # also be avoided.
167
+ #
168
+ def initialize args={}
169
+ if args.has? :abbreviation, syn!: :short then
170
+ @abbreviation = args.delete( :abbreviation ).to_sym
171
+ end
172
+
173
+ # FIXME: Here, we would have to watch out for :amount being set
174
+ # if it is a number, amount is in standard units
175
+ # however, if it is a magnitude, especially one of another equidimensional quantity,
176
+ # it estableshes a relationship between this and that quantity. It means that
177
+ # the unit amount automatically becomes ... one ... and such relationship can
178
+ # only be established for standard quantity
179
+ super args
180
+ end
181
+
182
+ # Addition: Unit is converted to a magnitude before the operation.
183
+ #
184
+ def + other
185
+ to_magnitude + other
186
+ end
187
+
188
+ # Subtraction: Unit is converted to a magnitude before the operation.
189
+ #
190
+ def - other
191
+ to_magnitude - other
192
+ end
193
+
194
+ # Multiplication: Unit is converted to a magnitude before the operation.
195
+ #
196
+ def * other
197
+ to_magnitude * other
198
+ end
199
+
200
+ # Division: Unit is converted to a magnitude before the operation.
201
+ #
202
+ def / other
203
+ to_magnitude / other
204
+ end
205
+
206
+ # Exponentiation: Unit is converted to a magnitude before the operation.
207
+ #
208
+ def ** exponent
209
+ to_magnitude ** exponent
210
+ end
211
+
212
+ # Coercion: Unit is converted to a magnitude before coercion is actually
213
+ # performed.
214
+ #
215
+ def coerce other
216
+ to_magnitude.coerce( other )
217
+ end
218
+
219
+ # Reframing: Unit is converted to a magnitude before reframing.
220
+ #
221
+ def reframe other_quantity
222
+ to_magnitude.reframe( other_quantity )
223
+ end
224
+
225
+ # Unit as string.
226
+ #
227
+ def to_s
228
+ name.nil? ? to_s_when_anonymous : to_s_when_named
229
+ end
230
+
231
+ # Inspect string for the unit.
232
+ #
233
+ def inspect
234
+ name.nil? ? inspect_when_anonymous : inspect_when_named
235
+ end
236
+
237
+ # Some prefixes of some units are almost exclusively used in certain areas
238
+ # of science or engineering, and their appearance would indicate such
239
+ # specific quantity. By default, this method simply returns unit's own
240
+ # quantity unchanged. But it is expected that the method will be overriden
241
+ # by a singleton method in those units, which have area-specific prefixes.
242
+ # For example, centimetre, typical for civil engineering, could cause
243
+ # reframing into its own CentimetreLength quantity. Assuming METRE unit,
244
+ # this could be specified for example by:
245
+ # <tt>
246
+ # METRE.define_singleton_method :quantity_by_prefix do |full_prefix|
247
+ # case full_prefix
248
+ # when :centi then CentimetreLength
249
+ # else self.quantity end
250
+ # end
251
+ # </tt>
252
+ #
253
+ def quantity_by_prefix prefix
254
+ quantity
255
+ end
256
+
257
+ private
258
+
259
+ # Constructs #to_s string when the unit is anonymous.
260
+ #
261
+ def to_s_when_anonymous
262
+ "[#{çς}: #{amount} of #{quantity}]"
263
+ end
264
+
265
+ # Constructs #to_s string when the unit is named.
266
+ #
267
+ def to_s_when_named
268
+ name
269
+ end
270
+
271
+ # Constructs inspect string when the unit is anonymous.
272
+ #
273
+ def inspect_when_anonymous
274
+ "#<#{çς}: #{to_magnitude} >"
275
+ end
276
+
277
+ # Constructs inspect string when the unit is named.
278
+ #
279
+ def inspect_when_named
280
+ "#<#{çς}: #{name} of #{quantity} >"
281
+ end
282
+
283
+ # String describing this class.
284
+ #
285
+ def çς
286
+ "Unit"
287
+ end
288
+ end # class SY::Unit
data/lib/sy/version.rb ADDED
@@ -0,0 +1,4 @@
1
+ module SY
2
+ VERSION = "1.0.0"
3
+ DEBUG = false # ignore this; debugging only
4
+ end
@@ -0,0 +1,29 @@
1
+ #encoding: utf-8
2
+
3
+ # Wildcard zero, stronger than ordinary numeric literal 0.
4
+ #
5
+ ( WILDCARD_ZERO = NullObject.new ).instance_exec {
6
+ ɪ = self
7
+ singleton_class.class_exec { define_method :zero do ɪ end }
8
+ def * other; other.class.zero end
9
+ def / other
10
+ self unless other.zero?
11
+ fail ZeroDivisionError, "The divisor is zero! (#{other})"
12
+ end
13
+ def + other; other end
14
+ def - other; -other end
15
+ def coerce other; return other, other.class.zero end
16
+ def zero?; true end
17
+ def to_s; "∅" end
18
+ def inspect; to_s end
19
+ def to_f; 0.0 end
20
+ def to_i; 0 end
21
+ def == other
22
+ z = begin
23
+ other.class.zero
24
+ rescue NoMethodError
25
+ return false
26
+ end
27
+ other == z
28
+ end
29
+ }
data/sy.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/sy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["boris"]
6
+ gem.email = ["\"boris@iis.sinica.edu.tw\""]
7
+ gem.description = %q{Physical units library}
8
+ gem.summary = %q{Simple and concise way to express physical units.}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "sy"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = SY::VERSION
17
+
18
+ gem.add_dependency "activesupport"
19
+ end
data/test/sy_test.rb ADDED
@@ -0,0 +1,385 @@
1
+ #encoding: utf-8
2
+ #! /usr/bin/ruby
3
+
4
+ # **************************************************************************
5
+ # THIS IS SPEC-STYLE TEST FILE FOR SY PHYSICAL UNITS LIBRARY
6
+ # **************************************************************************
7
+
8
+ # The following will load Ruby spec-style library
9
+ require 'mathn'
10
+ require 'minitest/spec'
11
+ require 'minitest/autorun'
12
+
13
+ # The following will load SY library
14
+ # require 'sy'
15
+ require './../lib/sy'
16
+
17
+ # **************************************************************************
18
+ # THE SPECIFICATIONS START HERE
19
+ # **************************************************************************
20
+
21
+ describe SY do
22
+ it "should have basic assets" do
23
+ # Basic physical dimensions:
24
+ SY::BASE_DIMENSIONS.to_a.sort
25
+ .must_equal [ [:L, :LENGTH], [:M, :MASS], [:T, :TIME],
26
+ [:Q, :ELECTRIC_CHARGE], [:Θ, :TEMPERATURE] ].sort
27
+
28
+ # Standard unit prefixes:
29
+ SY::PREFIX_TABLE.map{|row| row[:full] }.sort
30
+ .must_equal [ "exa", "peta", "tera", "giga", "mega", "kilo",
31
+ "mili", "micro", "nano", "pico", "femto", "atto",
32
+ "hecto", "deka","deci", "centi", "" ].sort
33
+ end
34
+ end
35
+
36
+ describe SY::Dimension do
37
+ it "should" do
38
+ # Dimension#new should return same instance when asked twice.
39
+ assert_equal *[ :L, :L ].map { |d| SY::Dimension.new( d ).object_id }
40
+
41
+ # Other Dimension constructors: #basic and #zero.
42
+ SY::Dimension.basic( :L ).must_equal SY.Dimension( :L )
43
+ SY::Dimension.zero.must_equal SY::Dimension.new( '' )
44
+
45
+ # SY should have table of standard quantities.
46
+ assert SY.Dimension( :L ).standard_quantity.is_a? SY::Quantity
47
+
48
+ # Instances should provide access to base dimensions.
49
+ assert_equal [0, 1], [:L, :M].map { |ß| SY.Dimension( :M ).send ß }
50
+ assert_equal [1, 0], [:L, :M].map { |ß| SY.Dimension( :L )[ß] }
51
+
52
+ # #to_a, #to_hash, #zero?,
53
+ ll = SY::BASE_DIMENSIONS.letters
54
+ SY.Dimension( :M ).to_a.must_equal ll.map { |l| l == :M ? 1 : 0 }
55
+ SY.Dimension( :M ).to_hash.must_equal Hash[ ll.zip SY.Dimension( :M ).to_a ]
56
+ SY.Dimension( :M ).zero?.must_equal false
57
+ SY::Dimension.zero.zero?.must_equal true
58
+ SY.Dimension( nil ).to_a.must_equal [ 0, 0, 0, 0, 0 ]
59
+
60
+ # Dimension arithmetic
61
+ assert SY.Dimension( :L ) + SY.Dimension( :M ) == SY.Dimension( 'L.M' )
62
+ assert SY.Dimension( :L ) - SY.Dimension( :M ) == SY.Dimension( 'L.M⁻¹' )
63
+ assert SY.Dimension( :L ) * 2 == SY.Dimension( 'L²' )
64
+ assert SY.Dimension( M: 2 ) / 2 == SY.Dimension( :M )
65
+ end
66
+ end
67
+
68
+ describe SY::Mapping do
69
+ it "should" do
70
+ i = SY::Mapping.identity
71
+ a, b = SY::Mapping.new( 2 ), SY::Mapping.new( 3 )
72
+ assert_equal 1, i.ratio
73
+ assert_equal 4, a.im.( 8 )
74
+ assert_equal 3, b.ex.( 1 )
75
+ assert_equal 6, (a * b).ex.( 1 )
76
+ assert_equal 2, (a * b / b).ex.( 1 )
77
+ assert_equal 4, (a ** 2).ex.( 1 )
78
+ assert_equal 2, a.inverse.im.( 1 )
79
+ end
80
+ end
81
+
82
+ describe SY::Composition do
83
+ it "should" do
84
+ assert_equal SY::Amount, SY.Dimension( :∅ ).standard_quantity
85
+ a = SY::Composition[ SY::Amount => 1 ]
86
+ l = SY::Composition[ SY::Length => 1 ]
87
+ assert SY::Composition.new.empty?
88
+ assert a.singular?
89
+ assert l.atomic?
90
+ assert_equal SY::Composition[ SY::Amount => 1, SY::Length => 1 ], a + l
91
+ assert_equal SY::Composition[ SY::Amount => 1, SY::Length => -1 ], a - l
92
+ assert_equal SY::Composition[ SY::Length => 2 ], l * 2
93
+ assert_equal l, l * 2 / 2
94
+ assert_equal l.to_hash, (a + l).simplify.to_hash
95
+ assert_equal SY::Amount, a.to_quantity
96
+ assert_equal SY::Length, l.to_quantity
97
+ assert_equal( SY.Dimension( 'L' ),
98
+ SY::Composition[ SY::Amount => 1, SY::Length => 1 ]
99
+ .to_quantity.dimension )
100
+ assert_equal SY.Dimension( 'L' ), l.dimension
101
+ assert_kind_of SY::Mapping, a.infer_mapping
102
+ end
103
+ end
104
+
105
+ describe SY::Quantity, SY::Magnitude do
106
+ before do
107
+ @q1 = SY::Quantity.new of: '∅'
108
+ @q2 = SY::Quantity.dimensionless
109
+ @amount_in_dozens = begin
110
+ SY.Quantity( "AmountInDozens" )
111
+ rescue
112
+ SY::Quantity.dimensionless mapping: 12, ɴ: "AmountInDozens"
113
+ end
114
+ @inch_length = begin
115
+ SY.Quantity( "InchLength" )
116
+ rescue NameError
117
+ SY::Quantity.of SY::Length.dimension, ɴ: "InchLength"
118
+ end
119
+ end
120
+
121
+ it "should" do
122
+ refute_equal @q1, @q2
123
+ assert @q1.absolute? && @q2.absolute?
124
+ assert @q1 == @q1.absolute
125
+ assert_equal false, @q1.relative?
126
+ assert_equal SY::Composition.new, @q1.composition
127
+ @q1.set_composition SY::Composition[ SY::Amount => 1 ]
128
+ assert_equal SY::Composition[ SY::Amount => 1 ], @q1.composition
129
+ @amount_in_dozens.must_be_kind_of SY::Quantity
130
+ d1 = @amount_in_dozens.magnitude 1
131
+ a12 = SY::Amount.magnitude 12
132
+ mda = @amount_in_dozens.mapping_to SY::Amount
133
+ i, e = mda.im, mda.ex
134
+ ia = i.( a12.amount )
135
+ @amount_in_dozens.magnitude ia
136
+ im = @amount_in_dozens.import( a12 )
137
+ assert_equal @amount_in_dozens.magnitude( 1 ),
138
+ @amount_in_dozens.import( SY::Amount.magnitude( 12 ) )
139
+ assert_equal SY::Amount.magnitude( 12 ),
140
+ @amount_in_dozens.export( 1, SY::Amount )
141
+ SY::Length.composition.must_equal SY::Composition.singular( :Length )
142
+ end
143
+
144
+ describe "Magnitude, Unit" do
145
+ before do
146
+ @m1 = 1.metre
147
+ @inch = SY::Unit.standard( of: @inch_length, amount: 2.54.cm,
148
+ ɴ: 'inch', short: '”' )
149
+ @i1 = @inch_length.magnitude 1
150
+ @il_mapping = @inch_length.mapping_to SY::Length
151
+ end
152
+
153
+ it "should" do
154
+ @m1.quantity.must_equal SY::Length.relative
155
+ @inch_length.colleague.name.must_equal :InchLength±
156
+ @m1.to_s.must_equal "1.m"
157
+ @i1.amount.must_equal 1
158
+ assert_kind_of SY::Mapping, @il_mapping
159
+ assert_kind_of Numeric, @il_mapping.ratio
160
+ assert_in_epsilon 0.0254, @il_mapping.ratio
161
+ @il_mapping.ex.( 1 ).must_be_within_epsilon 0.0254
162
+ begin
163
+ impossible_mapping = @inch_length.mapping_to SY::Amount
164
+ rescue SY::DimensionError
165
+ :dimension_error
166
+ end.must_equal :dimension_error
167
+ # reframing
168
+ 1.inch.reframe( @inch_length ).amount.must_equal 1
169
+ 1.inch.( @inch_length ).must_equal 1.inch
170
+ 1.inch.( SY::Length ).must_equal 2.54.cm
171
+ @inch_length.magnitude( 1 ).to_s.must_equal "1.”"
172
+ 1.inch.in( :mm ).must_equal 25.4
173
+ end
174
+ end
175
+ end
176
+
177
+ describe "expected behavior" do
178
+ it "should" do
179
+ # Length quantity and typical units
180
+ SY::METRE.must_be_kind_of SY::Unit
181
+ SY::METRE.absolute?.must_equal true
182
+ 1.metre.absolute.must_equal SY::METRE
183
+ assert 1.metre.absolute != 1.metre.relative
184
+ 1.metre.relative.relative?.must_equal true
185
+
186
+
187
+ SY::METRE.relative.must_equal 1.metre
188
+ 1.m.must_equal 1.metre
189
+ 1.m.must_equal 1000.mm
190
+ SY::METRE.quantity.name.must_equal :Length
191
+ assert_in_delta 0.9.µm, 900.nm, 1e-6.nm
192
+ [ 1.m, 1.m ].min.must_equal 1.m
193
+ 1.m + 1.m == 1.m
194
+ assert_in_epsilon 1.m, 1.m, 0.1
195
+ 600.m.must_equal 0.6.km
196
+ SY::METRE.quantity.must_equal SY::Length
197
+ SY::Length.dimension.must_equal SY.Dimension( :L )
198
+ SY.Dimension( :L ).standard_quantity.must_equal SY::Length
199
+ SY::Length.standard_unit.must_equal SY::METRE
200
+ SY::METRE.amount.must_equal 1
201
+ SY::METRE.mili.amount.must_equal 0.001
202
+ 3.km.in( :dm ).must_equal 30_000
203
+ ( 1.m + 20.cm ).must_equal 1_200.mm
204
+ assert 1.mm.object_id != 1.mm.object_id
205
+ assert 1.mm == 1.mm
206
+ assert 1.01.m != 1.m
207
+ assert_equal 1, 1.01.m <=> 1.m
208
+ assert_equal 0, 1.00.m <=> 1.m
209
+ assert_equal -1, 0.99.m <=> 1.m
210
+ assert 0.9.mm < 1.mm
211
+ assert 1.1.mm > 1.09.mm
212
+ assert ( 0.1.m - ( 1.m - 0.9.m ) ).abs < 1.nm.abs
213
+ # Mass quantity and typical units
214
+ SY::KILOGRAM.must_be_kind_of SY::Unit
215
+ SY::GRAM.must_be_kind_of SY::Unit
216
+ assert SY::Mass.standard_unit.equal?( SY::KILOGRAM )
217
+ 1.kilogram.must_be_kind_of SY::Magnitude
218
+ 1.gram.must_be_kind_of SY::Magnitude
219
+ 1.kilogram.absolute.quantity.must_equal SY::Mass
220
+ 1.gram.absolute.quantity.must_equal SY::Mass
221
+ ( SY::KILOGRAM * 1 ).must_equal SY::GRAM * 1000
222
+ 1.kilogram.must_equal 1000.g
223
+ 1.kg.to_f.must_equal 1
224
+ 1.g.to_f.must_equal 0.001
225
+ 1.miligram.must_equal 0.001.g
226
+ 1.mg.must_equal 1.miligram
227
+ 1.µg.must_equal 0.001.miligram
228
+ 1.ng.must_equal 0.001.microgram
229
+ 1.pg.quantity.must_equal 0.001.nanogram.quantity
230
+ 1.pg.amount.must_be_within_epsilon 0.001.nanogram.amount, 1e-6
231
+ assert_equal 1.g, [1.g, 2.g].min
232
+ assert_equal 1.mg, 1.g * 0.001
233
+ 1.pg.abs.must_be_within_epsilon 0.001.nanogram.abs, 1e-6
234
+ SY::TON.must_be_kind_of SY::Unit
235
+ 1.ton.must_equal 1000.kg
236
+ 1.t.must_equal 1.ton
237
+ 1.kt.must_equal 1000.ton
238
+ 1.Mt.must_equal 1000.kiloton
239
+ 1.mm.quantity.name.must_equal :Length±
240
+ SY::Length.standard_unit.must_equal SY::METRE
241
+ SY::Length.standard_unit.name.must_equal :metre
242
+ SY::Length.standard_unit.must_equal SY::METRE
243
+ SY.Quantity( :Length ).object_id.must_equal SY::Length.object_id
244
+ SY::Length.relative.object_id.must_equal SY.Quantity( :Length± ).object_id
245
+ SY.Quantity( :Length± ).colleague.name.must_equal :Length
246
+ SY.Quantity( :Length± ).colleague.class.must_equal SY::Quantity
247
+ SY.Quantity( :Length± ).colleague.object_id.must_equal SY::Length.object_id
248
+ SY.Quantity( :Length± ).send( :Unit ).object_id
249
+ .must_equal SY::Length.send( :Unit ).object_id
250
+ 1.mm.quantity.standard_unit.name.must_equal :metre
251
+ 1.mm.to_s.must_equal "0.001.m"
252
+ 1.mm.inspect.must_equal "#<±Magnitude: 0.001.m >"
253
+ 1.µs.inspect.must_equal "#<±Magnitude: 1e-06.s >"
254
+
255
+ SY::Area.dimension.must_equal SY.Dimension( :L² )
256
+ SY::Area.composition.must_equal SY::Composition[ SY::Length => 2 ]
257
+
258
+ SY::AMPERE.name.must_equal :ampere
259
+ SY::AMPERE.abbreviation.must_equal :A
260
+ SY::AMPERE.dimension.must_equal 1.A.dimension
261
+ SY.Magnitude( of: SY::ElectricCurrent, amount: 1 ).must_equal 1.A.absolute
262
+ 1.A.quantity.must_equal SY::ElectricCurrent.relative
263
+ 1.A.quantity.standard_unit.name.must_equal :ampere
264
+ 1.A.to_s( SY::AMPERE ).must_equal "1.A"
265
+ 1.A.to_s.must_equal "1.A"
266
+ 1.A.amount.must_equal 1
267
+ 1.A.quantity.standard_unit.abbreviation.must_equal :A
268
+ 1.A.inspect.must_equal "#<±Magnitude: 1.A >"
269
+
270
+ 1.l⁻¹.reframe( SY::Molarity ).quantity.must_equal SY::Molarity
271
+ x = ( SY::Nᴀ / SY::LITRE )
272
+ x = x.reframe( SY::Molarity )
273
+ y = 1.molar.absolute
274
+ y.quantity.must_equal x.quantity
275
+ y.amount.must_equal y.amount
276
+ SY::MoleAmount.protected?.must_equal true
277
+ SY::LitreVolume.protected?.must_equal true
278
+ SY::MOLAR.quantity.name.must_equal :Molarity
279
+ m = 1.µM
280
+ 1.µM.quantity.relative?.must_equal true
281
+ 1.µM.quantity.name.must_equal :Molarity±
282
+ 1.µM.quantity.absolute.name.must_equal :Molarity
283
+ 7.µM.must_be_within_epsilon 5.µM + 2.µM, 1e-6
284
+ +1.s.must_equal 1.s
285
+ # -1.s.must_equal -1 * 1.s # must raise
286
+ assert_equal -(-(1.s)), +(1.s)
287
+ 1.s⁻¹.quantity.must_equal ( 1.s ** -1 ).quantity
288
+ 1.s⁻¹.quantity.must_equal ( 1 / 1.s ).quantity
289
+ 1.s⁻¹.amount.must_equal ( 1.s ** -1 ).amount
290
+ 1.s⁻¹.must_equal 1.s ** -1
291
+ q1 = ( 1.s⁻¹ ).quantity
292
+ q1.composition.to_hash.must_equal( { SY::Time => -1 } )
293
+
294
+ q2 = ( 1 / 1.s ).quantity
295
+ q2.composition.to_hash.must_equal( { SY::Time => -1 } )
296
+
297
+ q1.relative?.must_equal true
298
+ q2.relative?.must_equal true
299
+
300
+ q1.object_id.must_equal q2.object_id
301
+ ( 1.s⁻¹ ).quantity.object_id.must_equal ( 1 / 1.s ).quantity.object_id
302
+ ( 1 / 1.s ).must_equal 1.s⁻¹
303
+ 1.s⁻¹.( SY::Frequency ).must_equal 1.Hz
304
+ # 7.°C.must_equal( 8.°C - 1.K )
305
+ # (-15).°C.must_equal 258.15.K
306
+ # 7000.µM.must_be_within_epsilon( 7.mM, 1e-9 )
307
+ ::SY::Unit.instances.map do |i|
308
+ begin
309
+ i.abbreviation
310
+ rescue
311
+ end
312
+ end.must_include :M
313
+ SY::Unit.instance_names.must_include :mole
314
+ # Avogadro's number is defined directly in SY
315
+ 1.mol.quantity.object_id.must_equal SY::Nᴀ.( SY::MoleAmount ).quantity.object_id
316
+ SY::Nᴀ.( SY::MoleAmount ).must_equal 1.mol
317
+ 0.7.mol.l⁻¹.amount.must_equal 0.7
318
+ 1.M.must_equal 1.mol.l⁻¹.( SY::Molarity )
319
+ # (if #reframe conversion method is not used, different quantities
320
+ # do not compare. Arithmetics is possible because Magnitude operators
321
+ # mostly give their results only in standard quantities.
322
+
323
+ # Avogadro's number is defined directly in SY
324
+ 1.mol.must_equal SY::Nᴀ.unit.( SY::MoleAmount )
325
+
326
+
327
+ 0.7.M.must_equal 0.7.mol.l⁻¹.( SY::Molarity )
328
+ # (if #is_actually! conversion method is not used, current
329
+ # implementation will refuse to compare different quantities,
330
+ # even if their dimensions match)
331
+
332
+ 30.Hz.must_equal 30.s⁻¹.( SY::Frequency )
333
+
334
+ # Dalton * Avogadro must be 1 gram
335
+ ( 1.Da * SY::Nᴀ ).must_be_within_epsilon( 1.g, 1e-6 )
336
+
337
+ # kilogram
338
+ 1.kg.must_equal 1000.g
339
+ SY::Speed.dimension.must_equal SY::Dimension( "L.T⁻¹" )
340
+ SY::Acceleration.dimension.must_equal SY::Dimension( "L.T⁻²" )
341
+ SY::Force.dimension.must_equal SY::Dimension( "L.M.T⁻²" )
342
+ ( 1.kg * 1.m.s⁻² ).( SY::Force ).must_be_within_epsilon 1.N, 1e-9
343
+
344
+ # joule
345
+ ( 1.N * 1.m ).( SY::Energy ).must_equal 1.J
346
+ 1e-23.J.K⁻¹.must_equal 1.0e-20.mJ.K⁻¹
347
+
348
+
349
+ # pascal
350
+ ( 1.N / 1.m ** 2 ).( SY::Pressure ).must_be_within_epsilon 1.Pa, 1e-9
351
+
352
+ # watt
353
+ ( 1.V * 1.A ).( SY::Power ).must_be_within_epsilon 1.W, 1e-9
354
+
355
+ # pretty representation
356
+ ( 1.m / 3.s ).to_s.must_equal( "0.333.m.s⁻¹" )
357
+ ( 1.m / 7.01e7.s ).to_s.must_equal( "1.43e-08.m.s⁻¹" )
358
+
359
+ assert_equal 1.m, 1.s * 1.m.s⁻¹
360
+ assert_equal 1.µM.s⁻¹, 1.µM / 1.s
361
+ assert_equal 1.m.s⁻¹, 1.m.s( -1 )
362
+ assert_equal 2_000.mm.s⁻², 2.m.s( -2 )
363
+ assert_equal 3.µM, 1.µM + 2.µM
364
+ assert_equal SY::Amount, SY::Molarity / SY::Molarity
365
+ assert_equal SY::Amount( 1 ), 1.µM / 1.µM
366
+ assert_equal SY::Amount( 1 ), 1.µM / ( 1.µM + 0.µM )
367
+ assert_equal 1.µM, 1.µM * 1.µM / ( 1.µM + 0.µM )
368
+ assert_in_epsilon 1.µM, 1.µmol / 1.dm( 3 ).( SY::LitreVolume )
369
+
370
+ assert_equal SY::Molarity.relative, 1.mol.l⁻¹.quantity
371
+
372
+ assert_equal 1 / SY::Time, 1 / SY::Time
373
+ assert_equal 1 / SY::Time.relative, 1 / SY::Time
374
+ assert_equal ( 1 / SY::Time.relative ), 1.mol.s⁻¹.( 1 / SY::Time ).quantity
375
+ assert_equal ( 1 / SY::Time ).object_id,
376
+ ( 1.0.µmol.min⁻¹.mg⁻¹ * 100.kDa ).( 1 / SY::Time ).quantity.object_id
377
+ assert_equal SY::Time.magnitude( 1 ), SY::SECOND
378
+ assert_equal Matrix[[60.mM], [60.mM]], Matrix[[1e-03.s⁻¹.M], [1e-3.s⁻¹.M]] * 60.s
379
+
380
+ assert_equal Matrix[[5.m]], Matrix[[1.m.s⁻¹, 2.m.s⁻¹]] * Matrix.column_vector( [1.s, 2.s] )
381
+ assert_equal Matrix[[2.m, 3.m], [4.m, 5.m]],
382
+ Matrix[[1.m, 2.m], [3.m, 4.m]] + Matrix[[1.m, 1.m], [1.m, 1.m]]
383
+ assert_equal Matrix[[5.µM]], Matrix[[1.µM]] + Matrix[[2.µM.s⁻¹]] * Matrix[[2.s]]
384
+ end
385
+ end