rri 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +3 -0
- data/.gemtest +0 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +16 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +131 -0
- data/Rakefile +66 -0
- data/examples/create_pdf.rb +18 -0
- data/examples/ggplot.rb +42 -0
- data/examples/simple.rb +19 -0
- data/gemspec.yml +11 -0
- data/lib/rri.rb +11 -0
- data/lib/rri/callback_objects.rb +1 -0
- data/lib/rri/callback_objects/r_engine_std_output.rb +6 -0
- data/lib/rri/engine.rb +390 -0
- data/lib/rri/java_gd.rb +23 -0
- data/lib/rri/jri.rb +24 -0
- data/lib/rri/r_converters.rb +4 -0
- data/lib/rri/r_converters/array_converter.rb +59 -0
- data/lib/rri/r_converters/float_converter.rb +28 -0
- data/lib/rri/r_converters/integer_converter.rb +28 -0
- data/lib/rri/r_converters/string_converter.rb +28 -0
- data/lib/rri/rexp.rb +8 -0
- data/lib/rri/rri_exception.rb +5 -0
- data/lib/rri/ruby_converters.rb +2 -0
- data/lib/rri/ruby_converters/double_converter.rb +28 -0
- data/lib/rri/ruby_converters/integer_converter.rb +28 -0
- data/lib/rri/version.rb +5 -0
- data/rri.gemspec +15 -0
- data/spec/rri/engine_spec.rb +203 -0
- data/spec/rri/r_converters/array_converter_spec.rb +56 -0
- data/spec/rri/r_converters/float_converter_spec.rb +19 -0
- data/spec/rri/r_converters/integer_converter_spec.rb +19 -0
- data/spec/rri/r_converters/string_converter_spec.rb +19 -0
- data/spec/rri/ruby_converters/double_converter.rb +20 -0
- data/spec/rri/ruby_converters/integer_converter_spec.rb +20 -0
- data/spec/rri_spec.rb +8 -0
- data/spec/spec_helper.rb +4 -0
- metadata +126 -0
data/.document
ADDED
data/.gemtest
ADDED
File without changes
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown --title "rri Documentation" --protected
|
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source :rubygems
|
2
|
+
|
3
|
+
#gemspec
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'rake', '~> 0.8.7'
|
7
|
+
gem 'ore-tasks', '~> 0.4'
|
8
|
+
gem 'rspec', '~> 2.5'
|
9
|
+
gem 'kramdown'
|
10
|
+
gem 'yard', '~> 0.6.0'
|
11
|
+
gem 'yardstick'
|
12
|
+
gem 'awesome_print'
|
13
|
+
gem 'guard', '~> 0.3.0'
|
14
|
+
gem 'guard-rspec', '~> 0.2.0'
|
15
|
+
gem 'rcov', '~> 0.9.9'
|
16
|
+
end
|
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Patrik Sundberg
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# rri
|
2
|
+
|
3
|
+
* [Homepage](https://github.com/sundbp/rri)
|
4
|
+
* [Documentation](http://rubydoc.info/gems/rri/frames)
|
5
|
+
|
6
|
+
## Description
|
7
|
+
|
8
|
+
This gem provides an interface from ruby to R by using [JRI](http://www.rforge.net/JRI/)
|
9
|
+
(the Java/R Interface) from JRuby. JRI is a well established way of interfacing with R from Java
|
10
|
+
and hence a great building block to enable JRuby to interface with R.
|
11
|
+
|
12
|
+
This gem also enables the use of the R package [JavaGD](http://www.rforge.net/JavaGD/). JavaGD is
|
13
|
+
a R graphics device used to output graphics to a Java class. This makes it possible to easily
|
14
|
+
integrate R graphics with java/JVM GUI applications (for example it is used by
|
15
|
+
[JGR](http://www.rforge.net/JGR/)). JavaGD allows you to provide a custom class used for painting
|
16
|
+
which could well be implemented in jruby. Read the JavaGD package documentation for more information.
|
17
|
+
|
18
|
+
At the moment this gem is mostly aimed towards using R from ruby, but there's nothing stopping us
|
19
|
+
from taking advantage of [rJava](http://www.rforge.net/rJava/) to also allow R to call into jruby.
|
20
|
+
|
21
|
+
## Features
|
22
|
+
|
23
|
+
* Call R functions from ruby
|
24
|
+
* An extendible system for converting between R and ruby data types (in both directions)
|
25
|
+
* Plot R graphics using user supplied class
|
26
|
+
|
27
|
+
## Examples
|
28
|
+
|
29
|
+
require 'rri'
|
30
|
+
|
31
|
+
engine = Rri::Engine.new
|
32
|
+
|
33
|
+
# high level API, converts results and arguments from and to R/ruby
|
34
|
+
result = engine.eval_and_convert("1.23 + 4.56") # result will be a noromal ruby Float (5.79)
|
35
|
+
engine.convert_and_assign(result, :x) # x = 5.79 in R
|
36
|
+
result = engine.get_and_convert(:x) # result is a ruby Float
|
37
|
+
|
38
|
+
# helpers for the low level API, useful when you're not bothered
|
39
|
+
# about converting any values to/from R/ruby
|
40
|
+
result = engine.simple_eval("x + 1") # returns a reference to R object
|
41
|
+
engine.simple_assign("y", result) # y = 6.79 in R, assumes the value is an R object,
|
42
|
+
# no conversion takes place.
|
43
|
+
result = engine.simple_get(:y) # returns a reference to R object
|
44
|
+
|
45
|
+
# we should always close an engine, if we don't the R thread
|
46
|
+
# seem to hang around forever causing the ruby program to not
|
47
|
+
# exit. There is however also a finalizer defined that should
|
48
|
+
# automatically take care of this.
|
49
|
+
#
|
50
|
+
# use normal JRIEngine API via method missing forwarding
|
51
|
+
engine.close
|
52
|
+
|
53
|
+
Please also see the examples in the 'examples/' directory for more interesting use cases.
|
54
|
+
|
55
|
+
## Requirements
|
56
|
+
|
57
|
+
* An installation of [R](http://www.r-project.org/)
|
58
|
+
* The R packages **rJava** and **JavaGD**
|
59
|
+
|
60
|
+
## Install
|
61
|
+
|
62
|
+
First we need the rri gem:
|
63
|
+
|
64
|
+
$ gem install rri
|
65
|
+
|
66
|
+
Next we switch to R to install the required R packages:
|
67
|
+
|
68
|
+
> install.packages(c("rJava", "JavaGD"))
|
69
|
+
|
70
|
+
After this we need to make sure rri is able to find the needed jar-files. rri will attempt
|
71
|
+
to read the jars in the path specified by the environment variables:
|
72
|
+
|
73
|
+
* **RRI_JRI_JAR_PATH**
|
74
|
+
* **RRI_JAVAGD_JAR_PATH**
|
75
|
+
|
76
|
+
Set the first environment variable to the path given by the following R command:
|
77
|
+
|
78
|
+
> system.file("jri",package="rJava")
|
79
|
+
|
80
|
+
and the second one to the path given by:
|
81
|
+
|
82
|
+
> system.file("java", package="JavaGD")
|
83
|
+
|
84
|
+
Make sure the OS can load dynamic libraries from the R directory. On Windows that means
|
85
|
+
making it part of the path. On my machine it means adding "C:\Program Files\R\R-2.12.1\bin\i386"
|
86
|
+
to the **PATH**.
|
87
|
+
|
88
|
+
The last thing we need to make sure of is that the shared library distributed with JRI is
|
89
|
+
accessible by the operating system. I have not gone over this exercise on unix type systems
|
90
|
+
yet but on Windows this means that the path set in **RRI_JRI_JAR_PATH** is also part of the
|
91
|
+
**PATH**.
|
92
|
+
|
93
|
+
I tried to add this to the path within the rri gem itself before loading the JRI jars
|
94
|
+
but it seems the jruby class loader fails to pick it up so it needs to be part of the **PATH**
|
95
|
+
before a program using rri is launched. On linux like systems I'd expect there may be a need
|
96
|
+
for something similar in relation to **LD_LIBRARY_PATH** but I haven't had a chance to test it yet.
|
97
|
+
|
98
|
+
## Type conversions
|
99
|
+
|
100
|
+
R and ruby have quite different type systems, but at least for the most common types of
|
101
|
+
data we can implement some natural conversions (in both directions).
|
102
|
+
|
103
|
+
Type conversion in both directions are tried in 3 levels:
|
104
|
+
* first custom specified (user) converters for a given engine are tried
|
105
|
+
* then custom specified (user) converters applicable to all engines are tried
|
106
|
+
* and finally a set of default converters specified by rri itself are applied
|
107
|
+
|
108
|
+
For custom converters the order in which they are tried is the reverse order
|
109
|
+
in which they were added, so the latest added converter is tried first.
|
110
|
+
|
111
|
+
To write your own custom converters please take a look at the default ones
|
112
|
+
in rri/r_converters and rri/ruby_converters. For help with the R type system
|
113
|
+
the REXP [JavaDocs](http://www.rforge.net/org/docs/org/rosuda/REngine/REXP.html)
|
114
|
+
are a good place to start.
|
115
|
+
|
116
|
+
**TODO**: describe the default converters, for now best docs are the specs
|
117
|
+
|
118
|
+
## Note about instantiating engines many times in the same process
|
119
|
+
|
120
|
+
There seem to be issues in the C layer of either JRI or the R library when
|
121
|
+
you create an engine many times in the same process. That applies also if
|
122
|
+
you never have more than one engine created at any one time.
|
123
|
+
|
124
|
+
Emperically I get an infinite hang in the C layer if I re-create an engine
|
125
|
+
more than ~12 times in the same process.
|
126
|
+
|
127
|
+
## Copyright
|
128
|
+
|
129
|
+
Copyright (c) 2011 Patrik Sundberg
|
130
|
+
|
131
|
+
See {file:LICENSE.txt} for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler'
|
5
|
+
rescue LoadError => e
|
6
|
+
STDERR.puts e.message
|
7
|
+
STDERR.puts "Run `gem install bundler` to install Bundler."
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
|
11
|
+
begin
|
12
|
+
Bundler.setup(:development)
|
13
|
+
rescue Bundler::BundlerError => e
|
14
|
+
STDERR.puts e.message
|
15
|
+
STDERR.puts "Run `bundle install` to install missing gems."
|
16
|
+
exit e.status_code
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rake'
|
20
|
+
|
21
|
+
require 'ore/tasks'
|
22
|
+
Ore::Tasks.new
|
23
|
+
|
24
|
+
require 'rspec/core/rake_task'
|
25
|
+
RSpec::Core::RakeTask.new
|
26
|
+
|
27
|
+
task :test => :spec
|
28
|
+
task :default => :spec
|
29
|
+
|
30
|
+
require 'yard'
|
31
|
+
YARD::Rake::YardocTask.new
|
32
|
+
task :doc => :yard
|
33
|
+
|
34
|
+
require 'yardstick/rake/measurement'
|
35
|
+
Yardstick::Rake::Measurement.new(:yardstick_measure) do |measurement|
|
36
|
+
measurement.output = 'measurement/report.txt'
|
37
|
+
end
|
38
|
+
|
39
|
+
require 'yardstick/rake/verify'
|
40
|
+
Yardstick::Rake::Verify.new do |verify|
|
41
|
+
verify.threshold = 80
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "Run specs with rcov"
|
45
|
+
RSpec::Core::RakeTask.new("spec:rcov") do |t|
|
46
|
+
t.rcov = true
|
47
|
+
t.rcov_opts = %w{--exclude "spec\/,jsignal_internal"}
|
48
|
+
end
|
49
|
+
|
50
|
+
# for rcov threshold testing
|
51
|
+
rcov_coverage_threshold = 90
|
52
|
+
require_exact_rcov_threshold = false
|
53
|
+
|
54
|
+
desc "Verify that rcov coverage is at least #{rcov_coverage_threshold}%"
|
55
|
+
task :verify_rcov => "spec:rcov" do
|
56
|
+
total_coverage = 0
|
57
|
+
File.open("coverage/index.html").each_line do |line|
|
58
|
+
if line =~ /<tt class='coverage_total'>\s*(\d+\.\d+)%\s*<\/tt>/
|
59
|
+
total_coverage = $1.to_f
|
60
|
+
break
|
61
|
+
end
|
62
|
+
end
|
63
|
+
puts "Coverage: #{total_coverage}% (threshold: #{rcov_coverage_threshold}%)"
|
64
|
+
raise "Coverage must be at least #{rcov_coverage_threshold}% but was #{total_coverage}%" if total_coverage < rcov_coverage_threshold
|
65
|
+
raise "Coverage has increased above the threshold of #{rcov_coverage_threshold}% to #{total_coverage}%. You should update your threshold value." if (total_coverage > threshold) and require_exact_rcov_threshold
|
66
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# example of producing a pdf from an R plot
|
2
|
+
|
3
|
+
$LOAD_PATH << File.dirname(__FILE__) + "/../lib"
|
4
|
+
require 'rri'
|
5
|
+
|
6
|
+
engine = Rri::Engine.new
|
7
|
+
|
8
|
+
filename = "plot_from_r.pdf"
|
9
|
+
begin
|
10
|
+
engine.simple_eval("data(iris)")
|
11
|
+
engine.simple_eval("pdf('#{filename}')")
|
12
|
+
engine.simple_eval("print(stripchart(iris[, 1:4], method = 'stack', pch = 16, cex = 0.4, offset = 0.6))")
|
13
|
+
engine.simple_eval("dev.off()")
|
14
|
+
rescue => e
|
15
|
+
puts "Caught exception: " + e
|
16
|
+
end
|
17
|
+
|
18
|
+
puts "Checkout plot generated in file: #{filename}"
|
data/examples/ggplot.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# example of drawing a density with ggplot2
|
2
|
+
|
3
|
+
$LOAD_PATH << File.dirname(__FILE__) + "/../lib"
|
4
|
+
require 'rri'
|
5
|
+
|
6
|
+
# Ruby Gaussian Random Number Generator
|
7
|
+
# Author: Glenn
|
8
|
+
# http://webhost101.net/rails/typo/articles/2007/07/31/ruby-gaussian-random-number-generator
|
9
|
+
|
10
|
+
def gaussian_rand
|
11
|
+
u1 = u2 = w = g1 = g2 = 0 # declare
|
12
|
+
begin
|
13
|
+
u1 = 2 * rand - 1
|
14
|
+
u2 = 2 * rand - 1
|
15
|
+
w = u1 * u1 + u2 * u2
|
16
|
+
end while w >= 1
|
17
|
+
|
18
|
+
w = Math::sqrt( ( -2 * Math::log(w)) / w )
|
19
|
+
g2 = u1 * w;
|
20
|
+
g1 = u2 * w;
|
21
|
+
# g1 is returned
|
22
|
+
end
|
23
|
+
|
24
|
+
random_numbers = []
|
25
|
+
1000.times { random_numbers << gaussian_rand }
|
26
|
+
|
27
|
+
# use the std out callback object to get some printouts from R, good for debugging.
|
28
|
+
engine = Rri::Engine.new(:callback_object => Rri::CallbackObjects::REngineStdOutput.new)
|
29
|
+
|
30
|
+
filename = "ggplot.pdf"
|
31
|
+
begin
|
32
|
+
engine.simple_eval("library(ggplot2)")
|
33
|
+
engine.convert_and_assign(random_numbers, :y)
|
34
|
+
engine.simple_eval("df <- data.frame(y)")
|
35
|
+
engine.simple_eval("pdf('#{filename}')")
|
36
|
+
engine.simple_eval("print(qplot(y, data=df, geom='histogram', binwidth=0.1))")
|
37
|
+
engine.simple_eval("dev.off()")
|
38
|
+
rescue => e
|
39
|
+
puts "Caught exception: " + e
|
40
|
+
end
|
41
|
+
|
42
|
+
puts "Checkout plot generated in file: #{filename}"
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# example of just setting up an engine, adding two numbers in R and retrieving the result
|
2
|
+
|
3
|
+
$LOAD_PATH << File.dirname(__FILE__) + "/../lib"
|
4
|
+
require 'rri'
|
5
|
+
|
6
|
+
engine = Rri::Engine.new
|
7
|
+
|
8
|
+
# low level interface
|
9
|
+
result = engine.simple_eval("1.23 + 4.56")
|
10
|
+
puts "Result is: #{result} (which is of type #{result.class})"
|
11
|
+
engine.simple_assign(:x, result)
|
12
|
+
|
13
|
+
# higher level interface including conversions
|
14
|
+
result = engine.eval_and_convert("y <- x + 1")
|
15
|
+
result = engine.get_and_convert(:y)
|
16
|
+
puts "Result is: #{result} (which is of type #{result.class})"
|
17
|
+
|
18
|
+
# close the engine
|
19
|
+
engine.close
|
data/gemspec.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
name: rri
|
2
|
+
summary: "Ruby/R Interface for JRuby"
|
3
|
+
description: "Ruby/R Interface - A wrapper of jri (Java/R Interface) for JRuby"
|
4
|
+
license: MIT
|
5
|
+
authors: Patrik Sundberg
|
6
|
+
homepage: http://rubygems.org/gems/rri
|
7
|
+
has_yard: true
|
8
|
+
|
9
|
+
development_dependencies:
|
10
|
+
bundler: ~> 1.0.0
|
11
|
+
yard: ~> 0.6.0
|
data/lib/rri.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rri/callback_objects/r_engine_std_output'
|
data/lib/rri/engine.rb
ADDED
@@ -0,0 +1,390 @@
|
|
1
|
+
require 'rri/jri'
|
2
|
+
require 'rri/r_converters'
|
3
|
+
require 'rri/ruby_converters'
|
4
|
+
|
5
|
+
module Rri
|
6
|
+
|
7
|
+
# The main class to interface with R
|
8
|
+
#
|
9
|
+
# Engine internally holds an instance of the java class JRIEngine
|
10
|
+
# which is used for all interaction with R. On top of this it provides
|
11
|
+
# a few abstractions to easily convert between ruby and R types.
|
12
|
+
#
|
13
|
+
# The main methods of the high level API are {#eval_and_convert} and {#convert_and_assign}.
|
14
|
+
# The high level API converts to/from R/ruby using a type conversion system described
|
15
|
+
# in the README.
|
16
|
+
#
|
17
|
+
# It also provides a couple of helper methods just to make the users life easier when
|
18
|
+
# working with the lower level java API, most notably {#simple_eval} and {#simple_assign}.
|
19
|
+
# These functions do not convert values but instead work with references to R objects
|
20
|
+
# and are preferred when you don't really care about converting to/from ruby (e.g.
|
21
|
+
# intermediate steps of calculations and similar)
|
22
|
+
#
|
23
|
+
# Apart from the methods on this ruby class the user can also use any other method
|
24
|
+
# on JRIEngine, it will be forwarded via {#method_missing}.
|
25
|
+
class Engine
|
26
|
+
|
27
|
+
# Default options used when creating an engine
|
28
|
+
DEFAULT_ENGINE_OPTIONS = {
|
29
|
+
:r_arguments => ["--no-save"],
|
30
|
+
:callback_object => nil,
|
31
|
+
:run_repl => false
|
32
|
+
}
|
33
|
+
|
34
|
+
# Add a custom converter used to convert ruby objects to R objects
|
35
|
+
#
|
36
|
+
# @param [#convert] converter the converter to add
|
37
|
+
# @return [Array] known (class level) converters
|
38
|
+
def self.add_r_converter(converter)
|
39
|
+
@@r_converters ||= []
|
40
|
+
@@r_converters << converter
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get all class level custom converters to convert ruby objects to R objects
|
44
|
+
#
|
45
|
+
# Converters are returned in the reversed order to how they were added
|
46
|
+
# (i.e. the last added is applied first)
|
47
|
+
#
|
48
|
+
# @return [Array] all class level custom converters
|
49
|
+
def self.r_converters
|
50
|
+
@@r_converters ||= []
|
51
|
+
@@r_converters.reverse
|
52
|
+
end
|
53
|
+
|
54
|
+
# Clear all class level custom R converters
|
55
|
+
def self.clear_r_converters
|
56
|
+
@@r_converters = []
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get default converters to convert ruby objects to R objects
|
60
|
+
#
|
61
|
+
# @return [Array] all default converters
|
62
|
+
def self.default_r_converters
|
63
|
+
[
|
64
|
+
RConverters::FloatConverter.new,
|
65
|
+
RConverters::IntegerConverter.new,
|
66
|
+
RConverters::StringConverter.new,
|
67
|
+
RConverters::ArrayConverter.new
|
68
|
+
]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Add a custom converter used to convert R objects to ruby objects
|
72
|
+
#
|
73
|
+
# @param [#convert] converter the converter to add
|
74
|
+
# @return [Array] known (class level) converters
|
75
|
+
def self.add_ruby_converter(converter)
|
76
|
+
@@ruby_converters ||= []
|
77
|
+
@@ruby_converters << converter
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get all class level custom converters to convert R objects to ruby objects
|
81
|
+
#
|
82
|
+
# Converters are returned in the reversed order to how they were added
|
83
|
+
# (i.e. the last added is applied first)
|
84
|
+
#
|
85
|
+
# @return [Array] all class level custom converters
|
86
|
+
def self.ruby_converters
|
87
|
+
@@ruby_converters ||= []
|
88
|
+
@@ruby_converters.reverse
|
89
|
+
end
|
90
|
+
|
91
|
+
# Clear all class level custom ruby converters
|
92
|
+
def self.clear_ruby_converters
|
93
|
+
@@ruby_converters = []
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get default converters to convert R objects to ruby objects
|
97
|
+
#
|
98
|
+
# @return [Array] all default converters
|
99
|
+
def self.default_ruby_converters
|
100
|
+
[
|
101
|
+
RubyConverters::DoubleConverter.new,
|
102
|
+
RubyConverters::IntegerConverter.new,
|
103
|
+
#RubyConverters::StringConverter.new,
|
104
|
+
]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Helper to make sure all engines are finalized so the R thread dies as it should
|
108
|
+
#
|
109
|
+
# @param engine engine to finalize
|
110
|
+
# @return [Proc] that closes the engine
|
111
|
+
def self.finalize(engine)
|
112
|
+
proc { engine.close }
|
113
|
+
end
|
114
|
+
|
115
|
+
# Create a new instance of Engine
|
116
|
+
#
|
117
|
+
# @param [Hash] options A Hash of options to override the defaults, or not given
|
118
|
+
# in which case the defaults will be used.
|
119
|
+
def initialize(options = {})
|
120
|
+
combined_options = DEFAULT_ENGINE_OPTIONS.merge(options)
|
121
|
+
|
122
|
+
@engine = Jri::JRIEngine.new(combined_options[:r_arguments].to_java(:string),
|
123
|
+
combined_options[:callback_object],
|
124
|
+
combined_options[:run_repl])
|
125
|
+
|
126
|
+
# the R thread wont die unless we call #close on @engine, so make sure this
|
127
|
+
# happens when this object is finalized.
|
128
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@engine))
|
129
|
+
end
|
130
|
+
|
131
|
+
# Forward any method calls not recognized to JRIEngine
|
132
|
+
def method_missing(method, *args, &block)
|
133
|
+
if @engine.respond_to? method
|
134
|
+
@engine.send(method, *args, &block)
|
135
|
+
else
|
136
|
+
super
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Add a custom converter used to convert ruby objects to R objects
|
141
|
+
#
|
142
|
+
# @param [#convert] converter the converter to add
|
143
|
+
# @return [Array] known (instance level) converters
|
144
|
+
def add_r_converter(converter)
|
145
|
+
@r_converters ||= []
|
146
|
+
@r_converters << converter
|
147
|
+
end
|
148
|
+
|
149
|
+
# Get all instance level custom converters to convert ruby objects to R objects
|
150
|
+
#
|
151
|
+
# Converters are returned in the reversed order to how they were added
|
152
|
+
# (i.e. the last added is applied first)
|
153
|
+
#
|
154
|
+
# @return [Array] all instance level custom converters
|
155
|
+
def r_converters
|
156
|
+
@r_converters ||= []
|
157
|
+
@r_converters.reverse
|
158
|
+
end
|
159
|
+
|
160
|
+
# Clear all instance level custom R converters
|
161
|
+
def clear_r_converters
|
162
|
+
@r_converters = []
|
163
|
+
end
|
164
|
+
|
165
|
+
# Add a custom converter used to convert R objects to ruby objects
|
166
|
+
#
|
167
|
+
# @param [#convert] converter the converter to add
|
168
|
+
# @return [Array] known (instance level) converters
|
169
|
+
def add_ruby_converter(converter)
|
170
|
+
@ruby_converters ||= []
|
171
|
+
@ruby_converters << converter
|
172
|
+
end
|
173
|
+
|
174
|
+
# Clear all instance level custom R converters
|
175
|
+
def clear_ruby_converters
|
176
|
+
@ruby_converters = []
|
177
|
+
end
|
178
|
+
|
179
|
+
# Get all instance level custom converters to convert R objects to ruby objects
|
180
|
+
#
|
181
|
+
# Converters are returned in the reversed order to how they were added
|
182
|
+
# (i.e. the last added is applied first)
|
183
|
+
#
|
184
|
+
# @return [Array] all instance level custom converters
|
185
|
+
def ruby_converters
|
186
|
+
@ruby_converters ||= []
|
187
|
+
@ruby_converters.reverse
|
188
|
+
end
|
189
|
+
|
190
|
+
# Helper method to evaluate expressions but avoid any R-to-ruby conversions
|
191
|
+
#
|
192
|
+
# Always uses the global environment and returns an R reference which the
|
193
|
+
# user can pass along to other R functions later on.
|
194
|
+
#
|
195
|
+
# @param [String] expression the R expression to evaluate
|
196
|
+
# @return [REXPReference] reference to the result of the expression
|
197
|
+
def simple_eval(expression)
|
198
|
+
parsed_expression = @engine.parse(expression, false)
|
199
|
+
@engine.eval(parsed_expression, nil, false)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Eval expression and convert result to the corresponding ruby type
|
203
|
+
#
|
204
|
+
# @param [String] expression the R epxression to evaluate
|
205
|
+
# @return the result converted to the corresponding ruby type
|
206
|
+
def eval_and_convert(expression)
|
207
|
+
success, value = convert_to_ruby_object(@engine.parseAndEval(expression))
|
208
|
+
raise RriException.new("Failed to convert R object to ruby object for: #{value}") unless success
|
209
|
+
value
|
210
|
+
end
|
211
|
+
|
212
|
+
# Helper method to assign expressions to R variables
|
213
|
+
#
|
214
|
+
# Always uses the global environment.
|
215
|
+
#
|
216
|
+
# @param [#to_s] symbol what to call the R variable
|
217
|
+
# @return [nil]
|
218
|
+
def simple_assign(symbol, rexp)
|
219
|
+
@engine.assign(symbol.to_s, rexp, nil)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Convert a ruby value to an R type and assign it to an R variable.
|
223
|
+
#
|
224
|
+
# @param obj ruby object to convert and assign to R varible
|
225
|
+
# @param [#to_s] symbol what to call the R variable
|
226
|
+
# @return [nil]
|
227
|
+
def convert_and_assign(obj, symbol)
|
228
|
+
success, rexp = convert_to_r_object(obj)
|
229
|
+
raise RriException.new("Failed to convert ruby object to R object for: #{obj}") unless success
|
230
|
+
simple_assign(symbol, rexp)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Helper method to get a variable from R
|
234
|
+
#
|
235
|
+
# Always uses the global environment.
|
236
|
+
#
|
237
|
+
# @param [#to_s] symbol name of the R variable to get
|
238
|
+
# @return [REXPReference] reference to the R variable
|
239
|
+
def simple_get(symbol)
|
240
|
+
@engine.get(symbol.to_s, nil, false)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Get a variable from R and convert it to a ruby object
|
244
|
+
#
|
245
|
+
# Always uses the global environment.
|
246
|
+
#
|
247
|
+
# @param [#to_s] symbol name of the R variable to get
|
248
|
+
# @return the R variable converted to a ruby object
|
249
|
+
def get_and_convert(symbol)
|
250
|
+
rexp = @engine.get(symbol.to_s, nil, true)
|
251
|
+
success, value = convert_to_ruby_object(rexp)
|
252
|
+
raise RriException.new("Failed to convert R object to ruby object for: #{value}") unless success
|
253
|
+
value
|
254
|
+
end
|
255
|
+
|
256
|
+
# Convert a ruby object to a R object
|
257
|
+
#
|
258
|
+
# Applies converters in 3 levels:
|
259
|
+
# * custom converters that is set for only this engine instance
|
260
|
+
# * custom converters that are set for all engine instances
|
261
|
+
# * default converters
|
262
|
+
#
|
263
|
+
# Converters are applied in the reverse order they were added in.
|
264
|
+
#
|
265
|
+
# @param obj ruby object to convert
|
266
|
+
# @return [Array] an array of size 2 where first element is a boolean indicating succes,
|
267
|
+
# and the second element is the converted object if conversion successful (otherwise
|
268
|
+
# the original obj)
|
269
|
+
def convert_to_r_object(obj)
|
270
|
+
# first try converters defined for just this instance
|
271
|
+
success, value = apply_local_r_converters(obj)
|
272
|
+
return [success, value] if success
|
273
|
+
|
274
|
+
# then try converters defined in general
|
275
|
+
success, value = apply_r_converters(obj)
|
276
|
+
return [success, value] if success
|
277
|
+
|
278
|
+
# and finally apply the default converters
|
279
|
+
success, value = apply_default_r_converters(obj)
|
280
|
+
return [success, value] if success
|
281
|
+
|
282
|
+
# give up
|
283
|
+
[false, obj]
|
284
|
+
end
|
285
|
+
|
286
|
+
# Convert an R object to a ruby object
|
287
|
+
#
|
288
|
+
# Applies converters in 3 levels:
|
289
|
+
# * custom converters that is set for only this engine instance
|
290
|
+
# * custom converters that are set for all engine instances
|
291
|
+
# * default converters
|
292
|
+
#
|
293
|
+
# Converters are applied in the reverse order they were added in.
|
294
|
+
#
|
295
|
+
# @param [REXP] rexp R object to convert
|
296
|
+
# @return [Array] an array of size 2 where first element is a boolean indicating succes,
|
297
|
+
# and the second element is the converted object if conversion was successful (otherwise
|
298
|
+
# the original rexp)
|
299
|
+
def convert_to_ruby_object(rexp)
|
300
|
+
# first try converters defined for just this instance
|
301
|
+
success, value = apply_local_ruby_converters(rexp)
|
302
|
+
return [success, value] if success
|
303
|
+
|
304
|
+
# then try converters defined in general
|
305
|
+
success, value = apply_ruby_converters(rexp)
|
306
|
+
return [success, value] if success
|
307
|
+
|
308
|
+
# and finally apply the default converters
|
309
|
+
success, value = apply_default_ruby_converters(rexp)
|
310
|
+
return [success, value] if success
|
311
|
+
|
312
|
+
# give up
|
313
|
+
[false, rexp]
|
314
|
+
end
|
315
|
+
|
316
|
+
############################ PRIVATE METHODS ##############################
|
317
|
+
|
318
|
+
private
|
319
|
+
|
320
|
+
# Helper method to apply converters
|
321
|
+
#
|
322
|
+
# @param [Array] converters array of converters to try to apply
|
323
|
+
# @param obj object to try to convert
|
324
|
+
# @return [Array] an array of size 2 where first element is a boolean indicating succes,
|
325
|
+
# and the second element is the converted object if conversion was successful
|
326
|
+
def apply_converters(converters, obj)
|
327
|
+
converters.each do |converter|
|
328
|
+
success, value = converter.convert(obj)
|
329
|
+
return [success, value] if success
|
330
|
+
end
|
331
|
+
# if no success, return failure and nil for value
|
332
|
+
[false, nil]
|
333
|
+
end
|
334
|
+
|
335
|
+
# Apply custom converters that are defined just for this engine to a ruby object
|
336
|
+
#
|
337
|
+
# @param obj ruby object to convert to R type
|
338
|
+
# @return [Array] an array of size 2 where first element is a boolean indicating succes,
|
339
|
+
# and the second element is the converted object if conversion was successful
|
340
|
+
def apply_local_r_converters(obj)
|
341
|
+
apply_converters(r_converters, obj)
|
342
|
+
end
|
343
|
+
|
344
|
+
# Apply custom converters that are defined for all engines to a ruby object
|
345
|
+
#
|
346
|
+
# @param obj ruby object to convert to R type
|
347
|
+
# @return [Array] an array of size 2 where first element is a boolean indicating succes,
|
348
|
+
# and the second element is the converted object if conversion was successful
|
349
|
+
def apply_r_converters(obj)
|
350
|
+
apply_converters(Engine.r_converters, obj)
|
351
|
+
end
|
352
|
+
|
353
|
+
# Apply default converters to ruby object
|
354
|
+
#
|
355
|
+
# @param obj ruby object to convert to R type
|
356
|
+
# @return [Array] an array of size 2 where first element is a boolean indicating succes,
|
357
|
+
# and the second element is the converted object if conversion was successful
|
358
|
+
def apply_default_r_converters(obj)
|
359
|
+
apply_converters(Engine.default_r_converters, obj)
|
360
|
+
end
|
361
|
+
|
362
|
+
# Apply custom converters that are defined just for this engine to an R object
|
363
|
+
#
|
364
|
+
# @param [REXP] rexp object to convert to ruby object
|
365
|
+
# @return [Array] an array of size 2 where first element is a boolean indicating succes,
|
366
|
+
# and the second element is the converted object if conversion was successful
|
367
|
+
def apply_local_ruby_converters(rexp)
|
368
|
+
apply_converters(ruby_converters, rexp)
|
369
|
+
end
|
370
|
+
|
371
|
+
# Apply custom converters that are defined for all engines to an R object
|
372
|
+
#
|
373
|
+
# @param [REXP] rexp R object to convert to ruby object
|
374
|
+
# @return [Array] an array of size 2 where first element is a boolean indicating succes,
|
375
|
+
# and the second element is the converted object if conversion was successful
|
376
|
+
def apply_ruby_converters(rexp)
|
377
|
+
apply_converters(Engine.ruby_converters, rexp)
|
378
|
+
end
|
379
|
+
|
380
|
+
# Apply default converters to R object
|
381
|
+
#
|
382
|
+
# @param [REXP] rexp R object to convert to ruby object
|
383
|
+
# @return [Array] an array of size 2 where first element is a boolean indicating succes,
|
384
|
+
# and the second element is the converted object if conversion was successful
|
385
|
+
def apply_default_ruby_converters(rexp)
|
386
|
+
apply_converters(Engine.default_ruby_converters, rexp)
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|
390
|
+
end
|