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
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)
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))
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}
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))
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))
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 }
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)))