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.
@@ -16,6 +16,10 @@ class VectorNumber
16
16
  #
17
17
  # @since 0.2.0
18
18
  module NumericRefinements
19
+ # CommutativeShuttle refinement works only on 3.1, so it almost never actually runs.
20
+ # There are tests for the correct behavior, however, so it's fine.
21
+ # :nocov:
22
+
19
23
  # Refinement module to provide a +#<=>+ method that can work backwards.
20
24
  #
21
25
  # @note Currently only applies to Complex on *3.1*,
@@ -49,6 +53,7 @@ class VectorNumber
49
53
  warn "Numeric refinements are not available on Ruby < 3.1"
50
54
  end
51
55
  end
56
+ # :nocov:
52
57
 
53
58
  # Refinement module to change Kernel#BigDecimal so it works with +#to_d+.
54
59
  #
@@ -79,6 +84,7 @@ class VectorNumber
79
84
  end
80
85
  end
81
86
 
87
+ # :nocov:
82
88
  if defined?(BigDecimal)
83
89
  refine(Kernel) do
84
90
  import_methods BigDecimalToD
@@ -86,5 +92,6 @@ class VectorNumber
86
92
  warn "Numeric refinements are not available on Ruby < 3.1"
87
93
  end
88
94
  end
95
+ # :nocov:
89
96
  end
90
97
  end
@@ -1,150 +1,150 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class VectorNumber
4
- # Methods for querying state of the number.
5
- # Mostly modeled after {::Complex}.
6
- module Querying
7
- # Whether this VectorNumber can be considered strictly numeric, e.g. real or complex.
8
- #
9
- # @example
10
- # VectorNumber[2].numeric? # => true
11
- # VectorNumber[2, 3i].numeric? # => true
12
- # VectorNumber[2, "a"].numeric? # => false
13
- # VectorNumber[2, 3i].numeric?(1) # => false
14
- #
15
- # @param dimensions [Integer] number of dimensions to consider "numeric"
16
- # - 0 — zero
17
- # - 1 — real number
18
- # - 2 — complex number, etc.
19
- # @return [Boolean]
20
- # @raise [ArgumentError] if +dimensions+ is negative
21
- #
22
- # @since 0.2.0
23
- def numeric?(dimensions = 2)
24
- raise ArgumentError, "`dimensions` must be non-negative" unless dimensions >= 0
4
+ # @group Querying
5
+ #
6
+ # Mostly modelled after {::Complex}.
25
7
 
26
- size <= dimensions && (1..dimensions).count { @data[UNIT[_1]].nonzero? } == size
27
- end
8
+ # Returns +true+ if all non-zero dimensions in this VectorNumber are numeric (real or complex),
9
+ # and +false+ otherwise.
10
+ #
11
+ # This is exactly the opposite of {#nonnumeric?}.
12
+ #
13
+ # @example
14
+ # VectorNumber[2].numeric? # => true
15
+ # VectorNumber[2, 3i].numeric? # => true
16
+ # VectorNumber[2, "a"].numeric? # => false
17
+ # VectorNumber[2, 3i].numeric?(1) # => false
18
+ #
19
+ # @param dimensions [Integer] number of dimensions to consider "numeric"
20
+ # - 0 — zero
21
+ # - 1 — real number
22
+ # - 2 — complex number
23
+ # @return [Boolean]
24
+ # @raise [ArgumentError] if +dimensions+ is negative
25
+ #
26
+ # @since 0.2.0
27
+ def numeric?(dimensions = 2)
28
+ raise ArgumentError, "`dimensions` must be non-negative" unless dimensions >= 0
28
29
 
29
- # Whether this VectorNumber contains any non-numeric parts.
30
- #
31
- # @example
32
- # VectorNumber[2].nonnumeric? # => false
33
- # VectorNumber[2, 3i].nonnumeric? # => false
34
- # VectorNumber[2, "a"].nonnumeric? # => true
35
- # VectorNumber[2, 3i].nonnumeric?(1) # => true
36
- #
37
- # @param (see #numeric?)
38
- # @return (see #numeric?)
39
- # @raise (see #numeric?)
40
- #
41
- # @since 0.2.1
42
- def nonnumeric?(dimensions = 2) = !numeric?(dimensions)
43
-
44
- # Returns +true+ if all coefficients are finite, +false+ otherwise.
45
- #
46
- # @example
47
- # VectorNumber[2].finite? # => true
48
- # VectorNumber[Float::NAN].finite? # => false
49
- # VectorNumber["a"].mult(Float::INFINITY).finite? # => false
50
- #
51
- # @return [Boolean]
52
- #
53
- # @since 0.1.0
54
- def finite?
55
- all? { |_u, v| v.finite? }
56
- end
30
+ size <= dimensions &&
31
+ (0...dimensions).count { (unit = NUMERIC_UNITS[_1]) && @data[unit].nonzero? } == size
32
+ end
57
33
 
58
- # Returns +1+ if any coefficients are infinite, +nil+ otherwise.
59
- #
60
- # This behavior is the same as +Complex+'s.
61
- #
62
- # @example
63
- # VectorNumber[2].infinite? # => nil
64
- # VectorNumber[Float::NAN].infinite? # => 1
65
- # VectorNumber["a"].mult(-Float::INFINITY).infinite? # => 1
66
- #
67
- # @return [1, nil]
68
- #
69
- # @since 0.1.0
70
- def infinite?
71
- finite? ? nil : 1 # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
72
- end
34
+ # Returns +true+ if this VectorNumber contains any non-zero dimensions with non-numeric units,
35
+ # and +false+ otherwise.
36
+ #
37
+ # This is exactly the opposite of {#numeric?}.
38
+ #
39
+ # @example
40
+ # VectorNumber[2].nonnumeric? # => false
41
+ # VectorNumber[2, 3i].nonnumeric? # => false
42
+ # VectorNumber[2, "a"].nonnumeric? # => true
43
+ # VectorNumber[2, 3i].nonnumeric?(1) # => true
44
+ #
45
+ # @param (see #numeric?)
46
+ # @return (see #numeric?)
47
+ # @raise (see #numeric?)
48
+ #
49
+ # @since 0.2.1
50
+ def nonnumeric?(dimensions = 2) = !numeric?(dimensions)
73
51
 
74
- # Returns +true+ if there are no non-zero coefficients, and +false+ otherwise.
75
- #
76
- # @example
77
- # VectorNumber["c"].zero? # => false
78
- # VectorNumber[].zero? # => true
79
- #
80
- # @return [Boolean]
81
- #
82
- # @since 0.1.0
83
- def zero? = size.zero?
52
+ # Returns +true+ if all coefficients are finite, +false+ otherwise.
53
+ #
54
+ # @example
55
+ # VectorNumber[2].finite? # => true
56
+ # VectorNumber[Float::NAN].finite? # => false
57
+ # VectorNumber["a"].mult(Float::INFINITY).finite? # => false
58
+ #
59
+ # @return [Boolean]
60
+ def finite?
61
+ all? { |_u, v| v.finite? }
62
+ end
84
63
 
85
- # Returns +self+ if there are any non-zero coefficients, +nil+ otherwise.
86
- #
87
- # This behavior is the same as +Numeric+'s.
88
- #
89
- # @example
90
- # VectorNumber["ab", "cd"].nonzero? # => (1⋅'ab' + 1⋅'cd')
91
- # VectorNumber[].nonzero? # => nil
92
- #
93
- # @return [VectorNumber, nil]
94
- #
95
- # @since 0.1.0
96
- def nonzero?
97
- zero? ? nil : self # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
98
- end
64
+ # Returns +1+ if any coefficients are non-finite, +nil+ otherwise.
65
+ #
66
+ # This behavior is the same as +Complex+'s.
67
+ #
68
+ # @example
69
+ # VectorNumber[2].infinite? # => nil
70
+ # VectorNumber[Float::NAN].infinite? # => 1
71
+ # VectorNumber["a"].mult(-Float::INFINITY).infinite? # => 1
72
+ #
73
+ # @return [1, nil]
74
+ def infinite?
75
+ finite? ? nil : 1 # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
76
+ end
99
77
 
100
- # Returns +true+ if number is non-zero and all non-zero coefficients are positive,
101
- # and +false+ otherwise.
102
- #
103
- # @example
104
- # VectorNumber["a"].positive? # => true
105
- # VectorNumber[2].neg.positive? # => false
106
- # (VectorNumber["1"] - VectorNumber[1]).positive? # => false
107
- # VectorNumber[0].positive? # => false
108
- #
109
- # @return [Boolean]
110
- #
111
- # @since 0.1.0
112
- def positive?
113
- !zero? && all? { |_u, c| c.positive? }
114
- end
78
+ # Returns +true+ if there are no non-zero coefficients, and +false+ otherwise.
79
+ #
80
+ # This is synonymous with +size+ being 0.
81
+ #
82
+ # @example
83
+ # VectorNumber["c"].zero? # => false
84
+ # VectorNumber[].zero? # => true
85
+ #
86
+ # @see #size
87
+ #
88
+ # @return [Boolean]
89
+ def zero? = size.zero?
115
90
 
116
- # Returns +true+ if number is non-zero and all non-zero coefficients are negative,
117
- # and +false+ otherwise.
118
- #
119
- # @example
120
- # VectorNumber["a"].neg.negative? # => true
121
- # VectorNumber[-2].neg.negative? # => false
122
- # (VectorNumber["1"] - VectorNumber[1]).negative? # => false
123
- # VectorNumber[0].negative? # => false
124
- #
125
- # @return [Boolean]
126
- #
127
- # @since 0.1.0
128
- def negative?
129
- !zero? && all? { |_u, c| c.negative? }
130
- end
91
+ # Returns +self+ if there are any non-zero coefficients, +nil+ otherwise.
92
+ #
93
+ # This is synonymous with +size+ not being equal to 0.
94
+ # Behavior of returning self or +nil+ is the same as +Numeric+'s.
95
+ #
96
+ # @example
97
+ # VectorNumber["ab", "cd"].nonzero? # => (1⋅"ab" + 1⋅"cd")
98
+ # VectorNumber[].nonzero? # => nil
99
+ #
100
+ # @see #size
101
+ #
102
+ # @return [VectorNumber, nil]
103
+ def nonzero?
104
+ zero? ? nil : self # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
105
+ end
131
106
 
132
- # Always returns +false+, as vectors are not real numbers.
133
- #
134
- # This behavior is the same as +Complex+'s.
135
- #
136
- # @see #numeric?
137
- #
138
- # @return [false]
139
- #
140
- # @since 0.1.0
141
- def real? = false
107
+ # Returns +true+ if number is non-zero and all non-zero coefficients are positive,
108
+ # and +false+ otherwise.
109
+ #
110
+ # @example
111
+ # VectorNumber["a"].positive? # => true
112
+ # VectorNumber[2].neg.positive? # => false
113
+ # (VectorNumber["1"] - VectorNumber[1]).positive? # => false
114
+ # VectorNumber[0].positive? # => false
115
+ #
116
+ # @return [Boolean]
117
+ def positive?
118
+ !zero? && all? { |_u, c| c.positive? }
119
+ end
142
120
 
143
- # Always returns +false+, as vectors are not +Integer+s.
144
- #
145
- # @return [false]
146
- #
147
- # @since 0.2.1
148
- def integer? = false
121
+ # Returns +true+ if number is non-zero and all non-zero coefficients are negative,
122
+ # and +false+ otherwise.
123
+ #
124
+ # @example
125
+ # VectorNumber["a"].neg.negative? # => true
126
+ # VectorNumber[-2].neg.negative? # => false
127
+ # (VectorNumber["1"] - VectorNumber[1]).negative? # => false
128
+ # VectorNumber[0].negative? # => false
129
+ #
130
+ # @return [Boolean]
131
+ def negative?
132
+ !zero? && all? { |_u, c| c.negative? }
149
133
  end
134
+
135
+ # Always returns +false+, as vectors are not real numbers.
136
+ #
137
+ # This behavior is the same as +Complex+'s.
138
+ #
139
+ # @see #numeric?
140
+ #
141
+ # @return [false]
142
+ def real? = false
143
+
144
+ # Always returns +false+, as vectors are not +Integer+s.
145
+ #
146
+ # @return [false]
147
+ #
148
+ # @since 0.2.1
149
+ def integer? = false
150
150
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class VectorNumber
4
+ # Class for representing special units.
5
+ #
6
+ # The public API consists of:
7
+ # - +#==+/+#eql?+/+#equal?+ (from +Object+)
8
+ # - +#hash+ (from +Object+)
9
+ # - {#to_s}
10
+ # - {#inspect}
11
+ #
12
+ # @since 0.6.0
13
+ class SpecialUnit
14
+ # @api private
15
+ # @param unit [#to_s] name for {#inspect}
16
+ # @param text [String] text for {#to_s}
17
+ def initialize(unit, text)
18
+ @unit = unit
19
+ @text = text
20
+ end
21
+
22
+ # Get predefined string representation of the unit.
23
+ #
24
+ # @return [String]
25
+ def to_s
26
+ @text
27
+ end
28
+
29
+ # Get string representation of the unit for debugging.
30
+ #
31
+ # @return [String]
32
+ def inspect
33
+ "unit/#{@unit}"
34
+ end
35
+ end
36
+ end
@@ -1,96 +1,105 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class VectorNumber
4
- # Methods and options for string representation.
5
- module Stringifying
6
- # Predefined symbols for multiplication to display between unit and coefficient.
7
- #
8
- # @return [Hash{Symbol => String}]
9
- #
10
- # @since 0.1.0
11
- MULT_STRINGS = {
12
- asterisk: "*", # U+002A
13
- cross: "×", # U+00D7
14
- dot: "⋅", # U+22C5
15
- invisible: "⁢", # U+2062, zero-width multiplication operator
16
- space: " ",
17
- none: "",
18
- }.freeze
4
+ # Predefined symbols for multiplication to display between unit and coefficient.
5
+ #
6
+ # @return [Hash{Symbol => String}]
7
+ MULT_STRINGS = {
8
+ asterisk: "*", # U+002A
9
+ cross: "×", # U+00D7
10
+ dot: "⋅", # U+22C5
11
+ invisible: "⁢", # U+2062, zero-width multiplication operator
12
+ space: " ",
13
+ none: "",
14
+ }.freeze
19
15
 
20
- # Get a string representation of the vector.
21
- #
22
- # @example
23
- # VectorNumber[5, "s"].to_s # => "5 + 1⋅'s'"
24
- # VectorNumber["s", 5].to_s # => "1⋅'s' + 5"
25
- # @example with :mult argument
26
- # VectorNumber[5, "s"].to_s(mult: :asterisk) # => "5 + 1*'s'"
27
- # @example :mult option specified for the vector
28
- # VectorNumber[5, "s", mult: :none].to_s # => "5 + 1's'"
29
- #
30
- # @param mult [Symbol, String]
31
- # text to use between coefficient and unit,
32
- # can be one of the keys in {MULT_STRINGS} or an arbitrary string
33
- # @return [String]
34
- # @raise [ArgumentError]
35
- # if +mult+ is not a String and is not in {MULT_STRINGS}'s keys
36
- #
37
- # @since 0.1.0
38
- def to_s(mult: options[:mult])
39
- return "0" if zero?
16
+ # @group Miscellaneous methods
40
17
 
41
- result = +""
42
- each_with_index do |(unit, coefficient), index|
18
+ # Return string representation of the vector.
19
+ #
20
+ # An optional block can be supplied to provide customized substrings
21
+ # for each unit and coefficient pair.
22
+ # Care needs to be taken in handling +VectorNumber::R+ and +VectorNumber::I+ units.
23
+ # {.numeric_unit?} can be used to check if a particular unit needs special handling.
24
+ #
25
+ # @example
26
+ # VectorNumber[5, "s"].to_s # => "5 + 1⋅\"s\""
27
+ # VectorNumber["s", 5].to_s # => "1⋅\"s\" + 5"
28
+ # @example with :mult argument
29
+ # VectorNumber[5, :s].to_s(mult: :asterisk) # => "5 + 1*s"
30
+ # (-VectorNumber[5, :s]).to_s(mult: "~~~") # => "-5 - 1~~~s"
31
+ # @example with a block
32
+ # VectorNumber[5, :s].to_s { |k, v| "#{format("%+.0f", v)}%#{k}" } # => "+5%1+1%s"
33
+ # VectorNumber[5, :s].to_s(mult: :cross) { |k, v, i, op|
34
+ # "#{',' unless i.zero?}#{v}#{op+k.to_s unless k == VectorNumber::R}"
35
+ # } # => "5,1×s"
36
+ #
37
+ # @param mult [Symbol, String]
38
+ # text to use between coefficient and unit,
39
+ # can be one of the keys in {MULT_STRINGS} or an arbitrary string
40
+ # @yieldparam unit [Object]
41
+ # @yieldparam coefficient [Numeric]
42
+ # @yieldparam index [Integer]
43
+ # @yieldparam operator [String]
44
+ # @yieldreturn [String] a string for this unit and coefficient
45
+ # @return [String]
46
+ # @raise [ArgumentError]
47
+ # if +mult+ is not a String and is not in {MULT_STRINGS}'s keys
48
+ def to_s(mult: :dot, &block)
49
+ if !mult.is_a?(String) && !MULT_STRINGS.key?(mult)
50
+ raise ArgumentError, "unknown key #{mult.inspect}", caller
51
+ end
52
+ return "0" if zero?
53
+
54
+ operator = mult.is_a?(String) ? mult : MULT_STRINGS[mult]
55
+ build_string(operator, &block)
56
+ end
57
+
58
+ # Return string representation of the vector.
59
+ #
60
+ # This is similar to +Complex#inspect+: it returns result of {#to_s} in round brackets.
61
+ #
62
+ # @example
63
+ # VectorNumber[5, :s].inspect # => "(5 + 1⋅s)"
64
+ #
65
+ # @return [String]
66
+ #
67
+ # @see to_s
68
+ def inspect
69
+ "(#{self})"
70
+ end
71
+
72
+ private
73
+
74
+ # @param operator [String]
75
+ # @return [String]
76
+ def build_string(operator)
77
+ result = +""
78
+ each_with_index do |(unit, coefficient), index|
79
+ if block_given?
80
+ result << (yield unit, coefficient, index, operator).to_s
81
+ else
43
82
  if index.zero?
44
83
  result << "-" if coefficient.negative?
45
84
  else
46
85
  result << (coefficient.positive? ? " + " : " - ")
47
86
  end
48
- result << value_to_s(unit, coefficient.abs, mult: mult)
87
+ result << value_to_s(unit, coefficient.abs, operator)
49
88
  end
50
- result
51
89
  end
90
+ result
91
+ end
52
92
 
53
- # Get a string representation of the vector.
54
- #
55
- # This is similar to +Complex#inspect+: it returns result of {#to_s} in round brackets.
56
- #
57
- # @example
58
- # VectorNumber[5, "s"].inspect # => "(5 + 1⋅'s')"
59
- #
60
- # @return [String]
61
- #
62
- # @see to_s
63
- #
64
- # @since 0.1.0
65
- def inspect
66
- # TODO: Probably make this independent of options.
67
- "(#{self})"
68
- end
69
-
70
- private
71
-
72
- # @param unit [Object]
73
- # @param coefficient [Numeric]
74
- # @param mult [Symbol, String]
75
- # @return [String]
76
- # @raise [ArgumentError] if +mult+ is not in {MULT_STRINGS}'s keys
77
- #
78
- # @since 0.1.0
79
- def value_to_s(unit, coefficient, mult:)
80
- if !mult.is_a?(String) && !MULT_STRINGS.key?(mult)
81
- raise ArgumentError, "unknown key #{mult.inspect}", caller
82
- end
83
-
84
- case unit
85
- when R
86
- coefficient.to_s
87
- when I
88
- "#{coefficient}i"
89
- else
90
- unit = "'#{unit}'" if unit.is_a?(String)
91
- operator = mult.is_a?(String) ? mult : MULT_STRINGS[mult]
92
- "#{coefficient}#{operator}#{unit}"
93
- end
93
+ # @param unit [Object]
94
+ # @param coefficient [Numeric]
95
+ # @param operator [String]
96
+ # @return [String]
97
+ def value_to_s(unit, coefficient, operator)
98
+ if NUMERIC_UNITS.include?(unit)
99
+ "#{coefficient}#{unit}"
100
+ else
101
+ unit = unit.inspect if unit.is_a?(String)
102
+ "#{coefficient}#{operator}#{unit}"
94
103
  end
95
104
  end
96
105
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  class VectorNumber
4
4
  # @return [String]
5
- VERSION = "0.4.3"
5
+ VERSION = "0.6.0"
6
6
  end