def main(): parser = argparse.ArgumentParser(prog='pade-fmi') parser.add_argument('-v', '--version', action='version', version=__version__) parser.add_argument('project_files', metavar='filename', type=str, nargs='+', help='File containing FMU settings') parser.add_argument('-d', '--dest', dest='dest', help='Where to save the FMU.', default='.') args = parser.parse_args() FmuBuilder.build_FMU( script_file=pade.fmi.fmi_wrapper.__file__, dest=args.dest, project_files=args.project_files + [os.path.join(os.path.dirname(__file__), 'requirements.txt')], needsExecutionTool=False)
def test_deploy(tmp_path, test_manager, requirements, test_requirements, expected): dummy_requirements = """numpy=1.16 scipy """ if requirements is not None: with TemporaryDirectory() as tempd: requirements_file = Path(tempd) / requirements requirements_file.write_text(dummy_requirements) fmu = FmuBuilder.build_FMU(PYTHON_SLAVE, dest=tmp_path, project_files=[ requirements_file, ]) else: fmu = FmuBuilder.build_FMU(PYTHON_SLAVE, dest=tmp_path) assert fmu.exists() with patch("subprocess.run") as run: if isinstance(expected, type) and issubclass(expected, Exception): with pytest.raises(expected): deploy(fmu, environment=test_requirements, package_manager=test_manager) run.assert_not_called() else: deploy(fmu, environment=test_requirements, package_manager=test_manager) run.assert_called_once() assert (test_manager or expected) in " ".join(run.call_args[0][0])
def test_integration_throw_py_error(tmp_path): fmpy = pytest.importorskip( "fmpy", reason="fmpy is not available for testing the produced FMU") slave_code = """from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Causality, Integer, Real, Boolean, String class PythonSlaveWithException(Fmi2Slave): def __init__(self, **kwargs): super().__init__(**kwargs) self.realIn = 22.0 self.realOut = 0.0 self.register_variable(Real("realIn", causality=Fmi2Causality.input)) self.register_variable(Real("realOut", causality=Fmi2Causality.output)) def do_step(self, current_time, step_size): raise RuntimeError() return True """ script_file = tmp_path / "orig" / "slavewithexception.py" script_file.parent.mkdir(parents=True, exist_ok=True) script_file.write_text(slave_code) fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path) assert fmu.exists() with pytest.raises(Exception): fmpy.simulate_fmu(str(fmu), stop_time=1.0)
def test_integration_get_state(tmp_path): script_file = Path(__file__).parent / DEMO fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false", canGetAndSetFMUstate="true") assert fmu.exists() model = pyfmi.load_fmu(str(fmu)) vr = model.get_model_variables()["realOut"].value_reference dt = 0.1 t = 0.0 def step_model(): nonlocal t model.do_step(t, dt, True) t += dt model.initialize() step_model() state = model.get_fmu_state() assert model.get_real([vr])[0] == pytest.approx(dt, rel=1e-7) step_model() assert model.get_real([vr])[0] == pytest.approx(dt * 2, rel=1e-7) model.set_fmu_state(state) assert model.get_real([vr])[0] == pytest.approx(dt, rel=1e-7) step_model() assert model.get_real([vr])[0] == pytest.approx(dt * 3, rel=1e-7) model.free_fmu_state(state)
def build(): with tempfile.TemporaryDirectory() as project_dir: project_dir = Path(project_dir) project_files = set() for f in pfiles: full_name = project_dir / f if full_name.suffix: full_name.parent.mkdir(parents=True, exist_ok=True) full_name.write_text("dummy content") else: full_name.mkdir(parents=True, exist_ok=True) # Add subfolder and not file if common parent exits to_remove = None for p in project_files: if p.parent == full_name.parent or p == full_name.parent: to_remove = p break if to_remove is None: project_files.add(full_name) else: project_files.remove(to_remove) project_files.add(full_name.parent) return FmuBuilder.build_FMU(script_file, dest=tmp_path, project_files=project_files)
def build(): with tempfile.TemporaryDirectory() as documentation_dir: doc_dir = Path(documentation_dir) license_file = doc_dir / "licenses" / "license.txt" license_file.parent.mkdir() license_file.write_text("Dummy license") index_file = doc_dir / "index.html" index_file.write_text("dummy index") return FmuBuilder.build_FMU(script_file, dest=tmp_path, documentation_folder=doc_dir)
def test_integration_throw_py_error(tmp_path): fmpy = pytest.importorskip( "fmpy", reason="fmpy is not available for testing the produced FMU") script_file = Path(__file__).parent / "slaves/PythonSlaveWithException.py" fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path) assert fmu.exists() with pytest.raises(Exception): fmpy.simulate_fmu(str(fmu), stop_time=1.0)
def test_simple_integration_fmpy(tmp_path): fmpy = pytest.importorskip( "fmpy", reason="fmpy is not available for testing the produced FMU") script_file = Path(__file__).parent / "slaves/pythonslave.py" fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path) assert fmu.exists() res = fmpy.simulate_fmu(str(fmu), stop_time=2.0) assert res["realOut"][-1] == pytest.approx(res["time"][-1], rel=1e-7)
def test_integration_demo(tmp_path): script_file = Path(__file__).parent / "slaves/pythonslave.py" fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false") assert fmu.exists() model = pyfmi.load_fmu(str(fmu)) res = model.simulate(final_time=0.5) assert res["realOut"][-1] == pytest.approx(res["time"][-1], rel=1e-7)
def test_integration_get_serialize_state(tmp_path): fmpy = pytest.importorskip( "fmpy", reason="fmpy is not available for testing the produced FMU") script_file = Path(__file__).parent / DEMO fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, canGetAndSetFMUstate="true", canSerializeFMUstate="true") assert fmu.exists() model_description = fmpy.read_model_description(fmu) unzip_dir = fmpy.extract(fmu) model = fmpy.fmi2.FMU2Slave( guid=model_description.guid, unzipDirectory=unzip_dir, modelIdentifier=model_description.coSimulation.modelIdentifier, instanceName='instance1') realOut = filter(lambda var: var.name == "realOut", model_description.modelVariables) vrs = list(map(lambda var: var.valueReference, realOut)) t = 0.0 dt = 0.1 def step_model(): nonlocal t model.doStep(t, dt) t += dt model.instantiate() model.setupExperiment() model.enterInitializationMode() model.exitInitializationMode() step_model() state = model.getFMUstate() assert model.getReal(vrs)[0] == pytest.approx(dt, rel=1e-7) step_model() assert model.getReal(vrs)[0] == pytest.approx(dt * 2, rel=1e-7) model.setFMUstate(state) assert model.getReal(vrs)[0] == pytest.approx(dt, rel=1e-7) step_model() assert model.getReal(vrs)[0] == pytest.approx(dt * 3, rel=1e-7) serialize_fmu_state = model.serializeFMUstate(state) model.freeFMUstate(state) de_serialize_fmu_state = model.deSerializeFMUstate(serialize_fmu_state) model.setFMUstate(de_serialize_fmu_state) assert model.getReal(vrs)[0] == pytest.approx(dt, rel=1e-7) model.freeFMUstate(de_serialize_fmu_state) model.terminate()
def test_zip_content(tmp_path): script_name = "pythonslave.py" script_file = Path(__file__).parent / "slaves" / script_name fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path) assert fmu.exists() assert zipfile.is_zipfile(fmu) with zipfile.ZipFile(fmu) as files: names = files.namelist() assert "modelDescription.xml" in names assert "/".join(("resources", script_name)) in names module_file = "/".join(("resources", "slavemodule.txt")) assert module_file in names nfiles = 20 if FmuBuilder.has_binary(): assert ("/".join( ("binaries", get_platform(), f"PythonSlave.{lib_extension}")) in names) else: nfiles -= 1 # Check sources src_folder = Path(pythonfmu.__path__[0]) / "pythonfmu-export" for f in itertools.chain( src_folder.rglob("*.hpp"), src_folder.rglob("*.cpp"), src_folder.rglob("CMakeLists.txt"), ): assert "/".join( ("sources", f.relative_to(src_folder).as_posix())) in names # Check pythonfmu is embedded pkg_folder = Path(pythonfmu.__path__[0]) for f in pkg_folder.rglob("*.py"): relative_f = f.relative_to(pkg_folder).as_posix() if "test" not in relative_f: assert "/".join( ("resources", "pythonfmu", relative_f)) in names assert len( names ) >= nfiles # Library + python script + XML + module name + sources with files.open(module_file) as myfile: assert myfile.read() == b"pythonslave"
def test_integration_has_local_dep(tmp_path): slave_code = """import math from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Causality, Integer, Real, Boolean, String from localmodule import get_amplitude, get_time_constant class PythonSlaveWithDep(Fmi2Slave): def __init__(self, **kwargs): super().__init__(**kwargs) self.realIn = 22.0 self.realOut = 0.0 self.register_variable(Real("realIn", causality=Fmi2Causality.input)) self.register_variable(Real("realOut", causality=Fmi2Causality.output)) def do_step(self, current_time, step_size): self.realOut = self.realIn * get_amplitude() * math.exp((current_time + step_size) / get_time_constant()) return True """ local_module = """def get_amplitude(): return 5. def get_time_constant(): return 0.1 """ script_file = tmp_path / "orig" / "slavewithdep.py" script_file.parent.mkdir(parents=True, exist_ok=True) script_file.write_text(slave_code) local_file = script_file.parent / "localmodule.py" local_file.write_text(local_module) fmu = FmuBuilder.build_FMU( script_file, dest=tmp_path, project_files=[local_file], needsExecutionTool="false", ) assert fmu.exists() model = pyfmi.load_fmu(str(fmu)) res = model.simulate(final_time=0.5) assert res["realOut"][-1] == pytest.approx(22.0 * 5.0 * math.exp(res["time"][-1] / 0.1), rel=1e-7)
def test_integration_read_from_file(tmp_path): script_file = Path(__file__).parent / "slaves/pythonslave_read_file.py" project_file = Path(__file__).parent / "data/hello.txt" fmu = FmuBuilder.build_FMU(script_file, project_files=[project_file], dest=tmp_path, needsExecutionTool="false") assert fmu.exists() model = pyfmi.load_fmu(str(fmu)) variables = model.get_model_variables() var = variables["file_content"] model_value = model.get_string([var.value_reference])[0] with (open(project_file, 'r')) as file: data = file.read() assert model_value == data
def test_integration_has_local_dep(tmp_path): script_file = Path(__file__).parent / "slaves/slavewithdep.py" local_file = Path(__file__).parent / "slaves/localmodule.py" fmu = FmuBuilder.build_FMU( script_file, dest=tmp_path, project_files=[local_file], needsExecutionTool="false", ) assert fmu.exists() model = pyfmi.load_fmu(str(fmu)) res = model.simulate(final_time=0.5) assert res["realOut"][-1] == pytest.approx(22.0 * 5.0 * math.exp(res["time"][-1] / 0.1), rel=1e-7)
def test_integration_reset(tmp_path): script_file = Path(__file__).parent / "slaves/pythonslave.py" fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false") assert fmu.exists() vr = 5 # realOut dt = 0.1 model = pyfmi.load_fmu(str(fmu)) initial_value = model.get_real([vr])[0] assert initial_value == pytest.approx(3.0, rel=1e-7) model.do_step(0.0, dt, True) read = model.get_real([vr])[0] assert read == pytest.approx(dt, rel=1e-7) model.reset() read = model.get_real([vr])[0] assert read == pytest.approx(initial_value, rel=1e-7)
def build(): with tempfile.TemporaryDirectory() as project_dir: project_dir = Path(project_dir) script_file = project_dir / DEMO with open(orig_script_file) as script_f: script_file.write_text(script_f.read()) for f in pfiles: full_name = project_dir / f if full_name.suffix: full_name.parent.mkdir(parents=True, exist_ok=True) full_name.write_text("dummy content") else: full_name.mkdir(parents=True, exist_ok=True) return FmuBuilder.build_FMU( script_file, dest=tmp_path, project_files=[script_file.parent] )
def buildFMU(self, dest: FilePath = ".") -> Path: """ Build the FMU @ In, dest, FilePath, destination of the built fmu @ Out, built, Path, the path of the built fmu """ if not os.path.exists(self.serializedModel): self.raiseAnError(ValueError, "No such file {}".format(self.serializedModel)) self._options["dest"] = os.path.dirname(dest) self._options["file_name"] = os.path.basename(dest) #Get files needed for running model self._options["project_files"] = {self.serializedModel}.union(self.model.getSerializationFiles()) scriptFile = Path(self._temp) / (self.model.name + "_RAVENfmu.py") with open(scriptFile, "+w") as f: f.write(self.createModelHandler()) self._options["script_file"] = scriptFile built = FmuBuilder.build_FMU(**self._options) if not self.keepModule: self._temp.cleanup() return built
def test_integration_set(tmp_path): script_file = Path(__file__).parent / DEMO fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false") assert fmu.exists() model = pyfmi.load_fmu(str(fmu)) to_test = { "intParam": 20, "realIn": 1.0 / 3.0, "booleanParameter": True, "stringParameter": "cat", "container.someReal": 42.0, "container.subContainer.someInteger": 421 } variables = model.get_model_variables() for key, value in to_test.items(): var = variables[key] if var.type == pyfmi.fmi.FMI2_INTEGER: model.set_integer([var.value_reference], [value]) model_value = model.get_integer([var.value_reference])[0] elif var.type == pyfmi.fmi.FMI2_REAL: model.set_real([var.value_reference], [value]) model_value = model.get_real([var.value_reference])[0] elif var.type == pyfmi.fmi.FMI2_BOOLEAN: model.set_boolean([var.value_reference], [value]) model_value = model.get_boolean([var.value_reference])[0] elif var.type == pyfmi.fmi.FMI2_STRING: model.set_string([var.value_reference], [value]) model_value = model.get_string([var.value_reference])[0] else: pytest.xfail("Unsupported type") assert model_value == value
def test_integration_get(tmp_path): script_file = Path(__file__).parent / "slaves/pythonslave.py" fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false") assert fmu.exists() model = pyfmi.load_fmu(str(fmu)) to_test = { "intParam": 42, "intOut": 23, "realOut": 3.0, "booleanVariable": True, "stringVariable": "Hello World!", "realIn": 2.0 / 3.0, "booleanParameter": False, "stringParameter": "dog", "container.someReal": 99.0, "container.subContainer.someInteger": -15 } model_value = None variables = model.get_model_variables() for key, value in to_test.items(): var = variables[key] if var.type == pyfmi.fmi.FMI2_INTEGER: model_value = model.get_integer([var.value_reference])[0] elif var.type == pyfmi.fmi.FMI2_REAL: model_value = model.get_real([var.value_reference])[0] elif var.type == pyfmi.fmi.FMI2_BOOLEAN: model_value = model.get_boolean([var.value_reference])[0] elif var.type == pyfmi.fmi.FMI2_STRING: model_value = model.get_string([var.value_reference])[0] else: pytest.xfail("Unsupported type") assert model_value == value
def test_integration_multiple_fmus_pyfmi(tmp_path): slave1_code = """import math from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Causality, Integer, Real, Boolean, String class Slave1(Fmi2Slave): def __init__(self, **kwargs): super().__init__(**kwargs) self.realIn = 22.0 self.realOut = 0.0 self.register_variable(Real("realIn", causality=Fmi2Causality.input)) self.register_variable(Real("realOut", causality=Fmi2Causality.output)) def do_step(self, current_time, step_size): self.log("Do step on Slave1.") self.realOut = self.realIn * 5.0 * (1.0 - math.exp(-1.0 * (current_time + step_size) / 0.1)) return True """ slave2_code = """from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Causality, Integer, Real, Boolean, String class Slave2(Fmi2Slave): def __init__(self, **kwargs): super().__init__(**kwargs) self.realIn = 22.0 self.realOut = 0.0 self.register_variable(Real("realIn", causality=Fmi2Causality.input)) self.register_variable(Real("realOut", causality=Fmi2Causality.output)) def do_step(self, current_time, step_size): self.log("Do step on Slave2.") self.realOut = -2.0 * self.realIn return True """ script1_file = tmp_path / "orig" / "slave1.py" script1_file.parent.mkdir(parents=True, exist_ok=True) script1_file.write_text(slave1_code) fmu1 = FmuBuilder.build_FMU( script1_file, dest=tmp_path, needsExecutionTool="false" ) assert fmu1.exists() script2_file = tmp_path / "orig" / "slave2.py" script2_file.write_text(slave2_code) fmu2 = FmuBuilder.build_FMU( script2_file, dest=tmp_path, needsExecutionTool="false" ) assert fmu2.exists() model1 = pyfmi.load_fmu(str(fmu1), log_level=7) model2 = pyfmi.load_fmu(str(fmu2), log_level=7) connections = [(model1, "realOut", model2, "realIn")] sim = pyfmi.Master([model1, model2], connections) res = sim.simulate(final_time=0.1, options={'step_size': 0.025}) res1 = res[model1] assert res1["realOut"][-1] == pytest.approx( 22.0 * 5.0 * (1.0 - math.exp(-1.0 * res1["time"][-1] / 0.1)), rel=1e-7 ) res2 = res[model2] assert res2["realIn"][-1] == pytest.approx(res1["realOut"][-1]) # pyfmi master algorithm seems all fmus at once with the output from the previous time step assert res2["realOut"][-1] == pytest.approx(-2.0 * res2["realIn"][-2])
def test_log_categories(tmp_path, debug_logging, categories): name = "PythonSlaveDebugCategories" if debug_logging else "PythonSlaveCategories" message = "log message" log_calls = [ (f"{status.name.upper()} - {debug} - {message}", status, debug) for debug, status in itertools.product([True, False], Fmi2Status) ] fmu_calls = "\n".join([ ' self.log("{}", Fmi2Status.{}, None, {})'.format( c[0], c[1].name, c[2]) for c in log_calls ]) slave_code = f"""from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Status, Fmi2Causality, Integer, Real, Boolean, String class {name}(Fmi2Slave): def __init__(self, **kwargs): super().__init__(**kwargs) self.realIn = 22.0 self.realOut = 0.0 self.register_variable(Real("realIn", causality=Fmi2Causality.input)) self.register_variable(Real("realOut", causality=Fmi2Causality.output)) def do_step(self, current_time, step_size): {fmu_calls} return True """ script_file = tmp_path / "orig" / f"{name.lower()}.py" script_file.parent.mkdir(parents=True, exist_ok=True) script_file.write_text(slave_code) fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path) assert fmu.exists() logger = MagicMock() # Load the model callbacks = fmpy.fmi2.fmi2CallbackFunctions() callbacks.logger = fmpy.fmi2.fmi2CallbackLoggerTYPE(logger) callbacks.allocateMemory = fmpy.fmi2.fmi2CallbackAllocateMemoryTYPE( fmpy.fmi2.allocateMemory) callbacks.freeMemory = fmpy.fmi2.fmi2CallbackFreeMemoryTYPE( fmpy.fmi2.freeMemory) model_description = fmpy.read_model_description(fmu) unzip_dir = fmpy.extract(fmu) model = fmpy.fmi2.FMU2Slave( guid=model_description.guid, unzipDirectory=unzip_dir, modelIdentifier=model_description.coSimulation.modelIdentifier, instanceName='instance1') # Instantiate the model model.instantiate(callbacks=callbacks) model.setDebugLogging(debug_logging, categories) model.setupExperiment() model.enterInitializationMode() model.exitInitializationMode() # Execute the model model.doStep(0., 0.1) # Clean the model model.terminate() expected_calls = [] for c in filter(lambda c: debug_logging or not c[2], log_calls): category = f"logStatus{c[1].name.capitalize()}" if category not in Fmi2Slave.log_categories: category = "logAll" if len(categories) == 0 or category in categories: expected_calls.append( call( logger.call_args[0][0], # Don't test the first argument b'instance1', int(c[1]), bytes(category, encoding="utf-8"), bytes(c[0], encoding="utf-8"))) n_calls = len(Fmi2Status) if len(categories) == 0 else len(categories) assert logger.call_count == n_calls * (1 + int(debug_logging)) logger.assert_has_calls(expected_calls)
def test_logger(tmp_path, debug_logging): name = "PythonSlaveWithDebugLogger" if debug_logging else "PythonSlaveWithLogger" category = "category" message = "log message" log_calls = [ (f"{status.name.upper()} - {debug} - {message}", status, category, debug) for debug, status in itertools.product([True, False], Fmi2Status) ] fmu_calls = "\n".join([ ' self.log("{}", Fmi2Status.{}, "{}", {})'.format( c[0], c[1].name, c[2], c[3]) for c in log_calls ]) slave_code = f"""from pythonfmu.fmi2slave import Fmi2Slave, Fmi2Status, Fmi2Causality, Integer, Real, Boolean, String class {name}(Fmi2Slave): def __init__(self, **kwargs): super().__init__(**kwargs) self.realIn = 22.0 self.realOut = 0.0 self.register_variable(Real("realIn", causality=Fmi2Causality.input)) self.register_variable(Real("realOut", causality=Fmi2Causality.output)) def do_step(self, current_time, step_size): {fmu_calls} return True """ script_file = tmp_path / "orig" / f"{name.lower()}.py" script_file.parent.mkdir(parents=True, exist_ok=True) script_file.write_text(slave_code) fmu = FmuBuilder.build_FMU(script_file, dest=tmp_path) assert fmu.exists() logger = MagicMock() fmpy.simulate_fmu(str(fmu), stop_time=1e-3, output_interval=1e-3, logger=logger, debug_logging=debug_logging) expected_calls = [ call( logger.call_args[0][0], # Don't test the first argument bytes(name, encoding="utf-8"), int(c[1]), bytes(c[2], encoding="utf-8"), bytes(c[0], encoding="utf-8")) for c in filter(lambda c: debug_logging or not c[3], log_calls) ] assert logger.call_count == len(Fmi2Status) * (1 + int(debug_logging)) logger.assert_has_calls(expected_calls)