Turbine Controllers
Turbine Controllers
Turbine Controllers
www.orcina.com
Turbine controllers
Introduction
Turbines often rely upon control systems to regulate their speed and the power they
generate. Control systems are supported in an OrcaFlex turbine model through external
functions.
This document introduces the types of turbine control system that can be modelled and
discusses several example external functions written in Python. A more general
discussion concerning the modelling of wind turbines can be found on the website:
https://www.orcina.com/SoftwareProducts/OrcaFlex/Examples/
Note, these examples are written to work with OrcaFlex v11.0a and later.
External functions
External functions, and the API, are documented in detail in the OrcFxAPI reference
documentation, which can be found on the website:
https://www.orcina.com/SoftwareProducts/OrcaFlex/Documentation/OrcFxAPIHelp/
A general guide to using external functions, with a non-turbine specific example set, is
also available through the website:
https://www.orcina.com/Support/
The below guide assumes your setup meets the minimum requirements to run a Python
external function and you have a basic knowledge of working with them. If this is not the
case, then you might like to review the above resources.
Turbine controllers
Note the sign convention for blade pitch, for which a positive value defines an
anticlockwise rotation about the blade z-axis, looking from root to tip, is contrary to the
usual OrcaFlex convention.
OrcaFlex supports two Pitch control modes: Common and Individual. When operating
in common mode, all blades share the same pitch value. In Individual mode, each blade
has a unique pitch value.
To control the pitch degree of freedom, an external function is specified as the Pitch
controller on the Blades page of the Turbine data form.
Common control mode
In common mode, each call to the specified external function should return the pitch
value in radians, and its first two derivatives, that are to be used for all blades.
For a Python external function, this is done by assigning the pitch motion to the
info.StructValue attribute in the external function’s Calculate method, for
example:
def Calculate(self, info):
if info.NewTimeStep:
info.StructValue.Value = pitch
info.StructValue.Velocity = pitchDot
info.StructValue.Acceleration = pitchDotDot
Turbine controllers
On the Blades page of the Turbine data form, the external function name is specified as
the Pitch controller and Common is selected as the Pitch control mode.
Turbine controllers
Open RampPitch.py in a text editor, it contains the Python class which defines our
external function. Because this is a very simple example, it only includes the Calculate
method. This method is implemented to: check if it is being called at the beginning of a
time step using info.NewTimeStep; calculate the pitch value, and its first two
derivatives, assuming the form of a logistic curve; and return these values in the
info.StructValue attribute. The logistic curve has the form of an "S" shape, which can
be used as a simple ramping function, with equation:
𝑓𝑚𝑎𝑥
𝑓(𝑡) =
1 + 𝑒 −(𝑡−𝑡0 )
Back in OrcaFlex, run the simulation and open the associated default workspace
(Workspace>Use file default). As expected, the turbine’s main shaft angular velocity initially
increases until the controller pitches the blade sufficiently to slow the rotor down.
Turbine controllers
Open RampTorque.py in a text editor, it contains the Python class which defines our
external function. Again, it only includes the Calculate method. This method is
Turbine controllers
implemented to: check if it is being called at the beginning of a time step; calculate the
torque value assuming the form of a logistic curve; and return this value in the
info.Value attribute.
Note in this example, we have used the OrcaFlexObject.UnitsConversionFactor
method to ensure the torque is returned in the unit system consistent with the model.
Multiplying by this conversion factor converts a value from SI units into model units. The
possible values for the base unit passed into it are detailed in the OrcFxAPI
documentation.
Back in OrcaFlex, run the simulation and open the associated default workspace. As
expected the generator torque is smoothly ramped up to 100 kN.m and the rotor
accelerates.
Gearbox
Generator rotor
Hub
z
Iz, Gen
The controller’s torque is applied to the generator rotor using the standard OrcaFlex
convention, i.e. clockwise when looking along the axis of rotation (or when viewing the
turbine face-on). As is depicted above, the controller’s torque is applied before the
generator’s rotational inertia, i.e. if the system is accelerating, and the generator’s
rotational inertia is non-zero, then the high speed shaft torque will not equal the applied
torque.
If the gear ratio is positive, the main shaft will spin in the same direction as the generator.
In this example the gear ratio is positive. So to make the rotor spin in the conventional
clockwise turbine direction, the controller is setup to apply a positive torque, i.e. to put
energy into the system. In a more realistic turbine model, if the gear ratio was also
positive, the generator torque controller would apply a negative torque and take energy
out of the system.
Turbine controllers
Turbine controllers
Python controller
This is a more realistic example of a conventional variable-speed, variable blade-pitch-to-
feather controller, supported purely with a Python module. Contained within
PythonController.py is a Python implementation of the NREL OC3 baseline controller
(J. Jonkman, 2009), with the flexibility to optionally apply the OC3 Phase IV modifications
(Jonkman, 2010).
The 5MWBaseline.dat OrcaFlex data file is an example of the NREL OC3 5-MW baseline
wind turbine in the fixed bottom configuration (J. Jonkman, 2009). This model uses the
PythonController.py Python module to provide the generator torque and pitch
control.
OrcaFlex model
Open 5MWBaseline.dat in OrcaFlex. The model has an external function, Controller
It specifies PythonController.py as the Python module containing the class that
defines it. From this module, the PythonController Python class is selected. The NREL-
5MW turbine object data sets this external function to provide the generator torque and
pitch control. Setting the external function and turbine data is discussed in the previous
sections.
To include this controller in a turbine model, the above is the minimum data that would
need to be set. However, unless user specified object tags are optionally included, which
are discussed below, the controller will, by default, not apply OC3 Phase IV modifications.
Nor will the actuator model be used. If you are modelling a floating system, the OC3 Phase
IV modifications are appropriate and the FloatingSystem object tag should be given.
Run the simulation and open the associated default workspace. The turbine is subjected
to an incident wind that increases from zero to a constant value of 13 m/s over the build-
up period. This wind speed is above the rated wind speed for this turbine and the pitch
control system is thus required to maintain the rated rotor speed of 12.1 rpm (1.267
rad/s). Initially the rotor accelerates, exceeding the rated rotor speed, and the controllers
then operate to correct it back to the rated value.
Note: the achieved generator power must be scaled by the turbine efficiency (94.4%) to
recover the rated 5MW.
Turbine controllers
Here neither the OC3 Phase IV modifications nor the actuator model are enabled.
Python module
The PythonController.py module contains the class that defines both the pitch and
generator torque control. If you open it in a text editor, the PytonController class can
be found at the bottom of the module. The Calculate method of the this class is
responsible for calling the update method of the ControllerEngine class. Depending
on the controller type, it returns the associated control values through the correct info
attributes.
The ControllerEngine class contains the implementation of the NREL controllers. Its
update method is responsible for updating state and calculating all the controller values
once per time step. The NREL OC3 baseline torque and pitch controllers share state and
so their control values are calculated in a single instance of the ControllerEngine class,
which is stored in their controller attributes.
The single ControllerEngine instance is shared between the PitchController and
TorqueController instances using the info.Workspace attribute. This is a Python
dictionary that is shared model wide. They access it using a key which is unique to the
calling turbine object. This is done in the Initialise method of the parent class:
def Initialise(self, info):
key = info.ModelObject.handle.value
controller = info.Workspace.get(key, None)
Turbine controllers
if controller is None:
controller = ControllerEngine(info)
info.Workspace[key] = controller
self.controller = controller
Turbine controllers
Turbine controllers
0.0, 5.0]. It represents a position vector, expressed relative and with respect to
the turbine frame. If not given, the turbine origin is used.
If the object tags are not specified, those that are interpreted as a Boolean are treated as
being False.
Unlike the pure Python controller, the OC3 Phase IV modifications cannot be enabled by
specifying an object tag. Instead, you must tell the external function to target a DLL which
has been compiled from a source in which the modifications have already been made. To
change this example to use the modifications, the ControllerDLL tag can be set to
DISCON_OC3Hywind.dll, depending on the bitness of your OrcaFlex.
In this example, InputFile is not used.
Python module
The Python controller example includes the BladedControllerWrapper.py module,
which contains the BladedController class that defines both the pitch and generator
torque control. The pitch and generator torque controllers will each have separate
instances of this class. The behaviour switches on info.DataName. Their controller
attribute contain an instance of the Controller class.
The Controller class contains the implementation of the wrapper. Its update method
is responsible for calling the DLL, using the callDLL method, once per time step. When
called, the DLL updates its state and calculates all the controller values.
The avrSwap swap array is used to communicate between the Python module and the
DLL. Before the DLL is called, the appropriate elements in the swap array are populated
with the values that are to be input to the calculation. This is then passed into the DLL
when it is called. After it has been called, the DLL will have calculated the control values
which are then read out of the swap array and returned through the external function.
Swap array
In this example, the avrSwap swap array is used to communicate with the DLL. The NREL
DLL is written in the Bladed-style as documented in Appendix A of the Bladed User's
Guide Version 3.51. The following elements are set by the wrapper before the DLL is
called:
• Record 1: flag set to: 0 at first call; 1 for subsequent calls; -1 on final call
• Record 2: time since simulation started (s)
• Record 3: model time step (2)
• Record 4: current blade 1 pitch angle (rad)
• Record 15: generator power (W)
• Record 20: current generator angular velocity (rad/s)
• Record 21: current main shaft angular velocity (rad/s)
• Record 23: generator torque (Nm)*
• Record 24: yaw error (rad)* *
Turbine controllers
Turbine controllers
The disadvantage is the source code is harder to read, edit, and needs to be complied
before it can be used. The source code is distributed with the example in the scr folder.
To help compile the DLL, the build folder contains both a Microsoft Visual Studio project
and a MSYS2 makefile.
Capabilities and Limitations
Unlike the pure Python controller, the wrapper example does not capture state when the
model is saved. This means if you continue, extend or restart a simulation file, it will do
so with fresh controller state.
The external function uses the OrcaFlexObject.UnitsConversionFactor method to
ensure the control values are returned consistently with the model unit system.
The NREL OC3 baseline controller expects the initial blade pitch to be zero. As such, the
external function has the same requirements.
The external function is not written to work with the implicit variable time step integration
scheme.
The wrapper is written to support the OC3 DLLs compiled by NREL. Other DLLs, written
to a consistent specification, might need extra elements of the swap array to be
populated or read from.
The wrapper is intended to support a Bladed-style DLL as documented in Appendix A of
the Bladed User's Guide Version 3.51. It will need to be modified to support later
specifications.
It is safe for multi-threading and models using it can be run in batch. However, the
DLLCanBeShared object tag must be set correctly. If DLLCanBeShared is False then for
each model in the batch, a distinct copy of the DLL, with a unique name, is created which
means that each of those DLLs have their own private global variables.
The bitness of the target DLL must match that of your OrcaFlex.
Using the Bladed controller wrapper with the NREL ROSCO control DLL
The external function in this example can be used as a wrapper for the NREL ROSCO
control DLL.
https://github.com/nrel/rosco
To use it, the InputFile object tag should now be specified. The InputFile tag should
give the path, relative to the model directory, of the DLL’s input file.
Please note, the wrapper populates the swap array sufficiently to support the control
modes used in the examples available on the Orcina website. If other controller modes
are required, the wrapper might need to be further extended. The control modes, which
are enabled in the controller input file, known not to be supported include: Flp_Mode
and OL_Mode.
Turbine controllers
If this script is run from the command line, then you should recover an error message
detailing the issue and allowing you to resolve it.
Bibliography
J. Jonkman, S. B. (2009). Definition of a 5-MW Reference Wind Turbine for Offshore System
Development .
Jonkman, J. (2010). Definition of the Floating System for Phase IV of OC3.