From e67e4c01a8cc479057070d15654eda832f4639df Mon Sep 17 00:00:00 2001
From: Daniel Brown <ddb@star.sr.bham.ac.uk>
Date: Thu, 15 Jan 2015 17:18:13 +0000
Subject: [PATCH] Attempt at fixing referencing issue when deep copying the
 various pykat objects. It was an issue because they create dynamic properties
 for classes on the fly depending on which components are added, e.g. so that
 you can do kat.m1. However on copying the class definitions also need to be
 deep-copied by creating a new class and copying over the properties.

---
 examples/length_tuning.py |  2 +-
 pykat/components.py       | 32 ++++++++++++++++++------
 pykat/detectors.py        | 33 ++++++++++++++++++++++---
 pykat/finesse.py          | 51 ++++++++++++++++++++++++++++++++-------
 pykat/node_network.py     | 14 ++++++++---
 5 files changed, 107 insertions(+), 25 deletions(-)

diff --git a/examples/length_tuning.py b/examples/length_tuning.py
index 8a1c524..2729774 100644
--- a/examples/length_tuning.py
+++ b/examples/length_tuning.py
@@ -38,7 +38,7 @@ xaxis b1 phi lin 0 180 100
 yaxis deg     % plotting the phase of the results
 """
 
-kat = finesse.kat(tempdir = "/home/sleavey/Desktop/")
+kat = finesse.kat()
 kat.parseCommands(code)
 
 maxtem = np.arange(0, 2, 2)
diff --git a/pykat/components.py b/pykat/components.py
index 99c8cdb..7193049 100644
--- a/pykat/components.py
+++ b/pykat/components.py
@@ -13,6 +13,7 @@ import pykat
 from pykat.node_network import *
 from pykat.exceptions import *
 import abc
+import copy
 
 from pykat.SIfloat import *
 from pykat.param import Param, AttrParam
@@ -70,8 +71,15 @@ class NodeGaussSetter(object):
         
 class Component(object):
     __metaclass__ = abc.ABCMeta
-    
-    def __init__(self, name):
+
+    def __new__(cls, *args, **kwargs):
+        # This creates an instance specific class for the component
+        # this enables us to add properties to instances rather than
+        # all classes
+        return object.__new__(type(cls.__name__, (cls,), {}), *args, **kwargs)
+        
+    def __init__(self, name=None):
+        
         self.__name = name
         self._svgItem = None
         self._requested_node_names = []
@@ -86,12 +94,20 @@ class Component(object):
         self.__id = next_component_id
         next_component_id += 1
         
-        # This creates an instance specific class for the component
-        # this enables us to add properties to instances rather than
-        # all classes
-        cls = type(self)
-        self.__class__ = type(cls.__name__, (cls,), {})
-    
+       
+        
+    def __deepcopy__(self, memo):
+        """
+        When deep copying a kat object we need to take into account
+        the instance specific properties.
+        """
+        result = self.__class__.__new__(self.__class__)
+        result.__dict__ = copy.deepcopy(self.__dict__, memo)
+        
+        result.__update_node_setters             
+        
+        return result
+        
     def _register_param(self, param):
         self._params.append(param)
     
diff --git a/pykat/detectors.py b/pykat/detectors.py
index 612b01c..e6c5e93 100644
--- a/pykat/detectors.py
+++ b/pykat/detectors.py
@@ -75,6 +75,8 @@ class BaseDetector(object) :
             else:
                 raise pkex.BasePyKatException("Nodes should be a list or tuple of node names or a singular node name as a string.")
                 
+                
+                  
     def _register_param(self, param):
         self._params.append(param)
         
@@ -349,7 +351,13 @@ class bp(Detector1):
 
 class pd(Detector1):
 
-    def __init__(self, name, num_demods, node_name, senstype=None, alternate_beam=False, pdtype=None, **kwargs):
+    def __new__(cls, *args, **kwargs):
+        # This creates an instance specific class for the component
+        # this enables us to add properties to instances rather than
+        # all classes
+        return object.__new__(type(cls.__name__, (cls,), {}), *args, **kwargs)
+        
+    def __init__(self, name=None, num_demods=1, node_name=None, senstype=None, alternate_beam=False, pdtype=None, **kwargs):
         BaseDetector.__init__(self, name, node_name)
         
         self.__num_demods = num_demods
@@ -394,11 +402,28 @@ class pd(Detector1):
             elif i<num_demods-1:
                 raise pkex.BasePyKatException("Missing demodulation phase {0} (phi{0})".format(i+1))
         
-        # define new class for assigning new attributes
-        cls = type(self)
-        self.__class__ = type(cls.__name__, (cls,), {})
     
         self.__set_demod_attrs()
+    
+    
+    def __deepcopy__(self, memo):
+        """
+        When deep copying a kat object we need to take into account
+        the instance specific properties.
+        """
+        
+        result = pd(self.name, self.num_demods, self.node.name)
+        
+        memo[id(self)] = result 
+        result.__dict__ = copy.deepcopy(self.__dict__, memo)
+
+        # Find all properties in class we are copying
+        # and deep copy these to the new class instance
+        for x in self.__class__.__dict__.items():
+            if isinstance(x[1], property):
+                setattr(result.__class__, x[0], x[1])
+                        
+        return result
         
     @property
     def senstype(self): return self.__senstype
diff --git a/pykat/finesse.py b/pykat/finesse.py
index e32e785..bc335e2 100644
--- a/pykat/finesse.py
+++ b/pykat/finesse.py
@@ -183,10 +183,9 @@ class katRun(object):
     
     def __getitem__(self, value):
         idx = [i for i in range(len(self.ylabels)) if self.ylabels[i].split()[0] == str(value)]
-
+        out = None
+        
         if len(idx) > 0:
-            out = self.y[:, idx]
-            
             if len(idx) == 1:
                 if self.yaxis == "abs:deg":
                     out = self.y[:, idx[0]]
@@ -198,6 +197,9 @@ class katRun(object):
                 elif self.yaxis == "re:im":
                     out = self.y[:, idx[0]] + 1j*self.y[:, idx[1]]
             
+            if out == None:
+                out = self.y[:, idx]
+            
             if out.size == 1:
                 return out[0].squeeze()
             else:
@@ -375,10 +377,18 @@ Constant = namedtuple('Constant', 'name, value, usedBy')
     
 class kat(object):  
         
+    def __new__(cls, *args, **kwargs):
+        # This may seem like an arbitrary step but here we are creating a 
+        # new class that is a base class of itself. This is because when
+        # the kat object adds new components it also adds properties for
+        # each of these. There properties are unique to each kat object,
+        # but properties are part of the class definition. Thus if two
+        # kat objects share the same class definition they also have the
+        # same properties regardless of whether they have the actual
+        # object added to it. So we create an instance specific class.
+        return object.__new__(type(pykat.finesse.kat.__name__, (pykat.finesse.kat,), {}), *args, **kwargs)
+        
     def __init__(self, kat_file=None, kat_code=None, katdir="", katname="", tempdir=None, tempname=None):
-
-        cls = type(self)
-        self.__class__ = type(cls.__name__, (cls,), {})
         
         self.scene = None # scene object for GUI
         self.verbose = True
@@ -424,7 +434,30 @@ class kat(object):
         
         if kat_file != None:
             self.loadKatFile(kat_file)
-
+			
+    def __deepcopy__(self, memo):
+        """
+        When deep copying a kat object we need to take into account
+        the instance specific properties. This is because when
+        the kat object adds new components it also adds properties for
+        each of these. There properties are unique to each kat object,
+        but properties are part of the class definition. Thus if two
+        kat objects share the same class definition they also have the
+        same properties regardless of whether they have the actual
+        object added to it. So we create an instance specific class.
+        """
+        result = self.__class__.__new__(self.__class__)
+        memo[id(self)] = result
+        result.__dict__ = copy.deepcopy(self.__dict__, memo)
+        
+        # Find all properties in class we are copying
+        # and deep copy these to the new class instance
+        for x in self.__class__.__dict__.items():
+            if isinstance(x[1], property):
+                setattr(result.__class__, x[0], x[1])
+                        
+        return result
+		
     @property
     def signals(self): return self.__signals
 
@@ -1229,7 +1262,7 @@ class kat(object):
             # need to parse 2D outputs slightly different as they are effectively 2D matrices
             # written in linear form
             x = data[0::(1+self.x2axis.steps),0].squeeze()
-            y = data[0:(1+self.x2axis.steps),1].squeeze()
+            y = data[0:(1+self.x2axis.steps),1]
             # get rows and columns lined up so that we can reshape a single column of all x/y data
             # into a matrix
             z = data[:,2:].transpose().reshape(data.shape[1]-2, 1+self.xaxis.steps, 1+self.x2axis.steps).squeeze()
@@ -1242,7 +1275,7 @@ class kat(object):
             rows,cols = data.shape
             
             x = data[:,0].squeeze()
-            y = data[:,1:cols].squeeze()
+            y = data[:,1:cols]
         
             return [x, y, hdr]
 
diff --git a/pykat/node_network.py b/pykat/node_network.py
index 973d42c..e5db683 100644
--- a/pykat/node_network.py
+++ b/pykat/node_network.py
@@ -15,9 +15,18 @@ import pykat.exceptions as pkex
 from pykat.components import Component, NodeGaussSetter
 from pykat.detectors import BaseDetector as Detector
 from pykat.optics.gaussian_beams import beam_param
+from copy import deepcopy
 
 class NodeNetwork(object):
+    
+    def __new__(cls, *args, **kwargs):
+        # This creates an instance specific class for the component
+        # this enables us to add properties to instances rather than
+        # all classes
+        return object.__new__(type(cls.__name__, (cls,), {}), *args, **kwargs)
+        
     def __init__(self, kat):
+        
         self.__nodes = {}
         self.__kat = kat
         self.__nodeComponents = {} # dictionary of tuples containing which components are connected to a node
@@ -25,9 +34,8 @@ class NodeNetwork(object):
         self.__componentCallback = {}
         self.__node_id = 1
         
-        cls = type(self)
-        self.__class__ = type(cls.__name__, (cls,), {})
-    
+
+        
     @property
     def kat(self): return self.__kat
         
-- 
GitLab