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,mkdir,remove
25 from shutil import copytree,rmtree
27 from .automatic_simulation import Automatic_Simulation
28 from .functions import get_cur_time
29 ###############################################################################
30 # CLASS Working_Point_Modification #
31 ###############################################################################
33 class Working_Point_Modification(object):
35 Class Working_Point_Modification aims at making it easier to make a Modelica model
36 converge when one (or several) of its variables has (have) to be changed. Depending on the impact of
37 the variable, changing the value by a few percent can indeed be very hard.
39 Generally, when the initial state is too far from the reference one for Dymola
40 to be able to converge towards the solution with its reference initialisation
41 script, it is possible to give ramps to Dymola (or other software) to help
42 it converging. Yet, if the model contains inverted values that should be
43 inverted only in a stationary state, giving ramps to help the model
44 converging does not work (the parameters that have been calculated at t=0
47 Class Working_Point_Modification aims at helping solving this difficulty.
48 It can also be used for a model which does not contain any inverted values, in
49 case the modeler does not want to change his model so that the parameters could
52 It runs several iterations, converging iteratively from the reference initial
53 state to the desired state.
55 Dymola must be able to simulate the Modelica model in the reference state.
59 Path of the directory in which directories will be created
60 simu_material_dir String
61 Name of the subdirectory of main_dir which contains all the required material to run a simulation (dymosim.exe, dsin.txt, .dll files, files that are necesssary to create an initialization script)
62 around_simulation0 Object of type Around_Simulation (class defined in around_simulation)
63 Contains the material to create an initialization script
64 dict_var_to_fix Dictionary
65 Contains the names of the variables that must be imposed to
66 different values between reference state and the target state,
67 and the values they must be given at each state between the
68 reference and target states (information contained by a function)
69 The other arguments are optional, but more information can be found in the attributes getters
71 New features introduced 02/2019:
72 - the possibility of using a dymosim situated in a given folder (featured introduced in automatic_simulation.py)
74 - the previous use of this module should remain unchanged compared to previous version
75 - possibility of running this script in Linux (not tested)
77 def __init__(self,main_dir,simu_material_dir,
78 around_simulation0,dict_var_to_fix,
80 simu_dir='SIMUS_DIR',store_res_dir='Working_point_change',
82 timeout=60,cur_step=1.,nit_max=20,var_to_follow_path='var_to_follow.csv',
83 gen_scripts_ini=False,pause=5.,min_step_val = 0.01,check_inputs=[]):
85 self.__main_dir = path.abspath(main_dir)
86 self.__simu_material_dir = simu_material_dir
88 self.__store_res_dir = store_res_dir
89 around_simulation0.verbose = False #To avoid repetitive messages
90 self.__around_simulation0 = around_simulation0
91 self.__dict_var_to_fix = dict_var_to_fix
92 self.__simu_dir = simu_dir
94 self.__timeout = timeout
95 self.__cur_step = cur_step
96 self.__nit_max = nit_max
97 self.__var_to_follow_path = var_to_follow_path
98 self.__gen_scripts_ini = gen_scripts_ini
100 self.__min_step_val = min_step_val
102 self.__around_simulation = None
103 self.__dict_inputs = {}
105 self.__ref_success_code = 99
106 self.__success_code = 99
108 self.__converging_val = None
109 self.__list_inputs = []
112 if dymosim_path is None:
113 self.__dymosim_path = None
114 elif path.isdir(dymosim_path):
116 self.__dymosim_path = path.join(path.abspath(dymosim_path),'dymosim')
118 self.__dymosim_path = path.join(path.abspath(dymosim_path),'dymosim.exe')
119 elif path.isfile(dymosim_path):
120 self.__dymosim_path = dymosim_path
122 raise ValueError('Model file or directory not found using %s'%str(dymosim_path))
124 if check_inputs is True:
125 self.__check_inputs = dict_var_to_fix.keys()
127 self.__check_inputs = check_inputs
129 ###############################################################################
131 ###############################################################################
136 Path of the main directory - relative path from the path where the script
137 is executed or absolute path.
139 return self.__main_dir
142 def check_inputs(self):
144 Variable used to select inputs that have to be checked: set value vs. used
146 - True : all variables in dict_inputs are checked
147 - List of variables name to be checked
149 return self.__check_inputs
152 def simu_material_dir(self):
155 Path of the main directory which contains all the files that are required to
156 simulate the model. These files will be copied and this directory will
157 not be changed - relative path from main_dir
159 return self.__simu_material_dir
162 def store_res_dir(self):
165 Name of the directory where the simulation results will be stored
167 return self.__store_res_dir
170 def around_simulation0(self):
172 Type: Around_Simulation
173 around_simulation0 must contain enough information to create a script that
174 can make the simulation converge in the reference state
175 More information about object of type Around_Simulation in file
176 around_simulation.py.
178 return self.__around_simulation0
181 def dict_var_to_fix(self):
184 Dictionary which associates
185 key (Modelica) name of the variable whose value must be fixed
186 value Function which returns the value of variable key (0-> ref_val,
188 dict_var_to_fix can be generated using class Dict_Var_To_Fix.
190 return self.__dict_var_to_fix
196 Path of the directory in which all the simulations will be run
198 return self.__simu_dir
200 def dymosim_path(self):
202 Path of the directory that contains the dymosim.exe (or equivalent) file,
203 can be different of simu_dir
205 return self.__dymosim_path
210 If the script has to be run on Linux, this attribute should be setted to True.
211 On Linux, the exe file is indeed named "dymosim" instead of "dymosim.exe".
219 If a simulation has not converged after timeout, the siumlation is stopped.
221 return self.__timeout
227 Current step (0: reference value;1: target value)
228 -> with cur_step=0.5,the simulation will try to converge in 2 steps.
230 return self.__cur_step
236 Maximum number of iterations
238 return self.__nit_max
241 def var_to_follow_path(self):
244 Path of the file containing the values of the interesting
245 variables which are saved each time a simulation converges
247 return self.__var_to_follow_path
250 def gen_scripts_ini(self):
253 If True, each time a simulation has converged, the associated
254 initialization script is generated.
256 return self.__gen_scripts_ini
262 Number of seconds that are given to the system to copy the files
263 I could try to replace it by calling a subprocess and waiting for the end
269 def around_simulation(self):
271 Type: Around_Simulation
272 around_simulation contains enough information to generated initialization
275 More information about object of type Around_Simulation in file
276 around_simulation.py.
278 return self.__around_simulation
281 def dict_inputs(self):
284 Dictionary associating to each variable whose value has to be given to
285 dsin.txt (variables whose values must be fixed + iteration variables) its value.
287 return self.__dict_inputs
293 Caracterise the distance from the reference state and to the target state.
294 0. in the reference state
295 1. in the final state when the working point change has reached its target
300 def success_code(self):
303 If a simulation converges, this attribute is setted to 0
304 If a simulation fails to initialize, this attribute is setted to 1
305 If a simulation fails after the initialization, this attribute is setted to 2
306 If a simulation does not fail but does not converge either, this attribute is setted to 3
309 return self.__success_code
312 def ref_success_code(self):
315 Before trying to reach the target state, the user should try to simulate
317 If this "reference simulation" converges, this attribute is setted to 0.
318 If this "reference simulation" fails to initialize, this attribute is setted to 1
319 If this "reference simulation" fails after the initialization, this attribute is setted to 2
320 If this "reference simulation" does not fail but does not converge either, this attribute is setted to 3
323 If the value of this attribute is 1, no further calculation will be made.
325 return self.__ref_success_code
331 Number of simulation that have been run
336 def converging_val(self):
339 Highest value for which the simulation has converged
343 return self.__converging_val
346 def min_step_val(self):
349 Mininum allowed value for the step. If the script tries to use a value
350 lower than min_step_val, it will stop.
352 return self.__min_step_val
355 def list_inputs(self):
358 List of the input variable (Modelica) names
360 return self.__list_inputs
366 It is a dictionary containing the values of self.list_inputs at the end
367 of a simulation (the values of the iteration variables in this attribute
368 will therefore be injected in the next simulation)
371 ###############################################################################
373 ###############################################################################
375 def set_success_code(self,value):
380 See the description of attribute success_code for more information
382 self.__success_code = value
384 def set_ref_success_code(self,value):
388 See the description of attribute success_code for more information
390 self.__ref_success_code = value
392 def set_converging_val(self,value):
396 Highest value for which the simulation has converged up to the moment
397 this method is called
399 self.__converging_val = value
401 ###############################################################################
403 ###############################################################################
406 Reset value of attribute val to its reference value: 0.
410 ###############################################################################
412 ###############################################################################
414 def increase_nit(self):
416 Increase value of attribute nit.
417 This method must be called when a simulation is run.
421 def get_path_store_res_dir(self):
423 Return the path in which the results are saved.
425 return path.abspath(path.join(self.main_dir,self.store_res_dir))
427 def create_working_directory(self):
429 A directory in which all the simulations will be made is created.
430 The files that are in self.simu_material_dir are copie into this new directory.
431 If self.store_res_dir does not exist in main_dir, a directory named self.store_res_dir is created
433 if path.exists(path.join(self.main_dir,self.simu_dir)):
434 rmtree(path.join(self.main_dir,self.simu_dir), ignore_errors=True) #added 19_07_2018
435 copytree(path.abspath(path.join(self.main_dir,self.simu_material_dir)),path.join(self.main_dir,self.simu_dir))
437 if path.exists(path.join(self.main_dir,self.store_res_dir)):
438 rmtree(path.join(self.main_dir,self.store_res_dir), ignore_errors=True) #added 19_07_2018
439 mkdir(path.join(self.main_dir,self.store_res_dir))
443 Reset value of attribute res to {} (=a dictionary without any keys)
449 Set the value of attribute res.
450 This method has te be called when a simulation has converged and before
451 the result is written into a file by method write_res
455 for var in self.list_inputs:
456 if var in self.dict_var_to_fix:
457 self.__res[var] = self.dict_inputs[var]
459 #After a simulation, self.dict_inputs is not modified (it would
460 #be a nonsense to change self.dict_inputs if no simulation has
461 #to be done), but self.around_simulation.dict_iter_var is modified,
462 #that is the reason why it is necessary to extract the values
463 #in self.around_simulation.dict_iter_var.
464 self.__res[var] = self.around_simulation.dict_iter_var[var]
467 def set_dict_inputs(self,ref=False):
469 Set the value of attribute dict_inputs
470 The values of the iterations variables comes from the last simulation
471 which has converged. The values of the parameters that have to be
472 fixed are calculated with the functions of self.dict_var_to_fix.
474 If ref=True, val is given the value 0 and the iteration variables are
475 given the values they have in self.simulation0.dict_iter_var.
479 self.__dict_inputs = copy(self.around_simulation0.dict_iter_var)
482 #Get the iteration variables and their values
483 self.__dict_inputs = copy(self.around_simulation.dict_iter_var)
485 #Get the values of the variables that have to be fixed
486 for var_to_fix in self.dict_var_to_fix:
487 self.dict_inputs[var_to_fix] = self.dict_var_to_fix[var_to_fix](self.val)
489 #Set the value of attribute list_inputs if it has no value
490 if self.list_inputs == []:
491 self.__list_inputs = list(self.dict_inputs.keys())
492 self.list_inputs.sort()
494 def write_res(self,ref=False):
496 Write in self.var_to_follow_path the values of the inputs at the end
499 with open(self.var_to_follow_path,'a') as output:
501 output.write('val'.ljust(50)+';')
502 for var in self.list_inputs[:-1]:
503 output.write(var.ljust(50)+';')
504 output.write(self.list_inputs[-1]+'\n')
506 output.write(str(self.val).ljust(50)+';')
507 for var in self.list_inputs[:-1]:
508 output.write(str(self.res[var]).ljust(50)+';')
509 output.write(str(self.res[var])+'\n')
511 def clean_var_to_follow(self):
513 If the file self.var_to_follow_path exists, it is erased when this
516 if path.exists(self.var_to_follow_path):
517 remove(self.var_to_follow_path)
519 def fauto_simu(self,ref=False,final_cleaning=False):
521 Erase the old simulation files from the simulation directory and run a
522 simulation with the current values of the iteration variables
524 #Create an object of type Automatic_Simulation, which will make it easy
525 #to run the simulation
526 #Check inputs only for the target case
527 logging.debug('Val %s' % self.val)
528 if (not ref) and (self.val == 1.) :
529 tmp_check = self.check_inputs
530 logging.debug('Performing input check on the target case (run %s)' % self.nit)
534 logging.debug('Input of the current case:')
535 for key in self.dict_inputs.keys():
536 logging.debug('%s = %s' % (key,self.dict_inputs[key]))
538 auto_simu = Automatic_Simulation(simu_dir=path.join(self.main_dir,self.simu_dir),
539 dict_inputs=self.dict_inputs,
540 dymosim_path = self.__dymosim_path,
542 timeout=self.timeout,
543 copy_dsres_to=path.join(self.get_path_store_res_dir(),str(self.val)+'.mat'),
544 check_inputs=tmp_check,
546 without_modelicares = True) # A garder à False car sinon write_in_dsin efface dic_inputs... update 12/2022: writ_in_dsin modifié pour ne pas vider dict_inputs
549 logging.info('Time = '+get_cur_time()+' \t'+'Current value: '+str(self.val))
551 self.set_success_code(99)
555 auto_simu.single_simulation()
558 #If the simulation has converged
559 if auto_simu.success_code == 0:
561 #If an initialization script has to be created
562 if self.gen_scripts_ini:
563 #If the simulation state is the reference state
565 self.around_simulation0.generated_script_options={'file_name':path.join(self.get_path_store_res_dir(),str(self.val)+'.mos')}
566 self.around_simulation0.create_script_ini()
568 # -----> Option should be added to the dictionary and not reset !!!!!
569 self.around_simulation.generated_script_options={'file_name':path.join(self.get_path_store_res_dir(),str(self.val)+'.mos')}
570 self.around_simulation.create_script_ini()
573 logging.info('Time = '+get_cur_time()+' \t'+'Reference simulation has converged')
574 self.set_ref_success_code(0)
575 self.__around_simulation = copy(self.around_simulation0)
577 logging.info('Time = '+get_cur_time()+' \t'+'Simulation has converged')
579 #If this simulation is the first to converge, the next values of the iteration variables will be built from the mat result file.
580 if self.converging_val == None:
581 self.around_simulation.iter_var_values_options['source'] = 'from_mat'
584 self.set_success_code(0)
585 self.set_converging_val(self.val)
586 self.around_simulation.mat = path.join(self.get_path_store_res_dir(),str(self.val)+".mat")
587 self.around_simulation.set_dict_iter_var()
589 self.write_res(ref=ref)
592 self.set_success_code(auto_simu.success_code)
595 self.set_ref_success_code(auto_simu.success_code)
596 logging.info('Time = '+get_cur_time()+' \t'+'Reference simulation has not converged. Success_code = '+str(self.ref_success_code))
598 logging.info('Time = '+get_cur_time()+' \t'+'Simulation has not converged. Success_code = '+str(self.success_code))
602 def check_ref_simulation(self):
604 Run the simulation in the reference state
606 #Erase the file in which the values of the inputs at the end of the
607 #simulations are saved
608 self.clean_var_to_follow()
610 #reset the values of the inputs
611 self.set_dict_inputs(ref=True)
614 self.fauto_simu(ref=True)
616 def change_val(self):
618 Adapt the step if it is necessary and change the value of attribute val.
619 The step is divided by 2 when a simulation does not converge.
621 if self.success_code == 1:
622 self.__cur_step = float(self.cur_step)/2.
623 elif self.success_code in [2,3]:
624 logging.info('Time = '+get_cur_time()+' \t'+'Method change_val has been called whereas the success_code is in [2,3] which \
625 is not normal. The methods designed in this script can only help to initialize the simulations. If the simulation fails \
626 whereas the initialization has been a success, it is not useful to decrease the step')
629 if self.val+self.cur_step > 1:
630 self.__cur_step = 1.-self.val
631 if self.converging_val == None:
632 self.__val = self.cur_step
634 self.__val = self.converging_val + self.cur_step
636 def working_point_modification(self,final_cleaning=False,skip_reference_simulation=False):
638 Main method of this class.
639 It tries to reach the target state from the reference state.
640 The step is adapted with change_val depending on the success of the simulations
641 Before a simulation is run, the variables are initialized to
642 Their calculated value if it is a variable whose value should be imposed
643 The value calculated by Dymola in the last simulation that has converged if it is an iteration variable
645 if skip_reference_simulation:
646 self.skip_reference_simulation()
648 if self.ref_success_code == 0 or skip_reference_simulation:
649 while self.nit < self.nit_max and (self.converging_val == None or self.converging_val < 1) and self.success_code not in [2,3] :
651 if self.crit_check() :
653 if self.cur_step < self.min_step_val : #Check after change_val call
655 self.set_dict_inputs(ref=False)
656 logging.debug(".nit : %s\n.converging_val : %s\n.around_simulation.mat : %s\n.around_simulation._source_ : %s" %(self.nit,self.converging_val,self.around_simulation.mat,self.around_simulation.iter_var_values_options['source']))
657 self.fauto_simu(ref=False,final_cleaning=final_cleaning)
659 if not(self.nit < self.nit_max):
660 logging.info('Time = '+get_cur_time()+' \t'+'STOP: maximum number of iterations reached.')
661 if self.converging_val != None and not(self.converging_val < 1):
662 logging.info('Time = '+get_cur_time()+' \t'+'FINISHED: Target values reached.')
663 if self.success_code in [2,3]:
664 logging.info('Time = '+get_cur_time()+' \t'+'STOP: Simulation initialization has succeeded, but simulation has failed or was not given enough time to converge')
666 def crit_check(self):
668 Check whether the step-size and the max-number-of-iterations criteria has been met
671 if self.success_code == 1 : #Check criteria only if the previous iteration did not initialize
672 if (self.cur_step < self.min_step_val) : #Minimum step size criterium
673 logging.info('Time = '+get_cur_time()+' \t'+'STOP: The step ('+str(self.cur_step)+') has crossed the minimum allowed value: '+str(self.min_step_val))
675 elif ( (self.val + self.cur_step*(self.nit_max - self.nit - 1)) < 1. ) : #Max number of step criterium (with "forecast")
676 logging.info('Time = '+get_cur_time()+' \t'+'STOP: Impossible to success before hitting the number of iterations limit: '+str(self.nit_max))
677 logging.info('Time = '+get_cur_time()+' \t'+'STOP: Completed iterations: '+str(self.nit)+'; next value: '+str(self.val)+'; current step: '+str(self.cur_step))
681 def skip_reference_simulation(self):
682 logging.info('Time = '+get_cur_time()+' \t'+'Reference simulation is not launched. It is safer to choose skip_reference_simulation=False')
683 self.__around_simulation = copy(self.around_simulation0)
685 ###############################################################################
686 # CLASS Dict_Var_To_Fix #
687 ###############################################################################
689 class Dict_Var_To_Fix(object):
691 This class makes it easier to create the attribute dict_var_to_fix of the objects
692 of class Working_Point_Modification in the case there are only a ref value and
695 def __init__(self,option,dict_var_to_fix=None,dict_auto_var_to_fix=None):
696 if dict_var_to_fix == None:
699 self.__option = option
700 self.__dict_var_to_fix = dict_var_to_fix
701 self.__dict_auto_var_to_fix = dict_auto_var_to_fix
707 Available values for option are
708 'manual' -> dict_var_to_fix is directly used
709 'automatic' -> dict_var_to_fix is built from dict_auto_var_to_fix
714 def dict_var_to_fix(self):
717 Dict_var_to_fix is a dictionary which associates
718 key name of the variable whose value must be fixed
719 value Function which returns the value of variable key when val is x
720 0 -> reference value of key
721 1 -> target value of key
723 return self.__dict_var_to_fix
726 def dict_auto_var_to_fix(self):
729 This dictionary is used if option == 'automatic'
731 key name of the variable whose value must be fixed
732 value (ref_val,target_val) : A 2-tuple composed of the value of
733 the variable key in the reference state and the target value
736 return self.__dict_auto_var_to_fix
738 def set_dict_var_to_fix(self):
740 Set the value of attribute dict_var_to_fix when option is 'automatic'.
741 If option is 'manual', attribute dict_var_to_fix already has a value.
743 Create a function for each variable, which returns
744 function(0) -> ref_val
745 function(1) -> target_val
747 if self.option == 'automatic':
748 for var in self.dict_auto_var_to_fix:
749 ref_val = self.dict_auto_var_to_fix[var][0]
750 target_val = self.dict_auto_var_to_fix[var][1]
751 self.__dict_var_to_fix[var] = self.f_creator(ref_val,target_val)
753 def f_creator(self,ref_val,target_val):
755 Return the linear function returning
756 ref_val if it is given 0
757 target_val if ti is given 1
758 This function is used in method set_dict_var_to_fix of class Dict_Var_To_Fix
759 when option == 'automatic'
763 exit("x has to be between 0 and 1")
765 return ref_val+x*(target_val-ref_val)
768 if __name__ == '__main__':
769 print('\n AUTODIAGNOSTIC\n ==============\n')