https://hal.archives-ouvertes.fr/hal-01897934
Raw File
Tip revision: f370e484d8579b4ccc863d1c2a2f10decad21914 authored by Software Heritage on 27 July 2018, 00:00:00 UTC
hal: Deposit 299 in collection hal
Tip revision: f370e48
slalomDevice.py
# -*- coding: utf-8 -*-

# ======================================================================================================
# SLALOM - Open-Source Solar Cell Multivariate Optimizer
# Copyright(C) 2012-2019 Sidi OULD SAAD HAMADY (1,2,*), Nicolas FRESSENGEAS (1,2). All rights reserved.
# (1) Université de Lorraine, Laboratoire Matériaux Optiques, Photonique et Systèmes, Metz, F-57070, France
# (2) Laboratoire Matériaux Optiques, Photonique et Systèmes, CentraleSupélec, Université Paris-Saclay, Metz, F-57070, France
# (*) sidi.hamady@univ-lorraine.fr
# SLALOM source code is available to download from:
# https://github.com/sidihamady/SLALOM
# https://hal.archives-ouvertes.fr/hal-01897934
# http://www.hamady.org/photovoltaics/slalom_source.zip
# Cite as: S Ould Saad Hamady and N Fressengeas, EPJ Photovoltaics, 9:13, 2018.
# See Copyright Notice in COPYRIGHT
# ======================================================================================================

# ------------------------------------------------------------------------------------------------------
# File:           slalomDevice.py
# Type:           Module
# Purposes:       Devices definition class:
#                 * slalomDevice class define a set of devices (e.g. "InGaN_PN", CZTS_NP", etc.)...
#                   ...for easier and more robust optimization work.
#                 * Extend slalomDevice class to include any set of devices for a specific project.
# ------------------------------------------------------------------------------------------------------

import numpy as np
import os
import random
import math
import datetime

from slalomCore import *

class slalomDevice(object):
    """ Device definition class. Contains a set of predefined solar cell structures """

    def __init__(self, deviceType, currentDir, randomInit = False):
        """ slalomDevice constructor where the solar cell structures are defined """

        self.__version__ = slalomVersion

        self.dirSepChar = '/'

        # Working directory
        self.currentDir = currentDir

        self.reset(deviceType)

        self.paramFormatOutput = "%.6f"

        # randomInit choose randomly a starting point for the optimizer.
        # Starting the optimizer with a random point could be a way
        # to check the uniqueness of the optimized set of parameters.
        if (randomInit == True) and (self.deviceType is not None):
            self.randomInit()
        #end if

    # end __init__

    def reset(self, deviceType):
        """ reset the device type """

        self.deviceType = deviceType

        # Deckbuild input filename
        self.inputFilename = ""
        # Device description
        self.mainTitle = ""
        # Output directory
        self.outputDir = ""
        # Parameters name, as defined is the Deckbuild input
        self.paramName = None
        # Parameters unit
        self.paramUnit = None
        # Parameters format string (e.g. for doping use "%.6e")
        self.paramFormat = None
        # Parameters short format string for console output (e.g. for doping use "%.4e")
        self.paramFormatShort = None
        # Normalized parameters format string for console output
        self.paramFormatNormalized = None
        # Normalization value for each parameter
        self.paramNorm = None
        # Parameters range limit (Start)
        self.paramStart = None
        # Parameters range limit (End)
        self.paramEnd = None
        # Parameters initial values (used as a starting point or when optimType is set to "Snap")
        self.paramInit = None
        # Parameters number of points (used as when optimType is set to "Brute")
        self.paramPoints = None
        # Parameters variation type (True for logarithmic variation (e.g. for doping), and False for linear)
        self.paramLogscale = None
        self.paramWeight = False
        # C source code files used to set the models used by the device simulator
        self.modelFilename =  None

        # device not defined: just define data and return.
        if (self.deviceType is None):
            return
        # end if

        # C files parameters and models common to all InGaN structures
        if self.deviceType.startswith("InGaN_"):
            # C source code files used to set the models used by the device simulator
             self.modelFilename = [ "InN_GaN_Parameters.h", "InGaN_Bandgap.c", "InGaN_Permittivity.c", "InGaN_Recomb.c", "InGaN_Index.c", "InGaN_Mobility.c" ]
        # end if

        if self.deviceType == "InGaN_PN":
            # on a server with 8-core Xeon processors and 32 GB of RAM...
            # one simulation takes up to five minutes, with the given input and parameters,
            # strongly depending on numerical parameters (mesh, propagation, solver...) and device parameters.
            # Deckbuild input filename
            self.inputFilename = "InGaN_PN.in"
            # Device description
            self.mainTitle = "PN InGaN PV Cell"
            # Output directory
            self.outputDir = self.currentDir + "output" + self.dirSepChar + self.deviceType + self.dirSepChar
            # Parameters name, as defined is the Deckbuild input
            self.paramName = ["PLayerThick", "PLayerDop", "NLayerThick", "NLayerDop", "AlloyComp"]
            # Parameters unit
            self.paramUnit = ["um", "1/cm3", "um", "1/cm3", ""]
            # Parameters format string (e.g. for doping use "%.6e")
            self.paramFormat = ["%.8f", "%.6e", "%.8f", "%.6e", "%.8f"]
            # Parameters short format string for console output (e.g. for doping use "%.4e")
            self.paramFormatShort = ["%.6f", "%.4e","%.6f",  "%.4e", "%.6f"]
            # Normalized parameters format string for console output
            self.paramFormatNormalized = ["%.8f", "%.8f", "%.8f", "%.8f", "%.8f"]
            # Normalization value for each parameter
            self.paramNorm = np.array([1.000, 1e17, 1.000, 1e17, 1.00])
            # Parameters range limit (Start)
            self.paramStart = np.array([0.01, 1e14, 0.01, 1e14, 0.01])
            # Parameters range limit (End)
            self.paramEnd = np.array([5.000, 1e20, 5.000, 1e20, 0.99])
            # Parameters initial values (used as a starting point or when optimType is set to "Snap")
            # should be in the [paramStart, paramEnd] range
            self.paramInit = np.array([0.100, 1e17, 0.500, 1e15, 0.56])
            # Parameters number of points (used as when optimType is set to "Brute")
            self.paramPoints = [5, 5, 5, 5, 1]
            # Parameters variation type (True for logarithmic variation (e.g. for doping), and False for linear)
            self.paramLogscale = [False, True, False, True, False]
            self.paramWeight = False

        elif self.deviceType == "InGaN_Schottky":
            # on a server with 8-core Xeon processors and 32 GB of RAM...
            # one simulation takes up to five minutes, with the given input and parameters,
            # strongly depending on numerical parameters (mesh, propagation, solver...) and device parameters.
            # Deckbuild input filename
            self.inputFilename = "InGaN_Schottky.in"
            # Device description
            self.mainTitle = "Schottky InGaN PV Cell"
            # Output directory
            self.outputDir = self.currentDir + "output" + self.dirSepChar + self.deviceType + self.dirSepChar
            # Parameters name, as defined is the Deckbuild input
            self.paramName = ["Workfunction", "NLayerThick", "NLayerDop", "AlloyComp"]
            # Parameters unit
            self.paramUnit = ["eV", "um", "1/cm3", ""]
            # Parameters format string (e.g. for doping use "%.6e")
            self.paramFormat = ["%.8f", "%.8f", "%.6e", "%.8f"]
            # Parameters short format string for console output (e.g. for doping use "%.4e")
            self.paramFormatShort = ["%.6f", "%.6f", "%.4e", "%.6f"]
            # Normalized parameters format string for console output
            self.paramFormatNormalized = ["%.8f", "%.8f", "%.8f", "%.8f"]
            # Normalization value for each parameter
            self.paramNorm = np.array([1.000, 1.000, 1e17, 1.00])
            # Parameters range limit (Start)
            self.paramStart = np.array([5.0, 0.01, 1e14, 0.01])
            # Parameters range limit (End)
            self.paramEnd = np.array([7.0, 5.000, 1e20, 0.99])
            # Parameters initial values (used as a starting point or when optimType is set to "Snap")
            # should be in the [paramStart, paramEnd] range
            self.paramInit = np.array([5.1, 0.500, 1e15, 0.56])
            # Parameters number of points (used as when optimType is set to "Brute")
            self.paramPoints = [5, 5, 5, 1]
            # Parameters variation type (True for logarithmic variation (e.g. for doping), and False for linear)
            self.paramLogscale = [False, False, True, False]
            self.paramWeight = False

        elif self.deviceType == "CZTS":
            # on a server with 8-core Xeon processors and 32 GB of RAM...
            # one simulation takes up to ten minutes, with the given input and parameters,
            # strongly depending on numerical parameters (mesh, propagation, solver...) and device parameters.
            # Deckbuild input filename
            self.inputFilename = "CZTS_NP.in"
            # Device description
            self.mainTitle = "CdS/CZTS PV Cell"
            # Output directory
            self.outputDir = self.currentDir + "output" + self.dirSepChar + self.deviceType + self.dirSepChar
            # Parameters name, as defined is the Deckbuild input
            self.paramName = ["CZTSLayerThick", "CZTSLayerDop"]
            # Parameters unit
            self.paramUnit = ["um", "1/cm3"]
            # Parameters format string (e.g. for doping use "%.6e")
            self.paramFormat = ["%.8f", "%.6e"]
            # Parameters short format string for console output (e.g. for doping use "%.4e")
            self.paramFormatShort = ["%.6f", "%.4e"]
            # Normalized parameters format string for console output
            self.paramFormatNormalized = ["%.8f", "%.8f"]
            # Normalization value for each parameter
            self.paramNorm = np.array([1.000, 1e17])
            # Parameters range limit (Start)
            self.paramStart = np.array([0.100, 1e14])
            # Parameters range limit (End)
            self.paramEnd = np.array([5.000, 1e19])
            # Parameters initial values (used as a starting point or when optimType is set to "Snap")
            # should be in the [paramStart, paramEnd] range
            self.paramInit = np.array([0.500, 1e17])
            # Parameters number of points (used as when optimType is set to "Brute")
            self.paramPoints = [ 1, 1]
            # Parameters variation type (True for logarithmic variation (e.g. for doping), and False for linear)
            self.paramLogscale = [False, True]
            self.paramWeight = False
            # C source code files used to set the models used by the device simulator
            self.modelFilename = [ "CZTS_Parameters.h", "CZTS_Index.c", "CdS_Index.c", "ZnO_Index.c" ]

        else:
            self.deviceType = None
        # end if

    # end reset

    def validate(self):
        """ validate the device data. Returns (status,message) """

        strMsg = None
        try:
            if self.deviceType is None:
                return (False, 'Device not defined')
            # end if

            self.mainTitle = self.deviceType

            if (not self.inputFilename) or (len(self.inputFilename) < 4):
                return (False, "Device input file '%s' not defined" % self.inputFilename)
            # end if

            if (not self.currentDir) or (len(self.currentDir) < 4) or (not os.path.exists(self.currentDir)):
                return (False, "Current directory '%s' not defined" % self.currentDir)
            # end if

            if (not os.path.exists(self.currentDir)) or (not os.path.exists(self.currentDir + self.inputFilename)):
                return (False, "Input directory/file '%s/%s' not found" % (self.currentDir, self.inputFilename))
            # end if

            iCount = len(self.paramName)
            if (iCount < 1) or (iCount > 12):
                return (False, "Invalid number of parameters '%d' (should be between 1 and 12)" % iCount)
            # end if

            if ((iCount != len(self.paramUnit))
                or (iCount != len(self.paramFormat)) or (iCount != len(self.paramFormatShort))
                or (iCount != len(self.paramFormatNormalized)) or (iCount != len(self.paramNorm))
                or (iCount != len(self.paramStart)) or (iCount != len(self.paramEnd))
                or (iCount != len(self.paramInit)) or (iCount != len(self.paramPoints)) 
                or (iCount != len(self.paramLogscale))):
                return (False, "Invalid number of parameters data (should correspond to the number of parameters name '%d')" % iCount)
            # end if

            if self.modelFilename:
                iml = len(self.modelFilename)
                for ii in range(0, iml):
                    if (not self.modelFilename[ii]) or (len(self.modelFilename[ii]) < 3):
                        break
                    # end if
                    if (not os.path.exists(self.currentDir + self.modelFilename[ii])):
                        return (False, "Model file '%s' not found" % (self.currentDir + self.modelFilename[ii]))
                    # end if
                # end for
            # end if

            del self.paramFormatShort[:]
            self.paramFormatShort = list(self.paramFormat)
            del self.paramFormatNormalized[:]
            self.paramFormatNormalized = list(self.paramFormat)
            del self.paramUnit[:]
            for ii in range(0, iCount):
                try:
                    strT = self.paramFormat[ii] % self.paramStart[0]
                except:
                    return (False, "Invalid number format '%s' ( example of valid format: %.8f )" % self.paramFormat[ii])
                # end try
                self.paramFormatNormalized[ii] = '%.8f'

                self.paramUnit.append(' ')

                if (self.paramPoints[ii] < 1) or (self.paramPoints[ii] > 1001):
                    return (False, "Invalid number of points '%d' (should be between 1 and 1001)", self.paramPoints[ii])
                # end if
                if (self.paramInit[ii] < self.paramStart[ii]) or (self.paramInit[ii] > self.paramEnd[ii]):
                    return (False, "Invalid initial parameter '%s' value '%g' (should be in the [%g, %g] range)" % (self.paramName[ii], self.paramInit[ii], self.paramStart[ii], self.paramEnd[ii]))
                # end if
            # end for

        except Exception as excT:
            return (False, 'Device data not valid ' + str(excT))
        # end try

        return (True, 'Device data valid')
    # end validate

    def randomInit(self, printOut = False):
        """ choose randomly a starting point for the optimizer\nStarting the optimizer with a random point could be a way\nto check the uniqueness of the optimized set of parameters"""

        self.randomParamStr = None
        if self.deviceType is not None:
            paramCount = len(self.paramName)
            paramStart = 0
            paramEnd = 0
            for ii in range(0, paramCount):
                if self.paramLogscale[ii]:
                    paramStart = math.log10(self.paramStart[ii]) / math.log10(self.paramNorm[ii])
                    paramEnd = math.log10(self.paramEnd[ii]) / math.log10(self.paramNorm[ii])
                    paramInit = random.uniform(paramStart, paramEnd)
                    self.paramInit[ii] = math.pow(10.0, (paramInit * math.log10(self.paramNorm[ii])))
                else:
                    paramStart = self.paramStart[ii] / self.paramNorm[ii]
                    paramEnd = self.paramEnd[ii] / self.paramNorm[ii]
                    paramInit = random.uniform(paramStart, paramEnd)
                    self.paramInit[ii] = paramInit * self.paramNorm[ii]
                # end if
            # end for

            if printOut == True:
                strT = "\n---------------------- RANDOM-IN START ------------------------\n"
                dateT = datetime.datetime.now()
                dateStr = dateT.strftime("%Y-%m-%d %H:%M:%S")
                strT += (dateStr + "\n")

                strT += "Parameter:\t"
                for ii in range(0, paramCount - 1):
                    strT += self.paramName[ii] + "\t"
                # end for
                strT += self.paramName[paramCount - 1] + "\n"

                strT += "InitValue:\t"
                for ii in range(0, paramCount - 1):
                    strT += (self.paramFormat[ii] % self.paramInit[ii]) + "\t"
                # end for
                strT += (self.paramFormat[paramCount - 1] % self.paramInit[paramCount - 1]) + "\n"

                strT += "---------------------------------------------------------------\n"
                print strT
            # end if printOut

        #end if

    # end randomInit

# end slalomDevice
back to top