slalomCore.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: slalomCore.py
# Type: Class
# Use: slalomCore is used throw the included slalom.py startup module
# To extend the slalomCore class, do not modify it directly but extend it by...
# ...creating a new inherited class. Here, perform only performance tuning and bug fixing.
# ------------------------------------------------------------------------------------------------------
slalomVersion = 'Version: 1.2 Build: 1904'
# Calculation
import math
import numpy as np
from scipy import optimize, interpolate, signal
from Bayes import BayesianOptimization
from Bayes import UtilityFunction
import random
# Control
import subprocess
import datetime, shutil, os, stat, sys, time
import zipfile
import traceback
import itertools
from slalomSimulator import *
def dispError(message, doExit = True, atExit = None, errFilename = None, **atExitArgs):
""" print out an error message and exit if doExit set to True """
try:
if doExit:
strT = "\n--------------------------- ERROR: ----------------------------\n"
else:
strT = "\n-------------------------- WARNING: ---------------------------\n"
# end if
strT += message
if not message.endswith("\n"):
strT += "\n"
# end if
strT += "---------------------------------------------------------------\n"
print strT
if doExit == False:
return
# end if
if atExit is not None:
atExit(atExitArgs)
# end if
if errFilename is not None:
fileErr = open(errFilename, 'w')
fileErr.write(message)
fileErr.close()
# end if
finally:
if doExit:
# sys.exit raise SystemExit (inherits from BaseException)
sys.exit(1)
# end if
# end try
# end dispError
class slalomCore(object):
""" the SLALOM core class """
def __init__(self, Device = None, pythonInterpreter = None, deviceSimulator = "atlas"):
""" slalomCore constructor """
self.__version__ = slalomVersion
self.pythonInterpreter = "python"
self.simulator = slalomSimulator(name = deviceSimulator)
self.optimType = ""
self.isRunning = False
self.isParamUpdated = False
self.isInputChecked = False
self.optimType = ""
self.minimizeMethodList = ["L-BFGS-B", "SLSQP", "Bayes"]
self.minimizeMethod = "Bayes"
self.maxIter = 100
self.isBound = True
self.tolerance = 1e-3
self.ftolerance = 0.005
self.jaceps = np.array([])
# Optimization cache
self.lastParam = list()
self.lastOutput = list()
self.lastParamLimit = 12
self.mainTitle = ""
self.pythonInterpreter = ""
self.inputFilename = ""
self.paramCount = 0
self.paramName = []
self.paramFormat = []
self.paramFormatShort = []
self.paramFormatNormalized = []
self.paramNorm = np.array([])
self.paramStart = np.array([])
self.paramEnd = np.array([])
self.paramInit = np.array([])
self.paramPoints = []
self.paramBounds = None
# Tukey Window (Parameters Weight: decreases near the bounds)
# If optimum is near the bounds, disable this feature or enlarge domain
self.paramWeight = False
self.weightFunc = None
self.weightAlpha = 0.20
self.weightPoints = 101
# optimPoints is used to approximate the jacobian. If increased, the optimisation time will dramatically increase. The default value is 51 and the maximum value is 201.
self.optimPoints = 51
self.paramLogscale = []
self.modelFilename = None
self.paramOptim = np.array([])
self.paramNatural = np.array([])
self.paramCountTotal = 0
self.paramFormatOutput = ""
self.outputOptimized = 0.0
self.outputOptimizedx = 0.0
self.outputOptimizedy = 0.0
self.outputOptimizedz = 0.0
self.optimCounter = 1
self.funcCounter = 1
self.jacCounter = 0
self.elapsedTime = 0
self.delayMin = 0
self.delayMax = 0
self.delayMean = 0
self.counterFormat = '{0:02d}'
self.guessParam = False
self.bruteSimul = False
self.inJac = False
# output filenames:
# * these names should be the same than the simulator output filenames
# * for the following three files, the position should be kept the same:
# * >> second file (position 1) = J(V) from V = 0 to V = VOC
# * >> before last file (position 6) = efficiency calculated by simulator
# * >> last file (position 7) = J(V) (for efficiency calculation)
# * the output file names remain unchanged since at every
# optimization a new output directory is created
self.outputFilename = [ "simuloutput_all.log",
"simuloutput_jvp.log",
"simuloutput_pv.log",
"simuloutput.log",
"simuloutput_spectralresponse_eqe.log",
"simuloutput_popt.log",
"simuloutput_efficiency.log",
"simuloutput_jv.log" ]
self.outputFilenameJVPposition = 1
self.outputFilenameEFFposition = 6
self.outputFilenameJVposition = 7
# output filenames description (each output file should have a description)
self.outputComment = [ "Voltage Sweep Data",
"J(V) Characteristic from 0V to VOC (J in mA/cm2)",
"P(V) Characteristic (P in mW/cm2)",
"PV Performances Data (JSC (mA/cm2), VOC (V), FF, EFF)",
"External quantum efficiency",
"Optical Power (in mW/cm2)",
"PV Efficiency",
"J(V) Characteristic (J in mA/cm2)" ]
self.outputCount = len(self.outputComment)
# pipe filename (used for the simulator standard output redirection)
self.verboseFilename = "simuloutput_stdout.txt"
# log filename: used for the internal optimizer logging
self.logFilename = "simuloutput_log.txt"
# weight filename: not used yet
self.weightFilename = "simuloutput_weight.txt"
# log filename:
# * contain the calculated efficiency at every optimization step along with
# the corresponding set of parameters
self.outputOptimizedFilename = "simuloutput_optimized.txt"
self.commandFilename = "Optimize.bat" if (os.name == "nt") else "Optimize.sh"
# internal filenames
self.stopFilename = "stop.txt"
self.stoppedFilename = "stopped.txt"
self.stoppedDone = False
self.delayFilename = "delay.txt"
self.currentDir = ""
self.outputDir = ""
self.outputRoot = ""
self.outputDirShort = ""
self.dirSepChar = '/'
if (Device is not None) and (pythonInterpreter is not None):
self.setParam(Device, pythonInterpreter)
# end if
return
# end __init__
def log(self, strT):
""" internal logging routine (the significant events are logged in the log file) """
try:
print strT
fileT = open(self.outputDir + self.logFilename, "a")
fileT.write(strT)
fileT.close()
except:
pass
# end try
# end log
def setCounterFormat(self, maxcount):
if maxcount < 100:
self.counterFormat = '{0:02d}'
elif maxcount >= 100 and maxcount < 1000:
self.counterFormat = '{0:03d}'
elif maxcount >= 1000 and maxcount < 10000:
self.counterFormat = '{0:04d}'
else:
self.counterFormat = '{0:07d}'
# endif
# end setCounterFormat
def setInterpreter(self, pythonInterpreter):
""" set the Python interpreter (usually just 'python') """
self.pythonInterpreter = pythonInterpreter
# end setInterpreter
@staticmethod
def chmodExec(strFilename):
""" chmod a+x the simulator launcher """
statT = os.stat(strFilename)
os.chmod(strFilename, statT.st_mode | stat.S_IEXEC)
return
# end chmodExec
def checkInput(self):
""" check if the simulator files (input, C models, etc.) can be accessed """
if (self.outputCount > 0):
pathIn = ""
# Check if the simulator input file contains the correct output filenames
try:
pathIn = os.path.join(self.currentDir, self.inputFilename)
if not os.path.isfile(pathIn):
dispError("cannot open input: file not found '%s'" % pathIn,
doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
fileT = open("{0}{1}".format(self.currentDir, self.inputFilename), "r")
foundCounter = 0
for lineT in fileT:
for ii in range(0, self.outputCount):
for ll in range(0, len(self.simulator.filedecl)):
statementT = self.simulator.filedecl[ll] % self.outputFilename[ii]
# syntax should be the same than what defined in slalomSimulator
# for Silvaco: outfile=filename (without spaces aroud '=')
if (statementT in lineT):
foundCounter += 1
break
# end if
# end for
# end for
if foundCounter == self.outputCount:
break
# end for
# end for
fileT.close()
if foundCounter != self.outputCount:
dispError("Output statements not found in the simulator input file",
doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
except Exception as excT:
# catch only Exception (since sys.exit raise BaseException)
dispError("Cannot open the simulator input file '%s' %s" % (pathIn, str(excT)),
doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end try
# end if
self.isInputChecked = True
return self.isInputChecked
# end checkInput
def updatePath(self, bCreateCommand = True):
""" update/normalize input files """
# Format simulator input file
fileT = open(self.currentDir + self.inputFilename, "r")
fileContent = ""
for lineT in fileT:
# Normalize line ending (Silvaco do not run input file if contains CRLF terminated lines)
lineT = lineT.rstrip("\r\n")
fileContent += (lineT + "\n")
# end for
fileT.close()
fileT = open(self.outputDir + self.inputFilename, "w")
fileT.write(fileContent)
fileT.close()
# Format model files
if self.modelCount > 0:
for ii in range(0, self.modelCount):
if self.modelFilename[ii] == "":
continue
# end if
fileT = open(self.currentDir + self.modelFilename[ii], "r")
fileContent = ""
for lineT in fileT:
# Normalize line ending (Silvaco do not run input file if contains CRLF terminated lines)
lineT = lineT.rstrip("\r\n")
fileContent += (lineT + "\n")
# end for
fileT.close()
fileT = open(self.outputDir + self.modelFilename[ii], "w")
fileT.write(fileContent)
fileT.close()
# end for
# end if
if bCreateCommand == True:
self.simulator.update(self.simulator.name, self.inputFilename, self.currentDir, self.outputDir, self.verboseFilename)
strT = ""
lenT = len(self.simulator.command)
for ii in range(0, lenT):
strT += self.simulator.command[ii]
if (ii < (lenT - 1)):
strT += "\n"
# end if
# end for
fileT = open(self.outputDir + self.commandFilename, "w")
fileT.write(strT)
fileT.close()
# chmod a+x self.commandFilename
self.chmodExec(self.outputDir + self.commandFilename)
# end if
return True
# end updatePath
def isChecked(self):
""" input files successfully checked? """
return (self.isInputChecked and self.isParamUpdated)
# end isChecked
@staticmethod
def printTime(secondsT, short=False):
""" print the time in a readable form """
strT = ""
if (secondsT < 60.0):
strT = ("%02d" % secondsT) + (" seconds" if not short else " s")
elif (secondsT < 3600.0):
strT = ("%.2f" % (secondsT / 60.0)) + (" minutes" if not short else " m")
else:
strT = ("%.2f" % (secondsT / 3600.0)) + (" hours" if not short else " h")
# end if
return strT
# end printTime
def finish(self, errorOccured=True, userStopped=True, x=None, success=None, message=None, nit=None, nlfev=None, xl=None, funl=None):
""" print out results at the optimization end """
try:
dateT = datetime.datetime.now()
dateStr = dateT.strftime("%Y-%m-%d %H:%M:%S")
if errorOccured == False:
strT = "\n---------------------------------------------------------------\n"
strT += ("Optimization ended @ " if (userStopped == False) else "Optimization interrupted @ ") + dateStr
strT += ("\nMAXIMUM Efficiency: %g %%" % self.outputOptimized)
strT += ("\nWITH Jsc = %g mA/cm2" % self.outputOptimizedx) + (" ; Voc = %.4f" % self.outputOptimizedy) + (
" ; FF = %.3f %%" % self.outputOptimizedz) + "\nOBTAINED FOR:\n"
for ii in range(0, self.paramCount - 1):
strT += self.paramName[ii] + "\t"
# end for
strT += self.paramName[self.paramCount - 1] + "\n"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormat[ii] % self.paramOptim[ii]) + "\t"
# end for
strT += (self.paramFormat[self.paramCount - 1] % self.paramOptim[self.paramCount - 1])
strT += "\nTotal duration: " + self.printTime(float(self.elapsedTime))
strT += ("\nNumber of function evaluations: %d" % self.funcCounter)
if (self.jacCounter >= self.paramCount):
strT += (" (%d for the Jacobian approximation)" % self.jacCounter)
# end if
strT += "\n---------------------------------------------------------------\n"
if (x is not None) and (success is not None) and (message is not None):
xt = np.zeros(self.paramCount)
for ii in range(0, self.paramCount):
if self.paramLogscale[ii]:
xt[ii] = math.pow(10.0, (x[ii] * math.log10(self.paramNorm[ii])))
else:
xt[ii] = x[ii] * self.paramNorm[ii]
# end if
# end for
strT += "\n---------------------------------------------------------------\n"
strT += "Optimization function (" + self.minimizeMethod + ") output:\n"
strT += "Parameter:\t"
for ii in range(0, self.paramCount - 1):
strT += self.paramName[ii] + "\t"
# end for
strT += self.paramName[self.paramCount - 1] + "\n"
strT += "x (natural):\t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormat[ii] % xt[ii]) + "\t"
# end for
strT += (self.paramFormat[self.paramCount - 1] % xt[self.paramCount - 1]) + "\n"
strT += "x (normalized):\t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormatNormalized[ii] % x[ii]) + "\t"
# end for
strT += (self.paramFormatNormalized[self.paramCount - 1] % x[self.paramCount - 1]) + "\n"
strT += "\nsuccess: " + str(success) + "\n"
strT += "\nmessage: " + str(message) + "\n"
strT += "\nevaluations: " + str(self.funcCounter)
strT += "\n---------------------------------------------------------------\n"
# end if
if (xl is not None) and (funl is not None) and (success is not None) and (message is not None):
nxl = len(xl)
nfunl = len(funl)
if (nxl == nfunl):
if (nxl > 10):
nxl = 10
nfunl = 10
# end if
ll = 0
for xll in xl:
effl = 100.0 * (1.0 - funl[ll])
ll = ll + 1
if (effl < 0.0) or (effl > 90.0):
# should never happen
continue
# end if
xt = np.zeros(self.paramCount)
for ii in range(0, self.paramCount):
if self.paramLogscale[ii]:
xt[ii] = math.pow(10.0, (xll[ii] * math.log10(self.paramNorm[ii])))
else:
xt[ii] = xll[ii] * self.paramNorm[ii]
# end if
# end for
strT += "\n---------------------------------------------------------------\n"
strT += "Optimization function (" + self.minimizeMethod + (") local output #%d:\n" % ll)
strT += "Parameter:\t"
for ii in range(0, self.paramCount - 1):
strT += self.paramName[ii] + "\t"
# end for
strT += self.paramName[self.paramCount - 1] + "\n"
strT += "xl (natural):\t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormat[ii] % xt[ii]) + "\t"
# end for
strT += (self.paramFormat[self.paramCount - 1] % xt[self.paramCount - 1]) + "\n"
strT += "xl (normalized):\t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormatNormalized[ii] % xll[ii]) + "\t"
# end for
strT += (self.paramFormatNormalized[self.paramCount - 1] % xll[self.paramCount - 1]) + "\n"
strT += "\nefficiency: %06.3f %%" % effl
strT += "\n---------------------------------------------------------------\n"
# end if
# end if
# end if
self.log(strT)
# end if
strLog = ("# Optimization ended @ " if (userStopped == False) else "# Optimization interrupted @ ") + dateStr + "\n"
fileOptim = open(self.outputDir + self.outputOptimizedFilename, "a")
fileOptim.write(strLog)
fileOptim.close()
self.log(strLog)
self.stopSet()
if self.stoppedDone == False:
pathStopped = os.path.join(self.outputDir, self.stoppedFilename)
fileT = open(pathStopped, "w")
fileT.write(strLog)
fileT.close()
self.stoppedDone = True
# end if
self.log("\nZipping optimization result files...")
zipFilename = self.outputRoot + self.outputDirShort + ".zip"
outFile = zipfile.ZipFile(zipFilename, "w", compression=zipfile.ZIP_DEFLATED)
dirToZip = self.outputDir.rstrip(self.dirSepChar)
for (dirPath, dirNames, fileNames) in os.walk(dirToZip):
for fileName in fileNames:
fileAbsolutePath = os.path.join(dirPath, fileName)
fileRelativePath = fileAbsolutePath.replace(dirToZip + self.dirSepChar, '')
outFile.write(fileAbsolutePath, fileRelativePath)
# end for
# end for
outFile.close()
self.log("\nZipping done (File: " + self.outputDirShort + ".zip" + ").")
self.isRunning = False
except:
pass
# end try
self.isRunning = False
if self.stoppedDone == False:
try:
pathStopped = os.path.join(self.outputDir, self.stoppedFilename)
fileT = open(pathStopped, "w")
fileT.write("SLALOM\nStopped\n")
fileT.close()
self.stoppedDone = True
except:
pass
# end try
# end if
sys.exit(0 if (errorOccured == False) else 1)
# end finish
def guess(self):
""" get the initial parameters set """
self.guessParam = True
self.bruteSimul = False
paramNormalized0 = np.zeros(self.paramCount)
for ii in range(0, self.paramCount):
if self.paramLogscale[ii]:
paramNormalized0[ii] = math.log10(self.paramInit[ii]) / math.log10(self.paramNorm[ii])
else:
paramNormalized0[ii] = self.paramInit[ii] / self.paramNorm[ii]
# end if
# end for
self.guessParam = False
return paramNormalized0
# end guess
def isErrorOccurred(self):
""" check if a simulator error has occurred """
maxLines = 16383
curLine = 0
simulatorError = None
try:
pathV = os.path.join(self.outputDir + self.verboseFilename)
if not os.path.isfile(pathV):
return None
# end if
iLines = 0;
fileT = open(self.outputDir + self.verboseFilename, "r")
for lineT in fileT:
if (simulatorError is not None):
simulatorError += lineT + '\n'
iLines += 1
if iLines >= 12:
return simulatorError
# end if
else:
for (name, descr) in self.simulator.error:
if lineT.find(name) != -1:
simulatorError = name + ': ' + descr + '\n'
# end if
# end for
# end if
curLine += 1
if curLine >= maxLines:
return simulatorError
# end if
# end for
fileT.close()
except:
pass
# end try
return simulatorError
# end isErrorOccurred
def printOutput(self):
""" print out the simulator output """
maxLines = 16383
curLine = 0
try:
pathV = os.path.join(self.outputDir + self.verboseFilename)
if not os.path.isfile(pathV):
return
# end if
fileT = open(self.outputDir + self.verboseFilename, "r")
strT = "\n# ---------------------- SIMULATOR OUTPUT: ----------------------\n\n"
print strT
for lineT in fileT:
strT += "# " + lineT
print lineT
curLine += 1
if curLine >= maxLines:
strT += "\n# SIMULATOR OUTPUT TOO LONG\n"
break
# end if
# end for
fileT.close()
strTT = "\n\n# ---------------------------------------------------------------\n"
print strTT
strT += strTT
except:
strT = "\n# ----------------------- SIMULATOR ERROR: -----------------------\n"
strT += "# Check the simulator output for details.\n"
strT += "\n# ---------------------------------------------------------------\n"
pass
# end try
return strT
# end printOutput
def stopSet(self):
""" check is the user has required the optimization to stop """
isStopSet = False
pathStop = os.path.join(self.outputDir, self.stopFilename)
try:
if os.path.isfile(pathStop):
isStopSet = True
shutil.move(self.outputDir + self.stopFilename, self.outputDir + "_" + self.stopFilename)
# end if
if isStopSet:
pathOf = os.path.join(self.currentDir, "ofname.txt")
if os.path.isfile(pathOf):
os.unlink(pathOf)
# end if
# end if
except:
pass
# end try
return isStopSet
# end stopSet
def getOptimizeJac(self, optimFunc):
""" Construct the Jacobian approximation function. Adapted from the SLSQSP code source (scipy/optimize/slsqp.py) """
def optimizeJac(x, *args):
x0 = np.asfarray(x)
self.inJac = False
f0 = np.atleast_1d(optimFunc(*((x0,)+args)))
self.inJac = True
ixcount = len(x0)
ifcount = len(f0)
jac = np.zeros([ixcount, ifcount])
dx = np.zeros(ixcount)
for ii in range(ixcount):
self.log("\nJacobian approximation [%d / %d]..." % (ii + 1, ixcount))
dx[ii] = self.jaceps[ii]
jac[ii] = (optimFunc(*((x0+dx,)+args)) - f0) / self.jaceps[ii]
dx[ii] = 0.0
# end for
self.jacCounter += ixcount
self.inJac = False
return jac.transpose()
# end optimizeJac
return optimizeJac
# end getOptimizeJac
def getWeight(self, paramIndex, paramNormalized):
""" weight/cost function for future use (giving each parameter a weight...) """
if (self.paramWeight == False) or (self.weightFunc is None) or (self.paramBounds is None) or (paramIndex < 0) or (paramIndex >= self.paramCount):
return 1.0
# end if
icw = len(self.weightFunc)
if (icw < 7):
return 1.0
# end if
(paramMin, paramMax) = self.paramBounds[paramIndex]
if (paramMax <= paramMin) or (paramNormalized < paramMin) or (paramNormalized > paramMax):
return 0.0
# end if
tdw = (paramMax - paramMin)
idx = int(((paramNormalized - paramMin) * float(icw)) / tdw)
if (idx < 0):
idx = 0
elif (idx >= icw):
idx = icw - 1
# end if
return self.weightFunc[idx]
# end if
def optimizeFuncBayesian(self, **paramNormalizedBayesian):
""" the optimizer maximization function for the Bayesian method """
paramCount = len(paramNormalizedBayesian)
if (self.paramCount != paramCount):
# should never happen
try:
self.finish(errorOccured=True, userStopped=True)
except:
self.isRunning = False
sys.exit(1)
# end try
return 0.0
# end if
paramNormalized = np.zeros(self.paramCount)
for paramT in paramNormalizedBayesian:
for ii in range(0, self.paramCount):
if (self.paramName[ii] == paramT):
paramNormalized[ii] = float(paramNormalizedBayesian[paramT])
break
# end if
# end for
# end for
return self.optimizeFunc(paramNormalized)
# end optimizeFuncBayesian
def optimizeFunc(self, paramNormalized):
""" the optimizer minimization function """
paramCount = len(paramNormalized)
if (self.paramCount != paramCount):
# should never happen
try:
self.finish(errorOccured=True, userStopped=True)
except:
self.isRunning = False
sys.exit(1)
# end try
return 0.0
# end if
bShowOutput = ((self.inJac == False) or (self.optimType == "Brute"))
# If stopFilename exists, stop optimization
if self.stopSet():
try:
self.finish(errorOccured=False, userStopped=True)
except:
self.isRunning = False
sys.exit(1)
# end try
return 0.0
# end if
for ii in range(0, self.paramCount):
if self.paramLogscale[ii]:
self.paramNatural[ii] = math.pow(10.0, (paramNormalized[ii] * math.log10(self.paramNorm[ii])))
else:
self.paramNatural[ii] = paramNormalized[ii] * self.paramNorm[ii]
# end if
# end for
# A cache strategy is implemented to avoid redundant calculation.
try:
if (self.funcCounter >= 1) and (len(self.lastParam) >= 1):
tParam = ""
for ii in range(0, self.paramCount - 1):
tParam += (self.paramFormat[ii] % self.paramNatural[ii]) + "\t"
# end for
tParam += (self.paramFormat[self.paramCount - 1] % self.paramNatural[self.paramCount - 1])
if (tParam in self.lastParam):
return self.lastOutput[self.lastParam.index(tParam)]
# end if
# end if
except:
pass
# end try
strT = ""
if (bShowOutput == True):
if self.guessParam:
strT = "\n-------------------- GUESS " + (self.counterFormat.format(self.optimCounter)) + " RUNNING -----------------------\n"
else:
strT = "\n----------------- OPTIMIZATION " + (self.counterFormat.format(self.optimCounter)) + " RUNNING ---------------------\n"
# end if
strT += self.title + ": Optimization (" + self.optimType
if self.optimType == "Optim":
strT += " " + self.minimizeMethod
# end if
strT += ") "
dateT = datetime.datetime.now()
dateStr = dateT.strftime("%Y-%m-%d %H:%M:%S")
strT += (dateStr + "\n")
strT += "Parameter:\t"
for ii in range(0, self.paramCount - 1):
if ((self.paramPoints[ii] > 1) or (self.bruteSimul == False)):
strT += "@" + self.paramName[ii] + "\t"
else:
strT += self.paramName[ii] + "\t"
# end if
# end for
if ((self.paramPoints[self.paramCount - 1] > 1) or (self.bruteSimul == False)):
strT += "@" + self.paramName[self.paramCount - 1] + "\n"
else:
strT += self.paramName[self.paramCount - 1] + "\n"
# end if
strT += "Natural:\t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormat[ii] % self.paramNatural[ii]) + "\t"
# end for
strT += (self.paramFormat[self.paramCount - 1] % self.paramNatural[self.paramCount - 1]) + "\n"
strT += "Normalized:\t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormatNormalized[ii] % paramNormalized[ii]) + "\t"
# end for
strT += (self.paramFormatNormalized[self.paramCount - 1] % paramNormalized[self.paramCount - 1])
strT += "\n---------------------------------------------------------------\n"
self.log(strT)
# end if bShowOutput
ticT = time.time()
pathIn = os.path.join(self.outputDir, self.inputFilename)
if not os.path.isfile(pathIn):
dispError("cannot open input: file not found", doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
fileContent = ""
fileT = open(self.outputDir + self.inputFilename, "r")
for lineT in fileT:
# Normalize line ending (Silvaco do not run input file if contains CRLF terminated lines)
lineT = lineT.rstrip("\r\n")
lineX = lineT.lstrip("\t ")
nSpaces = len(lineT) - len(lineX)
prefixT = lineT[0:nSpaces]
if (self.simulator.name == "atlas") and lineX.startswith("tonyplot"):
# skip tonyplot commands
continue
# endif
if not lineX.startswith("#"):
for ii in range(0, self.paramCount):
setparamT = self.simulator.vardeclpre % self.paramName[ii]
if lineX.startswith(setparamT):
# Need to format parameter to match simulator floating representation
strT = self.paramFormatShort[ii] % self.paramNatural[ii]
fT = float(strT)
lineT = self.simulator.vardecl % (self.paramName[ii], fT)
break
# end if
# end for
# end if
fileContent += (lineT + "\n")
# end for
fileT.close()
fileT = open(self.outputDir + self.inputFilename, "w")
fileT.write(fileContent)
fileT.close()
# format model files
if self.modelCount > 0:
for ii in range(0, self.modelCount):
if self.modelFilename[ii] == "":
break
# end if
pathCC = os.path.join(self.outputDir, self.modelFilename[ii])
if not os.path.isfile(pathCC):
dispError("cannot open model file: " + self.modelFilename[ii], doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
fileContent = ""
fileT = open(self.outputDir + self.modelFilename[ii], "r")
for lineT in fileT:
# Normalize line ending (Silvaco do not run input file if contains CRLF terminated lines)
lineT = lineT.rstrip("\r\n")
lineX = lineT.lstrip("\t ")
nSpaces = len(lineT) - len(lineX)
prefixT = lineT[0:nSpaces]
bFound = False
nn = len(self.paramName)
bFound = False
if nn > 0:
for jj in range(0, nn):
setparamT = "double " + self.paramName[jj] + " = "
if lineX.startswith(setparamT):
strT = self.paramFormat[jj] % self.paramNatural[jj]
fT = float(strT)
lineT = prefixT + setparamT + ("%g" % fT) + ";"
bFound = True
break
# end if
# end for
# end if
fileContent += (lineT + "\n")
# end while
fileT.close()
fileT = open(self.outputDir + self.modelFilename[ii], "w")
fileT.write(fileContent)
fileT.close()
# end for
# end if
# remove the simulator verbose output file before starting optimization
pathT = os.path.join(self.outputDir, self.verboseFilename)
try:
if os.path.isfile(pathT):
os.unlink(pathT)
# end if
except:
pass
# end try
# run optimization
try:
tEnv = dict(os.environ)
subprocess.check_call([self.outputDir + self.commandFilename, ""], shell=True, env=tEnv)
except subprocess.CalledProcessError, excT:
try:
strT = self.printOutput()
fileOptim = open(self.outputDir + self.outputOptimizedFilename, "a")
fileOptim.write(strT)
fileOptim.close()
except:
pass
# end try
dispError(traceback.format_exc(), doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
return 0.0
# end try
simulatorError = self.isErrorOccurred()
if (simulatorError is not None):
try:
fileOptim = open(self.outputDir + self.outputOptimizedFilename, "a")
fileOptim.write(simulatorError)
fileOptim.close()
except:
pass
# end try
dispError(simulatorError, doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
return 0.0
# end if
# Calculate the efficiency (Very important to be precise for the optimization algorithm)
LinesToSkip = 4
arrVoltage = np.array([])
arrCurrent = np.array([])
arrPower = np.array([])
iLine = 0
iPoints = 0
iPVPoints = 0
pathJV = os.path.join(self.outputDir, self.outputFilename[self.outputFilenameJVposition])
if not os.path.isfile(pathJV):
# do not necessarily exit, since the simulator can sometimes diverge for a set of parameters choosen by the optimizer
dispError("cannot evaluate efficiency: J-V file not found: check the simulator output file (%s)" % self.verboseFilename, doExit = False)
return 0.0
# end if
# :REV:1:20181115: J(V) from V = 0 to V = VOC (the photovoltaic part of the I(V) characteristic)
pathJVP = os.path.join(self.outputDir, self.outputFilename[self.outputFilenameJVPposition])
JVPcontent = ""
try:
iVpos = 0
fVocx = 0.0
fV = 0.0
fVprev = 0.0
fJ = 0.0
fJprev = 0.0
fP = 0.0
fPprev = 0.0
DblPrecision = 1e-13
bStarted = False
bFirstV = False
fileT = open(self.outputDir + self.outputFilename[self.outputFilenameJVposition], "r")
for lineT in fileT:
if (lineT.startswith("#")):
JVPcontent += lineT
continue
# end if
if iLine < LinesToSkip:
iLine += 1
JVPcontent += lineT
continue
# end if
arrLine = []
try:
arrLine = lineT.split(self.simulator.dataSeparator)
except:
break
# end try
if (len(arrLine) < 2):
continue
# end if
fV = float(arrLine[0])
fJ = float(arrLine[1])
fP = math.fabs(fV * fJ)
if (False == bStarted):
bStarted = True
fVprev = fV
fJprev = fJ
fPprev = fP
continue
# end if
# voltage should be in increasing order
if (fV <= fVprev):
fVprev = fV
fJprev = fJ
fPprev = fP
continue
# end if
if (math.fabs(fV) < DblPrecision):
fV = 0.0
# end if
if (False == bFirstV):
bFirstV = True
arrVoltage = np.append(arrVoltage, fVprev)
arrCurrent = np.append(arrCurrent, fJprev)
arrPower = np.append(arrPower, fPprev)
continue
# end if
arrVoltage = np.append(arrVoltage, fV)
arrCurrent = np.append(arrCurrent, fJ)
arrPower = np.append(arrPower, fP)
iPoints = len(arrVoltage)
if (fV * fJ) <= 0.0:
JVPcontent += lineT
# end if
if (iPoints != len(arrCurrent)) or (iPoints != len(arrPower)):
# do not necessarily exit, since the simulator can sometimes diverge for a set of parameters choosen by the optimizer
dispError("cannot evaluate efficiency: J-V file content not valid: check the simulator output file (%s)" % self.verboseFilename, doExit = False)
return 0.0
# end if
if ((fV > 0.0) and (fJ > 0.0)):
if (iVpos == 0):
fVocx = fVprev
# end if
iVpos += 1
if (iVpos > 2):
break
# end if
# end if
if ((fV * fJ) < 0.0):
iPVPoints += 1
#end if
fVprev = fV
iLine += 1
# end for
fileT.close()
except:
dispError("cannot evaluate efficiency: check the simulator output file (%s)" % self.verboseFilename, doExit = True)
pass
# end try
# :REV:1:20181115: J(V) from V = 0 to V = VOC
fileJVP = open(pathJVP, "w")
fileJVP.write(JVPcontent)
fileJVP.close()
outputT = 0.0
outputO = 0.0
# max current in mA/cm2
fJm = 0.0
# max voltage in Volts
fVm = 0.0
# Fill Factor
fFF = 0.0
# Short-circuit current in mA/cm2
fJsc = 0.0
# Open-circuit voltage in Volts
fVoc = 0.0
# Maximal power in mW/cm2
Pmax = 0.0
doCalc = True
if (iPVPoints < 12):
doCalc = False
# do not necessarily exit, since the simulator can sometimes diverge for a set of parameters choosen by the optimizer
dispError("Cannot evaluate efficiency: J-V curve has less than 12 points with V*J < 0", doExit = False)
return 0.0
# end if
try:
if doCalc == True:
windowLen = 8
# interpolate the J-V data to accurately calculate the efficiency
funcCurrent = interpolate.interp1d(arrVoltage, arrCurrent, kind='slinear')
funcPower = interpolate.interp1d(arrVoltage, arrPower, kind='cubic')
iPointsNew = iPoints * windowLen
#
dV = (arrVoltage[iPoints - 1] - arrVoltage[0]) / float(iPointsNew - 1)
arrVoltageNew = np.arange(arrVoltage[0], arrVoltage[iPoints - 1] + dV, dV)
iPointsNew = len(arrVoltageNew)
iC = iPointsNew
for ii in range(iPointsNew - 1, iPointsNew - windowLen - 1, -1):
if (arrVoltageNew[ii] > arrVoltage[iPoints - 1]):
iC -= 1
else:
break
# end if
# end for
if iC < iPointsNew:
arrVoltageNew = np.delete(arrVoltageNew, np.arange(iC, iPointsNew, 1))
iPointsNew = len(arrVoltageNew)
# end for
arrCurrentNew = funcCurrent(arrVoltageNew)
arrPowerNew = funcPower(arrVoltageNew)
dV = arrVoltageNew[1] - arrVoltageNew[0]
bFoundJsc = False
bFoundVoc = False
bFoundPmax = False
# Find PV parameters
for ii in range(0, iPointsNew - 1):
# Short-circuit current (mA/cm2)
if (bFoundJsc == False):
if ((arrVoltageNew[ii] < 0.0) and (arrVoltageNew[ii + 1] > 0.0)):
fJsc = 0.5 * (arrCurrentNew[ii] + arrCurrentNew[ii + 1])
bFoundJsc = True
if (bFoundJsc == True) and (bFoundVoc == True) and (bFoundPmax == True):
break
# end if
elif ((arrVoltageNew[ii] >= 0.0) and (arrVoltageNew[ii] <= dV)):
fJsc = arrCurrentNew[ii]
bFoundJsc = True
if (bFoundJsc == True) and (bFoundVoc == True) and (bFoundPmax == True):
break
# end if
# end if
# end if
# Open-circuit voltage (V)
if (bFoundVoc == False):
if ((arrCurrentNew[ii] < 0.0) and (arrCurrentNew[ii + 1] > 0.0)):
fVoc = 0.5 * (arrVoltageNew[ii] + arrVoltageNew[ii + 1])
bFoundVoc = True
if (bFoundJsc == True) and (bFoundVoc == True) and (bFoundPmax == True):
break
elif ((arrCurrentNew[ii] < 0.0) and (arrCurrentNew[ii + 1] >= 0.0)):
fVoc = arrVoltageNew[ii + 1]
bFoundVoc = True
if (bFoundJsc == True) and (bFoundVoc == True) and (bFoundPmax == True):
break
# end if
# end if
# end if
# Maximum power (mW/cm2)
if ((bFoundPmax == False) and (ii >= windowLen) and (ii <= (iPointsNew - windowLen))):
if ((arrPowerNew[ii - 2] < arrPowerNew[ii - 1]) and (arrPowerNew[ii - 1] < arrPowerNew[ii]) and (arrPowerNew[ii] > arrPowerNew[ii + 1]) and (arrPowerNew[ii + 1] > arrPowerNew[ii + 2])):
fJm = arrCurrentNew[ii]
fVm = arrVoltageNew[ii]
Pmax = arrPowerNew[ii]
bFoundPmax = True
if (bFoundJsc == True) and (bFoundVoc == True) and (bFoundPmax == True):
break
# end if
# end if
# end if
# Direct polarization
if (((arrVoltageNew[ii] > 0.0) and (arrCurrentNew[ii] > 0.0)) or ((arrVoltageNew[ii] < 0.0) and (arrCurrentNew[ii] < 0.0))) and bFoundPmax:
break
# end if
# end for (Find PV parameters)
outputT = 0.0
if (bFoundJsc == False):
if ((arrCurrentNew[0] < 0.0) and (arrVoltageNew[0] > 0.0)):
fJsc = -arrCurrentNew[0]
# end if
else:
fJsc = -fJsc
# end if
if (bFoundVoc == False):
if ((arrCurrentNew[iPointsNew - 1] < 0.0) and (arrVoltageNew[iPointsNew - 1] > 0.0)):
fVoc = arrVoltageNew[iPointsNew - 1]
else:
if (fVocx > 0.01):
fVoc = fVocx
bFoundVoc = True
# end if
# end if
# end if
if ((bFoundJsc == True) and (bFoundVoc == True) and (bFoundPmax == True)):
fFF = 100.0 * Pmax / math.fabs(fJsc * fVoc)
if (fFF >= 95.0):
bFoundPmax = False
# end if
# end if
if ((bFoundJsc == False) or (bFoundVoc == False) or (bFoundPmax == False)):
strT = "Cannot evaluate efficiency: "
if bFoundJsc:
strT += (" JSC = %.5f mA/cm2" % math.fabs(fJsc))
else:
strT += " JSC not found"
# end if
if bFoundVoc:
strT += (" VOC = %.5f V" % math.fabs(fVoc))
else:
strT += " VOC not found"
# end if
if bFoundPmax:
strT += (" Pmax = %.5f mW/cm2" % Pmax)
else:
strT += " Pmax not found"
Pmax = 0.0
# end if
strT += "\n -> increase V-range and/or decrease V-step"
dispError(strT, doExit = False)
# end if
# AM 1.5 power density ~ 100 mW/cm2 (Atlas default AM 1.5 spectrum gives 100.037 mW/cm2)
Psolar = 100.037
outputT = 100.0 * Pmax / Psolar
if (outputT == 0.0):
fJsc = 0.0
fVoc = 0.0
fFF = 0.0
# end if
# efficiency as calculated by the simulator
if ((bFoundJsc == True) and (bFoundVoc == True) and (bFoundPmax == True)):
pathEE = os.path.join(self.outputDir, self.outputFilename[self.outputFilenameEFFposition])
if not os.path.isfile(pathEE):
dispError("cannot evaluate efficiency: '%s' file not found" % pathEE, doExit = False)
# end if
outputO = 0.0
try:
fileO = open(self.outputDir + self.outputFilename[self.outputFilenameEFFposition], "r")
lineO = ""
iLT = len("Efficiency=20.123456789123456789123456789")
for lineOT in fileO:
if lineOT.startswith("Efficiency=") and (len(lineOT) <= iLT):
lineO = lineOT
# end if
# end for
fileO.close()
if lineO.startswith("Efficiency=") and (len(lineO) <= iLT):
outputO = float(lineO.split("=")[1].rstrip(" \t\r\n").lstrip(" \t\r\n"))
# end if
os.unlink(self.outputDir + self.outputFilename[self.outputFilenameEFFposition])
except:
outputO = 0.0
pass
# end try
# end if
if outputT > self.outputOptimized:
self.outputOptimized = outputT
self.paramOptim = np.zeros(self.paramCount)
for ii in range(0, self.paramCount):
self.paramOptim[ii] = self.paramNatural[ii]
# end if
# end if
if math.fabs(fJsc) > self.outputOptimizedx:
self.outputOptimizedx = math.fabs(fJsc)
# end if
if math.fabs(fVoc) > self.outputOptimizedy:
self.outputOptimizedy = math.fabs(fVoc)
# end if
if fFF > self.outputOptimizedz:
self.outputOptimizedz = fFF
# end if
# end if doCalc
except:
dispError(traceback.format_exc(), doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
pass
# end try
durationT = time.time() - ticT
self.elapsedTime += durationT
if self.funcCounter == 1:
self.delayMean = float(durationT)
else:
self.delayMean = float(self.elapsedTime) / float(self.funcCounter)
# end if
if (self.delayMin == 0) or (durationT < self.delayMin):
self.delayMin = durationT
if (self.delayMax == 0) or (durationT > self.delayMax):
self.delayMax = durationT
try:
# Timing information
pathDelay = os.path.join(self.outputDir, self.delayFilename)
fileT = open(pathDelay, "w")
fileT.write("DelayMin = %.1f" % self.delayMin)
fileT.write("\nDelayMax = %.1f" % self.delayMax)
fileT.write("\nDelayMean = %.1f" % self.delayMean)
fileT.close()
except:
pass
strT = ""
if (bShowOutput == True):
if self.guessParam:
strT = "\n---------------------- GUESS " + (self.counterFormat.format(self.optimCounter)) + " DONE --------------------------\n"
else:
strT = "\n------------------- OPTIMIZATION " + (self.counterFormat.format(self.optimCounter)) + " DONE ----------------------\n"
# end if
strT += self.title + ": Optimization (" + self.optimType
if self.optimType == "Optim":
strT += " " + self.minimizeMethod
# end if
strT += ") "
dateT = datetime.datetime.now()
dateStr = dateT.strftime("%Y-%m-%d %H:%M:%S")
strT += (dateStr + "\n")
strT += "Parameter:\t"
for ii in range(0, self.paramCount - 1):
strT += self.paramName[ii] + "\t"
# end for
strT += self.paramName[self.paramCount - 1] + "\n"
strT += "Natural:\t"
tParam = ""
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormat[ii] % self.paramNatural[ii]) + "\t"
tParam += (self.paramFormat[ii] % self.paramNatural[ii]) + "\t"
# end for
strT += (self.paramFormat[self.paramCount - 1] % self.paramNatural[self.paramCount - 1]) + "\n"
tParam += (self.paramFormat[self.paramCount - 1] % self.paramNatural[self.paramCount - 1])
if (len(self.lastParam) >= self.lastParamLimit):
self.lastParam.pop(0)
# end if
self.lastParam.append(tParam)
strT += "Normalized:\t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormatNormalized[ii] % paramNormalized[ii]) + "\t"
# end for
strT += (self.paramFormatNormalized[self.paramCount - 1] % paramNormalized[self.paramCount - 1])
strT += "\nPRESENT Efficiency: " + ("%g %%" % outputT) + " (Simulator: " + ("%g %%" % outputO) + ")"
strT += "\nPRESENT " + ("FF = %08.5f %%" % fFF) + (" ; Jsc = %08.5f mA/cm2" % math.fabs(fJsc)) + (" ; Voc = %08.5f V" % math.fabs(fVoc))
strT += "\nMAXIMUM Efficiency: %g %%" % self.outputOptimized
strT += "\nThis run duration: " + self.printTime(float(durationT)) + " (mean: " + self.printTime(self.delayMean) + ")"
strT += "\nElapsed time: " + self.printTime(float(self.elapsedTime))
strT += "\nNumber of function evaluations: %d" % self.funcCounter
if self.bruteSimul:
if self.funcCounter < (self.paramCountTotal - 1):
remainingT = (float(self.paramCountTotal) * self.delayMean) - float(self.elapsedTime)
strT += "\nEstimated remaining time: " + self.printTime(remainingT)
# end if
# end if
strT += "\n---------------------------------------------------------------\n"
self.log(strT)
dateStrCompact = None
if not self.guessParam:
dateT = datetime.datetime.now()
dateStrCompact = dateT.strftime("%Y%m%d-%H%M%S")
strT = (self.counterFormat.format(self.optimCounter)) + "\t" + dateStrCompact + "\t"
for ii in range(0, self.paramCount):
strT += (self.paramFormat[ii] % self.paramNatural[ii]) + "\t"
# end for
strT += ("%08.5f\t" % math.fabs(fJm)) + ("%08.5f\t" % fVm) + ("%08.5f\t" % fFF) + ("%08.5f\t" % math.fabs(fJsc)) + ("%08.5f\t" % math.fabs(fVoc)) + ("%08.5f" % outputT) + "\n"
fileOptim = open(self.outputDir + self.outputOptimizedFilename, "a")
fileOptim.write(strT)
fileOptim.close()
# end if
# in updateOutput, output files are moved
self.updateOutput(dateStrCompact)
else:
# delete output files before the next run
self.deleteOutput()
# end if bShowOutput
if self.inJac == False:
self.optimCounter += 1
# end if
self.funcCounter += 1
if not self.guessParam:
if self.paramWeight and self.isBound:
# Parameters Weight: decreases near the bounds
fa = 0.0
fb = 0.0
for ii in range(0, self.paramCount):
ft = (paramNormalized[ii] * paramNormalized[ii])
fa += ft
fw = self.getWeight(ii, paramNormalized[ii])
fb += ft * (fw * fw)
# end for
if (fa > 0.0):
outputT = outputT * (fb / fa)
# end if
# end if
# all methods minimize except the Bayesian method that maximizes
if (self.minimizeMethod == "Bayes"):
tOutput = outputT
else:
tOutput = (1.0 - (outputT / 100.0))
# end if
if (len(self.lastOutput) >= self.lastParamLimit):
self.lastOutput.pop(0)
# end if
self.lastOutput.append(tOutput)
return tOutput
else:
return outputT
# end if
# end optimizeFunc
@staticmethod
def removeOutputFiles(outputDirT):
""" delete the output directory content (everything in that directory will be deleted!) """
for fileT in os.listdir(outputDirT):
pathT = os.path.join(outputDirT, fileT)
try:
if os.path.isfile(pathT):
os.unlink(pathT)
elif os.path.isdir(pathT):
shutil.rmtree(pathT)
# end if
except:
pass
# end try
# end for
return
# end removeOutputFiles
def updateOutputFile(self, outputFilenameOld, outputComment, outputFilenameSuffix):
""" update/rename the output files"""
outputFilenameNew = ""
# Files in the output directory
try:
pathOld = os.path.join(self.outputDir, outputFilenameOld)
if not os.path.isfile(pathOld):
return
# end if
fileT = open(self.outputDir + outputFilenameOld, "r")
fileT.close()
# Exists... change filename and shutil.move it to the outpur dir
strT1 = outputFilenameOld.split(".")[0]
strT2 = outputFilenameOld.split(".")[1]
outputFilenameNew = strT1 + "_" + (self.counterFormat.format(self.optimCounter)) + "_" + outputFilenameSuffix + "." + strT2
shutil.move(self.outputDir + outputFilenameOld, self.outputDir + outputFilenameNew)
except:
return
# end try
try:
# Save optimization infos
fileHeader = "# " + self.title + "\n# "
fileHeader += outputComment + "\n# "
for ii in range(0, self.paramCount):
fileHeader += self.paramName[ii]
if (ii < (self.paramCount - 1)):
fileHeader += "\t"
# end if
# end for
fileHeader += "\n# "
for ii in range(0, self.paramCount):
fileHeader += (self.paramFormat[ii] % self.paramNatural[ii])
if (ii < (self.paramCount - 1)):
fileHeader += "\t"
# end if
# end for
fileHeader += "\n"
fileT = open(self.outputDir + outputFilenameNew, "r")
lll = False
fileContent = ""
for lineT in fileT:
if lll == False:
if lineT.startswith(self.simulator.header):
fileContent = lineT + fileHeader
else:
fileContent = fileHeader + lineT
lll = True
continue
# end if
fileContent += lineT
# end for
fileT.close()
fileContent += "\n"
fileT = open(self.outputDir + outputFilenameNew, "w")
fileT.write(fileContent)
fileT.close()
except Exception as excT:
# catch only Exception (since sys.exit raise BaseException)
dispError(traceback.format_exc(), doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
pass
# end try
return
# end updateOutputFile
def updateOutput(self, dateStrCompact):
""" update the simulator output files """
if dateStrCompact is None:
dateT = datetime.datetime.now()
dateStrCompact = dateT.strftime("%Y%m%d-%H%M%S")
# end if
for ii in range(0, self.outputCount):
self.updateOutputFile(self.outputFilename[ii], self.outputComment[ii], dateStrCompact)
# end for
return
# end updateOutput
def deleteOutput(self):
""" delete the simulator output files """
for ii in range(0, self.outputCount):
pathT = os.path.join(self.outputDir, self.outputFilename[ii])
try:
if os.path.isfile(pathT):
os.unlink(pathT)
# end if
except:
pass
# end try
# end for
return
# end deleteOutput
def prepare(self):
""" prepare the optimization """
if self.isRunning:
return False
# end if
if not self.isChecked():
dispError("Simulator path and parameters not up to date",
doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
if not self.updatePath():
dispError("Simulator path cannot be updated", doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
self.optimCounter = 1
self.funcCounter = 1
self.jacCounter = 0
self.elapsedTime = 0
self.isRunning = True
self.stopSet()
# remove the simulator verbose output file
pathT = os.path.join(self.outputDir, self.verboseFilename)
try:
if os.path.isfile(pathT):
os.unlink(pathT)
# end if
except:
pass
# end try
# remove the simulator output files
for ii in range(0, self.outputCount):
pathT = os.path.join(self.outputDir, self.outputFilename[ii])
try:
if os.path.isfile(pathT):
os.unlink(pathT)
# end if
except:
pass
# end try
# end for
self.optimCounter = 1
self.funcCounter = 1
self.elapsedTime = 0
dateT = datetime.datetime.now()
dateStr = dateT.strftime("%Y-%m-%d %H:%M:%S")
strT = "# ---------------------------------------------------------------\n"
strT += "# " + self.title + "\n# Optimization (" + self.optimType
if self.optimType == "Optim":
strT += " " + self.minimizeMethod
# end if
strT += ") started @ "
strT += dateStr
if self.optimType == "Optim":
strT += ("\n# With tolerance = %g" % self.tolerance)
if (self.minimizeMethod != "Bayes"):
strT += (" and jaceps = [ %.5f" % self.jaceps[0])
if self.paramCount > 1:
strT += (" %.5f" % self.jaceps[1])
# end if
if self.paramCount > 2:
strT += (" %.5f" % self.jaceps[2])
# end if
if self.paramCount > 3:
strT += (" %.5f" % self.jaceps[3])
# end if
if self.paramCount > 4:
strT += (" %.5f" % self.jaceps[4])
# end if
if self.paramCount > 5:
strT += " ..."
# end if
strT += " ]"
# end if
if self.paramWeight and self.isBound:
strT += " Weighted"
# end if
# end if
strT += "\n# Parameter:\t"
for ii in range(0, self.paramCount - 1):
strT += self.paramName[ii] + "\t"
# end for
strT += self.paramName[self.paramCount - 1] + "\n"
strT += "# StartValue:\t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormat[ii] % self.paramStart[ii]) + "\t"
# end for
strT += (self.paramFormat[self.paramCount - 1] % self.paramStart[self.paramCount - 1]) + "\n"
strT += "# EndValue: \t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormat[ii] % self.paramEnd[ii]) + "\t"
# end for
strT += (self.paramFormat[self.paramCount - 1] % self.paramEnd[self.paramCount - 1]) + "\n"
strT += "# InitValue: \t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormat[ii] % self.paramInit[ii]) + "\t"
# end for
strT += (self.paramFormat[self.paramCount - 1] % self.paramInit[self.paramCount - 1]) + "\n"
strT += "# NormValue: \t"
for ii in range(0, self.paramCount - 1):
strT += (self.paramFormat[ii] % self.paramNorm[ii]) + "\t"
# end for
strT += (self.paramFormat[self.paramCount - 1] % self.paramNorm[self.paramCount - 1])
if self.optimType == "Brute":
strT += "\n# Points: \t"
for ii in range(0, self.paramCount - 1):
strT += ("%9s" % self.paramPoints[ii]) + "\t"
# end for
strT += ("%9s" % self.paramPoints[self.paramCount - 1])
# end if
strT += "\n# ---------------------------------------------------------------\n\n"
self.log(strT)
fileOptim = open(self.outputDir + self.outputOptimizedFilename, "w")
fileOptim.write(strT)
fileOptim.close()
return True
# end prepare
def start(self, optimType):
""" start the optimization """
if optimType == "Snap":
self.startSnapshot()
elif optimType == "Brute":
self.startBrute()
elif optimType == "Optim":
self.startOptim()
else:
dispError("Specify the optimType to \"Brute\", \"Snap\" or \"Optim\"", doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
#end if
# end start
def startOptim(self):
""" start the optimization """
self.optimType = "Optim"
self.setCounterFormat(maxcount = 2 * self.optimPoints)
tStart = 0.0
tEnd = 0.0
tRangeMin = 0.0
tEps = 5.0 * self.tolerance
self.jaceps = np.zeros(self.paramCount)
self.paramBounds = list() if self.isBound else None
if self.paramWeight and self.isBound:
# Tukey Window (Parameters Weight: decreases near the bounds)
# If optimum is near the bounds, disable this feature or enlarge domain
nn = self.weightPoints
aa = self.weightAlpha
wn = np.arange(0, nn)
wm = nn % 2
ww = int(np.floor(aa * (float(nn - 1)) / 2.0))
wn1 = wn[0 : ww + 1]
wn2 = wn[ww + 1 : nn - ww - 1]
wn3 = wn[nn - ww - 1 :]
ww1 = 0.5 * (1.0 + np.cos(np.pi * (-1.0 + (2.0 * wn1 / aa / (nn - 1)))))
ww2 = np.ones(wn2.shape)
ww3 = 0.5 * (1.0 + np.cos(np.pi * ((-2.0 / aa) + 1.0 + (2.0 * wn3 / aa / (nn - 1)))))
self.weightFunc = np.concatenate((ww1, ww2, ww3))
# Save the weight window
try:
icw = len(self.weightFunc)
if (icw >= 7):
fileT = open(self.outputDir + self.weightFilename, "w")
strT = "# " + self.title + "\n"
strT += "# Tukey Window (Parameters Weight)\n"
for ii in range(0, icw):
strT += ("\n%d\t%g" % (ii, self.weightFunc[ii]))
# end for
fileT.write(strT)
fileT.close()
# end if
except:
pass
# end try
else:
self.weightFunc = None
# end if
strT = "Index\tTime\t"
for ii in range(0, self.paramCount):
if self.paramLogscale[ii]:
tStart = math.log10(self.paramStart[ii]) / math.log10(self.paramNorm[ii])
tEnd = math.log10(self.paramEnd[ii]) / math.log10(self.paramNorm[ii])
else:
tStart = self.paramStart[ii] / self.paramNorm[ii]
tEnd = self.paramEnd[ii] / self.paramNorm[ii]
# end if
if (ii == 0) or ((tEnd - tStart) < tRangeMin):
tRangeMin = tEnd - tStart
# end if
self.jaceps[ii] = (tEnd - tStart) / float(self.optimPoints - 1)
# tolerance should be kept less than eps
if self.jaceps[ii] <= self.tolerance:
self.tolerance = 0.2 * self.jaceps[ii]
# end if
if self.isBound:
self.paramBounds.append((tStart, tEnd))
# end if
strT += self.paramName[ii] + "\t"
# end for
if tRangeMin < 1e-9:
return False
# end if
tEps = tRangeMin / float(self.optimPoints - 1)
# tolerance should be kept less than eps
if tEps <= self.tolerance:
self.tolerance = 0.2 * tEps
# end if
if not self.prepare():
return False
# end if
strT += "Jm(mA/cm2)\tVm(V)\tFF(%)\tJsc(mA/cm2)\tVoc(V)\tEfficiency\n"
fileOptim = open(self.outputDir + self.outputOptimizedFilename, "a")
fileOptim.write(strT)
fileOptim.close()
self.optimCounter = 1
self.funcCounter = 1
self.jacCounter = 0
self.guessParam = False
self.bruteSimul = False
outX = None
outSuccess = None
outMessage = None
outNit = None
outFun = None
outNFev = None
outNJev = None
outNHev = None
outNLFev = None
outXl = None
outFunl = None
# Choose the initial values
paramNormalized0 = self.guess()
if self.isBound:
try:
self.isBound = True
if (self.minimizeMethod == "Bayes"):
BayesianBbounds = {}
for ii in range(0, self.paramCount):
BayesianBbounds[self.paramName[ii]] = self.paramBounds[ii]
# end for
BayesianOptimizer = BayesianOptimization(
f=self.optimizeFuncBayesian,
pbounds=BayesianBbounds,
verbose=0
)
BayesianOptimizer.maximize(
init_points=self.paramCount if (self.paramCount <= 5) else 5,
n_iter=self.maxIter,
)
outFun = BayesianOptimizer.max['target']
params = BayesianOptimizer.max['params']
outX = np.array([])
for ii in range(0, len(params)):
outX = np.append(outX, params[self.paramName[ii]])
# end for
outSuccess = True
outMessage = 'Done.'
outNit = self.maxIter
else:
outResult = optimize.minimize(self.optimizeFunc, paramNormalized0, method=self.minimizeMethod, jac=self.getOptimizeJac(self.optimizeFunc), bounds=self.paramBounds, tol=self.tolerance, options={ 'eps': tEps, 'maxiter': self.maxIter, 'disp': False, 'ftol': self.tolerance })
# end if
try:
if (self.minimizeMethod != "Bayes"):
outX = outResult.x
outSuccess = outResult.success
outMessage = outResult.message
outNit = outResult.nit
outFun = outResult.fun
outNFev = outResult.nfev
outNJev = outResult.njev
outNHev = outResult.nhev
outNLFev = outResult.nlfev
outXl = outResult.xl
outFunl = outResult.funl
# end if
except:
pass
# end try
except Exception as excT:
# catch only Exception (since sys.exit raise BaseException)
if self.stoppedDone is False:
dispError(traceback.format_exc(), doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
# end try
else:
# not bound methods
try:
outResult = optimize.minimize(self.optimizeFunc, paramNormalized0, method=self.minimizeMethod, jac=False, tol=self.tolerance, options={ 'maxiter': self.maxIter, 'disp': False })
outX = outResult.x
outSuccess = outResult.success
outMessage = outResult.message
outNit = outResult.nit
except Exception as excT:
# catch only Exception (since sys.exit raise BaseException)
dispError(traceback.format_exc(), doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end try
# end if
self.finish(errorOccured=False, userStopped=False, x=outX, success=outSuccess, message=outMessage, nit=outNit, nlfev=outNLFev, xl=outXl, funl=outFunl)
return True
# end startOptim
def startSnapshot(self):
""" start the optimization (Snap: one calculation for the initial parameters set) """
self.optimType = "Snap"
self.setCounterFormat(maxcount = 2)
if not self.prepare():
return False
# end if
strT = "Index\tTime\t"
for ii in range(0, self.paramCount):
strT += self.paramName[ii] + "\t"
# end for
strT += "Jm(mA/cm2)\tVm(V)\tFF(%)\tJsc(mA/cm2)\tVoc(V)\tEfficiency\n"
fileOptim = open(self.outputDir + self.outputOptimizedFilename, "a")
fileOptim.write(strT)
fileOptim.close()
self.optimCounter = 1
self.funcCounter = 1
self.jacCounter = 0
self.guessParam = False
self.bruteSimul = True
paramNormalized = np.zeros(self.paramCount)
for ii in range(0, self.paramCount):
self.paramNatural[ii] = self.paramInit[ii]
if self.paramLogscale[ii]:
paramNormalized[ii] = math.log10(self.paramNatural[ii]) / math.log10(self.paramNorm[ii])
else:
paramNormalized[ii] = self.paramNatural[ii] / self.paramNorm[ii]
# end if
# end for
# Run the optimization
self.optimizeFunc(paramNormalized)
self.finish(errorOccured=False, userStopped=False)
# end startSnapshot
# create the grid for the brute force iterations
def doGrid(self, arrP, tR, tGrid):
ns = arrP.shape[0]
ms = tGrid.shape[0] // (tR * ns)
for n in range(ns):
tVal = arrP[n]
for k in range(tR):
for m in range(ms):
idx = (k * ns * ms) + (n * ms) + m
tGrid[idx] = tVal
# end for
# end for
# end for
# end doGrid
def getGrid(self, arrParams):
arrParams = [np.array(tt) for tt in arrParams]
arrShapes = [tt.shape[0] for tt in arrParams]
tType = arrParams[0].dtype
na = len(arrParams)
ns = np.prod(arrShapes)
tGrid = np.zeros((ns, na), dtype=tType)
tRep = np.cumprod([1] + arrShapes[:-1])
for ii in range(na):
self.doGrid(arrParams[ii], tRep[ii], tGrid[:, ii])
# end for
iCount = 10 * ns * na
if iCount < 10:
iCount = 10
# end if
self.setCounterFormat(iCount)
return tGrid
# end getGrid
def startBrute(self):
""" start the optimization (brute force) """
self.optimType = "Brute"
if not self.prepare():
return False
# end if
strT = "Index\tTime\t"
for ii in range(0, self.paramCount):
strT += self.paramName[ii] + "\t"
# end for
strT += "Jm(mA/cm2)\tVm(V)\tFF(%)\tJsc(mA/cm2)\tVoc(V)\tEfficiency\n"
fileOptim = open(self.outputDir + self.outputOptimizedFilename, "a")
fileOptim.write(strT)
fileOptim.close()
self.optimCounter = 1
self.funcCounter = 1
self.jacCounter = 0
self.guessParam = False
self.bruteSimul = True
paramNormalized = np.zeros(self.paramCount)
dateT = datetime.datetime.now()
dateStr = dateT.strftime("%Y-%m-%d %H:%M:%S")
# For ... for all parameters, if not fixed
# Limited to 5 parameters
if self.paramCount > 5:
strT = "\n---------------------------------------------------------------\n"
strT += " The number of parameters is limited to 5 in the Brute optimization"
strT += dateStr
strT += "\n---------------------------------------------------------------\n"
self.log(strT)
try:
pathStopped = os.path.join(self.outputDir, self.stoppedFilename)
fileT = open(pathStopped, "w")
fileT.write("SLALOM\nStopped\n")
fileT.close()
except:
pass
# end try
sys.exit(0)
# end if
self.paramBounds = list()
tStart = 0.0
tEnd = 0.0
tInit = 0.0
tStep = 0.0
for ii in range(0, self.paramCount):
if self.paramLogscale[ii]:
tStart = math.log10(self.paramStart[ii]) / math.log10(self.paramNorm[ii])
tEnd = math.log10(self.paramEnd[ii]) / math.log10(self.paramNorm[ii])
tInit = math.log10(self.paramInit[ii]) / math.log10(self.paramNorm[ii])
else:
tStart = self.paramStart[ii] / self.paramNorm[ii]
tEnd = self.paramEnd[ii] / self.paramNorm[ii]
tInit = self.paramInit[ii] / self.paramNorm[ii]
# end if
if self.paramPoints[ii] > 1:
tStep = (tEnd - tStart) / float(self.paramPoints[ii] - 1)
arr = np.array([])
for jj in range(0, self.paramPoints[ii]):
arr = np.append(arr, tStart + float(jj) * tStep)
# end for
self.paramBounds.append(arr)
else:
self.paramBounds.append(np.array([tInit]))
# end if
# end for
paramNormalizedGrid = self.getGrid(self.paramBounds)
for paramNormalized in paramNormalizedGrid:
self.optimizeFunc(paramNormalized)
# end for
self.finish(errorOccured=False, userStopped=False)
self.bruteSimul = False
return True
# end startBrute
def getMinimizeMethod(self):
return self.minimizeMethod
# end getMinimizeMethod
def setMinimizeMethod(self, minimizeMethod, maxIter = 10, tolerance = 1e-3, optimPoints = 51):
""" set the optimization method ('L-BFGS-B' or 'SLSQP') """
if (maxIter >= 1) and (maxIter <= 1024):
self.maxIter = maxIter
# end if
if (tolerance >= 1e-6) and (tolerance <= 1.0):
self.tolerance = tolerance
# end if
# optimPoints is used to approximate the jacobian. If increased, the optimisation time will dramatically increase. The default value is 51 and the maximum value is 201.
if (optimPoints < 11):
self.optimPoints = 11
elif (optimPoints > 201):
self.optimPoints = 201
else:
self.optimPoints = optimPoints
# end if
self.weightPoints = self.optimPoints * 2
if minimizeMethod in self.minimizeMethodList:
self.minimizeMethod = minimizeMethod
if (self.minimizeMethod == "L-BFGS-B") or (self.minimizeMethod == "SLSQP") or (self.minimizeMethod == "Bayes"):
self.isBound = True
else:
self.isBound = False
# end if
else:
strT = minimizeMethod + " unknown. Supported algorithms: "
for ii in range(0, len(self.minimizeMethodList)):
strT += self.minimizeMethodList[ii] + " "
# end for
dispError(strT, doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
# end setMinimizeMethod
def getRunning(self):
return self.isRunning
# end getRunning
def getParamOptim(self):
return self.paramOptim
# end getParamOptim
def getOutputOptimized(self):
return self.outputOptimized
# end getOutputOptimized
def setTitle(self, title):
""" set the optimization title """
if self.isRunning:
return
# end if
self.title = title
return
# end setTitle
def setPath(self, Device):
""" set directory and optimization files information """
if self.isRunning:
return False
#end if
self.currentDir = Device.currentDir
dirT = os.path.dirname(self.currentDir)
if not os.path.exists(dirT):
dispError("Directory not found: " + self.currentDir, doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
# Output directory
self.outputDir = Device.outputDir
self.outputRoot = self.outputDir
self.outputDirSuffix = "_" + (Device.deviceType if Device.deviceType else "User")
dateT = datetime.datetime.now()
outputDirName = dateT.strftime("%Y%m%d_%H%M")
if self.outputDirSuffix is not None:
outputDirName += self.outputDirSuffix
# end if
self.outputDirShort = outputDirName
self.outputDir += outputDirName + self.dirSepChar
dirT = os.path.dirname(self.outputDir)
if not os.path.exists(dirT):
os.makedirs(dirT)
# end if
if not os.path.exists(dirT):
dispError("Directory not found and cannot be created: " + self.outputDir, doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
self.inputFilename = Device.inputFilename
if not os.path.exists(self.currentDir + self.inputFilename):
dispError("File not found: " + (self.currentDir + self.inputFilename), doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
fileT = open(Device.currentDir + "ofname.txt", "w")
fileT.write(self.outputDir + self.outputOptimizedFilename)
fileT.close()
return self.checkInput()
# end setPath
def getOutputDir(self):
return self.outputDir
# end getOutputDir
# outputOptimizedFilename is used to plot in 'realtime' the variation of the efficiency during the optimization
def getOptimizedFilename(self):
return os.path.join(self.outputDir, self.outputOptimizedFilename)
# end getOptimizedFilename
def setParam(self, Device, pythonInterpreter):
""" set the main optimization parameters """
if self.isRunning:
return False
# end if
self.setInterpreter(pythonInterpreter)
self.setTitle(Device.mainTitle)
if self.setPath(Device) == False:
return False
# end if
lenT = len(Device.paramName)
if (lenT < 1) or (lenT > 20) or (lenT != len(Device.paramFormat)) or (lenT != len(Device.paramStart)) or (
lenT != len(Device.paramEnd)) or (lenT != len(Device.paramInit)) or (lenT != len(Device.paramLogscale)):
dispError("Invalid parameters (setParam)", doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
self.paramCount = lenT
self.paramOptim = np.zeros(self.paramCount)
self.paramNatural = np.zeros(self.paramCount)
self.paramName = []
self.paramUnit = []
self.paramFormat = []
self.paramFormatShort = []
self.paramFormatNormalized = []
self.paramNorm = np.array([])
self.paramStart = np.array([])
self.paramEnd = np.array([])
self.paramPoints = []
self.paramInit = np.array([])
self.paramLogscale = []
for ii in range(0, self.paramCount):
self.paramName.append(Device.paramName[ii])
self.paramUnit.append(Device.paramUnit[ii])
self.paramFormat.append(Device.paramFormat[ii])
self.paramFormatShort.append(Device.paramFormatShort[ii])
self.paramFormatNormalized.append(Device.paramFormatNormalized[ii])
self.paramNorm = np.append(self.paramNorm, Device.paramNorm[ii])
self.paramStart = np.append(self.paramStart, Device.paramStart[ii])
self.paramEnd = np.append(self.paramEnd, Device.paramEnd[ii])
if (Device.paramPoints[ii] >= 1) and (Device.paramPoints[ii] <= 100):
self.paramPoints.append(Device.paramPoints[ii])
# end if
self.paramInit = np.append(self.paramInit, Device.paramInit[ii])
self.paramLogscale.append(Device.paramLogscale[ii])
# end for
self.paramWeight = Device.paramWeight
self.paramFormatOutput = Device.paramFormatOutput
if Device.modelFilename:
self.modelCount = len(Device.modelFilename)
if self.modelCount < 1:
dispError("modelFilename array size not valid", doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
self.modelFilename = []
nn = 0
for ii in range(0, self.modelCount):
self.modelFilename.append(Device.modelFilename[ii])
if self.modelFilename[ii] == "":
continue
# end if
if not os.path.exists(self.currentDir + self.modelFilename[ii]):
dispError("File not found: " + (self.currentDir + self.modelFilename[ii]), doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
# end for
else:
self.modelFilename = None
self.modelCount = 0
# end if
self.isParamUpdated = True
return self.isParamUpdated
# end setParam
def setParamInit(self, paramInit):
""" set main initial parameters """
if self.isRunning:
return False
# end if
lenT = len(paramInit)
if (lenT < 1) or (lenT > 20) or (lenT != len(self.paramFormat)) or (lenT != len(self.paramStart)) or (
lenT != len(self.paramEnd)) or (lenT != len(self.paramName)) or (lenT != len(self.paramLogscale)):
dispError("Invalid parameters (setParamInit)", doExit = True, atExit = self.finish, errFilename = self.currentDir + 'errlog.txt')
# end if
self.paramInit = np.array([])
for ii in range(0, self.paramCount):
self.paramInit = np.append(self.paramInit, paramInit[ii])
# end for
return True
# end setParam
@staticmethod
def maxDim():
return 1024
# end maxDim
# end slalomCore
# class to disable standard output buffering
class UnbufferedStdout(object):
def __init__(self, stream):
self.stream = stream
# end __init__
def __getattr__(self, attr):
return getattr(self.stream, attr)
# end __getattr__
def write(self, data):
self.stream.write(data)
self.stream.flush()
# end write
# end UnbufferedStdout