Show code cell content
###############################################################################
# The Institute for the Design of Advanced Energy Systems Integrated Platform
# Framework (IDAES IP) was produced under the DOE Institute for the
# Design of Advanced Energy Systems (IDAES).
#
# Copyright (c) 2018-2023 by the software owners: The Regents of the
# University of California, through Lawrence Berkeley National Laboratory,
# National Technology & Engineering Solutions of Sandia, LLC, Carnegie Mellon
# University, West Virginia University Research Corporation, et al.
# All rights reserved. Please see the files COPYRIGHT.md and LICENSE.md
# for full copyright and license information.
###############################################################################
NGCC Baseline and Turndown#
Maintainer: John Eslick
Author: John Eslick
Updated: 2023-06-01
This notebook runs a series of net electric power outputs from 650 MW to 160 MW (about 100% to 25%) for an NGCC with 97% CO2 capture. The NGCC model is based on the NETL report “Cost and Performance Baseline for Fossil Energy Plants Volume 1: Bituminous Coal and Natural Gas to Electricity.” Sept 2019, Case B31B (https://www.netl.doe.gov/projects/files/CostAndPerformanceBaselineForFossilEnergyPlantsVol1BitumCoalAndNGtoElectBBRRev4-1_092419.pdf).
Imports#
Import the modules that will be used.
import os
import numpy as np
import pandas as pd
from IPython.core.display import SVG
import pyomo.environ as pyo
import idaes
from idaes.core.solvers import use_idaes_solver_configuration_defaults
import idaes.core.util.scaling as iscale
import idaes.core.util as iutil
from idaes_examples.mod.power_gen import ngcc
import pytest
import logging
logging.getLogger("pyomo").setLevel(logging.ERROR)
Matplotlib is building the font cache; this may take a moment.
Make Output Directories#
This notebook can produce a large number of output files. To make it easier to manage, some subdirectories are used to organize output. This ensures that the directories exist.
def make_directory(path):
"""Make a directory if it doesn't exist"""
try:
os.mkdir(path)
except FileExistsError:
pass
make_directory("data")
make_directory("data_pfds")
make_directory("data_tabulated")
Global Solver Settings#
Use the IDAES configuration system for solver settings. These will apply to all Ipopt instances created, including the ones created in initialization methods.
use_idaes_solver_configuration_defaults()
idaes.cfg.ipopt.options.nlp_scaling_method = "user-scaling"
idaes.cfg.ipopt.options.linear_solver = "ma57"
idaes.cfg.ipopt.options.OF_ma57_automatic_scaling = "yes"
idaes.cfg.ipopt.options.ma57_pivtol = 1e-5
idaes.cfg.ipopt.options.ma57_pivtolmax = 0.1
solver = pyo.SolverFactory("ipopt")
Create the NGCC model#
Create the NGCC model and initialize it or read the saved initialization if available. The base initialized NGCC model is configured to match the baseline report with 90% capture using a Cansolv system.
m = pyo.ConcreteModel()
m.fs = ngcc.NgccFlowsheet(dynamic=False)
iscale.calculate_scaling_factors(m)
m.fs.initialize(
load_from="ngcc_init.json.gz",
save_to="ngcc_init.json.gz",
)
res = solver.solve(m, tee=True)
2024-05-09 18:33:29 [ERROR] idaes.core.base.process_block: Failure in build: fs.gt.prop_water
Traceback (most recent call last):
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py", line 41, in _rule_default
b.build()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/models/properties/general_helmholtz/helmholtz_functions.py", line 1441, in build
raise RuntimeError("Helmholtz EoS external functions not available")
RuntimeError: Helmholtz EoS external functions not available
ERROR: Constructing component 'fs.gt.prop_water' from data=None failed:
RuntimeError: Helmholtz EoS external functions not available
2024-05-09 18:33:29 [ERROR] idaes.core.base.process_block: Failure in build: fs.gt
Traceback (most recent call last):
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py", line 41, in _rule_default
b.build()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/gas_turbine.py", line 60, in build
self._add_properties()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/gas_turbine.py", line 94, in _add_properties
self.prop_water = iapws95.Iapws95ParameterBlock()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 571, in __setattr__
self.add_component(name, val)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 1129, in add_component
val.construct(data)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 2186, in construct
self._getitem_when_not_present(_idx)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 2101, in _getitem_when_not_present
obj = self._rule(_block, idx)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/initializer.py", line 316, in __call__
return self._fcn(parent, idx)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py", line 41, in _rule_default
b.build()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/models/properties/general_helmholtz/helmholtz_functions.py", line 1441, in build
raise RuntimeError("Helmholtz EoS external functions not available")
RuntimeError: Helmholtz EoS external functions not available
ERROR: Constructing component 'fs.gt' from data=None failed:
RuntimeError: Helmholtz EoS external functions not available
2024-05-09 18:33:29 [ERROR] idaes.core.base.process_block: Failure in build: fs
Traceback (most recent call last):
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py", line 41, in _rule_default
b.build()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/ngcc.py", line 41, in build
self._add_flowsheets()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/ngcc.py", line 150, in _add_flowsheets
self.gt = gas_turbine.GasTurbineFlowsheet(
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 571, in __setattr__
self.add_component(name, val)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 1129, in add_component
val.construct(data)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 2186, in construct
self._getitem_when_not_present(_idx)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 2101, in _getitem_when_not_present
obj = self._rule(_block, idx)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/initializer.py", line 316, in __call__
return self._fcn(parent, idx)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py", line 41, in _rule_default
b.build()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/gas_turbine.py", line 60, in build
self._add_properties()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/gas_turbine.py", line 94, in _add_properties
self.prop_water = iapws95.Iapws95ParameterBlock()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 571, in __setattr__
self.add_component(name, val)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 1129, in add_component
val.construct(data)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 2186, in construct
self._getitem_when_not_present(_idx)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py", line 2101, in _getitem_when_not_present
obj = self._rule(_block, idx)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/initializer.py", line 316, in __call__
return self._fcn(parent, idx)
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py", line 41, in _rule_default
b.build()
File "/home/docs/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/models/properties/general_helmholtz/helmholtz_functions.py", line 1441, in build
raise RuntimeError("Helmholtz EoS external functions not available")
RuntimeError: Helmholtz EoS external functions not available
ERROR: Constructing component 'fs' from data=None failed:
RuntimeError: Helmholtz EoS external functions not available
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
Cell In[5], line 2
1 m = pyo.ConcreteModel()
----> 2 m.fs = ngcc.NgccFlowsheet(dynamic=False)
3 iscale.calculate_scaling_factors(m)
4 m.fs.initialize(
5 load_from="ngcc_init.json.gz",
6 save_to="ngcc_init.json.gz",
7 )
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:571, in BlockData.__setattr__(self, name, val)
566 if name not in self.__dict__:
567 if isinstance(val, Component):
568 #
569 # Pyomo components are added with the add_component method.
570 #
--> 571 self.add_component(name, val)
572 else:
573 #
574 # Other Python objects are added with the standard __setattr__
575 # method.
576 #
577 super(BlockData, self).__setattr__(name, val)
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:1129, in BlockData.add_component(self, name, val)
1121 logger.debug(
1122 "Constructing %s '%s' on %s from data=%s",
1123 val.__class__.__name__,
(...)
1126 str(data),
1127 )
1128 try:
-> 1129 val.construct(data)
1130 except:
1131 err = sys.exc_info()[1]
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:2186, in Block.construct(self, data)
2184 obj.construct(data.get(name, None))
2185 # Trigger the (normal) initialization of the block
-> 2186 self._getitem_when_not_present(_idx)
2187 finally:
2188 # We must allow that id(self) may no longer be in
2189 # _BlockConstruction.data, as _getitem_when_not_present will
2190 # have already removed the entry for scalar blocks (as the
2191 # BlockData and the Block component are the same object)
2192 if data is not None:
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:2101, in Block._getitem_when_not_present(self, idx)
2098 data = None
2100 try:
-> 2101 obj = self._rule(_block, idx)
2102 # If the user returns a block, transfer over everything
2103 # they defined into the empty one we created. We do
2104 # this inside the try block so that any abstract
2105 # components declared by the rule have the opportunity
2106 # to be initialized with data from
2107 # _BlockConstruction.data as they are transferred over.
2108 if obj is not _block and isinstance(obj, BlockData):
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/initializer.py:316, in IndexedCallInitializer.__call__(self, parent, idx)
314 return self._fcn(parent, *idx)
315 else:
--> 316 return self._fcn(parent, idx)
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py:41, in _rule_default(b, *args)
35 """
36 Default rule for ProcessBlock, which calls build(). A different rule can
37 be specified to add additional build steps, or to not call build at all
38 using the normal rule argument to ProcessBlock init.
39 """
40 try:
---> 41 b.build()
42 except Exception:
43 logging.getLogger(__name__).exception(f"Failure in build: {b}")
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/ngcc.py:41, in NgccFlowsheetData.build(self)
39 def build(self):
40 super().build()
---> 41 self._add_flowsheets()
42 self._add_units()
43 self._add_arcs()
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/ngcc.py:150, in NgccFlowsheetData._add_flowsheets(self)
149 def _add_flowsheets(self):
--> 150 self.gt = gas_turbine.GasTurbineFlowsheet(
151 dynamic=self.config.dynamic,
152 time=self.time,
153 time_units=self.config.time_units,
154 )
155 self.hrsg = hrsg.HrsgFlowsheet(
156 dynamic=self.config.dynamic,
157 time=self.time,
158 time_units=self.config.time_units,
159 )
160 self.st = steam_turbine.SteamTurbineFlowsheet(
161 dynamic=self.config.dynamic,
162 time=self.time,
163 time_units=self.config.time_units,
164 )
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:571, in BlockData.__setattr__(self, name, val)
566 if name not in self.__dict__:
567 if isinstance(val, Component):
568 #
569 # Pyomo components are added with the add_component method.
570 #
--> 571 self.add_component(name, val)
572 else:
573 #
574 # Other Python objects are added with the standard __setattr__
575 # method.
576 #
577 super(BlockData, self).__setattr__(name, val)
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:1129, in BlockData.add_component(self, name, val)
1121 logger.debug(
1122 "Constructing %s '%s' on %s from data=%s",
1123 val.__class__.__name__,
(...)
1126 str(data),
1127 )
1128 try:
-> 1129 val.construct(data)
1130 except:
1131 err = sys.exc_info()[1]
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:2186, in Block.construct(self, data)
2184 obj.construct(data.get(name, None))
2185 # Trigger the (normal) initialization of the block
-> 2186 self._getitem_when_not_present(_idx)
2187 finally:
2188 # We must allow that id(self) may no longer be in
2189 # _BlockConstruction.data, as _getitem_when_not_present will
2190 # have already removed the entry for scalar blocks (as the
2191 # BlockData and the Block component are the same object)
2192 if data is not None:
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:2101, in Block._getitem_when_not_present(self, idx)
2098 data = None
2100 try:
-> 2101 obj = self._rule(_block, idx)
2102 # If the user returns a block, transfer over everything
2103 # they defined into the empty one we created. We do
2104 # this inside the try block so that any abstract
2105 # components declared by the rule have the opportunity
2106 # to be initialized with data from
2107 # _BlockConstruction.data as they are transferred over.
2108 if obj is not _block and isinstance(obj, BlockData):
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/initializer.py:316, in IndexedCallInitializer.__call__(self, parent, idx)
314 return self._fcn(parent, *idx)
315 else:
--> 316 return self._fcn(parent, idx)
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py:41, in _rule_default(b, *args)
35 """
36 Default rule for ProcessBlock, which calls build(). A different rule can
37 be specified to add additional build steps, or to not call build at all
38 using the normal rule argument to ProcessBlock init.
39 """
40 try:
---> 41 b.build()
42 except Exception:
43 logging.getLogger(__name__).exception(f"Failure in build: {b}")
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/gas_turbine.py:60, in GasTurbineFlowsheetData.build(self)
58 def build(self):
59 super().build()
---> 60 self._add_properties()
61 self._add_models()
62 self._add_performance_curves_gts1()
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/checkouts/latest/idaes_examples/mod/power_gen/gas_turbine.py:94, in GasTurbineFlowsheetData._add_properties(self, air_species, cmb_species, flue_species, rxns)
87 self.rxns = rxns
88 # Here three different type of property blocks are used, so that we can
89 # avoid components with zero flow, which can cause problems with
90 # certain property calculations (entropy for example). Three types of
91 # gas streams are Air, combstion mixture, and flue gas. Fortunately
92 # natural gas has some air components in it so the combustion property
93 # parameters can be used for natural gas and natural gas mixed with air.
---> 94 self.prop_water = iapws95.Iapws95ParameterBlock()
95 self.air_prop_params = GenericParameterBlock(
96 **get_prop(air_species, ["Vap"]),
97 doc="Air property parameters",
98 )
99 self.cmb_prop_params = GenericParameterBlock(
100 **get_prop(cmb_species, ["Vap"]),
101 doc="Natural gas or Natural gas + Air property parameters",
102 )
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:571, in BlockData.__setattr__(self, name, val)
566 if name not in self.__dict__:
567 if isinstance(val, Component):
568 #
569 # Pyomo components are added with the add_component method.
570 #
--> 571 self.add_component(name, val)
572 else:
573 #
574 # Other Python objects are added with the standard __setattr__
575 # method.
576 #
577 super(BlockData, self).__setattr__(name, val)
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:1129, in BlockData.add_component(self, name, val)
1121 logger.debug(
1122 "Constructing %s '%s' on %s from data=%s",
1123 val.__class__.__name__,
(...)
1126 str(data),
1127 )
1128 try:
-> 1129 val.construct(data)
1130 except:
1131 err = sys.exc_info()[1]
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:2186, in Block.construct(self, data)
2184 obj.construct(data.get(name, None))
2185 # Trigger the (normal) initialization of the block
-> 2186 self._getitem_when_not_present(_idx)
2187 finally:
2188 # We must allow that id(self) may no longer be in
2189 # _BlockConstruction.data, as _getitem_when_not_present will
2190 # have already removed the entry for scalar blocks (as the
2191 # BlockData and the Block component are the same object)
2192 if data is not None:
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/block.py:2101, in Block._getitem_when_not_present(self, idx)
2098 data = None
2100 try:
-> 2101 obj = self._rule(_block, idx)
2102 # If the user returns a block, transfer over everything
2103 # they defined into the empty one we created. We do
2104 # this inside the try block so that any abstract
2105 # components declared by the rule have the opportunity
2106 # to be initialized with data from
2107 # _BlockConstruction.data as they are transferred over.
2108 if obj is not _block and isinstance(obj, BlockData):
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/pyomo/core/base/initializer.py:316, in IndexedCallInitializer.__call__(self, parent, idx)
314 return self._fcn(parent, *idx)
315 else:
--> 316 return self._fcn(parent, idx)
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/core/base/process_block.py:41, in _rule_default(b, *args)
35 """
36 Default rule for ProcessBlock, which calls build(). A different rule can
37 be specified to add additional build steps, or to not call build at all
38 using the normal rule argument to ProcessBlock init.
39 """
40 try:
---> 41 b.build()
42 except Exception:
43 logging.getLogger(__name__).exception(f"Failure in build: {b}")
File ~/checkouts/readthedocs.org/user_builds/idaes-examples/envs/latest/lib/python3.8/site-packages/idaes/models/properties/general_helmholtz/helmholtz_functions.py:1441, in HelmholtzParameterBlockData.build(self)
1439 """Populate the parameter block"""
1440 if not self.available():
-> 1441 raise RuntimeError("Helmholtz EoS external functions not available")
1442 super().build()
1443 # Check if the specified component is supported
RuntimeError: Helmholtz EoS external functions not available
Show PFDs with baseline results#
This displays PFDs in the notebook, and saves them to files. The full NGCC model is too big to show well in a single PFD, so it is broken into the three main sections, gas turbine, heat recovery steam generator (HRSG), and steam turbine.
def display_pfd():
print("\n\nGas Turbine Section\n")
display(SVG(m.fs.gt.write_pfd()))
print("\n\nHRSG Section\n")
display(SVG(m.fs.hrsg.write_pfd()))
print("\n\nSteam Turbine Section\n")
display(SVG(m.fs.st.write_pfd()))
display_pfd()
m.fs.gt.write_pfd(fname="data_pfds/gt_baseline.svg")
m.fs.hrsg.write_pfd(fname="data_pfds/hrsg_baseline.svg")
m.fs.st.write_pfd(fname="data_pfds/st_baseline.svg")
Gas Turbine Section
HRSG Section
Steam Turbine Section
Test key model outputs against NETL baseline#
# Assert results approximately agree with baseline reoprt
assert pyo.value(m.fs.net_power_mw[0]) == pytest.approx(646)
assert pyo.value(m.fs.gross_power[0]) == pytest.approx(-690e6, rel=0.001)
assert pyo.value(100 * m.fs.lhv_efficiency[0]) == pytest.approx(52.8, abs=0.1)
assert pyo.value(
m.fs.total_variable_cost_rate[0] / m.fs.net_power_mw[0]
) == pytest.approx(37.2799, rel=0.01)
assert pyo.value(m.fs.fuel_cost_rate[0] / m.fs.net_power_mw[0]) == pytest.approx(
31.6462, rel=0.01
)
assert pyo.value(
m.fs.other_variable_cost_rate[0] / m.fs.net_power_mw[0]
) == pytest.approx(5.63373, rel=0.01)
assert pyo.value(m.fs.gt.gt_power[0]) == pytest.approx(-477e6, rel=0.001)
from matplotlib import pyplot as plt
variables = ["net_power", "gross_power", "gt_power"]
netl_baseline = [646, 690, 477]
idaes_prediction = [
pyo.value(m.fs.net_power_mw[0]),
-pyo.value(m.fs.gross_power[0]) * 1e-6,
-pyo.value(m.fs.gt.gt_power[0]) * 1e-6,
]
label_location = np.arange(len(variables))
width = 0.4
fig, ax = plt.subplots()
netl_data = ax.bar(variables, netl_baseline, label="NETL Baseline")
idaes_sim = ax.bar(
label_location + (width / 2), idaes_prediction, width, label="IDAES Prediction"
)
ax.set_ylabel("Power (MW)")
ax.set_xticks(label_location)
ax.set_xticklabels(variables)
ax.legend()
<matplotlib.legend.Legend at 0x2521cfeffd0>
Run turndown cases 5 MW interval#
Here we set the CO2 capture rate to 97% and set the specific reboiler duty to PZ advanced solvent system. The minimum power is 160 MW net, which corresponds to a bit under 25%. This is roughly the minimum load for the NGCC modeled. Results are tabulated for tags in the tags_output tag group in a Pandas data frame.
To run the series, change run_series to True. Running the turndown series takes a while, unless previous saved results are available.
run_series = False
if run_series:
idaes.cfg.ipopt.options.tol = 1e-6
idaes.cfg.ipopt.options.max_iter = 50
solver = pyo.SolverFactory("ipopt")
m.fs.cap_specific_reboiler_duty.fix(2.4e6)
m.fs.cap_fraction.fix(0.97)
powers = np.linspace(650, 160, int((650 - 160) / 5) + 1)
powers = list(powers)
powers.insert(1, 646)
df = pd.DataFrame(columns=m.fs.tags_output.table_heading())
for p in powers:
print("Simulation for net power = ", p)
fname = f"data/ngcc_{int(p)}.json.gz"
if os.path.exists(fname):
iutil.from_json(m, fname=fname, wts=iutil.StoreSpec(suffix=False))
else:
m.fs.net_power_mw.fix(p)
res = solver.solve(m, tee=False, symbolic_solver_labels=True)
if not pyo.check_optimal_termination(res):
break
iutil.to_json(m, fname=fname)
df.loc[m.fs.tags_output["net_power"].value] = m.fs.tags_output.table_row(
numeric=True
)
if abs(p - 650) < 0.1:
m.fs.gt.streams_dataframe().to_csv(
"data_tabulated/ngcc_stream_650mw_gt.csv"
)
m.fs.st.steam_streams_dataframe().to_csv(
"data_tabulated/ngcc_stream_650mw_st.csv"
)
m.fs.hrsg.steam_streams_dataframe().to_csv(
"data_tabulated/ngcc_stream_650mw_hrsg_steam.csv"
)
m.fs.hrsg.flue_gas_streams_dataframe().to_csv(
"data_tabulated/ngcc_stream_650mw_hrsg_gas.csv"
)
df.to_csv("data_tabulated/ngcc.csv")
# Display the results from the run stored in a pandas dataframe
pd.set_option("display.max_rows", None)
pd.set_option("display.max_columns", None)
display(df)
# Plot results
plt.plot(df["net_power (MW)"], df["lhv_efficiency (%)"])
plt.grid()
plt.xlabel("Net Power (MW)")
plt.ylabel("LHV Efficiency (%)")
plt.title("Net Power vs. Efficiency")
plt.show()