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 +4 -4
- data/README.md +47 -22
- data/doc/vector_space.svg +94 -0
- data/lib/vector_number/comparing.rb +103 -107
- data/lib/vector_number/converting.rb +137 -147
- data/lib/vector_number/enumerating.rb +94 -108
- data/lib/vector_number/math_converting.rb +113 -109
- data/lib/vector_number/mathing.rb +305 -312
- data/lib/vector_number/numeric_refinements.rb +7 -0
- data/lib/vector_number/querying.rb +136 -136
- data/lib/vector_number/special_unit.rb +36 -0
- data/lib/vector_number/stringifying.rb +89 -80
- data/lib/vector_number/version.rb +1 -1
- data/lib/vector_number.rb +155 -126
- data/sig/vector_number.rbs +141 -168
- metadata +14 -11
data/lib/vector_number.rb
CHANGED
|
@@ -1,123 +1,196 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
#
|
|
3
|
+
# VectorNumber provides a Numeric-like experience for doing arithmetics on heterogeneous objects,
|
|
4
|
+
# with more advanced operations based on real vector spaces available when needed.
|
|
5
|
+
#
|
|
6
|
+
# VectorNumber inherits from +Object+ and includes +Enumerable+ and +Comparable+.
|
|
7
|
+
# It implements mostly the same interface as +Numeric+ classes, but can {#coerce} any value.
|
|
8
|
+
# Its behavior follows +Complex+ when possible.
|
|
9
|
+
#
|
|
10
|
+
# All instances are frozen after creation.
|
|
11
|
+
#
|
|
12
|
+
# **Creation**
|
|
13
|
+
# - {.[]}: the simplest way to create a VectorNumber
|
|
14
|
+
# - {#initialize}: an alternative way, with some advanced features
|
|
15
|
+
#
|
|
16
|
+
# **Comparing**
|
|
17
|
+
# - {#==}: test for equal value
|
|
18
|
+
# - {#eql?}: test for equal value and type
|
|
19
|
+
# - {#<=>}: compare real values
|
|
20
|
+
# - {#hash}: calculate hash value for use in +Hash+es
|
|
21
|
+
#
|
|
22
|
+
# **Querying**
|
|
23
|
+
# - {#zero?}: check if all coefficients are zero
|
|
24
|
+
# - {#nonzero?}: check if any coefficient is non-zero
|
|
25
|
+
# - {#positive?}: check if all coefficients are positive
|
|
26
|
+
# - {#negative?}: check if all coefficients are negative
|
|
27
|
+
# - {#finite?}: check if all coefficients are finite
|
|
28
|
+
# - {#infinite?}: check if any coefficient is non-finite
|
|
29
|
+
# - {#numeric?}: test if value is real or complex
|
|
30
|
+
# - {#nonnumeric?}: test if value is not real or complex
|
|
31
|
+
# - {#integer?}: +false+
|
|
32
|
+
# - {#real?}: +false+
|
|
33
|
+
#
|
|
34
|
+
# **Unary** **math** **operations**
|
|
35
|
+
# - {#+}/{#dup}: return self
|
|
36
|
+
# - {#-@}/{#neg}: negate value
|
|
37
|
+
# - {#abs}/{#magnitude}: return absolute value (magnitude, length)
|
|
38
|
+
# - {#abs2}: return square of absolute value
|
|
39
|
+
#
|
|
40
|
+
# **Arithmetic** **operations**
|
|
41
|
+
# - {#coerce}: convert any object to a VectorNumber
|
|
42
|
+
# - {#+}/{#add}: add object
|
|
43
|
+
# - {#-}/{#sub}: subtract object
|
|
44
|
+
# - {#*}/{#mult}: multiply (scale) by a real number
|
|
45
|
+
# - {#/}/{#quo}: divide (scale) by a real number
|
|
46
|
+
# - {#fdiv}: divide (scale) by a real number, using +fdiv+
|
|
47
|
+
# - {#div}: perform integer division
|
|
48
|
+
# - {#%}/{#modulo}: return modulus from integer division
|
|
49
|
+
# - {#divmod}: combination of {#div} and {#modulo}
|
|
50
|
+
# - {#remainder}: return remainder from integer division
|
|
51
|
+
#
|
|
52
|
+
# **Rounding**
|
|
53
|
+
# - {#round}: round each coefficient
|
|
54
|
+
# - {#ceil}: round each coefficient up towards +∞
|
|
55
|
+
# - {#floor}: round each coefficient down towards -∞
|
|
56
|
+
# - {#truncate}: truncate each coefficient towards 0
|
|
57
|
+
#
|
|
58
|
+
# **Type** **conversion**
|
|
59
|
+
# - {#real}: return real part
|
|
60
|
+
# - {#imag}/{#imaginary}: return imaginary part
|
|
61
|
+
# - {#to_i}/{#to_int}: convert to +Integer+ if possible
|
|
62
|
+
# - {#to_f}: convert to +Float+ if possible
|
|
63
|
+
# - {#to_r}: convert to +Rational+ if possible
|
|
64
|
+
# - {#to_d}: convert to +BigDecimal+ if possible
|
|
65
|
+
# - {#to_c}: convert to +Complex+ if possible
|
|
66
|
+
#
|
|
67
|
+
# **Hash-like** **operations**
|
|
68
|
+
# - {#each}/{#each_pair}: iterate through every pair of unit and coefficient
|
|
69
|
+
# - {#[]}: get coefficient by unit
|
|
70
|
+
# - {#unit?}/{#key?}: check if a unit has a non-zero coefficient
|
|
71
|
+
# - {#units}/{#keys}: return an array of units
|
|
72
|
+
# - {#coefficients}/#{values}: return an array of coefficients
|
|
73
|
+
# - {#to_h}: convert to Hash
|
|
74
|
+
#
|
|
75
|
+
# **Miscellaneous** **methods**
|
|
76
|
+
# - {.numeric_unit?}: check if a unit represents a numeric dimension
|
|
77
|
+
# - {#size}: number of non-zero dimensions
|
|
78
|
+
# - {#to_s}: return string representation suitable for printing
|
|
79
|
+
# - {#inspect}: return string representation suitable for display
|
|
80
|
+
# - {#dup}/{#+}: return self
|
|
81
|
+
# - {#clone}: return self
|
|
82
|
+
#
|
|
83
|
+
# @since 0.1.0
|
|
13
84
|
class VectorNumber
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
85
|
+
require_relative "vector_number/comparing"
|
|
86
|
+
require_relative "vector_number/converting"
|
|
87
|
+
require_relative "vector_number/enumerating"
|
|
88
|
+
require_relative "vector_number/math_converting"
|
|
89
|
+
require_relative "vector_number/mathing"
|
|
90
|
+
require_relative "vector_number/querying"
|
|
91
|
+
require_relative "vector_number/stringifying"
|
|
92
|
+
require_relative "vector_number/version"
|
|
21
93
|
|
|
22
|
-
|
|
23
|
-
# Unknown options will be rejected when creating a vector.
|
|
24
|
-
#
|
|
25
|
-
# @return [Array<Symbol>]
|
|
26
|
-
#
|
|
27
|
-
# @since 0.2.0
|
|
28
|
-
KNOWN_OPTIONS = %i[mult].freeze
|
|
94
|
+
require_relative "vector_number/special_unit"
|
|
29
95
|
|
|
30
|
-
#
|
|
96
|
+
# List of special numeric unit constants.
|
|
31
97
|
#
|
|
32
|
-
# @
|
|
98
|
+
# @since 0.6.0
|
|
99
|
+
NUMERIC_UNITS = [SpecialUnit.new("1", "").freeze, SpecialUnit.new("i", "i").freeze].freeze
|
|
100
|
+
# Constant for real unit (1).
|
|
33
101
|
#
|
|
34
|
-
#
|
|
35
|
-
DEFAULT_OPTIONS = { mult: :dot }.freeze
|
|
36
|
-
|
|
37
|
-
# Get a unit for +n+th numeric dimension, where 1 is real, 2 is imaginary.
|
|
102
|
+
# Its string representation is an empty string.
|
|
38
103
|
#
|
|
39
|
-
# @since 0.
|
|
40
|
-
|
|
41
|
-
# Constant for
|
|
104
|
+
# @since 0.6.0
|
|
105
|
+
R = NUMERIC_UNITS[0]
|
|
106
|
+
# Constant for imaginary unit (i).
|
|
42
107
|
#
|
|
43
|
-
#
|
|
44
|
-
R = UNIT[1]
|
|
45
|
-
# Constant for imaginary unit.
|
|
108
|
+
# Its string representation is "i".
|
|
46
109
|
#
|
|
47
|
-
# @since 0.
|
|
48
|
-
I =
|
|
110
|
+
# @since 0.6.0
|
|
111
|
+
I = NUMERIC_UNITS[1]
|
|
49
112
|
|
|
50
|
-
#
|
|
113
|
+
# @group Creation
|
|
114
|
+
# Create new VectorNumber from a list of values.
|
|
51
115
|
#
|
|
52
116
|
# @example
|
|
53
117
|
# VectorNumber[1, 2, 3] # => (6)
|
|
54
118
|
# VectorNumber[[1, 2, 3]] # => (1⋅[1, 2, 3])
|
|
55
|
-
# VectorNumber["b", VectorNumber::I, mult: :asterisk] # => (1*'b' + 1i)
|
|
56
119
|
# VectorNumber[] # => (0)
|
|
57
120
|
# VectorNumber["b", VectorNumber["b"]] # => (2⋅'b')
|
|
58
121
|
# VectorNumber["a", "b", "a"] # => (2⋅'a' + 1⋅'b')
|
|
59
122
|
#
|
|
60
123
|
# @param values [Array<Object>] values to put in the number
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# @
|
|
124
|
+
def self.[](*values, **nil)
|
|
125
|
+
new(values)
|
|
126
|
+
end
|
|
127
|
+
# @endgroup
|
|
128
|
+
|
|
129
|
+
# Check if an object is a unit representing a numeric dimension
|
|
130
|
+
# (real or imaginary unit).
|
|
65
131
|
#
|
|
66
|
-
# @
|
|
67
|
-
|
|
68
|
-
|
|
132
|
+
# @example
|
|
133
|
+
# VectorNumber.numeric_unit?(VectorNumber::R) # => true
|
|
134
|
+
# VectorNumber.numeric_unit?(VectorNumber::I) # => true
|
|
135
|
+
# VectorNumber.numeric_unit?(:i) # => false
|
|
136
|
+
#
|
|
137
|
+
# @param unit [Object]
|
|
138
|
+
# @return [Boolean]
|
|
139
|
+
#
|
|
140
|
+
# @since 0.6.0
|
|
141
|
+
def self.numeric_unit?(unit)
|
|
142
|
+
NUMERIC_UNITS.include?(unit)
|
|
69
143
|
end
|
|
70
144
|
|
|
71
145
|
# Number of non-zero dimensions.
|
|
72
146
|
#
|
|
73
147
|
# @return [Integer]
|
|
74
|
-
#
|
|
75
|
-
# @since 0.1.0
|
|
76
148
|
attr_reader :size
|
|
77
149
|
|
|
78
|
-
#
|
|
150
|
+
# @group Creation
|
|
151
|
+
# Create new VectorNumber from an array of +values+,
|
|
152
|
+
# possibly modifying coefficients with a block.
|
|
79
153
|
#
|
|
80
|
-
#
|
|
154
|
+
# Using +VectorNumber.new([values...])+ directly is more efficient than +VectorNumber[values...]+.
|
|
81
155
|
#
|
|
82
|
-
#
|
|
156
|
+
# +values+ can be:
|
|
157
|
+
# - an array of values (see {.[]});
|
|
158
|
+
# - a VectorNumber to copy;
|
|
159
|
+
# - a hash in the format returned by {#to_h};
|
|
160
|
+
# - +nil+ to specify a 0-sized vector (same as an empty array or hash).
|
|
83
161
|
#
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
#
|
|
88
|
-
# possibly modifying coefficients with a block.
|
|
162
|
+
# Using a hash as +values+ is an advanced technique which allows to quickly
|
|
163
|
+
# construct a VectorNumber with desired units and coefficients,
|
|
164
|
+
# but it can also lead to unexpected results if care is not taken
|
|
165
|
+
# to provide only valid keys and values.
|
|
89
166
|
#
|
|
90
167
|
# @example
|
|
91
168
|
# VectorNumber.new(1, 2, 3) # ArgumentError
|
|
92
169
|
# VectorNumber.new([1, 2, 3]) # => (6)
|
|
93
|
-
# VectorNumber.new(["b", VectorNumber::I]
|
|
170
|
+
# VectorNumber.new(["b", VectorNumber::I]) # => (1⋅"b" + 1i)
|
|
94
171
|
# VectorNumber.new # => (0)
|
|
95
172
|
# @example with a block
|
|
96
|
-
# VectorNumber.new(["a", "b", "c", 3]) { _1 * 2 } # => (2⋅
|
|
97
|
-
# VectorNumber.new(["a", "b", "c", 3], &:-@) # => (-1⋅
|
|
173
|
+
# VectorNumber.new(["a", "b", "c", 3]) { _1 * 2 } # => (2⋅"a" + 2⋅"b" + 2⋅"c" + 6)
|
|
174
|
+
# VectorNumber.new(["a", "b", "c", 3], &:-@) # => (-1⋅"a" - 1⋅"b" - 1⋅"c" - 3)
|
|
98
175
|
# VectorNumber.new(["a", "b", "c", 3], &:digits) # RangeError
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
# @
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
# @yieldreturn [Integer, Float, Rational, BigDecimal] new coefficient
|
|
109
|
-
# @raise [RangeError] if any pesky non-reals get where they shouldn't
|
|
110
|
-
def initialize(values = nil, options = nil, &transform)
|
|
111
|
-
# @type var options: Hash[Symbol, Object]
|
|
176
|
+
# @example using hash for values
|
|
177
|
+
# v = VectorNumber.new({1 => 15, "a" => 3.4, nil => -3}) # => (15 + 3.4⋅"a" - 3⋅)
|
|
178
|
+
# v.to_h # => {1 => 15, "a" => 3.4, nil => -3}
|
|
179
|
+
#
|
|
180
|
+
# @param values [Array, VectorNumber, Hash{Object => Numeric}, nil] values for this vector
|
|
181
|
+
# @yieldparam coefficient [Numeric] a real number
|
|
182
|
+
# @yieldreturn [Numeric] new coefficient
|
|
183
|
+
# @raise [RangeError] if a block is used and it returns a non-number or non-real number
|
|
184
|
+
def initialize(values = nil, **nil, &transform)
|
|
112
185
|
initialize_from(values)
|
|
113
186
|
apply_transform(&transform)
|
|
114
187
|
finalize_contents
|
|
115
|
-
save_options(options, values: values)
|
|
116
|
-
@options.freeze
|
|
117
188
|
@data.freeze
|
|
118
189
|
freeze
|
|
119
190
|
end
|
|
120
191
|
|
|
192
|
+
# @group Miscellaneous methods
|
|
193
|
+
|
|
121
194
|
# Return self.
|
|
122
195
|
#
|
|
123
196
|
# @return [VectorNumber]
|
|
@@ -149,11 +222,11 @@ class VectorNumber
|
|
|
149
222
|
# Create new VectorNumber from a value or self, optionally applying a transform.
|
|
150
223
|
#
|
|
151
224
|
# @param from [Object] self if not specified
|
|
152
|
-
# @yieldparam coefficient [
|
|
153
|
-
# @yieldreturn [
|
|
225
|
+
# @yieldparam coefficient [Numeric] a real number
|
|
226
|
+
# @yieldreturn [Numeric] new coefficient
|
|
154
227
|
# @return [VectorNumber]
|
|
155
228
|
def new(from = self, &transform)
|
|
156
|
-
self.class.new(from,
|
|
229
|
+
self.class.new(from, &transform)
|
|
157
230
|
end
|
|
158
231
|
|
|
159
232
|
# Check if +other+ is a real number.
|
|
@@ -162,16 +235,12 @@ class VectorNumber
|
|
|
162
235
|
#
|
|
163
236
|
# @param value [Object]
|
|
164
237
|
# @return [Boolean]
|
|
165
|
-
#
|
|
166
|
-
# @since 0.1.0
|
|
167
238
|
def real_number?(value)
|
|
168
239
|
(value.is_a?(Numeric) && value.real?) || (value.is_a?(self.class) && value.numeric?(1))
|
|
169
240
|
end
|
|
170
241
|
|
|
171
|
-
# @param values [Array, Hash{Object =>
|
|
242
|
+
# @param values [Array, Hash{Object => Numeric}, VectorNumber, nil]
|
|
172
243
|
# @return [void]
|
|
173
|
-
#
|
|
174
|
-
# @since 0.1.0
|
|
175
244
|
def initialize_from(values)
|
|
176
245
|
@data = values.to_h and return if values.is_a?(VectorNumber)
|
|
177
246
|
|
|
@@ -190,8 +259,6 @@ class VectorNumber
|
|
|
190
259
|
|
|
191
260
|
# @param value [VectorNumber, Numeric, Object]
|
|
192
261
|
# @return [void]
|
|
193
|
-
#
|
|
194
|
-
# @since 0.1.0
|
|
195
262
|
def add_value_to_data(value)
|
|
196
263
|
case value
|
|
197
264
|
when Numeric
|
|
@@ -205,8 +272,6 @@ class VectorNumber
|
|
|
205
272
|
|
|
206
273
|
# @param value [Numeric]
|
|
207
274
|
# @return [void]
|
|
208
|
-
#
|
|
209
|
-
# @since 0.1.0
|
|
210
275
|
def add_numeric_value_to_data(value)
|
|
211
276
|
@data[R] += value.real
|
|
212
277
|
# Most numbers will be real, and this extra condition appreciably speeds up addition,
|
|
@@ -214,10 +279,8 @@ class VectorNumber
|
|
|
214
279
|
@data[I] += value.imaginary unless value.real?
|
|
215
280
|
end
|
|
216
281
|
|
|
217
|
-
# @param vector [VectorNumber, Hash{Object =>
|
|
282
|
+
# @param vector [VectorNumber, Hash{Object => Numeric}]
|
|
218
283
|
# @return [void]
|
|
219
|
-
#
|
|
220
|
-
# @since 0.1.0
|
|
221
284
|
def add_vector_to_data(vector)
|
|
222
285
|
vector.each_pair do |unit, coefficient|
|
|
223
286
|
raise RangeError, "#{coefficient} is not a real number" unless real_number?(coefficient)
|
|
@@ -226,12 +289,10 @@ class VectorNumber
|
|
|
226
289
|
end
|
|
227
290
|
end
|
|
228
291
|
|
|
229
|
-
# @yieldparam coefficient [
|
|
230
|
-
# @yieldreturn [
|
|
292
|
+
# @yieldparam coefficient [Numeric] a real number
|
|
293
|
+
# @yieldreturn [Numeric] new coefficient
|
|
231
294
|
# @return [void]
|
|
232
295
|
# @raise [RangeError]
|
|
233
|
-
#
|
|
234
|
-
# @since 0.1.0
|
|
235
296
|
def apply_transform
|
|
236
297
|
return unless block_given?
|
|
237
298
|
|
|
@@ -243,40 +304,8 @@ class VectorNumber
|
|
|
243
304
|
end
|
|
244
305
|
end
|
|
245
306
|
|
|
246
|
-
# @param options [Hash{Symbol => Object}, nil]
|
|
247
|
-
# @param values [Object] initializing object
|
|
248
|
-
# @return [void]
|
|
249
|
-
#
|
|
250
|
-
# @since 0.1.0
|
|
251
|
-
def save_options(options, values:)
|
|
252
|
-
@options =
|
|
253
|
-
case values
|
|
254
|
-
in VectorNumber
|
|
255
|
-
merge_options(values.options, options)
|
|
256
|
-
in Array[*, VectorNumber => vector, *]
|
|
257
|
-
merge_options(vector.options, options)
|
|
258
|
-
else
|
|
259
|
-
merge_options(DEFAULT_OPTIONS, options)
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
# @param base_options [Hash{Symbol => Object}]
|
|
264
|
-
# @param added_options [Hash{Symbol => Object}, nil]
|
|
265
|
-
# @return [Hash{Symbol => Object}]
|
|
266
|
-
#
|
|
267
|
-
# @since 0.3.0
|
|
268
|
-
def merge_options(base_options, added_options)
|
|
269
|
-
return base_options if !added_options || added_options.empty?
|
|
270
|
-
# Optimization for the common case of passing options through #new.
|
|
271
|
-
return base_options if added_options.equal?(base_options)
|
|
272
|
-
|
|
273
|
-
base_options.merge(added_options).slice(*KNOWN_OPTIONS)
|
|
274
|
-
end
|
|
275
|
-
|
|
276
307
|
# Compact coefficients, calculate size and freeze data.
|
|
277
308
|
# @return [void]
|
|
278
|
-
#
|
|
279
|
-
# @since 0.1.0
|
|
280
309
|
def finalize_contents
|
|
281
310
|
@data.delete_if { |_u, c| c.zero? }
|
|
282
311
|
@data.freeze
|