-
Notifications
You must be signed in to change notification settings - Fork 157
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
Add capability to sample and plot velocity profiles #699
Merged
rafmudaf
merged 35 commits into
NREL:develop
from
vallbog:feature/velocity-deficit-profiles
Dec 13, 2023
Merged
Changes from 14 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
c7c0fe3
Moved from other branch
vallbog 6688ee2
Preparing inputs in FlorisInterface
vallbog 55281b6
Created VelocityProfileGrid
vallbog b74f0ed
Velocity profiles returned successfully
vallbog 51079f7
Small fixes
vallbog 5c40659
Clarifications
vallbog 0a7ba7b
Draft of init for VelocityProfilesFigure class
vallbog 621556a
Small addition to class init
vallbog ae29d21
Methods for adding profiles and reference lines
vallbog 3b847a1
Improved example
vallbog 158cb99
Corrected self.axs
vallbog 77d0e42
Comments, style check, and updated output coords
vallbog c75c517
Details in example
vallbog 8165bcf
Updated comments
vallbog 79681bb
Merge remote-tracking branch 'nrel/develop' into pr/vallbog/699
rafmudaf d259c05
Change example number and add to docs listing
rafmudaf 21964c0
Replace LinesGrid with PointsGrid
rafmudaf 7c0a2e8
Merge pull request #1 from rafmudaf/feature/velocity-deficit-profiles
vallbog 452c1b7
Rotate sample points with the wind direction, and cleanup
vallbog afe10cd
Remove 5-dimensional indexing in point rotation
rafmudaf 97bc2d2
Return figure axes from CutPlane vis
rafmudaf 68fd463
Plot lines in horizontal plane
rafmudaf 3c74f0b
Merge pull request #2 from rafmudaf/feature/velocity-deficit-profiles
vallbog 782f711
Change coordinate notation and begin to update example
vallbog 947717a
Correct sampling, use solve_for_points, and WIP example
vallbog 8752a70
WIP: example
vallbog 07ca673
WIP: example
vallbog 96e8404
Finalize example
vallbog 299bddb
Details
vallbog b85a9c4
Auto-set xlim
vallbog 2747f72
Fix tabbing
rafmudaf 54fd2a7
Merge branch 'develop' into feature/velocity-deficit-profiles
rafmudaf 8eb47ff
Merge branch 'develop' into pr/vallbog/699
rafmudaf 41e3f1a
Merge branch 'develop' into pr/vallbog/699
rafmudaf ebc69a5
Improve handling the velocity derivate at z=0
rafmudaf 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
There are no files selected for viewing
This file contains 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,157 @@ | ||
# Copyright 2021 NREL | ||
|
||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||
# use this file except in compliance with the License. You may obtain a copy of | ||
# the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations under | ||
# the License. | ||
|
||
# See https://floris.readthedocs.io for documentation | ||
|
||
|
||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
|
||
from floris.tools import FlorisInterface | ||
from floris.tools.visualization import VelocityProfilesFigure | ||
|
||
|
||
""" | ||
The first part of this example illustrates how to plot velocity deficit profiles at | ||
several location downstream of a turbine. Here we use the following definition: | ||
velocity_deficit = (homogeneous_wind_speed - u) / homogeneous_wind_speed | ||
, where u is the wake velocity obtained when the incoming wind speed is the | ||
same at all heights and equal to `homogeneous_wind_speed`. | ||
The second part of the example shows a special case of how the profiles are affected | ||
by a change in wind direction as well as a change in turbine location and sampling | ||
starting point. | ||
""" | ||
|
||
def get_profiles(direction, resolution=100): | ||
return fi.sample_velocity_deficit_profiles( | ||
direction=direction, | ||
downstream_dists=downstream_dists, | ||
resolution=resolution, | ||
homogeneous_wind_speed=homogeneous_wind_speed, | ||
) | ||
|
||
if __name__ == '__main__': | ||
D = 126.0 # Turbine diameter | ||
hub_height = 90.0 | ||
fi = FlorisInterface("inputs/gch.yaml") | ||
fi.reinitialize(layout_x=[0.0], layout_y=[0.0]) | ||
|
||
downstream_dists = D * np.array([3, 5, 7]) | ||
homogeneous_wind_speed = 8.0 | ||
|
||
# Below, `get_profiles('y')` returns three velocity deficit profiles. These are sampled along | ||
# three corresponding lines that are all parallel to the y-axis (cross-stream direction). | ||
# The streamwise location of each line is given in `downstream_dists`. | ||
# Similarly, `get_profiles('z')` samples profiles in the vertical direction (z) at the | ||
# same streamwise locations. | ||
profiles = get_profiles('y') + get_profiles('z') | ||
|
||
# Initialize a VelocityProfilesFigure. The workflow is similar to a matplotlib Figure: | ||
# Initialize it, plot data, and then customize it further if needed. | ||
# The provided value of `layout` puts y-profiles on the top row of the figure and | ||
# z-profiles on the bottom row. | ||
profiles_fig = VelocityProfilesFigure( | ||
downstream_dists_D=downstream_dists / D, | ||
layout=['y', 'z'], | ||
) | ||
# Add profiles to the figure. This method automatically determines the direction and | ||
# streamwise location of each profile from the profile coordinates. | ||
profiles_fig.add_profiles(profiles, color='k') | ||
|
||
# Change velocity model to jensen, get the velocity deficit profiles, | ||
# and add them to the figure. | ||
floris_dict = fi.floris.as_dict() | ||
floris_dict['wake']['model_strings']['velocity_model'] = 'jensen' | ||
fi = FlorisInterface(floris_dict) | ||
profiles_y = get_profiles('y', resolution=400) | ||
profiles_z = get_profiles('z', resolution=400) | ||
profiles_fig.add_profiles(profiles_y + profiles_z, color='r') | ||
|
||
margin = 0.05 | ||
profiles_fig.set_xlim([0.0 - margin, 0.6 + margin]) | ||
profiles_fig.add_ref_lines_y([-0.5, 0.5]) | ||
profiles_fig.add_ref_lines_z([-0.5, 0.5]) | ||
|
||
profiles_fig.axs[0,0].legend(['gauss', 'jensen'], fontsize=11) | ||
profiles_fig.fig.suptitle( | ||
'Velocity decifit profiles from different velocity models', | ||
fontsize=14, | ||
) | ||
|
||
# Second part of example: | ||
# Case 1: Show that, if we have a single turbine, then the profiles are independent of | ||
# the wind direction. This is because x is defined to be in the streamwise direction | ||
# in sample_velocity_deficit_profiles and VelocityProfilesFigure. | ||
# Case 2: Show that the coordinates x/D, y/D and z/D returned by | ||
# sample_velocity_deficit_profiles are relative to the sampling starting point. | ||
# By default, this starting point is at (0.0, 0.0, fi.floris.flow_field.reference_wind_height) | ||
# in inertial coordinates. | ||
downstream_dists = D * np.array([3]) | ||
for case in [1, 2]: | ||
# The first added profile is a reference | ||
fi = FlorisInterface("inputs/gch.yaml") | ||
fi.reinitialize(layout_x=[0.0], layout_y=[0.0]) | ||
profiles = get_profiles('y', resolution=400) | ||
profiles_fig = VelocityProfilesFigure( | ||
downstream_dists_D=downstream_dists / D, | ||
layout=['y'], | ||
ax_width=3.5, | ||
ax_height=3.5, | ||
) | ||
profiles_fig.add_profiles(profiles, color='k') | ||
|
||
if case == 1: | ||
# Change wind direction compared to reference | ||
wind_directions = [315.0] | ||
layout_x, layout_y = [0.0], [0.0] | ||
# Same as the default starting point but specified for completeness | ||
x_inertial_start, y_inertial_start = 0.0, 0.0 | ||
elif case == 2: | ||
# Change turbine location compared to reference. Then, set the sampling starting | ||
# point to the new turbine location using the arguments | ||
# `x_inertial_start` and `y_inertial_start`. | ||
wind_directions = [270.0] | ||
layout_x, layout_y = [D], [D] | ||
x_inertial_start, y_inertial_start = D, D | ||
|
||
# Plot a second profile to show that it is equivalent to the reference | ||
fi.reinitialize(wind_directions=wind_directions, layout_x=layout_x, layout_y=layout_y) | ||
profiles = fi.sample_velocity_deficit_profiles( | ||
direction='y', | ||
downstream_dists=downstream_dists, | ||
resolution=21, | ||
homogeneous_wind_speed=homogeneous_wind_speed, | ||
x_inertial_start=x_inertial_start, | ||
y_inertial_start=y_inertial_start, | ||
) | ||
profiles_fig.add_profiles( | ||
profiles, | ||
linestyle='None', | ||
marker='o', | ||
color='b', | ||
markerfacecolor='None', | ||
markeredgewidth=1.3, | ||
) | ||
|
||
if case == 1: | ||
profiles_fig.axs[0,0].legend(['WD = 270 deg', 'WD = 315 deg']) | ||
elif case == 2: | ||
profiles_fig.fig.suptitle( | ||
'Legend (x, y) locations in inertial coordinates.\n' | ||
'x/D and y/D relative to sampling start point', | ||
) | ||
profiles_fig.axs[0,0].legend([ | ||
'turbine location: (0, 0)\nsampling start point: (0, 0)', | ||
'turbine location: (D, D)\nsampling start point: (D, D)', | ||
]) | ||
|
||
plt.show() |
This file contains 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 |
---|---|---|
|
@@ -44,6 +44,7 @@ | |
FlowFieldPlanarGrid, | ||
Grid, | ||
PointsGrid, | ||
LinesGrid, | ||
TurbineGrid, | ||
TurbineCubatureGrid | ||
) | ||
|
This file contains 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 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.
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.
When does the divide by zero issue occur? Is it when
self.reference_wind_height
is zero? I was able to run the example to completion with and without thisif
statementThere 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.
It occurs when
self.wind_shear
is zero andgrid.z_sorted
happens to include the value zero (a sample point at the ground level). For instance, removing theìf
statement and modifing example 29 by replacingwith
gives the following warnings
This is because the first element in
profile_range_z
makes the method include a sample point at ground level.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.
Thanks for clarifying! I see now: the$z^{(\alpha-1)}$ evaluates to $1/0$ when $z=0$ and $\alpha=0$ . This produces an $\alpha=0$ raises an invalid multiplication warning (0 x
inf
and raises a divide by zero warning; then, the multiplication byinf
) and results in anan
indwind_profile_plane
(the derivative of the shear profile layer).However, I actually think we should let these warnings be raised. In my testing, they only appear once, and they let the user know that something strange is happening when one evaluates the derivative of the shear profile at$z=0$ (the derivative is not well defined). In fact the divide by zero problem occurs when $z=0$ for any value of shear less than 1 (not just when shear is 0). The resulting figures appear to be the same regardless.
@rafmudaf @bayc , what are our practices regarding letting warnings be raised in "unusual" circumstances?
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.
Ok @vallbog @misi9170 I'm just coming up from my obsession on this question for the past couple of weeks. I started by trying to create a small unit test for this situation and realized that the Grid classes weren't able to be created from the
from_dict
method because they relied on theVec3
class, so that was addressed in #751. In the process of that, I noticed that many of the classes infloris.simulation
weren't proper slotted classes because the base classes weren't attrs-derived, and this exposed quite a bit of monkey patching - all of this was fixed in #750. Finally, I've been able to come back to this and now we can better test this particular situation through the unit testing infrastructure. I've written a general issue for this in #760.In testing this further, I noticed that the
if wind_shear == 0.0
condition is inadequate since the issue comes from the exponent in the derivate being less than 1 which causes the "divide by zero in reciprocal" runtime warning. As @misi9170 pointed out, this will happen if wind_shear is less than 1. However, it's actually the combination of wind shear less than 1 and z=0 that is problematic. To accommodate that, I propose to initialize the derivate to zeros and usenp.power
to calculate the derivative since it is a ufunc and supports thewhere=
option:I initially used
np.where
, but that evaluates all operations and then picks out the ones that fit the condition so the runtime warning is still raised.Here's a very simple script to test:
Without the proposed change, this raises a divide by zero warning.
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.
To not belabor the point, @misi9170 if you like it I'll make this commit and merge this pull request.
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.
@rafmudaf Thanks, this all makes sense. Feel free to merge. That
np.where()
evaluating both and therefore still raising the issue is something I've run into before, and is a pretty annoying limitation. But that's another story...