From 8633290c5ef8cb4f19a9486d5b63d285c1e27bb8 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Thu, 9 Oct 2025 14:39:26 -0600 Subject: [PATCH 1/4] Enable running externally provided op model --- floris/core/turbine/turbine.py | 5 +- floris/floris_model.py | 14 +++- ...rbine_operation_models_integration_test.py | 82 +++++++++++++++++++ 3 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 tests/turbine_operation_models_integration_test.py diff --git a/floris/core/turbine/turbine.py b/floris/core/turbine/turbine.py index 0994b5c8f..f195d09ed 100644 --- a/floris/core/turbine/turbine.py +++ b/floris/core/turbine/turbine.py @@ -559,7 +559,10 @@ def __post_init__(self) -> None: self.power_thrust_table = floris_numeric_dict_converter(self.power_thrust_table) def _initialize_power_thrust_functions(self) -> None: - turbine_function_model = TURBINE_MODEL_MAP["operation_model"][self.operation_model] + if isinstance(self.operation_model, str): + turbine_function_model = TURBINE_MODEL_MAP["operation_model"][self.operation_model] + else: + turbine_function_model = self.operation_model self.thrust_coefficient_function = turbine_function_model.thrust_coefficient self.axial_induction_function = turbine_function_model.axial_induction self.power_function = turbine_function_model.power diff --git a/floris/floris_model.py b/floris/floris_model.py index aaa384029..91333e17d 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -999,7 +999,7 @@ def get_farm_AVP( turbine_weights=turbine_weights ) * hours_per_year - def get_turbine_ais(self) -> NDArrayFloat: + def get_turbine_axial_induction_factors(self) -> NDArrayFloat: turbine_ais = axial_induction( velocities=self.core.flow_field.u, turbulence_intensities=self.core.flow_field.turbulence_intensity_field[:,:,None,None], @@ -1020,6 +1020,13 @@ def get_turbine_ais(self) -> NDArrayFloat: ) return turbine_ais + def get_turbine_ais(self) -> NDArrayFloat: + self.logger.warning( + "Computing axial inductions with get_turbine_ais is now deprecated. Please use" + " the more explicit get_turbine_axial_induction_factors method instead." + ) + return self.get_turbine_axial_induction_factors() + def get_turbine_thrust_coefficients(self) -> NDArrayFloat: turbine_thrust_coefficients = thrust_coefficient( velocities=self.core.flow_field.u, @@ -1568,7 +1575,7 @@ def set_operation_model(self, operation_model: str | List[str]): Args: operation_model (str): The operation model to set. """ - if isinstance(operation_model, str): + if (not isinstance(operation_model, (list, np.ndarray))): if len(self.core.farm.turbine_type) == 1: # Set a single one here, then, and return turbine_type = self.core.farm.turbine_definitions[0] @@ -1587,11 +1594,12 @@ def set_operation_model(self, operation_model: str | List[str]): "equal to the number of turbines." ) + # Proceed to update turbine definitions turbine_type_list = self.core.farm.turbine_definitions for tindex in range(self.core.farm.n_turbines): turbine_type_list[tindex]["turbine_type"] = ( - turbine_type_list[tindex]["turbine_type"]+"_"+operation_model[tindex] + turbine_type_list[tindex]["turbine_type"]+"_"+str(operation_model[tindex]) ) turbine_type_list[tindex]["operation_model"] = operation_model[tindex] diff --git a/tests/turbine_operation_models_integration_test.py b/tests/turbine_operation_models_integration_test.py new file mode 100644 index 000000000..e27d97463 --- /dev/null +++ b/tests/turbine_operation_models_integration_test.py @@ -0,0 +1,82 @@ +import numpy as np +import pytest + +from attrs import define, field + +from floris import FlorisModel +from floris.core.turbine.operation_models import BaseOperationModel + +def test_static_user_defined_op_model(): + # Establish a static class + @define + class UserDefinedStatic(BaseOperationModel): + def power(velocities, **_): + return 1000*np.ones(velocities.shape[:2]) + def thrust_coefficient(velocities, **_): + return 0.8*np.ones(velocities.shape[:2]) + def axial_induction(velocities, **_): + return 1/3*np.ones(velocities.shape[:2]) + + fmodel = FlorisModel("defaults") + fmodel.set( + layout_x=[0.0, 500.0, 1000.0], + layout_y=[0.0, 0.0, 0.0], + wind_speeds=[8.0, 9.0], + wind_directions=[270.0, 280.0], + turbulence_intensities=[0.06, 0.06] + ) + fmodel.set_operation_model(UserDefinedStatic) + fmodel.run() + power = fmodel.get_turbine_powers() + thrust_coefficients = fmodel.get_turbine_thrust_coefficients() + axial_inductions = fmodel.get_turbine_axial_induction_factors() + + assert np.all(power.shape == (2, 3)) + assert np.all(thrust_coefficients.shape == (2, 3)) + assert np.all(axial_inductions.shape == (2, 3)) + + assert np.allclose(power, 1000.0) + assert np.allclose(thrust_coefficients, 0.8) + assert np.allclose(axial_inductions, 1/3) + +def test_dynamic_user_defined_op_model(): + # Establish a dynamic class + @define + class UserDefinedDynamic(BaseOperationModel): + _flat_power = field(init=True, default=500.0) + _flat_thrust_coefficient = field(init=True, default=0.7) + _flat_axial_induction = field(init=True, default=0.3) + def power(self, velocities, **_): + return self._flat_power*np.ones(velocities.shape[:2]) + def thrust_coefficient(self, velocities, **_): + return self._flat_thrust_coefficient*np.ones(velocities.shape[:2]) + def axial_induction(self, velocities, **_): + return self._flat_axial_induction*np.ones(velocities.shape[:2]) + + fmodel = FlorisModel("defaults") + fmodel.set( + layout_x=[0.0, 500.0, 1000.0], + layout_y=[0.0, 0.0, 0.0], + wind_speeds=[8.0, 9.0], + wind_directions=[270.0, 280.0], + turbulence_intensities=[0.06, 0.06] + ) + # Try without instantiating (TODO: create more helpful error?) + with pytest.raises(TypeError): + fmodel.set_operation_model(UserDefinedDynamic) + fmodel.run() + # Now instantiate and try again + instantiated_operation_model = UserDefinedDynamic() + fmodel.set_operation_model(instantiated_operation_model) + fmodel.run() + power = fmodel.get_turbine_powers() + thrust_coefficients = fmodel.get_turbine_thrust_coefficients() + axial_inductions = fmodel.get_turbine_axial_induction_factors() + + assert np.all(power.shape == (2, 3)) + assert np.all(thrust_coefficients.shape == (2, 3)) + assert np.all(axial_inductions.shape == (2, 3)) + + assert np.allclose(power, 500.0) + assert np.allclose(thrust_coefficients, 0.7) + assert np.allclose(axial_inductions, 0.3) From e5e96ba6f10e3708a26811e4e3e0c014ca0be2b3 Mon Sep 17 00:00:00 2001 From: misi9170 Date: Thu, 9 Oct 2025 14:43:12 -0600 Subject: [PATCH 2/4] isort --- tests/turbine_operation_models_integration_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/turbine_operation_models_integration_test.py b/tests/turbine_operation_models_integration_test.py index e27d97463..5bafe3383 100644 --- a/tests/turbine_operation_models_integration_test.py +++ b/tests/turbine_operation_models_integration_test.py @@ -1,11 +1,11 @@ import numpy as np import pytest - from attrs import define, field from floris import FlorisModel from floris.core.turbine.operation_models import BaseOperationModel + def test_static_user_defined_op_model(): # Establish a static class @define From 9c8fca03f1520ea943c3080ac1e3ec8294f2663d Mon Sep 17 00:00:00 2001 From: misi9170 Date: Mon, 17 Nov 2025 17:35:55 -0700 Subject: [PATCH 3/4] Documentation describing how to implement a user-defined op model --- docs/user_defined_operation_models.ipynb | 499 +++++++++++++++++++++++ 1 file changed, 499 insertions(+) create mode 100644 docs/user_defined_operation_models.ipynb diff --git a/docs/user_defined_operation_models.ipynb b/docs/user_defined_operation_models.ipynb new file mode 100644 index 000000000..d1641b93e --- /dev/null +++ b/docs/user_defined_operation_models.ipynb @@ -0,0 +1,499 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ba9ae6ce", + "metadata": {}, + "source": [ + "# Turbine Operation Models\n", + "\n", + "Beginning in v4.x, FLORIS supports user-defined turbine operation models that can be passed directly into FLORIS using `fmodel.set_operation_model`. A user-defined operation model may be a dynamic or static class. If the operation model is a dynamic class, it must conform to the `attrs` package for declaring attributes. Additionally all user-defined operation models should inherit from the abstract parent class `BaseOperationModel`, available in FLORIS.\n", + "\n", + "All operation models must implement the following \"fundamental\" methods:\n", + "- `power`: computes the power output of the turbine in Watts\n", + "- `thrust_coefficient`: computes the dimensionless thrust coefficient of the turbine\n", + "- `axial_induction`: computes the dimensionless axial induction factor of the turbine\n", + "\n", + "Operation models may then implement additional methods as needed.\n", + "\n", + "The following arguments are passed to the operation model fundamental methods at runtime:\n", + "\n", + "| Argument | Data type | Description |\n", + "|----------|-----------|----------|\n", + "| `power_thrust_table` | `dict` | Dictionary of model parameters defined on the turbine input yaml |\n", + "| `velocities` | `NDArrayFloat` | Array of inflow velocities (in m/s) to each turbine grid point, dimensions `(n_findex, n_turbines, n_grid, n_grid)` |\n", + "| `turbulence_intensities` | `NDArrayFloat` | Array of inflow turbulence intensities (as decimal values) to each turbine, dimensions `(n_findex, n_turbines, 1, 1)` |\n", + "| `air_density` | `float` | Ambient air density in kg/m^3 |\n", + "| `yaw_angles` | `NDArrayFloat` | Array of turbine yaw angles (in degrees, as misalignments from the inflow wind direction), dimensions `(n_findex, n_turbines)` |\n", + "| `tilt_angles` | `NDArrayFloat` | Array of turbine absolute [CHECK] tilt angles (in degrees, positive means tilted backwards), dimensions `(n_findex, n_turbines)` |\n", + "| `power_setpoints` | `NDArrayFloat` | Array of turbine power setpoints (in Watts), dimensions `(n_findex, n_turbines)` |\n", + "| `awc_modes` | `NDArrayStr` | Array of strings specifying the AWC mode for each turbine, dimensions `(n_findex, n_turbines)` |\n", + "| `awc_amplitudes` | `NDArrayFloat` | Array of AWC amplitudes (in degrees) for each turbine, dimensions `(n_findex, n_turbines)` |\n", + "| `tilt_interp` | `interpolator` | Scipy 1D interpolator to find the (floating) tilt angle as a function of wind speed |\n", + "| `average_method` | `string` | Averaging method for combining velocities over the turbine grid points |\n", + "| `cubature_weights` | `NDArrayFloat` | Weights for cubature grid computation of rotor-effective velocity, dimensions `(1, n_grid x n_grid)`|\n", + "| `correct_cp_ct_for_tilt` | `NDArrayInt` | Flag for correcting power and thrust curves to account for platform tilt, dimensions `(n_findex, n_turbines)` |\n", + "| `**_` | -- | Catch-all for unused arguments |\n", + "\n", + "Not all of these arguments must be used or defined as arguments by the user, as long as the final argument be `**_` to allow for unused arguments.\n", + "\n", + "Each of the fundamental methods must return an array of floats (`NDArrayFloat`) with dimensions `(n_findex, n_turbines)`, representing the compute power, thrust coefficient, or axial induction factor for each turbine at each flow condition index." + ] + }, + { + "cell_type": "markdown", + "id": "aefe4f59", + "metadata": {}, + "source": [ + "### Static example\n", + "\n", + "We begin with a very simple example that will produce a constant power, thrust coefficient, and axial induction factor regardless of the inputs. We are using a static class for this example; this class does not need to be instantiated and has no attributes." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d751aa3c", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from attrs import define, field\n", + "from floris.type_dec import floris_float_type, NDArrayFloat\n", + "from floris.core.turbine.operation_models import BaseOperationModel\n", + "\n", + "@define\n", + "class ConstantValueTurbine(BaseOperationModel):\n", + " \"\"\"\n", + " A simple turbine operation model that returns constant values for power,\n", + " thrust coefficient, and axial induction factor regardless of input conditions.\n", + " \"\"\"\n", + " @staticmethod\n", + " def power(\n", + " velocities: NDArrayFloat,\n", + " **_\n", + " ) -> NDArrayFloat:\n", + " # Constant power of 500 kW, in correct shape (n_findex, n_turbines)\n", + " return 500000.0 * np.ones(velocities.shape[0:2], dtype=floris_float_type)\n", + "\n", + " @staticmethod\n", + " def thrust_coefficient(\n", + " velocities: NDArrayFloat,\n", + " **_\n", + " ) -> NDArrayFloat:\n", + " # Return thrust coefficient based on actuator disk theory\n", + " # Because the class is static, we can call the axial_induction method directly\n", + " a = ConstantValueTurbine.axial_induction(velocities)\n", + " return 4 * a * (1 - a)\n", + "\n", + " @staticmethod\n", + " def axial_induction(\n", + " velocities: NDArrayFloat,\n", + " **_\n", + " ) -> NDArrayFloat:\n", + " # Constant axial induction factor of 0.3\n", + " return 0.3 * np.ones(velocities.shape[0:2], dtype=floris_float_type)" + ] + }, + { + "cell_type": "markdown", + "id": "29ea6830", + "metadata": {}, + "source": [ + "Let's now use this constant operation model in FLORIS." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b74802c2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Powers [W]:\n", + " [[500000. 500000.]\n", + " [500000. 500000.]\n", + " [500000. 500000.]] \n", + "\n", + "Thrust coefficients [-]:\n", + " [[0.84 0.84]\n", + " [0.84 0.84]\n", + " [0.84 0.84]] \n", + "\n", + "Axial induction factors [-]:\n", + " [[0.3 0.3]\n", + " [0.3 0.3]\n", + " [0.3 0.3]] \n", + "\n" + ] + } + ], + "source": [ + "from floris import FlorisModel, TimeSeries\n", + "\n", + "fmodel = FlorisModel(\"defaults\")\n", + "time_series = TimeSeries(\n", + " wind_directions=np.array([270.0, 270.0, 280.0]),\n", + " wind_speeds=np.array([8.0, 10.0, 12.0]),\n", + " turbulence_intensities=np.array([0.06, 0.06, 0.06]),\n", + ")\n", + "fmodel.set(\n", + " layout_x = [0.0, 500.0],\n", + " layout_y = [0.0, 0.0],\n", + " wind_data=time_series,\n", + ")\n", + "fmodel.set_operation_model(ConstantValueTurbine)\n", + "\n", + "fmodel.run()\n", + "\n", + "print(\"Powers [W]:\\n\", fmodel.get_turbine_powers(), \"\\n\")\n", + "print(\"Thrust coefficients [-]:\\n\", fmodel.get_turbine_thrust_coefficients(), \"\\n\")\n", + "print(\"Axial induction factors [-]:\\n\", fmodel.get_turbine_axial_induction_factors(), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "aa2b73b0", + "metadata": {}, + "source": [ + "## Dynamic example\n", + "\n", + "Now, we will create an operation model that allows the user to set attributes at instantiation. In this example, we will create an operation model that allows the user to set constant power, thrust coefficient, and axial induction factor values at instantiation. These values will then be returned by the fundamental methods regardless of the inputs. We use the `attrs` package to define attributes of the class." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a7dbaf71", + "metadata": {}, + "outputs": [], + "source": [ + "@define\n", + "class DynamicValueTurbine(BaseOperationModel):\n", + " \"\"\"\n", + " A simple turbine operation model that returns constant values for power,\n", + " thrust coefficient, and axial induction factor regardless of input conditions,\n", + " based on user-defined attributes.\n", + " \"\"\"\n", + " power_value = field(init=True, default=600000.0, type=floris_float_type)\n", + " axial_induction_value = field(init=True, default=0.2, type=floris_float_type)\n", + " def power(\n", + " self,\n", + " velocities: NDArrayFloat,\n", + " **_\n", + " ) -> NDArrayFloat:\n", + " # Constant power of 500 kW, in correct shape (n_findex, n_turbines)\n", + " return self.power_value * np.ones(velocities.shape[0:2], dtype=floris_float_type)\n", + "\n", + " def thrust_coefficient(\n", + " self,\n", + " velocities: NDArrayFloat,\n", + " **_\n", + " ) -> NDArrayFloat:\n", + " # Return thrust coefficient based on actuator disk theory\n", + " # Because the class is static, we can call the axial_induction method directly\n", + " a = self.axial_induction(velocities)\n", + " return 4 * a * (1 - a)\n", + "\n", + " def axial_induction(\n", + " self,\n", + " velocities: NDArrayFloat,\n", + " **_\n", + " ) -> NDArrayFloat:\n", + " # Constant axial induction factor of 0.3\n", + " return self.axial_induction_value * np.ones(velocities.shape[0:2], dtype=floris_float_type)" + ] + }, + { + "cell_type": "markdown", + "id": "f9ef3c9a", + "metadata": {}, + "source": [ + "To use this class, we must first instantiate it. If we instantiate it without any arguments, the default values will be used. Otherwise, we can pass in our desired constant values." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "93f1e99f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Powers [W]:\n", + " [[600000. 600000.]\n", + " [600000. 600000.]\n", + " [600000. 600000.]] \n", + "\n", + "Thrust coefficients [-]:\n", + " [[0.64 0.64]\n", + " [0.64 0.64]\n", + " [0.64 0.64]] \n", + "\n", + "Axial induction factors [-]:\n", + " [[0.2 0.2]\n", + " [0.2 0.2]\n", + " [0.2 0.2]] \n", + "\n" + ] + } + ], + "source": [ + "turbine_operation_model = DynamicValueTurbine()\n", + "fmodel.set_operation_model(turbine_operation_model)\n", + "fmodel.run()\n", + "\n", + "print(\"Powers [W]:\\n\", fmodel.get_turbine_powers(), \"\\n\")\n", + "print(\"Thrust coefficients [-]:\\n\", fmodel.get_turbine_thrust_coefficients(), \"\\n\")\n", + "print(\"Axial induction factors [-]:\\n\", fmodel.get_turbine_axial_induction_factors(), \"\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "eeb38ea7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Powers [W]:\n", + " [[750000. 750000.]\n", + " [750000. 750000.]\n", + " [750000. 750000.]] \n", + "\n", + "Thrust coefficients [-]:\n", + " [[0.75 0.75]\n", + " [0.75 0.75]\n", + " [0.75 0.75]] \n", + "\n", + "Axial induction factors [-]:\n", + " [[0.25 0.25]\n", + " [0.25 0.25]\n", + " [0.25 0.25]] \n", + "\n" + ] + } + ], + "source": [ + "turbine_operation_model = DynamicValueTurbine(power_value=750000.0, axial_induction_value=0.25)\n", + "fmodel.set_operation_model(turbine_operation_model)\n", + "fmodel.run()\n", + "\n", + "print(\"Powers [W]:\\n\", fmodel.get_turbine_powers(), \"\\n\")\n", + "print(\"Thrust coefficients [-]:\\n\", fmodel.get_turbine_thrust_coefficients(), \"\\n\")\n", + "print(\"Axial induction factors [-]:\\n\", fmodel.get_turbine_axial_induction_factors(), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "08be8e42", + "metadata": {}, + "source": [ + "## More complex example\n", + "\n", + "Now, let's use an example where some parameters are defined on the `power_thrust_table` on the turbine input yaml, and some parameters are set upon instantiation of the class." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "38731e16", + "metadata": {}, + "outputs": [], + "source": [ + "from floris.core.turbine import SimpleTurbine\n", + "\n", + "@define\n", + "class ScaledTurbine(BaseOperationModel):\n", + " \"\"\"\n", + " A turbine operation model that scales power and thrust coefficient\n", + " based on a user-defined scaling factor. This will use methods from the\n", + " prepackaged SimpleTurbine model, leaving some values as default.\n", + "\n", + " Scaling only applies to power, not to thrust_coefficient or\n", + " axial_induction. We also demonstrate that other \"nonfundamental\" methods\n", + " can be used on the class.\n", + " \"\"\"\n", + " scaling_factor = field(init=True, default=1.0, type=floris_float_type)\n", + "\n", + " def power(\n", + " self,\n", + " power_thrust_table: dict,\n", + " velocities: NDArrayFloat,\n", + " air_density: float,\n", + " **_\n", + " ) -> NDArrayFloat:\n", + " unscaled_power = SimpleTurbine.power(\n", + " power_thrust_table=power_thrust_table,\n", + " velocities=velocities,\n", + " air_density=air_density,\n", + " )\n", + " scaled_power = self._compute_scaled_power(unscaled_power)\n", + " return scaled_power\n", + "\n", + " def _compute_scaled_power(self, power: NDArrayFloat) -> NDArrayFloat:\n", + " return self.scaling_factor * power\n", + "\n", + " def thrust_coefficient(\n", + " self,\n", + " power_thrust_table: dict,\n", + " velocities: NDArrayFloat,\n", + " **_\n", + " ) -> NDArrayFloat:\n", + " unscaled_thrust_coefficient = SimpleTurbine.thrust_coefficient(\n", + " power_thrust_table=power_thrust_table,\n", + " velocities=velocities,\n", + " )\n", + " return unscaled_thrust_coefficient\n", + "\n", + " def axial_induction(\n", + " self,\n", + " power_thrust_table: dict,\n", + " velocities: NDArrayFloat,\n", + " **_\n", + " ) -> NDArrayFloat:\n", + " unscaled_axial_induction = SimpleTurbine.axial_induction(\n", + " power_thrust_table=power_thrust_table,\n", + " velocities=velocities,\n", + " )\n", + " return unscaled_axial_induction" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "72ce900e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Unscaled Powers [W]:\n", + " [[1753954.45917917 354990.76412771]\n", + " [3417797.00509157 737171.66537995]\n", + " [5000000. 4927707.14320011]] \n", + "\n", + "ScaledTurbine powers [W]:\n", + " [[2104745.351015 425988.91695325]\n", + " [4101356.40610989 884605.99845594]\n", + " [6000000. 5913248.57184013]] \n", + "\n" + ] + } + ], + "source": [ + "# First, run with the unscaled SimpleTurbine model for comparison\n", + "fmodel.set_operation_model(SimpleTurbine)\n", + "fmodel.run()\n", + "initial_powers = fmodel.get_turbine_powers()\n", + "print(\"Unscaled Powers [W]:\\n\", initial_powers, \"\\n\")\n", + "\n", + "# Then, run with the scaled model\n", + "fmodel.set_operation_model(ScaledTurbine(scaling_factor=1.2))\n", + "fmodel.run()\n", + "\n", + "print(\"ScaledTurbine powers [W]:\\n\", fmodel.get_turbine_powers(), \"\\n\")" + ] + }, + { + "cell_type": "markdown", + "id": "0f3409fc", + "metadata": {}, + "source": [ + "## Prepackaged operation models\n", + "\n", + "Naturally, prepackaged operation models can also be used in this way. In fact, we just did that with the `SimpleTurbine` model! Let's take a look at using the `CosineLossTurbine` operation model from FLORIS, either as one of the preset defaults or by passing the class in directly." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1dd9ed49", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Powers under simple model [W]:\n", + " [[1753954.45917917 354990.76412771]\n", + " [3417797.00509157 737171.66537995]\n", + " [5000000. 4927707.14320011]] \n", + "\n", + "Powers under cosine-loss model [W]:\n", + " [[1753954.45917917 313616.60100764]\n", + " [3417797.00509157 660645.73932213]\n", + " [5000000. 4403849.27285162]] \n", + "\n", + "Powers under cosine-loss model (class) [W]:\n", + " [[1753954.45917917 313616.60100764]\n", + " [3417797.00509157 660645.73932213]\n", + " [5000000. 4403849.27285162]] \n", + "\n" + ] + } + ], + "source": [ + "from floris.core.turbine import CosineLossTurbine\n", + "\n", + "fmodel.set_operation_model(\"simple\")\n", + "fmodel.set(\n", + " yaw_angles=np.array([[0.0, 20.0], [0.0, 20.0], [0.0, 20.0]]),\n", + ")\n", + "fmodel.run()\n", + "\n", + "# Simple model does not respond to yaw angles, so powers are unaffected\n", + "print(\"Powers under simple model [W]:\\n\", fmodel.get_turbine_powers(), \"\\n\")\n", + "\n", + "# Now, switch to the cosine loss model as a built-in option\n", + "fmodel.set_operation_model(\"cosine-loss\")\n", + "fmodel.run()\n", + "\n", + "print(\"Powers under cosine-loss model [W]:\\n\", fmodel.get_turbine_powers(), \"\\n\")\n", + "\n", + "# Instead, we can pass in the class directly\n", + "fmodel.set_operation_model(CosineLossTurbine)\n", + "fmodel.run()\n", + "\n", + "print(\"Powers under cosine-loss model (class) [W]:\\n\", fmodel.get_turbine_powers(), \"\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "814df049", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "floris", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 3d0a11d0c5145452fa9a590290bfd339f0dedf7b Mon Sep 17 00:00:00 2001 From: misi9170 Date: Tue, 13 Jan 2026 16:19:54 -0700 Subject: [PATCH 4/4] Clear notebook outputs for version control --- docs/user_defined_operation_models.ipynb | 131 +++-------------------- 1 file changed, 13 insertions(+), 118 deletions(-) diff --git a/docs/user_defined_operation_models.ipynb b/docs/user_defined_operation_models.ipynb index d1641b93e..c0a3027a1 100644 --- a/docs/user_defined_operation_models.ipynb +++ b/docs/user_defined_operation_models.ipynb @@ -52,7 +52,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "d751aa3c", "metadata": {}, "outputs": [], @@ -105,32 +105,10 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "b74802c2", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Powers [W]:\n", - " [[500000. 500000.]\n", - " [500000. 500000.]\n", - " [500000. 500000.]] \n", - "\n", - "Thrust coefficients [-]:\n", - " [[0.84 0.84]\n", - " [0.84 0.84]\n", - " [0.84 0.84]] \n", - "\n", - "Axial induction factors [-]:\n", - " [[0.3 0.3]\n", - " [0.3 0.3]\n", - " [0.3 0.3]] \n", - "\n" - ] - } - ], + "outputs": [], "source": [ "from floris import FlorisModel, TimeSeries\n", "\n", @@ -166,7 +144,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "a7dbaf71", "metadata": {}, "outputs": [], @@ -217,32 +195,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "93f1e99f", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Powers [W]:\n", - " [[600000. 600000.]\n", - " [600000. 600000.]\n", - " [600000. 600000.]] \n", - "\n", - "Thrust coefficients [-]:\n", - " [[0.64 0.64]\n", - " [0.64 0.64]\n", - " [0.64 0.64]] \n", - "\n", - "Axial induction factors [-]:\n", - " [[0.2 0.2]\n", - " [0.2 0.2]\n", - " [0.2 0.2]] \n", - "\n" - ] - } - ], + "outputs": [], "source": [ "turbine_operation_model = DynamicValueTurbine()\n", "fmodel.set_operation_model(turbine_operation_model)\n", @@ -255,32 +211,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "eeb38ea7", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Powers [W]:\n", - " [[750000. 750000.]\n", - " [750000. 750000.]\n", - " [750000. 750000.]] \n", - "\n", - "Thrust coefficients [-]:\n", - " [[0.75 0.75]\n", - " [0.75 0.75]\n", - " [0.75 0.75]] \n", - "\n", - "Axial induction factors [-]:\n", - " [[0.25 0.25]\n", - " [0.25 0.25]\n", - " [0.25 0.25]] \n", - "\n" - ] - } - ], + "outputs": [], "source": [ "turbine_operation_model = DynamicValueTurbine(power_value=750000.0, axial_induction_value=0.25)\n", "fmodel.set_operation_model(turbine_operation_model)\n", @@ -303,7 +237,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "38731e16", "metadata": {}, "outputs": [], @@ -368,27 +302,10 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "72ce900e", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Unscaled Powers [W]:\n", - " [[1753954.45917917 354990.76412771]\n", - " [3417797.00509157 737171.66537995]\n", - " [5000000. 4927707.14320011]] \n", - "\n", - "ScaledTurbine powers [W]:\n", - " [[2104745.351015 425988.91695325]\n", - " [4101356.40610989 884605.99845594]\n", - " [6000000. 5913248.57184013]] \n", - "\n" - ] - } - ], + "outputs": [], "source": [ "# First, run with the unscaled SimpleTurbine model for comparison\n", "fmodel.set_operation_model(SimpleTurbine)\n", @@ -415,32 +332,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "1dd9ed49", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Powers under simple model [W]:\n", - " [[1753954.45917917 354990.76412771]\n", - " [3417797.00509157 737171.66537995]\n", - " [5000000. 4927707.14320011]] \n", - "\n", - "Powers under cosine-loss model [W]:\n", - " [[1753954.45917917 313616.60100764]\n", - " [3417797.00509157 660645.73932213]\n", - " [5000000. 4403849.27285162]] \n", - "\n", - "Powers under cosine-loss model (class) [W]:\n", - " [[1753954.45917917 313616.60100764]\n", - " [3417797.00509157 660645.73932213]\n", - " [5000000. 4403849.27285162]] \n", - "\n" - ] - } - ], + "outputs": [], "source": [ "from floris.core.turbine import CosineLossTurbine\n", "\n",