"""Program for creating the Hybrid and Equivalent PSSE case from the original PSSE case.

This program can only run on PSSE version 30 and above. if the power
flow base case is in version 29 or below, please save it as version 30 to begin
with.

**********************************************************************
*                                                                    *
*  THIS PROGRAM AND ITS DOCUMENTATION ARE PREPARED BY                *
*  ELECTRANIX CORPORATION, WINNIPEG, MANITOBA, CANADA                *
*                                                                    *
*  ELECTRANIX DOES NOT WARRANT EXPRESSLY OR BY IMPLICATION,          *
*  THE DATA OR COMPUTER PROGRAMMING IN THIS FILE TO BE CORRECT.      *
*  ELECTRANIX ASSUMES NO RESPONSIBILITY FOR                          *
*  APPROPRIATE USE OF THIS FILE AND ASSUMES NO LIABILITY FOR ANY     *
*  POSSIBLE DAMAGING STUDY RESULTS OBTAINED BY USE OF THIS FILE.     *
*                                                                    *
**********************************************************************
===============================================================================
VER   DATE        BY:          DESCRIPTION OF CHANGES
-------------------------------------------------------------------------------

1.0   06/02/2015  ELECTRANIX   Initial Version
1.1   03/06/2015  ELECTRANIX   New CCT format .dyr records added (V33 & up)
2.0   15/06/2016  SIEMENS PTI  Refactored for Efficiency and Python Style Guide
2.1   16/06/2016  ELECTRANIX   Add swing bus (if deleted) and EINIT .dyr record 
2.2   04/10/2016  ELECTRANIX   Revised the code to use python "execfile" instead of "import"  
2.3   20/12/2016  ELECTRANIX   Revised the initial powerflow solution method 
2.4   13/01/2017  ELECTRANIX   Fixed a bug associated with deleting FACTS devices 
2.5   13/07/2017  ELECTRANIX   Improve automatic modification of DYR file (API version 2)
===============================================================================
"""

import sys
import os
import math
import re

import psspy

try:
    set
except NameError:
    from sets import Set as set

def RedirectOutputToPSSE():
    # redirect output back to PSSE:
    psspy.lines_per_page_one_device(2, 10000000)
    psspy.progress_output(1, "", [0, 0])
    psspy.prompt_output(1, "", [0, 0])
    return;

python_hybrid_api_version = 2

psspy.psseinit(120000)

# cache the param defaults:
_i, _f = psspy.getbatdefaults()
_s = ""
# CONSTANTS:
_IS_EQ = isinstance((locals()['gen_r'])[0], list) # for deciding if we should run hybrid or equivalent logic

if _IS_EQ:#for messaging
    runType = "equivalent"
else:
    runType = "hybrid"

if 'sav_path' not in locals():
    acceptedFormats = ".sav"
    if _IS_EQ:
        acceptedFormats = acceptedFormats + " or .raw"
    print("\n Error: A valid " + acceptedFormats + " file must be loaded before the configuration script is run.\n")
    print(" Please load a valid " + acceptedFormats + " file and try again.\n")
    RedirectOutputToPSSE()
    raise SystemExit

# determine what file type to use, only equivalents can use raw files
if _IS_EQ and os.path.splitext(locals()['sav_path'])[1] == ".raw":
    fileRead = ".raw"
    error = psspy.read(0, locals()['sav_path'])
else:
    fileRead = ".sav"
    error = psspy.case(locals()['sav_path'])
if error != 0:
    print("\n An error occured while reading the " + fileRead + " case " + locals()['sav_path'] + "\n")
    print(" Either the file does not exist or a reading error occured.\n")
    print(" Execution of the python script has been halted.\n")
    RedirectOutputToPSSE()
    raise SystemExit

# different logs so they don't share the same name and overwrite eachother if running both
if _IS_EQ:
    PROGRESS_FILE = os.path.dirname(locals()['sav_path']) + r"\ETranPyEquivScriptResults.txt"
else:
    PROGRESS_FILE = os.path.dirname(locals()['sav_path']) + r"\ETranPyHybridScriptResults.txt"
print(" Outputting script log to " + PROGRESS_FILE)
#PROGRESS_FILE = "HBpython.txt"

# directing the output to a txt file
ierr = psspy.progress_output(2, PROGRESS_FILE, [0, 0])
if ierr != 0:
    print(" Error opening file " + PROGRESS_FILE)
ierr = psspy.prompt_output(2, PROGRESS_FILE, [0, 0])
if ierr != 0:
    print(" Error opening file " + PROGRESS_FILE)

calling_api_version = locals().get('hybrid_python_api_version', 1) #default to 1 for old cfg files
if (python_hybrid_api_version != calling_api_version):
    print("\n ERROR: The " + runType + " config file version (" + str(calling_api_version) + ") does not match the installed E-Tran Plus python interface (" + str(python_hybrid_api_version) + ")\n")
    if (python_hybrid_api_version > calling_api_version):
        print(" Your generated " + runType + "_cfg.py file is out-of-date\n")
        print(" Please re-translate your case using E-Tran to create an updated " + runType + "_cfg.py file\n")
    else:
        print(" Your E-Tran is out-of-date compared to the E-Tran install used to generate the " + runType + "_cfg.py file\n")
        print(" Please update your local E-Tran install and try again\n")
    #print again to PSSE window
    RedirectOutputToPSSE()
    print("\n ERROR: The " + runType + " config file version (" + str(calling_api_version) + ") does not match the installed E-Tran Plus python interface (" + str(python_hybrid_api_version) + ")\n")
    if (python_hybrid_api_version > calling_api_version):
        print(" Your generated " + runType + "_cfg.py file is out-of-date\n")
        print(" Please re-translate your case using E-Tran to create an updated " + runType + "_cfg.py file\n")
    else:
        print(" Your E-Tran is out-of-date compared to the E-Tran install used to generate the " + runType + "_cfg.py file\n")
        print(" Please update your local E-Tran install and try again\n")

    raise SystemExit()

# Display a message to indicate the execution of the python script:
print("*" * 64)
print(" Start executing the python script to insert the " + runType + " interface")
print(" (This script can only be used with PSSE version 30 or greater)")
print("*" * 64 + "\n")

# lists of popular solution settings
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]

# initially solve the power flow case using Fixed slope decoupled Newton Raphson
# locked taps, enabled all shunts, disabled area interchange, adjust DC taps,
# VAR limits apply automatically:

itr_best = 100

best_attempt = 100

print("\n Source " + fileRead + " path is " + locals()['sav_path'] + "\n")
if not _IS_EQ:
    print(" Source DYR path is " + locals()['dyr_path'] + "\n") # DYR is hybrid only 

# checking the number of iterations
for attempt in range(0,6):
    print("\n Solution attempt # %i" % ( attempt + 1))

    if _IS_EQ and os.path.splitext(locals()['sav_path'])[1] == ".raw":
        psspy.read(0, locals()['sav_path'])
    else:
        # open the .sav file:
        psspy.case(locals()['sav_path'])
    
    # load solution settings of this attempt
    this_soln = settings[attempt]
    
    if attempt < 3:
        psspy.fdns(this_soln)
    
    else:
        psspy.fnsl(this_soln)
        
    # checking if case solved
    result = psspy.solved()
    
    # checking iterations
    this_itr = psspy.iterat()
    
    # update lowest iteration and best attempt if case solved and this_itr is lowest number of iterations yet
    if (result == 0):
        itr_best = this_itr
        best_attempt = attempt
        break

# exit python execution if itr_best did not change from 100
if (itr_best == 100):
    print("\n The original base case could not be solved with six different solution settings.")
    print(" Check the base case and run the script once again")
    # exit from the python execution:
    RedirectOutputToPSSE()
    print("\n The original base case could not be solved with six different solution settings.")
    print(" Check the base case and run the script once again")
    raise SystemExit()

print(" Base case successfully solved on attempt # %i" % (best_attempt + 1))


name, major, minor, modlvl, date, stat = psspy.psseversion()
PSSE_VER = major  # save the PSSE version as some of the API's are new

print(" \n The PSSE version of your Powerflow case is " + str(PSSE_VER))

if not _IS_EQ:
    if locals()['new_dyr_format'] and PSSE_VER < 33:
        print(" The new CCT format of CONEC/CONET models requested")
        print(" are only applicable to PSSE V33 and up.\n")
        print(" Writing the .dyr record with conventional format...\n ")

        locals()['new_dyr_format'] = False

# this will be the name of the interface added .sav case:
hybrid_sav = locals()['sav_path'][:-4] + locals()['sav_suffix'] + ".sav"

# dyr is ignored in EQ
if not _IS_EQ:
    # this will be the new .dyr file that has the original .dyr plus required new models:
    # It will write out to the same folder as the sav file
    hybrid_dyr = locals()['sav_path'][0:locals()['sav_path'].rfind('\\')]   # start with the full path minus filename of the sav file
    hybrid_dyr = hybrid_dyr + locals()['dyr_path'][locals()['dyr_path'].rfind('\\'):]  # add on just filename of the dyr file
    hybrid_dyr = hybrid_dyr[:-4] + locals()['dyr_suffix'] + ".dyr"  # modify the full dyr path and filename to include the _hybrid suffix

# cache boundary bus count:
len_boundary = len(locals()['boundary_buses'])

# check that boundary buses are in the case. If one is missing, stop script:
for ibus in locals()['boundary_buses']:
    error, type_ = psspy.busint(ibus, "TYPE")
    if error:
        print(" Boundary bus " + str(ibus) + " is not in the base case")
        print(" Execution of the python script is abandoned")
        print(" Exiting from the python script\n")
        RedirectOutputToPSSE()
        raise SystemExit()

# check that boundary buses to see whether they are dummy buses associated with multi-section line. 
# If one dummy bus is found, stop script: 
for ibus in locals()['boundary_buses']: 
    ierr = psspy.inibrx(ibus, 1) 
    if ierr == 3:
        print(" Boundary bus " + str(ibus) + " is a dummy bus of a PSSE multisection line")
        print(" Boundary bus cannot be a dummy bus")
        print(" Execution of the python script is abandoned")
        print(" Exiting from the python script\n")
        RedirectOutputToPSSE()
        raise SystemExit()
      
# check that deleted buses are in the case. Remove missing ones from list:
for ibus in locals()['deleted_buses']:
    error, type_ = psspy.busint(ibus, 'TYPE')
    if error:
        s = " Bus to be deleted (i.e. Bus " + str(ibus) + ") is not in "
        s += "the base case, will continue.....\n"
        print(s)

        # delete the bus from the "DeletedBus" list:
        locals()['deleted_buses'].remove(ibus)

if not _IS_EQ:# disable boundary branches and shunts in hybrid only
    
    # disconnect the deleted buses:
    for ibus in locals()['deleted_buses']:
        psspy.dscn(ibus)
    
    # disconnect lines between the boundary buses
    # note that in V30, VSC DC links between boundary busses are not deleted
    # (due to the unavailablity of API routines in V30)

    # Create a bus subsystem for the circuit we are keeping in PSCAD

    sid = 1
    numbus=len(locals()['boundary_buses'])
    buses=locals()['boundary_buses']
    usekv = 0
    basekv = []
    numarea = 0
    areas = []
    numowner = 0
    owners = []
    numzone = 0
    zones = []

    err = psspy.bsys(sid, usekv, basekv, numarea, areas, numbus, buses, numowner, owners, numzone, zones)

    # Find and disable all branches in the circuit we are keeping in PSCAD:

    owner = 1  # to use bus ownership
    ties = 1  # for interior subsystem branches only
    flag = 1  # for only in - service non - transformer branches
    entry = 1  # for single entry (each branch once)

    string = ['FROMNUMBER', 'TONUMBER']
    err, (ibuses, jbuses) = psspy.abrnint(sid, owner, ties, flag, entry, string)

    string = ['ID']
    err, (ckts,) = psspy.abrnchar(sid, owner, ties, flag, entry, string)

    for ibus, jbus, ckt in zip(ibuses, jbuses, ckts):
        psspy.branch_data(ibus, jbus, ckt, intgar1=0)

    # Find and disable all 2 winding xfmrs in the circuit we are keeping in PSCAD:

    string = ['FROMNUMBER', 'TONUMBER']
    err, (ibuses, jbuses) = psspy.atrnint(sid, owner, ties, flag, entry, string)

    string = ['ID']
    err, (ckts,) = psspy.atrnchar(sid, owner, ties, flag, entry, string)

    for ibus, jbus, ckt in zip(ibuses, jbuses, ckts):
        psspy.two_winding_data(ibus, jbus, ckt, intgar1=0)

    # Find and disable all DC Links in the circuit we are keeping in PSCAD:

    if PSSE_VER == 30:
        for dc_id in range(1, 51):  # for two terminal DC
            ierr, recbus = psspy.dc2int(dc_id, 'RECT')
            if ierr == 2:
                continue
            for ibus in locals()['boundary_buses']:
                if recbus == ibus:
                    ierr, invbus = psspy.dc2int(dc_id, 'INV')
                    for jbus in locals()['boundary_buses']:
                        if invbus == jbus:
                            psspy.two_terminal_dc_line_data(dc_id, intgar1=0)

    else:  # PSSE VER 31+
        string = ['DCNAME']  # for two terminal DC
        err, (dc_names,) = psspy.a2trmdcchar(sid, ties, flag, string)

        for dc_name in dc_names:
            psspy.two_terminal_dc_line_data(dc_name, intgar1=0)

        string = ['VSCNAME']  # for two terminal VSC DC
        err, (vsc_names,) = psspy.avscdcchar(sid, ties, flag, string)

        for vsc_name in vsc_names:
            err = psspy.vsc_dc_line_data(vsc_name, intgar1=0)
    
    # disable shunt devices at boundary busses. Note that GNE devices at boundary
    # busses (introduced in V32 - from V32 and onwards) are not deleted by this
    # script.

    # disable all loads at the boundary busses:

    flag = 1  # for only in - service loads
    string = ['NUMBER']
    err, (ibuses,) = psspy.aloadint(sid, flag, string)

    string = ['ID']
    err, (loadids,) = psspy.aloadchar(sid, flag, string)

    for ibus, loadid in zip(ibuses, loadids):
        ierr = psspy.load_data(ibus, loadid, intgar1=0)

    # disable all bus shunts at the boundary busses:

    flag = 1  # for only in-service fixed shunts

    if PSSE_VER <= 31:  # bus code must also be set to 2
        for ibus in locals()['boundary_buses']:
            psspy.bus_data(ibus, intgar1=2, realar1=0.0, realar2=0.0)

    if PSSE_VER > 30:
        string = ['NUMBER']
        ierr, (ibuses,) = psspy.afxshuntint(sid, flag, string)

        string = ['ID']
        ierr, (shuntids,) = psspy.afxshuntchar(sid, flag, string)

        for ibus, shuntid in zip(ibuses, shuntids):
            ierr = psspy.shunt_data(ibus, shuntid, 0)

    # disable all switched shunts at the boundary busses:

    flag = 1  # for only in - service switched shunts
    string = ['NUMBER']
    err, (ibuses,) = psspy.aswshint(sid, flag, string)

    for ibus in ibuses:
        psspy.switched_shunt_data_3(ibus, intgar11=0)

    # out-of-service the existing FACTS devices (with only shunt element -
    # ie STATCOMS) at the boundary busses:

    if PSSE_VER > 30:
        flag = 1  # for only in - service FACTS devices
        ties = 1  # for interior subsystem FACTS only
        fcttyp = 1  # for only FACTS devices without a series element

        string = ['FACTSNAME']
        err, (ibuses,) = psspy.afactschar(sid, ties, flag, fcttyp, string)
        for ibus in ibuses:
            psspy.facts_data_2(ibus, intgar3=0, intgar5=0)

    else:  # version 30:
        for factid in range(1, 51):
            err, sendbus = psspy.fcdint(factid, 'SEND')
            if err:
                continue
            for ibus in locals()['boundary_buses']:
                if sendbus == ibus:
                    err, termbus = psspy.fcdint(factid, 'TERM')
                    if termbus == 0:
                        psspy.facts_data(ibus, intgar3=0, intgar5=0)

    # disable generators existing at the boundary busses:

    flag = 1  # for only in-service generators

    string = ['NUMBER']
    err, (gen_buses,) = psspy.amachint(sid, flag, string)

    string = ['ID']
    err, (machids,) = psspy.amachchar(sid, flag, string)

    for ibus, machid in zip(gen_buses, machids):
        psspy.machine_data_2(ibus, machid, intgar1=0, intgar6=0)

    # finished disabling all the required shunt devices at the boundary busses

    # Disable all 3 winding transformers in the circuit we are keeping in PSCAD    

    string = ['WIND1NUMBER', 'WIND2NUMBER', 'WIND3NUMBER']
    err, (ibuses, jbuses, kbuses) = psspy.atr3int(sid, owner, ties, flag, entry, string)

    string = ['ID']
    err, (ckts,) = psspy.atr3char(sid, owner, ties, flag, entry, string)

    for ibus, jbus, kbus, ckt in zip(ibuses, jbuses, kbuses, ckts):
        psspy.three_wnd_impedance_data(ibus, jbus, kbus, ckt, intgar8=0)
else:# Equivalent Work, rather then disabling things we outright delete them
    
    # create a new subsystem and fill it with everything to delete
    # then "extr" to delete the subsystem and all connected branches
    
    #Note: it is faster (espec. in larger deletes) to delete 1 bus at a time then to delete them all at once
    for ibus in locals()['deleted_buses']:
        psspy.bsysinit(11)
        psspy.bsyso(11, ibus)
        psspy.extr(11 , 0, [0, 0])


nodes = len(locals()['boundary_buses'])

gen_r_temp = locals()['gen_r']
gen_x_temp = locals()['gen_x']
ibus_temp = locals()['boundary_buses']
jbus_temp = locals()['boundary_buses']

if not _IS_EQ:# Hybrid work
    
    # Add the interface generators (with generator ID specified in "locals()['genid']")
    # at the boundary busses. The diagonal source impedance and off-diagonal
    # branches are computed by E-TRAN using an LDU reduction of the kept network:
    
    k = -1
    for i in range(0, nodes):
        k += 1
        # Add the off-diagonal branches at the boundary busses:
        for j in range(i + 1, nodes):
            k += 1
        
            if abs(gen_r_temp[k]) > 1.0E-10 or abs(gen_x_temp[k]) > 1.0E-10:
                ibus = ibus_temp[i]
                jbus = jbus_temp[j]          
                machid = locals()['genid']
                basemva = locals()['BMVA']
                err, ibus_BASEKV = psspy.busdat(ibus, 'BASE')
                err, jbus_BASEKV = psspy.busdat(jbus, 'BASE')
        
                if ibus_BASEKV == jbus_BASEKV: # off diagonal is a transmission line
                    err = psspy.branch_data(ibus, jbus, machid, [1, ibus, 1, 0, 0, 0],
                                        [gen_r_temp[k], gen_x_temp[k], 0.0, 0.0, 0.0,
                                        0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0,
                                        1.0, 1.0])

                    s = " ****** Equivalent Branch added between busses: %d and %d"
                    s += " - Error Status = %d."
                    print(s % (ibus, jbus, err))

                else:  # # off diagonal is a transformer
                    err, realaro = psspy.two_winding_data(ibus, jbus, machid,[1,ibus,1,0,0,0,33,0,ibus,0,1,0,1,1,1],[gen_r_temp[k], gen_x_temp[k], basemva, 1.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0, 1.0, 1.0, 1.0, 1.0,0.0,0.0, 1.1, 0.9, 1.1, 0.9,0.0,0.0],"")    
                    s = " ****** Equivalent transformer added between busses: %d and %d"
                    s += " - Error Status = %d."
                    print(s % (ibus, jbus, err))

    # Add  diagonal sources at the boundary busses:
    k = -1
    for i in range(0, nodes):
        k += 1
        ibus = ibus_temp[i]
        # extracting the interface point bus voltage:
        err, vpu = psspy.busdat(ibus, 'PU')

        # calculating the bus mismatch:
        err, mismatch = psspy.busmsm(ibus)

        # if the case is <= 31, bus code is set to 2 at the time of deleting the
        # bus shunts:
        if PSSE_VER > 31:
            # set the TYPE of the interface bus to 2 (i.e a generator bus):
            psspy.bus_data(ibus, intgar1=2)

        # add the plant:
        psspy.plant_data(ibus, 0, (1.0, 100.0))

        gen_z = complex(gen_r_temp[k], gen_x_temp[k])
        basemva = locals()['BMVA']
        err = 99

        if PSSE_VER <= 30:

            if gen_r_temp[k] < 0.0 or gen_x_temp[k] < 0.0:
                # add shunt R and X to ground:
                y = 1.0 / gen_z
                scalc = y * basemva

                err = psspy.bus_data(ibus,[_i,_i,_i,_i],[scalc.real, scalc.imag,_f,_f,_f],"")

                s = " ****** Equivalent Shunt added at bus: %d, Error Status = %d."
                print(s % (ibus, err))

                # Calculate source PQ flow:
                sload = scalc.conjugate() * vpu ** 2
                scalc = -mismatch + sload

                # add 999 ZSource:
                err = psspy.machine_data(ibus, locals()['genid'], [1, 1, 0, 0, 0],
                                    [scalc.real, scalc.imag, scalc.imag,
                                        scalc.imag, 9999.0, -9999.0, basemva,
                                        0.0, 999.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0,
                                        1.0])

                s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
                print(s % (ibus, err))

            else:  # r > 0 and x > 0):

                if abs(gen_z) > 1.0E-10:

                    # add ZSource:
                    err = psspy.machine_data(ibus, locals()['genid'], [1, 1, 0, 0, 0],
                                        [-mismatch.real, -mismatch.imag,
                                            -mismatch.imag, -mismatch.imag,
                                            9999.0, -9999.0, basemva,
                                            gen_r_temp[k], gen_x_temp[k],
                                            0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0])

                else:  # gen_z < 1E-10:
                    # add 999 ZSource:
                    err = psspy.machine_data(ibus, locals()['genid'], [1, 1, 0, 0, 0],
                                        [-mismatch.real, -mismatch.imag,
                                            -mismatch.imag, -mismatch.imag,
                                            9999.0, -9999.0, basemva, 0.0, 999.0,
                                            0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0])

                s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
                print(s % (ibus, err)) #TODO BZ test the difference here

        else:  # PSSE version 31+

            if gen_r_temp[k] < 0.0 or gen_x_temp[k] < 0.0:
                # add shunt R and X to ground:
                y = 1.0 / gen_z
                scalc = y * basemva
                err = psspy.shunt_data(ibus, locals()['genid'], 1, [scalc.real, scalc.imag])

                s = " ****** Equivalent Bus Shunt added at bus: %d, Error Status = %d."
                print(s % (ibus, err))

                # Calculate source PQ flow:
                sload = scalc.conjugate() * vpu ** 2
                scalc = -mismatch + sload

                # add 999 ZSource:
                err = psspy.machine_data_2(ibus, locals()['genid'], [1, 1, 0, 0, 0, 0],
                                        [scalc.real, scalc.imag, scalc.imag,
                                        scalc.imag, 9999.0, -9999.0, basemva,
                                        0.0, 999.0, 0.0, 0.0, 1.0, 1.0, 1.0,
                                        1.0, 1.0, 1.0])

                s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
                print(s % (ibus, err))

            else:  # r > 0 and x > 0):

                if abs(gen_z) > 1.0E-10:
                    # add ZSource:
                    err = psspy.machine_data_2(ibus, locals()['genid'], [1, 1, 0, 0, 0, 0],
                                            [-mismatch.real, -mismatch.imag,
                                            -mismatch.imag, -mismatch.imag,
                                            9999.0, -9999.0, basemva,
                                            gen_r_temp[k], gen_x_temp[k],
                                            0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0,
                                            1.0])

                else:  # gen_z < 1E-10:

                    # add 999 ZSource
                    err = psspy.machine_data_2(ibus, locals()['genid'], [1, 1, 0, 0, 0, 0],
                                            [-mismatch.real, -mismatch.imag,
                                            -mismatch.imag, -mismatch.imag,
                                            9999.0, -9999.0, basemva,
                                            0.0, 999.0, 0.0, 0.0, 1.0,
                                            1.0, 1.0, 1.0, 1.0, 1.0])

                s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
                print(s % (ibus, err))

        psspy.plant_data(ibus, _i, [vpu, _f])

        # increment k:
        for j in range(i + 1, nodes):
            k = k + 1
else: #equivalent work
    
    # Using the equivalent data provided by ETRAN
    # Write each given component in the EQ
    
    basemva = locals()['BMVA']
    for component in gen_r_temp: # for each  given component
        compType = component[0]
        if compType == 2: # source -> [type, ibus, 0(jbus), ckt, pg, qg, r, x]
            err, vpu = psspy.busdat(component[1], 'PU')
            psspy.bus_data(component[1], intgar1=2)
            psspy.plant_data(component[1], 0, (1.0, 100.0))
            err = 99
            r = component[6]
            x = component[7]
            if PSSE_VER <= 30:
                if component[6] < 0.0 or component[7] < 0.0:
                    # TODO: Investigate these shunts further as they are being added in the hybrid but skew the values in the system in the equivalent, leave out for now
                    # err = psspy.bus_data(component[1],[_i,_i,_i,_i],[component[4], component[5],_f,_f,_f],"")
                    # s = " ****** Equivalent Shunt added at bus: %d, Error Status = %d."
                    # print(s % (component[1], err))
                    r = 0.0
                    x = 999.0
                err = psspy.machine_data(component[1], component[3], [1, 1, 0, 0, 0],
                                            [component[4], component[5], component[5], component[5],
                                            9999.0, -9999.0, basemva, r, x,
                                            0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0])
                s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
                print(s % (component[1], err))
            else:  # PSSE version 31+
                if component[6] < 0.0 or component[7] < 0.0:
                    # err = psspy.shunt_data(component[1], component[3], 1, [component[4], component[5]])
                    # s = " ****** Equivalent Bus Shunt added at bus: %d, Error Status = %d."
                    # print(s % (component[1], err))
                    r = 0.0
                    x = 999.0                    
                err = psspy.machine_data_2(component[1], component[3], [1, 1, 0, 0, 0, 0],
                                            [component[4], component[5], component[5], component[5],
                                            9999.0, -9999.0, basemva, r, x,
                                            0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
                s = " ****** Equivalent Machine added at bus: %d, Error Status = %d."
                print(s % (component[1], err))               
            psspy.plant_data(component[1], _i, [vpu, _f])
        elif compType == 5: # branch -> [type, ibus, jbus, ckt, R, X]
            err = psspy.branch_data(component[1], component[2], component[3], [1, component[1], 1, 0, 0, 0],
                                    [component[4], component[5], 0.0, 0.0, 0.0,
                                    0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0,
                                    1.0, 1.0])
            s = " ****** Equivalent Branch added between busses: %d and %d"
            s += " - Error Status = %d."
            print(s % (component[1], component[2], err))
        elif compType == 4: # transformer -> [type, ibus, jbus, ckt, r, x]
            err, realaro = psspy.two_winding_data(component[1], component[2], component[3] ,[1,component[1],1,0,0,0,33,0,component[1],0,1,0,1,1,1],[component[4], component[5], basemva, 1.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0, 1.0, 1.0, 1.0, 1.0,0.0,0.0, 1.1, 0.9, 1.1, 0.9,0.0,0.0],"")    
            s = " ****** Equivalent transformer added between busses: %d and %d"
            s += " - Error Status = %d."
            print(s % (component[1], component[2], err))
            

# save the interface added power flow case:
psspy.save(hybrid_sav)

# At this point islands must be checked. An island detected means the original type 3 
# bus/es are delted from the PSSE case. If that is the case, then make one interface bus  
# to be type 3. This ensures the _hybird case can be subsiquently solved using power flow solution algorithms.
# However, adding a trpe 3 bus is not essential, as _hybird is prepared for PSSE dynamics (i.e activity
# CONG would change this back to type 2 bus prior to dynamic simulation. 

islands = psspy.chktre()
if islands > 0:
    print(" Island detected....One of the interface bus with generator is converted to Type 3....")
    psspy.alert_output(6,"",[0,0])  # supress the warning messages temporary
    psspy.bus_data(ibus_temp[0], intgar1=3)
    psspy.lines_per_page_one_device(2,10000000)
    psspy.alert_output(1,"",[0,0])
    psspy.save(hybrid_sav)

# At this point there cannot be any inlands, since a multiport network is modeled at the boundary buses.

# This time the power flow should converge in the same number of iterations

if best_attempt < 3:
    psspy.fdns(settings[best_attempt])
else:
    psspy.fnsl(settings[best_attempt])

itr = psspy.iterat()  # checking the number of iterations
itr_best = itr_best + 1 # add one iteration 
if itr > itr_best:
    print(" Power flow case did not converge in the same or lower number of iterations....Please check")

psspy.save(hybrid_sav)

if not _IS_EQ:
    # Write dynamics records for the Hybrid interface (i.e adding ETRCOM, INITAD,
    # CHOUT models) The content is written to a temporary file as specified in
    # Hybrid_dyr

    # this is the dyre string we will build up:

    dyrstrPrefix = " / E-Tran Hybrid (PSSE-PSCAD Co-Simulation) models have been added to the end of this file\n\n"

    # maintain list of existing USRMSC and MINS ids
    existingIds = set()
    # keep track of the ids of the following list of models to avoid conflicts with EINIT,CHOUT and CHIN record
    modelsListForIdHandling = ["USRMSC", "FRQDCAT", "FRQTPAT", "SAT2T", "SWCAPT", "VTGDCAT", "VTGTPAT"]

    dyrstr = ""

    commentingOutRecord = False

    commentedOutModels = []

    # Read in the existing DYR file and
    if not (os.path.isfile(locals()['dyr_path'])):
        print(" The .dyr file " + locals()['dyr_path'] + " does not exist.\n")
        print(" The script will now exit.\n")
        RedirectOutputToPSSE()
        print("\n The .dyr file " + locals()['dyr_path'] + " does not exist.\n")
        print(" The script will now exit.\n")
        raise SystemExit()
        
    else:
        f = open(locals()['dyr_path'])
        for line in f:
            # build up the hybrid DYR
            if not commentingOutRecord:
                # keep track of all the USRMSC and MINS ids
                if any(model in line.upper() for model in modelsListForIdHandling):
                    # use whitespace and commas as delimiters, and throw away any leading empty string
                    lineSplit = filter(None, re.split("[\s,]+", line))
                    if lineSplit[0].isdigit():                
                        existingIds.add(int(lineSplit[0]))
                # if there is a model with ckt id of HB matching one of the generator busses, comment it out to prevent issues
                if line.upper().find("HB") >= 0:
                    # use whitespace and commas as delimiters, and throw away any leading empty string
                    lineSplit = filter(None, re.split("[\s,]+", line))
                    if len(lineSplit) >= 3 and lineSplit[0].isdigit() and lineSplit[2].upper().find("HB") >= 0:
                        if int(lineSplit[0]) in gen_buses:
                            commentingOutRecord = True
                            commentedOutModels.append(lineSplit[0] + " " + lineSplit[1] + " " + lineSplit[2])
            
            if commentingOutRecord:
                dyrstr += "/ " + line
            else:            
                dyrstr += line
                
            if line.find("/") >= 0:
                commentingOutRecord = False
        f.close()


    if gen_buses:
        dyrstrPrefix += " / The following pre-existing generator models with machine ID 'HB' have been commented out\n"
        dyrstrPrefix += " / in this DYR file to prevent conflicts with the models added for the hybrid simulation:\n"
        for model in commentedOutModels:
            dyrstrPrefix += " / " + model + " ...\n"
        dyrstrPrefix += "\n"
                    
    dyrstr = dyrstrPrefix + dyrstr

    inout_cnt = 0

    if locals()['add_chout']:
        inout_cnt += 1

    if locals()['add_chin']:
        inout_cnt += 1

    inout_cnt += len_boundary

    minsVal = 0

    if locals()['new_dyr_format']:
        if existingIds:
            minsVal = max(existingIds)    
        minsVal += 1
        dyrstr += str(minsVal) + ", 'USRMSC', 'EINIT', 512, 0, 7, 0, 0, 0, "
        dyrstr += " 1" + ", "
        existingIds.add(minsVal)
    else:
        dyrstr += "0, 'USRMDL', 0, 'EINIT', 8, 0, 7, 0, 0, 0, "
        dyrstr += " 1" + ", "

    ip_addr = locals()['ip_addr']
    port = locals()['start_port']
    dyrstr += str(port) + ", " + str(port + inout_cnt - 1) + ", " + ip_addr + " / \n\n"
        

    # adding the ETRCOM model at each interface point:

    port = locals()['start_port']

    for ibus in locals()['boundary_buses']:
        dyrstr += str(ibus) + ", " + "'USRMDL', HB, 'ETRCOM', 1, 1, 5, 0, 0, 20, "
        dyrstr += str(port) + ", 0, 0, 0, 0 / \n\n"
        port += 1

    if locals()['add_chout']:
        if locals()['new_dyr_format'] == 0:
            dyrstr += "0, 'USRMDL', 0, 'CHOUT', 0, 2, 4, 0, 0, 10, "
            dyrstr += str(port)
            dyrstr += ", 0, 0, 0 / \n\n"
        else:
            if existingIds:
                minsVal = max(existingIds)   
            minsVal += 1
            dyrstr += str(minsVal) + ", 'USRMSC', 'CHOUT', 512, 2, 4, 0, 0, 10, "
            dyrstr += str(port)
            dyrstr += ", 0, 0, 0 / \n\n"
            existingIds.add(minsVal)
        port += 1

    if locals()['add_chin']:
        if locals()['new_dyr_format']:
            if existingIds:
                minsVal = max(existingIds)   
            minsVal += 1
            dyrstr += str(minsVal) + ", 'USRMSC', 'CHIN', 512, 0, 4, 0, 0, 10, "
            dyrstr += str(port)
            dyrstr += ", 0, 0, 0 / \n\n"
            existingIds.add(minsVal)
        else:
            dyrstr += "0, 'USRMDL', 0, 'CHIN', 8, 0, 4, 0, 0, 10" + ", "
            dyrstr += str(port)
            dyrstr += ", 0, 0, 0 / \n\n"

    # dump dyre string to file:
    dyrfile = open(hybrid_dyr, 'w')
    dyrfile.write(dyrstr)
    dyrfile.close()

RedirectOutputToPSSE()

print(" " + runType + " SAV file written to " + hybrid_sav)

if not _IS_EQ:
    print(" Hybrid DYR file written to " + hybrid_dyr)

print(" DONE")



