Repository: e2nIEE/pandapower

Issue:
Huge performance decrease for "deepcopy"
Hello, i noticed a huge performance decrease in copying pandapower grid.
This performance issue arises when copying the powergrid (operator deep copy).

With the following script:
```
import pandapower as pp
import pandapower.networks as pn
import time
import copy
print("Pandapowerversion: {}".format(pp.__version__))
nb_powerflow = 1000
beg_ = time.time()
grid = pn.case14()
for i in range(nb_powerflow):
    # pp.runpp(grid, check_connectivity=False, init="results")
    copy.deepcopy(grid)
print("time to copy {} grid: {:.2f}s".format(nb_powerflow, time.time() - beg_))
```
it get the following results with pandapower 2.1.0 vs 2.2.0:
```
Pandapowerversion: 2.1.0
time to copy 1000 grid: 5.98s
```
versus 
```
Pandapowerversion: 2.2.0
time to copy 1000 grid: 275.52s
```

This problem arises on both Ubuntu 16.04 and Fedora 27.
I tested it on python 3.6

I think this issue is related to the #620 : deep copy is now done by serializing / de serializing json leading to large overhead.



Discussion:
Yes, this issue arises from the json overload. Unfortunately, we have not yet found a solution of resolving circular references in the usual case of deepcopy for controllers, as the following test would fail:

    import copy
    net2 = copy.deepcopy(net)
    assert net2.controller.object.at[0].net == net2

Currently, this is only possible with the help of the json overload. We are working on a solution (also referenced in #577 ).

Resolving patch:
diff --git a/pandapower/auxiliary.py b/pandapower/auxiliary.py
--- a/pandapower/auxiliary.py
+++ b/pandapower/auxiliary.py
@@ -166,10 +166,32 @@ def __getattr__(self, key):
         return self._build(self[key])
 
     def __deepcopy__(self, memo):
-        from pandapower.file_io import PPJSONEncoder, from_json_string
-        from pandapower.io_utils import with_signature
-        json_string = json.dumps(with_signature(self, dict(self)), cls=PPJSONEncoder)
-        return from_json_string(json_string)
+        """
+        overloads the deepcopy function of pandapower if at least one DataFrame with column "object" is in net
+
+        reason: some of these objects contain a reference to net which breaks the default deepcopy function.
+        This fix was introduced in pandapower 2.2.1 and is rather a quick and dirty solution to the problem
+
+        """
+        save_to_json = False
+        for el in self:
+            if isinstance(self[el], pd.DataFrame) and "object" in self[el] and len(self[el]):
+                save_to_json = True
+                break
+        if save_to_json:
+            # deepcopy by dumping to json and loading from it
+            from pandapower.file_io import PPJSONEncoder, from_json_string
+            from pandapower.io_utils import with_signature
+            json_string = json.dumps(with_signature(self, dict(self)), cls=PPJSONEncoder)
+            return from_json_string(json_string)
+
+        # deepcopy of every element in self (== the pandapower net)
+        cls = self.__class__
+        result = cls.__new__(cls)
+        memo[id(self)] = result
+        for k, v in self.items():
+            setattr(result, k, copy.deepcopy(v, memo))
+        return result
 
     @classmethod
     def _valid_name(cls, key):


Test patch:
diff --git a/pandapower/test/api/test_file_io.py b/pandapower/test/api/test_file_io.py
--- a/pandapower/test/api/test_file_io.py
+++ b/pandapower/test/api/test_file_io.py
@@ -4,19 +4,20 @@
 # and Energy System Technology (IEE), Kassel. All rights reserved.
 
 
-import os
-import pytest
 import copy
+import json
+import os
 
+import numpy as np
 import pandas as pd
+import pytest
+
 import pandapower as pp
-import pandapower.topology as top
-from pandapower.test.toolbox import assert_net_equal, create_test_network, tempdir, net_in
-import pandapower.networks as nw
 import pandapower.control as ct
+import pandapower.networks as nw
+import pandapower.topology as top
 from pandapower.io_utils import PPJSONEncoder, PPJSONDecoder
-import json
-import numpy as np
+from pandapower.test.toolbox import assert_net_equal, create_test_network, tempdir, net_in
 from pandapower.timeseries import DFData
 
 
@@ -247,8 +248,10 @@ def test_deepcopy_controller():
     net = nw.mv_oberrhein()
     ct.ContinuousTapControl(net, 114, 1.01)
     assert net == net.controller.object.iloc[0].net
-    net2 = copy.copy(net)
+    net2 = copy.deepcopy(net)
     assert net2 == net2.controller.object.iloc[0].net
+    net3 = copy.copy(net)
+    assert net3 == net3.controller.object.iloc[0].net
 
 
 if __name__ == "__main__":