Skip to content

Commit 1e7e48c

Browse files
committed
[ModelicaSystemDoE] remove dependency on pandas
* no need to add aditional requirements * hint how to use poandas in the docstrings * update test to match code changes
1 parent ae68c42 commit 1e7e48c

4 files changed

Lines changed: 97 additions & 65 deletions

File tree

.pre-commit-config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,5 @@ repos:
3535
additional_dependencies:
3636
- pyparsing
3737
- types-psutil
38-
- pandas-stubs
3938
- pyzmq
4039
- numpy

OMPython/ModelicaSystem.py

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import numbers
4242
import numpy as np
4343
import os
44-
import pandas as pd
4544
import pathlib
4645
import platform
4746
import queue
@@ -1561,18 +1560,19 @@ def run_doe():
15611560
resdir = mypath / 'DoE'
15621561
resdir.mkdir(exist_ok=True)
15631562
1564-
mod_doe = OMPython.ModelicaSystemDoE(
1563+
doe_mod = OMPython.ModelicaSystemDoE(
15651564
fileName=model.as_posix(),
15661565
modelName="M",
15671566
parameters=param,
15681567
resultpath=resdir,
15691568
simargs={"override": {'stopTime': 1.0}},
15701569
)
1571-
mod_doe.prepare()
1572-
df_doe = mod_doe.get_doe()
1573-
mod_doe.simulate()
1574-
var_list = mod_doe.get_solutions()
1575-
sol_dict = mod_doe.get_solutions(var_list=var_list)
1570+
doe_mod.prepare()
1571+
doe_dict = doe_mod.get_doe()
1572+
doe_mod.simulate()
1573+
doe_sol = doe_mod.get_solutions()
1574+
1575+
# ... work with doe_df and doe_sol ...
15761576
15771577
15781578
if __name__ == "__main__":
@@ -1638,7 +1638,7 @@ def __init__(
16381638
else:
16391639
self._parameters = {}
16401640

1641-
self._sim_df: Optional[pd.DataFrame] = None
1641+
self._sim_dict: Optional[dict[str, dict[str, Any]]] = None
16421642
self._sim_task_query: queue.Queue = queue.Queue()
16431643

16441644
def prepare(self) -> int:
@@ -1663,7 +1663,7 @@ def prepare(self) -> int:
16631663
param_structure_combinations = list(itertools.product(*param_structure.values()))
16641664
param_simple_combinations = list(itertools.product(*param_simple.values()))
16651665

1666-
df_entries: list[pd.DataFrame] = []
1666+
self._sim_dict = {}
16671667
for idx_pc_structure, pc_structure in enumerate(param_structure_combinations):
16681668
mod_structure = ModelicaSystem(
16691669
fileName=self._fileName,
@@ -1709,21 +1709,18 @@ def prepare(self) -> int:
17091709
df_data = (
17101710
{
17111711
'ID structure': idx_pc_structure,
1712-
'ID simple': idx_pc_simple,
1713-
self.DF_COLUMNS_RESULT_FILENAME: resfilename,
1714-
'structural parameters ID': idx_pc_structure,
17151712
}
17161713
| sim_param_structure
17171714
| {
1718-
'non-structural parameters ID': idx_pc_simple,
1715+
'ID non-structure': idx_pc_simple,
17191716
}
17201717
| sim_param_simple
17211718
| {
17221719
self.DF_COLUMNS_RESULT_AVAILABLE: False,
17231720
}
17241721
)
17251722

1726-
df_entries.append(pd.DataFrame(data=df_data, index=[0]))
1723+
self._sim_dict[resfilename] = df_data
17271724

17281725
mscmd = mod_structure.simulate_cmd(
17291726
resultfile=resultfile.absolute().resolve(),
@@ -1735,17 +1732,26 @@ def prepare(self) -> int:
17351732

17361733
self._sim_task_query.put(mscmd)
17371734

1738-
self._sim_df = pd.concat(df_entries, ignore_index=True)
1739-
1740-
logger.info(f"Prepared {self._sim_df.shape[0]} simulation definitions for the defined DoE.")
1735+
logger.info(f"Prepared {self._sim_task_query.qsize()} simulation definitions for the defined DoE.")
17411736

1742-
return self._sim_df.shape[0]
1737+
return self._sim_task_query.qsize()
17431738

1744-
def get_doe(self) -> Optional[pd.DataFrame]:
1739+
def get_doe(self) -> Optional[dict[str, dict[str, Any]]]:
17451740
"""
1746-
Get the defined Doe as a poandas dataframe.
1741+
Get the defined DoE as a dict, where each key is the result filename and the value is a dict of simulation
1742+
settings including structural and non-structural parameters.
1743+
1744+
The following code snippet can be used to convert the data to a pandas dataframe:
1745+
1746+
```
1747+
import pandas as pd
1748+
1749+
doe_dict = doe_mod.get_doe()
1750+
doe_df = pd.DataFrame.from_dict(data=doe_dict, orient='index')
1751+
```
1752+
17471753
"""
1748-
return self._sim_df
1754+
return self._sim_dict
17491755

17501756
def simulate(
17511757
self,
@@ -1758,9 +1764,9 @@ def simulate(
17581764
"""
17591765

17601766
sim_query_total = self._sim_task_query.qsize()
1761-
if not isinstance(self._sim_df, pd.DataFrame):
1767+
if not isinstance(self._sim_dict, dict) or len(self._sim_dict) == 0:
17621768
raise ModelicaSystemError("Missing Doe Summary!")
1763-
sim_df_total = self._sim_df.shape[0]
1769+
sim_dict_total = len(self._sim_dict)
17641770

17651771
def worker(worker_id, task_queue):
17661772
while True:
@@ -1807,55 +1813,78 @@ def worker(worker_id, task_queue):
18071813
for thread in threads:
18081814
thread.join()
18091815

1810-
for row in self._sim_df.to_dict('records'):
1811-
resultfilename = row[self.DF_COLUMNS_RESULT_FILENAME]
1816+
sim_dict_done = 0
1817+
for resultfilename in self._sim_dict:
18121818
resultfile = self._resultpath / resultfilename
18131819

1814-
if resultfile.exists():
1815-
mask = self._sim_df[self.DF_COLUMNS_RESULT_FILENAME] == resultfilename
1816-
self._sim_df.loc[mask, self.DF_COLUMNS_RESULT_AVAILABLE] = True
1820+
# include check for an empty (=> 0B) result file which indicates a crash of the model executable
1821+
# see: https://github.com/OpenModelica/OMPython/issues/261
1822+
# https://github.com/OpenModelica/OpenModelica/issues/13829
1823+
if resultfile.is_file() and resultfile.stat().st_size > 0:
1824+
self._sim_dict[resultfilename][self.DF_COLUMNS_RESULTS_AVAILABLE] = True
1825+
sim_dict_done += 1
18171826

1818-
sim_df_done = self._sim_df[self.DF_COLUMNS_RESULT_AVAILABLE].sum()
1819-
logger.info(f"All workers finished ({sim_df_done} of {sim_df_total} simulations with a result file).")
1827+
logger.info(f"All workers finished ({sim_dict_done} of {sim_dict_total} simulations with a result file).")
18201828

1821-
return sim_df_total == sim_df_done
1829+
return sim_dict_total == sim_dict_done
18221830

18231831
def get_solutions(
18241832
self,
18251833
var_list: Optional[list] = None,
1826-
) -> Optional[tuple[str] | dict[str, pd.DataFrame | str]]:
1834+
) -> Optional[tuple[str] | dict[str, dict[str, np.ndarray]]]:
18271835
"""
18281836
Get all solutions of the DoE run. The following return values are possible:
18291837
1830-
* None, if there no simulation was run
1831-
18321838
* A list of variables if val_list == None
18331839
18341840
* The Solutions as dict[str, pd.DataFrame] if a value list (== val_list) is defined.
1841+
1842+
The following code snippet can be used to convert the solution data for each run to a pandas dataframe:
1843+
1844+
```
1845+
import pandas as pd
1846+
1847+
doe_sol = doe_mod.get_solutions()
1848+
for key in doe_sol:
1849+
data = doe_sol[key]['data']
1850+
if data:
1851+
doe_sol[key]['df'] = pd.DataFrame.from_dict(data=data)
1852+
else:
1853+
doe_sol[key]['df'] = None
1854+
```
1855+
18351856
"""
1836-
if self._sim_df is None:
1857+
if not isinstance(self._sim_dict, dict):
18371858
return None
18381859

1839-
if self._sim_df.shape[0] == 0 or self._sim_df[self.DF_COLUMNS_RESULT_AVAILABLE].sum() == 0:
1860+
if len(self._sim_dict) == 0:
18401861
raise ModelicaSystemError("No result files available - all simulations did fail?")
18411862

1842-
if var_list is None:
1843-
resultfilename = self._sim_df[self.DF_COLUMNS_RESULT_FILENAME].values[0]
1863+
sol_dict: dict[str, dict[str, Any]] = {}
1864+
for resultfilename in self._sim_dict:
18441865
resultfile = self._resultpath / resultfilename
1845-
return self._mod.getSolutions(resultfile=resultfile)
18461866

1847-
sol_dict: dict[str, pd.DataFrame | str] = {}
1848-
for row in self._sim_df.to_dict('records'):
1849-
resultfilename = row[self.DF_COLUMNS_RESULT_FILENAME]
1850-
resultfile = self._resultpath / resultfilename
1867+
sol_dict[resultfilename] = {}
1868+
1869+
if self._sim_dict[resultfilename][self.DF_COLUMNS_RESULTS_AVAILABLE] != True:
1870+
sol_dict[resultfilename]['msg'] = 'No result file available!'
1871+
sol_dict[resultfilename]['data'] = {}
1872+
continue
1873+
1874+
if var_list is None:
1875+
var_list_row = list(self._mod.getSolutions(resultfile=resultfile))
1876+
else:
1877+
var_list_row = var_list
18511878

18521879
try:
1853-
sol = self._mod.getSolutions(varList=var_list, resultfile=resultfile)
1854-
sol_data = {var: sol[idx] for idx, var in var_list}
1855-
sol_df = pd.DataFrame(sol_data)
1856-
sol_dict[resultfilename] = sol_df
1880+
sol = self._mod.getSolutions(varList=var_list_row, resultfile=resultfile)
1881+
sol_data = {var: sol[idx] for idx, var in enumerate(var_list_row)}
1882+
sol_dict[resultfilename]['msg'] = 'Simulation available'
1883+
sol_dict[resultfilename]['data'] = sol_data
18571884
except ModelicaSystemError as ex:
1858-
logger.warning(f"No solution for {resultfilename}: {ex}")
1859-
sol_dict[resultfilename] = str(ex)
1885+
msg = f"Error reading solution for {resultfilename}: {ex}"
1886+
logger.warning(msg)
1887+
sol_dict[resultfilename]['msg'] = msg
1888+
sol_dict[resultfilename]['data'] = {}
18601889

18611890
return sol_dict

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ license = "BSD-3-Clause OR LicenseRef-OSMC-PL-1.2 OR GPL-3.0-only"
1717
requires-python = ">=3.10"
1818
dependencies = [
1919
"numpy",
20-
"pandas",
2120
"psutil",
2221
"pyparsing",
2322
"pyzmq",

tests/test_ModelicaSystemDoE.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import numpy as np
22
import OMPython
3-
import pandas as pd
43
import pathlib
54
import pytest
65

@@ -42,25 +41,30 @@ def test_ModelicaSystemDoE(tmp_path, model_doe, param_doe):
4241
tmpdir = tmp_path / 'DoE'
4342
tmpdir.mkdir(exist_ok=True)
4443

45-
mod_doe = OMPython.ModelicaSystemDoE(
44+
doe_mod = OMPython.ModelicaSystemDoE(
4645
fileName=model_doe.as_posix(),
4746
modelName="M",
4847
parameters=param_doe,
4948
resultpath=tmpdir,
5049
simargs={"override": {'stopTime': 1.0}},
5150
)
52-
mod_doe.prepare()
53-
df_doe = mod_doe.get_doe()
54-
assert isinstance(df_doe, pd.DataFrame)
55-
assert df_doe.shape[0] == 16
56-
assert df_doe['results available'].sum() == 0
51+
doe_count = doe_mod.prepare()
52+
assert doe_count == 16
5753

58-
mod_doe.simulate()
59-
assert df_doe['results available'].sum() == 16
54+
doe_dict = doe_mod.get_doe()
55+
assert isinstance(doe_dict, dict)
56+
assert len(doe_dict.keys()) == 16
6057

61-
for row in df_doe.to_dict('records'):
62-
resultfilename = row[mod_doe.DF_COLUMNS_RESULT_FILENAME]
63-
resultfile = mod_doe._resultpath / resultfilename
58+
doe_status = doe_mod.simulate()
59+
assert doe_status is True
60+
61+
doe_sol = doe_mod.get_solutions()
62+
63+
for resultfilename in doe_dict:
64+
row = doe_dict[resultfilename]
65+
66+
assert resultfilename in doe_sol
67+
sol = doe_sol[resultfilename]
6468

6569
var_dict = {
6670
# simple / non-structural parameters
@@ -73,6 +77,7 @@ def test_ModelicaSystemDoE(tmp_path, model_doe, param_doe):
7377
f"x[{row['p']}]": float(row['a']),
7478
f"y[{row['p']}]": float(row['b']),
7579
}
76-
sol = mod_doe._mod.getSolutions(resultfile=resultfile.as_posix(), varList=list(var_dict.keys()))
7780

78-
assert np.isclose(sol[:, -1], np.array(list(var_dict.values()))).all()
81+
for var in var_dict:
82+
assert var in sol['data']
83+
assert np.isclose(sol['data'][var][-1], var_dict[var])

0 commit comments

Comments
 (0)