vector_number 0.6.1 → 0.7.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 +4 -4
- data/README.md +254 -85
- data/lib/vector_number/comparing.rb +16 -8
- data/lib/vector_number/converting.rb +37 -19
- data/lib/vector_number/enumerating.rb +237 -22
- data/lib/vector_number/mathing.rb +62 -22
- data/lib/vector_number/querying.rb +3 -1
- data/lib/vector_number/{math_converting.rb → rounding.rb} +12 -35
- data/lib/vector_number/similarity.rb +97 -0
- data/lib/vector_number/special_unit.rb +20 -8
- data/lib/vector_number/stringifying.rb +52 -18
- data/lib/vector_number/vectoring.rb +504 -0
- data/lib/vector_number/version.rb +1 -1
- data/lib/vector_number.rb +105 -106
- data/sig/manifest.yaml +4 -0
- data/sig/vector_number.rbs +143 -56
- metadata +14 -16
- data/lib/vector_number/numeric_refinements.rb +0 -97
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48be1b3c651d554736ea6ae2da95e31c934441e62c35e72a204d7dcda101e770
|
|
4
|
+
data.tar.gz: 964308ecfcac82ea1fe0503fb336171004e837035f39f13d8128ae81fdfae900
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb383677bb0908b8cc96b85bb25ee4a43c25767ce27d379ede252d3ed668ff26c7bba7d98831eb8bb1f5600adaa43bab91ebbc10c999a7daf79d3f304f9548e3
|
|
7
|
+
data.tar.gz: eac645b74326d103f270e8b9d6753c3821ca0ac430d8f129ff198e4f442ac17ae1235b916504988b5c557b2ecc3fdaf412473aad6a52f77fd98799c692dfa4d9
|
data/README.md
CHANGED
|
@@ -4,33 +4,96 @@
|
|
|
4
4
|
[](https://github.com/trinistr/vector_number/actions/workflows/CI.yaml)
|
|
5
5
|
|
|
6
6
|
> [!TIP]
|
|
7
|
-
> You may be viewing documentation for an older (or newer) version of the gem. Look at [Changelog](https://github.com/trinistr/
|
|
7
|
+
> You may be viewing documentation for an older (or newer) version of the gem. Look at [Changelog](https://github.com/trinistr/vector_number/blob/main/CHANGELOG.md) to see all versions, including unreleased changes.
|
|
8
8
|
|
|
9
9
|
***
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**Do full linear algebra on Ruby objects.** Yes, *any* objects.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
```ruby
|
|
14
|
+
# Create a vector where dimensions are whatever you want
|
|
15
|
+
v1 = VectorNumber[x: 3, y: 4] # 3 in :x direction, 4 in :y
|
|
16
|
+
v2 = VectorNumber["weight" => 2.5, :x => 1] # mix symbols and strings
|
|
17
|
+
v3 = VectorNumber[:y => 2, [1, 2, 3] => 5] # or any other objects
|
|
18
|
+
|
|
19
|
+
# Add them, scale them, find their lengths in a natural way
|
|
20
|
+
v1 + v2 # => (4⋅:x + 4⋅:y + 2.5⋅"weight")
|
|
21
|
+
v1 - v3 # => (3⋅:x + 2⋅:y - 5⋅[1, 2, 3])
|
|
22
|
+
v2 + "banana" # => (2.5⋅"weight" + 1⋅:x + 1⋅"banana")
|
|
23
|
+
(v1 * 2).magnitude # => 10.0 (since 2*√(3²+4²) = 10)
|
|
24
|
+
|
|
25
|
+
# Calculate dot products, angles, and projections
|
|
26
|
+
v1.dot_product(v2) # => 3 (= 3*1 + 4*0 + 0*2.5)
|
|
27
|
+
v1.angle(v2) # => 1.3460753063647353 (≈77.1°)
|
|
28
|
+
v1.vector_projection(v2).round(2) # => (1.03⋅"weight" + 0.41⋅:x)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
VectorNumber treats **every distinct Ruby object as a dimension** in a vector space over the real numbers. This means you can do proper linear algebra on anything: symbols, strings, arrays, custom classes—whatever you need.
|
|
32
|
+
|
|
33
|
+
## 🚀 Why VectorNumber?
|
|
34
|
+
|
|
35
|
+
### 1. Full Linear Algebra on Any Domain
|
|
36
|
+
Need to work with weighted tags? Feature vectors for machine learning? Coordinate systems with non-numeric axes? VectorNumber gives you the math:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# ML feature vectors with meaningful dimension names
|
|
40
|
+
doc1 = VectorNumber["word_ruby" => 3, "word_gem" => 2, "word_library" => 1]
|
|
41
|
+
doc2 = VectorNumber["word_ruby" => 1, "word_gem" => 3, "word_code" => 2]
|
|
42
|
+
|
|
43
|
+
# Cosine similarity for document comparison
|
|
44
|
+
similarity = doc1.cosine(doc2).round(5) # => 0.64286
|
|
45
|
+
|
|
46
|
+
# Find which document is "closer" to a query
|
|
47
|
+
query = VectorNumber["word_ruby" => 1, "word_gem" => 1]
|
|
48
|
+
doc1.cosine(query) > doc2.cosine(query) # => true
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Numeric-Like Behavior, Hash-Like Access
|
|
52
|
+
It feels like a number, but you can inspect it like a hash:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
v = VectorNumber["apple", "orange"] # => (1⋅"apple" + 1⋅"orange")
|
|
56
|
+
v += "orange" # => (1⋅"apple" + 2⋅"orange")
|
|
57
|
+
v["apple"] # => 1 (coefficient lookup)
|
|
58
|
+
v["kiwi"] # => 0 (missing dimensions are zero)
|
|
59
|
+
v.to_h # => {"apple" => 1, "orange" => 2}
|
|
60
|
+
v.units # => ["apple", "orange"]
|
|
61
|
+
v.coefficients # => [1, 2]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Plays Nicely with Ruby Numbers
|
|
65
|
+
Thanks to full `#coerce` support, VectorNumbers work seamlessly with Ruby's numeric types:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
5 + VectorNumber["x"] * 2 # => (5 + 2⋅"x")
|
|
69
|
+
3.14 * VectorNumber[:theta] # => (3.14⋅:theta)
|
|
70
|
+
VectorNumber[8] < 10 # => true (compares real value)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 4. Rich API
|
|
74
|
+
You want it? We got it!
|
|
75
|
+
|
|
76
|
+
| Category | Methods |
|
|
77
|
+
|-----------|---------|
|
|
78
|
+
| Basic Ops | `+` and `-`, `*` and `/` (scaling), `div` and `%` |
|
|
79
|
+
| Rounding | `round`, `ceil`, `floor`, `truncate` (per-coefficient) |
|
|
80
|
+
| Norms | `magnitude`/`abs`, `abs2`, `p_norm`, `maximum_norm` |
|
|
81
|
+
| Projections | `vector_projection`, `scalar_projection`, `vector_rejection`, `scalar_rejection` |
|
|
82
|
+
| Geometry | `dot_product`, `angle`, `subspace_basis`, `unit_vector` |
|
|
83
|
+
| Hash-like | `each`, `[]`, `transform_coefficients`, `transform_units` |
|
|
20
84
|
|
|
21
|
-
|
|
85
|
+
...and many, **many** more!
|
|
22
86
|
|
|
23
|
-
## Table of contents
|
|
87
|
+
## 📜 Table of contents
|
|
24
88
|
|
|
25
89
|
- [Installation](#installation)
|
|
26
|
-
- [Ruby engine support status](#ruby-engine-support-status)
|
|
27
90
|
- [Usage](#usage)
|
|
28
|
-
- [
|
|
29
|
-
|
|
30
|
-
- [
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
91
|
+
- [API Documentation](#api-documentation)
|
|
92
|
+
- [Quick Start](#quick-start)
|
|
93
|
+
- [Real-world Examples](#real-world-examples)
|
|
94
|
+
- [Advanced Vector Operations](#advanced-vector-operations)
|
|
95
|
+
- [Hash-like Operations](#hash-like-operations)
|
|
96
|
+
- [Custom String Conversion](#custom-string-conversion)
|
|
34
97
|
- [Conceptual basis](#conceptual-basis)
|
|
35
98
|
- [Development](#development)
|
|
36
99
|
- [Contributing](#contributing)
|
|
@@ -43,106 +106,212 @@ Install with `gem`:
|
|
|
43
106
|
gem install vector_number
|
|
44
107
|
```
|
|
45
108
|
|
|
46
|
-
|
|
109
|
+
Or, if using Bundler, add gem to your Gemfile:
|
|
47
110
|
```ruby
|
|
48
111
|
gem "vector_number"
|
|
49
112
|
```
|
|
50
113
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
VectorNumber is developed on MRI (CRuby) but should work on other engines too.
|
|
54
|
-
- TruffleRuby: minor differences in behavior, but otherwise works as expected.
|
|
55
|
-
- JRuby: minor differences in behavior, but otherwise works as expected.
|
|
56
|
-
- Other engines: untested, but should work, depending on compatibility with MRI.
|
|
114
|
+
> [!NOTE]
|
|
115
|
+
> VectorNumber is officially supported (and tested) on MRI (CRuby), JRuby and TruffleRuby.
|
|
57
116
|
|
|
58
117
|
## Usage
|
|
59
118
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
119
|
+
### API Documentation
|
|
120
|
+
|
|
121
|
+
Full documentation with all methods and examples for each method is generated from source and is available online:
|
|
122
|
+
- [Main branch](https://trinistr.github.io/vector_number)
|
|
123
|
+
- [Latest published vesion](https://rubydoc.info/gems/vector_number)
|
|
63
124
|
|
|
64
|
-
###
|
|
125
|
+
### Quick Start
|
|
65
126
|
|
|
66
|
-
VectorNumbers are mostly useful for summing up heterogeneous objects:
|
|
67
127
|
```ruby
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
#
|
|
75
|
-
VectorNumber[
|
|
76
|
-
VectorNumber.new(
|
|
128
|
+
require "vector_number"
|
|
129
|
+
|
|
130
|
+
# Create vectors
|
|
131
|
+
VectorNumber[5, "hello", 5, :sym] # => (10 + 1⋅"hello" + 1⋅:sym)
|
|
132
|
+
VectorNumber["x" => 3, "y" => 4] # => (3⋅"x" + 4⋅"y")
|
|
133
|
+
2 * VectorNumber[:a, :b, :c] # => (2⋅:a + 2⋅:b + 2⋅:c)
|
|
134
|
+
# or more explicitly
|
|
135
|
+
VectorNumber.new([5, "hello", 5, :sym])
|
|
136
|
+
VectorNumber.new({"x" => 3, "y" => 4})
|
|
137
|
+
|
|
138
|
+
# Basic arithmetic
|
|
139
|
+
v = VectorNumber["apple" => 3] + VectorNumber["orange" => 2]
|
|
140
|
+
v -= "orange" # => (3⋅"apple" + 1⋅"orange")
|
|
141
|
+
v *= 1.5 # => (4.5⋅"apple" + 1.5⋅"orange")
|
|
77
142
|
```
|
|
78
143
|
|
|
79
|
-
|
|
144
|
+
### Real-world Examples
|
|
145
|
+
|
|
146
|
+
#### 📦 Inventory Management
|
|
147
|
+
|
|
148
|
+
The most basic function of VectorNumber is the ability to act similarly to a Hash but with defined arithmetic operations. This naturally leads to intuitive operations like addition and subtraction of inventory items.
|
|
149
|
+
|
|
80
150
|
```ruby
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
151
|
+
class Inventory
|
|
152
|
+
def initialize(items)
|
|
153
|
+
@items = VectorNumber.new(items)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def add(item, quantity = 1)
|
|
157
|
+
@items += VectorNumber.new({item => quantity})
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def remove(item, quantity = 1)
|
|
161
|
+
@items -= VectorNumber.new({item => quantity})
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def has?(item, quantity = 1)
|
|
165
|
+
@items[item] >= quantity
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def total_value(prices)
|
|
169
|
+
# Multiply each item's quantity by its price and sum them up
|
|
170
|
+
@items.dot_product(VectorNumber.new(prices))
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
inventory = Inventory.new("apple" => 10, "banana" => 5)
|
|
175
|
+
inventory.add("apple", 3)
|
|
176
|
+
inventory.remove("banana", 2)
|
|
177
|
+
inventory.total_value("apple" => 0.5, "banana" => 0.3) # => 7.4
|
|
88
178
|
```
|
|
89
179
|
|
|
90
|
-
|
|
180
|
+
#### 📊 Weighted Scoring System
|
|
181
|
+
|
|
182
|
+
VectorNumber has several similarity measures out-of-the-box, and implementing custom ones can easily be done with `map` and `reduce`. This example shows how to calculate a match score between a candidate's skills and job requirements using cosine similarity.
|
|
183
|
+
|
|
91
184
|
```ruby
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
185
|
+
class Candidate
|
|
186
|
+
attr_reader :skills
|
|
187
|
+
|
|
188
|
+
# @param skills [Hash{Symbol => Numeric}]
|
|
189
|
+
# keys are skills and values are proficiency levels
|
|
190
|
+
def initialize(skills)
|
|
191
|
+
@skills = VectorNumber.new(skills)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Calculate similarity between candidate skills and job requirements
|
|
195
|
+
# @param job_requirements [Hash{Symbol => Numeric}]
|
|
196
|
+
# @return [Float] A score between 0 and 1
|
|
197
|
+
def match_score(job_requirements)
|
|
198
|
+
job_requirements = VectorNumber.new(job_requirements)
|
|
199
|
+
@skills.cosine_similarity(job_requirements)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
job = {ruby: 5, rails: 4, sql: 3, nosql: 2}
|
|
204
|
+
alice = Candidate.new(ruby: 5, rails: 5, sql: 2, python: 3)
|
|
205
|
+
bob = Candidate.new(ruby: 3, rails: 2, sql: 4, java: 4)
|
|
206
|
+
|
|
207
|
+
alice.match_score(job).round(2) # => 0.87
|
|
208
|
+
bob.match_score(job).round(2) # => 0.71
|
|
95
209
|
```
|
|
96
210
|
|
|
97
|
-
|
|
98
|
-
|
|
211
|
+
#### 🔬 Scientific/Domain Modeling
|
|
212
|
+
|
|
213
|
+
VectorNumber can be used for scientific and domain modeling where vector operations are common.
|
|
99
214
|
|
|
100
|
-
#### Getting values back
|
|
101
|
-
The simplest way to get a value for a specific unit is to use the `#[]` method:
|
|
102
215
|
```ruby
|
|
103
|
-
|
|
104
|
-
VectorNumber[
|
|
105
|
-
VectorNumber[
|
|
216
|
+
# Work done by a constant force
|
|
217
|
+
displacement = VectorNumber[x: 3, y: -2.5]
|
|
218
|
+
force = VectorNumber[x: 5, y: 1]
|
|
219
|
+
work = force.dot_product(displacement) # => 12.5
|
|
220
|
+
|
|
221
|
+
# Gravitational force
|
|
222
|
+
position_massive = VectorNumber[x: 1.5, y: -200, z: -150]
|
|
223
|
+
position_small = VectorNumber[x: -120, y: 13, z: 15.5]
|
|
224
|
+
direction = position_small - position_massive
|
|
225
|
+
unit_direction = direction.unit_vector
|
|
226
|
+
gravitational_force = -unit_direction * 10_000 * 10 * 6.674 / direction.abs2
|
|
227
|
+
# => (3.1317735497992065⋅:x - 5.490269679894905⋅:y - 4.265913765364352⋅:z)
|
|
106
228
|
```
|
|
107
229
|
|
|
108
|
-
|
|
109
|
-
> Accessing a unit that doesn't exist returns 0, not `nil` as you might expect.
|
|
110
|
-
|
|
111
|
-
### (Somewhat) advanced usage
|
|
230
|
+
### Advanced Vector Operations
|
|
112
231
|
|
|
113
|
-
|
|
114
|
-
> Look at API documentation for all methods.
|
|
232
|
+
VectorNumber supports many vector operations beside vector arithmetic. This is a sample of what's available:
|
|
115
233
|
|
|
116
|
-
|
|
117
|
-
|
|
234
|
+
```ruby
|
|
235
|
+
v = VectorNumber[x: 3, y: 4]
|
|
236
|
+
w = VectorNumber[x: 1, y: 2, z: 5]
|
|
237
|
+
|
|
238
|
+
# Vector properties
|
|
239
|
+
v.magnitude # => 5.0
|
|
240
|
+
v.p_norm(1) # => 7 (Manhattan distance)
|
|
241
|
+
v.unit_vector # => (0.6⋅:x + 0.8⋅:y)
|
|
242
|
+
|
|
243
|
+
# Relationships
|
|
244
|
+
v.dot_product(w) # => 11 (=3*1 + 4*2 + 0*5)
|
|
245
|
+
v.angle(w) # => 1.1574640509137637 (rad)
|
|
246
|
+
v.vector_projection(w) # => ((11/30)⋅:x + (11/15)⋅:y + (11/6)⋅:z)
|
|
247
|
+
v.scalar_projection(w) # => 2.008316044185609
|
|
248
|
+
v.vector_rejection(w) # => ((79/30)⋅:x + (49/15)⋅:y - (11/6)⋅:z)
|
|
249
|
+
|
|
250
|
+
# Basis operations
|
|
251
|
+
w.subspace_basis # => [(1⋅:x), (1⋅:y), (1⋅:z)]
|
|
252
|
+
w.uniform_vector # => (1⋅:x + 1⋅:y + 1⋅:z)
|
|
253
|
+
|
|
254
|
+
# Collinearity
|
|
255
|
+
v.collinear?(w) # => false
|
|
256
|
+
v.parallel?(v * 3) # => true
|
|
257
|
+
v.opposite?(v * -1) # => true
|
|
258
|
+
```
|
|
118
259
|
|
|
119
|
-
|
|
260
|
+
### Hash-Like Operations
|
|
120
261
|
|
|
121
|
-
|
|
122
|
-
VectorNumbers implement most of the methods you can find in `Numeric`, with appropriate behavior. For example:
|
|
123
|
-
- `abs` (`magnitude`) calculates length of the vector;
|
|
124
|
-
- `infinite?` checks whether any coefficient is infinite (or NaN), in the same way as `Complex` does it;
|
|
125
|
-
- `positive?` is true if all coefficients are positive, the same for `negative?` (though this is different from `Complex`) (and they can both be false);
|
|
126
|
-
- `round` and friends round each coefficient, with all the bells and whistles;
|
|
127
|
-
- `div` and `%` perform division and remainder operations elementwise;
|
|
128
|
-
- `5 < VectorNumber[6]` returns `true` and `5 < VectorNumber["string"]` raises `ArgumentError`, etc.
|
|
262
|
+
Most of Hash interface is implemented—though much of it comes from Enumerable—with the notable exception of self-modifying methods.
|
|
129
263
|
|
|
130
|
-
|
|
264
|
+
```ruby
|
|
265
|
+
v = VectorNumber[a: 2, b: 3, c: 5]
|
|
266
|
+
|
|
267
|
+
# Querying
|
|
268
|
+
v[:a] # => 2
|
|
269
|
+
v[:d] # => 0
|
|
270
|
+
v.unit?(:b) # => true
|
|
271
|
+
v.unit?(:d) # => false
|
|
272
|
+
v.fetch(:d, 42) # => 42
|
|
273
|
+
|
|
274
|
+
# Transformation
|
|
275
|
+
v.transform_coefficients { |c| c * 2 } # (4⋅:a + 6⋅:b + 10⋅:c)
|
|
276
|
+
v.transform_units { |u| u.to_s } # (2⋅"a" + 3⋅"b" + 5⋅"c")
|
|
277
|
+
|
|
278
|
+
# Enumeration
|
|
279
|
+
v.each { |unit, coeff| puts "#{coeff}×#{unit}" }
|
|
280
|
+
v.to_h # => {a: 2, b: 3, c: 5}
|
|
281
|
+
```
|
|
131
282
|
|
|
132
|
-
|
|
133
|
-
VectorNumbers implement `each` (`each_pair`) in the same way as Hash does, allowing `Enumerable` methods to be used in a familiar way.
|
|
283
|
+
### Custom String Conversion
|
|
134
284
|
|
|
135
|
-
|
|
285
|
+
While the default string representation works well for console output, there are many possible scenarios and use cases, so the `to_s` method supports customization:
|
|
136
286
|
|
|
137
|
-
|
|
287
|
+
```ruby
|
|
288
|
+
v = VectorNumber[:a => 2, "x" => 5.5, [] => -3.14]
|
|
289
|
+
# Replacing the multiplication symbol
|
|
290
|
+
v.to_s(mult: :asterisk)
|
|
291
|
+
# => "2*:a + 5.5*\"x\" - 3.14*[]"
|
|
292
|
+
# Custom formatting with a block
|
|
293
|
+
v.to_s { |unit, coeff, i| "#{' + ' unless i.zero?}(#{coeff}#{unit})" }
|
|
294
|
+
# => "(2a) + (5.5x) + (-3.14[])"
|
|
295
|
+
# Using Enumerator for complex processing
|
|
296
|
+
v.to_enum(:to_s).map { |unit, coeff| "#{unit.inspect}: #{coeff}" }.join(', ')
|
|
297
|
+
# => ":a: 2, \"x\": 5.5, []: -3.14"
|
|
298
|
+
```
|
|
138
299
|
|
|
139
|
-
|
|
300
|
+
## Conceptual Basis
|
|
140
301
|
|
|
141
|
-
|
|
302
|
+
VectorNumber is built on the mathematical concept of a **real vector space** with countably infinite dimensions:
|
|
303
|
+
- Every distinct Ruby object (determined by `eql?`) is a dimension
|
|
304
|
+
- Each dimension has a coefficient (a real number)
|
|
305
|
+
- The real unit `1` and imaginary unit `i` are special dimensions that subsume Ruby's numeric types
|
|
306
|
+
- All operations follow vector space axioms
|
|
142
307
|
|
|
143
|
-
|
|
308
|
+
Furthermore, VectorNumbers exist in a normed Euclidean inner product space:
|
|
309
|
+
- All dimensions are orthogonal and independent
|
|
310
|
+
- The norm (magnitude) of a vector is calculated using the Euclidean norm
|
|
311
|
+
- Inner (dot) product is defined, which allows angles between vectors to be calculated
|
|
312
|
+
- All unit vectors have a length of 1
|
|
144
313
|
|
|
145
|
-
This might be more easily imagined as a geometric vector. For example, this is a graphic representation of a vector `
|
|
314
|
+
This might be more easily imagined as a geometric vector. For example, this is a graphic representation of a vector `VectorNumber[3, 2i] + VectorNumber["string" => 3, [1,2,3] => 4.5]`:
|
|
146
315
|
|
|
147
316
|

|
|
148
317
|
|
|
@@ -160,7 +329,7 @@ To release a new version, run `rake version:{major|minor|patch}`, and then run `
|
|
|
160
329
|
|
|
161
330
|
Bug reports and pull requests are welcome on GitHub at [https://github.com/trinistr/vector_number]().
|
|
162
331
|
|
|
163
|
-
|
|
332
|
+
**Checklist for a new or updated feature**
|
|
164
333
|
|
|
165
334
|
- Running `rake spec` reports 100% coverage (unless it's impossible to achieve in one run).
|
|
166
335
|
- Running `rake rubocop` reports no offenses.
|
|
@@ -25,7 +25,7 @@ class VectorNumber
|
|
|
25
25
|
# VectorNumber["a", 14] == 14 # => false
|
|
26
26
|
# VectorNumber["a"] == "a" # => false
|
|
27
27
|
#
|
|
28
|
-
# @param other [
|
|
28
|
+
# @param other [Any]
|
|
29
29
|
# @return [Boolean]
|
|
30
30
|
#
|
|
31
31
|
# @since 0.2.0
|
|
@@ -34,7 +34,8 @@ class VectorNumber
|
|
|
34
34
|
|
|
35
35
|
case other
|
|
36
36
|
when VectorNumber
|
|
37
|
-
|
|
37
|
+
# HACK: Using instance variable access to avoid hash copying overhead.
|
|
38
|
+
size == other.size && @data == other.instance_variable_get(:@data)
|
|
38
39
|
when Numeric
|
|
39
40
|
numeric?(2) && other.real == real && other.imaginary == imaginary
|
|
40
41
|
else
|
|
@@ -56,13 +57,21 @@ class VectorNumber
|
|
|
56
57
|
# VectorNumber["a", 14].eql? 14 # => false
|
|
57
58
|
# VectorNumber["a"].eql? "a" # => false
|
|
58
59
|
#
|
|
59
|
-
# @param other [
|
|
60
|
+
# @param other [Any]
|
|
60
61
|
# @return [Boolean]
|
|
61
62
|
def eql?(other)
|
|
62
63
|
return true if equal?(other)
|
|
63
|
-
return false unless other.
|
|
64
|
+
return false unless self.class == other.class
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
# @type var other : vector_instance
|
|
67
|
+
size.eql?(other.size) && @data.eql?(other.instance_variable_get(:@data))
|
|
68
|
+
rescue NoMethodError => e
|
|
69
|
+
# :nocov:
|
|
70
|
+
raise unless e.receiver.equal?(other)
|
|
71
|
+
# :nocov:
|
|
72
|
+
|
|
73
|
+
# Should only happen if `other.class` is undefined.
|
|
74
|
+
false
|
|
66
75
|
end
|
|
67
76
|
|
|
68
77
|
# Generate an Integer hash value for self.
|
|
@@ -89,7 +98,7 @@ class VectorNumber
|
|
|
89
98
|
# VectorNumber[130] <=> 12 # => 1
|
|
90
99
|
# 1 <=> VectorNumber[13] # => -1
|
|
91
100
|
# VectorNumber[12.1] <=> Complex(12.1, 0) # => 0
|
|
92
|
-
# # This doesn't work as expected
|
|
101
|
+
# # This doesn't work as expected:
|
|
93
102
|
# Complex(12.1, 0) <=> VectorNumber[12.1] # => nil
|
|
94
103
|
#
|
|
95
104
|
# # Any non-real comparison returns nil:
|
|
@@ -99,9 +108,8 @@ class VectorNumber
|
|
|
99
108
|
#
|
|
100
109
|
# @see #numeric?
|
|
101
110
|
# @see Comparable
|
|
102
|
-
# @see NumericRefinements
|
|
103
111
|
#
|
|
104
|
-
# @param other [
|
|
112
|
+
# @param other [Any]
|
|
105
113
|
# @return [Integer]
|
|
106
114
|
# @return [nil] if +self+ or +other+ isn't a real number.
|
|
107
115
|
#
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class VectorNumber
|
|
4
|
-
# @group
|
|
4
|
+
# @group Type conversion
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Get real part of the vector.
|
|
7
7
|
#
|
|
8
8
|
# @example
|
|
9
9
|
# VectorNumber[23, "a"].real # => 23
|
|
10
10
|
# VectorNumber["a"].real # => 0
|
|
11
11
|
#
|
|
12
|
-
# @return [
|
|
12
|
+
# @return [Numeric]
|
|
13
13
|
def real = @data[R]
|
|
14
14
|
|
|
15
|
-
#
|
|
15
|
+
# Get imaginary part of the vector.
|
|
16
16
|
#
|
|
17
17
|
# @example
|
|
18
18
|
# VectorNumber[23, "a"].imaginary # => 0
|
|
19
19
|
# VectorNumber["a", Complex(1, 2r)].imag # => (2/1)
|
|
20
20
|
#
|
|
21
|
-
# @return [
|
|
21
|
+
# @return [Numeric]
|
|
22
22
|
def imaginary = @data[I]
|
|
23
23
|
|
|
24
24
|
# @since 0.2.1
|
|
25
25
|
alias imag imaginary
|
|
26
26
|
|
|
27
|
-
#
|
|
27
|
+
# Convert vector to an Integer, if only real dimension is non-zero.
|
|
28
28
|
#
|
|
29
29
|
# @example
|
|
30
30
|
# VectorNumber[13.5].to_i # => 13
|
|
@@ -37,7 +37,7 @@ class VectorNumber
|
|
|
37
37
|
# Integer(VectorNumber[2, "i"]) # RangeError
|
|
38
38
|
#
|
|
39
39
|
# @return [Integer]
|
|
40
|
-
# @raise [RangeError] if any non-real
|
|
40
|
+
# @raise [RangeError] if any non-real dimension is non-zero
|
|
41
41
|
def to_i
|
|
42
42
|
raise_convert_error(Integer) unless numeric?(1)
|
|
43
43
|
|
|
@@ -46,7 +46,7 @@ class VectorNumber
|
|
|
46
46
|
|
|
47
47
|
alias to_int to_i
|
|
48
48
|
|
|
49
|
-
#
|
|
49
|
+
# Convert vector to a Float if only real dimension is non-zero.
|
|
50
50
|
#
|
|
51
51
|
# @example
|
|
52
52
|
# VectorNumber[13.5].to_f # => 13.5
|
|
@@ -58,14 +58,14 @@ class VectorNumber
|
|
|
58
58
|
# Float(VectorNumber[2, "i"]) # RangeError
|
|
59
59
|
#
|
|
60
60
|
# @return [Float]
|
|
61
|
-
# @raise [RangeError] if any non-real
|
|
61
|
+
# @raise [RangeError] if any non-real dimension is non-zero
|
|
62
62
|
def to_f
|
|
63
63
|
raise_convert_error(Float) unless numeric?(1)
|
|
64
64
|
|
|
65
65
|
real.to_f
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
#
|
|
68
|
+
# Convert vector to a Rational if only real dimension is non-zero.
|
|
69
69
|
#
|
|
70
70
|
# @example
|
|
71
71
|
# VectorNumber[13.5].to_r # => (27/2)
|
|
@@ -77,14 +77,14 @@ class VectorNumber
|
|
|
77
77
|
# Rational(VectorNumber[2, "i"]) # RangeError
|
|
78
78
|
#
|
|
79
79
|
# @return [Rational]
|
|
80
|
-
# @raise [RangeError] if any non-real
|
|
80
|
+
# @raise [RangeError] if any non-real dimension is non-zero
|
|
81
81
|
def to_r
|
|
82
82
|
raise_convert_error(Rational) unless numeric?(1)
|
|
83
83
|
|
|
84
84
|
real.to_r
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
#
|
|
87
|
+
# Convert vector to a BigDecimal if only real dimension is non-zero.
|
|
88
88
|
#
|
|
89
89
|
# @example
|
|
90
90
|
# VectorNumber[13.5].to_d # => 0.135e2
|
|
@@ -93,29 +93,28 @@ class VectorNumber
|
|
|
93
93
|
# VectorNumber[2, 2i].to_d # RangeError
|
|
94
94
|
# VectorNumber[2, :i].to_d # RangeError
|
|
95
95
|
#
|
|
96
|
-
# # This does't work
|
|
96
|
+
# # This does't work:
|
|
97
97
|
# BigDecimal(VectorNumber[2]) # TypeError
|
|
98
|
-
# # #to_s can be used as a workaround
|
|
98
|
+
# # #to_s can be used as a workaround:
|
|
99
99
|
# BigDecimal(VectorNumber[2].to_s) # => 0.2e1
|
|
100
100
|
# BigDecimal(VectorNumber[2, "i"].to_s) # => ArgumentError
|
|
101
101
|
#
|
|
102
102
|
# @param ndigits [Integer] precision
|
|
103
103
|
# @return [BigDecimal]
|
|
104
|
-
# @raise [RangeError] if any non-real
|
|
104
|
+
# @raise [RangeError] if any non-real dimension is non-zero
|
|
105
105
|
# @raise [NameError] if BigDecimal is not defined
|
|
106
106
|
#
|
|
107
107
|
# @see Kernel.BigDecimal
|
|
108
|
-
# @see NumericRefinements
|
|
109
108
|
def to_d(ndigits = nil)
|
|
110
109
|
raise_convert_error(BigDecimal) unless numeric?(1)
|
|
111
110
|
|
|
112
111
|
return BigDecimal(real, ndigits) if ndigits
|
|
113
|
-
return BigDecimal(real, Float::DIG) if real
|
|
112
|
+
return BigDecimal(real, Float::DIG) if Float === real
|
|
114
113
|
|
|
115
114
|
BigDecimal(real)
|
|
116
115
|
end
|
|
117
116
|
|
|
118
|
-
#
|
|
117
|
+
# Convert vector to a Complex if only real and/or imaginary dimensions are non-zero.
|
|
119
118
|
#
|
|
120
119
|
# @example
|
|
121
120
|
# VectorNumber[13.5].to_c # => (13.5+0i)
|
|
@@ -127,13 +126,32 @@ class VectorNumber
|
|
|
127
126
|
# Complex(VectorNumber[2, "i"]) # RangeError
|
|
128
127
|
#
|
|
129
128
|
# @return [Complex]
|
|
130
|
-
# @raise [RangeError] if any non-real, non-imaginary
|
|
129
|
+
# @raise [RangeError] if any non-real, non-imaginary dimension is non-zero
|
|
131
130
|
def to_c
|
|
132
131
|
raise_convert_error(Complex) unless numeric?(2)
|
|
133
132
|
|
|
134
133
|
Complex(real, imaginary)
|
|
135
134
|
end
|
|
136
135
|
|
|
136
|
+
# Get mutable hash with vector's data.
|
|
137
|
+
#
|
|
138
|
+
# Returned hash has a default value of 0.
|
|
139
|
+
#
|
|
140
|
+
# @example
|
|
141
|
+
# VectorNumber["a", "b", 6, 1i].to_h # => {"a" => 1, "b" => 1, unit/1 => 6, unit/i => 1}
|
|
142
|
+
# VectorNumber["a", "b", 6, 1i].to_h["c"] # => 0
|
|
143
|
+
#
|
|
144
|
+
# @return [Hash{Any => Numeric}]
|
|
145
|
+
def to_h(&block)
|
|
146
|
+
# TODO: Remove block argument.
|
|
147
|
+
if block_given?
|
|
148
|
+
# @type var block: ^(unit_type, coefficient_type) -> each_value_type
|
|
149
|
+
@data.to_h(&block)
|
|
150
|
+
else
|
|
151
|
+
@data.dup
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
137
155
|
private
|
|
138
156
|
|
|
139
157
|
def raise_convert_error(klass)
|