from copy import deepcopy
import numpy as np
import pandas as pd
import cmath
import math
import datetime
import pypsa
from numpy import genfromtxt
from colorama import Fore, Back, Style
from termcolor import colored

# Require :
# - numpy
# - pandas

# - linopy 
# - cartopy

class Grid:

  def __init__( self, nbus, LINE_DATA_CSV, BUS_DATA_CSV, MVA_base, KV_base, SLACK_POS ):


    #Cleaning data from CSV
    dummy_data = genfromtxt(LINE_DATA_CSV, delimiter=',')
    dummy_data[:,0] = np.arange(1,len(dummy_data)+1).astype(int)
    dummy_data[:,1] = dummy_data[:,1].astype(int)
    dummy_data[:,2] = dummy_data[:,2].astype(int)
    
    self.line_data = pd.DataFrame( dummy_data [:,:5] , columns=['No','FromBus','ToBus','R','X'] )
    # self.line_data = pd.read_csv( LINE_DATA_CSV  , names=['No','FormBus','ToBus','R','X','S'] ).dropna(axis=1)
    self.line_data.set_index('No',inplace=True)
    self.line_data['FromBus'] = self.line_data['FromBus'].astype(np.uint8)
    self.line_data['ToBus'] = self.line_data['ToBus'].astype(np.uint8)
    #Convert to numeric, good guide(https://stackoverflow.com/questions/15891038/change-column-type-in-pandas)
    self.line_data.R = pd.to_numeric(self.line_data.R).astype(float).to_frame()
    self.line_data.X = pd.to_numeric(self.line_data.X).astype(float).to_frame()
    del dummy_data 
    print(self.line_data)

    #Cleaning data from CSV
    dummy_data = genfromtxt( BUS_DATA_CSV, delimiter=',')
    dummy_data[:,0] = np.arange(1,nbus+1).astype(int)
    dummy_data  = dummy_data [:,:3]
    self.load_data = pd.DataFrame( dummy_data , columns=['BusID','P_KW','Q_KVAr'] )
    self.load_data['BusID'] = self.load_data['BusID'].astype(np.uint8)
    del dummy_data 
    print(self.load_data)

    #Convert to numeric
    self.load_data.P_KW = pd.to_numeric(self.load_data.P_KW).astype(float)
    self.load_data.Q_KVAr= pd.to_numeric(self.load_data.Q_KVAr).astype(float)
    
    # self.load_data = pd.read_csv(BUS_DATA_CSV)
    # self.load_data.columns =['BusID','P_KW','Q_KVAr']

    # self.load_data = self.load_data[self.load_data['P_KW'] != 0][self.load_data['Q_KVAr'] != 0]

    self.slack_bus_pos = SLACK_POS 
    self.LINE_DATA_CSV = LINE_DATA_CSV 
    self.BUS_DATA_CSV = BUS_DATA_CSV  

    self.MVA_base = MVA_base   
    self.KV_base = KV_base
    self.Z_base = KV_base * KV_base / MVA_base 
    self.Y_base = 1 / self.Z_base

    # self.line_data.R = self.line_data.R 
    # self.line_data.X = self.line_data.X
    # self.load_data.P_KW = self.load_data.P_KW
    # self.load_data.Q_KVAr = self.load_data.Q_KVAr

    self.Bus = pd.DataFrame( np.zeros((nbus,4)), columns=['v_mag','v_ang','P_pu','Q_pu']  )
    self.nBus = nbus  

    self.total_kW_demand    = self.load_data.P_KW.sum() 
    self.total_kW_supply    = 0
    self.total_kVAr_demand  = self.load_data.Q_KVAr.sum()
    self.total_kVAr_supply  = 0
    self.Grid_kW_supply = 0
    self.Grid_kVAr_supply = 0 
    self.Grid_kW_loss = 0
    self.Grid_kVAr_loss = 0


    # self.reset_network()
    self.total_kW_loss = 0
    self.total_kVAr_loss = 0

    self.transmission_kW_loss_LPP =0
    self.transmission_kW_loss_LQQ =0
    self.transmission_kW_loss_LPQ =0

    # My special parameters (Transfer Voltage Stability Index)
        
    self.S = np.zeros(nbus, dtype=complex)
    self.V = np.zeros(nbus, dtype=complex)

    self.Psi_k = np.zeros(nbus, dtype=complex)
    self.Psi_p = np.zeros(nbus)
    self.Psi_q = np.zeros(nbus)
    
    self.Psi_P_SNB = np.zeros(nbus)
    self.Psi_Q_SNB = np.zeros(nbus)

    self.Ybus= np.zeros((nbus,nbus), dtype=complex) 

    self.Psi_P_SNB = np.zeros(nbus)
    self.Psi_Q_SNB = np.zeros(nbus)

    # Will be removed
    self.Theta = np.zeros(nbus)  
    self.Psi_d = np.zeros(nbus, dtype=complex)
    self.Psi_dk = np.zeros(nbus, dtype=complex)
    self.alpha = np.zeros(nbus)
    self.reset_PYPSAnetwork()
    self.LoadFlow()
    # self.org_system.network  = deepdopy(self.network)

  def reset_PYPSAnetwork(self):

    self.network = pypsa.Network()

    self.Bus = pd.DataFrame( np.zeros((self.nBus,4)), columns=['v_mag','v_ang','P_pu','Q_pu']  )
    
    #Add busess           
    for i in range(self.nBus):
      self.network.add("Bus",name="Bus No {}".format(int(i+1)), v_mag_pu_set = 1.0)

    #Add loads           
    for i in range(self.nBus):
      # try :
      self.network.add("Load", "Load No {}".format(int(i+1)),
          bus = "Bus No {}".format(int(i+1)),
          p_set =list(  self.load_data['P_KW'][self.load_data['BusID']==i+1] / 1000 / self.MVA_base)[0],
          q_set = list( self.load_data['Q_KVAr'][self.load_data['BusID']==i+1] / 1000 / self.MVA_base)[0])
      # except :
      #   self.network.add("Load", "Load No {}".format(i+1),
      #       bus = "Bus No {}".format(i+1),
      #       p_set = 0,
      #       q_set = 0)

    #Add lines
    for i in range(len(self.line_data.ToBus.loc[:])):
      self.network.add("Line","Line {}".format(str(int(self.line_data.FromBus.iloc[i]))+str('-')+ str(int(self.line_data.ToBus.iloc[i]))),              
                  bus0 = "Bus No {}".format(int(self.line_data.FromBus.iloc[i])),
                  bus1 = "Bus No {}".format(int(self.line_data.ToBus.iloc[i])),
                  r = self.line_data.R.iloc[i] / self.Z_base,
                  x = self.line_data.X.iloc[i] / self.Z_base)

    # print(network.lines.head())
    # print('-----------------------------------------------')
    #slack generator
    self.network.add("Generator","Slack Gen",
          bus = "Bus No {}".format(int(self.slack_bus_pos)),
          control = 'Slack',
          v_mag_pu_set = 1.00 )


  def LoadFlow(self): 

    # slack_id = self.slack_bus_pos - 1   
    self.network.pf()

    self.Bus.v_mag = np.array( self.network.buses_t.v_mag_pu.iloc[0][:] )
    self.Bus.v_ang = np.array( self.network.buses_t.v_ang.iloc[0][:] )
    self.Bus.P_pu = np.array( self.network.buses_t.p.iloc[0][:] )
    self.Bus.Q_pu = np.array( self.network.buses_t.q.iloc[0][:] )

    sum_supply_p = self.network.generators_t.p.sum().sum()
    sum_demand_p = self.network.loads_t.p.sum().sum()
    sum_supply_q = self.network.generators_t.q.sum().sum()
    sum_demand_q = self.network.loads_t.q.sum().sum()
    
    self.total_kW_supply    = sum_supply_p* self.MVA_base*1000
    self.total_kVAr_supply  =  sum_supply_q* self.MVA_base*1000

    self.total_kW_demand   = sum_demand_p* self.MVA_base*1000
    self.total_kVAr_demand   = sum_demand_q * self.MVA_base *1000

    self.total_kW_loss     = (sum_supply_p - sum_demand_p)* self.MVA_base*1000
    self.total_kVAr_loss   = (sum_supply_q - sum_demand_q)* self.MVA_base*1000


    self.Ybus =  np.copy(self.GetYbus())

    # Update my special parameters (Transfer Voltage Stability Index)
    Y_bus   =  np.copy(self.GetYbus())

    for i in range(self.nBus ):
      Ykk_ph   = Y_bus[i,i]
      Ykk   = abs(Y_bus[i,i])
      # Ykk_ph  = complex(  np.real( Y_bus[i,i]), np.imag(Y_bus[i,i]))
      # Ykk_ph.real =  Y_bus[i,i].real
      # Ykk_ph.imag =  Y_bus[i,i].imag 
      theta_kk = np.angle( Y_bus[i,i])

      Pk = self.Bus.loc[i].P_pu 
      Qk = self.Bus.loc[i].Q_pu 
      Sk_ph = np.empty([], dtype=np.complex128)
      Sk_ph.real  = Pk


      Sk_ph.imag  = Qk   


      phi_k   =  np.angle(Sk_ph) 
      Y_bus   =  np.copy(self.GetYbus())


      self.V.real[i] = self.Bus.v_mag[i]* np.cos(phi_k)
      self.V.imag[i] = self.Bus.v_mag[i]* np.sin(phi_k)
      
      # self.Phi[i] = phi_k 
      self.Theta[i] = theta_kk 

      self.S.real[i]  = self.Bus.loc[i].P_pu 
      self.S.imag[i]  = self.Bus.loc[i].Q_pu 

      self.Psi_p[i] =  np.real(np.conj(self.S[i])  / Y_bus[i,i])

      self.Psi_q[i] = np.imag(np.conj(self.S[i])  / Y_bus[i,i])
   
      self.Psi_k[i] = ( (np.conj(Sk_ph) / Y_bus[i,i])  -  np.abs( self.V[i])**2 )  / np.conj(self.V[i])

      self.alpha[i] =  self.Psi_q[i]/self.Psi_p[i] if abs(self.Psi_p[i]) > 10^-116 else np.nan
    

      # Will be removed
      self.Psi_d[i] = (np.conj(Sk_ph)  / Y_bus[i,i] ) -  self.Bus.v_mag[i]**2 
      self.Psi_dk[i] = self.Psi_d[i] / np.conj(self.V[i])
    

  def addGen(self, BusID, P_KW, Q_KVAr, Control, Gen_name):

#Example
# BusID = 2
# P_KW = 40
# Q_KVAr = 30
# Control='PQ'
# Gen_name = "Gen No 1"
# IEEE5.addGen(BusID, P_KW, Q_KVAr, Control, Gen_name)# BusID = 2
# P_KW = 40
# Q_KVAr = 30
# Control='PQ'
# IEEE5.addGen(BusID, P_KW, Q_KVAr, Control)

# Control (https://pypsa.readthedocs.io/en/latest/components.html#generator)
# P,Q,V control strategy for PF, must be “PQ”, “PV” or “Slack”.
  
    self.network.add("Generator", Gen_name, 
              bus = "Bus No {}".format(BusID), # BusID: Name of bus to which generator is attached
            p_set = float(P_KW)/self.MVA_base/1000,
            q_set = float(Q_KVAr)/self.MVA_base/1000,
          control = Control)   # Control: P,Q,V control strategy for PF,  must be “PQ”, “PV” or “Slack” 
    self.LoadFlow()
    # slack_id = self.slack_bus_pos - 1 

  def Report(self):

    # self.VSI()['VSI'][1:].mean() #Exclude Slack BUS

    # print( 'Active Power Suppy (From Grid) (kW): ', self.Grid_kW_supply)
    # print( 'Reactive Power Suppy (From Grid) (kVAr): ',self.Grid_kVAr_supply )
    print( 'Total Active Power Suppy (kW): ', self.total_kW_supply )
    print( 'Total Reactive Power Suppy (kVAr): ',self.total_kVAr_supply  )
    print( '-------------------------------------------')
    # print( 'Total (Reviece) Power Condumption (MW): ',sum(- IEEE33.Bus.P_pu.iloc[2:] ) * IEEE33.MVA_base)
    print( 'Total Power Demand (kW): ',self.total_kW_demand )
    # print( 'Total (Reviece) Power Condumption (MW): ',sum(- IEEE33.Bus.P_pu.iloc[2:] ) * IEEE33.MVA_base)
    print( 'Total Reactive Power Demand (KVAr): ', self.total_kVAr_demand )
    print( '-------------------------------------------')

    print( 'Total Active Power Loss (kW): ', self.total_kW_loss   )
    print( 'Total Reactive Power Loss (kVar): ', self.total_kVAr_loss  )
 
    # print( 'Mean Voltage Stability Index (MVSI): ', self.VSI()['VSI'][1:].mean() , "(Exclude the Slack bus's voltage)" )

    print( '---------------REPORT Transfer Voltage Stability Parameters------------------------')
    Y_bus   =  np.copy(self.GetYbus())
    print( '|Bus No.  |        v        |   p   |   q   |  phi  |    Ykk   |theta_k| abs(Psi_P)| abs(Psi_Q) |   Psi   | alpha | PF |') 
    for i in range(self.nBus):
      Ykk_ph  = complex(  np.real( Y_bus[i,i]), np.imag(Y_bus[i,i]))
      Pk = self.Bus.loc[i].P_pu 
      Qk = self.Bus.loc[i].Q_pu 
      Sk_ph   =  complex( Pk , Qk  )
      # Sk_ph =  cmath.polar(Sk_ph)[1]
      phi_k   =  cmath.polar(Sk_ph)[1]
      theta_kk = cmath.polar(Ykk_ph)[1]
      Y_bus   =  np.copy(self.GetYbus())
      Ykk     =  -cmath.polar(Ykk_ph)[0]
      theta_kk = cmath.polar(Ykk_ph)[1]
      Vk = np.abs( self.V[i] )
      delta_k = np.angle( self.V[i] )


      # # print('Ykk :', Ykk)
      # self.Psi_p[i] = Pk* np.cos( phi_k + theta_kk )/ Ykk 
      # self.Psi_q[i] = -Qk* np.sin( phi_k + theta_kk )/ Ykk 
      # self.alpha[i] = self.Psi_q[i]/self.Psi_p[i]
      # self.Psi_d[i] = abs( (Sk_ph/Ykk_ph) - np.power( Vk, 2)) 

      # print()
      # print(f' Bus {i+1} {Pk:.03f}')
      print(f'|  Bus {1+i:d}  | {Vk:.03f} rad: {delta_k :.02f}| {Pk:.03f} | {Qk:.02f} | {phi_k:.02f} | {Ykk:.02f} | {theta_kk:.02f} | {self.Psi_p[i]:0.05f} | {self.Psi_q[i]:.05f} | {self.Psi_k[i]:.02f} | {self.alpha[i]:0.02f} |{np.cos(phi_k ):0.02f} |') 

 
    print( '------------------------------------------------------------------------------------------------------')

  def GetYbus(self):

    network = self.network
    nBus = self.nBus 

    Y_bus = np.zeros(( nBus, nBus), dtype=complex)

    for i in range(len( network.lines)):
      fromBus = int( network.lines['bus0'].iloc[i].split(' ')[2])
      toBus = int( network.lines['bus1'].iloc[i].split(' ')[2])

      # Y_bus[fromBus-1,toBus-1] = (((network.lines.r.iloc[i]+network.lines.x.iloc[i] *1j)*self.Z_base )**(-1))/self.Y_base
      # Y_bus[toBus-1,fromBus-1] = (((network.lines.r.iloc[i]+network.lines.x.iloc[i] *1j)*self.Z_base)**(-1))/self.Y_base
      Y_bus[fromBus-1,toBus-1] = -(((network.lines.r.iloc[i]+network.lines.x.iloc[i] *1j))**(-1))
      Y_bus[toBus-1,fromBus-1] = -(((network.lines.r.iloc[i]+network.lines.x.iloc[i] *1j))**(-1))

      if fromBus-1  != toBus-1 :
        Y_bus[fromBus-1,fromBus-1 ] = Y_bus[fromBus-1,fromBus-1 ] - Y_bus[fromBus-1,toBus-1] 
        Y_bus[toBus-1,toBus-1 ] = Y_bus[toBus-1,toBus-1 ] - Y_bus[fromBus-1,toBus-1]  



    return Y_bus
      
  def L_index(self, factor_load ):
    from copy import deepcopy
    Network_dummy = deepcopy(self)

    Y_bus = np.copy(self.GetYbus())
    L = np.zeros( self.nBus ,dtype=float)

    Network_dummy.network.loads.p_set.iloc[:] = Network_dummy.network.loads.p_set.iloc[:] * factor_load 
    Network_dummy.network.loads.q_set.iloc[:] = Network_dummy.network.loads.q_set.iloc[:] * factor_load       


    S_corr = np.zeros(self.nBus, dtype=complex)
    Y_plus = np.zeros(( self.nBus , self.nBus ), dtype=complex)
    Z = np.linalg.inv(self.Ybus)
    Z_conj = np.zeros(( self.nBus , self.nBus ), dtype=complex)
    S_plus = np.zeros(self.nBus, dtype=complex)
      
    for j in range(self.nBus):
      for i in range(self.nBus):

        # Z[j,j] = 1/self.Ybus[j,j]

        Z_conj[j,i] = np.conjugate(Z[j,i])
        Z_conj[j,j] = np.conjugate(Z[j,j])
        Y_plus[j,j] = Z[j,j]**-1
        # print( Z_conj[i,j])

        if i != j:
          S_corr[j] = S_corr[j] + self.V[j]* ( Z_conj[j,i] * self.S[i] / Z_conj[j,j] /self.V[i] ) 
          
        S_plus[j]  = self.S[j] + S_corr[j]

        
          
      L[j] = float("{:.5f}".format(abs( S_plus[j]/ ( np.conjugate( self.Ybus[j,j])) / ( (self.V[j]) ** 2)   )))


      # print('S_corr[j]',S_corr[j])
      # print('Y_plus[j,j]',Y_plus[j,j])
      # print('S_plus[j]',S_plus[j])
      # print('self.V[j]',self.V[j])
      # print("-------")
    return L
   
  def Find_one_weak_bus( self, load_factor, off_load , inplace ):
    
    privot = 1 #Exclude the slack bus
    if inplace:
      print( "inplace mode")
      maxL = np.max( self.L_index(  load_factor, off_load ) )
    else :
      print( "None inplace mode")
      maxL = np.max( self.L_index(  load_factor, []) )

    MaxL_keep =0
    # print(self.L_index(  load_factor, [])  )  
    print("Max{L} at none-disable loads : ", maxL  )
    # print(off_load)
    weakBus_ind = []


    while privot < self.nBus :

      if not privot + 1 in off_load:
        dis_buses = [privot+1] +  off_load  if inplace else [privot+1]
        dis_buses = dis_buses.remove([]) if [] in dis_buses else dis_buses

        maxL_dmm =  max( self.L_index(  load_factor, dis_buses ) ) 
        MaxL_curr = maxL - maxL_dmm

        if MaxL_keep  <=  MaxL_curr :
          MaxL_keep = MaxL_curr
          maxL_min= maxL_dmm 
          weakBus_ind = privot+1
          # print("Found!,",privot +1)

      privot = privot + 1

    print("At load factor:", load_factor,"The weakest bus : ",off_load + [ weakBus_ind ] , " max(L):",maxL_min ,"Max L reduction (%)", [100*MaxL_keep/maxL if maxL!=0 else 'inf'])
    return weakBus_ind
 #------------------------------------------------s-------------------------------------------------- 
  def P_feasibleUpper(self, BusN, a_k):

    if BusN <= self.nBus and BusN >1 :
      Y_bus = self.GetYbus() 
      Ykk_ph = - Y_bus[BusN-1][BusN-1] 
      Ykk = cmath.polar(Ykk_ph)[0]
      theta_kk = cmath.polar(Ykk_ph)[1]
      Sk_ph = complex( self.Bus.loc[BusN-1].P_pu , self.Bus.loc[BusN-1].Q_pu ) #pu
      phi_k = cmath.polar(Sk_ph)[1]
      Vk =  self.Bus.loc[BusN-1].v_mag #pu
      Psi_dk = abs( self.Psi_dk[BusN-1])

      return - Psi_dk**2 * Ykk * (np.sqrt(a_k**2 +1) -1)/2/(a_k**2)/np.cos(phi_k + theta_kk)

    elif BusN <= 1  :

      print("A slack bus")
    else :
      print("A number is out of range.")

  def PVcurve(self,BusN, a_k, n_samples):
    Y_bus   = np.copy(self.GetYbus())
    Ykk_ph  =  - Y_bus[BusN-1][BusN-1] 
    Ykk     =  cmath.polar(Ykk_ph)[0]
    theta_kk = cmath.polar(Ykk_ph)[1]
    Sk_ph   =  complex( self.Bus.loc[BusN-1].P_pu , self.Bus.loc[BusN-1].Q_pu ) #pu
    phi_k   =  cmath.polar(Sk_ph)[1]
    Vk      =  self.Bus.loc[BusN-1].v_mag #pu
    Psi_dk  =  abs( self.Psi_dk[BusN-1])

    vk_pos = np.zeros( n_samples) 
    vk_neg = np.zeros( n_samples) 

    p_max = self.P_feasibleUpper(BusN, a_k)

    p_k = np.linspace(0.0001 , p_max  , n_samples)

    for i in range(1,n_samples):
      term_a    =   np.sqrt( np.power(Psi_dk,4) -(4*np.power(p_k[i],2)*np.power(a_k,2)*np.power( np.cos(phi_k+theta_kk) , 2)/ np.power(Ykk,2))+ (( 4*p_k[i]*np.power(Psi_dk,2)*np.cos(phi_k+theta_kk))/Ykk) )
      v_pos_temp =  float(  np.power(2,-0.5 ) * np.sqrt(  np.power(Psi_dk,2) + (2*p_k[i]*np.cos(phi_k+theta_kk)/Ykk ) + term_a ) )
      v_neg_temp =  float(  np.power(2,-0.5 ) * np.sqrt(  np.power(Psi_dk,2) + (2*p_k[i]*np.cos(phi_k+theta_kk)/Ykk ) - term_a ) )

      
      vk_pos[i] = v_pos_temp if not(np.isnan(v_pos_temp))   else vk_pos[i-1]
      vk_neg[i] = v_neg_temp if not(np.isnan(v_neg_temp))   else vk_neg[i-1]
    
    return [p_k, vk_pos, vk_neg ]

  # def q_req(self,V_reg, BusN):
    i = BusN-1
    # Ykk_ph  =  - Y_bus[BusN-1][] 
    # Ykk     =  cmath.polar(Ykk_ph)[0]
    # theta_kk = cmath.polar(Ykk_ph)[1]
    # Sk_ph   =  complex( IEEE5.Bus.loc[BusN-1].P_pu , IEEE5.Bus.loc[BusN-1].Q_pu ) #pu
    # pk      =  Sk_ph.real
    # phi_k   =  cmath.polar(Sk_ph)[1]
    # Psi_dk =  abs( (Sk_ph/Ykk_ph) - np.power(V_reg,2) ) 
    # Psi_pk =   pk  / Ykk * np.cos(phi_k + theta_kk)
    from copy import deepcopy
    Network_dummy = deepcopy(self)


    
    Psi_dk_dmm = abs(Network_dummy.Psi_d[i])
    Psi_pk_dmm = abs(Network_dummy.Psi_p[i])
    # Psi_qk_dmm = Network_dummy.Psi_q[i]

    Vmax = ((Psi_dk_dmm)**2 *0.5) + Psi_pk_dmm + np.sqrt((Psi_dk_dmm)**2*((Psi_dk_dmm)**2 +4*Psi_pk_dmm)   /2) 

    term_pos = np.power(V_reg,2)*(np.power(Psi_dk_dmm ,2) + 2*Psi_pk_dmm)
    term_neg = -np.power(V_reg,4) -  np.power(Psi_pk_dmm ,2) 



    # print('V_max',Vmax)
    # print('Pos',term_pos)
    # print('Neg',term_neg)
    q_re = -np.sqrt(abs(term_pos+term_neg ) )
    # print(pk,q_re)

    return q_re

  def ResetVars(self):
    Vk = Symbol('V_k')
    del_k = Symbol('\delta_{k}')
    Pk = Symbol('P_k')
    Qk = Symbol('Q_k')
    Sk = Symbol('S_k')
    Ykk = Symbol('Y_kk')
    Psi_pk = Symbol('\psi_{P.k}')
    Psi_qk = Symbol('\psi_{Q.k}')
    Psi_k = Symbol('\psi_{k}')
    Phi_k = Symbol('\phi_{k}')
    Theta_kk = Symbol('\Theta_{kk}')  
    ak = Symbol('alpha_k')
    bk = Symbol('beta_k')
    Dk = Symbol('D_k')
    Rc = Symbol('\gamma_k')
    delta_k = Symbol('\delta_k')
    phi_Dk  = Symbol('\phi_{Dk}')

    return Vk, del_k, Pk, Qk,Sk, Ykk, Psi_pk,Psi_qk, Psi_k, Phi_k, Theta_kk , ak, bk, Dk, Rc , delta_k , phi_Dk

  def truncate(self, n, decimals=0):
    multiplier = 10 ** decimals
    return int(n * multiplier) / multiplier

  def QSVS_system_profile(self, V_max, V_min, factor_load, off_load, show_data ):

    self.network.loads.p_set.iloc[:] = self.network.loads.p_set.iloc[:] * factor_load 
    self.network.loads.q_set.iloc[:] = self.network.loads.q_set.iloc[:] * factor_load 

    if off_load :
      for k in off_load:   
        if int(k)<=self.nBus:       
          # print("Off-load at Bus :", k, 'Index:',int(k-2))
          self.network.loads.p_set.iloc[int(k-2)] = 0
          self.network.loads.q_set.iloc[int(k-2)] = 0 
      
    else :     
      pass  

    self.LoadFlow()
    
    QSVS = np.zeros(self.nBus)
    QC_keep = np.zeros(self.nBus)
    QC_snb_keep = np.zeros(self.nBus)
    VQ = np.zeros(self.nBus)
    VQ_Vmin= np.zeros(self.nBus)
    VQ_Vmax = np.zeros(self.nBus)
    Vmin = np.min( np.abs(self.V) )
    Psi_d_min_dmm = np.min( np.abs( self.Psi_d ))

    for i in range(self.nBus):
        # Filtering Parameter
      Psi_p_dmm = self.Psi_p[i]
      Psi_q_dmm = self.Psi_q[i]
      if Psi_p_dmm !=0 and Psi_q_dmm !=0:
        BusID = i+1

        #Revised Feb 4, 2022
        Vsnb = self.Vsnb(BusID)
        # print(np.angle(systemObj.Ybus[i,i]))

        QC_snb_dmm  = float(self.Qsnb( BusID ))
        
        QC_dmm      = float(self.Q_support( BusID))
                      
        VQ[i]=  sqrt( (Vsnb-abs(self.V[i]))**2  + ( float(QC_snb_dmm - QC_dmm ))**2 )
        
        # Update for minimum power loss
        QSVS[i] = VQ[i]
        # QSVS[i] = self.truncate(QSVS[i]  , 5)
      else :  
        VQ[i] = np.nan
    
        QSVS[i] = np.nan

      # print("Bus:",i+1,", VQ:",VQ[i])
      if show_data :
        Vk = abs(self.V[i])

        Qk = self.Bus.Q_pu[i]
        
        Ykk = self.Ybus[i,i]

        print("======= The "+str(BusID)+" bus==============")
        print("V : ",Vk )
        print("V-SNB : ",Vsnb )
        print("Qk (Current) : ",Qk )
        print("Qc (total-support)", QC_keep [i] )
        print("Qg (Reactive Generator Require at The maximum load ) : ", self.truncate( Vk**2 / np.imag(Ykk ),5))
        print("QSVS :",QSVS[i])

    return QSVS, VQ

  def Plot_VQ_curve(self, BusID, N_samples):

    # from copy import deepcopy
    # systemObj = deepcopy(self)

    Qsnb =  Symbol('Q^{SNB}_k')
    Vk, del_k, Pk, Qk,Sk, Ykk, Psi_pk,Psi_qk, Psi_dk, Phi_k, Theta_kk , ak, Dk, Rc , delta_k , phi_Dk = self.ResetVars()

    #Function VQ
    # Eqn9 = Vk**4 -(2*(Psi_qk/ak)+ (Psi_dk)**2)*(Vk**2) + (Psi_qk/ak)**2 + Psi_qk**2
    # sol = solveset(Eqn9 ,Psi_qk)
    # # pprint(sol)
    # # f_qk_pos = sol.args[0]
    # f_qk_pos = sol.args[1]

    #revised Feb 4, 2022
    # Psi_Qk_func= (Vk**2 * ak**2  -  sqrt(-Vk**2 * ak**2 + Psi_dk**2 * ak**2 + Psi_dk**2) )/(ak**2 + 1)
    Psi_Qk_func = sqrt(Vk * Psi_k**2  - (Vk**2 - Psi_pk)**2 ) 
    Qk_dmm_func = - Psi_Qk_func * Ykk * sin(Phi_k ) / sin(Phi_k+ Theta_kk)
    
    Qk_dmm_func = Qk_dmm_func.subs(Psi_k , abs(self.Psi_d[BusID-1]))
    Qk_dmm_func = Qk_dmm_func.subs( Psi_pk , self.Psi_p[BusID-1])
    Qk_dmm_func = Qk_dmm_func.subs(Ykk, abs(self.Ybus[BusID-1,BusID-1]) )
    Qk_dmm_func = Qk_dmm_func.subs(Phi_k, self.Phi[BusID-1])
    Qk_dmm_func = Qk_dmm_func.subs(Theta_kk, self.Theta[BusID-1])
    Qk_dmm_func = Qk_dmm_func.subs(ak, self.alpha[BusID-1])
    # Qk_dmm_func = Qk_dmm_func - self.Bus.Q_pu[BusID-1] 
    #Voltage at SNB
    # Vsnb = sqrt( (Psi_dk**2 )/ 8  + sqrt( (Psi_dk**4 /8) - (2*Psi_qk**2)  ) )
    # Vsnb = abs( N(Vsnb.subs(Psi_dk , abs(systemObj.Psi_d[BusID-1])).subs(Psi_qk , abs(systemObj.Psi_q[BusID-1]))))
    
    #Update Feb4,2022
    Vsnb = float( self.Vsnb(BusID) )
    # Qsnb = - Pk*Vk * Ykk * sqrt( -(2*Vk - Psi_dk)*( 2*Vk + Psi_dk) ) /Sk/ cos(Phi_k + Theta_kk) # pprint(Qsnb)
    
    
    #Display

    Vk_var = linspace(0,2,N_samples)

    func_QCk = lambdify(Vk, Qk_dmm_func, modules=['numpy'])
    QCsnb =  func_QCk(Vsnb)   

    fig, ax = plt.subplots( figsize=(12, 8))
    

    ax.plot(Vk_var , func_QCk(Vk_var) );
    ax.plot(Vsnb ,QCsnb  ,'o',label=': $Q^{SNB}_{'+str(BusID)+'}$');
    ax.xlabel = '$V$ (pu)'
    ax.ylabel = '$Q$ (pu)'
    ax.set_title('$Bus '+ str(BusID) +'$ on IEEE '+str(self.nBus)+'-bus test distribution system')
    ax.legend()

  def Optimal_size_RDGs( self,  busID, N_samples, Control, Q_P_Ratio, power_limit ):

      # Limitation : 0 - 100% of maximum demand P or/and Q     
      #Input: 
      # Bus ID
      # dimension
    
      #Output: 
          # CASE I: P-DG kW
          # CASE II: P-DG kW and Q-DG kVAr
          # CASE III: Q-DG kVAr
      # Control: P,Q,V control strategy for PF, must be “PQ”, “PV” or “Slack”.

          # https://pypsa.readthedocs.io/en/latest/components.html#generator


      # from copy import deepcopy
      org_system = deepcopy(self)   
     
      const = np.arange(0,N_samples+1,1) / N_samples

      XP = power_limit * const
      XQ = XP * Q_P_Ratio

      ind = np.arange(1,N_samples+2,1)
      pLoss = np.zeros(len(ind ))
      qLoss = np.zeros(len(ind ))
      # L =np.zeros(len(ind ))
      # QiVS = np.zeros(len(ind ))
      Gen_name = 'RDG'
    
      SizeRDGs_plot = XP     

      chk = 10000000000000000
      
      for i in tqdm( range(len(XP))  ,desc='Finding the optimal sizing by minimizing total loss' ): 
         
        systemObj = deepcopy(org_system) #Reset grid 
        systemObj.addGen(busID, XP[i], -XQ[i], Control, Gen_name )        
        systemObj.LoadFlow()
    
        # tmp_loss = float("{:.4f}".format(systemObj.total_kW_loss.astype(type('float', (float,), {}))))
        
        # tmp_loss = float("{:.4f}".format(systemObj.total_kW_loss.astype(type('float', (float,), {}))))
        pLoss[i]=round(systemObj.total_kW_loss,4)
 
        loadFactor = 1
  
        # L_tmp = np.amax( systemObj.L_index(loadFactor) )

        V_max = 1.05 
        V_min = 0.95  
        factor_load = 1 
        off_load = []
        show_data = false
        # QiVS_tmp =   round(min(systemObj.QiVS( V_max, V_min, factor_load, off_load,show_data)),4)
       
        del systemObj

        if (chk > pLoss[i]):
          OptimalSizeRDGs = XP[i]
          pLoss_min = pLoss[i]
          qLoss = np.nan
          # L = L_tmp
          # QiVS = QiVS_tmp 
          chk = pLoss[i]

      pLoss_plot = pLoss
      
      return OptimalSizeRDGs, pLoss_min, SizeRDGs_plot, pLoss_plot
  
  def Optimal_size_2RDGs( self,  busID, N_samples, Control, Q_P_Ratio, power_limit, speed=False):

      # Limitation : 0 - 100% of maximum demand P or/and Q     
      #Input: 
      # Bus ID
      # dimension
    
      #Output: 
          # CASE I: P-DG kW
          # CASE II: P-DG kW and Q-DG kVAr
          # CASE III: Q-DG kVAr
      # Control: P,Q,V control strategy for PF, must be “PQ”, “PV” or “Slack”.

          # https://pypsa.readthedocs.io/en/latest/components.html#generator


      # from copy import deepcopy
      org_system = deepcopy(self)   
     
      const = np.arange(0,N_samples+1,1) / N_samples

      XP1 = power_limit * const
      XQ1 = XP1 * Q_P_Ratio
      XP2 = power_limit * const
      XQ2 = XP2 * Q_P_Ratio

      ind = np.arange(1,N_samples+2,1)
      pLoss = np.zeros((len(ind ), len(ind )))
      qLoss = np.zeros((len(ind ), len(ind )))
      # L =np.zeros(len(ind ))
      # QiVS = np.zeros(len(ind ))
      Gen_name1 = 'RDG1'
      Gen_name2 = 'RDG2'
      # Control = 'PQ'
      SizeRDGs1_plot = XP1     
      SizeRDGs2_plot = XP2
      OptimalSizeRDG1 = []
      OptimalSizeRDG2 = []

      chk = 10000000000000000
      loss_org = org_system.total_kW_loss
      prv_loss = loss_org 
      if speed:
        print("You are using Speed mode!!")
      for i in tqdm( range(len(XP1))  ,desc='Finding the optimal sizing RDG 1 by minimizing total loss' ):
        for j in tqdm( range(len(XP2))  ,desc='Finding the optimal sizing RDG 2 by minimizing total loss' ): 
         
          if speed:
            if prv_loss <= loss_org :
              systemObj = deepcopy(org_system) #Reset grid 
              systemObj.addGen(busID[0], XP1[i], XQ1[i], Control, Gen_name1 ) 
              systemObj.addGen(busID[1], XP2[j], XQ2[j], Control, Gen_name2 )         
              systemObj.LoadFlow()
              pLoss[i,j]=systemObj.total_kW_loss
              prv_loss = pLoss[i,j]

            else : 
              pLoss[i,j]=np.nan

            if speed:
              if prv_loss <= loss_org :
                systemObj = deepcopy(org_system) #Reset grid 
                systemObj.addGen(busID[0], XP1[i], XQ1[i], Control, Gen_name1 ) 
                systemObj.addGen(busID[1], XP2[j], XQ2[j], Control, Gen_name2 )         
                systemObj.LoadFlow()
                pLoss[i,j]=systemObj.total_kW_loss
                prv_loss = pLoss[i,j]

              else : 
                pLoss[i,j]=np.nan




          else :
            systemObj = deepcopy(org_system) #Reset grid 
            systemObj.addGen(busID[0], XP1[i], XQ1[i], Control, Gen_name1 ) 
            systemObj.addGen(busID[1], XP2[j], XQ2[j], Control, Gen_name2 )         
            systemObj.LoadFlow()
            pLoss[i,j]=systemObj.total_kW_loss
            # prv_loss = pLoss[i,j]
    
          # tmp_loss = float("{:.4f}".format(systemObj.total_kW_loss.astype(type('float', (float,), {}))))
          
          # tmp_loss = float("{:.4f}".format(systemObj.total_kW_loss.astype(type('float', (float,), {}))))
          
        
       
          # del systemObj

          if (chk > pLoss[i,j]):
            OptimalSizeRDG1 = XP1[i]
            OptimalSizeRDG2 = XP2[j]
            pLoss_min = pLoss[i,j]
            # qLoss = np.nan
            # L = L_tmp
            # QiVS = QiVS_tmp 
            chk = pLoss[i,j]

     
      
      return OptimalSizeRDG1, OptimalSizeRDG2,pLoss_min, SizeRDGs1_plot, SizeRDGs2_plot,  pLoss

  def Vsnb(self, BusID):
    # Voltage at SNB of Bus no. i : V_SNB(i)
    # Revision date: APR 13, 2022
    systemObj = deepcopy(self)
    Psi_k_dmm =np.abs(systemObj.Psi_d[BusID -1])
    V_k_dmm = abs(systemObj.V[BusID -1])
    Psi_qk_dmm = systemObj.Psi_q[BusID -1]
    Xi_dmm = np.angle(systemObj.Psi_d[BusID -1])
    delta_dmm = np.angle(systemObj.V[BusID -1])
    Delta_k_snb = np.arccos(-Psi_k_dmm/ V_k_dmm /2)

    # Psi_qk_snb = np.cos(delta_dmm)*2*V_k_dmm**2*sqrt(1-np.cos(delta_dmm)**2 )
    Delta_k_temp =  Xi_dmm-delta_dmm 

    dmm =abs( 4*V_k_dmm**2*np.cos(Delta_k_temp)**2 - Psi_k_dmm**2)
    Vsnb = sqrt( V_k_dmm**2 - sqrt( V_k_dmm**2 *(dmm)  * sin(Delta_k_temp )**2 ))    
    return Vsnb

  def Qc_snb(self, BusID):
    # Reactive support at SNB of Bus no. i : Qc_SNB(i)
    # Revision date: APR 13, 2022

    Vk_dmm    = np.abs(self.V[ BusID-1])
    Phi_dmm   = np.angle(self.S[ BusID-1] )
    Xi_dmm    = np.abs(self.Psi_q[BusID -1])
    delta_dmm = np.angle(self.V[ BusID-1])
    Delta_dmm = Xi_dmm - delta_dmm 
    Vsnb_dmm =  self.Vsnb( BusID)
    Ykk       = np.abs(self.Ybus[ BusID-1, BusID-1 ])
    Theta_kk  = np.angle(self.Ybus[ BusID-1, BusID-1 ])
    Psi_p_dmm = np.abs( self.Psi_p[BusID -1] )

    Psi_k_dmm =np.abs(self.Psi_d[BusID -1])
    Psi_q_SNB_dmm = np.sign( sin(Delta_dmm))* sqrt(abs(np.power(Vsnb_dmm,2)*np.power(Psi_k_dmm ,2)   - ((Vsnb_dmm**2 - Psi_p_dmm  )**2)))
    # print("Psi_Q snb : ",Psi_q_SNB_dmm)
    Qsnb = - Ykk * sin(Phi_dmm  )  * Psi_q_SNB_dmm / sin(Phi_dmm + Theta_kk)
  
    return Qsnb

  def Qc(self, BusID):
    # Reactive support at Bus no. i : Qc(i)
    # Revision date: APR 13, 2022

    Phi_dmm   = np.angle(self.S[ BusID-1] )
    Ykk       = np.abs(self.Ybus[ BusID-1, BusID-1 ])
    Theta_kk  = np.angle(self.Ybus[ BusID-1, BusID-1 ])
    Psi_q_dmm = np.abs( self.Psi_q[BusID -1] )
    Psi_k_dmm =np.abs(self.Psi_d[BusID -1])

    # Psi_q_dmm = np.sign( sin(Delta_dmm))* sqrt( np.power(Vk_dmm ,2)*np.power(Psi_k_dmm ,2)   - ((Vk_dmm **2 - Psi_p_dmm  )**2))
    Qc = - Ykk * np.sin(Phi_dmm) * Psi_q_dmm / np.sin(Phi_dmm + Theta_kk)
    return Qc

  def VQ_profile(self, factor_load, off_load, show_data ):
   
    # Revision date: APR 13, 2022

    systemObj = deepcopy(self)
    systemObj.network.loads.p_set.iloc[:] = systemObj.network.loads.p_set.iloc[:] * factor_load 
    systemObj.network.loads.q_set.iloc[:] = systemObj.network.loads.q_set.iloc[:] * factor_load 
    VQ_max = 0
    if len(off_load) :
      for k in list( off_load ):    
     
        systemObj.network.loads.p_set[systemObj.network.loads.bus == "Bus No "+str(k)] = 0
        systemObj.network.loads.q_set[systemObj.network.loads.bus == "Bus No "+str(k)] = 0
       
    else :     
      pass  

    systemObj.LoadFlow()
    
    QC_keep = np.zeros(systemObj.nBus)
    QC_snb_keep = np.zeros(systemObj.nBus)
    VQ = np.zeros(systemObj.nBus)
    # VQ_Vmin= np.zeros(systemObj.nBus)
    # VQ_Vmax = np.zeros(systemObj.nBus)
    
    for i in range(systemObj.nBus):
        # Filtering Parameter
      BusID = i+1
      Psi_p_dmm = abs(systemObj.Psi_d[i])
      Psi_q_dmm = abs(systemObj.Psi_q[i])
      Psi_k_dmm = abs(systemObj.Psi_d[i])
      Vsnb_dmm = systemObj.Vsnb( BusID )
      Vk_dmm = abs(systemObj.V[i])
      
      QC_snb_dmm  = systemObj.Qc_snb(BusID)
      
      QC_dmm      = systemObj.Qc(BusID)

      VQ_dmm =  sqrt( (Vsnb_dmm-Vk_dmm)**2  + ( QC_snb_dmm - QC_dmm )**2 )
    
      if QC_dmm!=0 :

        

        #Revised Feb 4, 2022
        # Vsnb_dmm = systemObj.Vsnb( BusID )
        # Vk_dmm = abs(systemObj.V[i])
        
        # QC_snb_dmm  = systemObj.Qc_snb(BusID)
        
        # QC_dmm      = systemObj.Qc(BusID)

        try:
          VQ[i]= sqrt( (Vsnb_dmm-Vk_dmm)**2  + ( QC_snb_dmm - QC_dmm )**2 )
       

        except :
          print("VQ at bus :",i+1," is infeasible to solve with off load(s) : ",off_load)
          print("Vsnb :",Vsnb_dmm)
          print("V :",Vk_dmm)
          print("QC_snb :",QC_snb_dmm)
          print("QC :",QC_dmm)
          
          # VQ[i] = np.nan

          
        
          # QSVS[i] =  systemObj.truncate(QSVS[i]  , 5)
      else :  
        VQ[i] = np.nan
    
        

      # print("Bus:",i+1,", VQ:",VQ[i])
      if show_data :
        Vk = abs(systemObj.V[i])

        Qk = systemObj.Bus.Q_pu[i]
        
        Ykk = systemObj.Ybus[i,i]

        print("======= The "+str(BusID)+" bus==============")
        print("dist(VQ) : ",VQ_dmm  )
        print("Vk: ",Vk_dmm )
        print("V-SNB : ",Vsnb_dmm )
        print("QC (Current) : ",QC_dmm)
        print("QC_snb :",QC_snb_dmm)

      del Psi_p_dmm, Psi_q_dmm, Psi_k_dmm, Vsnb_dmm, Vk_dmm, QC_snb_dmm ,QC_dmm,VQ_dmm 

        # print("Qc (total-support)", QC_dmm )
        # print("Qg (Reactive Generator Require at The maximum load ) : ", systemObj.truncate( Vk**2 / np.imag(Ykk ),5))
        # print("QSVS :",QSVS[i])
    del systemObj  
    return VQ

  def V_func(self, SystemObj_input, P_var, RCR , BusID ):

    tolerance = 10000
    systemObj = deepcopy(self)
    
    k = BusID
    if int( k )<= systemObj.nBus:       
      # print("Off-load at Bus :", k, 'Index:',int(k-2))
      systemObj.network.loads.p_set.iloc[int(k-2)] = P_var
      systemObj.network.loads.q_set.iloc[int(k-2)] = P_var * RCR 
      systemObj.LoadFlow()
      
      #Getting parameters
      Psi_pk = abs( systemObj.Psi_p[BusID-1] )
      Psi_dk = abs(  systemObj.Psi_d[BusID-1] )
      Psi_qk = abs(  systemObj.Psi_q[BusID-1] )
      alpha_k = abs(  systemObj.alpha[BusID-1] ) 
      Phi_k =  systemObj.Phi[BusID-1] 
      Theta_kk = np.angle( systemObj.Ybus[BusID-1,BusID-1] ) 
      Y_kk = abs( systemObj.Ybus[BusID-1,BusID-1] ) 
      try :
        Vsnb = sqrt( (Psi_dk**2 )/ 8  + sqrt( (Psi_dk**4 /8) - (2*Psi_qk**2)  ) )

        V = sqrt( Psi_dk**4 + ( 4 * (Psi_dk**2 )*Psi_pk) - (4 * Psi_pk**2 * alpha_k**2 ) )
        V = sqrt( (Psi_dk**2 / 2 )+ Psi_pk + (V/2) )

        if Vsnb > tolerance :
          print("Voltage Collapse at P_load = ",P_var)
          print("Voltage Collapse at Q_load = ",P_var * RCR)

      
      except :
        # print("Voltage Collapse at P_load = ",P_var)
        # print("Voltage Collapse at Q_load = ",P_var * RCR)
        Vsnb = tolerance*10
        V = tolerance*10

    return V, Vsnb

  def Loading_margin(self, RCR_input , BusID_input , P_var_limit=10,sensitivity = 0.01):
    
    systemObj_dmm = deepcopy(self)
    p_var =  np.arange(0.00001, P_var_limit, sensitivity)
    V_var =  np.zeros( len(p_var) ) 
    Vsnb = 0
    i = 0
    tolerance = 10000
    while  Vsnb < tolerance  and i < len(p_var):
      
      V_temp = self.V_func( systemObj_dmm, p_var[i], RCR_input , BusID_input )
      V_var[i] = V_temp [0]
      Vsnb = V_temp [1]
      # print("P_var[i] :", p_var[i])
      # print("V_var[i] :", V_temp [0])
      # print("Vsnb: ",V_temp [1])

      i = i+1
    
    print("--------- Voltage collapse ---------")
    print("At Bus : ", BusID_input)
    print("V snb : ", Vsnb )
    print("V (before)  : ",V_var[i-1] )
    print("Loading margin : ",p_var[i-1] )

    return p_var[i-1]

  def voltage_product_profile(self, factor_load = [1], off_load = [] ):
    
    # Revision date: APR 13, 2022
    systemObj = deepcopy(self)
    
    systemObj.network.loads.p_set.iloc[:] = systemObj.network.loads.p_set.iloc[:] * factor_load 
    systemObj.network.loads.q_set.iloc[:] = systemObj.network.loads.q_set.iloc[:] * factor_load 

    if off_load :
      for k in off_load:   
        if int(k)<=systemObj.nBus:       
          # print("Off-load at Bus :", k, 'Index:',int(k-2))
          systemObj.network.loads.p_set[systemObj.network.loads.bus== "Bus No "+str(k)] = 0
          systemObj.network.loads.q_set[systemObj.network.loads.bus== "Bus No "+str(k)] = 0
          # systemObj.network.loads.p_set.iloc[int(k-2)] = 0
          # systemObj.network.loads.q_set.iloc[int(k-2)] = 0     
    else :     
      pass 

    systemObj.LoadFlow()

    return abs(systemObj.Psi_d[:]) 

  def voltage_profile(self, factor_load = [1], off_load = [] ):
    
    # Revision date: APR 13, 2022
    systemObj = deepcopy(self)
    
    systemObj.network.loads.p_set.iloc[:] = systemObj.network.loads.p_set.iloc[:] * factor_load 
    systemObj.network.loads.q_set.iloc[:] = systemObj.network.loads.q_set.iloc[:] * factor_load 

    if off_load :
      for k in off_load:   
        if int(k)<=systemObj.nBus:       
          # print("Off-load at Bus :", k, 'Index:',int(k-2))
          systemObj.network.loads.p_set[systemObj.network.loads.bus== "Bus No "+str(k)] = 0
          systemObj.network.loads.q_set[systemObj.network.loads.bus== "Bus No "+str(k)] = 0
          # systemObj.network.loads.p_set.iloc[int(k-2)] = 0
          # systemObj.network.loads.q_set.iloc[int(k-2)] = 0     
    else :     
      pass 

    systemObj.LoadFlow()

    return abs(systemObj.V[:])

if __name__ == "__main__" :

    MVA_base = 100.0 # Defining the Base-MVA
    KV_base = 100 #KV 
    nBus = 5
    SLACK_POS = 1
    IEEE5 = Grid( nBus, 'faifah/5BusTest-line.csv', 'faifah/5BusTest-load.csv', MVA_base ,  KV_base, SLACK_POS)


    #Adding gen for IEEE 5-Bus test system
    BusID = 2
    P_KW = 40
    Q_KVAr = 30
    Control='PQ'
    Gen_name = "G2"
    IEEE5.addGen(BusID, P_KW, Q_KVAr, Control,Gen_name)
    IEEE5.LoadFlow()
    IEEE5.Report()

    print( IEEE5.line_data )
    