From 4989df75a39d70a218c8dc7257fa1aa718f784b0 Mon Sep 17 00:00:00 2001
From: Daniel Brown <ddb@star.sr.bham.ac.uk>
Date: Mon, 16 Feb 2015 11:57:17 +0000
Subject: [PATCH] fixing some optivis node convention issues. Adding other
 components and trying out aLIGO plotting, nearly working. Other ROM updates
 too

---
 bin/test_profile.py          |   2 +
 examples/LLO_matched.kat     | 283 +++++++++++++++++++++++++++++++++++
 examples/optivis_aligo_ex.py |   7 +
 examples/optivis_ex.py       |  21 ++-
 pykat/components.py          |  57 ++++++-
 pykat/finesse.py             |   5 +
 pykat/optics/knm.py          | 102 ++++++++-----
 pykat/optics/maps.py         |  30 ++--
 pykat/optics/romhom.py       | 233 ++++++++++++++++++----------
 9 files changed, 591 insertions(+), 149 deletions(-)
 create mode 100644 examples/LLO_matched.kat
 create mode 100644 examples/optivis_aligo_ex.py

diff --git a/bin/test_profile.py b/bin/test_profile.py
index 5c420ff..588b68d 100644
--- a/bin/test_profile.py
+++ b/bin/test_profile.py
@@ -13,6 +13,8 @@ attr m2 Rc 2
 pd circ n2
 xaxis m1 phi lin 0 180 1000
 yaxis log abs
+maxtem 0
+cav c1 m1 n1 m2 n2
 """)
 
 kat.timeCode = True
diff --git a/examples/LLO_matched.kat b/examples/LLO_matched.kat
new file mode 100644
index 0000000..7d54c8b
--- /dev/null
+++ b/examples/LLO_matched.kat
@@ -0,0 +1,283 @@
+%--------------------------------------------------------------------------
+%
+% Finesse model for the full dual recycled inteferometer, with Fabry-Perot
+% arm cavities and OMC.
+%
+% Currently plots the DARM coupled cavity transfer function for determining
+% cavity pole.
+%
+% Sources of 'real' numbers, unless otherwise stated:
+% - Mirror parameters (curvatures, losses etc.) from
+%   https://galaxy.ligo.caltech.edu/optics/  If no number for the loss is
+%   given we use 0.
+% - Distances between optics from LIGO-E1200274, the L1 master coordinate
+%   list.
+%
+% 3/12/14 - Updated with Galaxy page values for installed SRM-w
+%         - Includes locks for DARM (10pm offset), PRCL, MICH, CARM and SRCL
+% 4/12/14 - Added in more details about which Galaxy mirror data is taken from
+%
+% To-do:
+% - Check frequency and modulation index for side-bands
+% - AR surface for PRM
+%
+%
+# Charlotte Bond, Paul Fulda, Daniel Brown 
+%--------------------------------------------------------------------------
+
+
+# %%% FTblock Laser
+# ###########################################################################
+# # Laser
+# l L0 2 0 n0
+# s lmod1 1 n0 n1
+# # Modulation indices updated from LLO logbook denis.martynov@LIGO.ORG - 02:01,
+# # Thursday 03 October 2013 (8940)
+# mod mod1 $f1 0.25 1 pm n1 n2
+# s lmod2 1 n2 n3
+# mod mod2 $f2 0.25 1 pm n3 nin
+# # Input beam mode-matched to case with 50km lens in itm substrates (matched
+# # to arm cavities) for LLO design case
+# gauss gparam L0 n0 1.3498243m 4.3433202 1.3619432m 4.5706208
+# ###########################################################################
+# %%% FTend Laser
+
+# %%% FTblock PR
+# ###########################################################################
+# # Distance to power recycling mirror
+# s lin 1 nin nREFL
+# # Power recycling mirror PRM-02
+# m1 PRM $T_PRM $L_PRM $phi_PRM nREFL nPRMb
+# attr PRM Rc 11.009
+# # Distance between PRM and PR2
+# s lp1 16.6107 nPRMb nPR2a
+# # PR2 PR2-02
+# bs1 PR2 $T_PR2 $L_PR2 0 -0.79 nPR2a nPR2b dump nPOP
+# attr PR2 Rc -4.545
+# # Distance from PR2 to PR3
+# s lp2 16.1647 nPR2b nPR3a
+# # PR3 PR3-03
+# bs1 PR3 $T_PR3 $L_PR3 0 0.615 nPR3a nPR3b dump dump
+# attr PR3 Rc 36.027
+# # Distance from PR3 to BS
+# s lp3 19.5381 nPR3b nHRBS_PR
+# ###########################################################################
+# %%% FTend PR
+
+l L0 2 0 n0
+s s1 100 n0 nHRBS_PR
+%%% FTblock BS
+###########################################################################
+# BS beamsplitter BS-02
+##------------------------------------------------------------ 
+## BS 
+##                             ^ 
+##                  to IMY     |      
+##                             |      ,'-.
+##                             |     +    `. 
+##                        nYBS |   ,'       :'
+##      nPR3b                  |  +i1      +
+##         ---------------->    ,:._  i2 ,'
+##    from the PRC       nPRBS + \  `-. + nXBS
+##                           ,' i3\   ,' --------------->
+##                          +      \ +     to IMX
+##                        ,'     i4.'
+##                       `._      ..
+##                          `._ ,' |nSRBS
+##                             -   |
+##                                 |to the SRC
+##                                 |
+##                                 v
+##------------------------------------------------------------
+# BS BS-02
+bs1 HRBS 0.5 8.6u $phi_BS 45 nHRBS_PR nHRBS_Y nHRBS_X nHRBS_SR
+s sHRBStoARBSX 5 $nsilica nHRBS_X nARBSX_sub
+bs2 ARBSX 30u 1.7u $phi_ARBSX 29.1951 nARBSX_sub dump nARBSX_X dump
+s sHRBStoARBSSR 5 $nsilica nHRBS_SR nARBSSR_sub
+bs2 ARBSSR 30u 1.7u $phi_ARBSSR -29.1951 nARBSSR_sub dump nARBSSR_SR dump
+###########################################################################
+%%% FTend BS
+
+
+# %%% FTblock SR
+# ###########################################################################
+# # Distance from BS to SR3
+# s ls3 19.3661 nARBSSR_SR nSR3a
+#
+# # SR3 SR3-01
+# bs1 SR3 $T_SR2 $L_SR3 0 0.785 nSR3a nSR3b dump dump
+# attr SR3 Rc 35.97
+#
+# # Distance from SR3 to SR2
+# s ls2 15.4435 nSR3b nSR2a
+#
+# # SR2 SR2-04
+# bs1 SR2 $T_SR2 $L_SR2 0 0.87 nSR2a nSR2b dump dump
+# attr SR2 Rc -6.406
+#
+# # Distance from SR2 to SRMHR
+# s ls1 15.7566 nSR2b nSRMHRa
+#
+# # Signal recycling mirror SRM-08
+# m1 SRMHR $T_SRM $L_SRM $phi_SRM nSRMHRa nSRMHRb
+# s SRMsub 0.0749 $nsilica nSRMHRb nSRMARa
+# attr SRMHR Rc -5.677
+# m2 SRMAR 50u 0 $phi_SRM nSRMARa nSRMARb
+# ###########################################################################
+# %%% FTend SR
+
+
+%%% FTblock Yarm
+###########################################################################
+# Using values from E1200274
+s ly1 4.847 nHRBS_Y nCPYar1
+# Y arm compensation plate CP-08
+m2 CPYar1 48.9u 0.4u 0 nCPYar1 nCPYar1s
+s sCPY 0.10032 $nsilica nCPYar1s nCPYar2s
+m2 CPYar2 30.5u 0.3u 0 nCPYar2s nCPYar2
+s sCPYtoITMYar 0.02 nCPYar2 nITMYTLin
+# Y arm input mirror ITM-08
+# Thermal lens
+lens ITMYTL $TL_f nITMYTLin nITMYTLtrans
+s ITMYTL_null 10 nITMYTLtrans nITMYconstL_in
+# Constant ITMY substrate lens
+lens ITMYconstL inf nITMYconstL_in nITMYconstL_trans
+# s ITMYTL_null2 0 nITMYconstL_trans nITMYar_in
+# m2 ITMYar 250u 0.6u $phi_ITMY nITMYar_in nITMYs1
+# s lITMY 0.19961 $nsilica nITMYs1 nITMYs2
+# m1 ITMY $T_ITMY $L_ITMY $phi_ITMY nITMYs2 nITMY2
+# attr ITMY Rc -1940.7
+# # Y-arm
+# s Ly 10 nITMY2 nETMY1
+# # ETMY
+# m1 ETMY $T_ETMY $L_ETMY $phi_ETMY nETMY1 nTY
+# attr ETMY Rc 2242.4
+###########################################################################
+%%% FTend Yarm
+
+# %%% FTblock Xarm
+# ###########################################################################
+# # Now using length taken from E1200616
+# s lx1 4.829 nARBSX_X nCPXar1
+# # X arm compensation plate CP-06
+# m2 CPXar1 33u 0.6u 0 nCPXar1 nCPXar1s
+# s sCPX 0.10031 $nsilica nCPXar1s nCPXar2s
+# m2 CPXar2 8u 0.6u 0 nCPXar2s nCPXar2
+# s sCPXtoITMXar 0.02 nCPXar2 nITMXTLin
+# # X arm input mirror ITM-04
+# # Thermal lens
+# lens ITMXTL $TL_f nITMXTLin nITMXTLtrans
+# s ITMXtl_null 0 nITMXTLtrans nITMXconstL_in
+# # Non-thermal ITM lens
+# lens ITMXconstL inf nITMXconstL_in nITMXconstL_trans
+# s ITMXTL_null2 0 nITMXconstL_trans nITMXar_in
+# m2 ITMXar 164u 0.5u $phi_ITMX nITMXar_in nITMXs1
+# s lITMX1 0.20027 $nsilica nITMXs1 nITMXs2
+# m1 ITMX $T_ITMX $L_ITMX $phi_ITMX nITMXs2 nITMX2
+# # default Rc from nebula page
+# attr ITMX Rc -1937.9
+# # X-arm
+# s Lx 10 nITMX2 nETMX1
+# # ETMX
+# m1 ETMX $T_ETMX $L_ETMX $phi_ETMX nETMX1 nTX
+# attr ETMX Rc 2239.7
+# ###########################################################################
+# %%% FTend Xarm
+
+
+%%% FTblock Reflectivities
+###########################################################################
+# Galaxy number 100
+const T_PR2 243u
+const L_PR2 8.6u
+
+# Galaxy number 105
+const T_PR3 5.3u
+const L_PR3 17u
+
+# Galaxy number 107
+const T_PRM 0.021
+const L_PRM 5.9u
+
+# Galaxy number 113
+const T_SR2 18.3u
+const L_SR2 6.1u
+
+# Galaxy number 114
+const T_SR3 5u
+const L_SR3 19.1u
+
+# Galaxy number 126
+const T_SRM 0.3688	
+const L_SRM 8.3u
+
+# Galaxy number 75
+const T_ITMX 0.0148
+const L_ITMX 10.4u
+
+# Galaxy number 79
+const T_ITMY 0.0148
+const L_ITMY 14.3u
+
+# Galaxy number 30
+const T_ETMX 3.7u
+const L_ETMX 100.9u
+
+# Galaxy number 32
+const T_ETMY 3.6u
+const L_ETMY 9.3u
+###########################################################################
+%%% FTend Reflectivities
+
+
+%%% FTblock Constants
+###########################################################################
+const nsilica 1.44963098985906
+% Sidebands tuned to be resonant for PRC in this file (lprc = 57.6564)
+% Design sidebands
+%const f1 9099471
+%const mf1 -9099471
+%const f2 45497355
+%const mf2 -45497355
+% Measured?
+const f1 9099055
+const mf1 9099055
+const f2 45495275
+const mf2 -45495275
+const TL_f 34.5k
+###########################################################################
+%%% FTend Constants
+
+
+%%% FTblock Tunings
+###########################################################################
+# offset computed with zero_locks.py
+const phi_ETMX 0.106992111770048
+const phi_ETMY -0.109282418715425
+const phi_ITMX 0.106389383390564
+const phi_ITMY -0.106389383390564
+const phi_PRM -0.024603392339103
+const phi_SRM 90.108393822523524
+
+const phi_BS 0
+const phi_ARBSX 0
+const phi_ARBSSR 0
+const phi_IC 0
+###########################################################################
+%%% FTend Tunings
+
+
+%%% FTblock HOMs
+###########################################################################
+# Set PRY and PRX to use cavity eigenmodes for mode calculations
+cav SRCY SRMHR nSRMHRa ITMY nITMYs2
+cav SRCX SRMHR nSRMHRa ITMX nITMXs2
+cav PRCY PRM nPRMb ITMY nITMYs2
+cav PRCX PRM nPRMb ITMX nITMXs2
+cav Xarm ITMX nITMX2 ETMX nETMX1
+cav Yarm ITMY nITMY2 ETMY nETMY1
+cav OMC IC nICtrans IC nICout
+maxtem 4
+phase 2
+###########################################################################
+%%% FTend HOMs
diff --git a/examples/optivis_aligo_ex.py b/examples/optivis_aligo_ex.py
new file mode 100644
index 0000000..0e49704
--- /dev/null
+++ b/examples/optivis_aligo_ex.py
@@ -0,0 +1,7 @@
+import pykat
+
+kat = pykat.finesse.kat(kat_file="LLO_matched.kat")
+
+
+kat.optivis().show()
+
diff --git a/examples/optivis_ex.py b/examples/optivis_ex.py
index 130686d..e6638e0 100644
--- a/examples/optivis_ex.py
+++ b/examples/optivis_ex.py
@@ -4,20 +4,19 @@ kat = pykat.finesse.kat()
 
 kat.parseCommands("""
 l l1 1 0 n0
-s s1 100 n0 n1
-m m1 1 0 0 n1 n2
-s s1 50 n2 n3
+s s1 100 n0 n3
 
-bs bs1 1 0 45 45 n3 n4 n5 n6
+bs bs1 1 0 0 45 n3 n4 n5 n6
 
-s s2 200 n5 n5a
-m m2 1 0 0 n5a dump
+s s3 50 n4 n4a
+m m1 1 0 0 n4a n4b
+s s3a 200 n4b n7a
+m m2 1 0 0 n7a n7b
 
-s s3 200 n4 n4a
-m m3 1 0 0 n4a dump
-
-s s4 50 n6 n6a
-m m4 1 0 0 n6a n6b
+s s4 50 n5 n5a
+m m3 1 0 0 n5a n5b
+s s4a 200 n5b n8a
+m m4 1 0 0 n8a n8b
 """)
 
 kat.optivis().show()
diff --git a/pykat/components.py b/pykat/components.py
index 413a012..7593f1e 100644
--- a/pykat/components.py
+++ b/pykat/components.py
@@ -510,7 +510,7 @@ class beamSplitter(AbstractMirrorComponent):
     
     def getOptivisComponent(self):
         if self._optivis_component is None:
-            self._optivis_component = optivis_components.BeamSplitter(name=self.name, aoi=self.alpha)
+            self._optivis_component = optivis_components.BeamSplitter(name=self.name, aoi=-self.alpha)
         
         return self._optivis_component
     
@@ -526,14 +526,14 @@ class beamSplitter(AbstractMirrorComponent):
             elif kat_node is self.nodes[1]:
                 return self._optivis_component.getInputNode("frB")
             elif kat_node is self.nodes[2]:
-                return self._optivis_component.getInputNode("bkA")
-            elif kat_node is self.nodes[3]:
                 return self._optivis_component.getInputNode("bkB")
+            elif kat_node is self.nodes[3]:
+                return self._optivis_component.getInputNode("bkA")
         elif mode == "output":
             if kat_node is self.nodes[0]:
-                return self._optivis_component.getOutputNode("frA")
-            elif kat_node is self.nodes[1]:
                 return self._optivis_component.getOutputNode("frB")
+            elif kat_node is self.nodes[1]:
+                return self._optivis_component.getOutputNode("frA")
             elif kat_node is self.nodes[2]:
                 return self._optivis_component.getOutputNode("bkA")
             elif kat_node is self.nodes[3]:
@@ -915,7 +915,30 @@ class lens(Component):
             rtn.extend(p.getFinesseText())
             
         return rtn
+    
+    def getOptivisComponent(self):
+        if self._optivis_component is None:
+            self._optivis_component = optivis_components.ConvexLens(name=self.name)
         
+        return self._optivis_component
+    
+    def getOptivisNode(self, mode, kat_node):
+        mode = mode.lower()
+        
+        if mode != "input" and mode.lower() != "output":
+            raise pkex.BasePyKatException("Mode must be either input or output")
+        
+        if mode == "input":
+            if kat_node is self.nodes[0]:
+                return self._optivis_component.getInputNode("fr")
+            elif kat_node is self.nodes[1]:
+                return self._optivis_component.getInputNode("bk")
+        elif mode == "output":
+            if kat_node is self.nodes[0]:
+                return self._optivis_component.getnOutputNode("fr")
+            elif kat_node is self.nodes[1]:
+                return self._optivis_component.getOutputNode("bk")
+                
     def getQGraphicsItem(self):
         if not USE_GUI:
             raise NoGUIException
@@ -997,6 +1020,30 @@ class modulator(Component):
             
         return rtn
         
+    def getOptivisComponent(self):
+        if self._optivis_component is None:
+            #self._optivis_component = optivis_components.Modulator(name=self.name)
+            self._optivis_component = optivis_components.ConvexLens(name=self.name)
+            
+        return self._optivis_component
+    
+    def getOptivisNode(self, mode, kat_node):
+        mode = mode.lower()
+        
+        if mode != "input" and mode.lower() != "output":
+            raise pkex.BasePyKatException("Mode must be either input or output")
+        
+        if mode == "input":
+            if kat_node is self.nodes[0]:
+                return self._optivis_component.getInputNode("fr")
+            elif kat_node is self.nodes[1]:
+                return self._optivis_component.getInputNode("bk")
+        elif mode == "output":
+            if kat_node is self.nodes[0]:
+                return self._optivis_component.getnOutputNode("fr")
+            elif kat_node is self.nodes[1]:
+                return self._optivis_component.getOutputNode("bk")
+                
     def getQGraphicsItem(self):
         if not USE_GUI:
             raise NoGUIException
diff --git a/pykat/finesse.py b/pykat/finesse.py
index 25a12df..20354e8 100644
--- a/pykat/finesse.py
+++ b/pykat/finesse.py
@@ -1480,6 +1480,11 @@ class kat(object):
             
             a = c.connectingComponents()
             
+            # Need to handle where spaces don't connect two components but there is a loose
+            # node, which may or may not have detectors attached
+            if a[0] is None or a[1] is None:
+                continue
+                
             c1 = a[0].getOptivisComponent()
             c2 = a[1].getOptivisComponent()
             
diff --git a/pykat/optics/knm.py b/pykat/optics/knm.py
index b75ad51..69435a8 100644
--- a/pykat/optics/knm.py
+++ b/pykat/optics/knm.py
@@ -5,6 +5,7 @@ from pykat.optics.romhom import u_star_u
 from pykat.external.progressbar import ProgressBar, ETA, Percentage, Bar
 from scipy.interpolate import interp2d
 from scipy.integrate import dblquad
+from pykat.optics.romhom import ROMWeights
 
 import time
 import pykat.optics.maps
@@ -271,65 +272,84 @@ def ROM_HG_knm(weights, mode_in, mode_out, q1, q2, q1y=None, q2y=None, cache=Non
     npr = mode_in[1]
     mpr = mode_out[1]
     
-    if cache == None:
-        u_x_nodes = u_star_u(q1.z,   q2.z,  q1.w0,  q2.w0, n,     m,   weights.EI["xm"].nodes)
-        u_y_nodes = u_star_u(q1y.z,   q2y.z,  q1y.w0,  q2y.w0, npr, mpr,   weights.EI["ym"].nodes)
+    if isinstance(weights, ROMWeights):
+        if cache == None:
+            u_x_nodes = u_star_u(q1.z,   q2.z,  q1.w0,  q2.w0, n,     m,   weights.EI["x"].nodes)
+            u_y_nodes = u_star_u(q1y.z,   q2y.z,  q1y.w0,  q2y.w0, npr, mpr,   weights.EI["y"].nodes)
         
-        w_ij_Q1Q3 = weights.w_ij_Q1 + weights.w_ij_Q3
-        w_ij_Q2Q4 = weights.w_ij_Q2 + weights.w_ij_Q4
-        w_ij_Q1Q2 = weights.w_ij_Q1 + weights.w_ij_Q2
-        w_ij_Q1Q4 = weights.w_ij_Q1 + weights.w_ij_Q4
-        w_ij_Q2Q3 = weights.w_ij_Q2 + weights.w_ij_Q3
-        w_ij_Q3Q4 = weights.w_ij_Q3 + weights.w_ij_Q4
-        w_ij_Q1Q2Q3Q4 = weights.w_ij_Q1 + weights.w_ij_Q2 + weights.w_ij_Q3 + weights.w_ij_Q4
+            w_ij_Q1Q3 = weights.w_ij_Q1 + weights.w_ij_Q3
+            w_ij_Q2Q4 = weights.w_ij_Q2 + weights.w_ij_Q4
+            w_ij_Q1Q2 = weights.w_ij_Q1 + weights.w_ij_Q2
+            w_ij_Q1Q4 = weights.w_ij_Q1 + weights.w_ij_Q4
+            w_ij_Q2Q3 = weights.w_ij_Q2 + weights.w_ij_Q3
+            w_ij_Q3Q4 = weights.w_ij_Q3 + weights.w_ij_Q4
+            w_ij_Q1Q2Q3Q4 = weights.w_ij_Q1 + weights.w_ij_Q2 + weights.w_ij_Q3 + weights.w_ij_Q4
         
-    else:
-        strx = "x[%i,%i]" % (mode_in[0], mode_out[0])
-        stry = "y[%i,%i]" % (mode_in[1], mode_out[1])
+        else:
+            strx = "x[%i,%i]" % (mode_in[0], mode_out[0])
+            stry = "y[%i,%i]" % (mode_in[1], mode_out[1])
 
-        u_x_nodes = cache[strx]
-        u_y_nodes = cache[stry]
+            u_x_nodes = cache[strx]
+            u_y_nodes = cache[stry]
         
-        w_ij_Q1Q3 = cache["w_ij_Q1Q3"]
-        w_ij_Q2Q4 = cache["w_ij_Q2Q4"]
-        w_ij_Q1Q2 = cache["w_ij_Q1Q2"]
-        w_ij_Q1Q4 = cache["w_ij_Q1Q4"]
-        w_ij_Q2Q3 = cache["w_ij_Q2Q3"]
-        w_ij_Q3Q4 = cache["w_ij_Q3Q4"]
-        w_ij_Q1Q2Q3Q4 = cache["w_ij_Q1Q2Q3Q4"]
+            w_ij_Q1Q3 = cache["w_ij_Q1Q3"]
+            w_ij_Q2Q4 = cache["w_ij_Q2Q4"]
+            w_ij_Q1Q2 = cache["w_ij_Q1Q2"]
+            w_ij_Q1Q4 = cache["w_ij_Q1Q4"]
+            w_ij_Q2Q3 = cache["w_ij_Q2Q3"]
+            w_ij_Q3Q4 = cache["w_ij_Q3Q4"]
+            w_ij_Q1Q2Q3Q4 = cache["w_ij_Q1Q2Q3Q4"]
         
 
-    u_xy_nodes = np.outer(u_x_nodes, u_y_nodes)
+        u_xy_nodes = np.outer(u_x_nodes, u_y_nodes)
 
-    n_mod_2 = n % 2
-    m_mod_2 = m % 2
-    npr_mod_2 = npr % 2
-    mpr_mod_2 = mpr % 2
+        n_mod_2 = n % 2
+        m_mod_2 = m % 2
+        npr_mod_2 = npr % 2
+        mpr_mod_2 = mpr % 2
 
-    if n_mod_2 == m_mod_2 and npr_mod_2 == mpr_mod_2:
-        k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q1Q2Q3Q4)
+        if n_mod_2 == m_mod_2 and npr_mod_2 == mpr_mod_2:
+            k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q1Q2Q3Q4)
 
-    elif n_mod_2 != m_mod_2:
-        if npr_mod_2 == mpr_mod_2:
-            k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q1Q4) - np.einsum('ij,ij', u_xy_nodes, w_ij_Q2Q3)
-        else:
-            k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q2Q4) - np.einsum('ij,ij', u_xy_nodes, w_ij_Q1Q3)
+        elif n_mod_2 != m_mod_2:
+            if npr_mod_2 == mpr_mod_2:
+                k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q1Q4) - np.einsum('ij,ij', u_xy_nodes, w_ij_Q2Q3)
+            else:
+                k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q2Q4) - np.einsum('ij,ij', u_xy_nodes, w_ij_Q1Q3)
 
-    elif npr_mod_2 != mpr_mod_2:
-        if n_mod_2 == m_mod_2:
-            k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q3Q4) - np.einsum('ij,ij', u_xy_nodes,  w_ij_Q1Q2)
+        elif npr_mod_2 != mpr_mod_2:
+            if n_mod_2 == m_mod_2:
+                k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q3Q4) - np.einsum('ij,ij', u_xy_nodes,  w_ij_Q1Q2)
+            else:
+                k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q2Q4) - np.einsum('ij,ij', u_xy_nodes, w_ij_Q1Q3)
+    
+    else:
+        if cache == None:
+            u_x_nodes = u_star_u(q1.z,   q2.z,  q1.w0,  q2.w0, n,     m,   weights.EI["x"].nodes)
+            u_y_nodes = u_star_u(q1y.z,   q2y.z,  q1y.w0,  q2y.w0, npr, mpr,   weights.EI["y"].nodes)
+    
+            w_ij = weights.w_ij
         else:
-            k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij_Q2Q4) - np.einsum('ij,ij', u_xy_nodes, w_ij_Q1Q3)
+            strx = "x[%i,%i]" % (mode_in[0], mode_out[0])
+            stry = "y[%i,%i]" % (mode_in[1], mode_out[1])
+
+            u_x_nodes = cache[strx]
+            u_y_nodes = cache[stry]
     
-     
+        u_xy_nodes = np.outer(u_x_nodes, u_y_nodes)
+
+        k_ROQ = np.einsum('ij,ij', u_xy_nodes, w_ij)
+         
     return k_ROQ
 
 __fac_cache = []
 
 def fac(n):
     global __fac_cache
-    return __fac_cache[n]
-    #return math.factorial(int(n))
+    if len(__fac_cache) == 0:
+        return math.factorial(int(n))
+    else:
+        return __fac_cache[n]
 
 def m_1_pow(n):
     if n % 2 == 0:
diff --git a/pykat/optics/maps.py b/pykat/optics/maps.py
index 0eb5422..a399727 100644
--- a/pykat/optics/maps.py
+++ b/pykat/optics/maps.py
@@ -143,7 +143,7 @@ class surfacemap(object):
             raise BasePyKatException("Map type needs handling")
         
 
-    def generateROMWeights(self, isModeMatched=True, verbose=False, interpolate=False, interpolate_N=None, tolerance = 1e-12, sigma = 1, sort=False, greedyfile=None):
+    def generateROMWeights(self, isModeMatched=True, verbose=False, interpolate=False, interpolate_N=None, tolerance = 1e-12, sigma = 1, sort=False, greedyfile=None, useSymmetry=True):
         
         if interpolate:
             from scipy.interpolate import interp2d
@@ -175,27 +175,31 @@ class surfacemap(object):
             self.center = (np.array(data.shape)+1)/2.0
             
             self.data = data
-        
-        xm = self.x[self.x<0]
-        ym = self.y[self.y<0]
-        
-        symm = False
+    
+        if useSymmetry:
+            xm = self.x[self.x<0]
+            ym = self.y[self.y<0]
+        else:
+            xm = self.x
+            ym = self.y
         
         if min(xm) == min(ym) and max(xm) == max(ym) and len(xm) == len(ym):
             symm = True
-        
+        else:
+            symm = False
+            
         EI = {}
         
-        EI["xm"] = makeEmpiricalInterpolant(makeReducedBasis(xm, isModeMatched=isModeMatched, tolerance = tolerance, sigma = sigma, greedyfile=greedyfile), sort=sort)
+        EI["x"] = makeEmpiricalInterpolant(makeReducedBasis(xm, isModeMatched=isModeMatched, tolerance = tolerance, sigma = sigma, greedyfile=greedyfile), sort=sort)
         
         if symm:
-            EI["ym"] = EI["xm"]
+            EI["y"] = EI["x"]
         else:
-            EI["ym"] = makeEmpiricalInterpolant(makeReducedBasis(ym, isModeMatched=isModeMatched, tolerance = tolerance, sigma = sigma, greedyfile=greedyfile), sort=sort)
+            EI["y"] = makeEmpiricalInterpolant(makeReducedBasis(ym, isModeMatched=isModeMatched, tolerance = tolerance, sigma = sigma, greedyfile=greedyfile), sort=sort)
         
-        EI["limits"] = EI["xm"].limits
+        EI["limits"] = EI["x"].limits
         
-        self._rom_weights = makeWeights(self, EI, verbose=verbose)
+        self._rom_weights = makeWeights(self, EI, verbose=verbose, useSymmetry=useSymmetry)
         
         return self.ROMWeights, EI
         
@@ -306,7 +310,7 @@ class tiltmap(surfacemap):
         
         xx, yy = np.meshgrid(self.x, self.y)
         
-        self.data = (xx * self.tilt[1] + yy * self.tilt[0])/self.scaling
+        self.data = (yy * self.tilt[1] + xx * self.tilt[0])/self.scaling
         
 
 class zernikemap(surfacemap):
diff --git a/pykat/optics/romhom.py b/pykat/optics/romhom.py
index 1766b83..958e3e7 100644
--- a/pykat/optics/romhom.py
+++ b/pykat/optics/romhom.py
@@ -41,14 +41,14 @@ class ROMWeights:
         f.write("w0max=%16.16e\n" % self.limits.w0max)
         f.write("maxorder=%i\n" % self.limits.max_order)
         
-        f.write("xnodes=%i\n" % len(self.EI["xm"].nodes))
+        f.write("xnodes=%i\n" % len(self.EI["x"].nodes))
         
-        for v in self.EI["xm"].nodes.flatten():
+        for v in self.EI["x"].nodes.flatten():
             f.write("%s\n" % repr(float(v)))
         
-        f.write("ynodes=%i\n" % len(self.EI["ym"].nodes))
+        f.write("ynodes=%i\n" % len(self.EI["y"].nodes))
         
-        for v in self.EI["ym"].nodes.flatten():
+        for v in self.EI["y"].nodes.flatten():
             f.write("%s\n" % repr(float(v)))
             
         f.write(repr(self.w_ij_Q1.shape) + "\n")
@@ -73,6 +73,45 @@ class ROMWeights:
             
         f.close()
 
+class ROMWeightsFull:
+    
+    def __init__(self, w_ij, EI, limits):
+        self.w_ij = w_ij
+        
+        self.EI = EI
+        self.limits = limits
+        
+    def writeToFile(self, filename):
+        """
+        Writes this map's ROM weights to a file
+        that can be used with Finesse. The filename
+        is appended with '.rom' internally.
+        """
+        f = open(filename + ".rom", 'w+')
+        
+        f.write("zmin=%16.16e\n" % self.limits.zmin)
+        f.write("zmax=%16.16e\n" % self.limits.zmax)
+        f.write("w0min=%16.16e\n" % self.limits.w0min)
+        f.write("w0max=%16.16e\n" % self.limits.w0max)
+        f.write("maxorder=%i\n" % self.limits.max_order)
+        
+        f.write("xnodes=%i\n" % len(self.EI["x"].nodes))
+        
+        for v in self.EI["x"].nodes.flatten():
+            f.write("%s\n" % repr(float(v)))
+        
+        f.write("ynodes=%i\n" % len(self.EI["y"].nodes))
+        
+        for v in self.EI["y"].nodes.flatten():
+            f.write("%s\n" % repr(float(v)))
+            
+        f.write(repr(self.w_ij.shape) + "\n")
+        
+        for v in self.w_ij.flatten():
+            f.write("%s\n" % repr(complex(v))[1:-1])
+        
+        f.close()
+        
 def combs(a, r):
     """
     Return successive r-length combinations of elements in the array a.
@@ -273,100 +312,136 @@ def makeEmpiricalInterpolant(RB, sort=False):
     return EmpiricalInterpolant(B=B, nodes=nodes, node_indices=node_indices, limits=RB.limits, x=RB.x)
     
 
-def makeWeights(smap, EI, verbose=True):
+def makeWeights(smap, EI, verbose=True, useSymmetry=True):
     
-    # get full A_xy
-    A_xy = smap.z_xy()
+    if useSymmetry:
+        # get full A_xy
+        A_xy = smap.z_xy().transpose()
     
-    xm = smap.x[smap.x < 0]
-    xp = smap.x[smap.x > 0]
+        xm = smap.x[smap.x < 0]
+        xp = smap.x[smap.x > 0]
     
-    ym = smap.y[smap.y < 0]
-    yp = smap.y[smap.y > 0]
+        ym = smap.y[smap.y < 0]
+        yp = smap.y[smap.y > 0]
     
-    Q1xy = np.ix_(smap.x < 0, smap.y > 0)
-    Q2xy = np.ix_(smap.x > 0, smap.y > 0)
-    Q3xy = np.ix_(smap.x > 0, smap.y < 0)
-    Q4xy = np.ix_(smap.x < 0, smap.y < 0)
+        Q1xy = np.ix_(smap.x < 0, smap.y > 0)
+        Q2xy = np.ix_(smap.x > 0, smap.y > 0)
+        Q3xy = np.ix_(smap.x > 0, smap.y < 0)
+        Q4xy = np.ix_(smap.x < 0, smap.y < 0)
     
-    # get A_xy in the four quadrants of the x-y plane
-    A_xy_Q1 = A_xy[Q1xy]
-    A_xy_Q2 = A_xy[Q2xy]
-    A_xy_Q3 = A_xy[Q3xy]
-    A_xy_Q4 = A_xy[Q4xy]
+        # get A_xy in the four quadrants of the x-y plane
+        A_xy_Q1 = A_xy[Q1xy]
+        A_xy_Q2 = A_xy[Q2xy]
+        A_xy_Q3 = A_xy[Q3xy]
+        A_xy_Q4 = A_xy[Q4xy]
     
-    # Not sure if there is a neater way to select the x=0,y=0 cross elements
-    A_xy_0  = np.hstack([A_xy[:,smap.x==0].flatten(), A_xy[smap.y==0,:].flatten()])
+        # Not sure if there is a neater way to select the x=0,y=0 cross elements
+        A_xy_0  = np.hstack([A_xy[:,smap.x==0].flatten(), A_xy[smap.y==0,:].flatten()])
     
-    full_x = smap.x
-    full_y = smap.y
+        full_x = smap.x
+        full_y = smap.y
 
-    dx = full_x[1] - full_x[0]
-    dy = full_y[1] - full_y[0]
+        dx = full_x[1] - full_x[0]
+        dy = full_y[1] - full_y[0]
 
-    if verbose:
-        count  = 4*len(EI["xm"].B) * len(EI["ym"].B)
-        p = ProgressBar(maxval=count, widgets=["Computing weights: ", Percentage(), Bar(), ETA()])
+        if verbose:
+            count  = 4*len(EI["x"].B) * len(EI["y"].B)
+            p = ProgressBar(maxval=count, widgets=["Computing weights: ", Percentage(), Bar(), ETA()])
     
-    n = 0
+        n = 0
     
-    # make integration weights
-    Bx = EI["xm"].B
-    By = EI["ym"].B[:,::-1]
-    w_ij_Q1 = np.zeros((len(Bx),len(By)), dtype = complex)
+        # make integration weights
+        Bx = EI["x"].B
+        By = EI["y"].B[:,::-1]
+        w_ij_Q1 = np.zeros((len(Bx),len(By)), dtype = complex)
     
-    for i in range(len(Bx)):
-        for j in range(len(By)):
-            B_ij_Q1 = np.outer(Bx[i], By[j])
-            w_ij_Q1[i][j] = dx*dy*np.einsum('ij,ij', B_ij_Q1, A_xy_Q1)	
+        for i in range(len(Bx)):
+            for j in range(len(By)):
+                B_ij_Q1 = np.outer(Bx[i], By[j])
+                w_ij_Q1[i][j] = dx*dy*np.einsum('ij,ij', B_ij_Q1, A_xy_Q1)	
             
-            if verbose:
-                p.update(n)
-                n+=1
-
-    Bx = EI["xm"].B[:,::-1]
-    By = EI["ym"].B[:,::-1]
-    w_ij_Q2 = np.zeros((len(Bx),len(By)), dtype = complex)
-    
-    for i in range(len(Bx)):
-        for j in range(len(By)):
-            B_ij_Q2 = np.outer(Bx[i], By[j])
-            w_ij_Q2[i][j] = dx*dy*np.einsum('ij,ij', B_ij_Q2, A_xy_Q2)
+                if verbose:
+                    p.update(n)
+                    n+=1
+
+        Bx = EI["x"].B[:,::-1]
+        By = EI["y"].B[:,::-1]
+        w_ij_Q2 = np.zeros((len(Bx),len(By)), dtype = complex)
+    
+        for i in range(len(Bx)):
+            for j in range(len(By)):
+                B_ij_Q2 = np.outer(Bx[i], By[j])
+                w_ij_Q2[i][j] = dx*dy*np.einsum('ij,ij', B_ij_Q2, A_xy_Q2)
             
-            if verbose:
-                p.update(n)
-                n+=1
+                if verbose:
+                    p.update(n)
+                    n+=1
+
+        Bx = EI["x"].B[:,::-1]
+        By = EI["y"].B
+        w_ij_Q3 = np.zeros((len(Bx),len(By)), dtype = complex)
+    
+        for i in range(len(Bx)):
+            for j in range(len(By)):
+                B_ij_Q3 = np.outer(Bx[i], By[j])
+                w_ij_Q3[i][j] = dx*dy*np.einsum('ij,ij', B_ij_Q3, A_xy_Q3)
+
+                if verbose:
+                    p.update(n)
+                    n+=1
+
+        Bx = EI["x"].B
+        By = EI["y"].B
+        w_ij_Q4 = np.zeros((len(Bx),len(By)), dtype = complex)
+    
+        for i in range(len(Bx)):
+            for j in range(len(By)):
+                B_ij_Q4 = np.outer(Bx[i], By[j])
+                w_ij_Q4[i][j] = dx*dy*np.einsum('ij,ij', B_ij_Q4, A_xy_Q4)
+
+                if verbose:
+                    p.update(n)
+                    n+=1
+        if verbose:
+            p.finish()
+        
+        return ROMWeights(w_ij_Q1=w_ij_Q1, w_ij_Q2=w_ij_Q2, w_ij_Q3=w_ij_Q3, w_ij_Q4=w_ij_Q4, EI=EI, limits=EI["x"].limits)
+        
+    else:
+        # get full A_xy
+        A_xy = smap.z_xy()
 
-    Bx = EI["xm"].B[:,::-1]
-    By = EI["ym"].B
-    w_ij_Q3 = np.zeros((len(Bx),len(By)), dtype = complex)
-    
-    for i in range(len(Bx)):
-        for j in range(len(By)):
-            B_ij_Q3 = np.outer(Bx[i], By[j])
-            w_ij_Q3[i][j] = dx*dy*np.einsum('ij,ij', B_ij_Q3, A_xy_Q3)
+        full_x = smap.x
+        full_y = smap.y
 
-            if verbose:
-                p.update(n)
-                n+=1
+        dx = full_x[1] - full_x[0]
+        dy = full_y[1] - full_y[0]
 
-    Bx = EI["xm"].B
-    By = EI["ym"].B
-    w_ij_Q4 = np.zeros((len(Bx),len(By)), dtype = complex)
-    
-    for i in range(len(Bx)):
-        for j in range(len(By)):
-            B_ij_Q4 = np.outer(Bx[i], By[j])
-            w_ij_Q4[i][j] = dx*dy*np.einsum('ij,ij', B_ij_Q4, A_xy_Q4)
+        if verbose:
+            count  = len(EI["x"].B) * len(EI["y"].B)
+            p = ProgressBar(maxval=count, widgets=["Computing weights: ", Percentage(), Bar(), ETA()])
 
-            if verbose:
-                p.update(n)
-                n+=1
-    if verbose:
-        p.finish()
+        n = 0
+
+        # make integration weights
+        Bx = EI["x"].B
+        By = EI["y"].B
         
-    return ROMWeights(w_ij_Q1=w_ij_Q1, w_ij_Q2=w_ij_Q2, w_ij_Q3=w_ij_Q3, w_ij_Q4=w_ij_Q4, EI=EI, limits=EI["xm"].limits)
-    
+        w_ij = np.zeros((len(Bx), len(By)), dtype = complex)
+
+        for i in range(len(Bx)):
+            for j in range(len(By)):
+                B_ij = np.outer(Bx[i], By[j])
+                w_ij[i][j] = dx*dy*np.einsum('ij,ij', B_ij, A_xy)	
     
+                if verbose:
+                    p.update(n)
+                    n+=1
+                    
+        if verbose:
+            p.finish()
+
+        return ROMWeightsFull(w_ij=w_ij, EI=EI, limits=EI["x"].limits)
+
     
     
-- 
GitLab