Source code for src.Libs.Simulation.UFO.OpticsDataClass

#!/usr/bin/env python3
"""
This file is part of the PAFFrontendSim.

The PAFFrontendSim is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License v3 as published by the Free Software Foundation.

The PAFFrontendSim is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with the PAFFrontendSim. If not, see https://www.gnu.org/licenses/.
"""
#############################################################################
# import libs
#############################################################################
import sys
import numpy                     as np
import matplotlib.pyplot         as plt
from   copy                      import copy, deepcopy
from pathlib import Path

import LogClass
from Simulation.UFO              import getRotMatrixForAxis, getAngles, PhysicalTrace
from Simulation.UFO.UFOGaussCalc import complete
from Simulation.constants        import _Tiso, _ETA

#############################################################################
# constants
#############################################################################
_CLASSNAME   = "OpticsData"

#############################################################################
# classes
#############################################################################
[docs] class OpticsElementData : """ Individual optics element parameters """ def __init__ (self, ufo, inputZ = [0., 0., 1.], inputX = [1., 0., 0.], position = [0., 0., 0.], rotZ = False, trans = False, erIn = 1., log = None) : # initializing Element data if log == None: self.log = LogClass.LogClass(_CLASSNAME) else: self.log = deepcopy(log) # logging class used for all in and output messages self.log.addLabel(self) self.ufo = deepcopy( ufo ) self.printOut( "Creating new element with the following parameters:", self.log.FLOW_TYPE) self.printOut( " Input position [mm] : (%.2f, %.2f, %.2f)" % ( position[0], position[1], position[2] ), self.log.DATA_TYPE, False ) self.printOut( " Input path direction [mm] : (%.2f, %.2f, %.2f)" % ( inputZ[0], inputZ[1], inputZ[2] ), self.log.DATA_TYPE, False ) self.printOut( " Input pol-zeor dir [mm] : (%.2f, %.2f, %.2f)" % ( inputX[0], inputX[1], inputX[2] ), self.log.DATA_TYPE, False ) self.printOut( " Reflection angle [deg] : %.2f" % self.ufo.RefAngle, self.log.DATA_TYPE, False ) self.trans = trans # normalize input vectors of coordinate system orientation inputZ = np.asarray(inputZ) inputX = np.asarray(inputX) self.inputZ = inputZ / ( np.dot( inputZ, inputZ ) ) self.inputX = inputX / ( np.dot( inputX, inputX ) ) self.position = np.asarray(position) self.rotZ = rotZ self.ufo.resetPosition() # bring surface to nominal position if self.rotZ : self.ufo.rotZ( 180. ) rot = 180. rot2 = 0. if np.abs( self.ufo.RefAngle ) < ( 180. - 1e-3 ) : rot -= self.ufo.RefAngle / 2. if trans : beta = np.arcsin( np.sqrt(self.ufo.er) / np.sqrt( erIn ) * np.sin( self.ufo.RefAngle / 2. / 180. * np.pi ) ) * 180. / np.pi rot2 = 0. #-beta else: rot2 = -self.ufo.RefAngle + 180 self.ufo.rotY ( rot ) rotY = np.asarray ([ [ np.cos(np.pi * rot2 / 180.), 0, np.sin(np.pi * rot2 / 180.)], [ 0, 1, 0], [-np.sin(np.pi * rot2 / 180.), 0, np.cos(np.pi * rot2 / 180.)] ]) ufoIZ = np.asarray( [0., 0., 1.] ) ufoIX = np.asarray( [1., 0., 0.] ) ufoOZ = np.dot( rotY, ufoIZ ) ufoOX = np.dot( rotY, ufoIX ) # get rotation angles, rotate UFO dat object and move it to its designated position rot, rotInv, angles = getAngles( self.inputZ, self.inputX ) self.ufo.rotZ ( -angles [2] ) self.ufo.rotY ( -angles [1] ) self.ufo.rotX ( -angles [0] ) self.ufo.move ( self.position[0], self.position[1], self.position[2] ) self.outputZ = np.dot( rotInv, ufoOZ ) self.outputX = np.dot( rotInv, ufoOX ) self.printOut( " Output path direction [mm] : (%.2f, %.2f, %.2f)" % ( self.outputZ[0], self.outputZ[1], self.outputZ[2] ), self.log.DATA_TYPE, False ) self.printOut( " Output pol-zeor dir [mm] : (%.2f, %.2f, %.2f)" % ( self.outputX[0], self.outputX[1], self.outputX[2] ), self.log.DATA_TYPE, False ) # internal methods
[docs] def printOut ( self, line, message_type, prefix = True, lineFeed = True ): """ Wrapper for the printOut method of the actual log class """ self.log.printOut(line, message_type, prefix, lineFeed)
[docs] class OpticsData : """ Class to handle an optical system composed of UFODataClass objects. This system is an optional component placed between the telscopes focal point and the PAF. The development and implementation of this module is still WiP. """ ##################################### # internal data and init method # init method def __init__ ( self, inputPosition = [ 0., 0., 0. ], inputZ = [ 0., 0., 1.], inputX = [ 1., 0., 0.], log = None ): """ Initialize class. Parameters: inputPosition (array of float): position of the nominal optics input phase center inputZ (array of float): normal vector of the input field propagation direction inputX (array of float): orientation of the x-axis (polarization for pol-angle = 0 deg) log (LogClass): Logging Class used by the calling application """ if log == None: self.log = LogClass.LogClass(_CLASSNAME) else: self.log = deepcopy(log) # logging class used for all in and output messages self.log.addLabel(self) self.printOut( "Initializing optics data", self.log.FLOW_TYPE ) self.printOut( " Input position [mm] : (%.2f, %.2f, %.2f)" % ( inputPosition[0], inputPosition[1], inputPosition[2] ), self.log.DATA_TYPE, False ) self.printOut( " Input Z (path direction) [mm] : (%.2f, %.2f, %.2f)" % ( inputZ[0], inputZ[1], inputZ[2] ), self.log.DATA_TYPE, False ) self.printOut( " Input X [mm] : (%.2f, %.2f, %.2f)" % ( inputX[0], inputX[1], inputX[2] ), self.log.DATA_TYPE, False ) self.InputPosition = np.asarray( inputPosition ) # position of the nominal input phase center InputZ = np.asarray( inputZ ) # normal vector of the input field propagation direction self.InputZ = InputZ / np.dot( InputZ, InputZ ) InputX = np.asarray( inputX ) # normal vector of the zero-degree polarization angle [deg] at input position self.InputX = InputX / np.dot( InputX, InputX ) self.outputDistance = -1 # output distance (if negative, focal length of last element is used self.Elements = [] # list of optical elements # internal methods
[docs] def printOut ( self, line, message_type, prefix = True, lineFeed = True ): """ Wrapper for the printOut method of the actual log class """ self.log.printOut(line, message_type, prefix, lineFeed)
##################################### # setup optics
[docs] def addElement ( self, ufo, distance, rotation = 0., rotZ = False, trans = False ) : """ Add an optical element to the optics. Parameters: ufo (UFODataClass): UFODataClass object of the element to add distance (float): distance [mm] to last element or to starting point rotation (float): rotation angle (deg) of the element around the input axis (to change optical plane) rotZ (boolean) : True: rotate object around z-axis without changing in- and output (correct errorsome conic section creation if required) trans (boolean) : True: use transmitted field direction """ self.printOut( "Adding element (Name: '%s') in distance %.2f mm to optics" % ( ufo.Name, distance ), self.log.FLOW_TYPE ) self.printOut( " rotation around input beam axis : %.2f deg" % ( rotation ), self.log.DATA_TYPE ) self.printOut( " change in and output of the mirror : %s" % ( rotZ ), self.log.DATA_TYPE ) er = 1. if len( self.Elements ) == 0 : position = self.InputPosition + self.InputZ * distance inputZ = deepcopy( self.InputZ ) inputX = deepcopy( self.InputX ) else : position = self.Elements[-1].position+ self.Elements[-1].outputZ * distance inputZ = deepcopy( self.Elements[-1].outputZ ) inputX = deepcopy( self.Elements[-1].outputX ) er = self.Elements[-1].ufo.er inputX = np.dot( getRotMatrixForAxis(inputZ, rotation), inputX ) self.Elements.append( OpticsElementData( ufo, inputZ, inputX, position, rotZ = rotZ, trans = trans, erIn = er, log = self.log ) ) if self.outputDistance < 1. : self.outputDistance = ufo.FocalLength if self.outputDistance == None : self.outputDistance = 0.
[docs] def deleteLastElement ( self ) : """ delete last optical element """ self.printOut( "Deleting last optical element", self.log.FLOW_TYPE ) self.Elements.pop( )
[docs] def modifyElement ( self, ufo = None, index = 0 ) : """ """ oldElements = self.Elements for e in oldElements : pass pass
[docs] def setOutputDistance ( self, distance ) : """ """ self.printOut( "Set optics output distance to %.2f mm" % distance, self.log.FLOW_TYPE ) self.outputDistance = distance
##################################### # position optics in 3d-space
[docs] def move ( self, vector ): """ move the optics Parameters: vector (array of float): moving vector """ self.printOut( "Move optics by (%.2f, %.2f, %.2f) mm" % ( vector[0], vector[1], vector[2] ), self.log.FLOW_TYPE ) vector = np.asarray( vector ) self.InputPosition += vector for e in self.Elements : e.ufo.move( vector[0], vector[1], vector[2] ) e.position += vector
[docs] def rotate ( self, rotMatrix, center = [0., 0., 0.] ) : """ rotate optics by around the point defined by center """ self.printOut( "Rotating optics", self.log.FLOW_TYPE ) center = np.asarray( center ) rotMatrix = np.asarray( rotMatrix ) self.move( -center ) self.InputZ = np.dot( rotMatrix, self.InputZ ) self.InputX = np.dot( rotMatrix, self.InputX ) self.InputPosition = np.dot( rotMatrix, self.InputPosition ) for e in self.Elements : e.ufo.transform( rotMatrix ) e.inputZ = np.dot( rotMatrix, e.inputZ ) e.inputX = np.dot( rotMatrix, e.inputX ) e.outputZ = np.dot( rotMatrix, e.outputZ ) e.outputX = np.dot( rotMatrix, e.outputX ) e.position = np.dot( rotMatrix, e.position ) self.move( center )
[docs] def setOutputPort ( self, outputZ, outputX, outputPos ) : """ set output position and direction """ self.printOut( "Setting output port", self.log.FLOW_TYPE ) self.printOut( " Output position [mm] : (%.2f, %.2f, %.2f)" % ( outputPos[0], outputPos[1], outputPos[2] ), self.log.DATA_TYPE, False ) self.printOut( " Output path direction [mm] : (%.2f, %.2f, %.2f)" % ( outputZ[0], outputZ[1], outputZ[2] ), self.log.DATA_TYPE, False ) self.printOut( " Output pol-zeor dir [mm] : (%.2f, %.2f, %.2f)" % ( outputX[0], outputX[1], outputX[2] ), self.log.DATA_TYPE, False ) outputZ = np.asarray( outputZ ) outputX = np.asarray( outputX ) outputPos = np.asarray( outputPos ) if len( self.Elements ) == 0 : self.InputZ = outputZ self.InputX = outputX self.InputPosition = outputPos else : myOutput = self.Elements[-1].position + self.Elements[-1].outputZ * self.outputDistance self.move( -myOutput ) rot, iRot, angles = getAngles( self.Elements[-1].outputZ, self.Elements[-1].outputX ) self.rotate ( rot ) rot, iRot, angles = getAngles( outputZ, outputX ) self.rotate ( iRot ) self.move( outputPos )
[docs] def setInputPort ( self, inputZ, inputX, inputPos ) : """ set input position and direction """ self.printOut( "Setting output port", self.log.FLOW_TYPE ) self.printOut( " Input position [mm] : (%.2f, %.2f, %.2f)" % ( inputPos[0], inputPos[1], inputPos[2] ), self.log.DATA_TYPE, False ) self.printOut( " Input path direction [mm] : (%.2f, %.2f, %.2f)" % ( inputZ[0], inputZ[1], inputZ[2] ), self.log.DATA_TYPE, False ) self.printOut( " Input pol-zeor dir [mm] : (%.2f, %.2f, %.2f)" % ( inputX[0], inputX[1], inputX[2] ), self.log.DATA_TYPE, False ) inputZ = np.asarray( inputZ ) inputX = np.asarray( inputX ) inputPos = np.asarray( inputPos ) if len( self.Elements ) == 0 : self.InputZ = inputZ self.InputX = inputX self.InputPosition = inputPos else : self.move( -self.InputPosition ) rot, iRot, angles = getAngles( self.InputZ, self.InputX ) self.rotate ( rot ) rot, iRot, angles = getAngles( inputZ, inputX ) self.rotate ( iRot ) self.move( inputPos )
##################################### # data access and optics simulation
[docs] def getTraceList ( self ) : """ get the list of UFO objects for tracing """ self.printOut( "Getting trace-list", self.log.DEBUG_TYPE ) traceList = [] for e in self.Elements : traceList.append( e.ufo ) return traceList
[docs] def simulateOptics ( self, Source = None, opticsLoss = 0., opticsTemp = _Tiso ) : """ physical optics simulation """ self.printOut( "Simulating optics system using given source", self.log.FLOW_TYPE ) if len( self.Elements ) == 0 : return if type(Source) == type(None) : traceList = self.getTraceList() Source = traceList[0] else : traceList = [Source] + self.getTraceList() wavelength = Source.Wavelength phCenter = self.InputZ * wavelength * 1000. + self.InputPosition # calculate farfield loction result, loss, lossT = PhysicalTrace( traceList, wavelength, opticsLoss, opticsTemp, waist = None, phCenter = phCenter) # add optical losses to loss to get the correct temperature dependence phCenter = self.Elements[-1].outputZ * wavelength * 1000. + self.Elements[-1].position # get farfielt center point powerOut = result.getPower(phCenter = phCenter)[0] # get output power powerLoss = loss.getPower()[0] # get loss-power power = 0.5 * _ETA * Source.I0**2 #* 1e6 # get input power from current if ( powerLoss > 0. ) and ( power - powerOut > powerLoss ) : factor = np.sqrt( power - powerOut ) / powerLoss # calculate factor to rise fields in loss class to compensate for opticallosses loss._modField ( factor, onlyE = False) # adapt loss and loss weighted with temperature lossT._modField ( factor, onlyE = False) # set excitation current of the output to the nominal value for the output power (allowing therewith for cascading optics) #result.setI0( np.sqrt( 2. * powerOut / _ETA ))#* 1e-6 ) ) return result, loss, lossT