-
Notifications
You must be signed in to change notification settings - Fork 13
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
feat: analyze binding constraints (ramp, pmin, pmax) #121
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
from powersimdata.scenario.scenario import Scenario | ||
from powersimdata.scenario.analyze import Analyze | ||
|
||
|
||
def _check_scenario(scenario): | ||
"""Private function used only for type-checking for public functions. | ||
:param powersimdata.scenario.scenario.Scenario scenario: scenario instance. | ||
:raises TypeError: if scenario is not a Scenario object. | ||
:raises ValueError: if scenario is not in Analyze state. | ||
""" | ||
if not isinstance(scenario, Scenario): | ||
raise TypeError('scenario must be a Scenario object') | ||
if not isinstance(scenario.state, Analyze): | ||
raise ValueError('scenario.state must be Analyze') | ||
|
||
|
||
def _check_epsilon(epsilon): | ||
"""Private function used only for type-checking for public functions. | ||
:param float/int epsilon: precision for binding constraints. | ||
:raises TypeError: if epsilon is not a float or an int. | ||
:raises ValueError: if epsilon is negative. | ||
""" | ||
if not isinstance(epsilon, (float, int)): | ||
raise TypeError('epsilon must be numeric') | ||
if epsilon < 0: | ||
raise ValueError('epsilon must be non-negative') | ||
|
||
|
||
def pmin_constraints(scenario, epsilon=1e-3): | ||
"""Identify time periods in which generators are at minimum power. | ||
|
||
:param powersimdata.scenario.scenario.Scenario scenario: scenario instance. | ||
:param float epsilon: allowable 'fuzz' for whether constraint is binding. | ||
:return: (*pandas.DataFrame*) -- Boolean dataframe of same shape as PG. | ||
""" | ||
_check_scenario(scenario) | ||
_check_epsilon(epsilon) | ||
|
||
pg = scenario.state.get_pg() | ||
grid = scenario.state.get_grid() | ||
pmin = grid.plant['Pmin'] | ||
binding_pmin_constraints = ((pg - pmin) <= epsilon) | ||
|
||
return binding_pmin_constraints | ||
|
||
|
||
def pmax_constraints(scenario, epsilon=1e-3): | ||
"""Identify time periods in which generators are at maximum power. | ||
|
||
:param powersimdata.scenario.scenario.Scenario scenario: scenario instance. | ||
:param float epsilon: allowable 'fuzz' for whether constraint is binding. | ||
:return: (*pandas.DataFrame*) -- Boolean dataframe of same shape as PG. | ||
""" | ||
_check_scenario(scenario) | ||
_check_epsilon(epsilon) | ||
|
||
pg = scenario.state.get_pg() | ||
grid = scenario.state.get_grid() | ||
pmax = grid.plant['Pmax'] | ||
binding_pmax_constraints = ((pmax - pg) <= epsilon) | ||
|
||
return binding_pmax_constraints | ||
|
||
|
||
def ramp_constraints(scenario, epsilon=1e-3): | ||
"""Identify time periods in which generators have binding ramp constraints. | ||
.. note:: The first time period will always return *False* for each column. | ||
|
||
:param powersimdata.scenario.scenario.Scenario scenario: scenario instance. | ||
:param float epsilon: allowable 'fuzz' for whether constraint is binding. | ||
:return: (*pandas.DataFrame*) -- Boolean dataframe of same shape as PG. | ||
""" | ||
_check_scenario(scenario) | ||
_check_epsilon(epsilon) | ||
|
||
pg = scenario.state.get_pg() | ||
grid = scenario.state.get_grid() | ||
ramp = grid.plant['ramp_30'] | ||
diff = pg.diff(axis=0) | ||
binding_ramp_constraints = ((ramp * 2 - abs(diff)) <= epsilon) | ||
|
||
return binding_ramp_constraints |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
import unittest | ||
|
||
import pandas as pd | ||
|
||
from powersimdata.tests.mock_scenario import MockScenario | ||
from postreise.analyze.generation.binding import \ | ||
pmin_constraints, pmax_constraints, ramp_constraints, \ | ||
_check_scenario, _check_epsilon | ||
|
||
|
||
class TestCheckScenario(unittest.TestCase): | ||
|
||
def test_good_scenario(self): | ||
mock_plant = { | ||
'plant_id': ['A', 'B', 'C', 'D'], | ||
'ramp_30': [2.5, 5, 10, 25], | ||
} | ||
mock_scenario = MockScenario({'plant': mock_plant}) | ||
_check_scenario(mock_scenario) | ||
|
||
def test_bad_scenario_type(self): | ||
with self.assertRaises(TypeError): | ||
_check_scenario('307') | ||
|
||
def test_bad_scenario_state(self): | ||
mock_plant = { | ||
'plant_id': ['A', 'B', 'C', 'D'], | ||
'ramp_30': [2.5, 5, 10, 25], | ||
} | ||
mock_scenario = MockScenario({'plant': mock_plant}) | ||
mock_scenario.state = 'Create' | ||
with self.assertRaises(ValueError): | ||
_check_scenario(mock_scenario) | ||
|
||
|
||
class TestCheckEpsilon(unittest.TestCase): | ||
|
||
def test_good_float_value(self): | ||
_check_epsilon(5e-4) | ||
|
||
def test_good_int_value(self): | ||
_check_epsilon(1) | ||
|
||
def test_zero(self): | ||
_check_epsilon(0) | ||
|
||
def test_bad_type(self): | ||
with self.assertRaises(TypeError): | ||
_check_epsilon('0.001') | ||
|
||
def test_bad_value(self): | ||
with self.assertRaises(ValueError): | ||
_check_epsilon(-0.001) | ||
|
||
|
||
class TestRampConstraints(unittest.TestCase): | ||
|
||
def setUp(self): | ||
mock_plant = { | ||
'plant_id': ['A', 'B', 'C', 'D'], | ||
'ramp_30': [2.5, 5, 10, 25], | ||
} | ||
grid_attrs = {'plant': mock_plant} | ||
mock_pg = pd.DataFrame({ | ||
'UTC': ['t1', 't2', 't3', 't4'], | ||
'A': [100, 104, (99 + 1e-4), (104 + 1e-4 - 1e-7)], | ||
'B': [50, 45, 50, 45], | ||
'C': [20, 40, 60, 80], | ||
'D': [200, 150, 100, 50], | ||
}) | ||
mock_pg.set_index('UTC', inplace=True) | ||
self.mock_scenario = MockScenario(grid_attrs, pg=mock_pg) | ||
self.default_expected = pd.DataFrame({ | ||
'UTC': ['t1', 't2', 't3', 't4'], | ||
'A': [False, False, True, True], | ||
'B': [False, False, False, False], | ||
'C': [False, True, True, True], | ||
'D': [False, True, True, True], | ||
}) | ||
self.default_expected.set_index('UTC', inplace=True) | ||
|
||
def get_default_expected(self): | ||
return self.default_expected.copy() | ||
|
||
def test_ramp_constraints_default(self): | ||
binding_ramps = ramp_constraints(self.mock_scenario) | ||
expected = self.get_default_expected() | ||
assert binding_ramps.equals(self.get_default_expected()) | ||
|
||
def test_ramp_constraints_spec_epsilon1(self): | ||
# Same results as test_ramp_constraints_default | ||
binding_ramps = ramp_constraints(self.mock_scenario, epsilon=1e-3) | ||
expected = self.get_default_expected() | ||
assert binding_ramps.equals(expected) | ||
|
||
def test_ramp_constraints_spec_epsilon2(self): | ||
# One differece from test_ramp_constraints_default: ('A', 't3') | ||
binding_ramps = ramp_constraints(self.mock_scenario, epsilon=1e-6) | ||
expected = self.get_default_expected() | ||
expected.loc['t3', 'A'] = False | ||
assert binding_ramps.equals(expected) | ||
|
||
def test_ramp_constraints_spec_epsilon3(self): | ||
# Two differeces from test_ramp_constraints_default: ('A', ['t3'/'t4']) | ||
binding_ramps = ramp_constraints(self.mock_scenario, epsilon=1e-9) | ||
expected = self.get_default_expected() | ||
expected.loc[:, 'A'] = False | ||
assert binding_ramps.equals(expected) | ||
|
||
|
||
class TestPminConstraints(unittest.TestCase): | ||
|
||
def setUp(self): | ||
mock_plant = { | ||
'plant_id': ['A', 'B', 'C', 'D'], | ||
'Pmin': [0, 10, 20, 30], | ||
} | ||
grid_attrs = {'plant': mock_plant} | ||
mock_pg = pd.DataFrame({ | ||
'UTC': ['t1', 't2'], | ||
'A': [0, 0], | ||
'B': [(10 + 1e-4), 15], | ||
'C': [25, (20 + 1e-7)], | ||
'D': [35, 40], | ||
}) | ||
mock_pg.set_index('UTC', inplace=True) | ||
self.mock_scenario = MockScenario(grid_attrs, pg=mock_pg) | ||
self.default_expected = pd.DataFrame({ | ||
'UTC': ['t1', 't2'], | ||
'A': [True, True], | ||
'B': [True, False], | ||
'C': [False, True], | ||
'D': [False, False], | ||
}) | ||
self.default_expected.set_index('UTC', inplace=True) | ||
|
||
def get_default_expected(self): | ||
return self.default_expected.copy() | ||
|
||
def test_pmin_constraints_default(self): | ||
binding_pmins = pmin_constraints(self.mock_scenario) | ||
expected = self.get_default_expected() | ||
assert binding_pmins.equals(expected) | ||
|
||
def test_pmin_constraints_default_spec_epsilon1(self): | ||
binding_pmins = pmin_constraints(self.mock_scenario, epsilon=1e-3) | ||
expected = self.get_default_expected() | ||
assert binding_pmins.equals(expected) | ||
|
||
def test_pmin_constraints_default_spec_epsilon2(self): | ||
binding_pmins = pmin_constraints(self.mock_scenario, epsilon=1e-6) | ||
expected = self.get_default_expected() | ||
expected.loc['t1', 'B'] = False | ||
assert binding_pmins.equals(expected) | ||
|
||
def test_pmin_constraints_default_spec_epsilon3(self): | ||
binding_pmins = pmin_constraints(self.mock_scenario, epsilon=1e-9) | ||
expected = self.get_default_expected() | ||
expected.loc['t1', 'B'] = False | ||
expected.loc['t2', 'C'] = False | ||
assert binding_pmins.equals(expected) | ||
|
||
|
||
class TestPmaxConstraints(unittest.TestCase): | ||
|
||
def setUp(self): | ||
mock_plant = { | ||
'plant_id': ['A', 'B', 'C', 'D'], | ||
'Pmax': [50, 75, 100, 200], | ||
} | ||
grid_attrs = {'plant': mock_plant} | ||
mock_pg = pd.DataFrame({ | ||
'UTC': ['t1', 't2'], | ||
'A': [50, 50], | ||
'B': [(75 - 1e-4), 70], | ||
'C': [90, (100 - 1e-7)], | ||
'D': [150, 175], | ||
}) | ||
mock_pg.set_index('UTC', inplace=True) | ||
self.mock_scenario = MockScenario(grid_attrs, pg=mock_pg) | ||
self.default_expected = pd.DataFrame({ | ||
'UTC': ['t1', 't2'], | ||
'A': [True, True], | ||
'B': [True, False], | ||
'C': [False, True], | ||
'D': [False, False], | ||
}) | ||
self.default_expected.set_index('UTC', inplace=True) | ||
|
||
def get_default_expected(self): | ||
return self.default_expected.copy() | ||
|
||
def test_pmax_constraints_default(self): | ||
binding_pmaxs = pmax_constraints(self.mock_scenario) | ||
expected = self.get_default_expected() | ||
assert binding_pmaxs.equals(expected) | ||
|
||
def test_pmax_constraints_default_sepc_epsilon1(self): | ||
binding_pmaxs = pmax_constraints(self.mock_scenario, epsilon=1e-3) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sepc --> spec in function name. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
expected = self.get_default_expected() | ||
assert binding_pmaxs.equals(expected) | ||
|
||
def test_pmax_constraints_default_sepc_epsilon2(self): | ||
binding_pmaxs = pmax_constraints(self.mock_scenario, epsilon=1e-6) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
expected = self.get_default_expected() | ||
expected.loc['t1', 'B'] = False | ||
assert binding_pmaxs.equals(expected) | ||
|
||
def test_pmax_constraints_default_sepc_epsilon3(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
binding_pmaxs = pmax_constraints(self.mock_scenario, epsilon=1e-9) | ||
expected = self.get_default_expected() | ||
expected.loc['t1', 'B'] = False | ||
expected.loc['t2', 'C'] = False | ||
assert binding_pmaxs.equals(expected) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be 1 blank line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.