Market Analysis with Integrated Ultra-supercritical Power Plant Model: Pricetaker Assumption
Author: Naresh Susarla (naresh.susarla@netl.doe.gov)
This notebook presents a market analysis considering the pricetaker assumption for the integrated system of ultra-supercritical power plant and Solar salt as the storage fluid in thermal energy storage system. The electricity prices or LMP (locational marginal prices) are given and remain constant through the assumed time horizon. The prices used in this study are obtained from a synthetic database, i.e. RTS-GMLC.
To start this analysis, first import all packages and libraries required.
[1]:
from pyomo.environ import Param, Objective, Expression, SolverFactory, value
import numpy as np
from dispatches.models.fossil_case.ultra_supercritical_plant.storage.\
multiperiod_integrated_storage_usc import create_multiperiod_usc_model
# For plots
from matplotlib import pyplot as plt
import matplotlib
matplotlib.rc('font', size=24)
plt.rc('axes', titlesize=24)
Get the LMP data from a database or define a list of electricity prices. In this analysis, we define a list and populate it with custom electricity prices. This list is named price
.
[2]:
# Select LMP source data and scaling factor
use_rts_data = False
use_mod_rts_data = True
if use_rts_data:
print('>>>>>> Using RTS lmp data')
with open('rts_results_all_prices_base_case.npy', 'rb') as f:
price = np.load(f)
elif use_mod_rts_data:
price = [22.9684, 21.1168, 20.4, 20.419,
20.419, 21.2877, 23.07, 25,
18.4634, 0, 0, 0,
0, 0, 0, 0,
19.0342, 23.07, 200, 200,
200, 200, 200, 200]
else:
print('>>>>>> Using NREL lmp data')
price = np.load("nrel_scenario_average_hourly.npy")
if use_rts_data:
lmp = price[0:number_hours].tolist()
elif use_mod_rts_data:
lmp = price
Define the time horizon and period for the market analysis using the pricetaker assumption and add the initial status of the Solar salt storage tank.
[3]:
# Add number of days and hours per week
ndays = 1
nweeks = 1
number_hours = 24 * ndays
n_time_points = nweeks * number_hours
# Add status for storage tanks in kg
tank_status = "hot_empty"
tank_min = 1
tank_max = 6739292
Construct a time-indexed multiperiod model using the multiperiod method with the integrated ultra-supercritical power plant. During this step, the model is initialized for each time period.
[4]:
# Create the multiperiod model object
multiperiod_usc = create_multiperiod_usc_model(
n_time_points=n_time_points, pmin=None, pmax=None
)
# Retrieve pyomo model and active time blocks
m = multiperiod_usc.pyomo_model
blks = multiperiod_usc.get_active_process_blocks()
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
>>> Creating USC model and initialization for each time period
Use the LMP data from the price
list above to assign the electricity prices to each time period. Also, include revenue and operating cost functions to calculate a total cost.
[5]:
count = 0
for blk in blks:
blk_usc_mp = blk.usc_mp
blk.lmp_signal = Param(default=0, mutable=True)
blk.revenue = lmp[count]*blk.usc_mp.fs.net_power
blk.operating_cost = Expression(
expr=(
(blk_usc_mp.fs.operating_cost
+ blk_usc_mp.fs.plant_fixed_operating_cost
+ blk_usc_mp.fs.plant_variable_operating_cost) / (365 * 24)
)
)
blk.cost = Expression(expr=-(blk.revenue - blk.operating_cost))
count += 1
Add the total cost expression as the objective function.
[6]:
m.obj = Objective(expr=sum([blk.cost for blk in blks]))
Add initial state for the two linking variables in the model: the Solar salt tank for different scenarios and the power.
[7]:
# Initial state for Solar salt tank for differrent tank scenarios:"hot_empty","hot_full","hot_half_full"
if tank_status == "hot_empty":
blks[0].usc_mp.previous_salt_inventory_hot.fix(1103053.48)
blks[0].usc_mp.previous_salt_inventory_cold.fix(tank_max-1103053.48)
elif tank_status == "half_full":
blks[0].usc_mp.previous_salt_inventory_hot.fix(tank_max/2)
blks[0].usc_mp.previous_salt_inventory_cold.fix(tank_max/2)
elif tank_status == "hot_full":
blks[0].usc_mp.previous_salt_inventory_hot.fix(tank_max-tank_min)
blks[0].usc_mp.previous_salt_inventory_cold.fix(tank_min)
else:
print("Unrecognized scenario! Try hot_empty, hot_full, or half_full")
# Initial state for power linking variable
blks[0].usc_mp.previous_power.fix(447.66)
Finally, solve the entire multi-period model and save the results in lists to be used later to plot the results.
[8]:
# Declare NLP solver
opt = SolverFactory('ipopt')
# Declare lists to save results
hot_tank_level = []
cold_tank_level = []
net_power = []
hxc_duty = []
hxd_duty = []
for week in range(nweeks):
print()
print(">>>>>> Solving for week {}: {} hours of operation in {} day(s) "
.format(week + 1, number_hours, ndays))
# Solve the multi-period model
opt.solve(m, tee=True)
hot_tank_level.append([(value(blks[i].usc_mp.salt_inventory_hot)) * 1e-3
for i in range(n_time_points)])
cold_tank_level.append([(value(blks[i].usc_mp.salt_inventory_cold)) * 1e-3
for i in range(n_time_points)])
net_power.append([value(blks[i].usc_mp.fs.net_power)
for i in range(n_time_points)])
hxc_duty.append([value(blks[i].usc_mp.fs.hxc.heat_duty[0]) * 1e-6
for i in range(n_time_points)])
hxd_duty.append([value(blks[i].usc_mp.fs.hxd.heat_duty[0]) * 1e-6
for i in range(n_time_points)])
>>>>>> Solving for week 1: 24 hours of operation in 1 day(s)
Ipopt 3.13.2:
******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
Ipopt is released as open source code under the Eclipse Public License (EPL).
For more information visit http://projects.coin-or.org/Ipopt
This version of Ipopt was compiled from source code available at
https://github.com/IDAES/Ipopt as part of the Institute for the Design of
Advanced Energy Systems Process Systems Engineering Framework (IDAES PSE
Framework) Copyright (c) 2018-2019. See https://github.com/IDAES/idaes-pse.
This version of Ipopt was compiled using HSL, a collection of Fortran codes
for large-scale scientific computation. All technical papers, sales and
publicity material resulting from use of the HSL codes within IPOPT must
contain the following acknowledgement:
HSL, a collection of Fortran codes for large-scale scientific
computation. See http://www.hsl.rl.ac.uk.
******************************************************************************
This is Ipopt version 3.13.2, running with linear solver ma27.
Number of nonzeros in equality constraint Jacobian...: 38348
Number of nonzeros in inequality constraint Jacobian.: 284
Number of nonzeros in Lagrangian Hessian.............: 11880
Total number of variables............................: 14685
variables with only lower bounds: 0
variables with lower and upper bounds: 11997
variables with only upper bounds: 24
Total number of equality constraints.................: 14567
Total number of inequality constraints...............: 168
inequality constraints with only lower bounds: 24
inequality constraints with lower and upper bounds: 0
inequality constraints with only upper bounds: 144
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
0 -3.6195143e+05 3.34e+08 1.00e+02 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0
1 -3.7026756e+05 3.46e+08 1.25e+06 -1.0 4.01e+07 - 1.25e-06 9.48e-01f 1
2 -3.7025319e+05 3.45e+08 1.24e+06 -1.0 7.47e+07 - 9.36e-05 1.65e-03h 1
3 -3.7023560e+05 3.44e+08 1.24e+06 -1.0 4.95e+07 - 2.26e-04 1.94e-03h 1
4 -3.6961337e+05 3.30e+08 1.16e+06 -1.0 4.62e+07 - 2.36e-04 6.82e-02h 1
5 -3.6957659e+05 3.29e+08 1.15e+06 -1.0 3.90e+07 - 7.22e-04 4.11e-03h 1
6 -3.6183421e+05 4.56e+08 9.37e+04 -1.0 3.69e+07 - 7.23e-05 8.66e-01h 1
7 -3.6136822e+05 3.19e+08 6.57e+04 -1.0 4.23e+06 - 8.26e-03 3.00e-01h 1
8 -3.6022203e+05 9.52e+06 1.19e+04 -1.0 2.83e+06 - 1.48e-02 1.00e+00h 1
9 -3.6015086e+05 3.53e+05 3.85e+02 -1.0 8.53e+05 - 7.40e-01 1.00e+00h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
10 -3.6027717e+05 4.42e+02 1.21e+01 -1.0 2.00e+05 - 9.12e-01 1.00e+00f 1
11 -3.6177457e+05 1.30e+03 2.34e+00 -1.0 2.18e+06 - 2.70e-01 1.00e+00f 1
12 -3.6383428e+05 2.44e+03 3.85e-01 -1.0 3.00e+06 - 6.01e-01 1.00e+00f 1
13 -3.6905559e+05 1.56e+04 6.76e-01 -1.0 7.60e+06 - 4.55e-01 1.00e+00f 1
14 -3.7885905e+05 5.37e+04 1.12e+00 -1.0 1.43e+07 - 5.19e-01 1.00e+00f 1
15 -4.0022144e+05 2.46e+05 2.43e+00 -1.0 3.12e+07 - 5.86e-01 1.00e+00f 1
16 -4.1081688e+05 2.57e+05 3.19e+00 -1.0 8.53e+07 - 5.45e-01 1.82e-01f 1
17 -4.1094134e+05 2.57e+05 1.42e+01 -1.0 2.02e+08 - 4.93e-01 9.95e-04f 1
18 -4.1119824e+05 2.43e+05 3.05e+01 -1.0 2.79e+07 - 2.78e-01 5.44e-02f 1
19 -4.1251280e+05 1.61e+05 1.97e+01 -1.0 3.80e+07 - 3.24e-01 3.35e-01f 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
20 -4.1810215e+05 7.56e+05 2.67e+01 -1.0 5.42e+07 - 3.71e-01 1.00e+00f 1
21 -4.1966467e+05 6.78e+05 8.99e+00 -1.0 7.66e+07 - 6.63e-01 1.86e-01f 1
22 -4.1982865e+05 6.73e+05 9.09e+00 -1.0 1.95e+08 - 3.42e-01 7.92e-03f 1
23 -4.2176974e+05 7.08e+05 2.09e+01 -1.0 3.74e+08 - 3.47e-01 6.71e-02f 1
24 -4.2325496e+05 7.21e+05 1.95e+01 -1.0 5.47e+08 - 1.32e-01 3.82e-02f 1
25 -4.2476372e+05 7.44e+05 1.72e+01 -1.0 4.52e+08 - 1.46e-01 4.16e-02f 1
26 -4.2605895e+05 7.61e+05 2.34e+01 -1.0 4.50e+08 - 2.66e-01 3.66e-02f 1
27 -4.2849793e+05 9.79e+05 2.19e+01 -1.0 3.32e+08 - 2.10e-02 7.40e-02f 1
28 -4.2939062e+05 1.05e+06 2.14e+01 -1.0 2.39e+08 - 2.26e-02 3.17e-02f 1
29 -4.2960216e+05 1.05e+06 2.12e+01 -1.0 2.35e+08 - 9.25e-03 1.30e-02f 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
30 -4.3233755e+05 3.86e+06 1.68e+01 -1.0 2.36e+08 - 2.05e-01 1.71e-01f 1
31 -4.3245657e+05 3.84e+06 1.38e+01 -1.0 2.39e+08 - 1.57e-01 7.67e-03f 1
32 -4.3339951e+05 4.04e+06 9.05e+00 -1.0 1.85e+08 - 5.32e-01 6.01e-02f 1
33 -4.3702388e+05 1.04e+07 7.45e+00 -1.0 8.82e+07 - 5.42e-01 2.06e-01f 1
34 -4.5216928e+05 9.77e+07 3.02e+00 -1.0 7.08e+07 - 6.95e-01 9.48e-01f 1
35 -4.5420008e+05 3.17e+07 3.29e+00 -1.0 4.62e+07 - 7.51e-01 7.55e-01f 1
36 -4.5422349e+05 3.05e+07 5.02e+00 -1.0 5.57e+07 - 4.08e-01 3.84e-02h 1
37 -4.5428523e+05 2.72e+07 6.68e+00 -1.0 1.20e+08 - 4.33e-01 1.14e-01h 1
38 -4.5436396e+05 2.28e+07 6.54e+00 -1.0 1.61e+08 - 4.84e-01 1.64e-01h 1
39 -4.5445361e+05 1.67e+07 5.42e+00 -1.0 9.24e+07 - 6.97e-01 2.57e-01h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
40 -4.5449047e+05 1.28e+07 4.51e+00 -1.0 2.61e+07 - 9.01e-01 2.29e-01h 1
41 -4.5448693e+05 1.23e+07 4.38e+03 -1.0 3.09e+05 -4.0 2.83e-02 3.98e-02h 1
42 -4.5448689e+05 1.23e+07 4.37e+03 -1.0 2.96e+05 -4.5 1.39e-01 4.11e-04h 1
43 -4.5448689e+05 1.23e+07 8.03e+06 -1.0 1.12e+06 -5.0 1.05e-01 3.60e-05h 1
44 -4.5448656e+05 1.22e+07 1.31e+07 -1.0 1.26e+06 -5.4 2.48e-01 3.43e-03h 1
45 -4.5448532e+05 1.21e+07 1.28e+07 -1.0 1.34e+06 -5.9 8.16e-02 9.55e-03h 1
46 -4.5447754e+05 1.15e+07 9.93e+06 -1.0 1.33e+06 -6.4 2.87e-01 5.11e-02h 1
47 -4.5447672e+05 1.14e+07 8.67e+06 -1.0 1.29e+06 -6.9 1.38e-01 5.58e-03h 1
48 -4.5447277e+05 1.11e+07 3.32e+06 -1.0 1.29e+06 -7.3 6.21e-01 2.52e-02h 1
49 -4.5443704e+05 8.58e+06 2.88e+06 -1.0 1.27e+06 -7.8 1.38e-01 2.27e-01h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
50 -4.5433013e+05 8.46e+05 1.47e+06 -1.0 1.08e+06 -8.3 5.20e-01 8.80e-01h 1
51 -4.5432975e+05 7.92e+05 8.29e+04 -1.0 2.97e+05 -8.8 9.93e-01 6.45e-02h 1
52 -4.5434596e+05 3.95e+05 4.76e+04 -1.0 2.77e+05 -9.2 1.00e+00 5.02e-01h 1
53 -4.5436542e+05 3.18e+05 3.94e+04 -1.0 1.39e+05 -9.7 1.00e+00 1.94e-01h 1
54 -4.5437694e+05 1.30e+03 5.84e+02 -1.0 1.13e+05 -10.2 1.00e+00 1.00e+00h 1
55 -4.5441945e+05 6.52e+02 2.29e+02 -1.0 2.13e+05 -10.7 1.00e+00 6.08e-01f 1
56 -4.5447296e+05 7.47e+02 1.50e+02 -1.0 5.26e+05 -11.2 1.00e+00 3.47e-01f 1
57 -4.5449740e+05 1.00e+03 1.14e+02 -1.0 6.68e+05 -11.6 1.00e+00 2.39e-01f 1
58 -4.5450659e+05 3.20e+04 4.28e+01 -1.0 1.05e+06 -12.1 1.00e+00 6.25e-01h 1
59 -4.5451163e+05 5.39e+04 3.00e+01 -1.0 2.13e+06 -12.6 1.00e+00 3.02e-01h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
60 -4.5452071e+05 2.94e+04 4.37e-01 -1.0 1.14e+06 -13.1 1.00e+00 1.00e+00h 1
61 -4.5454348e+05 2.11e+05 4.06e-02 -1.0 3.06e+06 -13.5 1.00e+00 1.00e+00h 1
62 -4.5456536e+05 3.25e+05 1.22e+00 -1.0 5.87e+06 -14.0 1.00e+00 5.03e-01h 1
63 -4.5457458e+05 1.38e+06 1.27e+00 -1.0 1.59e+07 -14.5 1.00e+00 5.43e-01h 1
64 -4.5458411e+05 5.28e+06 3.73e-01 -1.0 2.47e+07 -15.0 1.00e+00 9.05e-01h 1
65 -4.5458287e+05 2.50e+06 7.68e-02 -1.0 1.24e+07 -15.5 1.00e+00 1.00e+00h 1
66 -4.5458556e+05 2.39e+05 1.70e-02 -1.0 3.73e+06 -15.0 1.00e+00 1.00e+00h 1
67 -4.5459207e+05 1.42e+07 2.01e+00 -1.0 4.17e+07 -15.5 1.00e+00 1.00e+00H 1
68 -4.5459536e+05 8.22e+06 6.16e-01 -1.0 2.19e+07 -15.1 1.00e+00 1.00e+00h 1
69 -4.5459781e+05 1.76e+05 1.16e-01 -1.0 8.15e+06 -14.7 1.00e+00 1.00e+00h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
70 -4.5459986e+05 8.63e+05 2.62e-01 -1.0 2.24e+07 -15.1 1.00e+00 4.00e-01h 1
71 -4.5459964e+05 1.72e+05 3.58e-02 -1.0 3.94e+06 -14.7 1.00e+00 1.00e+00h 1
72 -4.5460169e+05 3.55e+06 3.98e-01 -1.0 1.75e+07 -15.2 1.00e+00 1.00e+00h 1
73 -4.5460339e+05 3.64e+05 7.96e-02 -1.0 7.50e+06 -14.8 1.00e+00 1.00e+00h 1
74 -4.5460864e+05 1.12e+07 1.38e+00 -1.0 3.74e+07 -15.2 1.00e+00 8.51e-01h 1
75 -4.5460624e+05 4.80e+06 6.25e-01 -1.0 1.92e+07 - 1.00e+00 1.00e+00h 1
76 -4.5460938e+05 1.81e+06 1.95e-01 -1.0 1.10e+07 - 1.00e+00 1.00e+00h 1
77 -4.5460958e+05 8.58e+04 2.33e-02 -1.0 4.17e+06 - 1.00e+00 1.00e+00h 1
78 -4.5460960e+05 6.07e+02 8.30e-05 -1.0 2.43e+05 - 1.00e+00 1.00e+00h 1
79 -4.5462293e+05 1.01e+05 1.85e+01 -2.5 3.69e+06 - 9.72e-01 5.41e-01f 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
80 -4.5462888e+05 5.24e+04 6.09e+00 -2.5 1.29e+06 -14.8 1.00e+00 6.91e-01h 1
81 -4.5463144e+05 2.75e+06 2.30e-01 -2.5 1.43e+07 -15.3 1.00e+00 9.86e-01h 1
82 -4.5463183e+05 5.06e+05 1.52e-02 -2.5 4.03e+06 -14.9 1.00e+00 1.00e+00h 1
83 -4.5463266e+05 4.99e+06 3.33e-01 -2.5 1.24e+08 -15.3 2.60e-01 1.39e-01h 1
84 -4.5463404e+05 1.67e+06 1.68e-01 -2.5 1.15e+07 -14.9 1.00e+00 8.68e-01h 1
85 -4.5463454e+05 2.41e+05 4.24e-02 -2.5 3.17e+06 -14.5 1.00e+00 8.58e-01h 1
86 -4.5463568e+05 1.53e+06 1.50e-01 -2.5 1.42e+07 -15.0 1.00e+00 7.70e-01h 1
87 -4.5463672e+05 6.25e+05 3.88e-02 -2.5 5.53e+06 -14.5 1.00e+00 1.00e+00h 1
88 -4.5463780e+05 1.29e+06 1.86e-01 -2.5 2.58e+07 -15.0 1.00e+00 2.56e-01h 1
89 -4.5463786e+05 3.26e+05 4.34e-02 -2.5 6.68e+05 - 1.00e+00 7.24e-01h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
90 -4.5463797e+05 1.67e+04 2.38e-03 -2.5 4.70e+05 - 1.00e+00 1.00e+00h 1
91 -4.5463797e+05 6.73e-01 1.73e-06 -2.5 1.11e+03 - 1.00e+00 1.00e+00h 1
92 -4.5463850e+05 3.15e+01 4.15e-02 -3.8 4.36e+04 - 1.00e+00 9.65e-01f 1
93 -4.5463851e+05 1.51e+00 3.39e-07 -3.8 1.19e+04 - 1.00e+00 1.00e+00h 1
94 -4.5463854e+05 7.42e-02 7.69e-08 -5.7 2.39e+03 - 1.00e+00 1.00e+00h 1
95 -4.5463854e+05 1.06e-05 1.84e-11 -5.7 3.08e+01 - 1.00e+00 1.00e+00h 1
96 -4.5463854e+05 1.11e-05 1.22e-11 -8.6 2.96e+01 - 1.00e+00 1.00e+00h 1
97 -4.5463854e+05 9.54e-07 7.69e-13 -9.0 2.81e-02 - 1.00e+00 1.00e+00h 1
98 -4.5463854e+05 4.77e-07 9.03e-13 -9.0 6.97e-06 - 1.00e+00 1.00e+00h 1
99 -4.5463854e+05 9.54e-07 1.16e-12 -9.0 6.42e-06 - 1.00e+00 1.00e+00h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
100 -4.5463854e+05 9.54e-07 1.00e-12 -9.0 4.75e-06 - 1.00e+00 1.00e+00H 1
101 -4.5463854e+05 4.77e-07 1.12e-12 -9.0 4.91e-06 - 1.00e+00 2.50e-01h 3
102 -4.5463854e+05 4.77e-07 8.62e-13 -9.0 4.54e-06 - 1.00e+00 1.56e-02h 7
103 -4.5463854e+05 4.77e-07 8.16e-13 -9.0 4.71e-06 - 1.00e+00 1.00e+00h 1
104 -4.5463854e+05 1.43e-06 1.32e-12 -9.0 4.69e-06 - 1.00e+00 1.00e+00h 1
105 -4.5463854e+05 7.15e-07 1.32e-12 -9.0 6.95e-06 - 1.00e+00 2.50e-01h 3
106 -4.5463854e+05 7.15e-07 1.32e-12 -9.0 4.64e-06 - 1.00e+00 1.56e-02h 7
107 -4.5463854e+05 5.07e-07 1.32e-12 -9.0 4.41e-06 - 1.00e+00 3.12e-02h 6
108 -4.5463854e+05 5.07e-07 1.32e-12 -9.0 3.54e-06 - 1.00e+00 2.44e-04h 13
109 -4.5463854e+05 5.07e-07 1.32e-12 -9.0 3.54e-06 - 1.00e+00 1.22e-04h 14
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
110 -4.5463854e+05 4.77e-07 8.50e-13 -9.0 3.54e-06 - 1.00e+00 1.00e+00h 1
Number of Iterations....: 110
(scaled) (unscaled)
Objective...............: -2.2731927239529302e+05 -4.5463854479058605e+05
Dual infeasibility......: 8.4954444442330601e-13 1.6990888888466120e-12
Constraint violation....: 2.1287373134068081e-08 4.7683715820312500e-07
Complementarity.........: 9.1066538784262700e-10 1.8213307756852540e-09
Overall NLP error.......: 2.1287373134068081e-08 4.7683715820312500e-07
Number of objective function evaluations = 162
Number of objective gradient evaluations = 111
Number of equality constraint evaluations = 162
Number of inequality constraint evaluations = 162
Number of equality constraint Jacobian evaluations = 111
Number of inequality constraint Jacobian evaluations = 111
Number of Lagrangian Hessian evaluations = 110
Total CPU secs in IPOPT (w/o function evaluations) = 9.717
Total CPU secs in NLP function evaluations = 314.856
EXIT: Solved To Acceptable Level.
Add maximum and minimum values for the power plant power production and storage heat duty. This is needed when plotting the results.
[9]:
max_power = 436
max_power_storage = 30
max_power_total = max_power + max_power_storage
min_storage_heat_duty = 10
max_storage_heat_duty = 200
In Figure 1, plot the Solar salt levels in the storage tank for the entire time horizon.
[10]:
hours = np.arange(n_time_points * nweeks)
lmp_array = np.asarray(lmp[0:n_time_points])
hot_tank_array = np.asarray(hot_tank_level[0:nweeks]).flatten()
cold_tank_array = np.asarray(cold_tank_level[0:nweeks]).flatten()
# Convert array to list to include hot tank level at time zero
hot_tank_array0 = value(blks[0].usc_mp.previous_salt_inventory_hot) * 1e-3
cold_tank_array0 = value(blks[0].usc_mp.previous_salt_inventory_cold) * 1e-3
hours_list = hours.tolist() + [n_time_points]
hot_tank_list = [hot_tank_array0] + hot_tank_array.tolist()
cold_tank_list = [cold_tank_array0] + cold_tank_array.tolist()
# Declare settings for plot
font = {'size': 16}
plt.rc('font', **font)
fig1, ax1 = plt.subplots(figsize=(12, 8))
color = ['r', 'b', 'tab:green', 'k', 'tab:orange']
ax1.set_xlabel('Time Period (hr)')
ax1.set_ylabel('Salt Tank Level (metric ton)',
color=color[3])
ax1.spines["top"].set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.grid(linestyle=':', which='both',
color='gray', alpha=0.30)
plt.axhline(tank_max * 1e-3,
ls=':', lw=1.75,
color=color[4])
plt.text(n_time_points / 2 - 1.5,
tank_max * 1e-3 + 100,
'max salt',
color=color[4])
ax1.step(hours_list, hot_tank_list,
marker='^', ms=4,
lw=1, color=color[0],
label='Hot Salt')
ax1.step(hours_list, cold_tank_list,
marker='v', ms=4,
lw=1, color=color[1],
label='Cold Salt')
ax1.legend(loc="center right", frameon=False)
ax1.tick_params(axis='y')
ax1.set_xticks(np.arange(0, n_time_points * nweeks + 1, step=2))
ax2 = ax1.twinx()
ax2.set_ylabel('LMP ($/MWh)',
color=color[2])
ax2.step([x + 1 for x in hours], lmp_array,
marker='o', ms=3, alpha=0.5,
ls='-', lw=1,
color=color[2])
ax2.tick_params(axis='y',
labelcolor=color[2])
In Figure 2, plot the operating profile of the power plant in terms of total power for the entire time horizon.
[11]:
font = {'size': 18}
plt.rc('font', **font)
power_array = np.asarray(net_power[0:nweeks]).flatten()
# Convert array to list to include net power at time zero
power_array0 = value(blks[0].usc_mp.previous_power)
power_list = [power_array0] + power_array.tolist()
fig2, ax3 = plt.subplots(figsize=(12, 8))
ax3.set_xlabel('Time Period (hr)')
ax3.set_ylabel('Net Power Output (MW)',
color=color[1])
ax3.spines["top"].set_visible(False)
ax3.spines["right"].set_visible(False)
ax3.grid(linestyle=':', which='both',
color='gray', alpha=0.30)
plt.text(n_time_points / 2 - 3, max_power - 5.5,
'max plant power',
color=color[4])
plt.text(n_time_points / 2 - 2.8, max_power_total + 1,
'max net power',
color=color[4])
plt.axhline(max_power,
ls='-.', lw=1.75,
color=color[4])
plt.axhline(max_power_total,
ls=':', lw=1.75,
color=color[4])
ax3.step(hours_list, power_list,
marker='o', ms=4,
lw=1, color=color[1])
ax3.tick_params(axis='y',
labelcolor=color[1])
ax3.set_xticks(np.arange(0, n_time_points * nweeks + 1, step=2))
ax4 = ax3.twinx()
ax4.set_ylabel('LMP ($/MWh)',
color=color[2])
ax4.step([x + 1 for x in hours], lmp_array,
marker='o', ms=3, alpha=0.5,
ls='-', lw=1,
color=color[2])
ax4.tick_params(axis='y',
labelcolor=color[2])
In Figure 3, plot the storage heat exchanger operating profiles in terms of heat duties for the entire time horizon.
[12]:
zero_point = True
hxc_array = np.asarray(hxc_duty[0:nweeks]).flatten()
hxd_array = np.asarray(hxd_duty[0:nweeks]).flatten()
hxc_duty0 = 0
hxc_duty_list = [hxc_duty0] + hxc_array.tolist()
hxd_duty0 = 0
hxd_duty_list = [hxd_duty0] + hxd_array.tolist()
fig3, ax5 = plt.subplots(figsize=(12, 8))
ax5.set_xlabel('Time Period (hr)')
ax5.set_ylabel('Storage Heat Duty (MW)',
color=color[3])
ax5.spines["top"].set_visible(False)
ax5.spines["right"].set_visible(False)
ax5.grid(linestyle=':', which='both',
color='gray', alpha=0.30)
plt.text(n_time_points / 2 - 2.2,
max_storage_heat_duty + 1,
'max storage',
color=color[4])
plt.text(n_time_points / 2 - 2,
min_storage_heat_duty - 6.5,
'min storage',
color=color[4])
plt.axhline(max_storage_heat_duty,
ls=':', lw=1.75,
color=color[4])
plt.axhline(min_storage_heat_duty,
ls=':', lw=1.75,
color=color[4])
if zero_point:
ax5.step(hours_list, hxc_duty_list,
marker='^', ms=4,
color=color[0], label='Charge',)
ax5.step(hours_list, hxd_duty_list,
marker='v', ms=4,
color=color[1], label='Discharge')
else:
ax5.step([x + 1 for x in hours], hxc_array,
marker='^', ms=4, lw=1,
color=color[0], label='Charge')
ax5.step([x + 1 for x in hours], hxd_array,
marker='v', ms=4, lw=1,
color=color[1], label='Discharge')
ax5.legend(loc="center right", frameon=False)
ax5.tick_params(axis='y',
labelcolor=color[3])
ax5.set_xticks(np.arange(0, n_time_points * nweeks + 1, step=2))
ax6 = ax5.twinx()
ax6.set_ylabel('LMP ($/MWh)',
color=color[2])
ax6.step([x + 1 for x in hours], lmp_array,
marker='o', ms=3,
alpha=0.5, ls='-',
color=color[2])
ax6.tick_params(axis='y',
labelcolor=color[2])
[ ]: