1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2016-2024 EDF R&D
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License.
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 ###############################################################################
21 ###############################################################################
24 from os import path,remove,getcwd,chdir,pardir,mkdir
25 from shutil import copyfile,move
26 from buildingspy.io.outputfile import Reader
28 if sys.version_info[0]<3:
29 import subprocess32 as subprocess
33 from .functions import get_cur_time, tol_equality
35 #The library to write in dsin.txt is imported directly in method single_simulation
36 #It can be either modelicares or write_in_dsin
38 ###############################################################################
39 # CLASS Automatic_Simulation #
40 ###############################################################################
42 class DiscardedInput(Exception):
45 ###############################################################################
46 # CLASS Automatic_Simulation #
47 ###############################################################################
49 class Automatic_Simulation(object):
51 Aim: making it easier to run automatic Dymola simulations.
54 - Commentaries are written directly in the code
55 - Examples are provided with the code to make it easier to use
56 - There is a slight difference between the Python2 and Python3 implementation
57 of method singleSimulation
59 Required libraries that are not in the standard distribution:
61 - subprocess32 (only for Python2)
62 - subprocess (only for Python3)
64 New features introduced 02/2019:
65 - possibility of performing simulations with dsint.txt and dymosim.exe files
66 not nessarily in the same folder
67 - possibility of creating a results' folder in another folder
68 - possibility of using an equivalent file to dsin.txt but with a different name
69 - possibility of modifying the results file name
70 - the previous use of this module should remain unchanged compared to the previous version
71 - Not yet checked for Linux but should work, Python within a Linux environment is not yet implemented.
74 - correction of set_success_code function in order to consider the directory in which the results mat file is created (and not the default one)
77 - better handling of linux simulations (definition dymosim_path)
80 - bug correction in copy_dsres_to and check_inputs
83 def __init__(self,simu_dir,dict_inputs,
84 dymosim_path = None, dsin_name = 'dsin.txt', dsres_path = None,
85 logfile=None,timeout=60,copy_dsres_to=None,
86 copy_initial_dsin=None,without_modelicares=False,linux=False,check_inputs=[]):
87 self.__simu_dir = path.abspath(simu_dir)
88 self.__dsin_name = path.join(path.abspath(simu_dir),dsin_name)
89 if dsres_path is None:
90 self.__dsres_path = path.join(path.abspath(simu_dir),'dsres.mat')
91 elif path.basename(dsres_path)[-4:] =='.mat':
92 self.__dsres_path = dsres_path
94 self.__dsres_path = path.join(path.abspath(dsres_path),'dsres.mat')
95 self.__logfile = logfile
96 self.__timeout = timeout
97 self.__copy_dsres_to = copy_dsres_to
98 self.__copy_initial_dsin = copy_initial_dsin
99 self.__without_modelicares = without_modelicares
102 if dymosim_path is None:
104 self.__dymosim_path = path.join(path.abspath(simu_dir),'dymosim')
106 self.__dymosim_path = path.join(path.abspath(simu_dir),'dymosim.exe')
107 elif path.isdir(dymosim_path):
109 self.__dymosim_path = path.join(path.abspath(dymosim_path),'dymosim')
111 self.__dymosim_path = path.join(path.abspath(dymosim_path),'dymosim.exe')
112 elif path.isfile(dymosim_path):
113 self.__dymosim_path = dymosim_path
115 raise ValueError('Model file or directory not found using %s'%str(dymosim_path))
117 self.__success_code = 99
119 #Potential separation of standard inputs parameters from time varying inputs (dsu.txt)
120 if 'Time' in dict_inputs.keys() : #Reserved keyword for time variation
121 logging.info("Reserved variable Time found -> dsu.txt creation")
123 for key in dict_inputs :
124 #if hasattr(dict_inputs[key],'__iter__') and not isinstance(dict_inputs[key],str) : #Identifying iterables not string
125 if isinstance(dict_inputs[key],(list,tuple)) : #Identifying list or tuples
127 self.dsu_inputs = {key:dict_inputs[key] for key in dsu_keys} #Dictionary restricted to time varying inputs
128 for key in dsu_keys : del dict_inputs[key] #Removing the variable from the standard dictionary
130 self.dsu_inputs = None
131 self.__dict_inputs = dict_inputs
133 if check_inputs is True:
134 self.__check_inputs = dict_inputs.keys()
136 self.__check_inputs = check_inputs
138 ###############################################################################
140 ###############################################################################
144 Path of the directory in which the simulation will be done (directory which
145 contains file dsin.txt or equivalent)
147 return self.__simu_dir
150 def check_inputs(self):
152 Variable used to select inputs that have to be checked: set value vs. used
154 - True : all variables in dict_inputs are checked
155 - List of variables name to be checked
157 return self.__check_inputs
160 def dict_inputs(self):
162 Dictionary associating to each variable whose value has to be given to
163 dsin.txt (inputs + iteration variables if necessary) its value.
165 return self.__dict_inputs
172 return self.__logfile
177 If a simulation has not converged after timeout, the siumlation is stopped.
179 return self.__timeout
182 def copy_dsres_to(self):
184 Path of the file where dsres.mat should be copied if simulation converges
186 return self.__copy_dsres_to
189 def success_code(self):
192 If a simulation converges, this attribute is setted to 0
193 If a simulation fails to initialize, this attribute is setted to 1
194 If a simulation fails after the initialization, this attribute is setted to 2
195 If a simulation does not fail but does not converge either, this attribute is setted to 3
198 return self.__success_code
201 def copy_initial_dsin_to(self):
203 Path of the file where dsin.txt should be copied before the first simulation is run.
205 return self.__copy_initial_dsin
208 def without_modelicares(self):
210 If True, modelicares is not imported.
211 Instead, the script named "writer" is imported. A method of class Writer
212 is used to write in dsin.txt.
214 return self.__without_modelicares
219 If the script has to be run on Linux, this attribute should be setted to True.
220 On Linux, the exe file is indeed named "dymosim" instead of "dymosim.exe".
225 def dymosim_path(self):
227 Path of the directory that contains the dymosim.exe (or equivalent) file,
228 can be different of simu_dir
230 return self.__dymosim_path
234 Name of the dsint.txt (or equivalent) file, must be inclueded in simu_dir
236 return self.__dsin_name
238 def dsres_path(self):
240 Path of the file in which the results mat file will be created
242 return self.__dsres_path
243 ###############################################################################
245 ###############################################################################
246 def set_success_code(self):
248 This function sets the values of the following attributes:
250 True if simulation has converged (a file named success exists (success. on Linux))
251 False if simulation has failed (a file named failure exists (failure. on Linux))
252 None if simulation has not converged (For instance if the simulation was not given the time to converge)
254 True if the simulation succeeded to initialise (a dsres.mat file is found)
255 False if the initialization failed (no dsres.mat file is found)
257 if path.exists(self.__dsres_path):
259 if path.exists(path.join(path.dirname(self.__dsres_path),'success.')):
260 self.__success_code = 0
261 elif path.exists(path.join(path.dirname(self.__dsres_path),'failure.')):
262 self.__success_code = 2
264 self.__success_code = 3
266 if path.exists(path.join(path.dirname(self.__dsres_path),'success')):
267 self.__success_code = 0
268 elif path.exists(path.join(path.dirname(self.__dsres_path),'failure')):
269 self.__success_code = 2
271 self.__success_code = 3
273 self.__success_code = 1 #Suggestion: We should probably also consider timeout happens durng the initialization phase (neither 'dsres.mat' nor 'failure')
275 ###############################################################################
277 ###############################################################################
278 def reset_success_code(self):
280 Reset value of attribute success
282 self.__success_code = 99
283 ###############################################################################
285 ###############################################################################
289 Remove the files that are automatically created during a simulation
291 if path.exists(path.join(self.simu_dir,"success")):
292 remove(path.join(self.simu_dir,"success"))
293 if path.exists(path.join(self.simu_dir,"failure")):
294 remove(path.join(self.simu_dir,"failure"))
295 if path.exists(path.join(self.simu_dir,"null")):
296 remove(path.join(self.simu_dir,"null"))
297 if path.exists(path.join(self.simu_dir,"dsfinal.txt")):
298 remove(path.join(self.simu_dir,"dsfinal.txt"))
299 if path.exists(path.join(self.simu_dir,"status")):
300 remove(path.join(self.simu_dir,"status"))
301 if path.exists(path.join(self.simu_dir,"dslog.txt")):
302 remove(path.join(self.simu_dir,"dslog.txt"))
303 if path.exists(self.__dsres_path):
304 remove(self.__dsres_path)
306 def single_simulation(self):
308 Run an simulation using dymosim.exe after inserting the values that are
309 contained in self.dict_inputs in dsin.txt
311 #Import a library to write in dsin.txt
312 if self.without_modelicares == True:
313 from .write_in_dsin import Write_in_dsin
317 #Save the value of the current working directory in CUR_DIR
320 #It is necessary to change the working directory before running dymosim.exe
323 logging.info('Time = '+get_cur_time()+' \t'+'Script working in directory '+self.simu_dir)
325 #Cleaning the working directory (mandatory to check the intialisation success)
328 #File dsin.txt is copied before being changed if it has been given a value
329 if self.copy_initial_dsin_to:
330 dir_copy_dsin = path.abspath(path.join(self.copy_initial_dsin_to,pardir))
331 if not(path.exists(dir_copy_dsin)):
333 copyfile(src=path.basename(self.__dsin_name),dst=self.copy_initial_dsin_to)
335 # Change file dsin.txt
337 #print('Changing dsin.txt')
338 logging.info('Time = '+get_cur_time()+' \t'+'Changing dsin.txt')
339 if self.without_modelicares:
340 writer = Write_in_dsin(dict_inputs = self.dict_inputs,filedir = self.simu_dir, dsin_name = path.basename(self.__dsin_name), new_file_name = path.basename(self.__dsin_name))
341 writer.write_in_dsin()
343 modelicares.exps.write_params(self.dict_inputs,path.basename(self.__dsin_name))
345 #A folder for results file is created if it does not exist (usually for dsres.mat)
347 dir_dsres_path = path.abspath(path.join(self.__dsres_path,pardir))
348 if not(path.exists(dir_dsres_path)):
349 mkdir(dir_dsres_path)
351 # Simulation launching : execution of dymosim.exe
353 #print('Launching simulation')
354 logging.info('Time = '+get_cur_time()+' \t'+"Launching simulation")
355 ###############################################################################
356 # WARNING: DIFFERENT SOLUTIONS FOR PYTHON 2 AND PYTHON 3 #
357 ###############################################################################
358 #Python2 solution - working solution
359 if sys.version_info[0]<3:
362 #print('Running dymosim')
363 subprocess.call(str(self.__dymosim_path + ' ' + self.__dsin_name + ' ' + self.__dsres_path),timeout=self.timeout) #NOT TESTED
365 #print('Running dymosim.exe')
366 subprocess.call(str(self.__dymosim_path + ' ' + self.__dsin_name + ' ' + self.__dsres_path),timeout=self.timeout) #NOT TESTED
368 except subprocess.TimeoutExpired:
370 logging.info('Time = '+get_cur_time()+' \t'+'Time limit reached. Simulation is stopped.')
371 #Add information about the simulation time when the simulation is stopped
373 with open('status','r') as lines:
377 stop_time = line.split()[-1]
378 logging.info('Simulation time when simulation is stopped: '+str(stop_time))
382 #Python3 solution - best solution. Cannot be easily implemented on Python2 because
383 #the TimeoutExpired exception creates an error (apparently because of subprocess32)
386 #print('Running dymosim.exe')
387 proc = subprocess.Popen([str(arg) for arg in [self.__dymosim_path,self.__dsin_name,self.__dsres_path]],
388 stderr=subprocess.STDOUT,stdout=subprocess.PIPE,universal_newlines=True)
389 dymout, _ = proc.communicate(timeout=self.timeout)
391 logging.debug(dymout)
393 except subprocess.TimeoutExpired:
395 dymout, _ = proc.communicate() #Finalize communication (python guidelines)
397 logging.debug(dymout)
398 logging.info('Time = '+get_cur_time()+' \t'+'Time limit reached. Simulation is stopped.')
400 with open('status','r') as lines:
404 stop_time = line.split()[-1]
405 logging.info('Simulation time when simulation is stopped: '+str(stop_time))
408 ###############################################################################
410 ###############################################################################
412 #Set the value of attribute success
413 self.set_success_code()
415 #Copy dsres.mat if attribute copy_dsres_to is not None
416 if self.success_code == 0:
417 logging.info('Time = '+get_cur_time()+' \t'+'Simulation has converged')
418 if self.copy_dsres_to:
419 move(self.__dsres_path,self.copy_dsres_to) #Test
421 elif self.success_code == 1:
422 logging.info('Time = '+get_cur_time()+' \t'+'Simulation has failed - Error during initialization')
424 elif self.success_code == 2:
425 logging.info('Time = '+get_cur_time()+' \t'+'Simulation has failed - Error after the initialization')
427 elif self.success_code == 3:
428 #The message is written in the TimeoutExpired Exception
430 # Check whether the inputs have been taken into account
431 if self.success_code != 1 :
432 for key in self.check_inputs :
433 if self.copy_dsres_to:
434 outfile = Reader(self.copy_dsres_to,'dymola')
436 outfile = Reader(self.__dsres_path,'dymola')
437 used_val = outfile.values(key)[1][0]
439 logging.debug('Checking %s : %s (set) vs. %s (used)' % (key,self.dict_inputs[key],used_val))
440 if not tol_equality((self.dict_inputs[key],outfile.values(key)[1][0])):
441 raise DiscardedInput('Parameter %s: %s set but %s used' % (key,self.dict_inputs[key],used_val))
446 if __name__ == '__main__':
447 print('\n AUTODIAGNOSTIC\n ==============\n')