def test_fmi3(self): print('FMI 3.0') build_dir = os.path.join(test_fmus_dir, 'fmi3') if not os.path.exists(build_dir): os.makedirs(build_dir) subprocess.call(['cmake', '-G', generator, '-DFMI_VERSION=3', '..'], cwd=build_dir) subprocess.call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir) # run examples examples = [ 'cs_early_return', 'cs_event_mode', 'cs_intermediate_update', 'BouncingBall_cs', 'BouncingBall_me', 'import_shared_library', 'import_static_library', 'jacobian', 'scs_synchronous', 'Stair_cs', 'Stair_me' ] is_windows = os.name == 'nt' if is_windows: examples.append('scs_threaded') # runs only on Windows for example in examples: print("Running %s example..." % example) filename = os.path.join(build_dir, 'temp', example) subprocess.check_call(filename, cwd=os.path.join(build_dir, 'temp')) models = [ 'BouncingBall', 'Dahlquist', 'Feedthrough', 'Resource', 'Stair', 'VanDerPol' ] self.validate(build_dir, models=models) self.validate(build_dir, models=models, compile=True) copy_to_cross_check(build_dir=build_dir, model_names=models, fmi_version='3.0', fmi_types=['cs', 'me']) copy_to_cross_check(build_dir=build_dir, model_names=['Clocks'], fmi_version='3.0', fmi_types=['se']) for model in ['Clocks', 'LinearTransform']: problems = validate_fmu( filename=os.path.join(build_dir, 'dist', model + '.fmu')) self.assertEqual([], problems)
def test_create_fmu_container_me(resources_dir): configuration = Configuration( parallelDoStep=False, variables=[ Variable(type='Real', variability='continuous', causality='output', initial='calculated', name='h', description='Height', mapping=[('ball', 'h')]), Variable(type='Boolean', variability='discrete', causality='output', initial='calculated', name='reset', description="Reset", mapping=[('bounce', 'reset')]), Variable(type='Real', variability='discrete', causality='output', initial='calculated', name='ticks', description='Ticks', mapping=[('ticker', 'ticks')]), ], components=[ Component(filename=resources_dir / 'Bounce.fmu', interfaceType='ModelExchange', name='bounce'), Component(filename=resources_dir / 'Ball.fmu', interfaceType='ModelExchange', name='ball'), Component(filename=resources_dir / 'Ticker.fmu', interfaceType='ModelExchange', name='ticker') ], connections=[ Connection('ball', 'h', 'bounce', 'h'), Connection('bounce', 'reset', 'ball', 'reset'), ]) filename = 'BouncingAndBall.fmu' create_fmu_container(configuration, filename) problems = validate_fmu(filename) assert not problems result = simulate_fmu(filename, stop_time=3.5, fmi_call_logger=None)
def test_generate_fmu_generates_valid_fmu(tmp_path, csvfile): fmu = tmp_path / "model.fmu" dataframe = pandas.read_csv(csvfile) generate_fmu( dataframe=dataframe, # type: ignore model_name="Test Model", inputs=["x", "y"], outputs=["z"], outfile=fmu, strategy="linear", ) errors = validate_fmu(fmu) assert not errors
def validate(self, build_dir, fmi_types=['ModelExchange', 'CoSimulation'], models=models, compile=False): from fmpy.util import read_csv, validate_result for model in models: print(model) fmu_filename = os.path.join(build_dir, 'dist', model + '.fmu') problems = validate_fmu(fmu_filename) self.assertEqual([], problems) if model == 'Feedthrough': start_values = { 'real_fixed_param': 1, 'string_param': "FMI is awesome!" } in_csv = os.path.join(test_fmus_dir, model, model + '_in.csv') input = read_csv(in_csv) else: start_values = {} input = None ref_csv = os.path.join(test_fmus_dir, model, model + '_ref.csv') for fmi_type in fmi_types: ref = read_csv(ref_csv) if compile: compile_platform_binary(fmu_filename) result = simulate_fmu(fmu_filename, fmi_type=fmi_type, start_values=start_values, input=input, solver='Euler') dev = validate_result(result, ref) self.assertLess(dev, 0.2, "Failed to validate " + model)
def validate(build_dir, fmi_types, models, compile=False): from fmpy.util import read_csv, validate_result test_fmus_dir = Path(__file__).parent for model in models: print(model) fmu_filename = os.path.join(build_dir, 'dist', model + '.fmu') problems = validate_fmu(fmu_filename) assert not problems if model == 'Feedthrough': start_values = {'Float64_fixed_parameter': 1, 'String_parameter': "FMI is awesome!"} in_csv = os.path.join(test_fmus_dir, model, model + '_in.csv') input = read_csv(in_csv) else: start_values = {} input = None ref_csv = os.path.join(test_fmus_dir, model, model + '_ref.csv') for fmi_type in fmi_types: ref = read_csv(ref_csv) if compile: if model == 'Resource' and os.name == 'nt': continue compile_platform_binary(fmu_filename) result = simulate_fmu(fmu_filename, fmi_type=fmi_type, start_values=start_values, input=input, solver='Euler') dev = validate_result(result, ref) assert dev < 0.2, "Failed to validate " + model
def test_fmi3(): build_dir = Path(__file__).parent / 'fmi3' build_fmus(build_dir, fmi_version=3) # run examples examples = [ 'cs_early_return', 'cs_event_mode', 'cs_intermediate_update', 'BouncingBall_cs', 'BouncingBall_me', 'import_shared_library', 'import_static_library', 'jacobian', 'scs_synchronous', 'Stair_cs', 'Stair_me' ] if os.name == 'nt': examples.append('scs_threaded') # runs only on Windows for example in examples: print("Running %s example..." % example) subprocess.check_call(build_dir / 'temp' / example, cwd=build_dir / 'temp') models = ['BouncingBall', 'Dahlquist', 'Feedthrough', 'Resource', 'Stair', 'VanDerPol'] validate(build_dir, fmi_types=['CoSimulation', 'ModelExchange'], models=models) validate(build_dir, fmi_types=['CoSimulation', 'ModelExchange'], models=models, compile=True) for model in ['Clocks', 'LinearTransform']: problems = validate_fmu(filename=build_dir / 'dist' / f'{model}.fmu') assert len(problems) == 0 copy_to_cross_check(dist_dir=build_dir / 'dist', model_names=models, fmi_version='3.0', fmi_types=['cs', 'me']) copy_to_cross_check(dist_dir=build_dir / 'dist', model_names=['Clocks'], fmi_version='3.0', fmi_types=['se'])
def main(): import argparse import textwrap description = """\ 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 """ parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent(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") 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(args.fmu_filename, target_platform=args.target_platform) 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 test_create_fmu_container(self): resources = os.path.join(os.path.dirname(__file__), 'resources') configuration = Configuration( parallelDoStep=True, description="A controlled drivetrain", variableNamingConvention='structured', unitDefinitions=[ Unit(name='rad/s', baseUnit=BaseUnit(rad=1, s=-1), displayUnits=[ DisplayUnit(name='rpm', factor=0.1047197551196598) ]) ], typeDefinitions=[ SimpleType(name='AngularVelocity', type='Real', unit='rad/s') ], variables=[ Variable(type='Real', variability='tunable', causality='parameter', name='k', start='100', description='Gain of controller', mapping=[('controller', 'PI.k')]), Variable(type='Real', variability='continuous', causality='input', name='w_ref', start='0', description='Reference speed', mapping=[('controller', 'u_s')], declaredType='AngularVelocity'), Variable(type='Real', variability='continuous', causality='output', name='w', description="Gain of controller", mapping=[('drivetrain', 'w')], unit='rad/s', displayUnit='rpm'), ], components=[ Component(filename=os.path.join(resources, 'Controller.fmu'), name='controller'), Component( filename=os.path.join(resources, 'Drivetrain.fmu'), name='drivetrain', ) ], connections=[ Connection('drivetrain', 'w', 'controller', 'u_m'), Connection('controller', 'y', 'drivetrain', 'tau'), ]) filename = 'ControlledDrivetrain.fmu' create_fmu_container(configuration, filename) problems = validate_fmu(filename) self.assertEqual(problems, []) w_ref = np.array([(0.5, 0), (1.5, 1), (2, 1), (3, 0)], dtype=[('time', 'f8'), ('w_ref', 'f8')]) result = simulate_fmu(filename, start_values={'k': 20}, input=w_ref, output=['w_ref', 'w'], stop_time=4)
def test_create_fmu_container_cs(resources_dir): configuration = Configuration( parallelDoStep=True, description="A controlled drivetrain", variableNamingConvention='structured', unitDefinitions=[ Unit(name='rad/s', baseUnit=BaseUnit(rad=1, s=-1), displayUnits=[ DisplayUnit(name='rpm', factor=0.1047197551196598) ]) ], typeDefinitions=[ SimpleType(name='AngularVelocity', type='Real', unit='rad/s') ], variables=[ Variable(type='Real', variability='tunable', causality='parameter', initial='exact', name='k', start='40', description='Gain of controller', mapping=[('controller', 'PI.k')]), Variable(type='Real', variability='continuous', causality='input', name='w_ref', start='0', description='Reference speed', mapping=[('controller', 'u_s')], declaredType='AngularVelocity'), Variable(type='Real', variability='continuous', causality='output', initial='calculated', name='w', description="Gain of controller", mapping=[('drivetrain', 'w')], unit='rad/s', displayUnit='rpm'), ], components=[ Component(filename=resources_dir / 'Controller.fmu', interfaceType='CoSimulation', name='controller'), Component( filename=resources_dir / 'Drivetrain.fmu', interfaceType='CoSimulation', name='drivetrain', ) ], connections=[ Connection('drivetrain', 'w', 'controller', 'u_m'), Connection('controller', 'y', 'drivetrain', 'tau'), ]) filename = 'ControlledDrivetrain.fmu' create_fmu_container(configuration, filename) problems = validate_fmu(filename) assert not problems input = read_csv(resources_dir / 'ControlledDrivetrain_in.csv') result = simulate_fmu(filename, input=input, output=['w_ref', 'w', 'k'], stop_time=5, output_interval=5e-2) t_band, y_min, y_max, i_out = validate_signal(t=result['time'], y=result['w'], t_ref=input['time'], y_ref=input['w_ref'], dx=100, dy=0.4) assert result['k'][0] == 40, 'Default start value has not been set.' assert not i_out.any()
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 test_main_generates_valid_fmu(tmp_path, csvfile): fmu = str(tmp_path / "model.fmu") main([str(csvfile), "--inputs", "x", "y", "--outputs", "z", "-o", fmu]) errors = validate_fmu(fmu) assert not errors
def test_fmpy_validate(self): errs = validate_fmu(fmu_path) self.assertCountEqual(errs, [], 'Errors list should be empty')