diff --git a/pykat/gw_detectors/aLIGO.py b/pykat/gw_detectors/aLIGO.py
index 19e8954b4516969d096d7a4c8fb30828d25aaf46..1dc07595d9def2e48f1cee4bf92cffb8a86f979e 100644
--- a/pykat/gw_detectors/aLIGO.py
+++ b/pykat/gw_detectors/aLIGO.py
@@ -10,6 +10,8 @@ import warnings
 import cmath
 import pykat.components
 import pykat.exceptions as pkex
+import pykat.external.peakdetect as peak
+import matplotlib.pyplot as plt
 
 
 nsilica=1.44963098985906
@@ -40,10 +42,83 @@ def make_transparent(kat, _components):
                 components = [c for c in components if c != o.name]
             else:
                 raise pkex.BasePyKatException("{} is not a mirror or beamsplitter".format(o))
-            print(' Made {} transparent,'.format(o))
+            print('  made {} transparent'.format(o))
     if len(components) != 0:
         raise pkex.BasePyKatException("Cannot find component {}".format(components))
     return kat
+
+def magic_length(): # TODO: improve and reference this!
+    # Cavity lengths
+    #const Lprc 57.656
+    #const Lsrc 56.008
+    #const Lschnupp 0.08
+
+    # Individual lengths
+    # PRC
+    #const Lpr1 16.6107
+    #const Lpr2 16.1647
+    #const Lpr3 19.5381 
+
+    # SRC
+    #const Lsr1 15.7586
+    #const Lsr2 15.4435
+    #const Lsr3 19.3661
+
+    Laver = 57.656 - 16.6107 - 16.1647 - 19.5381
+    # x length between BS and ITM
+    Lmx = Laver + 0.5*0.08 - 0.06873 * 1.44963098985906 - 0.2*1.44963098985906
+    Lmy = Laver - 0.2*1.44963098985906 - 0.5*0.08
+    #func Lasrc = $Laver + $BSthickness * $nsilica
+    #func Lsr3 = $Lsrc - $Lsr1 - $Lsr2 - $Lasrc
+    #put ls3 L $Lsr3
+
+    return Lmx, Lmy
+
+def reconnect_nodes(kat, component1, idx1, node_name):
+    c_string = component1.getFinesseText()
+    c = c_string[0].split()
+    new_string = " ".join(c[:-2])
+    nodes = {}
+    nodes[0] = c[-2]
+    nodes[1] = c[-1]
+    nodes[idx1]=node_name
+    new_string = new_string + " " + nodes[0] + " " + nodes[1]
+    print(" new string ='{}'".format(new_string))
+    kat.parseCommands(new_string)
+
+def remove_commands(kat, _commands):
+    commands=make_list_copy(_commands)
+    # removing commands
+    for o in kat.commands.values():
+        if o.name in commands:
+            o.remove()
+            commands = [c for c in commands if c != o.name]
+            print('  {} removed'.format(o))
+    if len(commands) != 0:
+        raise pkex.BasePyKatException("Cannot find command(s) {}".format(commands))
+    return kat
+    
+def remove_components(kat, _components, component_in=None, component_out=None):
+    components=make_list_copy(_components)
+    if  kat.components[components[-1]].nodes[1]:
+        node_in  = kat.components[components[-1]].nodes[1].name
+    else:
+        node_in = None
+    node_out = kat.components[components[0]].nodes[0].name
+    # removing components
+    for o in kat.components.values():
+        if o.name in components:
+            o.remove()
+            components = [c for c in components if c != o.name]
+            print('  {} removed'.format(o))
+    if len(components) != 0:
+        raise pkex.BasePyKatException("Cannot find component(s) {}".format(components))
+    # reconnecting nodes if requested
+    if component_in:
+        reconnect_nodes(kat, kat.components[component_in],1, node_in)
+    if component_out:
+        reconnect_nodes(kat, kat.components[component_out],0, node_out)
+    return kat
         
 def BS_optical_path(thickness, n=nsilica, angle=45.0):
     """
@@ -60,6 +135,79 @@ def BS_optical_path(thickness, n=nsilica, angle=45.0):
     
     return math.degrees(angle_subst), L
 
+def scan_optics(_kat, _optics, _factors, detector, xlimits=[-100, 100], steps=200, minmax='max', debug=False): 
+    """
+    Scans one or more optics (by changing its tuning) and find the tuning with max/min
+    output on a sepcific detectos. Useful for pre-tuning cavities or interferometers.
+    Returns the tuning of the maximum (or minimum) and the precision
+    Parameters:
+    optics: list of names of components to be tuned
+    factors: list of scaling factors for the tuning for each optics, first element must be 1.0
+    detector: name of detetor to use
+    xlimits: limits of scan, defaults to [-100, 100]
+    steps: number of steps to use in scan, default is 200
+    minmax, string to indicate maximum or minum detection, default 'max'
+    Usage:
+    scan_optis(kat, "PRM", 1, "pdout")
+    scan_optis(kat, ["ETMX", "ETMY", [1, -1], "pdout", minmax='min')
+    """
+    kat = _kat.deepcopy()
+    
+    optics=make_list_copy(_optics)
+    factors=make_list_copy(_factors)
+    if len(optics) != len(factors):
+        raise pkex.BasePyKatException("You must provide a factor for each optics")
+    if factors[0]!=1.0:
+        raise pkex.BasePyKatException("First element in `factors' must be 1.0")
+
+    kat.parseCommands("""
+    xaxis {0} phi lin {1} {2} {3}
+    """.format(optics[0], xlimits[0], xlimits[1], steps))
+
+    idx=1
+    for o in optics[1:]:
+        kat.parseCommands("""
+        func nx{0} = ({1}) * $x1
+        put {2} phi $nx{0}""".format(idx,factors[idx],o))
+    
+    out = kat.run()
+    
+    stepsize = out.x[1]-out.x[0]
+    
+    print("  stepsize (precision) of scan: {0:g}".format(stepsize))
+
+    if debug==True:
+        plt.figure()
+        plt.plot(out.x,out[detector])
+
+    _max, _min = peak.peakdetect( out[detector],out.x, 1)
+    
+    if debug==True:
+        print("max: ")
+        print(_max)
+        print("min: ")
+        print(_min)
+        
+    if minmax == 'max':
+        X = [p[0] for p in _max]
+        Y = [p[1] for p in _max]
+        X_out = X[np.argmax(Y)]
+        Y_out = np.max(Y)
+    elif minmax == 'min':
+        X = [p[0] for p in _min]
+        Y = [p[1] for p in _min]
+        X_out = X[np.argmin(Y)]
+        Y_out = np.min(Y)
+    else:
+        raise pkex.BasePyKatException("maxmin must be 'max' or 'min'")
+        
+    if debug==True:
+        plt.plot(X_out,Y_out,'o')
+        plt.xlabel('{0} tuning [deg]'.format(optics[0]))
+        plt.ylabel('{0} output'.format(detector))
+        plt.show()
+    return X_out, stepsize
+
 def make_list_copy(_l):
     """
     Utility function, takes a list of strings or single string