unitwise-193 1.0.4
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 +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
|