Rotor interaction

[1]:
# install h2lib if not present
try:
    import h2lib
except ModuleNotFoundError:
    !pip install h2lib[test]

Import modules needed for this tutorial

[2]:
from h2lib import H2Lib
from h2lib_tests import tfp
import matplotlib.pyplot as plt
import numpy as np
from tqdm.notebook import tqdm
from IPython import display
model_path = tfp + 'DTU_10_MW/'
htc_path = 'htc/DTU_10MW_RWT.htc'

Diameter, position and orientation

[3]:
with H2Lib() as h2:
    h2.init(htc_path, model_path)
    D = h2.get_diameter()
    rotor_pos = h2.get_rotor_position()
    rotor_ori = h2.get_rotor_orientation()
    aerosection_pos = h2.get_aerosections_position()
[4]:
ax1, ax2 = plt.subplots(1,2, figsize=(12,6))[1]
x,y,z = rotor_pos
yaw, tilt, azi = rotor_ori

rotor = plt.Circle((x,-z), D/2,  fill=False)
ax1.plot(x,-z,'.k')
ax1.add_patch(rotor)

ax2.plot(y,-z,'.k')
ax2.plot([y-np.sin(tilt)*D/2, y+np.sin(tilt)*D/2], [-z-np.cos(tilt)*D/2, -z+np.cos(tilt)*D/2],'k')


for b in range(3):
    ax1.plot(aerosection_pos[b,:,0], -aerosection_pos[b,:,2],'.',ms=3, label=f'Blade {b}')
    ax2.plot(aerosection_pos[b,:,1], -aerosection_pos[b,:,2], label=f'Blade {b}')

ax1.axis('equal')
ax1.legend()
ax1.set_xlabel('x [m]')
ax2.set_ylabel('-z [m]')
ax2.axis('equal')
ax2.legend()
ax2.set_xlabel('y [m]')
ax2.set_ylabel('-z [m]')
[4]:
Text(0, 0.5, '-z [m]')
_images/RotorInteraction_6_1.png

DWM workflow

In a DWM (Dynamic wake meandering) workflow the wind speed including wakes but without induction is passed to HAWC2. Hawc2 calculates the induction which is passed back to the DWM which uses it to calculate the wakes.

[5]:
Nxyz = (64,32,32)
class DWM():
    def __init__(self):
        self.time = 0

    def get_windfield(self):
        return np.random.random((3,) + Nxyz) # 3 components

    def set_induction(self, induction):
        pass

    def step(self):
        self.time += .2
        return self.time


dwm = DWM()

with H2Lib() as h2:
    h2.init(htc_path, model_path)
    h2.init_windfield(Nxyz=Nxyz, dxyz=(1,1,1), box_offset_yz=(-16,0), transport_speed=10)

    for _ in tqdm(range(10)):
        t = dwm.step()
        h2.run(t)
        uvw = dwm.get_windfield()
        h2.set_windfield(uvw, box_offset_x=-60)
        induc = h2.get_induction_axisymmetric()
        # induc = h2.get_induction_polargrid() # alternative if the full induction grid is needed
        dwm.set_induction(induc)

Actuator line workflow

[6]:
class Flow():
    def __init__(self):
        self.time = 0

    def get_uvw(self, pos_xyz_lst):
        uvw = np.array(pos_xyz_lst) * 0
        uvw[:, :, 0] = 6
        return uvw.tolist()

    def step(self):
        self.time += .2
        return self.time

    def set_fxyz(self, pos_xyz, fxyz):
        pass


flow = Flow()
with H2Lib() as h2:
    h2.init(htc_path, model_path)
    for _ in tqdm(range(10)):
        t = flow.step()
        h2.run(t)
        pos_gl_xyz = h2.get_aerosections_position()
        uvw = flow.get_uvw(pos_gl_xyz)
        h2.set_aerosections_windspeed(uvw)
        frc_gl_xyz = h2.get_aerosections_forces()
        flow.set_fxyz(pos_gl_xyz, frc_gl_xyz)

Fully resolved blade and Miras workflow

image.png

These workflows have not been reimplemented in h2lib yet, and h2lib therefore still contains the old cpl routines.

To use these routines, the pyhawc2 package may be needed needed. This package has been updated to use the dll/so from h2lib.

Install via:

pip install pyhawc2 --index-url https://gitlab.windenergy.dtu.dk/api/v4/projects/796/packages/pypi/simple

Python controller workflow

In this example we will setup a workflow there the pitch demand value of blade 1 is updated from python with a sinusodial value.

Load htc file and find the sensor in htc.dll.type2_dll.output

[7]:
from wetb.hawc2.htc_file import HTCFile
htc = HTCFile(model_path + htc_path)
servo_with_limits = htc.dll.get_subsection_by_name('servo_with_limits')
pitch1_sensor = servo_with_limits.output.sensors[1]

Modify pitch1 output from HAWC2 to servo_with_limits dll to use value of general variable with index 1 (initial value=0) instead of value from DTU controller

[8]:
pitch1_sensor.type = "general"
pitch1_sensor.sensor = "variable"
pitch1_sensor.values = [1, 0]
pitch1_sensor.comments = ""
print ('Pitch sensor after:', pitch1_sensor)
Pitch sensor after: general variable    1 0;

Print full dll section, note sensor two in output

[9]:
print (servo_with_limits)
begin type2_dll;
  name  servo_with_limits;
  filename      ./control/servo_with_limits.dll;
  dll_subroutine_init   init_servo_with_limits;
  dll_subroutine_update update_servo_with_limits;
  arraysizes_init       100 1;
  arraysizes_update     100 100;
  begin init;
    constant    1 3;    Number of blades [-]
    constant    2 1;    Frequency of 2nd order servo model of pitch system [Hz]
    constant    3 0.7;  Damping ratio 2nd order servo model of pitch system [-]
    constant    4 10;   Max. pitch speed [deg/s]
    constant    5 15;   Max. pitch acceleration [deg/s^2]
    constant    6 -5;   Min. pitch angle [deg]
    constant    7 90;   Max. pitch angle [deg]
    constant    8 -1;   Time for pitch runaway [s]
    constant    9 -1;   Time for stuck blade 1 [s]
    constant    10 0;   Angle of stuck blade 1 [deg] (if > 90 deg then blade is stuck at instantaneous angle)
  end init;
  begin output;
    general time;       Time                         [s]
    general variable    1 0;
    dll inpvec  1 3;    Pitch2 demand angle          [rad]
    dll inpvec  1 4;    Pitch3 demand angle          [rad]
    dll inpvec  1 26;   Flag for emergency pitch stop         [0=off/1=on]
  end output;
;
  begin actions;
    constraint  bearing2 angle pitch1;  Angle pitch1 bearing    [rad]
    constraint  bearing2 angle pitch2;  Angle pitch2 bearing    [rad]
    constraint  bearing2 angle pitch3;  Angle pitch3 bearing    [rad]
  end actions;
end type2_dll;
;
;       --- DLL for tower-blade tip distance -- ;
;-------------------------------------------

rename htc and save

[10]:
htc.set_name('python_pitch_control')
htc.save()
[11]:
with H2Lib() as h2:
    h2.init('htc/python_pitch_control.htc', model_path)

    # Add sensors
    h2.add_sensor('constraint bearing1 shaft_rot 1')  # rotor angle and speed
    h2.add_sensor('General variable 1 0 # $name(P1_demand) $unit(rad)')  # variable used to control the pitch servo
    h2.add_sensor('constraint bearing2 pitch1 1')  # angle and angle velocity of blade 1
    h2.add_sensor('constraint bearing2 pitch2 1')   # angle and angle velocity of blade 2

    # plot legends
    h2.step()
    c_lst = []
    sensor_idx = [1,3,4,6]
    for i in sensor_idx:
        name, unit, desc = h2.get_sensor_info(i)
        p = plt.plot([],'.', label=f'{name} [{unit}]')
        c_lst.append(p[0].get_color())
    plt.legend()


    for i in tqdm(range(25)):
        t = h2.get_time()
        # update pitch demand of blade 1
        h2.set_variable_sensor_value(1, np.sin(t)/10)

        h2.run(i/5)

        # plot sensors
        for value, c in zip(h2.get_sensor_values(sensor_idx), c_lst):
            plt.plot(t, value , '.', color=c)
        display.clear_output(wait=True)
        display.display(plt.gcf())
_images/RotorInteraction_21_0.png
_images/RotorInteraction_21_1.png