#This is part of the E-Tran family of software tools. Copryight Electranix Corporation.

"""Creates Precision RAW files (RAWPR) which contain more accurate, updated, and additional values
    -> Works with PSSE v33, v34, and v35 RAW file formats
    -> Outputs logging into a //ConvertRawLogs folder and the RAWPR file created in the same directory as the target file
    
    RUN DIRECTLY
        -> Via command line, or as part of a ETRAN translation
        -> takes a RAW file, solves it, and updates the values to create the new file 
        -> Input -> rawPath: path to the raw file to convert, pssePath: path to the PSSE python lib to use ie ...PTI\\PSSE34\\PSSPY27
    RUN FROM PSSE GUI
        -> Run as a script within PSSE
        -> Takes the open case and creates a RAWPR file from the data as is (does not try to solve it)
        -> If the open case is from RAW, PSSE does not provide a way to the file, a prompt for the file path will be given
"""

import sys
import os
import os.path as path
from datetime import datetime
import importlib
import inspect
import traceback

#Getting input arguments if they exist
rawPathIn = "EMPTY"
pssePathIn = "EMPTY"
if len(sys.argv) == 3:
    rawPathIn = sys.argv[1]
    pssePathIn = sys.argv[2]
        
#if input arguments weren't given we assume we are being run from the GUI with an already solved case
runFromGUI = rawPathIn == "EMPTY" and pssePathIn == "EMPTY"

#checking input arguements lead to real locations if given
if not runFromGUI:
    if not (path.exists(rawPathIn) and ".raw" in rawPathIn):
        print("\nThe provided path to RAW is not valid\n")
        raise Exception("RAW path is invalid: " + rawPathIn)
    if not (path.exists(pssePathIn) and "PSSE" in pssePathIn):
        print("\nThe provided path to PSSE is not valid\n")
        raise Exception("PSSE path is invalid: " + pssePathIn)

try:
    #import PSSPY API for PSSE
    #if not running from GUI set the PSSE path in sys and OS then import the PSSE-XX module
    if not runFromGUI:
        if "PSSE33" in pssePathIn:
            PSSE_Path = path.dirname(pssePathIn) + r"\PSSBIN" #PSSE v33 keeps its python data in PSSBIN instead of PSSPY-XX, udpate the path to that
        else:
            PSSE_Path = pssePathIn
        indexOfPsseVer = PSSE_Path.index("PSSE")
        PSSE_Import_Name = PSSE_Path[indexOfPsseVer:indexOfPsseVer+6].lower()
        sys.path.append(PSSE_Path)
        os.environ['PATH'] += ";" + PSSE_Path
        importlib.import_module(PSSE_Import_Name)
    import psspy
except Exception as ex:
    print("\nUnable to connect to PSSE through Python\n")
    if len(ex.args) >= 1:
        ex.args = ("Failed to setup PSSE in Python: " + ex.args[0],) + ex.args[1:]
    else:
        ex.args = ("Failed to setup PSSE in Python",)
    raise

#the PSSE version of the RAW file
#Read in from the RAW file when loaded in
ver = -1

#paths to file I/O locations
savprpath = ""
filepath = ""
logpath = ""
progpath = ""

#Sets the file paths   
def setFilePaths(rawPath, savFilePath = None):
    global filepath
    global logpath
    global progpath
    global savprpath
    
    if not savFilePath == None:
        savprpath = savFilePath[:len(savFilePath)-4] + ".rawpr"
        
    dir = path.dirname(rawPath)
    filepath = rawPath
    logpath = dir + r"\convertRawLogs"
    progpath = logpath + r"\PSSE_Prog_Output.txt"
    if not path.exists(logpath):
        os.makedirs(logpath)

#use provided RAW file or setup temp RAW from sav file for translation
#if we cannot find a sav file open in PSSE we ask for a RAW file
if not runFromGUI:
    setFilePaths(rawPathIn)
else:
    savFile, snpFile = psspy.sfiles()
    
    if not savFile == "" and path.exists(savFile):#if SAV being used, create a temp RAW file to work from
        try:
            folder = path.dirname(savFile)
            tempFile = folder + "\\temp123456789ABCDEF"
            while path.exists(tempFile):
                tempFile = tempFile + "temp123456789ABCDEF"
            tempFile = tempFile + ".raw"
            err = psspy.rawd_2(out = 0, ofile = tempFile)
            if err != 0:
                print("\nUnable to create temp file from loaded SAV\n")
                raise Exception("Error saving SAV to RAW with psspy.rawd_2: " + str(err))
            elif not path.exists(tempFile):
                print("\Failed to create temp file from loaded SAV\n")
                raise Exception("Psspy.rawd_2 ran successfully but temp file does not exist")
            setFilePaths(tempFile, savFile)
        except Exception:
            if path.exists(tempFile):
                os.remove(tempFile)
            raise
    elif savFile == "": #if no SAV file is being used, we see if a RAW file is being used instead
        psspy.prompt("Could not find an open SAV file, if a RAW file is being used please enter a path to it")
        err, userPath = psspy.userin()
        if err != 0:
            print("\nthere was a problem receiving your input\n")
            raise Exception("Input error from psspy.userin: " + str(err))   
        print("\nchecking that " + userPath + " is valid\n")
        if path.exists(userPath) and ".raw" in userPath:
            print("\npath is good: starting\n")
            setFilePaths(userPath)
        else:
            print("\nsorry this path could not be found, you can try again by re-running the script\n")
            raise Exception("RAW path is invalid: " + rawPathIn)

#the only code that can error out and not get logged is the code above, this is because the paths the log needs to know where to output are being still being set
#if the logs have not been updated since last run or don't exist, consider running from console commands to view the error output instead

#################################################################################################
#class responsible for handling logging
#has some basic formatting help for logged output
#we need the log class seperated at the top so we can define logs right away and catch issues from as much of the code as possible
class Log:
    #opens a file for logging with the given name and inserts a generic header of log name and start time
    def __init__(self, logName):
        self.logpath = logpath + "\\" + logName + ".txt"
        self.file = open(self.logpath, "w")
        self.logName = logName
        self.file.write("Start of log: " + self.logName)
        now = datetime.now()
        self.file.write("\nStart Time: " + now.strftime("%Y/%m/%d %H:%M:%S") + "\n")
    
    #closes the log file and adds a generic ender of log name and end time
    #if passed stat = -1, will also add that program ended with an error
    def end(self, stat = 0):
        now = datetime.now()
        self.file.write("\n\n\nEnd Time: " + now.strftime("%Y/%m/%d %H:%M:%S") + "\n")
        self.file.write("End of log: " + self.logName)
        if stat == -1:
            self.file.write("\nProgram ended with an error")
        self.file.close()
    
    #add a header style line to the log
    def addSection(self, sectionName):
        self.file.write("\n\n***" + sectionName + "***")
    
    #add a line to the log, can set the depth variable to indent the line to format the info 
    def addLine(self, line, depth = 0):
        temp = "\n"
        for i in range(0, depth):
            temp = temp + "    "
        self.file.write(temp + "-> " + line)
#################################################################################################


#a normal and bug log
log = Log("RawPR Log")
bugLog = Log("RawPR Debug")    


#################################################################################################

#Handles interactions with Psse
#Handles the updated data retreived from PSSE
class Psse:    
    #sends printed output to PSSE console
    @staticmethod
    def redirectOutputToPsse():
        psspy.lines_per_page_one_device(2, 10000000)
        psspy.progress_output(1, "", [0, 0])
        psspy.prompt_output(1, "", [0, 0])
    
    #sends PSSE console output to a file
    #BUGGED: FILE NEVER CLOSES -> further attempt to write, attempt to delete, etc all fail because file in use
    @staticmethod
    def redirectPsseToFile():
        psspy.progress_output(2, progpath, [0, 0])
    
    #loads RAW file into PSSE
    @staticmethod
    def loadRaw():
        log.addLine("Attempting to load RAW", 1)
        err = psspy.read(0, filepath)
        if err != 0:
            print ("\nError loading file into PSSE\n")
            log.addSection("Error loading file into PSSE")
            bugLog.addLine("PSSE RAW LOAD ERROR", 1)
            raise Exception("Could not load " + filepath + " in PSSE: " + str(err))
        log.addLine("RAW loaded", 2)

    
    #runs through a series of different solve settings in an attempt to solve the case
    @staticmethod
    def attemptSolve():
        log.addLine("Attempting to solve case", 1)
        settings1 = [0, 0, 0, 1, 1, 0, 99, 0]
        settings2 = [0, 0, 0, 1, 1, 0, 0, 0]
        settings3 = [0, 0, 0, 1, 2, 0, 0, 0]
        settings4 = [0, 0, 0, 1, 1, 0, 99, 0]
        settings5 = [0, 0, 0, 1, 1, 0, 0, 0]
        settings6 = [0, 0, 0, 1, 2, 0, 0, 0]
        settings = [settings1,settings2,settings3,settings4,settings5,settings6]
        itr_best = 100
        best_attempt = 100  
        for attempt in range(0,len(settings)):
            this_soln = settings[attempt]
            if attempt < 3:
                psspy.fdns(this_soln)
            else:
                psspy.fnsl(this_soln)
            result = psspy.solved()
            this_itr = psspy.iterat()
            if result == 0:
                itr_best = this_itr
                best_attempt = attempt
                break
        if itr_best == 100:
            print("\nError solving the RAW file\n")
            log.addSection("Error, unable to solve case")
            bugLog.addLine("FILE SOLVE ERROR", 1)
            raise Exception("Could not solve " + filepath + " in PSSE")
        log.addLine("Case Solved!", 2)
        
    #constructs a 2 level dictionary of case data from PSSE organized by type and then ids for that sections entries (bus numbers, names, ckts, ...) {  "BUS": { "1": [...], "2": [...] },  "LOAD": {...},  ...  }
    #each section has a "format" entry with indices of data that need to be replaced (after splitting on commas)
    #   is 2D for multi-line entries with 1 sub-array for each line
    #sections may have a "nameSplit" entry as follows [index of name entry, size of name entry]
    #   this is for entries that contain "name" data which may have commas that can disrupt normal splitting
    #sections may have a "mod" entry that gives indices to insert extra data spaces
    #   used for some entries to add space for additional info being retrieved
    #   "format" entries should be given based on the entry after modification
    #General format for each section:
    #   create section with section info -> fetch identifying data (busses, ckt, name, etc.) -> fetch updated data -> map each id data to updated data
    _dataDict = {}
    @staticmethod
    def buildDataDict():
        log.addLine("Retrieving updated data from PSSE", 1)
        
        #bus data
        sectionKey = "BUS"
        Psse._dataDict[sectionKey] = {"format":[2, 7, 8, 9, 10, 11, 12], "nameSplit":[1, 12]}
        fetchBase = {"sid":-1, "flag":2}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.abusint, ["NUMBER"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.abusreal, ["BASE", "PU", "ANGLED", "NVLMHI", "NVLMLO", "EVLMHI", "EVLMLO"]))
        Psse.mapKeysToData(sectionKey, id, data)
        
        #load data
        sectionKey = "LOAD"
        Psse._dataDict[sectionKey] = {"format":[5, 7, 9]}
        fetchBase = {"sid":-1, "flag":4}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.aloadint, ["NUMBER"]))
        id.extend(Psse.fetchSection(fetchBase, psspy.aloadchar, ["ID"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.aloadcplx, ["MVANOM", "ILNOM", "YLNOM"]))
        Psse.mapKeysToData(sectionKey, id, data)
        
        #fixed shunt data
        sectionKey = "FIXED SHUNT"
        Psse._dataDict[sectionKey] = {"format":[3]}
        fetchBase = {"sid":-1, "flag":4}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.afxshuntint, ["NUMBER"]))
        id.extend(Psse.fetchSection(fetchBase, psspy.afxshuntchar, ["ID"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.afxshuntcplx, ["SHUNTNOM"]))
        Psse.mapKeysToData(sectionKey, id, data)
        
        #generator data
        sectionKey = "GENERATOR"
        if ver == 35:
            Psse._dataDict[sectionKey] = {"format":[4, 5, 9, 14, 17, 18, 2, 10, 12]}
        else:
            Psse._dataDict[sectionKey] = {"format":[4, 5, 8, 13, 16, 17, 2, 9, 11]}
        fetchBase = {"sid":-1, "flag":4}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.amachint, ["NUMBER"]))
        id.extend(Psse.fetchSection(fetchBase, psspy.amachchar, ["ID"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.amachreal, ["QMAX", "QMIN", "MBASE", "GENTAP", "PMAX", "PMIN"]))
        data.extend(Psse.fetchSection(fetchBase, psspy.amachcplx, ["PQGEN", "ZSORCE", "XTRAN"]))
        Psse.mapKeysToData(sectionKey, id, data)
        
        #branch data
        sectionKey = "BRANCH"
        if ver == 33:
            Psse._dataDict[sectionKey] = {"format":[5, 6, 7, 8, 3, 9, 11]}
        else:
            Psse._dataDict[sectionKey] = {"format":[5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 3, 19, 21], "nameSplit":[6, 40]}
        fetchBase = {"sid":-1, "flag":2, "entry":1}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.abrnint, ["FROMNUMBER", "TONUMBER"]))
        id.extend(Psse.fetchSection(fetchBase, psspy.abrnchar, ["ID"]))
        data = []
        if ver == 33:
            data.extend(Psse.fetchSection(fetchBase, psspy.abrnreal, ["CHARGING", "RATEA", "RATEB", "RATEC"]))
        else:
            data.extend(Psse.fetchSection(fetchBase, psspy.abrnreal, ["CHARGING", "RATE1", "RATE2", "RATE3", "RATE4", "RATE5", "RATE6", "RATE7", "RATE8", "RATE9", "RATE10", "RATE11", "RATE12"]))
        data.extend(Psse.fetchSection(fetchBase, psspy.abrncplx, ["RX", "FROMSHNT", "TOSHNT"]))
        Psse.mapKeysToData(sectionKey, id, data)
        
        #system switch data
        if ver == 34 or ver == 35:
            sectionKey = "SYSTEM SWITCHING DEVICE"
            Psse._dataDict[sectionKey] = {"format":[4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 3], "nameSplit":[20, 40]}
            fetchBase = {"sid":-1, "flag":2, "entry":1}
            id = []
            id.extend(Psse.fetchSection(fetchBase, psspy.abrnint, ["FROMNUMBER", "TONUMBER"]))
            id.extend(Psse.fetchSection(fetchBase, psspy.abrnchar, ["ID"]))
            data = []
            data.extend(Psse.fetchSection(fetchBase, psspy.abrnreal, ["RATE1", "RATE2", "RATE3", "RATE4", "RATE5", "RATE6", "RATE7", "RATE8", "RATE9", "RATE10", "RATE11", "RATE12"]))
            data.extend(Psse.fetchSection(fetchBase, psspy.abrncplx, ["RX"]))
            data[12] = [dat.imag for dat in data[12]] #unique case where an entry only needs half a complex number
            Psse.mapKeysToData(sectionKey, id, data)
        
        #Transformer data
        #needs to be fetched in multiple passes for 2 winding, 3 winding transformer, and 3 winding windings individually
        #2 winding transformer keys use 0 for bus K instead of having nothing (to be consistent with the RAW format)
        sectionKey = "TRANSFORMER"
        if ver == 33:
            Psse._dataDict[sectionKey] = {"format2w":[[7], [0, 2], [0, 1, 2, 8, 9, 10, 11, 14], [0, 1]],
                                        "format3w":[[7], [0, 3, 6, 9, 10, 2, 5, 8], [0, 1, 2, 8, 9, 10, 11, 14], [0, 1, 2, 8, 9, 10, 11, 14], [0, 1, 2, 8, 9, 10, 11, 14]]}
            Psse._dataDict[sectionKey]["nameSplit"] = [[10, 12], None, None, None, None]
        elif ver == 34:
            Psse._dataDict[sectionKey] = {"format2w":[[7], [0, 2], [0, 1, 2, 17, 18, 19, 20, 23], [0, 1]],
                                        "format3w":[[7], [0, 3, 6, 9, 10, 2, 5, 8], [0, 1, 2, 17, 18, 19, 20, 23], [0, 1, 2, 17, 18, 19, 20, 23], [0, 1, 2, 17, 18, 19, 20, 23]]}    
            Psse._dataDict[sectionKey]["nameSplit"] = [[10, 40], None, None, None, None]
        elif ver == 35:
            Psse._dataDict[sectionKey] = {"format2w":[[7], [0, 2], [0, 1, 2, 18, 19, 20, 21, 24], [0, 1]],
                            "format3w":[[7], [0, 3, 6, 9, 10, 2, 5, 8], [0, 1, 2, 18, 19, 20, 21, 24], [0, 1, 2, 18, 19, 20, 21, 24], [0, 1, 2, 18, 19, 20, 21, 24]]}
            Psse._dataDict[sectionKey]["nameSplit"] = [[10, 40], None, None, None, None]
        #2 winding
        fetchBase = {"sid":-1, "flag":2, "entry":2}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.atrnint, ["FROMNUMBER", "TONUMBER"]))
        id.append([0]*len(id[0]))
        id.extend(Psse.fetchSection(fetchBase, psspy.atrnchar, ["ID"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.atrncplx, ["YMAGCM", "RXNOMCZ"]))
        data.extend(Psse.fetchSection(fetchBase, psspy.atrnreal, ["SBASE1", "RATIOCW", "NOMV1", "ANGLE", "RMAXCW", "RMINCW", "VMAX", "VMIN"]))
        data.extend(Psse.fetchSection(fetchBase, psspy.atrncplx, ["COMPRX"]))
        data.extend(Psse.fetchSection(fetchBase, psspy.atrnreal, ["RATIO2CW", "NOMV2"]))
        Psse.mapKeysToData(sectionKey, id, data)
        #3 winding transformer
        fetchBase["entry"] = 2
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.atr3int, ["WIND1NUMBER", "WIND2NUMBER", "WIND3NUMBER"]))
        id.extend(Psse.fetchSection(fetchBase, psspy.atr3char, ["ID"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.atr3cplx, ["YMAGCM", "RX1-2NOMCZ", "RX2-3NOMCZ", "RX3-1NOMCZ"]))
        data.extend(Psse.fetchSection(fetchBase, psspy.atr3real, ["VMSTAR", "ANSTAR"]))
        #3 winding windings
        fetchBase["flag"] = 3
        tempWindingData = []
        tempWindingData.extend(Psse.fetchSection(fetchBase, psspy.awndreal, ["SBASE", "RATIOCW", "NOMV", "ANGLE", "RMAXCW", "RMINCW", "VMAX", "VMIN"]))
        tempWindingData.extend(Psse.fetchSection(fetchBase, psspy.awndcplx, ["COMPRX"]))
        #need to restructure the winding data since winding data arrays are 3x as long as other data arrays, need to line them up
        #new format:  [sbase123, winding 1 entries, winding 2 entries, winding 3 entries]
        restructuredWinding = [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
        for i in range(0, len(tempWindingData[0]), 3):
            #sbase values
            restructuredWinding[0].append(tempWindingData[0][i])
            restructuredWinding[1].append(tempWindingData[0][i+1])
            restructuredWinding[2].append(tempWindingData[0][i+2])
            
            #winding 1
            restructuredWinding[3].append(tempWindingData[1][i])
            restructuredWinding[4].append(tempWindingData[2][i])
            restructuredWinding[5].append(tempWindingData[3][i])
            restructuredWinding[6].append(tempWindingData[4][i])
            restructuredWinding[7].append(tempWindingData[5][i])
            restructuredWinding[8].append(tempWindingData[6][i])
            restructuredWinding[9].append(tempWindingData[7][i])
            restructuredWinding[10].append(tempWindingData[8][i])
            
            #winding 2
            restructuredWinding[11].append(tempWindingData[1][i+1])       
            restructuredWinding[12].append(tempWindingData[2][i+1])
            restructuredWinding[13].append(tempWindingData[3][i+1])
            restructuredWinding[14].append(tempWindingData[4][i+1]) 
            restructuredWinding[15].append(tempWindingData[5][i+1])
            restructuredWinding[16].append(tempWindingData[6][i+1])
            restructuredWinding[17].append(tempWindingData[7][i+1])
            restructuredWinding[18].append(tempWindingData[8][i+1])
            
            #winding 3
            restructuredWinding[19].append(tempWindingData[1][i+2])
            restructuredWinding[20].append(tempWindingData[2][i+2])
            restructuredWinding[21].append(tempWindingData[3][i+2])
            restructuredWinding[22].append(tempWindingData[4][i+2])
            restructuredWinding[23].append(tempWindingData[5][i+2])
            restructuredWinding[24].append(tempWindingData[6][i+2])
            restructuredWinding[25].append(tempWindingData[7][i+2])
            restructuredWinding[26].append(tempWindingData[8][i+2])
        data.extend(restructuredWinding)
        Psse.mapKeysToData(sectionKey, id, data)
        
        #TWO-TERMINAL DC data
        sectionKey = "TWO-TERMINAL DC"
        if ver == 35:
            Psse._dataDict[sectionKey] = {"format":[[2, 3, 4, 5, 6, 7, 9], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 17], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 17]]}
        else:
            Psse._dataDict[sectionKey] = {"format":[[2, 3, 4, 5, 6, 7, 9], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16], [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16]]}
        Psse._dataDict[sectionKey]["nameSplit"] = [[0, 12], None, None]
        fetchBase = {"sid":-1, "flag":2}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.a2trmdcchar, ["DCNAME"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.a2trmdcreal, ["RDC", "SETVAL", "VSCHD", "VCMOD", "RCOMP", "DELTI", "DCVMIN", "ANGMAX_R", "ANGMIN_R", "RC_R", "XC_R", "EBASE_R", "TR_R", "TAP_R", "TPMX_R", "TPMN_R", "STP_R", "XCAP_R",
                                                                     "ANGMAX_I", "ANGMIN_I", "RC_I", "XC_I", "EBASE_I", "TR_I", "TAP_I", "TPMX_I", "TPMN_I", "STP_I", "XCAP_I"]))
        Psse.mapKeysToData(sectionKey, id, data)
        
        #VSCDC data
        sectionKey = "VSC DC"
        if ver == 33:
            Psse._dataDict[sectionKey] = {"format":[[2, 3, 4, 5], [3, 4, 15, 16, 17, 18, 19], [3, 4, 15, 16, 17, 18, 19]], "nameSplit":[[0, 12], None, None], "mod": [[3, 3, 3], [15, 15, 15, 15, 15], [15, 15, 15, 15, 15]]}
        else:
            Psse._dataDict[sectionKey] = {"format":[[2, 3, 4, 5], [3, 4, 16, 17, 18, 19, 20], [3, 4, 16, 17, 18, 19, 20]], "nameSplit":[[0, 12], None, None], "mod": [[3, 3, 3], [16, 16, 16, 16, 16], [16, 16, 16, 16, 16]]}
        fetchBase = {"sid":-1, "flag":3}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.avscdcchar, ["VSCNAME"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.avscdcreal, ["RDC", "DCCUR", "PLOSS", "QLOSS"]))
        #VSCDC needs a similar operation to 3 winding windings to get all the data
        fetchBase["entry"] = 2
        tempConvData = Psse.fetchSection(fetchBase, psspy.avscdcconvreal, ["DCSET", "ACSET", "PAC", "QAC", "ACAMPS", "KVDC", "MVA"])
        #[CONV1, CONV2]
        restructuredConv = [[], [], [], [], [], [], [], [], [], [], [], [], [], []]
        for i in range(0, len(tempConvData[0]), 2):
            restructuredConv[0].append(tempConvData[0][i])
            restructuredConv[1].append(tempConvData[1][i])
            restructuredConv[2].append(tempConvData[2][i])
            restructuredConv[3].append(tempConvData[3][i])
            restructuredConv[4].append(tempConvData[4][i])
            restructuredConv[5].append(tempConvData[5][i])
            restructuredConv[6].append(tempConvData[6][i])
            
            restructuredConv[7].append(tempConvData[0][i+1])
            restructuredConv[8].append(tempConvData[1][i+1])
            restructuredConv[9].append(tempConvData[2][i+1])
            restructuredConv[10].append(tempConvData[3][i+1])
            restructuredConv[11].append(tempConvData[4][i+1])
            restructuredConv[12].append(tempConvData[5][i+1])
            restructuredConv[13].append(tempConvData[6][i+1])
        data.extend(restructuredConv)
        Psse.mapKeysToData(sectionKey, id, data)
        
        #Facts data
        sectionKey = "FACTS DEVICE"
        if ver == 33:
            Psse._dataDict[sectionKey] = {"format":[4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 21, 23, 25, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40], "nameSplit":[0, 12], 
                                          "mod":[21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21]}
        else:
            Psse._dataDict[sectionKey] = {"format":[4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 22, 24, 26, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41], "nameSplit":[0, 12], 
                                          "mod":[22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22]}
        fetchBase = {"sid":-1, "flag":2, "fcttyp":3}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.afactschar, ["FACTSNAME"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.afactsreal, ["PDES", "QDES", "VSET", "SHMX", "TRMX", "VTMN", "VTMX", "VSMX", "IMX", "LINX", "RMPCT"]))
        data.extend(Psse.fetchSection(fetchBase, psspy.afactscplx, ["PQSEND", "PQTERM", "PQSHNT", "PQLOSS"]))
        data.extend(Psse.fetchSection(fetchBase, psspy.afactsreal, ["VSERES", "AMPSND", "AMPTRM", "AMPSHN", "PCTSND", "PCTTRM", "PCTSHN", "PCTPBR", "PBRDG", "MVASND", "MVATRM", "MVASHN"]))
        Psse.mapKeysToData(sectionKey, id, data)
        
        #Switched Shunt
        sectionKey = "SWITCHED SHUNT"
        if ver == 35:
            Psse._dataDict[sectionKey] = {"format":[5, 6, 9]} 
        else:
            Psse._dataDict[sectionKey] = {"format":[4, 5, 7]}
        fetchBase = {"sid":-1, "flag":4}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.aswshint, ["NUMBER"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.aswshreal, ["VSWHI", "VSWLO", "RMPCT"]))
        Psse.mapKeysToData(sectionKey, id, data)
        
        #Induction Machine
        sectionKey = "INDUCTION MACHINE"
        Psse._dataDict[sectionKey] = {"format":[10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]}
        fetchBase = {"sid":-1, "flag":4}
        id = []
        id.extend(Psse.fetchSection(fetchBase, psspy.aindmacint, ["NUMBER"]))
        id.extend(Psse.fetchSection(fetchBase, psspy.aindmacchar, ["ID"]))
        data = []
        data.extend(Psse.fetchSection(fetchBase, psspy.aindmacreal, ["MBASE", "RATEKV", "PSETPOINT", "H", "TRQA", "TRQB", "TRQD", "TRQE", "RA", "XA", "XM", "R1", "X1", "R2", "X2", "X3", "E1", "SE1", "E2", "SE2", "IA1", "IA2", "XAMULT"]))
        Psse.mapKeysToData(sectionKey, id, data)
        
        log.addLine("Data retrieved", 2)
    
    
    #Takes the arrays of data and IDs to generate keys and entries for the dictionary
    #Ex.
    #      ID    Val1   Val2
    #   |  12  |  11  |  73  |          "12" -> [11, 73]
    #   |  13  |  02  |  82  |  ====>   "13" -> [02, 82]
    #   |  14  |  05  |  92  |          "14" -> [05, 92]
    #
    #a key is generated as a space seperated list of the minimum number of things that ids an entry
    #Ex.
    #   2 winding xformer key ===> i j 0 ckt
    #   bus key               ===> i
    @staticmethod
    def mapKeysToData(key, ids, newData):
        #transposing the data so each array corresponds to 1 entry instead of 1 data type
        id = [[row[i] for row in ids] for i in range(len(ids[0]))]
        data = [[row[i] for row in newData] for i in range(len(newData[0]))]
        
        for i in range(len(id)):
            newKey = " ".join([str(v) for v in id[i]])
            Psse._dataDict[key][newKey] = data[i]
    
    #gets the format array that specifies where data needs replacing for an entry
    @staticmethod
    def getFormat(key, sectionName):
        if sectionName == "TRANSFORMER":
            if " 0 " in key:
                format = Psse._dataDict[sectionName]["format2w"]
            else:
                format = Psse._dataDict[sectionName]["format3w"]
        else:
            format = Psse._dataDict[sectionName]["format"]

        return format
    
    #rejoins an entry into a single string after we have split it up for processing
    @staticmethod
    def joinEntry(entry):
        if type(entry[0]) == list:
            for i in range(len(entry)):
                entry[i] = ", ".join(entry[i])
            entry = "\n".join(entry)
        else:
            entry = ", ".join(entry)
            
        return entry
    
    #Used to split entries in place of the .split() function
    #Does extra work on lines with "name" data when splitting since they can have unexpected commas
    @staticmethod
    def splitLine(nameSplit, entry, isFacts):
        if nameSplit == None:
            return [dat.strip() for dat in entry.split(",")]
        else:
            if ver == 33 or ver == 35:#FACTS device entries have a second name entry near the end
                factPosFromEnd = 0
            else:
                factPosFromEnd = 1 
            
            nameIndex = nameSplit[0]
            nameSize = nameSplit[1]
            endPoint = len(entry)
            nameBounds = [-1, -1]
            index = 0
            splitString = []
            inName = False
            nameCounter = 0
            for i in range(endPoint):#go to the "name" section of entry and find the bounding commas
                if inName == False:      
                    if entry[i] == ",":
                        index = index + 1
                    elif index == nameIndex and (entry[i] == "'" or entry[i] == "\""):
                        inName = True
                        
                    if index == nameIndex and nameBounds[0] == -1:
                        nameBounds[0] = i
                    elif index == nameIndex + 1:
                        nameBounds[1] = i
                        break
                if inName == True:
                    nameCounter = nameCounter + 1
                    if nameCounter >= nameSize and (entry[i] == "'" or entry[i] == "\""):
                        inName = False
            if nameBounds[1] == -1:
                nameBounds[1] = endPoint
            if nameIndex > 0:
                upToName = entry[0:nameBounds[0]]
                splitString = upToName.split(",")
            else:
                nameBounds[0] = -1
            splitString.append(entry[nameBounds[0]+1 : nameBounds[1]])
            
            if isFacts:#getting additional bounds for the second name in FACTS device entries
                index = 0
                inName = False
                nameCounter = 0
                factsBounds = [-1, -1]
                for i in range(endPoint):
                    fromEnd = endPoint - i - 1
                    if inName == False:      
                        if entry[fromEnd] == ",":
                            index = index + 1
                        elif index == factPosFromEnd and (entry[fromEnd] == "'" or entry[fromEnd] == "\""):
                            inName = True    
                        
                        if index == factPosFromEnd and factsBounds[1] == -1:
                            factsBounds[1] = fromEnd
                        elif index == factPosFromEnd + 1:
                            factsBounds[0] = fromEnd
                            endPoint = fromEnd
                            break
                    if inName == True:
                        nameCounter = nameCounter + 1
                        if nameCounter >= nameSize and (entry[fromEnd] == "'" or entry[fromEnd] == "\""):
                            inName = False
                if factsBounds[1] == len(entry)-1:
                    factsBounds[1] = factsBounds[1] + 1
            
            if nameBounds[1] < endPoint:
                afterName = entry[nameBounds[1]+1:endPoint]
                afterName = afterName.split(",")
                splitString.extend(afterName)
            
            if isFacts:
                splitString.append(entry[factsBounds[0]+1 : factsBounds[1]])
                if factsBounds[1] < len(entry):
                    splitString.append(entry[factsBounds[1]+1 : len(entry)])
            
            return [dat.strip() for dat in splitString]

    #we want to modify some entries to read additional information not already in the PSSE format
    #this creates additional space to have those entries placed
    @staticmethod
    def modifyEntry(mod, entry):
        if not mod == None:
            for i in mod:
                entry.insert(i, "new data")
        return entry
        
    #goes through the given entry and replaces all indicated places that we have updated values for
    @staticmethod 
    def replaceDataOfEntry(key, sectionName, entry):
        if sectionName in Psse._dataDict:
            data = Psse._dataDict[sectionName][key]
            format = Psse.getFormat(key, sectionName)
            
            nameSplit = None
            if "nameSplit" in Psse._dataDict[sectionName]:
                nameSplit = Psse._dataDict[sectionName]["nameSplit"]
                
            mod = None
            if "mod" in Psse._dataDict[sectionName]:
                mod = Psse._dataDict[sectionName]["mod"]
            
            if "\n" in entry:
                entry = Psse.replaceMultiLineEntry(data, format, nameSplit, mod, sectionName, entry)
            else:
                entry = Psse.replaceSingleLineEntry(data, format, nameSplit, mod, sectionName, entry)      
        return entry
    
    @staticmethod 
    def replaceSingleLineEntry(data, format, nameSplit, mod, sectionName, entry):
        entry = Psse.splitLine(nameSplit, entry, sectionName == "FACTS DEVICE")
        entry = Psse.modifyEntry(mod, entry)
        for i in range(len(data)):
            if format[i] < len(entry):
                if type(data[i]) == complex:              
                    entry[format[i]] = str(data[i].real)
                    entry[format[i]+1] = str(data[i].imag)
                else:
                    entry[format[i]] = str(data[i])
        return Psse.joinEntry(entry)
                    
    @staticmethod
    def replaceMultiLineEntry(data, format, nameSplit, mod, sectionName, entry):
        dataCount = 0
        entry = entry.split("\n")
        
        for i in range(len(entry)):
            entry[i] = Psse.splitLine(nameSplit[i], entry[i], sectionName == "FACTS DEVICE")
            if not mod == None:
                entry[i] = Psse.modifyEntry(mod[i], entry[i])
            for j in range(len(format[i])):
                if format[i][j] < len(entry[i]):
                    if type(data[dataCount]) == complex:              
                        entry[i][format[i][j]] = str(data[dataCount].real)
                        entry[i][format[i][j]+1] = str(data[dataCount].imag)
                    else:
                        entry[i][format[i][j]] = str(data[dataCount])
                dataCount = dataCount + 1
        return Psse.joinEntry(entry)
            
    #acts like an interface for calls to psspy data fetching functions to have error checking
    @staticmethod
    def fetchSection(param, func, stringOption):
        param["string"] = stringOption
        err, val = func(**param)
        
        #error codes usually have a different meaning per component so we check that no data was returned instead of which error code is returned
        #we then return the error code to be looked up
        if err != 0 or val==None:
            fnc = [dat.strip() for dat in str(func).split(" ")][1]
            print("\nError while fetching data from PSSE\s")
            log.addSection("Error while fetching data from PSSE")
            bugLog.addLine("DATA RETRIEVAL FAILURE", 1)
            raise Exception("Failed to fetch data from " + fnc + ": " + str(err))
            
        return val

#################################################################################################        

#Contains operations for writing to a RAWPR file            
class RawPr:
    _filePointer = None
    _newFilePath = ""
    
    @staticmethod
    def startWrite():
        try:
            log.addLine("Setting up RAWPR file for writing", 1)
            if not savprpath == "":
                RawPr._newFilePath = savprpath
            else:
                RawPr._newFilePath = filepath + "pr"
            RawPr._filePointer = open(RawPr._newFilePath, "w")
            log.addLine("File opened for writing", 2)
        except OSError as ex:
            if not RawPr._filePointer == None:
                RawPr._filePointer.close()
            print("\nFile writing error\n")
            log.addSection("File writing error")
            bugLog.addLine("FILE WRITE OPEN ERROR", 1)
            if len(ex.args) >= 1:
                ex.args = ("Failed to open " + RawPr._newFilePath + "pr: " + ex.args[0],) + ex.args[1:]
            else:
                ex.args = ("Failed to open " + RawPr._newFilePath + "pr",)
            raise
    
    @staticmethod
    def finishWrite():
        try:
            if not RawPr._filePointer == None:
                RawPr._filePointer.close()
        except OSError as ex:
            print("\nFile writing error\n")
            log.addSection("File writing error")
            bugLog.addLine("FILE WRITE FINISH ERROR", 1)
            if len(ex.args) >= 1:
                ex.args = ("Failed to close " + RawPr._newFilePath + "pr: " + ex.args[0],) + ex.args[1:]
            else:
                ex.args = ("Failed to close " + RawPr._newFilePath + "pr",)
            raise
    
    @staticmethod
    def addLine(string):
        try:
            if not RawPr._filePointer == None:
                RawPr._filePointer.write(string)
                RawPr._filePointer.write("\n")
        except OSError as ex:
            if not RawPr._filePointer == None:
                RawPr._filePointer.close()
            print("\n\File writing error\n")
            log.addSection("File writing error")
            bugLog.addLine("FILE WRITE LINE ERROR", 1)
            if len(ex.args) >= 1:
                ex.args = ("Failed to write to " + RawPr._newFilePath + "pr: " + ex.args[0],) + ex.args[1:]
            else:
                ex.args = ("Failed to write to " + RawPr._newFilePath + "pr",)
            raise

#################################################################################################

#Contains operations for handling the RAW file and its contents
#namely opening and reading the file
class Raw:
    #variables for file reading
    _section = 0
    _sectionName = "CASE ID"
    if ver == 34 or ver == 35:
        _sectionName = _sectionName + "and SYSTEM WIDE DATA"
    _rawFile = None
    _line = -1
    _lineContent = ""
    
    _inCaseID = 4 #there is no transition line following case id data, but it is guarenteed to be 3 or 4 lines depending on version
    
    #variables that determine certain reading behavior for each section -> _sectionId denotes the first X values are the key, _multi tracks if we are reading in the middle of a multi line entry currently
    _sectionId = [None, 1, 2, 2, 2, 3, 3, 4, None, 1, 1, None, None, None, None, None, None, 1, 1, None, 2, None, None]
    _multi = False
    
    #opens the RAW file and parses it into lines
    @staticmethod
    def loadRaw ():      
        try:
            log.addLine("Attempting to read " + filepath, 1)
            tempF = open(filepath, "r")
            wholeFile = tempF.read()
            tempF.close()
            Raw._rawFile = wholeFile.split("\n")
            log.addLine("Read in " + str(len(Raw._rawFile)) +" lines", 2)
            global ver
            for i in range(len(Raw._rawFile)):#reading the PSSE version from the file
                if i > Raw._inCaseID:
                    raise Exception("PSSE version not indicated in CASE ID Data")
                line = Raw._rawFile[i]
                if "/ PSS" in line:
                    index = line.index("/ PSS")
                    index = index + line[index:].index("E")
                    ver = int(line[index+2 : index+4])
                    break
            if ver < 33:# only handle v33, v34, v35
                raise Exception("PSSE version not handled, expected v33, v34, v35")
            log.addLine("Found PSSE version of file: v" + str(ver), 2)
            if ver == 33:# update some data to better fit the v33 format
                Raw._sectionId.remove(3)
                Raw._inCaseID = 3
        except OSError as ex:
            print("\nFile read error\n")
            log.addSection("File read error")
            bugLog.addLine("FILE READ OPEN ERROR", 1)
            if len(ex.args) >= 1:
                ex.args = ("Failed to open the RAW file " + filepath + ": " + ex.args[0],) + ex.args[1:]
            else:
                ex.args = ("Failed to open the RAW file " + filepath,)
            raise
        except Exception as ex:
            print("\nFile version error\n")
            log.addSection("File version error")
            bugLog.addLine("FILE READ VERSION ERROR", 1)
            if len(ex.args) >= 1:
                ex.args = ("File PSSE version invalid for " + filepath + ": " + ex.args[0],) + ex.args[1:]
            else:
                ex.args = ("File PSSE version invalid for " + filepath,)
            raise
            
    #reads the next data line, other lines are automatically output since they are just formatting, structure, comment, etc, lines
    #if the entry is multi-lined (transformers, dc links, VSDC) it will read the entire entry out as a "line" with newlines replaced with commas (the data updater expects it this way)
    @staticmethod
    def readLine():
        lineFound = False
        line = "end"
        Raw._line = Raw._line + 1
        while lineFound == False and Raw._line < len(Raw._rawFile)-1:
            line = Raw._rawFile[Raw._line].strip()
            skipThisLine = False
            if len(line)  > 0: #sometimes a line is just a new line or something if so we auto skip in the else block
                if Raw._inCaseID > 0: #skip any lines in the CASE ID, ASSUMPTION: this data does not change
                    skipThisLine = True
                    Raw._inCaseID = Raw._inCaseID - 1
                    if Raw._inCaseID == 0: #setting the next section name manually since it isn't in the file
                        if ver == 33:
                            Raw._sectionName = "BUS"
                            Raw._section = Raw._section + 1
                            log.addLine("Updating " + Raw._sectionName + " data", 2)
                            print("Updating " + Raw._sectionName + " data")
                        else:
                            Raw._sectionName = "SYSTEM WIDE"
                            log.addLine("System Wide Data is directly copied, no changes assumed", 2)
                            print("System Wide Data is directly copied, no changes assumed")
                elif line[0] == "c" or line[0] == "C" or line[0] == "!" or line[0] == "/": #comments
                    skipThisLine = True
                elif line[0:2] == "@!": #column type, formatting line
                    skipThisLine = True
                elif line[0:3] == "0 /": #skip section start/end lines but also update the section and section name variables
                    skipThisLine = True
                    tempName = ""
                    tempLine = [dat.strip() for dat in line.split()]
                    startNaming = False
                    for i in range(len(tempLine)):#locating and reading the name
                        if startNaming == True:
                            tempName = " ".join(tempLine[i:len(tempLine)-1])
                            break
                        if tempLine[i].upper() == "BEGIN":
                            startNaming = True
                    Raw._sectionName = tempName.upper()
                    Raw._section = Raw._section + 1
                    if Raw._sectionName != "":
                        log.addLine("Updating " + Raw._sectionName + " data", 2)
                        print("Updating " + Raw._sectionName + " data")
                elif Raw._sectionName == "SYSTEM WIDE": #skip any lines in the SYSTEM WIDE DATA, ASSUMPTION: this data does not change
                    skipThisLine = True
                elif line[0] == "Q": #file end
                    RawPr.addLine(line)
                    return "end"
            else:
                skipThisLine = True
                
            if skipThisLine == True:#output skipped lines directly to RAWPR
                RawPr.addLine(line)
                Raw._line = Raw._line + 1
            else:
                lineFound = True
                
                #handling multi-lines
                if "TRANSFORMER" in Raw._sectionName and Raw._multi == False:
                    line += "\n" + Raw.readTransEntry(line)
                elif ("TWO-TERMINAL DC" in Raw._sectionName or "VSC DC" in Raw._sectionName) and Raw._multi == False:
                    line += "\n" + Raw.readDcEntry()
                
                Raw._lineContent = line        
        return line
    
    @staticmethod
    def readTransEntry(line):
        Raw._multi = True#set _multi = true so readLine() knows we are mid-entry still
        entry = Raw.readLine() + "\n" + Raw.readLine() + "\n" + Raw.readLine()
        if(int(line.split(",")[2]) != 0):#extra line in 3 winding entries
            entry += "\n" + Raw.readLine()
        Raw._multi = False   
        return entry
    
    @staticmethod
    def readDcEntry():
        Raw._multi = True
        entry = Raw.readLine() + "\n" + Raw.readLine()
        Raw._multi = False
        return entry
    
    @staticmethod
    def getSection():
        name = Raw._sectionName
        if "LINE" in name: #"LINE" can sometimes be omitted from the sectionName,
                           #For consistency "LINE" is removed before returning the section name
            name = name[:name.index("LINE")-1]
        return name
    
    @staticmethod
    def getLineNumber():
        return Raw._line
    
    @staticmethod
    def getLineKey():  
        if Raw._sectionId[Raw._section] != None:
            tempLine = [dat.strip() for dat in Raw._lineContent.split(",")]
            return " ".join(tempLine[0:Raw._sectionId[Raw._section]]).replace("'", "").replace("\"", "")
        else:              
            return "NoKey"

#################################################################################################

#run it
def run():
    try:
        if not runFromGUI:
            bugLog.addSection("Note about Debugging")
            bugLog.addLine("Running this program directly from command line sometimes provides different information on a crash, you can do this with...", 1)
            bugLog.addLine("\"" + str(sys.executable) + "\" \"" + str(inspect.getfile(inspect.currentframe())) + "\" \"" + filepath + "\" \"" + pssePathIn + "\"", 1)
        
        log.addSection("Startup Info")
        if not runFromGUI:
            log.addLine("Running from ETRAN or command line")
            log.addLine("PSSE Directory: " + PSSE_Path, 1)
        else:
            log.addLine("Running from PSSE GUI")
            log.addLine("Using PSSE version: v" + str(psspy.psseversion()[1]), 1)
        log.addLine("Python Version: " + str(sys.version), 1)
        
        log.addSection("Progress")
        
        #start PSSE
        if not runFromGUI:
            print("\nLaunching PSSE...")
            log.addLine("Launching PSSE", 1)
            psspy.psseinit(150000)
            print("\nPSSE launched...")
            log.addLine("PSSE Launched", 2)
        
        if savprpath == "":
            log.addLine("Beginning translation of " + filepath + " to " + filepath + "pr", 1)   
            print("\nBeginning translation of " + filepath + " to " + filepath + "pr...\n")
        else:
            log.addLine("Beginning translation of " + savprpath[:len(savprpath)-6] + ".sav to " + savprpath, 1)   
            print("\nBeginning translation of " + savprpath[:len(savprpath)-6] + ".sav to " + savprpath + "...\n")
        
        #load everything
        bugLog.addSection("File")    
        Raw.loadRaw()
        if not runFromGUI:#running from GUI assumes a loaded, solved case
            Psse.loadRaw() 
            Psse.attemptSolve()               
        Psse.buildDataDict()   
        RawPr.startWrite()
        
        log.addLine("Beginning update of RAW data", 1)
        bugLog.addSection("Data")

        #read through the entire file and attempt to update each line
        line = Raw.readLine()
        go = True
        while go == True:    
            currSection = Raw.getSection()        
            if line =="end":
                go = False
                break
            
            key = Raw.getLineKey()
            if key != "NoKey":       
                line = Psse.replaceDataOfEntry(key, currSection, line)          
            RawPr.addLine(line)          
            line = Raw.readLine()
        
        log.addLine("RAW data updated", 1)

        if savprpath == "":
            log.addLine("Finilizing writing the RAWPR file to " + filepath + "pr", 1)
        else:
            log.addLine("Finilizing writing the RAWPR file to " + savprpath, 1)
        RawPr.finishWrite() 
        log.addLine("RAWPR file written", 2)
        
        if savprpath == "":
            log.addLine("Translation of " + filepath + " to " + filepath + "pr finished", 1)
            print("\nTranslation of " + filepath + " to " + filepath + "pr finished!\n")
        else:
            log.addLine("Translation of " + savprpath[:len(savprpath)-6] + ".sav to " + savprpath + " finished", 1)   
            print("\nTranslation of " + savprpath[:len(savprpath)-6] + ".sav to " + savprpath + " finished\n")
            if path.exists(filepath):
                os.remove(filepath)#removing the temp file after finishing
        
        log.end()
        bugLog.end()
        
        if not runFromGUI:
            psspy.close_powerflow()
            
    except Exception as ex:#if we catch an error anywhere -> cleanup/close logs, print however far we got in the file
        print("\nError found, Exiting early\n")
        bugLog.addLine("Error occured while converting line: " + str(Raw.getLineNumber()), 3)
        if hasattr(ex, "__traceback__"):
            traceBack = traceback.format_exception(type(ex), ex, ex.__traceback__)
            bugLog.addLine(traceBack[0].replace("\n",""), 3)
            for entry in traceBack[1:]:
                bugLog.addLine(entry.strip().replace("\n", ":"), 4)
        log.addSection("Error encountered, Unable to continue")
        log.end(-1)
        bugLog.end(-1)
        RawPr.finishWrite()
        if savprpath == "":
            if path.exists(filepath + "pr"):
                os.remove(filepath + "pr") #we don't want to output a broken file that the user might mistake for "ok"
        else:
            if path.exists(savprpath):
                os.remove(savprpath)
            if path.exists(filepath):
                os.remove(filepath)
        
        if not runFromGUI:
            psspy.close_powerflow()
        
        raise

run()    