unitfy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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