def setUp(self):
     self.model = IsotropicGaussian()
     # Setup simple grid
     [gx, gy] = np.meshgrid(np.arange(35.5, 40., 0.5),
                            np.arange(40.5, 45., 0.5))
     ngp = np.shape(gx)[0] * np.shape(gx)[1]
     gx = np.reshape(gx, [ngp, 1])
     gy = np.reshape(gy, [ngp, 1])
     depths = 10. * np.ones(ngp)
     self.data = np.column_stack(
         [gx, gy, depths, np.zeros(ngp, dtype=float)])
示例#2
0
    def test_analysis_Frankel_comparison(self):
        '''
        To test the run_analysis function we compare test results with those
        from Frankel's fortran implementation, under the same conditions
        '''
        self.grid_limits = [-128., -113.0, 0.2, 30., 43.0, 0.2, 0., 100., 100.]
        comp_table = np.array([[1933., 4.0], [1900., 5.0], [1850., 6.0],
                               [1850., 7.0]])
        config = {'Length_Limit': 3., 'BandWidth': 50., 'increment': 0.1}
        self.model = SmoothedSeismicity(self.grid_limits, bvalue=0.8)
        self.catalogue = Catalogue()
        frankel_catalogue = np.genfromtxt(
            os.path.join(BASE_PATH, FRANKEL_TEST_CATALOGUE))
        self.catalogue.data['magnitude'] = frankel_catalogue[:, 0]
        self.catalogue.data['longitude'] = frankel_catalogue[:, 1]
        self.catalogue.data['latitude'] = frankel_catalogue[:, 2]
        self.catalogue.data['depth'] = frankel_catalogue[:, 3]
        self.catalogue.data['year'] = frankel_catalogue[:, 4]
        self.catalogue.end_year = 2006
        frankel_results = np.genfromtxt(
            os.path.join(BASE_PATH, FRANKEL_OUTPUT_FILE))
        # Run analysis
        output_data = self.model.run_analysis(
            self.catalogue,
            config,
            completeness_table=comp_table,
            smoothing_kernel=IsotropicGaussian())

        self.assertTrue(
            fabs(np.sum(output_data[:, -1]) -
                 np.sum(output_data[:, -2])) < 1.0)
        self.assertTrue(fabs(np.sum(output_data[:, -1]) - 390.) < 1.0)
 def setUp(self):
     self.model = IsotropicGaussian()
     # Setup simple grid
     [gx, gy] = np.meshgrid(np.arange(35.5, 40., 0.5),
                            np.arange(40.5, 45., 0.5))
     ngp = np.shape(gx)[0] * np.shape(gx)[1]
     gx = np.reshape(gx, [ngp, 1])
     gy = np.reshape(gy, [ngp, 1])
     depths = 10. * np.ones(ngp)
     self.data = np.column_stack([gx, gy, depths,
                                 np.zeros(ngp, dtype=float)])
    def run_analysis(self,
                     catalogue,
                     config,
                     completeness_table=None,
                     smoothing_kernel=None):
        '''
        Runs an analysis of smoothed seismicity in the manner
        originally implemented by Frankel (1995)

        :param catalogue:
            Instance of the openquake.hmtk.seismicity.catalogue.Catalogue class
            catalogue.data dictionary containing the following -
            'year' - numpy.ndarray vector of years
            'longitude' - numpy.ndarray vector of longitudes
            'latitude' - numpy.ndarray vector of latitudes
            'depth' - numpy.ndarray vector of depths

        :param dict config:
            Configuration settings of the algorithm:
            * 'Length_Limit' - Maximum number of bandwidths for use in
            smoothing (Float)
            * 'BandWidth' - Bandwidth (km) of the Smoothing Kernel (Float)
            * 'increment' - Output incremental (True) or cumulative a-value
            (False)

        :param np.ndarray completeness_table:
            Completeness of the catalogue assuming evenly spaced magnitudes
            from most recent bin to oldest bin [year, magnitude]

        :param smoothing_kernel:
            Smoothing kernel as instance of :class:
            `openquake.hmtk.seismicity.smoothing.kernels.base.BaseSmoothingKernel`

        :returns:
            Full smoothed seismicity data as np.ndarray, of the form
            [Longitude, Latitude, Depth, Observed, Smoothed]
        '''

        self.catalogue = catalogue
        if smoothing_kernel:
            self.kernel = smoothing_kernel
        else:
            self.kernel = IsotropicGaussian()

        # If no grid limits are specified then take from catalogue
        if isinstance(self.grid_limits, list):
            self.grid_limits = Grid.make_from_list(self.grid_limits)
            assert self.grid_limits['xmax'] >= self.grid_limits['xmin']
            assert self.grid_limits['xspc'] > 0.0
            assert self.grid_limits['ymax'] >= self.grid_limits['ymin']
            assert self.grid_limits['yspc'] > 0.0
        elif isinstance(self.grid_limits, float):
            self.grid_limits = Grid.make_from_catalogue(
                self.catalogue, self.grid_limits,
                config['Length_Limit'] * config['BandWidth'])

        completeness_table, mag_inc = utils.get_even_magnitude_completeness(
            completeness_table, self.catalogue)

        end_year = self.catalogue.end_year

        # Get Weichert factor
        t_f, _ = utils.get_weichert_factor(self.beta, completeness_table[:, 1],
                                           completeness_table[:, 0], end_year)
        # Get the grid
        self.create_3D_grid(self.catalogue, completeness_table, t_f, mag_inc)
        if config['increment']:
            # Get Hermann adjustment factors
            fval, fival = utils.hermann_adjustment_factors(
                self.bval, completeness_table[0, 1], config['increment'])
            self.data[:, -1] = fval * fival * self.data[:, -1]

        # Apply smoothing
        smoothed_data, sum_data, sum_smooth = self.kernel.smooth_data(
            self.data, config, self.use_3d)
        print('Smoothing Total Rate Comparison - '
              'Observed: %.6g, Smoothed: %.6g' % (sum_data, sum_smooth))
        self.data = np.column_stack([self.data, smoothed_data])
        return self.data
class SmoothedSeismicity(object):
    '''
    Class to implement an analysis of Smoothed Seismicity, including the
    grid counting of data and the smoothing.

    :param np.ndarray grid:
        Observed count in each cell [Long., Lat., Depth., Count]

    :param catalogue:
        Valid instance of the :class: openquake.hmtk.seismicity.catalogue.Catalogue

    :param bool use_3d:
        Decide if analysis is 2-D (False) or 3-D (True). If 3-D then distances
        will use hypocentral distance, otherwise epicentral distance

    :param float bval:
        b-value

    :param float beta:
        Beta value for exponential form (beta = bval * log(10.))

    :param np.ndarray data:
        Smoothed seismicity output

    :param dict grid_limits:
        Limits ot the grid used for defining the cells
    '''
    def __init__(self, grid_limits, use_3d=False, bvalue=None):
        '''
        Instatiate class with a set of grid limits

        :param grid_limits:
            It could be a float (in that case the grid is computed from the
            catalogue with the given spacing).

            Or an array of the form:
            [xmin, xmax, spcx, ymin, ymax, spcy, zmin, spcz]

        :param bool use_3d:
            Choose whether to use hypocentral distances for smoothing or only
            epicentral

        :param float bval:
            b-value for analysis
        '''
        self.grid = None
        self.catalogue = None
        self.use_3d = use_3d
        self.bval = bvalue
        if self.bval:
            self.beta = self.bval * log(10.)
        else:
            self.beta = None
        self.data = None

        self.grid_limits = grid_limits
        self.kernel = None

    def run_analysis(self,
                     catalogue,
                     config,
                     completeness_table=None,
                     smoothing_kernel=None):
        '''
        Runs an analysis of smoothed seismicity in the manner
        originally implemented by Frankel (1995)

        :param catalogue:
            Instance of the openquake.hmtk.seismicity.catalogue.Catalogue class
            catalogue.data dictionary containing the following -
            'year' - numpy.ndarray vector of years
            'longitude' - numpy.ndarray vector of longitudes
            'latitude' - numpy.ndarray vector of latitudes
            'depth' - numpy.ndarray vector of depths

        :param dict config:
            Configuration settings of the algorithm:
            * 'Length_Limit' - Maximum number of bandwidths for use in
            smoothing (Float)
            * 'BandWidth' - Bandwidth (km) of the Smoothing Kernel (Float)
            * 'increment' - Output incremental (True) or cumulative a-value
            (False)

        :param np.ndarray completeness_table:
            Completeness of the catalogue assuming evenly spaced magnitudes
            from most recent bin to oldest bin [year, magnitude]

        :param smoothing_kernel:
            Smoothing kernel as instance of :class:
            `openquake.hmtk.seismicity.smoothing.kernels.base.BaseSmoothingKernel`

        :returns:
            Full smoothed seismicity data as np.ndarray, of the form
            [Longitude, Latitude, Depth, Observed, Smoothed]
        '''

        self.catalogue = catalogue
        if smoothing_kernel:
            self.kernel = smoothing_kernel
        else:
            self.kernel = IsotropicGaussian()

        # If no grid limits are specified then take from catalogue
        if isinstance(self.grid_limits, list):
            self.grid_limits = Grid.make_from_list(self.grid_limits)
            assert self.grid_limits['xmax'] >= self.grid_limits['xmin']
            assert self.grid_limits['xspc'] > 0.0
            assert self.grid_limits['ymax'] >= self.grid_limits['ymin']
            assert self.grid_limits['yspc'] > 0.0
        elif isinstance(self.grid_limits, float):
            self.grid_limits = Grid.make_from_catalogue(
                self.catalogue, self.grid_limits,
                config['Length_Limit'] * config['BandWidth'])

        completeness_table, mag_inc = utils.get_even_magnitude_completeness(
            completeness_table, self.catalogue)

        end_year = self.catalogue.end_year

        # Get Weichert factor
        t_f, _ = utils.get_weichert_factor(self.beta, completeness_table[:, 1],
                                           completeness_table[:, 0], end_year)
        # Get the grid
        self.create_3D_grid(self.catalogue, completeness_table, t_f, mag_inc)
        if config['increment']:
            # Get Hermann adjustment factors
            fval, fival = utils.hermann_adjustment_factors(
                self.bval, completeness_table[0, 1], config['increment'])
            self.data[:, -1] = fval * fival * self.data[:, -1]

        # Apply smoothing
        smoothed_data, sum_data, sum_smooth = self.kernel.smooth_data(
            self.data, config, self.use_3d)
        print('Smoothing Total Rate Comparison - '
              'Observed: %.6g, Smoothed: %.6g' % (sum_data, sum_smooth))
        self.data = np.column_stack([self.data, smoothed_data])
        return self.data

    def create_2D_grid_simple(self,
                              longitude,
                              latitude,
                              year,
                              magnitude,
                              completeness_table,
                              t_f=1.,
                              mag_inc=0.1):
        '''
        Generates the grid from the limits using an approach closer to that of
        Frankel (1995)

        :param numpy.ndarray longitude:
            Vector of earthquake longitudes

        :param numpy.ndarray latitude:
            Vector of earthquake latitudes

        :param numpy.ndarray year:
            Vector of earthquake years

        :param numpy.ndarray magnitude:
            Vector of earthquake magnitudes

        :param numpy.ndarray completeness_table:
            Completeness table

        :param float t_f:
            Weichert adjustment factor

        :returns:
           Two-dimensional spatial grid of observed rates

        '''
        assert mag_inc > 0.

        xlim = np.ceil((self.grid_limits['xmax'] - self.grid_limits['xmin']) /
                       self.grid_limits['xspc'])
        ylim = np.ceil((self.grid_limits['ymax'] - self.grid_limits['ymin']) /
                       self.grid_limits['yspc'])
        ncolx = int(xlim)
        ncoly = int(ylim)
        grid_count = np.zeros(ncolx * ncoly, dtype=float)
        for iloc in range(0, len(longitude)):
            dlon = (longitude[iloc] - self.grid_limits['xmin']) /\
                self.grid_limits['xspc']
            if (dlon < 0.) or (dlon > xlim):
                # Earthquake outside longitude limits
                continue
            xcol = int(dlon)
            if xcol == ncolx:
                # If longitude is directly on upper grid line then retain
                xcol = ncolx - 1
            dlat = fabs(self.grid_limits['ymax'] - latitude[iloc]) /\
                self.grid_limits['yspc']
            if (dlat < 0.) or (dlat > ylim):
                # Earthquake outside latitude limits
                continue
            ycol = int(dlat)  # Correct for floating precision
            if ycol == ncoly:
                # If latitude is directly on upper grid line then retain
                ycol = ncoly - 1
            kmarker = (ycol * int(xlim)) + xcol
            adjust = _get_adjustment(magnitude[iloc], year[iloc],
                                     completeness_table[0, 1],
                                     completeness_table[:, 0], t_f, mag_inc)
            if adjust:
                grid_count[kmarker] = grid_count[kmarker] + adjust
        return grid_count

    def create_3D_grid(self,
                       catalogue,
                       completeness_table,
                       t_f=1.0,
                       mag_inc=0.1):
        '''
        Counts the earthquakes observed in a three dimensional grid


        :param catalogue:
            Instance of the openquake.hmtk.seismicity.catalogue.Catalogue class
            catalogue.data dictionary containing the following -
            'year' - numpy.ndarray vector of years
            'longitude' - numpy.ndarray vector of longitudes
            'latitude' - numpy.ndarray vector of latitudes
            'depth' - numpy.ndarray vector of depths

        :param np.ndarray completeness_table:
            Completeness of the catalogue assuming evenly spaced magnitudes
            from most recent bin to oldest bin [year, magnitude]

        :param float t_f:
            Weichert adjustment factor

        :param float mag_inc:
            Increment of the completeness magnitude (rendered 0.1)

        :returns:
           Three-dimensional spatial grid of observed rates (or two dimensional
           if only one depth layer is considered)

        '''
        x_bins = np.arange(self.grid_limits['xmin'], self.grid_limits['xmax'],
                           self.grid_limits['xspc'])
        if x_bins[-1] < self.grid_limits['xmax']:
            x_bins = np.hstack([x_bins, x_bins[-1] + self.grid_limits['xspc']])

        y_bins = np.arange(self.grid_limits['ymin'], self.grid_limits['ymax'],
                           self.grid_limits['yspc'])
        if y_bins[-1] < self.grid_limits['ymax']:
            y_bins = np.hstack([y_bins, y_bins[-1] + self.grid_limits['yspc']])

        z_bins = np.arange(self.grid_limits['zmin'],
                           self.grid_limits['zmax'] + self.grid_limits['zspc'],
                           self.grid_limits['zspc'])

        if z_bins[-1] < self.grid_limits['zmax']:
            z_bins = np.hstack([z_bins, z_bins[-1] + self.grid_limits['zspc']])

        # Define centre points of grid cells
        gridx, gridy = np.meshgrid((x_bins[1:] + x_bins[:-1]) / 2.,
                                   (y_bins[1:] + y_bins[:-1]) / 2.)

        n_x, n_y = np.shape(gridx)
        gridx = np.reshape(gridx, [n_x * n_y, 1])
        gridy = np.reshape(np.flipud(gridy), [n_x * n_y, 1])

        # Only one depth range
        idx = np.logical_and(catalogue.data['depth'] >= z_bins[0],
                             catalogue.data['depth'] < z_bins[1])
        mid_depth = (z_bins[0] + z_bins[1]) / 2.

        data_grid = np.column_stack([
            gridx, gridy, mid_depth * np.ones(n_x * n_y, dtype=float),
            self.create_2D_grid_simple(catalogue.data['longitude'][idx],
                                       catalogue.data['latitude'][idx],
                                       catalogue.data['year'][idx],
                                       catalogue.data['magnitude'][idx],
                                       completeness_table, t_f, mag_inc)
        ])

        if len(z_bins) < 3:
            # Only one depth range
            self.data = data_grid
            return

        # Multiple depth layers - append to grid
        for iloc in range(1, len(z_bins) - 1):
            idx = np.logical_and(catalogue.data['depth'] >= z_bins[iloc],
                                 catalogue.data['depth'] < z_bins[iloc + 1])
            mid_depth = (z_bins[iloc] + z_bins[iloc + 1]) / 2.

            temp_grid = np.column_stack([
                gridx, gridy, mid_depth * np.ones(n_x * n_y, dtype=float),
                self.create_2D_grid_simple(catalogue.data['longitude'][idx],
                                           catalogue.data['latitude'][idx],
                                           catalogue.data['year'][idx],
                                           catalogue.data['magnitude'][idx],
                                           completeness_table, t_f, mag_inc)
            ])

            data_grid = np.vstack([data_grid, temp_grid])
        self.data = data_grid

    def write_to_csv(self, filename):
        '''
        Exports to simple csv

        :param str filename:
            Path to file for export
        '''
        fid = open(filename, 'wt')
        # Create header list
        header_info = [
            'Longitude', 'Latitude', 'Depth', 'Observed Count',
            'Smoothed Rate', 'b-value'
        ]
        writer = csv.DictWriter(fid, fieldnames=header_info)
        headers = dict((name0, name0) for name0 in header_info)
        # Write to file
        writer.writerow(headers)
        for row in self.data:
            # institute crude compression by omitting points with no seismicity
            # and taking advantage of the %g format
            if row[4] == 0:
                continue
            row_dict = {
                'Longitude': '%g' % row[0],
                'Latitude': '%g' % row[1],
                'Depth': '%g' % row[2],
                'Observed Count': '%d' % row[3],
                'Smoothed Rate': '%.6g' % row[4],
                'b-value': '%g' % self.bval
            }
            writer.writerow(row_dict)
        fid.close()
class TestIsotropicGaussian(unittest.TestCase):
    '''
    Simple tests the of Isotropic Gaussian Kernel
    (as implemented by Frankel (1995))
    '''
    def setUp(self):
        self.model = IsotropicGaussian()
        # Setup simple grid
        [gx, gy] = np.meshgrid(np.arange(35.5, 40., 0.5),
                               np.arange(40.5, 45., 0.5))
        ngp = np.shape(gx)[0] * np.shape(gx)[1]
        gx = np.reshape(gx, [ngp, 1])
        gy = np.reshape(gy, [ngp, 1])
        depths = 10. * np.ones(ngp)
        self.data = np.column_stack(
            [gx, gy, depths, np.zeros(ngp, dtype=float)])

    def test_kernel_single_event(self):
        # ensure kernel is smoothing values correctly for a single event
        self.data[50, 3] = 1.
        config = {'Length_Limit': 3.0, 'BandWidth': 30.0}
        expected_array = np.genfromtxt(
            os.path.join(BASE_PATH, TEST_1_VALUE_FILE))
        (smoothed_array, sum_data, sum_smooth) = \
            self.model.smooth_data(self.data, config)
        raise unittest.SkipTest('Have Graeme fix this')
        np.testing.assert_array_almost_equal(expected_array, smoothed_array)
        self.assertAlmostEqual(sum_data, 1.)
        # Assert that sum of the smoothing is equal to the sum of the
        # data values to 3 dp
        self.assertAlmostEqual(sum_data, sum_smooth, 3)

    def test_kernel_multiple_event(self):
        # ensure kernel is smoothing values correctly for multiple events
        self.data[[5, 30, 65], 3] = 1.
        config = {'Length_Limit': 3.0, 'BandWidth': 30.0}
        expected_array = np.genfromtxt(
            os.path.join(BASE_PATH, TEST_3_VALUE_FILE))
        (smoothed_array, sum_data, sum_smooth) = \
            self.model.smooth_data(self.data, config)
        raise unittest.SkipTest('Have Graeme fix this')
        np.testing.assert_array_almost_equal(expected_array, smoothed_array)
        self.assertAlmostEqual(sum_data, 3.)
        # Assert that sum of the smoothing is equal to the sum of the
        # data values to 3 dp
        self.assertAlmostEqual(sum_data, sum_smooth, 2)

    def test_kernel_single_event_3d(self):
        # ensure kernel is smoothing values correctly for a single event
        self.data[50, 3] = 1.
        self.data[50, 2] = 20.
        config = {'Length_Limit': 3.0, 'BandWidth': 30.0}
        expected_array = np.genfromtxt(
            os.path.join(BASE_PATH, TEST_1_VALUE_3D_FILE))
        (smoothed_array, sum_data, sum_smooth) = \
            self.model.smooth_data(self.data, config, is_3d=True)
        raise unittest.SkipTest('Have Graeme fix this')
        np.testing.assert_array_almost_equal(expected_array, smoothed_array)
        self.assertAlmostEqual(sum_data, 1.)
        # Assert that sum of the smoothing is equal to the sum of the
        # data values to 2 dp
        self.assertAlmostEqual(sum_data, sum_smooth, 2)
class TestIsotropicGaussian(unittest.TestCase):
    '''
    Simple tests the of Isotropic Gaussian Kernel
    (as implemented by Frankel (1995))
    '''
    def setUp(self):
        self.model = IsotropicGaussian()
        # Setup simple grid
        [gx, gy] = np.meshgrid(np.arange(35.5, 40., 0.5),
                               np.arange(40.5, 45., 0.5))
        ngp = np.shape(gx)[0] * np.shape(gx)[1]
        gx = np.reshape(gx, [ngp, 1])
        gy = np.reshape(gy, [ngp, 1])
        depths = 10. * np.ones(ngp)
        self.data = np.column_stack([gx, gy, depths,
                                    np.zeros(ngp, dtype=float)])

    def test_kernel_single_event(self):
        # ensure kernel is smoothing values correctly for a single event
        self.data[50, 3] = 1.
        config = {'Length_Limit': 3.0, 'BandWidth': 30.0}
        expected_array = np.genfromtxt(os.path.join(BASE_PATH,
                                                    TEST_1_VALUE_FILE))
        (smoothed_array, sum_data, sum_smooth) = \
            self.model.smooth_data(self.data, config)
        raise unittest.SkipTest('Have Graeme fix this')
        np.testing.assert_array_almost_equal(expected_array, smoothed_array)
        self.assertAlmostEqual(sum_data, 1.)
        # Assert that sum of the smoothing is equal to the sum of the
        # data values to 3 dp
        self.assertAlmostEqual(sum_data, sum_smooth, 3)

    def test_kernel_multiple_event(self):
        # ensure kernel is smoothing values correctly for multiple events
        self.data[[5, 30, 65], 3] = 1.
        config = {'Length_Limit': 3.0, 'BandWidth': 30.0}
        expected_array = np.genfromtxt(os.path.join(BASE_PATH,
                                                    TEST_3_VALUE_FILE))
        (smoothed_array, sum_data, sum_smooth) = \
            self.model.smooth_data(self.data, config)
        raise unittest.SkipTest('Have Graeme fix this')
        np.testing.assert_array_almost_equal(expected_array, smoothed_array)
        self.assertAlmostEqual(sum_data, 3.)
        # Assert that sum of the smoothing is equal to the sum of the
        # data values to 3 dp
        self.assertAlmostEqual(sum_data, sum_smooth, 2)

    def test_kernel_single_event_3d(self):
        # ensure kernel is smoothing values correctly for a single event
        self.data[50, 3] = 1.
        self.data[50, 2] = 20.
        config = {'Length_Limit': 3.0, 'BandWidth': 30.0}
        expected_array = np.genfromtxt(os.path.join(BASE_PATH,
                                                    TEST_1_VALUE_3D_FILE))
        (smoothed_array, sum_data, sum_smooth) = \
            self.model.smooth_data(self.data, config, is_3d=True)
        raise unittest.SkipTest('Have Graeme fix this')
        np.testing.assert_array_almost_equal(expected_array, smoothed_array)
        self.assertAlmostEqual(sum_data, 1.)
        # Assert that sum of the smoothing is equal to the sum of the
        # data values to 2 dp
        self.assertAlmostEqual(sum_data, sum_smooth, 2)
示例#8
0
    def run_analysis(self, catalogue, config, completeness_table=None,
                     smoothing_kernel=None):
        '''
        Runs an analysis of smoothed seismicity in the manner
        originally implemented by Frankel (1995)

        :param catalogue:
            Instance of the openquake.hmtk.seismicity.catalogue.Catalogue class
            catalogue.data dictionary containing the following -
            'year' - numpy.ndarray vector of years
            'longitude' - numpy.ndarray vector of longitudes
            'latitude' - numpy.ndarray vector of latitudes
            'depth' - numpy.ndarray vector of depths

        :param dict config:
            Configuration settings of the algorithm:
            * 'Length_Limit' - Maximum number of bandwidths for use in
            smoothing (Float)
            * 'BandWidth' - Bandwidth (km) of the Smoothing Kernel (Float)
            * 'increment' - Output incremental (True) or cumulative a-value
            (False)

        :param np.ndarray completeness_table:
            Completeness of the catalogue assuming evenly spaced magnitudes
            from most recent bin to oldest bin [year, magnitude]

        :param smoothing_kernel:
            Smoothing kernel as instance of :class:
            `openquake.hmtk.seismicity.smoothing.kernels.base.BaseSmoothingKernel`

        :returns:
            Full smoothed seismicity data as np.ndarray, of the form
            [Longitude, Latitude, Depth, Observed, Smoothed]
        '''

        self.catalogue = catalogue
        if smoothing_kernel:
            self.kernel = smoothing_kernel
        else:
            self.kernel = IsotropicGaussian()

        # If no grid limits are specified then take from catalogue
        if isinstance(self.grid_limits, list):
            self.grid_limits = Grid.make_from_list(self.grid_limits)
            assert self.grid_limits['xmax'] >= self.grid_limits['xmin']
            assert self.grid_limits['xspc'] > 0.0
            assert self.grid_limits['ymax'] >= self.grid_limits['ymin']
            assert self.grid_limits['yspc'] > 0.0
        elif isinstance(self.grid_limits, float):
            self.grid_limits = Grid.make_from_catalogue(
                self.catalogue, self.grid_limits,
                config['Length_Limit'] * config['BandWidth'])

        completeness_table, mag_inc = utils.get_even_magnitude_completeness(
            completeness_table,
            self.catalogue)

        end_year = self.catalogue.end_year

        # Get Weichert factor
        t_f, _ = utils.get_weichert_factor(self.beta,
                                           completeness_table[:, 1],
                                           completeness_table[:, 0],
                                           end_year)
        # Get the grid
        self.create_3D_grid(self.catalogue, completeness_table, t_f, mag_inc)
        if config['increment']:
            # Get Hermann adjustment factors
            fval, fival = utils.hermann_adjustment_factors(
                self.bval,
                completeness_table[0, 1], config['increment'])
            self.data[:, -1] = fval * fival * self.data[:, -1]

        # Apply smoothing
        smoothed_data, sum_data, sum_smooth = self.kernel.smooth_data(
            self.data, config, self.use_3d)
        print('Smoothing Total Rate Comparison - '
              'Observed: %.6g, Smoothed: %.6g' % (sum_data, sum_smooth))
        self.data = np.column_stack([self.data, smoothed_data])
        return self.data
示例#9
0
class SmoothedSeismicity(object):
    '''
    Class to implement an analysis of Smoothed Seismicity, including the
    grid counting of data and the smoothing.

    :param np.ndarray grid:
        Observed count in each cell [Long., Lat., Depth., Count]

    :param catalogue:
        Valid instance of the :class: openquake.hmtk.seismicity.catalogue.Catalogue

    :param bool use_3d:
        Decide if analysis is 2-D (False) or 3-D (True). If 3-D then distances
        will use hypocentral distance, otherwise epicentral distance

    :param float bval:
        b-value

    :param float beta:
        Beta value for exponential form (beta = bval * log(10.))

    :param np.ndarray data:
        Smoothed seismicity output

    :param dict grid_limits:
        Limits ot the grid used for defining the cells
    '''

    def __init__(self, grid_limits, use_3d=False, bvalue=None):
        '''
        Instatiate class with a set of grid limits

        :param grid_limits:
            It could be a float (in that case the grid is computed from the
            catalogue with the given spacing).

            Or an array of the form:
            [xmin, xmax, spcx, ymin, ymax, spcy, zmin, spcz]

        :param bool use_3d:
            Choose whether to use hypocentral distances for smoothing or only
            epicentral

        :param float bval:
            b-value for analysis
        '''
        self.grid = None
        self.catalogue = None
        self.use_3d = use_3d
        self.bval = bvalue
        if self.bval:
            self.beta = self.bval * log(10.)
        else:
            self.beta = None
        self.data = None

        self.grid_limits = grid_limits
        self.kernel = None

    def run_analysis(self, catalogue, config, completeness_table=None,
                     smoothing_kernel=None):
        '''
        Runs an analysis of smoothed seismicity in the manner
        originally implemented by Frankel (1995)

        :param catalogue:
            Instance of the openquake.hmtk.seismicity.catalogue.Catalogue class
            catalogue.data dictionary containing the following -
            'year' - numpy.ndarray vector of years
            'longitude' - numpy.ndarray vector of longitudes
            'latitude' - numpy.ndarray vector of latitudes
            'depth' - numpy.ndarray vector of depths

        :param dict config:
            Configuration settings of the algorithm:
            * 'Length_Limit' - Maximum number of bandwidths for use in
            smoothing (Float)
            * 'BandWidth' - Bandwidth (km) of the Smoothing Kernel (Float)
            * 'increment' - Output incremental (True) or cumulative a-value
            (False)

        :param np.ndarray completeness_table:
            Completeness of the catalogue assuming evenly spaced magnitudes
            from most recent bin to oldest bin [year, magnitude]

        :param smoothing_kernel:
            Smoothing kernel as instance of :class:
            `openquake.hmtk.seismicity.smoothing.kernels.base.BaseSmoothingKernel`

        :returns:
            Full smoothed seismicity data as np.ndarray, of the form
            [Longitude, Latitude, Depth, Observed, Smoothed]
        '''

        self.catalogue = catalogue
        if smoothing_kernel:
            self.kernel = smoothing_kernel
        else:
            self.kernel = IsotropicGaussian()

        # If no grid limits are specified then take from catalogue
        if isinstance(self.grid_limits, list):
            self.grid_limits = Grid.make_from_list(self.grid_limits)
            assert self.grid_limits['xmax'] >= self.grid_limits['xmin']
            assert self.grid_limits['xspc'] > 0.0
            assert self.grid_limits['ymax'] >= self.grid_limits['ymin']
            assert self.grid_limits['yspc'] > 0.0
        elif isinstance(self.grid_limits, float):
            self.grid_limits = Grid.make_from_catalogue(
                self.catalogue, self.grid_limits,
                config['Length_Limit'] * config['BandWidth'])

        completeness_table, mag_inc = utils.get_even_magnitude_completeness(
            completeness_table,
            self.catalogue)

        end_year = self.catalogue.end_year

        # Get Weichert factor
        t_f, _ = utils.get_weichert_factor(self.beta,
                                           completeness_table[:, 1],
                                           completeness_table[:, 0],
                                           end_year)
        # Get the grid
        self.create_3D_grid(self.catalogue, completeness_table, t_f, mag_inc)
        if config['increment']:
            # Get Hermann adjustment factors
            fval, fival = utils.hermann_adjustment_factors(
                self.bval,
                completeness_table[0, 1], config['increment'])
            self.data[:, -1] = fval * fival * self.data[:, -1]

        # Apply smoothing
        smoothed_data, sum_data, sum_smooth = self.kernel.smooth_data(
            self.data, config, self.use_3d)
        print('Smoothing Total Rate Comparison - '
              'Observed: %.6g, Smoothed: %.6g' % (sum_data, sum_smooth))
        self.data = np.column_stack([self.data, smoothed_data])
        return self.data

    def create_2D_grid_simple(self, longitude, latitude, year, magnitude,
                              completeness_table, t_f=1., mag_inc=0.1):
        '''
        Generates the grid from the limits using an approach closer to that of
        Frankel (1995)

        :param numpy.ndarray longitude:
            Vector of earthquake longitudes

        :param numpy.ndarray latitude:
            Vector of earthquake latitudes

        :param numpy.ndarray year:
            Vector of earthquake years

        :param numpy.ndarray magnitude:
            Vector of earthquake magnitudes

        :param numpy.ndarray completeness_table:
            Completeness table

        :param float t_f:
            Weichert adjustment factor

        :returns:
           Two-dimensional spatial grid of observed rates

        '''
        assert mag_inc > 0.

        xlim = np.ceil(
            (self.grid_limits['xmax'] - self.grid_limits['xmin']) /
            self.grid_limits['xspc'])
        ylim = np.ceil(
            (self.grid_limits['ymax'] - self.grid_limits['ymin']) /
            self.grid_limits['yspc'])
        ncolx = int(xlim)
        ncoly = int(ylim)
        grid_count = np.zeros(ncolx * ncoly, dtype=float)
        for iloc in range(0, len(longitude)):
            dlon = (longitude[iloc] - self.grid_limits['xmin']) /\
                self.grid_limits['xspc']
            if (dlon < 0.) or (dlon > xlim):
                # Earthquake outside longitude limits
                continue
            xcol = int(dlon)
            if xcol == ncolx:
                # If longitude is directly on upper grid line then retain
                xcol = ncolx - 1
            dlat = fabs(self.grid_limits['ymax'] - latitude[iloc]) /\
                self.grid_limits['yspc']
            if (dlat < 0.) or (dlat > ylim):
                # Earthquake outside latitude limits
                continue
            ycol = int(dlat)  # Correct for floating precision
            if ycol == ncoly:
                # If latitude is directly on upper grid line then retain
                ycol = ncoly - 1
            kmarker = (ycol * int(xlim)) + xcol
            adjust = _get_adjustment(magnitude[iloc],
                                     year[iloc],
                                     completeness_table[0, 1],
                                     completeness_table[:, 0],
                                     t_f,
                                     mag_inc)
            if adjust:
                grid_count[kmarker] = grid_count[kmarker] + adjust
        return grid_count

    def create_3D_grid(self, catalogue, completeness_table, t_f=1.0,
                       mag_inc=0.1):
        '''
        Counts the earthquakes observed in a three dimensional grid


        :param catalogue:
            Instance of the openquake.hmtk.seismicity.catalogue.Catalogue class
            catalogue.data dictionary containing the following -
            'year' - numpy.ndarray vector of years
            'longitude' - numpy.ndarray vector of longitudes
            'latitude' - numpy.ndarray vector of latitudes
            'depth' - numpy.ndarray vector of depths

        :param np.ndarray completeness_table:
            Completeness of the catalogue assuming evenly spaced magnitudes
            from most recent bin to oldest bin [year, magnitude]

        :param float t_f:
            Weichert adjustment factor

        :param float mag_inc:
            Increment of the completeness magnitude (rendered 0.1)

        :returns:
           Three-dimensional spatial grid of observed rates (or two dimensional
           if only one depth layer is considered)

        '''
        x_bins = np.arange(self.grid_limits['xmin'],
                           self.grid_limits['xmax'],
                           self.grid_limits['xspc'])
        if x_bins[-1] < self.grid_limits['xmax']:
            x_bins = np.hstack([x_bins, x_bins[-1] + self.grid_limits['xspc']])

        y_bins = np.arange(self.grid_limits['ymin'],
                           self.grid_limits['ymax'],
                           self.grid_limits['yspc'])
        if y_bins[-1] < self.grid_limits['ymax']:
            y_bins = np.hstack([y_bins, y_bins[-1] + self.grid_limits['yspc']])

        z_bins = np.arange(self.grid_limits['zmin'],
                           self.grid_limits['zmax'] + self.grid_limits['zspc'],
                           self.grid_limits['zspc'])

        if z_bins[-1] < self.grid_limits['zmax']:
            z_bins = np.hstack([z_bins, z_bins[-1] + self.grid_limits['zspc']])

        # Define centre points of grid cells
        gridx, gridy = np.meshgrid((x_bins[1:] + x_bins[:-1]) / 2.,
                                   (y_bins[1:] + y_bins[:-1]) / 2.)

        n_x, n_y = np.shape(gridx)
        gridx = np.reshape(gridx, [n_x * n_y, 1])
        gridy = np.reshape(np.flipud(gridy), [n_x * n_y, 1])

        # Only one depth range
        idx = np.logical_and(catalogue.data['depth'] >= z_bins[0],
                             catalogue.data['depth'] < z_bins[1])
        mid_depth = (z_bins[0] + z_bins[1]) / 2.

        data_grid = np.column_stack([
            gridx,
            gridy,
            mid_depth * np.ones(n_x * n_y, dtype=float),
            self.create_2D_grid_simple(catalogue.data['longitude'][idx],
                                       catalogue.data['latitude'][idx],
                                       catalogue.data['year'][idx],
                                       catalogue.data['magnitude'][idx],
                                       completeness_table,
                                       t_f,
                                       mag_inc)])

        if len(z_bins) < 3:
            # Only one depth range
            self.data = data_grid
            return

        # Multiple depth layers - append to grid
        for iloc in range(1, len(z_bins) - 1):
            idx = np.logical_and(catalogue.data['depth'] >= z_bins[iloc],
                                 catalogue.data['depth'] < z_bins[iloc + 1])
            mid_depth = (z_bins[iloc] + z_bins[iloc + 1]) / 2.

            temp_grid = np.column_stack([
                gridx,
                gridy,
                mid_depth * np.ones(n_x * n_y, dtype=float),
                self.create_2D_grid_simple(catalogue.data['longitude'][idx],
                                           catalogue.data['latitude'][idx],
                                           catalogue.data['year'][idx],
                                           catalogue.data['magnitude'][idx],
                                           completeness_table,
                                           t_f,
                                           mag_inc)])

            data_grid = np.vstack([data_grid, temp_grid])
        self.data = data_grid

    def write_to_csv(self, filename):
        '''
        Exports to simple csv

        :param str filename:
            Path to file for export
        '''
        fid = open(filename, 'wt')
        # Create header list
        header_info = ['Longitude', 'Latitude', 'Depth', 'Observed Count',
                       'Smoothed Rate', 'b-value']
        writer = csv.DictWriter(fid, fieldnames=header_info)
        headers = dict((name0, name0) for name0 in header_info)
        # Write to file
        writer.writerow(headers)
        for row in self.data:
            # institute crude compression by omitting points with no seismicity
            # and taking advantage of the %g format
            if row[4] == 0:
                continue
            row_dict = {'Longitude': '%g' % row[0],
                        'Latitude': '%g' % row[1],
                        'Depth': '%g' % row[2],
                        'Observed Count': '%d' % row[3],
                        'Smoothed Rate': '%.6g' % row[4],
                        'b-value': '%g' % self.bval}
            writer.writerow(row_dict)
        fid.close()