Manipulate the Simulation#
This example shows how to play with the simulation, such as contingency analysis and manipulate the constraints.
[1]:
import ams
import datetime
[2]:
print("Last run time:", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
print(f'ams:{ams.__version__}')
Last run time: 2024-04-21 17:29:40
ams:0.9.6
[3]:
ams.config_logger(stream_level=20)
Manipulate the Simulation#
Load Case#
[4]:
sp = ams.load(ams.get_case('5bus/pjm5bus_uced.xlsx'),
setup=True,
no_output=True,)
Parsing input file "/Users/jinningwang/Documents/work/mambaforge/envs/amsre/lib/python3.9/site-packages/ams/cases/5bus/pjm5bus_uced.xlsx"...
Input file parsed in 0.0945 seconds.
Zero line rates detacted in rate_b, rate_c, adjusted to 999.
System set up in 0.0022 seconds.
The system load are defined in model PQ
.
[5]:
sp.PQ.as_df()
[5]:
idx | u | name | bus | Vn | p0 | q0 | vmax | vmin | owner | ctrl | |
---|---|---|---|---|---|---|---|---|---|---|---|
uid | |||||||||||
0 | PQ_1 | 1.0 | PQ 1 | Bus_2 | 230.0 | 3.0 | 0.9861 | 1.1 | 0.9 | None | 1.0 |
1 | PQ_2 | 1.0 | PQ 2 | Bus_3 | 230.0 | 3.0 | 0.9861 | 1.1 | 0.9 | None | 1.0 |
2 | PQ_3 | 1.0 | PQ 3 | Bus_4 | 230.0 | 4.0 | 1.3147 | 1.1 | 0.9 | None | 1.0 |
In RTED, system load is referred as pd
.
[6]:
sp.RTED.pd.v
[6]:
array([3., 3., 4.])
Run Simulation#
RTED can be solved and one can inspect the results as discussed in previous example.
[7]:
sp.RTED.run(solver='ECOS')
<RTED> initialized in 0.0137 seconds.
<RTED> solved as optimal in 0.0152 seconds, converged in 12 iterations with ECOS.
[7]:
True
Power generation pg
and line flow plf
can be accessed as follows.
[8]:
sp.RTED.pg.v
[8]:
array([2. , 2.1, 5.2, 0.7])
[9]:
sp.RTED.plf.v
[9]:
array([ 0.70595331, 0.68616798, 0.00192539, -1.58809337, 0.61190663,
-0.70192539, 0.70595331])
Change Load#
The load values can be manipulated in the model PQ
.
Note the difference between Model.set
and Model.alter
:
set
: This method WILL NOT modify the input values from the case file that have not been converted to the system base. As a result, changes applied by this method WILL NOT affect the dumped case file.
alter
: If the method operates on an input parameter, the new data should be in the same base as that in the input file. This function will convert value
to per unit in the system base whenever necessary. The values for storing the input data, i.e., the parameter’s vin
field, will be overwritten. As a result, altered values WILL BE reflected in the dumped case file.
Besides, Group
also has method set
but has no alter
.
[10]:
sp.PQ.set(src='p0', attr='v', idx=['PQ_1', 'PQ_2'], value=[3.2, 3.2])
[10]:
True
According parameters need to be updated to make the changes effective in the optimization model. If not sure which parameters need to be updated, one can use update()
to update all parameters.
[11]:
sp.RTED.update('pd')
[11]:
True
After manipulation, the routined can be solved again.
[12]:
sp.RTED.run(solver='ECOS')
<RTED> solved as optimal in 0.0017 seconds, converged in 12 iterations with ECOS.
[12]:
True
[13]:
sp.RTED.pg.v
[13]:
array([2. , 2.1, 5.2, 1.1])
An alternative way is to alter the load through RTED
.
As pd
has owner StaticLoad
and soruce p0
, the parameter update through RTED
actually happens to StaticLoad.p0
.
[14]:
sp.RTED.pd.owner
[14]:
StaticLoad (3 devices) at 0x17f154850
[15]:
sp.RTED.pd.src
[15]:
'p0'
Similarly, the load can be changed using set
method.
[16]:
sp.RTED.set(src='pd', attr='v', idx=['PQ_1', 'PQ_2'], value=[0.2, 0.2])
[16]:
True
Remember to update the optimization parameters after the change.
[17]:
sp.RTED.update('pd')
[17]:
True
We can see that the original load is also updated.
[18]:
sp.PQ.as_df()
[18]:
idx | u | name | bus | Vn | p0 | q0 | vmax | vmin | owner | ctrl | |
---|---|---|---|---|---|---|---|---|---|---|---|
uid | |||||||||||
0 | PQ_1 | 1.0 | PQ 1 | Bus_2 | 230.0 | 0.2 | 0.9861 | 1.1 | 0.9 | None | 1.0 |
1 | PQ_2 | 1.0 | PQ 2 | Bus_3 | 230.0 | 0.2 | 0.9861 | 1.1 | 0.9 | None | 1.0 |
2 | PQ_3 | 1.0 | PQ 3 | Bus_4 | 230.0 | 4.0 | 1.3147 | 1.1 | 0.9 | None | 1.0 |
[19]:
sp.RTED.run(solver='ECOS')
<RTED> solved as optimal in 0.0018 seconds, converged in 10 iterations with ECOS.
[19]:
True
As expected, the power generation also changed.
[20]:
sp.RTED.pg.v
[20]:
array([2. , 1.3, 0.5, 0.6])
Trip a Generator#
We can see that there are three PV generators in the system.
[21]:
sp.PV.as_df()
[21]:
idx | u | name | Sn | Vn | bus | busr | p0 | q0 | pmax | ... | Qc2min | Qc2max | Ragc | R10 | R30 | Rq | apf | pg0 | td1 | td2 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
uid | |||||||||||||||||||||
0 | PV_1 | 1.0 | Alta | 100.0 | 230.0 | Bus_1 | None | 1.0000 | 0.0 | 2.1 | ... | 0.0 | 0.0 | 999.0 | 999.0 | 999.0 | 999.0 | 0.0 | 0.0 | 0.5 | 0.0 |
1 | PV_3 | 1.0 | Solitude | 100.0 | 230.0 | Bus_3 | None | 3.2349 | 0.0 | 5.2 | ... | 0.0 | 0.0 | 999.0 | 999.0 | 999.0 | 999.0 | 0.0 | 0.0 | 0.5 | 0.0 |
2 | PV_5 | 1.0 | Brighton | 100.0 | 230.0 | Bus_5 | None | 4.6651 | 0.0 | 6.0 | ... | 0.0 | 0.0 | 999.0 | 999.0 | 999.0 | 999.0 | 0.0 | 0.0 | 0.5 | 0.0 |
3 rows × 33 columns
PV_1
is tripped by setting its connection status u
to 0.
[22]:
sp.StaticGen.set(src='u', attr='v', idx='PV_1', value=0)
[22]:
True
In AMS, some parameters are defiend as constants in the numerical optimization model to follow the CVXPY DCP and DPP rules. Once non-parametric parameters are changed, the optimization model will be re-initialized to make the changes effective.
More details can be found at CVXPY - Disciplined Convex Programming.
[23]:
sp.RTED.update()
<RTED> reinit OModel due to non-parametric change.
[23]:
True
Then we can re-solve the model.
[24]:
sp.RTED.run(solver='ECOS')
<RTED> solved as optimal in 0.0143 seconds, converged in 10 iterations with ECOS.
[24]:
True
We can see that the tripped generator has no power generation.
[25]:
sp.RTED.pg.v.round(2)
[25]:
array([2. , 0. , 1.8, 0.6])
Trip a Line#
We can inspect the Line
model to check the system topology.
[26]:
sp.Line.as_df()
[26]:
idx | u | name | bus1 | bus2 | Sn | fn | Vn1 | Vn2 | r | ... | tap | phi | rate_a | rate_b | rate_c | owner | xcoord | ycoord | amin | amax | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
uid | |||||||||||||||||||||
0 | Line_0 | 1.0 | Line AB | Bus_1 | Bus_2 | 100.0 | 60.0 | 230.0 | 230.0 | 0.00281 | ... | 1.0 | 0.0 | 4.0 | 999.0 | 999.0 | None | None | None | -6.283185 | 6.283185 |
1 | Line_1 | 1.0 | Line AD | Bus_1 | Bus_4 | 100.0 | 60.0 | 230.0 | 230.0 | 0.00304 | ... | 1.0 | 0.0 | 2.0 | 999.0 | 999.0 | None | None | None | -6.283185 | 6.283185 |
2 | Line_2 | 1.0 | Line AE | Bus_1 | Bus_5 | 100.0 | 60.0 | 230.0 | 230.0 | 0.00064 | ... | 1.0 | 0.0 | 2.0 | 999.0 | 999.0 | None | None | None | -6.283185 | 6.283185 |
3 | Line_3 | 1.0 | Line BC | Bus_2 | Bus_3 | 100.0 | 60.0 | 230.0 | 230.0 | 0.00108 | ... | 1.0 | 0.0 | 2.0 | 999.0 | 999.0 | None | None | None | -6.283185 | 6.283185 |
4 | Line_4 | 1.0 | Line CD | Bus_3 | Bus_4 | 100.0 | 60.0 | 230.0 | 230.0 | 0.00297 | ... | 1.0 | 0.0 | 2.0 | 999.0 | 999.0 | None | None | None | -6.283185 | 6.283185 |
5 | Line_5 | 1.0 | Line DE | Bus_4 | Bus_5 | 100.0 | 60.0 | 230.0 | 230.0 | 0.00297 | ... | 1.0 | 0.0 | 2.4 | 999.0 | 999.0 | None | None | None | -6.283185 | 6.283185 |
6 | Line_6 | 1.0 | Line AB2 | Bus_1 | Bus_2 | 100.0 | 60.0 | 230.0 | 230.0 | 0.00281 | ... | 1.0 | 0.0 | 4.0 | 999.0 | 999.0 | None | None | None | -6.283185 | 6.283185 |
7 rows × 28 columns
Here line 2
is tripped by setting its connection status u
to 0.
Note that in ANDES, dynamic simulation of line tripping should use model ``Toggle``.
[27]:
sp.Line.set(src='u', attr='v', idx='Line_1', value=0)
[27]:
True
[28]:
sp.RTED.update()
<RTED> reinit OModel due to non-parametric change.
[28]:
True
[29]:
sp.RTED.run(solver='ECOS')
<RTED> solved as optimal in 0.0149 seconds, converged in 10 iterations with ECOS.
[29]:
True
Here we can see the tripped line has no flow.
[30]:
sp.RTED.plf.v.round(2)
[30]:
array([-0.12, 0. , 0.24, -0.44, 1.16, -0.84, -0.12])
Disable Constraints#
In addition to the system parameters, the constraints can also be manipulated.
Here, we load the case to a new system.
[31]:
spc = ams.load(ams.get_case('5bus/pjm5bus_uced.xlsx'),
setup=True,
no_output=True,)
Parsing input file "/Users/jinningwang/Documents/work/mambaforge/envs/amsre/lib/python3.9/site-packages/ams/cases/5bus/pjm5bus_uced.xlsx"...
Input file parsed in 0.0397 seconds.
Zero line rates detacted in rate_b, rate_c, adjusted to 999.
System set up in 0.0031 seconds.
[32]:
spc.RTED.init()
<RTED> initialized in 0.0169 seconds.
[32]:
True
[33]:
spc.RTED.set(src='rate_a', attr='v', idx=['Line_3'], value=0.6)
[33]:
True
[34]:
spc.RTED.update('rate_a')
[34]:
True
We can inspect the constraints status as follows. All constraints are turned on by default.
[35]:
spc.RTED.constrs
[35]:
OrderedDict([('pglb', Constraint: pglb [ON]),
('pgub', Constraint: pgub [ON]),
('sba', Constraint: sbus [ON]),
('pb', Constraint: pb [ON]),
('plflb', Constraint: plflb [ON]),
('plfub', Constraint: plfub [ON]),
('alflb', Constraint: alflb [ON]),
('alfub', Constraint: alfub [ON]),
('rbu', Constraint: rbu [ON]),
('rbd', Constraint: rbd [ON]),
('rru', Constraint: rru [ON]),
('rrd', Constraint: rrd [ON]),
('rgu', Constraint: rgu [ON]),
('rgd', Constraint: rgd [ON])])
Then, solve the dispatch and inspect the line flow.
[36]:
spc.RTED.run(solver='ECOS')
<RTED> solved as optimal in 0.0154 seconds, converged in 11 iterations with ECOS.
[36]:
True
[37]:
spc.RTED.plf.v.round(2)
[37]:
array([ 1.2 , 0.87, -1.17, -0.6 , -0.02, -1.15, 1.2 ])
In the next, we can disable specific constraints, and the parameter name takes both single constraint name or a list of constraint names.
[38]:
spc.RTED.disable(['plflb', 'plfub'])
Turn off constraints: plflb, plfub
[38]:
True
Now, it can be seen that the two constraints are disabled.
[39]:
spc.RTED.constrs
[39]:
OrderedDict([('pglb', Constraint: pglb [ON]),
('pgub', Constraint: pgub [ON]),
('sba', Constraint: sbus [ON]),
('pb', Constraint: pb [ON]),
('plflb', Constraint: plflb [OFF]),
('plfub', Constraint: plfub [OFF]),
('alflb', Constraint: alflb [ON]),
('alfub', Constraint: alfub [ON]),
('rbu', Constraint: rbu [ON]),
('rbd', Constraint: rbd [ON]),
('rru', Constraint: rru [ON]),
('rrd', Constraint: rrd [ON]),
('rgu', Constraint: rgu [ON]),
('rgd', Constraint: rgd [ON])])
[40]:
spc.RTED.run(solver='ECOS')
Disabled constraints: plflb, plfub
<RTED> initialized in 0.0013 seconds.
<RTED> solved as optimal in 0.0122 seconds, converged in 11 iterations with ECOS.
[40]:
True
We can see that now the line flow limits are not in effect.
[41]:
spc.RTED.plf.v.round(2)
[41]:
array([ 0.71, 0.69, 0. , -1.59, 0.61, -0.7 , 0.71])
Similarly, you can also enable the constraints again.
[42]:
spc.RTED.enable(['plflb', 'plfub'])
Turn on constraints: plflb, plfub
[42]:
True
[43]:
spc.RTED.constrs
[43]:
OrderedDict([('pglb', Constraint: pglb [ON]),
('pgub', Constraint: pgub [ON]),
('sba', Constraint: sbus [ON]),
('pb', Constraint: pb [ON]),
('plflb', Constraint: plflb [ON]),
('plfub', Constraint: plfub [ON]),
('alflb', Constraint: alflb [ON]),
('alfub', Constraint: alfub [ON]),
('rbu', Constraint: rbu [ON]),
('rbd', Constraint: rbd [ON]),
('rru', Constraint: rru [ON]),
('rrd', Constraint: rrd [ON]),
('rgu', Constraint: rgu [ON]),
('rgd', Constraint: rgd [ON])])
[44]:
spc.RTED.run(solver='ECOS')
<RTED> initialized in 0.0008 seconds.
<RTED> solved as optimal in 0.0128 seconds, converged in 11 iterations with ECOS.
[44]:
True
[45]:
spc.RTED.plf.v.round(2)
[45]:
array([ 1.2 , 0.87, -1.17, -0.6 , -0.02, -1.15, 1.2 ])
Alternatively, you can also force init the dispatch to rebuild the system matrices, enable all constraints, and re-init the optimization models.
[46]:
spc.RTED.disable(['plflb', 'plfub', 'rgu', 'rgd'])
Turn off constraints: plflb, plfub, rgu, rgd
[46]:
True
[47]:
spc.RTED.init(force=True)
<RTED> initialized in 0.0017 seconds.
[47]:
True
[48]:
spc.RTED.constrs
[48]:
OrderedDict([('pglb', Constraint: pglb [ON]),
('pgub', Constraint: pgub [ON]),
('sba', Constraint: sbus [ON]),
('pb', Constraint: pb [ON]),
('plflb', Constraint: plflb [ON]),
('plfub', Constraint: plfub [ON]),
('alflb', Constraint: alflb [ON]),
('alfub', Constraint: alfub [ON]),
('rbu', Constraint: rbu [ON]),
('rbd', Constraint: rbd [ON]),
('rru', Constraint: rru [ON]),
('rrd', Constraint: rrd [ON]),
('rgu', Constraint: rgu [ON]),
('rgd', Constraint: rgd [ON])])
Alter Config#
Routines have an config
attribute as configuration settings.
[49]:
spf = ams.load(ams.get_case('5bus/pjm5bus_uced.xlsx'),
setup=True,
no_output=True,)
Parsing input file "/Users/jinningwang/Documents/work/mambaforge/envs/amsre/lib/python3.9/site-packages/ams/cases/5bus/pjm5bus_uced.xlsx"...
Input file parsed in 0.0401 seconds.
Zero line rates detacted in rate_b, rate_c, adjusted to 999.
System set up in 0.0030 seconds.
In RTED, the default interval is 5/60 [hour], and the formulations has been adjusted to fit the interval.
[50]:
spf.RTED.config
[50]:
OrderedDict([('t', 0.08333333333333333)])
[51]:
spf.RTED.run(solver='ECOS')
<RTED> initialized in 0.0119 seconds.
<RTED> solved as optimal in 0.0144 seconds, converged in 12 iterations with ECOS.
[51]:
True
[52]:
spf.RTED.obj.v
[52]:
0.19537500005620873
We can update the interval to 1 [hour] and re-solve the dispatch.
Note that in this senario, compared to DCOPF, RTED has extra costs for pru
and prd
.
[53]:
spf.RTED.config.t = 60/60
Remember to update the parameters after the change.
[54]:
spf.RTED.update()
<RTED> reinit OModel due to non-parametric change.
[54]:
True
[55]:
spf.RTED.run(solver='SCS')
<RTED> solved as optimal in 0.0164 seconds, converged in 325 iterations with SCS.
[55]:
True
We can then get the objective value.
[56]:
spf.RTED.obj.v
[56]:
2.3444999975679632
Note that in this build-in case, the cru
and crd
are defined as zero.
[57]:
spf.RTED.cru.v
[57]:
array([0., 0., 0., 0.])
[58]:
spf.RTED.crd.v
[58]:
array([0., 0., 0., 0.])
As benchmark, we can solve the DCOPF.
[59]:
spf.DCOPF.run(solver='SCS')
<DCOPF> initialized in 0.0058 seconds.
<DCOPF> solved as optimal in 0.0091 seconds, converged in 225 iterations with SCS.
[59]:
True
As expected, the DCOPF has a similar objective value.
[60]:
spf.DCOPF.obj.v
[60]:
2.3445000000033347