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]
@@color_name_by_value =
nil

Class Method Summary collapse

Class Method Details

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

Returns the nth first modes



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/ctioga2/utils.rb', line 177

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



239
240
241
242
243
244
# File 'lib/ctioga2/utils.rb', line 239

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.



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/ctioga2/utils.rb', line 318

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



500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
# File 'lib/ctioga2/utils.rb', line 500

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)



126
127
128
129
130
131
# File 'lib/ctioga2/utils.rb', line 126

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

.color_name_by_value(color) ⇒ Object

Returns the Tioga name for the given color. Can be nil if the color does not match a Tioga named color.



226
227
228
# File 'lib/ctioga2/utils.rb', line 226

def self.color_name_by_value(color)
  return color_name_by_value_hsh()[Utils::color_to_html(color)]
end

.color_name_by_value_hshObject



212
213
214
215
216
217
218
219
220
221
222
# File 'lib/ctioga2/utils.rb', line 212

def self.color_name_by_value_hsh
  if ! @@color_name_by_value
    @@color_name_by_value = {}
    colors = Tioga::ColorConstants::constants
    for c in colors
      color = Tioga::ColorConstants::const_get(c)
      @@color_name_by_value[Utils::color_to_html(color)] = c.to_s
    end
  end
  return @@color_name_by_value
end

.color_to_html(color) ⇒ Object

Transforms a Tioga color into a HTML color string “xxxxxx” (without the leeading #)



232
233
234
235
236
# File 'lib/ctioga2/utils.rb', line 232

def self.color_to_html(color)
  return color.map do |i|
    "%02x" % (i*255.to_i)
  end.join('')
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.



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

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

Returns:

  • (Boolean)


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



197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/ctioga2/utils.rb', line 197

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

.infinite_number?(flt) ⇒ Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/ctioga2/utils.rb', line 71

def self.infinite_number?(flt)
  return (flt.respond_to?(:infinite?) and flt.infinite?)
end

.integer_subdivisions(bot, top, delta) ⇒ Object

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



402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/ctioga2/utils.rb', line 402

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)



87
88
89
90
91
92
93
# File 'lib/ctioga2/utils.rb', line 87

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

.nan_number?(flt) ⇒ Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/ctioga2/utils.rb', line 67

def self.nan_number?(flt)
  return (flt.respond_to?(:nan?) and flt.nan?)
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.



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/ctioga2/utils.rb', line 260

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



476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/ctioga2/utils.rb', line 476

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



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/ctioga2/utils.rb', line 141

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



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

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



365
366
367
368
369
# File 'lib/ctioga2/utils.rb', line 365

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



524
525
526
527
528
529
530
531
532
533
534
# File 'lib/ctioga2/utils.rb', line 524

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



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/ctioga2/utils.rb', line 419

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



161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/ctioga2/utils.rb', line 161

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



113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ctioga2/utils.rb', line 113

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.



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/ctioga2/utils.rb', line 377

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



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

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



304
305
306
# File 'lib/ctioga2/utils.rb', line 304

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



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/ctioga2/utils.rb', line 456

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