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