Manipulate the Simulation#

This example shows how to play with the simulation, such as contingency analysis and manipulate the constraints.

[1]:
import ams
[2]:
ams.config_logger(stream_level=20)

Manipulate the Simulation#

Load Case#

[3]:
sp = ams.load(ams.get_case('5bus/pjm5bus_demo.xlsx'),
              setup=True,
              no_output=True,)
Parsing input file "/Users/jinningwang/work/ams/ams/cases/5bus/pjm5bus_demo.xlsx"...
Input file parsed in 0.0727 seconds.
Zero Line parameters detected, adjusted to default values: rate_b, rate_c.
All bus type are PQ, adjusted given load and generator connection status.
System set up in 0.0022 seconds.

The system load are defined in model PQ.

[4]:
sp.PQ.as_df()
[4]:
idx u name bus Vn p0 q0 vmax vmin owner ctrl
uid
0 PQ_1 1.0 PQ 1 1 230.0 3.0 0.9861 1.1 0.9 None 1.0
1 PQ_2 1.0 PQ 2 2 230.0 3.0 0.9861 1.1 0.9 None 1.0
2 PQ_3 1.0 PQ 3 3 230.0 4.0 1.3147 1.1 0.9 None 1.0

In RTED, system load is referred as pd.

[5]:
sp.RTED.pd.v
[5]:
array([3., 3., 4.])

Run Simulation#

RTED can be solved and we can inspect the results as discussed in previous example.

[6]:
sp.RTED.run(solver='CLARABEL')
Building system matrices
Parsing OModel for <RTED>
Evaluating OModel for <RTED>
Finalizing OModel for <RTED>
<RTED> initialized in 0.0148 seconds.
<RTED> solved as optimal in 0.0124 seconds, converged in 10 iterations with CLARABEL.
[6]:
True

Power generation pg and line flow plf can be accessed as follows.

[7]:
sp.RTED.pg.v
[7]:
array([0.2       , 1.43998388, 0.6       , 5.76001612, 2.        ])
[8]:
sp.RTED.plf.v
[8]:
array([-0.38000806,  0.78912154,  0.17089459,  2.        ,  0.43998388,
       -0.77089459, -0.38000806])

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.

[9]:
sp.PQ.alter(src='p0', idx=['PQ_1', 'PQ_2'], value=[3.2, 3.2])

According parameters need to be updated to make the changes effective in the optimization model. If not sure which parameters need to be updated, we can use update() to update all parameters.

[10]:
sp.RTED.update('pd')
<RTED> reinit OModel due to non-parametric change.
Evaluating OModel for <RTED>
Finalizing OModel for <RTED>
[10]:
True

After manipulation, the routined can be solved again.

[11]:
sp.RTED.run(solver='CLARABEL')
<RTED> solved as optimal in 0.0100 seconds, converged in 10 iterations with CLARABEL.
[11]:
True
[12]:
sp.RTED.pg.v
[12]:
array([0.20000001, 1.63998388, 0.6       , 5.96001612, 1.99999999])

An alternative way is to alter the load through RTED.

As pd0 has owner StaticLoad and soruce p0, the parameter update through RTED actually happens to StaticLoad.p0.

[13]:
sp.RTED.pd0.owner
[13]:
StaticLoad (3 devices) at 0x30790f170
[14]:
sp.RTED.pd0.src
[14]:
'p0'

Similarly, the load can be changed using set method.

[15]:
sp.RTED.set(src='pd0', attr='v', idx=['PQ_1', 'PQ_2'], value=[0.2, 0.2])
[15]:
True

Remember to update the optimization parameters after the change.

[16]:
sp.RTED.update('pd0')
[16]:
True

We can see that the original load is also updated.

[17]:
sp.PQ.as_df()
[17]:
idx u name bus Vn p0 q0 vmax vmin owner ctrl
uid
0 PQ_1 1.0 PQ 1 1 230.0 0.2 0.9861 1.1 0.9 None 1.0
1 PQ_2 1.0 PQ 2 2 230.0 0.2 0.9861 1.1 0.9 None 1.0
2 PQ_3 1.0 PQ 3 3 230.0 4.0 1.3147 1.1 0.9 None 1.0
[18]:
sp.RTED.run(solver='CLARABEL')
<RTED> solved as optimal in 0.0010 seconds, converged in 10 iterations with CLARABEL.
[18]:
True

As expected, the power generation also changed.

[19]:
sp.RTED.pg.v
[19]:
array([0.20000001, 1.63998388, 0.6       , 5.96001612, 1.99999999])

Trip a Generator#

We can see that there are three PV generators in the system.

Warning: in MatProcessor, StaticGen online status is NOT considered in its connectivity matrix Cg. The same applies for PQ, Line, and Shunt.

[20]:
sp.PV.as_df()
[20]:
idx u name Sn Vn bus busr p0 q0 pmax ... R30 Rq apf pg0 td1 td2 ton0 toff0 gentype genfuel
uid
0 PV_1 1.0 Alta 100.0 230.0 0 None 1.0000 0.0 2.1 ... 999.0 999.0 0.0 0.0 0.5 0.0 0.0 0.0 None None
1 PV_3 1.0 Solitude 100.0 230.0 2 None 3.2349 0.0 5.2 ... 999.0 999.0 0.0 0.0 0.5 0.0 0.0 0.0 None None
2 PV_5 1.0 Brighton 100.0 230.0 4 None 4.6651 0.0 6.0 ... 999.0 999.0 0.0 0.0 0.5 0.0 0.0 0.0 None None
3 PV_2 1.0 PV 2 100.0 230.0 1 None 0.1000 0.0 99.0 ... 999.0 999.0 0.0 0.0 0.5 0.0 0.0 0.0 None None

4 rows × 38 columns

PV_1 is tripped by setting its connection status u to 0.

[21]:
sp.StaticGen.set(src='u', idx='PV_1', attr='v', value=0)
[21]:
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.

[22]:
sp.RTED.update()
Building system matrices
<RTED> reinit OModel due to non-parametric change.
Evaluating OModel for <RTED>
Finalizing OModel for <RTED>
[22]:
True

Then we can re-solve the model.

[23]:
sp.RTED.run(solver='CLARABEL')
<RTED> solved as optimal in 0.0094 seconds, converged in 9 iterations with CLARABEL.
[23]:
True

We can see that the tripped generator has no power generation.

[24]:
sp.RTED.pg.v.round(2)
[24]:
array([-0.  ,  0.5 ,  0.6 ,  2.97,  0.33])

Trip a Line#

We can inspect the Line model to check the system topology.

[25]:
sp.Line.as_df()
[25]:
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_1 1.0 Line AB 0 1 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_2 1.0 Line AD 0 3 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_3 1.0 Line AE 0 4 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_4 1.0 Line BC 1 2 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_5 1.0 Line CD 2 3 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_6 1.0 Line DE 3 4 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_7 1.0 Line AB2 0 1 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``.

[26]:
sp.Line.alter(src='u', idx='Line_1', value=0)
[27]:
sp.RTED.update()
Building system matrices
<RTED> reinit OModel due to non-parametric change.
Evaluating OModel for <RTED>
Finalizing OModel for <RTED>
[27]:
True
[28]:
sp.RTED.run(solver='CLARABEL')
<RTED> solved as optimal in 0.0097 seconds, converged in 9 iterations with CLARABEL.
[28]:
True

Here we can see the tripped line has no flow.

[29]:
sp.RTED.plf.v.round(2)
[29]:
array([ 0.  ,  0.99,  0.34,  1.44,  1.74, -0.94, -1.33])

Disable Constraints#

In addition to the system parameters, the constraints can also be manipulated.

Here, we load the case to a new system.

[30]:
spc = ams.load(ams.get_case('5bus/pjm5bus_demo.xlsx'),
               setup=True,
               no_output=True,)
Parsing input file "/Users/jinningwang/work/ams/ams/cases/5bus/pjm5bus_demo.xlsx"...
Input file parsed in 0.0297 seconds.
Zero Line parameters detected, adjusted to default values: rate_b, rate_c.
All bus type are PQ, adjusted given load and generator connection status.
System set up in 0.0020 seconds.
[31]:
spc.RTED.init()
Building system matrices
Parsing OModel for <RTED>
Evaluating OModel for <RTED>
Finalizing OModel for <RTED>
<RTED> initialized in 0.0096 seconds.
[31]:
True
[32]:
spc.RTED.set(src='rate_a', idx=['Line_3'], attr='v', value=0.6)
[32]:
True
[33]:
spc.RTED.update('rate_a')
[33]:
True

We can inspect the constraints status as follows. All constraints are turned on by default.

[34]:
spc.RTED.constrs
[34]:
OrderedDict([('pb', Constraint: pb [ON]),
             ('sba', Constraint: sbus [ON]),
             ('pglb', Constraint: pglb [ON]),
             ('pgub', Constraint: pgub [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.

[35]:
spc.RTED.run(solver='CLARABEL')
<RTED> solved as optimal in 0.0108 seconds, converged in 10 iterations with CLARABEL.
[35]:
True
[36]:
spc.RTED.plf.v.round(2)
[36]:
array([-0.38,  0.79,  0.17,  2.  ,  0.44, -0.77, -0.38])

In the next, we can disable specific constraints, and the parameter name takes both single constraint name or a list of constraint names.

[37]:
spc.RTED.disable(['plflb', 'plfub'])
Turn off constraints: plflb, plfub
[37]:
True

Now, it can be seen that the two constraints are disabled.

[38]:
spc.RTED.constrs
[38]:
OrderedDict([('pb', Constraint: pb [ON]),
             ('sba', Constraint: sbus [ON]),
             ('pglb', Constraint: pglb [ON]),
             ('pgub', Constraint: pgub [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])])
[39]:
spc.RTED.run(solver='CLARABEL')
Disabled constraints: plflb, plfub
Finalizing OModel for <RTED>
<RTED> initialized in 0.0018 seconds.
<RTED> solved as optimal in 0.0083 seconds, converged in 9 iterations with CLARABEL.
[39]:
True

We can see that now the line flow limits are not in effect.

[40]:
spc.RTED.plf.v.round(2)
[40]:
array([-0.9 ,  1.36,  0.65,  3.48,  0.98, -1.25, -0.9 ])

Similarly, you can also enable the constraints again.

[41]:
spc.RTED.enable(['plflb', 'plfub'])
Turn on constraints: plflb, plfub
[41]:
True
[42]:
spc.RTED.constrs
[42]:
OrderedDict([('pb', Constraint: pb [ON]),
             ('sba', Constraint: sbus [ON]),
             ('pglb', Constraint: pglb [ON]),
             ('pgub', Constraint: pgub [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])])
[43]:
spc.RTED.run(solver='CLARABEL')
Finalizing OModel for <RTED>
<RTED> initialized in 0.0023 seconds.
<RTED> solved as optimal in 0.0105 seconds, converged in 10 iterations with CLARABEL.
[43]:
True
[44]:
spc.RTED.plf.v.round(2)
[44]:
array([-0.38,  0.79,  0.17,  2.  ,  0.44, -0.77, -0.38])

Alternatively, you can also force init the dispatch to rebuild the system matrices, enable all constraints, and re-init the optimization models.

[45]:
spc.RTED.disable(['plflb', 'plfub', 'rgu', 'rgd'])
Turn off constraints: plflb, plfub, rgu, rgd
[45]:
True
[46]:
spc.RTED.init(force=True)
Disabled constraints: plflb, plfub, rgu, rgd
Finalizing OModel for <RTED>
<RTED> initialized in 0.0021 seconds.
[46]:
True
[47]:
spc.RTED.constrs
[47]:
OrderedDict([('pb', Constraint: pb [ON]),
             ('sba', Constraint: sbus [ON]),
             ('pglb', Constraint: pglb [ON]),
             ('pgub', Constraint: pgub [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 [OFF]),
             ('rgd', Constraint: rgd [OFF])])

Alter Config#

Routines have an config attribute as configuration settings.

[48]:
spf = ams.load(ams.get_case('5bus/pjm5bus_demo.xlsx'),
               setup=True,
               no_output=True,)
Parsing input file "/Users/jinningwang/work/ams/ams/cases/5bus/pjm5bus_demo.xlsx"...
Input file parsed in 0.0505 seconds.
Zero Line parameters detected, adjusted to default values: rate_b, rate_c.
All bus type are PQ, adjusted given load and generator connection status.
System set up in 0.0025 seconds.

In RTED, the default interval is 5/60 [hour], and the formulations has been adjusted to fit the interval.

[49]:
spf.RTED.config
[49]:
OrderedDict({'t': 0.08333333333333333})
[50]:
spf.RTED.run(solver='CLARABEL')
Building system matrices
Parsing OModel for <RTED>
Evaluating OModel for <RTED>
Finalizing OModel for <RTED>
<RTED> initialized in 0.0095 seconds.
<RTED> solved as optimal in 0.0110 seconds, converged in 10 iterations with CLARABEL.
[50]:
True
[51]:
spf.RTED.obj.v
[51]:
np.float64(0.7946627709619764)

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.

[52]:
spf.RTED.config.update(t=1)
[53]:
spf.RTED.config
[53]:
OrderedDict({'t': 1})

Remember to update the parameters after the change.

[54]:
spf.RTED.update()
Building system matrices
<RTED> reinit OModel due to non-parametric change.
Evaluating OModel for <RTED>
Finalizing OModel for <RTED>
[54]:
True
[55]:
spf.RTED.run(solver='SCS')
<RTED> solved as optimal in 0.0136 seconds, converged in 1725 iterations with SCS.
[55]:
True

We can then get the objective value.

[56]:
spf.RTED.obj.v
[56]:
np.float64(9.535824499727658)

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., 0.])
[58]:
spf.RTED.crd.v
[58]:
array([0., 0., 0., 0., 0.])

As benchmark, we can solve the DCOPF.

[59]:
spf.DCOPF.run(solver='SCS')
Parsing OModel for <DCOPF>
Evaluating OModel for <DCOPF>
Finalizing OModel for <DCOPF>
<DCOPF> initialized in 0.0064 seconds.
<DCOPF> solved as optimal in 0.0068 seconds, converged in 825 iterations with SCS.
[59]:
True

As expected, the DCOPF has a similar objective value.

[60]:
spf.DCOPF.obj.v
[60]:
np.float64(9.535972247191847)