diff --git a/AUTHORS.rst b/AUTHORS.rst index 98c249faa4611e1412a91fb63fc5787a6df367d9..f22da4cd27e5f3090d1a9881b00a8ff435871ff9 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -3,4 +3,5 @@ Developers ========== * Mads Mølgaard Pedersen -* David Verelst +* David R.S. Verelst +* Carlo Tibaldi diff --git a/wetb/prepost/GenerateDLCs.py b/wetb/prepost/GenerateDLCs.py new file mode 100644 index 0000000000000000000000000000000000000000..3bcf9bd2d5db7d36c437f551bc6f0994da094196 --- /dev/null +++ b/wetb/prepost/GenerateDLCs.py @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Nov 20 10:11:06 2015 + +@author: tlbl +""" +from __future__ import print_function +from __future__ import division +from __future__ import unicode_literals +from __future__ import absolute_import + +# arctan and pi are required because they are in the formulas that are +# evaluated +from numpy import floor, arctan, pi +import pandas as pd +import xlrd + + +def multi_for(iterables): + """ + Routine to create list with combination of elements. + """ + if not iterables: + yield () + else: + for item in iterables[0]: + for rest_tuple in multi_for(iterables[1:]): + yield (item,) + rest_tuple + + +class GeneralDLC(object): + """ + Basic class to generate the DLC spreadsheets. It contains routines to + handle the different types of tags. + + * Constants: are fixed in the current DLC, e.g. reference turbulence\ + intensity, rotor radius, reference wind speed, .... + + * Variables: define the number of cases in a DLC through their combination\ + e.g. wind speed, number of turbulence seeds, yaw angle, .... + + + * Functions: depend on other tags e.g turbulence intensity, file name, .... + + """ + + def __init__(self): + + pass + + def remove_from_dict(self, non_defaults, defaults): + + for key in non_defaults.keys(): + try: + del defaults[key] + except: + pass + return defaults + + def add_variables_tag(self, dlc, variables, variables_order): + + cases_len = [] + for tag in variables_order: + dlc[tag] = [] + v = variables[tag] + for i in range(len(v)-1): + try: + v.remove('') + except: + pass + if tag == '[seed]': + cases_len.append(int(v[0])) + else: + cases_len.append(len(v)) + cases_index = multi_for(list(map(range, cases_len))) + + for irow, row in enumerate(cases_index): + counter = floor(irow/len(variables['[wsp]']))+1 + for icol, col in enumerate(row): + if variables_order[icol] == '[seed]': + value = '%4.4i' % (1000*counter + row[variables_order.index('[wsp]')]+1) + else: + value = variables[variables_order[icol]][col] + if not isinstance(value, float) and not isinstance(value, int): + value = str(value) + dlc[variables_order[icol]].append(value) + + def add_constants_tag(self, dlc, constants): + + for key in constants.keys(): + dlc[key] = [constants[key]]*len(dlc['[wsp]']) + + def sort_formulas(self, formulas): + # sort formulas based on their dependency + + keys_list = sorted(formulas) + for i in range(len(keys_list)): + for ikey, key in enumerate(keys_list): + formula = formulas[key] + for ikey2, key2 in enumerate(keys_list): + if key2 in formula: + if ikey < ikey2: + keys_list.pop(ikey) + keys_list.insert(ikey2, key) + break + return keys_list + + def eval_formulas(self, dlc): + + for key in dlc.keys(): + if isinstance(dlc[key][0], str): + if "[" in dlc[key][0]: + for key2 in dlc.keys(): + for iformula, formula in enumerate(dlc[key]): + if key2 in formula: + dlc[key][iformula] = dlc[key][iformula].replace(key2, '%s'%dlc[key2][iformula]) + for iformula, formula in enumerate(dlc[key]): + formula = formula.replace(',', '.') + formula = formula.replace(';', ',') + dlc[key][iformula] = eval(formula) + + def add_formulas(self, dlc, formulas): + + keys_list = self.sort_formulas(formulas) + + for fkey in keys_list: + flist = [] + for i in range(len(dlc['[wsp]'])): + formula = formulas[fkey] + for key in dlc.keys(): + if key in formula: + if formula[0] == '"': + if key == '[wsp]' or key == '[gridgustdelay]': + fmt = '%2.2i' + elif key == '[wdir]' or key == '[G_phi0]': + fmt = '%3.3i' + else: + fmt = '%4.4i' + formula = formula.replace(key, fmt % int(dlc[key][i])) + elif key in formula: + formula = formula.replace(key, '%s' % dlc[key][i]) + formula = formula.replace(',', '.') + formula = formula.replace(';', ',') + flist.append(eval(formula)) + + dlc[fkey] = flist + + +class GenerateDLCCases(GeneralDLC): + """ + Class to generate Excell sheets for each DLB case starting from a single + Excell sheet. + + Parameters + ---------- + + filename: str + Name of the excel spreadsheet containing the definition of all the + cases to generate. + + folder: str + Name of the folder in which to save the DLB cases. + + Example + ------- + DLB = GenerateDLCCases() + DLB.execute() + + + """ + + def execute(self, filename='DLCs.xlsx', folder=''): + + book = xlrd.open_workbook(filename) + + nsheets = book.nsheets + + # Loop through all the sheets. Each sheet correspond to a DLC. + for isheet in range(1, nsheets): + + # Read all the initialization constants and functions in the + # first sheet + general_constants = {} + general_functions = {} + sheet = book.sheets()[0] + for i in range(1, sheet.ncols): + if sheet.cell_value(9, i) != '': + general_constants[str(sheet.cell_value(9, i))] = \ + sheet.cell_value(10, i) + if sheet.cell_value(13, i) != '': + general_functions[str(sheet.cell_value(13, i))] = \ + sheet.cell_value(14, i) + + sheet = book.sheets()[isheet] + + print('Sheet #%i' % isheet, sheet.name) + + # Read the actual sheet. + constants = {} + variables = {} + formulas = {} + variables_order = [] + # Loop through the columns + for i in range(sheet.ncols): + if sheet.cell_value(1, i) is not None: + tag = str(sheet.cell_value(1, i)) + if tag is not '': + if sheet.cell_value(0, i) == 'C': + constants[tag] = sheet.cell_value(2, i) + if sheet.cell_value(0, i) == 'V': + variables_order.append(tag) + variables[tag] = \ + [sheet.cell_value(j, i) for j in range(2, sheet.nrows)] + if sheet.cell_value(0, i) == 'F': + formulas[tag] = str(sheet.cell_value(2, i)) + + dlc = {} + + general_constants = self.remove_from_dict(variables, + general_constants) + general_constants = self.remove_from_dict(constants, + general_constants) + general_functions = self.remove_from_dict(formulas, + general_functions) + + self.add_variables_tag(dlc, variables, variables_order) + self.add_constants_tag(dlc, general_constants) + self.add_constants_tag(dlc, constants) + self.add_formulas(dlc, formulas) + self.add_formulas(dlc, general_functions) + self.eval_formulas(dlc) + df = pd.DataFrame(dlc) + df.to_excel(folder+sheet.name+'.xls', index=False) + + +class RunTest(): + """ + Class to perform basic testing of the GenerateDLCCases class. It writes the + spreadsheets and compare them with a reference set. + """ + def execute(self): + + from pandas.util.testing import assert_frame_equal + a = GenerateDLCCases() + a.execute() + + book = xlrd.open_workbook('DLCs.xlsx') + nsheets = book.nsheets + for isheet in range(1, nsheets): + sheet = book.sheets()[isheet] + print('Sheet #%i' % isheet, sheet.name) + book1 = pd.read_excel('Reference/'+sheet.name+'.xlsx') + + book2 = pd.read_excel(sheet.name+'.xls') + + book2 = book2[book1.columns] + assert_frame_equal(book1, book2, check_dtype=False) + +if __name__ == '__main__': + DLB = GenerateDLCCases() + DLB.execute() + pass diff --git a/wetb/prepost/dlcdefs.py b/wetb/prepost/dlcdefs.py index 687cc4ee90de57aaa11dc825539a21009930b1a1..ce8dbc144de91964c096119bf9f4e63159aa1574 100644 --- a/wetb/prepost/dlcdefs.py +++ b/wetb/prepost/dlcdefs.py @@ -320,8 +320,10 @@ def excel_stabcon(proot, fext='xlsx', pignore=None, sheet=0, tags_dict['[res_dir]'] = 'res/%s/' % dlc_case tags_dict['[log_dir]'] = 'logfiles/%s/' % dlc_case tags_dict['[htc_dir]'] = 'htc/%s/' % dlc_case - tags_dict['[case_id]'] = tags_dict['[Case id.]'] - tags_dict['[time_stop]'] = tags_dict['[time stop]'] + if '[Case id.]' in tags_dict.keys(): + tags_dict['[case_id]'] = tags_dict['[Case id.]'] + if '[time stop]' in tags_dict.keys(): + tags_dict['[time_stop]'] = tags_dict['[time stop]'] try: tags_dict['[turb_base_name]'] = tags_dict['[Turb base name]'] except KeyError: diff --git a/wetb/prepost/h2_vs_hs2.py b/wetb/prepost/h2_vs_hs2.py index f0d1f1498fe0952002460d9ccfb813d2464320bb..5dcc64cff5759a0958a0228027b283840f5617bc 100644 --- a/wetb/prepost/h2_vs_hs2.py +++ b/wetb/prepost/h2_vs_hs2.py @@ -838,13 +838,20 @@ class Plots(object): subplots = mplutils.subplots fig, axes = subplots(nrows=nrows, ncols=ncols, dpi=dpi, figsize=size) - axes = axes.ravel() + if isinstance(axes, np.ndarray): + axes = axes.ravel() + else: + axes = [axes] if title is not None: fig.suptitle(title) return fig, axes def set_axes_label_grid(self, axes, setlegend=False): - for ax in axes.ravel(): + + if isinstance(axes, np.ndarray): + axes = axes.ravel() + + for ax in axes: if setlegend: leg = ax.legend(loc='best') if leg is not None: @@ -861,7 +868,7 @@ class Plots(object): def distribution(self, results, labels, title, channels, x_ax='pos_z', xlabel='Z-coordinate [m]', nrows=2, ncols=4, size=(16, 5), - i0=1): + i0=1, iplot_legend=0, legloc='best'): """ Compare blade distribution results """ @@ -874,7 +881,10 @@ class Plots(object): radius2 = res2[x_ax].values fig, axes = self.new_fig(title=title, nrows=nrows, ncols=ncols, size=size) - axesflat = axes.flatten() + if isinstance(axes, np.ndarray): + axesflat = axes.ravel() + else: + axesflat = axes for i, chan in enumerate(channels): ax = axesflat[i] ax.plot(radius1, res1[chan].values, color=self.h2c, @@ -919,11 +929,11 @@ class Plots(object): # if err.max() > 50: # axr.set_ylim([0, 35]) - # use axr for the legend, but only for the first plot - if i == 0: + # use axr for the legend, but only for defined plot + if i == iplot_legend: lines = ax.lines + axr.lines labels = [l.get_label() for l in lines] - leg = axr.legend(lines, labels, loc='best') + leg = axr.legend(lines, labels, loc=legloc) leg.get_frame().set_alpha(0.5) # x-label only on the last row @@ -960,7 +970,8 @@ class Plots(object): self.save_fig(fig, axes, fname) def h2_blade_distribution(self, fname_1, fname_2, title, labels, n0=0, - df_stats1=None, df_stats2=None): + df_stats1=None, df_stats2=None, + iplot_legend=0, legloc='best'): """ Compare blade distribution aerodynamics of two HAWC2 cases. """ @@ -975,11 +986,13 @@ class Plots(object): x_ax='pos_z', xlabel='Z-coordinate [m]', nrows=self.dist_nrows, ncols=self.dist_ncols, - size=self.dist_size) + size=self.dist_size, + iplot_legend=iplot_legend, legloc=legloc) return fig, axes - def hs_blade_distribution(self, fname_1, fname_2, title, labels, n0=0): + def hs_blade_distribution(self, fname_1, fname_2, title, labels, n0=0, + iplot_legend=0, legloc='best'): res1 = self.load_hs(fname_1) res2 = self.load_hs(fname_2) @@ -992,12 +1005,14 @@ class Plots(object): x_ax='pos_z', xlabel='Z-coordinate [m]', nrows=self.dist_nrows, ncols=self.dist_ncols, - size=self.dist_size) + size=self.dist_size, + iplot_legend=iplot_legend, legloc=legloc) return fig, axes def blade_distribution(self, fname_h2, fname_hs2, title, n0=0, - h2_df_stats=None, fname_h2_tors=None): + h2_df_stats=None, fname_h2_tors=None, + iplot_legend=0, legloc='best'): """Compare aerodynamics, blade deflections between HAWC2 and HAWCStab2. This is based on HAWCSTab2 *.ind files, and an HAWC2 output_at_time output file. @@ -1029,11 +1044,13 @@ class Plots(object): x_ax='pos_z', xlabel='Z-coordinate [m]', nrows=self.dist_nrows, ncols=self.dist_ncols, - size=self.dist_size) + size=self.dist_size, + iplot_legend=iplot_legend, legloc=legloc) return fig, axes - def blade_distribution2(self, fname_h2, fname_hs2, title, n0=0): + def blade_distribution2(self, fname_h2, fname_hs2, title, n0=0, + iplot_legend=0, legloc='best'): """Compare aerodynamics, blade deflections between HAWC2 and HAWCStab2. This is based on HAWCSTab2 *.ind files, and an HAWC2 output_at_time output file. @@ -1050,7 +1067,8 @@ class Plots(object): fig, axes = self.distribution(res, labels, title, channels, x_ax='pos_z', xlabel='Z-coordinate [m]', - nrows=3, ncols=4, size=(16, 12)) + nrows=3, ncols=4, size=(16, 12), + iplot_legend=iplot_legend, legloc=legloc) return fig, axes @@ -1066,7 +1084,8 @@ class Plots(object): # POWER --------------------------------------------------------------- ax = axes[0] - ax.set_title('Power [kW]') + ax.set_ylabel('Power [kW]') + ax.set_xlabel('Wind speed [m/s]') # HAWC2 keys = ['P_aero', 'P_mech'] lss = [self.h2ls, '--', ':'] @@ -1103,7 +1122,8 @@ class Plots(object): # THRUST -------------------------------------------------------------- ax = axes[1] - ax.set_title('Thrust [kN]') + ax.set_ylabel('Thrust [kN]') + ax.set_xlabel('Wind speed [m/s]') keys = ['T_aero', 'T_shafttip'] lss = [self.h2ls, '--', ':'] # HAWC2 @@ -1125,6 +1145,7 @@ class Plots(object): axr.plot(wind_hs, err, color=self.errc, ls=self.errls, alpha=0.6, label=self.errlab + ' T$_{aero}$') ax.set_xlim([wind_h2.min(), wind_h2.max()]) + ax.set_xlabel('Wind speed [m/s]') # legends lines, labels = ax.get_legend_handles_labels() @@ -1158,7 +1179,8 @@ class Plots(object): yerr2 = res2.pwr_h2_std[key] ax.errorbar(wind2, res2.pwr_h2_mean[key].values, color=self.hsc, yerr=yerr2, marker=self.hsms, ls=self.hsls, label=labels[1], alpha=0.7) - ax.set_title('Power [kW]') + ax.set_ylabel('Power [kW]') + ax.set_xlabel('Wind speed [m/s]') # relative errors on the right axes axr = ax.twinx() assert np.allclose(wind1, wind2) @@ -1191,7 +1213,8 @@ class Plots(object): err = np.abs(1.0 - (qq1 / qq2))*100.0 axr.plot(wind1, err, color=self.errc, ls=self.errls, alpha=0.6, label=self.errlab) - ax.set_title('Thrust [kN]') + ax.set_ylabel('Thrust [kN]') + ax.set_xlabel('Wind speed [m/s]') axes = self.set_axes_label_grid(axes, setlegend=True) # # use axr for the legend @@ -1221,7 +1244,8 @@ class Plots(object): alpha=0.9, color=self.h2c, ls=self.h2ls, marker=self.h2ms) ax.plot(wind2, res2.pwr_hs['P_aero'].values, label=labels[1], alpha=0.7, color=self.hsc, ls=self.hsls, marker=self.hsms) - ax.set_title('Power [kW]') + ax.set_ylabel('Power [kW]') + ax.set_xlabel('Wind speed [m/s]') # relative errors on the right axes axr = ax.twinx() assert np.allclose(wind1, wind2) @@ -1245,7 +1269,8 @@ class Plots(object): err = np.abs(1.0 - (qq1 / qq2))*100.0 axr.plot(wind1, err, color=self.errc, ls=self.errls, alpha=0.6, label=self.errlab) - ax.set_title('Thrust [kN]') + ax.set_ylabel('Thrust [kN]') + ax.set_xlabel('Wind speed [m/s]') axes = self.set_axes_label_grid(axes, setlegend=True) # # use axr for the legend diff --git a/wetb/prepost/hawcstab2.py b/wetb/prepost/hawcstab2.py index c90cb5bfd58a14f48568cb36983f2c05558d041c..00f705e93d8c53389d4860378ad0971ca40b2b54 100644 --- a/wetb/prepost/hawcstab2.py +++ b/wetb/prepost/hawcstab2.py @@ -291,73 +291,6 @@ class hs2_control_tuning(object): setattr(self.aero_damp, 'Ko2', 0.0) -class tests(unittest.TestCase): - """ - """ - - def setUp(self): - self.fpath_linear = 'data/controller_input_linear.txt' - self.fpath_quadratic = 'data/controller_input_quadratic.txt' - - def test_cmb_df(self): - fname1 = 'data/campbell_wind.cmb' - speed, freq, damp = results().load_cmb(fname1) - - df = results().load_cmb_df(fname1) - #mods = freq.shape[1] - ops = freq.shape[0] - - self.assertEqual(len(speed), ops) - - for k in range(ops): - df_oper = df[df['wind_ms']==speed[k]] - np.testing.assert_allclose(freq[k,:], df_oper['Fd_hz'].values) - np.testing.assert_allclose(damp[k,:], df_oper['damp_ratio'].values) - np.testing.assert_allclose(np.arange(1,len(df_oper)+1), df_oper['mode']) - self.assertEqual(len(df_oper['wind_ms'].unique()), 1) - self.assertEqual(df_oper['wind_ms'].unique()[0], speed[k]) - - def test_linear_file(self): - - hs2 = hs2_control_tuning() - hs2.read_parameters(self.fpath_linear) - - self.assertEqual(hs2.pi_gen_reg1.K, 0.108313E+07) - - self.assertEqual(hs2.pi_gen_reg2.I, 0.307683E+08) - self.assertEqual(hs2.pi_gen_reg2.Kp, 0.135326E+08) - self.assertEqual(hs2.pi_gen_reg2.Ki, 0.303671E+07) - - self.assertEqual(hs2.pi_pitch_reg3.Kp, 0.276246E+01) - self.assertEqual(hs2.pi_pitch_reg3.Ki, 0.132935E+01) - self.assertEqual(hs2.pi_pitch_reg3.K1, 5.79377) - self.assertEqual(hs2.pi_pitch_reg3.K2, 0.0) - - self.assertEqual(hs2.aero_damp.Kp2, 0.269403E+00) - self.assertEqual(hs2.aero_damp.Ko1, -4.21472) - self.assertEqual(hs2.aero_damp.Ko2, 0.0) - - def test_quadratic_file(self): - - hs2 = hs2_control_tuning() - hs2.read_parameters(self.fpath_quadratic) - - self.assertEqual(hs2.pi_gen_reg1.K, 0.108313E+07) - - self.assertEqual(hs2.pi_gen_reg2.I, 0.307683E+08) - self.assertEqual(hs2.pi_gen_reg2.Kp, 0.135326E+08) - self.assertEqual(hs2.pi_gen_reg2.Ki, 0.303671E+07) - - self.assertEqual(hs2.pi_pitch_reg3.Kp, 0.249619E+01) - self.assertEqual(hs2.pi_pitch_reg3.Ki, 0.120122E+01) - self.assertEqual(hs2.pi_pitch_reg3.K1, 7.30949) - self.assertEqual(hs2.pi_pitch_reg3.K2, 1422.81187) - - self.assertEqual(hs2.aero_damp.Kp2, 0.240394E-01) - self.assertEqual(hs2.aero_damp.Ko1, -1.69769) - self.assertEqual(hs2.aero_damp.Ko2, -15.02688) - - if __name__ == '__main__': unittest.main() diff --git a/wetb/prepost/tests/data/campbell_diagram.cmb b/wetb/prepost/tests/data/campbell_diagram.cmb new file mode 100755 index 0000000000000000000000000000000000000000..e23e1403864230e29cdb6c9690f06d0d8e0ebd5a --- /dev/null +++ b/wetb/prepost/tests/data/campbell_diagram.cmb @@ -0,0 +1,23 @@ + # Wind [m/s] 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 + 0.400000E+01 0.228933E+00 0.233973E+00 0.762776E+00 0.965817E+00 0.136002E+01 0.149639E+01 0.159423E+01 0.202127E+01 0.204610E+01 0.208646E+01 0.388822E+00 0.966751E+01 0.102903E+01 0.118873E+01 0.224277E+02 0.237007E+01 0.156716E+02 0.304645E+01 0.155964E+02 0.431867E+01 + 0.500000E+01 0.228944E+00 0.234004E+00 0.763301E+00 0.966955E+00 0.136149E+01 0.149576E+01 0.159339E+01 0.202086E+01 0.204910E+01 0.208435E+01 0.400712E+00 0.973227E+01 0.106285E+01 0.133466E+01 0.224476E+02 0.237088E+01 0.156581E+02 0.304886E+01 0.155855E+02 0.426180E+01 + 0.600000E+01 0.228950E+00 0.233713E+00 0.764151E+00 0.968410E+00 0.136286E+01 0.149520E+01 0.159243E+01 0.202039E+01 0.205218E+01 0.208226E+01 0.406516E+00 0.960985E+01 0.113674E+01 0.151399E+01 0.224486E+02 0.236761E+01 0.156309E+02 0.304953E+01 0.155767E+02 0.419995E+01 + 0.700000E+01 0.228969E+00 0.233943E+00 0.765058E+00 0.970449E+00 0.136386E+01 0.149485E+01 0.159148E+01 0.201989E+01 0.205516E+01 0.208023E+01 0.410362E+00 0.956280E+01 0.125579E+01 0.172544E+01 0.225723E+02 0.236204E+01 0.156830E+02 0.304991E+01 0.156414E+02 0.414330E+01 + 0.800000E+01 0.228981E+00 0.235547E+00 0.751657E+00 0.984742E+00 0.133566E+01 0.150181E+01 0.159480E+01 0.201923E+01 0.205065E+01 0.207803E+01 0.425543E+00 0.105481E+02 0.134744E+01 0.175350E+01 0.276486E+02 0.245076E+01 0.183743E+02 0.308534E+01 0.187826E+02 0.426964E+01 + 0.900000E+01 0.228996E+00 0.236970E+00 0.738114E+00 0.999088E+00 0.128575E+01 0.150896E+01 0.159184E+01 0.201868E+01 0.203967E+01 0.207649E+01 0.441459E+00 0.114513E+02 0.139929E+01 0.171638E+01 0.337118E+02 0.249114E+01 0.211607E+02 0.312301E+01 0.222187E+02 0.437718E+01 + 0.100000E+02 0.229012E+00 0.238179E+00 0.724468E+00 0.101353E+01 0.118278E+01 0.151608E+01 0.157955E+01 0.201827E+01 0.201894E+01 0.207574E+01 0.457749E+00 0.123038E+02 0.140490E+01 0.162917E+01 0.404780E+02 0.248329E+01 0.238259E+02 0.316032E+01 0.259943E+02 0.445668E+01 + 0.110000E+02 0.229026E+00 0.239178E+00 0.710773E+00 0.102810E+01 0.108411E+01 0.152309E+01 0.156056E+01 0.201801E+01 0.198169E+01 0.207582E+01 0.473968E+00 0.131326E+02 0.136375E+01 0.150699E+01 0.411061E+02 0.243142E+01 0.257758E+02 0.319485E+01 0.301264E+02 0.449558E+01 + 0.120000E+02 0.229021E+00 0.239633E+00 0.708997E+00 0.103066E+01 0.107780E+01 0.153402E+01 0.154335E+01 0.202064E+01 0.193052E+01 0.209214E+01 0.486960E+00 0.135941E+02 0.114255E+01 0.126920E+01 0.382470E+02 0.216812E+01 0.252810E+02 0.312680E+01 0.310102E+02 0.472517E+01 + 0.130000E+02 0.229026E+00 0.239785E+00 0.708960E+00 0.103053E+01 0.107743E+01 0.154125E+01 0.153408E+01 0.202211E+01 0.189783E+01 0.210716E+01 0.503753E+00 0.138570E+02 0.104718E+01 0.114424E+01 0.366660E+02 0.198152E+01 0.247994E+02 0.309199E+01 0.311264E+02 0.489292E+01 + 0.140000E+02 0.229030E+00 0.239833E+00 0.708652E+00 0.103012E+01 0.107707E+01 0.154680E+01 0.152747E+01 0.202305E+01 0.187153E+01 0.212208E+01 0.514565E+00 0.140661E+02 0.100617E+01 0.106417E+01 0.355180E+02 0.183957E+01 0.244292E+02 0.307265E+01 0.311685E+02 0.500880E+01 + 0.150000E+02 0.229055E+00 0.240017E+00 0.708136E+00 0.102951E+01 0.107701E+01 0.155133E+01 0.152248E+01 0.202372E+01 0.184933E+01 0.213830E+01 0.544804E+00 0.143364E+02 0.996830E+00 0.101040E+01 0.345994E+02 0.173088E+01 0.241219E+02 0.306084E+01 0.311506E+02 0.500600E+01 + 0.160000E+02 0.229071E+00 0.239853E+00 0.707432E+00 0.102872E+01 0.107759E+01 0.155510E+01 0.151899E+01 0.202423E+01 0.183087E+01 0.215318E+01 0.578800E+00 0.145899E+02 0.100993E+01 0.975174E+00 0.338448E+02 0.165052E+01 0.238541E+02 0.305342E+01 0.310630E+02 0.477124E+01 + 0.170000E+02 0.229085E+00 0.239595E+00 0.706561E+00 0.102777E+01 0.107867E+01 0.155827E+01 0.151662E+01 0.202463E+01 0.181533E+01 0.216331E+01 0.609440E+00 0.148314E+02 0.103970E+01 0.954033E+00 0.331993E+02 0.159293E+01 0.236152E+02 0.304854E+01 0.309315E+02 0.446567E+01 + 0.180000E+02 0.229100E+00 0.239299E+00 0.705540E+00 0.102667E+01 0.108022E+01 0.156096E+01 0.151516E+01 0.202495E+01 0.180218E+01 0.217047E+01 0.642610E+00 0.150961E+02 0.108264E+01 0.943946E+00 0.326383E+02 0.155714E+01 0.234027E+02 0.304542E+01 0.307706E+02 0.420507E+01 + 0.190000E+02 0.229100E+00 0.238941E+00 0.704393E+00 0.102545E+01 0.108151E+01 0.156324E+01 0.151377E+01 0.202521E+01 0.178960E+01 0.217609E+01 0.645467E+00 0.152863E+02 0.113498E+01 0.942057E+00 0.321266E+02 0.153398E+01 0.232221E+02 0.304319E+01 0.306240E+02 0.398727E+01 + 0.200000E+02 0.229117E+00 0.238524E+00 0.703123E+00 0.102411E+01 0.108297E+01 0.156518E+01 0.151271E+01 0.202543E+01 0.177816E+01 0.218066E+01 0.680310E+00 0.155290E+02 0.119543E+01 0.947265E+00 0.316630E+02 0.153812E+01 0.230628E+02 0.304242E+01 0.304641E+02 0.381123E+01 + 0.210000E+02 0.229136E+00 0.238084E+00 0.701736E+00 0.102265E+01 0.108452E+01 0.156681E+01 0.151186E+01 0.202562E+01 0.176741E+01 0.218452E+01 0.719375E+00 0.158119E+02 0.126269E+01 0.958046E+00 0.312303E+02 0.156212E+01 0.229212E+02 0.304250E+01 0.302974E+02 0.366667E+01 + 0.220000E+02 0.229147E+00 0.237523E+00 0.700230E+00 0.102107E+01 0.108627E+01 0.156814E+01 0.151132E+01 0.202577E+01 0.175757E+01 0.218779E+01 0.754378E+00 0.161008E+02 0.133578E+01 0.973260E+00 0.308276E+02 0.160451E+01 0.227919E+02 0.304323E+01 0.301112E+02 0.354965E+01 + 0.230000E+02 0.229161E+00 0.236848E+00 0.698611E+00 0.101939E+01 0.108881E+01 0.156918E+01 0.151172E+01 0.202590E+01 0.174988E+01 0.219054E+01 0.796036E+00 0.163966E+02 0.141457E+01 0.992719E+00 0.304529E+02 0.166798E+01 0.226548E+02 0.304454E+01 0.298809E+02 0.345932E+01 + 0.240000E+02 0.229178E+00 0.236114E+00 0.696892E+00 0.101760E+01 0.109145E+01 0.156999E+01 0.151235E+01 0.202601E+01 0.174276E+01 0.219296E+01 0.838889E+00 0.167029E+02 0.149901E+01 0.101590E+01 0.301033E+02 0.175021E+01 0.225316E+02 0.304628E+01 0.296496E+02 0.338727E+01 + 0.250000E+02 0.229186E+00 0.235316E+00 0.695102E+00 0.101572E+01 0.109457E+01 0.157057E+01 0.151364E+01 0.202611E+01 0.173710E+01 0.219518E+01 0.859064E+00 0.169785E+02 0.159098E+01 0.104301E+01 0.297783E+02 0.184710E+01 0.224109E+02 0.304806E+01 0.293981E+02 0.333190E+01 diff --git a/wetb/prepost/tests/data/controller_input_linear.txt b/wetb/prepost/tests/data/controller_input_linear.txt new file mode 100644 index 0000000000000000000000000000000000000000..4a75841485f6d252c3398508c91b123c9d95167d --- /dev/null +++ b/wetb/prepost/tests/data/controller_input_linear.txt @@ -0,0 +1,13 @@ +PI generator torque controller in region 1 (variable speed, variable power), starting at istate = 3 +K = 0.108313E+07 [Nm/(rad/s)^2] +PI generator torque controller in region 2 (constant speed, variable power) +I = 0.307683E+08 [kg*m^2] +Kp = 0.135326E+08 [Nm/(rad/s)] +Ki = 0.303671E+07 [Nm/rad] +PI pitch angle controller in region 3 (constant speed, constant power) +Kp = 0.276246E+01 [rad/(rad/s)] +Ki = 0.132935E+01 [rad/rad] +K1 = 5.79377 [deg] (dq/dtheta = -159.47784 kNm/deg) +Additional terms due to the Aerodynamic damping +Kp2 = 0.269403E+00 [rad/(rad/s)] +Ko1 = -4.21472 [deg] (dq/domega = 2461.64665 kNm/(rad/s)) diff --git a/wetb/prepost/tests/data/controller_input_quadratic.txt b/wetb/prepost/tests/data/controller_input_quadratic.txt new file mode 100644 index 0000000000000000000000000000000000000000..062781f61b1750095b78c74865637282accd90ea --- /dev/null +++ b/wetb/prepost/tests/data/controller_input_quadratic.txt @@ -0,0 +1,13 @@ +PI generator torque controller in region 1 (variable speed, variable power), starting at istate = 3 +K = 0.108313E+07 [Nm/(rad/s)^2] +PI generator torque controller in region 2 (constant speed, variable power) +I = 0.307683E+08 [kg*m^2] +Kp = 0.135326E+08 [Nm/(rad/s)] +Ki = 0.303671E+07 [Nm/rad] +PI pitch angle controller in region 3 (constant speed, constant power) +Kp = 0.249619E+01 [rad/(rad/s)] +Ki = 0.120122E+01 [rad/rad] +K1 = 7.30949 [deg], K2 = 1422.81187 [deg^2] (dq/dtheta = -176.48944 kNm/deg) +Additional terms due to the Aerodynamic damping +Kp2 = 0.240394E-01 [rad/(rad/s)] +Ko1 = -1.69769 [deg], Ko2 = -15.02688 [deg^2] (dq/domega = 243.08924 kNm/(rad/s)) diff --git a/wetb/prepost/tests/test_hawcstab2.py b/wetb/prepost/tests/test_hawcstab2.py new file mode 100644 index 0000000000000000000000000000000000000000..249df2097c7a4167640d420eb3af1a1064981151 --- /dev/null +++ b/wetb/prepost/tests/test_hawcstab2.py @@ -0,0 +1,92 @@ +''' +Created on 05/11/2015 + +@author: MMPE +''' +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +from future import standard_library +standard_library.install_aliases() + +import unittest +import os + +import numpy as np + +from wetb.prepost.hawcstab2 import results, hs2_control_tuning + + +class Tests(unittest.TestCase): + """ + """ + + def setUp(self): + self.fpath_linear = os.path.join(os.path.dirname(__file__), + 'data/controller_input_linear.txt') + self.fpath_quad = os.path.join(os.path.dirname(__file__), + 'data/controller_input_quadratic.txt') + + def test_cmb_df(self): + fname1 = os.path.join(os.path.dirname(__file__), + 'data/campbell_diagram.cmb') + speed, freq, damp = results().load_cmb(fname1) + + df = results().load_cmb_df(fname1) + #mods = freq.shape[1] + ops = freq.shape[0] + + self.assertEqual(len(speed), ops) + + for k in range(ops): + df_oper = df[df['wind_ms']==speed[k]] + np.testing.assert_allclose(freq[k,:], df_oper['Fd_hz'].values) + np.testing.assert_allclose(damp[k,:], df_oper['damp_ratio'].values) + np.testing.assert_allclose(np.arange(1,len(df_oper)+1), df_oper['mode']) + self.assertEqual(len(df_oper['wind_ms'].unique()), 1) + self.assertEqual(df_oper['wind_ms'].unique()[0], speed[k]) + + def test_linear_file(self): + + hs2 = hs2_control_tuning() + hs2.read_parameters(self.fpath_linear) + + self.assertEqual(hs2.pi_gen_reg1.K, 0.108313E+07) + + self.assertEqual(hs2.pi_gen_reg2.I, 0.307683E+08) + self.assertEqual(hs2.pi_gen_reg2.Kp, 0.135326E+08) + self.assertEqual(hs2.pi_gen_reg2.Ki, 0.303671E+07) + + self.assertEqual(hs2.pi_pitch_reg3.Kp, 0.276246E+01) + self.assertEqual(hs2.pi_pitch_reg3.Ki, 0.132935E+01) + self.assertEqual(hs2.pi_pitch_reg3.K1, 5.79377) + self.assertEqual(hs2.pi_pitch_reg3.K2, 0.0) + + self.assertEqual(hs2.aero_damp.Kp2, 0.269403E+00) + self.assertEqual(hs2.aero_damp.Ko1, -4.21472) + self.assertEqual(hs2.aero_damp.Ko2, 0.0) + + def test_quadratic_file(self): + + hs2 = hs2_control_tuning() + hs2.read_parameters(self.fpath_quad) + + self.assertEqual(hs2.pi_gen_reg1.K, 0.108313E+07) + + self.assertEqual(hs2.pi_gen_reg2.I, 0.307683E+08) + self.assertEqual(hs2.pi_gen_reg2.Kp, 0.135326E+08) + self.assertEqual(hs2.pi_gen_reg2.Ki, 0.303671E+07) + + self.assertEqual(hs2.pi_pitch_reg3.Kp, 0.249619E+01) + self.assertEqual(hs2.pi_pitch_reg3.Ki, 0.120122E+01) + self.assertEqual(hs2.pi_pitch_reg3.K1, 7.30949) + self.assertEqual(hs2.pi_pitch_reg3.K2, 1422.81187) + + self.assertEqual(hs2.aero_damp.Kp2, 0.240394E-01) + self.assertEqual(hs2.aero_damp.Ko1, -1.69769) + self.assertEqual(hs2.aero_damp.Ko2, -15.02688) + + +if __name__ == "__main__": + unittest.main() diff --git a/wetb/prepost/tests/test_windIO.py b/wetb/prepost/tests/test_windIO.py new file mode 100644 index 0000000000000000000000000000000000000000..e6951ab7d2a908446cbb7ff3b4df2601a3136375 --- /dev/null +++ b/wetb/prepost/tests/test_windIO.py @@ -0,0 +1,66 @@ +''' +Created on 05/11/2015 + +@author: MMPE +''' +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +from future import standard_library +standard_library.install_aliases() + +import unittest +import os + +import numpy as np + +from wetb.prepost.windIO import LoadResults + + +class TestsLoadResults(unittest.TestCase): + + def setUp(self): + self.respath = os.path.join(os.path.dirname(__file__), + '../../hawc2/tests/test_files/hawc2io/') + self.fascii = 'Hawc2ascii' + self.fbin = 'Hawc2bin' + + def loadresfile(self, resfile): + res = LoadResults(self.respath, resfile) + self.assertTrue(hasattr(res, 'sig')) + self.assertEqual(res.Freq, 40.0) + self.assertEqual(res.N, 800) + self.assertEqual(res.Nch, 28) + self.assertEqual(res.Time, 20.0) + self.assertEqual(res.sig.shape, (800, 28)) + return res + + def test_load_ascii(self): + res = self.loadresfile(self.fascii) + self.assertEqual(res.FileType, 'ASCII') + + def test_load_binary(self): + res = self.loadresfile(self.fbin) + self.assertEqual(res.FileType, 'BINARY') + + def test_compare_ascii_bin(self): + res_ascii = LoadResults(self.respath, self.fascii) + res_bin = LoadResults(self.respath, self.fbin) + + for k in range(res_ascii.sig.shape[1]): + np.testing.assert_allclose(res_ascii.sig[:,k], res_bin.sig[:,k], + rtol=1e-02, atol=0.001) + + def test_unified_chan_names(self): + res = LoadResults(self.respath, self.fascii, readdata=False) + self.assertFalse(hasattr(res, 'sig')) + + np.testing.assert_array_equal(res.ch_df.index.values, np.arange(0,28)) + self.assertEqual(res.ch_df.ch_name.values[0], 'Time') + self.assertEqual(res.ch_df.ch_name.values[27], + 'windspeed-global-Vy--2.50-1.00--52.50') + + +if __name__ == "__main__": + unittest.main() diff --git a/wetb/prepost/windIO.py b/wetb/prepost/windIO.py index ebf6e7647f4102f64e38e9a7a588b0d19abb515c..5c38c195d772b9eb74eba2f86c53c504060572ae 100755 --- a/wetb/prepost/windIO.py +++ b/wetb/prepost/windIO.py @@ -27,14 +27,12 @@ __version__ = '0.5' import os import copy -import unittest import struct import math from time import time import codecs import scipy -import scipy.io as sio import scipy.integrate as integrate import array import numpy as np @@ -130,9 +128,8 @@ class LoadResults(object): # remove .log, .dat, .sel extensions who might be accedental left if file_name[-4:] in ['.htc','.sel','.dat','.log']: file_name = file_name[:-4] - # FIXME: since HAWC2 will always have lower case output files, convert - # any wrongly used upper case letters to lower case here - self.file_name = file_name.lower() + + self.file_name = file_name self.read_sel() # create for any supported channel the # continue if the file has been succesfully read @@ -824,6 +821,8 @@ class LoadResults(object): channelinfo['pos'] = (x, y, z) channelinfo['units'] = units channelinfo['chi'] = ch + channelinfo['sensortype'] = 'windspeed' + channelinfo['component'] = direction[1:] # WIND SPEED AT BLADE # 0: WSP Vx, glco, R= 61.5 @@ -1013,19 +1012,34 @@ class LoadResults(object): # TODO: general signal method, this is not HAWC2 specific, move out def calc_fatigue(self, signal, no_bins=46, m=[3, 4, 6, 8, 10, 12], neq=1): """ - signal is 1D + Parameters + ---------- + + signal: 1D array + One dimentional array containing the signal. + no_bins: int + Number of bins for the binning of the amplitudes. + m: list + Values of the slope of the SN curve. + neq: int + Number of equivalent cycles + + Returns + ------- + eq: list + Damage equivalent loads for each m value. """ try: sig_rf = rainflow_astm(signal) - except: + except (TypeError) as e: + print(e) return [] if len(sig_rf) < 1 and not sig_rf: return [] hist_data, x, bin_avg = rfc_hist(sig_rf, no_bins) - m = np.atleast_1d(m) eq = [] @@ -1855,154 +1869,6 @@ class Bladed(object): return df -class Tests(unittest.TestCase): - - def setUp(self): - pass - - def print_test_info(self): - pass - - def test_reshaped(self): - """ - Make sure we correctly reshape the array instead of the manual - index reassignments - """ - fpath = 'data/turb_s100_3.00w.bin' - fid = open(fpath, 'rb') - turb = np.fromfile(fid, 'float32', 32*32*8192) - turb.shape - fid.close() - u = np.zeros((8192,32,32)) - - for i in range(8192): - for j in range(32): - for k in range(32): - u[i,j,k] = turb[ i*1024 + j*32 + k] - - u2 = np.reshape(turb, (8192, 32, 32)) - - self.assertTrue(np.alltrue(np.equal(u, u2))) - - def test_headers(self): - - fpath = 'data/' - - basename = 'turb_s100_3.00_refoctave_header' - fid = open(fpath + basename + '.wnd', 'rb') - R1 = struct.unpack("h",fid.read(2))[0] - R2 = struct.unpack("h",fid.read(2))[0] - turb = struct.unpack("i",fid.read(4))[0] - lat = struct.unpack("f",fid.read(4))[0] - # last line - fid.seek(100) - LongVertComp = struct.unpack("f",fid.read(4))[0] - fid.close() - - basename = 'turb_s100_3.00_python_header' - fid = open(fpath + basename + '.wnd', 'rb') - R1_p = struct.unpack("h",fid.read(2))[0] - R2_p = struct.unpack("h",fid.read(2))[0] - turb_p = struct.unpack("i",fid.read(4))[0] - lat_p = struct.unpack("f",fid.read(4))[0] - # last line - fid.seek(100) - LongVertComp_p = struct.unpack("f",fid.read(4))[0] - fid.close() - - self.assertEqual(R1, R1_p) - self.assertEqual(R2, R2_p) - self.assertEqual(turb, turb_p) - self.assertEqual(lat, lat_p) - self.assertEqual(LongVertComp, LongVertComp_p) - - def test_write_bladed(self): - - fpath = 'data/' - turb = Turbulence() - # write with Python - basename = 'turb_s100_3.00' - turb.write_bladed(fpath, basename, shape=(8192,32,32)) - python = turb.read_bladed(fpath, basename) - - # load octave - basename = 'turb_s100_3.00_refoctave' - octave = turb.read_bladed(fpath, basename) - - # float versions of octave - basename = 'turb_s100_3.00_refoctave_float' - fid = open(fpath + basename + '.wnd', 'rb') - octave32 = np.fromfile(fid, 'float32', 8192*32*32*3) - - # find the differences - nr_diff = (python-octave).__ne__(0).sum() - print(nr_diff) - print(nr_diff/len(python)) - - self.assertTrue(np.alltrue(python == octave)) - - def test_turbdata(self): - - shape = (8192,32,32) - - fpath = 'data/' - basename = 'turb_s100_3.00_refoctave' - fid = open(fpath + basename + '.wnd', 'rb') - - # check the last element of the header - fid.seek(100) - print(struct.unpack("f",fid.read(4))[0]) - # save in a list using struct - items = (os.path.getsize(fpath + basename + '.wnd')-104)/2 - data_list = [struct.unpack("h",fid.read(2))[0] for k in range(items)] - - - fid.seek(104) - data_16 = np.fromfile(fid, 'int16', shape[0]*shape[1]*shape[2]*3) - - fid.seek(104) - data_8 = np.fromfile(fid, 'int8', shape[0]*shape[1]*shape[2]*3) - - self.assertTrue(np.alltrue( data_16 == data_list )) - self.assertFalse(np.alltrue( data_8 == data_list )) - - def test_compare_octave(self): - """ - Compare the results from the original script run via octave - """ - - turb = Turbulence() - iu, iv, iw = turb.convert2bladed('data/', 'turb_s100_3.00', - shape=(8192,32,32)) - res = sio.loadmat('data/workspace.mat') - # increase tolerances, values have a range up to 5000-10000 - # and these values will be written to an int16 format for BLADED! - self.assertTrue(np.allclose(res['iu'], iu, rtol=1e-03, atol=1e-2)) - self.assertTrue(np.allclose(res['iv'], iv, rtol=1e-03, atol=1e-2)) - self.assertTrue(np.allclose(res['iw'], iw, rtol=1e-03, atol=1e-2)) - - def test_allindices(self): - """ - Verify that all indices are called - """ - fpath = 'data/turb_s100_3.00w.bin' - fid = open(fpath, 'rb') - turb = np.fromfile(fid, 'float32', 32*32*8192) - turb.shape - fid.close() - - check = [] - for i in range(8192): - for j in range(32): - for k in range(32): - check.append(i*1024 + j*32 + k) - - qq = np.array(check) - qdiff = np.diff(qq) - - self.assertTrue(np.alltrue(np.equal(qdiff, np.ones(qdiff.shape)))) - - if __name__ == '__main__': - unittest.main() + pass