# Sample N-1 contingency analysis automation with SimAuto

# Analyze the effect of removing any grid component under progressivly higher loads, and place data into an excel file for each violating branch

# -------------------------------------------------------------------------------------------------

# IMPORT TOOLS

# Import SimAuto
import win32com.client 
simauto = win32com.client.Dispatch('pwrworld.SimulatorAuto')

# Import csv, a python csv-file analysis tool
import csv

# Import os.path, another analysis tool
import os.path


# !!! SEVERAL VARIABLES MUST BE ASSIGNED BEFORE USING THIS CODE !!!

# TODO: assign variable 'filename' as the path to the pwb file you wish to analyze 
# For example: filename = 'C:\PowerWorld\Cases\B7FLAT.pwb'

# TODO: assign variable 'aux_path' as path to folder that contains the pwb file used in analysis (this is the same path as 'filename', minus the specific pwb file)
# NOTE: this string must end with a double backslash 
# For example: aux_path = 'C:\PowerWorld\Cases\\'

# TODO: assign variable 'max' as maximum desired load scale factor 
# For example: max = 1.5 

# TODO: assign variable 'step' as the desired incremental increase in the load's scale factor as it climbs from 1 to max
# For example: step = 0.02

# ----------


# PERFORM N-1 CONTINGENCY ANALYSIS UNDER INCREASING LOADS 

# Open case found at filename 
(err,) = simauto.OpenCase(filename)
if err:
        raise Exception(f'Error: {err}')

# Define 'scale' function, which changes the load on a system by a specific factor 
def scale(factor):
        # To scale the case, parameter 'SCALE' must be set to YES for each object. If desired, filters can be added to only scale part of the case
        (err,) = simauto.RunScriptCommand('SetData(Area, [SCALE], ["YES"], All);')
        if err:
                raise Exception(f'Error: {err}')
        # 'Scale' script command increases all parameters for which 'SCALE' is set to YES by a scale factor (in this case, the arguement to our scale() function)
        (err,) = simauto.RunScriptCommand('Scale(LOAD, FACTOR, [%f], AREA);' % factor)
        if err:
                raise Exception(f'Error: {err}')

        # This scaled case must be set as the reference case, thus preserving the scaled values when contingency analysis begins 
        (err,) = simauto.RunScriptCommand('CTGSetAsReference;')
        if err:
                raise Exception(f'Error: {err}')

# Define 'analyze' function, which performs N-1 contingency analysis for each branch
def analyze(factor):
        # Run script commands that create a list of each possible single-branch contingency, then insert these contingencies into the contingency analysis tool
        (err,) = simauto.RunScriptCommand('SetData(Ctg_AutoInsert_Options, [ElementType], [BRANCH]);')
        if err:
                raise Exception(f'Error: {err}')
        (err,) = simauto.RunScriptCommand('CTGAutoInsert;')
        if err:
                raise Exception(f'Error: {err}')

        # Solve each contingency 
        (err,) = simauto.RunScriptCommand('CTGSolveAll;') 
        if err:
                raise Exception(f'Error: {err}')

        # Save a csv file that shows each branch that exceeded its limit during contingency analysis
        percent = 100 * factor
        percent = round(percent)
        (err,) = simauto.RunScriptCommand('CTGSaveViolationMatrices("ViolationMatrices_%i.CSV", CSVCOLHEADER, YES, [BRANCH], YES, NO);' %percent)
        if err:
                raise Exception(f'Error: {err}')

# Perform N-1 contingency analysis, increase load by 'step' value, and repeat (until system reaches desired 'max' load)
factor = 1
(err,) = simauto.RunScriptCommand('EnterMode(RUN);')
if err:
        raise Exception(f'Error: {err}')

while factor <= max:
        # Scale case by 'factor', and run contingency analysis
        scale(factor)
        analyze(factor)
        
        # Return loads to initial state 
        inverse = 1 / factor
        scale(inverse)
        
        # Increase factor by 'step', and repeat until max
        factor += step
        factor = round(factor, 4)


# ----------

# CREATE AN EXCEL FILE DOCCUMENTING THE VIOLATIONS OF EACH BRANCH THAT EXCEEDED LIMIT DURING N-1 ANALYSIS 

# Convert variables to percentage values (which will be more useful in csv files)
file_number = 100
file_max = max * 100 
file_step = step * 100

# Copy all data from each branch into individual excel files 
while file_number <= file_max:
        
        # Open an auxiliary file and read all data from that file into violation_data, a list that will contain each row and column of the csv file in a 2-dimensional matrix
        violation_data = [] 
        with open(os.path.join(aux_path, f'ViolationMatrices_{file_number}_Contingency.csv')) as csv_file:
                # Create 'csv_reader' object    
                csv_reader = csv.reader(csv_file)
                
                # Read each row of csv_file into violation_data
                for row in csv_reader: 
                        violation_data.append(row)
                
                # Close file - for more information on csv tools, see the documentation for python's csv library
                csv_file.close()
        
        # Create file for each branch that records a violation during the analysis
        if len(violation_data) != 0: 
                j = 1
                length = len(violation_data[0]) - 1
                
                # Iterate through each column of violation_data
                # NOTE: violation_data [0][1] represents the second column of the top row of violation_data matrix 
                for j in range(length):
                        # Find the name of the violating branch at the top row of each column
                        branch = violation_data[0][j]
                        
                        # Check to see if a file for this violating branch exists already
                        file_exists = os.path.exists(os.path.join(aux_path, f'{branch}.csv'))
                        
                        # Create (or open) file for this branch, and make a writer object
                        new_file = open(os.path.join(aux_path, f'{branch}.csv'), 'a+', newline = '')
                        csv_writer = csv.writer(new_file)
                        
                        # If this branch has zero previous violations, write headers into csv file
                        if not file_exists: 
                                csv_writer.writerow(['Load Scale Percentage', 'Percentage of Limit', 'Contingency Name'])
                                
                        # Write load percent, contingency name, and limit percent for each violation into new branch file by iterating through each 
                        i = 1
                        while i < len(violation_data): 
                                # Iterate through each row of column j, writing the load percentage, limit percentage, and contingency name into new csv file
                                if violation_data[i][j] != '':
                                        csv_writer.writerow([file_number, violation_data[i][j], violation_data[i][0]])
                                
                                # Advance to next row
                                i += 1
                        
                        # Close file, advance to next column
                        new_file.close()
                        j += 1

                # Advance to next load 
                file_number += file_step
                file_number = round(file_number) 

# SUGGESTIONS ON HOW TO BEST GRAPH THIS DATA IN EXCEL CAN BE FOUND AT POWERWORLD.COM

# Close SimAuto
del(simauto)
