Skip to content
Snippets Groups Projects
Select Git revision
  • 1ffdfb27b94318df1dea4aef72ba549dc9d03025
  • master default
2 results

maps.py

Blame
  • Forked from finesse / pykat
    260 commits behind the upstream repository.
    maps.py 18.06 KiB
    """
    ------------------------------------------------------
    Utility functions for handling mirror surface
    maps. Some functions based on earlier version
    in Matlab (http://www.gwoptics.org/simtools/)
    Work in progress, currently these functions are
    untested!
    
    http://www.gwoptics.org/pykat/
    ------------------------------------------------------
    """
    from __future__ import absolute_import
    from __future__ import division
    from __future__ import print_function
    
    from pykat.optics.romhom import makeReducedBasis, makeEmpiricalInterpolant, makeWeights, makeWeightsNew
    from scipy.interpolate import interp2d, interp1d
    from pykat.maths.zernike import *        
    
    import numpy as np
    import math
    import pickle
    		   
    class surfacemap(object):
        def __init__(self, name, maptype, size, center, step_size, scaling, data=None):
            
            self.name = name
            self.type = maptype
            self.center = center
            self.step_size = step_size
            self.scaling = scaling
            self.__interp = None
            
            if data is None:
                self.data = np.zeros(size)
            else:
                self.data = data
    
            self._rom_weights = None
            
        def write_map(self, filename):
            with open(filename,'w') as mapfile:
                
                mapfile.write("% Surface map\n")
                mapfile.write("% Name: {0}\n".format(self.name))
                mapfile.write("% Type: {0}\n".format(self.type))
                mapfile.write("% Size: {0} {1}\n".format(self.data.shape[0], self.data.shape[1]))
                mapfile.write("% Optical center (x,y): {0} {1}\n".format(self.center[0], self.center[1]))
                mapfile.write("% Step size (x,y): {0} {1}\n".format(self.step_size[0], self.step_size[1]))
                mapfile.write("% Scaling: {0}\n".format(float(self.scaling)))
                mapfile.write("\n\n")
                
                for i in range(0, self.data.shape[0]):
                    for j in range(0, self.data.shape[1]):
                        mapfile.write("%.15g " % self.data[i,j])
                    mapfile.write("\n")
        
        @property
        def data(self):
            return self.__data
        
        @data.setter
        def data(self, value):
            self.__data = value
            self.__interp = None
        
        @property
        def center(self):
            return self.__center
        
        @center.setter
        def center(self, value):
            self.__center = value
            self.__interp = None
        
        @property
        def step_size(self):
            return self.__step_size
        
        @step_size.setter
        def step_size(self, value):
            self.__step_size = value
            self.__interp = None
    
        @property
        def scaling(self):
            return self.__scaling
        
        @scaling.setter
        def scaling(self, value):
            self.__scaling = value
            self.__interp = None
    
        @property
        def x(self):
            return self.step_size[0] * (np.array(range(1, self.data.shape[0]+1)) - self.center[0])
            
        @property
        def y(self):
            return self.step_size[1] * (np.array(range(1, self.data.shape[1]+1))- self.center[1])
    
        @property
        def size(self):
            return self.data.shape
                
        @property
        def offset(self):
            return np.array(self.step_size)*(np.array(self.center) - 1/2. - np.array(self.size)/2.0)
        
        @property
        def ROMWeights(self):
            return self._rom_weights
        
        def z_xy(self, x=None, y=None, wavelength=1064e-9, direction="11", nr1=1.0, nr2=1.0):
            """
            For this given map the field perturbation is computed. This data
            is used in computing the coupling coefficient. It returns a grid
            of complex values representing the change in amplitude or phase
            of the field.
            
                x, y      : Points to interpolate at, 'None' for no interpolation.
                wavelength: Wavelength of light in vacuum [m]
                direction : 11 (reflection front)
                            12 (transmission front to back)
                            21 (transmission back to front)
                            22 (reflection back)
                nr1       : refractive index on front side
                nr2       : refractive index on back side
            """
            
            assert(nr1 >= 1)
            assert(nr2 >= 1)
            
            if x is None and y is None:
                data = self.scaling * self.data
            else:
                if self.__interp is None:
                    self.__interp = interp2d(self.x, self.y, self.data * self.scaling)
                    
                data = self.__interp(x, y)
                
            if direction == "11" or direction == "22":
                if "phase" in self.type:
                    k = math.pi * nr1 * 2 / wavelength
                    
                    if direction == "11":
                        return np.exp(-2j * k * data)
                    else:
                        return np.exp(2j * k * data[:, ::-1])
                    
                elif "absorption" in self.type:
                    if direction == "11":
                        return np.sqrt(1.0 - data)
                    else:
                        return np.sqrt(1.0 - data[:, ::-1])
                else:
                    raise BasePyKatException("Map type needs handling")
                    
            elif direction == "12" or direction == "21":
                if "phase" in self.type:
                    k = math.pi * 2 / wavelength
                    
                    if direction == "12":
                        return np.exp((nr1-nr2)*k * data)
                    else:
                        return np.exp((nr1-nr2)*k * data[:, ::-1])
                    
                elif "absorption" in self.type:
                    if direction == "12":
                        return np.sqrt(1.0 - data)
                    else:
                        return np.sqrt(1.0 - data[:, ::-1])
                else:
                    raise BasePyKatException("Map type needs handling")
                    
            else:
                raise ValueError("Direction not valid")
            
    
        
        def generateROMWeights(self, EIxFilename, EIyFilename=None, verbose=False, interpolate=False, newtonCotesOrder=8):
            if interpolate == True:
                # Use EI nodes to interpolate if we
                with open(EIxFilename, 'rb') as f:
                    EIx = pickle.load(f)
    
                if EIyFilename is None:
                    EIy = EIx
                else:
                    with open(EIyFilename, 'rb') as f:
                        EIy = pickle.load(f)
    
                x = EIx.x
                x.sort()
                nx = np.unique(np.hstack((x, -x[::-1])))
            
                y = EIy.x
                y.sort()
                ny = np.unique(np.hstack((y, -y[::-1])))
                
                self.interpolate(nx, ny)
            
            self._rom_weights = makeWeightsNew(self, EIxFilename, EIyFilename, verbose=verbose, newtonCotesOrderMapWeight=newtonCotesOrder)
            return self.ROMWeights
    
        def interpolate(self, nx, ny, **kwargs):
            """
            Interpolates the map for some new x and y values.
            
            Uses scipy.interpolate.interp2d and any keywords arguments are
            passed on to it, thus settings like interpolation type and
            fill values can be set.
            
            The range of nx and ny must contain the value zero so that the
            center point of the map can be set.
            """
    
            D = interp2d(self.x, self.y, self.data, **kwargs)
            
            data = D(nx-self.offset[0], ny-self.offset[0])
            
            Dx = interp1d(nx, np.arange(1,len(nx)+1))
            Dy = interp1d(ny, np.arange(1,len(ny)+1))
            
            self.center = (Dx(0), Dy(0))
            self.step_size = (nx[1]-nx[0], ny[1]-ny[0])
            self.data = data
    
        def plot(self, show=True, clabel=None, xlim=None, ylim=None):
            
            import pylab
            
            if xlim is not None:
                _x = np.logical_and(self.x<=max(xlim)/100.0, self.x>=min(xlim)/100.0)
                xmin = np.min(np.where(_x == True))
                xmax = np.max(np.where(_x == True))
            else:
                xmin = 0
                xmax = len(self.x)-1
                xlim = [self.x.min()*100, self.x.max()*100]
        
            if ylim is not None:
                _y = np.logical_and(self.y<=max(ylim)/100.0, self.y>=min(ylim)/100.0)
                ymin = np.min(np.where(_y == True))
                ymax = np.max(np.where(_y == True))
            else:
                ymin = 0
                ymax = len(self.y)-1
                ylim = [self.y.min()*100, self.y.max()*100]
            
            zmin = self.data[xmin:xmax,ymin:ymax].min()
            zmax = self.data[xmin:xmax,ymin:ymax].max()
    
            # 100 factor for scaling to cm
            xrange = 100*self.x
            yrange = 100*self.y
    
            fig = pylab.figure()
            axes = pylab.pcolormesh(xrange, yrange, self.data, vmin=zmin, vmax=zmax)
            pylab.xlabel('x [cm]')
            pylab.ylabel('y [cm]')
    
            if xlim is not None: pylab.xlim(xlim)
            if ylim is not None: pylab.ylim(ylim)
    
            pylab.title('Surface map {0}, type {1}'.format(self.name, self.type))
    
            cbar = fig.colorbar(axes)
            cbar.set_clim(zmin, zmax)
            
            if clabel is not None:
                cbar.set_label(clabel)
        
            if show:
                pylab.show()
            
            return fig
    
    class mergedmap:
        """
        A merged map combines multiple surfaces map to form one. Such a map can be used
        for computations of coupling coefficients but it cannot be written to a file to 
        be used with Finesse. For this you must output each map separately.
        
        """
        
        def __init__(self, name, size, center, step_size, scaling):
            
            self.name = name
            self.center = center
            self.step_size = step_size
            self.scaling = scaling
            self.__interp = None
            self._rom_weights = None
            self.__maps = []
            self.weighting = None
            
        def addMap(self, m):
            self.__maps.append(m)
        
        @property
        def center(self):
            return self.__center
        
        @center.setter
        def center(self, value):
            self.__center = value
            self.__interp = None
        
        @property
        def step_size(self):
            return self.__step_size
        
        @step_size.setter
        def step_size(self, value):
            self.__step_size = value
            self.__interp = None
    
        @property
        def scaling(self):
            return self.__scaling
        
        @scaling.setter
        def scaling(self, value):
            self.__scaling = value
            self.__interp = None
        
        @property
        def x(self):
            return self.step_size[0] * (np.array(range(1, self.size[0]+1)) - self.center[0])
            
        @property
        def y(self):
            return self.step_size[1] * (np.array(range(1, self.size[1]+1))- self.center[1])
    
        @property
        def size(self):
            return self.__maps[0].data.shape
                
        @property
        def offset(self):
            return np.array(self.step_size)*(np.array(self.center) - 1/2. - np.array(self.size)/2.0)
        
        @property
        def ROMWeights(self):
            return self._rom_weights
        
        def z_xy(self, wavelength=1064e-9, direction="reflection", nr1=1.0, nr2=1.0):
            
            z_xy = np.ones(self.size, dtype=np.complex128)
            
            for m in self.__maps:
                z_xy *= m.z_xy(wavelength=wavelength, direction=direction, nr1=nr1, nr2=nr2)
                
            if self.weighting is None:
                return z_xy
            else:
                return z_xy * self.weighting
            
        def generateROMWeights(self, EIxFilename, EIyFilename=None, verbose=False, interpolate=False, newtonCotesOrder=8):
            if interpolate == True:
                # Use EI nodes to interpolate if we
                with open(EIxFilename, 'rb') as f:
                    EIx = pickle.load(f)
    
                if EIyFilename is None:
                    EIy = EIx
                else:
                    with open(EIyFilename, 'rb') as f:
                        EIy = pickle.load(f)
    
                x = EIx.x
                x.sort()
                nx = np.unique(np.hstack((x, -x[::-1])))
            
                y = EIy.x
                y.sort()
                ny = np.unique(np.hstack((y, -y[::-1])))
                
                self.interpolate(nx, ny)
            
            self._rom_weights = makeWeightsNew(self, EIxFilename, EIyFilename, verbose=verbose, newtonCotesOrderMapWeight=newtonCotesOrder)
            
            return self.ROMWeights
    
        def interpolate(self, nx, ny, **kwargs):
            """
            Interpolates all the maps that are used to fc
            
            Uses scipy.interpolate.interp2d and any keywords arguments are
            passed on to it, thus settings like interpolation type and
            fill values can be set.
            
            The range of nx and ny must contain the value zero so that the
            center point of the map can be set.
            """
    
            for m in self.__maps:
                m.interpolate(nx, ny)
    
        def plot(self, mode="absorption", show=True, clabel=None, xlim=None, ylim=None, wavelength=1064e-9):
            
            import pylab
            
            if xlim is not None:
                _x = np.logical_and(self.x<=max(xlim)/100.0, self.x>=min(xlim)/100.0)
                xmin = np.min(np.where(_x == True))
                xmax = np.max(np.where(_x == True))
            else:
                xmin = 0
                xmax = len(self.x)-1
                xlim = [self.x.min()*100, self.x.max()*100]
        
            if ylim is not None:
                _y = np.logical_and(self.y<=max(ylim)/100.0, self.y>=min(ylim)/100.0)
                ymin = np.min(np.where(_y == True))
                ymax = np.max(np.where(_y == True))
            else:
                ymin = 0
                ymax = len(self.y)-1
                ylim = [self.y.min()*100, self.y.max()*100]
    
            if mode == "absorption":
                # plots how much of field is absorbed
                data = 1-np.abs(self.z_xy())
            elif mode == "meter":
                # plot the phase in terms of meters of displacement
                k = 2*np.pi/wavelength
                data = np.angle(self.z_xy()) / (2*k)
                
            zmin = data[xmin:xmax,ymin:ymax].min()
            zmax = data[xmin:xmax,ymin:ymax].max()
    
            # 100 factor for scaling to cm
            xrange = 100*self.x
            yrange = 100*self.y
    
            fig = pylab.figure()
            axes = pylab.pcolormesh(xrange, yrange, data, vmin=zmin, vmax=zmax)
            pylab.xlabel('x [cm]')
            pylab.ylabel('y [cm]')
    
            if xlim is not None: pylab.xlim(xlim)
            if ylim is not None: pylab.ylim(ylim)
    
            pylab.title('Merged map {0}, mode {1}'.format(self.name, mode))
    
            cbar = fig.colorbar(axes)
            cbar.set_clim(zmin, zmax)
            
            if clabel is not None:
                cbar.set_label(clabel)
        
            if show:
                pylab.show()
            
            return fig
    
    class aperturemap(surfacemap):
        
        def __init__(self, name, size, step_size, R):
            surfacemap.__init__(self, name, "absorption both", size, (np.array(size)+1)/2.0, step_size, 1)
            self.R = R
            
        @property
        def R(self):
            return self.__R
        
        @R.setter
        def R(self, value):
            self.__R = value
        
            xx, yy = np.meshgrid(self.x, self.y)
            
            radius = np.sqrt(xx**2 + yy**2)
            
            self.data = np.zeros(self.size)
            self.data[radius > self.R] = 1.0
            
            
    class curvedmap(surfacemap):
        
        def __init__(self, name, size, step_size, Rc):
            surfacemap.__init__(self, name, "phase reflection", size, (np.array(size)+1)/2.0, step_size, 1e-6)
            self.Rc = Rc
            
        @property
        def Rc(self):
            return self.__Rc
        
        @Rc.setter
        def Rc(self, value):
            self.__Rc = value
        
            xx, yy = np.meshgrid(self.x, self.y)
            
            Rsq = xx**2 + yy**2
            self.data = (self.Rc - math.copysign(1.0, self.Rc) * np.sqrt(self.Rc**2 - Rsq))/ self.scaling
    
    class tiltmap(surfacemap):
        """
        To create a tiltmap, plot it and write it to a file to use with Finesse:
            
            tilts = (1e-6, 1e-8) # tilt in (x, y) radians\
            dx = 1e-4
            L = 0.2
            N = L/dx
            
            tmap = tiltmap("tilt", (N, N), (dx,dx), tilts)
            tmap.plot()
            tmap.write_map("mytilt.map")
        """
        
        def __init__(self, name, size, step_size, tilt):
            surfacemap.__init__(self, name, "phase reflection", size, (np.array(size)+1)/2.0, step_size, 1e-9)
            self.tilt = tilt
            
        @property
        def tilt(self):
            return self.__tilt
        
        @tilt.setter
        def tilt(self, value):
            self.__tilt = value
            
            xx, yy = np.meshgrid(self.x, self.y)
            
            self.data = (yy * self.tilt[1] + xx * self.tilt[0])/self.scaling
            
    
    class zernikemap(surfacemap):
    	def __init__(self, name, size, step_size, radius, scaling=1e-9):
    		surfacemap.__init__(self, name, "phase reflection", size, (np.array(size)+1)/2.0, step_size, scaling)
    		self.__zernikes = {}
    		self.radius = radius
    		
    	@property
    	def radius(self): return self.__radius
    
    	@radius.setter
    	def radius(self, value, update=True):
    		self.__radius = float(value)
    		if update: self.update_data()
    
    	def setZernike(self, m, n, amplitude, update=True):
    		self.__zernikes["%i%i" % (m, n)] = (m,n,amplitude)
    		if update: self.update_data()
    
    	def update_data(self):
    		X,Y = np.meshgrid(self.x, self.y)
    		R = np.sqrt(X**2 + Y**2)
    		PHI = np.arctan2(Y, X)
    
    		data = np.zeros(np.shape(R))
    
    		for i in self.__zernikes.items():
    			data += i[1][2] * zernike(i[1][0], i[1][1], R/self.radius, PHI)
    
    		self.data = data
    	
    			
    	
    def read_map(filename):
        with open(filename, 'r') as f:
            
            f.readline()
            name = f.readline().split(':')[1].strip()
            maptype = f.readline().split(':')[1].strip()
            size = tuple(map(lambda x: float(x), f.readline().split(':')[1].strip().split()))
            center = tuple(map(lambda x: float(x), f.readline().split(':')[1].strip().split()))
            step = tuple(map(lambda x: float(x), f.readline().split(':')[1].strip().split()))
            scaling = float(f.readline().split(':')[1].strip())
            
            
            
        data = np.loadtxt(filename, dtype=np.float64,ndmin=2,comments='%')    
            
        return surfacemap(name,maptype,size,center,step,scaling,data)
        
        
    # TODO: Recreate functions from Simtools:, List taken from: ligo_maps/FT_convert_ligo_map_for_finesse.m
    # map=FT_recenter_mirror_map(map);
    # [map2,A2,Rc_out]=FT_remove_zernike_curvatures_from_map(map,Rc_in);
    # [map2,Rc_out]=FT_remove_curvature_from_mirror_map(map,Rc_in,w, display_style);
    # [map2,offset]=FT_remove_offset_from_mirror_map(map2,1e-2);
    # [map3,x_tilt,y_tilt,offset2]=FT_remove_piston_from_mirror_map(map2,w, display_style);
    # map3=FT_invert_mirror_map(map3, invert);