File: RserveClient.pl

package info (click to toggle)
libstatistics-r-io-perl 1.0002-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 5,824 kB
  • sloc: perl: 10,895; makefile: 2
file content (237 lines) | stat: -rw-r--r-- 6,843 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
=head1 NAME

RserveClient.pl - Macros for evaluating R code on an Rserve server

=head1 SYNPOSIS

=head1 SYNOPSIS

    loadMacros('RserveClient.pl');
    
    rserve_start();
    my @rnorm = rserve_eval("rnorm(15, mean=$m, sd=$sd)");
    rserve_eval(data(stackloss));
    my @coeff = rserve_eval('lm(stack.loss ~ stack.x, stackloss)$coeff');
    rserve_finish();


=head1 DESCRIPTION

The macros in this file provide access to facilities of L<R
statistical computing environment|http://www.r-project.org>,
optionally located on another server, by using the
L<Rserve|http://www.rforge.net/Rserve/> protocol. 

B<IMPORTANT:> Before you can use these macros, you will need to
configure the location of your Rserve host by adding it to
C<$pg{specialPGEnvironmentVars}{Rserve}{host}>, for instance by
appending the following line to F<webwork2/conf/localOverrides.conf>:

    $pg{specialPGEnvironmentVars}{Rserve} = {host => "localhost"};

Without this configuration in place, Rserve macros will only print out
a warning about missing configuration and return C<undef>.

=head1 MACROS

The macros in this file set up a connection to the R server and
pass a string parameter to R for evaluation.  The resulting
vector is returned as a perl array object.

=over 4

=item rserve_eval REXPR

Evaluates an R expression, given as text string in REXPR, on the
L<Rserve|http://www.rforge.net/Rserve/> server and returns its result
as a Perl representation of the L<Statistics::R::REXP> object.
Multiple calls within the same problem share the R session and the
object workspace.

=item rserve_query

Evaluates an R expression, given as text string in REXPR, in a
single-use session on the L<Rserve|http://www.rforge.net/Rserve/>
server and returns its result as a Perl representation of the
L<Statistics::R::REXP> object.

This function is different from C<rserve_eval> in that each call is
completely self-enclosed and its R session is discarded after it
returns.

=item rserve_start, rserve_finish

Start up and close the current connection to the Rserve server. In
normal use, these functions are completely optional because the first
call to C<rserve_eval> will call start the session if one is not
already open. Similarly, the current session will be closed in its
destructor when the current question goes out of scope.

Other than backward compatibility, the only reason for using these
functions is to start a new clean session within a single problem,
which shouldn't be a common occurrence.

=item rserve_start_plot [IMG_TYPE, [WIDTH, HEIGHT]]

Opens a new R graphics device to capture subsequent graphics output in
a temporary file on the R server. IMG_TYPE can be 'png', 'jpg', or
'pdf', with 'png' as the default. If left unspecified, WIDTH and
HEIGHT, will use the R graphics device's default size. Returns the
name of the remote file.


=item rserve_finish_plot REMOTE_NAME

Closes the R graphics capture to file REMOTE_NAME, transfers the file
to WebWork's temporary file area, and returns the name of the local
file that can then be used by the image macro.

=item rserve_get_file REMOTE_NAME, [LOCAL_NAME]

Transfer the file REMOTE_NAME from the R server to WebWork's temporary
file area, and returns the name of the local file that can then be
used by the C<htmlLink> macro. If LOCAL_NAME is not specified, the
filename portion of the REMOTE_NAME is used.

=back


=head1 DEPENDENCIES

Requires perl 5.010 or newer and CPAN module Statistics::R::IO, which
has to be loaded in WebWork's Safe compartment by adding it to
${pg}{modules}.


=cut


my $rserve;                     # Statistics::R::IO::Rserve instance


sub _rserve_warn_no_config {
    my @trace = split /\n/, Value::traceback();
    my ($function, $line, $file) = $trace[0] =~ /^\s*in ([^ ]+) at line (\d+) of (.*)/;
    
    $PG->warning_message('Calling ' . $function .
                         ' is disabled unless Rserve host is configured in $pg{specialPGEnvironmentVars}{Rserve}{host}')
}


sub rserve_start {
    _rserve_warn_no_config && return unless $Rserve->{host};
    
    $rserve = Statistics::R::IO::Rserve->new(server => $Rserve->{host}, _usesocket => 1);

    # Keep R's RNG reproducible for this problem
    $rserve->eval("set.seed($problemSeed)")
}


sub rserve_finish {
    $rserve->close() if $rserve;
    undef $rserve
}


sub rserve_eval {
    _rserve_warn_no_config && return unless $Rserve->{host};
    
    my $query = shift;
    
    rserve_start unless $rserve;
    
    my $result = _try_eval($rserve, $query);
    _unref_rexp($result)
}


sub rserve_query {
    _rserve_warn_no_config && return unless $Rserve->{host};
    
    my $query = shift;
    $query = "set.seed($problemSeed)\n" . $query;
    my $rserve_client = Statistics::R::IO::Rserve->new(server => $Rserve->{host}, _usesocket => 1);
    my $result = _try_eval($rserve_client, $query);
    $rserve_client->close;
    _unref_rexp($result)
}


## Evaluates an R expression guarding it inside an R `try` function
##
## Returns the result as a REXP if no exceptions were raised, or
## `die`s with the text of the exception message.
sub _try_eval {
    my ($rserve, $query) = @_;

    my $result = $rserve->eval("try({ $query }, silent=TRUE)");
    die $result->to_pl->[0] if $result->inherits('try-error');

    $result
}


## Returns a REXP's Perl representation, dereferencing it if it's an
## array reference
##
## `REXP::to_pl` returns a string scalar for Symbol, undef for Null,
## and an array reference to contents for all vector types. This
## function is a utility wrapper to make it easy to assign a Vector's
## representation to an array variable, while still working sensibly
## for non-arrays.
sub _unref_rexp {
    my $rexp = shift;
    
    my $value = $rexp->to_pl;
    if (ref($value) eq ref([])) {
        @{$value}
    } else {
        $value
    }
}

sub rserve_start_plot {
    _rserve_warn_no_config && return unless $Rserve->{host};
    
    my $device = shift // 'png';
    my $width = shift // '';
    my $height = shift // '';

    die "Unsupported image type $device" unless $device =~ /^(png|pdf|jpg)$/;
    my $remote_image = (rserve_eval("tempfile(fileext='.$device')"))[0];
    
    $device =~ s/jpg/jpeg/;
    
    rserve_eval("$device('$remote_image', width = ${width}, height = ${height})");

    $remote_image
}


sub rserve_finish_plot {
    _rserve_warn_no_config && return unless $Rserve->{host};
    
    my $remote_image = shift or die "Missing remote image name";

    rserve_eval("dev.off()");

    rserve_get_file($remote_image)
}


sub rserve_get_file {
    _rserve_warn_no_config && return unless $Rserve->{host};
    
    my $remote = shift or die "Missing remote file name";
    my $local = shift // $PG->fileFromPath($remote);

    $local = $PG->surePathToTmpFile($local);

    $rserve->get_file($remote, $local);
    
    $local
}


1;