Exemple #1
0
    def get_waveforms_synthetic(self, event_name, station_id, long_iteration_name):
        """
        Gets the synthetic waveforms for the given event and station as a
        :class:`~obspy.core.stream.Stream` object.

        :param event_name: The name of the event.
        :param station_id: The id of the station in the form NET.STA.
        :param long_iteration_name: The long form of an iteration name.
        """
        from lasif import rotations

        st = self._get_waveforms(event_name, station_id, data_type="synthetic", tag_or_iteration=long_iteration_name)
        network, station = station_id.split(".")

        iteration = self.comm.iterations.get(long_iteration_name)

        # This maps the synthetic channels to ZNE.
        synthetic_coordinates_mapping = {"X": "N", "Y": "E", "Z": "Z", "E": "E", "N": "N"}

        for tr in st:
            tr.stats.network = network
            tr.stats.station = station
            if tr.stats.channel in ["X"]:
                tr.data *= -1.0
            tr.stats.starttime = self.comm.events.get(event_name)["origin_time"]
            tr.stats.channel = synthetic_coordinates_mapping[tr.stats.channel]

        if not "specfem" in iteration.solver_settings["solver"].lower():
            # Also need to be rotated.
            domain = self.comm.project.domain

            # Coordinates are required for the rotation.
            coordinates = self.comm.query.get_coordinates_for_station(event_name, station_id)

            # First rotate the station back to see, where it was
            # recorded.
            lat, lng = rotations.rotate_lat_lon(
                coordinates["latitude"], coordinates["longitude"], domain["rotation_axis"], -domain["rotation_angle"]
            )
            # Rotate the synthetics.
            n, e, z = rotations.rotate_data(
                st.select(channel="N")[0].data,
                st.select(channel="E")[0].data,
                st.select(channel="Z")[0].data,
                lat,
                lng,
                domain["rotation_axis"],
                domain["rotation_angle"],
            )
            st.select(channel="N")[0].data = n
            st.select(channel="E")[0].data = e
            st.select(channel="Z")[0].data = z

        return st
Exemple #2
0
 def test_RotateData(self):
     """
     Test the rotations.rotate_data() function.
     """
     north_data = np.linspace(0, 10, 20)
     east_data = np.linspace(33, 44, 20)
     vertical_data = np.linspace(-12, -34, 20)
     # A rotation around the rotation axis of the earth with a source at the
     # equator should not change anything.
     new_north_data, new_east_data, new_vertical_data = \
         rotations.rotate_data(north_data, east_data, vertical_data, 0.0,
         123.45, [0, 0, 1], 77.7)
     np.testing.assert_array_almost_equal(north_data, new_north_data, 5)
     np.testing.assert_array_almost_equal(east_data, new_east_data, 5)
     np.testing.assert_array_almost_equal(vertical_data, new_vertical_data,
         5)
     # A rotation around the rotation axis of the earth should not change
     # the vertical component.
     new_north_data, new_east_data, new_vertical_data = \
         rotations.rotate_data(north_data, east_data, vertical_data, -55.66,
         123.45, [0, 0, 1], 77.7)
     np.testing.assert_array_almost_equal(vertical_data, new_vertical_data,
         5)
     # The same is true for any other rotation with an axis through the
     # center of the earth.
     new_north_data, new_east_data, new_vertical_data = \
         rotations.rotate_data(north_data, east_data, vertical_data, -55.66,
         123.45, [123, 345.0, 0.234], 77.7)
     np.testing.assert_array_almost_equal(vertical_data, new_vertical_data,
         5)
     # Any data along the Greenwich meridian and the opposite one should not
     # change with a rotation around the "East Pole" or the "West Pole".
     new_north_data, new_east_data, new_vertical_data = \
         rotations.rotate_data(north_data, east_data, vertical_data, 0.0,
         0.0, [0, 1, 0], 55.0)
     np.testing.assert_array_almost_equal(north_data, new_north_data, 5)
     np.testing.assert_array_almost_equal(east_data, new_east_data, 5)
     np.testing.assert_array_almost_equal(vertical_data, new_vertical_data,
         5)
     new_north_data, new_east_data, new_vertical_data = \
         rotations.rotate_data(north_data, east_data, vertical_data, 0.0,
         0.0, [0, -1, 0], 55.0)
     np.testing.assert_array_almost_equal(north_data, new_north_data, 5)
     np.testing.assert_array_almost_equal(east_data, new_east_data, 5)
     np.testing.assert_array_almost_equal(vertical_data, new_vertical_data,
         5)
     # A rotation of one hundred degree around the x-axis inverts (in this
     # case) north and east components.
     new_north_data, new_east_data, new_vertical_data = \
         rotations.rotate_data(north_data, east_data, vertical_data, 0.0,
         90.0, [1, 0, 0], 180.0)
     np.testing.assert_array_almost_equal(north_data, -new_north_data, 5)
     np.testing.assert_array_almost_equal(east_data, -new_east_data, 5)
     np.testing.assert_array_almost_equal(vertical_data, new_vertical_data,
         5)
Exemple #3
0
    def get_waveforms_synthetic(self, event_name, station_id,
                                long_iteration_name):
        """
        Gets the synthetic waveforms for the given event and station as a
        :class:`~obspy.core.stream.Stream` object.

        :param event_name: The name of the event.
        :param station_id: The id of the station in the form ``NET.STA``.
        :param long_iteration_name: The long form of an iteration name.
        """
        from lasif import rotations
        import lasif.domain

        iteration = self.comm.iterations.get(long_iteration_name)

        st = self._get_waveforms(event_name,
                                 station_id,
                                 data_type="synthetic",
                                 tag_or_iteration=iteration.long_name)
        network, station = station_id.split(".")

        formats = list(set([tr.stats._format for tr in st]))
        if len(formats) != 1:
            raise ValueError(
                "The synthetics for one Earthquake must all have the same "
                "data format under the assumption that they all originate "
                "from the same solver. Found formats: %s" % (str(formats)))
        format = formats[0].lower()

        # In the case of data coming from SES3D the components must be
        # mapped to ZNE as it works in XYZ.
        if format == "ses3d":
            # This maps the synthetic channels to ZNE.
            synthetic_coordinates_mapping = {"X": "N", "Y": "E", "Z": "Z"}

            for tr in st:
                tr.stats.network = network
                tr.stats.station = station
                # SES3D X points south. Reverse it to arrive at ZNE.
                if tr.stats.channel in ["X"]:
                    tr.data *= -1.0
                # SES3D files have no starttime. Set to the event time.
                tr.stats.starttime = \
                    self.comm.events.get(event_name)["origin_time"]
                tr.stats.channel = \
                    synthetic_coordinates_mapping[tr.stats.channel]

            # Rotate if needed. Again only SES3D synthetics need to be rotated.
            domain = self.comm.project.domain
            if isinstance(domain, lasif.domain.RectangularSphericalSection) \
                    and domain.rotation_angle_in_degree and \
                    "ses3d" in iteration.solver_settings["solver"].lower():
                # Coordinates are required for the rotation.
                coordinates = self.comm.query.get_coordinates_for_station(
                    event_name, station_id)

                # First rotate the station back to see, where it was
                # recorded.
                lat, lng = rotations.rotate_lat_lon(
                    lat=coordinates["latitude"],
                    lon=coordinates["longitude"],
                    rotation_axis=domain.rotation_axis,
                    angle=-domain.rotation_angle_in_degree)
                # Rotate the synthetics.
                n, e, z = rotations.rotate_data(
                    st.select(channel="N")[0].data,
                    st.select(channel="E")[0].data,
                    st.select(channel="Z")[0].data, lat, lng,
                    domain.rotation_axis, domain.rotation_angle_in_degree)
                st.select(channel="N")[0].data = n
                st.select(channel="E")[0].data = e
                st.select(channel="Z")[0].data = z

        st.sort()

        # Apply the project function that modifies synthetics on the fly.
        fct = self.comm.project.get_project_function("process_synthetics")
        return fct(st,
                   iteration=iteration,
                   event=self.comm.events.get(event_name))
Exemple #4
0
    def get_waveforms_synthetic(self, event_name, station_id,
                                long_iteration_name):
        """
        Gets the synthetic waveforms for the given event and station as a
        :class:`~obspy.core.stream.Stream` object.

        :param event_name: The name of the event.
        :param station_id: The id of the station in the form ``NET.STA``.
        :param long_iteration_name: The long form of an iteration name.
        """
        from lasif import rotations
        import lasif.domain

        iteration = self.comm.iterations.get(long_iteration_name)

        st = self._get_waveforms(event_name, station_id,
                                 data_type="synthetic",
                                 tag_or_iteration=iteration.long_name)
        network, station = station_id.split(".")

        formats = list(set([tr.stats._format for tr in st]))
        if len(formats) != 1:
            raise ValueError(
                "The synthetics for one Earthquake must all have the same "
                "data format under the assumption that they all originate "
                "from the same solver. Found formats: %s" % (str(formats)))
        format = formats[0].lower()

        # In the case of data coming from SES3D the components must be
        # mapped to ZNE as it works in XYZ.
        if format == "ses3d":
            # This maps the synthetic channels to ZNE.
            synthetic_coordinates_mapping = {"X": "N", "Y": "E", "Z": "Z"}

            for tr in st:
                tr.stats.network = network
                tr.stats.station = station
                # SES3D X points south. Reverse it to arrive at ZNE.
                if tr.stats.channel in ["X"]:
                    tr.data *= -1.0
                # SES3D files have no starttime. Set to the event time.
                tr.stats.starttime = \
                    self.comm.events.get(event_name)["origin_time"]
                tr.stats.channel = \
                    synthetic_coordinates_mapping[tr.stats.channel]

            # Rotate if needed. Again only SES3D synthetics need to be rotated.
            domain = self.comm.project.domain
            if isinstance(domain, lasif.domain.RectangularSphericalSection) \
                    and domain.rotation_angle_in_degree and \
                    "ses3d" in iteration.solver_settings["solver"].lower():
                # Coordinates are required for the rotation.
                coordinates = self.comm.query.get_coordinates_for_station(
                    event_name, station_id)

                # First rotate the station back to see, where it was
                # recorded.
                lat, lng = rotations.rotate_lat_lon(
                    lat=coordinates["latitude"], lon=coordinates["longitude"],
                    rotation_axis=domain.rotation_axis,
                    angle=-domain.rotation_angle_in_degree)
                # Rotate the synthetics.
                n, e, z = rotations.rotate_data(
                    st.select(channel="N")[0].data,
                    st.select(channel="E")[0].data,
                    st.select(channel="Z")[0].data,
                    lat, lng,
                    domain.rotation_axis,
                    domain.rotation_angle_in_degree)
                st.select(channel="N")[0].data = n
                st.select(channel="E")[0].data = e
                st.select(channel="Z")[0].data = z

        st.sort()

        # Apply the project function that modifies synthetics on the fly.
        fct = self.comm.project.get_project_function("process_synthetics")
        return fct(st, iteration=iteration,
                   event=self.comm.events.get(event_name))
Exemple #5
0
            def get_value(self):
                station_id, coordinates = self.items[self.current_index]

                data = Stream()
                # Now get the actual waveform files. Also find the
                # corresponding station file and check the coordinates.
                this_waveforms = {_i["channel_id"]: _i for _i in waveforms
                    if _i["channel_id"].startswith(station_id + ".")}
                marked_for_deletion = []
                for key, value in this_waveforms.iteritems():
                    value["trace"] = read(value["filename"])[0]
                    data += value["trace"]
                    value["station_file"] = \
                        station_cache.get_station_filename(
                            value["channel_id"],
                            UTCDateTime(value["starttime_timestamp"]))
                    if value["station_file"] is None:
                        marked_for_deletion.append(key)
                        msg = ("Warning: Data and station information for '%s'"
                               " is available, but the station information "
                               "only for the wrong timestamp. You should try "
                               "and retrieve the correct station file.")
                        warnings.warn(msg % value["channel_id"])
                        continue
                    data[-1].stats.station_file = value["station_file"]
                for key in marked_for_deletion:
                    del this_waveforms[key]
                if not this_waveforms:
                    msg = "Could not retrieve data for station '%s'." % \
                        station_id
                    warnings.warn(msg)
                    return None
                # Now attempt to get the synthetics.
                synthetics_filenames = []
                for name, path in synthetic_files.iteritems():
                    if (station_id + ".") in name:
                        synthetics_filenames.append(path)

                if len(synthetics_filenames) != 3:
                    msg = "Found %i not 3 synthetics for station '%s'." % (
                        len(synthetics_filenames), station_id)
                    warnings.warn(msg)
                    return None

                synthetics = Stream()
                # Read all synthetics.
                for filename in synthetics_filenames:
                    synthetics += read(filename)
                for synth in synthetics:
                    if synth.stats.channel in ["X", "Z"]:
                        synth.data *= -1.0
                    synth.stats.channel = SYNTH_MAPPING[synth.stats.channel]
                    synth.stats.starttime = event_info["origin_time"]

                # Process the data.
                len_synth = synthetics[0].stats.endtime - \
                    synthetics[0].stats.starttime
                data.trim(synthetics[0].stats.starttime - len_synth * 0.05,
                    synthetics[0].stats.endtime + len_synth * 0.05)
                if data:
                    max_length = max([tr.stats.npts for tr in data])
                else:
                    max_length = 0
                if max_length == 0:
                    msg = ("Warning: After trimming the waveform data to "
                        "the time window of the synthetics, no more data is "
                        "left. The reference time is the one given in the "
                        "QuakeML file. Make sure it is correct and that "
                        "the waveform data actually contains data in that "
                        "time span.")
                    warnings.warn(msg)
                data.detrend("linear")
                data.taper()

                new_time_array = np.linspace(
                    synthetics[0].stats.starttime.timestamp,
                    synthetics[0].stats.endtime.timestamp,
                    synthetics[0].stats.npts)

                # Simulate the traces.
                for trace in data:
                    # Decimate in case there is a large difference between
                    # synthetic sampling rate and sampling_rate of the data.
                    # XXX: Ugly filter, change!
                    if trace.stats.sampling_rate > (6 *
                            synth.stats.sampling_rate):
                        new_nyquist = trace.stats.sampling_rate / 2.0 / 5.0
                        trace.filter("lowpass", freq=new_nyquist, corners=4,
                            zerophase=True)
                        trace.decimate(factor=5, no_filter=None)

                    station_file = trace.stats.station_file
                    if "/SEED/" in station_file:
                        paz = Parser(station_file).getPAZ(trace.id,
                            trace.stats.starttime)
                        trace.simulate(paz_remove=paz)
                    elif "/RESP/" in station_file:
                        trace.simulate(seedresp={"filename": station_file,
                            "units": "VEL", "date": trace.stats.starttime})
                    else:
                        raise NotImplementedError

                    # Make sure that the data array is at least as long as the
                    # synthetics array. Also add some buffer sample for the
                    # spline interpolation to work in any case.
                    buf = synth.stats.delta * 5
                    if synth.stats.starttime < (trace.stats.starttime + buf):
                        trace.trim(starttime=synth.stats.starttime - buf,
                            pad=True, fill_value=0.0)
                    if synth.stats.endtime > (trace.stats.endtime - buf):
                        trace.trim(endtime=synth.stats.endtime + buf, pad=True,
                            fill_value=0.0)

                    old_time_array = np.linspace(
                        trace.stats.starttime.timestamp,
                        trace.stats.endtime.timestamp,
                        trace.stats.npts)

                    # Interpolation.
                    trace.data = interp1d(old_time_array, trace.data,
                        kind=1)(new_time_array)
                    trace.stats.starttime = synthetics[0].stats.starttime
                    trace.stats.sampling_rate = \
                        synthetics[0].stats.sampling_rate

                data.filter("bandpass", freqmin=lowpass, freqmax=highpass)
                synthetics.filter("bandpass", freqmin=lowpass,
                    freqmax=highpass)

                # Rotate the synthetics if nessesary.
                if self.rot_angle:
                    # First rotate the station back to see, where it was
                    # recorded.
                    lat, lng = rotations.rotate_lat_lon(
                        coordinates["latitude"], coordinates["longitude"],
                        self.rot_axis, -self.rot_angle)
                    # Rotate the data.
                    n_trace = synthetics.select(component="N")[0]
                    e_trace = synthetics.select(component="E")[0]
                    z_trace = synthetics.select(component="Z")[0]
                    n, e, z = rotations.rotate_data(n_trace.data, e_trace.data,
                        z_trace.data, lat, lng, self.rot_axis, self.rot_angle)
                    n_trace.data = n
                    e_trace.data = e
                    z_trace.data = z

                return {"data": data, "synthetics": synthetics,
                    "coordinates": coordinates}
Exemple #6
0
    def finalize_adjoint_sources(self, iteration_name, event_name):
        """
        Finalizes the adjoint sources.
        """
        from itertools import izip
        import numpy as np

        from lasif import rotations

        all_coordinates = []
        _i = 0

        window_manager = self.comm.windows.get(event_name, iteration_name)
        event = self.comm.events.get(event_name)
        iteration = self.comm.iterations.get(iteration_name)
        iteration_event_def = iteration.events[event["event_name"]]
        iteration_stations = iteration_event_def["stations"]

        event_weight = iteration_event_def["event_weight"]

        output_folder = self.comm.project.get_output_folder(
            "adjoint_sources__ITERATION_%s__%s" % (iteration_name, event_name))

        l = sorted(window_manager.list())
        for station, windows in itertools.groupby(
                l, key=lambda x: ".".join(x.split(".")[:2])):
            if station not in iteration_stations:
                continue
            station_weight = iteration_stations[station]["station_weight"]
            channels = {}
            for w in windows:
                w = window_manager.get(w)
                channel_weight = 0
                srcs = []
                for window in w:
                    ad_src = window.adjoint_source
                    if not ad_src["adjoint_source"].ptp():
                        continue
                    srcs.append(ad_src["adjoint_source"] * window.weight)
                    channel_weight += window.weight
                if not srcs:
                    continue
                # Final adjoint source for that channel and apply all weights.
                adjoint_source = np.sum(srcs, axis=0) / channel_weight * \
                    event_weight * station_weight
                channels[w.channel_id[-1]] = adjoint_source
            if not channels:
                continue
            # Now all adjoint sources of a window should have the same length.
            length = set(len(v) for v in channels.values())
            assert len(length) == 1
            length = length.pop()
            # All missing channels will be replaced with a zero array.
            for c in ["Z", "N", "E"]:
                if c in  channels:
                    continue
                channels[c] = np.zeros(length)

            # Get the station coordinates
            coords = self.comm.query.get_coordinates_for_station(event_name,
                                                                 station)

            # Rotate. if needed
            rec_lat = coords["latitude"]
            rec_lng = coords["longitude"]
            domain = self.comm.project.domain

            if domain["rotation_angle"]:
                # Rotate the adjoint source location.
                r_rec_lat, r_rec_lng = rotations.rotate_lat_lon(
                    rec_lat, rec_lng, domain["rotation_axis"],
                    -domain["rotation_angle"])
                # Rotate the adjoint sources.
                channels["N"], channels["E"], channels["Z"] = \
                    rotations.rotate_data(
                        channels["N"], channels["E"],
                        channels["Z"], rec_lat, rec_lng,
                        domain["rotation_axis"],
                        -domain["rotation_angle"])
            else:
                r_rec_lat = rec_lat
                r_rec_lng = rec_lng
            r_rec_depth = 0.0
            r_rec_colat = rotations.lat2colat(r_rec_lat)

            CHANNEL_MAPPING = {"X": "N", "Y": "E", "Z": "Z"}

            _i += 1

            adjoint_src_filename = os.path.join(output_folder,
                                                "ad_src_%i" % _i)

            all_coordinates.append((r_rec_colat, r_rec_lng, r_rec_depth))

            # Actually write the adjoint source file in SES3D specific format.
            with open(adjoint_src_filename, "wt") as open_file:
                open_file.write("-- adjoint source ------------------\n")
                open_file.write("-- source coordinates (colat,lon,depth)\n")
                open_file.write("%f %f %f\n" % (r_rec_colat, r_rec_lng,
                                                r_rec_depth))
                open_file.write("-- source time function (x, y, z) --\n")
                for x, y, z in izip(-1.0 * channels[CHANNEL_MAPPING["X"]],
                                    channels[CHANNEL_MAPPING["Y"]],
                                    channels[CHANNEL_MAPPING["Z"]]):
                    open_file.write("%e %e %e\n" % (x, y, z))
                open_file.write("\n")

        # Write the final file.
        with open(os.path.join(output_folder, "ad_srcfile"), "wt") as fh:
            fh.write("%i\n" % _i)
            for line in all_coordinates:
                fh.write("%.6f %.6f %.6f\n" % (line[0], line[1], line[2]))
            fh.write("\n")

        print "Wrote %i adjoint sources to %s." % (
            _i, os.path.relpath(output_folder))
Exemple #7
0
    def finalize_adjoint_sources(self, iteration_name, event_name):
        """
        Finalizes the adjoint sources.
        """
        from itertools import izip
        import numpy as np
        from lasif import rotations

        window_manager = self.comm.windows.get(event_name, iteration_name)
        event = self.comm.events.get(event_name)
        iteration = self.comm.iterations.get(iteration_name)
        iteration_event_def = iteration.events[event["event_name"]]
        iteration_stations = iteration_event_def["stations"]

        # For now assume that the adjoint sources have the same
        # sampling rate as the synthetics which in LASIF's workflow
        # actually has to be true.
        dt = iteration.get_process_params()["dt"]

        # Current domain and solver.
        domain = self.comm.project.domain
        solver = iteration.solver_settings["solver"].lower()

        adjoint_source_stations = set()

        if "ses3d" in solver:
            ses3d_all_coordinates = []

        event_weight = iteration_event_def["event_weight"]

        output_folder = self.comm.project.get_output_folder(
            type="adjoint_sources",
            tag="ITERATION_%s__%s" % (iteration_name, event_name))

        l = sorted(window_manager.list())
        for station, windows in itertools.groupby(
                l, key=lambda x: ".".join(x.split(".")[:2])):
            if station not in iteration_stations:
                continue
            print ".",
            station_weight = iteration_stations[station]["station_weight"]
            channels = {}
            try:
                for w in windows:
                    w = window_manager.get(w)
                    channel_weight = 0
                    srcs = []
                    for window in w:
                        ad_src = window.adjoint_source
                        if not ad_src["adjoint_source"].ptp():
                            continue
                        srcs.append(ad_src["adjoint_source"] * window.weight)
                        channel_weight += window.weight
                    if not srcs:
                        continue
                    # Final adjoint source for that channel and apply all
                    # weights.
                    adjoint_source = np.sum(srcs, axis=0) / channel_weight * \
                        event_weight * station_weight
                    channels[w.channel_id[-1]] = adjoint_source
            except LASIFError as e:
                print("Could not calculate adjoint source for iteration %s "
                      "and station %s. Repick windows? Reason: %s" % (
                          iteration.name, station, str(e)))
                continue
            if not channels:
                continue
            # Now all adjoint sources of a window should have the same length.
            length = set(len(v) for v in channels.values())
            assert len(length) == 1
            length = length.pop()
            # All missing channels will be replaced with a zero array.
            for c in ["Z", "N", "E"]:
                if c in channels:
                    continue
                channels[c] = np.zeros(length)

            # Get the station coordinates
            coords = self.comm.query.get_coordinates_for_station(event_name,
                                                                 station)

            # Rotate. if needed
            rec_lat = coords["latitude"]
            rec_lng = coords["longitude"]

            # The adjoint sources depend on the solver.
            if "ses3d" in solver:
                # Rotate if needed.
                if domain.rotation_angle_in_degree:
                    # Rotate the adjoint source location.
                    r_rec_lat, r_rec_lng = rotations.rotate_lat_lon(
                        rec_lat, rec_lng, domain.rotation_axis,
                        -domain.rotation_angle_in_degree)
                    # Rotate the adjoint sources.
                    channels["N"], channels["E"], channels["Z"] = \
                        rotations.rotate_data(
                            channels["N"], channels["E"],
                            channels["Z"], rec_lat, rec_lng,
                            domain.rotation_axis,
                            -domain.rotation_angle_in_degree)
                else:
                    r_rec_lat = rec_lat
                    r_rec_lng = rec_lng
                r_rec_depth = 0.0
                r_rec_colat = rotations.lat2colat(r_rec_lat)

                # Now once again map from ZNE to the XYZ of SES3D.
                CHANNEL_MAPPING = {"X": "N", "Y": "E", "Z": "Z"}
                adjoint_source_stations.add(station)
                adjoint_src_filename = os.path.join(
                    output_folder, "ad_src_%i" % len(adjoint_source_stations))
                ses3d_all_coordinates.append(
                    (r_rec_colat, r_rec_lng, r_rec_depth))

                # Actually write the adjoint source file in SES3D specific
                # format.
                with open(adjoint_src_filename, "wt") as open_file:
                    open_file.write("-- adjoint source ------------------\n")
                    open_file.write(
                        "-- source coordinates (colat,lon,depth)\n")
                    open_file.write("%f %f %f\n" % (r_rec_colat, r_rec_lng,
                                                    r_rec_depth))
                    open_file.write("-- source time function (x, y, z) --\n")
                    # Revert the X component as it has to point south in SES3D.
                    for x, y, z in izip(-1.0 * channels[CHANNEL_MAPPING["X"]],
                                        channels[CHANNEL_MAPPING["Y"]],
                                        channels[CHANNEL_MAPPING["Z"]]):
                        open_file.write("%e %e %e\n" % (x, y, z))
                    open_file.write("\n")
            elif "specfem" in solver:
                s_set = iteration.solver_settings["solver_settings"]
                if "adjoint_source_time_shift" not in s_set:
                    warnings.warn("No <adjoint_source_time_shift> tag in the "
                                  "iteration XML file. No time shift for the "
                                  "adjoint sources will be applied.",
                                  LASIFWarning)
                    src_time_shift = 0
                else:
                    src_time_shift = float(s_set["adjoint_source_time_shift"])
                adjoint_source_stations.add(station)
                # Write all components. The adjoint sources right now are
                # not time shifted.
                for component in ["Z", "N", "E"]:
                    # XXX: M band code could be different.
                    adjoint_src_filename = os.path.join(
                        output_folder, "%s.MX%s.adj" % (station, component))
                    adj_src = channels[component]
                    l = len(adj_src)
                    to_write = np.empty((l, 2))
                    to_write[:, 0] = \
                        np.linspace(0, (l - 1) * dt, l) + src_time_shift

                    # SPECFEM expects non-time reversed adjoint sources and
                    # the sign is different for some reason.
                    to_write[:, 1] = -1.0 * adj_src[::-1]

                    np.savetxt(adjoint_src_filename, to_write)
            else:
                raise NotImplementedError(
                    "Adjoint source writing for solver '%s' not yet "
                    "implemented." % iteration.solver_settings["solver"])

        if not adjoint_source_stations:
            print("Could not create a single adjoint source.")
            return

        if "ses3d" in solver:
            with open(os.path.join(output_folder, "ad_srcfile"), "wt") as fh:
                fh.write("%i\n" % len(adjoint_source_stations))
                for line in ses3d_all_coordinates:
                    fh.write("%.6f %.6f %.6f\n" % (line[0], line[1], line[2]))
                fh.write("\n")
        elif "specfem" in solver:
            adjoint_source_stations = sorted(list(adjoint_source_stations))
            with open(os.path.join(output_folder, "STATIONS_ADJOINT"),
                      "wt") as fh:
                for station in adjoint_source_stations:
                    coords = self.comm.query.get_coordinates_for_station(
                        event_name, station)
                    fh.write("{sta} {net} {lat} {lng} {ele} {dep}\n".format(
                        sta=station.split(".")[1],
                        net=station.split(".")[0],
                        lat=coords["latitude"],
                        lng=coords["longitude"],
                        ele=coords["elevation_in_m"],
                        dep=coords["local_depth_in_m"]))

        print "Wrote adjoint sources for %i station(s) to %s." % (
            len(adjoint_source_stations), os.path.relpath(output_folder))
Exemple #8
0
            def get_value(self):
                station_id, coordinates = self.items[self.current_index]

                data = Stream()
                # Now get the actual waveform files. Also find the
                # corresponding station file and check the coordinates.
                this_waveforms = {
                    _i["channel_id"]: _i
                    for _i in waveforms
                    if _i["channel_id"].startswith(station_id + ".")
                }
                marked_for_deletion = []
                for key, value in this_waveforms.iteritems():
                    value["trace"] = read(value["filename"])[0]
                    data += value["trace"]
                    value["station_file"] = \
                        station_cache.get_station_filename(
                            value["channel_id"],
                            UTCDateTime(value["starttime_timestamp"]))
                    if value["station_file"] is None:
                        marked_for_deletion.append(key)
                        msg = ("Warning: Data and station information for '%s'"
                               " is available, but the station information "
                               "only for the wrong timestamp. You should try "
                               "and retrieve the correct station file.")
                        warnings.warn(msg % value["channel_id"])
                        continue
                    data[-1].stats.station_file = value["station_file"]
                for key in marked_for_deletion:
                    del this_waveforms[key]
                if not this_waveforms:
                    msg = "Could not retrieve data for station '%s'." % \
                        station_id
                    warnings.warn(msg)
                    return None
                # Now attempt to get the synthetics.
                synthetics_filenames = []
                for name, path in synthetic_files.iteritems():
                    if (station_id + ".") in name:
                        synthetics_filenames.append(path)

                if len(synthetics_filenames) != 3:
                    msg = "Found %i not 3 synthetics for station '%s'." % (
                        len(synthetics_filenames), station_id)
                    warnings.warn(msg)
                    return None

                synthetics = Stream()
                # Read all synthetics.
                for filename in synthetics_filenames:
                    synthetics += read(filename)
                for synth in synthetics:
                    if synth.stats.channel in ["X", "Z"]:
                        synth.data *= -1.0
                    synth.stats.channel = SYNTH_MAPPING[synth.stats.channel]
                    synth.stats.starttime = event_info["origin_time"]

                # Process the data.
                len_synth = synthetics[0].stats.endtime - \
                    synthetics[0].stats.starttime
                data.trim(synthetics[0].stats.starttime - len_synth * 0.05,
                          synthetics[0].stats.endtime + len_synth * 0.05)
                if data:
                    max_length = max([tr.stats.npts for tr in data])
                else:
                    max_length = 0
                if max_length == 0:
                    msg = (
                        "Warning: After trimming the waveform data to "
                        "the time window of the synthetics, no more data is "
                        "left. The reference time is the one given in the "
                        "QuakeML file. Make sure it is correct and that "
                        "the waveform data actually contains data in that "
                        "time span.")
                    warnings.warn(msg)
                data.detrend("linear")
                data.taper()

                new_time_array = np.linspace(
                    synthetics[0].stats.starttime.timestamp,
                    synthetics[0].stats.endtime.timestamp,
                    synthetics[0].stats.npts)

                # Simulate the traces.
                for trace in data:
                    # Decimate in case there is a large difference between
                    # synthetic sampling rate and sampling_rate of the data.
                    # XXX: Ugly filter, change!
                    if trace.stats.sampling_rate > (6 *
                                                    synth.stats.sampling_rate):
                        new_nyquist = trace.stats.sampling_rate / 2.0 / 5.0
                        trace.filter("lowpass",
                                     freq=new_nyquist,
                                     corners=4,
                                     zerophase=True)
                        trace.decimate(factor=5, no_filter=None)

                    station_file = trace.stats.station_file
                    if "/SEED/" in station_file:
                        paz = Parser(station_file).getPAZ(
                            trace.id, trace.stats.starttime)
                        trace.simulate(paz_remove=paz)
                    elif "/RESP/" in station_file:
                        trace.simulate(
                            seedresp={
                                "filename": station_file,
                                "units": "VEL",
                                "date": trace.stats.starttime
                            })
                    else:
                        raise NotImplementedError

                    # Make sure that the data array is at least as long as the
                    # synthetics array. Also add some buffer sample for the
                    # spline interpolation to work in any case.
                    buf = synth.stats.delta * 5
                    if synth.stats.starttime < (trace.stats.starttime + buf):
                        trace.trim(starttime=synth.stats.starttime - buf,
                                   pad=True,
                                   fill_value=0.0)
                    if synth.stats.endtime > (trace.stats.endtime - buf):
                        trace.trim(endtime=synth.stats.endtime + buf,
                                   pad=True,
                                   fill_value=0.0)

                    old_time_array = np.linspace(
                        trace.stats.starttime.timestamp,
                        trace.stats.endtime.timestamp, trace.stats.npts)

                    # Interpolation.
                    trace.data = interp1d(old_time_array, trace.data,
                                          kind=1)(new_time_array)
                    trace.stats.starttime = synthetics[0].stats.starttime
                    trace.stats.sampling_rate = \
                        synthetics[0].stats.sampling_rate

                data.filter("bandpass", freqmin=lowpass, freqmax=highpass)
                synthetics.filter("bandpass",
                                  freqmin=lowpass,
                                  freqmax=highpass)

                # Rotate the synthetics if nessesary.
                if self.rot_angle:
                    # First rotate the station back to see, where it was
                    # recorded.
                    lat, lng = rotations.rotate_lat_lon(
                        coordinates["latitude"], coordinates["longitude"],
                        self.rot_axis, -self.rot_angle)
                    # Rotate the data.
                    n_trace = synthetics.select(component="N")[0]
                    e_trace = synthetics.select(component="E")[0]
                    z_trace = synthetics.select(component="Z")[0]
                    n, e, z = rotations.rotate_data(n_trace.data, e_trace.data,
                                                    z_trace.data, lat, lng,
                                                    self.rot_axis,
                                                    self.rot_angle)
                    n_trace.data = n
                    e_trace.data = e
                    z_trace.data = z

                return {
                    "data": data,
                    "synthetics": synthetics,
                    "coordinates": coordinates
                }
Exemple #9
0
    def finalize_adjoint_sources(self, iteration_name, event_name):
        """
        Finalizes the adjoint sources.
        """

        import numpy as np
        from lasif import rotations

        window_manager = self.comm.windows.get(event_name, iteration_name)
        event = self.comm.events.get(event_name)
        iteration = self.comm.iterations.get(iteration_name)
        iteration_event_def = iteration.events[event["event_name"]]
        iteration_stations = iteration_event_def["stations"]

        # For now assume that the adjoint sources have the same
        # sampling rate as the synthetics which in LASIF's workflow
        # actually has to be true.
        dt = iteration.get_process_params()["dt"]

        # Current domain and solver.
        domain = self.comm.project.domain
        solver = iteration.solver_settings["solver"].lower()

        adjoint_source_stations = set()

        if "ses3d" in solver:
            ses3d_all_coordinates = []

        event_weight = iteration_event_def["event_weight"]

        output_folder = self.comm.project.get_output_folder(
            type="adjoint_sources",
            tag="ITERATION_%s__%s" % (iteration_name, event_name))

        l = sorted(window_manager.list())
        for station, windows in itertools.groupby(
                l, key=lambda x: ".".join(x.split(".")[:2])):
            if station not in iteration_stations:
                continue
            print(".", end=' ')
            station_weight = iteration_stations[station]["station_weight"]
            channels = {}
            try:
                for w in windows:
                    w = window_manager.get(w)
                    channel_weight = 0
                    srcs = []
                    for window in w:
                        ad_src = window.adjoint_source
                        if not ad_src["adjoint_source"].ptp():
                            continue
                        srcs.append(ad_src["adjoint_source"] * window.weight)
                        channel_weight += window.weight
                    if not srcs:
                        continue
                    # Final adjoint source for that channel and apply all
                    # weights.
                    adjoint_source = np.sum(srcs, axis=0) / channel_weight * \
                        event_weight * station_weight
                    channels[w.channel_id[-1]] = adjoint_source
            except LASIFError as e:
                print(("Could not calculate adjoint source for iteration %s "
                       "and station %s. Repick windows? Reason: %s" %
                       (iteration.name, station, str(e))))
                continue
            if not channels:
                continue
            # Now all adjoint sources of a window should have the same length.
            length = set(len(v) for v in list(channels.values()))
            assert len(length) == 1
            length = length.pop()
            # All missing channels will be replaced with a zero array.
            for c in ["Z", "N", "E"]:
                if c in channels:
                    continue
                channels[c] = np.zeros(length)

            # Get the station coordinates
            coords = self.comm.query.get_coordinates_for_station(
                event_name, station)

            # Rotate. if needed
            rec_lat = coords["latitude"]
            rec_lng = coords["longitude"]

            # The adjoint sources depend on the solver.
            if "ses3d" in solver:
                # Rotate if needed.
                if domain.rotation_angle_in_degree:
                    # Rotate the adjoint source location.
                    r_rec_lat, r_rec_lng = rotations.rotate_lat_lon(
                        rec_lat, rec_lng, domain.rotation_axis,
                        -domain.rotation_angle_in_degree)
                    # Rotate the adjoint sources.
                    channels["N"], channels["E"], channels["Z"] = \
                        rotations.rotate_data(
                            channels["N"], channels["E"],
                            channels["Z"], rec_lat, rec_lng,
                            domain.rotation_axis,
                            -domain.rotation_angle_in_degree)
                else:
                    r_rec_lat = rec_lat
                    r_rec_lng = rec_lng
                r_rec_depth = 0.0
                r_rec_colat = rotations.lat2colat(r_rec_lat)

                # Now once again map from ZNE to the XYZ of SES3D.
                CHANNEL_MAPPING = {"X": "N", "Y": "E", "Z": "Z"}
                adjoint_source_stations.add(station)
                adjoint_src_filename = os.path.join(
                    output_folder, "ad_src_%i" % len(adjoint_source_stations))
                ses3d_all_coordinates.append(
                    (r_rec_colat, r_rec_lng, r_rec_depth))

                # Actually write the adjoint source file in SES3D specific
                # format.
                with open(adjoint_src_filename, "wt") as open_file:
                    open_file.write("-- adjoint source ------------------\n")
                    open_file.write(
                        "-- source coordinates (colat,lon,depth)\n")
                    open_file.write("%f %f %f\n" %
                                    (r_rec_colat, r_rec_lng, r_rec_depth))
                    open_file.write("-- source time function (x, y, z) --\n")
                    # Revert the X component as it has to point south in SES3D.
                    for x, y, z in zip(-1.0 * channels[CHANNEL_MAPPING["X"]],
                                       channels[CHANNEL_MAPPING["Y"]],
                                       channels[CHANNEL_MAPPING["Z"]]):
                        open_file.write("%e %e %e\n" % (x, y, z))
                    open_file.write("\n")
            elif "specfem" in solver:
                s_set = iteration.solver_settings["solver_settings"]
                if "adjoint_source_time_shift" not in s_set:
                    warnings.warn(
                        "No <adjoint_source_time_shift> tag in the "
                        "iteration XML file. No time shift for the "
                        "adjoint sources will be applied.", LASIFWarning)
                    src_time_shift = 0
                else:
                    src_time_shift = float(s_set["adjoint_source_time_shift"])
                adjoint_source_stations.add(station)
                # Write all components. The adjoint sources right now are
                # not time shifted.
                for component in ["Z", "N", "E"]:
                    # XXX: M band code could be different.
                    adjoint_src_filename = os.path.join(
                        output_folder, "%s.MX%s.adj" % (station, component))
                    adj_src = channels[component]
                    l = len(adj_src)
                    to_write = np.empty((l, 2))
                    to_write[:, 0] = \
                        np.linspace(0, (l - 1) * dt, l) + src_time_shift

                    # SPECFEM expects non-time reversed adjoint sources and
                    # the sign is different for some reason.
                    to_write[:, 1] = -1.0 * adj_src[::-1]

                    np.savetxt(adjoint_src_filename, to_write)
            else:
                raise NotImplementedError(
                    "Adjoint source writing for solver '%s' not yet "
                    "implemented." % iteration.solver_settings["solver"])

        if not adjoint_source_stations:
            print("Could not create a single adjoint source.")
            return

        if "ses3d" in solver:
            with open(os.path.join(output_folder, "ad_srcfile"), "wt") as fh:
                fh.write("%i\n" % len(adjoint_source_stations))
                for line in ses3d_all_coordinates:
                    fh.write("%.6f %.6f %.6f\n" % (line[0], line[1], line[2]))
                fh.write("\n")
        elif "specfem" in solver:
            adjoint_source_stations = sorted(list(adjoint_source_stations))
            with open(os.path.join(output_folder, "STATIONS_ADJOINT"),
                      "wt") as fh:
                for station in adjoint_source_stations:
                    coords = self.comm.query.get_coordinates_for_station(
                        event_name, station)
                    fh.write("{sta} {net} {lat} {lng} {ele} {dep}\n".format(
                        sta=station.split(".")[1],
                        net=station.split(".")[0],
                        lat=coords["latitude"],
                        lng=coords["longitude"],
                        ele=coords["elevation_in_m"],
                        dep=coords["local_depth_in_m"]))

        print("Wrote adjoint sources for %i station(s) to %s." %
              (len(adjoint_source_stations), os.path.relpath(output_folder)))