Encapsuled the optimization problem calss, AMS provides direct access to the optimization formulation, where users have the option to customize the formulation without playing with the source code.
In this example, we will walk through the optimization problem structure and show how to customize the formulation.
import numpy as np
import ams
import datetime
print("Last run time:", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
print(f'ams:{ams.__version__}')
Last run time: 2024-03-06 20:33:51 ams:0.9.3
ams.config_logger(stream_level=20)
sp = ams.load(ams.get_case('5bus/pjm5bus_demo.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_demo.xlsx"... Input file parsed in 0.1019 seconds. Zero line rates detacted in rate_a, rate_b, rate_c, adjusted to 999. If expect a line outage, please set 'u' to 0. System set up in 0.0020 seconds.
In AMS, a routine collects the descriptive dispatch formulations.
DCOPF
, RTED
, etc, are the subclasses of RoutineBase
.
sp.DCOPF.init()
<DCOPF> initialized in 0.0092 seconds.
True
After successful initialization, the attribute om
is populated with CVXPY-based optimization problem.
The user can even hack to the source prob
attribute to customize it if necessary.
type(sp.DCOPF.om.prob)
cvxpy.problems.problem.Problem
Here we extend DCOPF with consideration of CO2 emission, where the original formulation can be found in the documentation Routine Reference - DCOPF. To simplify the demonstration, following assumptions are made:
Thus, the revised formulation is as follows, where box indicates the revision:
min. ∑(c2p2g+c1pg+ugc0+ceeg)
s.t.
eg−kepg=0
∑eg−te≤0
−pg+ctrl,nepg,0+ctrl,epg,min≤0
pg−ctrl,nepg,0−ctrl,epg,max≤0
Bbusθbus+pinjbus+Clpd+Cshgsh−Cpgp=0
−Bfθbus−pinjf−RATEA≤0
Bfθbus+pinjf−RATEA≤0
−CTfθbus−θmax≤0
CTfθbus−θmax≤0
Decision variables: pg, θbus, eg
Note that line flow plf is calculated as Bfθbus+pinjf after solving the problem.
Services are used to store values or build matrix for easier formulation.
sp.DCOPF.addService(name='te', tex_name='t_e',
unit='t', info='emission cap',
value=12)
ValueService: DCOPF.te
We need the following parameters to be defined as RParam
: ke
and ce
. They should be 1D array in the same length as the number of generators and te
is a scalar.
For a general RParam
, it has attributes model
, indexer
, and imodel
to describe its source model and index model. The definition of c2
in DCOPF source code is a good example.
However, for ones defined through API, since there is no model containing it, all above attributes are not applicable, and the user should be aware of the sequence of the parameters.
Considering the sequence can be indexed by the generator index, it is used to reference the variables order.
Assuming ke
is reciprocal to the generator capacity, and ce
is the same for each generator, we can define the parameters as follows:
# get the generator indices
stg_idx = sp.DCOPF.pg.get_idx()
# get the value of pmax
pmax = sp.DCOPF.get(src='pmax', attr='v', idx=stg_idx)
# assume the emission factor is 1 for all generators
ke = np.ones_like(pmax)
# assume tax is reciprocal of pmax
ce = np.reciprocal(pmax)
sp.DCOPF.addRParam(name='ke', tex_name='k_e',
info='gen emission factor',
model=None, src=None, unit=None,
v=ke)
RParam: DCOPF.ke
sp.DCOPF.addRParam(name='ce', tex_name='c_e',
info='gen emission tax',
model=None, src=None, unit=None,
v=ce)
RParam: DCOPF.ce
The gerator emission eg
is added as a new variable.
sp.DCOPF.addVars(name='eg', tex_name='e_g',
info='Gen emission', unit='t',
model='StaticGen', src=None)
Var: StaticGen.eg
The CO2 emission is an equality constraint, and the CO2 emission cap is a simple linear inequality constraint.
If wish to revise an existing built-in constraint, you can redefine the constraint e_str
attribute.
sp.DCOPF.addConstrs(name='egb', info='Gen emission balance',
e_str='eg - mul(ke, pg)', is_eq=True)
Constraint: egb [ON]
sp.DCOPF.addConstrs(name='eub', info='emission upper bound',
e_str='sum(eg) - te', is_eq=False,)
Constraint: eub [ON]
The e_str
can be revised to include the CO2 emission tax.
Here we only need to append the tax term to the original objective function.
sp.DCOPF.obj.e_str += '+ sum(mul(ce, pg))'
After revising the problem, remember to initialize it before solving.
sp.DCOPF.init()
<DCOPF> initialized in 0.0066 seconds.
True
sp.DCOPF.run(solver='ECOS')
<DCOPF> solved as optimal in 0.0109 seconds, converged in 9 iterations with ECOS.
True
Inspect the results.
sp.DCOPF.eg.v
array([0.2, 5.2, 4.4, 0.2])
sp.DCOPF.pg.v
array([0.2, 5.2, 4.4, 0.2])
sp.DCOPF.obj.v
5.297571428627203
Load the original problem as a baseline for comparison.
sp0 = ams.load(ams.get_case('5bus/pjm5bus_demo.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_demo.xlsx"... Input file parsed in 0.0422 seconds. Zero line rates detacted in rate_a, rate_b, rate_c, adjusted to 999. If expect a line outage, please set 'u' to 0. System set up in 0.0075 seconds.
sp0.DCOPF.run(solver='ECOS')
<DCOPF> initialized in 0.0068 seconds. <DCOPF> solved as optimal in 0.0126 seconds, converged in 9 iterations with ECOS.
True
From the comparasion, we can see that the generation schedule changes.
sp0.DCOPF.pg.v
array([2.1, 5.2, 0.7, 2. ])
sp0.DCOPF.obj.v
2.3445000004668826