# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# # Load Frequency Control in ANDES

# This examples shows (1) how to trip a generator, and (2) how to drive frequency back by load shedding.

# +
import andes
import numpy as np

andes.main.config_logger(stream_level=20)
# -

# ## Tripping a Generator in the IEEE 14-Bus System

# +
# using the IEEE 14-bus model as an example. 
# The example here contains a variety of models: generators, exciters, turbine governors, and PSS
# To speed up, one can remove unneeded ones, e.g., PSS

ieee14_raw = andes.get_case("ieee14/ieee14.raw")
ieee14_dyr = andes.get_case("ieee14/ieee14.dyr")

# +
# use `andes.load` to load the test system
# Need to set `setup=False` to be able to add new Togglers that turns off generators.

ss = andes.load(ieee14_raw, addfile=ieee14_dyr, setup=False)

# +
# Add a Toggler that disconnects `GENROU_2` at t=1 s

ss.add("Toggler", dict(model='SynGen', dev="GENROU_2", t=1.0))

# +
# Call setup manually

ss.setup()

# +
# double check that Togglers are set up correctly
# Check `u` of the Togglers - the first two line switches are disabled, and the generator trip is enabled

ss.Toggler.as_df()

# +
# disable existing line switches
# The IEEE 14-bus system contains predefined line switches. Disabling them to study generator trip only.

ss.Toggler.u.v[[0, 1]] = 0

# +
# calculate power flow

# use constant power model for PQ (we will come back to this later)

ss.PQ.config.p2p = 1
ss.PQ.config.q2q = 1
ss.PQ.config.p2z = 0
ss.PQ.config.q2z = 0

# turn off under-voltage PQ-to-Z conversion
ss.PQ.pq2z = 0

ss.PFlow.run()

# +
# set the first simulation stop and run it

ss.TDS.config.tf = 20

ss.TDS.run()

# +
# Show the frequency response of online generators

# Refer to `plot` documentation by using `help(ss.TDS.plt.plot)` and `help(ss.TDS.plt.plot_data)`
ss.TDS.load_plotter()

ss.TDS.plt.plot(ss.GENROU.omega,
                a=(0, 2, 3, 4),
                ytimes=60,
               )
# -

# ## Adjusting Load to Compensate for the Generation Loss

# Check the power of the lost generator by inspecting the power flow inputs:

ss.PV.as_df()

# The tripped GENROU_2 correspond to the first PV (GENROU_1 corresponds to Slack). Thus, the lost active power is 0.40 pu.
#
# Let's compensate for that by shedding 0.4 pu of active power load at t=2.0 s.
#
# By checking the equation documentation of PQ (using `print(ss.PQ.doc())`, we can tell that the imposed active power for time-domain simulation is from `Ppf`, because we used the constant power model with `p2p = 1`.
#
# ```
# Algebraic Equations
#
# Name |   Type   |                RHS of Equation "0 = g(x, y)"                
# -----+----------+-------------------------------------------------------------
#  a   | ExtAlgeb | u * (dae_t <= 0) * (p0 * vcmp_zi + Rlb * vcmp_zl * v**2 +   
#      |          | Rub * vcmp_zu * v**2) + u * (dae_t > 0) * (p2p * Ppf + p2i *
#      |          | Ipeq * v + p2z * Req * v**2)                                
#  v   | ExtAlgeb | u * (dae_t <= 0) * (q0 * vcmp_zi + Xlb * vcmp_zl * v**2 +   
#      |          | Xub * vcmp_zu * v**2) + u * (dae_t > 0) * (q2q * Qpf + q2i *
#      |          | Iqeq * v + q2z * Xeq * v**2)                                
#
# ```
#
# `Ppf` may be different from `p0` specified in the data file.

# +
# active power from power flow solution - make a copy

Ppf = np.array(ss.PQ.Ppf.v)

Ppf
# -

# Reload the system and add the generator trip.

# +
ss = andes.load(ieee14_raw, addfile=ieee14_dyr, setup=False)

ss.add("Toggler", dict(model='SynGen', dev="GENROU_2", t=1.0))
ss.setup()
ss.Toggler.u.v[[0, 1]] = 0


ss.PQ.config.p2p = 1
ss.PQ.config.q2q = 1
ss.PQ.config.p2z = 0
ss.PQ.config.q2z = 0
ss.PQ.pq2z = 0

ss.PFlow.run()
# -

# But let's run to 2 seconds.

# +
ss.TDS.config.tf = 2.0

ss.TDS.run()

# +
# all `Ppf` before shedding

ss.PQ.Ppf.v
# -

# And then apply the load shedding on buses 2, 3, 4, 5, 6, 9.

# +
shed_buses = [2, 3, 4, 5, 6, 9]

# find the `idx` of the loads on these buses

pq_shed_idx = ss.PQ.find_idx(keys='bus', values=shed_buses)
pq_shed_idx

# +
# get `Ppf` on these buses before shedding

pq_p = ss.PQ.get(src='Ppf', idx=pq_shed_idx, attr='v')
pq_p

# +
pq_p_new = pq_p - 0.4 / len(shed_buses)

ss.PQ.set(src='Ppf', idx=pq_shed_idx, attr='v', value=pq_p_new)

# +
# double check

ss.PQ.Ppf.v

# +
ss.TDS.config.tf = 20

ss.TDS.run()


ss.TDS.plt.plot(ss.GENROU.omega,
                a=(0, 2, 3, 4),
                ytimes=60,
               )
# -

# The result shows the generator speed (frequency) returns to 60 Hz after load shedding. 

StarcoderdataJupyterScriptsDedupFiltered