Module: CTioga2::Utils

Defined in:
lib/ctioga2/utils.rb

Overview

Various utilities

Constant Summary collapse

UNCOMPRESSORS =

A constant holding a relation extension -> command to decompress (to be fed to sprintf with the filename as argument)

{
  ".gz" => "gunzip -c %s",
  ".bz2" => "bunzip2 -c %s",
  ".lzma" => "unlzma -c %s",
  ".lz" => "unlzma -c %s",
  ".xz" => "unxz -c %s",
}
NaturalSubdivisions =
[1.0, 2.0, 5.0, 10.0]

Class Method Summary collapse

Class Method Details

.average_vector(vect, istart, iend, n = 1) ⇒ Object

Returns the nth first modes



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/ctioga2/utils.rb', line 169

def self.average_vector(vect, istart, iend, n = 1)
  rv = [0] * n
  nb = 0
  istart.upto(iend) do |i|
    v = 1
    y = vect[i]
    n.times do |j|
      v *= y
      rv[j] += v
    end
    nb += 1
  end

  return rv.map do |v|
    v/nb
  end
end

.chdir(dir, &blk) ⇒ Object

An instrumentized version of Dir::chdir



203
204
205
206
207
208
# File 'lib/ctioga2/utils.rb', line 203

def self.chdir(dir, &blk)
  @current_dirs ||= []
  @current_dirs << Pathname.new(dir)
  Dir::chdir(dir, &blk)
  @current_dirs.pop
end

.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.



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/ctioga2/utils.rb', line 282

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



464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/ctioga2/utils.rb', line 464

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)



118
119
120
121
122
123
# File 'lib/ctioga2/utils.rb', line 118

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.



315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/ctioga2/utils.rb', line 315

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

.finite_number?(flt) ⇒ Boolean

Returns true if the argument is a finite number



63
64
65
# File 'lib/ctioga2/utils.rb', line 63

def self.finite_number?(flt)
  return (flt.is_a?(Numeric) and flt.to_f.finite?)
end

.group_by_prefix(strings, pref_re) ⇒ Object

Groups the given strings by prefixes



189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/ctioga2/utils.rb', line 189

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



366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/ctioga2/utils.rb', line 366

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)



79
80
81
82
83
84
85
# File 'lib/ctioga2/utils.rb', line 79

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

.open(file) ⇒ Object

This opens a file for reading, keeping track of the opened files and possibly transparently decompressing files when applicable. Returns the file object, or runs the given block with it, closing the file at the end.



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/ctioga2/utils.rb', line 224

def self.open(file)
  if not File.readable?(file)
    # Try to find a compressed version
    for ext,method in UNCOMPRESSORS
      if File.readable? "#{file}#{ext}"
        info { "Using compressed file #{file}#{ext} in stead of #{file}" }
        ff = "#{file}#{ext}"
        handle = IO.popen(method % ff)
        break
      end
    end
  else
    for ext, method in UNCOMPRESSORS
      if file =~ /#{ext}$/
        info { "Taking file #{file} as a compressed file" }
        ff = file
        handle = IO.popen(method % file)
        break
      end
    end
  end
  if ! handle
    ff = file
    handle = File::open(file)
  end

  # Unwrap directory 
  @used_files ||= {}

  @current_dirs ||= []
  rf = (@current_dirs + [file]).inject :+
  rff = (@current_dirs + [ff]).inject :+
  # The files referenced
  @used_files[rf] = rff
  if block_given?
    yield handle
    handle.close
  else
    return handle
  end
end

.osObject



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/ctioga2/utils.rb', line 440

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



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/ctioga2/utils.rb', line 133

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).



89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ctioga2/utils.rb', line 89

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



329
330
331
332
333
# File 'lib/ctioga2/utils.rb', line 329

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



488
489
490
491
492
493
494
495
496
497
498
# File 'lib/ctioga2/utils.rb', line 488

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



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/ctioga2/utils.rb', line 383

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



153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/ctioga2/utils.rb', line 153

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).



105
106
107
108
109
110
111
112
113
114
115
# File 'lib/ctioga2/utils.rb', line 105

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.



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

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



69
70
71
72
73
74
75
# File 'lib/ctioga2/utils.rb', line 69

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

.used_filesObject

Returns the list of files that were read by ctioga2



268
269
270
# File 'lib/ctioga2/utils.rb', line 268

def self.used_files()
  return @used_files || {}
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



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/ctioga2/utils.rb', line 420

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