Repository: e2nIEE/pandapower

Issue:
log variable with specific name in outputwriter
I just came across a peculiarity of the outputwriter. When adding an eval_name to a log variable (e.g. "maximum" for the minimum of a variable in some table), it is precisely stored as "maximum" globally in the numpy-output. As I am just a user and mainly working with the file output, this comes a little unexpected, as I thought that it just influences the name of the column that is stored in the file. The behavior is now that when adding "maximum" to res_line.loading_percent and to res_bus.vm_pu, csv-files are written for both, but they show the same values (which one depends on which "maximum" log variable was defined first).

Does it make sense to you to change this kind of behavior (i.e. in `_get_np_name` the name should be formed from table.value.eval_name instead of eval_name only)? Otherwise we should definitely add warnings here, in the documentation and upon adding a log variable. If the values are just overwritten, that is totally confusing.

Discussion:

Patch:
diff --git a/pandapower/timeseries/output_writer.py b/pandapower/timeseries/output_writer.py
index 487dfed3..d7908d10 100644
--- a/pandapower/timeseries/output_writer.py
+++ b/pandapower/timeseries/output_writer.py
@@ -323,9 +323,9 @@ class OutputWriter(JSONSerializableClass):
         **eval_function** (function, None) - A function to be applied on the table / variable / index combination.
         example: pd.min or pd.mean
 
-        **eval_name** (str, None) - The name for an applied function. If None the name consists of the table, variable,
-        index and eval function
-        example: "Sir_Lancelot"
+        **eval_name** (str, None) - The name for an applied function. It *must* be unique.
+                                    If the name is None the name consists of the table, variable, index and eval function
+                                    example: "max_load_p_mw_values"
 
         EXAMPLE:
             >>> ow.log_variable('res_bus', 'vm_pu') # add logging for bus voltage magnitudes
@@ -338,6 +338,11 @@ class OutputWriter(JSONSerializableClass):
         append = True
         # check if new log_variable is already in log_variables. If so either append or delete
         for i, log_args in enumerate(self.log_variables):
+            if len(log_args) > 4 and eval_name is not None and log_args[4] == eval_name:
+                logger.warning("eval_name '{}' already exists for table '{}' and variable '{}'. "
+                                 "Please choose a unique eval_name. "
+                               "I'll use the default instead.".format(eval_name, log_args[0], log_args[1]))
+                eval_name = None
             if log_args[0] == table and log_args[1] == variable:
                 # table and variable exist in log_variables
                 if eval_function is not None or eval_name is not None:


Test patch:
diff --git a/pandapower/test/timeseries/test_output_writer.py b/pandapower/test/timeseries/test_output_writer.py
index 1e4fe201..18346e3e 100644
--- a/pandapower/test/timeseries/test_output_writer.py
+++ b/pandapower/test/timeseries/test_output_writer.py
@@ -15,11 +15,14 @@ import pytest
 
 import pandapower as pp
 import pandapower.control as ct
+import pandapower.networks as nw
+import pandapower.timeseries as ts
 from pandapower.control import ConstControl
+from pandapower.networks import simple_four_bus_system
 from pandapower.test.timeseries.test_timeseries import create_data_source, simple_test_net
 from pandapower.timeseries import DFData, OutputWriter
 from pandapower.timeseries.run_time_series import run_timeseries
-from pandapower.networks import simple_four_bus_system
+
 logger = logging.getLogger(__name__)
 ow_logger = logging.getLogger("hp.control.output_writer")
 logger.setLevel(logging.ERROR)
@@ -272,30 +275,50 @@ def test_ppc_log(simple_test_net):
 
 def test_ow_index():
     net = simple_four_bus_system()
-    steps = [3,5,7]
-    p_data = pd.DataFrame(index=steps, columns=["0", "1"], data=[[0.01, 0.02], 
+    steps = [3, 5, 7]
+    p_data = pd.DataFrame(index=steps, columns=["0", "1"], data=[[0.01, 0.02],
                                                                  [0.03, 0.04],
                                                                  [0.05, 0.06],
                                                                  ])
     v_data = pd.DataFrame(index=steps, columns=["0"], data=[1.01, 1.03, 1.02])
-        
+
     ds_p = DFData(p_data)
     ds_v = DFData(v_data)
-    
+
     ct.ConstControl(net, element='load', variable='p_mw',
-                 element_index=net.load.index.tolist(), data_source=ds_p,
-                 profile_name=p_data.columns)
-    ct.ConstControl(net, element='ext_grid', variable='vm_pu', 
-                 element_index=0, data_source=ds_v, 
-                 profile_name='0')
-    
+                    element_index=net.load.index.tolist(), data_source=ds_p,
+                    profile_name=p_data.columns)
+    ct.ConstControl(net, element='ext_grid', variable='vm_pu',
+                    element_index=0, data_source=ds_v,
+                    profile_name='0')
+
     ow = OutputWriter(net)
     ow.log_variable('res_bus', 'vm_pu')
     ow.log_variable('res_line', 'loading_percent')
-    
+
     run_timeseries(net, time_steps=p_data.index, verbose=False)
-    
+
     assert np.all(ow.output["res_line.loading_percent"].index == p_data.index)
-    
+
+
+def test_equal_eval_name_warning():
+    net = nw.case5()
+    df = pd.DataFrame({0: [200, 300, 400, 500], 1: [400, 300, 100, 50], 2: [100, 300, 200, 100]})
+    ds = ts.DFData(df.astype(np.float64))
+    _ = ct.ConstControl(net, "load", "p_mw", net.load.index, profile_name=net.load.index,
+                           data_source=ds)
+    ow = ts.OutputWriter(net, output_path=None)
+    ow.log_variable("res_sgen", "p_mw", None, np.max, 'warnme')
+    ow.log_variable("res_load", "p_mw", None, np.max, 'warnme')
+    ow.remove_log_variable("res_bus", "vm_pu")
+    ow.remove_log_variable("res_line", "loading_percent")
+    ts.run_timeseries(net, verbose=False)
+
+    p_sgen = ow.output["res_sgen.p_mw"]
+    p_load = ow.output["res_load.p_mw"]
+    assert not np.all(p_sgen.values == p_load.values)
+    assert len(ow.np_results) == 2
+
+
 if __name__ == '__main__':
-    pytest.main(['-s', __file__])
\ No newline at end of file
+    pytest.main(['-s', __file__])