Example #1
0
 def test__set_time_value_handle_none(self):
     """Check TypeError exception for private method"""
     try:
         ossm = CyTimeseries(timeseries=self.tval)
         ossm._set_time_value_handle(None)
     except TypeError:
         assert True
Example #2
0
    def test_readfile_constant_wind(self):
        """
        Read contents for a filename that contains a constant wind.
        This will be just 1 line in the text filename.
        Test get_time_values method. It gets the time value pair
        for the constant wind per the data filename.
        This test just gets the time value pair that was created
        from the filename. It then invokes get_time_value for that time
        in the time series and also looks at the velocity 100 sec later.
        Since wind is constant, the value should be unchanged
        """
        ossmT = CyTimeseries(
            filename=testdata["timeseries"]["wind_constant"], file_format=ts_format.magnitude_direction
        )
        # Let's see what is stored in the Handle to expected result
        t_val = ossmT.timeseries

        actual = np.array(t_val["value"], dtype=velocity_rec)
        time = np.array(t_val["time"] + (0, 100), dtype=seconds)

        vel_rec = ossmT.get_time_value(time)

        tol = 1e-6
        msg = "{0} is not within a tolerance of " "{1}".format("get_time_value", tol)
        for vel in vel_rec:
            np.testing.assert_allclose(vel["u"], actual["u"], tol, tol, msg, 0)
            np.testing.assert_allclose(vel["v"], actual["v"], tol, tol, msg, 0)
Example #3
0
 def test__set_time_value_handle_none(self):
     """Check TypeError exception for private method"""
     try:
         ossm = CyTimeseries(timeseries=self.tval)
         ossm._set_time_value_handle(None)
     except TypeError:
         assert True
Example #4
0
    def test_timeseries(self):
        """
        test setting the timeseries using timeseries property
        """
        ossmT = CyTimeseries(timeseries=self.tval)
        t_val = ossmT.timeseries

        print 't_val before:', t_val['value']
        # need to learn how to do the following in 1 line of code
        t_val['value']['u'] += 2
        t_val['value']['v'] += 2
        print 't_val after:', t_val['value']

        ossmT.timeseries = t_val
        new_val = ossmT.timeseries

        tol = 1e-10
        msg = ('{0} is not within a tolerance of '
               '{1}'.format('get_time_value', tol))
        np.testing.assert_allclose(t_val['time'], new_val['time'], tol, tol,
                                   msg, 0)
        np.testing.assert_allclose(t_val['value']['u'], new_val['value']['u'],
                                   tol, tol, msg, 0)
        np.testing.assert_allclose(t_val['value']['v'], new_val['value']['v'],
                                   tol, tol, msg, 0)
Example #5
0
    def test_move_out_of_bounds(self):
        '''
            Our wind mover should fail in the prepare_for_model_step() function
            if our wind time series is out of bounds with respect to the
            model time we are preparing for, unless our wind time series is
            configured to extrapolate wind values.
        '''
        # setup a time series that's out of range of our model time
        time_val = np.zeros((2, ), dtype=time_value_pair)
        (time_val['time'])[:] = np.add([3600, 7200], self.cm.model_time)
        (time_val['value']['v'])[:] = [100, 200]

        # extrapolation should be off by default
        ossm = CyTimeseries(timeseries=time_val)
        self.wm.set_ossm(ossm)

        # this should fail because our time series is not set to extrapolate
        with raises(OSError):
            self.wm.prepare_for_model_step(self.cm.model_time,
                                           self.cm.time_step)

        ossm = CyTimeseries(timeseries=time_val, extrapolation_is_allowed=True)
        self.wm.set_ossm(ossm)

        # We set our time series to extrapolate, so this should pass
        self.wm.prepare_for_model_step(self.cm.model_time,
                                       self.cm.time_step)

        # clean up our time series
        self.wm.set_ossm(self.ossm)
    def test_timeseries(self):
        """
        test setting the timeseries using timeseries property
        """
        ossmT = CyTimeseries(timeseries=self.tval)
        t_val = ossmT.timeseries

        print 't_val before:', t_val['value']
        # need to learn how to do the following in 1 line of code
        t_val['value']['u'] += 2
        t_val['value']['v'] += 2
        print 't_val after:', t_val['value']

        ossmT.timeseries = t_val
        new_val = ossmT.timeseries

        tol = 1e-10
        msg = ('{0} is not within a tolerance of '
               '{1}'.format('get_time_value', tol))
        np.testing.assert_allclose(t_val['time'], new_val['time'],
                                   tol, tol, msg, 0)
        np.testing.assert_allclose(t_val['value']['u'], new_val['value']['u'],
                                   tol, tol, msg, 0)
        np.testing.assert_allclose(t_val['value']['v'], new_val['value']['v'],
                                   tol, tol, msg, 0)
Example #7
0
    def test_readfile_constant_wind(self):
        """
        Read contents for a filename that contains a constant wind.
        This will be just 1 line in the text filename.
        Test get_time_values method. It gets the time value pair
        for the constant wind per the data filename.
        This test just gets the time value pair that was created
        from the filename. It then invokes get_time_value for that time
        in the time series and also looks at the velocity 100 sec later.
        Since wind is constant, the value should be unchanged
        """
        ossmT = CyTimeseries(filename=testdata['timeseries']['wind_constant'],
                             file_format=ts_format.magnitude_direction)
        # Let's see what is stored in the Handle to expected result
        t_val = ossmT.timeseries

        actual = np.array(t_val['value'], dtype=velocity_rec)
        time = np.array(t_val['time'] + (0, 100), dtype=seconds)

        vel_rec = ossmT.get_time_value(time)

        tol = 1e-6
        msg = ('{0} is not within a tolerance of '
               '{1}'.format('get_time_value', tol))
        for vel in vel_rec:
            np.testing.assert_allclose(vel['u'], actual['u'], tol, tol, msg, 0)
            np.testing.assert_allclose(vel['v'], actual['v'], tol, tol, msg, 0)
    def test_get_time_out_of_bounds(self):
        time_values = np.array([(1, (1, 2)), (2, (2, 3))],
                               dtype=basic_types.time_value_pair)

        ossm = CyTimeseries(timeseries=time_values)

        # testing before the start time
        with pytest.raises(IndexError):
            _vel_rec, _err = ossm.get_time_value(np.array([0], dtype=seconds))

        # testing after the end time
        with pytest.raises(IndexError):
            _vel_rec, _err = ossm.get_time_value(np.array([3], dtype=seconds))

        # now we allow extrapolation
        ossm.extrapolation_is_allowed = True

        begin_value, _err = ossm.get_time_value(np.array([1], dtype=seconds))
        end_value, _err = ossm.get_time_value(np.array([2], dtype=seconds))

        # before the start time should now return without an exception
        # and it should return the value associated with the start time.
        vel_rec, _err = ossm.get_time_value(np.array([0], dtype=seconds))
        assert vel_rec == begin_value

        # after the end time should now return without an exception
        # and it should return the value associated with the end time.
        vel_rec, _err = ossm.get_time_value(np.array([3], dtype=seconds))
        assert vel_rec == end_value
Example #9
0
    def test_get_time_value(self):
        ossm = CyTimeseries(timeseries=self.tval)

        actual = np.array(self.tval["value"], dtype=velocity_rec)
        time = np.array(self.tval["time"], dtype=seconds)
        vel_rec = ossm.get_time_value(time)
        print vel_rec

        tol = 1e-6
        msg = "{0} is not within a tolerance of " "{1}".format("get_time_value", tol)
        np.testing.assert_allclose(vel_rec["u"], actual["u"], tol, tol, msg, 0)
        np.testing.assert_allclose(vel_rec["v"], actual["v"], tol, tol, msg, 0)
Example #10
0
    def test_get_time_value(self):
        ossm = CyTimeseries(timeseries=self.tval)

        actual = np.array(self.tval['value'], dtype=velocity_rec)
        time = np.array(self.tval['time'], dtype=seconds)
        vel_rec = ossm.get_time_value(time)
        print vel_rec

        tol = 1e-6
        msg = ('{0} is not within a tolerance of '
               '{1}'.format('get_time_value', tol))
        np.testing.assert_allclose(vel_rec['u'], actual['u'], tol, tol, msg, 0)
        np.testing.assert_allclose(vel_rec['v'], actual['v'], tol, tol, msg, 0)
Example #11
0
    def test_get_time_value(self):
        ossm = CyTimeseries(timeseries=self.tval)

        actual = np.array(self.tval['value'], dtype=velocity_rec)
        time = np.array(self.tval['time'], dtype=seconds)
        vel_rec, _err = ossm.get_time_value(time)
        print vel_rec

        tol = 1e-6
        msg = ('{0} is not within a tolerance of '
               '{1}'.format('get_time_value', tol))

        np.testing.assert_allclose(vel_rec['u'], actual['u'], tol, tol, msg, 0)
        np.testing.assert_allclose(vel_rec['v'], actual['v'], tol, tol, msg, 0)
Example #12
0
    def _obj_to_create(self, filename):
        """
        open file, read a few lines to determine if it is an ossm file
        or a shio file
        """
        # mode 'U' means universal newline support
        fh = open(filename, 'rU')

        lines = [fh.readline() for i in range(4)]

        if len(lines[1]) == 0:
            # look for \r for lines instead of \n
            lines = string.split(lines[0], '\r', 4)

        if len(lines[1]) == 0:
            # if this is still 0, then throw an error!
            raise ValueError('This does not appear to be a valid file format '
                             'that can be read by OSSM or Shio to get '
                             'tide information')

        # look for following keywords to determine if it is a Shio or OSSM file
        shio_file = ['[StationInfo]', 'Type=', 'Name=', 'Latitude=']

        if all([shio_file[i] == (lines[i])[:len(shio_file[i])]
                for i in range(4)]):
            return CyShioTime(filename)
        elif len(string.split(lines[3], ',')) == 7:
            # maybe log / display a warning that v=0 for tide file and will be
            # ignored
            # if float( string.split(lines[3],',')[-1]) != 0.0:
            return CyTimeseries(filename, file_format=tsformat('uv'))
        else:
            raise ValueError('This does not appear to be a valid file format '
                             'that can be read by OSSM or Shio to get '
                             'tide information')
Example #13
0
class TestObjectSerialization:
    '''
        Test all the serialization and deserialization methods that are
        available to the CyTimeseries object.
    '''
    ossmT = CyTimeseries(filename=testdata['timeseries']['wind_ts'],
                         file_format=ts_format.magnitude_direction)
    wm = CyWindMover()
    wm.set_ossm(ossmT)

    def test_repr(self):
        '''
            Test that the repr method produces a string capable of reproducing
            the object.
        '''
        # This works, but in order to properly eval the repr string, we need
        # the top level gnome module, as well as the numpy 'array' type in the
        # global name space.
        # So before we try it, we first need to import like this:
        import gnome
        from numpy import array

        new_wm = eval(repr(self.wm))

        assert new_wm == self.wm
        assert repr(new_wm) == repr(self.wm)
Example #14
0
    def __init__(self):
        super(ConstantWindWithOSSM, self).__init__()

        time_val = np.empty((1, ), dtype=time_value_pair)
        time_val['time'] = 0  # should not matter
        time_val['value'] = const_wind

        self.ossm = CyTimeseries(timeseries=time_val)
        self.wm.set_ossm(self.ossm)
Example #15
0
    def test_timeseries(self):
        """
        test setting the timeseries using timeseries property
        """
        ossmT = CyTimeseries(timeseries=self.tval)
        t_val = ossmT.timeseries

        print "t_val before:", t_val["value"]
        # need to learn how to do the following in 1 line of code
        t_val["value"]["u"] += 2
        t_val["value"]["v"] += 2
        print "t_val after:", t_val["value"]

        ossmT.timeseries = t_val
        new_val = ossmT.timeseries

        tol = 1e-10
        msg = "{0} is not within a tolerance of " "{1}".format("get_time_value", tol)
        np.testing.assert_allclose(t_val["time"], new_val["time"], tol, tol, msg, 0)
        np.testing.assert_allclose(t_val["value"]["u"], new_val["value"]["u"], tol, tol, msg, 0)
        np.testing.assert_allclose(t_val["value"]["v"], new_val["value"]["v"], tol, tol, msg, 0)
Example #16
0
    def __init__(self, wind=None, timeseries=None, past_hours_to_average=3,
                 **kwargs):
        """
        Initializes a running average object from a wind and past hours
        to average

        If no wind is given, timeseries gets initialized as:

            timeseries = np.zeros((1,), dtype=basic_types.datetime_value_2d)
            units = 'mps'
        (note: probably should be an error)

        All other keywords are optional. Optional parameters (kwargs):

        :param past_hours_to_average: default is 3

        """
        self.units = 'mps'
        self.format = 'uv'
        self._past_hours_to_average = past_hours_to_average
        self.wind = wind

        if (wind is None and timeseries is None):
            mvg_timeseries = np.array([(sec_to_date(zero_time()), [0.0, 0.0])],
                                      dtype=basic_types.datetime_value_2d)
            moving_timeseries = self._convert_to_time_value_pair(mvg_timeseries)

        else:
            if wind is not None:
                moving_timeseries = wind.ossm.create_running_average(self._past_hours_to_average)
            else:
                self.wind = Wind(timeseries, units='mps', format='uv')
                moving_timeseries = self.wind.ossm.create_running_average(self._past_hours_to_average)

        # print "moving_timeseries"
        # print moving_timeseries

        self.ossm = CyTimeseries(timeseries=moving_timeseries)

        super(RunningAverage, self).__init__(**kwargs)
Example #17
0
 def test_init_from_timeseries(self):
     """
     Sets the time series in OSSMTimeValue_c equal to the
     externally supplied numpy array containing time_value_pair data
     It then reads it back to make sure data was set correctly
     """
     ossm = CyTimeseries(timeseries=self.tval)
     t_val = ossm.timeseries
     msg = ('{0}().get_time_value() did not return expected '
            'numpy array').format(ossm.__class__.__name__)
     np.testing.assert_array_equal(t_val, self.tval, msg, 0)
     assert ossm.user_units == 'undefined'  # meters/second
     assert ossm.station_location is None
     assert ossm.station is None
     assert ossm.filename is None
     assert ossm.scale_factor == 1.0
Example #18
0
class TestVariableWind:
    """
    Uses OSSMTimeValue_c to define a variable wind
    - variable wind has 'v' component, so movement
      should only be in 'lat' direction of world point

    Leave as a class as we may add more methods to it for testing
    """
    wm = CyWindMover()
    cm = cy_fixtures.CyTestMove()
    delta = np.zeros((cm.num_le, ), dtype=world_point)

    time_val = np.zeros((2, ), dtype=time_value_pair)
    (time_val['time'])[:] = np.add([0, 3600], cm.model_time)  # after 1 hour
    (time_val['value']['v'])[:] = [100, 200]

    # CyTimeseries needs the same scope as CyWindMover because CyWindMover
    # uses the C++ pointer defined in CyTimeseries.time_dep. This must be defined
    # for the scope of CyWindMover

    ossm = CyTimeseries(timeseries=time_val)
    wm.set_ossm(ossm)

    def test_move(self):
        for x in range(0, 3):
            vary_time = x * 1800
            self.wm.prepare_for_model_step(self.cm.model_time + vary_time,
                                           self.cm.time_step)
            self.wm.get_move(self.cm.model_time + vary_time,
                             self.cm.time_step,
                             self.cm.ref,
                             self.delta,
                             self.cm.windage,
                             self.cm.status,
                             spill_type.forecast)

            print self.delta
            assert np.all(self.delta['lat'] != 0)
            assert np.all(self.delta['long'] == 0)
            assert np.all(self.delta['z'] == 0)
Example #19
0
    def __init__(self, wind=None, timeseries=None, past_hours_to_average=3,
                 **kwargs):
        """
        Initializes a running average object from a wind and past hours
        to average

        If no wind is given, timeseries gets initialized as:

            timeseries = np.zeros((1,), dtype=basic_types.datetime_value_2d)
            units = 'mps'
        (note: probably should be an error)

        All other keywords are optional. Optional parameters (kwargs):

        :param past_hours_to_average: default is 3

        """
        self.units = 'mps'
        self.format = 'uv'
        self._past_hours_to_average = past_hours_to_average
        self.wind = wind

        if (wind is None and timeseries is None):
            mvg_timeseries = np.array([(sec_to_date(zero_time()), [0.0, 0.0])],
                                      dtype=basic_types.datetime_value_2d)
            moving_ts = self._convert_to_time_value_pair(mvg_timeseries)
        elif wind is not None:
            moving_ts = (wind.ossm
                         .create_running_average(self._past_hours_to_average))
        else:
            self.wind = Wind(timeseries, units='mps', coord_sys='uv')
            moving_ts = (self.wind.ossm
                         .create_running_average(self._past_hours_to_average))

        self.ossm = CyTimeseries(timeseries=moving_ts)

        super(RunningAverage, self).__init__(**kwargs)
Example #20
0
class RunningAverage(Environment):
    '''
    Defines a running average time series for a wind or tide
    '''

    _schema = RunningAverageSchema

    def __init__(self, wind=None, timeseries=None, past_hours_to_average=3,
                 **kwargs):
        """
        Initializes a running average object from a wind and past hours
        to average

        If no wind is given, timeseries gets initialized as:

            timeseries = np.zeros((1,), dtype=basic_types.datetime_value_2d)
            units = 'mps'
        (note: probably should be an error)

        All other keywords are optional. Optional parameters (kwargs):

        :param past_hours_to_average: default is 3

        """
        self.units = 'mps'
        self.format = 'uv'
        self._past_hours_to_average = past_hours_to_average
        self.wind = wind

        if (wind is None and timeseries is None):
            mvg_timeseries = np.array([(sec_to_date(zero_time()), [0.0, 0.0])],
                                      dtype=basic_types.datetime_value_2d)
            moving_ts = self._convert_to_time_value_pair(mvg_timeseries)
        elif wind is not None:
            moving_ts = (wind.ossm
                         .create_running_average(self._past_hours_to_average))
        else:
            self.wind = Wind(timeseries, units='mps', coord_sys='uv')
            moving_ts = (self.wind.ossm
                         .create_running_average(self._past_hours_to_average))

        self.ossm = CyTimeseries(timeseries=moving_ts)

        super(RunningAverage, self).__init__(**kwargs)

    def __repr__(self):
        self_ts = self.timeseries.__repr__()
        return ('{0.__class__.__module__}.{0.__class__.__name__}('
                'timeseries={1})'
                .format(self, self_ts))

    def __str__(self):
        return ("Running Average ( "
                "timeseries=RunningAverage.get_timeseries('uv'), "
                "format='uv')")

    @property
    def past_hours_to_average(self):
        return self._past_hours_to_average

    @past_hours_to_average.setter
    def past_hours_to_average(self, value):
        """
        How many hours for running average
        """
        # may want a check on value
        self._past_hours_to_average = value

    @property
    def timeseries(self):
        return self.get_timeseries()

    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(self, datetime=None):
        """
        Returns the timeseries in the requested format. If datetime=None,
        then the original timeseries that was entered is returned.
        If datetime is a list containing datetime objects, then the wind value
        for each of those date times is determined by the underlying
        CyOSSMTime object and the timeseries is returned.

        The output format is defined by the strings 'r-theta', 'uv'

        :param datetime: [optional] datetime object or list of datetime
                         objects for which the value is desired
        :type datetime: datetime object

        :returns: numpy array containing dtype=basic_types.datetime_value_2d.
                  Contains user specified datetime and the corresponding
                  values in 'm/s' and 'uv' format
        """
        if datetime is None:
            datetimeval = to_datetime_value_2d(self.ossm.timeseries, 'uv')
        else:
            datetime = np.asarray(datetime, dtype='datetime64[s]').reshape(-1)

            timeval = np.zeros((len(datetime), ),
                               dtype=basic_types.time_value_pair)
            timeval['time'] = date_to_sec(datetime)
            timeval['value'] = self.ossm.get_time_value(timeval['time'])

            datetimeval = to_datetime_value_2d(timeval, 'uv')

        return datetimeval

    def prepare_for_model_run(self, model_time):
        """
        Make sure we are up to date with the referenced time series
        """
        if self.wind is None:
            msg = "wind object not defined for WindMover"
            raise ReferencedObjectNotSet(msg)

        model_time = date_to_sec(model_time)

        self.create_running_average_timeseries(self._past_hours_to_average,
                                               model_time)

    def prepare_for_model_step(self, model_time):
        """
        Make sure we are up to date with the referenced time series
        """
        model_time = date_to_sec(model_time)

        if self.ossm.check_time_in_range(model_time):
            return

        self.create_running_average_timeseries(self._past_hours_to_average,
                                               model_time)

    def create_running_average_timeseries(self, past_hours_to_average,
                                          model_time=0):
        """
        Creates the timeseries of the RunningAverage object

        :param past_hours_to_average: amount of data to use in the averaging
        """
        # first get the time series from the C++ function
        # self.timeseries = wind.ossm.create_running_average(past_hours)
        # do we need to dispose of old one here?
        moving_timeseries = (self.wind.ossm
                             .create_running_average(past_hours_to_average,
                                                     model_time))

        # here should set the timeseries since the CyOSSMTime
        # should already exist
        self.ossm.timeseries = moving_timeseries

    def get_value(self, time):
        '''
        Return the value at specified time and location. Timeseries are
        independent of location; however, a gridded datafile may require
        location so this interface may get refactored if it needs to support
        different types of data.
        It assumes the data in SI units (m/s) and 'uv' format

        .. note:: It invokes get_timeseries(..) function
        '''
        if self.ossm.timeseries is None:
            self.create_running_average_timeseries(self.past_hours_to_average)

        # if check on time range here:
        #     self.create_running_average_timeseries(self.past_hours,
        #                                            'm/s', 'uv')
        data = self.get_timeseries(time)

        return tuple(data[0]['value'])
Example #21
0
class TestVariableWind:
    """
    Uses OSSMTimeValue_c to define a variable wind
    - variable wind has 'v' component, so movement
      should only be in 'lat' direction of world point

    Leave as a class as we may add more methods to it for testing
    """
    wm = CyWindMover()
    cm = cy_fixtures.CyTestMove()
    delta = np.zeros((cm.num_le, ), dtype=world_point)

    time_val = np.zeros((2, ), dtype=time_value_pair)
    (time_val['time'])[:] = np.add([0, 3600], cm.model_time)  # after 1 hour
    (time_val['value']['v'])[:] = [100, 200]

    # CyTimeseries needs the same scope as CyWindMover because CyWindMover
    # uses the C++ pointer defined in CyTimeseries.time_dep.
    # This must be defined for the scope of CyWindMover

    ossm = CyTimeseries(timeseries=time_val)
    wm.set_ossm(ossm)

    def test_move(self):
        for x in range(0, 3):
            vary_time = x * 1800
            self.wm.prepare_for_model_step(self.cm.model_time + vary_time,
                                           self.cm.time_step)

            self.wm.get_move(self.cm.model_time + vary_time,
                             self.cm.time_step,
                             self.cm.ref,
                             self.delta,
                             self.cm.windage,
                             self.cm.status,
                             spill_type.forecast)

            print self.delta
            assert np.all(self.delta['lat'] != 0)
            assert np.all(self.delta['long'] == 0)
            assert np.all(self.delta['z'] == 0)

    def test_move_out_of_bounds(self):
        '''
            Our wind mover should fail in the prepare_for_model_step() function
            if our wind time series is out of bounds with respect to the
            model time we are preparing for, unless our wind time series is
            configured to extrapolate wind values.
        '''
        # setup a time series that's out of range of our model time
        time_val = np.zeros((2, ), dtype=time_value_pair)
        (time_val['time'])[:] = np.add([3600, 7200], self.cm.model_time)
        (time_val['value']['v'])[:] = [100, 200]

        # extrapolation should be off by default
        ossm = CyTimeseries(timeseries=time_val)
        self.wm.set_ossm(ossm)

        # this should fail because our time series is not set to extrapolate
        with raises(OSError):
            self.wm.prepare_for_model_step(self.cm.model_time,
                                           self.cm.time_step)

        ossm = CyTimeseries(timeseries=time_val, extrapolation_is_allowed=True)
        self.wm.set_ossm(ossm)

        # We set our time series to extrapolate, so this should pass
        self.wm.prepare_for_model_step(self.cm.model_time,
                                       self.cm.time_step)

        # clean up our time series
        self.wm.set_ossm(self.ossm)
Example #22
0
class RunningAverage(Environment):
    '''
    Defines a running average time series for a wind or tide
    '''

    _schema = RunningAverageSchema

    def __init__(self,
                 wind=None,
                 timeseries=None,
                 past_hours_to_average=3,
                 **kwargs):
        """
        Initializes a running average object from a wind and past hours
        to average

        If no wind is given, timeseries gets initialized as:

            timeseries = np.zeros((1,), dtype=basic_types.datetime_value_2d)
            units = 'mps'
        (note: probably should be an error)

        All other keywords are optional. Optional parameters (kwargs):

        :param past_hours_to_average: default is 3

        """
        self.units = 'mps'
        self.format = 'uv'
        self._past_hours_to_average = past_hours_to_average
        self.wind = wind

        if (wind is None and timeseries is None):
            mvg_timeseries = np.array([(sec_to_date(zero_time()), [0.0, 0.0])],
                                      dtype=basic_types.datetime_value_2d)
            moving_ts = self._convert_to_time_value_pair(mvg_timeseries)
        elif wind is not None:
            moving_ts = (wind.ossm.create_running_average(
                self._past_hours_to_average))
        else:
            self.wind = Wind(timeseries, units='mps', format='uv')
            moving_ts = (self.wind.ossm.create_running_average(
                self._past_hours_to_average))

        self.ossm = CyTimeseries(timeseries=moving_ts)

        super(RunningAverage, self).__init__(**kwargs)

    def __repr__(self):
        self_ts = self.timeseries.__repr__()
        return ('{0.__class__.__module__}.{0.__class__.__name__}('
                'timeseries={1})'.format(self, self_ts))

    def __str__(self):
        return ("Running Average ( "
                "timeseries=RunningAverage.get_timeseries('uv'), "
                "format='uv')")

    @property
    def past_hours_to_average(self):
        return self._past_hours_to_average

    @past_hours_to_average.setter
    def past_hours_to_average(self, value):
        """
        How many hours for running average
        """
        # may want a check on value
        self._past_hours_to_average = value

    @property
    def timeseries(self):
        return self.get_timeseries()

    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(self, datetime=None):
        """
        Returns the timeseries in the requested format. If datetime=None,
        then the original timeseries that was entered is returned.
        If datetime is a list containing datetime objects, then the wind value
        for each of those date times is determined by the underlying
        CyOSSMTime object and the timeseries is returned.

        The output format is defined by the strings 'r-theta', 'uv'

        :param datetime: [optional] datetime object or list of datetime
                         objects for which the value is desired
        :type datetime: datetime object

        :returns: numpy array containing dtype=basic_types.datetime_value_2d.
                  Contains user specified datetime and the corresponding
                  values in 'm/s' and 'uv' format
        """
        if datetime is None:
            datetimeval = to_datetime_value_2d(self.ossm.timeseries, 'uv')
        else:
            datetime = np.asarray(datetime, dtype='datetime64[s]').reshape(-1)

            timeval = np.zeros((len(datetime), ),
                               dtype=basic_types.time_value_pair)
            timeval['time'] = date_to_sec(datetime)
            timeval['value'] = self.ossm.get_time_value(timeval['time'])

            datetimeval = to_datetime_value_2d(timeval, 'uv')

        return datetimeval

    def prepare_for_model_run(self, model_time):
        """
        Make sure we are up to date with the referenced time series
        """
        if self.wind is None:
            msg = "wind object not defined for WindMover"
            raise ReferencedObjectNotSet(msg)

        model_time = date_to_sec(model_time)

        self.create_running_average_timeseries(self._past_hours_to_average,
                                               model_time)

    def prepare_for_model_step(self, model_time):
        """
        Make sure we are up to date with the referenced time series
        """
        model_time = date_to_sec(model_time)

        if self.ossm.check_time_in_range(model_time):
            return

        self.create_running_average_timeseries(self._past_hours_to_average,
                                               model_time)

    def create_running_average_timeseries(self,
                                          past_hours_to_average,
                                          model_time=0):
        """
        Creates the timeseries of the RunningAverage object

        :param past_hours_to_average: amount of data to use in the averaging
        """
        # first get the time series from the C++ function
        # self.timeseries = wind.ossm.create_running_average(past_hours)
        # do we need to dispose of old one here?
        moving_timeseries = (self.wind.ossm.create_running_average(
            past_hours_to_average, model_time))

        # here should set the timeseries since the CyOSSMTime
        # should already exist
        self.ossm.timeseries = moving_timeseries

    def get_value(self, time):
        '''
        Return the value at specified time and location. Timeseries are
        independent of location; however, a gridded datafile may require
        location so this interface may get refactored if it needs to support
        different types of data.
        It assumes the data in SI units (m/s) and 'uv' format

        .. note:: It invokes get_timeseries(..) function
        '''
        if self.ossm.timeseries is None:
            self.create_running_average_timeseries(self.past_hours_to_average)

        # if check on time range here:
        #     self.create_running_average_timeseries(self.past_hours,
        #                                            'm/s', 'uv')
        data = self.get_timeseries(time)

        return tuple(data[0]['value'])
Example #23
0
class Timeseries(GnomeId):
    _schema = ObjTypeSchema

    def __init__(self, timeseries=None, filename=None, coord_sys='uv',
                 extrapolation_is_allowed=False, **kwargs):
        """
        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 coordinate system is 'uv'.
        The C++ stores the data in 'uv' coordinates - transformations are done
        in this Python code (set_timeseries(), get_timeseries()).

        C++ code only transforms the data from 'r-theta' to 'uv' coordinates
        if data is read from file. And this happens during initialization
        because C++ stores data in 'uv' coordinates 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 the
        coordinate system specified. Choose coord_sys='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 containing
                          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 coord_sys: (Optional) default timeseries coordinate system is
                          magnitude direction: 'r-theta'
        :type coord_sys: string 'r-theta' or 'uv'. Default is 'r-theta'.
            Converts string to integer defined by
            gnome.basic_types.ts_format.*
        """
        super(Timeseries, self).__init__(**kwargs)
        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, coord_sys)

            self.ossm = CyTimeseries(timeseries=time_value_pair)
        else:
            ts_format = tsformat(coord_sys)
            self.ossm = CyTimeseries(filename=self._filename,
                                     file_format=ts_format)

        self.extrapolation_is_allowed = extrapolation_is_allowed

    def _check_timeseries(self, timeseries):
        """
        Run some checks to make sure timeseries is valid.
        - We accept numpy arrays
        - We will also accept a list of timeseries values of the form
          (datetime(...), (N, N))
        - we will also accept a constant single timeseries  value.
        """
        if not isinstance(timeseries, np.ndarray):
            if self._is_timeseries_value(timeseries):
                return True
            else:
                for i in timeseries:
                    if not self._is_timeseries_value(i):
                        raise TimeseriesError('value: {} '
                                              'is not a timeseries value'
                                              .format(i))
                return True

        if not self._timeseries_is_ascending(timeseries):
            self.logger.error('{0} - timeseries are not in ascending order. '
                              'The datetime values in the array must be in '
                              'ascending order'
                              .format(self._pid))
            raise TimeseriesError("timeseries is not in ascending order")

        return True

    def __len__(self):
        """
        length is the number of data points in the timeseries
        """
        return self.ossm.get_num_values()

    def __eq__(self, o):
        t1 = super(Timeseries, self).__eq__(o)
        return t1 and hasattr(self, 'ossm') and np.all(self.ossm.timeseries == o.ossm.timeseries)

    def get_start_time(self):
        """
        :this will be the real_data_start time (seconds).
        """
        return self.ossm.get_start_time()

    def get_end_time(self):
        """
        :this will be the real_data_stop time (seconds).
        """
        return (self.ossm.get_end_time())

    def _is_timeseries_value(self, value):
        if not isinstance(value, (list, tuple)):
            return False

        if len(value) != 2:
            return False

        if not isinstance(value[0], (datetime.datetime, np.datetime64)):
            return False

        if len(value[1]) not in (1, 2):
            return False

        return True

    def _timeseries_is_ascending(self, timeseries):
        """
        Check if values are monotonically increasing

        This should catch both out of order and duplicate values.
        """

        # FixMe: does this ever have to work for a true scalar??
        if timeseries.shape == () or timeseries.shape == (1,):
            # scalar or single value -- must be OK
            return True

        if np.any(np.diff(timeseries['time']) <= np.timedelta64(0, 's')):
            return False
        else:
            return True

    def _xform_input_timeseries(self, timeseries):
        '''
        Ensure input data is numpy array with correct dtype and check
        timeseries doesn't have invalid data
        Derived classes can use this before updating timeseries, prior to
        converting units.
        '''
        # following fails for 0-d objects so make sure we have a 1-D array
        # to work with
        timeseries = np.asarray(timeseries,
                                dtype=basic_types.datetime_value_2d)

        if timeseries.shape == ():
            timeseries = np.asarray([timeseries],
                                    dtype=basic_types.datetime_value_2d)

        return timeseries

    def __str__(self):
        return '{0.__module__}.{0.__class__.__name__}'.format(self)

    @property
    def filename(self):
        return self._filename

    @property
    def extrapolation_is_allowed(self):
        return self.ossm.extrapolation_is_allowed

    @extrapolation_is_allowed.setter
    def extrapolation_is_allowed(self, value):
        self.ossm.extrapolation_is_allowed = value

    def get_timeseries(self, datetime=None, coord_sys='uv'):
        """
        Returns the timeseries in requested coordinate system.
        If datetime=None, then the original timeseries that was entered is
        returned.
        If datetime is a list containing datetime objects, then the value
        for each of those date times is determined by the underlying
        C++ object and the timeseries is returned.

        The output coordinate system is defined by the strings 'r-theta', 'uv'

        :param datetime: [optional] datetime object or list of datetime
                         objects for which the value is desired
        :type datetime: datetime object

        :param coord_sys: output coordinate system for the times series:
                          either 'r-theta' or 'uv'
        :type coord_sys: either string or integer value defined by
                         basic_types.ts_format.* (see cy_basic_types.pyx)

        :returns: numpy array containing dtype=basic_types.datetime_value_2d.
                  Contains user specified datetime and the corresponding
                  values in user specified ts_format
        """
        if datetime is None:
            datetimeval = to_datetime_value_2d(self.ossm.timeseries, coord_sys)
        else:
            datetime = np.asarray(datetime, dtype='datetime64[s]').reshape(-1)
            timeval = np.zeros((len(datetime), ),
                               dtype=basic_types.time_value_pair)
            timeval['time'] = date_to_sec(datetime)
            (timeval['value'], err) = self.ossm.get_time_value(timeval['time'])

            if err != 0:
                msg = ('No available data in the time interval that is being '
                       'modeled\n'
                       '\tModel time: {}\n'
                       '\tMover: {} of type {}\n'
                       .format(datetime, self.name, self.__class__))

                self.logger.error(msg)
                raise RuntimeError(msg)

            datetimeval = to_datetime_value_2d(timeval, coord_sys)

        return np.copy(datetimeval)

    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
Example #24
0
class RunningAverage(Environment, Serializable):
    '''
    Defines a running average time series for a wind or tide
    '''

    _update = []

    # used to create new obj or as readonly parameter
    _create = []
    _create.extend(_update)

    _state = copy.deepcopy(Environment._state)
    _state += [Field('wind', save=True, update=True, save_reference=True)]
    _state.add(save=_create, update=_update)
    _schema = RunningAverageSchema

    # _state.add_field([serializable.Field('timeseries', save=True,
    #                                      update=True)
    #                   ])
    # _state['name'].test_for_eq = False

    # list of valid velocity units for timeseries
    # valid_vel_units = _valid_units('Velocity')

    def __init__(self, wind=None, timeseries=None, past_hours_to_average=3,
                 **kwargs):
        """
        Initializes a running average object from a wind and past hours
        to average

        If no wind is given, timeseries gets initialized as:

            timeseries = np.zeros((1,), dtype=basic_types.datetime_value_2d)
            units = 'mps'
        (note: probably should be an error)

        All other keywords are optional. Optional parameters (kwargs):

        :param past_hours_to_average: default is 3

        """
        self.units = 'mps'
        self.format = 'uv'
        self._past_hours_to_average = past_hours_to_average
        self.wind = wind

        if (wind is None and timeseries is None):
            mvg_timeseries = np.array([(sec_to_date(zero_time()), [0.0, 0.0])],
                                      dtype=basic_types.datetime_value_2d)
            moving_timeseries = self._convert_to_time_value_pair(mvg_timeseries)

        else:
            if wind is not None:
                moving_timeseries = wind.ossm.create_running_average(self._past_hours_to_average)
            else:
                self.wind = Wind(timeseries, units='mps', format='uv')
                moving_timeseries = self.wind.ossm.create_running_average(self._past_hours_to_average)

        # print "moving_timeseries"
        # print moving_timeseries

        self.ossm = CyTimeseries(timeseries=moving_timeseries)

        super(RunningAverage, self).__init__(**kwargs)

    def __repr__(self):
        self_ts = self.timeseries.__repr__()
        return ('{0.__class__.__module__}.{0.__class__.__name__}('
                'timeseries={1}'
                ')').format(self, self_ts)

    def __str__(self):
        return ("Running Average ( "
                "timeseries=RunningAverage.get_timeseries('uv'), "
                "format='uv')")

    @property
    def past_hours_to_average(self):
        return self._past_hours_to_average

    @past_hours_to_average.setter
    def past_hours_to_average(self, value):
        """
        How many hours for running average
        """
        # may want a check on value
        self._past_hours_to_average = value

    @property
    def timeseries(self):
        return self.get_timeseries()

    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 get_timeseries(self, datetime=None):
        """
        Returns the timeseries in the requested format. If datetime=None,
        then the original timeseries that was entered is returned.
        If datetime is a list containing datetime objects, then the wind value
        for each of those date times is determined by the underlying
        CyOSSMTime object and the timeseries is returned.

        The output format is defined by the strings 'r-theta', 'uv'

        :param datetime: [optional] datetime object or list of datetime
                         objects for which the value is desired
        :type datetime: datetime object

        :returns: numpy array containing dtype=basic_types.datetime_value_2d.
                  Contains user specified datetime and the corresponding
                  values in 'm/s' and 'uv' format
        """
        if datetime is None:
            datetimeval = to_datetime_value_2d(self.ossm.timeseries, 'uv')
        else:
            datetime = np.asarray(datetime, dtype='datetime64[s]').reshape(-1)
            timeval = np.zeros((len(datetime), ),
                               dtype=basic_types.time_value_pair)
            timeval['time'] = date_to_sec(datetime)
            timeval['value'] = self.ossm.get_time_value(timeval['time'])
            datetimeval = to_datetime_value_2d(timeval, 'uv')

        return datetimeval

    def prepare_for_model_run(self, model_time):
        """
        Make sure we are up to date with the referenced time series
        """
        if self.wind is None:
            msg = "wind object not defined for WindMover"
            raise ReferencedObjectNotSet(msg)

        model_time = date_to_sec(model_time)

        self.create_running_average_timeseries(self._past_hours_to_average,
                                               model_time)

    def prepare_for_model_step(self, model_time):
        """
        Make sure we are up to date with the referenced time series
        """
        model_time = date_to_sec(model_time)
        if self.ossm.check_time_in_range(model_time):
            return
        else:
            if self.wind.ossm.check_time_in_range(model_time):
                # there is wind data for this time so create
                # a new running average
                self.create_running_average_timeseries(self._past_hours_to_average, model_time)

        self.create_running_average_timeseries(self._past_hours_to_average,
                                               model_time)

    def create_running_average_timeseries(self, past_hours_to_average,
                                          model_time=0):
        """
        Creates the timeseries of the RunningAverage object

        :param past_hours_to_average: amount of data to use in the averaging
        """
        # first get the time series from the C++ function
        # self.timeseries = wind.ossm.create_running_average(past_hours)
        # do we need to dispose of old one here?
        moving_timeseries = self.wind.ossm.create_running_average(past_hours_to_average, model_time)

        # here should set the timeseries since the CyOSSMTime
        # should already exist
        self.ossm.timeseries = moving_timeseries
        # self.ossm = CyOSSMTime(timeseries=moving_timeseries)

    def get_value(self, time):
        '''
        Return the value at specified time and location. Timeseries are
        independent of location; however, a gridded datafile may require
        location so this interface may get refactored if it needs to support
        different types of data.
        It assumes the data in SI units (m/s) and 'uv' format

        .. note:: It invokes get_timeseries(..) function
        '''
        if self.ossm.timeseries is None:
            self.create_running_average_timeseries(self.past_hours_to_average)

        # if check on time range here:
        #     self.create_running_average_timeseries(self.past_hours,
        #                                            'm/s', 'uv')
        data = self.get_timeseries(time)

        return tuple(data[0]['value'])

    def serialize(self, json_='webapi'):
        """
        Since 'wind' property is saved as references in save file
        need to add appropriate node to WindMover schema for 'webapi'
        """
        toserial = self.to_serialize(json_)
        schema = self.__class__._schema()
        if json_ == 'webapi':
            if self.wind:
                # add wind schema
                schema.add(WindSchema(name='wind'))

        serial = schema.serialize(toserial)

        return serial

    @classmethod
    def deserialize(cls, json_):
        """
        append correct schema for wind object
        """
        schema = cls._schema()
        if 'wind' in json_:
            schema.add(WindSchema(name='wind'))

        _to_dict = schema.deserialize(json_)

        return _to_dict
Example #25
0
class Timeseries(GnomeId):
    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 _check_timeseries(self, timeseries):
        '''
        Run some checks to make sure timeseries is valid.
        - We accept numpy arrays
        - We will also accept a list of timeseries values of the form
          (datetime(...), (N, N))
        - we will also accept a constant single timeseries  value.
        Also, make the resolution to minutes as opposed to seconds
        '''
        if not isinstance(timeseries, np.ndarray):
            if self._is_timeseries_value(timeseries):
                return True
            else:
                for i in timeseries:
                    if not self._is_timeseries_value(i):
                        return False

                return True

        if not self._timeseries_is_ascending(timeseries):
            self.logger.error('{0} - timeseries are not in ascending order. '
                              'The datetime values in the array must be in '
                              'ascending order'
                              .format(self._pid))
            return False

        if self._timeseries_has_duplicates(timeseries):
            self.logger.error('{0} - timeseries must contain unique '
                              'time entries'
                              .format(self._pid))
            return False

        return True

    def _is_timeseries_value(self, value):
        if not isinstance(value, (list, tuple)):
            return False

        if len(value) != 2:
            return False

        if not isinstance(value[0], (datetime.datetime, np.datetime64)):
            return False

        if len(value[1]) not in (1, 2):
            return False

        return True

    def _timeseries_is_ascending(self, timeseries):
        # we need to have a valid shape to sort
        if timeseries.shape == ():
            timeseries = np.asarray([timeseries],
                                    dtype=basic_types.datetime_value_2d)

        if np.any(timeseries['time'][np.argsort(timeseries['time'])] !=
                  timeseries['time']):
            return False
        else:
            return True

    def _timeseries_has_duplicates(self, timeseries):
        # we need to have a valid shape to sort
        if timeseries.shape == ():
            timeseries = np.asarray([timeseries],
                                    dtype=basic_types.datetime_value_2d)

        unique = np.unique(timeseries['time'])
        if len(unique) != len(timeseries['time']):
            return True
        else:
            return False

    def _xform_input_timeseries(self, timeseries):
        '''
        Ensure input data is numpy array with correct dtype and check
        timeseries doesn't have invalid data
        Derived classes can use this before updating timeseries, prior to
        converting units.
        '''
        # following fails for 0-d objects so make sure we have a 1-D array
        # to work with
        timeseries = np.asarray(timeseries,
                                dtype=basic_types.datetime_value_2d)

        if timeseries.shape == ():
            timeseries = np.asarray([timeseries],
                                    dtype=basic_types.datetime_value_2d)

        # Our datetime granularity is in minutes, so zero out the seconds.
        for ix, tm in enumerate(timeseries['time'].astype(datetime.datetime)):
            timeseries['time'][ix] = tm.replace(second=0)

        return timeseries

    def __str__(self):
        return '{0.__module__}.{0.__class__.__name__}'.format(self)

    @property
    def filename(self):
        return self._filename

    def get_timeseries(self, datetime=None, format='uv'):
        """
        Returns the timeseries in requested format. If datetime=None,
        then the original timeseries that was entered is returned.
        If datetime is a list containing datetime objects, then the value
        for each of those date times is determined by the underlying
        C++ object and the timeseries is returned.

        The output format is defined by the strings 'r-theta', 'uv'

        :param datetime: [optional] datetime object or list of datetime
                         objects for which the value is desired
        :type datetime: datetime object
        :param format: output format for the times series:
                       either 'r-theta' or 'uv'
        :type format: either string or integer value defined by
                      basic_types.ts_format.* (see cy_basic_types.pyx)

        :returns: numpy array containing dtype=basic_types.datetime_value_2d.
                  Contains user specified datetime and the corresponding
                  values in user specified ts_format
        """
        if datetime is None:
            datetimeval = to_datetime_value_2d(self.ossm.timeseries, format)
        else:
            datetime = np.asarray(datetime, dtype='datetime64[s]').reshape(-1)
            timeval = np.zeros((len(datetime), ),
                               dtype=basic_types.time_value_pair)
            timeval['time'] = date_to_sec(datetime)
            timeval['value'] = self.ossm.get_time_value(timeval['time'])
            datetimeval = to_datetime_value_2d(timeval, format)

        return datetimeval

    def set_timeseries(self, datetime_value_2d, format='uv'):
        """
        Sets the timeseries 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
        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 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)
        """
        datetime_value_2d = self._xform_input_timeseries(datetime_value_2d)
        timeval = to_time_value_pair(datetime_value_2d, format)

        self.ossm.timeseries = timeval

    def __eq__(self, other):
        '''
        only checks the timeseries data is equal in (m/s), in 'uv' format
        filename is irrelevant after data is loaded
        checks self.get_timeseries() == other.get_timeseries()

        Duck typing check - it does not expect type(self) == type(other)
        '''
        self_ts = self.get_timeseries()
        other_ts = other.get_timeseries()
        if not np.all(self_ts['time'] == other_ts['time']):
            return False

        if not np.allclose(self_ts['value'], other_ts['value']):
            return False

        return True

    def __ne__(self, other):
        return not self == other
Example #26
0
    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)
Example #27
0
    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)
Example #28
0
class Timeseries(GnomeId):
    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 _check_timeseries(self, timeseries):
        """
        Run some checks to make sure timeseries is valid.
        - We accept numpy arrays
        - We will also accept a list of timeseries values of the form
          (datetime(...), (N, N))
        - we will also accept a constant single timeseries  value.
        """
        if not isinstance(timeseries, np.ndarray):
            if self._is_timeseries_value(timeseries):
                return True
            else:
                for i in timeseries:
                    if not self._is_timeseries_value(i):
                        raise TimeseriesError(
                            'value: {} '
                            'is not a timeseries value'.format(i))
                return True

        if not self._timeseries_is_ascending(timeseries):
            self.logger.error('{0} - timeseries are not in ascending order. '
                              'The datetime values in the array must be in '
                              'ascending order'.format(self._pid))
            raise TimeseriesError("timeseries is not in ascending order")

        return True

    def get_start_time(self):
        """
        :this will be the real_data_start time (seconds).
        """
        return (self.ossm.get_start_time())

    def get_end_time(self):
        """
        :this will be the real_data_stop time (seconds).
        """
        return (self.ossm.get_end_time())

    def _is_timeseries_value(self, value):
        if not isinstance(value, (list, tuple)):
            return False

        if len(value) != 2:
            return False

        if not isinstance(value[0], (datetime.datetime, np.datetime64)):
            return False

        if len(value[1]) not in (1, 2):
            return False

        return True

    def _timeseries_is_ascending(self, timeseries):
        """
        Check if values are monotonically increasing

        This should catch both out of order and duplicate values.
        """

        # FixMe: does this ever have to work for a true scalar??
        if timeseries.shape == () or timeseries.shape == (1, ):
            # scalar or single value -- must be OK
            return True

        if np.any(np.diff(timeseries['time']) <= np.timedelta64(0, 's')):
            return False
        else:
            return True

    # not needed -- _timeseries_is_ascending should catch this
    # def _timeseries_has_duplicates(self, timeseries):
    #     # we need to have a valid shape to sort
    #     if timeseries.shape == ():
    #         timeseries = np.asarray([timeseries],
    #                                 dtype=basic_types.datetime_value_2d)

    #     unique = np.unique(timeseries['time'])
    #     if len(unique) != len(timeseries['time']):
    #         return True
    #     else:
    #         return False

    def _xform_input_timeseries(self, timeseries):
        '''
        Ensure input data is numpy array with correct dtype and check
        timeseries doesn't have invalid data
        Derived classes can use this before updating timeseries, prior to
        converting units.
        '''
        # following fails for 0-d objects so make sure we have a 1-D array
        # to work with
        timeseries = np.asarray(timeseries,
                                dtype=basic_types.datetime_value_2d)

        if timeseries.shape == ():
            timeseries = np.asarray([timeseries],
                                    dtype=basic_types.datetime_value_2d)

        return timeseries

    def __str__(self):
        return '{0.__module__}.{0.__class__.__name__}'.format(self)

    @property
    def filename(self):
        return self._filename

    def get_timeseries(self, datetime=None, format='uv'):
        """
        Returns the timeseries in requested format. If datetime=None,
        then the original timeseries that was entered is returned.
        If datetime is a list containing datetime objects, then the value
        for each of those date times is determined by the underlying
        C++ object and the timeseries is returned.

        The output format is defined by the strings 'r-theta', 'uv'

        :param datetime: [optional] datetime object or list of datetime
                         objects for which the value is desired
        :type datetime: datetime object
        :param format: output format for the times series:
                       either 'r-theta' or 'uv'
        :type format: either string or integer value defined by
                      basic_types.ts_format.* (see cy_basic_types.pyx)

        :returns: numpy array containing dtype=basic_types.datetime_value_2d.
                  Contains user specified datetime and the corresponding
                  values in user specified ts_format
        """
        if datetime is None:
            datetimeval = to_datetime_value_2d(self.ossm.timeseries, format)
        else:
            datetime = np.asarray(datetime, dtype='datetime64[s]').reshape(-1)
            timeval = np.zeros((len(datetime), ),
                               dtype=basic_types.time_value_pair)
            timeval['time'] = date_to_sec(datetime)
            timeval['value'] = self.ossm.get_time_value(timeval['time'])
            datetimeval = to_datetime_value_2d(timeval, format)

        return datetimeval

    def set_timeseries(self, datetime_value_2d, format='uv'):
        """
        Sets the timeseries 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
        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 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)
        """
        datetime_value_2d = self._xform_input_timeseries(datetime_value_2d)
        timeval = to_time_value_pair(datetime_value_2d, format)

        self.ossm.timeseries = timeval

    def __eq__(self, other):
        '''
        only checks the timeseries data is equal in (m/s), in 'uv' format
        filename is irrelevant after data is loaded
        checks self.get_timeseries() == other.get_timeseries()

        Duck typing check - it does not expect type(self) == type(other)
        '''
        self_ts = self.get_timeseries()
        other_ts = other.get_timeseries()
        if not np.all(self_ts['time'] == other_ts['time']):
            return False

        if not np.allclose(
                self_ts['value'], other_ts['value'], atol=1e-10, rtol=1e-10):
            return False

        return True

    def __ne__(self, other):
        return not self == other