Skip to content
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

Calculate wind_offshore curtailment #115

Merged
merged 3 commits into from
Jun 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions postreise/analyze/generation/curtailment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@


# What is the name of the function in scenario.state to get the profiles?
_resource_func = {'solar': 'get_solar', 'wind': 'get_wind'}
_resource_func = {
'solar': 'get_solar',
'wind': 'get_wind',
'wind_offshore': 'get_wind',
}


def _check_scenario(scenario):
Expand Down Expand Up @@ -72,36 +76,40 @@ def _check_curtailment_in_grid(curtailment, grid):
raise ValueError(err_msg)


def calculate_curtailment_time_series(scenario, resources=('solar', 'wind')):
def calculate_curtailment_time_series(scenario, resources=None):
"""Calculate a time series of curtailment for a set of valid resources.
:param powersimdata.scenario.scenario.Scenario scenario: scenario instance.
:param tuple/list/set resources: names of resources to analyze.
:return: (*dict*) -- keys are resources, values are pandas.DataFrames
indexed by (datetime, plant) where plant is only plants of matching type.
"""
if resources is None:
resources = ('solar', 'wind', 'wind_offshore')
_check_scenario(scenario)
_check_resources(resources)
_check_resource_in_scenario(resources, scenario)

# Get input dataframes from scenario object
pg = scenario.state.get_pg()
rentype_genpotential = {
r: getattr(scenario.state, _resource_func[r])() for r in resources}
grid = scenario.state.get_grid()
profile_functions = {_resource_func[r] for r in resources}
relevant_profiles = pd.concat(
[getattr(scenario.state, p)() for p in profile_functions], axis=1)

# Calculate differences for each resource
curtailment = {}
for rentype, genpotential in rentype_genpotential.items():
ren_plants = list(genpotential.columns)
curtailment[rentype] = genpotential - pg[ren_plants]
for r in resources:
ren_plants = grid.plant.groupby('type').get_group(r).index
curtailment[r] = relevant_profiles[ren_plants] - pg[ren_plants]

return curtailment


def calculate_curtailment_percentage(scenario, resources=('solar', 'wind')):
"""Calculate year-long average curtailment for selected resources.
"""Calculate scenario-long average curtailment for selected resources.
:param powersimdata.scenario.scenario.Scenario scenario: scenario instance.
:param tuple/list/set resources: names of resources to analyze.
:return: (*float*) -- Average curtailment fraction over the year.
:return: (*float*) -- Average curtailment fraction over the scenario.
"""
_check_scenario(scenario)
_check_resources(resources)
Expand Down
41 changes: 23 additions & 18 deletions postreise/analyze/generation/tests/test_curtailment.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
# plant_id is the index
mock_plant = {
'plant_id': ['A', 'B', 'C', 'D'],
'bus_id': [1, 1, 2, 3],
'bus_id': [1, 2, 3, 4],
'lat': [47.6, 47.6, 37.8, 37.8],
'lon': [122.3, 122.3, 122.4, 122.4],
'type': ['solar', 'solar', 'wind', 'wind']
'type': ['solar', 'solar', 'wind', 'wind_offshore']
}

mock_pg = pd.DataFrame({
Expand Down Expand Up @@ -44,26 +44,28 @@

mock_curtailment = {
'solar': mock_curtailment_data[['A', 'B']],
'wind': mock_curtailment_data[['C', 'D']],
'wind': mock_curtailment_data[['C']],
'wind_offshore': mock_curtailment_data[['D']],
}

grid_attrs = {'plant': mock_plant}
scenario = MockScenario(
grid_attrs, pg=mock_pg, solar=mock_solar, wind=mock_wind)


class TestCalculateCurtailmentTimeSeries(unittest.TestCase):

def _check_curtailment_vs_expected(self, curtailment, expected):
self.assertIsInstance(curtailment, dict)
self.assertEqual(curtailment.keys(), expected.keys())
for key in curtailment.keys():
self.assertIsInstance(curtailment[key], pd.DataFrame)
assert_array_equal(curtailment[key].index.to_numpy(),
expected[key].index.to_numpy())
assert_array_equal(curtailment[key].columns.to_numpy(),
expected[key].columns.to_numpy())
assert_array_equal(curtailment[key].to_numpy(),
expected[key].to_numpy())
assert_array_equal(curtailment[key].index.to_numpy(),
expected[key].index.to_numpy())
assert_array_equal(curtailment[key].columns.to_numpy(),
expected[key].columns.to_numpy())
assert_array_equal(curtailment[key].to_numpy(),
expected[key].to_numpy())

def test_calculate_curtailment_time_series_solar(self):
expected_return = {'solar': mock_curtailment['solar']}
Expand Down Expand Up @@ -95,19 +97,19 @@ def test_calculate_curtailment_time_series_default(self):
self._check_curtailment_vs_expected(curtailment, expected_return)

def test_calculate_curtailment_time_series_solar_wind_tuple(self):
expected_return = mock_curtailment
expected_return = {r: mock_curtailment[r] for r in ('solar', 'wind')}
curtailment = calculate_curtailment_time_series(
BainanXia marked this conversation as resolved.
Show resolved Hide resolved
scenario, resources=('solar', 'wind'))
self._check_curtailment_vs_expected(curtailment, expected_return)

def test_calculate_curtailment_time_series_solar_wind_set(self):
expected_return = mock_curtailment
expected_return = {r: mock_curtailment[r] for r in ('solar', 'wind')}
curtailment = calculate_curtailment_time_series(
scenario, resources={'solar', 'wind'})
self._check_curtailment_vs_expected(curtailment, expected_return)

def test_calculate_curtailment_time_series_wind_solar_list(self):
expected_return = mock_curtailment
expected_return = {r: mock_curtailment[r] for r in ('solar', 'wind')}
curtailment = calculate_curtailment_time_series(
scenario, resources=['wind', 'solar'])
self._check_curtailment_vs_expected(curtailment, expected_return)
Expand Down Expand Up @@ -141,18 +143,18 @@ def test_calculate_curtailment_percentage_solar(self):
self.assertAlmostEqual(total_curtailment, expected_return)

def test_calculate_curtailment_percentage_wind(self):
expected_return = 3 / 23
expected_return = 0.5 / 23
total_curtailment = calculate_curtailment_percentage(
scenario, resources=('wind',))
self.assertAlmostEqual(total_curtailment, expected_return)

def test_calculate_curtailment_percentage_default(self):
expected_return = 6.5 / 48
expected_return = 4 / 48
total_curtailment = calculate_curtailment_percentage(scenario)
self.assertAlmostEqual(total_curtailment, expected_return)

def test_calculate_curtailment_percentage_solar_wind(self):
expected_return = 6.5 / 48
expected_return = 4 / 48
total_curtailment = calculate_curtailment_percentage(
scenario, resources=('solar', 'wind'))
self.assertAlmostEqual(total_curtailment, expected_return)
Expand All @@ -163,19 +165,22 @@ class TestSummarizeCurtailmentByBus(unittest.TestCase):
def test_summarize_curtailment_by_bus(self):
grid = scenario.state.get_grid()
expected_return = {
'solar': {1: 3.5},
'wind': {2: 0.5, 3: 2.5},
'solar': {1: 1, 2: 2.5},
'wind': {3: 0.5},
'wind_offshore': {4: 2.5},
}
bus_curtailment = summarize_curtailment_by_bus(mock_curtailment, grid)
self.assertEqual(bus_curtailment, expected_return)


class TestSummarizeCurtailmentByLocation(unittest.TestCase):

def test_summarize_curtailment_by_location(self):
grid = scenario.state.get_grid()
expected_return = {
'solar': {(47.6, 122.3): 3.5},
'wind': {(37.8, 122.4): 3},
'wind': {(37.8, 122.4): 0.5},
'wind_offshore': {(37.8, 122.4): 2.5},
}
location_curtailment = summarize_curtailment_by_location(
mock_curtailment, grid)
Expand Down