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:
@friederikemeier and @FlorianShepherd what do you think about this possibility:

```
    def _get_np_name(self, partial_args):
        table = partial_args[0]
        variable = partial_args[1]
        eval_name = partial_args[4]
        np_name = "%s.%s" % (table, variable)
        if eval_name is not None:
            np_name += ".%s" % eval_name
        return np_name
```

instead of this:

```
    def _get_np_name(self, partial_args):
        eval_name = partial_args[4]
        if eval_name is not None:
            return eval_name
        else:
            table = partial_args[0]
            variable = partial_args[1]
            return "%s.%s" % (table, variable)
```
@dlohmeier please provide a minimal example that reproduces the issue
This is what I mean:

	import pandapower as pp
	import pandapower.networks as nw
	import pandas as pd
	import pandapower.control as ctrl
	import pandapower.timeseries as ts
	import numpy as np
	import tempfile


	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))
	cc = ctrl.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, "max")
	ow.log_variable("res_load", "p_mw", None, np.max, "max")
	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 == p_load)
	assert len(ow.np_results) == 2

So, as the sgens do not have any time step values read in to them, I would expect p_sgen and p_load to be different. But as it seems, the sgen results through the "max" label are just overwritten by the load results. I think, that kind of behaviour is not really desired, as it is very implicit and does not raise any warning. Btw. both asserts fail, as the np_results only has a "max" entry. Yet this entry is copied to both "res_load.max" and "res_sgen.max" in the output.
Thanks @dlohmeier overwriting the results is indeed and undesired behaviour. The eval name must be unique though and I think it would be correct to raise an error if the same name is being chosen twice. I can add that

Resolving patch:
diff --git a/pandapower/timeseries/output_writer.py b/pandapower/timeseries/output_writer.py
--- a/pandapower/timeseries/output_writer.py
+++ b/pandapower/timeseries/output_writer.py
@@ -323,9 +323,9 @@ def log_variable(self, table, variable, index=None, eval_function=None, eval_nam
         **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 @@ def log_variable(self, table, variable, index=None, eval_function=None, eval_nam
         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
--- a/pandapower/test/timeseries/test_output_writer.py
+++ b/pandapower/test/timeseries/test_output_writer.py
@@ -15,11 +15,14 @@
 
 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__])