Source code for pyts.base

"""
This is the 'base' module for the PyTurbSim program.

This module:

 a) imports common numpy functions/methods (from pyts_numpy.py),

 b) defines the :class:`gridObj` class (and the :func:`tsGrid` helper
    function),

 c) imports the tslib Fortran module (if it is available) and

 d) Defines several abstract base classes.

"""
from . import pyts_numpy as np
from numpy import float32, complex64
from .misc import lowPrimeFact_near
from os import path
try:
    from .tslib import tslib  # The file tslib.so contains the module 'tslib'.
except ImportError:
    print """
    ***Warning***: 'tslib' did not load correctly.  pyTurbSim
    will produce accurate results, but MUCH less efficiently.
    Consider compiling the tslib to improve performance.
    """
    tslib = None

dbg = None
#import dbg

tsroot = path.realpath(__file__).replace("\\", "/").rsplit('/', 1)[0] + '/'
userroot = path.expanduser('~')

ts_float = float32
ts_complex = complex64
#ts_float={'dtype':np.float32,'order':'F'}
#ts_complex={'dtype':np.complex64,'order':'F'}


[docs]class statObj(float): def __new__(self, dat, ubar=None): return float.__new__(self, dat.mean()) @property def mean(self): return self def __init__(self, dat, ubar=None): self.max = dat.max() self.min = dat.min() self.sigma = np.std(dat) if ubar is None: ubar = self.mean self.ti = (self.sigma / ubar) * 100 def __repr__(self,): return '<statObj mean: %0.2f, min: %0.2f, max: %0.2f>' % (self.mean, self.min, self.max)
[docs]class tsBaseObj(object): """ An abstract, base object, that contains the component (u,v,w) information for derived classes. """ comp_name = ['u', 'v', 'w'] n_comp = len(comp_name) comp = range(n_comp)
[docs]class calcObj(tsBaseObj): """ This is a base class for objects that are have .array properties. It creates a shortcut for accessing the array through the getitem method. This is used in each of the model packages as a base-class for the output 'statistics' of that class. In other words: profObj, specObj, stressObj, and cohereObj all derive from this class. """ _alias0 = ['u', 'v', 'w'] def __getitem__(self, ind): if ind in self._alias0: ind = self._alias0.index(ind) if hasattr(self, '_alias1') and ind in self._alias1: ind = self._alias1.index(ind) return self.array[ind] def __setitem__(self, ind, val): if ind in self._alias0: ind = self._alias0.index(ind) if hasattr(self, '_alias1') and ind in self._alias1: ind = self._alias1.index(ind) self.array[ind] = val
[docs]class gridProps(tsBaseObj): """ An abstract base class that provides shortcuts for objects that have the grid as one of their attributes. """ @property def z(self,): return self.grid.z @property def y(self,): return self.grid.y @property def f(self,): return self.grid.f @property def n_p(self,): return self.grid.n_p @property def n_z(self,): return self.grid.n_z @property def n_y(self,): return self.grid.n_y @property def n_f(self,): return self.grid.n_f @property def dt(self,): return self.grid.dt
def _parse_inputs(n, l, d, plus_one=1): """ Parse inputs that describe a grid dimension. Parameters ---------- n : int The number of points in that dimension. l : float The total length of that dimension. d : float The spacing between points. plus_one : bool Specifies whether or not the number of points includes an endpoint, or not. This should be 1 for spatial inputs, and 0 for time inputs. Returns ------- n : int number of points l : float length of dimension d : float delta between points Notes ----- Any one of `n`,`l`,`d` may be 'None', in which case it is computed from the other two. If all three are specified the value of `d` is disregaurded and computed from `n` and `l`. """ if (n is None) + (l is None) + (d is None) > 1: raise Exception('Invalid inputs to Grid Initialization.') if n is None: d = ts_float(d) n = int(l / d) + plus_one l = (n - plus_one) * d elif l is None: d = ts_float(d) l = (n - plus_one) * d else: # Always override d if the other two are specified. l = ts_float(l) d = l / (n - plus_one) return n, l, d
[docs]def tsGrid(center=None, ny=None, nz=None, width=None, height=None, dy=None, dz=None, nt=None, time_sec=None, time_min=None, dt=None, time_sec_out=None, findClose_nt_lowPrimeFactors=True, prime_max=31, clockwise=True): """ Create a TurbSim grid. Parameters ---------- center : float height of the center of the grid. ny, nz : int, optional* number of points in the y and z directions. width,height : float, optional* total width and height of grid. dy, dz : float, optional* spacing between points in the y and z directions. nt : int, optional* number of timesteps time_sec : float, optional* length of run (seconds) time_min : float, optional* length of run (minutes, subordinate to time_sec) dt : float, optional* timestep (seconds, subordinate to nt and time_sec) time_sec_out : float, optional (`time_sec`) length of output timeseries in seconds, must be >=`time_sec` findClose_nt_lowPrimeFactors : bool, optional (True) Adjust nfft to be a multiple of low primes? prime_max : int, optional (31) The maximum prime number allowed as a 'low prime'. clockwise : bool, optional (True) Should the simulation write a 'clockwise' rotation output file. This is only used when writing 'Bladed' output files. Notes ----- * Each grid dimension (z,y,time) can be specified by any combination of 2 inputs. For the y-grid, for example, you may specify: dy and ny, or width and dy, or width and ny. If all three are specified, dy ignored and computed from ny and width. """ out = gridObj() if center is None: raise TypeError( "tsGrid objects require that the height of the grid center \ (input parameter 'center') be specified.") if time_sec is None and time_min is not None: time_sec = time_min * 60. if time_sec_out is None and time_sec is not None: time_sec_out = time_sec if not (time_sec_out is None or time_sec is None): time_sec = max(time_sec_out, time_sec) n_y, width, dy = _parse_inputs(ny, width, dy) n_z, height, dz = _parse_inputs(nz, height, dz) out.y = np.arange(-width / 2, width / 2 + dy / 10, dy, dtype=ts_float) out.z = center + np.arange(-height / 2, height / 2 + dz / 10, dz, dtype=ts_float) out.n_t, out.time_sec, out.dt = _parse_inputs(nt, time_sec, dt, plus_one=0) if time_sec_out is None: time_sec_out = out.time_sec (out.n_t_out, time_sec_out, junk) = _parse_inputs(None, time_sec_out, dt, plus_one=0) if findClose_nt_lowPrimeFactors: out.n_t = lowPrimeFact_near(out.n_t, nmin=out.n_t_out, pmax=prime_max) out.n_t, out.time_sec, junk = _parse_inputs( out.n_t, None, out.dt, plus_one=0) out.f = np.arange(out.n_f, dtype=ts_float) * out.df + out.df # !!!CHECKTHIS return out
[docs]class gridObj(tsBaseObj): """ The base 'grid' class. In general, functions should be used to construct the grid. Notes ----- 1) The grid is defined so that the first row is the bottom and the last is the top. 2) Irregular grids are not yet supported. """ clockwise = True n_tower = 0 # This is a placeholder. def __getitem__(self, ind): if hasattr(ind, '__len__'): if len(ind) == 1: iy = ind[0] iz = slice(None) elif len(ind) == 2: iy, iz = ind else: raise Exception( "Indices for accessing grids must have len==1 or 2.") else: iy = ind iz = slice(None) out = type(self)() out.n_t = self.n_t out.n_t_out = self.n_t_out out.dt = self.dt out.f = self.f out.time_sec = self.time_sec out.y = self.y[iy] out.z = self.z[iz] return out def __repr__(self,): return ('<TurbSim Grid:%5.1fm high x %0.1fm wide grid (%d x %d points)' ', centered at %0.1fm.\n %5.1fsec simulation, dt=%0.1fsec ' '(%d timesteps).>' % (self.height, self.width, self.n_z, self.n_y, self.center, self.time_sec, self.dt, self.n_t)) @property def center(self,): return (self.z[-1] + self.z[0]) / 2 @property def time_sec_out(self,): return self.dt * self.n_t_out @property def width(self,): return self.y[-1] - self.y[0] @property def height(self,): return self.z[-1] - self.z[0] @property def dz(self,): return self.height / (self.n_z - 1) @property def dy(self,): return self.width / (self.n_y - 1) @property def n_z(self): return len(self.z) @property def n_y(self): return len(self.y) @property def n_f(self,): return self.n_t / 2 @property def df(self,): return 1. / self.time_sec @property def n_p(self,): return self.n_y * self.n_z @property def ihub(self,): return (self.n_z / 2, self.n_y / 2) @property def shape(self,): """ The grid shape: (n_z, n_y). """ return [self.n_z, self.n_y] @property def shape_wf(self,): """ The grid shape, including frequency (n_z, n_y, n_f). """ return [self.n_z, self.n_y, self.n_f]
[docs] def dist(self, ii, jj): """ Compute the distance between the points `ii` and `jj`. Parameters ---------- ii : Index of first grid-point. jj : Index of second grid-point. Returns ------- r : The distance between the two grid points. Notes ----- Each input index can either be a grid-point pair (e.g. a tuple, indicating the grid-point, or a linear index such as would be returned by :attr:`sub2ind`). """ if not hasattr(ii, '__len__'): ii = self.ind2sub(ii) if not hasattr(jj, '__len__'): jj = self.ind2sub(jj) return np.sqrt((self.y[ii[1]] - self.y[jj[1]]) ** ts_float(2) + (self.z[ii[0]] - self.z[jj[0]]) ** ts_float(2))
@property def zhub(self,): """ The height of the hub. """ if not hasattr(self, '_zhub'): self._zhub = self.center return self._zhub @zhub.setter def zhub(self, val): self._zhub = val @property def rotor_diam(self,): """ Return the min of the grid width and height. This is how TurbSim quantifies rotor diameter. """ return min(self.width, self.height) ## def ind2sub(self,ind): ## """ ## Return the subscripts (iz,iy) corresponding to the ## `flattened' index *ind* (column-order) for this grid. ## """ ## iy=ind/self.n_z ## if iy>=self.n_y: ## raise IndexError('Index beyond range of grid.') ## iz=np.mod(ind,self.n_z) ## return (iz,iy) ## def sub2ind(self,subs): ## """ ## Return the `flattened' index (column-order) corresponding ## to the subscript *subs* (iz,iy) for this grid. ## """ ## return subs[1]*self.n_z+subs[0] ## def flatten(self,arr): ## """ ## Reshape an array so that the z-y grid points are ## one-dimension of the array (for Cholesky factorization). ## """ ## if arr.ndim>2 and arr.shape[0]==3 and ## arr.shape[1]==self.n_z and arr.shape[2]==self.n_y: ## shp=[3,self.n_p]+list(arr.shape[3:]) ## elif arr.shape[0]==self.n_z and arr.shape[1]==self.n_y: ## shp=[self.n_p]+list(arr.shape[2:]) ## else: ## raise ValueError('The array shape does not match this grid.') ## return arr.reshape(shp,order='F') ## def reshape(self,arr): ## """ ## Reshape the array *arr* so that its z-y grid points are ## two-dimensions of the array (after Cholesky factorization). ## """ ## if arr.shape[0]==3 and arr.shape[1]==self.n_p: ## shp=[3,self.n_z,self.n_y]+list(arr.shape[2:]) ## elif arr.shape[0]==self.n_p: ## shp=[self.n_z,self.n_y]+list(arr.shape[1:]) ## else: ## raise ValueError('The array shape does not match this grid.') ## return arr.reshape(shp,order='F')
[docs] def ind2sub(self, ind): """ Return the subscripts (iz,iy) corresponding to the 'flattened' index `ind` (row/C-order) for this grid. """ iz = ind / self.n_y if iz >= self.n_z: raise IndexError('Index beyond range of grid.') iy = np.mod(ind, self.n_y) return (iz, iy)
[docs] def sub2ind(self, subs): """ Return the 'flattened' index (row/C-order) corresponding to the subscript `subs` (iz,iy) for this grid. """ if subs[0] < 0: subs = (self.n_z + subs[0], subs[1]) if subs[1] < 0: subs = (subs[0], self.n_y + subs[1]) return subs[0] * self.n_y + subs[1]
[docs] def flatten(self, arr): """ Reshape `arr` so that the z-y grid points are one-dimension of the array (e.g. prior to Cholesky factorization). """ if ( arr.ndim > 2 and arr.shape[0] == 3 and arr.shape[1] == self.n_z and arr.shape[2] == self.n_y ): shp = [3, self.n_p] + list(arr.shape[3:]) elif arr.shape[0] == self.n_z and arr.shape[1] == self.n_y: shp = [self.n_p] + list(arr.shape[2:]) else: raise ValueError('The array shape does not match this grid.') return arr.reshape(shp, order='C')
[docs] def reshape(self, arr): """ Reshape `arr` so that its z-y grid points are two-dimensions of the array. """ if arr.shape[0] == 3 and arr.shape[1] == self.n_p: shp = [3, self.n_z, self.n_y] + list(arr.shape[2:]) elif arr.shape[0] == self.n_p: shp = [self.n_z, self.n_y] + list(arr.shape[1:]) else: raise ValueError('The array shape does not match this grid.') return arr.reshape(shp, order='C')
[docs]class modelBase(tsBaseObj): """ An abstract base class for all TurbSim models. """ @property def model_name(self,): """The model name is the class definition name""" return str(self.__class__).rsplit('.', 1)[-1].rstrip("'>") @property def model_desc(self,): """The model description is the first line of the docstring.""" return self.__doc__.splitlines()[0].rstrip('.') @property def parameters(self,): """ This property stores information about the TurbSim model initialization variables, for writing to summary files. This functionality is not yet implemented, and this is a placeholder for now. """ return dict(self.__dict__) def _sumfile_string(self, tsrun, ): return "## No '_sumfile_string' defined for %s ##\n" % (str(self.__class__))