rust 0.4 → 0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,195 @@
1
+ require_relative '../rust'
2
+
3
+ module Rust
4
+
5
+ ##
6
+ # Represents a data-type that can be loaded from and written to R.
7
+
8
+ class RustDatatype
9
+
10
+ ##
11
+ # Retrieves the given +variable+ from R and transforms it into the appropriate Ruby counterpart.
12
+ # To infer the type, it uses the class method #can_pull? of all the RustDatatype classes to check the types
13
+ # that are compatible with the given R variable (type and class). If more than a candidate is available, the one
14
+ # with maximum #pull_priority is chosen.
15
+
16
+ def self.pull_variable(variable, forced_interpreter = nil)
17
+ r_type = Rust._pull("as.character(typeof(#{variable}))")
18
+ r_class = Rust._pull("as.character(class(#{variable}))")
19
+
20
+ if forced_interpreter
21
+ raise ArgumentError, "Expected null or class as forced_interpreter" if forced_interpreter && !forced_interpreter.is_a?(Class)
22
+ raise ArgumentError, "Class #{forced_interpreter} can not handle type #{r_type}, class #{r_class}" unless forced_interpreter.can_pull?(r_type, r_class)
23
+
24
+ return forced_interpreter.pull_variable(variable, r_type, r_class)
25
+ end
26
+
27
+ candidates = []
28
+ ObjectSpace.each_object(Class) do |type|
29
+ if type < RustDatatype
30
+ if type.can_pull?(r_type, r_class)
31
+ candidates << type
32
+ end
33
+ end
34
+ end
35
+
36
+ if candidates.size > 0
37
+ type = candidates.max_by { |c| c.pull_priority }
38
+
39
+ puts "Using #{type} to pull #{variable}" if Rust.debug?
40
+ return type.pull_variable(variable, r_type, r_class)
41
+ else
42
+ if Rust._pull("length(#{variable})") == 0
43
+ return []
44
+ else
45
+ return Rust._pull(variable)
46
+ end
47
+ end
48
+ end
49
+
50
+ ##
51
+ # Returns the priority of this type when a #pull_variable operation is performed. Higher priority means that
52
+ # the type is to be preferred over other candidate types.
53
+
54
+ def self.pull_priority
55
+ 0
56
+ end
57
+
58
+ ##
59
+ # Writes the current object in R as +variable_name+.
60
+
61
+ def load_in_r_as(variable_name)
62
+ raise "Loading #{self.class} in R was not implemented"
63
+ end
64
+
65
+ ##
66
+ # EXPERIMENTAL: Do not use
67
+
68
+ def r_mirror_to(other_variable)
69
+ varname = self.mirrored_R_variable_name
70
+
71
+ Rust._eval("#{varname} = #{other_variable}")
72
+ Rust["#{varname}.hash"] = self.r_hash
73
+
74
+ return varname
75
+ end
76
+
77
+ ##
78
+ # EXPERIMENTAL: Do not use
79
+
80
+ def r_mirror
81
+ varname = self.mirrored_R_variable_name
82
+
83
+ if !Rust._pull("exists(\"#{varname}\")") || Rust._pull("#{varname}.hash") != self.r_hash
84
+ puts "Loading #{varname}" if Rust.debug?
85
+ Rust[varname] = self
86
+ Rust["#{varname}.hash"] = self.r_hash
87
+ else
88
+ puts "Using cached value for #{varname}" if Rust.debug?
89
+ end
90
+
91
+ return varname
92
+ end
93
+
94
+ ##
95
+ # Returns the hash of the current object.
96
+
97
+ def r_hash
98
+ self.hash.to_s
99
+ end
100
+
101
+ private
102
+ def mirrored_R_variable_name
103
+ return "rust.mirrored.#{self.object_id}"
104
+ end
105
+ end
106
+
107
+ ##
108
+ # The null value in R
109
+
110
+ class Null < RustDatatype
111
+ def self.can_pull?(type, klass)
112
+ return type == "NULL" && klass == "NULL"
113
+ end
114
+
115
+ def self.pull_variable(variable, type, klass)
116
+ return nil
117
+ end
118
+ end
119
+ end
120
+
121
+ class TrueClass
122
+ def to_R
123
+ "TRUE"
124
+ end
125
+ end
126
+
127
+ class FalseClass
128
+ def to_R
129
+ "FALSE"
130
+ end
131
+ end
132
+
133
+ class Object
134
+
135
+ ##
136
+ # Returns a string with the R representation of this object. Raises an exception for unsupported objects.
137
+
138
+ def to_R
139
+ raise TypeError, "Unsupported type for #{self.class}"
140
+ end
141
+ end
142
+
143
+ class NilClass
144
+ def to_R
145
+ return "NULL"
146
+ end
147
+
148
+ def load_in_r_as(variable)
149
+ Rust._eval("#{variable} <- NULL")
150
+ end
151
+ end
152
+
153
+ class Numeric
154
+ def to_R
155
+ self.inspect
156
+ end
157
+ end
158
+
159
+ class Float
160
+ def to_R
161
+ return self.nan? ? "NA" : super
162
+ end
163
+ end
164
+
165
+ class Symbol
166
+ def to_R
167
+ return self.to_s.inspect
168
+ end
169
+ end
170
+
171
+ class Array
172
+ def to_R
173
+ return "c(#{self.map { |e| e.to_R }.join(",")})"
174
+ end
175
+
176
+ def distribution
177
+ result = {}
178
+ self.each do |value|
179
+ result[value] = result[value].to_i + 1
180
+ end
181
+ return result
182
+ end
183
+ end
184
+
185
+ class String
186
+ def to_R
187
+ return self.inspect
188
+ end
189
+ end
190
+
191
+ class Range
192
+ def to_R
193
+ [range.min, range.max].to_R
194
+ end
195
+ end
@@ -0,0 +1,158 @@
1
+ require_relative 'datatype'
2
+
3
+ module Rust
4
+
5
+ ##
6
+ # Mirror of the factor type in R.
7
+
8
+ class Factor < RustDatatype
9
+ def self.can_pull?(type, klass)
10
+ return klass == "factor"
11
+ end
12
+
13
+ def self.pull_variable(variable, type, klass)
14
+ levels = Rust["levels(#{variable})"]
15
+ values = Rust["as.integer(#{variable})"]
16
+
17
+ return Factor.new(values, levels)
18
+ end
19
+
20
+ def load_in_r_as(variable_name)
21
+ Rust['tmp.levels'] = @levels.map { |v| v.to_s }
22
+ Rust['tmp.values'] = @values
23
+
24
+ Rust._eval("#{variable_name} <- factor(tmp.values, labels=tmp.levels)")
25
+ end
26
+
27
+ ##
28
+ # Creates a new factor given an array of numeric +values+ and symbolic +levels+.
29
+
30
+ def initialize(values, levels)
31
+ @levels = levels.map { |v| v.to_sym }
32
+ @values = values
33
+ end
34
+
35
+ ##
36
+ # Returns the levels of the factor.
37
+
38
+ def levels
39
+ @levels
40
+ end
41
+
42
+ def ==(other)
43
+ return false unless other.is_a?(Factor)
44
+
45
+ return @levels == other.levels && self.to_a == other.to_a
46
+ end
47
+
48
+ ##
49
+ # Returns the value of the +i+-th element in the factor.
50
+
51
+ def [](i)
52
+ FactorValue.new(@values[i], @levels[@values[i] - 1])
53
+ end
54
+
55
+ ##
56
+ # Sets the +value+ of the +i+-th element in the factor. If it is an Integer, the +value+ must be between 1 and
57
+ # the number of levels of the factor. +value+ can be either a FactorValue or a String/Symbol.
58
+
59
+ def []=(i, value)
60
+ raise "The given value is outside the factor bounds" if value.is_a?(Integer) && (value < 1 || value > @levels.size)
61
+
62
+ if value.is_a?(FactorValue)
63
+ raise "Incompatible factor value, different levels used" unless @levels.include?(value.level) || @levels.index(value.level) + 1 == @value.value
64
+ value = value.value
65
+ end
66
+
67
+ if value.is_a?(String) || value.is_a?(Symbol)
68
+ value = value.to_sym
69
+ raise "Unsupported value #{value}; expected #{@levels.join(", ")}" unless @levels.include?(value)
70
+
71
+ value = @levels.index(value) + 1
72
+ end
73
+
74
+ @values[i] = value
75
+ end
76
+
77
+ ##
78
+ # Returns an array of FactorValue for the values in this factor.
79
+
80
+ def to_a
81
+ @values.map { |v| FactorValue.new(v, @levels[v - 1]) }
82
+ end
83
+
84
+ def method_missing(method, *args, &block)
85
+ raise NoMethodError, "Undefined method #{method} for Factor" if method.to_s.end_with?("!") || method.end_with?("=")
86
+
87
+ self.to_a.method(method).call(*args, &block)
88
+ end
89
+
90
+ def to_s
91
+ self.to_a.to_s
92
+ end
93
+
94
+ def inspect
95
+ self.to_a.inspect
96
+ end
97
+ end
98
+
99
+ ##
100
+ # Represents a single value in a factor.
101
+
102
+ class FactorValue
103
+
104
+ ##
105
+ # Creates a factor with a given +value+ (numeric) and +level+ (symbolic).
106
+
107
+ def initialize(value, level)
108
+ @value = value
109
+ @level = level
110
+ end
111
+
112
+ def value
113
+ @value
114
+ end
115
+
116
+ def level
117
+ @level
118
+ end
119
+
120
+ def to_i
121
+ @value
122
+ end
123
+
124
+ def to_sym
125
+ @level
126
+ end
127
+
128
+ def to_R
129
+ self.to_i
130
+ end
131
+
132
+ def inspect
133
+ @level.inspect
134
+ end
135
+
136
+ def ==(other)
137
+ if other.is_a?(FactorValue)
138
+ @value == other.value && @level == other.level
139
+ elsif other.is_a?(Integer)
140
+ @value == other
141
+ elsif other.is_a?(Symbol)
142
+ @level == other
143
+ end
144
+ end
145
+
146
+ def hash
147
+ @value.hash + @level.hash
148
+ end
149
+
150
+ def eql?(other)
151
+ return self == other
152
+ end
153
+
154
+ def method_missing(method, *args, &block)
155
+ @level.method(method).call(*args, &block)
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,199 @@
1
+ require_relative 'datatype'
2
+
3
+ module Rust
4
+
5
+ ##
6
+ # Mirror of the formula type in R.
7
+
8
+ class Formula < RustDatatype
9
+ def self.can_pull?(type, klass)
10
+ return klass == "formula" || (klass.is_a?(Array) && klass.include?("formula"))
11
+ end
12
+
13
+ def self.pull_variable(variable, type, klass)
14
+ formula_elements = Rust._pull("as.character(#{variable})")
15
+
16
+ assert("The number of elements of a formula must be 2 or 3: #{formula_elements} given") { formula_elements.size > 1 && formula_elements.size < 4 }
17
+ if formula_elements.size == 2
18
+ return Formula.new(nil, formula_elements[1])
19
+ elsif formula_elements.size == 3
20
+ return Formula.new(formula_elements[2], formula_elements[1])
21
+ end
22
+ end
23
+
24
+ def load_in_r_as(variable_name)
25
+ Rust._eval("#{variable_name} <- #{self.left_part} ~ #{self.right_part}")
26
+ end
27
+
28
+ attr_reader :left_part
29
+ attr_reader :right_part
30
+
31
+ ##
32
+ # Creates a new formula with a given +left_part+ (optional) and +right_part+ (as strings).
33
+
34
+ def initialize(left_part, right_part)
35
+ raise ArgumentError, "Expected string" if left_part && !left_part.is_a?(String)
36
+ raise ArgumentError, "Expected string" if !right_part.is_a?(String)
37
+
38
+ @left_part = left_part || ""
39
+ @right_part = right_part
40
+ end
41
+
42
+ def ==(oth)
43
+ return false unless oth.is_a?(Formula)
44
+
45
+ return @left_part == oth.left_part && @right_part == oth.right_part
46
+ end
47
+
48
+ def to_R
49
+ return "#@left_part ~ #@right_part"
50
+ end
51
+
52
+ def inspect
53
+ return self.to_R.strip
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Mirror of the call type in R.
59
+
60
+ class Call < RustDatatype
61
+ def self.can_pull?(type, klass)
62
+ return klass == "call"
63
+ end
64
+
65
+ def self.pull_variable(variable, type, klass)
66
+ return Call.new(Rust["deparse(#{variable})"])
67
+ end
68
+
69
+ def load_in_r_as(variable_name)
70
+ Rust["call.str"] = @value
71
+ Rust._eval("#{variable_name} <- str2lang(call.str)")
72
+ end
73
+
74
+ ##
75
+ # Creates a new call with the given +value+ (String).
76
+
77
+ def initialize(value)
78
+ @value = value
79
+ end
80
+
81
+ def value
82
+ @value
83
+ end
84
+
85
+ def inspect
86
+ @value
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Mirror of the environment type in R. Currently not supported.
92
+
93
+ class Environment < RustDatatype
94
+ def self.can_pull?(type, klass)
95
+ return type == "environment" && klass == "environment"
96
+ end
97
+
98
+ def self.pull_variable(variable, type, klass)
99
+ warn "Exchanging R environments is not supported!"
100
+ return Environment.new
101
+ end
102
+
103
+ def self.load_in_r_as(variable)
104
+ warn "Exchanging R environments is not supported!"
105
+ Rust._eval("#{variable} <- environment()")
106
+ end
107
+ end
108
+
109
+ ##
110
+ # Represents a function call in R. After having set up its name (constructor) and, optionally, its arguments
111
+ # and options, it can be used the call method to execute it in the R environment.
112
+
113
+ class Function
114
+ attr_reader :name
115
+ attr_reader :arguments
116
+ attr_reader :options
117
+
118
+ ##
119
+ # Creates a new function with a given +name+.
120
+
121
+ def initialize(name)
122
+ @function = name
123
+ @arguments = Arguments.new
124
+ @options = Options.new
125
+ end
126
+
127
+ ##
128
+ # Sets the +options+ (Options type) of the function.
129
+
130
+ def options=(options)
131
+ raise TypeError, "Expected Options" unless options.is_a?(Options)
132
+
133
+ @options = options
134
+ end
135
+
136
+ ##
137
+ # Sets the +arguments+ (Arguments type) of the function.
138
+
139
+ def arguments=(arguments)
140
+ raise TypeError, "Expected Arguments" unless options.is_a?(Arguments)
141
+
142
+ @arguments = arguments
143
+ end
144
+
145
+ def to_R
146
+ params = [@arguments.to_R, @options.to_R].select { |v| v != "" }.join(",")
147
+ return "#@function(#{params})"
148
+ end
149
+
150
+ ##
151
+ # Calls the function in the R environment.
152
+
153
+ def call
154
+ Rust._eval(self.to_R)
155
+ end
156
+ end
157
+
158
+ ##
159
+ # Represents an R variable.
160
+
161
+ class Variable
162
+ ##
163
+ # Creates a variable with the given +name+.
164
+
165
+ def initialize(name)
166
+ @name = name
167
+ end
168
+
169
+ def to_R
170
+ @name
171
+ end
172
+ end
173
+
174
+ ##
175
+ # Represents the arguments of a function in R. Works as an Array of objects.
176
+
177
+ class Arguments < Array
178
+ def to_R
179
+ return self.map { |v| v.to_R }.join(", ")
180
+ end
181
+ end
182
+
183
+ ##
184
+ # Represents the options of a function in R. Works as a Hash associating option names to objects.
185
+
186
+ class Options < Hash
187
+ def to_R
188
+ return self.map { |k, v| "#{k}=#{v.to_R}" }.join(", ")
189
+ end
190
+
191
+ def self.from_hash(hash)
192
+ options = Options.new
193
+ hash.each do |key, value|
194
+ options[key.to_s] = value
195
+ end
196
+ return options
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,97 @@
1
+ require_relative 'datatype'
2
+
3
+ module Rust
4
+
5
+ ##
6
+ # Mirror of the list type in R.
7
+
8
+ class List < RustDatatype
9
+ def self.can_pull?(type, klass)
10
+ return type == "list"
11
+ end
12
+
13
+ def self.pull_variable(variable, type, klass)
14
+ return List.new(klass) if Rust._pull("length(#{variable})") == 0
15
+
16
+ names = [Rust["names(#{variable})"]].flatten
17
+ length = Rust["length(#{variable})"]
18
+
19
+ list = List.new(klass, names)
20
+ for i in 0...length
21
+ list[i] = Rust["#{variable}[[#{i + 1}]]"]
22
+ end
23
+
24
+ return list
25
+ end
26
+
27
+ def load_in_r_as(variable_name)
28
+ Rust._eval("#{variable_name} <- list()")
29
+ @data.each do |key, value|
30
+ Rust["#{variable_name}[[#{key + 1}]]"] = value
31
+ end
32
+ end
33
+
34
+ ##
35
+ # Creates an empty list of a given class (+klass+) and the specified +names+.
36
+
37
+ def initialize(klass, names = [])
38
+ @data = {}
39
+ @names = names
40
+ @klass = klass
41
+ end
42
+
43
+ ##
44
+ # Returns the elements for the name +key+.
45
+
46
+ def [](key)
47
+ key = get_key(key)
48
+
49
+ return @data[key]
50
+ end
51
+ alias :| :[]
52
+
53
+ ##
54
+ # Sets the +value+ for name +key+.
55
+
56
+ def []=(key, value)
57
+ key = get_key(key)
58
+
59
+ return @data[key] = value
60
+ end
61
+
62
+ ##
63
+ # Returns the names of the list.
64
+
65
+ def names
66
+ @names
67
+ end
68
+
69
+ def inspect
70
+ result = ""
71
+ values_inspected = @data.map { |k, v| [k, v.inspect.split("\n").map { |l| " " + l }.join("\n")] }.to_h
72
+ max_length = [values_inspected.map { |k, v| v.split("\n").map { |line| line.length }.max.to_i }.max.to_i, 100].min
73
+
74
+ @data.keys.each do |i|
75
+ result << "-" * max_length + "\n"
76
+ result << (@names[i] || "[[#{i}]]") + "\n"
77
+ result << values_inspected[i] + "\n"
78
+ end
79
+ result << "-" * max_length
80
+
81
+ return result
82
+ end
83
+
84
+ private
85
+ def get_key(key)
86
+ if key.is_a?(String)
87
+ new_key = @names.index(key)
88
+ raise ArgumentError, "Wrong key: #{key}" unless new_key
89
+ key = new_key
90
+ end
91
+
92
+ raise ArgumentError, "The key should be either a string or an integer" unless key.is_a?(Integer)
93
+
94
+ return key
95
+ end
96
+ end
97
+ end