Module: CTioga2::Utils

Defined in:
lib/ctioga2/utils.rb

Overview

Various utilities

Constant Summary collapse

NaturalSubdivisions =
[1.0, 2.0, 5.0, 10.0]

Class Method Summary collapse

Class Method Details

.closest_subdivision(x, what = :closest) ⇒ Object

Returns the closest element of the correct power of ten of NaturalSubdivisions above or below the given number.

If what is :below, returns the closest below. If what is :above, returns the closest above. Else, returns the one the closest between the two values.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/ctioga2/utils.rb', line 189

def self.closest_subdivision(x, what = :closest)
  fact = 10**(Math.log10(x).floor)

  normed_x = x/fact
  (NaturalSubdivisions.size()-1).times do |i|
    if normed_x == NaturalSubdivisions[i]
      return x
    end
    if (normed_x > NaturalSubdivisions[i]) && 
       (normed_x < NaturalSubdivisions[i+1])
      below = NaturalSubdivisions[i]*fact
      above = NaturalSubdivisions[i+1]*fact
      if what == :below
        return below
      elsif what == :above
        return above
      else
        if x*x/(below * above) > 1
          return above
        else
          return below
        end
      end
    end
  end
  raise "Should not reach this !"
end

.cluster_by_value(list, funcall) ⇒ Object

TODO:

with block too ?

Cluster a series of objects by the values returned by the given funcall. It returns an array of arrays where the elements are in the same order, and in each sub-array, the functions all return the same value



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/ctioga2/utils.rb', line 371

def self.cluster_by_value(list, funcall)
  if list.size == 0
    return []
  end
  ret = [ [list[0]] ]
  cur = ret[0]
  last = cur.first.send(funcall)

  for o in list[1..-1]
    val = o.send(funcall)
    if last == val
      cur << o
    else
      cur = [o]
      ret << cur
      last = val
    end
  end

  return ret
end

.cnk(n, k) ⇒ Object

Binomial coefficients (for the smooth filter)



113
114
115
116
117
118
# File 'lib/ctioga2/utils.rb', line 113

def self.cnk(n,k)
  res = 1.0
  n.downto(n - k) { |i| res *= i}
  k.downto(1) {|i| res = res/i }
  return res
end

.common_pow10(vect, method = :floor, tolerance = 1e-8) ⇒ Object

Returns the smallest power of 10 within the given buffer (excluding 0 or anything really close). That is, if you multiply by 10 to the power what is returned, the smallest will be in the range 1-10.



222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/ctioga2/utils.rb', line 222

def self.common_pow10(vect, method = :floor, tolerance = 1e-8)
  a = vect.abs
  a.sort!
  while (a.size > 1) && (a.first < tolerance * a.last)
    a.shift
  end
  if a.first == 0
    return 0
  end

  return Math.log10(a.first).send(method)
end

.group_by_prefix(strings, pref_re) ⇒ Object

Groups the given strings by prefixes



166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/ctioga2/utils.rb', line 166

def self.group_by_prefix(strings, pref_re)
  sets_by_prefix = {}
  for s in strings
    pref = s
    if s =~ pref_re
      pref = $1
    end
    sets_by_prefix[pref] ||= []
    sets_by_prefix[pref] << s
  end
  return sets_by_prefix
end

.integer_subdivisions(bot, top, delta) ⇒ Object

Returns the biggest vector of multiples of delta contained within bot and top



273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/ctioga2/utils.rb', line 273

def self.integer_subdivisions(bot, top, delta)
  if bot > top
    bot, top = top, bot
  end
  tn = (top/delta).floor
  bn = (bot/delta).ceil
  ret = Dobjects::Dvector.new()
  nb = (tn - bn).to_i + 1

  nb.times do |i|
    ret << (bn + i) * delta
  end
  return ret
end

.mix_objects(a, b, r) ⇒ Object

Takes two arrays of the same size (vectors) and mix them a * r + b * (1 - r)



74
75
76
77
78
79
80
# File 'lib/ctioga2/utils.rb', line 74

def self.mix_objects(a,b,r)
  ret = a.dup
  a.each_index do |i|
    ret[i] = a[i] * r + b[i] * (1 - r)
  end
  return ret
end

.osObject



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/ctioga2/utils.rb', line 347

def self.os
  host_os = RbConfig::CONFIG['host_os']
  case host_os
  when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
    :windows
  when /darwin|mac os/
    :macosx
  when /linux/
    :linux
  when /solaris|bsd/
    :unix
  else
    warn {"Unknown os: #{host_os.inspect}"}
    :unknown
  end
end

.parse_formula(formula, parameters = nil, header = nil) ⇒ Object

This converts a text formula that can contain:

  • any litteral thing

  • references to columns in the form of $1 for column 1 (ie the second one)

  • references to named columns in the form $name$

  • references to parameters

The return value is ready to be passed to Dvector.compute_formula



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/ctioga2/utils.rb', line 128

def self.parse_formula(formula, parameters = nil, header = nil)
  formula = formula.dup
  if parameters
    for k,v in parameters
      formula.gsub!(/\b#{k}\b/, v.to_s)
    end
  end
  formula.gsub!(/\$(\d+)/, 'column[\1]')
  if header
    for k,v in header
      formula.gsub!("$#{k}$", "column[#{v}]")
    end
  end
  if formula =~ /(\$[^$]+\$)/
    raise  "'#{$1}' looks like a column name, but there is no corresponding column of that name"
  end
  return formula
end

.pdftex_quote_string(str) ⇒ Object

Quotes a string so it can be included directly within a pdfinfo statement (for instance).



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ctioga2/utils.rb', line 84

def self.pdftex_quote_string(str)
  return str.gsub(/([%#])|([()])|([{}~_^])|\\/) do 
    if $1
      "\\#{$1}"
    elsif $2                  # Quoting (), as they can be quite nasty !!
      "\\string\\#{$2}"
    elsif $3
      "\\string#{$3}"
    else                      # Quoting \
      "\\string\\\\"
    end
  end
end

.scale_segment(left, right, factor) ⇒ Object

Returns the segment scaled about its center by the given factor



236
237
238
239
240
# File 'lib/ctioga2/utils.rb', line 236

def self.scale_segment(left, right, factor)
  dx = 0.5*(right - left)
  mid = 0.5*(right + left)
  return mid - factor * dx, mid + factor * dx
end

.shell_quote_string(str) ⇒ Object

Takes a string a returns a quoted version that should be able to go through shell expansion.



49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/ctioga2/utils.rb', line 49

def self.shell_quote_string(str)
  if str =~ /[\s"*$()\[\]{}';\\]/
    if str =~ /'/
      a = str.gsub(/(["$\\])/) { "\\#$1" }
      return "\"#{a}\""
    else 
      return "'#{str}'"
    end
  else
    return str
  end
end

.sort_by_value(list, funcall) ⇒ Object

Returns a hash value -> [elements] in which the elements are in the same order



395
396
397
398
399
400
401
402
403
404
405
# File 'lib/ctioga2/utils.rb', line 395

def self.sort_by_value(list, funcall)
  ret = {}
  
  for el in list
    val = el.send(funcall)
    ret[val] ||= []

    ret[val] << el
  end
  return ret
end

.split_homogeneous_deltas(vect, tolerance = 1e-4) ⇒ Object

Takes a vector, and splits it into a series of contiguous subvectors which



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/ctioga2/utils.rb', line 290

def self.split_homogeneous_deltas(vect, tolerance = 1e-4)
  rv = []
  idx = 1
  dx = nil
  lst = nil
  while idx < vect.size
    cdx = vect[idx] - vect[idx - 1]
    if ! dx 
      dx = cdx
      lst = Dobjects::Dvector.new()
      rv << lst
      lst << vect[idx-1] << vect[idx]
    else
      if (cdx - dx).abs <= tolerance * dx
        # keep going
        lst << vect[idx]
      else
        dx = nil
      end
    end
    idx += 1
  end

  # Flush the last one if alone
  if ! dx
    nv = Dobjects::Dvector.new()
    nv << vect.last
    rv << nv
  end
  return rv
end

.suffix_numeric_sort(strings) ⇒ Object

Sorts strings according to their numeric suffix



148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/ctioga2/utils.rb', line 148

def self.suffix_numeric_sort(strings)
  strings.sort do |a,b|
    a =~ /.*?(\d+)$/
    a_i = $1 ? $1.to_i : nil
    b =~ /.*?(\d+)$/
    b_i = $1 ? $1.to_i : nil
    
    if a_i && b_i
      a_i <=> b_i
    else
      a <=> b
    end
  end
end

.tex_quote_string(str) ⇒ Object

Quotes a string so it can be included directly within a pdfinfo statement (for instance).



100
101
102
103
104
105
106
107
108
109
110
# File 'lib/ctioga2/utils.rb', line 100

def self.tex_quote_string(str)
  return str.gsub(/([%#])|([{}~_^])|\\/) do 
    if $1
      "\\#{$1}"
    elsif $2
      "\\string#{$2}"
    else                      # Quoting \
      "\\string\\\\"
    end
  end
end

.transcode_until_found(file) ⇒ Object

Transcodes the given string from all encodings into the target encoding until an encoding is found in which the named file exists.

Works around encoding problems on windows.



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/ctioga2/utils.rb', line 248

def self.transcode_until_found(file)
  if File.exists? file
    return file
  end
  begin                     # We wrap in a begin/rescue block for
                            # Ruby 1.8
    for e in Encoding::constants
      e = Encoding.const_get(e)
      if e.is_a? Encoding
        begin
          ts = file.encode(Encoding::default_external, e)
          if File.exists? ts
            return ts
          end
        rescue
        end
      end
    end
  rescue
  end
  return file               # But that will fail later on.
end

.txt_to_float(txt) ⇒ Object

Converts a number to a float while trying to stay as lenient as possible



64
65
66
67
68
69
70
# File 'lib/ctioga2/utils.rb', line 64

def self.txt_to_float(txt)
  v = txt.to_f
  if v == 0.0
    return Float(txt)
  end
  return v
end

.which(cmd) ⇒ Object

Cross-platform way of finding an executable in the $PATH.

This is adapted from stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/ctioga2/utils.rb', line 327

def self.which(cmd)
  return nil unless cmd
  exts = ['']
  if ENV['PATHEXT']
    exts += ENV['PATHEXT'].split(';')
  end

  ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
    exts.each { |ext|
      exe = File.join(path, "#{cmd}#{ext}")
      return exe if File.executable? exe
    }
  end
  return nil
end