unitwise-193 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +297 -0
- data/Rakefile +21 -0
- data/data/base_unit.yaml +43 -0
- data/data/derived_unit.yaml +3542 -0
- data/data/prefix.yaml +121 -0
- data/lib/unitwise.rb +70 -0
- data/lib/unitwise/atom.rb +121 -0
- data/lib/unitwise/base.rb +58 -0
- data/lib/unitwise/compatible.rb +60 -0
- data/lib/unitwise/errors.rb +7 -0
- data/lib/unitwise/expression.rb +35 -0
- data/lib/unitwise/expression/composer.rb +41 -0
- data/lib/unitwise/expression/decomposer.rb +68 -0
- data/lib/unitwise/expression/matcher.rb +47 -0
- data/lib/unitwise/expression/parser.rb +58 -0
- data/lib/unitwise/expression/transformer.rb +37 -0
- data/lib/unitwise/ext.rb +2 -0
- data/lib/unitwise/ext/numeric.rb +45 -0
- data/lib/unitwise/functional.rb +117 -0
- data/lib/unitwise/measurement.rb +198 -0
- data/lib/unitwise/prefix.rb +24 -0
- data/lib/unitwise/scale.rb +139 -0
- data/lib/unitwise/search.rb +46 -0
- data/lib/unitwise/standard.rb +29 -0
- data/lib/unitwise/standard/base.rb +73 -0
- data/lib/unitwise/standard/base_unit.rb +21 -0
- data/lib/unitwise/standard/derived_unit.rb +49 -0
- data/lib/unitwise/standard/extras.rb +17 -0
- data/lib/unitwise/standard/function.rb +35 -0
- data/lib/unitwise/standard/prefix.rb +17 -0
- data/lib/unitwise/standard/scale.rb +25 -0
- data/lib/unitwise/term.rb +142 -0
- data/lib/unitwise/unit.rb +181 -0
- data/lib/unitwise/version.rb +3 -0
- data/test/support/scale_tests.rb +117 -0
- data/test/test_helper.rb +19 -0
- data/test/unitwise/atom_test.rb +129 -0
- data/test/unitwise/base_test.rb +6 -0
- data/test/unitwise/expression/decomposer_test.rb +45 -0
- data/test/unitwise/expression/matcher_test.rb +42 -0
- data/test/unitwise/expression/parser_test.rb +109 -0
- data/test/unitwise/ext/numeric_test.rb +54 -0
- data/test/unitwise/functional_test.rb +17 -0
- data/test/unitwise/measurement_test.rb +233 -0
- data/test/unitwise/prefix_test.rb +25 -0
- data/test/unitwise/scale_test.rb +7 -0
- data/test/unitwise/search_test.rb +18 -0
- data/test/unitwise/term_test.rb +55 -0
- data/test/unitwise/unit_test.rb +87 -0
- data/test/unitwise_test.rb +35 -0
- data/unitwise.gemspec +33 -0
- metadata +246 -0
data/data/prefix.yaml
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
---
|
2
|
+
- :names: yotta
|
3
|
+
:symbol: Y
|
4
|
+
:primary_code: Y
|
5
|
+
:secondary_code: YA
|
6
|
+
:scalar: 1e24
|
7
|
+
- :names: zetta
|
8
|
+
:symbol: Z
|
9
|
+
:primary_code: Z
|
10
|
+
:secondary_code: ZA
|
11
|
+
:scalar: 1e21
|
12
|
+
- :names: exa
|
13
|
+
:symbol: E
|
14
|
+
:primary_code: E
|
15
|
+
:secondary_code: EX
|
16
|
+
:scalar: 1e18
|
17
|
+
- :names: peta
|
18
|
+
:symbol: P
|
19
|
+
:primary_code: P
|
20
|
+
:secondary_code: PT
|
21
|
+
:scalar: 1e15
|
22
|
+
- :names: tera
|
23
|
+
:symbol: T
|
24
|
+
:primary_code: T
|
25
|
+
:secondary_code: TR
|
26
|
+
:scalar: 1e12
|
27
|
+
- :names: giga
|
28
|
+
:symbol: G
|
29
|
+
:primary_code: G
|
30
|
+
:secondary_code: GA
|
31
|
+
:scalar: 1e9
|
32
|
+
- :names: mega
|
33
|
+
:symbol: M
|
34
|
+
:primary_code: M
|
35
|
+
:secondary_code: MA
|
36
|
+
:scalar: 1e6
|
37
|
+
- :names: kilo
|
38
|
+
:symbol: k
|
39
|
+
:primary_code: k
|
40
|
+
:secondary_code: K
|
41
|
+
:scalar: 1e3
|
42
|
+
- :names: hecto
|
43
|
+
:symbol: h
|
44
|
+
:primary_code: h
|
45
|
+
:secondary_code: H
|
46
|
+
:scalar: 1e2
|
47
|
+
- :names: deka
|
48
|
+
:symbol: da
|
49
|
+
:primary_code: da
|
50
|
+
:secondary_code: DA
|
51
|
+
:scalar: 1e1
|
52
|
+
- :names: deci
|
53
|
+
:symbol: d
|
54
|
+
:primary_code: d
|
55
|
+
:secondary_code: D
|
56
|
+
:scalar: 1e-1
|
57
|
+
- :names: centi
|
58
|
+
:symbol: c
|
59
|
+
:primary_code: c
|
60
|
+
:secondary_code: C
|
61
|
+
:scalar: 1e-2
|
62
|
+
- :names: milli
|
63
|
+
:symbol: m
|
64
|
+
:primary_code: m
|
65
|
+
:secondary_code: M
|
66
|
+
:scalar: 1e-3
|
67
|
+
- :names: micro
|
68
|
+
:symbol: "μ"
|
69
|
+
:primary_code: u
|
70
|
+
:secondary_code: U
|
71
|
+
:scalar: 1e-6
|
72
|
+
- :names: nano
|
73
|
+
:symbol: n
|
74
|
+
:primary_code: n
|
75
|
+
:secondary_code: N
|
76
|
+
:scalar: 1e-9
|
77
|
+
- :names: pico
|
78
|
+
:symbol: p
|
79
|
+
:primary_code: p
|
80
|
+
:secondary_code: P
|
81
|
+
:scalar: 1e-12
|
82
|
+
- :names: femto
|
83
|
+
:symbol: f
|
84
|
+
:primary_code: f
|
85
|
+
:secondary_code: F
|
86
|
+
:scalar: 1e-15
|
87
|
+
- :names: atto
|
88
|
+
:symbol: a
|
89
|
+
:primary_code: a
|
90
|
+
:secondary_code: A
|
91
|
+
:scalar: 1e-18
|
92
|
+
- :names: zepto
|
93
|
+
:symbol: z
|
94
|
+
:primary_code: z
|
95
|
+
:secondary_code: ZO
|
96
|
+
:scalar: 1e-21
|
97
|
+
- :names: yocto
|
98
|
+
:symbol: y
|
99
|
+
:primary_code: y
|
100
|
+
:secondary_code: YO
|
101
|
+
:scalar: 1e-24
|
102
|
+
- :names: kibi
|
103
|
+
:symbol: Ki
|
104
|
+
:primary_code: Ki
|
105
|
+
:secondary_code: KIB
|
106
|
+
:scalar: '1024'
|
107
|
+
- :names: mebi
|
108
|
+
:symbol: Mi
|
109
|
+
:primary_code: Mi
|
110
|
+
:secondary_code: MIB
|
111
|
+
:scalar: '1048576'
|
112
|
+
- :names: gibi
|
113
|
+
:symbol: Gi
|
114
|
+
:primary_code: Gi
|
115
|
+
:secondary_code: GIB
|
116
|
+
:scalar: '1073741824'
|
117
|
+
- :names: tebi
|
118
|
+
:symbol: Ti
|
119
|
+
:primary_code: Ti
|
120
|
+
:secondary_code: TIB
|
121
|
+
:scalar: '1099511627776'
|
data/lib/unitwise.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'liner'
|
2
|
+
require 'memoizable'
|
3
|
+
require 'parslet'
|
4
|
+
require 'signed_multiset'
|
5
|
+
require 'yaml'
|
6
|
+
require 'bigdecimal'
|
7
|
+
|
8
|
+
require 'unitwise/version'
|
9
|
+
require 'unitwise/base'
|
10
|
+
require 'unitwise/compatible'
|
11
|
+
require 'unitwise/expression'
|
12
|
+
require 'unitwise/scale'
|
13
|
+
require 'unitwise/functional'
|
14
|
+
require 'unitwise/measurement'
|
15
|
+
require 'unitwise/atom'
|
16
|
+
require 'unitwise/prefix'
|
17
|
+
require 'unitwise/term'
|
18
|
+
require 'unitwise/unit'
|
19
|
+
require 'unitwise/search'
|
20
|
+
require 'unitwise/errors'
|
21
|
+
|
22
|
+
# Unitwise is a library for performing mathematical operations and conversions
|
23
|
+
# on all units defined by the [Unified Code for Units of Measure(UCUM).
|
24
|
+
module Unitwise
|
25
|
+
|
26
|
+
# Search for available compounds. This is just a helper method for
|
27
|
+
# convenience
|
28
|
+
# @param term [String, Regexp]
|
29
|
+
# @return [Array]
|
30
|
+
# @api public
|
31
|
+
def self.search(term)
|
32
|
+
Search.search(term)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determine if a given string is a valid unit expression
|
36
|
+
# @param expression [String]
|
37
|
+
# @return [true, false]
|
38
|
+
# @api public
|
39
|
+
def self.valid?(expression)
|
40
|
+
begin
|
41
|
+
!!Unitwise::Expression.decompose(expression)
|
42
|
+
rescue ExpressionError
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# The system path for the installed gem
|
48
|
+
# @api private
|
49
|
+
def self.path
|
50
|
+
@path ||= File.dirname(File.dirname(__FILE__))
|
51
|
+
end
|
52
|
+
|
53
|
+
# A helper to get the location of a yaml data file
|
54
|
+
# @api private
|
55
|
+
def self.data_file(key)
|
56
|
+
File.join path, 'data', "#{key}.yaml"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Measurement initializer shorthand. Use this to instantiate new measurements.
|
61
|
+
# @param first [Numeric, String] Either a numeric value or a unit expression
|
62
|
+
# @param last [String, Nil] Either a unit expression, or nil
|
63
|
+
# @return [Unitwise::Measurement]
|
64
|
+
# @example
|
65
|
+
# Unitwise(20, 'mile') # => #<Unitwise::Measurement 20 mile>
|
66
|
+
# Unitwise('km') # => #<Unitwise::Measurement 1 km>
|
67
|
+
# @api public
|
68
|
+
def Unitwise(*args)
|
69
|
+
Unitwise::Measurement.new(*args)
|
70
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Unitwise
|
2
|
+
# Atoms are the most basic elements in Unitwise. They are named coded and
|
3
|
+
# scaled units without prefixes, multipliers, exponents, etc. Examples are
|
4
|
+
# 'meter', 'hour', 'pound force'.
|
5
|
+
class Atom < Base
|
6
|
+
liner :classification, :property, :metric, :special, :arbitrary, :dim
|
7
|
+
include Compatible
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Array of hashes representing atom properties.
|
11
|
+
# @api private
|
12
|
+
def data
|
13
|
+
@data ||= data_files.map { |file| YAML.load(File.open file) }.flatten
|
14
|
+
end
|
15
|
+
|
16
|
+
# Data files containing atom data
|
17
|
+
# @api private
|
18
|
+
def data_files
|
19
|
+
%w(base_unit derived_unit).map { |type| Unitwise.data_file type }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Determine if an atom is base level. All atoms that are not base are
|
24
|
+
# defined directly or indirectly in reference to a base atom.
|
25
|
+
# @return [true, false]
|
26
|
+
# @api public
|
27
|
+
def base?
|
28
|
+
!!(@dim && !scale)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determine if an atom is derived. Derived atoms are defined with respect
|
32
|
+
# to other atoms.
|
33
|
+
# @return [true, false]
|
34
|
+
# @api public
|
35
|
+
def derived?
|
36
|
+
!base?
|
37
|
+
end
|
38
|
+
|
39
|
+
# Determine if an atom is metric. Metric atoms can be combined with metric
|
40
|
+
# prefixes.
|
41
|
+
# @return [true, false]
|
42
|
+
# @api public
|
43
|
+
def metric
|
44
|
+
base? ? true : !!@metric
|
45
|
+
end
|
46
|
+
alias_method :metric?, :metric
|
47
|
+
|
48
|
+
# Determine if a unit is special. Special atoms are not defined on a
|
49
|
+
# traditional ratio scale.
|
50
|
+
# @return [true, false]
|
51
|
+
# @api public
|
52
|
+
def special
|
53
|
+
!!@special
|
54
|
+
end
|
55
|
+
alias_method :special?, :special
|
56
|
+
|
57
|
+
# Determine if a unit is arbitrary. Arbitrary atoms are not of any specific
|
58
|
+
# dimension and have no general meaning, therefore cannot be compared with
|
59
|
+
# any other unit.
|
60
|
+
# @return [true, false]
|
61
|
+
# @api public
|
62
|
+
def arbitrary
|
63
|
+
!!@arbitrary
|
64
|
+
end
|
65
|
+
alias_method :arbitrary?, :arbitrary
|
66
|
+
|
67
|
+
# Determine how far away a unit is from a base unit.
|
68
|
+
# @return [Integer]
|
69
|
+
# @api public
|
70
|
+
def depth
|
71
|
+
base? ? 0 : scale.depth + 1
|
72
|
+
end
|
73
|
+
memoize :depth
|
74
|
+
|
75
|
+
# Determine if this is the last atom in the scale chain
|
76
|
+
# @return [true, false]
|
77
|
+
# @api public
|
78
|
+
def terminal?
|
79
|
+
depth <= 3
|
80
|
+
end
|
81
|
+
|
82
|
+
# A representation of an atoms composition. Used to determine if two
|
83
|
+
# different atoms are compatible.
|
84
|
+
# @return [String]
|
85
|
+
# @api public
|
86
|
+
def dim
|
87
|
+
terminal? ? @dim || property : composition_string
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set the atom's scale. It can be set as a Scale or a Functional
|
91
|
+
# @return [Unitwise::Functional, Unitwise::Scale]
|
92
|
+
# @api public
|
93
|
+
def scale=(attrs)
|
94
|
+
@scale = if attrs[:function_code]
|
95
|
+
Functional.new(attrs[:value], attrs[:unit_code], attrs[:function_code])
|
96
|
+
else
|
97
|
+
Scale.new(attrs[:value], attrs[:unit_code])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get a numeric value that can be used to with other atoms to compare with
|
102
|
+
# or operate on. Base units have a scalar of 1.
|
103
|
+
# @return [Numeric]
|
104
|
+
# @api public
|
105
|
+
def scalar(magnitude = 1)
|
106
|
+
base? ? BigDecimal(magnitude.to_s) : scale.scalar(magnitude)
|
107
|
+
end
|
108
|
+
|
109
|
+
def magnitude(scalar = scalar())
|
110
|
+
special? ? scale.magnitude(scalar) : BigDecimal('1')
|
111
|
+
end
|
112
|
+
|
113
|
+
# An atom may have a complex scale with several base atoms at various
|
114
|
+
# depths. This method returns all of this atoms base level terms.
|
115
|
+
# @return [Array] An array containing base Unitwise::Term
|
116
|
+
def root_terms
|
117
|
+
base? ? [Term.new(:atom_code => primary_code)] : scale.root_terms
|
118
|
+
end
|
119
|
+
memoize :root_terms
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Unitwise
|
2
|
+
# The base class that Atom and Prefix are extended from. This class provides
|
3
|
+
# shared functionality for said classes.
|
4
|
+
class Base
|
5
|
+
liner :names, :primary_code, :secondary_code, :symbol, :scale
|
6
|
+
include Memoizable
|
7
|
+
|
8
|
+
# The list of tracked items.
|
9
|
+
# @return [Array] An array of memoized instances.
|
10
|
+
# @api public
|
11
|
+
def self.all
|
12
|
+
@all ||= data.map { |d| new d }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Find a matching instance by a specified attribute.
|
16
|
+
# @param string [String] The search term
|
17
|
+
# @param method [Symbol] The attribute to search by
|
18
|
+
# @return The first matching instance
|
19
|
+
# @example
|
20
|
+
# Unitwise::Atom.find('m')
|
21
|
+
# @api public
|
22
|
+
def self.find(string, method = :primary_code)
|
23
|
+
all.find do |i|
|
24
|
+
key = i.send(method)
|
25
|
+
if key.is_a? Array
|
26
|
+
key.include?(string)
|
27
|
+
else
|
28
|
+
key == string
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Setter for the names attribute. Will always set as an array.
|
34
|
+
# @api semipublic
|
35
|
+
def names=(names)
|
36
|
+
@names = Array(names)
|
37
|
+
end
|
38
|
+
|
39
|
+
# A set of method friendly names.
|
40
|
+
# @return [Array] An array of strings
|
41
|
+
# @api semipublic
|
42
|
+
def slugs
|
43
|
+
names.map do |n|
|
44
|
+
n.downcase.strip.gsub(/\s/, '_').gsub(/\W/, '')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
memoize :slugs
|
48
|
+
|
49
|
+
# String representation for the instance.
|
50
|
+
# @param mode [symbol] The attribute to for stringification
|
51
|
+
# @return [String]
|
52
|
+
# @api public
|
53
|
+
def to_s(mode = :primary_code)
|
54
|
+
res = send(mode)
|
55
|
+
res.respond_to?(:each) ? res.first.to_s : res.to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Unitwise
|
2
|
+
# Compatible is used to establish compatibility between units, terms, or
|
3
|
+
# measurements. This is done by determining the objects atomic composition
|
4
|
+
# represented as a Signed Multiset.
|
5
|
+
module Compatible
|
6
|
+
# @api private
|
7
|
+
def self.included(base)
|
8
|
+
base.send :include, Comparable
|
9
|
+
base.send :include, Memoizable unless base < Memoizable
|
10
|
+
base.send :memoize, :composition, :composition_string
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
super(*args)
|
15
|
+
freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
# A representation of a unit based on the atoms it's derived from.
|
19
|
+
# @return [SignedMultiset]
|
20
|
+
# @api public
|
21
|
+
def composition
|
22
|
+
root_terms.reduce(SignedMultiset.new) do |s, t|
|
23
|
+
s.increment(t.atom.dim, t.exponent) if t.atom
|
24
|
+
s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Define a default #dim for included classes.
|
29
|
+
# @return [String]
|
30
|
+
# @api public
|
31
|
+
def dim
|
32
|
+
composition_string
|
33
|
+
end
|
34
|
+
|
35
|
+
# A string representation of a unit based on the atoms it's derived from
|
36
|
+
# @return [String]
|
37
|
+
# @api public
|
38
|
+
def composition_string
|
39
|
+
composition.sort.map do |k, v|
|
40
|
+
v == 1 ? k.to_s : "#{k}#{v}"
|
41
|
+
end.join('.')
|
42
|
+
end
|
43
|
+
|
44
|
+
# Determine if this instance is similar to or compatible with other
|
45
|
+
# @return [true false]
|
46
|
+
# @api public
|
47
|
+
def compatible_with?(other)
|
48
|
+
composition == other.composition
|
49
|
+
end
|
50
|
+
|
51
|
+
# Compare whether the instance is greater, less than or equal to other.
|
52
|
+
# @return [-1 0 1]
|
53
|
+
# @api public
|
54
|
+
def <=>(other)
|
55
|
+
if other.respond_to?(:composition) && compatible_with?(other)
|
56
|
+
scalar <=> other.scalar
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'unitwise/expression/matcher'
|
2
|
+
require 'unitwise/expression/parser'
|
3
|
+
require 'unitwise/expression/transformer'
|
4
|
+
require 'unitwise/expression/composer'
|
5
|
+
require 'unitwise/expression/decomposer'
|
6
|
+
|
7
|
+
module Unitwise
|
8
|
+
# The Expression module encompases all functions around encoding and decoding
|
9
|
+
# strings into Measurement::Units and vice-versa.
|
10
|
+
module Expression
|
11
|
+
class << self
|
12
|
+
# Build a string representation of a collection of terms
|
13
|
+
# @param terms [Array]
|
14
|
+
# @return [String]
|
15
|
+
# @example
|
16
|
+
# Unitwise::Expression.compose(terms) # => "m2/s2"
|
17
|
+
# @api public
|
18
|
+
def compose(terms, method = :primary_code)
|
19
|
+
Composer.new(terms, method).expression
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convert a string representation of a unit into an array of terms
|
23
|
+
# @param expression [String] The string you wish to convert
|
24
|
+
# @return [Array]
|
25
|
+
# @example
|
26
|
+
# Unitwise::Expression.decompose("m2/s2")
|
27
|
+
# # => [<Unitwise::Term m2>, <Unitwise::Term s-2>]
|
28
|
+
# @api public
|
29
|
+
def decompose(expression)
|
30
|
+
Decomposer.parse(expression)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|