-
Notifications
You must be signed in to change notification settings - Fork 56
Cover the committed quantity in Mc, and tighten it #2411
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Flix6x
wants to merge
12
commits into
main
Choose a base branch
from
fix/tighten-commitment-bigm
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
47acb73
Cover the committed quantity in Mc, and tighten it
Flix6x da16b14
Reference PR #2411 in the changelog entry
Flix6x 3104c26
Assert the aggregate commitment's cost instead of the degenerate devi…
Flix6x b3ba037
Merge remote-tracking branch 'origin/main' into fix/tighten-commitmen…
Flix6x e912fa5
Merge remote-tracking branch 'origin/main' into fix/tighten-commitmen…
Flix6x faf6cf2
Address Copilot review: scale stock-change bounds by conversion effic…
Flix6x d8262d0
End comment lines at punctuation, per the docstring conventions
Flix6x 4f95ccd
Merge remote-tracking branch 'origin/main' into fix/tighten-commitmen…
Flix6x b1e960e
Address suppressed review notes: comment line breaks and approximate …
Flix6x 20bdc31
Merge remote-tracking branch 'origin/main' into fix/tighten-commitmen…
Flix6x ab7ba78
Restore the EV/battery cost split as a fairness benchmark
Flix6x 1e9821e
Merge remote-tracking branch 'origin/main' into fix/tighten-commitmen…
Flix6x File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| """Tests for the big-M values bounding the scheduler's search space.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from datetime import timedelta | ||
|
|
||
| import numpy as np | ||
| import pandas as pd | ||
| import pytest | ||
|
|
||
| from flexmeasures.data.models.planning import FlowCommitment, StockCommitment | ||
| from flexmeasures.data.models.planning.linear_optimization import device_scheduler | ||
| from flexmeasures.data.models.planning.scheduling_problem import ( | ||
| prepare_scheduling_problem, | ||
| ) | ||
| from flexmeasures.data.models.planning.utils import initialize_df | ||
|
|
||
| #: Run every test in this module under both scheduler backends (see conftest). | ||
| RUN_UNDER_EACH_SOLVER = True | ||
|
|
||
| COLUMNS = [ | ||
| "equals", | ||
| "max", | ||
| "min", | ||
| "efficiency", | ||
| "derivative equals", | ||
| "derivative max", | ||
| "derivative min", | ||
| "derivative down efficiency", | ||
| "derivative up efficiency", | ||
| "stock delta", | ||
| ] | ||
|
|
||
| START = pd.Timestamp("2020-01-01T00:00:00") | ||
| END = pd.Timestamp("2020-01-01T04:00:00") | ||
| RESOLUTION = timedelta(hours=1) | ||
|
|
||
|
|
||
| def make_device_constraints(power_capacity: float) -> pd.DataFrame: | ||
| device_constraints = initialize_df(COLUMNS, START, END, RESOLUTION) | ||
| device_constraints["derivative max"] = power_capacity | ||
| device_constraints["derivative min"] = -power_capacity | ||
| return device_constraints | ||
|
|
||
|
|
||
| def make_index() -> pd.DatetimeIndex: | ||
| return initialize_df(COLUMNS, START, END, RESOLUTION).index | ||
|
|
||
|
|
||
| def test_Mc_covers_flow_limits_per_step_plus_the_committed_quantity(): | ||
| """For flow commitments, a deviation spans at most the committed quantity plus one time step's summed flow limits.""" | ||
| commitment = FlowCommitment( | ||
| name="energy", | ||
| quantity=-100, | ||
| upwards_deviation_price=1, | ||
| downwards_deviation_price=-1, | ||
| index=make_index(), | ||
| ) | ||
| problem = prepare_scheduling_problem( | ||
| device_constraints=[make_device_constraints(0.5), make_device_constraints(2)], | ||
| ems_constraints=initialize_df(COLUMNS, START, END, RESOLUTION), | ||
| commitments=[commitment], | ||
| ) | ||
| assert problem.Md == 2 | ||
| assert problem.Mc == 0.5 + 2 + 100 | ||
|
|
||
|
|
||
| def test_Mc_covers_the_horizon_for_stock_commitments(): | ||
| """A stock commitment's deviation accumulates flows since the start, so Mc must cover the whole horizon.""" | ||
| index = make_index() | ||
| commitment = StockCommitment( | ||
| name="soc", | ||
| quantity=0.5, | ||
| upwards_deviation_price=1, | ||
| downwards_deviation_price=-1, | ||
| device=pd.Series(0, index=index), | ||
| index=index, | ||
| ) | ||
| problem = prepare_scheduling_problem( | ||
| device_constraints=[make_device_constraints(0.5), make_device_constraints(2)], | ||
| ems_constraints=initialize_df(COLUMNS, START, END, RESOLUTION), | ||
| commitments=[commitment], | ||
| ) | ||
| # 4 time steps of 0.5 + 2 flow limits each, plus the committed quantity. | ||
| assert problem.Mc == 4 * 2.5 + 0.5 | ||
|
|
||
|
|
||
| def test_Mc_scales_stock_changes_by_conversion_efficiencies_and_stock_deltas(): | ||
| """A stock change passes through the derivative efficiencies and includes the stock delta, unlike a raw flow.""" | ||
| index = make_index() | ||
| commitment = StockCommitment( | ||
| name="soc", | ||
| quantity=0.5, | ||
| upwards_deviation_price=1, | ||
| downwards_deviation_price=-1, | ||
| device=pd.Series(0, index=index), | ||
| index=index, | ||
| ) | ||
| device_constraints = make_device_constraints(0.5) | ||
| device_constraints["derivative up efficiency"] = 2 | ||
| device_constraints["derivative down efficiency"] = 0.5 | ||
| device_constraints["stock delta"] = 0.25 | ||
| problem = prepare_scheduling_problem( | ||
| device_constraints=[device_constraints], | ||
| ems_constraints=initialize_df(COLUMNS, START, END, RESOLUTION), | ||
| commitments=[commitment], | ||
| ) | ||
| # 4 time steps of 0.5 flow scaled by the worst-case conversion gain max(2, 1/0.5) plus a 0.25 stock delta, | ||
| # plus the committed quantity. | ||
| assert problem.Mc == 4 * (0.5 * 2 + 0.25) + 0.5 | ||
|
|
||
|
|
||
| def test_Mc_is_at_least_one(): | ||
| problem = prepare_scheduling_problem( | ||
| device_constraints=[make_device_constraints(0.001)], | ||
| ems_constraints=initialize_df(COLUMNS, START, END, RESOLUTION), | ||
| ) | ||
| assert problem.Mc == 1 | ||
|
|
||
|
|
||
| def test_large_committed_quantity_remains_feasible_under_a_non_convex_cost_curve(): | ||
| """A committed quantity far beyond the devices' flow limits must not be cut off by Mc. | ||
|
|
||
| The non-convex prices (summed upwards price below summed downwards price) activate the commitment-sign constraints, | ||
| in which Mc caps the deviations. | ||
| Before Mc accounted for the committed quantity, the required deviation exceeded Mc and the problem was infeasible. | ||
| """ | ||
| commitment = FlowCommitment( | ||
| name="energy", | ||
| quantity=-100, | ||
| upwards_deviation_price=-1, | ||
| downwards_deviation_price=1, | ||
| index=make_index(), | ||
| ) | ||
| schedule, costs, results, model = device_scheduler( | ||
| device_constraints=[make_device_constraints(0.5)], | ||
| ems_constraints=initialize_df(COLUMNS, START, END, RESOLUTION), | ||
| commitments=[commitment], | ||
| ) | ||
| assert results.solver.termination_condition == "optimal" | ||
| # The upwards deviation earns 1 per unit, so the device consumes at full power. | ||
| np.testing.assert_allclose(schedule[0].values, 0.5, atol=1e-6) | ||
| # Each of the 4 steps deviates upwards by 100.5 at price -1. | ||
| assert costs == pytest.approx(-4 * 100.5) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.