Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit cd94e85

Browse files
committed
IO: TurbSimFile: adding fromAMRWind_PD and AMRWind class
1 parent 1bb67f9 commit cd94e85

File tree

2 files changed

+272
-43
lines changed

2 files changed

+272
-43
lines changed

pyFAST/input_output/amrwind_file.py

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""Read AMR-Wind NETCDF file
2+
3+
"""
4+
import xarray as xr
5+
import numpy as np
6+
7+
class AMRWindFile(dict):
8+
"""
9+
Read a AMR-Wind output file (.nc)
10+
"""
11+
12+
@staticmethod
13+
def defaultExtensions():
14+
""" List of file extensions expected for this fileformat"""
15+
return ['.nc']
16+
17+
@staticmethod
18+
def formatName():
19+
""" Short string (~100 char) identifying the file format"""
20+
return 'NetCDF plane sampling file from AMRWind'
21+
22+
@staticmethod
23+
def priority(): return 60 # Priority in weio.read fileformat list between 0=high and 100:low
24+
25+
def __init__(self, filename=None, timestep=None, output_frequency=None, **kwargs):
26+
self.filename = filename
27+
self.amrwind_dt = timestep
28+
self.output_dt = timestep * output_frequency
29+
30+
if filename:
31+
self.read(**kwargs)
32+
33+
def read(self, group_name):
34+
"""
35+
Parameters
36+
----------
37+
38+
group_name : str,
39+
group name inside netcdf file that you want to read, e.g. p_slice
40+
41+
TODO: see if group_name can be avoided, and add a read_group function
42+
"""
43+
# --- Standard tests and exceptions (generic code)
44+
if filename:
45+
self.filename = filename
46+
if not self.filename:
47+
raise Exception('No filename provided')
48+
if not os.path.isfile(self.filename):
49+
raise OSError(2,'File not found:',self.filename)
50+
if os.stat(self.filename).st_size == 0:
51+
raise Exception('File is empty:',self.filename)
52+
53+
54+
ds = xr.open_dataset(self.filename,group=group_name)
55+
56+
coordinates = {"x":(0,"axial"), "y":(1,"lateral"),"z":(2,"vertical")}
57+
c = {}
58+
for coordinate,(i,desc) in coordinates.items():
59+
c[coordinate] = xr.IndexVariable(
60+
dims=[coordinate],
61+
data=np.sort(np.unique(ds['coordinates'].isel(ndim=i))),
62+
attrs={"description":"{0} coordinate".format(desc),"units":"m"}
63+
)
64+
c["t"] = xr.IndexVariable(
65+
dims=["t"],
66+
data=ds.num_time_steps*self.output_dt,
67+
attrs={"description":"time from start of simulation","units":"s"}
68+
)
69+
70+
self.nt = len(c["t"])
71+
self.nx = len(c["x"])
72+
self.ny = len(c["y"])
73+
self.nz = len(c["z"])
74+
75+
coordinates = {"x":(0,"axial","u"), "y":(1,"lateral","v"),"z":(2,"vertical","w")}
76+
v = {}
77+
for coordinate,(i,desc,u) in coordinates.items():
78+
v[u] = xr.DataArray(np.reshape(getattr(ds,"velocity{0}".format(coordinate)).values,(self.nt,self.nx,self.ny,self.nz)),
79+
coords=c,
80+
dims=["t","x","y","z"],
81+
name="{0} velocity".format(desc),
82+
attrs={"description":"velocity along {0}".format(coordinate),"units":"m/s"})
83+
84+
ds = xr.Dataset(data_vars=v, coords=v[u].coords)
85+
ds.attrs = {"original file":self.filename}
86+
87+
self.data = ds
88+
89+
90+
def write(self, filename=None):
91+
""" Rewrite object to file, or write object to `filename` if provided """
92+
if filename:
93+
self.filename = filename
94+
if not self.filename:
95+
raise Exception('No filename provided')
96+
raise NotImplementedError()
97+
98+
def toDataFrame(self):
99+
""" Returns object into one DataFrame, or a dictionary of DataFrames"""
100+
# --- Example (returning one DataFrame):
101+
# return pd.DataFrame(data=np.zeros((10,2)),columns=['Col1','Col2'])
102+
# --- Example (returning dict of DataFrames):
103+
#dfs={}
104+
#cols=['Alpha_[deg]','Cl_[-]','Cd_[-]','Cm_[-]']
105+
#dfs['Polar1'] = pd.DataFrame(data=..., columns=cols)
106+
#dfs['Polar1'] = pd.DataFrame(data=..., columns=cols)
107+
# return dfs
108+
raise NotImplementedError()
109+
110+
# --- Optional functions
111+
def __repr__(self):
112+
""" String that is written to screen when the user calls `print()` on the object.
113+
Provide short and relevant information to save time for the user.
114+
"""
115+
s='<{} object>:\n'.format(type(self).__name__)
116+
s+='|Main attributes:\n'
117+
s+='| - filename: {}\n'.format(self.filename)
118+
# --- Example printing some relevant information for user
119+
#s+='|Main keys:\n'
120+
#s+='| - ID: {}\n'.format(self['ID'])
121+
#s+='| - data : shape {}\n'.format(self['data'].shape)
122+
s+='|Main methods:\n'
123+
s+='| - read, write, toDataFrame, keys'
124+
return s
125+

pyFAST/input_output/turbsim_file.py

+147-43
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def __init__(self, filename=None, **kwargs):
5858
if filename:
5959
self.read(filename, **kwargs)
6060

61-
def read(self, filename=None, header_only=False):
61+
def read(self, filename=None, header_only=False, tdecimals=8):
6262
""" read BTS file, with field:
6363
u (3 x nt x ny x nz)
6464
uTwr (3 x nt x nTwr)
@@ -98,11 +98,11 @@ def read(self, filename=None, header_only=False):
9898
self['uTwr'] = uTwr
9999
self['info'] = info
100100
self['ID'] = ID
101-
self['dt'] = np.round(dt,3) # dt is stored in single precision in the TurbSim output
101+
self['dt'] = np.round(dt, tdecimals) # dt is stored in single precision in the TurbSim output
102102
self['y'] = np.arange(ny)*dy
103103
self['y'] -= np.mean(self['y']) # y always centered on 0
104104
self['z'] = np.arange(nz)*dz +zBottom
105-
self['t'] = np.round(np.arange(nt)*dt, 3)
105+
self['t'] = np.round(np.arange(nt)*dt, tdecimals)
106106
self['zTwr'] =-np.arange(nTwr)*dz + zBottom
107107
self['zRef'] = zHub
108108
self['uRef'] = uHub
@@ -592,45 +592,13 @@ def __repr__(self):
592592
s+=' ux: min: {}, max: {}, mean: {} \n'.format(np.min(ux), np.max(ux), np.mean(ux))
593593
s+=' uy: min: {}, max: {}, mean: {} \n'.format(np.min(uy), np.max(uy), np.mean(uy))
594594
s+=' uz: min: {}, max: {}, mean: {} \n'.format(np.min(uz), np.max(uz), np.mean(uz))
595-
595+
s += ' Useful methods:\n'
596+
s += ' - read, write, toDataFrame, keys\n'
597+
s += ' - valuesAt, vertProfile, horizontalPlane, verticalPlane, closestPoint\n'
598+
s += ' - fitPowerLaw\n'
599+
s += ' - makePeriodic, checkPeriodic\n'
596600
return s
597601

598-
def toDataSet(self, datetime=False):
599-
import xarray as xr
600-
601-
if datetime:
602-
timearray = pd.to_datetime(self['t'], unit='s', origin=pd.to_datetime('2000-01-01 00:00:00'))
603-
timestr = 'datetime'
604-
else:
605-
timearray = self['t']
606-
timestr = 'time'
607-
608-
ds = xr.Dataset(
609-
data_vars=dict(
610-
u=([timestr,'y','z'], self['u'][0,:,:,:]),
611-
v=([timestr,'y','z'], self['u'][1,:,:,:]),
612-
w=([timestr,'y','z'], self['u'][2,:,:,:]),
613-
),
614-
coords={
615-
timestr : timearray,
616-
'y' : self['y'],
617-
'z' : self['z'],
618-
},
619-
)
620-
621-
# Add mean computations
622-
ds['up'] = ds['u'] - ds['u'].mean(dim=timestr)
623-
ds['vp'] = ds['v'] - ds['v'].mean(dim=timestr)
624-
ds['wp'] = ds['w'] - ds['w'].mean(dim=timestr)
625-
626-
if datetime:
627-
# Add time (in s) to the variable list
628-
ds['time'] = (('datetime'), self['t'])
629-
630-
return ds
631-
632-
633-
634602
def toDataFrame(self):
635603
dfs={}
636604

@@ -713,31 +681,167 @@ def toDataFrame(self):
713681
# pass
714682
return dfs
715683

684+
def toDataset(self):
685+
"""
686+
Convert the data that was read in into a xarray Dataset
687+
688+
# TODO SORT OUT THE DIFFERENCE WITH toDataSet
689+
"""
690+
from xarray import IndexVariable, DataArray, Dataset
691+
692+
print('[TODO] pyFAST.input_output.turbsim_file.toDataset: merge with function toDataSet')
693+
694+
y = IndexVariable("y", self.y, attrs={"description":"lateral coordinate","units":"m"})
695+
zround = np.asarray([np.round(zz,6) for zz in self.z]) #the open function here returns something like *.0000000001 which is annoying
696+
z = IndexVariable("z", zround, attrs={"description":"vertical coordinate","units":"m"})
697+
time = IndexVariable("time", self.t, attrs={"description":"time since start of simulation","units":"s"})
698+
699+
da = {}
700+
for component,direction,velname in zip([0,1,2],["x","y","z"],["u","v","w"]):
701+
# the dataset produced here has y/z axes swapped relative to data stored in original object
702+
velocity = np.swapaxes(self["u"][component,...],1,2)
703+
da[velname] = DataArray(velocity,
704+
coords={"time":time,"y":y,"z":z},
705+
dims=["time","y","z"],
706+
name="velocity",
707+
attrs={"description":"velocity along {0}".format(direction),"units":"m/s"})
708+
709+
return Dataset(data_vars=da, coords={"time":time,"y":y,"z":z})
710+
711+
def toDataSet(self, datetime=False):
712+
"""
713+
Convert the data that was read in into a xarray Dataset
714+
715+
# TODO SORT OUT THE DIFFERENCE WITH toDataset
716+
"""
717+
import xarray as xr
718+
719+
print('[TODO] pyFAST.input_output.turbsim_file.toDataSet: should be discontinued')
720+
print('[TODO] pyFAST.input_output.turbsim_file.toDataSet: merge with function toDataset')
721+
722+
if datetime:
723+
timearray = pd.to_datetime(self['t'], unit='s', origin=pd.to_datetime('2000-01-01 00:00:00'))
724+
timestr = 'datetime'
725+
else:
726+
timearray = self['t']
727+
timestr = 'time'
728+
729+
ds = xr.Dataset(
730+
data_vars=dict(
731+
u=([timestr,'y','z'], self['u'][0,:,:,:]),
732+
v=([timestr,'y','z'], self['u'][1,:,:,:]),
733+
w=([timestr,'y','z'], self['u'][2,:,:,:]),
734+
),
735+
coords={
736+
timestr : timearray,
737+
'y' : self['y'],
738+
'z' : self['z'],
739+
},
740+
)
741+
742+
# Add mean computations
743+
ds['up'] = ds['u'] - ds['u'].mean(dim=timestr)
744+
ds['vp'] = ds['v'] - ds['v'].mean(dim=timestr)
745+
ds['wp'] = ds['w'] - ds['w'].mean(dim=timestr)
746+
747+
if datetime:
748+
# Add time (in s) to the variable list
749+
ds['time'] = (('datetime'), self['t'])
750+
751+
return ds
716752

717753
# Useful converters
718-
def fromAMRWind(self, filename, dt, nt):
754+
def fromAMRWind(self, filename, timestep, output_frequency, sampling_identifier, verbose=1, fileout=None, zref=None, xloc=None):
755+
"""
756+
Reads a AMRWind netcdf file, grabs a group of sampling planes (e.g. p_slice),
757+
return an instance of TurbSimFile, optionally write turbsim file to disk
758+
759+
760+
Parameters
761+
----------
762+
filename : str,
763+
full path to netcdf file generated by amrwind
764+
timestep : float,
765+
amr-wind code timestep (time.fixed_dt)
766+
output_frequency : int,
767+
frequency chosen for sampling output in amrwind input file (sampling.output_frequency)
768+
sampling_identifier : str,
769+
identifier of the sampling being requested (an entry of sampling.labels in amrwind input file)
770+
zref : float,
771+
height to be written to turbsim as the reference height. if none is given, it is taken as the vertical centerpoint of the slice
772+
"""
773+
try:
774+
from pyFAST.input_output.amrwind_file import AMRWindFile
775+
except:
776+
try:
777+
from .amrwind_file import AMRWindFile
778+
except:
779+
from amrwind_file import AMRWindFile
780+
781+
obj = AMRWindFile(filename,timestep,output_frequency, group_name=sampling_identifier)
782+
783+
self["u"] = np.ndarray((3,obj.nt,obj.ny,obj.nz))
784+
785+
xloc = float(obj.data.x[0]) if xloc is None else xloc
786+
if verbose:
787+
print("Grabbing the slice at x={0} m".format(xloc))
788+
self['u'][0,:,:,:] = np.swapaxes(obj.data.u.sel(x=xloc).values,1,2)
789+
self['u'][1,:,:,:] = np.swapaxes(obj.data.v.sel(x=xloc).values,1,2)
790+
self['u'][2,:,:,:] = np.swapaxes(obj.data.w.sel(x=xloc).values,1,2)
791+
self['t'] = obj.data.t.values
792+
793+
self['y'] = obj.data.y.values
794+
self['z'] = obj.data.z.values
795+
self['dt'] = obj.output_dt
796+
797+
self['ID'] = 7
798+
ltime = time.strftime('%d-%b-%Y at %H:%M:%S', time.localtime())
799+
self['info'] = 'Converted from AMRWind output file {0} {1:s}.'.format(filename,ltime)
800+
801+
iz = int(obj.nz/2)
802+
self['zRef'] = float(obj.data.z[iz]) if zref is None else zref
803+
if verbose:
804+
print("Setting the TurbSim file reference height to z={0} m".format(self["zRef"]))
805+
806+
self['uRef'] = float(obj.data.u.sel(x=xloc).sel(y=0).sel(z=self["zRef"]).mean().values)
807+
self['zRef'], self['uRef'], bHub = self.hubValues()
808+
809+
if fileout is not None:
810+
filebase = os.path.splitext(filename)[1]
811+
fileout = filebase+".bts"
812+
if verbose:
813+
print("===> {0}".format(fileout))
814+
self.write(fileout)
815+
816+
817+
def fromAMRWind_legacy(self, filename, dt, nt, y, z, sampling_identifier='p_sw2'):
719818
"""
720819
Convert current TurbSim file into one generated from AMR-Wind LES sampling data in .nc format
721820
Assumes:
722821
-- u, v, w (nt, nx * ny * nz)
723822
-- u is aligned with x-axis (flow is not rotated) - this consideration needs to be added
724823
824+
725825
INPUTS:
726826
- filename: (string) full path to .nc sampling data file
727-
- plane_label: (string) name of sampling plane group from .inp file (e.g. "p_sw2")
827+
- sampling_identifier: (string) name of sampling plane group from .inp file (e.g. "p_sw2")
728828
- dt: timestep size [s]
729829
- nt: number of timesteps (sequential) you want to read in, starting at the first timestep available
830+
INPUTS: TODO
730831
- y: user-defined vector of coordinate positions in y
731832
- z: user-defined vector of coordinate positions in z
732833
- uref: (float) reference mean velocity (e.g. 8.0 hub height mean velocity from input file)
733834
- zref: (float) hub height (e.t. 150.0)
734835
"""
735836
import xarray as xr
837+
838+
print('[TODO] fromAMRWind_legacy: function might be unfinished. Merge with fromAMRWind')
839+
print('[TODO] fromAMRWind_legacy: figure out y, and z from data (see fromAMRWind)')
736840

737841
# read in sampling data plane
738842
ds = xr.open_dataset(filename,
739843
engine='netcdf4',
740-
group=plane_label)
844+
group=sampling_identifier)
741845
ny, nz, _ = ds.attrs['ijk_dims']
742846
noffsets = len(ds.attrs['offsets'])
743847
t = np.arange(0, dt*(nt-0.5), dt)

0 commit comments

Comments
 (0)