Skip to content

Commit

Permalink
Merge branch 'develop' into pr/Bartdoekemeijer/1024
Browse files Browse the repository at this point in the history
  • Loading branch information
paulf81 committed Dec 12, 2024
2 parents f413c2a + a3620f5 commit 4776c9a
Show file tree
Hide file tree
Showing 30 changed files with 586 additions and 173 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/check-working-examples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
os: [ubuntu-latest] #, macos-latest, windows-latest]
fail-fast: False

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
os: [ubuntu-latest] #, macos-latest, windows-latest]
fail-fast: False
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-pages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.13"

- name: Install dependencies
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
pip install build twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
python -m build
twine upload dist/*
2 changes: 1 addition & 1 deletion .github/workflows/quality-metrics-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.10"]
python-version: ["3.13"]
os: [ubuntu-latest]
fail-fast: False

Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
FLORIS is a controls-focused wind farm simulation software incorporating
steady-state engineering wake models into a performance-focused Python
framework. It has been in active development at NREL since 2013 and the latest
release is [FLORIS v4.1.1](https://github.com/NREL/floris/releases/latest).
release is [FLORIS v4.2.1](https://github.com/NREL/floris/releases/latest).
Online documentation is available at https://nrel.github.io/floris.

The software is in active development and engagement with the development team
Expand All @@ -13,6 +13,9 @@ the conversation in [GitHub Discussions](https://github.com/NREL/floris/discussi

## Installation

**WARNING:**
Support for python version 3.8 will be dropped in FLORIS v4.3. See [Installation documentation](https://nrel.github.io/floris/installation.html#installation) for details.

**If upgrading from a previous version, it is recommended to install FLORIS v4 into a new virtual environment**.
If you intend to use [pyOptSparse](https://mdolab-pyoptsparse.readthedocs-hosted.com/en/latest/) with FLORIS,
it is recommended to install that package first before installing FLORIS.
Expand Down Expand Up @@ -79,7 +82,7 @@ PACKAGE CONTENTS
wind_data

VERSION
4.2
4.2.1

FILE
~/floris/floris/__init__.py
Expand Down
4 changes: 2 additions & 2 deletions docs/dev_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ is located at `floris/.github/workflows/continuous-integration-workflow.yaml`.
The online documentation is built with Jupyter Book which uses Sphinx
as a framework. It is automatically built and hosted by GitHub, but it
can also be compiled locally. Additional dependencies are required
for the documentation, and they are listed in the `EXTRAS` of `setup.py`.
for the documentation, and they are listed in the `project.optional-dependencies` of `pyproject.toml`.
The commands to build the docs are given below. After successfully
compiling, a file should be located at ``docs/_build/html/index.html``.
This file can be opened in any browser.
Expand Down Expand Up @@ -246,7 +246,7 @@ Be sure to complete each step in the sequence as described.
with a commit message such as "Update version to vN.M".
The version number must be updated in the following two files:
- [floris/README.md](https://github.com/NREL/floris/blob/main/README.md)
- [floris/floris/version.py](https://github.com/NREL/floris/blob/main/floris/version.py)
- [pyproject.toml](https://github.com/NREL/floris/blob/main/pyproject.toml)
Note that a `.0` version number is left off meaning that valid versions
are `v3`, `v3.1`, `v3.1.1`, etc.
Expand Down
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ is highly encouraged. If you are interested in using FLORIS to conduct studies
of a wind farm or extending FLORIS to include your own wake model, please join
the conversation in [GitHub Discussions](https://github.com/NREL/floris/discussions/)!

```{note}
Support for python version 3.8 will be dropped in FLORIS v4.3. See {ref}`installation` for details.
```

## Quick Start

FLORIS is a Python package run on the command line typically by providing
Expand Down
7 changes: 6 additions & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ The following sections detail how download and install FLORIS for each use case.
(requirements)=
## Requirements

FLORIS is intended to be used with Python 3.8 and up, and it is highly recommended that users
FLORIS is a python package. FLORIS is intended to work with all [active versions of python](https://devguide.python.org/versions/). Support will drop for python versions once they reach end-of-life.
It is highly recommended that users
work within a virtual environment for both working with and working on FLORIS, to maintain a clean
and sandboxed environment. The simplest way to get started with virtual environments is through
[conda](https://docs.conda.io/en/latest/miniconda.html).

```{warning}
Support for python version 3.8 will be dropped in FLORIS v4.3.
```

Installing into a Python environment that contains a previous version of FLORIS may cause conflicts.
If you intend to use [pyOptSparse](https://mdolab-pyoptsparse.readthedocs-hosted.com/en/latest/)
with FLORIS, it is recommended to install that package first before installing FLORIS.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Example: Optimizing yaw angles with disabled turbines
This example demonstrates how to optimize yaw angles in FLORIS, when some turbines are disabled.
The example optimization is run using both YawOptimizerSR and YawOptimizerGeometric, the two
yaw optimizers that support disabling turbines.
"""

import numpy as np

from floris import FlorisModel
from floris.optimization.yaw_optimization.yaw_optimizer_geometric import YawOptimizationGeometric
from floris.optimization.yaw_optimization.yaw_optimizer_sr import YawOptimizationSR


# Load a 3-turbine model
fmodel = FlorisModel("../inputs/gch.yaml")

# Set wind conditions to be the same for two cases
fmodel.set(wind_directions=[270.]*2, wind_speeds=[8.]*2, turbulence_intensities=[.06]*2)

# First run the case where all turbines are active and print results
yaw_opt = YawOptimizationSR(fmodel)
df_opt = yaw_opt.optimize()
print("Serial Refine optimized yaw angles (all turbines active) [deg]:\n", df_opt.yaw_angles_opt)

yaw_opt = YawOptimizationGeometric(fmodel)
df_opt = yaw_opt.optimize()
print("\nGeometric optimized yaw angles (all turbines active) [deg]:\n", df_opt.yaw_angles_opt)

# Disable turbines (different pattern for each of the two cases)
# First case: disable the middle turbine
# Second case: disable the front turbine
fmodel.set_operation_model('mixed')
fmodel.set(disable_turbines=np.array([[False, True, False], [True, False, False]]))

# Rerun optimizations and print results
yaw_opt = YawOptimizationSR(fmodel)
df_opt = yaw_opt.optimize()
print(
"\nSerial Refine optimized yaw angles (some turbines disabled) [deg]:\n",
df_opt.yaw_angles_opt
)
# Note that disabled turbines are assigned a zero yaw angle, but their yaw angle is arbitrary as it
# does not affect the total power output.

yaw_opt = YawOptimizationGeometric(fmodel)
df_opt = yaw_opt.optimize()
print("\nGeometric optimized yaw angles (some turbines disabled) [deg]:\n", df_opt.yaw_angles_opt)
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
wind_speeds=wind_speeds,
turbulence_intensities=turbulence_intensities,
turbine_type=[turbine_dict],
reference_wind_height=fmodel.reference_wind_height
)
fmodel.run()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@
### Start by visualizing a single turbine in and its wake with the new model
# Load the new TurboPark implementation and switch to constant CT turbine
fmodel_new = FlorisModel("../inputs/turboparkgauss_cubature.yaml")
fmodel_new.set(turbine_type=[const_CT_turb])
fmodel_new.set(
turbine_type=[const_CT_turb],
reference_wind_height=fmodel_new.reference_wind_height
)
fmodel_new.run()
u0 = fmodel_new.wind_speeds[0]

Expand Down Expand Up @@ -94,7 +97,10 @@
### Look at the wake profile at a single downstream distance for a range of wind directions
# Load the original TurboPark implementation and switch to constant CT turbine
fmodel_orig = FlorisModel("../inputs/turbopark_cubature.yaml")
fmodel_orig.set(turbine_type=[const_CT_turb])
fmodel_orig.set(
turbine_type=[const_CT_turb],
reference_wind_height=fmodel_orig.reference_wind_height
)

# Set up and solve flows
wd_array = np.arange(225,315,0.1)
Expand Down
4 changes: 2 additions & 2 deletions floris/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

from importlib.metadata import version
from pathlib import Path


with open(Path(__file__).parent / "version.py") as _version_file:
__version__ = _version_file.read().strip()
__version__ = version("floris")


from .floris_model import FlorisModel
Expand Down
92 changes: 61 additions & 31 deletions floris/core/turbine/operation_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,22 +382,22 @@ def axial_induction(
@define
class MixedOperationTurbine(BaseOperationModel):

@staticmethod
def power(
yaw_angles: NDArrayFloat,
power_setpoints: NDArrayFloat,
**kwargs
):
# Yaw angles mask all yaw_angles not equal to zero
yaw_angles_mask = yaw_angles != 0.0
power_setpoints_mask = power_setpoints < POWER_SETPOINT_DEFAULT
neither_mask = np.logical_not(yaw_angles_mask) & np.logical_not(power_setpoints_mask)

if (power_setpoints_mask & yaw_angles_mask).any():
raise ValueError((
"Power setpoints and yaw angles are incompatible."
"If yaw_angles entry is nonzero, power_setpoints must be greater than"
" or equal to {0}.".format(POWER_SETPOINT_DEFAULT)
))
(
yaw_angles,
power_setpoints,
yaw_angles_mask,
power_setpoints_mask,
neither_mask
) = MixedOperationTurbine._handle_mixed_operation_setpoints(
yaw_angles=yaw_angles,
power_setpoints=power_setpoints
)

powers = np.zeros_like(power_setpoints)
powers[yaw_angles_mask] += CosineLossTurbine.power(
Expand All @@ -414,21 +414,22 @@ def power(

return powers

@staticmethod
def thrust_coefficient(
yaw_angles: NDArrayFloat,
power_setpoints: NDArrayFloat,
**kwargs
):
yaw_angles_mask = yaw_angles != 0.0
power_setpoints_mask = power_setpoints < POWER_SETPOINT_DEFAULT
neither_mask = np.logical_not(yaw_angles_mask) & np.logical_not(power_setpoints_mask)

if (power_setpoints_mask & yaw_angles_mask).any():
raise ValueError((
"Power setpoints and yaw angles are incompatible."
"If yaw_angles entry is nonzero, power_setpoints must be greater than"
" or equal to {0}.".format(POWER_SETPOINT_DEFAULT)
))
(
yaw_angles,
power_setpoints,
yaw_angles_mask,
power_setpoints_mask,
neither_mask
) = MixedOperationTurbine._handle_mixed_operation_setpoints(
yaw_angles=yaw_angles,
power_setpoints=power_setpoints
)

thrust_coefficients = np.zeros_like(power_setpoints)
thrust_coefficients[yaw_angles_mask] += CosineLossTurbine.thrust_coefficient(
Expand All @@ -445,21 +446,22 @@ def thrust_coefficient(

return thrust_coefficients

@staticmethod
def axial_induction(
yaw_angles: NDArrayFloat,
power_setpoints: NDArrayFloat,
**kwargs
):
yaw_angles_mask = yaw_angles != 0.0
power_setpoints_mask = power_setpoints < POWER_SETPOINT_DEFAULT
neither_mask = np.logical_not(yaw_angles_mask) & np.logical_not(power_setpoints_mask)

if (power_setpoints_mask & yaw_angles_mask).any():
raise ValueError((
"Power setpoints and yaw angles are incompatible."
"If yaw_angles entry is nonzero, power_setpoints must be greater than"
" or equal to {0}.".format(POWER_SETPOINT_DEFAULT)
))
(
yaw_angles,
power_setpoints,
yaw_angles_mask,
power_setpoints_mask,
neither_mask
) = MixedOperationTurbine._handle_mixed_operation_setpoints(
yaw_angles=yaw_angles,
power_setpoints=power_setpoints
)

axial_inductions = np.zeros_like(power_setpoints)
axial_inductions[yaw_angles_mask] += CosineLossTurbine.axial_induction(
Expand All @@ -476,6 +478,34 @@ def axial_induction(

return axial_inductions

@staticmethod
def _handle_mixed_operation_setpoints(
yaw_angles: NDArrayFloat,
power_setpoints: NDArrayFloat,
):
"""
Check for incompatible yaw angles and power setpoints and raise an error if found.
Return masks and updated setpoints.
"""
# If any turbines are disabled, set their yaw angles to zero
yaw_angles[power_setpoints <= POWER_SETPOINT_DISABLED] = 0.0

# Create masks for whether yaw angles and power setpoints are set
yaw_angles_mask = yaw_angles != 0.0
power_setpoints_mask = power_setpoints < POWER_SETPOINT_DEFAULT
neither_mask = np.logical_not(yaw_angles_mask) & np.logical_not(power_setpoints_mask)

# Check for incompatibility and raise error if found.
if (power_setpoints_mask & yaw_angles_mask).any():
raise ValueError((
"Power setpoints and yaw angles are incompatible."
"If yaw_angles entry is nonzero, power_setpoints must be greater than"
" or equal to {0}.".format(POWER_SETPOINT_DEFAULT)
))

# Return updated setpoints as well as masks
return yaw_angles, power_setpoints, yaw_angles_mask, power_setpoints_mask, neither_mask

@define
class AWCTurbine(BaseOperationModel):
"""
Expand Down
Loading

0 comments on commit 4776c9a

Please sign in to comment.