This notebook runs sanity checks on the results of the calculations of marginal cost of electricity (MCOE) that we do based on EIA 923 and EIA 860. Currently this only includes per-generator fuel costs, which also necessitates the calculation of per-generator heat rates and capacity factors. These are the same tests which are run by the mcoe validation tests using PyTest. The notebook and visualizations are meant to be used as a diagnostic tool, to help understand what's wrong when the PyTest based data validations fail for some reason.
In [ ]:
%load_ext autoreload
%autoreload 2
In [ ]:
import sys
import pandas as pd
import numpy as np
import sqlalchemy as sa
import pudl
In [ ]:
import warnings
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(stream=sys.stdout)
formatter = logging.Formatter('%(message)s')
handler.setFormatter(formatter)
logger.handlers = [handler]
In [ ]:
import matplotlib.pyplot as plt
import matplotlib as mpl
%matplotlib inline
In [ ]:
plt.style.use('ggplot')
mpl.rcParams['figure.figsize'] = (10,4)
mpl.rcParams['figure.dpi'] = 150
pd.options.display.max_columns = 56
In [ ]:
pudl_settings = pudl.workspace.setup.get_defaults()
ferc1_engine = sa.create_engine(pudl_settings['ferc1_db'])
pudl_engine = sa.create_engine(pudl_settings['pudl_db'])
pudl_settings
In order to validate the output from the MCOE calculation we first have to... do that calculation. We can do it at both monthly and annual resolution. Because we are testing the overall calculation, we don't want to impose the min/max heat rate and capacitiy factor constraints -- that would artificially clean up the outputs, which is what we're trying to evaluate.
In [ ]:
pudl_out_year = pudl.output.pudltabl.PudlTabl(pudl_engine, freq="AS")
pudl_out_month = pudl.output.pudltabl.PudlTabl(pudl_engine, freq="MS")
In [ ]:
%%time
#mcoe_year = pudl_out_year.mcoe(
# update=True,
# min_heat_rate=None,
# min_fuel_cost_per_mwh=None,
# min_cap_fact=None,
# max_cap_fact=None
#)
In [ ]:
%%time
mcoe_month = pudl_out_month.mcoe(
update=True,
min_heat_rate=None,
min_fuel_cost_per_mwh=None,
min_cap_fact=None,
max_cap_fact=None
)
Some of the MCOE outputs have a fixed range of reasonable values, like the generator heat rates or capacity factors. These varaibles can be tested for validity against external standards directly. In general we have two kinds of tests in this section:
heat_rate_mmbtu_mwh
(gas, coal)capacity_factor
(gas, coal)fuel_cost_per_mmbtu
(gas, coal)fuel_cost_per_mwh
(gas, coal)
In [ ]:
# mcoe = mcoe_year
mcoe = mcoe_month
In [ ]:
pudl.validate.plot_vs_self(mcoe, pudl.validate.mcoe_self)
In [ ]:
pudl.validate.plot_vs_self(mcoe, pudl.validate.mcoe_self_fuel_cost_per_mmbtu)
In [ ]:
pudl.validate.plot_vs_self(mcoe, pudl.validate.mcoe_self_fuel_cost_per_mwh)
In [ ]:
pudl.validate.plot_vs_bounds(mcoe, pudl.validate.mcoe_gas_heat_rate)
In [ ]:
pudl.validate.plot_vs_bounds(mcoe, pudl.validate.mcoe_coal_heat_rate)
In [ ]:
pudl.validate.plot_vs_bounds(mcoe, pudl.validate.mcoe_fuel_cost_per_mwh)
In [ ]:
pudl.validate.plot_vs_bounds(mcoe, pudl.validate.mcoe_fuel_cost_per_mmbtu)
In [ ]:
mcoe_gas = mcoe.query("fuel_type_code_pudl=='gas'")
nonzero_cf = mcoe_gas.query("capacity_factor!=0.0")
idle_gas_capacity = 1.0 - (nonzero_cf.capacity_mw.sum() / mcoe_gas.capacity_mw.sum())
logger.info(f"Idle gas capacity: {idle_gas_capacity:.2%}")
pudl.validate.plot_vs_bounds(mcoe, pudl.validate.mcoe_gas_capacity_factor)
In [ ]:
mcoe_coal = mcoe.query("fuel_type_code_pudl=='coal'")
nonzero_cf = mcoe_coal.query("capacity_factor!=0.0")
idle_coal_capacity = 1.0 - (nonzero_cf.capacity_mw.sum() / mcoe_coal.capacity_mw.sum())
logger.info(f"Idle coal capacity: {idle_coal_capacity:.2%}")
pudl.validate.plot_vs_bounds(mcoe[mcoe.capacity_factor!=0.0], pudl.validate.mcoe_coal_capacity_factor)
In [ ]: