unitwise 0.5.0 → 0.5.1
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 +4 -4
- data/.travis.yml +1 -3
- data/README.md +5 -6
- data/lib/unitwise.rb +2 -2
- data/lib/unitwise/atom.rb +9 -10
- data/lib/unitwise/base.rb +6 -5
- data/lib/unitwise/compatible.rb +5 -5
- data/lib/unitwise/errors.rb +1 -1
- data/lib/unitwise/expression.rb +14 -6
- data/lib/unitwise/ext.rb +1 -1
- data/lib/unitwise/functional.rb +27 -13
- data/lib/unitwise/measurement.rb +20 -20
- data/lib/unitwise/prefix.rb +9 -2
- data/lib/unitwise/scale.rb +4 -4
- data/lib/unitwise/standard.rb +1 -2
- data/lib/unitwise/term.rb +65 -8
- data/lib/unitwise/unit.rb +19 -9
- data/lib/unitwise/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6ff99c65554976100b5a3cc719eb53aa95fc769
|
4
|
+
data.tar.gz: f3264f24f7f00d2380f2433ac4f5c6d0b7631b04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83c29762b50698411daacac88989649953282968257a932f86e224b8b9e84e5139cc912b25bf8ff4647df1dda3c6b1e5884e3285284a7b3fc463ff928ad1046c
|
7
|
+
data.tar.gz: a93702efcf3fcb0f849be1a6cd6a2219c5892df763ffa14f1284c2bb45a2d75c451a9c40db141591cde42ddde0dcbeecb6efcc1386ad795bd1f65d75542e2fdd
|
data/.travis.yml
CHANGED
@@ -6,7 +6,6 @@ rvm:
|
|
6
6
|
- 2.0.0
|
7
7
|
- 2.1.0
|
8
8
|
- ruby-head
|
9
|
-
- rbx
|
10
9
|
- rbx-2
|
11
10
|
matrix:
|
12
11
|
include:
|
@@ -14,8 +13,7 @@ matrix:
|
|
14
13
|
env: JRUBY_OPTS="$JRUBY_OPTS --debug"
|
15
14
|
- rvm: jruby-head
|
16
15
|
env: JRUBY_OPTS="$JRUBY_OPTS --debug"
|
17
|
-
|
18
|
-
- rvm: rbx
|
16
|
+
allow_failures:
|
19
17
|
- rvm: ruby-head
|
20
18
|
- rvm: jruby-head
|
21
19
|
fast_finish: true
|
data/README.md
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
# [Unitwise](//github.com/joshwlewis/unitwise)
|
2
2
|
|
3
|
-
[](http://inch-pages.github.io/github/joshwlewis/unitwise)
|
3
|
+
[](https://rubygems.org/gems/unitwise)
|
4
|
+
[](https://travis-ci.org/joshwlewis/unitwise)
|
5
|
+
[](https://gemnasium.com/joshwlewis/unitwise)
|
6
|
+
[](https://coveralls.io/r/joshwlewis/unitwise)
|
7
|
+
[](https://codeclimate.com/github/joshwlewis/unitwise)
|
9
8
|

|
10
9
|
|
11
10
|
Unitwise is a Ruby library for unit measurement conversion and math.
|
data/lib/unitwise.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'liner'
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'unitwise/version'
|
4
4
|
require 'unitwise/base'
|
5
5
|
require 'unitwise/expression'
|
6
6
|
require 'unitwise/compatible'
|
@@ -36,7 +36,7 @@ module Unitwise
|
|
36
36
|
# A helper to get the location of a yaml data file
|
37
37
|
# @api private
|
38
38
|
def self.data_file(key)
|
39
|
-
File.join path,
|
39
|
+
File.join path, 'data', "#{key}.yaml"
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
data/lib/unitwise/atom.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Unitwise
|
2
2
|
# Atoms are the most basic elements in Unitwise. They are named coded and
|
3
|
-
# scaled units without prefixes, multipliers, exponents, etc. Examples are
|
3
|
+
# scaled units without prefixes, multipliers, exponents, etc. Examples are
|
4
4
|
# 'meter', 'hour', 'pound force'.
|
5
5
|
class Atom < Base
|
6
6
|
liner :classification, :property, :metric, :special, :arbitrary, :dim
|
@@ -11,13 +11,13 @@ module Unitwise
|
|
11
11
|
# Array of hashes representing atom properties.
|
12
12
|
# @api private
|
13
13
|
def data
|
14
|
-
@data ||= data_files.
|
14
|
+
@data ||= data_files.map { |file| YAML.load(File.open file) }.flatten
|
15
15
|
end
|
16
16
|
|
17
17
|
# Data files containing atom data
|
18
18
|
# @api private
|
19
19
|
def data_files
|
20
|
-
%w(base_unit derived_unit).map{|type| Unitwise.data_file type}
|
20
|
+
%w(base_unit derived_unit).map { |type| Unitwise.data_file type }
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -26,10 +26,10 @@ module Unitwise
|
|
26
26
|
# @return [true, false]
|
27
27
|
# @api public
|
28
28
|
def base?
|
29
|
-
|
29
|
+
!!(@dim && !scale)
|
30
30
|
end
|
31
31
|
|
32
|
-
# Determine if an atom is derived. Derived atoms are defined with respect
|
32
|
+
# Determine if an atom is derived. Derived atoms are defined with respect
|
33
33
|
# to other atoms.
|
34
34
|
# @return [true, false]
|
35
35
|
# @api public
|
@@ -55,7 +55,7 @@ module Unitwise
|
|
55
55
|
end
|
56
56
|
alias_method :special?, :special
|
57
57
|
|
58
|
-
# Determine if a unit is arbitrary. Arbitrary atoms are not of any specific
|
58
|
+
# Determine if a unit is arbitrary. Arbitrary atoms are not of any specific
|
59
59
|
# dimension and have no general meaning, therefore cannot be compared with
|
60
60
|
# any other unit.
|
61
61
|
# @return [true, false]
|
@@ -98,7 +98,7 @@ module Unitwise
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
-
# Get a numeric value that can be used to with other atoms to compare with
|
101
|
+
# Get a numeric value that can be used to with other atoms to compare with
|
102
102
|
# or operate on. Base units have a scalar of 1.
|
103
103
|
# @return [Numeric]
|
104
104
|
# @api public
|
@@ -111,7 +111,7 @@ module Unitwise
|
|
111
111
|
# @param x [Numeric] The number to convert to or convert from
|
112
112
|
# @param forward [true, false] Convert to or convert from
|
113
113
|
# @return [Numeric] The converted value
|
114
|
-
def functional(x=scalar, forward=true)
|
114
|
+
def functional(x = scalar, forward = true)
|
115
115
|
scale.functional(x, forward)
|
116
116
|
end
|
117
117
|
|
@@ -121,6 +121,5 @@ module Unitwise
|
|
121
121
|
def root_terms
|
122
122
|
base? ? [Term.new(atom_code: primary_code)] : scale.root_terms
|
123
123
|
end
|
124
|
-
|
125
124
|
end
|
126
|
-
end
|
125
|
+
end
|
data/lib/unitwise/base.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
module Unitwise
|
3
|
+
# The base class that Atom and Prefix are extended from. This class includes
|
4
|
+
# shared functionality from those classes only.
|
3
5
|
class Base
|
4
6
|
liner :names, :primary_code, :secondary_code, :symbol, :scale
|
5
7
|
|
6
8
|
def self.all
|
7
|
-
@all ||= data.map{|d|
|
9
|
+
@all ||= data.map { |d| new d }
|
8
10
|
end
|
9
11
|
|
10
|
-
def self.find(string, method
|
11
|
-
|
12
|
+
def self.find(string, method = :primary_code)
|
13
|
+
all.find do |i|
|
12
14
|
key = i.send(method)
|
13
15
|
if key.respond_to?(:each)
|
14
16
|
key.include?(string)
|
@@ -36,6 +38,5 @@ module Unitwise
|
|
36
38
|
def to_s
|
37
39
|
primary_code
|
38
40
|
end
|
39
|
-
|
40
41
|
end
|
41
|
-
end
|
42
|
+
end
|
data/lib/unitwise/compatible.rb
CHANGED
@@ -3,7 +3,6 @@ module Unitwise
|
|
3
3
|
# measurements. This is done by determining the objects atomic composition
|
4
4
|
# represented as a Signed Multiset.
|
5
5
|
module Compatible
|
6
|
-
|
7
6
|
# @api private
|
8
7
|
def self.included(base)
|
9
8
|
base.send :include, Comparable
|
@@ -14,7 +13,8 @@ module Unitwise
|
|
14
13
|
# @api public
|
15
14
|
def composition
|
16
15
|
root_terms.reduce(SignedMultiset.new) do |s, t|
|
17
|
-
s.increment(t.atom.dim, t.exponent) if t.atom
|
16
|
+
s.increment(t.atom.dim, t.exponent) if t.atom
|
17
|
+
s
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -29,7 +29,7 @@ module Unitwise
|
|
29
29
|
# @return [String]
|
30
30
|
# @api public
|
31
31
|
def composition_string
|
32
|
-
composition.sort.map do |k,v|
|
32
|
+
composition.sort.map do |k, v|
|
33
33
|
v == 1 ? k.to_s : "#{k}#{v}"
|
34
34
|
end.join('.')
|
35
35
|
end
|
@@ -38,7 +38,7 @@ module Unitwise
|
|
38
38
|
# @return [true false]
|
39
39
|
# @api public
|
40
40
|
def compatible_with?(other)
|
41
|
-
|
41
|
+
composition == other.composition
|
42
42
|
end
|
43
43
|
|
44
44
|
# Compare whether the instance is greater, less than or equal to other.
|
@@ -50,4 +50,4 @@ module Unitwise
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
53
|
-
end
|
53
|
+
end
|
data/lib/unitwise/errors.rb
CHANGED
data/lib/unitwise/expression.rb
CHANGED
@@ -26,22 +26,30 @@ module Unitwise
|
|
26
26
|
# @param expression [String] The string you wish to convert
|
27
27
|
# @return [Array]
|
28
28
|
# @example
|
29
|
-
# Unitwise::Expression.decompose("m2/s2")
|
29
|
+
# Unitwise::Expression.decompose("m2/s2")
|
30
30
|
# # => [<Unitwise::Term m2>, <Unitwise::Term s-2>]
|
31
31
|
# @api public
|
32
32
|
def decompose(expression)
|
33
33
|
expression = expression.to_s
|
34
|
-
|
35
|
-
|
36
|
-
@decompose[expression]
|
34
|
+
if decomposed.key?(expression)
|
35
|
+
decomposed[expression]
|
37
36
|
else
|
38
|
-
|
37
|
+
decomposed[expression] = begin
|
39
38
|
Decomposer.new(expression).terms
|
40
39
|
rescue ExpressionError
|
41
40
|
nil
|
42
41
|
end
|
43
42
|
end
|
44
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# A cache of decomposed strings.
|
48
|
+
# @return [Hash]
|
49
|
+
# @api private
|
50
|
+
def decomposed
|
51
|
+
@decomposed ||= {}
|
52
|
+
end
|
45
53
|
end
|
46
54
|
end
|
47
|
-
end
|
55
|
+
end
|
data/lib/unitwise/ext.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
require 'unitwise'
|
2
|
-
require 'unitwise/ext/numeric'
|
2
|
+
require 'unitwise/ext/numeric'
|
data/lib/unitwise/functional.rb
CHANGED
@@ -1,36 +1,40 @@
|
|
1
1
|
module Unitwise
|
2
|
+
# Functional is an alterative function-based scale for atoms with a
|
3
|
+
# non-linear (or non-zero y-intercept) scale. This is most commonly used for
|
4
|
+
# temperatures. Known functions for converting to and from special atoms
|
5
|
+
# are setup as class methods here.
|
2
6
|
class Functional < Scale
|
3
7
|
extend Math
|
4
8
|
|
5
|
-
def self._cel(x, forward=true)
|
9
|
+
def self._cel(x, forward = true)
|
6
10
|
forward ? x - 273.15 : x + 273.15
|
7
11
|
end
|
8
12
|
|
9
|
-
def self._degf(x, forward=true)
|
13
|
+
def self._degf(x, forward = true)
|
10
14
|
forward ? 9.0 * x / 5.0 - 459.67 : 5.0 / 9 * (x + 459.67)
|
11
15
|
end
|
12
16
|
|
13
|
-
def self._hpX(x, forward=true)
|
17
|
+
def self._hpX(x, forward = true)
|
14
18
|
forward ? -log10(x) : 10 ** -x
|
15
19
|
end
|
16
20
|
|
17
|
-
def self._hpC(x, forward=true)
|
18
|
-
forward ? -log(x) / log(100) : 100 ** -x
|
21
|
+
def self._hpC(x, forward = true)
|
22
|
+
forward ? -log(x) / log(100) : 100 ** -x
|
19
23
|
end
|
20
24
|
|
21
|
-
def self._tan100(x, forward=true)
|
25
|
+
def self._tan100(x, forward = true)
|
22
26
|
forward ? 100 * tan(x) : atan(x / 100)
|
23
27
|
end
|
24
28
|
|
25
|
-
def self._ph(x, forward=true)
|
29
|
+
def self._ph(x, forward = true)
|
26
30
|
_hpX(x,forward)
|
27
31
|
end
|
28
32
|
|
29
|
-
def self._ld(x, forward=true)
|
33
|
+
def self._ld(x, forward = true)
|
30
34
|
forward ? log2(x) : 2 ** x
|
31
35
|
end
|
32
36
|
|
33
|
-
def self._ln(x, forward=true)
|
37
|
+
def self._ln(x, forward = true)
|
34
38
|
forward ? log(x) : Math::E ** x
|
35
39
|
end
|
36
40
|
|
@@ -39,19 +43,29 @@ module Unitwise
|
|
39
43
|
end
|
40
44
|
|
41
45
|
def self._2lg(x, forward = true)
|
42
|
-
forward ? 2 * log10(x) : 10 ** (
|
46
|
+
forward ? 2 * log10(x) : 10 ** (x / 2)
|
43
47
|
end
|
44
48
|
|
45
49
|
attr_reader :function_name
|
46
50
|
|
51
|
+
# Setup a new functional.
|
52
|
+
# @param value [Numeric] The magnitude of the scale
|
53
|
+
# @param unit [Unitwise::Unit, String] The unit of the scale
|
54
|
+
# @param function_name [String, Symbol] One of the class methods above to be
|
55
|
+
# used for conversion
|
47
56
|
def initialize(value, unit, function_name)
|
48
57
|
@function_name = function_name
|
49
58
|
super(value, unit)
|
50
59
|
end
|
51
60
|
|
52
|
-
|
61
|
+
# Get the equivalent scalar value of a unit based on the atom's function.
|
62
|
+
# @params x [Numeric]
|
63
|
+
# @params forward [true, false] The direction of the conversion. Use true
|
64
|
+
# when converting from the special, use false when converting to the special.
|
65
|
+
# @return [Numeric]
|
66
|
+
# @api public
|
67
|
+
def functional(x = scalar, forward = true)
|
53
68
|
self.class.send(:"_#{function_name}", x, forward)
|
54
69
|
end
|
55
|
-
|
56
70
|
end
|
57
|
-
end
|
71
|
+
end
|
data/lib/unitwise/measurement.rb
CHANGED
@@ -4,19 +4,16 @@ module Unitwise
|
|
4
4
|
# the value is the magnitued. This is the primary class that outside code
|
5
5
|
# will interact with. Comes with conversion, comparison, and math methods.
|
6
6
|
class Measurement < Scale
|
7
|
-
|
8
7
|
# Create a new Measurement
|
9
8
|
# @param value [Numeric] The scalar value for the measurement
|
10
9
|
# @param unit [String, Measurement::Unit] Either a string expression, or a
|
11
10
|
# Measurement::Unit
|
12
11
|
# @example
|
13
|
-
# Unitwise::Measurement.new(20, 'm/s')
|
12
|
+
# Unitwise::Measurement.new(20, 'm/s')
|
14
13
|
# @api public
|
15
14
|
def initialize(*args)
|
16
15
|
super(*args)
|
17
|
-
if terms.nil?
|
18
|
-
raise ExpressionError, "Could not evaluate `#{unit}`."
|
19
|
-
end
|
16
|
+
fail ExpressionError, "Could not evaluate `#{unit}`." if terms.nil?
|
20
17
|
end
|
21
18
|
|
22
19
|
# Convert this measurement to a compatible unit.
|
@@ -31,7 +28,7 @@ module Unitwise
|
|
31
28
|
if compatible_with?(other_unit)
|
32
29
|
new(converted_value(other_unit), other_unit)
|
33
30
|
else
|
34
|
-
|
31
|
+
fail ConversionError, "Can't convert #{self} to #{other_unit}."
|
35
32
|
end
|
36
33
|
end
|
37
34
|
|
@@ -42,7 +39,8 @@ module Unitwise
|
|
42
39
|
# measurement * some_other_measurement
|
43
40
|
# @api public
|
44
41
|
def *(other)
|
45
|
-
operate(:*, other) ||
|
42
|
+
operate(:*, other) ||
|
43
|
+
fail(TypeError, "Can't multiply #{self} by #{other}.")
|
46
44
|
end
|
47
45
|
|
48
46
|
# Divide this measurement by a number or another measurement
|
@@ -52,7 +50,7 @@ module Unitwise
|
|
52
50
|
# measurement / some_other_measurement
|
53
51
|
# @api public
|
54
52
|
def /(other)
|
55
|
-
operate(:/, other) ||
|
53
|
+
operate(:/, other) || fail(TypeError, "Can't divide #{self} by #{other}")
|
56
54
|
end
|
57
55
|
|
58
56
|
# Add another measurement to this unit. Units must be compatible.
|
@@ -61,7 +59,7 @@ module Unitwise
|
|
61
59
|
# measurement + some_other_measurement
|
62
60
|
# @api public
|
63
61
|
def +(other)
|
64
|
-
combine(:+, other) ||
|
62
|
+
combine(:+, other) || fail(TypeError, "Can't add #{other} to #{self}.")
|
65
63
|
end
|
66
64
|
|
67
65
|
# Subtract another measurement from this unit. Units must be compatible.
|
@@ -70,7 +68,8 @@ module Unitwise
|
|
70
68
|
# measurement - some_other_measurement
|
71
69
|
# @api public
|
72
70
|
def -(other)
|
73
|
-
combine(:-, other) ||
|
71
|
+
combine(:-, other) ||
|
72
|
+
fail(TypeError, "Can't subtract #{other} from #{self}.")
|
74
73
|
end
|
75
74
|
|
76
75
|
# Raise a measurement to a numeric power.
|
@@ -78,11 +77,11 @@ module Unitwise
|
|
78
77
|
# @example
|
79
78
|
# measurement ** 2
|
80
79
|
# @api public
|
81
|
-
def **(
|
82
|
-
if
|
83
|
-
new(
|
80
|
+
def **(other)
|
81
|
+
if other.is_a?(Numeric)
|
82
|
+
new(value ** other, unit ** other)
|
84
83
|
else
|
85
|
-
|
84
|
+
fail TypeError, "Can't raise #{self} to #{other} power."
|
86
85
|
end
|
87
86
|
end
|
88
87
|
|
@@ -97,7 +96,7 @@ module Unitwise
|
|
97
96
|
when Numeric
|
98
97
|
return self.class.new(other, '1'), self
|
99
98
|
else
|
100
|
-
|
99
|
+
fail TypeError, "#{self.class} can't be coerced into #{other.class}"
|
101
100
|
end
|
102
101
|
end
|
103
102
|
|
@@ -108,7 +107,7 @@ module Unitwise
|
|
108
107
|
def to_i
|
109
108
|
Integer(value)
|
110
109
|
end
|
111
|
-
|
110
|
+
alias_method :to_int, :to_i
|
112
111
|
|
113
112
|
# Convert a measurement to a Float.
|
114
113
|
# @example
|
@@ -184,12 +183,13 @@ module Unitwise
|
|
184
183
|
elsif other.respond_to?(:composition)
|
185
184
|
if compatible_with?(other)
|
186
185
|
converted = other.convert_to(unit)
|
187
|
-
new(value.send(operator, converted.value),
|
186
|
+
new(value.send(operator, converted.value),
|
187
|
+
unit.send(operator, converted.unit))
|
188
188
|
else
|
189
|
-
new(value.send(operator, other.value),
|
189
|
+
new(value.send(operator, other.value),
|
190
|
+
unit.send(operator, other.unit))
|
190
191
|
end
|
191
192
|
end
|
192
193
|
end
|
193
|
-
|
194
194
|
end
|
195
|
-
end
|
195
|
+
end
|
data/lib/unitwise/prefix.rb
CHANGED
@@ -1,17 +1,24 @@
|
|
1
1
|
module Unitwise
|
2
|
+
# A prefix can be used with metric atoms to modify their scale.
|
2
3
|
class Prefix < Base
|
3
4
|
liner :scalar
|
4
5
|
|
6
|
+
# The data loaded from the UCUM spec files
|
7
|
+
# @api semipublic
|
5
8
|
def self.data
|
6
|
-
@data ||= YAML
|
9
|
+
@data ||= YAML.load File.open(data_file)
|
7
10
|
end
|
8
11
|
|
12
|
+
# The location of the UCUM spec prefix data file
|
13
|
+
# @api semipublic
|
9
14
|
def self.data_file
|
10
15
|
Unitwise.data_file 'prefix'
|
11
16
|
end
|
12
17
|
|
18
|
+
# Set the scalar value for the prefix, always as a Fixnum
|
19
|
+
# @api semipublic
|
13
20
|
def scalar=(value)
|
14
21
|
@scalar = value.to_f
|
15
22
|
end
|
16
23
|
end
|
17
|
-
end
|
24
|
+
end
|
data/lib/unitwise/scale.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Unitwise
|
2
|
-
# A Unitwise::Scale represents a value and a unit, sort of like a vector, it
|
2
|
+
# A Unitwise::Scale represents a value and a unit, sort of like a vector, it
|
3
3
|
# has two components. In this case, it's a value and unit rather than a
|
4
4
|
# magnitude and direction. This class should be considered mostly privateish.
|
5
5
|
class Scale
|
@@ -46,7 +46,7 @@ module Unitwise
|
|
46
46
|
# @param forward [true, false] whether to convert to this unit or from it.
|
47
47
|
# @return [Numeric]
|
48
48
|
# @api public
|
49
|
-
def functional(x=value, forward=true)
|
49
|
+
def functional(x = value, forward = true)
|
50
50
|
unit.functional(x, forward)
|
51
51
|
end
|
52
52
|
|
@@ -58,7 +58,7 @@ module Unitwise
|
|
58
58
|
value * unit.scalar
|
59
59
|
end
|
60
60
|
|
61
|
-
# The base
|
61
|
+
# The base terms this scale's unit is derived from
|
62
62
|
# @return [Array] An array of Unitwise::Term
|
63
63
|
# @api public
|
64
64
|
def root_terms
|
@@ -82,4 +82,4 @@ module Unitwise
|
|
82
82
|
"#<#{self.class} value=#{value} unit=#{unit}>"
|
83
83
|
end
|
84
84
|
end
|
85
|
-
end
|
85
|
+
end
|
data/lib/unitwise/standard.rb
CHANGED
data/lib/unitwise/term.rb
CHANGED
@@ -1,49 +1,92 @@
|
|
1
1
|
require 'signed_multiset'
|
2
2
|
module Unitwise
|
3
|
+
# A Term is the combination of an atom, prefix, factor and annotation.
|
4
|
+
# Not all properties have to be present. Examples: 'g', 'mm', 'mi2', '4[pi]',
|
5
|
+
# 'kJ{Electric Potential}'
|
3
6
|
class Term < Liner.new(:atom, :prefix, :factor, :exponent, :annotation)
|
4
7
|
include Unitwise::Compatible
|
5
8
|
|
9
|
+
# Setup a new term. Send a hash of properties, or ordered property values.
|
10
|
+
# @api public
|
6
11
|
def initialize(*args)
|
7
12
|
super(*args)
|
8
13
|
freeze
|
9
14
|
end
|
10
15
|
|
16
|
+
# Set the atom.
|
17
|
+
# @param value [String, Atom] Either a string representing an Atom, or an
|
18
|
+
# Atom
|
19
|
+
# @api public
|
11
20
|
def atom=(value)
|
12
21
|
value.is_a?(Atom) ? super(value) : super(Atom.find(value.to_s))
|
13
22
|
end
|
14
23
|
|
24
|
+
# Set the prefix.
|
25
|
+
# @param value [String, Prefix] Either a string representing a Prefix, or
|
26
|
+
# a Prefix
|
15
27
|
def prefix=(value)
|
16
28
|
value.is_a?(Prefix) ? super(value) : super(Prefix.find(value.to_s))
|
17
29
|
end
|
18
30
|
|
31
|
+
# Is this term special?
|
32
|
+
# @return [true, false]
|
19
33
|
def special?
|
20
34
|
atom.special rescue false
|
21
35
|
end
|
22
36
|
|
37
|
+
# Determine how far away a unit is from a base unit.
|
38
|
+
# @return [Integer]
|
39
|
+
# @api public
|
23
40
|
def depth
|
24
41
|
atom ? atom.depth + 1 : 0
|
25
42
|
end
|
26
43
|
|
44
|
+
# Determine if this is the last term in the scale chain
|
45
|
+
# @return [true, false]
|
46
|
+
# @api public
|
27
47
|
def terminal?
|
28
48
|
depth <= 3
|
29
49
|
end
|
30
50
|
|
51
|
+
# The multiplication factor for this term. The default value is 1.
|
52
|
+
# @return [Numeric]
|
53
|
+
# @api public
|
31
54
|
def factor
|
32
55
|
super || 1
|
33
56
|
end
|
34
57
|
|
58
|
+
# The exponent for this term. The default value is 1.
|
59
|
+
# @return [Numeric]
|
60
|
+
# @api public
|
35
61
|
def exponent
|
36
62
|
super || 1
|
37
63
|
end
|
38
64
|
|
65
|
+
# The unitless value for this term. This is the equivalent value of it's
|
66
|
+
# base atom
|
67
|
+
# @return [Numeric]
|
68
|
+
# @api public
|
39
69
|
def scalar
|
40
|
-
(factor * (prefix ? prefix.scalar : 1) *
|
70
|
+
(factor * (prefix ? prefix.scalar : 1) *
|
71
|
+
(atom ? atom.scalar : 1)) ** exponent
|
41
72
|
end
|
42
73
|
|
43
|
-
|
44
|
-
|
74
|
+
# Get the equivalent scalar value of a unit based on the atom's function.
|
75
|
+
# @params x [Numeric]
|
76
|
+
# @params forward [true, false] The direction of the conversion. Use true
|
77
|
+
# when converting from the special, use false when converting to the
|
78
|
+
# special.
|
79
|
+
# @return [Numeric]
|
80
|
+
# @api public
|
81
|
+
# @param x [Numeric] The value
|
82
|
+
def functional(x = scalar, forward = true)
|
83
|
+
(factor * (prefix ? prefix.scalar : 1)) *
|
84
|
+
(atom ? atom.functional(x, forward) : 1) ** exponent
|
45
85
|
end
|
46
86
|
|
87
|
+
# The base units this term is derived from
|
88
|
+
# @return [Array] An array of Unitwise::Term
|
89
|
+
# @api public
|
47
90
|
def root_terms
|
48
91
|
if terminal?
|
49
92
|
[self]
|
@@ -54,6 +97,9 @@ module Unitwise
|
|
54
97
|
end
|
55
98
|
end
|
56
99
|
|
100
|
+
# Term multiplication. Multiply by a Unit, another Term, or a Numeric.
|
101
|
+
# params other [Unit, Term, Numeric]
|
102
|
+
# @return [Term]
|
57
103
|
def *(other)
|
58
104
|
if other.respond_to?(:terms)
|
59
105
|
Unit.new(other.terms << self)
|
@@ -64,9 +110,12 @@ module Unitwise
|
|
64
110
|
end
|
65
111
|
end
|
66
112
|
|
113
|
+
# Term division. Divide by a Unit, another Term, or a Numeric.
|
114
|
+
# params other [Unit, Term, Numeric]
|
115
|
+
# @return [Term]
|
67
116
|
def /(other)
|
68
117
|
if other.respond_to?(:terms)
|
69
|
-
Unit.new(other.terms.map{|t| t ** -1} << self)
|
118
|
+
Unit.new(other.terms.map { |t| t ** -1 } << self)
|
70
119
|
elsif other.respond_to?(:atom)
|
71
120
|
Unit.new([self, other ** -1])
|
72
121
|
elsif other.is_a?(Numeric)
|
@@ -74,14 +123,22 @@ module Unitwise
|
|
74
123
|
end
|
75
124
|
end
|
76
125
|
|
77
|
-
|
78
|
-
|
126
|
+
# Term exponentiation. Raise a term to a numeric power.
|
127
|
+
# params other [Numeric]
|
128
|
+
# @return [Term]
|
129
|
+
def **(other)
|
130
|
+
if other.is_a?(Numeric)
|
131
|
+
self.class.new(to_hash.merge(exponent: exponent * other))
|
132
|
+
else
|
133
|
+
fail TypeError, "Can't raise #{self} to #{other}."
|
134
|
+
end
|
79
135
|
end
|
80
136
|
|
137
|
+
# String representation for this term.
|
138
|
+
# @return [String]
|
81
139
|
def to_s
|
82
140
|
[(factor if factor != 1), prefix.to_s,
|
83
141
|
atom.to_s, (exponent if exponent != 1)].compact.join('')
|
84
142
|
end
|
85
|
-
|
86
143
|
end
|
87
|
-
end
|
144
|
+
end
|
data/lib/unitwise/unit.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
module Unitwise
|
2
|
+
# A Unit is essentially a collection of Unitwise::Term. Terms can be combined
|
3
|
+
# through multiplication or division to create a unit. A unit does not have
|
4
|
+
# a magnitude, but it does have a scale.
|
2
5
|
class Unit
|
3
6
|
liner :expression, :terms
|
4
7
|
include Unitwise::Compatible
|
5
8
|
|
9
|
+
# Create a new unit. You can send an expression or a collection of terms
|
10
|
+
# @param input [String, Unit, [Term]] A string expression, a unit, or a
|
11
|
+
# collection of tems.
|
6
12
|
def initialize(input)
|
7
13
|
if input.respond_to?(:expression)
|
8
14
|
@expression = input.expression
|
@@ -29,7 +35,7 @@ module Unitwise
|
|
29
35
|
terms.count == 1 && terms.all?(&:special?)
|
30
36
|
end
|
31
37
|
|
32
|
-
def functional(x=scalar, forward=true)
|
38
|
+
def functional(x = scalar, forward = true)
|
33
39
|
terms.first.functional(x, forward)
|
34
40
|
end
|
35
41
|
|
@@ -42,7 +48,7 @@ module Unitwise
|
|
42
48
|
end
|
43
49
|
|
44
50
|
def scalar
|
45
|
-
terms.map(&:scalar).
|
51
|
+
terms.map(&:scalar).reduce(&:*)
|
46
52
|
end
|
47
53
|
|
48
54
|
def *(other)
|
@@ -51,28 +57,32 @@ module Unitwise
|
|
51
57
|
elsif other.respond_to?(:atom)
|
52
58
|
self.class.new(terms << other)
|
53
59
|
elsif other.is_a?(Numeric)
|
54
|
-
self.class.new(terms.map{ |t| t * other })
|
60
|
+
self.class.new(terms.map { |t| t * other })
|
55
61
|
else
|
56
|
-
|
62
|
+
fail TypeError, "Can't multiply #{self} by #{other}."
|
57
63
|
end
|
58
64
|
end
|
59
65
|
|
60
66
|
def /(other)
|
61
67
|
if other.respond_to?(:terms)
|
62
|
-
self.class.new(terms + other.terms.map{ |t| t ** -1})
|
68
|
+
self.class.new(terms + other.terms.map { |t| t ** -1 })
|
63
69
|
elsif other.respond_to?(:atom)
|
64
70
|
self.class.new(terms << other ** -1)
|
65
71
|
else
|
66
|
-
|
72
|
+
fail TypeError, "Can't divide #{self} by #{other}."
|
67
73
|
end
|
68
74
|
end
|
69
75
|
|
70
|
-
def **(
|
71
|
-
|
76
|
+
def **(other)
|
77
|
+
if other.is_a?(Numeric)
|
78
|
+
self.class.new(terms.map { |t| t ** other })
|
79
|
+
else
|
80
|
+
fail TypeError, "Can't raise #{self} to #{other}."
|
81
|
+
end
|
72
82
|
end
|
73
83
|
|
74
84
|
def to_s
|
75
85
|
expression
|
76
86
|
end
|
77
87
|
end
|
78
|
-
end
|
88
|
+
end
|
data/lib/unitwise/version.rb
CHANGED