diff --git a/src/common/algorithm_drivers.py b/src/common/algorithm_drivers.py index 684b68ed..f3ce2659 100644 --- a/src/common/algorithm_drivers.py +++ b/src/common/algorithm_drivers.py @@ -39,7 +39,7 @@ def get_result(self): pass @classmethod - def get_default_options(self): + def get_default_options(cls): pass class ResultBase(object): diff --git a/src/common/core.py b/src/common/core.py index 948a3c8d..05213660 100644 --- a/src/common/core.py +++ b/src/common/core.py @@ -385,6 +385,7 @@ def unzip_unit(archive, path='.'): # extract all into temp_dir archive.extractall(path=tmpdir) + archive.close() return tmpdir @@ -458,10 +459,7 @@ def list_to_string(item_list): item_list = [1, 2, 3] return value: '1;2;3' """ - ret_str = '' - for l in item_list: - ret_str =ret_str+str(l)+os.pathsep - return ret_str + return os.pathsep.join(str(l) for l in item_list) class Trajectory: """ diff --git a/src/common/diagnostics.py b/src/common/diagnostics.py index 0657191e..04457cbe 100644 --- a/src/common/diagnostics.py +++ b/src/common/diagnostics.py @@ -243,7 +243,7 @@ def setup_diagnostics_variables(model, start_time, options, solver_options): if (rtol is None) or (atol is None): rtol, atol = model.get_tolerances() - # is atol is scalar, convert to list + # if atol is scalar, convert to list if isinstance(atol, numbers.Number): atol = [atol]*len(states_list) # atol is "pseudoscalar", array/list with single entry; diff --git a/src/common/io.py b/src/common/io.py index 304b99c7..9f80336c 100644 --- a/src/common/io.py +++ b/src/common/io.py @@ -401,28 +401,32 @@ def __init__(self, filename, delimiter=";"): fid = filename fid.seek(0, 0) # need to start reading from beginning - if delimiter == ";": - name = fid.readline().strip().split(delimiter) - elif delimiter == ",": - name = [s[1:-1] for s in re.findall('".+?"', fid.readline().strip())] - else: - raise JIOError('Unsupported separator.') - self._name = name + try: + if delimiter == ";": + name = fid.readline().strip().split(delimiter) + elif delimiter == ",": + name = [s[1:-1] for s in re.findall('".+?"', fid.readline().strip())] + else: + raise JIOError('Unsupported separator.') + self._name = name - self.data_matrix = {} - for i,n in enumerate(name): - self.data_matrix[n] = i + self.data_matrix = {} + for i,n in enumerate(name): + self.data_matrix[n] = i - data = [] - while True: - row = fid.readline().strip().split(delimiter) + data = [] + while True: + row = fid.readline().strip().split(delimiter) - if row[-1] == "" or row[-1] == "\n": - break + if row[-1] == "" or row[-1] == "\n": + break - data.append([float(d) for d in row]) + data.append([float(d) for d in row]) - self.data = np.array(data) + self.data = np.array(data) + finally: + if isinstance(filename, (str, Path)): + fid.close() def get_variable_names(self) -> list[str]: return list(self.data_matrix.keys()) @@ -543,6 +547,7 @@ def write_header(self, file_name='', parameters=None): # Open file f = codecs.open(file_name,'w','utf-8') self._file_open = True + self._file = f # Write header f.write('#1\n') @@ -789,7 +794,6 @@ def write_header(self, file_name='', parameters=None): # f.write('%s,%d)\n' % (' '*14, self._nvariables)) - self._file = f self._data_order = valueref_of_continuous_states def write_point(self, data=None, parameter_data=[]): @@ -1010,71 +1014,75 @@ def __init__(self,fname): fid = fname fid.seek(0,0) # Needs to start from beginning of file - # Read Aclass section - nLines = self._find_phrase(fid, 'char Aclass') + try: + # Read Aclass section + nLines = self._find_phrase(fid, 'char Aclass') - nLines = int(nLines[0]) - self.Aclass = [fid.readline().strip() for i in range(nLines)] + nLines = int(nLines[0]) + self.Aclass = [fid.readline().strip() for i in range(nLines)] - # Read name section - nLines = self._find_phrase(fid, 'char name') + # Read name section + nLines = self._find_phrase(fid, 'char name') - nLines = int(nLines[0]) - self._name = [fid.readline().strip().replace(" ","") for i in range(nLines)] - self.name_lookup = {key:ind for ind,key in enumerate(self._name)} + nLines = int(nLines[0]) + self._name = [fid.readline().strip().replace(" ","") for i in range(nLines)] + self.name_lookup = {key:ind for ind,key in enumerate(self._name)} - # Read description section - nLines = self._find_phrase(fid, 'char description') + # Read description section + nLines = self._find_phrase(fid, 'char description') - nLines = int(nLines[0]) - self.description = [fid.readline().strip() for i in range(nLines)] + nLines = int(nLines[0]) + self.description = [fid.readline().strip() for i in range(nLines)] - # Read dataInfo section - nLines = self._find_phrase(fid, 'int dataInfo') + # Read dataInfo section + nLines = self._find_phrase(fid, 'int dataInfo') - nCols = nLines[2].partition(')') - nLines = int(nLines[0]) - nCols = int(nCols[0]) + nCols = nLines[2].partition(')') + nLines = int(nLines[0]) + nCols = int(nCols[0]) - self.dataInfo = np.array([list(map(int,fid.readline().split()[0:nCols])) for i in range(nLines)]) + self.dataInfo = np.array([list(map(int,fid.readline().split()[0:nCols])) for i in range(nLines)]) - # Find out how many data matrices there are - if len(self._name) == 1: # Only time - nData = 2 - else: - nData = max(self.dataInfo[:,0]) + # Find out how many data matrices there are + if len(self._name) == 1: # Only time + nData = 2 + else: + nData = max(self.dataInfo[:,0]) - self.data = [] - for i in range(0,nData): - line = fid.readline() - tmp = line.partition(' ') - while tmp[0]!='float' and tmp[0]!='double' and line!='': + self.data = [] + for i in range(0,nData): line = fid.readline() tmp = line.partition(' ') - if line=='': - raise JIOError('The result does not seem to be of a supported format.') - tmp = tmp[2].partition('(') - nLines = tmp[2].partition(',') - nCols = nLines[2].partition(')') - nLines = int(nLines[0]) - nCols = int(nCols[0]) - data = [] - for i in range(0,nLines): - info = [] - while len(info) < nCols and line != '': + while tmp[0]!='float' and tmp[0]!='double' and line!='': line = fid.readline() - info.extend(line.split()) - try: - data.append(list(map(float,info[0:nCols]))) - except ValueError: # Handle 1.#INF's and such - data.append(list(map(robust_float,info[0:nCols]))) - if len(info) == 0 and i < nLines-1: - raise JIOError("Inconsistent number of lines in the result data.") - del(info) - self.data.append(np.array(data)) - - if len(self.data) == 0: - raise JIOError('Could not find any variable data in the result file.') + tmp = line.partition(' ') + if line=='': + raise JIOError('The result does not seem to be of a supported format.') + tmp = tmp[2].partition('(') + nLines = tmp[2].partition(',') + nCols = nLines[2].partition(')') + nLines = int(nLines[0]) + nCols = int(nCols[0]) + data = [] + for i in range(0,nLines): + info = [] + while len(info) < nCols and line != '': + line = fid.readline() + info.extend(line.split()) + try: + data.append(list(map(float,info[0:nCols]))) + except ValueError: # Handle 1.#INF's and such + data.append(list(map(robust_float,info[0:nCols]))) + if len(info) == 0 and i < nLines-1: + raise JIOError("Inconsistent number of lines in the result data.") + del(info) + self.data.append(np.array(data)) + + if len(self.data) == 0: + raise JIOError('Could not find any variable data in the result file.') + finally: + if isinstance(fname, (str, Path)): + fid.close() def _find_phrase(self,fid, phrase): line = fid.readline() diff --git a/src/common/log/__init__.py b/src/common/log/__init__.py index d590ea08..6ed3c7f9 100644 --- a/src/common/log/__init__.py +++ b/src/common/log/__init__.py @@ -19,7 +19,7 @@ The log analysis toolkit. """ -from pyfmi.common.log.parser import parse_xml_log, parse_xml_log, extract_xml_log, parse_fmu_xml_log +from pyfmi.common.log.parser import parse_xml_log, extract_xml_log, parse_fmu_xml_log from pyfmi.common.log.prettyprinter import prettyprint_to_file __all__=['parser', 'tree', 'prettyprinter'] diff --git a/src/common/plotting/plot_gui.py b/src/common/plotting/plot_gui.py index 8f1f422e..c7de910c 100644 --- a/src/common/plotting/plot_gui.py +++ b/src/common/plotting/plot_gui.py @@ -1512,7 +1512,7 @@ def DrawRectZoom(self, drawNew=True): if y1 > y0: y0, y1 = y1, y0 - if x1 < y0: + if x1 < x0: x0, x1 = x1, x0 w = x1 - x0 diff --git a/src/pyfmi/__init__.py b/src/pyfmi/__init__.py index aafb79f9..19ee8c41 100644 --- a/src/pyfmi/__init__.py +++ b/src/pyfmi/__init__.py @@ -91,7 +91,6 @@ def check_packages(): error_packages=[] warning_packages=[] - fp = None for package in packages: try: vers="--" @@ -113,9 +112,6 @@ def check_packages(): sys.stdout.write("%s %s %s " % (package.ljust(le,'.'), vers.ljust(le_short), "Package missing - Error issued, see details below.".ljust(le_short))) error_packages.append(package) pass - finally: - if fp: - fp.close() sys.stdout.write("\n") sys.stdout.flush() time.sleep(0.25) diff --git a/src/pyfmi/examples/fmi_bouncing_ball_native.py b/src/pyfmi/examples/fmi_bouncing_ball_native.py index a021bde4..9e5004f8 100644 --- a/src/pyfmi/examples/fmi_bouncing_ball_native.py +++ b/src/pyfmi/examples/fmi_bouncing_ball_native.py @@ -124,6 +124,7 @@ def run_demo(with_plots=True): #Get new nominal values. if eInfo.stateValueReferencesChanged: + rtol = 1e-6 atol = 0.01*rtol*bouncing_fmu.nominal_continuous_states #Check for new time event diff --git a/src/pyfmi/fmi_algorithm_drivers.py b/src/pyfmi/fmi_algorithm_drivers.py index a0fb1ae6..6b04d18a 100644 --- a/src/pyfmi/fmi_algorithm_drivers.py +++ b/src/pyfmi/fmi_algorithm_drivers.py @@ -392,44 +392,48 @@ def __init__(self, else: self.result_handler.simulation_start() - self.timings["initializing_result"] = timer() - time_start + time_res_init - - # Sensitivities? - if self.options["sensitivities"]: - if self.model.get_generation_tool() != "JModelica.org" and \ - self.model.get_generation_tool() != "Optimica Compiler Toolkit": - if isinstance(self.model, FMUModelME2): - for var in self.options["sensitivities"]: - causality = self.model.get_variable_causality(var) - if causality != FMI2_INPUT: - raise FMUException("The sensitivity parameter is not specified as an input which is required.") - else: - raise FMUException("Sensitivity calculations only possible with JModelica.org generated FMUs") - - if self.options["solver"] != "CVode": - raise FMUException("Sensitivity simulations currently only supported using the solver CVode.") - - # Checks to see if all the sensitivities are inside the model - # else there will be an exception - self.model.get(self.options["sensitivities"]) - - self.probl = get_fmi_ode_problem( - model = self.model, - result_file_name = self.result_file_name, - with_jacobian = self.with_jacobian, - start_time = self.start_time, - logging = self.options["logging"], - result_handler = self.result_handler, - input_traj = input_traj, - number_of_diagnostics_variables = number_of_diagnostics_variables, - sensitivities = self.options["sensitivities"], - extra_equations = self.options["extra_equations"], - synchronize_simulation = self.options["synchronize_simulation"] - ) - - # instantiate solver and set options - self.simulator = self.solver(self.probl) - self._set_solver_options() + try: + self.timings["initializing_result"] = timer() - time_start + time_res_init + + # Sensitivities? + if self.options["sensitivities"]: + if self.model.get_generation_tool() != "JModelica.org" and \ + self.model.get_generation_tool() != "Optimica Compiler Toolkit": + if isinstance(self.model, FMUModelME2): + for var in self.options["sensitivities"]: + causality = self.model.get_variable_causality(var) + if causality != FMI2_INPUT: + raise FMUException("The sensitivity parameter is not specified as an input which is required.") + else: + raise FMUException("Sensitivity calculations only possible with JModelica.org generated FMUs") + + if self.options["solver"] != "CVode": + raise FMUException("Sensitivity simulations currently only supported using the solver CVode.") + + # Checks to see if all the sensitivities are inside the model + # else there will be an exception + self.model.get(self.options["sensitivities"]) + + self.probl = get_fmi_ode_problem( + model = self.model, + result_file_name = self.result_file_name, + with_jacobian = self.with_jacobian, + start_time = self.start_time, + logging = self.options["logging"], + result_handler = self.result_handler, + input_traj = input_traj, + number_of_diagnostics_variables = number_of_diagnostics_variables, + sensitivities = self.options["sensitivities"], + extra_equations = self.options["extra_equations"], + synchronize_simulation = self.options["synchronize_simulation"] + ) + + # instantiate solver and set options + self.simulator = self.solver(self.probl) + self._set_solver_options() + except: + self.result_handler.simulation_end() + raise def _set_options(self): """ @@ -1031,54 +1035,55 @@ def solve(self): #Start of simulation, start the clock time_start = timer() - for step, t in enumerate(grid): - if self._synchronize_factor > 0: - under_run = t/self._synchronize_factor - (timer()-time_start) - if under_run > 0: - time.sleep(under_run) + try: + for step, t in enumerate(grid): + if self._synchronize_factor > 0: + under_run = t/self._synchronize_factor - (timer()-time_start) + if under_run > 0: + time.sleep(under_run) - status = self.model.do_step(t,h) - self.status = status + status = self.model.do_step(t,h) + self.status = status - if status != 0: + if status != 0: - if status == FMI_ERROR: - result_handler.simulation_end() - raise FMUException("The simulation failed. See the log for more information. Return flag %d."%status) + if status == FMI_ERROR: + raise FMUException("The simulation failed. See the log for more information. Return flag %d."%status) - elif status == FMI_DISCARD and isinstance(self.model, (FMUModelCS1, FMUModelCS2)): + elif status == FMI_DISCARD and isinstance(self.model, (FMUModelCS1, FMUModelCS2)): - try: - if isinstance(self.model, FMUModelCS1): - last_time = self.model.get_real_status(FMI1_LAST_SUCCESSFUL_TIME) - else: - last_time = self.model.get_real_status(FMI2_LAST_SUCCESSFUL_TIME) - if last_time > t: #Solver succeeded in taken a step a little further than the last time - self.model.time = last_time - final_time = last_time - - start_time_point = timer() - result_handler.integration_point() - self.timings["storing_result"] += timer() - start_time_point - except FMUException: - pass - break - #result_handler.simulation_end() - #raise Exception("The simulation failed. See the log for more information. Return flag %d"%status) - - final_time = t+h - - start_time_point = timer() - # down-sampling of result; step starts at 0 - if ((step + 1) % self.result_downsampling_factor == 0) or ((step + 1) == self.ncp): - self.result_handler.integration_point() - self.timings["storing_result"] += timer() - start_time_point - - if self.options["time_limit"] and (timer() - time_start) > self.options["time_limit"]: - raise TimeLimitExceeded("The time limit was exceeded at integration time %.8E."%final_time) - - if self.input_traj is not None: - self.model.set(self.input_traj[0], self.input_traj[1].eval(t+h)[0,:]) + try: + if isinstance(self.model, FMUModelCS1): + last_time = self.model.get_real_status(FMI1_LAST_SUCCESSFUL_TIME) + else: + last_time = self.model.get_real_status(FMI2_LAST_SUCCESSFUL_TIME) + if last_time > t: #Solver succeeded in taken a step a little further than the last time + self.model.time = last_time + final_time = last_time + + start_time_point = timer() + result_handler.integration_point() + self.timings["storing_result"] += timer() - start_time_point + except FMUException: + pass + break + + final_time = t+h + + start_time_point = timer() + # down-sampling of result; step starts at 0 + if ((step + 1) % self.result_downsampling_factor == 0) or ((step + 1) == self.ncp): + self.result_handler.integration_point() + self.timings["storing_result"] += timer() - start_time_point + + if self.options["time_limit"] and (timer() - time_start) > self.options["time_limit"]: + raise TimeLimitExceeded("The time limit was exceeded at integration time %.8E."%final_time) + + if self.input_traj is not None: + self.model.set(self.input_traj[0], self.input_traj[1].eval(t+h)[0,:]) + except Exception: + result_handler.simulation_end() + raise #End of simulation, stop the clock time_stop = timer()