chris_lib 2.2.1 → 2.2.2

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: 1d8fd49f3985821fec93f9e490e89fca4cbe483047cdeb8b8b794eaa246e317e
4
- data.tar.gz: 726e074b5edc14e8e274b9b092a89cf92b5a39f71ecf3ba0f6ea8a245e11b430
3
+ metadata.gz: ed68c4652e1c8bb9171e1c6faa07dbcfd8dfacfc4ade1eefba8f64698040a367
4
+ data.tar.gz: 7b6e5522b4861ea44572dcee282119409383e2434bff297229ed5330b8191629
5
5
  SHA512:
6
- metadata.gz: 6456985f4b3418a424c6f9bcd19bce4b9d1783b91c55730214d68b5ea259c1345b05c88115303b89df4a7720eba7d012a93887004b823c5f65e19018e08b0b75
7
- data.tar.gz: 037fccb47857527464af9987b088d492f85559219d6af30c9677ad18ecbc7a0a5897b11f57cd151260998c73e091f9ed929b11efcc420187a5b338fed580bc59
6
+ metadata.gz: f10142175fdfbd73a02475c480ab41f6040355c00fd2e6382bd8cfc92fe8328ba34255f9bdd0a165bcf261ef58c1afff88fd73b39e1c29923df6e79b13892c61
7
+ data.tar.gz: 2bdda10220d21a555725f6c88d71746f5db73813c67a9f587e6ada6792c1a1817c003fbead5dae9aad481e4c5fd236c7717ab898f8172b123f3ec8bff9df936b
@@ -1,30 +1,38 @@
1
1
  require 'matrix'
2
2
  require 'quaternion'
3
3
  Quaternion.class_eval do
4
- # rounds each element of quaternion to n decimal places
4
+ # @param n [Integer] number of decimal places to retain
5
+ # @return [Quaternion] a new quaternion with each component rounded
5
6
  def round(n)
6
7
  q = self
7
8
  Quaternion.new(q[0].round(n), q[1].round(n), q[2].round(n), q[3].round(n))
8
9
  end
9
10
 
10
- # Creates identity quaternion
11
+ # @return [Quaternion] the identity quaternion (1, 0, 0, 0)
11
12
  def self.identity
12
13
  Quaternion.new(1.0, 0.0, 0.0, 0.0)
13
14
  end
14
15
 
15
- # Creates zero quaternion
16
+ # @return [Quaternion] the zero quaternion (0, 0, 0, 0)
16
17
  def self.zero
17
18
  Quaternion.new(0.0, 0.0, 0.0, 0.0)
18
19
  end
19
20
 
20
21
  # Creates quaternion using square brackets (for compatability with Matrix and Vector)
21
- # see https://stackoverflow.com/questions/69155796/how-to-define-a-class-method-when-using-class-eval
22
+ # @see https://stackoverflow.com/questions/69155796/how-to-define-a-class-method-when-using-class-eval
23
+ # @param q0 [Numeric]
24
+ # @param q1 [Numeric]
25
+ # @param q2 [Numeric]
26
+ # @param q3 [Numeric]
27
+ # @return [Quaternion]
22
28
  def self.[](q0, q1, q2, q3)
23
29
  Quaternion.new(q0, q1, q2, q3)
24
30
  end
25
31
  end
26
32
 
27
33
  Integer.class_eval do
34
+ # @return [Integer] factorial of the number
35
+ # @raise [RuntimeError] when the receiver is greater than 20 to avoid overflow
28
36
  def factorial
29
37
  n = self
30
38
  if n > 20
@@ -36,49 +44,63 @@ Integer.class_eval do
36
44
  end
37
45
 
38
46
  Matrix.class_eval do
39
- # right pseudo-inverse for linearly independent rows
47
+ # Right pseudo-inverse for linearly independent rows
48
+ # @return [Matrix]
49
+ # @raise [ExceptionForMatrix::ErrNotRegular] when the matrix is rank-deficient
40
50
  def pinv
41
51
  full_rank = (rank == [row_count, column_count].min)
42
52
  raise ExceptionForMatrix::ErrNotRegular unless full_rank
43
53
  transpose * (self * transpose).inv
44
54
  end
45
55
 
46
- # for linearly independent rows
56
+ # Alias for {#pinv} maintained for backwards compatibility
57
+ # @return [Matrix]
58
+ # @raise [ExceptionForMatrix::ErrNotRegular] when the matrix is rank-deficient
47
59
  def pinv_right
48
60
  full_rank = (rank == [row_count, column_count].min)
49
61
  raise ExceptionForMatrix::ErrNotRegular unless full_rank
50
62
  transpose * (self * transpose).inv
51
63
  end
52
64
 
53
- # for linearly independent columns
65
+ # Left pseudo-inverse for linearly independent columns
66
+ # @return [Matrix]
54
67
  def pinv_left
55
68
  (transpose * self).inv * transpose
56
69
  end
57
70
  end
58
71
 
59
72
  Array.class_eval do
60
- # converts radians to degrees
61
- # Also rounds to n_decimal places unless n_decimimals is nil
73
+ # Converts radians to degrees for each element
74
+ # @param n_decimals [Integer, nil] decimal places to round to, pass nil for no rounding
75
+ # @return [Array<Float>] converted values
62
76
  def to_deg(n_decimals = nil)
63
77
  map { |e| e.to_deg(n_decimals) }
64
78
  end
65
79
 
66
- # converts degrees to radians
67
- # Also rounds to n_decimal places unless n_decimimals is nil
80
+ # Converts degrees to radians for each element
81
+ # @param n_decimals [Integer, nil] decimal places to round to, pass nil for no rounding
82
+ # @return [Array<Float>] converted values
68
83
  def to_rad(n_decimals = nil)
69
84
  map { |e| e.to_rad(n_decimals) }
70
85
  end
71
86
 
72
- # round each element
87
+ # Rounds each element in-place
88
+ # @param decimal_points [Integer] decimal places to round to
89
+ # @return [Array<Numeric>] rounded values
73
90
  def round(decimal_points = 0)
74
91
  map { |e| e.round(decimal_points) }
75
92
  end
76
93
 
94
+ # Rounds elements using {Float#eround}
95
+ # @param decimal_points [Integer]
96
+ # @return [Array<Float>]
77
97
  def eround(decimal_points = 0)
78
98
  map { |e| e.eround(decimal_points) }
79
99
  end
80
100
 
81
- # mean of array
101
+ # Calculates the arithmetic mean of elements
102
+ # @return [Numeric, Vector]
103
+ # @raise [RuntimeError] when the array is empty
82
104
  def mean
83
105
  raise 'chris_lib - f - Length must be greater than 0.' if length < 1
84
106
  return sum.to_f / length unless all? { |e| e.class == Vector }
@@ -86,24 +108,29 @@ Array.class_eval do
86
108
  Vector.elements ary
87
109
  end
88
110
 
89
- # unbiased sample variance of array
111
+ # Computes the unbiased sample variance
112
+ # @return [Float]
113
+ # @raise [RuntimeError] when the array has fewer than two elements
90
114
  def var
91
115
  raise 'Length must be greater than 1' if length < 2
92
116
  mu = mean
93
117
  map { |v| (v**2 - mu**2) }.sum.to_f / (length - 1)
94
118
  end
95
119
 
96
- # sample standard deviation
120
+ # Computes the sample standard deviation
121
+ # @return [Float]
97
122
  def std
98
123
  return 0 if var < 0.0 # perhaps due to rounding errors
99
124
  Math.sqrt(var)
100
125
  end
101
126
 
102
- # standard error of sample mean
127
+ # Standard error of the sample mean
128
+ # @return [Float]
103
129
  def std_err
104
130
  std / Math.sqrt(size)
105
131
  end
106
132
 
133
+ # @return [Numeric] median value of the array
107
134
  def median
108
135
  return self[0] if length <= 1
109
136
  sorted = sort
@@ -115,16 +142,18 @@ Array.class_eval do
115
142
  end
116
143
  end
117
144
 
118
- # deep dup, takes about 20 microseconds for scores arrays
119
- # https://www.thoughtco.com/making-deep-copies-in-ruby-2907749
145
+ # Deep duplicate the array
146
+ # @return [Object] a deep copy of the array
147
+ # @see https://www.thoughtco.com/making-deep-copies-in-ruby-2907749
120
148
  def deep_dup
121
149
  Marshal.load(Marshal.dump(self))
122
150
  end
123
151
  end
124
152
 
125
153
  Float.class_eval do
126
- # converts radians to degrees
127
- # Also rounds to n_decimal places unless n_decimimals is nil
154
+ # Converts radians to degrees
155
+ # @param n_decimals [Integer, nil] decimal places to round to, pass nil for no rounding
156
+ # @return [Float]
128
157
  def to_deg(n_decimals = nil)
129
158
  include Math unless defined?(Math)
130
159
  degrees = self * 180.0 / PI
@@ -132,8 +161,9 @@ Float.class_eval do
132
161
  degrees.round(n_decimals)
133
162
  end
134
163
 
135
- # converts degrees to radians
136
- # Also rounds to n_decimal places unless n_decimimals is nil
164
+ # Converts degrees to radians
165
+ # @param n_decimals [Integer, nil] decimal places to round to, pass nil for no rounding
166
+ # @return [Float]
137
167
  def to_rad(n_decimals = nil)
138
168
  include Math unless defined?(Math)
139
169
  radians = self * PI / 180.0
@@ -141,39 +171,57 @@ Float.class_eval do
141
171
  radians.round(n_decimals)
142
172
  end
143
173
 
144
- # rounds exponential notation to n decimal places
174
+ # Rounds a float represented in exponential notation
175
+ # @param decimal_points [Integer]
176
+ # @return [Float]
145
177
  def eround(decimal_points = 0)
146
178
  ("%.#{decimal_points}e" % self).to_f
147
179
  end
148
180
 
181
+ # @param n [Integer] decimal place to round down to
182
+ # @return [Float]
149
183
  def round_down(n=0)
150
184
  # n is decimal place to round down at
151
185
  int,dec=self.to_s.split('.')
152
186
  "#{int}.#{dec[0...n]}".to_f
153
187
  end
154
188
 
189
+ # @return [Float]
155
190
  def round1
156
191
  round(1)
157
192
  end
158
193
 
194
+ # @return [Float]
159
195
  def round2
160
196
  round(2)
161
197
  end
162
198
 
199
+ # @return [Float]
163
200
  def round3
164
201
  round(3)
165
202
  end
166
203
 
204
+ # @return [Float]
167
205
  def round4
168
206
  round(4)
169
207
  end
170
208
  end
171
209
 
210
+ # Numerically focused helpers and distribution utilities.
172
211
  module ChrisMath
173
212
 
174
213
  include Math
175
214
 
215
+ # Generates normally distributed random numbers using Box-Muller
216
+ # @param n [Integer] number of samples to generate
217
+ # @return [Array<Float>] array of N(0,1) samples
218
+ # @note Returns an empty array and warns when `n <= 0`.
176
219
  def gaussian_array(n = 1)
220
+ raise ArgumentError, 'n must be an Integer' unless n.is_a?(Integer)
221
+ if n <= 0
222
+ warn 'gaussian_array called with non-positive sample size; returning empty array'
223
+ return []
224
+ end
177
225
  (1..n).map do
178
226
  u1 = rand()
179
227
  u2 = rand()
@@ -181,6 +229,8 @@ module ChrisMath
181
229
  end
182
230
  end
183
231
 
232
+ # Generates a pair of independent standard normal deviates
233
+ # @return [Array<Float>] two-element array [z0, z1]
184
234
  def bi_gaussian_rand
185
235
  u1 = rand()
186
236
  u2 = rand()
@@ -189,7 +239,21 @@ module ChrisMath
189
239
  [z0,z1]
190
240
  end
191
241
 
242
+ # Generates a normally distributed random number with the provided mean and std
243
+ # @param mean [Numeric]
244
+ # @param std [Numeric]
245
+ # @return [Float]
246
+ # @note Warns when `std <= 0`, returning the mean or using the absolute value.
192
247
  def gaussian_rand(mean,std)
248
+ raise ArgumentError, 'mean must be Numeric' unless mean.is_a?(Numeric)
249
+ raise ArgumentError, 'std must be Numeric' unless std.is_a?(Numeric)
250
+ if std.negative?
251
+ warn 'std supplied to gaussian_rand was negative; using absolute value'
252
+ std = std.abs
253
+ elsif std.zero?
254
+ warn 'std supplied to gaussian_rand was zero; returning mean'
255
+ return mean
256
+ end
193
257
  u1 = rand()
194
258
  u2 = rand()
195
259
  z0 = sqrt(-2*log(u1))*cos(2*PI*u2)
@@ -197,7 +261,12 @@ module ChrisMath
197
261
  end
198
262
 
199
263
 
264
+ # Sample standard deviation for raw values
265
+ # @param values [Array<Numeric>]
266
+ # @return [Float]
267
+ # @raise [RuntimeError] when fewer than two observations are provided
200
268
  def std(values)
269
+ raise ArgumentError, 'values must respond to #length and #inject' unless values.respond_to?(:length) && values.respond_to?(:inject)
201
270
  n = values.length
202
271
  raise 'n = #{n} but must be greater than 1' if n < 2
203
272
  m = mean(values)
@@ -206,7 +275,16 @@ module ChrisMath
206
275
  end
207
276
 
208
277
 
278
+ # Cumulative probability of at least r successes in n Bernoulli trials
279
+ # @param n [Integer]
280
+ # @param r [Integer]
281
+ # @param p [Float] probability of success per trial
282
+ # @return [Float]
283
+ # @raise [RuntimeError] when r is outside 0..n
209
284
  def combinatorial_distribution(n,r,p)
285
+ raise ArgumentError, 'n must be a non-negative Integer' unless n.is_a?(Integer) && n >= 0
286
+ raise ArgumentError, 'r must be a non-negative Integer' unless r.is_a?(Integer) && r >= 0
287
+ raise ArgumentError, 'p must be between 0 and 1' unless p.is_a?(Numeric) && p.between?(0.0,1.0)
210
288
  # probability that r out n or greater hits with the
211
289
  # probability of one hit is p.
212
290
  if r <= n && r >= 0
@@ -220,13 +298,29 @@ module ChrisMath
220
298
  end
221
299
  end
222
300
 
301
+ # Factorial without overflow protection
302
+ # @param n [Integer]
303
+ # @return [Integer]
304
+ # @note Warns and returns `nil` when `n` is negative.
223
305
  def factorial(n)
224
306
  #from rosetta code
307
+ raise ArgumentError, 'n must be an Integer' unless n.is_a?(Integer)
308
+ if n.negative?
309
+ warn 'factorial called with negative n; returning nil'
310
+ return nil
311
+ end
225
312
  (1..n).inject {|prod, i| prod * i}
226
313
  end
227
314
 
315
+ # Binomial coefficient "n choose k"
316
+ # @param n [Integer]
317
+ # @param k [Integer]
318
+ # @return [Integer]
228
319
  def combinatorial(n, k)
229
320
  #from rosetta code
321
+ raise ArgumentError, 'n must be a non-negative Integer' unless n.is_a?(Integer) && n >= 0
322
+ raise ArgumentError, 'k must be a non-negative Integer' unless k.is_a?(Integer) && k >= 0
323
+ raise ArgumentError, 'k must be <= n' if k > n
230
324
  (0...k).inject(1) do |m,i| (m * (n - i)) / (i + 1) end
231
325
  end
232
326
 
@@ -1,4 +1,6 @@
1
- # extensions to Date
1
+ # Extensions to {Date} for friendly formatting helpers.
2
+ require 'date'
3
+
2
4
  module DateExt
3
5
  # To format a date, use <tt>date.charmians_format</tt>
4
6
  # on any Date object.
@@ -10,23 +12,28 @@ module DateExt
10
12
  # If using a DateTime object, need to convert to date i.e.
11
13
  # <tt>datetime.to_date.charmians_format</tt>.
12
14
  Date.class_eval do
15
+ # @return [String] Month day, year (e.g. "January 3, 2025")
13
16
  def us_format
14
17
  "#{strftime('%B')} #{day}, #{year}"
15
18
  end
16
19
 
20
+ # @return [String] Short month name day, year (e.g. "Jan 3, 2025")
17
21
  def short_format
18
22
  "#{strftime('%b')} #{day}, #{year}"
19
23
  end
20
24
 
25
+ # @return [String] Weekday, Month day, year (e.g. "Monday, January 3, 2025")
21
26
  def us_format_with_weekday
22
27
  "#{strftime('%A')}, #{strftime('%B')} #{day}, #{year}"
23
28
  end
24
29
 
30
+ # @return [String] "Weekday, Day<sup>suffix</sup> Month, Year" with textual suffix
25
31
  def charmians_format
26
32
  d = cardinalation(day)
27
33
  "#{strftime('%A')}, #{d} #{strftime('%B')}, #{year}"
28
34
  end
29
35
 
36
+ # @return [String] HTML-safe string with ordinal suffix wrapped in `<sup>`
30
37
  def charmians_format_sup
31
38
  d = cardinalation(day, true)
32
39
  "#{strftime('%A')}, #{d} #{strftime('%B')}, #{year}"
@@ -34,12 +41,17 @@ module DateExt
34
41
 
35
42
  private
36
43
 
44
+ # @param day [Integer]
45
+ # @param html_sup [Boolean]
46
+ # @return [String]
37
47
  def cardinalation(day, html_sup = false)
38
48
  s = suffix day
39
49
  s = "<sup>#{s}</sup>" if html_sup
40
50
  "#{day}#{s}"
41
51
  end
42
52
 
53
+ # @param day [Integer]
54
+ # @return [String] ordinal suffix for the provided day of month
43
55
  def suffix(day)
44
56
  case day
45
57
  when 1, 21, 31