""" :module EstherPhotonMatterInteractorParameters: Hosting the parameter class for the EstherPhotonMatterInteractor."""
##########################################################################
#                                                                        #
# Copyright (C) 2016, 2017 Richard Briggs, Carsten Fortmann-Grote        #
# Contact: Carsten Fortmann-Grote <carsten.grote@xfel.eu>                #
#                                                                        #
# This file is part of simex_platform.                                   #
# simex_platform 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.                                    #
#                                                                        #
# simex_platform 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 this program.  If not, see <http://www.gnu.org/licenses/>.  #
#                                                                        #
##########################################################################
import numpy
import os
import tempfile
import json
from SimEx.Parameters.AbstractCalculatorParameters import AbstractCalculatorParameters
ESTHER_MATERIAL_DICT = { "Aluminium" : {"name" : "Aluminium",
                       "shortname" : "Al#",
                       "eos_name" : "Al#_e_ses",
                       "mass_density" : 2.7,
                       },
                       "CH" : {"name" : "CH",
                       "shortname" : "CH2",
                       "eos_name" : "CH2_e_ses",
                       "mass_density" : 1.044,
                       },
                       "Diamond" : {"name" : "Diamond",
                       "shortname" : "Dia",
                       "eos_name" : "Dia_e_ses",
                       "mass_density" : 3.51,
                       },
                       "Kapton" : {"name" : "Kapton",
                       "shortname" : "Kap",
                       "eos_name" : "Kap_e_ses",
                       "mass_density" : 1.42,
                       },
                       "Molybdenum" : {"name" : "Mo",
                       "shortname" : "Mo#",
                       "eos_name" : "Mo#_e_ses",
                       "mass_density" : 10.2,
                       },
                       "Gold" : {"name" : "Gold",
                       "shortname" : "Au#",
                       "eos_name" : "Au#_e_ses",
                       "mass_density" : 19.3,
                       },
                       "Iron" : {"name" : "Iron",
                       "shortname" : "Fe#",
                       "eos_name" : "Fe#_e_ses",
                       "mass_density" : 7.85,
                       },
                       "Copper" : {"name" : "Copper",
                       "shortname" : "Cu#",
                       "eos_name" : "Cu#_e_ses",
                       "mass_density" : 8.93,
                       },
                       "Tin" : {"name" : "Tin",
                       "shortname" : "Sn#",
                       "eos_name" : "Sn#_e_ses",
                       "mass_density" : 7.31,
                       },
                       "LiF" : {"name" : "Lithium Fluoride",
                       "shortname" : "LiF",
                       "eos_name" : "LiF_e_ses",
                       "mass_density" : 2.64,
                       },
                       "Titanium" : {"name" : "Titanium",
                       "shortname" : "Ti#",
                       "eos_name" : "Ti#_e_ses",
                       "mass_density" : 4.43,
                       },
                       "Berylium" : {"name" : "Berylium",
                       "shortname" : "Be#",
                       "eos_name" : "Be#_e_ses",
                       "mass_density" : 1.85,
                       },
                       "Cobalt" : {"name" : "Cobalt",
                       "shortname" : "Co#",
                       "eos_name" : "Co#_e_ses",
                       "mass_density" : 8.9,
                       },
                       "Chromium" : {"name" : "Chromium",
                       "shortname" : "Cr#",
                       "eos_name" : "Cr#_e_ses",
                       "mass_density" : 7.19,
                       },
                       "Iron2" : {"name" : "Iron2",
                       "shortname" : "Fe2",
                       "eos_name" : "Fe2_e_ses",
                       "mass_density" : 7.87,
                       },
                       "Water" : {"name" : "Water",
                       "shortname" : "H2O",
                       "eos_name" : "H20_e_ses",
                       "mass_density" : 1.0,
                       },
                       "Magnesium" : {"name" : "Magnesium",
                       "shortname" : "Mg#",
                       "eos_name" : "Mg#_e_ses",
                       "mass_density" : 1.74,
                       },
                       "Mylar" : {"name" : "Mylar",
                       "shortname" : "Myl",
                       "eos_name" : "Myl_e_ses",
                       "mass_density" : 1.38,
                       },
                       "Nickel" : {"name" : "Nickel",
                       "shortname" : "Ni#",
                       "eos_name" : "Ni#_e_ses",
                       "mass_density" : 8.9,
                       },
                       "Lead" : {"name" : "Lead",
                       "shortname" : "Pb#",
                       "eos_name" : "Pb#_e_ses",
                       "mass_density" : 11.35,
                       },
                       "Quartz" : {"name" : "Quartz",
                       "shortname" : "Qua",
                       "eos_name" : "Qua_e_ses",
                       "mass_density" : 2.65,
                       },
                       "Silicon" : {"name" : "Silicon",
                       "shortname" : "Si#",
                       "eos_name" : "Si#_e_ses",
                       "mass_density" : 2.33,
                       },
                       "SiliconOxide" : {"name" : "SiliconOxide",
                       "shortname" : "SiO",
                       "eos_name" : "SiO_e_ses",
                       "mass_density" : 2.65,
                       },
                       "Titanium" : {"name" : "Titanium",
                       "shortname" : "Ti#",
                       "eos_name" : "Ti#_e_ses",
                       "mass_density" : 4.54,
                       },
                       "Vanadium" : {"name" : "Vanadium",
                       "shortname" : "Va#",
                       "eos_name" : "Va#_e_ses",
                       "mass_density" : 6.11,
                       },
                       "Tungsten" : {"name" : "Tungsten",
                       "shortname" : "W##",
                       "eos_name" : "W##_e_ses",
                       "mass_density" : 19.35,
                       },
                       "Silver" : {"name" : "Silver",
                       "shortname" : "Ag#",
                       "eos_name" : "Ag#_e_ses",
                       "mass_density" : 10.5,
                       },
           }
[docs]class EstherPhotonMatterInteractorParameters(AbstractCalculatorParameters):
    """
    :class EstherPhotonMatterInteractorParameters: representing parameters for the Esthe Hydrocode Calculator.
    """
    def __init__(self,
                 number_of_layers=None,
                 ablator=None,
                 ablator_thickness=None,
                 sample=None,
                 sample_thickness=None,
                 layer1=None,
                 layer1_thickness=None,
                 layer2=None,
                 layer2_thickness=None,
                 window=None,
                 window_thickness=None,
                 laser_wavelength=None,
                 laser_pulse=None,
                 laser_pulse_duration=None,
                 laser_intensity=None,
                 run_time=None,
                 delta_time=None,
                 read_from_file=None,
                 force_passage=None,
                 without_therm_conduc=None,
                 rad_transfer=None,
                 ):
        """
        :param ablator: The ablating material ( "Al" | "CH" | "Diamond" | "Kapton" | "Mylar" )
        :type ablator: str
        :param ablator_thickness: The ablator thickness (micrometers)
        :type ablator_thickness:
        :param sample: The sample material (from list of materials)
        :type sample: str
        :param sample_thickness: The sample thickness (micrometers)
        :type sample_thickness: float
        :param layer1: The layer1 material (from list of materials)
        :type layer1: str
        :param layer2: The layer2 material (from list of materials)
        :type layer2: str
        :param window: The window material (LiF | SiO2 | Diamond)
        :type window: str
        :param window_thickness: The window thickness, if using window (micrometers)
        :type window_thickness: float
        :param laser_pulse: Pulse type ("flat" | "ramp" | "other")
        :type laser_pulse: str
        :param laser_pulse_duration: Pulse duration of the pump laser (ns)
        :type laser_pulse_duration: float
        :param laser_wavelength: Laser wavelength (nm)
        :type laser_wavelength: float
        :param laser_intensity: Laser intensity (TW/cm2)
        :type laser_intensity: float
        :param run_time: Simulation run time (ns)
        :type run_time: float
        :param delta_time: Time steps resolution (ns)
        :type delta_time: float
        :param force_passage: Expert option to force passage of simulation through minor errors
        :type force_passage: boolean
        :param without_therm_conduc: Expert option to use without thermal conductivity options
        :type without_therm_conduc: boolean
        :param rad_transfer: Expert option to use radiative transfer
        :type rad_transfer: boolean
        """
        # If parameters already exist, read from parameters file
        if read_from_file is not None:
            print(( "Parameters file is located here: %s" % (read_from_file)))
            self._readParametersFromFile(read_from_file)
            # Update parameters from arguments.
            for key,val in list({
                    'number_of_layers':number_of_layers,
                    'ablator':ablator,
                    'ablator_thickness':ablator_thickness,
                    'sample':sample,
                    'sample_thickness':sample_thickness,
                    'layer1':layer1,
                    'layer1_thickness':layer1_thickness,
                    'layer2':layer2,
                    'layer2_thickness':layer2_thickness,
                    'window':window,
                    'window_thickness':window_thickness,
                    'laser_wavelength':laser_wavelength,
                    'laser_pulse':laser_pulse,
                    'laser_pulse_duration':laser_pulse_duration,
                    'laser_intensity':laser_intensity,
                    'run_time':run_time,
                    'delta_time':delta_time}.items()):
                if val is not None:
                    setattr(self, key, val)
        else:
            # Check and set all parameters
            self.__number_of_layers = checkAndSetNumberOfLayers(number_of_layers)
            self.__ablator = checkAndSetAblator(ablator)
            self.__ablator_thickness = checkAndSetAblatorThickness(ablator_thickness)
            self.__sample = checkAndSetSample(sample)
            self.__sample_thickness = checkAndSetSampleThickness(sample_thickness)
            self.__layer1 = checkAndSetLayer1(layer1)
            self.__layer1_thickness = checkAndSetLayer1Thickness(layer1_thickness)
            self.__layer2 = checkAndSetLayer2(layer2)
            self.__layer2_thickness = checkAndSetLayer2Thickness(layer2_thickness)
            self.__window = checkAndSetWindow(window)
            self.__window_thickness = checkAndSetWindowThickness(window_thickness)
            self.__laser_wavelength = checkAndSetLaserWavelength(laser_wavelength)
            self.__laser_pulse = checkAndSetLaserPulse(laser_pulse)
            self.__laser_pulse_duration = checkAndSetLaserPulseDuration(laser_pulse_duration)
            self.__laser_intensity = checkAndSetLaserIntensity(laser_intensity)
            self.__run_time = checkAndSetRunTime(run_time)
            self.__delta_time = checkAndSetDeltaTime(delta_time)
            self.force_passage = force_passage
            self.without_therm_conduc = without_therm_conduc
            self.rad_transfer = rad_transfer
        self.checkConsistency()
        # Define start up options (called Demmarage in esther)
        self._setDemmargeFlags()
        # Expert mode: Choose EOS type, SESAME or BLF
        # Expert mode: Setup zone feathering (spatial resolution)
        self._setupFeathering()
        # Set state to not-initialized (e.g. input deck is not written)
        self.__is_initialized = False
    def _readParametersFromFile(self,path):
        # Read from parameters file
        json_path = os.path.join(path, 'parameters.json')
        print(( "Parameters file is: %s" % (json_path)))
        with open(json_path, 'r') as j:
            dictionary = json.load(j)
            j.close()
        self.__dict__ = dictionary
    def _setDemmargeFlags(self):
        # Expert users options to include in the start up options
        self.__use_usi = "USI" # Use SI units.
        self.__use_force_passage = "FORCER_LE_PASSAGE" # Forces simulation through ignoring minor issues
        self.__use_without_therm_conduc = "SANS_COND_THERMIQUE" # Run without thermal conducivity
        self.__use_radiative_transfer = "TRANSFERT_RADIATIF" # Run with radiative transfer
    def _setupFeathering(self, number_of_zones=250, feather_zone_width=4.0, minimum_zone_width=4e-4):
        """ Method to fix feathering
        :param number_of_zones: The number of zones in the first ablator section (default 200).
        :type number_of_zones: int
        :param feather_zone_width: Width of feather zone (default 5.0 [microns]).
        :type feather_zone_width: float
        :param minimum_zone_width: Minimal zone width (default 2e-4 [microns]).
        :type minimum_zone_width: float
        """
        # Determine the correct zone feathering for ablator
        n = number_of_zones
        feather_list=numpy.zeros(n+1) # Create list of n zones
        feather_list[0]=1. # First zone is 1
        feather_list[-2]=-feather_zone_width/minimum_zone_width
        feather_list[-1]=-feather_list[-2] - 1.
        # Find roots in polynomial over the feather list
        f = numpy.poly1d(feather_list)
        roots = numpy.roots(f)
        root_found = False
        # Get all purely real roots above 1.
        for i in range(n):
            if roots[i].imag == 0 and roots[i].real > 1.000001: # Why not > 1.? This would exclude 1.0f
                r = round(roots[i].real,4)
                root_found = True
        if root_found == False:
            raise RuntimeError( "No ratio bigger than 1.000001 was found.")
        # Store feather information on object.
        self._feather_zone_width = feather_zone_width
        self._minimum_zone_width = minimum_zone_width
        self._final_feather_zone_width = round(minimum_zone_width*(r**n),4)
        self._non_feather_zone_width = self.ablator_thickness-feather_zone_width
        self._non_feather_zones = int(self._non_feather_zone_width/(minimum_zone_width*(r**n)))
        self._mass_of_zone = self._final_feather_zone_width*ESTHER_MATERIAL_DICT[self.ablator]["mass_density"]
        width_of_sample_zone = self._mass_of_zone/ESTHER_MATERIAL_DICT[self.sample]["mass_density"]
        self.__number_of_sample_zones=int(self.sample_thickness/width_of_sample_zone)
        print(("Final feather zone width: ", self._final_feather_zone_width))
        print(("Mass of zone: ", self._mass_of_zone))
        print(("Number of non-feathered zones: ", self._non_feather_zones))
        if self.layer1 is not None:
            width_of_layer1_zone = self._mass_of_zone/ESTHER_MATERIAL_DICT[self.layer1]["mass_density"]
            self.__number_of_layer1_zones=int(self.layer1_thickness/width_of_layer1_zone)
        if self.layer2 is not None:
            width_of_layer2_zone = self._mass_of_zone/ESTHER_MATERIAL_DICT[self.layer2]["mass_density"]
            self.__number_of_layer2_zones=int(self.layer2_thickness/width_of_layer2_zone)
        if self.window is not None:
            width_of_window_zone = self._mass_of_zone/ESTHER_MATERIAL_DICT[self.window]["mass_density"]
            self.__number_of_window_zones=int(self.window_thickness/width_of_window_zone)
    def _serialize(self, path=None, filename=None):
        """ Write the input deck for the Esther hydrocode. """
        # Make a temporary directory or use existing path and filename
        self._esther_files_path = path
        self._esther_filename = filename
        if path is None:
            self._esther_files_path = tempfile.mkdtemp(prefix='esther_')
        if filename is None:
            self._esther_filename='tmp_input'
        # Write the input file
        input_deck_path = os.path.join( self._esther_files_path, self._esther_filename+'.txt')
        print(("Writing input deck to ", input_deck_path, "."))
        # Write json file of this parameter class instance.
        json_path = os.path.join( self._esther_files_path, 'parameters.json')
        with open( json_path, 'w') as j:
            json.dump( self.__dict__, j)
            j.close()
        # Write the file.
        with open(input_deck_path, 'w') as input_deck:
            input_deck.write('DEMARRAGE,%s\n' % (self.__use_usi))
            if self.force_passage is True:
                input_deck.write('%s\n' % (self.__use_force_passage)) # Use force passage
            if self.without_therm_conduc is True:
                input_deck.write('%s\n' % (self.__use_without_therm_conduc)) # Use without thermal conductivity option
            if self.rad_transfer is True:
                input_deck.write('%s\n' % (self.__use_radiative_transfer)) # Use without thermal conductivity option
            input_deck.write('\n')
            input_deck.write('MILIEUX_INT_VERS_EXT\n')
            input_deck.write('\n')
            # If using a window, write the window layer here
            if self.window is not None:
                input_deck.write('- %.1f um %s layer\n' % (self.window_thickness, self.window))
                input_deck.write('NOM_MILIEU=%s\n' % (ESTHER_MATERIAL_DICT[self.window]["shortname"]))
                input_deck.write('EQUATION_ETAT=%s\n' % (ESTHER_MATERIAL_DICT[self.window]["eos_name"]))
                input_deck.write('EPAISSEUR_VIDE=100e-6\n')
                input_deck.write('EPAISSEUR_MILIEU=%.1fe-6\n' % (self.window_thickness))
                # Calculate number of zones in window
                input_deck.write('NOMBRE_MAILLES=%d\n' % (self.__number_of_window_zones))
                input_deck.write('MECANIQUE_RAM\n')
                input_deck.write('\n')
            # If using 4 layers (ablator, layer1, sample, layer2)
            if self.number_of_layers == 4:
                input_deck.write('- %.1f um %s layer\n' % (self.layer2_thickness, self.layer2))
                input_deck.write('NOM_MILIEU=%s_2\n' % (ESTHER_MATERIAL_DICT[self.layer2]["shortname"]))
                input_deck.write('EQUATION_ETAT=%s\n' % (ESTHER_MATERIAL_DICT[self.layer2]["eos_name"]))
                input_deck.write('EPAISSEUR_MILIEU=%.1fe-6\n' % (self.layer2_thickness))
                # Calculate number of zones
                input_deck.write('NOMBRE_MAILLES=%d\n' % (self.__number_of_layer2_zones))
                input_deck.write('MECANIQUE_RAM\n')
                input_deck.write('\n')
            input_deck.write('- %.1f um %s layer\n' % (self.sample_thickness, self.sample))
            input_deck.write('NOM_MILIEU=%s_2\n' % (ESTHER_MATERIAL_DICT[self.sample]["shortname"]))
            input_deck.write('EQUATION_ETAT=%s\n' % (ESTHER_MATERIAL_DICT[self.sample]["eos_name"]))
            if self.window is None:
                input_deck.write('EPAISSEUR_VIDE=100e-6\n')
            input_deck.write('EPAISSEUR_MILIEU=%.1fe-6\n' % (self.sample_thickness))
            # Calculate number of zones
            input_deck.write('NOMBRE_MAILLES=%d\n' % (self.__number_of_sample_zones))
            input_deck.write('MECANIQUE_RAM\n')
            input_deck.write('\n')
            # If using 3 layers (ablator, layer1, sample)
            if self.number_of_layers == 3:
                input_deck.write('- %.1f um %s layer\n' % (self.layer1_thickness, self.layer1))
                input_deck.write('NOM_MILIEU=%s_2\n' % (ESTHER_MATERIAL_DICT[self.layer1]["shortname"]))
                input_deck.write('EQUATION_ETAT=%s\n' % (ESTHER_MATERIAL_DICT[self.layer1]["eos_name"]))
                input_deck.write('EPAISSEUR_MILIEU=%.1fe-6\n' % (self.layer1_thickness))
                # Calculate number of zones
                input_deck.write('NOMBRE_MAILLES=%d\n' % (self.__number_of_layer1_zones))
                input_deck.write('MECANIQUE_RAM\n')
                input_deck.write('\n')
            # Write ablator
            input_deck.write('- %.1f um %s layer\n' % (self.ablator_thickness, self.ablator))
            input_deck.write('NOM_MILIEU=%s_abl1\n' % (ESTHER_MATERIAL_DICT[self.ablator]["shortname"])) # 1st PART OF ABLATOR
            input_deck.write('EQUATION_ETAT=%s\n' % (ESTHER_MATERIAL_DICT[self.ablator]["eos_name"]))# ABLATOR EOS
            # if only simulating ablator layer, then must include empty (VIDE) layer
            if self.number_of_layers == 1:
                input_deck.write('EPAISSEUR_VIDE=100e-6\n')
            input_deck.write('EPAISSEUR_MILIEU=%.1fe-6\n' % (self._non_feather_zone_width)) # Non-feather thickness
            input_deck.write('NOMBRE_MAILLES=%d\n' % (self._non_feather_zones)) # Number of zones
            input_deck.write('MECANIQUE_RAM\n') # Needs to be an option to use this.
            input_deck.write('\n')
            input_deck.write('NOM_MILIEU=%s_abl2\n' % (ESTHER_MATERIAL_DICT[self.ablator]["shortname"])) # 2nd PART OF ABLATOR
            input_deck.write('EQUATION_ETAT=%s\n' % (ESTHER_MATERIAL_DICT[self.ablator]["eos_name"])) # ABLATOR EOS
            input_deck.write('EPAISSEUR_MILIEU=%.1fe-6\n' % (self._feather_zone_width)) # Feather thickness
            input_deck.write('EPAISSEUR_INTERNE=%.3fe-6\n' % (self._final_feather_zone_width)) # Feather final zone width
            input_deck.write('EPAISSEUR_EXTERNE=4.0e-10\n') #Min zone width
            input_deck.write('MECANIQUE_RAM\n') # Needs to be an option to use this.
            input_deck.write('\n')
            # TO DO: GIT ISSUE #96: Expert mode
            # Internal parameters to add to input flags
            input_deck.write('INDICE_REEL_LASER=1.46\n')
            input_deck.write('INDICE_IMAG_LASER=1.0\n')
            input_deck.write('\n')
            # Laser parameters
            # TO DO: GIT ISSUE #96: Expert mode
            input_deck.write('DEPOT_ENERGIE,LASER,DEPOT_HELMHOLTZ\n') # Expert mode option
            input_deck.write('LONGUEUR_ONDE_LASER=%.3fe-6\n' % (self.laser_wavelength))
            input_deck.write('DUREE_IMPULSION=%.2fe-9\n' % (self.laser_pulse_duration))
            input_deck.write('INTENSITE_IMPUL_MAX=%.3fe16\n' % (self.laser_intensity))
            input_deck.write('TEMPS_IMPUL_TABULE=0.0e-9,INTENSITE_TABULEE=0.\n') # These need to change for approrpriate laser designs.
            input_deck.write('TEMPS_IMPUL_TABULE=0.2e-9,INTENSITE_TABULEE=1\n')
            input_deck.write('TEMPS_IMPUL_TABULE=5.8e-9,INTENSITE_TABULEE=1\n')
            input_deck.write('TEMPS_IMPUL_TABULE=6.0e-9,INTENSITE_TABULEE=0.\n')
            #input_deck.write('IMPULSION_FICHIER\n')
            input_deck.write('\n')
            # Output parameters
            input_deck.write('SORTIES_GRAPHIQUES\n')
            input_deck.write('DECOUPAGE_TEMPS\n')
            input_deck.write('BORNE_TEMPS=%.2fe-9\n' % (self.run_time))
            input_deck.write('INCREMENT_TEMPS=%.2fe-9\n' % (self.delta_time))
            input_deck.write('\n')
            # End of input file
            input_deck.write('ARRET\n')
            input_deck.write('TEMPS_ARRET=%.2fe-9\n' % (self.run_time))
            input_deck.write('\n')
            input_deck.write('FIN_DES_INSTRUCTIONS')
        # Write the laser input file
        laser_input_deck_path = os.path.join( self._esther_files_path, self._esther_filename+'_intensite_impulsion.txt')
        print(("Writing laser input deck to ", laser_input_deck_path, "."))
        # Write the parameters file (_intensitie_impulsion)
        with open(laser_input_deck_path, 'w') as laser_input_deck:
            if self.laser_pulse == "flat":
                # Write flat top pulse shape to file
                laser_input_deck.write('4\n')
                laser_input_deck.write('temps (s ou u.a.) intensite (W/m2 ou u.a.)\n')
                laser_input_deck.write('0. \t 0\n')
                laser_input_deck.write('0.1e-9\t%.3f\n' % (self.laser_intensity))
                laser_input_deck.write('%.2fe-9\t%.3f\n' % (self.laser_pulse_duration-0.1, self.laser_intensity))
                laser_input_deck.write('%.2fe-9\t0.0\n' % (self.laser_pulse_duration))
                laser_input_deck.write('fin_de_fichier')
            elif self.laser_pulse == "ramp":
                # Write ramp pulse shape to file
                x = numpy.arange(0.,self.laser_pulse_duration+1.0,1)
                y = x**3
                y = y/numpy.amax(y)
                Number_lines = len(x)
                x[Number_lines-1]=self.laser_pulse_duration-0.1 # Set the max intensity at 100 ps before final pulse time
                laser_input_deck.write('%d\n' % (Number_lines+1)) # Number of lines to add in pulse shape
                laser_input_deck.write('temps (s ou u.a.) intensite (W/m2 ou u.a.)\n')
                for i in range(0,Number_lines):
                    laser_input_deck.write('%.2fe-9\t%0.3f\n' % (x[i],y[i]*self.laser_intensity))
                laser_input_deck.write('%.2fe-9\t0.0\n' % (self.laser_pulse_duration))
                laser_input_deck.write('fin_de_fichier')
            else:
                # Use a default Gaussian? or quit?
                # TO DO: GIT ISSUE #96: Expert mode: User defined pulse shape?
                print ("No default laser chosen?")
    @property
    def number_of_layers(self):
           """ Query for the number of layers. """
           return self.__number_of_layers
    @number_of_layers.setter
    def number_of_layers(self, value):
           """ Set the number of layers to the value. """
           self.__number_of_layers = checkAndSetNumberOfLayers(value)
    @property
    def ablator(self):
           """ Query for the ablator type. """
           return self.__ablator
    @ablator.setter
    def ablator(self, value):
           """ Set the ablator to the value. """
           self.__ablator = checkAndSetAblator(value)
    @property
    def ablator_thickness(self):
           """ Query for the ablator thickness. """
           return self.__ablator_thickness
    @ablator_thickness.setter
    def ablator_thickness(self, value):
           """ Set the ablator thickness to the value. """
           self.__ablator_thickness = checkAndSetAblatorThickness(value)
    @property
    def sample(self):
           """ Query for the sample type. """
           return self.__sample
    @sample.setter
    def sample(self, value):
           """ Set the sample type to the value. """
           self.__sample = checkAndSetSample(value)
    @property
    def sample_thickness(self):
           """ Query for the sample thickness type. """
           return self.__sample_thickness
    @sample_thickness.setter
    def sample_thickness(self, value):
           """ Set the sample thickness to the value. """
           self.__sample_thickness = checkAndSetSampleThickness(value)
    @property
    def layer1(self):
           """ Query for the layer1 type. """
           return self.__layer1
    @layer1.setter
    def layer1(self, value):
           """ Set the layer1 type to the value. """
           self.__layer1 = checkAndSetLayer1(value)
    @property
    def layer1_thickness(self):
           """ Query for the layer1 thickness type. """
           return self.__layer1_thickness
    @layer1_thickness.setter
    def layer1_thickness(self, value):
           """ Set the layer1 thickness to the value. """
           self.__layer1_thickness = checkAndSetLayer1Thickness(value)
    @property
    def layer2(self):
           """ Query for the layer2 type. """
           return self.__layer2
    @layer2.setter
    def layer2(self, value):
           """ Set the layer2 type to the value. """
           self.__layer2 = checkAndSetLayer2(value)
    @property
    def layer2_thickness(self):
           """ Query for the layer2 thickness type. """
           return self.__layer2_thickness
    @layer2_thickness.setter
    def layer2_thickness(self, value):
           """ Set the layer2 thickness to the value. """
           self.__layer2_thickness = checkAndSetLayer2Thickness(value)
    @property
    def window(self):
           """ Query for the window type. """
           return self.__window
    @window.setter
    def window(self, value):
           """ Set the window to the value. """
           self.__window = checkAndSetWindow(value)
    @property
    def window_thickness(self):
           """ Query for the window thickness type. """
           return self.__window_thickness
    @window_thickness.setter
    def window_thickness(self, value):
           """ Set the window thickness to the value. """
           self.__window_thickness = checkAndSetWindowThickness(value)
    @property
    def laser_wavelength(self):
           """ Query for the laser wavelength type. """
           return self.__laser_wavelength
    @laser_wavelength.setter
    def laser_wavelength(self, value):
           """ Set the laser wavelength to the value. """
           self.__laser_wavelength = checkAndSetLaserWavelength(value)
    @property
    def laser_pulse(self):
        """Query for laser pulse type"""
        return self.__laser_pulse
    @laser_pulse.setter
    def laser_pulse(self,value):
        """ Set the laser pulse to type value """
        self.__laser_pulse = checkAndSetLaserPulse(value)
    @property
    def laser_pulse_duration(self):
        """ Query for laser pulse duration """
        return self.__laser_pulse_duration
    @laser_pulse_duration.setter
    def laser_pulse_duration(self,value):
        """ Set laser pulse duration """
        self.__laser_pulse_duration = checkAndSetLaserPulseDuration(value)
    @property
    def laser_intensity(self):
        """ Query for laser intensity """
        return self.__laser_intensity
    @laser_intensity.setter
    def laser_intensity(self,value):
        """ Set laser intensity """
        self.__laser_intensity = checkAndSetLaserIntensity(value)
    @property
    def run_time(self):
        """ Query for simulation run time """
        return self.__run_time
    @run_time.setter
    def run_time(self,value):
        """ Set simulation run time """
        self.__run_time = checkAndSetRunTime(value)
    @property
    def delta_time(self):
        """ Query for simulation time resolution (delta t ns) """
        return self.__delta_time
    @delta_time.setter
    def delta_time(self,value):
        """ Set simulation time resolution delta t, ns"""
        self.__delta_time = checkAndSetDeltaTime(value)
    def _setDefaults(self):
        """ Method to pick sensible defaults for all parameters. """
        pass
    def checkConsistency(self):
        if self.window is not None:
            if self.window_thickness == 0.0:
                raise ValueError( "Window thickness cannot be 0.0") 
###########################
# Check and set functions #
###########################
#################################
# Material check and set functions
##################################
[docs]def checkAndSetNumberOfLayers(number_of_layers):
    """
    Utility to check if the number of layers is reasonable.
    :param number_of_layers: The number of layers to check
    :return: Checked number of layers
    :raise ValueError: not (1 < number_of_layers <= 4 )
    """
    if number_of_layers is None:
        raise RuntimeError( "Number of layers is not defined.")
    if not isinstance( number_of_layers, int ):
        raise TypeError("The parameter 'number_of_layers' must be an int.")
    if number_of_layers <1 or number_of_layers > 4:
        raise ValueError( "Number of layers must be between 1 and 4 only.")
    return number_of_layers 
[docs]def checkAndSetAblator(ablator):
    """
    Utility to check if the ablator exists in the EOS database.
    :param ablator: The ablator material to check.
    :return: The ablator choice after being checked.
    :raise ValueError: ablator not in ["CH", "Al", "Diamond", "Mylar", "Kapton"].
    """
    if ablator is None:
        raise RuntimeError( "The parameter 'ablator' is not defined.")
    # Check type.
    if not isinstance( ablator, str):
        raise TypeError("The parameters 'ablator' must be a str.")
    ### Could check if isinstance(ablator, str)
    if ablator == 'CH':
        print ( "Setting CH as ablator.")
    elif ablator.lower() in ['al', 'aluminium']:
        print ( "Setting Al as ablator.")
    elif ablator.lower() in ['dia', 'diamond']:
        print ( "Setting diamond as ablator.")
    elif ablator.lower() in ['mylar', 'myl']:
        print ( "Setting mylar as ablator.")
    elif ablator.lower() in ['kap', 'kapton']:
        print ( "Setting Kapton as ablator.")
    else:
        raise ValueError( "Ablator is not valid. Use 'CH', 'Al', 'dia', 'Myl', or 'Kap' as ablator.")
    return ablator 
[docs]def checkAndSetAblatorThickness(ablator_thickness):
    """
    Utility to check that the ablator thickness is > 5 um and < 100 um
    """
    # Raise if not set.
    if ablator_thickness is None:
        raise RuntimeError( "Ablator thickness not specified.")
    # Check type.
    if not isinstance( ablator_thickness, (int, float)):
        raise TypeError("The parameters 'ablator_thickness' must be of numeric type (int or float).")
    # Check if ablator is between 5 and 100 um
    if ablator_thickness < 5.0 or ablator_thickness > 100.0:
        raise ValueError( "Ablator must be between 5.0 and 100.0 microns")
    # TO DO PLACEHOLDER
    # IF LASER INTENSITY IS TOO HIGH, ABLATOR MUST BE THICK TO ALLOW FOR ENOUGH MATERIAL TO VAPORISE (CH)
    print(( "Ablator thickness is %4.1f " % ablator_thickness))
    return ablator_thickness 
[docs]def checkAndSetSample(sample):
    """
    Utility to check if the sample is in the list of known EOS materials
    """
    elements = [ "Aluminium", "Gold", "Carbon", "CH", "Cobalt", "Copper", "Diamond",
                "Iron", "Molybdenum", "Nickel", "Lead", "Silicon", "Tin", "Tantalum",
                "Berylium", "Chromium", "Iron2", "Kapton", "LiF", "Magnesium", "Mylar",
                "Quartz", "SiliconOxide", "Silver", "Titanium", "Vanadium", "Water" ]
    # Set default
    if sample is None:
        raise RuntimeError( "sample not specified.")
    if not isinstance(sample, str): raise TypeError("The parameter 'sample' must be a str.")
    # Check each element
    if sample in elements:
        pass
    else:
        raise ValueError( "sample is not in list of known EOS materials")
    return sample 
[docs]def checkAndSetSampleThickness(sample_thickness):
    """
    Utility to check that the sample thickness is in permitted range set by Esther.
    """
    # Raise if not set.
    if sample_thickness is None:
        raise RuntimeError( "Sample thickness not specified.")
    # Check type.
    if not isinstance( sample_thickness, (int, float)):
        raise TypeError("The parameters 'sample_thickness' must be of numeric type (int or float).")
    # Check if sample is between 1 and 200 um
    if sample_thickness < 1.0 or sample_thickness > 200.0:
        raise ValueError( "Ablator must be between 1.0 and 200.0 microns")
    return sample_thickness 
[docs]def checkAndSetLayer1(layer1):
    """
    Utility to check if the layer1 is in the list of known EOS materials
    """
    elements = [ "Aluminium", "Gold", "Carbon", "CH", "Cobalt", "Copper", "Diamond",
                "Iron", "Molybdenum", "Nickel", "Lead", "Silicon", "Tin", "Tantalum",
                "Berylium", "Chromium", "Iron2", "Kapton", "LiF", "Magnesium", "Mylar",
                "Quartz", "SiliconOxide", "Silver", "Titanium", "Vanadium", "Water" ]
    # Set default
    if layer1 is None:
        print ( "Running simulation without layer1 material")
        return None
    if not isinstance(layer1, str): raise TypeError("The parameter 'layer1' must be a str.")
    # Check each element
    if layer1 in elements:
        pass
    else:
        raise ValueError( "layer1 is not in list of known EOS materials")
    return layer1 
[docs]def checkAndSetLayer1Thickness(layer1_thickness):
    """
    Utility to check that the layer1 thickness is in permitted range set by Esther.
    """
    # Set default
    if layer1_thickness is None:
        return 0.0
    # Check if number.
    if not isinstance( layer1_thickness, (float, int)):
        raise TypeError( "The parameter 'layer1_thickness' must be a numerical type (float or int.)")
    # Check if layer1 is between 1 and 100 um
    if layer1_thickness < 1.0 or layer1_thickness > 200.0:
        raise ValueError( "layer1 must be between 1.0 and 200.0 microns")
    return layer1_thickness 
[docs]def checkAndSetLayer2(layer2):
    """
    Utility to check if the layer2 is in the list of known EOS materials
    """
    elements = [ "Aluminium", "Gold", "Carbon", "CH", "Cobalt", "Copper", "Diamond",
                "Iron", "Molybdenum", "Nickel", "Lead", "Silicon", "Tin", "Tantalum",
                "Berylium", "Chromium", "Iron2", "Kapton", "LiF", "Magnesium", "Mylar",
                "Quartz", "SiliconOxide", "Silver", "Titanium", "Vanadium", "Water" ]
    # Set default
    if layer2 is None:
        print ( "Running simulation without layer2 material")
        return None
    if not isinstance(layer2, str): raise TypeError("The parameter 'layer2' must be a str.")
    # Check each element
    if layer2 in elements:
        pass
    else:
        raise ValueError( "layer2 is not in list of known EOS materials")
    return layer2 
[docs]def checkAndSetLayer2Thickness(layer2_thickness):
    """
    Utility to check that the layer2 thickness is in permitted range set by Esther.
    """
    # Set default
    if layer2_thickness is None:
        return 0.0
    # Check if number.
    if not isinstance( layer2_thickness, (float, int)):
        raise TypeError( "The parameter 'layer2_thickness' must be a numerical type (float or int.)")
    # Check if layer2 is between 1 and 100 um
    if layer2_thickness < 1.0 or layer2_thickness > 200.0:
        raise ValueError( "layer2 must be between 1.0 and 200.0 microns")
    return layer2_thickness 
[docs]def checkAndSetWindow(window):
    """
    Utility to check that the window exists in the EOS database.
    """
    elements = ["LiF", "SiO2", "Diamond", "Quartz" ]
    if window is None:
        print ( "Running simulation without window material")
        return None
    if not isinstance( window, str):
        raise TypeError("The parameter 'window' must be a str.")
    # Check each element
    if window not in elements:
        raise ValueError( "window is not in list of known EOS materials")
    return window 
[docs]def checkAndSetWindowThickness(window_thickness):
    """
    Utility to check that the window thickness is > 1 um and < 500 um
    """
    # Set default
    if window_thickness is None:
        return 0.0
    # Check if number.
    if not isinstance( window_thickness, (float, int)):
        raise TypeError( "The parameter 'window_thickness' must be a numerical type (float or int.)")
    # Check if ablator is between 1 and 100 um
    if window_thickness == 0.0:
        pass
    elif window_thickness < 1.0 or window_thickness > 500.0:
        raise ValueError( "Window must be between 1.0 and 500.0 microns")
    return window_thickness 
#################################
# LASER CHECK AND SET FUNCTIONS #
#################################
[docs]def checkAndSetLaserWavelength(laser_wavelength):
    """
    Utility to check that the laser wavelength is correct.
    """
    if laser_wavelength is None:
        raise RuntimeError( "Laser wavelength is not defined")
    # Check if number.
    if not isinstance( laser_wavelength, (float, int)):
        raise TypeError( "The parameter 'laser_wavelength' must be a numerical type (float or int.)")
    if laser_wavelength <= 300 or laser_wavelength > 1200:
        raise ValueError( "laser wavelength must be between 300 and 1200 nm")
 # Convert to microns.
    laser_wavelength = laser_wavelength*1e-3
    print(("Laser wavelength = %.3fe-6" % (laser_wavelength)))
    return laser_wavelength 
[docs]def checkAndSetLaserPulse(laser_pulse):
    """
    Utility to check that the laser pulse type is correct.
    """
    if laser_pulse is None:
        raise RuntimeError( "Laser pulse type has not been chosen")
    pulse_shapes = ["flat","quasiflat","ramp"]
    # Check if str.
    if not isinstance(laser_pulse, str):
        raise TypeError("The parameter 'laser_pulse' must be a str.")
    # Check if pulseshape exists
    if laser_pulse in pulse_shapes:
        pass
    else:
        raise ValueError( "Laser pulse shape is not in specified list.")
    return laser_pulse 
[docs]def checkAndSetLaserPulseDuration(laser_pulse_duration):
    """
    Utility to check that the laser pulse duration is valid.
    """
    if laser_pulse_duration is None:
        raise RuntimeError( "Laser pulse duration has not been set")
    # Check if number.
    if not isinstance( laser_pulse_duration, (float, int)):
        raise TypeError( "The parameter 'laser_pulse_duration' must be a numerical type (float or int.)")
    if laser_pulse_duration < 1.0 or laser_pulse_duration > 50.0:
        raise ValueError( "Laser pulse must be between 1.0 and 50.0 ns")
    return laser_pulse_duration 
[docs]def checkAndSetLaserIntensity(laser_intensity):
    """
    Utility to check that the laser intensity is valid.
    """
    if laser_intensity is None:
        raise RuntimeError( "Laser intensity has not been set")
    # Check if number.
    if not isinstance( laser_intensity, (float, int)):
        raise TypeError( "The parameter 'laser_intensity' must be a numerical type (float or int.)")
    # TODO: Check these for more realistic limits of TW/cm**2
    if laser_intensity < 0.001 or laser_intensity > 100.0:
        raise ValueError( "Laser pulse must be between 1.0 and 50.0 ns")
    return laser_intensity 
[docs]def checkAndSetRunTime(run_time):
    """
    Utility for checking the simulation run time is valid
    """
    if run_time is None:
        raise RuntimeError( "Simulation run time is not set")
    # TODO: Check run times
    if run_time < 1.0 or run_time > 50.0:
        raise ValueError( "Simulation run time should be > 5.0 ns and < 50.0 ns")
    return run_time 
[docs]def checkAndSetDeltaTime(delta_time):
    """
    Utility for checking the simulation delta time (resolution) is valid
    """
    if delta_time is None:
        raise RuntimeError( "Simulation delta time (time resolution) is not set")
    if delta_time < 0.001 or delta_time > 0.5:
        raise ValueError( "Simulation delta time should be > 10 ps and < 500 ps")
    return delta_time