Class: RinRuby

Inherits:
Object
  • Object
show all
Defined in:
lib/rinruby.rb

Constant Summary collapse

VERSION =
'2.0.3'
EngineClosed =

Exception for closed engine

Class.new(Exception)
ParseError =

Parse error

Class.new(Exception)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ RinRuby

Returns a new instance of RinRuby.



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/rinruby.rb', line 106

def initialize(*args)
opts=Hash.new
if args.size==1 and args[0].is_a? Hash
  opts=args[0]
else
  opts[:echo]=args.shift unless args.size==0
  opts[:interactive]=args.shift unless args.size==0
  opts[:executable]=args.shift unless args.size==0
  opts[:port_number]=args.shift unless args.size==0
  opts[:port_width]=args.shift unless args.size==0
end
default_opts= {:echo=>true, :interactive=>true, :executable=>nil, :port_number=>38442, :port_width=>1000, :hostname=>'127.0.0.1'}

  @opts=default_opts.merge(opts)
  @port_width=@opts[:port_width]
  @executable=@opts[:executable]
  @hostname=@opts[:hostname]
  while true
    begin
      @port_number = @opts[:port_number] + rand(port_width)
      @server_socket = TCPServer::new(@hostname, @port_number)
      break
    rescue Errno::EADDRINUSE
      sleep 0.5 if port_width == 1
    end
  end
  @echo_enabled = @opts[:echo]
  @echo_stderr = false
  @interactive = @opts[:interactive]
  @platform = case RUBY_PLATFORM
    when /mswin/ then 'windows'
    when /mingw/ then 'windows'
    when /bccwin/ then 'windows'
    when /cygwin/ then 'windows-cygwin'
    when /java/
      require 'java' #:nodoc:
      if java.lang.System.getProperty("os.name") =~ /[Ww]indows/
        'windows-java'
      else
        'default-java'
      end
    else 'default'
  end
  if @executable == nil
    @executable = ( @platform =~ /windows/ ) ? find_R_on_windows(@platform =~ /cygwin/) : 'R'
  end
  platform_options = []
  if ( @interactive )
    begin
      require 'readline'
    rescue LoadError
    end
    @readline = defined?(Readline)
    platform_options << ( ( @platform =~ /windows/ ) ? '--ess' : '--interactive' )
  else
    @readline = false
  end
  cmd = %Q<#{executable} #{platform_options.join(' ')} --slave>
  @engine = IO.popen(cmd,"w+")
  @reader = @engine
  @writer = @engine
  raise "Engine closed" if @engine.closed?
  @writer.puts <<-EOF
    #{RinRuby_KeepTrying_Variable} <- TRUE
    while ( #{RinRuby_KeepTrying_Variable} ) {
      #{RinRuby_Socket} <- try(suppressWarnings(socketConnection("#{@hostname}", #{@port_number}, blocking=TRUE, open="rb")),TRUE)
      if ( inherits(#{RinRuby_Socket},"try-error") ) {
        Sys.sleep(0.1)
      } else {
        #{RinRuby_KeepTrying_Variable} <- FALSE
      }
    }
    rm(#{RinRuby_KeepTrying_Variable})
  EOF
  r_rinruby_get_value
  r_rinruby_pull
  r_rinruby_parseable
  @socket = @server_socket.accept
  echo(nil,true) if @platform =~ /.*-java/      # Redirect error messages on the Java platform
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object

If a method is called which is not defined, then it is assumed that the user is attempting to either pull or assign a variable to R. This allows for the short-hand equivalents to the pull and assign methods. For example:

>> R.x = 2

is the same as:

>> R.assign("x",2)

Also:

>> n = R.x

is the same as:

>> n = R.pull("x")

The parameters passed to method_missing are those used for the pull or assign depending on the context.



354
355
356
357
358
359
360
361
362
363
364
# File 'lib/rinruby.rb', line 354

def method_missing(symbol, *args)
  name = symbol.id2name
  if name =~ /(.*)=$/
    raise ArgumentError, "You shouldn't assign nil" if args==[nil]
    super if args.length != 1
    assign($1,args[0])
  else
    super if args.length != 0
    pull(name)
  end
end

Instance Attribute Details

#echo_enabledObject (readonly)

RinRuby is invoked within a Ruby script (or the interactive “irb” prompt denoted >>) using:

>> require "rinruby"

The previous statement reads the definition of the RinRuby class into the current Ruby interpreter and creates an instance of the RinRuby class named R. There is a second method for starting an instance of R which allows the user to use any name for the instance, in this case myr:

>> require "rinruby"
>> myr = RinRuby.new
>> myr.eval "rnorm(1)"

Any number of independent instances of R can be created in this way.

Parameters that can be passed to the new method using a Hash:

  • :echo: By setting the echo to false, output from R is suppressed, although warnings are still printed. This option can be changed later by using the echo method. The default is true.

  • :interactive: When interactive is false, R is run in non-interactive mode, resulting in plots without an explicit device being written to Rplots.pdf. Otherwise (i.e., interactive is true), plots are shown on the screen. The default is true.

  • :executable: The path of the R executable (which is “R” in Linux and Mac OS X, or “Rterm.exe” in Windows) can be set with the executable argument. The default is nil which makes RinRuby use the registry keys to find the path (on Windows) or use the path defined by $PATH (on Linux and Mac OS X).

  • :port_number: This is the smallest port number on the local host that could be used to pass data between Ruby and R. The actual port number used depends on port_width.

  • :port_width: RinRuby will randomly select a uniform number between port_number and port_number + port_width - 1 (inclusive) to pass data between Ruby and R. If the randomly selected port is not available, RinRuby will continue selecting random ports until it finds one that is available. By setting port_width to 1, RinRuby will wait until port_number is available. The default port_width is 1000.

It may be desirable to change the parameters to the instance of R, but still call it by the name of R. In that case the old instance of R which was created with the ‘require “rinruby”’ statement should be closed first using the quit method which is explained below. Unless the previous instance is killed, it will continue to use system resources until exiting Ruby. The following shows an example by changing the parameter echo:

>> require "rinruby"
>> R.quit
>> R = RinRuby.new(false)


101
102
103
# File 'lib/rinruby.rb', line 101

def echo_enabled
  @echo_enabled
end

#executableObject (readonly)

Returns the value of attribute executable.



102
103
104
# File 'lib/rinruby.rb', line 102

def executable
  @executable
end

#hostnameObject (readonly)

Returns the value of attribute hostname.



105
106
107
# File 'lib/rinruby.rb', line 105

def hostname
  @hostname
end

#interactiveObject (readonly)

Returns the value of attribute interactive.



68
69
70
# File 'lib/rinruby.rb', line 68

def interactive
  @interactive
end

#port_numberObject (readonly)

Returns the value of attribute port_number.



103
104
105
# File 'lib/rinruby.rb', line 103

def port_number
  @port_number
end

#port_widthObject (readonly)

Returns the value of attribute port_width.



104
105
106
# File 'lib/rinruby.rb', line 104

def port_width
  @port_width
end

#readlineObject (readonly)

Returns the value of attribute readline.



69
70
71
# File 'lib/rinruby.rb', line 69

def readline
  @readline
end

Instance Method Details

#assign(name, value) ⇒ Object

Data is copied from Ruby to R using the assign method or a short-hand equivalent. For example:

>> names = ["Lisa","Teasha","Aaron","Thomas"]
>> R.assign "people", names
>> R.eval "sort(people)"

produces the following :

[1] "Aaron"     "Lisa"     "Teasha" "Thomas"

The short-hand equivalent to the assign method is simply:

>> R.people = names

Some care is needed when using the short-hand of the assign method since the label (i.e., people in this case) must be a valid method name in Ruby. For example, R.copy.of.names = names will not work, but R.copy_of_names = names is permissible.

The assign method supports Ruby variables of type Fixnum (i.e., integer), Bignum (i.e., integer), Float (i.e., double), String, and arrays of one of those three fundamental types. Note that Fixnum or Bignum values that exceed the capacity of R’s integers are silently converted to doubles. Data in other formats must be coerced when copying to R.

Parameters that can be passed to the assign method:

  • name: The name of the variable desired in R.

  • value: The value the R variable should have. The assign method supports Ruby variables of type Fixnum (i.e., integer), Bignum (i.e., integer), Float (i.e., double), String, and arrays of one of those three fundamental types. Note that Fixnum or Bignum values that exceed the capacity of R’s integers are silently converted to doubles. Data in other formats must be coerced when copying to R.

The assign method is an alternative to the simplified method, with some additional flexibility. When using the simplified method, the parameters of name and value are automatically used, in other words:

>> R.test = 144

is the same as:

>> R.assign("test",144)

Of course it would be confusing to use the shorthand notation to assign a variable named eval, echo, or any other already defined function. RinRuby would assume you were calling the function, rather than trying to assign a variable.

When assigning an array containing differing types of variables, RinRuby will follow R’s conversion conventions. An array that contains any Strings will result in a character vector in R. If the array does not contain any Strings, but it does contain a Float or a large integer (in absolute value), then the result will be a numeric vector of Doubles in R. If there are only integers that are suffciently small (in absolute value), then the result will be a numeric vector of integers in R.

Raises:



402
403
404
405
406
407
408
409
# File 'lib/rinruby.rb', line 402

def assign(name, value)
   raise EngineClosed if @engine.closed?
  if assignable?(name)
    assign_engine(name,value)
  else
    raise ParseError, "Parse error"
  end
end

#complete?(string) ⇒ Boolean

Returns:

  • (Boolean)


725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
# File 'lib/rinruby.rb', line 725

def complete?(string)
  assign_engine(RinRuby_Parse_String, string)
  @writer.puts "rinruby_parseable(#{RinRuby_Parse_String})"
  buffer=""
  @socket.read(4,buffer)
  @writer.puts "rm(#{RinRuby_Parse_String})"
  result = to_signed_int(buffer.unpack('N')[0].to_i)
  return result==-1 ? false : true
  
=begin    
  
  result = pull_engine("unlist(lapply(c('.*','^Error in parse.*','^Error in parse.*unexpected end of input.*'),
    grep,try({parse(text=#{RinRuby_Parse_String}); 1}, silent=TRUE)))")
  
  return true if result.length == 1
  return false if result.length == 3
  raise ParseError, "Parse error"
=end
end

#echo(enable = nil, stderr = nil) ⇒ Object

The echo method controls whether the eval method displays output from R and, if echo is enabled, whether messages, warnings, and errors from stderr are also displayed.

Parameters that can be passed to the eval method

  • enable: Setting enable to false will turn all output off until the echo command is used again with enable equal to true. The default is nil, which will return the current setting.

  • stderr: Setting stderr to true will force messages, warnings, and errors from R to be routed through stdout. Using stderr redirection is typically not needed for the C implementation of Ruby and is thus not not enabled by default for this implementation. It is typically necessary for jRuby and is enabled by default in this case. This redirection works well in practice but it can lead to interleaving output which may confuse RinRuby. In such cases, stderr redirection should not be used. Echoing must be enabled when using stderr redirection.



485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/rinruby.rb', line 485

def echo(enable=nil,stderr=nil)
  if ( enable == false ) && ( stderr == true )
    raise "You can only redirect stderr if you are echoing is enabled."
  end
  if ( enable != nil ) && ( enable != @echo_enabled )
    echo(nil,false) if ! enable
    @echo_enabled = ! @echo_enabled
  end
  if @echo_enabled && ( stderr != nil ) && ( stderr != @echo_stderr )
    @echo_stderr = ! @echo_stderr
    if @echo_stderr
      eval "sink(stdout(),type='message')"
    else
      eval "sink(type='message')"
    end
  end
  [ @echo_enabled, @echo_stderr ]
end

#eval(string, echo_override = nil) ⇒ Object

The eval instance method passes the R commands contained in the supplied string and displays any resulting plots or prints the output. For example:

>>  sample_size = 10
>>  R.eval "x <- rnorm(#{sample_size})"
>>  R.eval "summary(x)"
>>  R.eval "sd(x)"

produces the following:

   Min. 1st Qu.        Median      Mean 3rd Qu.         Max.
-1.88900 -0.84930 -0.45220 -0.49290 -0.06069          0.78160
[1] 0.7327981

This example used a string substitution to make the argument to first eval method equivalent to x <- rnorm(10). This example used three invocations of the eval method, but a single invoke is possible using a here document:

>> R.eval <<EOF
        x <- rnorm(#{sample_size})
        summary(x)
        sd(x)
   EOF

Parameters that can be passed to the eval method

  • string: The string parameter is the code which is to be passed to R, for example, string = “hist(gamma(1000,5,3))”. The string can also span several lines of code by use of a here document, as shown:

    R.eval <<EOF
       x<-rgamma(1000,5,3)
       hist(x)
    EOF
    
  • echo_override: This argument allows one to set the echo behavior for this call only. The default for echo_override is nil, which does not override the current echo behavior.

Raises:



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
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
# File 'lib/rinruby.rb', line 240

def eval(string, echo_override=nil)
  raise EngineClosed if @engine.closed?
  echo_enabled = ( echo_override != nil ) ? echo_override : @echo_enabled
  if complete?(string)
    @writer.puts string
    @writer.puts "warning('#{RinRuby_Stderr_Flag}',immediate.=TRUE)" if @echo_stderr
    @writer.puts "print('#{RinRuby_Eval_Flag}')"
  else
    raise ParseError, "Parse error on eval:#{string}"
  end
  Signal.trap('INT') do
    @writer.print ''
    @reader.gets if @platform !~ /java/
    Signal.trap('INT') do
    end
    return true
  end
  found_eval_flag = false
  found_stderr_flag = false
  while true
    echo_eligible = true
    begin
      line = @reader.gets
    rescue
      return false
    end
    if ! line
      return false
    end
    while line.chomp!
    end
    line = line[8..-1] if line[0] == 27     # Delete escape sequence
    if line == "[1] \"#{RinRuby_Eval_Flag}\""
      found_eval_flag = true
      echo_eligible = false
    end
    if line == "Warning: #{RinRuby_Stderr_Flag}"
      found_stderr_flag = true
      echo_eligible = false
    end
    break if found_eval_flag && ( found_stderr_flag == @echo_stderr )
    return false if line == RinRuby_Exit_Flag
    if echo_enabled && echo_eligible
      puts line
      $stdout.flush if @platform !~ /windows/
    end
  end
  Signal.trap('INT') do
  end
  true
end

#prompt(regular_prompt = "> ", continue_prompt = "+ ") ⇒ Object

When sending code to Ruby using an interactive prompt, this method will change the prompt to an R prompt. From the R prompt commands can be sent to R exactly as if the R program was actually running. When the user is ready to return to Ruby, then the command exit() will return the prompt to Ruby. This is the ideal situation for the explorative programmer who needs to run several lines of code in R, and see the results after each command. This is also an easy way to execute loops without the use of a here document. It should be noted that the prompt command does not work in a script, just Ruby’s interactive irb.

Parameters that can be passed to the prompt method:

  • regular_prompt: This defines the string used to denote the R prompt.

  • continue_prompt: This is the string used to denote R’s prompt for an incomplete statement (such as a multiple for loop).



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/rinruby.rb', line 300

def prompt(regular_prompt="> ", continue_prompt="+ ")
  raise "The 'prompt' method only available in 'interactive' mode" if ! @interactive
  return false if ! eval("0",false)
  prompt = regular_prompt
  while true
    cmds = []
    while true
      if @readline && @interactive
        cmd = Readline.readline(prompt,true)
      else
        print prompt
        $stdout.flush
        cmd = gets.strip
      end
      cmds << cmd
      begin
        if complete?(cmds.join("\n"))
          prompt = regular_prompt
          break
        else
          prompt = continue_prompt
        end
      rescue
        puts "Parse error"
        prompt = regular_prompt
        cmds = []
        break
      end
    end
    next if cmds.length == 0
    break if cmds.length == 1 && cmds[0] == "exit()"
    break if ! eval(cmds.join("\n"),true)
  end
  true
end

#pull(string, singletons = false) ⇒ Object

Data is copied from R to Ruby using the pull method or a short-hand equivalent. The R object x defined with an eval method can be copied to Ruby object copy_of_x as follows:

>> R.eval "x <- rnorm(10)"
>> copy_of_x = R.pull "x"
>> puts copy_of_x

which produces the following :

-0.376404489256671
-1.0759798269397
-0.494240140140996
0.131171385795721
-0.878328334369391
-0.762290423047929
-0.410227216105828
0.0445512804225151
-1.88887454545995
0.781602719849499

RinRuby also supports a convenient short-hand notation when the argument to pull is simply a previously-defined R object (whose name conforms to Ruby’s requirements for method names). For example:

>> copy_of_x = R.x

The explicit assign method, however, can take an arbitrary R statement. For example:

>> summary_of_x = R.pull "as.numeric(summary(x))"
>> puts summary_of_x

produces the following:

-1.889
-0.8493
-0.4522
-0.4929
-0.06069
0.7816

Notice the use above of the as.numeric function in R. This is necessary since the pull method only supports R vectors which are numeric (i.e., integers or doubles) and character (i.e., strings). Data in other formats must be coerced when copying to Ruby.

Parameters that can be passed to the pull method:

  • string: The name of the variable that should be pulled from R. The pull method only supports R vectors which are numeric (i.e., integers or doubles) or character (i.e., strings). The R value of NA is pulled as nil into Ruby. Data in other formats must be coerced when copying to Ruby.

  • singletons: R represents a single number as a vector of length one, but in Ruby it is often more convenient to use a number rather than an array of length one. Setting singleton=false will cause the pull method to shed the array, while singletons=true will return the number of string within an array. The default is false.

The pull method is an alternative to the simplified form where the parameters are automatically used. For example:

>> puts R.test

is the same as:

>> puts R.pull("test")

Raises:



464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/rinruby.rb', line 464

def pull(string, singletons=false)
  raise EngineClosed if @engine.closed?
  if complete?(string)
    result = pull_engine(string)
    if ( ! singletons ) && ( result.length == 1 ) && ( result.class != String )
      result = result[0]
    end
    result
  else
    raise ParseError, "Parse error"
  end
end

#quitObject

The quit method will properly close the bridge between Ruby and R, freeing up system resources. This method does not need to be run when a Ruby script ends.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/rinruby.rb', line 189

def quit
  begin 
    @writer.puts "q(save='no')"
    # TODO: Verify if read is needed
    @socket.read()
    #@socket.close 
    @engine.close 

    
    @server_socket.close 
    #@reader.close 
    #@writer.close
    true
  ensure
    @engine.close unless @engine.closed?
    @server_socket.close unless @server_socket.closed?
  end
end