From 4d1730297f07871bff09fc8e97a49c62b89da4c5 Mon Sep 17 00:00:00 2001
From: Daniel Brown <ddb@star.sr.bham.ac.uk>
Date: Fri, 31 Jan 2014 01:21:22 +0000
Subject: [PATCH] adding in fsig command (not parsing yet). See example
 test_fsig.py in bin folder. Also made component variable an optional argument
 for xaxis and x2axis which will break previous scripts. Did this as when
 setting the parameter to tune, the Param object contains whatever component
 owns that parameter so no need to pass it twice. Also stops someone passing a
 parameter not for the component stated.

---
 bin/test_fsig.py    |  50 +++++++++++++++++++
 pykat/commands.py   |  24 +++++----
 pykat/components.py |  12 ++---
 pykat/finesse.py    | 116 ++++++++++++++++++++++++++++++++++++++++++--
 pykat/param.py      |  19 +++++++-
 5 files changed, 195 insertions(+), 26 deletions(-)
 create mode 100644 bin/test_fsig.py

diff --git a/bin/test_fsig.py b/bin/test_fsig.py
new file mode 100644
index 0000000..2e5b318
--- /dev/null
+++ b/bin/test_fsig.py
@@ -0,0 +1,50 @@
+from pykat import finesse
+from pykat.detectors import *
+from pykat.components import *
+from pykat.commands import *
+from pykat.structs import *
+
+import numpy as np
+import pylab as pl
+
+code = """
+l l1 2 0 n1
+m m1 0.99 0.01 0 n1 n2
+s cav1 1200 n2 n3
+m m2 0.99 0.01 -0.1 n3 n4
+
+attr m2 m 1  # mech sus1
+
+ad up_refl 0 n1
+ad low_refl 0 n1
+
+qd refl_A 0 0 n1
+qd refl_Q 0 90 n1
+qd tran_A 0 0 n4
+qd tran_Q 0 90 n4
+
+put up_refl f $x1
+put low_refl f $mx1
+
+yaxis log re:im
+"""
+
+kat = finesse.kat(kat_code=code)
+
+kat.signals.apply(kat.l1.power, 1, 0)
+kat.signals.apply(kat.m1.phi, 1, 90)
+
+kat.add(xaxis('log', [1, 1000], kat.signals.f, 100))
+
+out = kat.run(printout=0, printerr=0)
+
+# using real and imag part compute the complex value of the upper and lower sidebands
+a_up = out.y[:,0] + out.y[:,1]*1j
+a_lo = out.y[:,2] + out.y[:,3]*-1j
+
+pl.figure(1)
+pl.loglog(out.x, np.abs(a_up + a_lo), out.x, np.abs((a_up - a_lo) / (1j)))
+pl.xlabel(out.xlabel)
+pl.title("Reflection quadratures with no relative carrier phase")
+pl.legend(["Amplitude","Phase"])
+pl.show()
\ No newline at end of file
diff --git a/pykat/commands.py b/pykat/commands.py
index 27a3bda..a47b0dd 100644
--- a/pykat/commands.py
+++ b/pykat/commands.py
@@ -10,6 +10,7 @@ import exceptions
 from components import *
 from structs import *
 from pykat.param import Param, putter
+import pykat.exceptions as pkex
 
 class Command(object):
     def __init__(self):
@@ -50,7 +51,7 @@ class gauss(object):
         
 class xaxis(Command):
 
-    def __init__(self, scale, limits, comp, param, steps, axis_type="xaxis"):
+    def __init__(self, scale, limits, param, steps, comp=None, axis_type="xaxis"):
         self._axis_type = axis_type
 
         self.x = putter("x1")
@@ -79,20 +80,17 @@ class xaxis(Command):
 
         self.steps = int(steps)
 
-        # if entered component is a string then just store and use that
-        if isinstance(comp, str):
-            self.__comp = comp
-        elif not isinstance(comp, Component):
-            raise exceptions.ValueError("{0} has not been added".format(comp))
-        else:
-            self.__comp = comp
-
         if isinstance(param, str):
             self.__param = param
+            if comp == None:
+                raise pkex.BasePyKatException("If parameter is set with a string, the comp argument must set the component name")
+                
+            self.__comp = comp
         elif not isinstance(param, Param) :
-            raise exceptions.ValueError("param argument is not of type Param")
+            raise pkex.BasePyKatException("param argument is not of type Param")
         else:
             self.__param = param
+            self.__comp = param._owner.name
 
     @staticmethod
     def parseFinesseText(text):
@@ -108,7 +106,7 @@ class xaxis(Command):
         if len(values) != 6:
             raise exceptions.RuntimeError("xaxis Finesse code format incorrect '{0}'".format(text))
 
-        return xaxis(values[2], [values[3], values[4]], values[0], values[1], values[5], axis_type=axis_type)
+        return xaxis(values[2], [values[3], values[4]], values[1], values[5], comp=values[0], axis_type=axis_type)
 
     def getFinesseText(self):
         # store either the component name of the string provided
@@ -120,8 +118,8 @@ class xaxis(Command):
                 min(self.limits), max(self.limits), self.steps, axis_type=self._axis_type);
 
 class x2axis(xaxis):
-    def __init__(self, scale, limits, comp, param, steps):
-        xaxis.__init__(self, scale, limits, comp, param, steps, axis_type="x2axis")
+    def __init__(self, scale, limits, param, steps, comp=None):
+        xaxis.__init__(self, scale, limits, param, steps, comp=comp, axis_type="x2axis")
         self.x = putter("x2")
         self.mx = putter("mx2")
 
diff --git a/pykat/components.py b/pykat/components.py
index 8a37f6f..696c42e 100644
--- a/pykat/components.py
+++ b/pykat/components.py
@@ -158,11 +158,11 @@ class AbstractMirrorComponent(Component):
 
         self.__R = Param("R", self, SIfloat(R))
         self.__T = Param("T", self, SIfloat(T))
-        self.__phi = Param("phi", self, SIfloat(phi))
+        self.__phi = Param("phi", self, SIfloat(phi), canFsig=True, fsig_name="phs")
         self.__Rcx = AttrParam("Rcx", self, SIfloat(Rcx))
         self.__Rcy = AttrParam("Rcy", self, SIfloat(Rcy))
-        self.__xbeta = AttrParam("xbeta", self, SIfloat(xbeta))
-        self.__ybeta = AttrParam("ybeta", self, SIfloat(ybeta))
+        self.__xbeta = AttrParam("xbeta", self, SIfloat(xbeta), canFsig=True, fsig_name="x")
+        self.__ybeta = AttrParam("ybeta", self, SIfloat(ybeta), canFsig=True, fsig_name="y")
         self.__mass = AttrParam("mass", self, SIfloat(mass))
         self.__r_ap = AttrParam("r_ap", self, SIfloat(r_ap))
         
@@ -682,9 +682,9 @@ class laser(Component):
         
         self._requested_node_names.append(node)
         
-        self.__power = Param("P", self, SIfloat(P))
-        self.__f_offset = Param("f", self, SIfloat(f_offset))
-        self.__phase = Param("phase", self, SIfloat(phase))
+        self.__power = Param("P", self, SIfloat(P), canFsig=True, fsig_name="amp")
+        self.__f_offset = Param("f", self, SIfloat(f_offset), canFsig=True, fsig_name="f")
+        self.__phase = Param("phase", self, SIfloat(phase), canFsig=True, fsig_name="phs")
         self.__noise = AttrParam("noise", self, 0)
         
     @property
diff --git a/pykat/finesse.py b/pykat/finesse.py
index a40f858..c94ef79 100644
--- a/pykat/finesse.py
+++ b/pykat/finesse.py
@@ -40,6 +40,7 @@ from pykat.components import Component
 from pykat.commands import Command, xaxis
 from pykat.gui.gui import pyKatGUI
 from pykat.SIfloat import *
+from pykat.param import Param, AttrParam
 
 import pykat.exceptions as pkex
 
@@ -127,7 +128,98 @@ class katRun2D(object):
             return self.z[idx].squeeze()
         else:
             raise  pkex.BasePyKatException("No output by the name {0} found".format(str(value)))
-      
+    
+class Signals:
+    class fsig:
+        def __init__(self, param, name, amplitude, phase):
+            self._params = []
+            self.__target = param
+            self.__name = name
+            self.__amplitude = Param("amp", self, SIfloat(amplitude))
+            self.__phase = Param("phase", self, SIfloat(phase))
+            
+            # unfortunatenly the target names for fsig are not the same as the
+            # various parameter names of the components, e.g. mirror xbeta is x 
+            # for fsig. So we need to check here what type of component we are targetting
+            # and then based on the parameter specfied get the name
+            if not param.canFsig:
+                raise  pkex.BasePyKatException("Cannot fsig parameter {1} on component {0}".format(str(param._owner), param.name))
+            
+            
+        def _register_param(self, param):
+            self._params.append(param)
+  
+        @property
+        def name(self): return self.__name
+
+        @property
+        def amplitude(self): return self.__amplitude
+
+        @property
+        def phase(self): return self.__phase
+
+        @property
+        def target(self): return self.__target.fsig_name
+
+        @property
+        def owner(self): return self.__target._owner.name
+    
+        def getFinesseText(self):
+            rtn = []
+    
+            for p in self._params:
+                rtn.extend(p.getFinesseText())
+        
+            return rtn
+    
+    @property
+    def name(self):
+        # if we don't have any signals yet then use a dummy name
+        # however we need to always tune a real fsig command
+        # so need to get the name of at least one of them
+        # as if you tune one you tune them all
+        if len(self.targets) == 0:
+            return "signal"
+        else:
+            return self.targets[0].name
+
+    @property
+    def f(self): return self.__f
+    
+    def __init__(self):
+
+        self.targets = []
+        self._params = []
+        
+        self.__f = Param("f", self, 1)
+    
+    def _register_param(self, param):
+        self._params.append(param)
+        
+    def apply(self, target, amplitude, phase, name=None):
+        
+        if target == None:
+            raise  pkex.BasePyKatException("No target was specified for signal to be applied")
+        
+        if name == None:
+            name = "sig_" +target._owner.name + "_" + target.name
+        
+        self.targets.append(Signals.fsig(target, name, amplitude, phase))
+        
+        
+    def getFinesseText(self):
+        rtn = []
+        
+        for t in self.targets:
+            rtn.extend(t.getFinesseText())
+            rtn.append("fsig {name} {comp} {target} {frequency} {phase} {amplitude}".format(name = t.name, comp=t.owner, target=t.target, frequency=str(self.f), phase=str(t.phase), amplitude=str(t.amplitude)))
+        
+        for p in self._params:
+            rtn.extend(p.getFinesseText())
+        
+        return rtn
+        
+        
 class Block:
     def __init__(self, name):
         self.__name = name
@@ -154,10 +246,11 @@ class kat(object):
         self.__tempdir = tempdir
         self.__tempname = tempname
         self.pykatgui = None
-
-	# initialise default block
-	self.__currentTag= NO_BLOCK
-	self.__blocks[NO_BLOCK] = Block(NO_BLOCK)
+        self.__signals = Signals()
+        
+        # initialise default block
+        self.__currentTag= NO_BLOCK
+        self.__blocks[NO_BLOCK] = Block(NO_BLOCK)
         
         # Various options for running finesse, typicaly the commands with just 1 input
         # and have no name attached to them.
@@ -181,6 +274,9 @@ class kat(object):
         cls = type(self)
         self.__class__ = type(cls.__name__, (cls,), {})
         
+    @property
+    def signals(self): return self.__signals
+
     @property
     def maxtem(self): return self.__maxtem
     @maxtem.setter
@@ -689,6 +785,16 @@ class kat(object):
                     out.append(txt + "\n")
         
 
+        # now get any signal commands
+        txt = self.signals.getFinesseText()
+        
+        if txt != None:
+            if isinstance(txt,list):
+                for t in txt: out.append(t+ "\n")
+            else:
+                out.append(txt + "\n")
+                            
+
         if self.scale != None and self.scale !='': out.append("scale {0}\n".format(self.scale))
         if self.phase != None: out.append("phase {0}\n".format(self.phase))
         if self.maxtem != None: out.append("maxtem {0}\n".format(self.maxtem))            
diff --git a/pykat/param.py b/pykat/param.py
index e47d378..52a5fc8 100644
--- a/pykat/param.py
+++ b/pykat/param.py
@@ -57,13 +57,22 @@ class putter(object):
         
 class Param(putable, putter):
 
-    def __init__(self, name, owner, value, isPutable=True, isPutter=True, isTunable=True, var_name=None):
+    def __init__(self, name, owner, value, canFsig=False, fsig_name=None, isPutable=True, isPutter=True, isTunable=True, var_name=None):
         self._name = name
         self._owner = owner
         self._value = value
         self._isPutter = isPutter
         self._isTunable = isTunable
         self._owner._register_param(self)
+        self._canFsig = False
+        
+        if canFsig:
+            self._canFsig = True
+
+            if fsig_name == None:
+                raise pkex.BasePyKatException("If parameter is a possible fsig target the fsig_name argument must be set")
+
+            self.__fsig_name = fsig_name
         
         if isPutter:
             if var_name == None:
@@ -73,7 +82,13 @@ class Param(putable, putter):
             
         if isPutable:
             putable.__init__(self, owner.name, name, isPutable)
-        
+    
+    @property
+    def canFsig(self): return self._canFsig
+    
+    @property
+    def fsig_name(self): return self.__fsig_name
+    
     @property
     def name(self): return self._name
     
-- 
GitLab