1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2019 EDF R&D
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
25 from . import salome_proxy
26 from . import samplecsvmanager
27 from . import parameters
28 from . import configuration
29 from . import defaultschemabuilder
30 from .studyexception import StudyUseException, StudyRunException
31 from .studyresult import StudyResult
33 def defaultSampleManager():
34 return samplecsvmanager.SampleManager()
37 JOB_DUMP_NAME = "jobDump.xml"
38 def __init__(self, sampleManager=None, schemaBuilder=None):
40 self.global_result = StudyResult()
41 if sampleManager is None:
42 self.sampleManager = defaultSampleManager()
44 self.sampleManager = sampleManager
45 if schemaBuilder is None:
46 self.schemaBuilder = defaultschemabuilder.DefaultSchemaBuilder()
48 self.schemaBuilder = schemaBuilder
50 # Study creation functions
51 def createNewJob(self, script, sample, params):
53 Create a new job out of those parameters:
54 script : script / pyscript type
55 sample : sample to be evaluated (Sample class)
56 params : job submission parameters (Parameters class)
57 The result directory will contain all the files needed for a launch and a
58 job is created but not launched.
60 self._check(script,sample)
63 self.params.salome_parameters.job_type = self.jobType()
64 tmp_workdir = self.params.salome_parameters.result_directory
65 schema_path, extra_files = self._prepareDirectoryForLaunch(tmp_workdir,
67 # this list manipulation is needed because in_files is not a python list
68 # if we don't use a salome session. In that case swig uses a python tuple
69 # in order to map a std::list as a parameter of a structure.
70 in_files_as_list = list(self.params.salome_parameters.in_files)
71 self.params.salome_parameters.in_files = in_files_as_list + extra_files
72 self.params.salome_parameters.job_file = schema_path
73 launcher = salome_proxy.getLauncher()
74 self.job_id = launcher.createJob(self.params.salome_parameters)
77 def loadFromDirectory(self, path):
79 Recover a study from a result directory where a previous study was launched.
81 self.sample = self.sampleManager.restoreSample(path)
82 job_string = loadJobString(path)
83 launcher = salome_proxy.getLauncher()
84 self.job_id = launcher.restoreJob(job_string)
86 salome_params = launcher.getJobParameters(job_id)
87 self.params = parameters.Parameters(salome_parameters=salome_params)
91 def loadFromString(self, jobstring):
93 Recover a study from a string which contains the description of the job.
94 This string can be obtained by launcher.dumpJob.
96 launcher = salome_proxy.getLauncher()
97 self.job_id = launcher.restoreJob(jobstring)
101 salome_params = launcher.getJobParameters(self.job_id)
102 self.params = parameters.Parameters(salome_parameters=salome_params)
103 #TODO: sampleManager should be loaded from result_directory
104 self.sample = self.sampleManager.restoreSample(
105 salome_params.result_directory)
108 raise StudyRunException("Failed to restore the job.")
110 def loadFromId(self, jobid):
112 Connect the study to an already created job.
113 The result directory of the job must be already prepared for launch.
118 launcher = salome_proxy.getLauncher()
119 salome_params = launcher.getJobParameters(job_id)
120 self.params = parameters.Parameters(salome_parameters=salome_params)
121 #TODO: sampleManager should be loaded from result_directory
122 self.sample=self.sampleManager.restoreSample(salome_params.result_directory)
126 # launch parameters functions
130 # TODO: may be deprecated
131 def createDefaultParameters(self, resource="localhost",
133 result_base_dir=None):
135 Create the Parameters structure and the result directory.
136 The result directory created here is needed by the job.
138 newParams = parameters.Parameters(resource, nb_branches)
139 newParams.salome_parameters.job_type = self.jobType()
140 newParams.salome_parameters.job_name = "idefix_job"
141 newParams.salome_parameters.result_directory = configuration.newResultDirectory(result_base_dir)
144 # Job management functions
147 The job should have been already created.
150 raise StudyUseException("Nothing to launch! Job is not created!")
151 tmp_workdir = self.params.salome_parameters.result_directory
153 launcher = salome_proxy.getLauncher()
154 launcher.launchJob(self.job_id)
156 job_string = launcher.dumpJob(self.job_id)
157 jobDumpPath = os.path.join(tmp_workdir, PyStudy.JOB_DUMP_NAME)
158 with open(jobDumpPath, "w") as f:
163 Try to get the result file and if it was possible the results are loaded in
165 An exception may be thrown if it was not possible to get the file.
166 Return a StudyResult object.
168 self.global_result = StudyResult()
170 raise StudyUseException("Cannot get the results if the job is not created!")
171 launcher = salome_proxy.getLauncher()
172 state = launcher.getJobState(self.job_id)
173 tmp_workdir = self.params.salome_parameters.result_directory
174 searchResults = False
175 errorIfNoResults = False
177 if state == "CREATED" :
178 raise StudyUseException("Cannot get the results if the job is not launched!")
179 elif state == "QUEUED" or state == "IN_PROCESS":
180 # no results available at this point. Try again later! Not an error.
181 searchResults = False
182 elif state == "FINISHED" :
183 # verify the return code of the execution
185 if(launcher.getJobWorkFile(self.job_id, "logs/exit_code.log", tmp_workdir)):
186 exit_code_file = os.path.join(tmp_workdir, "exit_code.log")
188 if os.path.isfile(exit_code_file):
189 with open(exit_code_file) as myfile:
190 exit_code = myfile.read()
191 exit_code = exit_code.strip()
192 self.global_result.exit_code = exit_code
193 if exit_code == "0" :
194 errorIfNoResults = True # we expect to have full results
196 errorMessage = "An error occured during the execution of the YACS schema."
198 errorMessage = "Failed to get the exit code of the YACS schema execution."
200 elif state == "RUNNING" or state == "PAUSED" or state == "ERROR" :
201 # partial results may be available
203 elif state == "FAILED":
204 # We may have some partial results because the job could have been
205 # canceled or stoped by timeout.
207 errorMessage = "Job execution failed!"
209 if 1 == launcher.getJobWorkFile(self.job_id,
210 self.sampleManager.getResultFileName(),
213 res = self.sampleManager.loadResult(self.sample, tmp_workdir)
214 self.global_result.result = res
215 except Exception as err:
218 elif errorIfNoResults:
219 errorMessage = "The job is finished but we cannot get the result file!"
220 if len(errorMessage) > 0 :
222 The results you get may be incomplete or incorrect.
223 For further details, see {}/logs directory on {}.""".format(
224 self.params.salome_parameters.work_directory,
225 self.params.salome_parameters.resource_required.name)
226 errorMessage += warningMessage
227 self.global_result.error_message = errorMessage
228 raise StudyRunException(errorMessage)
229 return self.global_result
231 def resultAvailable(self):
233 Try to get the result and return True in case of success with no exception.
234 In case of success the results are loaded in the sample.
244 def getJobState(self):
247 launcher = salome_proxy.getLauncher()
248 return launcher.getJobState(self.job_id)
250 def getProgress(self):
253 state = self.getJobState()
254 if state == "CREATED" or state == "QUEUED" :
256 if not self.resultAvailable():
258 return self.sample.progressRate()
262 raise StudyUseException("Cannot dump the job if it is not created!")
263 launcher = salome_proxy.getLauncher()
264 return launcher.dumpJob(self.job_id)
266 def wait(self, sleep_delay=10):
267 """ Wait for the end of the job """
268 launcher = salome_proxy.getLauncher()
270 jobState = launcher.getJobState(job_id)
272 while jobState=="QUEUED" or jobState=="IN_PROCESS" or jobState=="RUNNING" :
273 time.sleep(sleep_delay)
274 jobState = launcher.getJobState(job_id)
276 def _prepareDirectoryForLaunch(self, result_directory, script):
278 result_directory : path to a result working directory.
279 script : script / pyscript type
281 yacs_schema_path: path to the yacs schema (xml file).
282 extra_in_files: list of files to add to salome_parameters.in_files
284 if not os.path.exists(result_directory):
285 os.makedirs(result_directory)
286 # export sample to result_directory
287 inputFiles = self.sampleManager.prepareRun(self.sample, result_directory)
290 configpath = os.path.join(result_directory, "idefixconfig.json")
292 dicconfig["nbbranches"] = self.params.nb_branches
293 dicconfig["studymodule"] = "idefixstudy"
294 dicconfig["sampleIterator"] = self.sampleManager.getModuleName()
295 with open(configpath, "w") as f:
296 json.dump(dicconfig, f, indent=2)
297 studypath = os.path.join(result_directory, "idefixstudy.py")
298 with open(studypath, "w") as f:
299 f.write(script.script)
300 schema_path, extra_files = self.schemaBuilder.buildSchema(result_directory)
302 extra_files.extend([configpath, studypath])
303 extra_files.extend(inputFiles)
304 return schema_path, extra_files
306 def _check(self, script, sample):
307 "Raise StudyUseException if the sample does not match with the sample."
308 script_params = script.getInputNames()
309 sample_inputs = sample.getInputNames()
310 if len(script_params) < 1:
311 raise StudyUseException("The study function should have at least one parameter. None found.")
312 if len(script_params) != len(sample_inputs):
313 m="The study function should have the same number of parameters as the input variables in the sample ({} != {})."
314 raise StudyUseException(m.format(len(script_params), len(sample_inputs)))
315 for nm in script_params:
316 if nm not in sample_inputs:
317 raise StudyUseException("Parameter {} not found in the sample.".format(nm))
320 def dumpJob(result_directory, jobString):
322 Save the jobString to a file into result_directory.
323 result_directory is a string representing a path to a directory.
324 jobString is a string representing the serialization of a job.
325 Use loadJobString for reloading the string saved here.
327 jobDumpPath = os.path.join(result_directory, PyStudy.JOB_DUMP_NAME)
328 with open(jobDumpPath, "w") as f:
331 def loadJobString(result_directory):
333 Return the jobString saved by the dumpJob function into a directory.
334 Use dumpJob for saving a the string.
336 jobDumpPath = os.path.join(result_directory, PyStudy.JOB_DUMP_NAME)
337 with open(jobDumpPath, "r") as f:
338 job_string = f.read()