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

feat: more wind power curves #53

Merged
merged 12 commits into from
Dec 20, 2019
Merged

feat: more wind power curves #53

merged 12 commits into from
Dec 20, 2019

Conversation

danielolsen
Copy link
Contributor

This branch contains changes to the way wind profiles are generated that have been implemented in the latest profiles for Eastern. EIA Form 860 data are used to create state average turbine power curves (based on turbine models installed at real wind farms, the heights they're installed at, and the power curve for each turbine). When power_curves.py is imported, state power curves are build and saved if they are not already present in the expected directory.

Where turbine power curves are not available, or there are no wind farms listed in EIA Form 860, the IEC Class 2 curve is used by default. Tests are contained in test_power_curves.py to ensure that these functions are working properly.

Wind power profiles for farms are generated from state average turbine power curves based on a distribution of wind speeds throughout an hour (temporally) and throughout a farm (spatially). More information is given in the README.

python -m unittest -v
test_build_state_curves (prereise.gather.winddata.rap.tests.test_power_curves.TestBuildStateCurves) ... building state_power_curves
ok
test_shift_turbine_curve_higher_hub (prereise.gather.winddata.rap.tests.test_power_curves.TestShiftTurbineCurve) ... ok
test_shift_turbine_curve_lower_hub (prereise.gather.winddata.rap.tests.test_power_curves.TestShiftTurbineCurve) ... ok
test_shift_turbine_curve_sameheight_hub (prereise.gather.winddata.rap.tests.test_power_curves.TestShiftTurbineCurve) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.015s

OK

@rouille rouille force-pushed the more_wind_power_curves branch from ad7a9df to ecb5402 Compare December 16, 2019 18:34
Copy link
Collaborator

@merrielle merrielle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a bunch of comments, most of them are small. Nice tests!

Comment on lines +67 to +68
Once the U and V components of the wind are converted to a non-directional
wind speed magnitude, this speed is converted to power using wind turbine
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't most wind turbines only work with wind in a specific direction?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe all modern turbines pivot to follow the wind.

Comment on lines +85 to +89
wind speed. This distribution tends to boost the power produced at lower
wind speeds (since the power curve in this region is convex) and lower the
power produced at higher wind speeds (since the power curve in this region is
concave as the turbine tops out, and shuts down at higher wind speeds). This
tracks with the wind-farm level data shown in NREL's validation report.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


data_dir = path.abspath(path.join(path.dirname(__file__), '..', 'data'))
Form860 = get_form_860(data_dir)
powercurves_path = path.join(data_dir, 'PowerCurves.csv')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I believe this makes the paths work on both windows and mac?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path.join makes path-gluing work on all systems. path.abspath takes any relative path (including ..s) and transforms it to an absolute path.

Comment on lines +108 to +109
if not path.isdir(data_dir):
raise ValueError('data_dir is not a valid directory')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

if not isinstance(year, int):
raise TypeError('year is not an int')

form_860_filename = ''.join(['3_2_Wind_Y', str(year), '.csv'])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hot tip: you can use a format string and it's way simpler: f'3_2_Wind_Y{year}.csv'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't seen this before! Very handy.

Comment on lines 117 to 124
except FileNotFoundError:
regex_str = r'3_2_Wind_Y(\d{4}).csv'
matching_years = [
re.match(regex_str, f).group(1)
for f in os.listdir(data_dir) if re.match(regex_str, f)]
err_msg = ' '.join(['form data for year', str(year), 'not found. '])
err_msg += 'Years with data: ' + ', '.join(matching_years)
raise ValueError(err_msg)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's some stuff on glob if you want to check it out https://docs.python.org/3/library/glob.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm not grepping glob yet, but I think os + regex is a bit clearer for this application. I want to look through the directory, for filenames that match a pattern, and then extract part of that filename.

Comment on lines 147 to 157
data_dir = path.abspath(path.join(path.dirname(__file__), '..', 'data'))
Form860 = get_form_860(data_dir)
powercurves_path = path.join(data_dir, 'PowerCurves.csv')
PowerCurves = pd.read_csv(powercurves_path, index_col=0, header=None).T
PowerCurves.set_index('Speed bin (m/s)', inplace=True)
statepowercurves_path = path.join(data_dir, 'StatePowerCurves.csv')
try:
StatePowerCurves = pd.read_csv(statepowercurves_path, index_col=0)
except FileNotFoundError:
StatePowerCurves = build_state_curves(Form860, PowerCurves, rsd=0.4)
StatePowerCurves.to_csv(statepowercurves_path)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed in person and we're planning on making this into two functions instead

cumulative_curve = np.zeros_like(curve_x)
cumulative_capacity = 0
state_wind_farms = Form860[Form860['State'] == s]
for i, f in enumerate(state_wind_farms.index):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know enumerate() was a thing! That's super useful. Also I think you might be able to operate over the entire dataframe at once instead of looping over it. Let me get back to you on that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enumerate is great. It beats the hell out of for i in range(len(foo)):

Comment on lines 79 to 82
for s in states:
x = state_curves.index
y = np.zeros_like(x)
for i, w in enumerate(x):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

starting to get hard to follow the variable names

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

sample_points = np.logical_and((x > min_point),(x < max_point))
cdf_points = norm.cdf(x[sample_points], loc=w, scale=sd)
pdf_points = np.concatenate((np.zeros(1), np.diff(cdf_points)))
#pdf_points *= 1 / np.sum(pdf_points)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot to delete this

@danielolsen
Copy link
Contributor Author

Changes made based on the review by @merrielle. @rouille, I think you'll want to take a look at some of the changes I made here. The biggest one is that get_power() now takes the PowerCurves and StatePowerCurves dataframes as inputs, and therefore avoids running any code upon import of power_curves.py. When we want to call get_power(), first we have to import and call get_turbine_power_curves() and get_state_power_curves(), and then we pass these on each call of get_power(). I made what I think are the appropriate changes in rap.py and impute.py, but you may have other notebook code that I can't see.

More tests added as well.

> python -m unittest -v
test_build_state_curves (prereise.gather.winddata.rap.tests.test_power_curves.TestBuildStateCurves) ... building state_power_curves
ok
test_bad_dir (prereise.gather.winddata.rap.tests.test_power_curves.TestGetForm860) ... ok
test_bad_year (prereise.gather.winddata.rap.tests.test_power_curves.TestGetForm860) ... ok
test_default_year (prereise.gather.winddata.rap.tests.test_power_curves.TestGetForm860) ... ok
test_good_year (prereise.gather.winddata.rap.tests.test_power_curves.TestGetForm860) ... ok
test_year_str (prereise.gather.winddata.rap.tests.test_power_curves.TestGetForm860) ... ok
test_get_power_badstate_ten_different_default (prereise.gather.winddata.rap.tests.test_power_curves.TestGetPower) ... AR not found, defaulting to GE 1.5 SLE
ok
test_get_power_default_five (prereise.gather.winddata.rap.tests.test_power_curves.TestGetPower) ... foo not found, defaulting to IEC class 2
ok
test_get_power_default_ten (prereise.gather.winddata.rap.tests.test_power_curves.TestGetPower) ... foo not found, defaulting to IEC class 2
ok
test_get_power_default_thirty (prereise.gather.winddata.rap.tests.test_power_curves.TestGetPower) ... foo not found, defaulting to IEC class 2
ok
test_get_power_default_twenty (prereise.gather.winddata.rap.tests.test_power_curves.TestGetPower) ... foo not found, defaulting to IEC class 2
ok
test_get_power_default_zero (prereise.gather.winddata.rap.tests.test_power_curves.TestGetPower) ... foo not found, defaulting to IEC class 2
ok
test_get_power_named_ten (prereise.gather.winddata.rap.tests.test_power_curves.TestGetPower) ... ok
test_get_power_named_ten2 (prereise.gather.winddata.rap.tests.test_power_curves.TestGetPower) ... ok
test_get_state_power_curves (prereise.gather.winddata.rap.tests.test_power_curves.TestGetStatePowerCurves) ... ok
test_get_turbine_power_curves (prereise.gather.winddata.rap.tests.test_power_curves.TestGetTurbinePowerCurves) ... ok
test_shift_turbine_curve_higher_hub (prereise.gather.winddata.rap.tests.test_power_curves.TestShiftTurbineCurve) ... ok
test_shift_turbine_curve_lower_hub (prereise.gather.winddata.rap.tests.test_power_curves.TestShiftTurbineCurve) ... ok
test_shift_turbine_curve_sameheight_hub (prereise.gather.winddata.rap.tests.test_power_curves.TestShiftTurbineCurve) ... ok

----------------------------------------------------------------------
Ran 19 tests in 0.485s

OK

@rouille
Copy link
Collaborator

rouille commented Dec 19, 2019

It is working.

Copy link
Collaborator

@merrielle merrielle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 👍 This looks awesome. Great job with the changes and excellent tests!

if w == 0:
xs = state_curves.index
ys = np.zeros_like(xs)
for i, x in enumerate(xs):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, this actually helps a lot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

w for wind speed totally made sense in my head at the time...

@danielolsen danielolsen merged commit f954c4a into develop Dec 20, 2019
@danielolsen danielolsen deleted the more_wind_power_curves branch March 14, 2020 00:09
@ahurli ahurli mentioned this pull request Mar 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants