Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
RWTH-EBC
GitHub Repository: RWTH-EBC/ebcpy
Path: blob/master/tests/test_simulationapi.py
505 views
1
"""Test-module for all classes inside
2
ebcpy.simulationapi."""
3
4
import unittest
5
import sys
6
import os
7
from pathlib import Path
8
import shutil
9
import numpy as np
10
import pandas as pd
11
from pydantic import ValidationError
12
from ebcpy.simulationapi import dymola_api, fmu, Variable
13
from ebcpy import load_time_series_data
14
15
16
def postprocess_mat_result(mat_result_file, variable_names, n):
17
"""
18
Dummy function to test postprocessing of mat results.
19
Loads only given variable names and returns the last n values.
20
21
Must be defined globally to allow multiprocessing.
22
"""
23
return load_time_series_data(mat_result_file, variable_names=variable_names).iloc[-n:]
24
25
26
class TestVariable(unittest.TestCase):
27
28
def test_min_max(self):
29
"""Test the boundaries for variables"""
30
for _type in [float, int, bool]:
31
for _value in [1, 1.0, "1", True]:
32
_var = Variable(
33
value=_value,
34
type=_type
35
)
36
self.assertIsInstance(Variable(
37
value=_value, type=_type,
38
min=_var.value - 1
39
).min, _type)
40
self.assertIsInstance(Variable(
41
value=_value, type=_type,
42
max=_var.value
43
).max, _type)
44
with self.assertRaises(ValidationError):
45
Variable(value=1, max="1c", type=int)
46
with self.assertRaises(ValidationError):
47
Variable(value=1, min="1c", type=int)
48
self.assertIsNone(Variable(value="s", type=str, max=0).max)
49
self.assertIsNone(Variable(value="s", type=str, min=0).min)
50
51
def test_value(self):
52
"""Test value conversion"""
53
for _type in [float, int, bool]:
54
for _value in [1, 1.0, "1", True]:
55
self.assertIsInstance(
56
Variable(value=_value, type=_type).value,
57
_type
58
)
59
with self.assertRaises(TypeError):
60
Variable(value="10c", type="int")
61
self.assertIsInstance(Variable(value="Some String", type=str).value, str)
62
63
64
class PartialTestSimAPI(unittest.TestCase):
65
66
def setUp(self) -> None:
67
self.sim_api = None
68
self.parameters = {}
69
self.new_sim_setup = {}
70
self.data_dir = Path(__file__).parent.joinpath("data")
71
self.example_sim_dir = self.data_dir.joinpath("testzone")
72
if not os.path.exists(self.example_sim_dir):
73
os.mkdir(self.example_sim_dir)
74
if self.__class__ == PartialTestSimAPI:
75
self.skipTest("Just a partial class")
76
77
def start_api(self, save_logs: bool, **kwargs):
78
raise NotImplementedError
79
80
def test_simulate(self):
81
"""Test simulate functionality of dymola api"""
82
self.sim_api.set_sim_setup({"start_time": 0.0,
83
"stop_time": 10.0})
84
result_names = list(self.sim_api.states.keys())[:5]
85
self.sim_api.result_names = result_names
86
res = self.sim_api.simulate() # Test with no parameters
87
self.assertIsInstance(res, pd.DataFrame)
88
self.assertEqual(len(res.columns), len(result_names))
89
res = self.sim_api.simulate(return_option='last_point')
90
self.assertIsInstance(res, dict)
91
res = self.sim_api.simulate(parameters=self.parameters,
92
return_option='savepath')
93
self.assertTrue(os.path.isfile(res))
94
self.assertIsInstance(res, str)
95
res = self.sim_api.simulate(parameters=self.parameters,
96
return_option='savepath',
97
savepath=os.path.join(self.example_sim_dir, "my_new_folder"),
98
result_file_name="my_other_name")
99
self.assertTrue(os.path.isfile(res))
100
self.assertIsInstance(res, str)
101
102
def test_savepath_handling(self):
103
"""Test correct errors for wrong savepath allocation"""
104
self.sim_api.set_sim_setup({"start_time": 0.0,
105
"stop_time": 10.0})
106
result_names = list(self.sim_api.states.keys())[:5]
107
self.sim_api.result_names = result_names
108
_some_par = list(self.sim_api.parameters.keys())[0]
109
pars = {_some_par: self.sim_api.parameters[_some_par].value}
110
parameters = [pars for i in range(2)]
111
with self.assertRaises(ValueError):
112
res = self.sim_api.simulate(parameters=parameters,
113
return_option='savepath')
114
with self.assertRaises(ValueError):
115
res = self.sim_api.simulate(parameters=parameters,
116
return_option='savepath',
117
result_file_name=["t", "t"],
118
savepath=[self.example_sim_dir, self.example_sim_dir])
119
120
# Test multiple result_file_names
121
_saves = [os.path.join(self.example_sim_dir, f"test_{i}") for i in range(len(parameters))]
122
_save_tests = [
123
os.path.join(self.example_sim_dir, "my_save_folder"),
124
_saves,
125
_saves
126
]
127
_names = [f"test_{i}" for i in range(len(parameters))]
128
_name_tests = [
129
_names,
130
"test",
131
_names
132
]
133
for _save, _name in zip(_save_tests, _name_tests):
134
res = self.sim_api.simulate(
135
parameters=parameters,
136
return_option="savepath",
137
savepath=_save,
138
result_file_name=_name
139
)
140
for r in res:
141
self.assertTrue(os.path.isfile(r))
142
self.assertIsInstance(r, str)
143
144
def test_set_working_directory(self):
145
"""Test set_working_directory functionality of dymola api"""
146
# Test the setting of the function
147
self.sim_api.set_working_directory(self.data_dir)
148
self.assertEqual(self.data_dir, self.sim_api.working_directory)
149
# Test setting a str:
150
self.sim_api.set_working_directory(str(self.data_dir))
151
self.assertEqual(self.data_dir, self.sim_api.working_directory)
152
153
def test_set_sim_setup(self):
154
"""Test set_sim_setup functionality of fmu api"""
155
self.sim_api.set_sim_setup(sim_setup=self.new_sim_setup)
156
for key, value in self.new_sim_setup.items():
157
self.assertEqual(self.sim_api.sim_setup.model_dump()[key],
158
value)
159
with self.assertRaises(ValidationError):
160
self.sim_api.set_sim_setup(sim_setup={"NotAValidKey": None})
161
with self.assertRaises(ValidationError):
162
self.sim_api.set_sim_setup(sim_setup={"stop_time": "not_a_float_or_int"})
163
164
def test_no_log(self):
165
self.sim_api.close()
166
import logging
167
for handler in self.sim_api.logger.handlers:
168
handler.flush()
169
handler.close()
170
logger = logging.getLogger(self.sim_api.__class__.__name__)
171
while len(logger.handlers) > 0:
172
for handler in logger.handlers:
173
logger.removeHandler(handler)
174
log_file = self.example_sim_dir.joinpath(f"{self.sim_api.__class__.__name__}.log")
175
if os.path.exists(log_file):
176
os.remove(log_file)
177
self.start_api(save_logs=False, mos_script=self.data_dir.joinpath("mos_script_test.mos"))
178
self.sim_api.logger.error("This log should not be saved")
179
self.assertFalse(os.path.exists(log_file))
180
181
def tearDown(self):
182
"""Delete all files created while testing"""
183
try:
184
self.sim_api.close()
185
except AttributeError:
186
pass
187
try:
188
shutil.rmtree(self.example_sim_dir)
189
except (FileNotFoundError, PermissionError):
190
pass
191
192
193
class PartialTestDymolaAPI(PartialTestSimAPI):
194
n_cpu = None
195
196
def setUp(self) -> None:
197
super().setUp()
198
if self.__class__ == PartialTestDymolaAPI:
199
self.skipTest("Just a partial class")
200
ebcpy_test_package_dir = self.data_dir.joinpath("TestModelVariables.mo")
201
self.packages = [ebcpy_test_package_dir]
202
self.parameters = {"test_real": 10.0,
203
"test_int": 5,
204
"test_bool": 0,
205
"test_enum": 2
206
}
207
self.new_sim_setup = {
208
"solver": "Dassl",
209
"tolerance": 0.001
210
}
211
self.start_api()
212
213
def start_api(self, save_logs: bool = True, **kwargs):
214
# Just for tests in the gitlab-ci:
215
if "linux" in sys.platform:
216
dymola_exe_path = "/usr/local/bin/dymola"
217
else:
218
dymola_exe_path = None
219
mos_script = kwargs.get("mos_script", self.data_dir.joinpath("mos_script_test.mos"))
220
model_name = kwargs.get("model_name", "TestModelVariables")
221
extract_variables = kwargs.get("extract_variables", True)
222
223
try:
224
self.sim_api = dymola_api.DymolaAPI(
225
working_directory=self.example_sim_dir,
226
model_name=model_name,
227
packages=self.packages,
228
dymola_exe_path=dymola_exe_path,
229
n_cpu=self.n_cpu,
230
mos_script_pre=mos_script,
231
mos_script_post=mos_script,
232
save_logs=save_logs,
233
extract_variables=extract_variables
234
)
235
except (FileNotFoundError, ImportError, ConnectionError) as error:
236
self.skipTest(f"Could not load the dymola interface "
237
f"on this machine. Error message: {error}")
238
239
def test_no_model_none(self):
240
self.sim_api.close()
241
self.start_api(
242
mos_script=None, model_name=None,
243
)
244
with self.assertRaises(ValueError):
245
self.sim_api.simulate()
246
self.sim_api.simulate(model_names=["TestModelVariables"], parameters=self.parameters)
247
248
def test_no_extract_model_vars(self):
249
self.sim_api.close()
250
self.start_api(
251
mos_script=None, extract_variables=False
252
)
253
self.assertEqual(self.sim_api.variables, [])
254
255
def test_close(self):
256
"""Test close functionality of dymola api"""
257
self.sim_api.close()
258
self.assertIsNone(self.sim_api.dymola)
259
260
def test_parameters(self):
261
"""Test non-existing parameter"""
262
self.sim_api.result_names.extend(list(self.parameters.keys()))
263
self.sim_api.result_names.extend(["test_out", "test_local"])
264
res = self.sim_api.simulate(parameters=self.parameters,
265
return_option="last_point")
266
for k, v in res.items():
267
if k in self.parameters:
268
self.assertEqual(v, self.parameters[k])
269
self.assertEqual(res["test_local"], 0)
270
self.assertEqual(res["test_out"], self.parameters["test_int"])
271
# Check boolean conversion:
272
res_2 = self.sim_api.simulate(parameters={
273
"test_bool": True,
274
}, return_option="last_point")
275
res_1 = self.sim_api.simulate(parameters={
276
"test_bool": 1,
277
}, return_option="last_point")
278
self.assertEqual(res_1, res_2)
279
# Wrong types
280
with self.assertRaises(TypeError):
281
self.sim_api.simulate(parameters={"test_bool": "True"})
282
# Wrong parameter
283
with self.assertRaises(KeyError):
284
self.sim_api.simulate(
285
parameters={"C2": 10},
286
return_option='savepath'
287
)
288
# Model with no parameters:
289
with self.assertRaises(ValueError):
290
self.sim_api.parameters = {}
291
self.sim_api.simulate() # Test with no parameters
292
293
def test_structural_parameters(self):
294
"""Test structural parameters"""
295
some_val = np.random.rand()
296
self.sim_api.result_names = ["test_local"]
297
res = self.sim_api.simulate(
298
parameters={"test_real_eval": some_val},
299
return_option="last_point",
300
structural_parameters=["test_real_eval"]
301
)
302
self.assertEqual(res["test_local"], some_val)
303
res = self.sim_api.simulate(
304
parameters={"test_real_eval": some_val},
305
return_option="last_point"
306
)
307
self.assertEqual(res["test_local"], some_val)
308
309
def test_variables_to_save(self):
310
all_false = dymola_api.ExperimentSetupOutput(
311
states=False,
312
derivatives=False,
313
inputs=False,
314
outputs=False,
315
auxiliaries=False,
316
)
317
parameters = list(self.sim_api.parameters.keys())
318
cases_to_test = [
319
{
320
"experiment_setup": all_false.model_copy(update={"outputs": True}),
321
"variables": list(self.sim_api.outputs.keys()) + parameters
322
},
323
{
324
"experiment_setup": all_false.model_copy(update={"inputs": True}),
325
"variables": list(self.sim_api.inputs.keys()) + parameters
326
},
327
{
328
"experiment_setup": all_false.model_copy(
329
update={"states": True, "derivatives": True, "auxiliaries": True}
330
),
331
"variables": list(self.sim_api.states.keys()) + parameters
332
},
333
]
334
self.sim_api.set_sim_setup({"start_time": 0.0,
335
"stop_time": 10.0})
336
for case in cases_to_test:
337
variables = case["variables"]
338
self.sim_api.update_experiment_setup_output(case["experiment_setup"])
339
res = self.sim_api.simulate(
340
parameters=self.parameters,
341
return_option='savepath'
342
)
343
df = load_time_series_data(res, variables_names=variables)
344
self.assertEqual(sorted(variables), sorted(df.columns))
345
346
def test_postprocessing_injection(self):
347
"""Test injection of postprocessing function for mats"""
348
self.sim_api.set_sim_setup({"start_time": 0.0,
349
"stop_time": 10.0})
350
result_names = list(self.sim_api.states.keys())[:5]
351
self.sim_api.result_names = result_names
352
n_values_to_return = np.random.randint(1, 4)
353
kwargs_postprocessing = {
354
"variable_names": result_names[:2],
355
"n": n_values_to_return
356
}
357
res = self.sim_api.simulate(
358
parameters=self.parameters,
359
return_option='savepath',
360
postprocess_mat_result=postprocess_mat_result,
361
kwargs_postprocessing=kwargs_postprocessing
362
)
363
self.assertIsInstance(res, pd.DataFrame)
364
self.assertEqual(len(res.index), n_values_to_return)
365
self.assertEqual(len(res.columns), 2)
366
367
368
class TestDymolaAPIMultiCore(PartialTestDymolaAPI):
369
"""Test-Class for the DymolaAPI class on single core."""
370
371
n_cpu = 2
372
373
374
class TestDymolaAPISingleCore(PartialTestDymolaAPI):
375
"""Test-Class for the DymolaAPI class on multi core."""
376
377
n_cpu = 1
378
379
380
class TestFMUAPI(PartialTestSimAPI):
381
"""Test-Class for the FMUAPI class."""
382
383
n_cpu = None
384
385
def setUp(self):
386
"""Called before every test.
387
Used to setup relevant paths and APIs etc."""
388
super().setUp()
389
if self.__class__ == TestFMUAPI:
390
self.skipTest("Just a partial class")
391
self.start_api()
392
393
def _get_model_name(self):
394
if "win" in sys.platform:
395
return self.data_dir.joinpath("PumpAndValve_windows.fmu")
396
return self.data_dir.joinpath("PumpAndValve_linux.fmu")
397
398
def start_api(self, save_logs: bool = True, **kwargs):
399
self.sim_api = fmu.FMU_API(
400
working_directory=self.example_sim_dir,
401
model_name=self._get_model_name(),
402
save_logs=save_logs
403
)
404
405
def test_relative_working_directory(self):
406
self.sim_api.close()
407
self.sim_api = fmu.FMU_API(
408
# Complex solution to enable tests from any cwd
409
working_directory=Path(__file__).parent.relative_to(Path().absolute()).joinpath("data", "testzone"),
410
model_name=self._get_model_name()
411
)
412
self.assertEqual(self.sim_api.working_directory, self.example_sim_dir)
413
414
def test_no_working_directory(self):
415
self.sim_api.close()
416
self.sim_api = fmu.FMU_API(model_name=self._get_model_name())
417
self.assertEqual(self.sim_api.working_directory, self.data_dir)
418
419
def test_close(self):
420
"""Test close functionality of fmu api"""
421
# pylint: disable=protected-access
422
self.sim_api.close()
423
self.assertIsNone(self.sim_api._unzip_dir)
424
425
426
class TestFMUAPISingleCore(TestFMUAPI):
427
"""Test-Class for the FMU_API class on single core"""
428
429
n_cpu = 1
430
431
432
class TestFMUAPIMultiCore(TestFMUAPI):
433
"""Test-Class for the FMU_API class on multi core"""
434
435
n_cpu = 2
436
437
438
if __name__ == "__main__":
439
unittest.main()
440
441