unitfy 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +18 -0
  4. data/Gemfile.lock +89 -0
  5. data/Guardfile +15 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README +0 -0
  8. data/Rakefile +3 -0
  9. data/lib/unity.rb +20 -0
  10. data/lib/unity/arithmetic.rb +64 -0
  11. data/lib/unity/comparison.rb +33 -0
  12. data/lib/unity/conversion.rb +46 -0
  13. data/lib/unity/dimension.rb +37 -0
  14. data/lib/unity/dimension/integer.rb +34 -0
  15. data/lib/unity/dimension/vector.rb +43 -0
  16. data/lib/unity/fraction.rb +118 -0
  17. data/lib/unity/lookup.rb +102 -0
  18. data/lib/unity/lookup/definitions.rb +50 -0
  19. data/lib/unity/lookup/derived_unit.rb +24 -0
  20. data/lib/unity/lookup/property.rb +21 -0
  21. data/lib/unity/lookup/simple_unit.rb +26 -0
  22. data/lib/unity/quantity.rb +20 -0
  23. data/lib/unity/version.rb +3 -0
  24. data/spec/arithmetic_spec.rb +6 -0
  25. data/spec/comparison_spec.rb +6 -0
  26. data/spec/conversion_spec.rb +8 -0
  27. data/spec/dimension/integer_spec.rb +7 -0
  28. data/spec/dimension/vector_spec.rb +7 -0
  29. data/spec/dimension_spec.rb +7 -0
  30. data/spec/fabricators/unit_fabricator.rb +14 -0
  31. data/spec/fraction_spec.rb +7 -0
  32. data/spec/lookup/derived_unit_spec.rb +44 -0
  33. data/spec/lookup/property_spec.rb +18 -0
  34. data/spec/lookup/simple_unit_spec.rb +41 -0
  35. data/spec/lookup_spec.rb +135 -0
  36. data/spec/quantity_spec.rb +16 -0
  37. data/spec/spec_helper.rb +28 -0
  38. data/spec/support/load_debugger.rb +7 -0
  39. data/spec/support/shared_examples/units/arithmetic.rb +127 -0
  40. data/spec/support/shared_examples/units/comparison.rb +69 -0
  41. data/spec/support/shared_examples/units/conversion.rb +49 -0
  42. data/spec/support/shared_examples/units/dimension/integer.rb +41 -0
  43. data/spec/support/shared_examples/units/dimension/vector.rb +10 -0
  44. data/spec/support/shared_examples/units/fractions.rb +131 -0
  45. data/unitfy.gemspec +24 -0
  46. metadata +133 -0
@@ -0,0 +1,118 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Unity
3
+ class InvalidFormat < RuntimeError
4
+ end
5
+
6
+ module Fraction
7
+ extend ::ActiveSupport::Concern
8
+
9
+ included do
10
+
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ end
16
+
17
+ def initialize *args
18
+ self.value ||= 0.0
19
+ self.numerator ||= []
20
+ self.denominator ||= []
21
+ end
22
+
23
+
24
+ def expression=string
25
+ unless string.blank?
26
+ num_denom = string.split('/')
27
+ raise(InvalidFormat, string) if num_denom.length > 2
28
+ self.numerator = num_denom[0].split('*').map(&:strip)
29
+ self.denominator = (num_denom[1].nil? ? [] : num_denom[1].split('*')).map(&:strip)
30
+ self.value = extract_value!(:numerator) / extract_value!(:denominator)
31
+ reduce
32
+ else
33
+ self.value = 0.0
34
+ self.numerator = []
35
+ self.denominator = []
36
+ end
37
+ end
38
+
39
+ def expression
40
+ numertor_string = ([value] + (numerator || [])).join('*')
41
+ denominator.empty? ? numertor_string : "#{numertor_string}/#{denominator.join('*')}"
42
+ end
43
+
44
+ def unit
45
+ numertor_string = (numerator.blank? && !denominator.blank?) ? '1.0' : (numerator || []).join('*')
46
+ denominator.blank? ? numertor_string : "#{numertor_string}/#{denominator.join('*')}"
47
+ end
48
+
49
+ def expanded_numerator
50
+ expand_unit_array numerator
51
+ end
52
+
53
+ def expanded_denominator
54
+ expand_unit_array denominator
55
+ end
56
+
57
+ def inverse
58
+ new_value = value == 0.0 ? 0.0 : 1.0 / value
59
+ self.class.new :value => new_value, :numerator => denominator, :denominator => numerator
60
+ end
61
+
62
+ def reduce
63
+ expand
64
+
65
+ self.denominator.delete_if do |unit|
66
+ if nindex = self.numerator.index(unit)
67
+ self.numerator.delete_at(nindex)
68
+ end
69
+ end
70
+
71
+ self.numerator = reduce_unit_array(self.numerator)
72
+ self.denominator = reduce_unit_array(self.denominator)
73
+
74
+ end
75
+
76
+ private
77
+
78
+ def expand
79
+ self.numerator = expanded_numerator
80
+ self.denominator = expanded_denominator
81
+ end
82
+
83
+ def seperate! array
84
+ [].tap{|rejected| array.delete_if { |v| yield(v) && rejected << v }}
85
+ end
86
+
87
+ def extract_value! method
88
+ number_regex = /\A[-+]?\d*\.?\d+([eE][-+]?\d+)?\Z/
89
+ seperate!(self.send(method)){|x| number_regex =~ x }.map(&:to_f).
90
+ inject(1.0){|product, number| product*number }
91
+ end
92
+
93
+ def reduce_unit_array array
94
+ old_array = array.clone
95
+ [].tap do |new_array|
96
+ while !old_array.empty?
97
+ unit = old_array[0]
98
+ count = old_array.count(unit)
99
+ old_array.delete(unit)
100
+ count = (count == 1) ? '' : "^#{count}"
101
+ new_array.push("#{unit}#{count}")
102
+ end
103
+ end
104
+ end
105
+
106
+ def expand_unit_array array
107
+ array.collect do |unit|
108
+ if /\A([^\^\s]+)(\^(\d+))?\Z/ =~ unit
109
+ unit = Regexp.last_match(1)
110
+ power = Regexp.last_match(3).blank? ? 1 : Regexp.last_match(3).to_i
111
+ Array.new(power, unit)
112
+ else
113
+ raise InvalidFormat, "Invalid format for Unit '#{unit}'"
114
+ end
115
+ end.flatten.compact
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,102 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'active_model'
3
+ require 'unity/lookup/simple_unit'
4
+ require 'unity/lookup/derived_unit'
5
+ require 'unity/lookup/property'
6
+ require 'securerandom'
7
+
8
+ module Unity
9
+ class Lookup
10
+
11
+ class Invalid < RuntimeError
12
+ end
13
+
14
+ class Duplicate < RuntimeError
15
+ end
16
+
17
+ class Undefined < RuntimeError
18
+ end
19
+
20
+
21
+ class << self
22
+ def add object
23
+ raise Invalid, object.errors.inspect unless object.valid?
24
+ raise Duplicate, "The Unit #{object.name} already exists" unless @@unit_names[object.name.to_sym].nil?
25
+ key = store_object object
26
+ @@unit_names[object.name.to_sym] = key
27
+ @@unit_ints[object.dimension_int] ||= []
28
+ @@unit_ints[object.dimension_int] << key
29
+ end
30
+
31
+ def find! name
32
+ key = @@unit_names[name.to_sym].tap {|k| raise Undefined, "Unit #{name} is undefined" unless k }
33
+ get_object key
34
+ end
35
+
36
+ def compatible_units int_or_sym_or_string
37
+ int =
38
+ if int_or_sym_or_string.is_a? Integer
39
+ int_or_sym_or_string
40
+ else
41
+ tmp = find_property(int_or_sym_or_string)
42
+ tmp.nil? ? nil : tmp.dimension_int
43
+ end
44
+ (@@unit_ints[int] || []).map do |key|
45
+ get_object key
46
+ end
47
+ end
48
+
49
+ def find_property int_or_sym_or_string
50
+ key =
51
+ if int_or_sym_or_string.is_a? Integer
52
+ @@property_ints[int_or_sym_or_string]
53
+ else
54
+ @@property_names[int_or_sym_or_string.to_sym]
55
+ end.tap {|k| return nil unless k }
56
+ get_object key
57
+ end
58
+
59
+ def add_property object
60
+ raise Invalid, object.errors.inspect unless object.valid?
61
+ existing = @@property_ints[object.dimension_int]
62
+ raise Duplicate, "The property for dimension int #{object.dimension_int} is already defined as #{get_object(existing).name}" unless existing.nil?
63
+ key = store_object object
64
+ @@property_names[object.name.to_sym] = key
65
+ @@property_ints[object.dimension_int]= key
66
+ end
67
+
68
+ def property_names
69
+ @@property_names.keys.map(&:to_sym)
70
+ end
71
+
72
+ def clear!
73
+ @@object_cache = {}
74
+ @@unit_names = Hash.new
75
+ @@unit_ints = Hash.new
76
+ @@property_ints = Hash.new
77
+ @@property_names= Hash.new
78
+ end
79
+
80
+ def reload!
81
+ load "#{File.dirname(__FILE__)}/lookup/definitions.rb"
82
+ end
83
+
84
+ private
85
+
86
+ def store_object object
87
+ SecureRandom.uuid.tap do |key|
88
+ @@object_cache[key] = object
89
+ end
90
+ end
91
+
92
+ def get_object key
93
+ @@object_cache[key]
94
+ end
95
+
96
+ end
97
+
98
+ clear!
99
+ reload!
100
+
101
+ end
102
+ end
@@ -0,0 +1,50 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module Unity
4
+
5
+ Lookup.clear!
6
+
7
+ #SIMPLE UNITS
8
+
9
+ #TIME
10
+ Lookup.add SimpleUnit.new(:name => "s", :si_factor => 1, :dimension => :time)
11
+ Lookup.add SimpleUnit.new(:name => "h", :si_factor => 3600, :dimension => :time)
12
+
13
+ Lookup.add_property Property.new(:name => :time, :expression => 'h')
14
+
15
+ #LENGTH
16
+ Lookup.add SimpleUnit.new(:name => "m", :si_factor => 1, :dimension => :length)
17
+ Lookup.add SimpleUnit.new(:name => "km", :si_factor => 1000, :dimension => :length)
18
+
19
+ Lookup.add_property Property.new(:name => :distance, :expression => 'km')
20
+
21
+
22
+ #MASS
23
+ Lookup.add SimpleUnit.new(:name => "g", :si_factor => 1, :dimension => :mass)
24
+ Lookup.add SimpleUnit.new(:name => "kg", :si_factor => 1000, :dimension => :mass)
25
+ Lookup.add SimpleUnit.new(:name => "tonne", :si_factor => 1000000, :dimension => :mass)
26
+
27
+ Lookup.add_property Property.new(:name => :mass, :expression => 'tonne')
28
+
29
+
30
+ #DERIVED UNITS
31
+
32
+ #ENERGY
33
+ Lookup.add DerivedUnit.new(:name => "J", :expression => 'kg*m^2/s^2')
34
+ Lookup.add DerivedUnit.new(:name => "kWh", :expression => '3.6e6*J')
35
+
36
+ Lookup.add_property Property.new(:name => :energy, :expression => 'kWh')
37
+
38
+ #AREA
39
+
40
+ Lookup.add DerivedUnit.new(:name => "hectare", :expression => '10000*m^2')
41
+ Lookup.add DerivedUnit.new(:name => "acre", :expression => '4046.85642*m^2')
42
+
43
+ Lookup.add_property Property.new(:name => :area, :expression => 'hectare')
44
+
45
+ end
46
+
47
+
48
+
49
+
50
+
@@ -0,0 +1,24 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Unity
3
+ class DerivedUnit
4
+
5
+ include ActiveModel::Validations
6
+ include Dimension::Vector
7
+ include Conversion
8
+
9
+ attr_accessor :name, :numerator, :denominator, :value
10
+
11
+ alias :to_s :name
12
+ validates_presence_of :name
13
+ validates_format_of :name, :with => /\A[^\s*\/\^]+\Z/, :message => "cannot contain *, / or ^"
14
+
15
+ def initialize(attributes = {})
16
+ attributes.each do |name, value|
17
+ send("#{name}=", value)
18
+ end
19
+ super
20
+ end
21
+
22
+
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Unity
3
+ class Property
4
+
5
+ include ActiveModel::Validations
6
+ include Dimension::Vector
7
+ include Conversion
8
+
9
+ attr_accessor :name, :numerator, :denominator, :value
10
+
11
+ validates_presence_of :name
12
+
13
+ def initialize(attributes = {})
14
+ attributes.each do |name, value|
15
+ send("#{name}=", value)
16
+ end
17
+ super
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Unity
3
+ class SimpleUnit
4
+
5
+ include ActiveModel::Validations
6
+ include Dimension::Integer
7
+
8
+ attr_accessor :dimension, :name, :si_factor
9
+ alias :to_s :name
10
+
11
+ validates_inclusion_of :dimension, :in => Dimension::LIST
12
+ validates_presence_of :si_factor, :dimension, :name
13
+ validates_format_of :name, :with => /\A[^\s*\/\^]+\Z/, :message => "cannot contain *, / or ^"
14
+
15
+ def dimension_vector
16
+ @dimension_vector ||= Dimension::LIST.map{|d| d == dimension ? 1 : 0 }
17
+ end
18
+
19
+ def initialize(attributes = {})
20
+ attributes.each do |name, value|
21
+ send("#{name}=", value)
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Unity
3
+ class Quantity
4
+
5
+ include Dimension::Vector
6
+ include Conversion
7
+ include Arithmetic
8
+
9
+ def initialize(attributes = {})
10
+ attributes.each do |name, value|
11
+ send("#{name}=", value)
12
+ end
13
+ super
14
+ end
15
+
16
+ attr_accessor :name, :numerator, :denominator, :cached_expression, :value
17
+ alias :to_s :expression
18
+
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Unity
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Arithmetic do
5
+
6
+ end
@@ -0,0 +1,6 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Comparison do
5
+
6
+ end
@@ -0,0 +1,8 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Conversion do
5
+
6
+ end
7
+
8
+
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Dimension::Integer do
5
+
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Dimension::Vector do
5
+
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Dimension do
5
+
6
+
7
+ end
@@ -0,0 +1,14 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'unity/lookup/simple_unit'
3
+ require 'unity/lookup/derived_unit'
4
+
5
+ Fabricator(:simple_unit, :class_name => "Unity::SimpleUnit") do
6
+ si_factor 1000.0
7
+ name 'km'
8
+ dimension :length
9
+ end
10
+
11
+ Fabricator(:derived_unit, :class_name => "Unity::DerivedUnit") do
12
+ name 'kWh'
13
+ end
14
+
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Fraction do
5
+
6
+
7
+ end