vector_number 0.4.3 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05d6547284c73b585a149026d1a0a123f5914e13c81856e2419180f44f05404c
4
- data.tar.gz: 2b942db9aa53c4e8027540a630355f2defd64de96d3c90bbd55e539844f32bfa
3
+ metadata.gz: 686890049523273762a1dea1835f990e987d556bf9399356cf53572727df7ad9
4
+ data.tar.gz: 7c90c791c17eb9ffdcb1455174141ca857456642770acdff2e9d2ee3e19e4fab
5
5
  SHA512:
6
- metadata.gz: a8d079e1d0915ae151ac2dcb28621f672365e55efa0cb505ad3e846eb3619eababa4e39e5654098bb305fafa2ff602a1085c913c5d6fc8ea02922688087fb09b
7
- data.tar.gz: f09e3d71187e9c202ff1a4960cbf19b7a02f679dc6b49b7b1e96e42e074a66990507262ab1648182217b8dacc980fe0ed777c48ac76db44734dccfdf34a7b488
6
+ metadata.gz: d22a64381f58d7c8a1dd858510734e960326af64ca9f1d109c7fceee0f7e7fd2dfee2df7ba5b7a16fe5fc67dd1b192c60caa069215c20c03e231cee050a9acd0
7
+ data.tar.gz: 1c2de92ef2ae14f127c0fcd121ad361cb552875999a9c3eba31d90896fb104448c925793b34d47695785042a6a03420605d4ba017c91683348ac47f074cc7c86
data/README.md CHANGED
@@ -18,12 +18,7 @@ Features:
18
18
  - Enjoy a mix of vector-, complex- and polynomial-like behavior at appropriate times.
19
19
  - No dependencies, no extensions. It just works!
20
20
 
21
- Similar projects:
22
- - [vector_space](https://github.com/tomstuart/vector_space) aims to provide typed vector spaces with limited dimensions and nice formatting;
23
- - [named_vector](https://rubygems.org/gems/named_vector) provides simple vectors with named dimensions;
24
- - various quaternion libraries like [quaternion](https://github.com/tanahiro/quaternion) or [rmath3d](https://github.com/vaiorabbit/rmath3d).
25
-
26
- However, none of them have been updated in *years*.
21
+ Other people have created some similar gems over the years, like [vector_space](https://github.com/tomstuart/vector_space) or [named_vector](https://rubygems.org/gems/named_vector) but they don't have the characterics that I wanted, and none of them have been updated in *years*.
27
22
 
28
23
  ## Table of contents
29
24
 
@@ -31,10 +26,12 @@ However, none of them have been updated in *years*.
31
26
  - [Ruby engine support status](#ruby-engine-support-status)
32
27
  - [Usage](#usage)
33
28
  - [Basics](#basics)
29
+ - [Getting values back](#getting-values-back)
34
30
  - [(Somewhat) advanced usage](#somewhat-advanced-usage)
35
31
  - [Frozenness](#frozenness)
36
32
  - [Numerical behavior](#numerical-behavior)
37
33
  - [Enumeration and hash-like behavior](#enumeration-and-hash-like-behavior)
34
+ - [Conceptual basis](#conceptual-basis)
38
35
  - [Development](#development)
39
36
  - [Contributing](#contributing)
40
37
  - [License](#license)
@@ -54,8 +51,8 @@ gem "vector_number"
54
51
  ### Ruby engine support status
55
52
 
56
53
  VectorNumber is developed on MRI (CRuby) but should work on other engines too.
57
- - TruffleRuby: there are some minor differences in behavior, but otherwise works as expected.
58
- - JRuby: significant problems, but may work, currently not tested.
54
+ - TruffleRuby: minor differences in behavior, but otherwise works as expected.
55
+ - JRuby: minor differences in behavior, but otherwise works as expected.
59
56
  - Other engines: untested, but should work, depending on compatibility with MRI.
60
57
 
61
58
  ## Usage
@@ -66,12 +63,12 @@ VectorNumber is developed on MRI (CRuby) but should work on other engines too.
66
63
 
67
64
  ### Basics
68
65
 
69
- VectorNumbers are mostly useful for tallying up heterogeneous objects:
66
+ VectorNumbers are mostly useful for summing up heterogeneous objects:
70
67
  ```ruby
71
- sum = [4, "death", "death", 13, nil].reduce(VectorNumber[], :+)
72
- sum # => (17 + 2⋅'death' + 1⋅)
73
- sum.to_h # => {1=>17, "death"=>2, nil=>1}
74
- sum.to_a # => [[1, 17], ["death", 2], [nil, 1]]
68
+ sum = VectorNumber[4] + "death" + "death" + nil
69
+ sum # => (17 + 2⋅"death" + 1⋅)
70
+ sum.to_h # => {unit/1 => 17, "death" => 2, nil => 1}
71
+ sum.to_a # => [[unit/1, 17], ["death", 2], [nil, 1]]
75
72
 
76
73
  # Alternatively, the same result can be equivalently (and more efficiently)
77
74
  # achieved by passing all values to a constructor:
@@ -81,17 +78,36 @@ VectorNumber.new([4, "death", "death", 13, nil])
81
78
 
82
79
  Doing arithmetic with vectors is simple and intuitive:
83
80
  ```ruby
84
- VectorNumber["string"] + "string" # => (2⋅'string')
85
- VectorNumber["string"] - "str" # => (1⋅'string' - 1⋅'str')
86
- VectorNumber[5] + VectorNumber["string"] - 0.5 # => (4.5 + 1⋅'string')
87
- VectorNumber["string", "string", "string", "str"] # => (3⋅'string' + 1⋅'str')
81
+ VectorNumber["string"] + "string" # => (2⋅"string")
82
+ VectorNumber["string"] - "str" # => (1⋅"string" - 1⋅"str")
83
+ VectorNumber[5] + VectorNumber["string"] - 0.5 # => (4.5 + 1⋅"string")
84
+ VectorNumber["string", "string", "string", "str"] # => (3⋅"string" + 1⋅"str")
88
85
  # Multiply and divide by any real number:
89
- VectorNumber[:s] * 2 + VectorNumber["string"] * 0.3 # => (2⋅s + 0.3⋅'string')
86
+ VectorNumber[:s] * 2 + VectorNumber["string"] * 0.3 # => (2⋅s + 0.3⋅"string")
90
87
  VectorNumber[:s] / VectorNumber[3] # => (1/3⋅s)
91
- # Multiplication even works when the left operand is a regular number:
88
+ ```
89
+
90
+ Ruby numbers rely on `#coerce` to promote values to a common type. This allows using regular numbers as first operand in arithmetic operations:
91
+ ```ruby
92
+ 2 + VectorNumber["string"] # => (2 + 1⋅"string")
92
93
  1/3r * VectorNumber[[]] # => (1/3⋅[])
94
+ 13 / VectorNumber[2] # => (13/2)
95
+ ```
96
+
97
+ > [!NOTE]
98
+ > VectorNumbers don't perform "integer division" to prevent unexpected loss of precision. `#div` and rounding methods can achieve this if required.
99
+
100
+ #### Getting values back
101
+ The simplest way to get a value for a specific unit is to use the `#[]` method:
102
+ ```ruby
103
+ VectorNumber["string", "string", "string", "str"]["string"] # => 3
104
+ VectorNumber["string", "string", "string", "str"]["str"] # => 1
105
+ VectorNumber["string", "string", "string", "str"]["nonexistent"] # => 0
93
106
  ```
94
107
 
108
+ > [!NOTE]
109
+ > Accessing a unit that doesn't exist returns 0, not `nil` as you might expect.
110
+
95
111
  ### (Somewhat) advanced usage
96
112
 
97
113
  > [!TIP]
@@ -118,8 +134,17 @@ VectorNumbers implement `each` (`each_pair`) in the same way as Hash does, allow
118
134
 
119
135
  There are also the usual `[]`, `unit?` (`key?`), `units` (`keys`), `coefficients` (`values`) methods. `to_h` and `to_a` can be used if a regular Hash or Array is needed.
120
136
 
121
- > [!NOTE]
122
- > Be aware that `[]` always returns `0` for "missing" units. `unit?` will return `false` for them.
137
+ ## Conceptual basis
138
+
139
+ VectorNumbers are based on the concept of a vector space over the field of real numbers (real vector space). In the case of VectorNumber, the dimensionality of the vector space is countably infinite, as most distinct objects in Ruby signify a separate dimension.
140
+
141
+ For most dimensions, an object is that distinct dimension's unit. There are two exceptions currently: real unit (1) and imaginary unit (i) which define the real and imaginary dimensions and subsume all real and complex numbers. A VectorNumber can not be a unit itself. Distinction of objects is determined by `eql?`, same as for Hash.
142
+
143
+ Length of a VectorNumber in any given dimension is given by a real number, called its coefficient. All dimensions are linearly independent — change in one coefficient does not affect any other coefficient. There is no distinction between a dimension explicitly specified as having a 0 coefficient (or arriving at 0 through calculation) and a dimension not specified at all.
144
+
145
+ This might be more easily imagined as a geometric vector. For example, this is a graphic representation of a vector `3 * VectorNumber[1] + 2 * VectorNumber[1i] + 3 * VectorNumber["string"] + 4.5 * VectorNumber[[1,2,3]]` in the vector space:
146
+
147
+ ![Vector space](doc/vector_space.svg)
123
148
 
124
149
  ## Development
125
150
 
@@ -147,4 +172,4 @@ Bug reports and pull requests are welcome on GitHub at [https://github.com/trini
147
172
 
148
173
  ## License
149
174
 
150
- This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT), see [LICENSE.txt](https://github.com/trinistr/vector_number/blob/main/LICENSE.txt).
175
+ This gem is available as open source under the terms of the MIT License, see [LICENSE.txt](https://github.com/trinistr/vector_number/blob/main/LICENSE.txt).
@@ -0,0 +1,94 @@
1
+ <svg width="500" height="400" viewBox="-250 -200 500 400" xmlns="http://www.w3.org/2000/svg">
2
+ <style>
3
+ text {
4
+ font-family: 'Arial', sans-serif;
5
+ font-size: 14px;
6
+ }
7
+ .axis-label {
8
+ font-weight: bold;
9
+ font-size: 16px;
10
+ }
11
+ .dim-label {
12
+ font-size: 12px;
13
+ fill: #666;
14
+ }
15
+ </style>
16
+
17
+ <!-- Background -->
18
+ <rect x="-250" y="-200" width="500" height="400" fill="#fff0cc"/>
19
+
20
+ <!-- Coordinate system center -->
21
+ <circle cx="0" cy="0" r="5" fill="#000"/>
22
+ <text x="-50" y="-5" class="dim-label">(0,0,...)</text>
23
+
24
+ <!-- 1 (numeric) axis -->
25
+ <line x1="0" y1="0" x2="150" y2="0" stroke="#2196F3" stroke-width="2"/>
26
+ <polygon points="155,0 145,-4 145,4" fill="#2196F3"/>
27
+ <text x="160" y="5" class="axis-label">1</text>
28
+
29
+ <!-- i (imaginary) axis -->
30
+ <line x1="0" y1="0" x2="0" y2="-150" stroke="#4CAF50" stroke-width="2"/>
31
+ <polygon points="0,-155 -4,-145 4,-145" fill="#4CAF50"/>
32
+ <text x="10" y="-145" class="axis-label">i</text>
33
+
34
+ <!-- [1,2,3] axis -->
35
+ <line x1="0" y1="0" x2="-150" y2="150" stroke="#FF9800" stroke-width="2"/>
36
+ <polygon points="-154,154 -150,144 -144,150" fill="#FF9800"/>
37
+ <text x="-205" y="140" class="axis-label">[1,2,3]</text>
38
+
39
+ <!-- "string" axis -->
40
+ <line x1="0" y1="0" x2="100" y2="150" stroke="#9C27B0" stroke-width="2"/>
41
+ <polygon points="103,155 101,144 93,149" fill="#9C27B0"/>
42
+ <text x="110" y="160" class="axis-label">"string"</text>
43
+
44
+ <!-- VectorNumber -->
45
+ <line x1="0" y1="0" x2="118" y2="-78" stroke="#F44336" stroke-width="3"/>
46
+ <polygon points="120,-80 107,-78 113,-68" fill="#F44336"/>
47
+ <text x="120" y="-90" text-anchor="middle" font-weight="bold" fill="#F44336">
48
+ (3 + 2i + 3⋅"string" + 4.5⋅[1,2,3])
49
+ </text>
50
+
51
+ <!-- Projection lines to axes -->
52
+ <!-- To numeric axis -->
53
+ <line x1="120" y1="-80" x2="120" y2="0" stroke="#2196F3" stroke-width="1" stroke-dasharray="5,5"/>
54
+ <circle cx="120" cy="0" r="3" fill="#2196F3"/>
55
+ <text x="120" y="15" class="dim-label">3</text>
56
+
57
+ <!-- To imaginary axis -->
58
+ <line x1="120" y1="-80" x2="0" y2="-80" stroke="#4CAF50" stroke-width="1" stroke-dasharray="5,5"/>
59
+ <circle cx="0" cy="-80" r="3" fill="#4CAF50"/>
60
+ <text x="-15" y="-80" class="dim-label">2</text>
61
+
62
+ <!-- To [1,2,3] axis -->
63
+ <line x1="120" y1="-80" x2="-90" y2="90" stroke="#FF9800" stroke-width="1" stroke-dasharray="5,5"/>
64
+ <circle cx="-90" cy="90" r="3" fill="#FF9800"/>
65
+ <text x="-115" y="90" class="dim-label">4.5</text>
66
+
67
+ <!-- To "string" axis -->
68
+ <line x1="120" y1="-80" x2="55" y2="83" stroke="#9C27B0" stroke-width="1" stroke-dasharray="5,5"/>
69
+ <circle cx="55" cy="83" r="3" fill="#9C27B0"/>
70
+ <text x="45" y="95" class="dim-label">3</text>
71
+
72
+ <!-- Legend -->
73
+ <rect x="-220" y="-180" width="180" height="145" fill="white" stroke="#ccc" stroke-width="1"/>
74
+
75
+ <line x1="-210" y1="-160" x2="-202" y2="-168" stroke="#F44336" stroke-width="3"/>
76
+ <polygon points="-200,-170 -205,-169 -201,-165" fill="#F44336"/>
77
+ <text x="-190" y="-160">VectorNumber</text>
78
+
79
+ <line x1="-212" y1="-145" x2="-200" y2="-145" stroke="#2196F3" stroke-width="2"/>
80
+ <text x="-190" y="-140">Real dimension</text>
81
+
82
+ <line x1="-212" y1="-125" x2="-200" y2="-125" stroke="#4CAF50" stroke-width="2"/>
83
+ <text x="-190" y="-120">Imaginary dimension</text>
84
+
85
+ <line x1="-212" y1="-105" x2="-200" y2="-105" stroke="#9C27B0" stroke-width="2"/>
86
+ <text x="-190" y="-100">"string" dimension</text>
87
+
88
+ <line x1="-212" y1="-85" x2="-200" y2="-85" stroke="#FF9800" stroke-width="2"/>
89
+ <text x="-190" y="-80">[1,2,3] dimension</text>
90
+
91
+ <line x1="-212" y1="-65" x2="-200" y2="-65" stroke="gray" stroke-width="2"/>
92
+ <text x="-190" y="-60">Zero dimensions</text>
93
+ <text x="-190" y="-45">(not portrayed)</text>
94
+ </svg>
@@ -1,125 +1,121 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class VectorNumber
4
- # Methods for comparing with other numbers.
5
- #
6
- # +Comparable+ is included for parity with numbers.
4
+ # @group Comparing
5
+
6
+ # +Comparable+ is included for parity with +Numeric+.
7
7
  # @example using Comparable methods
8
8
  # VectorNumber[12] < 0 # => false
9
9
  # VectorNumber[12, "a"] < 0 # ArgumentError
10
10
  # (VectorNumber[12]..VectorNumber[15]).include?(13) # => true
11
- module Comparing
12
- # @since 0.2.0
13
- include ::Comparable
11
+ #
12
+ # @since 0.2.0
13
+ include ::Comparable
14
14
 
15
- # Compare to +other+ for equality.
16
- #
17
- # Values are considered equal if
18
- # - +other+ is a VectorNumber and it is +eql?+ to this one, or
19
- # - +other+ is a Numeric equal in value to this (real or complex) number.
20
- #
21
- # @example
22
- # VectorNumber[3.13] == 3.13 # => true
23
- # VectorNumber[1.4, 1.5i] == Complex(1.4, 1.5) # => true
24
- # VectorNumber["a", "b", "c"] == VectorNumber["c", "b", "a"] # => true
25
- # VectorNumber["a", 14] == 14 # => false
26
- # VectorNumber["a"] == "a" # => false
27
- #
28
- # @param other [Object]
29
- # @return [Boolean]
30
- #
31
- # @since 0.2.0
32
- def ==(other)
33
- return true if eql?(other)
15
+ # Test whether +other+ has the same value with +==+ semantics.
16
+ #
17
+ # Values are considered equal if
18
+ # - +other+ is a VectorNumber and it has the same units and equal coefficients, or
19
+ # - +other+ is a Numeric equal in value to this (real or complex) number.
20
+ #
21
+ # @example
22
+ # VectorNumber[3.13] == 3.13 # => true
23
+ # VectorNumber[1.4, 1.5i] == Complex(1.4, 1.5) # => true
24
+ # VectorNumber["a", "b", "c"] == VectorNumber["c", "b", "a"] # => true
25
+ # VectorNumber["a", 14] == 14 # => false
26
+ # VectorNumber["a"] == "a" # => false
27
+ #
28
+ # @param other [Object]
29
+ # @return [Boolean]
30
+ #
31
+ # @since 0.2.0
32
+ def ==(other)
33
+ return true if equal?(other)
34
34
 
35
- case other
36
- when Numeric
37
- numeric?(2) && other.real == real && other.imaginary == imaginary
38
- else
39
- # Can't compare a number-like value to a non-number.
40
- false
41
- end
35
+ case other
36
+ when VectorNumber
37
+ size == other.size && @data == other.to_h
38
+ when Numeric
39
+ numeric?(2) && other.real == real && other.imaginary == imaginary
40
+ else
41
+ # Can't compare a number-like value to a non-number.
42
+ false
42
43
  end
44
+ end
43
45
 
44
- # Compare to +other+ for strict equality.
45
- #
46
- # Values are considered equal only if +other+ is a VectorNumber
47
- # and it has exactly the same units and coefficients, though possibly in a different order.
48
- # Additionally, `a.eql?(b)` implies `a.hash == b.hash`.
49
- #
50
- # Note that {#options} are not considered for equality.
51
- #
52
- # @example
53
- # VectorNumber["a", "b", "c"].eql? VectorNumber["c", "b", "a"] # => true
54
- # VectorNumber[3.13].eql? 3.13 # => false
55
- # VectorNumber[1.4, 1.5i].eql? Complex(1.4, 1.5) # => false
56
- # VectorNumber["a", 14].eql? 14 # => false
57
- # VectorNumber["a"].eql? "a" # => false
58
- #
59
- # @param other [Object]
60
- # @return [Boolean]
61
- #
62
- # @since 0.1.0
63
- def eql?(other)
64
- return true if equal?(other)
46
+ # Test whether +other+ is VectorNumber and has the same value with +eql?+ semantics.
47
+ #
48
+ # Values are considered equal only if +other+ is a VectorNumber
49
+ # and it has exactly the same units and coefficients, though possibly in a different order.
50
+ # Additionally, `a.eql?(b)` implies `a.hash == b.hash`.
51
+ #
52
+ # @example
53
+ # VectorNumber["a", "b", "c"].eql? VectorNumber["c", "b", "a"] # => true
54
+ # VectorNumber[3.13].eql? 3.13 # => false
55
+ # VectorNumber[1.4, 1.5i].eql? Complex(1.4, 1.5) # => false
56
+ # VectorNumber["a", 14].eql? 14 # => false
57
+ # VectorNumber["a"].eql? "a" # => false
58
+ #
59
+ # @param other [Object]
60
+ # @return [Boolean]
61
+ def eql?(other)
62
+ return true if equal?(other)
63
+ return false unless other.is_a?(VectorNumber)
65
64
 
66
- if other.is_a?(VectorNumber)
67
- other.size == size && other.each_pair.all? { |u, c| @data[u] == c }
68
- else
69
- false
70
- end
71
- end
65
+ size.eql?(other.size) && @data.eql?(other.to_h)
66
+ end
72
67
 
73
- # Generate an Integer hash value for self.
74
- #
75
- # Hash values are stable during runtime, but not between processes.
76
- #
77
- # @example
78
- # VectorNumber["b", "a"].hash # => 3081872088394655324
79
- # VectorNumber["a", "b", mult: :cross].hash # => 3081872088394655324
80
- # VectorNumber["b", "c"].hash # => -1002381358514682371
81
- #
82
- # @return [Integer]
83
- #
84
- # @since 0.4.2
85
- def hash
86
- [self.class, @data].hash
87
- end
68
+ # Generate an Integer hash value for self.
69
+ #
70
+ # @example
71
+ # VectorNumber["b", "a"].hash # => 3081872088394655324
72
+ # VectorNumber["a", "b"].hash # => 3081872088394655324
73
+ # VectorNumber["b", "c"].hash # => -1002381358514682371
74
+ #
75
+ # @return [Integer]
76
+ #
77
+ # @since 0.4.2
78
+ def hash
79
+ [self.class, @data].hash
80
+ end
88
81
 
89
- # Compare to +other+ and return -1, 0, or 1
90
- # if +self+ is less than, equal, or larger than +other+.
91
- #
92
- # @example
93
- # VectorNumber[130] <=> 12 # => 1
94
- # 1 <=> VectorNumber[13] # => -1
95
- # VectorNumber[12.1] <=> Complex(12.1, 0) # => 0
96
- # # This doesn't work as expected without NumericRefinements:
97
- # Complex(12.1, 0) <=> VectorNumber[12.1] # => nil
98
- #
99
- # # Any non-real comparison returns nil:
100
- # VectorNumber[12.1] <=> Complex(12.1, 1) # => nil
101
- # VectorNumber[12.1i] <=> 2 # => nil
102
- # VectorNumber["a"] <=> 2 # => nil
103
- #
104
- # @param other [Object]
105
- # @return [Integer]
106
- # @return [nil] if +self+ or +other+ isn't a real number.
107
- #
108
- # @see Comparable
109
- # @see NumericRefinements
110
- #
111
- # @since 0.2.0
112
- def <=>(other)
113
- return nil unless numeric?(1)
82
+ # Compare to +other+ and return -1, 0, or 1
83
+ # if +self+ is less than, equal, or larger than +other+ on real number line,
84
+ # or +nil+ if any or both values are non-real.
85
+ #
86
+ # Most VectorNumbers are non-real and therefore not comparable with this method.
87
+ #
88
+ # @example
89
+ # VectorNumber[130] <=> 12 # => 1
90
+ # 1 <=> VectorNumber[13] # => -1
91
+ # VectorNumber[12.1] <=> Complex(12.1, 0) # => 0
92
+ # # This doesn't work as expected without NumericRefinements:
93
+ # Complex(12.1, 0) <=> VectorNumber[12.1] # => nil
94
+ #
95
+ # # Any non-real comparison returns nil:
96
+ # VectorNumber[12.1] <=> Complex(12.1, 1) # => nil
97
+ # VectorNumber[12.1i] <=> 2 # => nil
98
+ # VectorNumber["a"] <=> 2 # => nil
99
+ #
100
+ # @see #numeric?
101
+ # @see Comparable
102
+ # @see NumericRefinements
103
+ #
104
+ # @param other [Object]
105
+ # @return [Integer]
106
+ # @return [nil] if +self+ or +other+ isn't a real number.
107
+ #
108
+ # @since 0.2.0
109
+ def <=>(other)
110
+ return nil unless numeric?(1)
114
111
 
115
- case other
116
- when VectorNumber
117
- other.numeric?(1) ? real <=> other.real : nil
118
- when Numeric
119
- other.imaginary.zero? ? real <=> other.real : nil
120
- else
121
- nil
122
- end
112
+ case other
113
+ when VectorNumber
114
+ other.numeric?(1) ? real <=> other.real : nil
115
+ when Numeric
116
+ other.imaginary.zero? ? real <=> other.real : nil
117
+ else
118
+ nil
123
119
  end
124
120
  end
125
121
  end