Simulate#

This example gives a “hello world” example to use AMS.

Import and Setting the Verbosity Level#

We first import the ams library.

[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:32
ams:0.9.6

We can configure the verbosity level for logging (output messages) by passing a verbosity level (10-DEBUG, 20-INFO, 30-WARNING, 40-ERROR, 50-CRITICAL) to the stream_level argument of ams.main.config_logger(). Verbose level 10 is useful for getting debug output.

The logging level can be altered by calling config_logger again with new stream_level and file_level.

[3]:
ams.config_logger(stream_level=20)

Note that the above ams.config_logger() is a shorthand to ams.main.config_logger().

If this step is omitted, the default INFO level (stream_level=20) will be used.

Run Simulations#

Load Case#

AMS supports multiple input file formats, including AMS .xlsx file, MATPOWER .m file, PYPOWER .py file, and PSS/E .raw file.

Here we use the AMS .xlsx file as an example. The source file locates at $HOME/ams/ams/cases/ieee39/ieee39_uced.xlsx.

[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.1118 seconds.
Zero line rates detacted in rate_b, rate_c, adjusted to 999.
System set up in 0.0020 seconds.

Inspect Models and Routines#

In AMS, model refers to the device-level models, and they are registered to an OrderedDict models.

[5]:
sp.models
[5]:
OrderedDict([('Summary', Summary (3 devices) at 0x1271e9970),
             ('Bus', Bus (5 devices) at 0x2920881c0),
             ('PQ', PQ (3 devices) at 0x292088940),
             ('Slack', Slack (1 device) at 0x29209d5b0),
             ('PV', PV (3 devices) at 0x2920ad490),
             ('Shunt', Shunt (0 devices) at 0x2920adee0),
             ('Line', Line (7 devices) at 0x2920bb3d0),
             ('PVD1', PVD1 (0 devices) at 0x2920c9ac0),
             ('ESD1', ESD1 (0 devices) at 0x2920db160),
             ('REGCA1', REGCA1 (0 devices) at 0x2920db6d0),
             ('REGCV1', REGCV1 (0 devices) at 0x2920dbcd0),
             ('REGCV2', REGCV2 (0 devices) at 0x2920e8550),
             ('Area', Area (3 devices) at 0x2920e8af0),
             ('Region', Region (2 devices) at 0x2920f02b0),
             ('SFR', SFR (2 devices) at 0x2920f0a60),
             ('SR', SR (2 devices) at 0x292207100),
             ('NSR', NSR (2 devices) at 0x292207520),
             ('VSGR', VSGR (0 devices) at 0x292207940),
             ('GCost', GCost (4 devices) at 0x292207d90),
             ('SFRCost', SFRCost (4 devices) at 0x292211460),
             ('SRCost', SRCost (4 devices) at 0x292211a00),
             ('NSRCost', NSRCost (4 devices) at 0x292211e20),
             ('VSGCost', VSGCost (0 devices) at 0x29221e280),
             ('DCost', DCost (3 devices) at 0x29221e580),
             ('TimeSlot', TimeSlot (0 devices) at 0x29221eaf0),
             ('EDTSlot', EDTSlot (24 devices) at 0x2922295b0),
             ('UCTSlot', UCTSlot (24 devices) at 0x2922299d0)])

One can inspect the detailed model data in the form of DataFrame.

[6]:
sp.PQ.as_df()
[6]:
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 AMS, all supported routines are registered to an OrderedDict routines.

[7]:
sp.routines
[7]:
OrderedDict([('DCPF', DCPF at 0x2920880d0),
             ('PFlow', PFlow at 0x292239640),
             ('CPF', CPF at 0x292239d00),
             ('ACOPF', ACOPF at 0x29224c3a0),
             ('DCOPF', DCOPF at 0x29224cc70),
             ('ED', ED at 0x29226cc40),
             ('EDDG', EDDG at 0x2922a4100),
             ('EDES', EDES at 0x2922b8d30),
             ('RTED', RTED at 0x2922ef490),
             ('RTEDDG', RTEDDG at 0x2922ef550),
             ('RTEDES', RTEDES at 0x292327040),
             ('RTEDVIS', RTEDVIS at 0x29233bfa0),
             ('UC', UC at 0x29235c940),
             ('UCDG', UCDG at 0x2960517f0),
             ('UCES', UCES at 0x296075940),
             ('DOPF', DOPF at 0x2960ae580),
             ('DOPFVIS', DOPFVIS at 0x2960c0a60)])

Solve a Routine#

Before solving a routine, we need to initialize it first. Here Real-time Economic Dispatch (RTED) is used as an example.

[8]:
sp.RTED.init()
<RTED> initialized in 0.0133 seconds.
[8]:
True

Then, one can solve it by calling run(). Here, argument solver can be passed to specify the solver to use, such as solver='ECOS'.

Installed solvers can be listed by ams.shared.INSTALLED_SOLVERS, and more detailes of solver can be found at CVXPY-Choosing a solver.

[9]:
ams.shared.INSTALLED_SOLVERS
[9]:
['CLARABEL', 'ECOS', 'ECOS_BB', 'GUROBI', 'OSQP', 'SCIP', 'SCIPY', 'SCS']
[10]:
sp.RTED.run(solver='ECOS')
<RTED> solved as optimal in 0.0165 seconds, converged in 12 iterations with ECOS.
[10]:
True

The solved results are stored in each variable itself. For example, the solved power generation of ten generators are stored in pg.v.

[11]:
sp.RTED.pg.v
[11]:
array([2. , 2.1, 5.2, 0.7])

Here, get_idx() can be used to get the index of a variable.

[12]:
sp.RTED.pg.get_idx()
[12]:
['Slack_4', 'PV_1', 'PV_3', 'PV_5']

Part of the solved results can be accessed with given indices.

[13]:
sp.RTED.get(src='pg', attr='v', idx=['PV_1', 'PV_3'])
[13]:
array([2.1, 5.2])

All Vars are listed in an OrderedDict vars.

[14]:
sp.RTED.vars
[14]:
OrderedDict([('pg', Var: StaticGen.pg),
             ('aBus', Var: Bus.aBus),
             ('pi', Var: Bus.pi),
             ('plf', Var: Line.plf),
             ('pru', Var: StaticGen.pru),
             ('prd', Var: StaticGen.prd)])

The Objective value can be accessed with obj.v.

[15]:
sp.RTED.obj.v
[15]:
0.19537500005620873

Similarly, all Constrs are listed in an OrderedDict constrs, and the expression values can also be accessed.

[16]:
sp.RTED.constrs
[16]:
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])])

One can also inspect the Constraint values.

[17]:
sp.RTED.rgu.v
[17]:
array([-997. , -996.9, -993.8, -998.3])