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