Source code for soapy.confParse

#Copyright Durham University and Andrew Reeves
#2014

# This file is part of soapy.

#     soapy is free software: you can redistribute it and/or modify
#     it under the terms of the GNU General Public License as published by
#     the Free Software Foundation, either version 3 of the License, or
#     (at your option) any later version.

#     soapy 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 soapy.  If not, see <http://www.gnu.org/licenses/>.
"""
A module to generate configuration objects for Soapy, given a parameter file.

This module defines a number of classes, which when instantiated, create objects used to configure the entire simulation, or just submodules. All configuration objects are stored in the ``Configurator`` object which deals with loading parameters from file, checking some potential conflicts and using parameters to calculate some other parameters used in parts of the simulation.

The ``ConfigObj`` provides a base class used by other module configuration objects, and provides methods to read the parameters from the dictionary read from file, and set defaults if appropriate. Each other module in the system has its own configuration object, and for components such as wave-front sensors (WFSs), Deformable Mirrors (DMs), Laser Guide Stars (LGSs) and Science Cameras,  lists of the config objects for each component are created.


"""

import numpy
import traceback
import copy

from . import logger

# Check if can use yaml configuration style
try:

    import yaml
    YAML = True
except ImportError:
    logger.info("Can't import pyyaml. Can only use old python config style")
    YAML = False

# Attributes that can be contained in all configs
CONFIG_ATTRIBUTES = [
        'N',
            ]

RAD2ASEC = 206264.849159
ASEC2RAD = 1./RAD2ASEC

[docs]class ConfigurationError(Exception): pass
[docs]class PY_Configurator(object): """ The configuration class holding all simulation configuration information This class is used to load the parameter dictionary from file, instantiate each configuration object and calculate some other parameters from the parameters given. The configuration file given to this class must contain a python dictionary, named ``simConfiguration``. This must contain other dictionaries for each sub-module of the system, ``Sim``, ``Atmosphere``, ``Telescope``, ``WFS``, ``LGS``, ``DM``, ``Science``. For the final 4 sub-dictionaries, each entry must be formatted as a list (or numpy array) where each value corresponds to that component. The number of components on the module will only depend on the number set in the ``Sim`` dict. For example, if ``nGS`` is set to 2 in ``Sim``, then in the ``WFS`` dict, each parameters must have at least 2 entries, e.g. ``subaps : [10,10]``. If the parameter has more than 2 entries, then only the first 2 will be noted and any others discarded. Descriptions of the available parameters for each sub-module are given in that that config classes documentation Args: filename (string): The name of the configuration file """ def __init__(self, filename): self.filename = filename # placeholder for param objs self.wfss = [] # self.lgss = [] self.scis = [] self.dms = [] self.sim = SimConfig() self.atmos = AtmosConfig() self.tel = TelConfig() self.recon = ReconstructorConfig()
[docs] def readfile(self): #Exec the config file, which should contain a dict ``simConfiguration`` try: with open(self.filename) as file_: exec(file_.read(), globals()) except: traceback.print_exc() raise ConfigurationError( "Error loading config file: {}".format(self.filename)) self.configDict = simConfiguration
[docs] def loadSimParams(self): self.readfile() logger.debug("\nLoad Sim Params...") self.sim.loadParams(self.configDict["Sim"]) logger.debug("\nLoad Atmosphere Params...") self.atmos.loadParams(self.configDict["Atmosphere"]) logger.debug("\nLoad Telescope Params...") self.tel.loadParams(self.configDict["Telescope"]) for nWfs in range(self.sim.nGS): logger.debug("Load LGS {} Params".format(nWfs)) lgs = LgsConfig(nWfs) lgs.loadParams(self.configDict["LGS"]) logger.debug("Load WFS {} Params...".format(nWfs)) self.wfss.append(WfsConfig(nWfs)) self.wfss[nWfs].loadParams(self.configDict["WFS"]) if self.wfss[nWfs].lgs: self.wfss[nWfs].lgs = lgs else: self.wfss[nWfs].lgs = None for dm in range(self.sim.nDM): logger.debug("Load DM {} Params".format(dm)) self.dms.append(DmConfig(dm)) self.dms[dm].loadParams(self.configDict["DM"]) logger.debug("Load Reconstructor Params") self.recon.loadParams(self.configDict["Reconstructor"]) for sci in range(self.sim.nSci): logger.debug("Load Science {} Params".format(sci)) self.scis.append(SciConfig(sci)) self.scis[sci].loadParams(self.configDict["Science"]) self.calcParams()
[docs] def calcParams(self): """ Calculates some parameters from the configuration parameters. """ # Run calcparams on each config object self.sim.calcParams() self.atmos.calcParams() self.tel.calcParams() for w in self.wfss: if w is not None: w.calcParams() # for l in self.lgss: # if l is not None: # l.calcParams() for d in self.dms: if d is not None: d.calcParams() for s in self.scis: if s is not None: s.calcParams() self.sim.pxlScale = (float(self.sim.pupilSize)/ self.tel.telDiam) # We oversize the pupil to what we'll call the "simulation size" simPadRatio = (self.sim.simOversize-1)/2. self.sim.simPad = int(round(self.sim.pupilSize*simPadRatio)) self.sim.simSize = self.sim.pupilSize + 2 * self.sim.simPad # Furthest out GS or SCI target defines the sub-scrn size gsPos = [] for gs in range(self.sim.nGS): pos = self.wfss[gs].GSPosition.astype('float') # Need to add bit if the GS is an elongated off-axis LGS if (hasattr(self.wfss[gs].lgs, 'elongationDepth') and self.wfss[gs].lgs.elongationDepth is not 0): # This calculation is done more explicitely in the WFS module # in the ``calcElongPos`` method maxLaunch = abs(numpy.array( self.wfss[gs].lgs.launchPosition)).max()*self.tel.telDiam/2. dh = numpy.array([ -1*self.wfss[gs].lgs.elongationDepth/2., self.wfss[gs].lgs.elongationDepth/2.]) H = self.wfss[gs].GSHeight theta_n = (max(pos) - (dh*maxLaunch)/(H*(H+dh))* RAD2ASEC).max() pos += theta_n gsPos.append(abs(numpy.array(pos))) for sci in range(self.sim.nSci): gsPos.append(self.scis[sci].position) if len(gsPos)!=0: maxGSPos = numpy.array(gsPos).max() else: maxGSPos = 0 self.sim.scrnSize = numpy.ceil(2* self.sim.pxlScale * self.atmos.scrnHeights.max() * abs(maxGSPos) * ASEC2RAD)+self.sim.simSize self.sim.scrnSize = int(round(self.sim.scrnSize)) # Make scrn_size even if self.sim.scrnSize % 2 != 0: self.sim.scrnSize += 1 # Check if any WFS use physical propogation. # If so, make oversized phase scrns wfsPhys = False for wfs in range(self.sim.nGS): if self.wfss[wfs].propagationMode=="Physical": wfsPhys = True break if wfsPhys: self.sim.scrnSize *= 2 # If any wfs exposure times set to None, set to the sim loopTime for wfs in self.wfss: if not wfs.exposureTime: wfs.exposureTime = self.sim.loopTime logger.info("Pixel Scale: {0:.2f} pxls/m".format(self.sim.pxlScale)) logger.info("subScreenSize: {:d} simulation pixels".format(int(self.sim.scrnSize))) # If outer scale is None, set all to really big number. Will introduce bugs when we make # telescopes with diameter >1000000s of kilometres if self.atmos.L0 is None: self.atmos.L0 = [] for scrn in range(self.atmos.scrnNo): self.atmos.L0.append(10e9) # Check if SH WFS with 1 subap. Feild stop must be FOV for wfs in self.wfss: if wfs.nxSubaps==1 and wfs.subapFieldStop==False: logger.warning("Setting WFS:{} to have field stop at sub-ap FOV as it only has 1 sub-aperture".format(wfs)) wfs.subapFieldStop = True # If dm diameter is None, set to telescope diameter for dm in self.dms: if dm.diameter is None: dm.diameter = self.tel.telDiam
def __iter__(self): objs = {'Sim': dict(self.sim), 'Atmosphere': dict(self.atmos), 'Telescope': dict(self.tel), 'WFS': [], 'LGS': [], 'DM': [], 'Science': [] } for w in self.wfss: if w is not None: objs['WFS'].append(dict(w)) else: objs['WFS'].append(None) # for l in self.lgss: # if l is not None: # objs['LGS'].append(dict(l)) # else: # objs['LGS'].append(None) for d in self.dms: if d is not None: objs['DM'].append(dict(d)) else: objs['DM'].append(None) for s in self.scis: if s is not None: objs['Science'].append(dict(s)) else: objs['Science'].append(None) for configName, configObj in objs.iteritems(): yield configName, configObj def __len__(self): # Always have sim, atmos, tel, DMs, WFSs, LGSs, and Scis return 7
[docs]class YAML_Configurator(PY_Configurator):
[docs] def readfile(self): # load config file from Yaml file with open(self.filename) as file_: self.configDict = yaml.load(file_)
[docs] def loadSimParams(self): self.readfile() logger.debug("\nLoad Sim Params...") self.sim.loadParams(self.configDict) logger.debug("\nLoad Atmosphere Params...") self.atmos.loadParams(self.configDict["Atmosphere"]) logger.debug("\nLoad Telescope Params...") self.tel.loadParams(self.configDict["Telescope"]) for nWfs in range(self.sim.nGS): logger.debug("Load WFS {} Params...".format(nWfs)) wfsDict = self.configDict['WFS'][nWfs] self.wfss.append(WfsConfig(None)) self.wfss[nWfs].loadParams(wfsDict) logger.debug("Load LGS Params") lgsDict = self.wfss[nWfs].lgs if lgsDict is None: self.wfss[nWfs].lgs = None else: self.wfss[nWfs].lgs = (LgsConfig(None)) self.wfss[nWfs].lgs.loadParams(lgsDict) for nDm in range(self.sim.nDM): logger.debug("Load DM {} Params".format(nDm)) dmDict = self.configDict['DM'][nDm] self.dms.append(DmConfig(None)) self.dms[nDm].loadParams(dmDict) logger.debug("Load Reconstructor Params") self.recon.loadParams(self.configDict["Reconstructor"]) for nSci in range(self.sim.nSci): logger.debug("Load Science {} Params".format(nSci)) sciDict = self.configDict['Science'][nSci] self.scis.append(SciConfig(None)) self.scis[nSci].loadParams(sciDict) self.calcParams()
[docs]class ConfigObj(object): # Parameters that can be had by any configuration object def __init__(self, N=None): # This is the index of some config object, i.e. WFS 1, 2, 3..N self.N = N
[docs] def warnAndExit(self, param): message = "{0} not set!".format(param) logger.warning(message) raise ConfigurationError(message)
[docs] def warnAndDefault(self, param, newValue): message = "{0} not set, default to {1}".format(param, newValue) self.__setattr__(param, newValue) logger.debug(message)
[docs] def initParams(self): for param in self.requiredParams: self.__setattr__(param, None)
[docs] def loadParams(self, configDict): if self.N!=None: for param in self.requiredParams: try: self.__setattr__(param, configDict[param][self.N]) except KeyError: self.warnAndExit(param) except IndexError: raise ConfigurationError( "Not enough values for {0}".format(param)) except: raise ConfigurationError( "Failed while loading {0}. Check config file.".format(param)) for param in self.optionalParams: try: self.__setattr__(param[0], configDict[param[0]][self.N]) except KeyError: self.warnAndDefault(param[0], param[1]) except IndexError: raise ConfigurationError( "Not enough values for {0}".format(param)) except: raise ConfigurationError( "Failed while loading {0}. Check config file.".format(param)) else: for param in self.requiredParams: try: self.__setattr__(param, configDict[param]) except KeyError: self.warnAndExit(param) except: raise ConfigurationError( "Failed while loading {0}. Check config file.".format(param)) for param in self.optionalParams: try: self.__setattr__(param[0], configDict[param[0]]) except KeyError: self.warnAndDefault(param[0], param[1]) except: raise ConfigurationError( "Failed while loading {0}. Check config file.".format(param)) self.calcParams()
[docs] def calcParams(self): """ Dummy method to be overidden if required """ pass
def __iter__(self): for param in self.requiredParams: yield param, self.__dict__[param] for param in self.optionalParams: yield param[0], self.__dict__[param[0]] def __len__(self): return len(self.requiredParams)+len(self.optionalParams) def __setattr__(self, name, value): if name in self.allowedAttrs: self.__dict__[name] = value else: raise ConfigurationError("'{}' Attribute not a configuration parameter".format(name)) def __repr__(self): return str(dict(self))
[docs]class SimConfig(ConfigObj): """ Configuration parameters relavent for the entire simulation. These should be held at the beginning of the parameter file with no indendation. Required: ============= =================== **Parameter** **Description** ------------- ------------------- ``pupilSize`` int: Number of phase points across the simulation pupil ``nIters`` int: Number of iteration to run simulation ``loopTime`` float: Time between simulation frames (1/framerate) ============= =================== Optional: ================== ================================= =============== **Parameter** **Description** **Default** ------------------ --------------------------------- --------------- ``nGS`` int: Number of Guide Stars and WFS ``0`` ``nDM`` int: Number of deformable Mirrors ``0`` ``nSci`` int: Number of Science Cameras ``0`` ``reconstructor`` str: name of reconstructor class to use. See ``reconstructor`` module for available reconstructors. ``"MVM"`` ``simName`` str: directory name to store simulation data ``None`` ``wfsMP`` bool: Each WFS uses its own process ``False`` ``verbosity`` int: debug output for the simulation ranging from 0 (no-ouput) to 3 (all debug output) ``2`` ``logfile`` str: name of file to store logging data, ``None`` ``learnIters`` int: Number of `learn` iterations for Learn & Apply reconstructor ``0`` ``learnAtmos`` str: if ``random``, then random phase screens used for `learn` ``random`` ``simOversize`` float: The fraction to pad the pupil size with to reduce edge effects ``1.2`` ``loopDelay`` int: loop delay in integer count of ``loopTime`` ``0`` ``threads`` int: Number of threads to use for multithreaded operations ``1`` ``photometric_zp`` float: Photometric zeropoint - number of photons/meter/second from a magnitude 0 star ``2e9`` ================== ================================= =============== Data Saving (all default to False): ====================== =================== **Parameter** **Description** ---------------------- ------------------- ``saveSlopes`` Save all WFS slopes. Accessed from sim with ``sim.allSlopes`` ``saveDmCommands`` Saves all DM Commands. Accessed from sim with ``sim.allDmCommands`` ``saveWfsFrames`` Saves all WFS pixel data. Saves to disk a after every frame to avoid using too much memory ``saveStrehl`` Saves the science camera Strehl Ratio. Accessed from sim with ``sim.longStrehl`` and ``sim.instStrehl`` ``saveWfe`` Saves the science camera wave front error. Accessed from sim with ``sim.WFE``. ``saveSciPsf`` Saves the science PSF. ``saveInstPsf`` Saves the instantenous science PSF. ``saveInstScieField`` Saves the instantaneous electric field at focal plane. ``saveSciRes`` Save Science residual phase ====================== =================== """ requiredParams = [ "pupilSize", "nIters", "loopTime", ] optionalParams = [ ("nGS", 0), ("nDM", 0), ("nSci", 0), ("gain", 0.6), ("reconstructor", "MVM"), ("simName", None), ("saveSlopes", False), ("saveDmCommands", False), ("saveLgsPsf", False), ("saveLearn", False), ("saveStrehl", False), ("saveWfsFrames", False), ("saveSciPsf", False), ("saveInstPsf", False), ("saveInstScieField", False), ("saveWfe", False), ("saveSciRes", False), ("wfsMP", False), ("verbosity", 2), ("logfile", None), ("learnIters", 0), ("learnAtmos", "random"), ("simOversize", 1.02), ("loopDelay", 0), ("threads", 1), ("photometric_zp", 2e9), ] # Parameters which may be set at some point and are allowed calculatedParams = [ 'pxlScale', 'simPad', 'simSize', 'scrnSize', 'totalWfsData', 'totalActs', 'saveHeader', ] allowedAttrs = copy.copy( requiredParams + calculatedParams + CONFIG_ATTRIBUTES) for p in optionalParams: allowedAttrs.append(p[0])
[docs]class AtmosConfig(ConfigObj): """ Configuration parameters characterising the atmosphere. These should be held in the ``Atmosphere`` group in the parameter file. Required: ================== =================== **Parameter** **Description** ------------------ ------------------- ``scrnNo`` int: Number of turbulence layers ``scrnHeights`` list, int: Phase screen heights in metres ``scrnStrength`` list, float: Relative layer scrnStrength ``windDirs`` list, float: Wind directions in degrees. ``windSpeeds`` list, float: Wind velocities in m/s ``r0`` float: integrated seeing strength (metres at 500nm) ================== =================== Optional: ================== ================================= =========== **Parameter** **Description** **Default** ------------------ --------------------------------- ----------- ``scrnNames`` list, string: filenames of phase if loading from fits files. If ``None`` will make new screens. ``None`` ``subHarmonics`` bool: Use sub-harmonic screen generation algorithm for better tip-tilt statistics - useful for small phase screens. ``False`` ``L0`` list, float: Outer scale of each layer. Kolmogorov turbulence if ``None``. ``None`` ``randomScrns`` bool: Use a random set of phase phase screens for each loop iteration? ``False`` ``infinite`` bool: Use infinite phase screens? ``False`` ``tau0`` float: Turbulence coherence time, if set wind speeds are scaled. ``None`` ``wholeScrnSize`` int: Size of the phase screens to store in the ``atmosphere`` object. Required if large screens used. ``None`` ================== ================================= =========== """ requiredParams = [ "scrnNo", "scrnHeights", "scrnStrengths", "r0", "windDirs", "windSpeeds", ] optionalParams = [ ("scrnNames",None), ("subHarmonics",False), ("L0", None), ("randomScrns", False), ("tau0", None), ("infinite", False), ("wholeScrnSize", None) ] # Parameters which may be set at some point and are allowed calculatedParams = [ 'normScrnStrengths', ] allowedAttrs = copy.copy(requiredParams + calculatedParams + CONFIG_ATTRIBUTES) for p in optionalParams: allowedAttrs.append(p[0])
[docs] def calcParams(self): # Turn lists into numpy arrays self.scrnHeights = numpy.array(self.scrnHeights) self.scrnStrengths = numpy.array(self.scrnStrengths) self.windDirs = numpy.array(self.windDirs) self.windSpeeds = numpy.array(self.windSpeeds) if self.L0 is not None: self.L0 = numpy.array(self.L0)
[docs]class WfsConfig(ConfigObj): """ Configuration parameters characterising Wave-front Sensors. These should be held in the ``WFS`` group in the parameter file. Each WFS is specified by first specifying an index, then the WFS parameters. Any entries above ``sim.nGS`` will be ignored. Required: ================== =================== **Parameter** **Description** ------------------ ------------------- ``GSPosition`` tuple: position of GS on-sky in arc-secs ``wavelength`` float: wavelength of GS light in metres ``nxSubaps`` int: number of SH sub-apertures ================== =================== Optional: ===================== ================================== =========== **Parameter** **Description** **Default** --------------------- ---------------------------------- ----------- ``type`` string: Which WFS object to load from WFS.py? ``ShackHartmann`` ``GSMag`` float: Apparent magnitude of the guide star ``0`` ``photonNoise`` bool: Include photon (shot) noise. ``False`` ``eReadNoise`` float: Electrons of read noise ``0`` ``throughput`` float: Throughput of the entire optical and electronic system from guide star photons to recorded WFS detector counts. Includes atmospheric effects, the optical train and detector gain. ``1.`` ``propagationMode`` string: Mode of light propogation from GS. Can be "Physical" or "Geometric"\**. ``"Geometric"`` ``subapFieldStop`` bool: if True, add a field stop to the wfs to prevent spots wandering into adjacent sub-apertures. if False, oversample subap FOV by a factor of 2 to allow into adjacent subaps. ``False`` ``removeTT`` bool: if True, remove TT signal from WFS slopes before reconstruction.\** ``False`` ``fftOversamp`` int: Multiplied by the number of of phase points required for FOV to increase fidelity from FFT. ``3`` ``GSHeight`` float: Height of GS beacon. ``0`` if at infinity. ``0`` ``subapThreshold`` float: How full should subap be to be used for wavefront sensing? ``0.5`` ``lgs`` bool: is WFS an LGS? ``False`` ``centMethod`` string: Method used for Centroiding. Can be ``centreOfGravity``, ``brightestPxl``, or ``correlation``.\** ``centreOfGravity`` ``referenceImage`` array: Reference images used in the correlation centroider. Full image plane image, each subap has a separate reference image ``None`` ``angleEquivNoise`` float: width of gaussian noise added to slopes measurements in arc-secs ``0`` ``centThreshold`` float: Centroiding threshold as a fraction of the max subap value.\** ``0.1`` ``exposureTime`` float: Exposure time of the WFS camera - must be higher than loopTime. If None, will be set to loopTime. ``None`` ``wvlBandWidth`` float: Width of wavelength band sent to WFS in nm ``100`` ``extendedObject`` ndarray or str: The object used as extended source for WFS, of size 2*fftOversamp*pxlsPerSubap. The FOV of the object should be twice the FOV of the sub-aperture. ``None`` ``fftwThreads`` int: number of threads for fftw to use. If ``0``, will use system processor number. ``1`` ``fftwFlag`` str: Flag to pass to FFTW when preparing plan. ``FFTW_PATIENT`` ``pxlsPerSubap`` int: number of pixels per sub-apertures ``10`` ``subapFOV`` float: Field of View of sub-aperture in arc-secs ``5`` ``correlationFFTPad`` int: Padding for correlation WFS ``None`` ``nx_guard_pixels`` int: Guard Pixels between Shack-Hartmann sub-apertures (Not currently operational) ``0`` ===================== ================================== =========== """ requiredParams = [ "GSPosition", "wavelength", "nxSubaps", ] optionalParams = [ ("propagationMode", "Geometric"), ("fftwThreads", 1), ("fftwFlag", "FFTW_PATIENT"), ("angleEquivNoise", 0), ("subapFieldStop", False), ("removeTT", "False"), ("angleEquivNoise", 0), ("fftOversamp", 3), ("GSHeight", 0), ("subapThreshold", 0.5), ("lgs", None), ("centThreshold", 0.), ("centMethod", "centreOfGravity"), ("type", "ShackHartmann"), ("exposureTime", None), ("referenceImage", None), ("throughput", 1.), ("eReadNoise", 0), ("photonNoise", False), ("GSMag", 0.0), ("wvlBandWidth", 100.), ("extendedObject", None), ("pxlsPerSubap", 10), ("subapFOV", 5), ("correlationFFTPad", None), ("nx_guard_pixels", 0), ] # Parameters which may be Set at some point and are allowed calculatedParams = [ 'position', 'pxlsPerSubap2', 'dataStart', 'lgs', ] allowedAttrs = copy.copy( requiredParams + calculatedParams + CONFIG_ATTRIBUTES) for p in optionalParams: allowedAttrs.append(p[0])
[docs] def calcParams(self): # Set some parameters to correct type self.GSPosition = numpy.array(self.GSPosition) self.position = self.GSPosition # For compatability # Ensure wavelength is a float self.wavelength = float(self.wavelength)
# If LGS exists, calc its params too # if self.lgs is not None: # self.lgs.calcParams()
[docs]class TelConfig(ConfigObj): """ Configuration parameters characterising the Telescope. These should be held in the ``Telescope`` group in the parameter file. Required: ============= =================== **Parameter** **Description** ------------- ------------------- ``telDiam`` float: Diameter of telescope pupil in metres ============= =================== Optional: ================== ================================= =========== **Parameter** **Description** **Default** ------------------ --------------------------------- ----------- ``obsDiam`` float: Diameter of central obscuration ``0`` ``mask`` str: Shape of pupil (only accepts ``circle`` currently) ``circle`` ================== ================================= =========== """ requiredParams = [ "telDiam", ] optionalParams = [ ("obsDiam", 0), ("mask", "circle") ] calculatedParams = [ ] allowedAttrs = copy.copy(requiredParams + calculatedParams + CONFIG_ATTRIBUTES) for p in optionalParams: allowedAttrs.append(p[0])
[docs]class LgsConfig(ConfigObj): """ Configuration parameters characterising the Laser Guide Stars. These should be held in the ``LGS`` sub-group of the WFS parameter group. Optional: ==================== ================================= =========== **Parameter** **Description** **Default** -------------------- --------------------------------- ----------- ``uplink`` bool: Include LGS uplink effects ``False`` ``pupilDiam`` float: Diameter of LGS launch aperture in metres. ``0.3`` ``wavelength`` float: Wavelength of laser beam in metres ``600e-9`` ``propagationMode`` str: Mode of light propogation from GS. Can be "Physical" or "Geometric". ``"Phsyical"`` ``height`` float: Height to use physical propogation of LGS (does not effect cone-effect) in metres ``90000`` ``elongationDepth`` float: Depth of LGS elongation in metres ``0`` ``elongationLayers`` int: Number of layers to simulate for elongation. ``10`` ``launchPosition`` tuple: The launch position of the LGS in units of the pupil radii, where ``(0,0)`` is the centre launched case, and ``(1,0)`` is side-launched. ``(0,0)`` ``fftwThreads`` int: number of threads for fftw to use. If ``0``, will use system processor number. ``1`` ``fftwFlag`` str: Flag to pass to FFTW when preparing plan. ``FFTW_PATIENT`` ``naProfile`` list: The relative sodium layer strength for each elongation layer. If None, all equal. ``None`` ==================== ================================= =========== """ requiredParams = [ ] optionalParams = [ ("uplink", False), ("pupilDiam", 0.3), ("wavelength", 600e-9), ("propagationMode", "Physical"), ("height", 90000), ("fftwFlag", "FFTW_PATIENT"), ("fftwThreads", 0), ("elongationDepth", 0), ("elongationLayers", 10), ("launchPosition", numpy.array([0,0])), ("naProfile", None), ] calculatedParams = ["position"] allowedAttrs = copy.copy( requiredParams + calculatedParams + CONFIG_ATTRIBUTES) for p in optionalParams: allowedAttrs.append(p[0])
[docs] def calcParams(self): # If lgs sodium layer profile is none, set it to 1s for each layer if not hasattr(self, "naProfile") or self.naProfile is None: self.naProfile = numpy.ones(self.elongationLayers) if len(self.naProfile)<self.elongationLayers: raise ConfigurationError("Not enough values for naProfile") self.wavelength = float(self.wavelength) self.height = float(self.height)
[docs]class DmConfig(ConfigObj): """ Configuration parameters characterising Deformable Mirrors. These should be held in the ``DM`` sub-group of the parameter file. Each DM is specified seperately, by first specifying an index, then the DM parameters. Any entries above ``sim.nGS`` will be ignored. Required: =================== =============================================== **Parameter** **Description** ------------------- ----------------------------------------------- ``type`` string: Type of DM. This must the name of a class in the ``DM`` module. ``nxActuators`` int: Number independent DM shapes. e.g., for stack-array DMs this is number of actuators in one dimension, for Zernike DMs this is number of Zernike modes. ``gain`` float: The loop gain for the DM.\** ``svdConditioning`` float: The conditioning parameter used in the pseudo inverse of the interaction matrix. This is performed by `numpy.linalg.pinv <http://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.pinv.html>`_. =================== =============================================== Optional: ==================== ================================= =========== **Parameter** **Description** **Default** -------------------- --------------------------------- ----------- ``closed`` bool:Is DM closed loop of WFS?\** ``True`` ``iMatValue`` float: Value to push actuators when making iMat ``10`` ``wfs`` int: which Wfs to take iMat and use to correct for. ``0`` ``rotation`` float: A DM rotation with respect to the pupil in degrees ``0`` ``interpOrder`` Order of interpolation for dm, including piezo actuators and rotation. ``2`` ``gaussWidth`` float: Width of Guass DM actuator as a fraction of the inter-actuator spacing. ``0.5`` ``altitude`` float: Altitude to which DM is optically conjugated. ``0`` ``diameter`` float: Diameter covered by DM in metres. If ``None`` if telescope diameter. ``None`` ``gauss_width`` float: Width of gaussian influence functions in units of actuator spacing. ``1`` ==================== ================================= =========== """ requiredParams = [ "type", ] optionalParams = [ ("nxActuators", None), ("svdConditioning", 0), ("gain", 0.6), ("closed", True), ("iMatValue", 10), ("wfs", None), ("rotation", 0), ("interpOrder", 2), ("gaussWidth", 0.5), ("altitude", 0.), ("diameter", None), ("gauss_width", 0.7), ] calculatedParams = [ ] allowedAttrs = copy.copy(requiredParams + calculatedParams + CONFIG_ATTRIBUTES) for p in optionalParams: allowedAttrs.append(p[0])
[docs] def calcParams(self): # Some params commonly written in Scientific notation self.iMatValue = float(self.iMatValue) self.svdConditioning = float(self.svdConditioning)
[docs]class ReconstructorConfig(ConfigObj): """ Configuration parameters describing the reconstructor that will be used to calculate DM commands from WFS measurements. The ``type`` must be an object in the ``soapy.reconstruction`` module. Other parameters may be specific to this reconstructor Optional: ==================== ================================= =========== **Parameter** **Description** **Default** -------------------- --------------------------------- ----------- ``type`` string: Type of reconstructor to use. Must be a class in reconstruction module. ``MVM`` ``svdConditioning`` float: Conditioning parameter to be using in Least Squares reconstructor inversion SVD to cut off unwanted DM modes. See ``numpy.linalg.pinv`` for details about the inversion. ``0`` ``gain`` float: Gain of the integrator loop. ``0.6`` ``imat_noise`` bool: include WFS noise when making in interaction matrix ``True`` ==================== ================================= =========== """ requiredParams = [ ] optionalParams = [ ("type", "MVM"), ("svdConditioning", 0.), ("gain", 0.6), ("imat_noise", True) ] calculatedParams = [ ] allowedAttrs = copy.copy(requiredParams + calculatedParams + CONFIG_ATTRIBUTES) for p in optionalParams: allowedAttrs.append(p[0])
[docs]class SciConfig(ConfigObj): """ Configuration parameters characterising Science Cameras. These should be held in the ``Science`` of the parameter file. Each Science target is created seperately with an integer index. Any entries above ``sim.nSci`` will be ignored. Required: ================== ============================================ **Parameter** **Description** ------------------ -------------------------------------------- ``position`` tuple: The position of the science camera in the field in arc-seconds ``FOV`` float: The field of fiew of the science detector in arc-seconds ``wavelength`` float: The wavelength of the science detector light ``pxls`` int: Number of pixels in the science detector ================== ============================================ Optional: ==================== ================================= =========== **Parameter** **Description** **Default** -------------------- --------------------------------- ----------- ``pxlScale`` float: Pixel scale of science camera, in arcseconds. If set, overwrites ``FOV``. ``None`` ``type`` string: Type of science camera This must the name of a class in the ``SCI`` module. ``PSF`` ``fftOversamp`` int: Multiplied by the number of of phase points required for FOV to increase fidelity from FFT. ``2`` ``fftwThreads`` int: number of threads for fftw to use. If ``0``, will use system processor number. ``1`` ``fftwFlag`` str: Flag to pass to FFTW when preparing plan. ``FFTW_MEASURE`` ``height`` float: Altitude of the object. 0 denotes infinity. ``0`` ``propagationMode`` str: Mode of light propogation from object. Can be "Physical" or "Geometric". ``"Geometric"`` ``instStrehlWithTT`` bool: Whether or not to include tip/tilt in instantaneous Strehl calculations. ``False`` ==================== ================================= =========== """ requiredParams = [ "position", "wavelength", "pxls", ] optionalParams = [ ("pxlScale", None), ("FOV", None), ("type", "PSF"), ("fftOversamp", 2), ("fftwFlag", "FFTW_MEASURE"), ("fftwThreads", 1), ("instStrehlWithTT", False), ("height", 0), ("propagationMode", "Geometric") ] calculatedParams = [ ] allowedAttrs = copy.copy(requiredParams + calculatedParams + CONFIG_ATTRIBUTES) for p in optionalParams: allowedAttrs.append(p[0])
[docs] def calcParams(self): # Set some parameters to correct type self.position = numpy.array(self.position) self.wavelength = float(self.wavelength) if (self.pxlScale is None) and (self.FOV is None): raise ConfigurationError("Must supply either FOV or pxlScale for SCI") if (self.pxlScale is not None) and ((self.pxlScale * self.pxls) != self.FOV): logger.warning("Overriding sci FOV with pxlscale") self.FOV = self.pxlScale * self.pxls else: self.pxlScale = float(self.FOV)/self.pxls
[docs]def loadSoapyConfig(configfile): # Find configfile extension file_ext = configfile.split('.')[-1] # If YAML use yaml configurator if file_ext=='yml' or file_ext=='yaml': if YAML: config = YAML_Configurator(configfile) else: raise ImportError("Requires pyyaml for YAML config file") # Otherwise, try and execute as python else: config = PY_Configurator(configfile) config.loadSimParams() return config
# compatability Configurator = PY_Configurator
[docs]def test(): C = Configurator("conf/testConfNew.py") C.readfile() C.loadSimParams() print("Test Passesd!") return 0
if __name__ == "__main__": test()