coderunner 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +20 -0
- data/README.md +4 -0
- data/README.rdoc +19 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/lib/coderunner.rb +419 -0
- data/lib/coderunner/class_methods.rb +727 -0
- data/lib/coderunner/fortran_namelist.rb +1281 -0
- data/lib/coderunner/graphs_and_films.rb +744 -0
- data/lib/coderunner/heuristic_run_methods.rb +202 -0
- data/lib/coderunner/instance_methods.rb +1741 -0
- data/lib/coderunner/interactive_methods.rb +776 -0
- data/lib/coderunner/long_regexen.rb +201 -0
- data/lib/coderunner/merged_code_runner.rb +40 -0
- data/lib/coderunner/run.rb +1000 -0
- data/test/helper.rb +18 -0
- data/test/test_coderunner.rb +7 -0
- metadata +139 -0
@@ -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
|