diff --git a/tests/test_c_code.py b/tests/test_c_code.py index 79712164..69a768a1 100644 --- a/tests/test_c_code.py +++ b/tests/test_c_code.py @@ -1,98 +1,92 @@ -from fmpy import simulate_fmu +# Test compilation of source code FMUs from various vendors -import unittest +import pytest +from fmpy import simulate_fmu from fmpy.util import download_file import os from fmpy.util import compile_platform_binary, create_cmake_project -class CCodeTest(unittest.TestCase): - """ Test compilation of source code FMUs from various vendors """ - - url = 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/cs/c-code/' - - fmus = [ - 'MapleSim/2016.2/Rectifier/Rectifier.fmu', - 'Dymola/2017/IntegerNetwork1/IntegerNetwork1.fmu', - ] - - def test_compile(self): - """ Compile the platform binary """ +urls = [ + 'https://github.com/modelica/fmi-cross-check/raw/refs/heads/master/fmus/2.0/cs/c-code/MapleSim/2021.2/Rectifier/Rectifier.fmu', + 'https://github.com/CATIA-Systems/dymola-fmi-compatibility/raw/refs/heads/main/2025x,%202024-10-11/CoupledClutches_fmi2_Cvode.fmu', + # 'https://github.com/CATIA-Systems/dymola-fmi-compatibility/raw/refs/heads/main/2025x,%202024-10-11/CoupledClutches_fmi3_Cvode.fmu', +] - # add debug info - if os.name == 'nt': - compiler_options = '/LDd /Zi' - else: - compiler_options = '-g -fPIC' - for fmu in self.fmus: +@pytest.mark.parametrize('url', urls) +def test_compile(url): + """ Compile the platform binary """ - filename = download_file(self.url + fmu) + # add debug info + if os.name == 'nt': + compiler_options = '/LDd /Zi' + else: + compiler_options = '-g -fPIC' - compile_platform_binary(filename, compiler_options=compiler_options) + filename = download_file(url) - result = simulate_fmu(filename=filename) - self.assertIsNotNone(result) + compile_platform_binary(filename, compiler_options=compiler_options) - def test_cmake(self): - """ Create a CMake project """ + result = simulate_fmu(filename=filename) - from subprocess import check_call - import shutil - from fmpy.util import visual_c_versions + assert result is not None - try: - # check if CMake is installed - check_call(['cmake']) - cmake_available = True - except: - cmake_available = False - for fmu in self.fmus: +@pytest.mark.parametrize('url', urls) +def test_cmake(url): + """ Create a CMake project """ - filename = download_file(self.url + fmu) + from subprocess import check_call + import shutil + from fmpy.util import visual_c_versions - model_name, _ = os.path.splitext(filename) + try: + # check if CMake is installed + check_call(['cmake']) + cmake_available = True + except: + cmake_available = False - # clean up - if os.path.isdir(model_name): - shutil.rmtree(model_name) + filename = download_file(url) - # create the CMake project - create_cmake_project(filename, model_name) + model_name, _ = os.path.splitext(filename) - if not cmake_available: - continue # skip compilation + # clean up + if os.path.isdir(model_name): + shutil.rmtree(model_name) - # generate the build system - cmake_args = ['cmake', '.'] + # create the CMake project + create_cmake_project(filename, model_name) - vc_versions = visual_c_versions() + if not cmake_available: + return # skip compilation - if os.name == 'nt': - if 170 in vc_versions: - cmake_args += ['-G', 'Visual Studio 17 2022', '-A', 'x64'] - elif 160 in vc_versions: - cmake_args += ['-G', 'Visual Studio 16 2019', '-A', 'x64'] - elif 150 in vc_versions: - cmake_args += ['-G', 'Visual Studio 15 2017 Win64'] - elif 140 in vc_versions: - cmake_args += ['-G', 'Visual Studio 14 2015 Win64'] - elif 120 in vc_versions: - cmake_args += ['-G', 'Visual Studio 12 2013 Win64'] - elif 110 in vc_versions: - cmake_args += ['-G', 'Visual Studio 11 2012 Win64'] + # generate the build system + cmake_args = ['cmake', '.'] - check_call(args=cmake_args, cwd=model_name) + vc_versions = visual_c_versions() - # run the build system - check_call(args=['cmake', '--build', '.'], cwd=model_name) + if os.name == 'nt': + if 170 in vc_versions: + cmake_args += ['-G', 'Visual Studio 17 2022', '-A', 'x64'] + elif 160 in vc_versions: + cmake_args += ['-G', 'Visual Studio 16 2019', '-A', 'x64'] + elif 150 in vc_versions: + cmake_args += ['-G', 'Visual Studio 15 2017 Win64'] + elif 140 in vc_versions: + cmake_args += ['-G', 'Visual Studio 14 2015 Win64'] + elif 120 in vc_versions: + cmake_args += ['-G', 'Visual Studio 12 2013 Win64'] + elif 110 in vc_versions: + cmake_args += ['-G', 'Visual Studio 11 2012 Win64'] - # simulate the FMU - result = simulate_fmu(filename=os.path.join(model_name, filename)) + check_call(args=cmake_args, cwd=model_name) - self.assertIsNotNone(result) + # run the build system + check_call(args=['cmake', '--build', '.'], cwd=model_name) + # simulate the FMU + result = simulate_fmu(filename=os.path.join(model_name, filename)) -if __name__ == '__main__': - unittest.main() + assert result is not None diff --git a/tests/test_command_line.py b/tests/test_command_line.py index a8fe4a44..8f26510b 100644 --- a/tests/test_command_line.py +++ b/tests/test_command_line.py @@ -1,51 +1,45 @@ -import unittest +# Test command line interface ('fmpy' entry point must be registered through setup.py or conda package) + from subprocess import call, check_output from fmpy.util import download_test_file, download_file -class CommandLineTest(unittest.TestCase): - """ Test command line interface ('fmpy' entry point must be registered through setup.py or conda package) """ - - @classmethod - def setUpClass(cls): - # download the FMU and input file - download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') - download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches_in.csv') - download_file('https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/me/win64/Dymola/2019FD01/Rectifier/Rectifier.fmu') - - def test_info(self): - status = call(['fmpy', 'info', 'CoupledClutches.fmu']) - self.assertEqual(0, status) - - def test_validate(self): - status = call(['fmpy', 'validate', 'Rectifier.fmu']) - self.assertEqual(0, status) - - def test_simulate(self): - - output = check_output([ - 'fmpy', 'simulate', 'CoupledClutches.fmu', - '--validate', - '--start-time', '0', - '--stop-time', '0.1', - '--solver', 'CVode', - '--relative-tolerance', '1e-4', - '--dont-record-events', - '--start-values', 'CoupledClutches1_freqHz', '0.2', - '--apply-default-start-values', - '--output-interval', '1e-2', - '--input-file', 'CoupledClutches_in.csv', - '--output-variables', 'outputs[1]', 'outputs[3]', - '--output-file', 'CoupledClutches_out.csv', - '--timeout', '10', - '--debug-logging', - '--fmi-logging', - # '--show-plot', - ]) - - self.assertTrue(output.startswith(b'[OK] [ModelExchange]: GUID = {'), - "Placeholders have not been substituted w/ variadic arguments.") - - -if __name__ == '__main__': - unittest.main() +download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') +download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches_in.csv') +download_file('https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/me/win64/Dymola/2019FD01/Rectifier/Rectifier.fmu') + + +def test_info(): + status = call(['fmpy', 'info', 'CoupledClutches.fmu']) + assert status == 0 + + +def test_validate(): + status = call(['fmpy', 'validate', 'Rectifier.fmu']) + assert status == 0 + + +def test_simulate(): + + output = check_output([ + 'fmpy', 'simulate', 'CoupledClutches.fmu', + '--validate', + '--start-time', '0', + '--stop-time', '0.1', + '--solver', 'CVode', + '--relative-tolerance', '1e-4', + '--dont-record-events', + '--start-values', 'CoupledClutches1_freqHz', '0.2', + '--apply-default-start-values', + '--output-interval', '1e-2', + '--input-file', 'CoupledClutches_in.csv', + '--output-variables', 'outputs[1]', 'outputs[3]', + '--output-file', 'CoupledClutches_out.csv', + '--timeout', '10', + '--debug-logging', + '--fmi-logging', + # '--show-plot', + ]) + + assert output.startswith(b'[OK] [ModelExchange]: GUID = {'),\ + "Placeholders have not been substituted w/ variadic arguments." diff --git a/tests/test_cswrapper.py b/tests/test_cswrapper.py index b36cd846..9846b184 100644 --- a/tests/test_cswrapper.py +++ b/tests/test_cswrapper.py @@ -1,23 +1,20 @@ -import unittest from fmpy import read_model_description, simulate_fmu from fmpy.util import download_test_file from fmpy.cswrapper import add_cswrapper -class CSWrapperTest(unittest.TestCase): +def test_cswrapper(): - def test_cswrapper(self): + filename = 'CoupledClutches.fmu' - filename = 'CoupledClutches.fmu' + download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', filename) - download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', filename) + model_description = read_model_description(filename) - model_description = read_model_description(filename) + assert model_description.coSimulation is None - self.assertIsNone(model_description.coSimulation) + outfilename = filename[:-4] + '_cs.fmu' - outfilename = filename[:-4] + '_cs.fmu' + add_cswrapper(filename, outfilename=outfilename) - add_cswrapper(filename, outfilename=outfilename) - - simulate_fmu(outfilename, fmi_type='CoSimulation') + simulate_fmu(outfilename, fmi_type='CoSimulation') diff --git a/tests/test_cvode.py b/tests/test_cvode.py index 6b4ec84e..560bbeae 100644 --- a/tests/test_cvode.py +++ b/tests/test_cvode.py @@ -1,4 +1,3 @@ -import unittest from fmpy.sundials.nvector_serial import N_VNew_Serial, N_VDestroy_Serial, NV_DATA_S from fmpy.sundials.sunmatrix_dense import SUNDenseMatrix from fmpy.sundials.sunlinsol_dense import SUNLinSol_Dense @@ -7,93 +6,87 @@ import numpy as np -class CVodeTest(unittest.TestCase): +def test_bouncing_ball(): + """ Test CVode with a simple bouncing ball equation """ - def test_bouncing_ball(self): - """ Test CVode with a simple bouncing ball equation """ + # arrays to collect the samples + time = [] + value = [] - # arrays to collect the samples - time = [] - value = [] + T0 = 0.0 + nx = 2 # number of states (height, velocity) + nz = 1 # number of event indicators - T0 = 0.0 - nx = 2 # number of states (height, velocity) - nz = 1 # number of event indicators + def rhsf(t, y, ydot, user_data): + x = np.ctypeslib.as_array(NV_DATA_S(y), (2,)) + dx = np.ctypeslib.as_array(NV_DATA_S(ydot), (2,)) + dx[0] = x[1] # velocity + dx[1] = -9.81 # gravity + time.append(t) + value.append(x[0]) + return 0 - def rhsf(t, y, ydot, user_data): - x = np.ctypeslib.as_array(NV_DATA_S(y), (2,)) - dx = np.ctypeslib.as_array(NV_DATA_S(ydot), (2,)) - dx[0] = x[1] # velocity - dx[1] = -9.81 # gravity - time.append(t) - value.append(x[0]) - return 0 + f = CVRhsFn(rhsf) - f = CVRhsFn(rhsf) + def rootf(t, y, gout, user_data): + x = np.ctypeslib.as_array(NV_DATA_S(y), (nz,)) + gout_ = np.ctypeslib.as_array(gout, (nz,)) + gout_[0] = x[0] + return 0 - def rootf(t, y, gout, user_data): - x = np.ctypeslib.as_array(NV_DATA_S(y), (nz,)) - gout_ = np.ctypeslib.as_array(gout, (nz,)) - gout_[0] = x[0] - return 0 + g = CVRootFn(rootf) - g = CVRootFn(rootf) + RTOL = 1e-5 - RTOL = 1e-5 + abstol = N_VNew_Serial(nx) + abstol_array = np.ctypeslib.as_array(NV_DATA_S(abstol), (nx,)) + abstol_array[:] = RTOL - abstol = N_VNew_Serial(nx) - abstol_array = np.ctypeslib.as_array(NV_DATA_S(abstol), (nx,)) - abstol_array[:] = RTOL + y = N_VNew_Serial(nx) + x_ = np.ctypeslib.as_array(NV_DATA_S(y), (nx,)) + x_[0] = 1 + x_[1] = 5 - y = N_VNew_Serial(nx) - x_ = np.ctypeslib.as_array(NV_DATA_S(y), (nx,)) - x_[0] = 1 - x_[1] = 5 + cvode_mem = CVodeCreate(CV_BDF) - cvode_mem = CVodeCreate(CV_BDF) + flag = CVodeInit(cvode_mem, f, T0, y) - flag = CVodeInit(cvode_mem, f, T0, y) + flag = CVodeSVtolerances(cvode_mem, RTOL, abstol) - flag = CVodeSVtolerances(cvode_mem, RTOL, abstol) + flag = CVodeRootInit(cvode_mem, nz, g) - flag = CVodeRootInit(cvode_mem, nz, g) + A = SUNDenseMatrix(nx, nx) - A = SUNDenseMatrix(nx, nx) + LS = SUNLinSol_Dense(y, A); - LS = SUNLinSol_Dense(y, A); + flag = CVodeSetLinearSolver(cvode_mem, LS, A) - flag = CVodeSetLinearSolver(cvode_mem, LS, A) + # flag = CVDense(cvode_mem, nx) + # + tNext = 2.0 + tret = realtype(0.0) - # flag = CVDense(cvode_mem, nx) - # - tNext = 2.0 - tret = realtype(0.0) + while tret.value < 2.0: - while tret.value < 2.0: + flag = CVode(cvode_mem, tNext, y, byref(tret), CV_NORMAL) - flag = CVode(cvode_mem, tNext, y, byref(tret), CV_NORMAL) + if flag == CV_ROOT_RETURN: - if flag == CV_ROOT_RETURN: + rootsfound = (c_int * nz)() - rootsfound = (c_int * nz)() + flag = CVodeGetRootInfo(cvode_mem, rootsfound) - flag = CVodeGetRootInfo(cvode_mem, rootsfound) + if rootsfound[0] == -1: + x_[1] = -x_[1] * 0.5 - if rootsfound[0] == -1: - x_[1] = -x_[1] * 0.5 + # reset solver + flag = CVodeReInit(cvode_mem, tret, y) - # reset solver - flag = CVodeReInit(cvode_mem, tret, y) + # clean up + CVodeFree(byref(c_void_p(cvode_mem))) + N_VDestroy_Serial(y) - # clean up - CVodeFree(byref(c_void_p(cvode_mem))) - N_VDestroy_Serial(y) - - # import matplotlib.pyplot as plt - # - # plt.plot(time, value, '.-') - # plt.show() - - -if __name__ == '__main__': - unittest.main() + # import matplotlib.pyplot as plt + # + # plt.plot(time, value, '.-') + # plt.show() diff --git a/tests/test_extracted_fmu.py b/tests/test_extracted_fmu.py index cca68d6e..27acdcf0 100644 --- a/tests/test_extracted_fmu.py +++ b/tests/test_extracted_fmu.py @@ -1,27 +1,22 @@ -import unittest - import shutil - from fmpy import extract, read_model_description, simulate_fmu from fmpy.util import download_test_file -class ExtractedFMUTest(unittest.TestCase): - - def test_extracted_fmu(self): - """ Simulate an extracted FMU """ +def test_extracted_fmu(): + """ Simulate an extracted FMU """ - download_test_file('2.0', 'cs', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') + download_test_file('2.0', 'cs', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') - # extract the FMU - tempdir = extract('CoupledClutches.fmu') + # extract the FMU + tempdir = extract('CoupledClutches.fmu') - # load the model description before the simulation - model_description = read_model_description(tempdir) + # load the model description before the simulation + model_description = read_model_description(tempdir) - result = simulate_fmu(tempdir, model_description=model_description) + result = simulate_fmu(tempdir, model_description=model_description) - self.assertIsNotNone(result) + assert result is not None - # clean up - shutil.rmtree(tempdir) + # clean up + shutil.rmtree(tempdir) diff --git a/tests/test_fmu_info.py b/tests/test_fmu_info.py index d6fefe27..e7a56908 100644 --- a/tests/test_fmu_info.py +++ b/tests/test_fmu_info.py @@ -1,66 +1,69 @@ -import unittest -from fmpy import platform, dump, simulate_fmu -from fmpy.util import download_test_file, fmu_info +import pytest +from fmpy import dump, simulate_fmu +from fmpy.util import fmu_info -class FMUInfoTest(unittest.TestCase): +def test_illegal_fmi_type(reference_fmus_dist_dir): - @classmethod - def setUpClass(cls): - # download the FMU - download_test_file('2.0', 'me', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') + filename = reference_fmus_dist_dir / '2.0' / 'BouncingBall.fmu' - def test_illegal_fmi_type(self): - with self.assertRaises(Exception) as context: - simulate_fmu('CoupledClutches.fmu', fmi_type='Hybrid') - self.assertEqual('fmi_type must be one of "ModelExchange" or "CoSimulation"', str(context.exception)) + with pytest.raises(Exception) as context: + simulate_fmu(filename, fmi_type='Hybrid') - def test_unsupported_fmi_type(self): - with self.assertRaises(Exception) as context: - simulate_fmu('CoupledClutches.fmu', fmi_type='CoSimulation') - self.assertEqual('FMI type "CoSimulation" is not supported by the FMU', str(context.exception)) + assert 'fmi_type must be one of "ModelExchange" or "CoSimulation"' == str(context.value) - def test_fmu_info(self): +def test_unsupported_fmi_type(reference_fmus_dist_dir): - info = fmu_info('CoupledClutches.fmu') + filename = reference_fmus_dist_dir / '1.0' / 'me' / 'BouncingBall.fmu' - generation_dates = { - 'darwin64': '2017-01-19T17:56:19Z', - 'linux64': '2017-01-19T18:38:03Z', - 'win32': '2017-01-19T18:48:24Z', - 'win64': '2017-01-19T18:42:35Z', - } + with pytest.raises(Exception) as context: + simulate_fmu(filename, fmi_type='CoSimulation') - expected = f""" + assert 'FMI type "CoSimulation" is not supported by the FMU' == str(context.value) + +def test_fmu_info(reference_fmus_dist_dir): + + filename = reference_fmus_dist_dir / '2.0' / 'BouncingBall.fmu' + + info = fmu_info(filename) + + generation_dates = { + 'darwin64': '2017-01-19T17:56:19Z', + 'linux64': '2017-01-19T18:38:03Z', + 'win32': '2017-01-19T18:48:24Z', + 'win64': '2017-01-19T18:42:35Z', + } + + expected = """ Model Info FMI Version 2.0 - FMI Type Model Exchange - Model Name CoupledClutches - Description Model CoupledClutches - Platforms {platform} - Continuous States 18 - Event Indicators 25 - Variables 178 - Generation Tool MapleSim (1196527/1196706/1196706) - Generation Date {generation_dates[platform]} + FMI Type Model Exchange, Co-Simulation + Model Name BouncingBall + Description This model calculates the trajectory, over time, of a ball dropped from a height of 1 m. + Platforms c-code, darwin64, linux64, win64 + Continuous States 2 + Event Indicators 1 + Variables 8 + Generation Tool Reference FMUs (v0.0.25) + Generation Date 2023-08-04T08:43:49.469027+00:00 Default Experiment - Stop Time 1.5 - Tolerance 0.0001 + Stop Time 3.0 + Step Size 0.01 Variables (input, output) Name Causality Start Value Unit Description - inputs input 0.00000000000000000e+00 RI1 - outputs[1] output 1.00000000000000000e+01 rad/s J1.w - outputs[2] output 0.00000000000000000e+00 rad/s J2.w - outputs[3] output 0.00000000000000000e+00 rad/s J3.w - outputs[4] output 0.00000000000000000e+00 rad/s J4.w""" + h output 1 m Position of the ball + v output 0 m/s Velocity of the ball""" + + assert expected == info + +def test_dump(reference_fmus_dist_dir): - self.assertEqual(expected, info) + filename = reference_fmus_dist_dir / '2.0' / 'BouncingBall.fmu' - def test_dump(self): - # dump the FMU info - dump('CoupledClutches.fmu') + # dump the FMU info + dump(filename) diff --git a/tests/test_get_directional_derivative.py b/tests/test_get_directional_derivative.py index 7f99f65f..39a084fa 100644 --- a/tests/test_get_directional_derivative.py +++ b/tests/test_get_directional_derivative.py @@ -1,63 +1,59 @@ -import unittest +import pytest from shutil import rmtree -from unittest import skipIf - from fmpy import platform, read_model_description, extract from fmpy.util import download_test_file from fmpy.fmi2 import FMU2Slave -class GetDirectionalDerivativeTest(unittest.TestCase): - - @skipIf(platform not in ['win32', 'win64'], "Current platform not supported by this FMU") - def test_get_directional_derivative(self): +@pytest.mark.skipif(platform not in ['win32', 'win64'], reason="Current platform not supported by this FMU") +def test_get_directional_derivative(): - fmu_filename = 'Rectifier.fmu' + fmu_filename = 'Rectifier.fmu' - download_test_file('2.0', 'CoSimulation', 'Dymola', '2017', 'Rectifier', fmu_filename) + download_test_file('2.0', 'CoSimulation', 'Dymola', '2017', 'Rectifier', fmu_filename) - model_description = read_model_description(filename=fmu_filename) + model_description = read_model_description(filename=fmu_filename) - unzipdir = extract(fmu_filename) + unzipdir = extract(fmu_filename) - fmu = FMU2Slave(guid=model_description.guid, - unzipDirectory=unzipdir, - modelIdentifier=model_description.coSimulation.modelIdentifier) + fmu = FMU2Slave(guid=model_description.guid, + unzipDirectory=unzipdir, + modelIdentifier=model_description.coSimulation.modelIdentifier) - fmu.instantiate() - fmu.setupExperiment() - fmu.enterInitializationMode() + fmu.instantiate() + fmu.setupExperiment() + fmu.enterInitializationMode() - # get the partial derivative for an initial unknown - unknown = model_description.initialUnknowns[1] + # get the partial derivative for an initial unknown + unknown = model_description.initialUnknowns[1] - self.assertEqual('iAC[1]', unknown.variable.name) + assert 'iAC[1]' == unknown.variable.name - vrs_unknown = [unknown.variable.valueReference] - vrs_known = [v.valueReference for v in unknown.dependencies] - dv_known = [1.0] * len(unknown.dependencies) + vrs_unknown = [unknown.variable.valueReference] + vrs_known = [v.valueReference for v in unknown.dependencies] + dv_known = [1.0] * len(unknown.dependencies) - partial_der = fmu.getDirectionalDerivative(vUnknown_ref=vrs_unknown, vKnown_ref=vrs_known, dvKnown=dv_known) + partial_der = fmu.getDirectionalDerivative(vUnknown_ref=vrs_unknown, vKnown_ref=vrs_known, dvKnown=dv_known) - self.assertEqual([-2.0], partial_der) + assert [-2.0] == partial_der - fmu.exitInitializationMode() + fmu.exitInitializationMode() - # get the partial derivative for three output variables - unknowns = model_description.outputs[4:7] + # get the partial derivative for three output variables + unknowns = model_description.outputs[4:7] - self.assertEqual(['uAC[1]', 'uAC[2]', 'uAC[3]'], [u.variable.name for u in unknowns]) + assert ['uAC[1]', 'uAC[2]', 'uAC[3]'] == [u.variable.name for u in unknowns] - vrs_unknown = [u.variable.valueReference for u in unknowns] - vrs_known = [v.valueReference for v in unknowns[0].dependencies] - dv_known = [1.0] * len(vrs_known) + vrs_unknown = [u.variable.valueReference for u in unknowns] + vrs_known = [v.valueReference for v in unknowns[0].dependencies] + dv_known = [1.0] * len(vrs_known) - partial_der = fmu.getDirectionalDerivative(vUnknown_ref=vrs_unknown, vKnown_ref=vrs_known, dvKnown=dv_known) + partial_der = fmu.getDirectionalDerivative(vUnknown_ref=vrs_unknown, vKnown_ref=vrs_known, dvKnown=dv_known) - self.assertAlmostEqual(-1500, partial_der[0]) - self.assertAlmostEqual(0, partial_der[1]) - self.assertAlmostEqual(1500, partial_der[2]) + assert pytest.approx(-1500) == partial_der[0] + assert pytest.approx(0) == partial_der[1] + assert pytest.approx(1500) == partial_der[2] - fmu.terminate() - fmu.freeInstance() - rmtree(unzipdir) + fmu.terminate() + fmu.freeInstance() + rmtree(unzipdir) diff --git a/tests/test_get_start_values.py b/tests/test_get_start_values.py index 2155c67d..91efd024 100644 --- a/tests/test_get_start_values.py +++ b/tests/test_get_start_values.py @@ -1,25 +1,23 @@ -import unittest +import pytest from fmpy import platform from fmpy.util import download_test_file, get_start_values -class GetStartValuesTest(unittest.TestCase): +def test_get_start_values(): - def test_get_start_values(self): + if platform.startswith('win'): + fmi_versions = ['2.0'] # quick fix until FMUs are available + elif platform.startswith(('darwin', 'linux')): + fmi_versions = ['2.0'] + else: + pytest.fail('Platform not supported') - if platform.startswith('win'): - fmi_versions = ['2.0'] # quick fix until FMUs are available - elif platform.startswith(('darwin', 'linux')): - fmi_versions = ['2.0'] - else: - self.fail('Platform not supported') + for fmi_version in fmi_versions: - for fmi_version in fmi_versions: + for fmi_type in ['CoSimulation', 'ModelExchange']: - for fmi_type in ['CoSimulation', 'ModelExchange']: + download_test_file(fmi_version, fmi_type, 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') - download_test_file(fmi_version, fmi_type, 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') + start_values = get_start_values('CoupledClutches.fmu') - start_values = get_start_values('CoupledClutches.fmu') - - self.assertEqual(start_values['CoupledClutches1_freqHz'], '0.2') + assert start_values['CoupledClutches1_freqHz'] == '0.2' diff --git a/tests/test_input.py b/tests/test_input.py index 39a96c99..b7848d9b 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -1,4 +1,3 @@ -import unittest import numpy as np from fmpy.simulation import Input from fmpy.model_description import ModelDescription, ScalarVariable @@ -6,117 +5,110 @@ inf = float('Inf') -class InputTest(unittest.TestCase): +def test_single_sample(): + t = np.array([0]) + y = np.array([2]) - def test_single_sample(self): - t = np.array([0]) - y = np.array([2]) + # "interpolate" input with only one sample + u, du = Input.interpolate(1, t, y) - # "interpolate" input with only one sample - u, du = Input.interpolate(1, t, y) + assert u == 2 + assert du == 0 - self.assertEqual(u, 2) - self.assertEqual(du, 0) +def test_input_continuous(): - def test_input_continuous(self): + t = np.array( [ 0, 1, 2, 3]) + y = np.array([[ 0, 0, 3, 3], + [-1, 0, 1, 2]]) - t = np.array( [ 0, 1, 2, 3]) - y = np.array([[ 0, 0, 3, 3], - [-1, 0, 1, 2]]) + # extrapolate left (hold) + (u1, u2), (du1, du2) = Input.interpolate(-1, t, y) + assert (u1, u2) == (0, -1) + assert (du1, du2) == (0, 0) - # extrapolate left (hold) - (u1, u2), (du1, du2) = Input.interpolate(-1, t, y) - self.assertTrue((u1, u2) == (0, -1)) - self.assertTrue((du1, du2) == (0, 0)) + # hit sample + (u1, u2), (du1, du2) = Input.interpolate(1, t, y) + assert (u1, u2) == (0, 0) + assert (du1, du2) == (0, 1) - # hit sample - (u1, u2), (du1, du2) = Input.interpolate(1, t, y) - self.assertTrue((u1, u2) == (0, 0)) - self.assertTrue((du1, du2) == (0, 1)) + # interpolate (linear) + (u1, u2), (du1, du2) = Input.interpolate(1.5, t, y) + assert (u1, u2) == (1.5, 0.5) + assert (du1, du2) == (3, 1) - # interpolate (linear) - (u1, u2), (du1, du2) = Input.interpolate(1.5, t, y) - self.assertTrue((u1, u2) == (1.5, 0.5)) - self.assertTrue((du1, du2) == (3, 1)) + # extrapolate right (hold) + (u1, u2), (du1, du2) = Input.interpolate(4, t, y) + assert (u1, u2) == (3, 2) + assert (du1, du2) == (0, 0) - # extrapolate right (hold) - (u1, u2), (du1, du2) = Input.interpolate(4, t, y) - self.assertTrue((u1, u2) == (3, 2)) - self.assertTrue((du1, du2) == (0, 0)) +def test_continuous_signal_events(): - def test_continuous_signal_events(self): + dtype = np.dtype([('time', np.float64)]) - dtype = np.dtype([('time', np.float64)]) + model_description = ModelDescription() - model_description = ModelDescription() + # no event + signals = np.array([(0,), (1,)], dtype=dtype) + t_events = Input.findEvents(signals, model_description) + assert [inf] == t_events - # no event - signals = np.array([(0,), (1,)], dtype=dtype) - t_events = Input.findEvents(signals, model_description) - self.assertEqual([inf], t_events) + # time grid with events at 0.5 and 0.8 + signals = np.array(list(zip([0.1, 0.2, 0.3, 0.4, 0.5, 0.5, 0.6, 0.7, 0.8, 0.8, 0.8, 0.9, 1.0])), dtype=dtype) + t_events = Input.findEvents(signals, model_description) + assert np.all([0.5, 0.8, inf] == t_events) - # time grid with events at 0.5 and 0.8 - signals = np.array(list(zip([0.1, 0.2, 0.3, 0.4, 0.5, 0.5, 0.6, 0.7, 0.8, 0.8, 0.8, 0.9, 1.0])), dtype=dtype) - t_events = Input.findEvents(signals, model_description) - self.assertTrue(np.all([0.5, 0.8, inf] == t_events)) +def test_discrete_signal_events(): - def test_discrete_signal_events(self): + # model with one discrete variable 'x' + model_description = ModelDescription() + variable = ScalarVariable('x', 0) + variable.variability = 'discrete' + model_description.modelVariables.append(variable) - # model with one discrete variable 'x' - model_description = ModelDescription() - variable = ScalarVariable('x', 0) - variable.variability = 'discrete' - model_description.modelVariables.append(variable) + # discrete events at 0.1 and 0.4 + signals = np.array([ + (0.0, 0), + (0.1, 0), + (0.2, 1), + (0.3, 1), + (0.4, 2)], + dtype=np.dtype([('time', np.float64), ('x', int)])) - # discrete events at 0.1 and 0.4 - signals = np.array([ - (0.0, 0), - (0.1, 0), - (0.2, 1), - (0.3, 1), - (0.4, 2)], - dtype=np.dtype([('time', np.float64), ('x', int)])) + t_event = Input.findEvents(signals, model_description) - t_event = Input.findEvents(signals, model_description) + assert np.all([0.2, 0.4, inf] == t_event) - self.assertTrue(np.all([0.2, 0.4, inf] == t_event)) +def test_input_discrete(): - def test_input_discrete(self): + t = np.array( [0, 1, 1, 1, 2]) + y = np.array([[0, 0, 4, 3, 3]]) - t = np.array( [0, 1, 1, 1, 2]) - y = np.array([[0, 0, 4, 3, 3]]) + # extrapolate left + u, du = Input.interpolate(-1, t, y) + assert u == 0, "Expecting first value" + assert du == 0 - # extrapolate left - u, du = Input.interpolate(-1, t, y) - self.assertEqual(u, 0, "Expecting first value") - self.assertEqual(du, 0) + # hit sample + u, du = Input.interpolate(0, t, y) + assert u == 0, "Expecting value at sample" + assert du == 0 - # hit sample - u, du = Input.interpolate(0, t, y) - self.assertEqual(u, 0, "Expecting value at sample") - self.assertEqual(du, 0) + # interpolate + u, du = Input.interpolate(0.5, t, y) + assert u == 0, "Expecting to hold previous value" + assert du == 0 - # interpolate - u, du = Input.interpolate(0.5, t, y) - self.assertEqual(u, 0, "Expecting to hold previous value") - self.assertEqual(du, 0) + # before event + u, du = Input.interpolate(1, t, y) + assert u == 0, "Expecting value before event" + assert du == 0 - # before event - u, du = Input.interpolate(1, t, y) - self.assertEqual(u, 0, "Expecting value before event") - self.assertEqual(du, 0) + # after event + u, du = Input.interpolate(1, t, y, after_event=True) + assert u == 3, "Expecting value after event" + assert du == 0 - # after event - u, du = Input.interpolate(1, t, y, after_event=True) - self.assertEqual(u, 3, "Expecting value after event") - self.assertEqual(du, 0) - - # extrapolate right - u, du = Input.interpolate(3, t, y) - self.assertEqual(u, 3, "Expecting last value") - self.assertEqual(du, 0) - - -if __name__ == '__main__': - - unittest.main() + # extrapolate right + u, du = Input.interpolate(3, t, y) + assert u == 3, "Expecting last value" + assert du == 0 diff --git a/tests/test_jupyter_notebook.py b/tests/test_jupyter_notebook.py index 36f73b7f..18ca4c25 100644 --- a/tests/test_jupyter_notebook.py +++ b/tests/test_jupyter_notebook.py @@ -1,26 +1,15 @@ -import sys -import unittest from subprocess import check_call from fmpy.util import download_test_file, create_jupyter_notebook -import os -@unittest.skipIf(os.name == 'nt', "CI hangs on Windows") -class JupyterNotebookTest(unittest.TestCase): - """ Test the Jupyter Notebook generation """ +def test_create_juypter_notebook(): - def test_create_juypter_notebook(self): + download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') - download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') + create_jupyter_notebook('CoupledClutches.fmu') - create_jupyter_notebook('CoupledClutches.fmu') + args = ['jupyter', 'nbconvert', '--to', 'notebook', '--execute', + '--ExecutePreprocessor.timeout=60', + '--output', 'CoupledClutches_out.ipynb', 'CoupledClutches.ipynb'] - args = ['jupyter', 'nbconvert', '--to', 'notebook', '--execute', - '--ExecutePreprocessor.timeout=60', - '--output', 'CoupledClutches_out.ipynb', 'CoupledClutches.ipynb'] - - check_call(args) - - -if __name__ == '__main__': - unittest.main() + check_call(args) diff --git a/tests/test_output_grid.py b/tests/test_output_grid.py index 0b9235d5..5bd2a75a 100644 --- a/tests/test_output_grid.py +++ b/tests/test_output_grid.py @@ -1,93 +1,85 @@ -import unittest +import pytest import numpy as np -import sys -import os from fmpy import simulate_fmu from fmpy.util import download_test_file, download_file -class OutputGridTest(unittest.TestCase): +def test_step_size_cs(): - @classmethod - def setUpClass(cls): - print("Python:", sys.version) + url = 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/cs/win64/Test-FMUs/0.0.2/Dahlquist/Dahlquist.fmu' + sha256 = '6df6ab64705615dfa1217123a103c23384a081763a6f71726ba7943503da8fc0' - def test_step_size_cs(self): + filename = download_file(url, checksum=sha256) - url = 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/cs/win64/Test-FMUs/0.0.2/Dahlquist/Dahlquist.fmu' - sha256 = '6df6ab64705615dfa1217123a103c23384a081763a6f71726ba7943503da8fc0' + h = 0.02 - filename = download_file(url, checksum=sha256) + result = simulate_fmu(filename, output_interval=h, stop_time=10) - h = 0.02 + time = result['time'] - result = simulate_fmu(filename, output_interval=h, stop_time=10) + grid = np.array(range(501)) * h - time = result['time'] + assert np.all(time == grid) - grid = np.array(range(501)) * h +def test_step_size_me(): - self.assertTrue(np.all(time == grid)) + # download the FMU and input file + for filename in ['CoupledClutches.fmu', 'CoupledClutches_in.csv']: + download_test_file('2.0', 'me', 'MapleSim', '2016.2', 'CoupledClutches', filename) - def test_step_size_me(self): + # load the input + input = np.genfromtxt('CoupledClutches_in.csv', delimiter=',', names=True) - # download the FMU and input file - for filename in ['CoupledClutches.fmu', 'CoupledClutches_in.csv']: - download_test_file('2.0', 'me', 'MapleSim', '2016.2', 'CoupledClutches', filename) + assert np.sum(input['time'] == 0.9) > 1, "Input event expected at t=0.9" - # load the input - input = np.genfromtxt('CoupledClutches_in.csv', delimiter=',', names=True) + start_time = 0.0 + stop_time = 1.5 + step_size = 1e-2 + output_interval = 2e-2 + T2 = 0.5 - self.assertTrue(np.sum(input['time'] == 0.9) > 1, msg="Input event expected at t=0.9") + # common arguments + kwargs = { + 'filename': 'CoupledClutches.fmu', + 'start_time': start_time, + 'stop_time': stop_time, + 'fmi_type': 'ModelExchange', + 'step_size': step_size, + 'output_interval': output_interval, + 'input': input, + 'start_values': {'CoupledClutches1_T2': T2} + } - start_time = 0.0 - stop_time = 1.5 - step_size = 1e-2 - output_interval = 2e-2 - T2 = 0.5 + # fixed step w/o events + result = simulate_fmu(solver='Euler', record_events=False, **kwargs) - # common arguments - kwargs = { - 'filename': 'CoupledClutches.fmu', - 'start_time': start_time, - 'stop_time': stop_time, - 'fmi_type': 'ModelExchange', - 'step_size': step_size, - 'output_interval': output_interval, - 'input': input, - 'start_values': {'CoupledClutches1_T2': T2} - } + time = result['time'] + assert time[0] == pytest.approx(start_time), "First sample time must be equal to start_time" + assert time[-1] == pytest.approx(stop_time), "Last sample time must be equal to stop_time" + assert np.all(np.isclose(np.diff(time), output_interval)), "Output intervals must be regular" - # fixed step w/o events - result = simulate_fmu(solver='Euler', record_events=False, **kwargs) + # fixed step w/ events + result = simulate_fmu(solver='Euler', record_events=True, **kwargs) - time = result['time'] - self.assertAlmostEqual(time[0], start_time, msg="First sample time must be equal to start_time") - self.assertAlmostEqual(time[-1], stop_time, msg="Last sample time must be equal to stop_time") - self.assertTrue(np.all(np.isclose(np.diff(time), output_interval)), msg="Output intervals must be regular") + time = result['time'] + assert time[0] == pytest.approx(start_time), "First sample time must be equal to start_time" + assert time[-1] == pytest.approx(stop_time), "Last sample time must be equal to stop_time" - # fixed step w/ events - result = simulate_fmu(solver='Euler', record_events=True, **kwargs) + # variable step w/o events + result = simulate_fmu(solver='CVode', record_events=False, **kwargs) - time = result['time'] - self.assertAlmostEqual(time[0], start_time, msg="First sample time must be equal to start_time") - self.assertAlmostEqual(time[-1], stop_time, msg="Last sample time must be equal to stop_time") + time = result['time'] + assert time[0] == pytest.approx(start_time), "First sample time must be equal to start_time" + assert time[-1] == pytest.approx(stop_time), "Last sample time must be equal to stop_time" + steps = np.diff(time) + steps = steps[steps > 1e-13] # remove events + assert np.all(np.isclose(steps, output_interval)), "Output intervals must be regular" - # variable step w/o events - result = simulate_fmu(solver='CVode', record_events=False, **kwargs) + # variable step w/ events + result = simulate_fmu(solver='CVode', record_events=True, **kwargs) - time = result['time'] - self.assertAlmostEqual(time[0], start_time, msg="First sample time must be equal to start_time") - self.assertAlmostEqual(time[-1], stop_time, msg="Last sample time must be equal to stop_time") - steps = np.diff(time) - steps = steps[steps > 1e-13] # remove events - self.assertTrue(np.all(np.isclose(steps, output_interval)), msg="Output intervals must be regular") - - # variable step w/ events - result = simulate_fmu(solver='CVode', record_events=True, **kwargs) - - time = result['time'] - self.assertAlmostEqual(time[0], start_time, msg="First sample time must be equal to start_time") - self.assertAlmostEqual(time[-1], stop_time, msg="Last sample time must be equal to stop_time") - self.assertTrue(np.sum(time == 0.9) > 1, msg="Input event expected at t=0.9") - self.assertTrue(np.sum(np.isclose(time, T2)) > 1, msg="Time event expected at t=T2") + time = result['time'] + assert time[0] == pytest.approx(start_time), "First sample time must be equal to start_time" + assert time[-1] == pytest.approx(stop_time), "Last sample time must be equal to stop_time" + assert np.sum(time == 0.9) > 1, "Input event expected at t=0.9" + assert np.sum(np.isclose(time, T2)) > 1, "Time event expected at t=T2" diff --git a/tests/test_read_csv.py b/tests/test_read_csv.py index 0c31f04e..da8f7d84 100644 --- a/tests/test_read_csv.py +++ b/tests/test_read_csv.py @@ -1,42 +1,34 @@ -import unittest import numpy as np from fmpy.util import write_csv, read_csv -class CSVTest(unittest.TestCase): +def test_structured_csv(): - def test_structured_csv(self): + cols = [ + ('time', np.float64, None), + ('y', np.float64, (3,)), + ] - cols = [ - ('time', np.float64, None), - ('y', np.float64, (3,)), - ] + rows = [ + (0.0, (1.0, 2.0, 3.0)), + (0.1, (1.1, 2.1, 3.1)), + (0.2, (1.2, 2.2, 3.2)), + (0.3, (1.3, 2.3, 3.3)), + ] - rows = [ - (0.0, (1.0, 2.0, 3.0)), - (0.1, (1.1, 2.1, 3.1)), - (0.2, (1.2, 2.2, 3.2)), - (0.3, (1.3, 2.3, 3.3)), - ] + # create a structured array with a 1-d array signal + result = np.array(rows, dtype=np.dtype(cols)) - # create a structured array with a 1-d array signal - result = np.array(rows, dtype=np.dtype(cols)) + # arrays are saved as single columns + write_csv('structured.csv', result) - # arrays are saved as single columns - write_csv('structured.csv', result) + # read as-is (single columns) + traj = read_csv('structured.csv') - # read as-is (single columns) - traj = read_csv('structured.csv') + assert traj.dtype.names == ('time', 'y[1]', 'y[2]', 'y[3]') - self.assertEqual(traj.dtype.names, ('time', 'y[1]', 'y[2]', 'y[3]')) + # read structured (restore arrays) + traj = read_csv('structured.csv', structured=True) - # read structured (restore arrays) - traj = read_csv('structured.csv', structured=True) - - self.assertEqual(traj.dtype.names, ('time', 'y')) - self.assertEqual(traj['y'].shape, (4, 3)) - - -if __name__ == '__main__': - - unittest.main() + assert traj.dtype.names == ('time', 'y') + assert traj['y'].shape == (4, 3) diff --git a/tests/test_remoting.py b/tests/test_remoting.py index 33f136c4..97713349 100644 --- a/tests/test_remoting.py +++ b/tests/test_remoting.py @@ -1,86 +1,82 @@ -import unittest +import pytest from os.path import dirname, join, isfile -from unittest import skipIf - import fmpy from fmpy import platform, supported_platforms, simulate_fmu from fmpy.util import add_remoting, download_file, has_wsl, has_wine64 -@skipIf(True, "Set to False to run remoting tests") -class RemotingTest(unittest.TestCase): - - @skipIf(platform != 'win64', "Windows 32-bit is only supported on Windows 64-bit") - def test_remoting_win32_on_win64_cs(self): - - filename = download_file( - 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/cs/win32/FMUSDK/2.0.4/vanDerPol/vanDerPol.fmu', - checksum='6a782ae3b3298081f9c620a17dedd54370622bd2bb78f42cb027243323a1b805') +pytest.skip( + reason="Comment out to run remoting test", + allow_module_level=True +) - self.assertNotIn('win64', supported_platforms(filename)) +@pytest.mark.skipif(platform != 'win64', reason="Windows 32-bit is only supported on Windows 64-bit") +def test_remoting_win32_on_win64_cs(): - simulate_fmu(filename, fmi_type='CoSimulation', remote_platform='win32') + filename = download_file( + 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/cs/win32/FMUSDK/2.0.4/vanDerPol/vanDerPol.fmu', + checksum='6a782ae3b3298081f9c620a17dedd54370622bd2bb78f42cb027243323a1b805') - add_remoting(filename, host_platform='win64', remote_platform='win32') + assert 'win64' not in supported_platforms(filename) - self.assertIn('win64', supported_platforms(filename)) + simulate_fmu(filename, fmi_type='CoSimulation', remote_platform='win32') - simulate_fmu(filename, fmi_type='CoSimulation', remote_platform=None) + add_remoting(filename, host_platform='win64', remote_platform='win32') - @skipIf(platform != 'win64', "Windows 32-bit is only supported on Windows 64-bit") - def test_remoting_win32_on_win64_me(self): + assert 'win64' in supported_platforms(filename) - filename = download_file( - 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/me/win32/MapleSim/2021.1/CoupledClutches/CoupledClutches.fmu', - checksum='2a22c800285bcda810d9fe59234ee72c29e0fea86bb6ab7d6eb5b703f0afbe4e') + simulate_fmu(filename, fmi_type='CoSimulation', remote_platform=None) - self.assertNotIn('win64', supported_platforms(filename)) +@pytest.mark.skipif(platform != 'win64', reason="Windows 32-bit is only supported on Windows 64-bit") +def test_remoting_win32_on_win64_me(): - simulate_fmu(filename, fmi_type='ModelExchange', remote_platform='win32', fmi_call_logger=None) + filename = download_file( + 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/me/win32/MapleSim/2021.1/CoupledClutches/CoupledClutches.fmu', + checksum='2a22c800285bcda810d9fe59234ee72c29e0fea86bb6ab7d6eb5b703f0afbe4e') - add_remoting(filename, 'win64', 'win32') + assert 'win64' not in supported_platforms(filename) - self.assertIn('win64', supported_platforms(filename)) + simulate_fmu(filename, fmi_type='ModelExchange', remote_platform='win32', fmi_call_logger=None) - simulate_fmu(filename, fmi_type='ModelExchange', remote_platform=None) + add_remoting(filename, 'win64', 'win32') - @skipIf(not has_wsl(), "Requires Windows 64-bit and WSL") - def test_remoting_linux64_on_win64(self): + assert 'win64' in supported_platforms(filename) - if not isfile(join(dirname(fmpy.__file__), 'remoting', 'linux64', 'server_tcp')): - return # Linux binary is missing + simulate_fmu(filename, fmi_type='ModelExchange', remote_platform=None) - filename = download_file( - 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/cs/linux64/MapleSim/2021.1/Rectifier/Rectifier.fmu', - checksum='b9238cd6bb684f1cf5b240ca140ed5b3f75719cacf81df5ff0cae74c2e31e52e') +@pytest.mark.skipif(not has_wsl(), reason="Requires Windows 64-bit and WSL") +def test_remoting_linux64_on_win64(): - self.assertNotIn('win64', supported_platforms(filename)) + if not isfile(join(dirname(fmpy.__file__), 'remoting', 'linux64', 'server_tcp')): + return # Linux binary is missing - simulate_fmu(filename, remote_platform='linux64') + filename = download_file( + 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/cs/linux64/MapleSim/2021.1/Rectifier/Rectifier.fmu', + checksum='b9238cd6bb684f1cf5b240ca140ed5b3f75719cacf81df5ff0cae74c2e31e52e') - add_remoting(filename, host_platform='win64', remote_platform='linux64') + assert 'win64' not in supported_platforms(filename) - self.assertIn('win64', supported_platforms(filename)) + simulate_fmu(filename, remote_platform='linux64') - simulate_fmu(filename, remote_platform=None) + add_remoting(filename, host_platform='win64', remote_platform='linux64') - @skipIf(not has_wine64(), "Requires Linux 64-bit and wine 64-bit") - def test_remoting_win64_on_linux64_cs(self): + assert 'win64' in supported_platforms(filename) - filename = download_file( - 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/cs/win64/Dymola/2019FD01/DFFREG/DFFREG.fmu', - checksum='b4baf75e189fc7078b76c3d9f23f6476ec103d93f60168df4e82fa4dc053a93c') + simulate_fmu(filename, remote_platform=None) - self.assertNotIn('linux64', supported_platforms(filename)) +@pytest.mark.skipif(not has_wine64(), reason="Requires Linux 64-bit and wine 64-bit") +def test_remoting_win64_on_linux64_cs(): - simulate_fmu(filename, remote_platform='win64', stop_time=5, output_interval=0.01) + filename = download_file( + 'https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/cs/win64/Dymola/2019FD01/DFFREG/DFFREG.fmu', + checksum='b4baf75e189fc7078b76c3d9f23f6476ec103d93f60168df4e82fa4dc053a93c') - add_remoting(filename, host_platform='linux64', remote_platform='win64') + assert 'linux64' not in supported_platforms(filename) - self.assertIn('win64', supported_platforms(filename)) + simulate_fmu(filename, remote_platform='win64', stop_time=5, output_interval=0.01) - simulate_fmu(filename, remote_platform=None) + add_remoting(filename, host_platform='linux64', remote_platform='win64') + assert 'win64' in supported_platforms(filename) -if __name__ == '__main__': - unittest.main() + simulate_fmu(filename, remote_platform=None) diff --git a/tests/test_ssp.py b/tests/test_ssp.py index 539e1885..92deeb21 100644 --- a/tests/test_ssp.py +++ b/tests/test_ssp.py @@ -1,192 +1,157 @@ -import unittest -from unittest import skipIf +import pytest import numpy as np -import sys from fmpy import platform from fmpy.ssp.ssd import read_ssd, read_ssv from fmpy.ssp.simulation import simulate_ssp import os -@skipIf('SSP_STANDARD_DEV' not in os.environ, "Environment variable SSP_STANDARD_DEV must point to the clone of https://github.com/modelica/ssp-standard-dev") -class SSPTest(unittest.TestCase): +if 'SSP_STANDARD_DEV' not in os.environ: + pytest.skip( + reason="Environment variable SSP_STANDARD_DEV must point to the clone of https://github.com/modelica/ssp-standard-dev", + allow_module_level=True + ) - @classmethod - def setUpClass(cls): - print("Python:", sys.version) - print() +def ssp_dev_path(*segments): + return os.path.join(os.environ['SSP_STANDARD_DEV'], *segments) - @staticmethod - def ssp_dev_path(*segments): - return os.path.join(os.environ['SSP_STANDARD_DEV'], *segments) +@pytest.mark.skipif(platform != 'win32', reason="Current platform not supported by this SSP") +def test_simulate_sample_system_with_parameters(): - @skipIf(platform != 'win32', "Current platform not supported by this SSP") - def test_simulate_sample_system_with_parameters(self): + ssv_filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemParameterValues.ssv') + parameter_set = read_ssv(ssv_filename) - ssv_filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemParameterValues.ssv') - parameter_set = read_ssv(ssv_filename) - - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystem.ssp') - sine = lambda t: np.sin(t * 2 * np.pi) - result = simulate_ssp(filename, stop_time=1.0, step_size=0.01, parameter_set=parameter_set, input={'In1': sine}) - - # check if the input has been applied correctly - self.assertTrue(np.all(np.abs(result['In1'] - sine(result['time'])) < 0.01)) - - # plot_result(result, names=['In1', 'Out1'], window_title=filename) - - @skipIf(platform != 'win32', "Current platform not supported by this SSP") - def test_simulate_sub_system(self): - - ssp_filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystem.ssp') - sine = lambda t: np.sin(t * 2 * np.pi) - result = simulate_ssp(ssp_filename, stop_time=1.0, step_size=0.01, input={'In1': sine}) - - # check if the input has been applied correctly - self.assertTrue(np.all(np.abs(result['In1'] - sine(result['time'])) < 0.01)) - - # plot_result(result, names=['In1', 'Out1'], window_title=ssp_filename) - - def test_read_ssd_with_referenced_signal_dictionary(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemDictionary.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Simple System with SubSystem and Dictionary') - self.assertEqual(1, len(ssd.system.elements[2].elements[0].parameterBindings)) - - def test_read_ssd_with_inlined_signal_dictionary(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemDictionaryInline.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Simple System with SubSystem and Dictionary all inline') - - def test_SampleSystem(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystem.ssp') - ssd = read_ssd(filename) - self.assertEqual('Simple Sample System', ssd.name) - - # System - self.assertEqual('SampleSystem', ssd.system.name) - self.assertEqual('Very simple Sample System', ssd.system.description) - - def test_SampleSystemSubSystem(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystem.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Simple System with SubSystem') - - def test_SampleSystemSubSystemDictionary(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemDictionary.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Simple System with SubSystem and Dictionary') - - # parameter bindings from .ssv file - parameter_binding = ssd.system.parameterBindings[0] - self.assertEqual('SubSystem.', parameter_binding.prefix) - self.assertEqual('application/x-ssp-parameter-set', parameter_binding.type) - - p1, p2, p3 = parameter_binding.parameterValues[0].parameters - self.assertEqual(('FirstFMUInstance1.Gain_Gain', 'Real', '8.0'), (p1.name, p1.type, p1.value)) - self.assertEqual(('FirstFMUInstance2.Gain_Gain', 'Real', '3.0'), (p2.name, p2.type, p2.value)) - self.assertEqual(('FirstFMUInstance2.Gain1_Gain', 'Real', '18.0'), (p3.name, p3.type, p3.value)) - - # signal dictionary from .ssb file - signal_dictionary = ssd.system.signalDictionaries[0] - self.assertEqual('MyDictionary', signal_dictionary.name) - self.assertEqual('resources/SampleSignalDictionary.ssb', signal_dictionary.source) - self.assertEqual('application/x-ssp-signal-dictionary', signal_dictionary.type) - - sd_entry1, sd_entry2 = signal_dictionary.entries - self.assertEqual(('Var2', 'Real', 'km/h'), (sd_entry1.name, sd_entry1.type, sd_entry1.unit)) - self.assertEqual(('Var4', 'Real', 'km/h'), (sd_entry2.name, sd_entry2.type, sd_entry2.unit)) - - # units - u1, u2 = ssd.units - self.assertEqual(('km/h', 1, -1, 0.2777777777777778), (u1.name, u1.m, u1.s, u1.factor)) - self.assertEqual(('m/s', 1, -1), (u2.name, u2.m, u1.s)) - - def test_SampleSystemSubSystemDictionaryInline(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemDictionaryInline.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Simple System with SubSystem and Dictionary all inline') - - # inline parameter bindings - parameter_binding = ssd.system.parameterBindings[0] - self.assertEqual('SubSystem.', parameter_binding.prefix) - self.assertEqual('application/x-ssp-parameter-set', parameter_binding.type) - - p1, p2, p3 = parameter_binding.parameterValues[0].parameters - self.assertEqual(('FirstFMUInstance1.Gain_Gain', 'Real', '8.0'), (p1.name, p1.type, p1.value)) - self.assertEqual(('FirstFMUInstance2.Gain_Gain', 'Real', '3.0'), (p2.name, p2.type, p2.value)) - self.assertEqual(('FirstFMUInstance2.Gain1_Gain', 'Real', '18.0'), (p3.name, p3.type, p3.value)) - - # inline signal dictionary - signal_dictionary = ssd.system.signalDictionaries[0] - self.assertEqual('MyDictionary', signal_dictionary.name) - self.assertEqual('application/x-ssp-signal-dictionary', signal_dictionary.type) - - sd_entry1, sd_entry2 = signal_dictionary.entries - self.assertEqual(('Var2', 'Real', 'km/h'), (sd_entry1.name, sd_entry1.type, sd_entry1.unit)) - self.assertEqual(('Var4', 'Real', 'km/h'), (sd_entry2.name, sd_entry2.type, sd_entry2.unit)) - - def test_SampleSystemSubSystemParamConnectors(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemParamConnectors.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Simple System with SubSystem and Dictionary all inline, and parameter connectors') - - def test_SampleSystemSubSystemReuse(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemReuse.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Simple System with SubSystem and Reuse') - - def test_SampleSystemSubSystemReuseNested(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemReuseNested.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Simple System with SubSystem and External Reuse') - - def test_SampleSystemVariants(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemVariants.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Simple Sample System') - - def test_SubSystem(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SubSystem.ssp') - ssd = read_ssd(filename) - self.assertEqual(ssd.name, 'Subsystem for External Reuse') - - def test_read_ssv(self): - filename = self.ssp_dev_path('SystemStructureDescription', 'examples', 'SampleParameterValues.ssv') - parameter_set = read_ssv(filename) - self.assertEqual(6, len(parameter_set.parameters)) - - # def test_ControlledTemperature(self): - # - # ssp_filename = r'ControlledTemperature.ssp' - # - # if not os.path.isfile(ssp_filename): - # return - # - # def Tenv(t): - # return 20.0 - # - # def Tref(t): - # return 20.0 + np.floor(t) * 3 - # - # result = simulate_ssp(ssp_filename, stop_time=10, step_size=1e-2, input={'Tenv': Tenv, 'Tref': Tref}) - # - # # plot_result(result, names=['Tenv', 'Tref', 'controller.onSwitch', 'T'], window_title=os.path.basename(ssp_filename)) - - # def test_eDrive(self): - # ssp_filename = r'Z:\Development\SSP\trunk\Examples\Electrical_Drive\Example_eDrive.ssp' - # # ssd = read_ssd(filename) - # result = simulate_ssp(ssp_filename, stop_time=1.0) - # # self.assertEqual(ssd.name, 'ElectricalDrive') - - def test_ControlledDrivetrain(self): - - ssp_filename = self.ssp_dev_path('Examples', 'ControlledDrivetrain', 'ControlledDrivetrain.ssp') - - result = simulate_ssp(ssp_filename, step_size=1e-2, stop_time=4.0) - - # plot_result(result) - - -if __name__ == '__main__': - unittest.main() + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystem.ssp') + sine = lambda t: np.sin(t * 2 * np.pi) + result = simulate_ssp(filename, stop_time=1.0, step_size=0.01, parameter_set=parameter_set, input={'In1': sine}) + + # check if the input has been applied correctly + assert np.all(np.abs(result['In1'] - sine(result['time'])) < 0.01) + + # plot_result(result, names=['In1', 'Out1'], window_title=filename) + +@pytest.mark.skipif(platform != 'win32', reason="Current platform not supported by this SSP") +def test_simulate_sub_system(): + + ssp_filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystem.ssp') + sine = lambda t: np.sin(t * 2 * np.pi) + result = simulate_ssp(ssp_filename, stop_time=1.0, step_size=0.01, input={'In1': sine}) + + # check if the input has been applied correctly + assert np.all(np.abs(result['In1'] - sine(result['time'])) < 0.01) + + # plot_result(result, names=['In1', 'Out1'], window_title=ssp_filename) + +def test_read_ssd_with_referenced_signal_dictionary(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemDictionary.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Simple System with SubSystem and Dictionary' + assert 1 == len(ssd.system.elements[2].elements[0].parameterBindings) + +def test_read_ssd_with_inlined_signal_dictionary(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemDictionaryInline.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Simple System with SubSystem and Dictionary all inline' + +def test_SampleSystem(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystem.ssp') + ssd = read_ssd(filename) + assert 'Simple Sample System' == ssd.name + + # System + assert 'SampleSystem' == ssd.system.name + assert 'Very simple Sample System' == ssd.system.description + +def test_SampleSystemSubSystem(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystem.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Simple System with SubSystem' + +def test_SampleSystemSubSystemDictionary(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemDictionary.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Simple System with SubSystem and Dictionary' + + # parameter bindings from .ssv file + parameter_binding = ssd.system.parameterBindings[0] + assert 'SubSystem.' == parameter_binding.prefix + assert 'application/x-ssp-parameter-set' == parameter_binding.type + + p1, p2, p3 = parameter_binding.parameterValues[0].parameters + assert ('FirstFMUInstance1.Gain_Gain', 'Real', '8.0') == (p1.name, p1.type, p1.value) + assert ('FirstFMUInstance2.Gain_Gain', 'Real', '3.0') == (p2.name, p2.type, p2.value) + assert ('FirstFMUInstance2.Gain1_Gain', 'Real', '18.0') == (p3.name, p3.type, p3.value) + + # signal dictionary from .ssb file + signal_dictionary = ssd.system.signalDictionaries[0] + assert 'MyDictionary' == signal_dictionary.name + assert 'resources/SampleSignalDictionary.ssb' == signal_dictionary.source + assert 'application/x-ssp-signal-dictionary' == signal_dictionary.type + + sd_entry1, sd_entry2 = signal_dictionary.entries + assert ('Var2', 'Real', 'km/h') == (sd_entry1.name, sd_entry1.type, sd_entry1.unit) + assert ('Var4', 'Real', 'km/h') == (sd_entry2.name, sd_entry2.type, sd_entry2.unit) + + # units + u1, u2 = ssd.units + assert ('km/h', 1, -1, 0.2777777777777778) == (u1.name, u1.m, u1.s, u1.factor) + assert ('m/s', 1, -1) == (u2.name, u2.m, u1.s) + +def test_SampleSystemSubSystemDictionaryInline(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemDictionaryInline.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Simple System with SubSystem and Dictionary all inline' + + # inline parameter bindings + parameter_binding = ssd.system.parameterBindings[0] + assert 'SubSystem.' == parameter_binding.prefix + assert 'application/x-ssp-parameter-set' == parameter_binding.type + + p1, p2, p3 = parameter_binding.parameterValues[0].parameters + assert ('FirstFMUInstance1.Gain_Gain', 'Real', '8.0') == (p1.name, p1.type, p1.value) + assert ('FirstFMUInstance2.Gain_Gain', 'Real', '3.0') == (p2.name, p2.type, p2.value) + assert ('FirstFMUInstance2.Gain1_Gain', 'Real', '18.0') == (p3.name, p3.type, p3.value) + + # inline signal dictionary + signal_dictionary = ssd.system.signalDictionaries[0] + assert 'MyDictionary' == signal_dictionary.name + assert 'application/x-ssp-signal-dictionary' == signal_dictionary.type + + sd_entry1, sd_entry2 = signal_dictionary.entries + assert ('Var2', 'Real', 'km/h') == (sd_entry1.name, sd_entry1.type, sd_entry1.unit) + assert ('Var4', 'Real', 'km/h') == (sd_entry2.name, sd_entry2.type, sd_entry2.unit) + +def test_SampleSystemSubSystemParamConnectors(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemParamConnectors.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Simple System with SubSystem and Dictionary all inline, and parameter connectors' + +def test_SampleSystemSubSystemReuse(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemReuse.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Simple System with SubSystem and Reuse' + +def test_SampleSystemSubSystemReuseNested(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemSubSystemReuseNested.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Simple System with SubSystem and External Reuse' + +def test_SampleSystemVariants(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleSystemVariants.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Simple Sample System' + +def test_SubSystem(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SubSystem.ssp') + ssd = read_ssd(filename) + assert ssd.name == 'Subsystem for External Reuse' + +def test_read_ssv(): + filename = ssp_dev_path('SystemStructureDescription', 'examples', 'SampleParameterValues.ssv') + parameter_set = read_ssv(filename) + assert 6 == len(parameter_set.parameters) + +def test_ControlledDrivetrain(): + ssp_filename = ssp_dev_path('Examples', 'ControlledDrivetrain', 'ControlledDrivetrain.ssp') + result = simulate_ssp(ssp_filename, step_size=1e-2, stop_time=4.0) + # plot_result(result) diff --git a/tests/test_start_value_units.py b/tests/test_start_value_units.py index 2e6a75a4..08adfcbb 100644 --- a/tests/test_start_value_units.py +++ b/tests/test_start_value_units.py @@ -1,57 +1,47 @@ -import unittest -from unittest import skipIf - +import pytest from fmpy import * from fmpy.util import download_test_file -@skipIf(platform not in ['win32', 'win64'], "FMU only available for Windows") -class StartValueUnitTest(unittest.TestCase): +if platform not in ['win32', 'win64']: + pytest.skip(reason="FMU only available for Windows", allow_module_level=True) + +download_test_file('2.0', 'CoSimulation', 'Dymola', '2017', 'Rectifier', 'Rectifier.fmu') + - @classmethod - def setUpClass(cls): - download_test_file('2.0', 'CoSimulation', 'Dymola', '2017', 'Rectifier', 'Rectifier.fmu') +def test_start_value_units(): - def test_start_value_units(self): + start_values = { + 'IdealDiode1.T': 294.15, # no unit + 'IdealDiode2.T': (295.15, None), # no unit as tuple + 'IdealDiode3.T': (296.15, 'K'), # base unit + 'IdealDiode4.T': (24, 'degC'), # display unit + } - start_values = { - 'IdealDiode1.T': 294.15, # no unit - 'IdealDiode2.T': (295.15, None), # no unit as tuple - 'IdealDiode3.T': (296.15, 'K'), # base unit - 'IdealDiode4.T': (24, 'degC'), # display unit - } + result = simulate_fmu('Rectifier.fmu', start_values=start_values, output=['IdealDiode1.T', 'IdealDiode2.T', 'IdealDiode3.T', 'IdealDiode4.T']) - result = simulate_fmu('Rectifier.fmu', start_values=start_values, output=['IdealDiode1.T', 'IdealDiode2.T', 'IdealDiode3.T', 'IdealDiode4.T']) + assert 294.15 == result['IdealDiode1.T'][0] + assert 295.15, result['IdealDiode2.T'][0] + assert 296.15, result['IdealDiode3.T'][0] + assert 297.15, result['IdealDiode4.T'][0] - self.assertEqual(294.15, result['IdealDiode1.T'][0]) - self.assertEqual(295.15, result['IdealDiode2.T'][0]) - self.assertEqual(296.15, result['IdealDiode3.T'][0]) - self.assertEqual(297.15, result['IdealDiode4.T'][0]) +def test_illegal_tuple(): - def test_illegal_tuple(self): + with pytest.raises(Exception) as exception_info: + simulate_fmu('Rectifier.fmu', start_values={'IdealDiode1.T': (294.15,)}) - try: - simulate_fmu('Rectifier.fmu', start_values={'IdealDiode1.T': (294.15,)}) - self.assertFail() - except Exception as e: - self.assertEqual(str(e), 'The start value for variable IdealDiode1.T must be a scalar value or a tuple (, {|}) but was "(294.15,)".') + assert str(exception_info.value) == 'The start value for variable IdealDiode1.T must be a scalar value or a tuple (, {|}) but was "(294.15,)".' - def test_no_base_unit(self): +def test_no_base_unit(): - try: - simulate_fmu('Rectifier.fmu', start_values={'IdealDiode1.off': (True, '?')}) - self.assertFail() - except Exception as e: - self.assertEqual(str(e), 'Variable IdealDiode1.off has no unit but the unit "?" was specified for its start value.') + with pytest.raises(Exception) as exception_info: + simulate_fmu('Rectifier.fmu', start_values={'IdealDiode1.off': (True, '?')}) - def test_illegal_unit(self): + assert str(exception_info.value) == 'Variable IdealDiode1.off has no unit but the unit "?" was specified for its start value.' - try: - simulate_fmu('Rectifier.fmu', start_values={'IdealDiode1.T': (294.15, 'm2')}) - self.assertFail() - except Exception as e: - self.assertEqual(str(e), 'The unit "m2" of the start value for variable IdealDiode1.T is not defined.') +def test_illegal_unit(): + with pytest.raises(Exception) as exception_info: + simulate_fmu('Rectifier.fmu', start_values={'IdealDiode1.T': (294.15, 'm2')}) -if __name__ == '__main__': - unittest.main() + assert str(exception_info.value) == 'The unit "m2" of the start value for variable IdealDiode1.T is not defined.' diff --git a/tests/test_type_definitions.py b/tests/test_type_definitions.py index f14dc992..3a9bb083 100644 --- a/tests/test_type_definitions.py +++ b/tests/test_type_definitions.py @@ -1,39 +1,32 @@ -import unittest from fmpy import read_model_description from fmpy.util import download_file -class TypeDefinitionsTest(unittest.TestCase): +def test_type_definitions(): + """ Read the Type Definitions from the modelDescription.xml """ - def test_type_definitions(self): - """ Read the Type Definitions from the modelDescription.xml """ + for fmi_version in ['1.0', '2.0']: - for fmi_version in ['1.0', '2.0']: + filename = download_file('https://github.com/modelica/fmi-cross-check/raw/master/fmus/' + + fmi_version + '/cs/win64/Dymola/2017/DFFREG/DFFREG.fmu') - filename = download_file('https://github.com/modelica/fmi-cross-check/raw/master/fmus/' - + fmi_version + '/cs/win64/Dymola/2017/DFFREG/DFFREG.fmu') + model_description = read_model_description(filename) - model_description = read_model_description(filename) + real = model_description.typeDefinitions[0] - real = model_description.typeDefinitions[0] + assert 'Real' == real.type + assert 'Modelica.SIunits.Time' == real.name + assert 'Time' == real.quantity + assert 's' == real.unit - self.assertEqual('Real', real.type) - self.assertEqual('Modelica.SIunits.Time', real.name) - self.assertEqual('Time', real.quantity) - self.assertEqual('s', real.unit) + logic = model_description.typeDefinitions[1] - logic = model_description.typeDefinitions[1] + assert 'Enumeration' == logic.type + assert 'Modelica.Electrical.Digital.Interfaces.Logic' == logic.name + assert 9 == len(logic.items) - self.assertEqual('Enumeration', logic.type) - self.assertEqual('Modelica.Electrical.Digital.Interfaces.Logic', logic.name) - self.assertEqual(9, len(logic.items)) + high_impedance = logic.items[4] - high_impedance = logic.items[4] - - self.assertEqual("'Z'", high_impedance.name) - self.assertEqual(5, int(high_impedance.value)) - self.assertEqual("Z High Impedance", high_impedance.description) - - -if __name__ == '__main__': - unittest.main() + assert "'Z'" == high_impedance.name + assert 5 == int(high_impedance.value) + assert "Z High Impedance" == high_impedance.description diff --git a/tests/test_validation.py b/tests/test_validation.py index 85c104ca..f91bfef8 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,56 +1,40 @@ -from fmpy import read_model_description, simulate_fmu - -import unittest +# Test the validation of model description +import pytest +from fmpy import read_model_description, simulate_fmu from fmpy.model_description import ValidationError from fmpy.util import download_file, download_test_file -class ValidationTest(unittest.TestCase): - """ Test the validation of model description """ - - - def test_validate_derivatives(self): - - filename = download_file( - url='https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/me/win64/MapleSim/2015.1/CoupledClutches/CoupledClutches.fmu', - checksum='af8f8ca4d7073b2d6207d8eea4a3257e3a23a69089f03181236ee3ecf13ff77f' - ) - - problems = [] - - try: - read_model_description(filename, validate=True, validate_variable_names=False) - except ValidationError as e: - problems = e.problems - - self.assertEqual(problems[0], 'The unit "" of variable "inputs" (line 183) is not defined.') +def test_validate_derivatives(): - def test_validate_variable_names(self): + filename = download_file( + url='https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/me/win64/MapleSim/2015.1/CoupledClutches/CoupledClutches.fmu', + checksum='af8f8ca4d7073b2d6207d8eea4a3257e3a23a69089f03181236ee3ecf13ff77f' + ) - filename = download_file( - url='https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/me/win64/MapleSim/2015.1/CoupledClutches/CoupledClutches.fmu', - checksum='af8f8ca4d7073b2d6207d8eea4a3257e3a23a69089f03181236ee3ecf13ff77f' - ) + with pytest.raises(ValidationError) as exception_info: + read_model_description(filename, validate=True, validate_variable_names=False) - problems = [] + assert exception_info.value.problems[0] == 'The unit "" of variable "inputs" (line 183) is not defined.' - try: - read_model_description(filename, validate=True, validate_variable_names=True) - except ValidationError as e: - problems = e.problems +def test_validate_variable_names(): - self.assertEqual(len(problems), 124) + filename = download_file( + url='https://github.com/modelica/fmi-cross-check/raw/master/fmus/2.0/me/win64/MapleSim/2015.1/CoupledClutches/CoupledClutches.fmu', + checksum='af8f8ca4d7073b2d6207d8eea4a3257e3a23a69089f03181236ee3ecf13ff77f' + ) - def test_validate_start_values(self): + with pytest.raises(ValidationError) as exception_info: + read_model_description(filename, validate=True, validate_variable_names=True) - filename = download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') + assert len(exception_info.value.problems) == 124 - with self.assertRaises(Exception) as context: - simulate_fmu(filename, start_values={'clutch1.sa': 0.0}) +def test_validate_start_values(): - self.assertEqual('The start values for the following variables could not be set: clutch1.sa', str(context.exception)) + filename = download_test_file('2.0', 'ModelExchange', 'MapleSim', '2016.2', 'CoupledClutches', 'CoupledClutches.fmu') + with pytest.raises(Exception) as exception_info: + simulate_fmu(filename, start_values={'clutch1.sa': 0.0}) -if __name__ == '__main__': - unittest.main() + assert 'The start values for the following variables could not be set: clutch1.sa' == str(exception_info.value)