def test_exceptions(wind_ts, invalid_rq): """ test exceptions """ with pytest.raises(ValueError): # incorrect dtype to_time_value_pair(wind_ts['tv'], ts_format.magnitude_direction) with pytest.raises(ValueError): # incorrect format to_time_value_pair(wind_ts['dtv_rq'], -1) to_datetime_value_2d(wind_ts['tv'], -1) with pytest.raises(ValueError): # string input can only be 'r-theta' or 'uv' to_time_value_pair(wind_ts['dtv_rq'], 'magnitude') to_datetime_value_2d(wind_ts['tv'], 'magnitude') # following also raises ValueError. This gives invalid (r,theta) inputs # which are rejected by the transforms.r_theta_to_uv_wind method. # It tests the inner exception is correct with pytest.raises(ValueError): length_of_dim1 = len(invalid_rq['rq']) zero_times = [zero_time()] * length_of_dim1 invalid_dtv_rq = np.array(zip(zero_times, invalid_rq['rq']), dtype=datetime_value_2d) invalid_dtv_rq['value'] = invalid_rq['rq'] to_time_value_pair(invalid_dtv_rq, ts_format.magnitude_direction)
def test_exceptions(wind_ts, invalid_rq): """ test exceptions """ with pytest.raises(ValueError): # incorrect dtype to_time_value_pair(wind_ts['tv'], ts_format.magnitude_direction) with pytest.raises(ValueError): # incorrect format to_time_value_pair(wind_ts['dtv_rq'], -1) to_datetime_value_2d(wind_ts['tv'], -1) with pytest.raises(ValueError): # string input can only be 'r-theta' or 'uv' to_time_value_pair(wind_ts['dtv_rq'], 'magnitude') to_datetime_value_2d(wind_ts['tv'], 'magnitude') # following also raises ValueError. This gives invalid (r,theta) inputs # which are rejected by the transforms.r_theta_to_uv_wind method. # It tests the inner exception is correct with pytest.raises(ValueError): invalid_dtv_rq = np.zeros((len(invalid_rq['rq']), ), dtype=datetime_value_2d) invalid_dtv_rq['value'] = invalid_rq['rq'] to_time_value_pair(invalid_dtv_rq, ts_format.magnitude_direction)
def set_timeseries(self, datetime_value_2d, coord_sys='uv'): """ Sets the timeseries to the new value given by a numpy array. The coordinate system for the input data defaults to basic_types.format.magnitude_direction but can be changed by the user. Assumes timeseries is valid so _check_timeseries has been invoked and any unit conversions are done. This function simply converts datetime_value_2d to time_value_pair and updates the data in underlying cython/C++ object :param datetime_value_2d: timeseries of wind data defined in a numpy array :type datetime_value_2d: numpy array of dtype basic_types.datetime_value_2d :param coord_sys: output coordinate system for the times series, as defined by basic_types.ts_format. :type coord_sys: either string or integer value defined by basic_types.ts_format.* (see cy_basic_types.pyx) """ datetime_value_2d = self._xform_input_timeseries(datetime_value_2d) timeval = to_time_value_pair(datetime_value_2d, coord_sys) self.ossm.timeseries = timeval
def test_to_time_value_pair_from_1d(): data = np.zeros((4,), dtype=datetime_value_1d) data['value'] = np.random.uniform(1, 10, len(data)).reshape(-1, 1) out_tv = to_time_value_pair(data) assert np.all(out_tv['value']['v'] == 0.0) assert np.all(out_tv['value']['u'] == data['value'].reshape(-1))
def test_to_time_value_pair(wind_ts, in_ts_format): out_tv = (to_time_value_pair(wind_ts['dtv_rq'], in_ts_format) .view(dtype=np.recarray)) # assert np.all(wind_ts['tv'].time == out_tv.time) assert np.allclose(wind_ts['tv'].value.u, out_tv.value.u, atol, rtol) assert np.allclose(wind_ts['tv'].value.v, out_tv.value.v, atol, rtol)
def test_to_time_value_pair(wind_ts, in_ts_format): out_tv = (to_time_value_pair(wind_ts['dtv_rq'], in_ts_format).view(dtype=np.recarray)) # assert np.all(wind_ts['tv'].time == out_tv.time) assert np.allclose(wind_ts['tv'].value.u, out_tv.value.u, atol, rtol) assert np.allclose(wind_ts['tv'].value.v, out_tv.value.v, atol, rtol)
def set_timeseries( self, datetime_value_2d, units, format='r-theta', ): """ Sets the timeseries of the Wind object to the new value given by a numpy array. The format for the input data defaults to basic_types.format.magnitude_direction but can be changed by the user :param datetime_value_2d: timeseries of wind data defined in a numpy array :type datetime_value_2d: numpy array of dtype basic_types.datetime_value_2d :param units: units associated with the data. Valid units defined in Wind.valid_vel_units list :param format: output format for the times series; as defined by basic_types.format. :type format: either string or integer value defined by basic_types.format.* (see cy_basic_types.pyx) """ self._check_units(units) self._check_timeseries(datetime_value_2d, units) datetime_value_2d['value'] = \ self._convert_units(datetime_value_2d['value'], format, units, 'meter per second') timeval = convert.to_time_value_pair(datetime_value_2d, format) self.ossm.timeseries = timeval
def test_to_time_value_pair_from_1d(): length_of_dim1 = 4 zero_times = [sec_to_date(zero_time())] * length_of_dim1 rand_data = np.random.uniform(1, 10, length_of_dim1) data = np.array(zip(zero_times, rand_data), dtype=datetime_value_1d) out_tv = to_time_value_pair(data) assert np.all(out_tv['value']['v'] == 0.0) assert np.all(out_tv['value']['u'] == data['value'])
def _convert_to_time_value_pair(self, datetime_value_2d): ''' fmt datetime_value_2d so it is a numpy array with dtype=basic_types.time_value_pair as the C++ code expects ''' # following fails for 0-d objects so make sure we have a 1-D array # to work with datetime_value_2d = np.asarray(datetime_value_2d, dtype=basic_types.datetime_value_2d) if datetime_value_2d.shape == (): datetime_value_2d = np.asarray([datetime_value_2d], dtype=basic_types.datetime_value_2d) timeval = to_time_value_pair(datetime_value_2d, "uv") return timeval
def _get_timeseries_from_cpp(windmover): """ local method for tests - returns the timeseries used internally by the C++ WindMover_c object. This should be the same as the timeseries stored in the self.wind object Data is returned as a datetime_value_2d array in units of meter per second in format = uv This is simply used for testing. """ dtv = windmover.wind.get_wind_data(format=ts_format.uv) tv = convert.to_time_value_pair(dtv, ts_format.uv) val = windmover.mover.get_time_value(tv['time']) tv['value']['u'] = val['u'] tv['value']['v'] = val['v'] return convert.to_datetime_value_2d(tv, ts_format.uv)
def new_set_timeseries(self, value, coord_sys): if self._check_timeseries(value): units = self.units wind_data = self._xform_input_timeseries(value) self._timeseries = wind_data.copy() wind_data['value'] = self._convert_units(wind_data['value'], coord_sys, units, 'meter per second') datetime_value_2d = self._xform_input_timeseries(wind_data) timeval = to_time_value_pair(wind_data, coord_sys) self.ossm.timeseries = timeval if not hasattr(self, '_time') or self._time is None: self._time = Time() self.time.data = self._timeseries['time'].astype(datetime.datetime) else: raise ValueError('Bad timeseries as input')
def _convert_to_time_value_pair(self, datetime_value_2d): ''' fmt datetime_value_2d so it is a numpy array with dtype=basic_types.time_value_pair as the C++ code expects ''' # following fails for 0-d objects so make sure we have a 1-D array # to work with datetime_value_2d = np.asarray(datetime_value_2d, dtype=basic_types.datetime_value_2d) if datetime_value_2d.shape == (): datetime_value_2d = np.asarray([datetime_value_2d], dtype=basic_types.datetime_value_2d) # self._check_units(units) # self._check_timeseries(datetime_value_2d, units) # datetime_value_2d['value'] = \ # self._convert_units(datetime_value_2d['value'], # fmt, units, 'meter per second') timeval = to_time_value_pair(datetime_value_2d, "uv") return timeval
def _convert_to_time_value_pair(self, datetime_value_2d, units, format): ''' format datetime_value_2d so it is a numpy array with dtype=basic_types.time_value_pair as the C++ code expects ''' # following fails for 0-d objects so make sure we have a 1-D array # to work with datetime_value_2d = np.asarray(datetime_value_2d, dtype=basic_types.datetime_value_2d) if datetime_value_2d.shape == (): datetime_value_2d = np.asarray([datetime_value_2d], dtype=basic_types.datetime_value_2d) self._check_units(units) self._check_timeseries(datetime_value_2d, units) datetime_value_2d['value'] = \ self._convert_units(datetime_value_2d['value'], format, units, 'meter per second') timeval = to_time_value_pair(datetime_value_2d, format) return timeval
def __init__(self, timeseries=None, filename=None, format='uv'): """ Initializes a timeseries object from either a timeseries or datafile containing the timeseries. If both timeseries and file are given, it will read data from the file If neither are given, timeseries gets initialized as: timeseries = np.zeros((1,), dtype=basic_types.datetime_value_2d) If user provides timeseries, the default format is 'uv'. The C++ stores the data in 'uv' format - transformations are done in this Python code (set_timeseries(), get_timeseries()). C++ code only transforms the data from 'r-theta' to 'uv' format if data is read from file. And this happens during initialization because C++ stores data in 'uv' format internally. Units option are not included - let derived classes manage units since the units for CyTimeseries (OSSMTimeValue_c) are limited. No unit conversion is performed when get_timeseries, set_timeseries is invoked. It does, however convert between 'uv' and 'r-theta' depending on format specified. Choose format='uv' if no transformation is desired. .. note:: For the Wind datafiles, the units will get read from the file. These are stored in ossm.user_units. It would be ideal to remove units and unit conversion from here, but can't completely do away with it since C++ file reading uses/sets it. But, managing units is responsibility of derived objects. All other keywords are optional :param timeseries: numpy array containing time_value_pair :type timeseries: numpy.ndarray containing basic_types.datetime_value_2d or basic_types.datetime_value_1d. It gets converted to an array containging basic_types.time_value_pair datatype since that's what the C++ code expects :param filename: path to a timeseries file from which to read data. Datafile must contain either a 3 line or a 5 line header with following info: 1. Station Name: name of the station as a string 2. (long, lat, z): station location as tuple containing floats 3. units: for wind this is knots, meteres per second or miles per hour. For datafile containing something other than velocity, this should be 'undefined' Optional parameters (kwargs): :param format: (Optional) default timeseries format is magnitude direction: 'r-theta' :type format: string 'r-theta' or 'uv'. Default is 'r-theta'. Converts string to integer defined by gnome.basic_types.ts_format.* TODO: 'format' is a python builtin keyword. We should not use it as an argument name """ if (timeseries is None and filename is None): timeseries = np.array([(sec_to_date(zero_time()), [0.0, 0.0])], dtype=basic_types.datetime_value_2d) self._filename = filename if filename is None: # will raise an Exception if it fails self._check_timeseries(timeseries) datetime_value_2d = self._xform_input_timeseries(timeseries) time_value_pair = to_time_value_pair(datetime_value_2d, format) self.ossm = CyTimeseries(timeseries=time_value_pair) else: ts_format = tsformat(format) self.ossm = CyTimeseries(filename=self._filename, file_format=ts_format)
def __init__(self, timeseries=None, filename=None, format='uv'): """ Initializes a timeseries object from either a timeseries or datafile containing the timeseries. If both timeseries and file are given, it will read data from the file If neither are given, timeseries gets initialized as: timeseries = np.zeros((1,), dtype=basic_types.datetime_value_2d) If user provides timeseries, the default format is 'uv'. The C++ stores the data in 'uv' format - transformations are done in this Python code (set_timeseries(), get_timeseries()). C++ code only transforms the data from 'r-theta' to 'uv' format if data is read from file. And this happens during initialization because C++ stores data in 'uv' format internally. Units option are not included - let derived classes manage units since the units for CyTimeseries (OSSMTimeValue_c) are limited. No unit conversion is performed when get_timeseries, set_timeseries is invoked. It does, however convert between 'uv' and 'r-theta' depending on format specified. Choose format='uv' if no transformation is desired. .. note:: For the Wind datafiles, the units will get read from the file. These are stored in ossm.user_units. It would be ideal to remove units and unit conversion from here, but can't completely do away with it since C++ file reading uses/sets it. But, managing units is responsibility of derived objects. All other keywords are optional :param timeseries: numpy array containing time_value_pair :type timeseries: numpy.ndarray containing basic_types.datetime_value_2d or basic_types.datetime_value_1d. It gets converted to an array containging basic_types.time_value_pair datatype since that's what the C++ code expects :param filename: path to a timeseries file from which to read data. Datafile must contain either a 3 line or a 5 line header with following info: 1. Station Name: name of the station as a string 2. (long, lat, z): station location as tuple containing floats 3. units: for wind this is knots, meteres per second or miles per hour. For datafile containing something other than velocity, this should be 'undefined' Optional parameters (kwargs): :param format: (Optional) default timeseries format is magnitude direction: 'r-theta' :type format: string 'r-theta' or 'uv'. Default is 'r-theta'. Converts string to integer defined by gnome.basic_types.ts_format.* TODO: 'format' is a python builtin keyword. We should not use it as an argument name """ if (timeseries is None and filename is None): timeseries = np.array([(sec_to_date(zero_time()), [0.0, 0.0])], dtype=basic_types.datetime_value_2d) self._filename = filename if filename is None: if self._check_timeseries(timeseries): datetime_value_2d = self._xform_input_timeseries(timeseries) time_value_pair = to_time_value_pair(datetime_value_2d, format) self.ossm = CyTimeseries(timeseries=time_value_pair) else: raise ValueError('Bad timeseries as input') else: ts_format = tsformat(format) self.ossm = CyTimeseries(filename=self._filename, file_format=ts_format)
def __init__(self, **kwargs): """ Initializes a wind object. It only takes keyword arguments as input, these are defined below. Invokes super(Wind,self).__init__(\*\*kwargs) for parent class initialization It requires one of the following to initialize: 1. 'timeseries' along with 'units' or 2. a 'filename' containing a header that defines units amongst other meta data All other keywords are optional. Optional parameters (kwargs): :param timeseries: (Required) numpy array containing time_value_pair :type timeseries: numpy.ndarray[basic_types.time_value_pair, ndim=1] :param filename: path to a long wind file from which to read wind data :param units: units associated with the timeseries data. If 'filename' is given, then units are read in from the file. get_timeseries() will use these as default units to output data, unless user specifies otherwise. These units must be valid as defined in the hazpy unit_conversion module: unit_conversion.GetUnitNames('Velocity') :type units: string, for example: 'knot', 'meter per second', 'mile per hour' etc. Default units for input/output timeseries data :param format: (Optional) default timeseries format is magnitude direction: 'r-theta' :type format: string 'r-theta' or 'uv'. Converts string to integer defined by gnome.basic_types.ts_format.* TODO: 'format' is a python builtin keyword :param name: (Optional) human readable string for wind object name. Default is filename if data is from file or "Wind Object" :param source_type: (Optional) Default is undefined, but can be one of the following: ['buoy', 'manual', 'undefined', 'file', 'nws'] If data is read from file, then it is 'file' :param latitude: (Optional) latitude of station or location where wind data is obtained from NWS :param longitude: (Optional) longitude of station or location where wind data is obtained from NWS Remaining kwargs ('id' if present) are passed onto Environment's __init__ using super. See base class documentation for remaining valid kwargs. """ if 'timeseries' in kwargs and 'filename' in kwargs: raise TypeError('Cannot instantiate Wind object with both timeseries and file as input' ) if 'timeseries' not in kwargs and 'filename' not in kwargs: raise TypeError('Either provide a timeseries or a wind file with a header, containing wind data' ) # default lat/long - can these be set from reading data in the file? self.longitude = None self.latitude = None # format of data 'uv' or 'r-theta'. Default is 'r-theta' # TODO: 'format' is a python builtin keyword format = kwargs.pop('format', 'r-theta') self.description = kwargs.pop('description', 'Wind Object') if 'timeseries' in kwargs: if 'units' not in kwargs: raise TypeError("Provide 'units' argument with the 'timeseries' input" ) timeseries = kwargs.pop('timeseries') units = kwargs.pop('units') self._check_units(units) self._check_timeseries(timeseries, units) timeseries['value'] = self._convert_units(timeseries['value' ], format, units, 'meter per second') # ts_format is checked during conversion time_value_pair = convert.to_time_value_pair(timeseries, format) # this has same scope as CyWindMover object self.ossm = CyOSSMTime(timeseries=time_value_pair) # do not set ossm.user_units since that only has a subset of # possible units self._user_units = units self.name = kwargs.pop('name', 'Wind Object') self.source_type = (kwargs.pop('source_type' ) if kwargs.get('source_type') in basic_types.wind_datasource._attr else 'undefined' ) else: ts_format = convert.tsformat(format) self.ossm = CyOSSMTime(filename=kwargs.pop('filename'), file_contains=ts_format) self._user_units = self.ossm.user_units self.name = kwargs.pop('name', os.path.split(self.ossm.filename)[1]) self.source_type = 'file' # this must be file # For default: if read from file and filename exists, # then use last modified time of file # else # default to datetime.datetime.now # not sure if this should be datetime or string self.updated_at = kwargs.pop('updated_at', (time_utils.sec_to_date(os.path.getmtime(self.ossm.filename)) if self.ossm.filename else datetime.datetime.now())) self.source_id = kwargs.pop('source_id', 'undefined') self.longitude = kwargs.pop('longitude', self.longitude) self.latitude = kwargs.pop('latitude', self.latitude) super(Wind, self).__init__(**kwargs)
def __init__( self, filename=None, timeseries=None, yeardata=os.path.join(os.path.dirname(gnome.__file__), 'data', 'yeardata'), **kwargs ): """ Tide information can be obtained from a filename or set as a timeseries (timeseries is NOT TESTED YET) Invokes super(Tides,self).__init__(\*\*kwargs) for parent class initialization It requires one of the following to initialize: 1. 'timeseries' assumed to be in 'uv' format (NOT TESTED/IMPLEMENTED OR USED YET) 2. a 'filename' containing a header that defines units amongst other meta data :param timeseries: numpy array containing datetime_value_2d, ts_format is always 'uv' :type timeseries: numpy.ndarray[basic_types.time_value_pair, ndim=1] :param units: units associated with the timeseries data. If 'filename' is given, then units are read in from the filename. unit_conversion - NOT IMPLEMENTED YET :type units: (Optional) string, for example: 'knot', 'meter per second', 'mile per hour' etc Default is None for now :param filename: path to a long wind filename from which to read wind data :param yeardata: (Optional) path to yeardata used for Shio data filenames. Default location is gnome/data/yeardata/ Remaining kwargs ('id' if present) are passed onto Environment's __init__ using super. See base class documentation for remaining valid kwargs. """ # define locally so it is available even for OSSM files, # though not used by OSSM files self._yeardata = None if timeseries is None and filename is None: raise ValueError('Either provide timeseries or a valid filename containing Tide data' ) if timeseries is not None: # if units is None: # raise ValueError("Provide valid units as string or unicode " \ # "for timeseries") # will probably need to move this function out # self._check_timeseries(timeseries, units) # data_format is checked during conversion time_value_pair = convert.to_time_value_pair(timeseries, convert.tsformat('uv')) # this has same scope as CyWindMover object self.cy_obj = CyOSSMTime(timeseries=time_value_pair) # not sure what these should be self._user_units = kwargs.pop('units', None) else: # self.filename = os.path.abspath( filename) self.cy_obj = self._obj_to_create(filename) # self.yeardata = os.path.abspath( yeardata ) # set yeardata self.yeardata = yeardata # set yeardata super(Tide, self).__init__(**kwargs)