def simulate_fmu(filename, validate=True, start_time=None, stop_time=None, solver='CVode', step_size=None, relative_tolerance=None, output_interval=None, record_events=True, fmi_type=None, use_source_code=False, start_values={}, apply_default_start_values=False, input=None, output=None, timeout=None, debug_logging=False, logger=None, fmi_call_logger=None, step_finished=None, model_description=None): """ Simulate an FMU Parameters: filename filename of the FMU or directory with extracted FMU validate validate the FMU start_time simulation start time (None: use default experiment or 0 if not defined) stop_time simulation stop time (None: use default experiment or start_time + 1 if not defined) solver solver to use for model exchange ('Euler' or 'CVode') step_size step size for the 'Euler' solver relative_tolerance relative tolerance for the 'CVode' solver output_interval interval for sampling the output record_events record outputs at events (model exchange only) fmi_type FMI type for the simulation (None: determine from FMU) use_source_code compile the shared library (requires C sources) start_values dictionary of variable name -> value pairs apply_default_start_values apply the start values from the model description input a structured numpy array that contains the input (see :class:`Input`) output list of variables to record (None: record outputs) timeout timeout for the simulation debug_logging enable the FMU's debug logging fmi_call_logger callback function to log FMI calls logger callback function passed to the FMU (experimental) step_finished callback to interact with the simulation (experimental) model_description the previously loaded model description (experimental) Returns: result a structured numpy array that contains the result """ from fmpy import supported_platforms from fmpy.model_description import read_model_description if not use_source_code and platform not in supported_platforms(filename): raise Exception( "The current platform (%s) is not supported by the FMU." % platform) if model_description is None: model_description = read_model_description(filename, validate=validate) else: model_description = model_description if fmi_type is None: # determine the FMI type automatically fmi_type = 'CoSimulation' if model_description.coSimulation is not None else 'ModelExchange' if fmi_type not in ['ModelExchange', 'CoSimulation']: raise Exception( 'fmi_type must be one of "ModelExchange" or "CoSimulation"') experiment = model_description.defaultExperiment if start_time is None: if experiment is not None and experiment.startTime is not None: start_time = experiment.startTime else: start_time = 0.0 if stop_time is None: if experiment is not None and experiment.stopTime is not None: stop_time = experiment.stopTime else: stop_time = start_time + 1.0 if relative_tolerance is None: if experiment is not None and experiment.tolerance is not None: relative_tolerance = experiment.tolerance else: relative_tolerance = 1e-5 if step_size is None: total_time = stop_time - start_time step_size = 10**(np.round(np.log10(total_time)) - 3) if os.path.isfile(os.path.join(filename, 'modelDescription.xml')): unzipdir = filename tempdir = None else: tempdir = extract(filename) unzipdir = tempdir # common FMU constructor arguments fmu_args = { 'guid': model_description.guid, 'unzipDirectory': unzipdir, 'instanceName': None, 'fmiCallLogger': fmi_call_logger } if use_source_code: from .util import compile_dll # compile the shared library from the C sources fmu_args['libraryPath'] = compile_dll( model_description=model_description, sources_dir=os.path.join(unzipdir, 'sources')) if logger is None: logger = printLogMessage if model_description.fmiVersion == '1.0': callbacks = fmi1CallbackFunctions() callbacks.logger = fmi1CallbackLoggerTYPE(logger) callbacks.allocateMemory = fmi1CallbackAllocateMemoryTYPE( allocateMemory) callbacks.freeMemory = fmi1CallbackFreeMemoryTYPE(freeMemory) callbacks.stepFinished = None else: callbacks = fmi2CallbackFunctions() callbacks.logger = fmi2CallbackLoggerTYPE(logger) callbacks.allocateMemory = fmi2CallbackAllocateMemoryTYPE( allocateMemory) callbacks.freeMemory = fmi2CallbackFreeMemoryTYPE(freeMemory) # simulate_fmu the FMU if fmi_type == 'ModelExchange' and model_description.modelExchange is not None: fmu_args[ 'modelIdentifier'] = model_description.modelExchange.modelIdentifier result = simulateME(model_description, fmu_args, start_time, stop_time, solver, step_size, relative_tolerance, start_values, apply_default_start_values, input, output, output_interval, record_events, timeout, callbacks, debug_logging, step_finished) elif fmi_type == 'CoSimulation' and model_description.coSimulation is not None: fmu_args[ 'modelIdentifier'] = model_description.coSimulation.modelIdentifier result = simulateCS(model_description, fmu_args, start_time, stop_time, start_values, apply_default_start_values, input, output, output_interval, timeout, callbacks, debug_logging, step_finished) else: raise Exception('FMI type "%s" is not supported by the FMU' % fmi_type) # clean up if tempdir is not None: shutil.rmtree(tempdir) return result
def main(): import argparse import fmpy import sys import os description = f"""\ Validate and simulate Functional Mock-up Units (FMUs) Get information about an FMU: fmpy info Rectifier.fmu Simulate an FMU: fmpy simulate Rectifier.fmu --show-plot Compile a source code FMU: fmpy compile Rectifier.fmu Create a Jupyter Notebook fmpy create-jupyter-notebook Rectifier.fmu About FMPy FMPy version: {fmpy.__version__} FMI platform: {fmpy.platform} Installation path: {os.path.dirname(__file__)} Python interpreter: {sys.executable} Python version: {sys.version} """ parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=description) parser.add_argument('command', choices=[ 'info', 'validate', 'simulate', 'compile', 'add-cswrapper', 'add-remoting', 'create-cmake-project', 'create-jupyter-notebook' ], help="Command to execute") parser.add_argument('fmu_filename', help="filename of the FMU") parser.add_argument('--validate', action='store_true', help="validate the FMU") parser.add_argument('--start-time', type=float, help="start time for the simulation") parser.add_argument('--stop-time', type=float, help="stop time for the simulation") parser.add_argument('--solver', choices=['Euler', 'CVode'], default='CVode', help="solver to use for Model Exchange") parser.add_argument('--step-size', type=float, help="step size for fixed-step solvers") parser.add_argument( '--relative-tolerance', type=float, help= "relative tolerance for the 'CVode' solver and FMI 2.0 co-simulation FMUs" ) parser.add_argument( '--dont-record-events', action='store_true', help="dont't record outputs at events (model exchange only)") parser.add_argument('--start-values', nargs='+', help="name-value pairs of start values") parser.add_argument( '--apply-default-start-values', action='store_true', help="apply the start values from the model description") parser.add_argument('--output-interval', type=float, help="interval for sampling the output") parser.add_argument('--input-file', help="CSV file to use as input") parser.add_argument('--output-variables', nargs='+', help="Variables to record") parser.add_argument('--output-file', help="CSV to store the results") parser.add_argument('--timeout', type=float, help="max. time to wait for the simulation to finish") parser.add_argument('--debug-logging', action='store_true', help="enable the FMU's debug logging") parser.add_argument('--visible', action='store_true', help="enable interactive mode") parser.add_argument('--fmi-logging', action='store_true', help="enable FMI logging") parser.add_argument('--show-plot', action='store_true', help="plot the results") parser.add_argument('--cmake-project-dir', help="Directory for the CMake project") parser.add_argument('--target-platform', help="The target platform to compile the binary for") parser.add_argument('--compiler-options', help="Options used when compiling the platform binary") args = parser.parse_args() if args.command == 'info': from fmpy import dump dump(args.fmu_filename) elif args.command == 'validate': import sys from fmpy.validation import validate_fmu problems = validate_fmu(args.fmu_filename) if len(problems) == 0: print('No problems found.') else: print('%d problems were found:' % len(problems)) for message in problems: print() print(message) sys.exit(len(problems)) elif args.command == 'compile': from fmpy.util import compile_platform_binary compile_platform_binary(filename=args.fmu_filename, target_platform=args.target_platform, compiler_options=args.compiler_options) elif args.command == 'add-cswrapper': from fmpy.cswrapper import add_cswrapper add_cswrapper(args.fmu_filename) elif args.command == 'add-remoting': from fmpy.util import add_remoting from fmpy import supported_platforms platforms = supported_platforms(args.fmu_filename) if 'win32' in platforms and 'win64' not in platforms: add_remoting(args.fmu_filename, 'win64', 'win32') elif 'win64' in platforms and 'linux64' not in platforms: add_remoting(args.fmu_filename, 'linux64', 'win64') else: print("Failed to add remoting binaries.") elif args.command == 'create-cmake-project': import os from fmpy.util import create_cmake_project project_dir = args.cmake_project_dir if project_dir is None: project_dir = os.path.basename(args.fmu_filename) project_dir, _ = os.path.splitext(project_dir) print("Creating CMake project in %s" % os.path.abspath(project_dir)) create_cmake_project(args.fmu_filename, project_dir) elif args.command == 'create-jupyter-notebook': from fmpy.util import create_jupyter_notebook create_jupyter_notebook(args.fmu_filename) elif args.command == 'simulate': from fmpy import simulate_fmu from fmpy.util import read_csv, write_csv, plot_result if args.start_values: if len(args.start_values) % 2 != 0: raise Exception("Start values must be name-value pairs.") start_values = { k: v for k, v in zip(args.start_values[::2], args.start_values[1::2]) } else: start_values = {} input = read_csv(args.input_file) if args.input_file else None if args.fmi_logging: fmi_call_logger = lambda s: print('[FMI] ' + s) else: fmi_call_logger = None result = simulate_fmu( args.fmu_filename, validate=args.validate, start_time=args.start_time, stop_time=args.stop_time, solver=args.solver, step_size=args.step_size, relative_tolerance=args.relative_tolerance, output_interval=args.output_interval, record_events=not args.dont_record_events, fmi_type=None, start_values=start_values, apply_default_start_values=args.apply_default_start_values, input=input, output=args.output_variables, timeout=args.timeout, debug_logging=args.debug_logging, visible=args.visible, fmi_call_logger=fmi_call_logger) if args.output_file: write_csv(filename=args.output_file, result=result) if args.show_plot: plot_result(result=result, window_title=args.fmu_filename)
def simulate_fmu(filename, validate=True, start_time=None, stop_time=None, solver='CVode', step_size=None, relative_tolerance=None, output_interval=None, record_events=True, fmi_type=None, start_values={}, apply_default_start_values=False, input=None, output=None, timeout=None, debug_logging=False, visible=False, logger=None, fmi_call_logger=None, step_finished=None, model_description=None, fmu_instance=None): """ Simulate an FMU Parameters: filename filename of the FMU or directory with extracted FMU validate validate the FMU start_time simulation start time (None: use default experiment or 0 if not defined) stop_time simulation stop time (None: use default experiment or start_time + 1 if not defined) solver solver to use for model exchange ('Euler' or 'CVode') step_size step size for the 'Euler' solver relative_tolerance relative tolerance for the 'CVode' solver and FMI 2.0 co-simulation FMUs output_interval interval for sampling the output record_events record outputs at events (model exchange only) fmi_type FMI type for the simulation (None: determine from FMU) start_values dictionary of variable name -> value pairs apply_default_start_values apply the start values from the model description input a structured numpy array that contains the input (see :class:`Input`) output list of variables to record (None: record outputs) timeout timeout for the simulation debug_logging enable the FMU's debug logging visible interactive mode (True) or batch mode (False) fmi_call_logger callback function to log FMI calls logger callback function passed to the FMU (experimental) step_finished callback to interact with the simulation (experimental) model_description the previously loaded model description (experimental) fmu_instance the previously instantiated FMU (experimental) Returns: result a structured numpy array that contains the result """ from fmpy import supported_platforms from fmpy.model_description import read_model_description platforms = supported_platforms(filename) # use 32-bit DLL remoting use_remoting = platform == 'win64' and 'win64' not in platforms and 'win32' in platforms if fmu_instance is None and platform not in platforms and not use_remoting: raise Exception("The current platform (%s) is not supported by the FMU." % platform) if model_description is None: model_description = read_model_description(filename, validate=validate) else: model_description = model_description if fmi_type is None: if fmu_instance is not None: # determine FMI type from the FMU instance fmi_type = 'CoSimulation' if type(fmu_instance) in [FMU1Slave, FMU2Slave, fmi3.FMU3Slave] else 'ModelExchange' else: # determine the FMI type automatically fmi_type = 'CoSimulation' if model_description.coSimulation is not None else 'ModelExchange' if fmi_type not in ['ModelExchange', 'CoSimulation']: raise Exception('fmi_type must be one of "ModelExchange" or "CoSimulation"') experiment = model_description.defaultExperiment if start_time is None: if experiment is not None and experiment.startTime is not None: start_time = experiment.startTime else: start_time = 0.0 if stop_time is None: if experiment is not None and experiment.stopTime is not None: stop_time = experiment.stopTime else: stop_time = start_time + 1.0 if relative_tolerance is None and experiment is not None: relative_tolerance = experiment.tolerance if step_size is None: total_time = stop_time - start_time step_size = 10 ** (np.round(np.log10(total_time)) - 3) if output_interval is None and fmi_type == 'CoSimulation' and experiment is not None and experiment.stepSize is not None: output_interval = experiment.stepSize while (stop_time - start_time) / output_interval > 1000: output_interval *= 2 if os.path.isfile(os.path.join(filename, 'modelDescription.xml')): unzipdir = filename tempdir = None else: tempdir = extract(filename) unzipdir = tempdir if use_remoting: # start 32-bit server from subprocess import Popen server_path = os.path.dirname(__file__) server_path = os.path.join(server_path, 'remoting', 'server.exe') if fmi_type == 'ModelExchange': model_identifier = model_description.modelExchange.modelIdentifier else: model_identifier = model_description.coSimulation.modelIdentifier dll_path = os.path.join(unzipdir, 'binaries', 'win32', model_identifier + '.dll') server = Popen([server_path, dll_path]) else: server = None if fmu_instance is None: fmu = instantiate_fmu(unzipdir, model_description, fmi_type, visible, debug_logging, logger, fmi_call_logger, use_remoting) else: fmu = fmu_instance # simulate_fmu the FMU if fmi_type == 'ModelExchange': result = simulateME(model_description, fmu, start_time, stop_time, solver, step_size, relative_tolerance, start_values, apply_default_start_values, input, output, output_interval, record_events, timeout, step_finished) elif fmi_type == 'CoSimulation': result = simulateCS(model_description, fmu, start_time, stop_time, relative_tolerance, start_values, apply_default_start_values, input, output, output_interval, timeout, step_finished) if fmu_instance is None: fmu.freeInstance() if server is not None: server.kill() # clean up if tempdir is not None: shutil.rmtree(tempdir, ignore_errors=True) return result
def load(self, filename): if not self.isVisible(): self.show() try: self.modelDescription = md = read_model_description(filename) except Exception as e: QMessageBox.warning(self, "Failed to load FMU", "Failed to load %s. %s" % (filename, e)) return self.filename = filename platforms = supported_platforms(self.filename) self.variables.clear() self.selectedVariables.clear() self.startValues.clear() for v in md.modelVariables: self.variables[v.name] = v if v.causality == 'output': self.selectedVariables.add(v) fmi_types = [] if md.coSimulation: fmi_types.append('Co-Simulation') if md.modelExchange: fmi_types.append('Model Exchange') # toolbar if md.defaultExperiment is not None: if md.defaultExperiment.stopTime is not None: self.stopTimeLineEdit.setText( str(md.defaultExperiment.stopTime)) if md.defaultExperiment.startTime is not None: self.startTimeLineEdit.setText( str(md.defaultExperiment.startTime)) # actions can_compile = md.fmiVersion == '2.0' and 'c-code' in platforms self.ui.actionCompilePlatformBinary.setEnabled(can_compile) self.ui.actionCreateCMakeProject.setEnabled(can_compile) # variables view self.treeModel.setModelDescription(md) self.tableModel.setModelDescription(md) self.treeFilterModel.invalidate() self.tableFilterModel.invalidate() self.ui.treeView.reset() self.ui.tableView.reset() # settings page self.ui.fmiVersionLabel.setText(md.fmiVersion) self.ui.fmiTypeLabel.setText(', '.join(fmi_types)) self.ui.platformsLabel.setText(', '.join(platforms)) self.ui.modelNameLabel.setText(md.modelName) self.ui.descriptionLabel.setText(md.description) self.ui.numberOfContinuousStatesLabel.setText( str(md.numberOfContinuousStates)) self.ui.numberOfEventIndicatorsLabel.setText( str(md.numberOfEventIndicators)) self.ui.numberOfVariablesLabel.setText(str(len(md.modelVariables))) self.ui.generationToolLabel.setText(md.generationTool) self.ui.generationDateAndTimeLabel.setText(md.generationDateAndTime) if md.defaultExperiment is not None and md.defaultExperiment.stepSize is not None: output_interval = float(md.defaultExperiment.stepSize) while output_interval > 1000: output_interval *= 0.5 else: output_interval = float(self.stopTimeLineEdit.text()) / 500 self.ui.outputIntervalLineEdit.setText(str(output_interval)) self.fmiTypeComboBox.clear() self.fmiTypeComboBox.addItems(fmi_types) self.updateSimulationSettings() self.setCurrentPage(self.ui.settingsPage) self.ui.dockWidget.show() self.ui.actionSettings.setEnabled(True) self.ui.actionShowLog.setEnabled(True) self.ui.actionShowResults.setEnabled(False) can_simulate = platform in platforms self.ui.actionSimulate.setEnabled(can_simulate) self.startTimeLineEdit.setEnabled(can_simulate) self.stopTimeLineEdit.setEnabled(can_simulate) self.fmiTypeComboBox.setEnabled(can_simulate and len(fmi_types) > 1) self.ui.settingsGroupBox.setEnabled(can_simulate) settings = QSettings() recent_files = settings.value("recentFiles", defaultValue=[]) recent_files = self.removeDuplicates([filename] + recent_files) # save the 10 most recent files settings.setValue('recentFiles', recent_files[:10]) self.setWindowTitle("%s - FMPy" % os.path.normpath(filename)) self.createGraphics()
def process_fmu(fmu_filename): basename, _ = os.path.splitext(fmu_filename) pickle_filename = basename + '.p' fmu_hash = os.path.basename(basename) try: model_description = read_model_description(fmu_filename, validate=False) except Exception as e: alert = dbc.Alert( [html.I(className='fas fa-times mr-3'), f"Failed to read model description. {e}"], id='alert', color='danger', className='mt-3') with open(pickle_filename, 'wb') as f: pickle.dump([alert], f) return platforms = supported_platforms(fmu_filename) with zipfile.ZipFile(fmu_filename, 'r') as zf: nl = filter(lambda n: not n.endswith('/'), zf.namelist()) fmi_types = [] if model_description.modelExchange: fmi_types.append('Model Exchange') if model_description.coSimulation: fmi_types.append('Co-Simulation') def na(attr): value = getattr(model_description, attr) if value: return value else: return html.Span('n/a', className='text-muted') rows = [ dbc.Row([ dbc.Col(html.Span("FMI Version"), width=4), dbc.Col(html.Span(model_description.fmiVersion), width=8), ], className='py-1'), dbc.Row([ dbc.Col("FMI Type", width=4), dbc.Col(', '.join(fmi_types), width=8), ], className='py-1'), dbc.Row([ dbc.Col("Model Name", width=4), dbc.Col(model_description.modelName, width=8), ], className='py-1'), dbc.Row([ dbc.Col("Platforms", width=4), dbc.Col(', '.join(platforms), width=8), ], className='py-1'), dbc.Row([ dbc.Col(html.Span("Continuous States"), width=4), dbc.Col(html.Span(model_description.numberOfContinuousStates), width=8), ], className='py-1'), dbc.Row([ dbc.Col(html.Span("Event Indicators"), width=4), dbc.Col(html.Span(model_description.numberOfEventIndicators), width=8), ], className='py-1'), dbc.Row([ dbc.Col(html.Span("Model Variables"), width=4), dbc.Col(html.Span(len(model_description.modelVariables)), width=8), ], className='py-1'), dbc.Row([ dbc.Col(html.Span("Generation Date"), width=4), dbc.Col(na('generationDateAndTime'), width=8) ], className='py-1'), dbc.Row([ dbc.Col(html.Span("Generation Tool"), width=4), dbc.Col(na('generationTool'), width=8) ], className='py-1'), dbc.Row([ dbc.Col(html.Span("Description"), width=4), dbc.Col(na('description'), width=8) ], className='py-1'), dbc.Row([ dbc.Col(html.Span("SHA256"), width=4), dbc.Col(html.Span(fmu_hash), width=8), ], className='py-1'), dbc.Row([ dbc.Col(html.Span("File Size"), width=4), dbc.Col(html.Span(f'{os.path.getsize(fmu_filename)} bytes'), width=8), ], className='py-1'), # dbc.Row([ # dbc.Col(html.Span("Contained Files"), width=4), # dbc.Col(html.Pre('\n'.join(nl)), width=8), # ], className='py-1'), ] try: problems = validate_fmu(fmu_filename) except Exception as e: problems = [str(e)] if problems: alert = dbc.Alert( [ html.P( [ html.I(className='fas fa-exclamation-circle mr-3'), f"Validation failed. {len(problems)} {'problem was' if len(problems) == 1 else 'problems were'} found:" ] ), html.Ul( [html.Li(problem) for problem in problems] ) ], id='alert', color='danger', className='mt-3') else: alert = dbc.Alert([html.I(className='fas fa-check mr-3'), "Validation passed. No problems found."], id='alert', color='success', className='mt-3') variables = [] table_header = [ html.Thead(html.Tr([ html.Th("Type"), html.Th("Name"), # html.Th("Variability"), html.Th("Causality"), html.Th("Start", className='text-right'), html.Th("Unit"), html.Th("Description") ])) ] for variable in model_description.modelVariables: unit = variable.unit if unit is None and variable.declaredType is not None: unit = variable.declaredType.unit if variable.type == 'Boolean': color = '#c900c9' elif variable.type == 'Binary': color = '#ab0000' elif variable.type.startswith(('Int', 'Enum')): color = '#c78f00' elif variable.type.startswith(('Real', 'Float')): color = '#0000bf' else: # String color = '#00a608' variables.append( html.Tr( [ html.Td(html.Small(variable.type, style={ 'color': color, 'border': '1px solid ' + color, 'border-radius': '1em', 'padding': '0 0.5em 0 0.5em', # 'font-family': 'Georgia, "Times New Roman", Times, serif' })), html.Td(variable.name), # html.Td(variable.variability), html.Td(variable.causality), html.Td(variable.start, className='text-right'), html.Td(unit), html.Td(variable.description, className='text-muted') ] ) ) table = dbc.Table(table_header + [html.Tbody(variables)], borderless=True, size='sm') tabs = dbc.Tabs( [ dbc.Tab(rows, label="Model Info", className='p-4'), # dbc.Tab(variables, label="Variables", className='p-4'), dbc.Tab(table, label="Variables", className='p-4'), dbc.Tab(html.Pre('\n'.join(nl)), label="Files", className='p-4'), ], id='tabs' ) with open(pickle_filename, 'wb') as f: pickle.dump([alert, tabs], f)
def load(self, filename): import zipfile if not self.isVisible(): self.show() try: self.modelDescription = md = read_model_description(filename) except Exception as e: QMessageBox.warning(self, "Failed to load FMU", "Failed to load %s. %s" % (filename, e)) return # show model.png try: pixmap = QPixmap() # load the model.png with zipfile.ZipFile(filename, 'r') as zf: pixmap.loadFromData(zf.read('model.png'), format='PNG') # show the unscaled version in tooltip buffer = QBuffer() buffer.open(QIODevice.WriteOnly) pixmap.save(buffer, "PNG", quality=100) image = bytes(buffer.data().toBase64()).decode() html = '<img src="data:image/png;base64,{}">'.format(image) self.ui.modelImageLabel.setToolTip(html) # show a scaled preview in "Model Info" pixmap = pixmap.scaled(200, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.ui.modelImageLabel.setPixmap(pixmap) except: self.ui.modelImageLabel.setPixmap(QPixmap()) self.ui.modelImageLabel.setToolTip(None) self.filename = filename platforms = supported_platforms(self.filename) self.variables.clear() self.selectedVariables.clear() self.startValues.clear() for v in md.modelVariables: self.variables[v.name] = v if v.causality == 'output': self.selectedVariables.add(v) fmi_types = [] if md.coSimulation: fmi_types.append('Co-Simulation') if md.modelExchange: fmi_types.append('Model Exchange') # toolbar if md.defaultExperiment is not None: if md.defaultExperiment.stopTime is not None: self.stopTimeLineEdit.setText( str(md.defaultExperiment.stopTime)) # actions can_compile = md.fmiVersion == '2.0' and 'c-code' in platforms self.ui.actionCompilePlatformBinary.setEnabled(can_compile) self.ui.actionCreateCMakeProject.setEnabled(can_compile) can_add_remoting = md.fmiVersion == '2.0' and 'win32' in platforms and 'win64' not in platforms self.ui.actionAddRemoting.setEnabled(can_add_remoting) # variables view self.treeModel.setModelDescription(md) self.tableModel.setModelDescription(md) self.treeFilterModel.invalidate() self.tableFilterModel.invalidate() self.ui.treeView.reset() self.ui.tableView.reset() # settings page self.ui.fmiVersionLabel.setText(md.fmiVersion) self.ui.fmiTypeLabel.setText(', '.join(fmi_types)) self.ui.platformsLabel.setText(', '.join(platforms)) self.ui.modelNameLabel.setText(md.modelName) self.ui.descriptionLabel.setText(md.description) self.ui.numberOfContinuousStatesLabel.setText( str(md.numberOfContinuousStates)) self.ui.numberOfEventIndicatorsLabel.setText( str(md.numberOfEventIndicators)) self.ui.numberOfVariablesLabel.setText(str(len(md.modelVariables))) self.ui.generationToolLabel.setText(md.generationTool) self.ui.generationDateAndTimeLabel.setText(md.generationDateAndTime) if md.defaultExperiment is not None and md.defaultExperiment.stepSize is not None: output_interval = float(md.defaultExperiment.stepSize) while output_interval > 1000: output_interval *= 0.5 else: output_interval = float(self.stopTimeLineEdit.text()) / 500 self.ui.outputIntervalLineEdit.setText(str(output_interval)) self.fmiTypeComboBox.clear() self.fmiTypeComboBox.addItems(fmi_types) self.updateSimulationSettings() self.setCurrentPage(self.ui.settingsPage) self.ui.dockWidget.show() self.ui.actionReload.setEnabled(True) self.ui.actionSettings.setEnabled(True) self.ui.actionShowLog.setEnabled(True) self.ui.actionShowResults.setEnabled(False) can_simulate = platform in platforms or platform == 'win64' and 'win32' in platforms self.ui.actionLoadStartValues.setEnabled(can_simulate) self.ui.actionSimulate.setEnabled(can_simulate) self.stopTimeLineEdit.setEnabled(can_simulate) self.fmiTypeComboBox.setEnabled(can_simulate and len(fmi_types) > 1) self.ui.settingsGroupBox.setEnabled(can_simulate) settings = QSettings() recent_files = settings.value("recentFiles", defaultValue=[]) recent_files = self.removeDuplicates([filename] + recent_files) # save the 10 most recent files settings.setValue('recentFiles', recent_files[:10]) self.setWindowTitle("%s - FMPy" % os.path.normpath(filename)) self.createGraphics()
# check the input file input = None if input_variables: in_path = os.path.join(root, fmu_name + '_in.csv') input, in_csv_cell = check_csv_file(filename=in_path, variables=input_variables) else: in_csv_cell = '<td class="status"><span class="label label-default">n/a</span></td>' # check the reference file ref_path = os.path.join(root, fmu_name + '_ref.csv') reference, ref_csv_cell = check_csv_file(filename=ref_path, variables=output_variables) supported_platforms = fmpy.supported_platforms(fmu_filename) # this will remove any trailing (back)slashes fmus_dir = os.path.normpath(args.fmus_dir) model_path = fmu_filename[len(fmus_dir) + 1:] model_path = os.path.dirname(model_path) fmu_simple_filename = os.path.basename(fmu_filename) model_name, _ = os.path.splitext(fmu_simple_filename) # build the filenames result = None ############## # SIMULATION # ##############
def simulate_fmu(filename, validate: bool = True, start_time: Union[float, str] = None, stop_time: Union[float, str] = None, solver: str = 'CVode', step_size: Union[float, str] = None, relative_tolerance: Union[float, str] = None, output_interval: Union[float, str] = None, record_events: bool = True, fmi_type: str = None, start_values: Dict[str, Any] = {}, apply_default_start_values: bool = False, input: np.ndarray = None, output: Sequence[str] = None, timeout: Union[float, str] = None, debug_logging: bool = False, visible: bool = False, logger: Callable = None, fmi_call_logger: Callable[[str], None] = None, step_finished: Callable[[float, Recorder], bool] = None, model_description: ModelDescription = None, fmu_instance: _FMU = None, set_input_derivatives: bool = False, remote_platform: str = 'auto', early_return_allowed: bool = False, use_event_mode: bool = False, initialize: bool = True, terminate: bool = True, fmu_state: Union[bytes, c_void_p] = None) -> SimulationResult: """ Simulate an FMU Parameters: filename filename of the FMU or directory with extracted FMU validate validate the FMU and start values start_time simulation start time (None: use default experiment or 0 if not defined) stop_time simulation stop time (None: use default experiment or start_time + 1 if not defined) solver solver to use for model exchange ('Euler' or 'CVode') step_size step size for the 'Euler' solver relative_tolerance relative tolerance for the 'CVode' solver and FMI 2.0 co-simulation FMUs output_interval interval for sampling the output record_events record outputs at events (model exchange only) fmi_type FMI type for the simulation (None: determine from FMU) start_values dictionary of variable name -> value pairs apply_default_start_values apply the start values from the model description input a structured numpy array that contains the input (see :class:`Input`) output list of variables to record (None: record outputs) timeout timeout for the simulation debug_logging enable the FMU's debug logging visible interactive mode (True) or batch mode (False) fmi_call_logger callback function to log FMI calls logger callback function passed to the FMU (experimental) step_finished callback to interact with the simulation (experimental) model_description the previously loaded model description (experimental) fmu_instance the previously instantiated FMU (experimental) set_input_derivatives set the input derivatives (FMI 2.0 Co-Simulation only) remote_platform platform to use for remoting server ('auto': determine automatically if current platform is not supported, None: no remoting; experimental) early_return_allowed allow early return in FMI 3.0 Co-Simulation use_event_mode use event mode in FMI 3.0 Co-Simulation if the FMU supports it initialize initialize the FMU terminate terminate the FMU fmu_state the FMU state or serialized FMU state to initialize the FMU Returns: result a structured numpy array that contains the result """ from fmpy import supported_platforms from fmpy.model_description import read_model_description from fmpy.util import can_simulate platforms = supported_platforms(filename) if fmu_instance is None and platform not in platforms and remote_platform is None: raise Exception(f"The current platform ({platform}) is not supported by the FMU.") can_sim, remote_platform = can_simulate(platforms, remote_platform) if not can_sim: raise Exception(f"The FMU cannot be simulated on the current platform ({platform}).") if model_description is None: model_description = read_model_description(filename, validate=validate) if fmi_type is None: if fmu_instance is not None: # determine FMI type from the FMU instance fmi_type = 'CoSimulation' if type(fmu_instance) in [FMU1Slave, FMU2Slave, fmi3.FMU3Slave] else 'ModelExchange' else: # determine the FMI type automatically fmi_type = 'CoSimulation' if model_description.coSimulation is not None else 'ModelExchange' if fmi_type not in ['ModelExchange', 'CoSimulation']: raise Exception('fmi_type must be one of "ModelExchange" or "CoSimulation"') if initialize is False: if fmi_type != 'CoSimulation': raise Exception("If initialize is False, the interface type must be 'CoSimulation'.") if fmu_instance is None and fmu_state is None: raise Exception("If initialize is False, fmu_instance or fmu_state must be provided.") experiment = model_description.defaultExperiment if start_time is None: if experiment is not None and experiment.startTime is not None: start_time = experiment.startTime else: start_time = 0.0 start_time = float(start_time) if stop_time is None: if experiment is not None and experiment.stopTime is not None: stop_time = experiment.stopTime else: stop_time = start_time + 1.0 stop_time = float(stop_time) if relative_tolerance is None and experiment is not None: relative_tolerance = experiment.tolerance if step_size is None: total_time = stop_time - start_time step_size = 10 ** (np.round(np.log10(total_time)) - 3) if output_interval is None and fmi_type == 'CoSimulation': co_simulation = model_description.coSimulation if co_simulation is not None and co_simulation.fixedInternalStepSize is not None: output_interval = float(model_description.coSimulation.fixedInternalStepSize) elif experiment is not None and experiment.stepSize is not None: output_interval = float(experiment.stepSize) if output_interval is not None: while (stop_time - start_time) / output_interval > 1000: output_interval *= 2 if os.path.isfile(os.path.join(filename, 'modelDescription.xml')): unzipdir = filename tempdir = None else: required_paths = ['resources', 'binaries/'] if remote_platform: required_paths.append(os.path.join('binaries', remote_platform)) tempdir = extract(filename, include=None if remote_platform else lambda n: n.startswith(tuple(required_paths))) unzipdir = tempdir if remote_platform: add_remoting(unzipdir, host_platform=platform, remote_platform=remote_platform) if fmu_instance is None: fmu = instantiate_fmu(unzipdir, model_description, fmi_type, visible, debug_logging, logger, fmi_call_logger, None, early_return_allowed, use_event_mode) else: fmu = fmu_instance if fmu_state is not None: if model_description.fmiVersion == '2.0' or model_description.fmiVersion.startswith('3.0'): if isinstance(fmu_state, bytes): fmu_state = fmu.deserializeFMUState(fmu_state) fmu.setFMUState(fmu_state) fmu.freeFMUState(fmu_state) else: fmu.setFMUState(fmu_state) else: raise Exception(f"Setting the FMU state is not supported for FMI version {model_description.fmiVersion}.") initialize = False # simulate_fmu the FMU if fmi_type == 'ModelExchange': result = simulateME(model_description, fmu, start_time, stop_time, solver, step_size, relative_tolerance, start_values, apply_default_start_values, input, output, output_interval, record_events, timeout, step_finished, validate) elif fmi_type == 'CoSimulation': result = simulateCS(model_description, fmu, start_time, stop_time, relative_tolerance, start_values, apply_default_start_values, input, output, output_interval, timeout, step_finished, set_input_derivatives, use_event_mode, early_return_allowed, validate, initialize, terminate) if fmu_instance is None: fmu.freeInstance() # clean up if tempdir is not None: shutil.rmtree(tempdir, ignore_errors=True) return result