Hide 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
../../../_images/a644a32e555170d619ff3eb1826c59fb4cfddc501202018c7a81c0668e5669e4.svg
HRSG Section
../../../_images/3a8615280ad63e35aaf8e6230055e1de079cf8e616aabcbafdd39851236aa5a1.svg
Steam Turbine Section
../../../_images/16656a8fde8a2a3cbab5d2dd6a637f841adcb6b55b115961758f4bec83bf9441.svg

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>
../../../_images/b8058b06f35488a34b13c56ea6423a52d5588b7acdf4d4db782edee872f375d3.png

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()