Release Notes¶
0.6.0 (2023-XX-XX)¶
What’s New?¶
Initial implementation of dynamic storage reserves. If storage does not have a reserve or the reserve is
0.0
, we calculate a dynamic reserve based on the next 24 hours of net load using (1). The value ofcoeff
can be set manually when creatingDispatchModel
, or automatically (default) by trying a number of values between 0.0 and 1.5 and selecting the one that best minimizes deficit and curtailment, usingdispatch.engine.choose_best_coefficient()
.
Additional deficit and curtailment metrics in
DispatchModel.system_level_summary()
.DispatchModel.dispatchable_summary()
andDispatchModel.full_output()
now include all columns provided in<x>_specs
.Enable multiple storage generators under a single
plant_id_eia
so long as they don’t share aplant_id_eia
with a renewable generator.DispatchModel.system_level_summary()
allows for the roll-up of storage facilities to create higher-level metrics using thestorage_rollup
parameter.DispatchModel.hourly_data_check()
allows exploration of hours preceding and including both deficit and curtailment hours.DispatchModel
now acceptsdispatchable_cost
with monthly frequency.Added
DispatchModel.set_metadata()
to safely set metadata.DispatchModel.__getattr__()
allows retrieving metadata using attribute access.Added
marginal_for_startup_rank
flag todispatch_engine()
andDispatchModel
config
to use marginal cost rank to order generators for startup rather than startup cost. Given costs fromrmi.gencost
have very low startup cost relative to marginal costs, marginal costs can be a better metric for committing units.Update build tool versions in
pyproject.toml
andenvironment.yml
.Address
pandas
DeprecationWarning
inpandas.DataFrame.groupby()
andpandas.value_counts()
.Remove local pytest and blackdoc hooks from pre-commit.
Switch from black to ruff format for autoformatting.
New storage features to represent more types of storage:
Storage charge rate can be different from discharge rate, to use, provide
charge_mw
column instorage_specs
.charge_eff
anddischarge_eff
will replaceroundtrip_eff
instorage_specs
to enable finer-grained control of when losses occur. Previouslyroundtrip_eff
was effectively treated as the charge efficiency and there were no discharge losses.
Add support for Python 3.12.
New
DispatchModel.redispatch_lambda()
andDispatchModel.historical_lambda()
to calculate hourly marginal costs.Updates to narrative description of dispatch logic in
approach.rst
.Attempts to reduce memory use and unnecessary allocations in
zero_profiles_outside_operating_dates()
and by validate inputs inplace.Create new
DispatchModel.system_summary_core()
that includes only a subset of the columns inDispatchModel.system_level_summary()
that can be produced faster and excludes storage metrics.
Bug Fixes¶
Fixed a bug where
operating_date
andretirement_date
did not apply to dispatchable resources, whereno_limit
isTrue
.Fixed a bug where
capacity_mw
for storage and renewables was not zero in outputs before their operating date. We now compare the modeled year to the operating year rather than the date. This provides the expected output when outputs are aggregated annually as is the typical case.Fixed renewable name maps for plotting in
dispatch.constants.PLOT_MAP
.Added a second fallback method for determining the frequency of cost data in
Validator.dispatchable_cost()
. While this isn’t needed within the model anymore, it is used to determine if there is missing data.Fixed a bug where profile index validation failed because
pandera
pandas.DatetimeIndex
type validation was applied inconsistently betweenpandas.DataFrame
andpandas.Series
.Fixed a bug where the
capacity_mw
column returned byDispatchModel.re_summary()
andDispatchModel.storage_summary()
was zero in the first year of operations whenfreq='YS'
.
0.5.0 (2023-05-15)¶
What’s New?¶
Added checks to make sure that the datetime indexes of
load_profile
,dispatchable_profiles
, andre_profiles
match.Prep for deprecating
DispatchModel.from_patio()
andDispatchModel.from_fresh()
.Extracted
calculate_generator_output()
fromdispatch_engine()
to make the latter easier to read and to more easily test the former’s logic.Many updates to internal variable names in
engine
to make the code easier to read.Renamed
apply_op_ret_date()
tozero_profiles_outside_operating_dates()
for clarity, use of the former name will be removed in the future.Code cleanup along with adoption of ruff and removal of bandit, flake8, isort, etc.
Added the ability to specify in
dispatchable_specs
via ano_limit
column that a generator not limited to its historical hourly output by the model without affecting historical dispatch data.Added the ability to specify in
dispatchable_specs
via amin_uptime
column the minimum number of hours a generator must have been operating before it can start ramping down.Adjusted process of determining the provisional deficit used to dispatch currently operating generators. Previously, we adjusted our target for dispatchable generation based on the assumption we would want to use up all storage state of charge before dispatching operating generators. We now set the provisional deficit so that we hold 2x
reserve
state of charge in reserve. If state of charge is belowreserve
, we increase the provisional deficit in order to replenish the reserve.Changed battery discharge so that only a part of storage can be used before dispatchable start-up, only down to the
reserve
. After dispatchable start-up, storage is dispatched a second time in case a deficit remains, in this part of the sequence, all storage state of charge can be used.dispatch now works with Python 3.11 using newly released
numba
version 0.57.dispatch now works with
pandas
2.0.
0.4.0 (2023-01-25)¶
What’s New?¶
Tests for
engine.dispatch_engine()
,copy_profile()
.DispatchModel.hourly_data_check()
to help in checking for dispatch errors, and running down why deficits are occuring.DispatchModel
now takesload_profile
that resources will be dispatched against. Ifre_profiles
andre_plant_specs
are not provided, this should be a net load profile. If they are provided, this must be a gross load profile, or at least, gross of those RE resources. These calculations are done byDispatchModel.re_and_net_load()
.DispatchModel
now accepts (and requires) raw DCre_profiles
, it determines actual renewable output using capacity data and ilr provided inre_plant_specs
. This will allowDispatchModel
to model DC-coupled RE+Storage facilities that can charge from otherwise clipped generation. The calculations for the amount of charging from DC-coupled RE is inDispatchModel.dc_charge()
.Updates to
engine.dispatch_engine()
andengine.validate_inputs()
to accommodate DC-coupled RE charging data. Storage can now be charged from DC-coupled RE in addition to the grid. This includes trackinggridcharge
in addition tocharge
, where the latter includes charging from the grid and DC-coupled RE.All output charging metrics use the
gridcharge
data because from the grid’s perspective, this is what matters.discharge
data does not distinguish, so in some cases net charge data may be positive, this reflects RE generation run through the battery that otherwise would have been curtailed.DataZip
, a subclass ofzipfile.ZipFile
that has methods for easily reading and writingpandas.DataFrame
asparquet
anddict
asjson
. This includes storing column names separately that cannot be included in aparquet
.Extracted
engine.charge_storage()
andengine.make_rank_arrays()
fromengine.dispatch_engine()
. This allows easier unit testing and, in the former case, makes sure all charging is implemented consistently.Added plotting functions
DispatchModel.plot_output()
to visualize columns fromDispatchModel.full_output()
and updatedDispatchModel.plot_period()
to display data by generator ifby_gen=True
.DispatchModel.plot_year()
can now display the results with daily or hourly frequency.For renewables,
plant_id_eia
no longer need by unique, now for renewables,plant_id_eia
andgenerator_id
must be jointly unique. In cases where a singleplant_id_eia
has two renewable generator’s as well as storage,DispatchModel.dc_charge()
assumes excess renewable generation from the several generators can be combined to charge the facility’s storage.re_plant_specs
,dispatchable_specs
, andstorage_specs
, now allow zeros forcapacity_mw
andduration_hrs
.DataZip
,DispatchModel.to_file()
, andDispatchModel.from_file()
now supportio.BytesIO
asfile
orpath
. This now allows any object that implementsto_file
/from_file
methods usingDataZip
, to be written into and recovered from anotherDataZip
.Added the ability to specify in
dispatchable_specs
via anexclude
column that a generator not be dispatched by the model without affecting historical dispatch data.Migrating
DataZip
functionality toetoolbox.datazip.DataZip
.Updates to constants to allow Nuclear and Conventional Hydroelectric to be properly displayed in plots.
Updates to
re_plant_specs
, its validation, andDispatchModel.re_and_net_load()
for a new column,interconnect_mw
, that allows interconnection capacity for a renewable facility to independent of its capacity. By default, this is the same ascapacity_mw
but can be reduced to reflect facility-specific transmission / interconnection constraints. If the facility has storage, storage can be charged by the constrained excess.Added
compare_hist
argument toDispatchModel.plot_period()
which creates panel plot showing both historical dispatch and redispatch for the period.DispatchModel.plot_output()
adds a row facet to show both historical and redispatch versions of the requested data if available.Cleanup of configuration and packaging files. Contents of
setup.cfg
andtox.ini
moved topyproject.toml
.Added the ability to specify FOM for renewables in
re_plant_specs
via an optionalfom_per_kw
column. This allowsDispatchModel.re_summary()
and derived outputs to include aredispatch_cost_fom
column.DispatchModel
now contains examples as doctests.DispatchModel.plot_all_years()
to create daily redispatch plot faceted by month and year.DispatchModel.dispatchable_summary()
now includes mmbtu and co2 data for historical, redispatch, and avoided column groupings. These metrics are based onheat_rate
andco2_factor
columns indispatchable_cost
, these columns are optional.Updates to
DispatchModel
to work with the new simpler, cleanerDataZip
.
Bug Fixes¶
Fixed an issue in
engine.dispatch_engine()
where a storage resource’s state of charge would not be carried forward if it wasn’t charged or discharged in that hour.Fixed a bug where storage metrics in
DispatchModel.system_level_summary()
werenumpy.nan
because selecting of data fromstorage_specs
returned apandas.Series
rather than aint
orfloat
. Further, in cases of division be zero in these calculations, the result is now 0 rather thannumpy.nan
. Tests now make sure that no newnumpy.nan
show up.Fixed a bug in
DispatchModel.dispatchable_summary()
wherepct_replaced
would benumpy.nan
because of division by zero in these calculations, the result is now 0 rather thannumpy.nan
. Tests now make sure that no newnumpy.nan
show up.Fixed an issue where
DispatchModel.full_output()
and methods that use it, i.e.DispatchModel.plot_output()
improperly aggregatedDispatchModel.system_data
whenfreq
was not ‘YS’.Fixed an issue where
DispatchModel.full_output()
didn’t properly showCurtailment
andStorage
.
Known Issues¶
The storage in DC-coupled RE+Storage system can be charged by either the grid or excess RE that would have been curtailed because of the size of the inverter. It is not possible to restrict grid charging in these systems. It is also not possible to charge storage rather than export to the grid when RE output can fit through the inverter.
It is possible that output from DC-coupled RE+Storage facilities during some hours will exceed the system’s inverter capacity because when we discharge these storage facilities, we do not know how much ‘room’ there is in the inverter because we do not know the RE-side’s output. This issue is now in some sense compounded when
interconnect_mw
is less thancapacity_mw
.DataZip
are effectively immutable once they are created so thea
mode is not allowed and thew
mode is not allowed on existing files. This is because it is not possible to overwrite or remove a file already in azipfile.ZipFile
. That fact prevents us from updating metadata aboutpandas.DataFrame
that cannot be stored in theparquet
itself. Ways of addressing this get messy and still wouldn’t allow updating existing data without copying everything which a user can do if that is needed.
0.3.0 (2022-10-08)¶
What’s New?¶
DispatchModel.to_file()
can create an output with summary outputs.Adopting
pandera
for metadata and validation usingValidator
to organize and specialize data input checking.Adding cost component details and capacity data to
DispatchModel.dispatchable_summary()
.We now automatically apply
operating_date
andretirement_date
fromDispatchModel.dispatchable_plant_specs
toDispatchModel.dispatchable_profiles
usingapply_op_ret_date()
.Added validation and processing for
DispatchModel.re_plant_specs
andDispatchModel.re_profiles
, as well asDispatchModel.re_summary()
to, when the data is provided create a summary of renewable operations analogous toDispatchModel.dispatchable_summary()
.Added
DispatchModel.storage_summary()
to create a summary of storage operations analogous toDispatchModel.dispatchable_summary()
.Added
DispatchModel.full_output()
to create the kind of outputs needed by Optimus and other post-dispatch analysis tools.Added validation steps for each type of specs that raise an error when an operating_date is after the dispatch period which would otherwise result in dispatch errors.
New helpers (
DataZip.dfs_to_zip()
andDataZip.dfs_from_zip()
) that simplify saving and reading in groups ofpandas.DataFrame
.Added plotting functions
DispatchModel.plot_period()
andDispatchModel.plot_year()
.
Known Issues¶
DispatchModel.re_summary()
andDispatchModel.storage_summary()
have null operations cost data.There is still no nice way to include nuclear and hydro resources.
DispatchModel.plot_year()
doesn’t seem to really work. At all.
Bug Fixes¶
A validation check throws an error when ramp rates are zero which otherwise would prevent plant output from ever changing on a fresh dispatch.
Fixed a
TypeError
issue inapply_op_ret_date()
when some dates were inexplicably converted toint
rather thannumpy.datetime64
bypandas.DataFrame.to_numpy()
.
0.2.0 (2022-09-15)¶
What’s New?¶
DispatchModel
now uses__slots__
New
DispatchModel.to_file()
andDispatchModel.from_file()
methods that allow aDispatchModel
object to be saved to disk and recreated from a file. This uses azip
of manyparquet
files for size and to avoidpickle
being tied to a particular module layout.Methods to calculate hourly cost for historical and redispatch.
Method to simplify aggregating hourly generator-level data to less granular frequencies and asset specificity.
Storage resources can now be added to the portfolio over time based on their
operating_date
instorage_specs
.When using
DispatchModel.from_fresh()
,operating_date
andretirement_date
columns indispatchable_plant_specs
determine the period during dispatch that a generator may operate. This provides a straightforward method for having the portfolio you wish to dispatch change over time.Cleanup and rationalization of
DispatchModel.to_file()
andDispatchModel.from_file()
methods.Updates to system for storing and processing marginal cost data. This is now a separate argument to
DispatchModel.__init__()
rather than a messy confusing part ofdispatchable_plant_specs
. This is now consistent with howpatio
prepares and stores the data.
Bug Fixes¶
DispatchModel.to_file()
andDispatchModel.from_file()
now properly deal with internal data stored in bothpandas.DataFrame
andpandas.Series
.
Known Issues¶
Tests are still pretty rudimentary.
0.1.0 (2022-08-23)¶
What’s New?¶
A dispatch model with no RMI dependencies and in its own repository!
Repository built off of catalyst-cooperative.cheshire that uses cool tools like
tox
,sphinx
, etc.
Bug Fixes¶
Known Issues¶
DispatchModel
only set up to work properly with patio-model.Test thoroughness is lacking.
No substantive readme or documentation.