Exemple #1
0
    def check_aki_richards_convention(cls, edges):
        """
        Verify that surface (as defined by corner points) conforms with Aki and
        Richard convention (i.e. surface dips right of surface strike)

        This method doesn't have to be called by hands before creating the
        surface object, because it is called from :meth:`from_fault_data`.
        """
        # 1) extract 4 corner points of surface mesh
        # 2) compute cross products between left and right edges and top edge
        # (these define vectors normal to the surface)
        # 3) compute dot products between cross product results and
        # position vectors associated with upper left and right corners (if
        # both angles are less then 90 degrees then the surface is correctly
        # defined)
        ul = edges[0].points[0]
        ur = edges[0].points[-1]
        bl = edges[-1].points[0]
        br = edges[-1].points[-1]
        ul, ur, bl, br = spherical_to_cartesian(
            [ul.longitude, ur.longitude, bl.longitude, br.longitude],
            [ul.latitude, ur.latitude, bl.latitude, br.latitude],
            [ul.depth, ur.depth, bl.depth, br.depth],
        )

        top_edge = ur - ul
        left_edge = bl - ul
        right_edge = br - ur
        left_cross_top = numpy.cross(left_edge, top_edge)
        right_cross_top = numpy.cross(right_edge, top_edge)

        left_cross_top /= numpy.sqrt(numpy.dot(left_cross_top, left_cross_top))
        right_cross_top /= numpy.sqrt(
            numpy.dot(right_cross_top, right_cross_top)
        )
        ul /= numpy.sqrt(numpy.dot(ul, ul))
        ur /= numpy.sqrt(numpy.dot(ur, ur))

        # rounding to 1st digit, to avoid ValueError raised for floating point
        # imprecision
        angle_ul = round(
            numpy.degrees(numpy.arccos(numpy.dot(ul, left_cross_top))), 1
        )
        angle_ur = round(
            numpy.degrees(numpy.arccos(numpy.dot(ur, right_cross_top))), 1
        )

        if (angle_ul > 90) or (angle_ur > 90):
            raise ValueError(
                "Surface does not conform with Aki & Richards convention"
            )
Exemple #2
0
    def from_fault_data(cls, edges, mesh_spacing):
        """
        Create and return a fault surface using fault source data.

        :param edges:
            A list of at least two horizontal edges of the surface
            as instances of :class:`openquake.hazardlib.geo.line.Line`. The
            list should be in top-to-bottom order (the shallowest edge first).
        :param mesh_spacing:
            Distance between two subsequent points in a mesh, in km.
        :returns:
            An instance of :class:`ComplexFaultSurface` created using
            that data.
        :raises ValueError:
            If requested mesh spacing is too big for the surface geometry
            (doesn't allow to put a single mesh cell along length and/or
            width).

        Uses :meth:`check_fault_data` for checking parameters.
        """
        cls.check_fault_data(edges, mesh_spacing)
        surface_nodes = [complex_fault_node(edges)]
        mean_length = numpy.mean([edge.get_length() for edge in edges])
        num_hor_points = int(round(mean_length / mesh_spacing)) + 1
        if num_hor_points <= 1:
            raise ValueError(
                'mesh spacing %.1f km is too big for mean length %.1f km' %
                (mesh_spacing, mean_length))
        edges = [
            edge.resample_to_num_points(num_hor_points).points
            for i, edge in enumerate(edges)
        ]

        vert_edges = [Line(v_edge) for v_edge in zip(*edges)]
        mean_width = numpy.mean([v_edge.get_length() for v_edge in vert_edges])
        num_vert_points = int(round(mean_width / mesh_spacing)) + 1
        if num_vert_points <= 1:
            raise ValueError(
                'mesh spacing %.1f km is too big for mean width %.1f km' %
                (mesh_spacing, mean_width))

        points = zip(*[
            v_edge.resample_to_num_points(num_vert_points).points
            for v_edge in vert_edges
        ])
        mesh = RectangularMesh.from_points_list(list(points))
        assert 1 not in mesh.shape
        self = cls(mesh)
        self.surface_nodes = surface_nodes
        return self
    def from_fault_data(cls, edges, mesh_spacing):
        """
        Create and return a fault surface using fault source data.

        :param edges:
            A list of at least two horizontal edges of the surface
            as instances of :class:`openquake.hazardlib.geo.line.Line`. The
            list should be in top-to-bottom order (the shallowest edge first).
        :param mesh_spacing:
            Distance between two subsequent points in a mesh, in km.
        :returns:
            An instance of :class:`ComplexFaultSurface` created using
            that data.
        :raises ValueError:
            If requested mesh spacing is too big for the surface geometry
            (doesn't allow to put a single mesh cell along length and/or
            width).

        Uses :meth:`check_fault_data` for checking parameters.
        """
        cls.check_fault_data(edges, mesh_spacing)
        surface_nodes = [complex_fault_node(edges)]
        mean_length = numpy.mean([edge.get_length() for edge in edges])
        num_hor_points = int(round(mean_length / mesh_spacing)) + 1
        if num_hor_points <= 1:
            raise ValueError(
                'mesh spacing %.1f km is too big for mean length %.1f km' %
                (mesh_spacing, mean_length)
            )
        edges = [edge.resample_to_num_points(num_hor_points).points
                 for i, edge in enumerate(edges)]

        vert_edges = [Line(v_edge) for v_edge in zip(*edges)]
        mean_width = numpy.mean([v_edge.get_length() for v_edge in vert_edges])
        num_vert_points = int(round(mean_width / mesh_spacing)) + 1
        if num_vert_points <= 1:
            raise ValueError(
                'mesh spacing %.1f km is too big for mean width %.1f km' %
                (mesh_spacing, mean_width)
            )

        points = zip(*[v_edge.resample_to_num_points(num_vert_points).points
                       for v_edge in vert_edges])
        mesh = RectangularMesh.from_points_list(list(points))
        assert 1 not in mesh.shape
        self = cls(mesh)
        self.surface_nodes = surface_nodes
        return self
Exemple #4
0
def intervals_between(lon1, lat1, depth1, lon2, lat2, depth2, length):
    """
    Find a list of points between two given ones that lie on the same
    great circle arc and are equally spaced by ``length`` km.

    :param float lon1, lat1, depth1:
        Coordinates of a point to start placing intervals from. The first
        point in the resulting list has these coordinates.
    :param float lon2, lat2, depth2:
        Coordinates of the other end of the great circle arc segment
        to put intervals on. The last resulting point might be closer
        to the first reference point than the second one or further,
        since the number of segments is taken as rounded division of
        length between two reference points and ``length``.
    :param length:
        Required distance between two subsequent resulting points, in km.
    :returns:
        Tuple of three 1d numpy arrays: longitudes, latitudes and depths
        of resulting points respectively.

    Rounds the distance between two reference points with respect
    to ``length`` and calls :func:`npoints_towards`.
    """
    assert length > 0
    hdist = geodetic_distance(lon1, lat1, lon2, lat2)
    vdist = depth2 - depth1
    # if this method is called multiple times with coordinates that are
    # separated by the same distance, because of floating point imprecisions
    # the total distance may have slightly different values (for instance if
    # the distance between two set of points is 65 km, total distance can be
    # 64.9999999999989910 and 65.0000000000020322). These two values bring to
    # two different values of num_intervals (32 in the first case and 33 in
    # the second), and this is a problem because for the same distance we
    # should have the same number of intervals. To reduce potential differences
    # due to floating point errors, we therefore round total_distance to a
    # fixed precision (7)
    total_distance = round(numpy.sqrt(hdist ** 2 + vdist ** 2), 7)
    num_intervals = int(round(total_distance / length))
    if num_intervals == 0:
        return numpy.array([lon1]), numpy.array([lat1]), numpy.array([depth1])
    dist_factor = (length * num_intervals) / total_distance
    return npoints_towards(
        lon1, lat1, depth1, azimuth(lon1, lat1, lon2, lat2),
        hdist * dist_factor, vdist * dist_factor, num_intervals + 1
    )
Exemple #5
0
def intervals_between(lon1, lat1, depth1, lon2, lat2, depth2, length):
    """
    Find a list of points between two given ones that lie on the same
    great circle arc and are equally spaced by ``length`` km.

    :param float lon1, lat1, depth1:
        Coordinates of a point to start placing intervals from. The first
        point in the resulting list has these coordinates.
    :param float lon2, lat2, depth2:
        Coordinates of the other end of the great circle arc segment
        to put intervals on. The last resulting point might be closer
        to the first reference point than the second one or further,
        since the number of segments is taken as rounded division of
        length between two reference points and ``length``.
    :param length:
        Required distance between two subsequent resulting points, in km.
    :returns:
        Tuple of three 1d numpy arrays: longitudes, latitudes and depths
        of resulting points respectively.

    Rounds the distance between two reference points with respect
    to ``length`` and calls :func:`npoints_towards`.
    """
    assert length > 0
    hdist = geodetic_distance(lon1, lat1, lon2, lat2)
    vdist = depth2 - depth1
    # if this method is called multiple times with coordinates that are
    # separated by the same distance, because of floating point imprecisions
    # the total distance may have slightly different values (for instance if
    # the distance between two set of points is 65 km, total distance can be
    # 64.9999999999989910 and 65.0000000000020322). These two values bring to
    # two different values of num_intervals (32 in the first case and 33 in
    # the second), and this is a problem because for the same distance we
    # should have the same number of intervals. To reduce potential differences
    # due to floating point errors, we therefore round total_distance to a
    # fixed precision (7)
    total_distance = round(numpy.sqrt(hdist**2 + vdist**2), 7)
    num_intervals = int(round(total_distance / length))
    if num_intervals == 0:
        return numpy.array([lon1]), numpy.array([lat1]), numpy.array([depth1])
    dist_factor = (length * num_intervals) / total_distance
    return npoints_towards(lon1, lat1, depth1, azimuth(lon1, lat1, lon2, lat2),
                           hdist * dist_factor, vdist * dist_factor,
                           num_intervals + 1)
Exemple #6
0
    def _get_rupture_dimensions(self, fault_length, fault_width, mag):
        """
        Calculate rupture dimensions for a given magnitude.

        :param fault_length:
            The length of the fault as a sum of all segments, in km.
        :param fault_width:
            The width of the fault, in km.
        :param mag:
            Magnitude value to calculate rupture geometry for.
        :returns:
            A tuple of two integer items, representing rupture's dimensions:
            number of mesh points along length and along width respectively.

        The rupture is reshaped (conserving area, if possible) if one
        of dimensions exceeds fault geometry. If both do, the rupture
        is considered to cover the whole fault.
        """
        area = self.magnitude_scaling_relationship.get_median_area(
            mag, self.rake)
        rup_length = math.sqrt(area * self.rupture_aspect_ratio)
        rup_width = area / rup_length

        # clip rupture's length and width to fault's length and width
        # if there is no way to fit the rupture otherwise
        if area >= fault_length * fault_width:
            rup_length = fault_length
            rup_width = fault_width
        # reshape rupture (conserving area) if its length or width
        # exceeds fault's length or width
        elif rup_width > fault_width:
            rup_length = rup_length * (rup_width / fault_width)
            rup_width = fault_width
        elif rup_length > fault_length:
            rup_width = rup_width * (rup_length / fault_length)
            rup_length = fault_length

        # round rupture dimensions with respect to mesh_spacing
        # and compute number of points in the rupture along length
        # and width (aka strike and dip)
        rup_cols = int(round(rup_length / self.rupture_mesh_spacing) + 1)
        rup_rows = int(round(rup_width / self.rupture_mesh_spacing) + 1)
        return rup_cols, rup_rows
    def _get_rupture_dimensions(self, fault_length, fault_width, mag):
        """
        Calculate rupture dimensions for a given magnitude.

        :param fault_length:
            The length of the fault as a sum of all segments, in km.
        :param fault_width:
            The width of the fault, in km.
        :param mag:
            Magnitude value to calculate rupture geometry for.
        :returns:
            A tuple of two integer items, representing rupture's dimensions:
            number of mesh points along length and along width respectively.

        The rupture is reshaped (conserving area, if possible) if one
        of dimensions exceeds fault geometry. If both do, the rupture
        is considered to cover the whole fault.
        """
        area = self.magnitude_scaling_relationship.get_median_area(
            mag, self.rake)
        rup_length = math.sqrt(area * self.rupture_aspect_ratio)
        rup_width = area / rup_length

        # clip rupture's length and width to fault's length and width
        # if there is no way to fit the rupture otherwise
        if area >= fault_length * fault_width:
            rup_length = fault_length
            rup_width = fault_width
        # reshape rupture (conserving area) if its length or width
        # exceeds fault's length or width
        elif rup_width > fault_width:
            rup_length = rup_length * (rup_width / fault_width)
            rup_width = fault_width
        elif rup_length > fault_length:
            rup_width = rup_width * (rup_length / fault_length)
            rup_length = fault_length

        # round rupture dimensions with respect to mesh_spacing
        # and compute number of points in the rupture along length
        # and width (aka strike and dip)
        rup_cols = int(round(rup_length / self.rupture_mesh_spacing) + 1)
        rup_rows = int(round(rup_width / self.rupture_mesh_spacing) + 1)
        return rup_cols, rup_rows
    def _get_min_mag_and_num_bins(self):
        """
        Estimate the number of bins in the histogram and return it along with
        the first bin center value.

        Rounds ``min_mag`` and ``max_mag`` with respect to ``bin_width`` to
        make the distance between them include integer number of bins.

        :returns:
            A tuple of 2 items: first bin center, and total number of bins.
        """
        min_mag = round(self.min_mag / self.bin_width) * self.bin_width
        max_mag = (round((self.char_mag + DELTA_CHAR / 2) /
                   self.bin_width) * self.bin_width)
        min_mag += self.bin_width / 2.0
        max_mag -= self.bin_width / 2.0
        # here we use math round on the result of division and not just
        # cast it to integer because for some magnitude values that can't
        # be represented as an IEEE 754 double precisely the result can
        # look like 7.999999999999 which would become 7 instead of 8
        # being naively casted to int so we would lose the last bin.
        num_bins = int(round((max_mag - min_mag) / self.bin_width)) + 1
        return min_mag, num_bins
Exemple #9
0
    def _return_tables(self, mag, imt, val_type):
        """
        Returns the vector of ground motions or standard deviations
        corresponding to the specific magnitude and intensity measure type.

        :param val_type:
            String indicating the type of data {"IMLs", "Total", "Inter" etc}
        """
        if imt.name in 'PGA PGV':
            # Get scalar imt
            if val_type == "IMLs":
                iml_table = self.imls[imt.name][:]
            else:
                iml_table = self.stddevs[val_type][imt.name][:]
            n_d, n_s, n_m = iml_table.shape
            iml_table = iml_table.reshape([n_d, n_m])
        else:
            if val_type == "IMLs":
                periods = self.imls["T"][:]
                iml_table = self.imls["SA"][:]
            else:
                periods = self.stddevs[val_type]["T"][:]
                iml_table = self.stddevs[val_type]["SA"][:]

            low_period = round(periods[0], 7)
            high_period = round(periods[-1], 7)
            if (round(imt.period, 7) < low_period) or (round(imt.period, 7) >
                                                       high_period):
                raise ValueError("Spectral period %.3f outside of valid range "
                                 "(%.3f to %.3f)" %
                                 (imt.period, periods[0], periods[-1]))
            # Apply log-log interpolation for spectral period
            interpolator = interp1d(numpy.log10(periods),
                                    numpy.log10(iml_table),
                                    axis=1)
            iml_table = 10.**interpolator(numpy.log10(imt.period))
        return self.apply_magnitude_interpolation(mag, iml_table)
    def _return_tables(self, mag, imt, val_type):
        """
        Returns the vector of ground motions or standard deviations
        corresponding to the specific magnitude and intensity measure type.

        :param val_type:
            String indicating the type of data {"IMLs", "Total", "Inter" etc}
        """
        if isinstance(imt, (imt_module.PGA, imt_module.PGV)):
            # Get scalar imt
            if val_type == "IMLs":
                iml_table = self.imls[str(imt)][:]
            else:
                iml_table = self.stddevs[val_type][str(imt)][:]
            n_d, n_s, n_m = iml_table.shape
            iml_table = iml_table.reshape([n_d, n_m])
        else:
            if val_type == "IMLs":
                periods = self.imls["T"][:]
                iml_table = self.imls["SA"][:]
            else:
                periods = self.stddevs[val_type]["T"][:]
                iml_table = self.stddevs[val_type]["SA"][:]

            low_period = round(periods[0], 7)
            high_period = round(periods[-1], 7)

            if imt.period < low_period or imt.period > high_period:
                raise ValueError("Spectral period %.3f outside of valid range "
                                 "(%.3f to %.3f)" % (imt.period, periods[0],
                                                     periods[-1]))
            # Apply log-log interpolation for spectral period
            interpolator = interp1d(numpy.log10(periods),
                                    numpy.log10(iml_table),
                                    axis=1)
            iml_table = 10. ** interpolator(numpy.log10(imt.period))
        return self.apply_magnitude_interpolation(mag, iml_table)