unitwise 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -13
- data/lib/unitwise.rb +20 -5
- data/lib/unitwise/atom.rb +56 -16
- data/lib/unitwise/base.rb +8 -5
- data/lib/unitwise/composable.rb +19 -0
- data/lib/unitwise/expression/composer.rb +2 -8
- data/lib/unitwise/expression/decomposer.rb +15 -14
- data/lib/unitwise/expression/parser.rb +3 -3
- data/lib/unitwise/expression/transformer.rb +7 -3
- data/lib/unitwise/ext.rb +1 -0
- data/lib/unitwise/functional.rb +45 -9
- data/lib/unitwise/measurement.rb +11 -2
- data/lib/unitwise/prefix.rb +1 -1
- data/lib/unitwise/scale.rb +2 -2
- data/lib/unitwise/term.rb +9 -13
- data/lib/unitwise/unit.rb +8 -2
- data/lib/unitwise/version.rb +1 -1
- data/simple_bench.rb +21 -0
- data/test/unitwise/atom_test.rb +8 -0
- data/test/unitwise/expression/decomposer_test.rb +16 -7
- data/test/unitwise/expression/parser_test.rb +3 -3
- data/test/unitwise/functional_test.rb +17 -0
- data/test/unitwise/measurement_test.rb +21 -8
- data/test/unitwise/prefix_test.rb +8 -0
- data/unitwise.gemspec +3 -2
- metadata +27 -13
- data/lib/unitwise/function.rb +0 -51
- data/test/unitwise/function_test.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a382c394857814457fe921d7556aa91211d9a752
|
4
|
+
data.tar.gz: 24ecd6c8f3fcd11a281cc0fb860462f2b9766b4c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a23988ac0f39b46d1f325bb4766e13c3ce025eaee602f5946426e4258b07b9b32f4752298ce78160f049c6be05b7699cfbefaf63a451763559de5fecf2336cbc
|
7
|
+
data.tar.gz: 75f123f3fd5195239f728efc037ce2038dece84b647df8234fca6391b4a9280f0e6d6fad614502737daf62976795253c099fa03a4a5c459837f8b92feaedfaa1
|
data/README.md
CHANGED
@@ -38,9 +38,8 @@ require 'unitwise/ext'
|
|
38
38
|
|
39
39
|
### Conversion
|
40
40
|
|
41
|
-
|
42
|
-
compatible unit
|
43
|
-
`convert(unit)` method.
|
41
|
+
Unitwise is able to convert any unit within the UCUM spec to any other
|
42
|
+
compatible unit.
|
44
43
|
|
45
44
|
```ruby
|
46
45
|
distance = Unitwise(5, 'kilometer')
|
@@ -50,7 +49,8 @@ distance.convert('mile')
|
|
50
49
|
# => <Unitwise::Measurement 3.106849747474748 mile>
|
51
50
|
```
|
52
51
|
|
53
|
-
The prettier version of `convert(unit)` is just calling the unit
|
52
|
+
The prettier version of `convert(unit)` is just calling the unit as a method
|
53
|
+
name:
|
54
54
|
|
55
55
|
```ruby
|
56
56
|
distance = 26.2.mile
|
@@ -70,8 +70,8 @@ It also has the ability to compare measurements with the same or different units
|
|
70
70
|
1.meter > 1.yard # => true
|
71
71
|
```
|
72
72
|
|
73
|
-
Again, you have to compare compatible units.
|
74
|
-
|
73
|
+
Again, you have to compare compatible units. For example, comparing two
|
74
|
+
temperatures will work, comparing a mass to a length would fail.
|
75
75
|
|
76
76
|
### SI abbreviations
|
77
77
|
|
@@ -115,7 +115,7 @@ You can multiply or divide measurements and numbers.
|
|
115
115
|
|
116
116
|
```ruby
|
117
117
|
110.volt * 2
|
118
|
-
=> <Unitwise::Measurement 220 volt>
|
118
|
+
# => <Unitwise::Measurement 220 volt>
|
119
119
|
```
|
120
120
|
|
121
121
|
You can multiply or divide measurements with measurements. Here is a fun example
|
@@ -162,14 +162,23 @@ and foot:
|
|
162
162
|
3.convert('[in_br]') == 3.convert('[in_i]') # => false
|
163
163
|
```
|
164
164
|
|
165
|
-
###
|
165
|
+
### Available Units
|
166
166
|
|
167
|
-
|
168
|
-
|
167
|
+
If you are looking for a particular unit (or 'atom' in the UCUM spec), chances
|
168
|
+
are that it is in here. There is a rudimentary search function for both
|
169
|
+
`Unitwise::Atom` and `Unitwise::Prefix`.
|
169
170
|
|
170
|
-
|
171
|
-
|
172
|
-
|
171
|
+
```ruby
|
172
|
+
Unitwise::Atom.search('fathom')
|
173
|
+
# => [ ... ]
|
174
|
+
Unitwise::Prefix.search('milli')
|
175
|
+
# => [ ... ]
|
176
|
+
```
|
177
|
+
|
178
|
+
You can also get the official list from the UCUM website in XML format at
|
179
|
+
[unitsofmeasure.org/ucum-essence.xml](http://unitsofmeasure.org/ucum-essence.xml)
|
180
|
+
or a YAML version within this repo
|
181
|
+
[github.com/joshwlewis/unitwise/tree/master/data](//github.com/joshwlewis/unitwise/tree/master/data).
|
173
182
|
|
174
183
|
|
175
184
|
## Installation
|
data/lib/unitwise.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'liner'
|
2
|
+
|
1
3
|
require "unitwise/version"
|
2
4
|
require 'unitwise/base'
|
3
5
|
require 'unitwise/expression'
|
@@ -9,24 +11,37 @@ require 'unitwise/atom'
|
|
9
11
|
require 'unitwise/prefix'
|
10
12
|
require 'unitwise/term'
|
11
13
|
require 'unitwise/unit'
|
12
|
-
require 'unitwise/function'
|
13
14
|
require 'unitwise/errors'
|
14
15
|
|
16
|
+
# Unitwise is a library for performing mathematical operations and conversions
|
17
|
+
# on all units defined by the [Unified Code for Units of Measure(UCUM).
|
15
18
|
module Unitwise
|
19
|
+
# The system path for the installed gem
|
20
|
+
# @api private
|
16
21
|
def self.path
|
17
22
|
@path ||= File.dirname(File.dirname(__FILE__))
|
18
23
|
end
|
19
24
|
|
25
|
+
# A helper to get the location of a yaml data file
|
26
|
+
# @api private
|
20
27
|
def self.data_file(key)
|
21
28
|
File.join path, "data", "#{key}.yaml"
|
22
29
|
end
|
23
30
|
end
|
24
31
|
|
25
|
-
|
26
|
-
|
27
|
-
|
32
|
+
# Measurement initializer shorthand. Use this to instantiate new measurements.
|
33
|
+
# @param first [Numeric, String] Either a numeric value or a unit expression
|
34
|
+
# @param last [String, Nil] Either a unit expression, or nil
|
35
|
+
# @return [Unitwise::Measurement]
|
36
|
+
# @example
|
37
|
+
# Unitwise(20, 'mile') # => #<Unitwise::Measurement 20 mile>
|
38
|
+
# Unitwise('km') # => #<Unitwise::Measurement 1 km>
|
39
|
+
# @api public
|
40
|
+
def Unitwise(first, last=nil)
|
41
|
+
if last
|
42
|
+
Unitwise::Measurement.new(first, last)
|
28
43
|
else
|
29
|
-
Unitwise::Measurement.new(1,
|
44
|
+
Unitwise::Measurement.new(1, first)
|
30
45
|
end
|
31
46
|
end
|
32
47
|
|
data/lib/unitwise/atom.rb
CHANGED
@@ -1,56 +1,92 @@
|
|
1
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'.
|
2
5
|
class Atom < Base
|
3
|
-
|
4
|
-
attr_writer :dim
|
6
|
+
liner :classification, :property, :metric, :special, :arbitrary, :dim
|
5
7
|
|
6
8
|
include Unitwise::Composable
|
7
9
|
|
8
10
|
class << self
|
11
|
+
# Array of hashes representing atom properties.
|
12
|
+
# @api private
|
9
13
|
def data
|
10
14
|
@data ||= data_files.reduce([]){|m,f| m += YAML::load File.open(f)}
|
11
15
|
end
|
12
16
|
|
17
|
+
# Data files containing atom data
|
18
|
+
# @api private
|
13
19
|
def data_files
|
14
20
|
%w(base_unit derived_unit).map{|type| Unitwise.data_file type}
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
24
|
+
# Determine if an atom is base level. All atoms that are not base are
|
25
|
+
# defined directly or indirectly in reference to a base atom.
|
26
|
+
# @return [true, false]
|
27
|
+
# @api public
|
18
28
|
def base?
|
19
29
|
scale.nil? && !@dim.nil?
|
20
30
|
end
|
21
31
|
|
32
|
+
# Determine if an atom is derived. Derived atoms are defined with respect
|
33
|
+
# to other atoms.
|
34
|
+
# @return [true, false]
|
35
|
+
# @api public
|
22
36
|
def derived?
|
23
37
|
!base?
|
24
38
|
end
|
25
39
|
|
40
|
+
# Determine if an atom is metric. Metric atoms can be combined with metric
|
41
|
+
# prefixes.
|
42
|
+
# @return [true, false]
|
43
|
+
# @api public
|
26
44
|
def metric?
|
27
45
|
base? ? true : !!metric
|
28
46
|
end
|
29
47
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
34
52
|
def special?
|
35
53
|
!!special
|
36
54
|
end
|
37
55
|
|
56
|
+
# Determine if a unit is arbitrary. Arbitrary atoms are not of any specific
|
57
|
+
# dimension and have no general meaning, therefore cannot be compared with
|
58
|
+
# any other unit.
|
59
|
+
# @return [true, false]
|
60
|
+
# @api public
|
38
61
|
def arbitrary?
|
39
62
|
!!arbitrary
|
40
63
|
end
|
41
64
|
|
65
|
+
# Determine how far away a unit is from a base unit.
|
66
|
+
# @return [Integer]
|
67
|
+
# @api public
|
42
68
|
def depth
|
43
69
|
base? ? 0 : scale.depth + 1
|
44
70
|
end
|
45
71
|
|
72
|
+
# Determine if this is the last atom in the scale chain
|
73
|
+
# @return [true, false]
|
74
|
+
# @api public
|
46
75
|
def terminal?
|
47
76
|
depth <= 3
|
48
77
|
end
|
49
78
|
|
79
|
+
# A representation of an atoms composition. Used to determine if two
|
80
|
+
# different atoms are compatible.
|
81
|
+
# @return [String]
|
82
|
+
# @api public
|
50
83
|
def dim
|
51
84
|
terminal? ? @dim || property : composition_string
|
52
85
|
end
|
53
86
|
|
87
|
+
# Set the atom's scale. It can be set as a Scale or a Functional
|
88
|
+
# @return [Unitwise::Functional, Unitwise::Scale]
|
89
|
+
# @api public
|
54
90
|
def scale=(attrs)
|
55
91
|
@scale = if attrs[:function_code]
|
56
92
|
Functional.new(attrs[:value], attrs[:unit_code], attrs[:function_code])
|
@@ -59,25 +95,29 @@ module Unitwise
|
|
59
95
|
end
|
60
96
|
end
|
61
97
|
|
98
|
+
# Get a numeric value that can be used to with other atoms to compare with
|
99
|
+
# or operate on. Base units have a scalar of 1.
|
100
|
+
# @return [Numeric]
|
101
|
+
# @api public
|
62
102
|
def scalar
|
63
103
|
base? ? 1 : scale.scalar
|
64
104
|
end
|
65
105
|
|
66
|
-
|
67
|
-
|
106
|
+
# Get a functional value that can be used with other atoms to compare with
|
107
|
+
# or operate on.
|
108
|
+
# @param x [Numeric] The number to convert to or convert from
|
109
|
+
# @param forward [true, false] Convert to or convert from
|
110
|
+
# @return [Numeric] The converted value
|
111
|
+
def functional(x=scalar, forward=true)
|
112
|
+
scale.functional(x, forward)
|
68
113
|
end
|
69
114
|
|
115
|
+
# An atom may have a complex scale with several base atoms at various
|
116
|
+
# depths. This method returns all of this atoms base level terms.
|
117
|
+
# @return [Array] An array containing base Unitwise::Term
|
70
118
|
def root_terms
|
71
119
|
base? ? [Term.new(atom: self)] : scale.root_terms
|
72
120
|
end
|
73
121
|
|
74
|
-
def to_s
|
75
|
-
"#{primary_code} (#{names.join('|')})"
|
76
|
-
end
|
77
|
-
|
78
|
-
def inspect
|
79
|
-
"<#{self.class} #{to_s}>"
|
80
|
-
end
|
81
|
-
|
82
122
|
end
|
83
123
|
end
|
data/lib/unitwise/base.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
module Unitwise
|
3
3
|
class Base
|
4
|
-
|
5
|
-
attr_reader :names, :scale
|
4
|
+
liner :names, :primary_code, :secondary_code, :symbol, :scale
|
6
5
|
|
7
6
|
def self.all
|
8
7
|
@all ||= data.map{|d| self.new d }
|
@@ -27,9 +26,9 @@ module Unitwise
|
|
27
26
|
end
|
28
27
|
end
|
29
28
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
29
|
+
def self.search(term)
|
30
|
+
self.all.select do |i|
|
31
|
+
i.search_strings.any? { |string| string =~ /#{term}/i }
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
@@ -43,5 +42,9 @@ module Unitwise
|
|
43
42
|
end
|
44
43
|
end
|
45
44
|
|
45
|
+
def search_strings
|
46
|
+
[primary_code, secondary_code, names, slugs, symbol].flatten.compact
|
47
|
+
end
|
48
|
+
|
46
49
|
end
|
47
50
|
end
|
data/lib/unitwise/composable.rb
CHANGED
@@ -1,30 +1,49 @@
|
|
1
1
|
module Unitwise
|
2
|
+
# Composable provides methods for determing the composition of unit, term, or
|
3
|
+
# measurement. This is used to establish compatibility between units, terms,
|
4
|
+
# or measurements.
|
2
5
|
module Composable
|
3
6
|
|
7
|
+
# @api private
|
4
8
|
def self.included(base)
|
5
9
|
base.send :include, Comparable
|
6
10
|
end
|
7
11
|
|
12
|
+
# A representation of a unit based on the atoms it's derived from.
|
13
|
+
# @return [SignedMultiset]
|
14
|
+
# @api public
|
8
15
|
def composition
|
9
16
|
root_terms.reduce(SignedMultiset.new) do |s, t|
|
10
17
|
s.increment(t.atom.dim, t.exponent) if t.atom; s
|
11
18
|
end
|
12
19
|
end
|
13
20
|
|
21
|
+
# Define a default #dim for included classes.
|
22
|
+
# @return [String]
|
23
|
+
# @api public
|
14
24
|
def dim
|
15
25
|
composition_string
|
16
26
|
end
|
17
27
|
|
28
|
+
# A string representation of a unit based on the atoms it's derived from
|
29
|
+
# @return [String]
|
30
|
+
# @api public
|
18
31
|
def composition_string
|
19
32
|
composition.sort.map do |k,v|
|
20
33
|
v == 1 ? k.to_s : "#{k}#{v}"
|
21
34
|
end.join('.')
|
22
35
|
end
|
23
36
|
|
37
|
+
# Determine if this instance is similar to or compatible with other
|
38
|
+
# @return [true false]
|
39
|
+
# @api public
|
24
40
|
def similar_to?(other)
|
25
41
|
self.composition == other.composition
|
26
42
|
end
|
27
43
|
|
44
|
+
# Compare whether the instance is greater, less than or equal to other.
|
45
|
+
# @return [-1 0 1]
|
46
|
+
# @api public
|
28
47
|
def <=>(other)
|
29
48
|
if other.respond_to?(:composition) && similar_to?(other)
|
30
49
|
scalar <=> other.scalar
|
@@ -2,14 +2,8 @@ module Unitwise
|
|
2
2
|
module Expression
|
3
3
|
class Composer
|
4
4
|
attr_reader :terms
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
@terms = input.terms
|
8
|
-
elsif input.respond_to?(:each)
|
9
|
-
@terms = input
|
10
|
-
else
|
11
|
-
@terms = Expression.decompose(input.to_s)
|
12
|
-
end
|
5
|
+
def initialize(terms)
|
6
|
+
@terms = terms
|
13
7
|
end
|
14
8
|
|
15
9
|
def set
|
@@ -2,8 +2,10 @@ module Unitwise
|
|
2
2
|
module Expression
|
3
3
|
class Decomposer
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
METHODS = [:primary_code, :secondary_code, :names, :slugs, :symbol]
|
6
|
+
|
7
|
+
PARSERS = METHODS.reduce({}) do |hash, method|
|
8
|
+
hash[method] = Parser.new(method); hash
|
7
9
|
end
|
8
10
|
|
9
11
|
TRANSFORMER = Transformer.new
|
@@ -17,23 +19,22 @@ module Unitwise
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
|
-
def
|
21
|
-
PARSERS.reduce(nil) do |
|
22
|
-
if
|
23
|
-
return
|
22
|
+
def transform
|
23
|
+
PARSERS.reduce(nil) do |foo, (method, parser)|
|
24
|
+
if parsed = parser.parse(expression) rescue next
|
25
|
+
return TRANSFORMER.apply(parsed, key: method)
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
28
|
-
def transform
|
29
|
-
@transform ||= TRANSFORMER.apply(parse)
|
30
|
-
end
|
31
|
-
|
32
30
|
def terms
|
33
|
-
|
34
|
-
transform
|
35
|
-
|
36
|
-
|
31
|
+
@terms ||= begin
|
32
|
+
transformation = transform
|
33
|
+
if transformation.respond_to?(:terms)
|
34
|
+
transformation.terms
|
35
|
+
else
|
36
|
+
Array(transformation)
|
37
|
+
end
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
@@ -30,18 +30,18 @@ module Unitwise
|
|
30
30
|
|
31
31
|
rule (:number) { fixnum | integer }
|
32
32
|
|
33
|
-
rule (:exponent) {
|
33
|
+
rule (:exponent) { integer.as(:exponent) }
|
34
34
|
|
35
35
|
rule (:factor) { number.as(:factor) }
|
36
36
|
|
37
37
|
rule (:operator) { (str('.') | str('/')).as(:operator) }
|
38
38
|
|
39
39
|
rule (:term) do
|
40
|
-
((simpleton | factor) >> exponent.maybe >> annotation.maybe).as(:term)
|
40
|
+
((factor >> simpleton | simpleton | factor) >> exponent.maybe >> annotation.maybe).as(:term)
|
41
41
|
end
|
42
42
|
|
43
43
|
rule (:group) do
|
44
|
-
(str('(') >> expression.as(:nested) >> str(')') >> exponent.maybe).as(:group)
|
44
|
+
(factor.maybe >> str('(') >> expression.as(:nested) >> str(')') >> exponent.maybe).as(:group)
|
45
45
|
end
|
46
46
|
|
47
47
|
rule (:expression) do
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module Unitwise
|
2
2
|
module Expression
|
3
3
|
class Transformer < Parslet::Transform
|
4
|
+
|
4
5
|
rule(integer: simple(:i)) { i.to_i }
|
5
6
|
rule(fixnum: simple(:f)) { f.to_f }
|
6
7
|
|
7
|
-
rule(prefix_code: simple(:c)) { Prefix.
|
8
|
-
rule(atom_code: simple(:c))
|
9
|
-
rule(term: subtree(:h))
|
8
|
+
rule(prefix_code: simple(:c)) { |ctx| Prefix.find_by(ctx[:key], ctx[:c]) }
|
9
|
+
rule(atom_code: simple(:c)) { |ctx| Atom.find_by(ctx[:key], ctx[:c]) }
|
10
|
+
rule(term: subtree(:h)) { Term.new(h) }
|
10
11
|
|
11
12
|
rule(operator: simple(:o), right: simple(:r)) do
|
12
13
|
o == '/' ? r ** -1 : r
|
@@ -18,6 +19,9 @@ module Unitwise
|
|
18
19
|
|
19
20
|
rule(left: simple(:l)) { l }
|
20
21
|
|
22
|
+
rule(group: { factor: simple(:f) , nested: simple(:n), exponent: simple(:e) }) do
|
23
|
+
( n ** e ) * f
|
24
|
+
end
|
21
25
|
rule(group: { nested: simple(:n) , exponent: simple(:e)}) { n ** e }
|
22
26
|
|
23
27
|
rule(group: { nested: simple(:n) }) { n }
|
data/lib/unitwise/ext.rb
CHANGED
data/lib/unitwise/functional.rb
CHANGED
@@ -1,20 +1,56 @@
|
|
1
1
|
module Unitwise
|
2
2
|
class Functional < Scale
|
3
|
+
extend Math
|
3
4
|
|
4
|
-
|
5
|
+
def self._cel(x, forward=true)
|
6
|
+
forward ? x - 273.15 : x + 273.15
|
7
|
+
end
|
5
8
|
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
+
def self._degf(x, forward=true)
|
10
|
+
forward ? 9.0 * x / 5.0 - 459.67 : 5.0 / 9 * (x + 459.67)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self._hpX(x, forward=true)
|
14
|
+
forward ? -log10(x) : 10 ** -x
|
15
|
+
end
|
16
|
+
|
17
|
+
def self._hpC(x, forward=true)
|
18
|
+
forward ? -log(x) / log(100) : 100 ** -x
|
19
|
+
end
|
20
|
+
|
21
|
+
def self._tan100(x, forward=true)
|
22
|
+
forward ? 100 * tan(x) : atan(x / 100)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self._ph(x, forward=true)
|
26
|
+
_hpX(x,forward)
|
9
27
|
end
|
10
28
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
29
|
+
def self._ld(x, forward=true)
|
30
|
+
forward ? log2(x) : 2 ** x
|
31
|
+
end
|
32
|
+
|
33
|
+
def self._ln(x, forward=true)
|
34
|
+
forward ? log(x) : Math::E ** x
|
35
|
+
end
|
36
|
+
|
37
|
+
def self._lg(x, forward = true)
|
38
|
+
forward ? log10(x) : 10 ** x
|
39
|
+
end
|
40
|
+
|
41
|
+
def self._2lg(x, forward = true)
|
42
|
+
forward ? 2 * log10(x) : 10 ** ( x / 2 )
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :function_name
|
46
|
+
|
47
|
+
def initialize(value, unit, function_name)
|
48
|
+
super(value, unit)
|
49
|
+
@function_name = function_name
|
14
50
|
end
|
15
51
|
|
16
|
-
def functional(x=scalar,
|
17
|
-
|
52
|
+
def functional(x=scalar, forward = true)
|
53
|
+
self.class.send(:"_#{function_name}", x, forward)
|
18
54
|
end
|
19
55
|
|
20
56
|
end
|
data/lib/unitwise/measurement.rb
CHANGED
@@ -41,6 +41,15 @@ module Unitwise
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
def coerce(other)
|
45
|
+
case other
|
46
|
+
when Numeric
|
47
|
+
return self.class.new(other, '1'), self
|
48
|
+
else
|
49
|
+
raise TypeError, "#{self.class} can't be coerced into #{other.class}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
44
53
|
def method_missing(meth, *args, &block)
|
45
54
|
if Unitwise::Expression.decompose(meth)
|
46
55
|
self.convert(meth)
|
@@ -58,9 +67,9 @@ module Unitwise
|
|
58
67
|
def converted_value(other_unit)
|
59
68
|
if unit.special?
|
60
69
|
if other_unit.special?
|
61
|
-
other_unit.functional functional(value,
|
70
|
+
other_unit.functional functional(value, false)
|
62
71
|
else
|
63
|
-
functional(value,
|
72
|
+
functional(value, false) / other_unit.scalar
|
64
73
|
end
|
65
74
|
else
|
66
75
|
if other_unit.special?
|
data/lib/unitwise/prefix.rb
CHANGED
data/lib/unitwise/scale.rb
CHANGED
data/lib/unitwise/term.rb
CHANGED
@@ -1,18 +1,10 @@
|
|
1
1
|
require 'signed_multiset'
|
2
2
|
module Unitwise
|
3
3
|
class Term
|
4
|
-
|
5
|
-
attr_writer :factor, :exponent
|
6
|
-
attr_accessor :annotation
|
4
|
+
liner :atom_code, :prefix_code, :atom, :prefix, :factor, :exponent, :annotation
|
7
5
|
|
8
6
|
include Unitwise::Composable
|
9
7
|
|
10
|
-
def initialize(attributes)
|
11
|
-
attributes.each do |k,v|
|
12
|
-
public_send :"#{k}=", v
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
8
|
def prefix_code
|
17
9
|
@prefix_code ||= (@prefix.primary_code if @prefix)
|
18
10
|
end
|
@@ -53,8 +45,8 @@ module Unitwise
|
|
53
45
|
(factor * (prefix ? prefix.scalar : 1) * (atom ? atom.scalar : 1)) ** exponent
|
54
46
|
end
|
55
47
|
|
56
|
-
def functional(x=scalar,
|
57
|
-
(factor * (prefix ? prefix.scalar : 1)) * (atom ? atom.functional(x,
|
48
|
+
def functional(x=scalar, forward=true)
|
49
|
+
(factor * (prefix ? prefix.scalar : 1)) * (atom ? atom.functional(x, forward) : 1) ** exponent
|
58
50
|
end
|
59
51
|
|
60
52
|
def root_terms
|
@@ -76,16 +68,20 @@ module Unitwise
|
|
76
68
|
def *(other)
|
77
69
|
if other.respond_to?(:terms)
|
78
70
|
Unit.new(other.terms << self)
|
79
|
-
|
71
|
+
elsif other.respond_to?(:atom)
|
80
72
|
Unit.new([self, other])
|
73
|
+
elsif other.is_a?(Numeric)
|
74
|
+
self.class.new(to_hash.merge(factor: factor * other))
|
81
75
|
end
|
82
76
|
end
|
83
77
|
|
84
78
|
def /(other)
|
85
79
|
if other.respond_to?(:terms)
|
86
80
|
Unit.new(other.terms.map{|t| t ** -1} << self)
|
87
|
-
|
81
|
+
elsif other.respond_to?(:atom)
|
88
82
|
Unit.new([self, other ** -1])
|
83
|
+
elsif other.is_a?(Numeric)
|
84
|
+
self.class.new(to_hash.merge(factor: factor / other))
|
89
85
|
end
|
90
86
|
end
|
91
87
|
|
data/lib/unitwise/unit.rb
CHANGED
@@ -28,8 +28,8 @@ module Unitwise
|
|
28
28
|
terms.count == 1 && terms.all?(&:special?)
|
29
29
|
end
|
30
30
|
|
31
|
-
def functional(x=scalar,
|
32
|
-
terms.first.functional(x,
|
31
|
+
def functional(x=scalar, forward=true)
|
32
|
+
terms.first.functional(x, forward)
|
33
33
|
end
|
34
34
|
|
35
35
|
def dup
|
@@ -59,6 +59,10 @@ module Unitwise
|
|
59
59
|
def *(other)
|
60
60
|
if other.respond_to?(:terms)
|
61
61
|
self.class.new(terms + other.terms)
|
62
|
+
elsif other.respond_to?(:atom)
|
63
|
+
self.class.new(terms << other)
|
64
|
+
elsif other.is_a?(Numeric)
|
65
|
+
self.class.new(terms.map{ |t| t * other })
|
62
66
|
else
|
63
67
|
raise TypeError, "Can't multiply #{inspect} by #{other}."
|
64
68
|
end
|
@@ -67,6 +71,8 @@ module Unitwise
|
|
67
71
|
def /(other)
|
68
72
|
if other.respond_to?(:terms)
|
69
73
|
self.class.new(terms + other.terms.map{ |t| t ** -1})
|
74
|
+
elsif other.respond_to?(:atom)
|
75
|
+
self.class.new(terms << other ** -1)
|
70
76
|
else
|
71
77
|
raise TypeError, "Can't divide #{inspect} by #{other}."
|
72
78
|
end
|
data/lib/unitwise/version.rb
CHANGED
data/simple_bench.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'unitwise'
|
2
|
+
require 'ruby-units'
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
Benchmark.bm(10) do |x|
|
6
|
+
|
7
|
+
x.report('unitwise') do
|
8
|
+
100000.times do |x|
|
9
|
+
expression = "mm#{x % 20}"
|
10
|
+
Unitwise::Measurement.new(x, expression)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
x.report('ruby-units') do
|
15
|
+
100000.times do |x|
|
16
|
+
expression = "mm^#{x % 20}"
|
17
|
+
Unit(x, expression)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/test/unitwise/atom_test.rb
CHANGED
@@ -23,6 +23,14 @@ describe Unitwise::Atom do
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
describe "::search" do
|
27
|
+
it "must search for atoms" do
|
28
|
+
result = subject.search("inch")
|
29
|
+
result.count.must_equal 10
|
30
|
+
result.must_include subject.find("inch")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
26
34
|
let(:second) { Unitwise::Atom.find("s") }
|
27
35
|
let(:yard) { Unitwise::Atom.find("[yd_i]")}
|
28
36
|
let(:pi) { Unitwise::Atom.find("[pi]")}
|
@@ -3,13 +3,6 @@ require 'test_helper'
|
|
3
3
|
describe Unitwise::Expression::Decomposer do
|
4
4
|
subject { Unitwise::Expression::Decomposer }
|
5
5
|
|
6
|
-
describe "PARSERS" do
|
7
|
-
it "should return multiple parsers" do
|
8
|
-
subject::PARSERS.must_be_instance_of Array
|
9
|
-
subject::PARSERS.sample.must_be_instance_of Unitwise::Expression::Parser
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
6
|
describe "#terms" do
|
14
7
|
it "should accept codes" do
|
15
8
|
fts = subject.new("[ft_i]/s").terms
|
@@ -31,6 +24,22 @@ describe Unitwise::Expression::Decomposer do
|
|
31
24
|
saff = subject.new("<i>g<sub>n</sub></i>").terms
|
32
25
|
saff.count.must_equal 1
|
33
26
|
end
|
27
|
+
it "should accept complex units" do
|
28
|
+
complex = subject.new("(mg.(km/s)3/J)2.Pa").terms
|
29
|
+
complex.count.must_equal 5
|
30
|
+
end
|
31
|
+
it "should accept more complex units" do
|
32
|
+
complex = subject.new("4.1(mm/2s3)4.7.3J-2").terms
|
33
|
+
complex.count.must_equal 3
|
34
|
+
end
|
35
|
+
it "should accept weird units" do
|
36
|
+
frequency = subject.new("/s").terms
|
37
|
+
frequency.count.must_equal 1
|
38
|
+
end
|
39
|
+
it "should accept units with a factor and unit" do
|
40
|
+
oddity = subject.new("2ms2").terms
|
41
|
+
oddity.count.must_equal 1
|
42
|
+
end
|
34
43
|
end
|
35
44
|
|
36
45
|
end
|
@@ -36,11 +36,11 @@ describe Unitwise::Expression::Parser do
|
|
36
36
|
end
|
37
37
|
|
38
38
|
describe "#exponent" do
|
39
|
-
it "must match positives
|
39
|
+
it "must match positives integers" do
|
40
40
|
subject.exponent.parse('4')[:exponent].must_equal(integer: '4')
|
41
41
|
end
|
42
|
-
it "must match
|
43
|
-
subject.exponent.parse('-5
|
42
|
+
it "must match negative integers" do
|
43
|
+
subject.exponent.parse('-5')[:exponent].must_equal(integer: '-5')
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Unitwise::Functional do
|
4
|
+
subject { Unitwise::Functional }
|
5
|
+
%w{cel degf hpX hpC tan100 ph ld ln lg 2lg}.each do |function|
|
6
|
+
function = :"_#{function}"
|
7
|
+
describe function do
|
8
|
+
it 'should convert back and forth' do
|
9
|
+
number = rand.round(5)
|
10
|
+
there = subject.send function, number, true
|
11
|
+
back_again = subject.send function, there, false
|
12
|
+
back_again.round(5).must_equal number
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -37,15 +37,15 @@ describe Unitwise::Measurement do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
let(:mph)
|
41
|
-
let(:kmh)
|
40
|
+
let(:mph) { Unitwise::Measurement.new(60, '[mi_i]/h') }
|
41
|
+
let(:kmh) { Unitwise::Measurement.new(100, 'km/h') }
|
42
42
|
let(:mile) { Unitwise::Measurement.new(3, '[mi_i]') }
|
43
|
-
let(:hpm)
|
44
|
-
let(:cui)
|
45
|
-
let(:cel)
|
46
|
-
let(:k)
|
47
|
-
let(:f)
|
48
|
-
let(:r)
|
43
|
+
let(:hpm) { Unitwise::Measurement.new(6, 'h/[mi_i]') }
|
44
|
+
let(:cui) { Unitwise::Measurement.new(12, "[in_i]3") }
|
45
|
+
let(:cel) { Unitwise::Measurement.new(22, 'Cel') }
|
46
|
+
let(:k) { Unitwise::Measurement.new(373.15, 'K') }
|
47
|
+
let(:f) { Unitwise::Measurement.new(98.6, '[degF]')}
|
48
|
+
let(:r) { Unitwise::Measurement.new(491.67, '[degR]') }
|
49
49
|
|
50
50
|
describe "#scalar" do
|
51
51
|
it "must return value relative to terminal atoms" do
|
@@ -153,6 +153,19 @@ describe Unitwise::Measurement do
|
|
153
153
|
exp.value.must_equal 0.037037037037037035
|
154
154
|
exp.unit.to_s.must_equal "1/[mi_i]3"
|
155
155
|
end
|
156
|
+
it "must not raise to a weird power" do
|
157
|
+
-> { mile ** 'weird' }.must_raise TypeError
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe "#coerce" do
|
162
|
+
let(:meter) { Unitwise::Measurement.new(1, 'm') }
|
163
|
+
it "must coerce numerics" do
|
164
|
+
meter.coerce(5).must_equal [ Unitwise::Measurement.new(5, '1'), meter ]
|
165
|
+
end
|
166
|
+
it "should raise an error for other crap" do
|
167
|
+
-> { meter.coerce("foo") }.must_raise TypeError
|
168
|
+
end
|
156
169
|
end
|
157
170
|
|
158
171
|
describe "#method_missing" do
|
data/unitwise.gemspec
CHANGED
@@ -17,8 +17,9 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^test/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
-
gem.add_dependency "
|
21
|
-
gem.add_dependency "
|
20
|
+
gem.add_dependency "liner", "~> 0.2.1"
|
21
|
+
gem.add_dependency "signed_multiset", "~> 0.2.0"
|
22
|
+
gem.add_dependency "parslet", "~> 1.5.0"
|
22
23
|
|
23
24
|
gem.add_development_dependency "minitest"
|
24
25
|
gem.add_development_dependency "rake"
|
metadata
CHANGED
@@ -1,43 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unitwise
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Lewis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: liner
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.1
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: signed_multiset
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
|
-
- -
|
31
|
+
- - ~>
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
33
|
+
version: 0.2.0
|
20
34
|
type: :runtime
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
24
|
-
- -
|
38
|
+
- - ~>
|
25
39
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
40
|
+
version: 0.2.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: parslet
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
|
-
- -
|
45
|
+
- - ~>
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
47
|
+
version: 1.5.0
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
|
-
- -
|
52
|
+
- - ~>
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
54
|
+
version: 1.5.0
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: minitest
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -138,7 +152,6 @@ files:
|
|
138
152
|
- lib/unitwise/expression/transformer.rb
|
139
153
|
- lib/unitwise/ext.rb
|
140
154
|
- lib/unitwise/ext/numeric.rb
|
141
|
-
- lib/unitwise/function.rb
|
142
155
|
- lib/unitwise/functional.rb
|
143
156
|
- lib/unitwise/measurement.rb
|
144
157
|
- lib/unitwise/prefix.rb
|
@@ -154,6 +167,7 @@ files:
|
|
154
167
|
- lib/unitwise/term.rb
|
155
168
|
- lib/unitwise/unit.rb
|
156
169
|
- lib/unitwise/version.rb
|
170
|
+
- simple_bench.rb
|
157
171
|
- test/test_helper.rb
|
158
172
|
- test/unitwise/atom_test.rb
|
159
173
|
- test/unitwise/base_test.rb
|
@@ -161,7 +175,7 @@ files:
|
|
161
175
|
- test/unitwise/expression/matcher_test.rb
|
162
176
|
- test/unitwise/expression/parser_test.rb
|
163
177
|
- test/unitwise/ext/numeric_test.rb
|
164
|
-
- test/unitwise/
|
178
|
+
- test/unitwise/functional_test.rb
|
165
179
|
- test/unitwise/measurement_test.rb
|
166
180
|
- test/unitwise/prefix_test.rb
|
167
181
|
- test/unitwise/term_test.rb
|
@@ -201,7 +215,7 @@ test_files:
|
|
201
215
|
- test/unitwise/expression/matcher_test.rb
|
202
216
|
- test/unitwise/expression/parser_test.rb
|
203
217
|
- test/unitwise/ext/numeric_test.rb
|
204
|
-
- test/unitwise/
|
218
|
+
- test/unitwise/functional_test.rb
|
205
219
|
- test/unitwise/measurement_test.rb
|
206
220
|
- test/unitwise/prefix_test.rb
|
207
221
|
- test/unitwise/term_test.rb
|
data/lib/unitwise/function.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
module Unitwise
|
2
|
-
class Function
|
3
|
-
include Math
|
4
|
-
class << self
|
5
|
-
def all
|
6
|
-
@all ||= defaults
|
7
|
-
end
|
8
|
-
|
9
|
-
def defaults
|
10
|
-
[ ["cel", ->(x){ x - 273.15}, ->(x){ x + 273.15} ],
|
11
|
-
["degf", ->(x){9.0 * x / 5.0 - 459.67},->(x){ 5.0/9 * (x + 459.67)}],
|
12
|
-
["hpX", ->(x){ -log10(x) }, ->(x){ 10 ** -x } ],
|
13
|
-
["hpC", ->(x){ -log(x) / log(100) }, ->(x){ 100 ** -x } ],
|
14
|
-
["tan100",->(x){ 100 * tan(x) }, ->(x){ atan(x / 100) } ],
|
15
|
-
["ph", ->(x){ -log10(x) }, ->(x){ 10 ** -x } ],
|
16
|
-
["ld", ->(x){ log2(x) }, ->(x){ 2 ** -x } ],
|
17
|
-
["ln", ->(x){ log(x) }, ->(x){ Math::E ** x } ],
|
18
|
-
["lg", ->(x){ log10(x) }, ->(x){ 10 ** x } ],
|
19
|
-
["2lg", ->(x){ 2 * log10(x) }, ->(x){ (10 ** x) / 2 } ]
|
20
|
-
].map {|args| new(*args) }
|
21
|
-
end
|
22
|
-
|
23
|
-
def add(*args)
|
24
|
-
new_instance = self.new(*args)
|
25
|
-
all << new_instance
|
26
|
-
new_instance
|
27
|
-
end
|
28
|
-
|
29
|
-
def find(name)
|
30
|
-
all.find{|fp| fp.name == name}
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
attr_reader :name, :direct, :inverse
|
35
|
-
|
36
|
-
def initialize(name, direct, inverse)
|
37
|
-
@name = name
|
38
|
-
@direct = direct
|
39
|
-
@inverse = inverse
|
40
|
-
end
|
41
|
-
|
42
|
-
def functional(x, direction=1)
|
43
|
-
if direction == 1
|
44
|
-
direct.call(x)
|
45
|
-
elsif direction == -1
|
46
|
-
inverse.call(x)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
51
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
describe Unitwise::Function do
|
4
|
-
subject { Unitwise::Function }
|
5
|
-
|
6
|
-
it '::all must be an Array' do
|
7
|
-
subject.all.must_be_instance_of Array
|
8
|
-
end
|
9
|
-
|
10
|
-
it '::add must add to :all' do
|
11
|
-
defined = subject.add('foo(2 3N)', ->{ 4 }, ->{ 6 })
|
12
|
-
subject.all.must_include(defined)
|
13
|
-
end
|
14
|
-
|
15
|
-
it "::find must find by name" do
|
16
|
-
defined = subject.add("foo", -> { 3 }, -> { 7 })
|
17
|
-
subject.find("foo").must_equal(defined)
|
18
|
-
end
|
19
|
-
|
20
|
-
let (:fp) { Unitwise::Function.new('foo(1 4J)', ->(x){ x + 1}, ->(x){ x - 1 }) }
|
21
|
-
|
22
|
-
it 'must have a #name' do
|
23
|
-
fp.name.must_equal('foo(1 4J)')
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'must have a direct lambda' do
|
27
|
-
fp.direct.call(1).must_equal 2
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'must have a inverse lambda' do
|
31
|
-
fp.inverse.call(1).must_equal 0
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'must have a direct scalar' do
|
35
|
-
fp.functional(1, 1).must_equal 2
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'must have an inverse scalar' do
|
39
|
-
fp.functional(1, -1).must_equal 0
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|