vector_space 0.1.2

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.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Tom Stuart
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,110 @@
1
+ VectorSpace
2
+ ===========
3
+
4
+ A Ruby library for treating multidimensional values as elements of a vector space.
5
+
6
+ Examples
7
+ --------
8
+
9
+ A simple vector space:
10
+
11
+ class Round < VectorSpace::SimpleVector
12
+ has_dimension :hoegaarden
13
+ has_dimension :franziskaner
14
+ has_dimension :fruli
15
+ has_dimension :water
16
+ end
17
+
18
+ >> Round.new
19
+ => 0 and 0 and 0 and 0
20
+
21
+ >> Round.new :hoegaarden => 5, :fruli => 3
22
+ => 5 and 0 and 3 and 0
23
+
24
+ With custom descriptions:
25
+
26
+ class Round < VectorSpace::SimpleVector
27
+ has_dimension :hoegaarden, :describe => lambda { |n| "#{n} Hoegaardens" unless n.zero? }
28
+ has_dimension :franziskaner, :describe => lambda { |n| "#{n} Franziskaners" unless n.zero? }
29
+ has_dimension :fruli, :describe => lambda { |n| "#{n} Frülis" unless n.zero? }
30
+ has_dimension :water, :describe => lambda { |n| "#{n} waters" unless n.zero? }
31
+ has_zero_description 'no drinks'
32
+ end
33
+
34
+ >> Round.new
35
+ => no drinks
36
+
37
+ >> Round.new :hoegaarden => 5, :fruli => 3
38
+ => 5 Hoegaardens and 3 Frülis
39
+
40
+ >> Round.new(:hoegaarden => 2, :franziskaner => 3) + Round.new(:water => 2, :fruli => 2, :hoegaarden => 1)
41
+ => 3 Hoegaardens and 3 Franziskaners and 2 Frülis and 2 waters
42
+
43
+ >> (Round.new(:hoegaarden => 2, :franziskaner => 3) * 2) - Round.new(:hoegaarden => 1)
44
+ => 3 Hoegaardens and 6 Franziskaners
45
+
46
+ An indexed family of vector spaces with custom descriptions:
47
+
48
+ class Cocktail < VectorSpace::SimpleIndexedVector
49
+ indexed_by :units
50
+ has_dimension :gin, :describe => lambda { |units, n| "#{n}#{units} gin" unless n.zero? }
51
+ has_dimension :vermouth, :describe => lambda { |units, n| "#{n}#{units} vermouth" unless n.zero? }
52
+ has_dimension :whisky, :describe => lambda { |units, n| "#{n}#{units} whisky" unless n.zero? }
53
+ has_dimension :vodka, :describe => lambda { |units, n| "#{n}#{units} vodka" unless n.zero? }
54
+ has_dimension :kahlua, :describe => lambda { |units, n| "#{n}#{units} Kahlúa" unless n.zero? }
55
+ has_dimension :cream, :describe => lambda { |units, n| "#{n}#{units} cream" unless n.zero? }
56
+ end
57
+
58
+ >> martini = Cocktail.new :units => :cl, :gin => 5.5, :vermouth => 1.5
59
+ => 5.5cl gin and 1.5cl vermouth
60
+
61
+ >> manhattan = Cocktail.new :units => :cl, :whisky => 5, :vermouth => 2
62
+ => 2cl vermouth and 5cl whisky
63
+
64
+ >> white_russian = Cocktail.new :units => :oz, :vodka => 2, :kahlua => 1, :cream => 1.5
65
+ => 2oz vodka and 1oz Kahlúa and 1.5oz cream
66
+
67
+ >> martini * 2
68
+ => 11cl gin and 3cl vermouth
69
+
70
+ >> martini + manhattan
71
+ => 5.5cl gin and 3.5cl vermouth and 5cl whisky
72
+
73
+ >> martini + white_russian
74
+ ArgumentError: can't add 5.5cl gin and 1.5cl vermouth to 2oz vodka and 1oz Kahlúa and 1.5oz cream
75
+
76
+ A real (one-dimensional) example:
77
+
78
+ class Money < VectorSpace::SimpleIndexedVector
79
+ indexed_by :currency, :default => Currency::GBP
80
+ has_dimension :cents,
81
+ :coerce => lambda { |n| n.round }, # or just :coerce => :round if you have Symbol#to_proc
82
+ :describe => lambda { |currency, cents| "#{currency.symbol}#{cents / 100}.#{sprintf('%02d', cents % 100)}" }
83
+ end
84
+
85
+ >> a = Money.new(:currency => Currency::GBP, :cents => 9900)
86
+ => £99.00
87
+
88
+ >> b = Money.new(:currency => Currency::GBP, :cents => 1500)
89
+ => £15.00
90
+
91
+ >> a < b
92
+ => false
93
+
94
+ >> a > b
95
+ => true
96
+
97
+ >> a + b
98
+ => £114.00
99
+
100
+ >> c = Money.new(:currency => Currency::USD, :cents => 99)
101
+ => $0.99
102
+
103
+ >> a < c
104
+ => false
105
+
106
+ >> c < a
107
+ => false
108
+
109
+ >> a + c
110
+ ArgumentError: can't add $0.99 to £99.00
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ * write documentation
2
+ * release extracted specs to GitHub
3
+ * support "dynamic dimensions" (arbitrary numbers of dimensions, created when used)
@@ -0,0 +1,13 @@
1
+ require 'forwardable'
2
+
3
+ require 'vector_space/component_reflection'
4
+ require 'vector_space/dimension_reflection'
5
+
6
+ require 'vector_space/partial_order'
7
+ require 'vector_space/arithmetic'
8
+ require 'vector_space/vector_space'
9
+ require 'vector_space/simple_vector'
10
+
11
+ require 'vector_space/index_reflection'
12
+ require 'vector_space/family'
13
+ require 'vector_space/simple_indexed_vector'
@@ -0,0 +1,33 @@
1
+ module VectorSpace
2
+ # These operations depend only upon addition and multiplication of the underlying values, so work in a vector space
3
+ module Arithmetic
4
+ # Vector addition
5
+ def +(vector)
6
+ if compatible_with?(vector)
7
+ operate_on_values(vector) { |a, b| a + b }
8
+ else
9
+ raise ArgumentError, "can't add #{vector.inspect} to #{self.inspect}"
10
+ end
11
+ end
12
+
13
+ # Scalar multiplication
14
+ def *(n)
15
+ operate_on_values { |value| value * n }
16
+ end
17
+
18
+ # Additive inverse
19
+ def -@
20
+ operate_on_values { |value| -value }
21
+ end
22
+
23
+ # Vector subtraction (by addition)
24
+ def -(vector)
25
+ self + (-vector)
26
+ end
27
+
28
+ # Scalar division (by multiplication)
29
+ def /(n)
30
+ self * (1.0 / n)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ module VectorSpace
2
+ class ComponentReflection
3
+ def initialize(component, options = {})
4
+ @component, @options = component, options
5
+ end
6
+
7
+ def to_s
8
+ "#{component} component (#{default.class})"
9
+ end
10
+
11
+ attr_reader :component, :options
12
+
13
+ def getter
14
+ options[:getter] || component.to_sym
15
+ end
16
+
17
+ def value(value)
18
+ value ||= default
19
+
20
+ if options[:coerce]
21
+ options[:coerce].to_proc.call(value)
22
+ else
23
+ value
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ module VectorSpace
2
+ class DimensionReflection < ComponentReflection
3
+ def zero
4
+ @options[:zero] || 0
5
+ end
6
+
7
+ def default
8
+ zero
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ module VectorSpace
2
+ module Family
3
+ def self.included(base)
4
+ base.class_eval do
5
+ include VectorSpace
6
+ extend ClassMethods
7
+ include InstanceMethods
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ attr_reader :index
13
+
14
+ def indexed_by(index, options = {})
15
+ @index = index
16
+ (@component_reflections ||= {})[index] = IndexReflection.new(index, options)
17
+ end
18
+
19
+ def components
20
+ [index] + super
21
+ end
22
+
23
+ def zero(attributes = {})
24
+ new Hash[*([index, attributes[index] || reflect_on_component(index).default] + dimensions.map { |dimension| [dimension, reflect_on_component(dimension).zero] }).flatten]
25
+ end
26
+ end
27
+
28
+ module InstanceMethods
29
+ def self.included(base)
30
+ base.class_eval do
31
+ def_delegator :'self.class', :index
32
+ end
33
+ end
34
+
35
+ def compatible_with?(other)
36
+ super && project(index) == other.project(index)
37
+ end
38
+
39
+ private
40
+ def operate_on_values(*vectors, &block)
41
+ self.class.new Hash[*(map_components(index, *vectors) { |*values| values.inject { |a, b| a if a == b } } + map_components(dimensions, *vectors, &block)).flatten]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,7 @@
1
+ module VectorSpace
2
+ class IndexReflection < ComponentReflection
3
+ def default
4
+ @options[:default]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module VectorSpace
2
+ module PartialOrder
3
+ # Ruby's Comparable isn't well-behaved for partial orders: its operations raise ArgumentError if #<=> returns nil.
4
+ # This module provides operations which simply return false when two values are incomparable.
5
+
6
+ [:<, :<=, :==, :>=, :>].each do |operation|
7
+ define_method operation do |other|
8
+ ((result = self <=> other) && result.send(operation, 0)) == true
9
+ end
10
+ end
11
+
12
+ def comparable_with?(other)
13
+ !(self <=> other).nil?
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ module VectorSpace
2
+ class SimpleIndexedVector < SimpleVector
3
+ include Family
4
+
5
+ def initialize(attributes = {})
6
+ if index_value = attributes[index] || reflect_on_component(index).default
7
+ super attributes.merge(index => index_value)
8
+ else
9
+ raise ArgumentError, "must provide value for #{index} index or configure a default"
10
+ end
11
+ end
12
+
13
+ private
14
+ def describe_value(dimension)
15
+ if describe = reflect_on_component(dimension).options[:describe]
16
+ describe.to_proc.call(project(index), project(dimension))
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ class << self
23
+ def indexed_by(index, attributes = {})
24
+ define_method (reflection = super(index, attributes)).getter do
25
+ reflection.value(@attributes[index])
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,70 @@
1
+ module VectorSpace
2
+ class SimpleVector
3
+ include Enumerable
4
+ include VectorSpace
5
+ def_delegators :'self.class', :description_prefix, :description_suffix, :description_separator, :zero_description
6
+
7
+ def initialize(attributes = {})
8
+ @attributes = attributes
9
+ end
10
+
11
+ def to_s
12
+ if zero? && zero_description
13
+ zero_description
14
+ else
15
+ [
16
+ description_prefix,
17
+ dimensions.map { |dimension| describe_value(dimension) }.compact.join(description_separator),
18
+ description_suffix
19
+ ].join
20
+ end
21
+ end
22
+
23
+ def [](component)
24
+ project(component)
25
+ end
26
+
27
+ def each
28
+ dimensions.each do |dimension| yield [dimension, project(dimension)] end
29
+ end
30
+
31
+ private
32
+ def describe_value(dimension)
33
+ if describe = reflect_on_component(dimension).options[:describe]
34
+ describe.to_proc.call(project(dimension))
35
+ else
36
+ project(dimension)
37
+ end
38
+ end
39
+
40
+ class << self
41
+ attr_reader :description_prefix, :description_suffix, :zero_description
42
+
43
+ def has_dimension(dimension, attributes = {})
44
+ define_method (reflection = super(dimension, attributes)).getter do
45
+ reflection.value(@attributes[dimension])
46
+ end
47
+ end
48
+
49
+ def has_description_prefix(description_prefix)
50
+ @description_prefix = description_prefix
51
+ end
52
+
53
+ def has_description_suffix(description_suffix)
54
+ @description_suffix = description_suffix
55
+ end
56
+
57
+ def has_description_separator(description_separator)
58
+ @description_separator = description_separator
59
+ end
60
+
61
+ def has_zero_description(zero_description)
62
+ @zero_description = zero_description
63
+ end
64
+
65
+ def description_separator
66
+ @description_separator || ' and '
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,101 @@
1
+ module VectorSpace
2
+ def self.included(base)
3
+ base.class_eval do
4
+ extend ClassMethods
5
+ include InstanceMethods
6
+ end
7
+ end
8
+
9
+ module ClassMethods
10
+ attr_reader :dimensions
11
+
12
+ def components
13
+ dimensions
14
+ end
15
+
16
+ def reflect_on_component(component)
17
+ @component_reflections[component]
18
+ end
19
+
20
+ def zero
21
+ new Hash[*dimensions.map { |dimension| [dimension, reflect_on_component(dimension).zero] }.flatten]
22
+ end
23
+
24
+ def order
25
+ @order || :product
26
+ end
27
+
28
+ private
29
+ def has_dimension(dimension, options = {})
30
+ (@dimensions ||= []) << dimension
31
+ (@component_reflections ||= {})[dimension] = DimensionReflection.new(dimension, options)
32
+ end
33
+
34
+ def has_order(order)
35
+ @order = order
36
+ end
37
+ end
38
+
39
+ module InstanceMethods
40
+ def self.included(base)
41
+ base.class_eval do
42
+ extend Forwardable
43
+ def_delegators :'self.class', :components, :dimensions, :order, :reflect_on_component
44
+
45
+ include PartialOrder
46
+ include Arithmetic
47
+ end
48
+ end
49
+
50
+ def project(component)
51
+ reflection = reflect_on_component(component)
52
+ reflection.value(send(reflection.getter))
53
+ end
54
+
55
+ def map_components(components, *vectors)
56
+ Array(components).map { |component| [component, yield(*([self] + vectors).map { |vector| vector.project(component) })] }
57
+ end
58
+
59
+ def map_values(components, *vectors, &block)
60
+ map_components(components, *vectors, &block).map { |component, value| value }
61
+ end
62
+
63
+ def zero?
64
+ map_values(dimensions) { |value| value.zero? }.all?
65
+ end
66
+
67
+ # Decides whether the receiver is compatible with some other object for general operations (e.g. arithmetic and comparison).
68
+ def compatible_with?(other)
69
+ other.class == self.class
70
+ end
71
+
72
+ # Compares the receiver against another object, returning -1, 0, +1 or nil depending on whether the receiver is less than,
73
+ # equal to, greater than, or incomparable with the other object.
74
+ # This works just like the #<=> expected by Comparable, but also returns nil iff the two objects are incomparable.
75
+ def <=>(other)
76
+ if compatible_with?(other)
77
+ comparisons = map_values(dimensions, other) { |a, b| a <=> b }
78
+
79
+ case order
80
+ when :product
81
+ comparisons.inject { |a, b| a + b if a && b && a * b >= 0 }
82
+ when :lexicographic
83
+ comparisons.inject { |a, b| a == 0 ? b : a }
84
+ end
85
+ end
86
+ end
87
+
88
+ def eql?(other)
89
+ map_values(dimensions, other) { |a, b| a.eql?(b) }.all?
90
+ end
91
+
92
+ def hash
93
+ map_values(dimensions) { |value| value }.hash
94
+ end
95
+
96
+ private
97
+ def operate_on_values(*vectors, &block)
98
+ self.class.new Hash[*map_components(dimensions, *vectors, &block).flatten]
99
+ end
100
+ end
101
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vector_space
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Tom Stuart
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-23 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: tom@experthuman.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/vector_space/arithmetic.rb
26
+ - lib/vector_space/component_reflection.rb
27
+ - lib/vector_space/dimension_reflection.rb
28
+ - lib/vector_space/family.rb
29
+ - lib/vector_space/index_reflection.rb
30
+ - lib/vector_space/partial_order.rb
31
+ - lib/vector_space/simple_indexed_vector.rb
32
+ - lib/vector_space/simple_vector.rb
33
+ - lib/vector_space/vector_space.rb
34
+ - lib/vector_space.rb
35
+ - LICENSE
36
+ - README.markdown
37
+ - TODO
38
+ has_rdoc: true
39
+ homepage: http://github.com/tomstuart/vector_space
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.5
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: A Ruby library for treating multidimensional values as elements of a vector space
66
+ test_files: []
67
+