Skip to content

Commit 0c41738

Browse files
authored
Merge 1280f17 into 11d67a3
2 parents 11d67a3 + 1280f17 commit 0c41738

File tree

4 files changed

+41
-0
lines changed

4 files changed

+41
-0
lines changed

docs/release_notes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ What's New?
5757
there were no discharge losses.
5858

5959
* Add support for Python 3.12.
60+
* New :meth:`.DispatchModel.redispatch_lambda` and
61+
:meth:`.DispatchModel.historical_lambda` to calculate hourly marginal costs.
6062

6163
Bug Fixes
6264
^^^^^^^^^

src/dispatch/model.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,30 @@ def _cost(self, profiles: pd.DataFrame) -> dict[str, pd.DataFrame]:
874874
)
875875
return {"fuel": fuel_cost, "vom": vom_cost, "startup": start_cost, "fom": fom}
876876

877+
def redispatch_lambda(self) -> pd.DataFrame:
878+
"""Return hourly marginal cost (aka system lambda) of redispatch."""
879+
return (
880+
self.dispatchable_cost[["fuel_per_mwh", "vom_per_mwh"]]
881+
.sum(axis=1)
882+
.reset_index()
883+
.pivot(index="datetime", columns=["plant_id_eia", "generator_id"])
884+
.droplevel(0, axis=1)
885+
.reindex(index=self.load_profile.index, method="ffill")
886+
* (self.redispatch > 0).astype(int)
887+
).max(axis=1)
888+
889+
def historical_lambda(self) -> pd.DataFrame:
890+
"""Return hourly marginal cost (aka system lambda) of historic dispatch."""
891+
return (
892+
self.dispatchable_cost[["fuel_per_mwh", "vom_per_mwh"]]
893+
.sum(axis=1)
894+
.reset_index()
895+
.pivot(index="datetime", columns=["plant_id_eia", "generator_id"])
896+
.droplevel(0, axis=1)
897+
.reindex(index=self.load_profile.index, method="ffill")
898+
* (self.dispatchable_profiles > 0).astype(int)
899+
).max(axis=1)
900+
877901
def grouper(
878902
self,
879903
df: pd.DataFrame | dict[str, pd.DataFrame],

tests/data/bad_adj.zip

353 KB
Binary file not shown.

tests/model_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ def test_storage_summary(self, mini_dm):
188188
("storage_capacity", {}, (), "notna"),
189189
("hourly_data_check", {}, (), "notna"),
190190
("dc_charge", {}, (), "notna"),
191+
("redispatch_lambda", {}, (), "notna"),
192+
("historical_lambda", {}, (), "notna"),
191193
pytest.param("full_output", {}, (), "notna", marks=pytest.mark.xfail),
192194
(
193195
"dispatchable_summary",
@@ -220,6 +222,8 @@ def test_outputs_parametric(self, ent_dm, func, args, drop_cols, expected):
220222
"""Test that outputs are not empty or do not have unexpected nans."""
221223
ind, ent_dm = ent_dm
222224
df = getattr(ent_dm, func)(**args)
225+
if isinstance(df, pd.Series):
226+
df = df.to_frame(name=func)
223227
df = df[[c for c in df if c not in drop_cols]]
224228
if expected == "notna":
225229
assert df.notna().all().all()
@@ -836,3 +840,14 @@ def test_file(ent_fresh):
836840
self.plot_year(2008)
837841

838842
raise AssertionError
843+
844+
845+
@pytest.mark.skip(reason="for debugging only")
846+
def test_weird_adj(test_dir):
847+
"""Investigation of unexpected hourly load adjustment.
848+
849+
Potential testing for fossil startup to charge storage.
850+
"""
851+
with DataZip(test_dir / "data/bad_adj.zip") as z:
852+
dm = DispatchModel(**z["data"], jit=False)
853+
dm()

0 commit comments

Comments
 (0)