coderunner 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,202 @@
1
+ class CodeRunner
2
+
3
+ class Run
4
+
5
+ def run_heuristic_analysis
6
+
7
+ begin
8
+
9
+ puts Dir.pwd
10
+ raise CRMild.new("can't find input file") unless @rcp.input_file_extension
11
+ input_file = Dir.entries.find_all{|file| file =~ Regexp.new("^[^.].*"+Regexp.escape(rcp.input_file_extension) + "$")}[0]
12
+ raise CRMild.new("can't find input file") unless input_file
13
+ log(input_file)
14
+ input_file_text = File.read(input_file)
15
+ @run_name = File.basename(input_file, rcp.input_file_extension) if rcp.use_file_name_as_run_name
16
+ analyse_input_file_text(input_file_text)
17
+ log("Automatic analysis complete")
18
+ # logiv
19
+ rescue CRMild => err
20
+ log(err)
21
+ input_file = Feedback.get_choice("Unable to find a code runner inputs file. If there is another input file please choose it from this list", Dir.entries(Dir.pwd).find_all{|file| not [".",".."].include? file and File.file? file} + ["not available"])
22
+ if input_file == "not available"
23
+ FileUtils.touch('.CODE_RUNNER_IGNORE_THIS_DIRECTORY')
24
+ raise CRError.new("run could be analysed, or folder does not contain a run")
25
+ end
26
+ input_file_text = File.read(input_file)
27
+ log(input_file_text)
28
+ analyse_input_file_text(input_file_text)
29
+ # logiv
30
+ rcp.input_file_extension = File.extname(input_file)
31
+ @rcp.use_file_name_as_run_name = Feedback.get_boolean("Do you want to use the input file name (minus the extension) as a run name? (Your answer will apply to all directories with code runner inputs files).") if rcp.use_file_name_as_run_name.class == NilClass
32
+ @run_name = File.basename(input_file, rcp.input_file_extension) if rcp.use_file_name_as_run_name
33
+ end
34
+ end
35
+
36
+
37
+ def analyse_input_file_text(input_file_text)
38
+ logf(:analyse_input_file_text)
39
+ pars = self.class.analyse_input_file_text(input_file_text, rcp.matching_regex)[0]
40
+ # log("These pars were found in the input file"); logi(pars)
41
+ logi(rcp.variables[rcp.variables.keys[0]])
42
+ pars.each do |id, hash|
43
+ # puts hash.inspect
44
+ # puts @@variables[hash[:name].to_sym].inspect
45
+ if rcp.variables[hash[:name].to_sym]
46
+ set(hash[:name].to_sym, hash[:default].send(rcp.variables[hash[:name].to_sym]))
47
+ # @@variables[hash[:name].to_sym] is the type conversion appropriate for the variable hash[:name]
48
+ end
49
+ end
50
+ end
51
+
52
+
53
+ @@successful_trial_system = nil
54
+ @@successful_trial_class = nil
55
+
56
+ def try_by_system(expected_return=NilClass, &block)
57
+ # puts "Trying by system"
58
+ # puts @@system_run_classes[0].new(@runner).inspect
59
+ @@system_triers ||= rcp.system_run_classes.inject({}){|hash, run_class|
60
+ # puts run_class.new(@runner).learn_from(self).inspect
61
+ log hash.class
62
+ hash[run_class] = run_class.new(@runner).learn_from(self).freeze
63
+ hash}
64
+ # puts @system_triers.inspect
65
+ # i=0
66
+ answer = nil
67
+ if @@successful_trial_class
68
+ begin
69
+ answer = yield(@@system_triers[@@successful_trial_class].dup, self)
70
+ raise CRError.new("trial returned an answer, but answer was not of the right class") unless expected_return == NilClass or answer.is_a? expected_return
71
+ return answer
72
+ rescue => err
73
+ log err
74
+ end
75
+ end
76
+
77
+ @@system_triers.values.each do |trier|
78
+ begin
79
+ # puts i
80
+ # i+=1
81
+ # puts trier.class.ancestors
82
+ # puts "asld"
83
+ answer = yield(trier.dup, self)
84
+ # puts expected_return
85
+ # puts answer.is_a? Fixnum
86
+ # puts "Sd"
87
+ raise CRError.new("trial returned an answer, but answer was not of the right class") unless expected_return == NilClass or answer.is_a? expected_return
88
+ # puts answer, "HASG"
89
+ # puts "asfd"
90
+ @@successful_trial_system = trier.class.run_sys_name
91
+ @@successful_trial_class = trier.class
92
+ # puts @@successful_trial_system
93
+ return answer
94
+ rescue Errno::ENOENT, TypeError, CRMild, CRError => err
95
+ # puts err
96
+ next
97
+ end
98
+ end
99
+ # puts answer; gets
100
+ # answer
101
+ raise CRError.new("try by system was not successful")
102
+ end
103
+
104
+
105
+ def try_to_get_job_number
106
+ begin
107
+ job_no = try_by_system(Fixnum) do |trier, myself|
108
+
109
+ trier.executable_name = "mx123456zz"
110
+ trier.job_no = "mx123456yy"
111
+ trier.version = ""
112
+
113
+ scanner = Regexp.new(Regexp.escape(trier.output_file).sub("mx123456zz", ".+").sub("mx123456yy", "(?<jobno>\\d+)$"))
114
+ answer = nil
115
+ Dir.entries(Dir.pwd).each do |file|
116
+
117
+ return $~[:jobno].to_i if file =~ scanner
118
+ # end
119
+ end
120
+
121
+ nil
122
+ end
123
+ rescue CRError => err
124
+ job_no = -1
125
+ end
126
+ job_no
127
+ # exit
128
+ end
129
+
130
+ def try_to_get_output_file
131
+ #very hacky!
132
+ logf(:try_to_get_output_file)
133
+ begin
134
+ out_file = try_by_system(String) do |trier, myself|
135
+ # trier = trier.dup
136
+ # puts trier
137
+ trier.executable_name = "mx123456zz"
138
+ trier.job_no = "mx123456yy"
139
+ trier.version = ""
140
+ scanner = Regexp.new("(?<outputfile>"+Regexp.escape(trier.output_file).sub("mx123456zz", ".+").sub("mx123456yy", "\\d+")+")")
141
+ # ep scanner
142
+ ans = nil
143
+ Dir.entries(Dir.pwd).each do |file|
144
+ ans = $~[:outputfile] if file =~ scanner
145
+ end
146
+ ans
147
+ end
148
+ rescue CRError => err
149
+ log(err)
150
+ out_file = nil
151
+ end
152
+ out_file
153
+ end
154
+
155
+ def try_to_get_error_file
156
+ logf(:try_to_get_error_file)
157
+ begin
158
+ error_file = try_by_system(String) do |trier, myself|
159
+ # trier = trier.dup
160
+ # puts trier
161
+ trier.executable_name = "mx123456zz"
162
+ trier.job_no = "mx123456yy"
163
+ trier.version = ""
164
+ scanner = Regexp.new("(?<outputfile>"+Regexp.escape(trier.error_file).sub("mx123456zz", ".+").sub("mx123456yy", "\\d+")+")")
165
+ ans = nil
166
+ Dir.entries.each do |file|
167
+ if file =~ scanner
168
+ # puts $~[:outputfile]
169
+ ans = $~[:outputfile]
170
+ end
171
+ end
172
+ ans
173
+ end
174
+ rescue CRError => err
175
+ log(err)
176
+ error_file = nil
177
+ end
178
+ log("Error file was: ", error_file)
179
+ if error_file
180
+ begin
181
+ logi(File.readlines(error_file))
182
+ logi(File.readlines(error_file).size)
183
+ logi(File.readlines(error_file).size.class)
184
+ rescue => err
185
+ log(err)
186
+ end
187
+ end
188
+ return error_file
189
+ end
190
+
191
+ def try_to_find_job_output_ends
192
+ output = try_to_get_output_file
193
+ return nil unless output
194
+ # return try_by_system(Fixnum) do |trier, myself|
195
+ found = File.read(output) =~ /job output ends/i
196
+ return found ? true : false
197
+ # end
198
+ end
199
+
200
+ end
201
+
202
+ end
@@ -0,0 +1,1741 @@
1
+
2
+ # A comment
3
+
4
+ class CodeRunner # declare the constant
5
+ end
6
+
7
+
8
+ # = CodeRunner Overview
9
+ #
10
+ # CodeRunner is a class designed to make the running an analysis of large simulations and easy task. An instance of this class is instantiated for a given root folder. The runner, as it is known, knows about every simulation in this folder, each of which has a unique id and a unique subfolder.
11
+ #
12
+ # The heart of the runner is the variable run_list. This is a hash of IDs and runs. A run is an instance of a class which inherits from CodeRunner::Run, and which is customised to be able to handle the input variables, and the output data, from the given simulation code. This is achieved by a module which contains a child class of CodeRunner::Run, which is provided independently of CodeRunner.
13
+ #
14
+ # CodeRunner has methods to sort these runs, filter them according to quite complex conditions, print out the status of these runs, submit new runs, plot graphs using data from these runs, cancel running jobs, delete unwanted runs, and so on.
15
+ #
16
+ # = CodeRunner Interfaces
17
+ #
18
+ # CodeRunner has two different interfaces:
19
+ #
20
+ # 1. Instance methods
21
+ # 2. Class methods
22
+ #
23
+ # == Instance Methods
24
+ #
25
+ # The instance methods provide a classic Ruby scripting interface. A runner is instantiated from the CodeRunner class, and passed a root folder, and possibly some default options. Instance methods can then be called individually. This is what should be used for complex and non-standard tasks.
26
+ #
27
+ # == Class methods
28
+ #
29
+ # The class methods are what are used by the command line interface. They define a standard set of tasks, each of which can be customised by a set of options known as command options, or <i>copts</i> for short.
30
+ #
31
+ # There is a one-to-one correspondence between the long form of the commandline commands, and the class methods that handle those commands, and between the command line flags, and the options that are passed to the class methods. So for example:
32
+ #
33
+ # $ coderunner submit -p '{time: 23.4, resolution: 256}' -n 32x4 -W 600
34
+ #
35
+ # Becomes
36
+ #
37
+ # CodeRunner.submit(p: '{time: 23.4, resolution: 256}', n: "32x4", W: 600)
38
+ # # remembering that braces are not needed around a hash if it is the final parameter.
39
+ #
40
+ # These methods are what should be used to automate a large number of standard tasks, which would be a pain to run from the command line.
41
+
42
+ class CodeRunner
43
+
44
+ class CRMild < StandardError # more of a dead end than an error. Should never be allowed to halt execution
45
+ def initialize(mess="")
46
+ mess += "\n\n#{self.class} created in directory #{Dir.pwd}"
47
+ super(mess)
48
+ end
49
+ end
50
+ class CRError < StandardError # usually can be handled
51
+ def initialize(mess="")
52
+ mess += "\n\n#{self.class} created in directory #{Dir.pwd}"
53
+ super(mess)
54
+ end
55
+ end
56
+ class CRFatal < StandardError # should never be rescued - must always terminate execution
57
+ def initialize(mess="")
58
+ mess += "\n\n#{self.class} created in directory #{Dir.pwd}"
59
+ super(mess)
60
+ end
61
+ end
62
+
63
+ # Parameters important to the submission of a run, which can be set by command line flags. The runner values provide the default values for the submit function, but can be overidden in that function. All the runner does with them is set them as properties of the run to be submitted. It is the run itself for which the options are relevant.
64
+
65
+ SUBMIT_OPTIONS = [:nprocs, :wall_mins, :sys, :project, :comment, :executable]
66
+
67
+ # A hash containing the defaults for most runner options. They are overridden by any options provided during initialisation. They are mostly set at the command line (in practice, the command line flags are read into the command options, which set these defaults in the function CodeRunner.process_command_options which calls CodeRunner.set_runner_defaults). However, if Code Runner is being scripted, these defaults must be set manually or else the options they specify must be provided when initialising a runner.
68
+
69
+ DEFAULT_RUNNER_OPTIONS = ([:conditions, :code, :executable, :sort, :debug, :script_folder, :recalc_all, :multiple_processes, :heuristic_analysis, :test_submission, :reprocess_all, :use_large_cache, :use_large_cache_but_recheck_incomplete, :use_phantom, :no_run, :server, :version, :parameters] + SUBMIT_OPTIONS).inject({}){|hash, option| hash[option] = nil; hash}
70
+
71
+ # Options that apply across the CodeRunner class
72
+
73
+ CLASS_OPTIONS = [:multiple_processes].inject({}){|hash, option|
74
+ class_accessor option
75
+ set(option, nil)
76
+ hash[option] = nil;
77
+ hash
78
+ }
79
+
80
+
81
+ DEFAULT_RUNNER_OPTIONS.keys.each do |variable|
82
+ #define accessors for class options and instance options
83
+ # class_accessor(variable)
84
+ attr_accessor variable
85
+ # class_variable_set(variable, nil)
86
+ end
87
+
88
+
89
+ # def self.default_script_info
90
+ # Hash.phoenix('.code_runner_script_defaults.rb')
91
+ # # eval(File.read('.code_runner_script_defaults.rb'))
92
+ # end
93
+ #
94
+ # def self.add_to_default_script_info(hash)
95
+ # Hash.phoenix('.code_runner_script_defaults.rb') do |defaults|
96
+ # hash.each{|key,value| defaults[key] = value}
97
+ # end
98
+ # end
99
+
100
+ DEFAULT_RUNNER_OPTIONS[:use_phantom] = :real
101
+ DEFAULT_RUNNER_OPTIONS[:script_folder] = SCRIPT_FOLDER #File.dirname(File.expand_path(__FILE__))
102
+
103
+ # These are properties of the run class that must be defined. For more details see CodeRunner::Run.
104
+
105
+ NECESSARY_RUN_CLASS_PROPERTIES = {
106
+ :code => [String],
107
+ :variables => [Array],
108
+ :naming_pars => [Array],
109
+ :results => [Array],
110
+ :run_info => [Array],
111
+ :code_long => [String],
112
+ :excluded_sub_folders => [Array],
113
+ :modlet_required => [TrueClass, FalseClass],
114
+ :uses_mpi => [TrueClass, FalseClass]
115
+ }
116
+
117
+ # These are methods that the run class must implement. They should be defined in a code module.
118
+
119
+ NECESSARY_RUN_CODE_METHODS = [
120
+ :process_directory_code_specific,
121
+ :print_out_line,
122
+ :parameter_string,
123
+ :generate_input_file,
124
+ :parameter_transition,
125
+ :executable_location,
126
+ :executable_name
127
+ ]
128
+
129
+
130
+ # These are methods that the run class must implement. They should be defined in a system module.
131
+
132
+ NECESSARY_RUN_SYSTEM_METHODS = [
133
+ :queue_status,
134
+ :run_command,
135
+ :execute,
136
+ :error_file,
137
+ :output_file,
138
+ :cancel_job
139
+ ]
140
+
141
+ # These are the only permitted values for the run instance variable <tt>@status</tt>.
142
+
143
+ PERMITTED_STATI = [:Unknown, :Complete, :Incomplete, :NotStarted, :Failed, :Queueing, :Running]
144
+
145
+
146
+ include Log
147
+ # Log.log_file = nil # Dir.pwd + "/.cr_logfile.txt"
148
+ # puts Log.log_file, 'hello'
149
+ Log.clean_up
150
+
151
+
152
+
153
+ attr_accessor :run_list, :phantom_run_list, :combined_run_list, :ids, :phantom_ids, :combined_ids, :current_status, :run_class, :requests, :current_request, :root_folder, :print_out_size, :cache, :modlet, :code, :executable, :defaults_file
154
+ attr_reader :max_id, :maxes, :cmaxes, :mins, :cmins, :start_id
155
+
156
+ # Instantiate a new runner. The root folder contains a set of simulations, each of which has a unique ID. There is a one-to-one correspondence between a runner and a root folder: no two runners should ever be given the same root folder in the same script (there are safeguards to prevent this causing much trouble, but it should still be avoided on philosophical grounds), and no runner should be given a folder which has more than one set of simulations within it (as these simulations will contain duplicate IDs).
157
+ #
158
+ #
159
+ # Options is a hash whose keys may be any of the keys of the constant <tt>DEFAULT_RUNNER_OPTIONS</tt>. I.e. to see what options can be passed:
160
+ #
161
+ # p CodeRunner::DEFAULT_RUNNER_OPTIONS.keys
162
+
163
+ def initialize(root_folder, options={})
164
+ logf :initialize
165
+ raise CRFatal.new("System not defined") unless SYS
166
+ root_folder.sub!(/~/, ENV['HOME'])
167
+ @root_folder = root_folder
168
+ read_defaults
169
+ options.each do |key,value|
170
+ key = LONG_TO_SHORT.key(key) if LONG_TO_SHORT.key(key)
171
+ set(key, value) if value
172
+ end
173
+ # ep options
174
+
175
+ log 'modlet in initialize', @modlet
176
+
177
+ @version= options[:version]
178
+ # ep 'ex', @executable
179
+ @run_class = setup_run_class(@code, modlet: @modlet, version: @version, executable: @executable)
180
+
181
+ @cache = {}
182
+
183
+ @n_checks = 0
184
+ @print_out_size = 0
185
+
186
+ @run_list = {}; @ids = []
187
+ set_max_id(0)
188
+
189
+ @phantom_run_list = {}; @phantom_ids = []
190
+ @phantom_id = -1
191
+
192
+ @combined_run_list = {}; @combined_ids = []
193
+
194
+ @current_request = nil
195
+ @requests = []
196
+
197
+ @pids= []
198
+ @maxes = {}; @mins = {}
199
+ @cmaxes = {}; @cmins = {}
200
+ end
201
+
202
+ # Read the default values of runner options from the constant hash <tt>CodeRunner::DEFAULT_RUNNER_OPTIONS</tt>. This hash usually contains options set from the command line, but it can have its values edited manually in a script.
203
+ #
204
+ # Also calls read_folder_defaults.
205
+
206
+
207
+ def read_defaults
208
+ DEFAULT_RUNNER_OPTIONS.each{|key,value| set(key, value)}
209
+ # ep DEFAULT_RUNNER_OPTIONS, @multiple_processes
210
+
211
+ read_folder_defaults
212
+ end
213
+
214
+ # Increase the value of <tt>@max_id</tt> by 1.
215
+
216
+ def increment_max_id
217
+ @max_id +=1
218
+ end
219
+
220
+ # Return an array of runs (run_list.values)
221
+
222
+ def runs
223
+ run_list.values
224
+ end
225
+
226
+ # Set the max_id. If the number given is lower than start_id, start_id is used. Use with extreme caution... if you set max_id to be lower than the highest run id, you may end up with duplicate ids.
227
+
228
+ def set_max_id(number) # :doc:
229
+ # ep 'MAXES', @max_id, number, @start_id, 'END'
230
+ @max_id = @start_id ? [@start_id, number].max : number
231
+ end
232
+
233
+ private :set_max_id
234
+
235
+ # The defaults that are saved in the root folder
236
+
237
+ FOLDER_DEFAULTS = [:code, :modlet, :executable, :defaults_file, :project]
238
+
239
+ # Read any default options contained in the file <tt>.code_runner_script_defaults.rb</tt> in the root folder.
240
+
241
+ def read_folder_defaults
242
+ # p @root_folder + '/.code_runner_script_defaults.rb'
243
+ Hash.phoenix(@root_folder + '/.code_runner_script_defaults.rb') do |hash|
244
+ # ep hash
245
+ FOLDER_DEFAULTS.each do |var|
246
+ # p send(var), hash[var]
247
+ hash[var] = (send(var) or hash[var])
248
+ hash[:code_runner_version] ||= CodeRunner::CODE_RUNNER_VERSION.to_s
249
+ set(var, hash[var])
250
+ end
251
+ @start_id = hash[:start_id] if hash[:start_id]
252
+ # ep "start_id: #@start_id"
253
+ hash
254
+ end
255
+
256
+ raise "No default information exists for this folder. If you are running CodeRunner from the commmand line please run again with the -C <code> and -X <executable> (and -m <modlet>, if required) flag (you only need to specify these flags once in each folder). Else, please specify :code and :executable (and :modlet if required) as options in CodeRunner.new(folder, options)" unless @code and @executable
257
+ end
258
+
259
+ # Here we redefine the inspect function p to raise an error if anything is written to standard out
260
+ # while in server mode. This is because the server mode uses standard out to communicate
261
+ # and writing to standard out can break it. All messages, debug information and so on, should always
262
+ # be written to standard error.
263
+
264
+ def p(*args)
265
+ if @server
266
+ raise "Writing to stdout in server mode will break things!"
267
+ else
268
+ super(*args)
269
+ end
270
+ end
271
+
272
+ # Here we redefine the function puts to raise an error if anything is written to standard out
273
+ # while in server mode. This is because the server mode uses standard out to communicate
274
+ # and writing to standard out can break it. All messages, debug information and so on, should always
275
+ # be written to standard error.
276
+
277
+ def puts(*args)
278
+ if @server
279
+ raise "Writing to stdout in server mode will break things!"
280
+ else
281
+ super(*args)
282
+ end
283
+ end
284
+
285
+ # Here we redefine the function print to raise an error if anything is written to standard out
286
+ # while in server mode. This is because the server mode uses standard out to communicate
287
+ # and writing to standard out can break it. All messages, debug information and so on, should always
288
+ # be written to standard error.
289
+
290
+ def print(*args)
291
+ if @server
292
+ raise "Writing to stdout in server mode will break things!"
293
+ else
294
+ super(*args)
295
+ end
296
+ end
297
+
298
+ def self.server_dump(obj)
299
+ "code_runner_server_dump_start_E#{Marshal.dump(obj)}code_runner_server_dump_end_E"
300
+ end
301
+
302
+ def server_dump(obj)
303
+ self.class.server_dump(obj)
304
+ end
305
+
306
+ def set_start_id(id)
307
+ raise "start_id #{id} lower than max_id #@max_id" if @max_id and id < @max_id
308
+ Hash.phoenix(@root_folder + '/.code_runner_script_defaults.rb') do |hash|
309
+ @start_id = hash[:start_id] = id
310
+ hash
311
+ end
312
+ end
313
+
314
+ # See CodeRunner.get_run_class_name
315
+
316
+ def get_run_class_name(code, modlet)
317
+ self.class.get_run_class_name(code, modlet)
318
+ end
319
+
320
+ # See CodeRunner.setup_run_class
321
+
322
+ def setup_run_class(code, options)
323
+ options[:runner] ||= self
324
+ self.class.setup_run_class(code, options)
325
+ end
326
+
327
+ def self.old_get_run_class_name(code, modlet) # :nodoc:
328
+ return modlet ? "#{code.capitalize}#{modlet.capitalize.sub(/\.rb$/, '').sub(/_(\w)/){"#$1".capitalize}}Run" : "#{code.capitalize}Run"
329
+ end
330
+
331
+ # Return the name of the run class according to the standard CodeRunner naming scheme. If the code name is 'a_code_name', with no modlet, the run class name will be <tt>ACodeName</tt>. If on the other hand there is a modlet called 'modlet_name', the class name will be <tt>ACodeName::ModletName</tt>.
332
+
333
+ def self.get_run_class_name(code, modlet=nil)
334
+ return modlet ? "#{code.capitalize}::#{modlet.capitalize.sub(/\.rb$/, '').variable_to_class_name}" : "#{code.capitalize}"
335
+ end
336
+
337
+
338
+ def self.repair_marshal_run_class_not_found_error(err)
339
+ code, modlet = err.message.scan(/CodeRunner\:\:([A-Z][a-z0-9_]+)(?:::([A-Z]\w+))?/)[0]
340
+ #ep 'merror', err, code, modlet; gets
341
+ code.gsub!(/([a-z0-9])([A-Z])/, '\1_\2')
342
+ (modlet.gsub!(/([a-z0-9])([A-Z])/, '\1_\2'); modlet.downcase) if modlet
343
+ setup_run_class(code.downcase, modlet: modlet)
344
+ end
345
+
346
+
347
+ # Create, set up and check the validity of the custom class which deals with a particular simulation code. This class will be defined in a custom module in the folder <tt>code_modules/code_name</tt>, where <tt>'code_name'</tt> is the name of the code. The only option is <tt>modlet:</tt>.
348
+ #
349
+ # If the custom class has already been set up, this method just returns the class.
350
+
351
+
352
+ SETUP_RUN_CLASSES =[]
353
+ def self.setup_run_class(code, options={})
354
+ # logf(:setup_code)
355
+ # log(:code, code)
356
+
357
+ modlet = options[:modlet]
358
+ version = options[:version]
359
+ # log('modlet in setup_run_class', modlet)
360
+ eputs "Loading modules for #{code}, #{modlet.inspect}..."
361
+
362
+ # modlet = modlet.sub(/\.rb$/, '') if modlet
363
+ raise CRFatal.new("Code must contain only lowercase letters, digits, and underscore, and must begin with a letter or underscore; it is '#{code}'") unless code =~ /\A[a-z_][a-z_\d]*\Z/
364
+ raise CRFatal.new("Input_file_module must contain only lowercase letters, digits, and underscore, and must begin with a letter or underscore; it is '#{modlet}'") if modlet and not modlet =~ /\A[a-z_][a-z_\d]*\Z/
365
+
366
+ run_class_name = get_run_class_name(code, modlet)
367
+
368
+ # p run_class_name
369
+
370
+ return const_get(run_class_name) if constants.include? (run_class_name).to_sym unless options[:force]
371
+ SETUP_RUN_CLASSES.push run_class_name.downcase
372
+
373
+ #Create the run_class, a special dynamically created class which knows how to process runs of the given code on the current system.
374
+ #run.rb contains the basic methods of the class
375
+ # puts run_class_name; gets
376
+ # run_class = add_a_child_class(run_class_name, "")
377
+
378
+ # run_class.class_eval(%[
379
+ # @@code=#{code.inspect}; @@version=#{version.inspect}
380
+ # @@modlet=#{modlet.inspect} #modlet should be nil if not @@modlet_required
381
+ # SYS = #{SYS.inspect}
382
+ # include Log
383
+ # Log.logi(:code_just_after_runfile, @@code)
384
+ # ]
385
+ # )
386
+
387
+
388
+
389
+ code_module_name = SCRIPT_FOLDER+ "/code_modules/#{code}/#{code}.rb"
390
+ require code_module_name
391
+ # ep get_run_class_name(code, nil)
392
+ run_class = const_get(get_run_class_name(code, nil))
393
+ run_class.instance_variable_set(:@code, code)
394
+
395
+ raise "#{run_class} must inherit from CodeRunner::Run: its ancestors are: #{run_class.ancestors}" unless run_class.ancestors.include? Run
396
+
397
+ if options[:runner]
398
+ run_class.runner = options[:runner]
399
+ run_class.instance_variable_set(:@runner, options[:runner])
400
+ end
401
+ #Add methods appropriate to the current system
402
+
403
+ system_module_name = SCRIPT_FOLDER+ "/system_modules/#{SYS}.rb"
404
+ require system_module_name
405
+ run_class.send(:include, const_get(SYS.variable_to_class_name))
406
+ # run_class.class_eval(File.read(system_module_name), system_module_name)
407
+
408
+
409
+
410
+ #Some codes require an modlet; the flag modlet_required is specified in the code module
411
+ if run_class.rcp.modlet_required
412
+ raise CRFatal.new("Modlet necessary and none given") unless modlet
413
+ # Log.logi(:modlet, modlet)
414
+ if ( modlet_location = "#{SCRIPT_FOLDER}/code_modules/#{code}/default_modlets";
415
+ file_name = "#{modlet_location}/#{modlet}.rb";
416
+ FileTest.exist? file_name
417
+ )
418
+ elsif ( modlet_location = "#{SCRIPT_FOLDER}/code_modules/#{code}/my_modlets";
419
+ file_name = "#{modlet_location}/#{modlet}.rb";
420
+ FileTest.exist? file_name
421
+ )
422
+ else
423
+ raise CRFatal.new("Could not find modlet file: #{modlet}.rb")
424
+ end
425
+ require file_name
426
+ # ep run_class.constants
427
+ # ep run_class_names
428
+
429
+ run_class = recursive_const_get(run_class_name)
430
+ run_class.instance_variable_set(:@modlet, modlet)
431
+ end
432
+
433
+ run_class.check_and_update
434
+ # log("random element of variables", run_class.variables.random)
435
+
436
+
437
+ # logi("run_class.variables[0] in setup_code", run_class.variables[0])
438
+ # ep 'finished'
439
+
440
+ return run_class
441
+ end
442
+
443
+ # Traverse the directory tree below the root folder, detecting and analysing all runs within that folder. Although runs submitted by CodeRunner will all be in one folder in the root folder, it is not necessary for the runs to be organised like that. This is because CodeRunner can also be used to analyse runs which it did not submit.
444
+ #
445
+ # All subfolders of the root_folder will be analysed, except for ones specified in the run class property <tt>excluded_sub_folders</tt>, or those containing the hidden file '.CODE_RUNNER_IGNORE_THIS_DIRECTORY'
446
+
447
+ def traverse_directories # :doc:
448
+ string = ""
449
+ #ep 'traversing', Dir.pwd
450
+ if FileTest.exist?("code_runner_info.rb") or FileTest.exist?("CODE_RUNNER_INPUTS") or FileTest.exist?("README") or @heuristic_analysis and (Dir.entries(Dir.pwd).find{|file| not [".","..","data.txt"].include? file and File.file? file} and not Dir.entries(Dir.pwd).include? '.CODE_RUNNER_IGNORE_THIS_DIRECTORY')
451
+ #i.e. if there are some files in this directory (not just directories)
452
+ eprint '.' if @write_status_dots
453
+ begin
454
+
455
+ raise CRMild.new("must recalc all") if @recalc_all or @reprocess_all #must recalculate the run results, not load them if @@recalc_all
456
+ run = @run_class.load(Dir.pwd, self) #NB this doesn't always return an object of class run_class - since the run may actually be from a different code
457
+ raise CRMild.new("not complete: must recalc") unless run.status == :Complete or run.status == :Failed
458
+ raise CRMild.new("Not the right directory, must recalc") unless run.directory == Dir.pwd
459
+ rescue ArgumentError, CRMild => err
460
+ if err.class == ArgumentError
461
+ unless err.message =~ /marshal data too short/
462
+ #puts err
463
+ #puts err.backtrace
464
+ raise err
465
+ end
466
+ end
467
+ log(err)
468
+ # puts err.class
469
+ begin
470
+ #interrupted = false;
471
+ #old_trap2 = trap(2){}
472
+ #old_trap2 = trap(2){eputs "Interrupt acknowledged...#{Dir.pwd} finishing folder analyis. Interrupt again to override (may cause file corruption)."; interrupted = true}
473
+ run = @run_class.new(self).process_directory #NB this doesn't always return an object of class run_class - since the run may actually be from a different code
474
+ #trap(2, old_trap2)
475
+ #(eputs "Calling old interrupt #{Dir.pwd}"; Process.kill(2, 0)) if interrupted
476
+ rescue => err
477
+ log(err)
478
+ unless @heuristic_analysis and (err.class == CRMild or err.class == CRError)
479
+ # puts Dir.pwd
480
+ logd
481
+ eputs Dir.pwd
482
+ eputs err.class
483
+ eputs err
484
+ # eputs err.backtrace
485
+ eputs "----Only allowed to fail processing a directory if a heuristic analysis is being run"
486
+ raise err
487
+ end
488
+ # puts err.class
489
+ run = nil
490
+ # puts @requests
491
+ end
492
+ end
493
+ check = false
494
+ if run
495
+ # puts run.id, @run_list[run.id]; gets
496
+ # raise CRFatal.new("\n\n-----Duplicate run ids: #{run.directory} and #{@run_list[run.id].directory}----") if @run_list[run.id]
497
+ if @run_list[run.id]
498
+ check = true
499
+ raise <<EOF
500
+ Duplicate run ids:
501
+ New: #{run.run_name} #{run.status} in #{run.directory}
502
+ Old: #{@run_list[run.id].run_name} #{@run_list[run.id].status} in #{@run_list[run.id].directory}
503
+ EOF
504
+ choice = Feedback.get_choice("Which do you want to keep, new or old? (The other folder will be deleted. Press Ctrl+C to cancel and sort the problem out manually)", ["New", "Old"])
505
+ case choice
506
+ when /New/
507
+ raise "Aborting... this function has not been fully tested"
508
+ FileUtils.rm_r(@run_list[run.id].directory)
509
+ when /Old/
510
+ raise "Aborting... this function has not been fully tested"
511
+ FileUtils.rm_r(run.directory)
512
+ run = nil
513
+ end
514
+ end
515
+ end
516
+ if run
517
+ (puts "you shouldn't see this if you chose old"; gets) if check
518
+ run.save
519
+ @run_list[run.id] = run
520
+ @ids.push run.id
521
+ @ids = @ids.uniq.sort
522
+ @max_id = @max_id>run.id ? @max_id : run.id
523
+ end
524
+ if @heuristic_analysis
525
+ Dir.foreach(Dir.pwd)do |directory|
526
+ next if [".",".."].include? directory or File.file? directory or directory =~ /\.rb/ or @run_class.rcp.excluded_sub_folders.include? directory
527
+ # begin
528
+ Dir.chdir(directory) do
529
+ traverse_directories
530
+ end
531
+ # rescue Errno::ENOENT
532
+ # log Dir.entries
533
+ # puts directory + " was not a directory"
534
+ # end
535
+ end
536
+ end
537
+
538
+ else
539
+ Dir.foreach(Dir.pwd)do |directory|
540
+ next if [".",".."].include? directory or File.file? directory or directory =~ /\.rb/ or @run_class.rcp.excluded_sub_folders.include? directory
541
+ # begin
542
+ Dir.chdir(directory) do
543
+ traverse_directories
544
+ end
545
+ # rescue Errno::ENOENT
546
+ # log Dir.entries
547
+ # puts directory + " was not a directory"
548
+ # end
549
+ end
550
+ end
551
+ end
552
+ private :traverse_directories
553
+
554
+ # Write out a simple datafile containing all the inputs and outputs listed in the run class property <tt>readout_string</tt>. What is actually written out can be customised by redefining the method <tt>data_string</tt> in the run class, or changing <tt>readout_string</tt> or both.
555
+
556
+ def write_data(filename = "data.txt")
557
+ logf(:write_data)
558
+ generate_combined_ids
559
+ File.open(filename, "w") do |file|
560
+ @combined_ids.each do |id|
561
+ run = @combined_run_list[id]
562
+ if run.status =~ /Complete/
563
+ data_string = run.data_string
564
+ raise CRFatal.new("data_string did not return a string") unless data_string.class == String
565
+ file.puts data_string
566
+ end
567
+ end
568
+ end
569
+ end
570
+
571
+ # Sort the runs according to the variable <tt>@sort</tt> which can be either whitespace separated string or an array of strings. In the former case, the string is split to form an array of strings, using the separator <tt>/\s+/</tt>. For example, if
572
+ #
573
+ # @sort == ['height', '-weight', 'colour']
574
+ #
575
+ # Then the runs will be sorted according to first height, then (descending) weight, then colour. What actually happens is that the variable <tt>@ids</tt> is sorted, rather than <tt>@run_list</tt>. For this to work, height, weight and colour must be either variables or results or instance methods of the run class.
576
+ #
577
+ # Type can be either '' or 'phantom', in which case the variable sorted will be either <tt>@ids</tt> or <tt>@phantom_ids</tt> respectively.
578
+
579
+ def sort_runs(type = @use_phantom.to_s.sub(/real/, ''))
580
+ logf(:sort_runs)
581
+ log(:type, type)
582
+ #ep 'sort', @sort
583
+ #sort_list = @sort ? (@sort.class == String ? eval(@sort) : @sort) : []
584
+ run_list_name = [type.to_s, 'run_list'].join('_').gsub(/^_/, '')
585
+ ids_name = [type.to_s, 'ids'].join('_').gsub(/^_/, '')
586
+ log run_list_name
587
+ set(ids_name, send(ids_name).sort_by do |id|
588
+ run = send(run_list_name)[id]
589
+ sortkey = run.instance_eval((@sort or '[]'))
590
+ #ep 'sortkey', sortkey
591
+ sortkey
592
+
593
+ #sort_list.map{|str| run.instance_eval(str)}
594
+ end)
595
+ end
596
+
597
+ # Print out a summary of all the runs in the root folder, formatted in nice pretty colours. Since this is often called in a loop, if called twice without any arguments it will erase the first printout. To stop this happening set rewind to 0. If the command is being issued not in a terminal, so that CodeRunner cannot determine the size of the terminal, the second argument must be passed as an array of [rows, columns].
598
+
599
+ def print_out(rewind = nil, options={}) # terminal_size = [rows, cols]
600
+ rewind ||= @print_out_size
601
+ terminal_size = options[:terminal_size]
602
+ logf(:print_out)
603
+ # raise CRFatal.new("terminal size must be given if this is called any where except inside a terminal (for example if yo u've called this as a subprocess)") unless terminal_size or $stdout.tty?
604
+ terminal_size ||= Terminal.terminal_size
605
+ #lots of gritty terminal jargon in here. Edit at your peril!
606
+
607
+ unless ENV['CODE_RUNNER_NO_COLOUR']=='true' or ENV['CODE_RUNNER_NO_COLOR']=='true'
608
+ dc= Terminal::WHITE # .default_colour
609
+ green= Terminal::LIGHT_GREEN
610
+ cyan = Terminal::CYAN
611
+ bblck = Terminal::BACKGROUND_BLACK
612
+ else
613
+ dc= ""# Terminal::WHITE # .default_colour
614
+ green= "" # Terminal::LIGHT_GREEN
615
+ cyan = "" #Terminal::CYAN
616
+ bblck = "" #Terminal::BACKGROUND_BLACK
617
+ end
618
+ cln = Terminal::CLEAR_LINE
619
+ # print "\033[#{rewind}A"
620
+ deco = '-'*terminal_size[1]
621
+
622
+ Terminal.erewind(rewind)
623
+
624
+ eputs "\n#{cln}\n#{cln}\n#{bblck}#{dc}#{deco}\n#{@run_class.rcp.code_long} Status:#{cln}\n#{deco}"
625
+
626
+ i = 0; j=0 # i is no. of actual terminal lines; j is number of results lines
627
+
628
+ # Group the lines by major sort key
629
+ #@split = @sort && @sort.split(/\s+/).size > 1 ? @sort.split(/\s+/)[0].to_sym : nil
630
+ @split_point = nil
631
+
632
+ generate_combined_ids
633
+ @combined_ids.each do |id|
634
+ begin
635
+ # puts id, :hello; gets
636
+ @run = @combined_run_list[id]
637
+ if filter
638
+ # puts @run[:id], @id; gets
639
+ #@new_split_point = @split ? @run.send(@split) : nil
640
+ #if @split_point && @split_point != @new_split_point then eputs sprintf(" #{cln}", ""); i+=1 end
641
+ #@split_point = @new_split_point
642
+ eprint j%2==0 ? j%4==0 ? cyan : green : dc
643
+ line = options[:with_comments] ? @run.comment_line : @run.print_out_line.chomp
644
+ eprint line
645
+ eputs cln
646
+ # puts (line.size / Terminal.terminal_size[1]).class
647
+ # puts (line.size / Terminal.terminal_size[1])
648
+
649
+ i+=((line.sub(/\w*$/, '').size-1) / terminal_size[1]).ceiling
650
+ j+=1
651
+ end
652
+ # raise "monkeys"
653
+ rescue => err
654
+ eputs err
655
+ eputs err.backtrace
656
+ eputs "---------------------\nUnable to print out line for this job:"
657
+ eputs "run_name: #{@run.run_name}"
658
+ eputs "status: #{@run.status}\n-----------------------"
659
+ Terminal.reset
660
+ return
661
+ # raise CRFatal.new
662
+ end
663
+ end
664
+ @print_out_size = i+7# + (@run_list.keys.find{|id| not [:Complete, :Failed].include? @run_list[id].status } ? 0 : 1)
665
+ eprint dc, deco; Terminal.reset; eputs
666
+ # puts
667
+ # puts
668
+ # puts dc # "\033[1;37m"
669
+ # print 'rewind size is', rewind
670
+
671
+ end
672
+
673
+
674
+ # def filtered_run_list
675
+ # logf :filtered_run_list
676
+ # unless @use_phantom == :phantom
677
+ # return @run_list.find_all{|id, run| filter(run)}
678
+ # else
679
+ # log 'using phantom'
680
+ # return @phantom_run_list.find_all{|id, run| filter(run)}
681
+ # end
682
+ # end
683
+
684
+ # Return a list of ids, filtered according to conditions. See CodeRunner#filter
685
+
686
+ def filtered_ids(conditions=@conditions)
687
+ generate_combined_ids
688
+ # ep @combined_run_list.keys
689
+ # sort_runs
690
+ return @combined_ids.find_all{|id| filter(@combined_run_list[id], conditions)}
691
+ end
692
+
693
+ # Return true if
694
+ # run.instance_eval(conditions) == true
695
+ # and false if
696
+ # run.instance_eval(conditions) == false.
697
+ # performing some checks on the validity of the variable <tt>conditions</tt>. For people who are new to Ruby instance_eval means 'evaluate in the context of the run'. Generally <tt>conditions</tt> will be something like 'status == :Complete and height == 4.5 and not width == 20', where height and width might be some input parameters or results from the diminishing.
698
+
699
+ def filter(run=@run, conditions=@conditions)
700
+ logf(:filter)
701
+ @run = run
702
+ # to_instance_variables(directory)
703
+ return true unless conditions
704
+ # p conditions, @run.id, @run.is_phantom
705
+ conditions = conditions.dup
706
+ raise CRFatal.new("
707
+ -----------------------------
708
+ Conditions contain a single = sign: #{conditions}
709
+ -----------------------------") if conditions =~ /[^!=<>]=[^=~<>]/
710
+ log conditions
711
+ begin
712
+ fil = @run.instance_eval(conditions)
713
+ rescue => err
714
+ eputs run.directory
715
+ eputs conditions
716
+ raise err
717
+ end
718
+ return fil
719
+ end
720
+
721
+ # Similar to CodeRunner#write_data, except that the readout is written to stdout, and formatted a bit. Will probably be rarely used.
722
+
723
+ def readout
724
+ logf(:readout)
725
+ generate_combined_ids
726
+ @split = @sort && @sort.split(/\s+/).size > 1 ? @sort.split(/\s+/)[0].to_sym : nil
727
+ @split_point = nil
728
+ string = @combined_ids.inject("") do |s, id|
729
+ run = @combined_run_list[id]
730
+ if run.status =~ /Complete/ and filter(run)
731
+ @new_split_point = @split ? run.send(@split) : nil
732
+ splitter = (@split_point and @split_point != @new_split_point) ? "\n\n" : ""
733
+ @split_point = @new_split_point
734
+ splitter = "" # comment to put split points back in
735
+ # puts s.class, splitter.class, data_string(directory)
736
+ data_string = run.data_string
737
+ raise CRFatal.new("data_string did not return a string") unless data_string.class == String
738
+ s + splitter + data_string
739
+ else
740
+ s
741
+ end
742
+ end
743
+ # puts @max.inspect
744
+
745
+ return string
746
+ end
747
+
748
+ # ? Probably can get rid of this one
749
+
750
+ def readout_cols(*var_list) # :nodoc:
751
+ logf :readout_cols
752
+ ans = [[]] * var_list.size
753
+ generate_combined_ids
754
+ filtered_ids.each do |id|
755
+ run = combined_run_list[id]
756
+ var_list.each_with_index do |var, index|
757
+ ans[index].push run.send(var)
758
+ end
759
+ end
760
+ ans
761
+ end
762
+
763
+ # List every file in the root folder.
764
+
765
+ def get_all_root_folder_contents # :nodoc:
766
+ @root_folder_contents =[]
767
+ Find.find(@root_folder){|file| @root_folder_contents.push file}
768
+ end
769
+
770
+ #Update the information about all the runs stored in the variable <tt>@run_list</tt>. By default, this is done by calling CodeRunner#traverse_directories. If, on the other hand, <tt>@use_large_cache</tt> is set to true, this is done by reading the temporary cache maintained in ".CODE_RUNNER_TEMP_RUN_LIST_CACHE" in the root folder. This is much quicker. If in addition <tt>@use_large_cache_but_recheck_incomplete</tt> is set to true, all runs whose status is not either :Complete or :Failed will be rechecked.
771
+
772
+ def update(write_status_dots=true, use_large_cache=@use_large_cache, use_large_cache_but_recheck_incomplete=@use_large_cache_but_recheck_incomplete)
773
+ @use_large_cache = use_large_cache
774
+ logf(:update)
775
+ @write_status_dots = write_status_dots
776
+
777
+ @run_list={}
778
+ @ids=[]
779
+ @phantom_run_list = {}
780
+ @phantom_ids = []
781
+ @run_store =[]
782
+ # @multiple_processes_directories = []
783
+ set_max_id 0
784
+ @run_class.update_status(self)
785
+ if @use_large_cache and not @recalc_all and not @reprocess_all
786
+ log("using large cache")
787
+ begin
788
+ begin
789
+ eputs 'Loading large cache...' if @write_status_dots
790
+ Dir.chdir(@root_folder) do
791
+ @run_list = Marshal.load(File.read(".CODE_RUNNER_TEMP_RUN_LIST_CACHE"))
792
+ end
793
+ @run_list.values.each{|run| run.runner = self}
794
+ rescue ArgumentError => err
795
+ eputs err
796
+ raise err unless err.message =~ /undefined class/
797
+ #NB all code_names have to contain only lowercase letters:
798
+ # modlet, code = err.message.scan(/CodeRunner\:\:([A-Z][\w+])?([A-Z]\w+)Run/)[0]
799
+ code, modlet = err.message.scan(/CodeRunner\:\:([A-Z][a-z0-9_]+)(?:::([A-Z]\w+))?/)[0]
800
+ # ep code.downcase, modlet
801
+ modlet.downcase! if modlet
802
+ setup_run_class(code.downcase, modlet: modlet)
803
+ retry
804
+ end
805
+ # ep @run_list
806
+ @ids = @run_list.keys
807
+ @run_list.each{|id, run| run.runner = self }
808
+ #eputs "Setting max id..."
809
+ set_max_id(@ids.max || 0)
810
+ #eputs "max_id = #@max_id"
811
+ # puts @max_id; gets
812
+ # @use_large_cache = false
813
+ @ids.sort!
814
+ redone_count = 0
815
+
816
+
817
+ # puts "hello"
818
+ # ep @use_large_cache_but_recheck_incomplete; exit
819
+ recheck_incomplete_runs if use_large_cache_but_recheck_incomplete
820
+ # sort_runs
821
+ eprint "Updating runs..." if @write_status_dots
822
+ # get_all_root_folder_contents
823
+ # puts @run_list.values[0].directory, File.expand_path(@root_folder).esc_regex
824
+ fix_directories = (run_list.size > 0 and not @run_list.values[0].directory =~ File.expand_path(@root_folder).esc_regex)
825
+ # eputs 'fdirectories', fix_directories
826
+ # exit
827
+ eputs "Fixing Directories..." if fix_directories
828
+ @run_list.each do |id, run|
829
+ eprint '.' if @write_status_dots
830
+ run.directory = File.join(@root_folder, run.relative_directory) if fix_directories
831
+ # run.directory = "#@root_folder/#{run.relative_directory}"
832
+ # unless @root_folder_contents.include? run.directory# File.directory? run.directory and run.directory =~ File.expand_path(@root_folder).esc_regex
833
+ # if @root_folder_contents.include?(rel = File.join(@root_folder, run.relative_directory))
834
+ # run.directory = rel
835
+ # else
836
+ # raise CRFatal.new("Directory #{run.directory} not found")
837
+ # end
838
+ # end
839
+ # eputs @use_phantom
840
+ #run.generate_phantom_runs #if @use_phantom.to_s =~ /phantom/i
841
+ run.phantom_runs.each{|r| add_phantom_run(r)} if run.phantom_runs
842
+ end
843
+ eputs if @write_status_dots
844
+ save_large_cache if fix_directories
845
+ # puts redone_count
846
+ return self
847
+ rescue Errno::ENOENT, ArgumentError, TypeError => err
848
+ if err.class == ArgumentError and not err.message =~ /marshal data too short/ or err.class == TypeError and not err.message =~ /incompatible marshal file format/
849
+ eputs err
850
+ eputs err.backtrace
851
+ raise CRFatal.new
852
+ end
853
+ eputs err, "Rereading run data"
854
+ # @use_large_cache = false
855
+ end
856
+ end
857
+
858
+ log("not using large cache")
859
+ @run_list={}
860
+ @ids=[]
861
+ @phantom_run_list = {}
862
+ @phantom_ids = []
863
+ @run_store =[]
864
+ # @multiple_processes_directories = []
865
+ set_max_id 0
866
+
867
+ log("traversing directories")
868
+ eprint 'Analysing runs..' if @write_status_dots
869
+ #interrupted = false;
870
+ #old_trap2 = trap(2){}
871
+ #trap(2){eputs "Interrupt acknowledged... reloading saved cache. Interrupt again to override (may cause file corruption)."; trap(2, old_trap2); update(true, true, false); Process.kill(2,0)}
872
+ Dir.chdir(@root_folder){traverse_directories}
873
+ @max_id ||= 0
874
+ eputs
875
+ # puts 'before request', @ids, @requests;
876
+ respond_to_requests
877
+ # @n_checks += 1
878
+ # exit if ($nruns > 0 && @n_checks > $nruns)
879
+ sort_runs
880
+ @recalc_all = false
881
+ # pp @run_list
882
+ save_large_cache
883
+ #@run_list.each{|id, run| run.generate_phantom_runs}
884
+ #trap(2, old_trap2)
885
+ #Process.kill(2, 0) if interrupted
886
+ return self
887
+ end
888
+
889
+ # Dump all the instance variables of the runner to stdout as Marshalled binary data. This is used for RemoteCodeRunner server functions.
890
+
891
+ def marshalled_variables
892
+ #ep 'marsh1'
893
+ instance_vars = {}
894
+ instance_variables.each do |var|
895
+ instance_vars[var] = instance_variable_get(var)
896
+ end
897
+ instance_vars[:@run_list].values.each{|run| run.runner=nil}
898
+ #Kernel.puts server_dump(instance_vars)
899
+ instance_vars[:@cache]={}
900
+ instance_vars[:@phantom_run_list].values.each{|run| run.runner = nil}
901
+ #ep 'marsh2'
902
+ #eputs instance_vars.pretty_inspect
903
+ #instance_vars.each do |var, val|
904
+ #ep var
905
+ #eputs server_dump(val)
906
+ #end
907
+
908
+
909
+ instance_vars
910
+ end
911
+
912
+ # Write the variable <tt>@run_list</tt>, which contains all information currently known about the simulations in the root folder, as Marshalled binary data in the file ".CODE_RUNNER_TEMP_RUN_LIST_CACHE". This cache will be used later by CodeRunner#update.
913
+
914
+ def save_large_cache
915
+ # pp self
916
+ # ep @run_list
917
+ # pp @run_list.values.map{|run| run.instance_variables.map{|var| [var, run.instance_variable_get(var).class]}}
918
+
919
+ generate_combined_ids
920
+ @combined_run_list.each{|id, run| run.runner = nil}
921
+ File.open(@root_folder + "/.CODE_RUNNER_TEMP_RUN_LIST_CACHE", 'w'){|file| file.puts Marshal.dump @run_list}
922
+ @combined_run_list.each{|id, run| run.runner = self}
923
+ end
924
+
925
+ # Self-explanatory! Call CodeRunner::Run#process_directory for every run whose status is not either :Complete or :Failed. (Note, for historical reasons traverse_directories is called rather than CodeRunner::Run#process_directory directly but the effect is nearly the same).
926
+
927
+ def recheck_incomplete_runs
928
+ logf :recheck_incomplete_runs
929
+ @run_class.update_status(self)
930
+ redone_count = 0
931
+ run_list, @run_list = @run_list, {}; @ids = [];
932
+ run_list.each do |id, run|
933
+ # print id
934
+ if run.status == :Complete or run.status == :Failed
935
+ # print ".", id, "___"
936
+ @run_list[id] = run
937
+ @ids.push id
938
+ else
939
+ # print id, "%%%";
940
+ redone_count+=1
941
+ Dir.chdir(run.directory){traverse_directories}
942
+ end
943
+ end
944
+ @ids.sort!
945
+ # @ids = @run_list.keys
946
+ set_max_id(@ids.max || 0)
947
+ sort_runs
948
+ respond_to_requests
949
+ save_large_cache
950
+ end
951
+
952
+ # Self-explanatory! Call CodeRunner::Run#process_directory for every run for which CodeRunner#filter(run) is true. (Note, for historical reasons traverse_directories is called rather than CodeRunner::Run#process_directory directly but the effect is nearly the same).
953
+
954
+ def recheck_filtered_runs(write_status_dots=false)
955
+ @write_status_dots = write_status_dots
956
+ logf :recheck_filtered_runs
957
+ @run_class.update_status(self)
958
+ @run_list.each do |id, run|
959
+ if filter(run) and not (run.status == :Complete or run.status == :Failed)
960
+ # eputs run.directory
961
+ Dir.chdir(run.directory){run.process_directory}
962
+ # eputs run.status
963
+ # ep run
964
+ end
965
+ end
966
+ save_large_cache
967
+ # run_list, @run_list = @run_list, {}; @ids = []; @max_id = 0
968
+ # run_list.each do |id, run|
969
+ # if not filter(run) or run.status == :Complete
970
+ # @run_list[id] = run
971
+ # @ids.push id
972
+ # else
973
+ # Dir.chdir(run.directory){traverse_directories}
974
+ # end
975
+ # end
976
+ # @ids.sort!
977
+ # @ids = @run_list.keys
978
+ set_max_id(@ids.max || 0)
979
+ sort_runs
980
+ respond_to_requests
981
+ save_large_cache
982
+ end
983
+
984
+ # One of the features of CodeRunner is two way communication between a runner and its runs. The runs can request actions from the runner directly by calling its instance methods, but often the runs want something to happen after the runner has processed every run in the directory. For example if a run wanted to check if it was resolved, it would need to know about all the other runs so it could compare itself with them. In this case it would place an instance method that it wanted to call in the variable <tt>@requests</tt> in the runner. The runner would then call that instance method on every run after it had finished processing all the runs.
985
+ #
986
+ # In summary, this method is called after every time the runner has checked through the directory. When it is called, it looks in the variable <tt>@requests</tt> which contains symbols representing methods. It calls each symbol as an instance method of every run in turn. So if <tt>@requests</tt> was <tt>[:check_converged]</tt> it would call <tt>run.check_converged</tt> for every run.
987
+
988
+ def respond_to_requests
989
+ logf(:respond_to_requests)
990
+ logi(:@requests, @requests)
991
+ log('@phantom_run_list.class', @phantom_run_list.class)
992
+ while @requests[0]
993
+ old_requests = @requests.uniq
994
+ @requests = []
995
+ old_requests.each do |request|
996
+ @current_request = request
997
+ if request == :traverse_directories # a special case
998
+ @ids = []
999
+ @run_list = {}
1000
+ @phantom_run_list = {}
1001
+ @phantom_ids = []
1002
+
1003
+ Dir.chdir(@root_folder){traverse_directories}
1004
+ else
1005
+ filtered_ids.each do |id|
1006
+ run = @run_list[id]
1007
+ Dir.chdir(run.directory){run.instance_eval(request.to_s); run.save; run.write_results}
1008
+ end
1009
+ end
1010
+ end
1011
+ end
1012
+ end
1013
+
1014
+ # Start a simulation: submit the run that is passed. What happens is as follows:
1015
+ #
1016
+ # 1. Modify the run according to options.
1017
+ # 2. Check if any other processes are submitting runs in the same root folder. In this case there will be a file called 'submitting' in the folder. If there is such a file wait until it is gone.
1018
+ # 3. Check if a run with identical parameters has been submitted before. In which case skip submitting the run unless options[:skip] == false.
1019
+ # 4. Call <tt>run.submit</tt>
1020
+ #
1021
+ #Options can be any one of <tt>CodeRunner::SUBMIT_OPTIONS</tt>. The options passed here will override values stored as instance variables of the runner with the same name, which will override these values if they are set in the runs itself. For example if
1022
+ # run.nprocs == '32x4'
1023
+ # runner.nprocs == nil
1024
+ # options[:nprocs] == nil
1025
+ # the number of processes will be 32x4. On the other hand if
1026
+ # run.nprocs == '32x4'
1027
+ # runner.nprocs == '24x4'
1028
+ # options[:nprocs] == '48x4'
1029
+ # the number of processes will be 48x4.
1030
+
1031
+ def submit(runs, options={})
1032
+ eputs "System " + SYS
1033
+ eputs "No. Procs " + @nprocs.inspect
1034
+
1035
+ runs = [runs] unless runs.class == Array #can pass a single run to submit
1036
+ outruns = runs.dup
1037
+ skip = true unless options[:skip] == false
1038
+ SUBMIT_OPTIONS.each do |option|
1039
+ set(option, options[option]) if options.keys.include? option
1040
+ end
1041
+ logf(:submit)
1042
+ Dir.chdir(@root_folder) do
1043
+ @skip=skip
1044
+ mess = false
1045
+ while FileTest.exist?("submitting")
1046
+ (eputs " Waiting for another process to finish submitting. If you know that no other CodeRunner processes are submitting in this folder (#@root_folder) then delete the file 'submitting' and try again"; mess = true) unless mess
1047
+ sleep rand
1048
+ end
1049
+ # old_trap = trap(0)
1050
+ old_trap0 = trap(0){eputs "Aborted Submit!"; File.delete("#@root_folder/submitting"); exit!}
1051
+ old_trap2 = trap(2){eputs "Aborted Submit!"; File.delete("#@root_folder/submitting") if FileTest.exist? "#@root_folder/submitting"; trap(2, "DEFAULT"); trap(0, "DEFAULT"); Process.kill(2, 0)}
1052
+ # File.open("submitting", "w"){|file| file.puts ""}
1053
+ FileUtils.touch("submitting")
1054
+ unless options[:no_update_before_submit]
1055
+ @use_large_cache, ulc = false, @use_large_cache; update; @use_large_cache = ulc
1056
+ end
1057
+ generate_combined_ids(:real)
1058
+ # old_job_nos = queue_status.scan(/^\s*(\d+)/).map{|match| match[0].to_i}
1059
+ script = "" if options[:job_chain]
1060
+ runs.each_with_index do |run, index|
1061
+ similar = similar_runs([], run)
1062
+ if @skip and similar[0] and not (options[:replace_existing] or options[:rerun])
1063
+ eputs "Found similar run: #{@run_list[similar[0]].run_name}"
1064
+ eputs "Skipping submission..."
1065
+ runs[index] = nil
1066
+ next
1067
+ end
1068
+ unless options[:replace_existing] or options[:rerun]
1069
+ @max_id+=1
1070
+ run.id = @max_id
1071
+ else
1072
+ if options[:replace_existing]
1073
+ FileUtils.rm_r run.directory
1074
+ elsif options[:rerun]
1075
+ ################# For backwards compatibility
1076
+ SUBMIT_OPTIONS.each do |opt|
1077
+ run.set(opt, send(opt)) unless run.send(opt)
1078
+ end
1079
+ ###########################################
1080
+ FileUtils.rm "#{run.directory}/code_runner_results.rb"
1081
+ FileUtils.rm "#{run.directory}/.code_runner_run_data"
1082
+ end
1083
+ @run_list.delete(run.id)
1084
+ @ids.delete run.id
1085
+ generate_combined_ids
1086
+ end
1087
+
1088
+ begin
1089
+
1090
+ unless options[:job_chain]
1091
+ run.prepare_submission unless options[:rerun]
1092
+ next if @test_submission
1093
+ Dir.chdir(run.directory) do
1094
+ old_job_nos = queue_status.scan(/^\s*(\d+)/).map{|match| match[0].to_i}
1095
+ ######################### The big tomale!
1096
+ run.job_no = run.execute # Start the simulation and get the job_number
1097
+ #########################
1098
+ run.job_no = get_new_job_no(old_job_nos) unless run.job_no.kind_of? Integer # (if the execute command does not return the job number, look for it manually)
1099
+ # eputs 'run.job_no', run.job_no
1100
+ run.output_file # Sets the output_file on first call
1101
+ run.error_file # Sets the error_file on first call
1102
+ run.write_info
1103
+ eputs "Submitted run: #{run.run_name}"
1104
+ end
1105
+ else
1106
+ run.prepare_submission unless options[:rerun]
1107
+ script << "cd #{run.directory}\n"
1108
+ script << "#{run.run_command}\n"
1109
+ next if @test_submission
1110
+ end
1111
+ rescue => err
1112
+ File.delete("submitting")
1113
+ raise(err)
1114
+ end
1115
+ end # runs.each
1116
+ runs.compact!
1117
+ if options[:job_chain] and not @test_submission and runs.size > 0
1118
+ FileUtils.makedirs('job_chain_files')
1119
+ @id_list = runs.map{|run| run.id}
1120
+
1121
+ @submission_script = script
1122
+ # A hook... default is to do nothing
1123
+ @submission_script = @run_class.modify_job_script(self, runs, @submission_script)
1124
+ # To get out of job_chain_files folder
1125
+ @submission_script = "cd .. \n" + @submission_script
1126
+ old_job_nos = queue_status.scan(/^\s*(\d+)/).map{|match| match[0].to_i}
1127
+ ################ Submit the run
1128
+ Dir.chdir('job_chain_files'){job_no = execute}
1129
+ ################
1130
+ job_no = get_new_job_no(old_job_nos) unless job_no.kind_of? Integer # (if the execute command does not return the job number, look for it manually)
1131
+ # eputs 'jobchain no', job_no
1132
+ #runs.each{|run| run.job_no = job_no}
1133
+ runs.each do |run|
1134
+ run.job_no = @job_no = job_no
1135
+ run.output_file = run.relative_directory.split("/").map{|d| ".."}.join("/") + "/job_chain_files/" + output_file
1136
+ run.error_file = run.relative_directory.split("/").map{|d| ".."}.join("/") + "/job_chain_files/" + error_file
1137
+ run.write_info
1138
+ eputs "Submitted run: #{run.run_name}"
1139
+ end
1140
+ end
1141
+ @write_status_dots, wsd = false, @write_status_dots
1142
+ @run_class.update_status(self)
1143
+ runs.each do |run|
1144
+ # ep run.id, run_list.keys
1145
+ Dir.chdir(run.directory){traverse_directories}
1146
+ end
1147
+ @write_status_dots = wsd
1148
+ save_large_cache
1149
+ File.delete("submitting")
1150
+ trap(0, old_trap0)
1151
+ trap(2, old_trap2)
1152
+
1153
+
1154
+ end # Dir.chdir(@root_folder)
1155
+ # eputs
1156
+ #ep 'runs submitted', outruns
1157
+ return outruns[0].id if outruns.size == 1 #used in parameter scans
1158
+
1159
+ end # def submit
1160
+
1161
+ def rcp
1162
+ @run_class.rcp
1163
+ end
1164
+
1165
+ def executable_name
1166
+ File.basename(@executable)
1167
+ end
1168
+
1169
+ def executable_location
1170
+ File.dirname(@executable)
1171
+ end
1172
+
1173
+ def code_run_environment
1174
+ run_class.new(self).code_run_environment
1175
+ end
1176
+
1177
+ def run_command
1178
+ #ep 'submission_script', @submission_script
1179
+ @submission_script
1180
+ end
1181
+
1182
+ private :run_command
1183
+
1184
+ def job_identifier
1185
+ "#{@id_list[0]}-#{@id_list[-1]}"
1186
+ end
1187
+
1188
+ #private :job_identifier
1189
+
1190
+ # Assuming only one new job has been created, detect its job number.
1191
+
1192
+ def get_new_job_no(old_job_nos) # :doc:
1193
+ #|| ""
1194
+ # qstat = qstat=~/\S/ ? qstat : nil
1195
+ job_no = nil
1196
+ 5.times do # job_no may not appear instantly
1197
+ # eputs queue_status
1198
+ new_job_nos = queue_status.scan(/^\s*(\d+)/).map{|match| match[0].to_i}
1199
+ job_no = (new_job_nos-old_job_nos).sort[0]
1200
+ # eputs "job_no", job_no
1201
+ break if job_no
1202
+ sleep 0.2
1203
+ qstat = queue_status
1204
+ end
1205
+ job_no ||= -1 # some functions don't work if job_no couldn't be found, but most are ok
1206
+ end
1207
+
1208
+ private :get_new_job_no
1209
+
1210
+ # def submit
1211
+ #
1212
+ # # logi(:job_nos, job_nos)
1213
+ # ##################
1214
+ #
1215
+ # ##################
1216
+ #
1217
+ # eputs info_file
1218
+ # @sys = SYS
1219
+ #
1220
+ # end
1221
+ #
1222
+ #
1223
+ # else
1224
+ # File.delete("submitting")
1225
+ # raise CRFatal.new("queue_status did not return a string; submission cancelled. Suggest editing system_modules/#{SYS}.rb")
1226
+ # end
1227
+ #
1228
+ # end
1229
+
1230
+
1231
+ # Create a new instance of the class <tt>@run_class</tt>
1232
+
1233
+ def new_run
1234
+ logf(:new_run)
1235
+ @run_class.new(self)
1236
+ end
1237
+
1238
+ @@wait = true #Do you wait for the previous run to have completed when using simple scan?
1239
+
1240
+ # Submit a series of runs according to scan_string. scan_string specifies a number of scans separated by hashes. Each scan has a number which is the ID of the run to start from followed by a colon. After the colon the user can write some code which modifies the original run. For example:
1241
+ #
1242
+ # simple_scan('23: @height *= 2; @weight *=1.1')
1243
+ #
1244
+ # will submit a series of runs, where each successive run has height twice the last one, and weight 1.1 times the last one, where we assume that height and weight are input parameters.
1245
+ #
1246
+ # Options are the same as CodeRunner#submit
1247
+
1248
+ def simple_scan(scan_string, options={})
1249
+ scans = scan_string.split('#')
1250
+ ppipe = PPipe.new(scans.size + 1, true, controller_refresh: 0.5, redirect: false)
1251
+ pipe_numbers = ppipe.fork(scans.size - 1)
1252
+
1253
+ #p @run_class.naming_pars
1254
+ instructions = (scans[(ppipe.mpn > 0 ? ppipe.mpn - 1 : 0)])
1255
+ id = instructions.scan(/^\d+\:/)[0].to_i
1256
+ instructions = instructions.sub(/^\d+\:/, '')
1257
+ @run= id > 0 ? @run_list[id] : (run = @run_class.new(self); run.update_submission_parameters((options[:parameters] or '{}')); run)
1258
+ @run_class.rcp.variables.each do |par|
1259
+ #p par, @run_class.naming_pars
1260
+ @run.naming_pars.push par if scan_string =~ Regexp.new(Regexp.escape(par.to_s))
1261
+ end
1262
+ @run.naming_pars.uniq!
1263
+ # @run.naming_pars +== @run_class.naming_pars
1264
+ catch(:finished) do
1265
+ loop do #submit loop
1266
+ ppipe.synchronize(:submit) do
1267
+ @running = submit(@run, nprocs: options[:nprocs], version: @run_class.rcp.version)
1268
+ @conditions = "id == #@running"
1269
+ #print_out
1270
+ #ep 'Running run', @running
1271
+ end
1272
+ loop do # check finished loop
1273
+ dir = @run_list[@running].directory
1274
+ @run_list.delete(@running)
1275
+ @run_class.update_status(self)
1276
+ Dir.chdir(dir) do
1277
+ traverse_directories
1278
+ end
1279
+ unless @@wait and (@run_list[@running].status == :Incomplete or @run_list[@running].status == :NotStarted or @run_list[@running].status == :Queueing)
1280
+ @run.parameter_transition(@run_list[@running])
1281
+ @old_run = @run_list[@running]
1282
+ break
1283
+ end
1284
+ #p @running
1285
+ ppipe.i_send(:running, @running, tp: 0)
1286
+ if ppipe.is_root
1287
+ arr = (pipe_numbers + [0]).inject([]) do |arr, pn|
1288
+ arr.push ppipe.w_recv(:running, fp: pn)
1289
+ end
1290
+ #p arr
1291
+ @conditions = "#{arr.inspect}.include? id"
1292
+ print_out
1293
+ end
1294
+ sleep 3
1295
+ end
1296
+ @run.instance_eval(instructions)
1297
+ end
1298
+ end
1299
+ (ppipe.die; exit) unless ppipe.is_root
1300
+ ppipe.finish
1301
+ end
1302
+
1303
+
1304
+
1305
+ # nprocs = options[:nprocs]
1306
+ # # version = options[:version]
1307
+ # skip = true unless options[:skip] == false
1308
+ # logf(:submit)
1309
+ # Dir.chdir(@root_folder) do
1310
+
1311
+
1312
+ # run.nprocs =nprocs;
1313
+
1314
+ # A parameter scan array is a list of parameter_scans:
1315
+ # [parameter_scan, parameter_scan, ...]
1316
+ # A parameter_scan is a list of variable scans:
1317
+ # [variable_scan, variable_scan, ...]
1318
+ #
1319
+ # A variable_scan consists of a name, and a list of values;
1320
+ #
1321
+ # ['width', [0.3, 0.5, 0.6]]
1322
+ #
1323
+ # A parameter scan will scan through every possible combination of variable values, varying the final variable fastest.
1324
+ #
1325
+ # In between each run it will call the hook function parameter_transition. This allows you to adjust the input variables of the next run based on the results of the previous.
1326
+ # e.g.
1327
+ # parameter_scan_array = [
1328
+ # [
1329
+ # ['width', [0.3, 0.5, 0.6]], ['height', [0.5, 4.3]]
1330
+ # ],
1331
+ # [
1332
+ # ['width', [7.2, 0.6]], ['height', [3.6, 12.6, 12.9, 0.26]]
1333
+ # ]
1334
+ # ]
1335
+ #
1336
+ # This will run two simultaneous parameter scans: one with 3x2 = 6 runs; one with 2x4 = 8 runs, a total of 14 runs
1337
+ #
1338
+ # Any variables not specified in the parameter scan will be given their default values.
1339
+
1340
+
1341
+ def parameter_scan(parameter_scan, parameters, options={})
1342
+ skip = true unless options[:skip] == false
1343
+ # version= (options[:version] or "")
1344
+ nprocs = options[:nprocs]
1345
+ logf(:parameter_scan)
1346
+ raise CRFatal.new("Wrong directory: parameter scan being conducted in #{Dir.pwd} instead of my root folder: #{@root_folder}") unless Dir.pwd == File.expand_path(@root_folder)
1347
+ @skip = skip
1348
+ puts parameter_scan.inspect
1349
+
1350
+ @nprocs = nprocs; @skip=skip;
1351
+ log '@run_class', @run_class
1352
+ @run = @run_class.new(self)
1353
+ @run.update_submission_parameters(parameters)
1354
+ # @running_scans = {}; @gammas = {}
1355
+ beginning = "catch(:finished) do \n"
1356
+ end_string = "\nend"
1357
+ parameter_scan.each do |variable_scan|
1358
+ beginning += %[ #{variable_scan[1].inspect}.each do |value|\n\t@run.#{variable_scan[0]} = value\n]
1359
+ @run_class.rcp.naming_pars.push variable_scan[0].to_sym; @run_class.rcp.naming_pars.uniq!
1360
+ end_string += %[\nend]
1361
+ end
1362
+ middle = <<EOF
1363
+ @@psppipe.synchronize(:pssubmit){@running = submit(@run, nprocs: @nprocs, version: @version, skip: @skip); update};
1364
+ loop do
1365
+ # @@mutex.synchronize{}
1366
+ @run_class.update_status(self)
1367
+ # puts run_class.current_status
1368
+ Dir.chdir(@run_list[@running].directory){@run_list.delete(@running); traverse_directories}
1369
+ # ep @run_list[@running].status, @run_list[@running].id, @run_list[@running].job_no, queue_status
1370
+ unless @run_list[@running].status == :Incomplete or @run_list[@running].status == :NotStarted
1371
+
1372
+ @run.parameter_transition(@run_list[@running])
1373
+ break
1374
+ end
1375
+ sleep 3
1376
+ # Thread.pass
1377
+ # puts Thread.current.to_s + " is looping, @run_list[@running].status = " + @run_list[@running].status.to_s + " @running = " + @running.to_s + " @run_list[@running].job_no = " + @run_list[@running].job_no.to_s
1378
+ end
1379
+ EOF
1380
+ command = beginning + middle + end_string
1381
+ puts command
1382
+ instance_eval(command, 'parameter_scan_code')
1383
+ # puts Thread.current.object_id
1384
+ # puts Thread.current.to_s; print " is finished"
1385
+ # @@psppipe.i_send(:finished, true, tp: 0)
1386
+
1387
+ end
1388
+
1389
+ # Find runs whose input parameters are all the same as those for <tt>run</tt>, with the exception of those listed in <tt>exclude_variables</tt>
1390
+
1391
+ def similar_runs(exclude_variables=[], run=@run) #all runs for which variables are the same as 'run', with the exception of exclude_variables
1392
+ logf(:similar_runs)
1393
+ raise CRFatal.new("generate_combined_ids must be called before this function is called") unless (@combined_run_list.size > 0 and @combined_ids.size > 0) or @ids.size ==0
1394
+ command = (run.class.rcp.variables+run.class.rcp.run_info-exclude_variables - [:output_file, :error_file]).inject("@combined_ids.find_all{|id| @combined_run_list[id].class == run.class}"){ |s,v|
1395
+ s + %<.find_all{|id| @combined_run_list[id].#{v}.class == #{run.send(v).inspect}.class and @combined_run_list[id].#{v} == #{run.send(v).inspect}}>} #the second send call retrieves the type conversion
1396
+
1397
+ # log command
1398
+ # puts command
1399
+ begin
1400
+ similar = instance_eval(command)
1401
+ rescue => err
1402
+ log command
1403
+ raise err
1404
+ end
1405
+ return similar
1406
+ end
1407
+
1408
+
1409
+ # def max_conditional(variable,sweep=nil, conditions=nil)
1410
+ # logf(:max_complete)
1411
+ # return get_max_complete(variable,sweep, complete) == @run.id
1412
+ # end
1413
+
1414
+ #
1415
+ # def max(variable,sweep=nil, complete=nil)
1416
+ # logf(:max)
1417
+ # return get_max(variable,sweep, complete) == @run.id
1418
+ # end
1419
+ #
1420
+ # def get_max(variable,sweep=nil, complete=nil)
1421
+ # logf :get_max
1422
+ # sweep ||= variable
1423
+ # logi @run.maxes
1424
+ # @run.maxes[variable] ||= {}
1425
+ # similar = similar_runs([sweep])
1426
+ # similar = similar.find_all{|id| @combined_run_list[id].status == :Complete} if complete
1427
+ # logi(:similar, similar, @combined_run_list[similar[0]].send(variable))
1428
+ # @run.maxes[variable][sweep] ||= similar[1] ? similar.max{|id1,id2| @combined_run_list[id1].send(variable) <=> @combined_run_list[id2].send(variable)} : @run.id
1429
+ # # puts "got_here"
1430
+ # logi("@run.maxes[#{variable}][#{sweep}]", @run.maxes[variable][sweep])
1431
+ # return @run.maxes[variable][sweep]
1432
+ # end
1433
+ def get_max(run, variable,sweep, complete=nil)
1434
+ logf :get_max
1435
+ generate_combined_ids
1436
+ sweep = [sweep] unless sweep.class == Array
1437
+ similar = similar_runs(sweep, run)
1438
+ similar = similar.find_all{|id| @combined_run_list[id].status == :Complete} if complete
1439
+ logi(:similar, similar, @combined_run_list[similar[0]].send(variable))
1440
+ max = similar[1] ? similar.max{|id1,id2| @combined_run_list[id1].send(variable) <=> @combined_run_list[id2].send(variable)} : similar[0]
1441
+ # puts "got_here"
1442
+ return max
1443
+ end
1444
+
1445
+ def get_min(run, variable,sweep, complete=nil)
1446
+ logf :get_min
1447
+ generate_combined_ids
1448
+ sweep = [sweep] unless sweep.class == Array
1449
+ similar = similar_runs(sweep, run)
1450
+ similar = similar.find_all{|id| @combined_run_list[id].status == :Complete} if complete
1451
+ logi(:similar, similar, @combined_run_list[similar[0]].send(variable))
1452
+ min = similar[1] ? similar.min{|id1,id2| @combined_run_list[id1].send(variable) <=> @combined_run_list[id2].send(variable)} : similar[0]
1453
+ # puts "got_here"
1454
+ return min
1455
+ end
1456
+
1457
+ #
1458
+ # def get_max_conditional(variable,sweep=nil, conditions=nil)
1459
+ # logf(:get_max_conditional)
1460
+ # raise CRFatal.new("generate_combined_ids must be called before this function is called") unless @combined_run_list[0]
1461
+ # sweep ||= variable
1462
+ # similar = similar_runs([sweep]).find_all{|id| filter(@combined_run_list[id], conditions)}
1463
+ # similar = similar.find_all{|id| @combined_run_list[id].status == :Complete} if complete
1464
+ # logi(:similar, similar, @combined_run_list[similar[0]].send(variable))
1465
+ # id_of_max = similar[1] ? similar.max{|id1,id2| @combined_run_list[id1].send(variable) <=> @combined_run_list[id2].send(variable)} : @run.id
1466
+ # # puts "got_here"
1467
+ # return id_of_max
1468
+ # end
1469
+
1470
+
1471
+ # Duplicate the runner, trying to be intelligent as far as possible in duplicating instance variables. Not fully correct yet. Avoid using at the moment.
1472
+
1473
+ def dup
1474
+ logf(:dup)
1475
+ new_one = self.class.new(@code, @root_folder, modlet: @modlet, version: @version)
1476
+ new_one.ids = @ids.dup; new_one.phantom_ids = @phantom_ids.dup;
1477
+ new_one.run_list = @run_list.dup; new_one.phantom_run_list = @phantom_run_list.dup
1478
+ new_one.run_class = @run_class
1479
+ return new_one
1480
+ end
1481
+
1482
+ # Delete the folders of all runs for whom CodeRunner#filter(run) is true. This will permanently erase the runs. This is an interactive method which asks for confirmation.
1483
+
1484
+ def destroy(options={})
1485
+ ids = @ids.find_all{|id| filter @run_list[id]}
1486
+ unless options[:no_confirm]
1487
+ logf(:destroy)
1488
+ puts "About to delete:"
1489
+ ids.each{|id| eputs @run_list[id].run_name}
1490
+ return unless Feedback.get_boolean("You are about to DESTROY #{ids.size} jobs. There is no way back. All data will be eliminated. Please confirm the delete.")
1491
+ #gets
1492
+ eputs "Please confirm again. Press Enter to confirm, Ctrl + C to cancel"
1493
+ gets
1494
+ end
1495
+ ids.each{|id| FileUtils.rm_r @run_list[id].directory; @run_list.delete(id); @ids.delete(id); generate_combined_ids}
1496
+ set_max_id(@ids.max || 0)
1497
+ save_large_cache
1498
+ generate_combined_ids
1499
+ end
1500
+
1501
+ # Cancel the job with the given ID. Options are:
1502
+ # :no_confirm ---> true or false, cancel without asking for confirmation if true
1503
+ # :delete ---> if (no_confirm and delete), delete cancelled run
1504
+
1505
+ def cancel_job(id, options={})
1506
+ @run=@run_list[id]
1507
+ raise "Run with id #{id} does not exist" unless @run
1508
+ unless options[:no_confirm]
1509
+ eputs "Cancelling job: #{@run.job_no}: #{@run.run_name}. \n Press enter to confirm"
1510
+ # puts 'asfda'
1511
+ gets
1512
+ end
1513
+ @run.cancel_job
1514
+ if options[:no_confirm]
1515
+ delete = options[:delete]
1516
+ else
1517
+ delete = Feedback.get_boolean("Do you want to delete the folder as well?")
1518
+ end
1519
+ FileUtils.rm_r(@run.directory) if delete
1520
+ update
1521
+ print_out
1522
+ end
1523
+
1524
+ # Needs to be fixed.
1525
+
1526
+ # def rename_variable(old, new)
1527
+ # puts "Please confirm complete renaming of #{old} to #{new}"
1528
+ # gets
1529
+ # @run_list.each do |directory|
1530
+ # Dir.chdir directory[:directory] do
1531
+ # begin
1532
+ # @readme = File.read("CODE_RUNNER_INPUTS")
1533
+ # rescue Errno::ENOENT => err
1534
+ # # puts err, err.class
1535
+ # @readme = File.read("README")
1536
+ # end
1537
+ # @readme.sub!(Regexp.new("^\s+#{old}:"), "^\s+#{new}:")
1538
+ # File.open("CODE_RUNNER_INPUTS_TEST", 'w'){|file| file.puts @readme}
1539
+ # end
1540
+ # end
1541
+ # old_recalc, @recalc_all = @recalc_all, true
1542
+ # update
1543
+ # @recalc_all = old_recalc
1544
+ # end
1545
+
1546
+ def add_phantom_run(run)
1547
+ @phantom_run_list[@phantom_id] = run
1548
+ @phantom_ids.push @phantom_id
1549
+ #run.real_id = run.id
1550
+ run.id = @phantom_id
1551
+ @phantom_id += -1
1552
+ end
1553
+
1554
+ def generate_combined_ids(kind= nil)
1555
+ logf(:generate_combined_ids)
1556
+ # case purpose
1557
+ # when :print_out
1558
+ # @combined_ids = []
1559
+ # @combined_ids += @phantom_ids if @run_class.print_out_phantom_run_list
1560
+ # @combined_ids += @ids if @run_class.print_out_real_run_list
1561
+ # when :readout
1562
+ # @combined_ids = []
1563
+ # @combined_ids += @phantom_ids if @run_class.readout_phantom_run_list
1564
+ # @combined_ids += @ids if @run_class.readout_real_run_list
1565
+ # when :submitting
1566
+ # @combined_ids = @ids
1567
+ kind ||= @use_phantom
1568
+ case kind
1569
+ when :real
1570
+ @combined_ids = @ids
1571
+ when :phantom
1572
+ @combined_ids = @phantom_ids
1573
+ when :both
1574
+ @combined_ids = @ids + @phantom_ids
1575
+ else
1576
+ raise CRFatal.new("Bad use phantom variable: #{kind.inspect}")
1577
+ end
1578
+ log('@phantom_run_list.class', @phantom_run_list.class)
1579
+ #puts 'crlist', @phantom_run_list.keys, @run_list.keys
1580
+ @combined_run_list = @phantom_run_list + @run_list
1581
+ log(:kind, kind)
1582
+ # log(:combined_ids, @combined_ids)
1583
+ sort_runs(:combined)
1584
+ end
1585
+
1586
+ def save_all
1587
+ save_large_cache
1588
+ @run_list.values.each do |run|
1589
+ run.save
1590
+ run.write_results
1591
+ end
1592
+ end
1593
+
1594
+ def save_all_and_overwrite_info
1595
+ save_all
1596
+ @run_list.values.each do |run|
1597
+ run.write_info
1598
+ end
1599
+ end
1600
+
1601
+ private :save_all_and_overwrite_info
1602
+
1603
+ # Permanently change the id of every run in the folder by adding num to them. Num can be negative unless it makes any of the ids negative. Use if you want to combine these runs with the runs in another folder, either by creating an instance of CodeRunner::Merge, or by directly copying and pasting the run folders.
1604
+
1605
+ def alter_ids(num, options={})
1606
+ Dir.chdir(@root_folder) do
1607
+ return unless options[:no_confirm] or Feedback.get_boolean("This will permanently alter all the ids in the folder #@root_folder. Scripts that use those ids may be affected. Do you wish to continue?")
1608
+ raise ArgumentError.new("Cannot have negative ids") if @run_list.keys.min + num < 0
1609
+ runs = @run_list.values
1610
+ fids = filtered_ids
1611
+ @run_list = {}
1612
+ runs.each do |run|
1613
+ old_id = run.id
1614
+ if fids.include? old_id
1615
+ run.id += num
1616
+ old_dir = run.relative_directory
1617
+ new_dir = old_dir.sub(Regexp.new("id_#{old_id}(\\D|$)")){"id_#{run.id}#$1"}
1618
+ # ep old_dir, new_dir
1619
+ FileUtils.mv(old_dir, new_dir)
1620
+ run.relative_directory = new_dir
1621
+ run.directory = File.expand_path(new_dir)
1622
+ end
1623
+ @run_list[run.id] = run
1624
+ end
1625
+ @ids = @run_list.keys
1626
+ set_max_id(@ids.max || 0)
1627
+ save_all_and_overwrite_info
1628
+ end
1629
+ end
1630
+
1631
+ def continue_in_new_folder(folder, options={})
1632
+ Dir.chdir(@root_folder) do
1633
+ raise "Folder already exists" if FileTest.exist?(folder)
1634
+ FileUtils.makedirs("#{folder}/v")
1635
+ #FileUtils.makedirs(folder)
1636
+ FileUtils.cp(".code_runner_script_defaults.rb", "#{folder}/.code_runner_script_defaults.rb")
1637
+ FileUtils.cp(".code-runner-irb-save-history", "#{folder}/.code-runner-irb-save-history")
1638
+ FileUtils.cp("#{@defaults_file}_defaults.rb", "#{folder}/#{@defaults_file}_defaults.rb")
1639
+ if options[:copy_ids]
1640
+ options[:copy_ids].each do |id|
1641
+ FileUtils.cp_r(@run_list[id].directory, "#{folder}/v/id_#{id}")
1642
+ end
1643
+ end
1644
+
1645
+ end
1646
+ end
1647
+
1648
+ # Create a tar archive of the root folder and all the files in it. Options are
1649
+ # :compression => true or false
1650
+ # :folder => folder in which to place the archive.
1651
+ # :verbose => true or false
1652
+ # :group => group of new files
1653
+ #
1654
+ def create_archive(options={})
1655
+ verbose = options[:verbose] ? 'v' : ''
1656
+ very_verbose = options[:very_verbose] ? 'v' : ''
1657
+ comp = options[:compression]
1658
+ Dir.chdir(@root_folder) do
1659
+ temp_root = ".tmparch/#{File.basename(@root_folder)}"
1660
+ FileUtils.makedirs(temp_root)
1661
+ system "chgrp #{options[:group]} #{temp_root}" if options[:group]
1662
+ size=@run_list.size
1663
+ @run_list.values.each_with_index do |run, index|
1664
+ archive_name = "#{File.basename(run.directory)}.tar#{comp ?
1665
+ '.gz' :
1666
+ ''}"
1667
+ tar_name = archive_name.delsubstr('.gz')
1668
+ relative = run.directory.delete_substrings(@root_folder, File.basename(run.directory))
1669
+ FileUtils.makedirs(temp_root + relative)
1670
+ unless FileTest.exist? temp_root + relative + archive_name
1671
+ eputs "Archiving #{index} out of #{size}" if options[:verbose]
1672
+ Dir.chdir(run.directory + '/..') do
1673
+ command = "tar -cW#{very_verbose}f #{tar_name} #{File.basename(run.directory)}"
1674
+ eputs command if options[:verbose]
1675
+ unless system command
1676
+ raise "Archiving failed"
1677
+ end
1678
+ break unless comp
1679
+ command = "gzip -4 -vf #{tar_name}"
1680
+ eputs command if options[:verbose]
1681
+ unless system command
1682
+ raise "Compression failed"
1683
+ end
1684
+ command = "gzip -t #{archive_name}"
1685
+ eputs command if options[:verbose]
1686
+ unless system command
1687
+ raise "Compression failed"
1688
+ end
1689
+ #exit
1690
+ end
1691
+ FileUtils.mv(relative.delsubstr('/') + archive_name, temp_root + '/' + relative + archive_name)
1692
+ end
1693
+
1694
+ system "chgrp -R #{options[:group]} #{temp_root}" if options[:group]
1695
+ end
1696
+ Dir.entries.each do |file|
1697
+ case file
1698
+ when '.', '..', '.tmparch'
1699
+ next
1700
+ when /^v/
1701
+ next unless File.file? file
1702
+ else
1703
+ FileUtils.cp_r(file, "#{temp_root}/#{file}")
1704
+ end
1705
+ end
1706
+ Dir.chdir('.tmparch') do
1707
+ command = "tar -cWv --remove-files -f #{File.basename(@root_folder)}.tar #{File.basename(@root_folder)}"
1708
+ command = "tar -cWv -f #{File.basename(@root_folder)}.tar #{File.basename(@root_folder)}"
1709
+ eputs command if options[:verbose]
1710
+ raise "Archiving Failed" unless system command
1711
+ end
1712
+ FileUtils.mv(".tmparch/#{File.basename(@root_folder)}.tar", "#{File.basename(@root_folder)}.tar")
1713
+ #FileUtils.rm_r(".tmparch")
1714
+ end
1715
+ end
1716
+
1717
+
1718
+
1719
+
1720
+
1721
+ end
1722
+
1723
+
1724
+
1725
+
1726
+
1727
+
1728
+
1729
+
1730
+ [
1731
+ "/graphs_and_films.rb",
1732
+ "/remote_code_runner.rb",
1733
+ "/merged_code_runner.rb",
1734
+ '/run.rb',
1735
+ '/heuristic_run_methods.rb',
1736
+ '/run_backwards_compatibility.rb'
1737
+ ].each do |file|
1738
+ file = CodeRunner::SCRIPT_FOLDER + file
1739
+ require file
1740
+ eprint '.' unless $has_put_startup_message_for_code_runner
1741
+ end