named_vector 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.
- checksums.yaml +15 -0
- data/lib/named_vector.rb +162 -0
- data/specs/initialization.rb +56 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NGQwYzQ2NzYwOTQyNmM2NzUzODFlMWNhNjllODY2NDliYTAxNjZlZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDk0MzM2ODNkNTAyOWY2YjNjZTg4MzZhNmYwODRjNTVmYjFmMTEyNQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZTIxNTE0ODMwYWJiZGIxZjViNzNhMTFkNjI4MTRiM2YzYTNjOTJkM2VlMzI2
|
10
|
+
ZDYyMjFhOWIzNGEwZTE3NjQzNzM3NDAyODJlYzY2YWJkYTUzOTk5MzJhMzcy
|
11
|
+
Y2Q0NjQxM2JkN2RkOWE3OTIyYTNhZTczM2M0MWM3MmI1ZTIyYmQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YjFiNmVhNmI0NTRkNjhmYTdiY2YzMTVjMzJkY2I1OWMxZmJjYTQ3NzkyMTZm
|
14
|
+
OGFlNGFmMmY1YmFhNmM2ZDhmYjg2NDQyOWFlMjc0MTZiMzJlNDE0YmMwZjEx
|
15
|
+
YWYwNmQyYjlmNzY2ZDkzOTMyMWQ2OWRlNTFkMzA0MjNkMzVmNzk=
|
data/lib/named_vector.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# @author Jonathan Raiman
|
3
|
+
|
4
|
+
|
5
|
+
# Vectors with named dimensions. Infinite dimensional objects with simple vector calculus operations defined.
|
6
|
+
class NamedVector
|
7
|
+
attr_reader :keys
|
8
|
+
alias :dimensions :keys
|
9
|
+
|
10
|
+
# creates a new named vector
|
11
|
+
# @param dims [Hash<String,Fixnum>, Hash<Symbol,Fixnum>, Array<String>, Array<Symbol>] the dimensions to initialize the vectors with, either as a hash of assignments or simply the names of the dimensions.
|
12
|
+
def initialize(*dims)
|
13
|
+
@vector = Hash.new
|
14
|
+
@keys = Set.new
|
15
|
+
if dims && dims.first
|
16
|
+
case dims.first
|
17
|
+
when Hash
|
18
|
+
hash = dims.first
|
19
|
+
hash.keys.each {|d| modify_dimension(d, hash[d])}
|
20
|
+
when String, Symbol, Fixnum
|
21
|
+
@keys = Set.new(dims.map {|i| i.to_s})
|
22
|
+
dims.each {|d| new_dimension(d)}
|
23
|
+
end
|
24
|
+
else
|
25
|
+
@keys = []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Perform a dot product with another vector
|
30
|
+
# @param value [Hash, Fixnum, Float, NamedVector] the vector or scalar to multiply this vector with. Scalar multiplication is destructive.
|
31
|
+
def *(value)
|
32
|
+
case value
|
33
|
+
when Fixnum, Float #scalar multiplication product
|
34
|
+
each_dimension {|i| @vector[i] = self[i]*value}
|
35
|
+
when NamedVector #dot product
|
36
|
+
NamedVector.dot_product(self, value)
|
37
|
+
when Hash #dot product
|
38
|
+
NamedVector.add_dimension_iterator(value)
|
39
|
+
NamedVector.dot_product(self,value)
|
40
|
+
else
|
41
|
+
raise TypeError, "#{value.class} cannot be coerced to NamedVector."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# adds a dimension iterator (iterate through the keys) to a Hash or NamedVector
|
46
|
+
# @param obj [Hash, NamedVector] the object to add the method to.
|
47
|
+
def self.add_dimension_iterator(obj)
|
48
|
+
obj.define_singleton_method(:each_dimension) do |&block|
|
49
|
+
self.keys.each do |key|
|
50
|
+
block.call(key)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Perform a dot product with another vector
|
56
|
+
# @param value [Hash, NamedVector] the vector to multiply this vector with.
|
57
|
+
def dot_product(value)
|
58
|
+
NamedVector.dot_product(self, value)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Perform a dot product with another vector
|
62
|
+
# @param a [Hash, NamedVector] the vector or scalar to multiply this vector with.
|
63
|
+
# @param b [Hash, NamedVector] the vector or scalar to multiply this vector with.
|
64
|
+
def self.dot_product(a, b)
|
65
|
+
sum = 0.0
|
66
|
+
if a.keys.length < b.keys.length
|
67
|
+
a.each_dimension {|i| sum+=a[i]*b[i]}
|
68
|
+
else
|
69
|
+
b.each_dimension {|i| sum+=b[i]*a[i]}
|
70
|
+
end
|
71
|
+
sum
|
72
|
+
end
|
73
|
+
|
74
|
+
# iterates through each dimension of a vector
|
75
|
+
def each_dimension
|
76
|
+
@keys.each do |key|
|
77
|
+
yield(key)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# fetches the value at the dimension specified
|
82
|
+
# @param key [Symbol, String] the dimension requested
|
83
|
+
def [](key)
|
84
|
+
@vector.fetch(key.to_s,0.0)
|
85
|
+
end
|
86
|
+
|
87
|
+
def modify_dimension(key,value)
|
88
|
+
new_dimension(key.to_s)
|
89
|
+
@vector[key.to_s] = value
|
90
|
+
end
|
91
|
+
|
92
|
+
# assigns a value at the dimension specified
|
93
|
+
# @param key [Symbol, String] the dimension requested
|
94
|
+
# @param value [Fixnum, Float] the assignment for this dimension
|
95
|
+
def []=(key,value)
|
96
|
+
modify_dimension(key,value)
|
97
|
+
end
|
98
|
+
|
99
|
+
def method_missing(mid, *args) # :nodoc:
|
100
|
+
mname = mid.id2name
|
101
|
+
len = args.length
|
102
|
+
if mname.chomp!('=')
|
103
|
+
if len != 1
|
104
|
+
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
|
105
|
+
end
|
106
|
+
modify_dimension(mname, args[0])
|
107
|
+
elsif len == 0
|
108
|
+
@vector.fetch(mname,0.0)
|
109
|
+
else
|
110
|
+
raise NoMethodError, "undefined method `#{mid}' for #{self}", caller(1)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def new_dimension(name)
|
115
|
+
sym_name = name.to_sym
|
116
|
+
unless respond_to?(sym_name)
|
117
|
+
@keys << name
|
118
|
+
define_singleton_method(sym_name) { @vector.fetch(name,0.0) }
|
119
|
+
define_singleton_method("#{sym_name}=") { |x| @vector[name] = x }
|
120
|
+
end
|
121
|
+
name
|
122
|
+
end
|
123
|
+
protected :new_dimension
|
124
|
+
|
125
|
+
# the squared Euclidean norm of a vector
|
126
|
+
def squared_norm
|
127
|
+
norm = 0.0
|
128
|
+
each_dimension {|i| norm+=self[i]**2}
|
129
|
+
norm
|
130
|
+
end
|
131
|
+
|
132
|
+
# the Euclidean norm of a vector
|
133
|
+
def norm
|
134
|
+
Math.sqrt(squared_norm)
|
135
|
+
end
|
136
|
+
|
137
|
+
# normalizes the vector destructively.
|
138
|
+
def normalize
|
139
|
+
current_norm = norm
|
140
|
+
if current_norm > 0 then self*(1.0/current_norm) end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Mongo conversion
|
144
|
+
def to_mongo
|
145
|
+
{"keys"=> @keys, "dimensions"=> @keys.map {|i| self[i]}}
|
146
|
+
end
|
147
|
+
|
148
|
+
# Mongo retrieval
|
149
|
+
# @param doc [BSON::OrderedHash] the mongo document to retrieve the vector from.
|
150
|
+
def self.from_mongo(doc)
|
151
|
+
hash = {}
|
152
|
+
doc["keys"].each_with_index do |k, index|
|
153
|
+
hash[k] = doc["dimensions"][index]
|
154
|
+
end
|
155
|
+
self.new hash
|
156
|
+
end
|
157
|
+
|
158
|
+
private :new_dimension
|
159
|
+
private :method_missing
|
160
|
+
private :modify_dimension
|
161
|
+
|
162
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require './lib/named_vector.rb'
|
2
|
+
describe 'Initialization' do
|
3
|
+
it 'should have all the dimensions given in the initilization' do
|
4
|
+
dimensions = ["x","y"]
|
5
|
+
vector = NamedVector.new(*dimensions)
|
6
|
+
vector.dimensions.should include(*dimensions)
|
7
|
+
dimensions.each do |dim|
|
8
|
+
vector.should respond_to(dim.to_sym).with(0).arguments
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should initialize from a hash as well' do
|
13
|
+
hash_vector = {"x" => 1.0, "y" => 2.0, "z" => 0.0}
|
14
|
+
vector = NamedVector.new(hash_vector)
|
15
|
+
hash_vector.keys.should eql vector.keys.to_a
|
16
|
+
hash_vector.keys.each do |key|
|
17
|
+
vector.should respond_to(key.to_sym).with(0).arguments
|
18
|
+
vector[key].should eql hash_vector[key]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should allow dot products between vectors' do
|
23
|
+
dimensions = ["x","y"]
|
24
|
+
vectorA = NamedVector.new(:x => 1, :y => 1)
|
25
|
+
vectorB = NamedVector.new(:x => 1)
|
26
|
+
(vectorA*vectorB).should eql 1.0
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should allow dot products between mismatched vectors' do
|
30
|
+
vectorA = NamedVector.new( "x" => 0, "z" => 2)
|
31
|
+
vectorB = NamedVector.new("b" => 10, "z" => 1)
|
32
|
+
vectorA.x = 0
|
33
|
+
vectorB.z = 1
|
34
|
+
vectorA.z = 2
|
35
|
+
vectorB.a = 10
|
36
|
+
(vectorA*vectorB).should eql 2.0
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should allow dot products between vectors and hashes' do
|
40
|
+
vectorA = NamedVector.new("x","y","z")
|
41
|
+
vectorB = {:x => 1, :y => 2, :z => 3}
|
42
|
+
vectorA.x = 1
|
43
|
+
vectorA.y = 1
|
44
|
+
vectorA.z = 1
|
45
|
+
(vectorA*vectorB).should eql 6.0
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should normalize vectors' do
|
49
|
+
vector = NamedVector.new("x","y","z")
|
50
|
+
vector.x = 1
|
51
|
+
vector.y = 1
|
52
|
+
vector.z = 1
|
53
|
+
vector.normalize
|
54
|
+
vector.norm.should be_within(0.01).of 1.0
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: named_vector
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Raiman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-28 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Vectors with named dimensions.
|
14
|
+
email: jraiman@mit.edu
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/named_vector.rb
|
20
|
+
- specs/initialization.rb
|
21
|
+
homepage: http://github.org/JonathanRaiman/named_vector
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata: {}
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 2.1.10
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: Vectors with named dimensions.
|
45
|
+
test_files:
|
46
|
+
- specs/initialization.rb
|
47
|
+
has_rdoc: yard
|