rri 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|