diff --git a/wetb/flex/__init__.py b/wetb/flex/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9bdc6ddbf9c798d100054b06b33e5d956b703bcf --- /dev/null +++ b/wetb/flex/__init__.py @@ -0,0 +1,7 @@ +from ._io import load, read_sensor_info +from wetb import gtsdf +import numpy as np + +class Dataset(gtsdf.Dataset): + def __init__(self, filename, name_stop=8,dtype=np.float): + self.time, self.data, self.info = load(filename, name_stop,dtype) \ No newline at end of file diff --git a/wetb/flex/_io.py b/wetb/flex/_io.py new file mode 100644 index 0000000000000000000000000000000000000000..f9419f8b409bacc87ba8c6f42adcbf8f1e631934 --- /dev/null +++ b/wetb/flex/_io.py @@ -0,0 +1,179 @@ +''' +Created on 11. apr. 2017 + +@author: mmpe +''' +import struct +import numpy as np +import os + +def load(filename, name_stop=8, dtype=np.float): + """ + Parameters + ---------- + - filename + - name_stop : int or str + if int: Number of characters for name + if str: name-description delimiter, e.g. " " + - dtype + """ + if isinstance(filename, str): + fid = open(filename,'rb') + elif hasattr(filename, "name"): + fid = filename + filename = fid.name + try: + _ = struct.unpack('i', fid.read(4)) + _ = struct.unpack('i', fid.read(4)) + title = fid.read(60).strip() + + _ = struct.unpack('i', fid.read(4)) + _ = struct.unpack('i', fid.read(4)) + no_sensors = struct.unpack('i', fid.read(4))[0] + + sensor_numbers = [struct.unpack('i', fid.read(4))[0] for _ in range(no_sensors)] + _ = struct.unpack('i', fid.read(4)) + _ = struct.unpack('i', fid.read(4)) + time_start = struct.unpack('f', fid.read(4))[0] + time_step = struct.unpack('f', fid.read(4))[0] + scale_factors = np.array([struct.unpack('f', fid.read(4))[0] for _ in range(no_sensors)], dtype=np.double) + data = np.fromstring(fid.read(), 'int16').astype(dtype) + finally: + fid.close() + +# if title.isalnum(): +# self.set_info(title, "", self.filename) +# else: +# self.set_info(os.path.basename(self.name), "", self.name) + try: + data = data.reshape(len(data) // no_sensors, no_sensors) + except ValueError: + raise ValueError("The number of data values (%d) is not divisible by the number of sensors (%d)" % (len(data), no_sensors)) + + for i in range(data.shape[1]): + data[:, i] *= scale_factors[i] + no_scans = data.shape[0] + + + # Load sensor file if exists + + + sensor_filename = os.path.join(os.path.dirname(filename), "sensor") + sensor_info = {info[0]:info[1:] for info in read_sensor_info(sensor_filename, name_stop) } + + # set gain and offset for "Time" + gains = [] + offsets = [] + names, units, descriptions = [], [], [] + + for sensor_nr in sensor_numbers: + + name, unit, description, gain, offset = sensor_info.get(sensor_nr, ["Attribute %d"%sensor_nr, '-','',1,0]) + if sensor_nr==1 and name=="Time" and unit=="s": + data = data[:,1:] + continue + names.append(name) + units.append(unit) + descriptions.append(description) + gains.append(gain) + offsets.append(offset) + + time = np.arange(time_start, time_start + data.shape[0] * time_step, time_step).reshape((no_scans, 1)) + #data = np.concatenate((time, data), axis=1) + #gains = np.r_[1,gains] + #offsets = np.r_[0,offsets] + # self[:]*=self.gains + # self[:]+=self.offsets + info = {"name": title, + "description": filename, + "attribute_names": names, + "attribute_units": units, + "attribute_descriptions": descriptions} + return time, data, info + + +def read_sensor_info(sensor_file, name_stop=8): + """ + Parameters + ---------- + - sensor_file + - name_stop : int or str + if int: Number of characters for name + if str: name-description delimiter, e.g. " " + """ + + if hasattr(sensor_file, 'readlines'): + sensor_info_lines = sensor_file.readlines()[2:] + else: + with open(sensor_file, encoding="utf-8") as fid: + sensor_info_lines = fid.readlines()[2:] + sensor_info = [] + for line in sensor_info_lines: + # while " " in line: + # line = line.replace(" "," ") + line = line.strip().split() + nr = int(line[0]) + gain = float(line[1]) + offset = float(line[2]) + unit = line[5] + if isinstance(name_stop,int): + name_desc = " ".join(line[6:]) + name = name_desc[:name_stop].strip() + description = name_desc[name_stop:].strip() + elif isinstance(name_stop,str): + name_desc = (" ".join(line[6:])).split(name_stop) + name = name_desc[0].strip() + description = name_stop.join(name_desc[1:]).strip() + + + sensor_info.append((nr, name, unit, description, gain, offset)) + return sensor_info + +# def save(dataset, filename): +# ds = dataset +# # Write int data file +# f = open(filename, 'wb') +# time_att = ds.basis_attribute() +# sensors = [s for s in ds.attributes() if s is not time_att] +# +# if isinstance(ds, FLEX4Dataset): +# data = ds[:] # (ds[:]-ds.offsets)/ds.gains +# else: +# data = ds[:] +# if time_att.index != -1: # time_att may be "Index" with index=-1 if "Time" not exists +# data = np.delete(data, time_att.index, axis=1) +# f.write(struct.pack('ii', 0, 0)) # 2x empty int +# title = ("%-60s" % str(ds.name)).encode() +# f.write(struct.pack('60s', title)) # title +# f.write(struct.pack('ii', 0, 0)) # 2x empty int +# ns = len(sensors) +# f.write(struct.pack('i', ns)) +# f.write(struct.pack('i' * ns, *range(1, ns + 1))) # sensor number +# f.write(struct.pack('ii', 0, 0)) # 2x empty int +# time = ds.basis_attribute() +# f.write(struct.pack('ff', time[0], time[1] - time[0])) # start time and time step +# +# scale_factors = np.max(np.abs(data), 0) / 32000 +# f.write(struct.pack('f' * len(scale_factors), *scale_factors)) +# # avoid dividing by zero +# not0 = np.where(scale_factors != 0) +# data[:, not0] /= scale_factors[not0] +# #flatten and round +# data = np.round(data.flatten()).astype(np.int16) +# f.write(struct.pack('h' * len(data), *data.tolist())) +# f.close() +# +# # write sensor file +# f = open(os.path.join(os.path.dirname(filename), 'sensor'), 'w') +# f.write("Sensor list for %s\n" % filename) +# f.write(" No forst offset korr. c Volt Unit Navn Beskrivelse------------\n") +# sensorlineformat = "%3s %.3f %.3f 1.00 0.00 %7s %-8s %s\n" +# +# if isinstance(ds, FLEX4Dataset): +# gains = np.r_[ds.gains[1:], np.ones(ds.shape[1] - len(ds.gains))] +# offsets = np.r_[ds.offsets[1:], np.zeros(ds.shape[1] - len(ds.offsets))] +# sensorlines = [sensorlineformat % ((nr + 1), gain, offset, att.unit[:7], att.name.replace(" ", "_")[:8], att.description[:512]) for nr, att, gain, offset in zip(range(ns), sensors, gains, offsets)] +# else: +# sensorlines = [sensorlineformat % ((nr + 1), 1, 0, att.unit[:7], att.name.replace(" ", "_")[:8], att.description[:512]) for nr, att in enumerate(sensors)] +# f.writelines(sensorlines) +# f.close() \ No newline at end of file diff --git a/wetb/flex/tests/__init__.py b/wetb/flex/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/wetb/flex/tests/test_files/test1/sensor b/wetb/flex/tests/test_files/test1/sensor new file mode 100644 index 0000000000000000000000000000000000000000..88e89f63990b9e53bef2ba37ebcd5abef5083e88 --- /dev/null +++ b/wetb/flex/tests/test_files/test1/sensor @@ -0,0 +1,9 @@ +Sensor list for C:/mmpe/programming/python/WindEnergyToolbox/wetb/flex/tests/test_files/test1/test.int + No forst offset korr. c Volt Unit Navn Beskrivelse------------ + 1 1.000 0.000 1.00 0.00 m/s WSP_gl._ Free wind speed Vy, gl. coo, of gl. pos 0.75, 0.00, -40.75 + 2 1.000 0.000 1.00 0.00 m/s WSP_gl._ Free wind speed Vy, gl. coo, of gl. pos 1.00, 0.00, -39.00 + 3 1.000 0.000 1.00 0.00 m/s WSP_gl._ Free wind speed Vy, gl. coo, of gl. pos 7.00, 0.00, -33.00 + 4 1.000 0.000 1.00 0.00 rad/s Omega Rotor speed + 5 1.000 0.000 1.00 0.00 m Ae_pos_x Aero position x of blade 1 at radius 20.50, global coo. + 6 1.000 0.000 1.00 0.00 m Ae_pos_y Aero position y of blade 1 at radius 20.50, global coo. + 7 1.000 0.000 1.00 0.00 m Ae_pos_z Aero position z of blade 1 at radius 20.50, global coo. diff --git a/wetb/flex/tests/test_files/test1/test.int b/wetb/flex/tests/test_files/test1/test.int new file mode 100644 index 0000000000000000000000000000000000000000..5df18a5d5b28c9b45ec0b48786925a5fb7d9a306 Binary files /dev/null and b/wetb/flex/tests/test_files/test1/test.int differ diff --git a/wetb/flex/tests/test_files/test_sensor_info/sensor b/wetb/flex/tests/test_files/test_sensor_info/sensor new file mode 100644 index 0000000000000000000000000000000000000000..593044b9a59183b02162c7acc03652b2fa20f864 --- /dev/null +++ b/wetb/flex/tests/test_files/test_sensor_info/sensor @@ -0,0 +1,69 @@ + Version ID : HAWC2MB 12.3 (beta -rev_TJ6) + No forst offset korr. c Volt Unit Navn Beskrivelse--------------- + 1 1.0 0.0 0.00 1.0 s Time Time + 2 1.0 0.0 0.00 1.0 deg bea1 an shaft_rot angle + 3 1.0 0.0 0.00 1.0 rpm bea1 an shaft_rot angle speed + 4 1.0 0.0 0.00 1.0 deg bea2 an pitch1 angle + 5 1.0 0.0 0.00 1.0 deg/s bea2 an pitch1 angle speed + 6 1.0 0.0 0.00 1.0 deg bea2 an pitch2 angle + 7 1.0 0.0 0.00 1.0 deg/s bea2 an pitch2 angle speed + 8 1.0 0.0 0.00 1.0 deg bea2 an pitch3 angle + 9 1.0 0.0 0.00 1.0 deg/s bea2 an pitch3 angle speed + 10 1.0 0.0 0.00 1.0 kNm Mx coo: MomentMx Mbdy:towertop nodenr: 1 coo: tower tower top -1: below top mass + 11 1.0 0.0 0.00 1.0 kNm My coo: MomentMy Mbdy:towertop nodenr: 1 coo: tower tower top -1: below top mass + 12 1.0 0.0 0.00 1.0 kNm Mz coo: MomentMz Mbdy:towertop nodenr: 1 coo: tower tower top -1: below top mass + 13 1.0 0.0 0.00 1.0 kN Fx coo: Force Fx Mbdy:towertop nodenr: 1 coo: tower tower top -1: below top mass + 14 1.0 0.0 0.00 1.0 kN Fy coo: Force Fy Mbdy:towertop nodenr: 1 coo: tower tower top -1: below top mass + 15 1.0 0.0 0.00 1.0 kN Fz coo: Force Fz Mbdy:towertop nodenr: 1 coo: tower tower top -1: below top mass + 16 1.0 0.0 0.00 1.0 kNm Mx coo: MomentMx Mbdy:tower nodenr: 1 coo: tower tower base flange + 17 1.0 0.0 0.00 1.0 kNm My coo: MomentMy Mbdy:tower nodenr: 1 coo: tower tower base flange + 18 1.0 0.0 0.00 1.0 kNm Mz coo: MomentMz Mbdy:tower nodenr: 1 coo: tower tower base flange + 19 1.0 0.0 0.00 1.0 kNm Mx coo: MomentMx Mbdy:towertop nodenr: 2 coo: towertop yaw bearing + 20 1.0 0.0 0.00 1.0 kNm My coo: MomentMy Mbdy:towertop nodenr: 2 coo: towertop yaw bearing + 21 1.0 0.0 0.00 1.0 kNm Mz coo: MomentMz Mbdy:towertop nodenr: 2 coo: towertop yaw bearing + 22 1.0 0.0 0.00 1.0 kN Fx coo: Force Fx Mbdy:towertop nodenr: 2 coo: towertop yaw bering + 23 1.0 0.0 0.00 1.0 kN Fy coo: Force Fy Mbdy:towertop nodenr: 2 coo: towertop yaw bering + 24 1.0 0.0 0.00 1.0 kN Fz coo: Force Fz Mbdy:towertop nodenr: 2 coo: towertop yaw bering + 25 1.0 0.0 0.00 1.0 kNm Mx coo: MomentMx Mbdy:shaft nodenr: 4 coo: shaft main bearing + 26 1.0 0.0 0.00 1.0 kNm My coo: MomentMy Mbdy:shaft nodenr: 4 coo: shaft main bearing + 27 1.0 0.0 0.00 1.0 kNm Mz coo: MomentMz Mbdy:shaft nodenr: 4 coo: shaft main bearing + 28 1.0 0.0 0.00 1.0 kNm Mx coo: MomentMx Mbdy:blade1 nodenr: 3 coo: blade1 blade 1 root + 29 1.0 0.0 0.00 1.0 kNm My coo: MomentMy Mbdy:blade1 nodenr: 3 coo: blade1 blade 1 root + 30 1.0 0.0 0.00 1.0 kNm Mz coo: MomentMz Mbdy:blade1 nodenr: 3 coo: blade1 blade 1 root + 31 1.0 0.0 0.00 1.0 kNm Mx coo: MomentMx Mbdy:blade1 nodenr: 10 coo: local blade 1 50% local e coo + 32 1.0 0.0 0.00 1.0 kNm My coo: MomentMy Mbdy:blade1 nodenr: 10 coo: local blade 1 50% local e coo + 33 1.0 0.0 0.00 1.0 kNm Mz coo: MomentMz Mbdy:blade1 nodenr: 10 coo: local blade 1 50% local e coo + 34 1.0 0.0 0.00 1.0 kN Fx coo: Force Fx Mbdy:blade1 nodenr: 1 coo: blade1 blade 1 root + 35 1.0 0.0 0.00 1.0 kN Fy coo: Force Fy Mbdy:blade1 nodenr: 1 coo: blade1 blade 1 root + 36 1.0 0.0 0.00 1.0 kN Fz coo: Force Fz Mbdy:blade1 nodenr: 1 coo: blade1 blade 1 root + 37 1.0 0.0 0.00 1.0 kNm Mx coo: MomentMx Mbdy:blade2 nodenr: 1 coo: blade2 blade 2 root + 38 1.0 0.0 0.00 1.0 kNm My coo: MomentMy Mbdy:blade2 nodenr: 1 coo: blade2 blade 2 root + 39 1.0 0.0 0.00 1.0 kNm Mz coo: MomentMz Mbdy:blade2 nodenr: 1 coo: blade2 blade 2 root + 40 1.0 0.0 0.00 1.0 kNm Mx coo: MomentMx Mbdy:blade3 nodenr: 1 coo: blade3 blade 3 root + 41 1.0 0.0 0.00 1.0 kNm My coo: MomentMy Mbdy:blade3 nodenr: 1 coo: blade3 blade 3 root + 42 1.0 0.0 0.00 1.0 kNm Mz coo: MomentMz Mbdy:blade3 nodenr: 1 coo: blade3 blade 3 root + 43 1.0 0.0 0.00 1.0 m State p State pos x Mbdy:towertop E-nr: 1 Z-rel:1.00 coo: global tower top flange position + 44 1.0 0.0 0.00 1.0 m State p State pos y Mbdy:towertop E-nr: 1 Z-rel:1.00 coo: global tower top flange position + 45 1.0 0.0 0.00 1.0 m State p State pos z Mbdy:towertop E-nr: 1 Z-rel:1.00 coo: global tower top flange position + 46 1.0 0.0 0.00 1.0 m State p State pos x Mbdy:blade1 E-nr: 18 Z-rel:1.00 coo: blade1 blade 1 tip pos + 47 1.0 0.0 0.00 1.0 m State p State pos y Mbdy:blade1 E-nr: 18 Z-rel:1.00 coo: blade1 blade 1 tip pos + 48 1.0 0.0 0.00 1.0 m State p State pos z Mbdy:blade1 E-nr: 18 Z-rel:1.00 coo: blade1 blade 1 tip pos + 49 1.0 0.0 0.00 1.0 m State p State pos x Mbdy:blade2 E-nr: 18 Z-rel:1.00 coo: blade2 blade 2 tip pos + 50 1.0 0.0 0.00 1.0 m State p State pos y Mbdy:blade2 E-nr: 18 Z-rel:1.00 coo: blade2 blade 2 tip pos + 51 1.0 0.0 0.00 1.0 m State p State pos z Mbdy:blade2 E-nr: 18 Z-rel:1.00 coo: blade2 blade 2 tip pos + 52 1.0 0.0 0.00 1.0 m State p State pos x Mbdy:blade3 E-nr: 18 Z-rel:1.00 coo: blade3 blade 3 tip pos + 53 1.0 0.0 0.00 1.0 m State p State pos y Mbdy:blade3 E-nr: 18 Z-rel:1.00 coo: blade3 blade 3 tip pos + 54 1.0 0.0 0.00 1.0 m State p State pos z Mbdy:blade3 E-nr: 18 Z-rel:1.00 coo: blade3 blade 3 tip pos + 55 1.0 0.0 0.00 1.0 m State p State pos x Mbdy:blade1 E-nr: 18 Z-rel:1.00 coo: global blade 1 tip pos + 56 1.0 0.0 0.00 1.0 m State p State pos y Mbdy:blade1 E-nr: 18 Z-rel:1.00 coo: global blade 1 tip pos + 57 1.0 0.0 0.00 1.0 m State p State pos z Mbdy:blade1 E-nr: 18 Z-rel:1.00 coo: global blade 1 tip pos + 58 1.0 0.0 0.00 1.0 m State p State pos x Mbdy:blade1 E-nr: 18 Z-rel:1.00 coo: hub1 blade 1 tip pos hub coo + 59 1.0 0.0 0.00 1.0 m State p State pos y Mbdy:blade1 E-nr: 18 Z-rel:1.00 coo: hub1 blade 1 tip pos hub coo + 60 1.0 0.0 0.00 1.0 m State p State pos z Mbdy:blade1 E-nr: 18 Z-rel:1.00 coo: hub1 blade 1 tip pos hub coo + 61 1.0 0.0 0.00 1.0 m State p State pos x Mbdy:blade2 E-nr: 18 Z-rel:1.00 coo: hub2 blade 2 tip pos hub coo + 62 1.0 0.0 0.00 1.0 m State p State pos y Mbdy:blade2 E-nr: 18 Z-rel:1.00 coo: hub2 blade 2 tip pos hub coo + 63 1.0 0.0 0.00 1.0 m State p State pos z Mbdy:blade2 E-nr: 18 Z-rel:1.00 coo: hub2 blade 2 tip pos hub coo + 64 1.0 0.0 0.00 1.0 m State p State pos x Mbdy:blade3 E-nr: 18 Z-rel:1.00 coo: hub3 blade 3 tip pos hub coo + 65 1.0 0.0 0.00 1.0 m State p State pos y Mbdy:blade3 E-nr: 18 Z-rel:1.00 coo: hub3 blade 3 tip pos hub coo + 66 1.0 0.0 0.00 1.0 m State p State pos z Mbdy:blade3 E-nr: 18 Z-rel:1.00 coo: hub3 blade 3 tip pos hub coo + 67 1.0 0.0 0.00 1.0 rad/s omega t State_rot omega tz Mbdy:shaft E-nr: 4 Z-rel:1.00 coo: shaft diff --git a/wetb/flex/tests/test_flex_io.py b/wetb/flex/tests/test_flex_io.py new file mode 100644 index 0000000000000000000000000000000000000000..b82a693c0a8297d6aabd54d5193b38c7f2a9cb02 --- /dev/null +++ b/wetb/flex/tests/test_flex_io.py @@ -0,0 +1,27 @@ +''' +Created on 11. apr. 2017 + +@author: mmpe +''' +import os +import unittest +from wetb import flex + + +tfp = os.path.join(os.path.dirname(__file__), 'test_files/') # test file path +class Test(unittest.TestCase): + + + def test_load(self): + time, data, info = flex.load(tfp+"test1/test.int") + self.assertEqual(data.shape, (800,7)) + self.assertEqual(info['attribute_names'][0], "WSP_gl._") + self.assertEqual(info['attribute_units'][0], "m/s") + self.assertEqual(info['attribute_descriptions'][0], "Free wind speed Vy, gl. coo, of gl. pos 0.75, 0.00, -40.75") + + self.assertAlmostEqual(data[0, 1], 12.037,3) + + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.testName'] + unittest.main() \ No newline at end of file diff --git a/wetb/flex/tests/test_sensor.py b/wetb/flex/tests/test_sensor.py new file mode 100644 index 0000000000000000000000000000000000000000..5a3e3ffeebe9f02e8dd34034dcb4469511afe235 --- /dev/null +++ b/wetb/flex/tests/test_sensor.py @@ -0,0 +1,33 @@ +''' +Created on 9. sep. 2016 + +@author: mmpe +''' +import os +import unittest +from wetb.flex import read_sensor_info + + +tfp = os.path.join(os.path.dirname(__file__), 'test_files/') # test file path +class Test(unittest.TestCase): + + + def test_sensor_load(self): + sensor_info = read_sensor_info(tfp + "test_sensor_info/sensor") + nr, name, unit, description, _, _ = sensor_info[17] + self.assertEqual(nr, 18) + self.assertEqual(name, "Mz coo:") + self.assertEqual(unit, "kNm") + self.assertEqual(description, "MomentMz Mbdy:tower nodenr: 1 coo: tower tower base flange") + + def test_sensor_load_name_stop(self): + sensor_info = read_sensor_info(tfp + "test_sensor_info/sensor"," ") + nr, name, unit, description, _, _ = sensor_info[17] + self.assertEqual(nr, 18) + self.assertEqual(name, "Mz") + self.assertEqual(unit, "kNm") + self.assertEqual(description, "coo: MomentMz Mbdy:tower nodenr: 1 coo: tower tower base flange") + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.test_sensor_load'] + unittest.main() diff --git a/wetb/gtsdf/__init__.py b/wetb/gtsdf/__init__.py index 0c84199f52b04b0e90da6e7ac32a120f6e28a62b..4d48fa17a46720365d1de4717a393db9771bf8a9 100644 --- a/wetb/gtsdf/__init__.py +++ b/wetb/gtsdf/__init__.py @@ -67,7 +67,9 @@ class Dataset(object): for i, n in enumerate(self.info['attribute_names']): print (i,n) raise e - + + def __contains__(self, name): + return name in self.info['attribute_names'] __all__ = sorted([m for m in set(dir()) - set(d)]) diff --git a/wetb/hawc2/ae_file.py b/wetb/hawc2/ae_file.py index cee13d9f107fea4ad249ad514e480e70d61d2233..d3628fee19eebc8bc152588b7c572a703e01c179 100644 --- a/wetb/hawc2/ae_file.py +++ b/wetb/hawc2/ae_file.py @@ -45,14 +45,24 @@ class AEFile(object): def _value(self, radius, column, set_nr=1): ae_data = self.ae_sets[set_nr] - return np.interp(radius, ae_data[:, 0], ae_data[:, column]) + if radius: + return np.interp(radius, ae_data[:, 0], ae_data[:, column]) + else: + return ae_data[:,column] - def chord(self, radius, set_nr=1): + def chord(self, radius=None, set_nr=1): return self._value(radius, 1, set_nr) - def thickness(self, radius, set_nr=1): + def thickness(self, radius=None, set_nr=1): return self._value(radius, 2, set_nr) - + + def radius_ae(self, radius=None, set_nr=1): + radii = self.ae_sets[set_nr][:,0] + if radius: + return radii[np.argmin(np.abs(radii-radius))] + else: + return radii + def pc_set_nr(self, radius, set_nr=1): ae_data = self.ae_sets[set_nr] index = np.searchsorted(ae_data[:, 0], radius) @@ -68,6 +78,7 @@ class AEFile(object): if __name__ == "__main__": ae = AEFile(r"tests/test_files/NREL_5MW_ae.txt") - print (ae.thickness(36)) + print (ae.radius_ae(36)) + print (ae.thickness()) print (ae.chord(36)) print (ae.pc_set_nr(36)) diff --git a/wetb/hawc2/at_time_file.py b/wetb/hawc2/at_time_file.py index 10ed8583dd11b1bb280db37a1aa216d43bfcbde1..c84c75e6fdd38bb6646aac366556b261e40744fa 100644 --- a/wetb/hawc2/at_time_file.py +++ b/wetb/hawc2/at_time_file.py @@ -16,22 +16,37 @@ import numpy as np class AtTimeFile(object): """Loads an at time file generated by HAWC2 - Note that the radius in this context is the curved radius - - >>> atfile = AtTimeFile("at_time.dat") # load file - >>> atfile.attribute_names # Attribute names + Functions are generated for each sensor in the file from the template: + xxx(radius=None, curved_length=None) + + E.g. + twist(radius=36) # Twist at radius 36m (Straight line radius, blade_radius must be specified in constructor) + twist(curved_length=36) # Twist 36m from the root along the (curved) center line + twist() # Twist at all aerodynamic calculation points + + + >>> at = AtTimeFile("at.dat", 86.3655) # load file + >>> at.attribute_names # Attribute names ['radius_s', 'twist', 'chord'] - >>> atfile[:3,1]) # first 3 twist rows - [ 0. -0.775186 -2.91652 ] - >>> atfile.twist()[:3]) # first 3 twist rows - [ 0. -0.775186 -2.91652 ] - >>> atfile.twist(curved_length=10) # Twist at curved_length = 10 (interpolated) - -5.34743208242399 - >>> atfile.twist(radius=10) # Twist at curved_length = 10 (interpolated) - -5.34743208242399 + >>> at[:3,1]) # first 3 twist rows + [-14.5 -14.5002 -14.5007] + >>> at.twist()[:3]) # Twist first 3 twist rows + [-14.5 -14.5002 -14.5007] + >>> at.twist(curved_length=36) # Twist at curved_length = 10 (interpolated) + -5.172195702789108 + >>> at.twist(radius=36) # Twist at curved_length = 10 (interpolated) + -5.162084567646019 """ - def __init__(self, filename, blade_radius=None): - self.blade_radius = blade_radius + def __init__(self, filename, bladetip_radius=None): + """ + Parameters + ---------- + filename : string + Filename + bladetip_radius : int, float or None, optional + Radius of blade tip. Used to convert from curved length to radius + """ + self.blade_radius = bladetip_radius with open(filename, encoding='utf-8') as fid: lines = fid.readlines() self.attribute_names = lines[2].lower().replace("#", "").split() @@ -43,7 +58,7 @@ class AtTimeFile(object): if radius is None and curved_length is None: return self.data[:, column] elif radius is not None: - assert self.blade_radius is not None, "blade_radius must be specified in __init__ when requesting value of radius (alternatively you can request for curved_length)" + assert self.blade_radius is not None, "bladetip_radius must be specified in __init__ when requesting value of radius (alternatively you can request for curved_length)" return np.interp(radius/self.blade_radius, self.data[:, 0]/self.data[-1, 0], self.data[:, column]) else: return np.interp(curved_length, self.data[:, 0], self.data[:, column]) @@ -51,7 +66,7 @@ class AtTimeFile(object): for column, att_name in enumerate(self.attribute_names): setattr(self, att_name, func_factory(column)) - def ac_radius(self, radius=None): + def radius_ac(self, radius=None): """Radius (curved distance) of aerodynamic calculation point(s) Parameters @@ -66,7 +81,7 @@ class AtTimeFile(object): Radius of calculation points or radius of calculation point nearest radius """ if radius is None: - return self.radius_s(radius) + return self.radius_s() else: return self.radius_s()[np.argmin(np.abs(self.radius_s() - radius))] @@ -78,8 +93,18 @@ class AtTimeFile(object): if __name__ == "__main__": - at = AtTimeFile(r"tests/test_files/at_time.dat") - print (at.attribute_names) - print (at.twist(36)) - print (at.chord(36)) + at = AtTimeFile(r"tests/test_files/at.dat", 86.3655) # load file + print (at.attribute_names) # Attribute names + print (at[:3,1]) # first 3 twist rows + print (at.twist()[:3]) # first 3 twist rows + print (at.twist(curved_length=36)) # Twist at curved_length = 10 (interpolated) + print (at.twist(radius=36)) # Twist at curved_length = 10 (interpolated) + +# at = AtTimeFile(r"tests/test_files/at.dat", 86.3655) +# print (at.attribute_names) +# print (at.radius_s()) +# print (at.twist(curved_length=36)) +# print (at.chord(curved_length=36)) +# print (at.twist(curved_length=36)) +# print (at.twist(radius=36)) diff --git a/wetb/hawc2/bladeinfo.py b/wetb/hawc2/bladeinfo.py new file mode 100644 index 0000000000000000000000000000000000000000..745685f31f1550761127592954c20890b2f658dc --- /dev/null +++ b/wetb/hawc2/bladeinfo.py @@ -0,0 +1,210 @@ +''' +Created on 3. maj 2017 + +@author: mmpe +''' +import numpy as np +from wetb.hawc2.at_time_file import AtTimeFile +from wetb.hawc2.pc_file import PCFile +from wetb.hawc2.htc_file import HTCFile +from scipy.interpolate.interpolate import interp1d +import os +from wetb.utils.geometry import rad +from wetb.hawc2.st_file import StFile + + + +class BladeInfo(object): + def twist(self, radius=None): + """Aerodynamic twist [deg]. Negative when leading edge is twisted towards wind(opposite to normal definition)\n + + Parameters + --------- + radius : float or array_like, optional + radius [m] of interest + If None (default) the twist of all points are returned + + Returns + ------- + twist [deg] : float + """ + raise NotImplementedError() + + def chord(self, radius=None): + """Aerodynamic chord + + Parameters + --------- + radius : float or array_like, optional + radius [m] of interest + If None (default) the twist of all points are returned + + Returns + ------- + chord [m] : float + """ + raise NotImplementedError() + + + + def c2nd(self, radius_nd): + """Return center line position + + Parameters + --------- + radius_nd : float or array_like, optional + non dimensional radius + + Returns + ------- + x,y,z : float + """ + +class H2BladeInfo(BladeInfo, PCFile, AtTimeFile): + """Provide HAWC2 info about a blade + + From AE file: + - chord(radius=None, set_nr=1): + - thickness(radius=None, set_nr=1) + - radius_ae(radius=None, set_nr=1) + + From PC file + - CL(radius, alpha, ae_set_nr=1) + - CD(radius, alpha, ae_set_nr=1) + - CM(radius, alpha, ae_set_nr=1) + + From at_time_filename + - attribute_names + - xxx(radius=None, curved_length=None) # xxx for each attribute name + - radius_ac(radius=None) # Curved length of nearest/all aerodynamic calculation points + + From ST file + - radius_st(radius=None, mset=1, set=1) + - xxx(radius=None, mset=1, set=1) # xxx for each of r, m, x_cg,y_cg, ri_x, ri_y, xs, ys, E, G, Ix, Iy, K, kx, ky, A, pitch, xe, ye + with template + + """ + + def __init__(self, htcfile, ae_filename=None, pc_filename=None, at_time_filename=None, st_filename=None, blade_name=None): + + if isinstance(htcfile, str): + assert htcfile.lower().endswith('.htc') + htcfile = HTCFile(htcfile) + + blade_name = blade_name or htcfile.aero.link[2] + s = htcfile.new_htc_structure + at_time_filename = at_time_filename or os.path.join(htcfile.modelpath, htcfile.output_at_time.filename[0] + ".dat") + pc_filename = pc_filename or os.path.join(htcfile.modelpath, htcfile.aero.pc_filename[0]) + ae_filename = ae_filename or os.path.join(htcfile.modelpath, htcfile.aero.ae_filename[0]) + + mainbodies = [s[k] for k in s.keys() if s[k].name_ == "main_body"] + mainbody_blade = [mb for mb in mainbodies if mb.name[0] == blade_name][0] + st_filename = st_filename or os.path.join(htcfile.modelpath, mainbody_blade.timoschenko_input.filename[0]) + + if os.path.isfile(pc_filename) and os.path.isfile(ae_filename): + PCFile.__init__(self, pc_filename, ae_filename) + blade_radius = self.ae_sets[1][-1,0] + + if os.path.isfile(st_filename): + StFile.__init__(self, st_filename) + if os.path.isfile(at_time_filename): + AtTimeFile.__init__(self, at_time_filename, blade_radius) + + + self.c2def = np.array([v.values[1:5] for v in mainbody_blade.c2_def if v.name_ == "sec"]) + + #self.c2nd = lambda x : interp1d(self.c2def[:, 2] / self.c2def[-1, 2], self.c2def[:, :3], axis=0, kind='cubic')(np.max([np.min([x, np.ones_like(x)], 0), np.zeros_like(x) + self.c2def[0, 2] / self.c2def[-1, 2]], 0)) + x, y, z, twist = self.hawc2_splines() + def interpolate(r): + r = (np.max([np.min([r, np.ones_like(r)], 0), np.zeros_like(r) + self.c2def[0, 2] / self.c2def[-1, 2]], 0)) + return np.array([np.interp(r, np.array(z) / z[-1], xyz) for xyz in [x,y,z, twist]]).T + + self.c2nd = interpolate #lambda r : interp1d(np.array(z) / z[-1], np.array([x, y, z]).T, axis=0, kind=1)(np.max([np.min([r, np.ones_like(r)], 0), np.zeros_like(r) + self.c2def[0, 2] / self.c2def[-1, 2]], 0)) + + def hawc2_splines(self): + curve_z = np.r_[0, np.cumsum(np.sqrt(np.sum(np.diff(self.c2def[:, :3], 1, 0) ** 2, 1)))] + curve_z_nd = curve_z / curve_z[-1] + + def akima(x, y): + n = len(x) + var = np.zeros((n + 3)) + z = np.zeros((n)) + co = np.zeros((n, 4)) + for i in range(n - 1): + var[i + 2] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]) + var[n + 1] = 2 * var[n] - var[n - 1] + var[n + 2] = 2 * var[n + 1] - var[n] + var[1] = 2 * var[2] - var[3] + var[0] = 2 * var[1] - var[2] + + for i in range(n): + wi1 = abs(var[i + 3] - var[i + 2]) + wi = abs(var[i + 1] - var[i]) + if (wi1 + wi) == 0: + z[i] = (var[i + 2] + var[i + 1]) / 2 + else: + z[i] = (wi1 * var[i + 1] + wi * var[i + 2]) / (wi1 + wi) + + for i in range(n - 1): + dx = x[i + 1] - x[i] + a = (z[i + 1] - z[i]) * dx + b = y[i + 1] - y[i] - z[i] * dx + co[i, 0] = y[i] + co[i, 1] = z[i] + co[i, 2] = (3 * var[i + 2] - 2 * z[i] - z[i + 1]) / dx + co[i, 3] = (z[i] + z[i + 1] - 2 * var[i + 2]) / dx ** 2 + co[n - 1, 0] = y[n - 1] + co[n - 1, 1] = z[n - 1] + co[n - 1, 2] = 0 + co[n - 1, 3] = 0 + return co + + def coef2spline(s, co): + x, y = [], [] + for i, c in enumerate(co.tolist()[:-1]): + p = np.poly1d(c[::-1]) + z = np.linspace(0, s[i + 1] - s[i ], 10, endpoint=i >= co.shape[0] - 2) + x.extend(s[i] + z) + y.extend(p(z)) + return y + + x, y, z, twist = [coef2spline(curve_z_nd, akima(curve_z_nd, self.c2def[:, i])) for i in range(4)] + return x, y, z, twist + + def c2def_twist(self, radius=None): + if radius is None: + return self.c2def[:, 3] + else: + return np.interp(radius, self.c2def[:, 2], self.c2def[:, 3]) + + + +class H2aeroBladeInfo(H2BladeInfo): + + def __init__(self, at_time_filename, ae_filename, pc_filename, htc_filename): + """ + at_time_filename: file name of at time file containing twist and chord data + """ + PCFile.__init__(self, pc_filename, ae_filename) + blade_radius = self.ae_sets[1][-1,0] + AtTimeFile.__init__(self, at_time_filename, blade_radius) + + assert('twist' in self.attribute_names) + htcfile = HTCFile(htc_filename) + + + self.c2def = np.array([v.values[1:5] for v in htcfile.blade_c2_def if v.name_ == "sec"]) + #self._c2nd = interp1d(self.c2def[:, 2] / self.c2def[-1, 2], self.c2def[:, :3], axis=0, kind='cubic') + + ac_radii = self.ac_radius() + c2_axis_length = np.r_[0, np.cumsum(np.sqrt((np.diff(self.c2def[:, :3], 1, 0) ** 2).sum(1)))] + self._c2nd = interp1d(c2_axis_length / c2_axis_length[-1], self.c2def[:, :3], axis=0, kind='cubic') + #self._c2nd = interp1d(self.c2def[:,2]/self.c2def[-1,2], self.c2def[:, :3], axis=0, kind='cubic') + + def c2nd(self, r_nd): + r_nd_min = np.zeros_like(r_nd) + self.c2def[0, 2] / self.c2def[-1, 2] + r_nd_max = np.ones_like(r_nd) + r_nd = np.max([np.min([r_nd, r_nd_max], 0), r_nd_min], 0) + return self._c2nd(r_nd) + + diff --git a/wetb/hawc2/htc_contents.py b/wetb/hawc2/htc_contents.py index 5337613d47c902590c483da4ec6fd8e22d501596..93b58f077cfd2fb2cdb12c9cfdb663f0a813cb76 100644 --- a/wetb/hawc2/htc_contents.py +++ b/wetb/hawc2/htc_contents.py @@ -120,7 +120,7 @@ class HTCContents(object): self._add_contents(section) return section - def add_line(self, name, values, comments): + def add_line(self, name, values, comments=""): line = HTCLine(name, values, comments) self._add_contents(line) return line diff --git a/wetb/hawc2/pc_file.py b/wetb/hawc2/pc_file.py index f42cc1076518a42f14b38cb307b65c6847a28a39..bd0da0a7403545b893cbea7ac1a2b4dd5f432689 100644 --- a/wetb/hawc2/pc_file.py +++ b/wetb/hawc2/pc_file.py @@ -43,7 +43,7 @@ class PCFile(AEFile): with open (filename) as fid: lines = fid.readlines() nsets = int(lines[0].split()[0]) - self.sets = {} + self.pc_sets = {} lptr = 1 for nset in range(1, nsets + 1): nprofiles = int(lines[lptr].split()[0]) @@ -59,12 +59,12 @@ class PCFile(AEFile): thicknesses.append(thickness) profiles.append(data) lptr += n_rows - self.sets[nset] = (np.array(thicknesses), profiles) + self.pc_sets[nset] = (np.array(thicknesses), profiles) def _Cxxx(self, radius, alpha, column, ae_set_nr=1): thickness = self.thickness(radius, ae_set_nr) pc_set_nr = self.pc_set_nr(radius, ae_set_nr) - thicknesses, profiles = self.sets[pc_set_nr] + thicknesses, profiles = self.pc_sets[pc_set_nr] index = np.searchsorted(thicknesses, thickness) if index == 0: index = 1 @@ -112,7 +112,6 @@ class PCFile(AEFile): return self._Cxxx(radius, alpha, 2, ae_set_nr) def CM(self, radius, alpha, ae_set_nr=1): - return self._Cxxx(radius, alpha, 3, ae_set_nr) if __name__ == "__main__": diff --git a/wetb/hawc2/st_file.py b/wetb/hawc2/st_file.py index f03efafb96d5b93b58c330e427abaed48f09e2cb..7859144fab6a442ff110c681297e46fd4a32690a 100644 --- a/wetb/hawc2/st_file.py +++ b/wetb/hawc2/st_file.py @@ -68,8 +68,9 @@ class StFile(object): def __init__(self, filename): with open (filename) as fid: txt = fid.read() - no_maindata_sets = int(txt.replace("#","").strip()[0]) - assert no_maindata_sets == txt.count("#") +# Some files starts with first set ("#1...") with out specifying number of sets +# no_maindata_sets = int(txt.strip()[0]) +# assert no_maindata_sets == txt.count("#") self.main_data_sets = {} for mset in txt.split("#")[1:]: mset_nr = int(mset.strip().split()[0]) @@ -91,7 +92,7 @@ class StFile(object): radius = self.radius(None, mset_nr, set_nr) return np.interp(radius, st_data[:, 0], st_data[:, column]) - def radius(self, radius=None, mset=1, set=1): + def radius_st(self, radius=None, mset=1, set=1): r = self.main_data_sets[mset][set][:, 0] if radius is None: return r diff --git a/wetb/hawc2/tests/test_AtTimeFile.py b/wetb/hawc2/tests/test_AtTimeFile.py index 6f17562bab8741498c04106893e0541d9f50a17f..09573fd33ba82feb1f5dc5d52cc5ba1ec1ea0b21 100644 --- a/wetb/hawc2/tests/test_AtTimeFile.py +++ b/wetb/hawc2/tests/test_AtTimeFile.py @@ -15,7 +15,7 @@ import numpy as np import os - +tfp = os.path.join(os.path.dirname(__file__), 'test_files/') # test file path class TestAtTimeFile(unittest.TestCase): def setUp(self): @@ -24,7 +24,7 @@ class TestAtTimeFile(unittest.TestCase): def test_doc_examples(self): - atfile = AtTimeFile(self.testfilepath + "at_time.dat", blade_radius=20.501) # load file + atfile = AtTimeFile(self.testfilepath + "at_time.dat", bladetip_radius=20.501) # load file self.assertEqual(atfile.attribute_names, ['radius_s', 'twist', 'chord']) np.testing.assert_array_equal(atfile[:3, 1], [ 0., -0.775186, -2.91652 ]) np.testing.assert_array_equal(atfile.twist()[:3], [ 0. , -0.775186 , -2.91652 ]) @@ -48,7 +48,7 @@ class TestAtTimeFile(unittest.TestCase): self.assertEqual(atfile.chord(curved_length=9), 1.3888996578373045) def test_at_time_file_at_radius(self): - atfile = AtTimeFile(self.testfilepath + "at_time.dat", blade_radius=20.501/2) + atfile = AtTimeFile(self.testfilepath + "at_time.dat", bladetip_radius=20.501/2) self.assertEqual(atfile.radius_s(radius=9/2), 9) self.assertEqual(atfile.twist(radius=9/2), -6.635983309665461) self.assertEqual(atfile.chord(radius=9/2), 1.3888996578373045) @@ -56,9 +56,9 @@ class TestAtTimeFile(unittest.TestCase): def test_at_time_file_radius(self): atfile = AtTimeFile(self.testfilepath + "at_time.dat") - self.assertEqual(atfile.ac_radius()[12], 10.2505) - self.assertEqual(atfile.ac_radius(10), 10.2505) - self.assertEqual(atfile.ac_radius(10.5), 10.2505) + self.assertEqual(atfile.radius_ac()[12], 10.2505) + self.assertEqual(atfile.radius_ac(10), 10.2505) + self.assertEqual(atfile.radius_ac(10.5), 10.2505) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] diff --git a/wetb/hawc2/tests/test_files/at.dat b/wetb/hawc2/tests/test_files/at.dat new file mode 100644 index 0000000000000000000000000000000000000000..f93af10f4f3c18596f1dd613503a5bc82847fffe --- /dev/null +++ b/wetb/hawc2/tests/test_files/at.dat @@ -0,0 +1,53 @@ + # Version ID : HAWC2MB 12.4 + # Radial output at time: 0.120000000000000 + # Radius_s # twist # Chord + 0.00000E+00 -1.45000E+01 5.38000E+00 + 8.88594E-02 -1.45002E+01 5.38000E+00 + 3.55072E-01 -1.45007E+01 5.38000E+00 + 7.97544E-01 -1.45012E+01 5.38000E+00 + 1.41446E+00 -1.45013E+01 5.38000E+00 + 2.20328E+00 -1.45008E+01 5.38000E+00 + 3.16076E+00 -1.44998E+01 5.38000E+00 + 4.28297E+00 -1.44959E+01 5.38000E+00 + 5.56530E+00 -1.44880E+01 5.38150E+00 + 7.00248E+00 -1.44609E+01 5.40757E+00 + 8.58860E+00 -1.43497E+01 5.47316E+00 + 1.03171E+01 -1.40434E+01 5.57870E+00 + 1.21810E+01 -1.34021E+01 5.71677E+00 + 1.41726E+01 -1.24242E+01 5.87200E+00 + 1.62836E+01 -1.11781E+01 6.02007E+00 + 1.85054E+01 -9.82623E+00 6.13078E+00 + 2.08289E+01 -8.68972E+00 6.18999E+00 + 2.32446E+01 -7.84255E+00 6.19714E+00 + 2.57424E+01 -7.19893E+00 6.15543E+00 + 2.83122E+01 -6.63223E+00 6.06940E+00 + 3.09433E+01 -6.10664E+00 5.94438E+00 + 3.36250E+01 -5.60732E+00 5.78666E+00 + 3.63463E+01 -5.10875E+00 5.60337E+00 + 3.90959E+01 -4.60713E+00 5.40154E+00 + 4.18626E+01 -4.07749E+00 5.18393E+00 + 4.46350E+01 -3.51118E+00 4.95580E+00 + 4.74017E+01 -2.93766E+00 4.72176E+00 + 5.01513E+01 -2.36236E+00 4.48569E+00 + 5.28726E+01 -1.79469E+00 4.25127E+00 + 5.55543E+01 -1.25288E+00 4.02160E+00 + 5.81854E+01 -7.42980E-01 3.79894E+00 + 6.07552E+01 -2.63044E-01 3.58510E+00 + 6.32530E+01 1.76184E-01 3.38191E+00 + 6.56687E+01 5.69023E-01 3.19012E+00 + 6.79922E+01 9.24087E-01 3.01062E+00 + 7.02140E+01 1.25492E+00 2.84386E+00 + 7.23251E+01 1.55945E+00 2.68993E+00 + 7.43167E+01 1.84168E+00 2.54872E+00 + 7.61805E+01 2.10535E+00 2.42040E+00 + 7.79091E+01 2.34909E+00 2.29766E+00 + 7.94952E+01 2.57177E+00 2.17211E+00 + 8.09324E+01 2.77156E+00 2.04065E+00 + 8.22147E+01 2.94258E+00 1.90297E+00 + 8.33369E+01 3.08259E+00 1.75760E+00 + 8.42945E+01 3.19421E+00 1.59874E+00 + 8.50832E+01 3.28140E+00 1.41325E+00 + 8.56999E+01 3.34684E+00 1.20310E+00 + 8.61425E+01 3.39238E+00 1.00803E+00 + 8.64090E+01 3.41914E+00 8.77215E-01 + 8.64979E+01 3.42796E+00 8.33540E-01 diff --git a/wetb/hawc2/tests/test_st_file.py b/wetb/hawc2/tests/test_st_file.py index 8d794c6ea2d6258f23d713ffe94c5d1be3856592..8b678b65b1a466d61749dd380938d515a0c18ad8 100644 --- a/wetb/hawc2/tests/test_st_file.py +++ b/wetb/hawc2/tests/test_st_file.py @@ -19,8 +19,8 @@ class TestStFile(unittest.TestCase): def test_stfile(self): st = StFile(testfilepath + "DTU_10MW_RWT_Blade_st.dat") - self.assertEqual(st.radius()[2], 3.74238) - self.assertEqual(st.radius(3), 3.74238) + self.assertEqual(st.radius_st()[2], 3.74238) + self.assertEqual(st.radius_st(3), 3.74238) self.assertEqual(st.x_e(67.7351), 4.4320990737400E-01) self.assertEqual(st.E(3.74238, 1, 1), 1.2511695058500E+10) self.assertEqual(st.E(3.74238, 1, 2), 1.2511695058500E+27) diff --git a/wetb/signal/fix/_rotor_position.py b/wetb/signal/fix/_rotor_position.py index 22aac76de54ba82a0cd4d21f501885e9a7364cb9..3876204dc3c039b8bfa047b028f489f0e8053107 100644 --- a/wetb/signal/fix/_rotor_position.py +++ b/wetb/signal/fix/_rotor_position.py @@ -5,6 +5,8 @@ Created on 30. mar. 2017 ''' import numpy as np from wetb.signal.fit._spline_fit import spline_fit +from wetb.signal.filters._differentiation import differentiation + def fix_rotor_position(rotor_position, sample_frq, rotor_speed, fix_dt=None, plt=None): """Rotor position fitted with spline @@ -33,7 +35,7 @@ def fix_rotor_position(rotor_position, sample_frq, rotor_speed, fix_dt=None, plt from wetb.signal.subset_mean import revolution_trigger t = np.arange(len(rotor_position)) - indexes = revolution_trigger(rotor_position[:], sample_frq, rotor_speed, max_no_round_diff=4) + indexes = revolution_trigger(rotor_position[:], sample_frq, rotor_speed, max_rev_diff=4) rp = rotor_position[:].copy() @@ -69,7 +71,9 @@ def fix_rotor_position(rotor_position, sample_frq, rotor_speed, fix_dt=None, plt plt.plot(t/sample_frq, spline_fit(x,y)(t)-t*a, label='Spline (detrended)') plt.legend() plt.show() - return spline_fit(x,y)(t)%360 + fit = spline_fit(x,y)(t) + fit[differentiation(fit, "left")<0]=np.nan + return fit%360 def find_fix_dt(rotor_position, sample_frq, rotor_speed, plt=None): """Find the optimal fix_dt parameter for fix_rotor_position (function above). @@ -95,7 +99,8 @@ def find_fix_dt(rotor_position, sample_frq, rotor_speed, plt=None): """ from wetb.signal.filters import differentiation def err(i): - rpm_pos = differentiation(fix_rotor_position(rotor_position, sample_frq, rotor_speed, i))%180 / 360 * sample_frq * 60 + drp = differentiation(fix_rotor_position(rotor_position, sample_frq, rotor_speed, i)) + rpm_pos = drp%180 / 360 * sample_frq * 60 return np.sum((rpm_pos - rotor_speed)**2) best = 27 @@ -110,4 +115,69 @@ def find_fix_dt(rotor_position, sample_frq, rotor_speed, plt=None): plt.show() return best - \ No newline at end of file + +def check_rotor_position(rotor_position, sample_frq, rotor_speed, max_rev_diff=1, plt=None): + """Rotor position fitted with spline + + Parameters + ---------- + rotor_position : array_like + Rotor position [deg] (0-360) + sample_frq : int or float + Sample frequency [Hz] + rotor_speed : array_like + Rotor speed [RPM] + fix_dt : int, float or None, optional + Time distance [s] between spline fix points\n + If None (default) a range of seconds is tested and the result that minimize the RMS + between differentiated rotor position fit and rotor speed is used.\n + Note that a significant speed up is achievable by specifying the parameter + plt : PyPlot or None + If PyPlot a visual interpretation is plotted + + Returns + ------- + y : nd_array + Fitted rotor position + """ + + from wetb.signal.subset_mean import revolution_trigger + + t = np.arange(len(rotor_position))/sample_frq + indexes = revolution_trigger(rotor_position[:], sample_frq, rotor_speed, max_rev_diff=max_rev_diff) + + rotor_position = rotor_position[:]%360 + + rp_fit = fix_rotor_position(rotor_position, sample_frq, rotor_speed, None) + + if plt: + #a = (rp.max()-rp.min())/t.max() + #plt.plot(t/sample_frq,rp-t*a,label='Continus rotor position (detrended)') + f, axarr = plt.subplots(2) + print (rp_fit) + axarr[0].plot(t, rotor_position) + axarr[0].plot(indexes/sample_frq, rotor_position[indexes],'.') + axarr[0].plot(t, rp_fit) + axarr[1].plot(t, rotor_speed) + axarr[1].plot(indexes/sample_frq,60/( differentiation(indexes)/sample_frq)) + print (t[170:200]) + drp = differentiation(rp_fit) + #drp[(drp<0)]= 0 + axarr[1].plot(t, drp%180 / 360 * sample_frq * 60, label='fix') + plt.legend() + + i1,i2 = indexes[0], indexes[-1] + print ("Rev from rotor position", np.sum(np.diff(rotor_position[i1:i2])%180)/360) + print ("Rev from rotor speed", np.mean(rotor_speed[i1:i2])*(i2-i1)/60/sample_frq) + print ("Rev from fitted rotor position", np.sum(np.diff(rp_fit[i1:i2])%180)/360) + + print ("Mean RPM from rotor speed", np.mean(rotor_speed)) + print ("Mean RPM from fitted rotor position", np.sum(np.diff(rp_fit[i1:i2])%180)/360 / ((i2-i1)/60/sample_frq)) + + spr1 = (np.diff(indexes)/sample_frq) + + #rs1 = 60/( np.diff(indexes)/sample_frq) + spr2 = np.array([60/rotor_speed[i1:i2].mean() for i1,i2 in zip(indexes[:-1], indexes[1:])]) + + err = spr1-spr2 + print (err.max()) \ No newline at end of file diff --git a/wetb/signal/subset_mean.py b/wetb/signal/subset_mean.py index 0de3ea896f8185e932998ddd85229b97523e2a9a..7578c11ac477856a237b7ec00ea0f2e418ba6695 100644 --- a/wetb/signal/subset_mean.py +++ b/wetb/signal/subset_mean.py @@ -9,6 +9,7 @@ import unittest from wetb.utils.geometry import rpm2rads from _collections import deque from tables.tests.test_index_backcompat import Indexes2_0TestCase +from wetb.signal.filters._differentiation import differentiation def power_mean(power, trigger_indexes, I, rotor_speed, time, air_density=1.225, rotor_speed_mean_samples=1) : @@ -136,7 +137,7 @@ def cycle_trigger(values, trigger_value=None, step=1, ascending=True, tolerance= else: return np.where((values[1:] < trigger_value - tolerance) & (values[:-1] >= trigger_value + tolerance))[0][::step] -def revolution_trigger(rotor_position, sample_frq, rotor_speed, max_no_round_diff=1): +def revolution_trigger(rotor_position, sample_frq, rotor_speed, max_rev_diff=1): """Returns one index per revolution (minimum rotor position) Parameters @@ -155,16 +156,12 @@ def revolution_trigger(rotor_position, sample_frq, rotor_speed, max_no_round_dif if isinstance(rotor_speed, (float, int)): rotor_speed = np.ones_like(rotor_position)*rotor_speed deg_per_sample = rotor_speed*360/60/sample_frq - sample_per_round = 1/(rotor_speed/60/sample_frq) - thresshold = deg_per_sample.max()*2 - - nround_rotor_speed = np.nansum(rotor_speed/60/sample_frq) - - mod = [v for v in [5,10,30,60,90] if v>thresshold][0] + thresshold = deg_per_sample.max()*3 + drp = np.diff(rotor_position)%360 + rp = rotor_position + - nround_rotor_position = np.nansum(np.diff(rotor_position)%mod)/360 - #assert abs(nround_rotor_position-nround_rotor_speed)<max_no_round_diff, "No of rounds from rotor_position (%.2f) mismatch with no_rounds from rotor_speed (%.2f)"%(nround_rotor_position, nround_rotor_speed) - #print (nround_rotor_position, nround_rotor_speed) + rp = np.array(rotor_position).copy() #filter degree increase > thresshold @@ -176,10 +173,47 @@ def revolution_trigger(rotor_position, sample_frq, rotor_speed, max_no_round_dif # Best lower is the first lower after upper best_lower = lower_indexes[np.searchsorted(lower_indexes, upper_indexes)] upper2lower = best_lower - upper_indexes - best_lower = best_lower[upper2lower<upper2lower.mean()*2] - #max_dist_error = max([np.abs((i2-i1)- np.mean(sample_per_round[i1:i2])) for i1,i2 in zip(best_lower[:-1], best_lower[1:])]) - #assert max_dist_error < sample_frq/5, max_dist_error - return best_lower + trigger_indexes = best_lower[upper2lower<upper2lower.mean()*2].tolist() + + if len(trigger_indexes)>1: + + + + rpm_rs = np.array([rev.mean() for rev in np.split(rotor_speed, trigger_indexes)[1:-1]]) + rpm_i = 1/np.diff(trigger_indexes)*60*sample_frq + spr_rs = np.array([rev.mean() for rev in np.split(1/rotor_speed*60*sample_frq, trigger_indexes)[1:-1]]) + spr_i = np.diff(trigger_indexes) + + + while np.any(spr_rs>spr_i*1.9): + i = np.where(spr_rs>spr_i*1.9)[0][0] + if np.abs(spr_i[i-1] + spr_i[i] - spr_rs[i])< np.abs(spr_i[i] + spr_i[i+1] - spr_rs[i]): + del trigger_indexes[i] + else: + del trigger_indexes[i+1] + spr_i = np.diff(trigger_indexes) + spr_rs = np.array([rev.mean() for rev in np.split(1/rotor_speed*60*sample_frq, trigger_indexes)[1:-1]]) + + + + # if a revolution is too long add trigger + if np.any(rpm_rs>rpm_i*2.1): + # >1 missing triggers + raise NotImplementedError + trigger_indexes.extend([np.mean(trigger_indexes[i:i+2]) for i in np.where(rpm_rs>rpm_i*1.9)[0]]) + trigger_indexes = np.sort(trigger_indexes).astype(np.int) + + + i1,i2 = trigger_indexes[0], trigger_indexes[-1] + nround_rotor_speed = np.nansum(rotor_speed[i1:i2]/60/sample_frq) + #mod = [v for v in [5,10,30,60,90] if v>thresshold][0] + nround_rotor_position = len(trigger_indexes)-1 #np.nansum(np.diff(rotor_position[i1:i2])%mod)/360 + if max_rev_diff is not None: + diff_pct = abs(nround_rotor_position-nround_rotor_speed)/nround_rotor_position*100 + assert diff_pct<max_rev_diff, "No of revolution mismatch: rotor_position (%d), rotor_speed (%.1f), diff %.1f%%"%(nround_rotor_position, nround_rotor_speed, diff_pct) + + + return trigger_indexes def revolution_trigger_old(values, rpm_dt=None, dmin=5, dmax=10, ): diff --git a/wetb/signal/tests/test_fix.py b/wetb/signal/tests/test_fix.py index 8663986285a6fdb9f3a37f487b751b619bd9cc83..86c9ce472931b9aacd3d5b0d081d0639988f6a2c 100644 --- a/wetb/signal/tests/test_fix.py +++ b/wetb/signal/tests/test_fix.py @@ -42,6 +42,8 @@ class TestFix(unittest.TestCase): self.assertEqual(find_fix_dt(ds.azi, sample_frq, ds.Rot_cor), 4) + + if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testRotorPositionFix'] diff --git a/wetb/signal/tests/test_subset_mean.py b/wetb/signal/tests/test_subset_mean.py index 23642ae7509bc80188499122807d5b22ddfce2fc..8c4ecd862d112fad16d5db3f85c86604d5e45ac8 100644 --- a/wetb/signal/tests/test_subset_mean.py +++ b/wetb/signal/tests/test_subset_mean.py @@ -92,21 +92,32 @@ class TestSubsetMean(unittest.TestCase): x2 = np.random.randint(0, len(rotor_position),10) print (list(x2)) else: - x1 = [447, 854, 595, 804, 847, 488, 412, 199, 675, 766] + x1 = [447, 854, 847, 488, 412, 199, 675] x2 = [92, 647, 821, 422, 33, 159, 369, 99, 157, 464] rotor_position[x1] += 360 rotor_position[x2] -= 360 rotor_position[90] = 180 + rotor_position[270:276] = 15 + rotor_position[598:602] = [360,360,0,0] + rotor_position[748:752] = [360,360,0,0] indexes = revolution_trigger(rotor_position, 20,1/(90/20/60)) - np.testing.assert_array_equal(indexes, [ 91, 180, 270, 360, 450, 540, 630, 720, 810]) if 0: import matplotlib.pyplot as plt plt.plot(rotor_position) plt.plot(indexes, np.zeros_like(indexes),'.') plt.show() + np.testing.assert_array_equal(indexes, [ 91, 180, 270, 360, 450, 540, 630, 720, 810]) + + def test_revolution_trigger_max_rev_diff(self): + rotor_position = np.arange(0.,360*10,4) + rotor_position += np.random.random(len(rotor_position)) + rotor_position = rotor_position % 360 + rotor_speed = 1/(90/20/60) + revolution_trigger(rotor_position, 20, rotor_speed*1.02, max_rev_diff=3) + self.assertRaisesRegex(AssertionError, "No of revolution mismatch", revolution_trigger, rotor_position, 20, rotor_speed*1.02, max_rev_diff=1) + - if __name__ == "__main__": unittest.main() diff --git a/wetb/wind/__init__.py b/wetb/wind/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae17e9a567028038ec2029f25b2abf6d80ffa4ee 100644 --- a/wetb/wind/__init__.py +++ b/wetb/wind/__init__.py @@ -0,0 +1 @@ +from .turbulence import * \ No newline at end of file diff --git a/wetb/wind/turbulence/mann_parameters.py b/wetb/wind/turbulence/mann_parameters.py index b6a1d806cf5d083cdd0510d391285bedfd7f922c..b0a94bc7c9dfa87895e986769c1f45013c030642 100644 --- a/wetb/wind/turbulence/mann_parameters.py +++ b/wetb/wind/turbulence/mann_parameters.py @@ -254,20 +254,18 @@ def fit_ae(sf, u, L, G, min_bin_count=None, plt=False): return ae -def plot_spectra(ae, L, G, k1, uu, vv, ww=None, uw=None, mean_u=1, log10_bin_size=.2, show=True, plt=None): +def plot_spectra(ae, L, G, k1, uu, vv=None, ww=None, uw=None, mean_u=1, log10_bin_size=.2, plt=None): # if plt is None: # import matplotlib.pyplot as plt _plot_spectra(k1, uu, vv, ww, uw, mean_u, log10_bin_size, plt) plot_mann_spectra(ae, L, G, "-", mean_u, plt) - if show: - plt.show() -def _plot_spectra(k1, uu, vv, ww=None, uw=None, mean_u=1, log10_bin_size=.2, plt=None): +def _plot_spectra(k1, uu, vv=None, ww=None, uw=None, mean_u=1, log10_bin_size=.2, plt=None): if plt is None: import matplotlib.pyplot as plt bk1, buu, bvv, bww, buw = logbin_spectra(k1, uu, vv, ww, uw, log10_bin_size) def plot(xx, label, color, plt): - plt.semilogx(bk1, bk1 * xx * 10 ** 0 / mean_u ** 2 , '.' + color) + plt.semilogx(bk1, bk1 * xx * 10 ** 0 / mean_u ** 2 , '.' + color, label=label) plot(buu, 'uu', 'r', plt) if (bvv) is not None: plot(bvv, 'vv', 'g', plt) @@ -280,8 +278,9 @@ def plot_mann_spectra(ae, L, G, style='-', u_ref=1, plt=None, spectra=['uu', 'vv import matplotlib.pyplot as plt mf = 10 ** (np.linspace(-4, 1, 1000)) muu, mvv, mww, muw = get_mann_model_spectra(ae, L, G, mf) + plt.title("ae: %.3f, L: %.2f, G:%.2f"%(ae,L,G)) if 'uu' in spectra: plt.semilogx(mf, mf * muu * 10 ** 0 / u_ref ** 2, 'r' + style) - if 'vv' in spectra: plt.semilogx(mf, mf * mvv * 10 ** 0 / u_ref ** 2, 'g' + style) + if 'vv' in spectra: plt.semilogx(mf, mf * mvv * 10 ** 0 / u_ref ** 2, 'g' + style) if 'ww' in spectra: plt.semilogx(mf, mf * mww * 10 ** 0 / u_ref ** 2, 'b' + style) if 'uw' in spectra: plt.semilogx(mf, mf * muw * 10 ** 0 / u_ref ** 2, 'm' + style)