sy 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/lib/sy.rb +274 -0
- data/lib/sy/absolute_magnitude.rb +96 -0
- data/lib/sy/abstract_algebra.rb +102 -0
- data/lib/sy/composition.rb +219 -0
- data/lib/sy/dimension.rb +182 -0
- data/lib/sy/expressible_in_units.rb +135 -0
- data/lib/sy/fixed_assets_of_the_module.rb +287 -0
- data/lib/sy/magnitude.rb +515 -0
- data/lib/sy/mapping.rb +84 -0
- data/lib/sy/matrix.rb +69 -0
- data/lib/sy/quantity.rb +455 -0
- data/lib/sy/signed_magnitude.rb +51 -0
- data/lib/sy/unit.rb +288 -0
- data/lib/sy/version.rb +4 -0
- data/lib/sy/wildcard_zero.rb +29 -0
- data/sy.gemspec +19 -0
- data/test/sy_test.rb +385 -0
- metadata +80 -0
@@ -0,0 +1,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
|