示例#1
0
def test_XyzToLatLonRadius():
    """
    Test the rotations.xyz_to_lat_lon_radius() function.
    """
    # For (0/0)
    lat, lon, radius = rotations.xyz_to_lat_lon_radius(1.0, 0.0, 0.0)
    np.testing.assert_almost_equal(lat, 0.0)
    np.testing.assert_almost_equal(lon, 0.0)
    np.testing.assert_almost_equal(radius, 1.0)
    # At the North Pole
    lat, lon, radius = rotations.xyz_to_lat_lon_radius(0.0, 0.0, 1.0)
    np.testing.assert_almost_equal(lat, 90.0)
    np.testing.assert_almost_equal(lon, 0.0)
    np.testing.assert_almost_equal(radius, 1.0)
    # At the South Pole
    lat, lon, radius = rotations.xyz_to_lat_lon_radius(0.0, 0.0, -1.0)
    np.testing.assert_almost_equal(lat, -90.0)
    np.testing.assert_almost_equal(lon, 0.0)
    np.testing.assert_almost_equal(radius, 1.0)
    # At the "West Pole"
    lat, lon, radius = rotations.xyz_to_lat_lon_radius(0.0, -1.0, 0.0)
    np.testing.assert_almost_equal(lat, 0.0)
    np.testing.assert_almost_equal(lon, -90.0)
    np.testing.assert_almost_equal(radius, 1.0)
    # At the "East Pole"
    lat, lon, radius = rotations.xyz_to_lat_lon_radius(0.0, 1.0, 0.0)
    np.testing.assert_almost_equal(lat, 0.0)
    np.testing.assert_almost_equal(lon, 90.0)
    np.testing.assert_almost_equal(radius, 1.0)
示例#2
0
 def test_XyzToLatLonRadius(self):
     """
     Test the rotations.xyz_to_lat_lon_radius() function.
     """
     # For (0/0)
     lat, lon, radius = rotations.xyz_to_lat_lon_radius(1.0, 0.0, 0.0)
     self.assertAlmostEqual(lat, 0.0)
     self.assertAlmostEqual(lon, 0.0)
     self.assertAlmostEqual(radius, 1.0)
     # At the North Pole
     lat, lon, radius = rotations.xyz_to_lat_lon_radius(0.0, 0.0, 1.0)
     self.assertAlmostEqual(lat, 90.0)
     self.assertAlmostEqual(lon, 0.0)
     self.assertAlmostEqual(radius, 1.0)
     # At the South Pole
     lat, lon, radius = rotations.xyz_to_lat_lon_radius(0.0, 0.0, -1.0)
     self.assertAlmostEqual(lat, -90.0)
     self.assertAlmostEqual(lon, 0.0)
     self.assertAlmostEqual(radius, 1.0)
     # At the "West Pole"
     lat, lon, radius = rotations.xyz_to_lat_lon_radius(0.0, -1.0, 0.0)
     self.assertAlmostEqual(lat, 0.0)
     self.assertAlmostEqual(lon, -90.0)
     self.assertAlmostEqual(radius, 1.0)
     # At the "East Pole"
     lat, lon, radius = rotations.xyz_to_lat_lon_radius(0.0, 1.0, 0.0)
     self.assertAlmostEqual(lat, 0.0)
     self.assertAlmostEqual(lon, 90.0)
     self.assertAlmostEqual(radius, 1.0)
示例#3
0
    def _read(self):
        """
        Reads the HDF5 file and gathers basic information such as the
        coordinates of the edge nodes. In the case of domain that spans
        the entire earth, all points will lie inside the domain, therefore
        further processing is not necessary.
        """
        try:
            self.m = h5py.File(self.mesh_file, mode="r")
        except AssertionError:
            msg = (
                "Could not open the project's mesh file. "
                "Please ensure that the path specified "
                "in config is correct."
            )
            raise LASIFNotFoundError(msg)

        # if less than 2 side sets, this must be a global mesh.  Return
        self.side_set_names = list(self.m["SIDE_SETS"].keys())
        if (
            len(self.side_set_names) <= 2
            and "outer_boundary" not in self.side_set_names
        ):
            self.is_global_mesh = True
            self.min_lat = -90.0
            self.max_lat = 90.0
            self.min_lon = -180.0
            self.max_lon = 180.0
            return
        if "a0" in self.side_set_names:
            self.is_global_mesh = True
            self.min_lat = -90.0
            self.max_lat = 90.0
            self.min_lon = -180.0
            self.max_lon = 180.0
            return

        side_elements = []
        earth_surface_elements = []
        earth_bottom_elements = []
        for side_set in self.side_set_names:
            if side_set == "surface":
                continue
            elif side_set == "r0":
                earth_bottom_elements = self.m["SIDE_SETS"][side_set][
                    "elements"
                ][()]

            elif side_set == "r1":
                earth_surface_elements = self.m["SIDE_SETS"][side_set][
                    "elements"
                ][()]
            elif side_set == "r1_ol":
                earth_surface_elements = self.m["SIDE_SETS"][side_set][
                    "elements"
                ][()]

            else:
                side_elements.append(
                    self.m["SIDE_SETS"][side_set]["elements"][()]
                )

        side_elements_tmp = np.array([], dtype=np.int)
        for i in range(len(side_elements)):
            side_elements_tmp = np.concatenate(
                (side_elements_tmp, side_elements[i])
            )

        # Remove Duplicates
        side_elements = np.unique(side_elements_tmp)

        # Get node numbers of the nodes specifying the domain boundaries
        surface_boundaries = np.intersect1d(
            side_elements, earth_surface_elements
        )
        bottom_boundaries = np.intersect1d(
            side_elements, earth_bottom_elements
        )

        # Get coordinates
        coords = self.m["MODEL/coordinates"][()]
        self.domain_edge_coords = coords[surface_boundaries]
        self.earth_surface_coords = coords[earth_surface_elements]
        self.earth_bottom_coords = coords[earth_bottom_elements]
        self.bottom_edge_coords = coords[bottom_boundaries]

        # Get approximation of element width, take second smallest value

        # For now we will just take a random point on the surface and
        # take the maximum distance between gll points and use that
        # as the element with. It should be an overestimation
        x, y, z = self.earth_surface_coords[:, 0, :].T

        # # Get extent and center of domain
        # x, y, z = self.domain_edge_coords.T

        # # pick a random GLL point to represent the boundary
        # x = x[0]
        # y = y[0]
        # z = z[0]

        # get center lat/lon
        x_cen, y_cen, z_cen = np.median(x), np.median(y), np.median(z)
        self.center_lat, self.center_lon, _ = xyz_to_lat_lon_radius(
            x_cen, y_cen, z_cen
        )

        lats, lons, _ = xyz_to_lat_lon_radius(x, y, z)
        self.min_lat = np.min(lats)
        self.max_lat = np.max(lats)
        self.min_lon = np.min(lons)
        self.max_lon = np.max(lons)

        # Find point outside the domain:
        outside_point = self.find_outside_point()
        # Get coords for the bottom edge of mesh
        # x, y, z = self.bottom_edge_coords.T
        x, y, z = self.earth_bottom_coords.T
        x, y, z = x[0], y[0], z[0]

        # Figure out maximum depth of mesh
        _, _, r = xyz_to_lat_lon_radius(x, y, z)
        min_r = min(r)
        self.max_depth = self.r_earth - min_r

        self.is_read = True

        # In order to create the self.edge_polygon we need to make sure that
        # the points on the boundary are arranged in a way that a proper
        # polygon will be drawn.
        sorted_indices = self.get_sorted_edge_coords()
        x, y, z = self.domain_edge_coords[np.append(sorted_indices, 0)].T
        lats, lons, _ = xyz_to_lat_lon_radius(x[0], y[0], z[0])

        x, y, z = normalize_coordinates(x[0], y[0], z[0])
        points = np.array((x, y, z)).T

        self.boundary = np.array([lats, lons]).T
        self.edge_polygon = lasif.spherical_geometry.SphericalPolygon(
            points, outside_point
        )
        # Close file
        self.m.close()
示例#4
0
    def plot(
        self, ax=None, plot_inner_boundary: bool = False,
    ):
        """
        Plots the domain
        Global domain is plotted using an equal area Mollweide projection.
        Smaller domains have eihter Orthographic projections or PlateCarree.

        :param ax: matplotlib axes, defaults to None
        :type ax: matplotlib.axes.Axes, optional
        :param plot_inner_boundary: plot the convex hull of the mesh
            surface nodes that lie inside the domain. Defaults to False
        :type plot_inner_boundary: bool, optional
        :return: The created GeoAxes instance.
        """
        if not self.is_read:
            self._read()

        import matplotlib.pyplot as plt

        # if global mesh return moll
        transform = cp.crs.Geodetic()
        if self.is_global_mesh:
            projection = cp.crs.Mollweide()
            if ax is None:
                m = plt.axes(projection=projection)
            else:
                m = ax
            _plot_features(m, projection=projection)
            return m, projection

        lat_extent = self.max_lat - self.min_lat
        lon_extent = self.max_lon - self.min_lon
        max_extent = max(lat_extent, lon_extent)

        # Use a global plot for very large domains.
        if lat_extent >= 90.0 and lon_extent >= 90.0:
            projection = cp.crs.Mollweide()
            if ax is None:
                m = plt.axes(projection=projection)
            else:
                m = ax

        elif max_extent >= 75.0:
            projection = cp.crs.Orthographic(
                central_longitude=self.center_lon,
                central_latitude=self.center_lat,
            )
            if ax is None:
                m = plt.axes(projection=projection)
            else:
                m = ax

        else:
            projection = cp.crs.PlateCarree(central_longitude=self.center_lon,)
            if ax is None:
                m = plt.axes(projection=projection,)
            else:
                m = ax
            m.set_extent(
                [
                    self.min_lon - 3.0,
                    self.max_lon + 3.0,
                    self.min_lat - 3.0,
                    self.max_lat + 3.0,
                ],
                crs=transform,
            )

        try:
            _plot_lines(
                m,
                self.boundary,
                transform=transform,
                color="red",
                lw=2,
                label="Domain Edge",
            )

            if plot_inner_boundary:
                # Get surface points
                x, y, z = self.earth_surface_coords.T
                latlonrad = np.array(xyz_to_lat_lon_radius(x[0], y[0], z[0]))
                # This part is potentially slow when lots
                # of points need to be checked
                in_domain = []
                idx = 0
                for lat, lon, _ in latlonrad.T:
                    if self.point_in_domain(latitude=lat, longitude=lon,):
                        in_domain.append(idx)
                    idx += 1
                lats, lons, rad = np.array(latlonrad[:, in_domain])

                # Get the complex hull from projected (to 2D) points
                from scipy.spatial import ConvexHull

                points = np.array((lons, lats)).T
                hull = ConvexHull(points)

                # Plot the hull simplices
                for simplex in hull.simplices:
                    m.plot(
                        points[simplex, 0],
                        points[simplex, 1],
                        c="black",
                        transform=transform,
                    )
                m.plot(
                    points[simplex, 0],
                    points[simplex, 1],
                    c="black",
                    transform=transform,
                    label="Convex Hull of Inner boundary",
                )

        except LASIFError:
            # Back up plot if the other one fails, which happens for
            # very weird meshes sometimes.
            # This Scatter all edge nodes on the plotted domain
            x, y, z = self.domain_edge_coords.T
            lats, lons, _ = xyz_to_lat_lon_radius(x[0], y[0], z[0])
            plt.scatter(
                lons,
                lats,
                color="k",
                label="Edge nodes",
                zorder=3000,
                transform=transform,
            )

        _plot_features(m, projection=projection)
        m.legend(framealpha=0.5, loc="lower right")

        return m, projection
示例#5
0
文件: actions.py 项目: Debesys/LASIF
    def generate_input_files(self, iteration_name, event_name,
                             simulation_type):
        """
        Generate the input files for one event.

        :param iteration_name: The name of the iteration.
        :param event_name: The name of the event for which to generate the
            input files.
        :param simulate_type: The type of simulation to perform. Possible
            values are: 'normal simulate', 'adjoint forward', 'adjoint
            reverse'
        """
        from wfs_input_generator import InputFileGenerator

        # =====================================================================
        # read iteration xml file, get event and list of stations
        # =====================================================================

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

        # Check that the event is part of the iterations.
        if event_name not in iteration.events:
            msg = ("Event '%s' not part of iteration '%s'.\nEvents available "
                   "in iteration:\n\t%s" %
                   (event_name, iteration_name, "\n\t".join(
                       sorted(iteration.events.keys()))))
            raise ValueError(msg)

        event = self.comm.events.get(event_name)
        stations_for_event = iteration.events[event_name]["stations"].keys()

        # Get all stations and create a dictionary for the input file
        # generator.
        stations = self.comm.query.get_all_stations_for_event(event_name)
        stations = [{"id": key, "latitude": value["latitude"],
                     "longitude": value["longitude"],
                     "elevation_in_m": value["elevation_in_m"],
                     "local_depth_in_m": value["local_depth_in_m"]}
                    for key, value in stations.iteritems()
                    if key in stations_for_event]

        # =====================================================================
        # set solver options
        # =====================================================================

        solver = iteration.solver_settings

        # Currently only SES3D 4.1 is supported
        solver_format = solver["solver"].lower()
        if solver_format not in ["ses3d 4.1", "ses3d 2.0",
                                 "specfem3d cartesian", "specfem3d globe cem"]:
            msg = ("Currently only SES3D 4.1, SES3D 2.0, SPECFEM3D "
                   "CARTESIAN, and SPECFEM3D GLOBE CEM are supported.")
            raise ValueError(msg)
        solver_format = solver_format.replace(' ', '_')
        solver_format = solver_format.replace('.', '_')

        solver = solver["solver_settings"]

        # =====================================================================
        # create the input file generator, add event and stations,
        # populate the configuration items
        # =====================================================================

        # Add the event and the stations to the input file generator.
        gen = InputFileGenerator()
        gen.add_events(event["filename"])
        gen.add_stations(stations)

        if solver_format in ["ses3d_4_1", "ses3d_2_0"]:
            # event tag
            gen.config.event_tag = event_name

            # Time configuration.
            npts = solver["simulation_parameters"]["number_of_time_steps"]
            delta = solver["simulation_parameters"]["time_increment"]
            gen.config.number_of_time_steps = npts
            gen.config.time_increment_in_s = delta

            # SES3D specific configuration
            gen.config.output_folder = solver["output_directory"].replace(
                "{{EVENT_NAME}}", event_name.replace(" ", "_"))
            gen.config.simulate_type = simulation_type

            gen.config.adjoint_forward_wavefield_output_folder = \
                solver["adjoint_output_parameters"][
                    "forward_field_output_directory"].replace(
                    "{{EVENT_NAME}}", event_name.replace(" ", "_"))
            gen.config.adjoint_forward_sampling_rate = \
                solver["adjoint_output_parameters"][
                    "sampling_rate_of_forward_field"]

            # Visco-elastic dissipation
            diss = solver["simulation_parameters"]["is_dissipative"]
            gen.config.is_dissipative = diss

            # Only SES3D 4.1 has the relaxation parameters.
            if solver_format == "ses3d_4_1":
                gen.config.Q_model_relaxation_times = \
                    solver["relaxation_parameter_list"]["tau"]
                gen.config.Q_model_weights_of_relaxation_mechanisms = \
                    solver["relaxation_parameter_list"]["w"]

            # Discretization
            disc = solver["computational_setup"]
            gen.config.nx_global = disc["nx_global"]
            gen.config.ny_global = disc["ny_global"]
            gen.config.nz_global = disc["nz_global"]
            gen.config.px = disc["px_processors_in_theta_direction"]
            gen.config.py = disc["py_processors_in_phi_direction"]
            gen.config.pz = disc["pz_processors_in_r_direction"]
            gen.config.lagrange_polynomial_degree = \
                disc["lagrange_polynomial_degree"]

            # Configure the mesh.
            domain = self.comm.project.domain
            gen.config.mesh_min_latitude = \
                domain["bounds"]["minimum_latitude"]
            gen.config.mesh_max_latitude = \
                domain["bounds"]["maximum_latitude"]
            gen.config.mesh_min_longitude = \
                domain["bounds"]["minimum_longitude"]
            gen.config.mesh_max_longitude = \
                domain["bounds"]["maximum_longitude"]
            gen.config.mesh_min_depth_in_km = \
                domain["bounds"]["minimum_depth_in_km"]
            gen.config.mesh_max_depth_in_km = \
                domain["bounds"]["maximum_depth_in_km"]

            # Set the rotation parameters.
            gen.config.rotation_angle_in_degree = domain["rotation_angle"]
            gen.config.rotation_axis = domain["rotation_axis"]

            # Make source time function
            gen.config.source_time_function = \
                iteration.get_source_time_function()["data"]
        elif solver_format == "specfem3d_cartesian":
            gen.config.NSTEP = \
                solver["simulation_parameters"]["number_of_time_steps"]
            gen.config.DT = \
                solver["simulation_parameters"]["time_increment"]
            gen.config.NPROC = \
                solver["computational_setup"]["number_of_processors"]
            if simulation_type == "normal simulation":
                msg = ("'normal_simulate' not supported for SPECFEM3D "
                       "Cartesian. Please choose either 'adjoint_forward' or "
                       "'adjoint_reverse'.")
                raise NotImplementedError(msg)
            elif simulation_type == "adjoint forward":
                gen.config.SIMULATION_TYPE = 1
            elif simulation_type == "adjoint reverse":
                gen.config.SIMULATION_TYPE = 2
            else:
                raise NotImplementedError
            solver_format = solver_format.upper()

        elif solver_format == "specfem3d_globe_cem":
            cs = solver["computational_setup"]
            gen.config.NPROC_XI = cs["number_of_processors_xi"]
            gen.config.NPROC_ETA = cs["number_of_processors_eta"]
            gen.config.NCHUNKS = cs["number_of_chunks"]
            gen.config.NEX_XI = cs["elements_per_chunk_xi"]
            gen.config.NEX_ETA = cs["elements_per_chunk_eta"]
            gen.config.OCEANS = cs["simulate_oceans"]
            gen.config.ELLIPTICITY = cs["simulate_ellipticity"]
            gen.config.TOPOGRAPHY = cs["simulate_topography"]
            gen.config.GRAVITY = cs["simulate_gravity"]
            gen.config.ROTATION = cs["simulate_rotation"]
            gen.config.ATTENUATION = cs["simulate_attenuation"]
            gen.config.ABSORBING_CONDITIONS = True
            if cs["fast_undo_attenuation"]:
                gen.config.PARTIAL_PHYS_DISPERSION_ONLY = True
                gen.config.UNDO_ATTENUATION = False
            else:
                gen.config.PARTIAL_PHYS_DISPERSION_ONLY = False
                gen.config.UNDO_ATTENUATION = True
            gen.config.GPU_MODE = cs["use_gpu"]
            gen.config.SOURCE_TIME_FUNCTION = \
                iteration.get_source_time_function()["data"]

            if simulation_type == "normal simulation":
                gen.config.SIMULATION_TYPE = 1
                gen.config.SAVE_FORWARD = False
            elif simulation_type == "adjoint forward":
                gen.config.SIMULATION_TYPE = 1
                gen.config.SAVE_FORWARD = True
            elif simulation_type == "adjoint reverse":
                gen.config.SIMULATION_TYPE = 2
                gen.config.SAVE_FORWARD = True
            else:
                raise NotImplementedError

            # Use the current domain setting to derive the bounds in the way
            # SPECFEM specifies them.
            domain = self.comm.project.domain

            lat_range = domain["bounds"]["maximum_latitude"] - \
                domain["bounds"]["minimum_latitude"]
            lng_range = domain["bounds"]["maximum_longitude"] - \
                        domain["bounds"]["minimum_longitude"]

            c_lat = \
                domain["bounds"]["minimum_latitude"] + lat_range / 2.0
            c_lng = \
                domain["bounds"]["minimum_longitude"] + lng_range / 2.0

            # Rotate the point.
            c_lat_1, c_lng_1 = rotations.rotate_lat_lon(
                c_lat, c_lng, domain["rotation_axis"],
                domain["rotation_angle"])

            # SES3D rotation.
            A = rotations._get_rotation_matrix(
                domain["rotation_axis"], domain["rotation_angle"])

            latitude_rotation = -(c_lat_1 - c_lat)
            longitude_rotation = c_lng_1 - c_lng

            # Rotate the latitude. The rotation axis is latitude 0 and
            # the center longitude + 90 degree
            B = rotations._get_rotation_matrix(
                rotations.lat_lon_radius_to_xyz(0.0, c_lng + 90, 1.0),
                latitude_rotation)
            # Rotate around the North pole.
            C = rotations._get_rotation_matrix(
                [0.0, 0.0, 1.0], longitude_rotation)

            D = A * np.linalg.inv(C * B)

            axis, angle = rotations._get_axis_and_angle_from_rotation_matrix(D)
            rotated_axis = rotations.xyz_to_lat_lon_radius(*axis)

            # Consistency check
            if abs(rotated_axis[0] - c_lat_1) >= 0.01 or \
                    abs(rotated_axis[1] - c_lng_1) >= 0.01:
                axis *= -1.0
                angle *= -1.0
                rotated_axis = rotations.xyz_to_lat_lon_radius(*axis)

            if abs(rotated_axis[0] - c_lat_1) >= 0.01 or \
                        abs(rotated_axis[1] - c_lng_1) >= 0.01:
                msg = "Failed to describe the domain in terms that SPECFEM " \
                      "understands"
                raise LASIFError(msg)

            gen.config.ANGULAR_WIDTH_XI_IN_DEGREES = lng_range
            gen.config.ANGULAR_WIDTH_ETA_IN_DEGREES = lat_range
            gen.config.CENTER_LATITUDE_IN_DEGREES = c_lat_1
            gen.config.CENTER_LONGITUDE_IN_DEGREES = c_lng_1
            gen.config.GAMMA_ROTATION_AZIMUTH = angle

            gen.config.MODEL = cs["model"]

            pp = iteration.get_process_params()
            gen.config.RECORD_LENGTH_IN_MINUTES = \
                (pp["npts"] * pp["dt"]) / 60.0
            solver_format = solver_format.upper()

        else:
            msg = "Unknown solver '%s'." % solver_format
            raise NotImplementedError(msg)

        # =================================================================
        # output
        # =================================================================
        output_dir = self.comm.project.get_output_folder(
            "input_files___ITERATION_%s__%s__EVENT_%s" % (
                iteration_name, simulation_type.replace(" ", "_"),
                event_name))

        gen.write(format=solver_format, output_dir=output_dir)
        print "Written files to '%s'." % output_dir
示例#6
0
    def generate_input_files(self, iteration_name, event_name,
                             simulation_type):
        """
        Generate the input files for one event.

        :param iteration_name: The name of the iteration.
        :param event_name: The name of the event for which to generate the
            input files.
        :param simulate_type: The type of simulation to perform. Possible
            values are: 'normal simulate', 'adjoint forward', 'adjoint
            reverse'
        """
        from wfs_input_generator import InputFileGenerator

        # =====================================================================
        # read iteration xml file, get event and list of stations
        # =====================================================================

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

        # Check that the event is part of the iterations.
        if event_name not in iteration.events:
            msg = ("Event '%s' not part of iteration '%s'.\nEvents available "
                   "in iteration:\n\t%s" %
                   (event_name, iteration_name, "\n\t".join(
                       sorted(iteration.events.keys()))))
            raise ValueError(msg)

        event = self.comm.events.get(event_name)
        stations_for_event = list(
            iteration.events[event_name]["stations"].keys())

        # Get all stations and create a dictionary for the input file
        # generator.
        stations = self.comm.query.get_all_stations_for_event(event_name)
        stations = [{
            "id": key,
            "latitude": value["latitude"],
            "longitude": value["longitude"],
            "elevation_in_m": value["elevation_in_m"],
            "local_depth_in_m": value["local_depth_in_m"]
        } for key, value in stations.items() if key in stations_for_event]

        # =====================================================================
        # set solver options
        # =====================================================================

        solver = iteration.solver_settings

        # Currently only SES3D 4.1 is supported
        solver_format = solver["solver"].lower()
        if solver_format not in [
                "ses3d 4.1", "ses3d 2.0", "specfem3d cartesian",
                "specfem3d globe cem"
        ]:
            msg = ("Currently only SES3D 4.1, SES3D 2.0, SPECFEM3D "
                   "CARTESIAN, and SPECFEM3D GLOBE CEM are supported.")
            raise ValueError(msg)
        solver_format = solver_format.replace(' ', '_')
        solver_format = solver_format.replace('.', '_')

        solver = solver["solver_settings"]

        # =====================================================================
        # create the input file generator, add event and stations,
        # populate the configuration items
        # =====================================================================

        # Add the event and the stations to the input file generator.
        gen = InputFileGenerator()
        gen.add_events(event["filename"])
        gen.add_stations(stations)

        if solver_format in ["ses3d_4_1", "ses3d_2_0"]:
            # event tag
            gen.config.event_tag = event_name

            # Time configuration.
            npts = solver["simulation_parameters"]["number_of_time_steps"]
            delta = solver["simulation_parameters"]["time_increment"]
            gen.config.number_of_time_steps = npts
            gen.config.time_increment_in_s = delta

            # SES3D specific configuration
            gen.config.output_folder = solver["output_directory"].replace(
                "{{EVENT_NAME}}", event_name.replace(" ", "_"))
            gen.config.simulation_type = simulation_type

            gen.config.adjoint_forward_wavefield_output_folder = \
                solver["adjoint_output_parameters"][
                    "forward_field_output_directory"].replace(
                    "{{EVENT_NAME}}", event_name.replace(" ", "_"))
            gen.config.adjoint_forward_sampling_rate = \
                solver["adjoint_output_parameters"][
                    "sampling_rate_of_forward_field"]

            # Visco-elastic dissipation
            diss = solver["simulation_parameters"]["is_dissipative"]
            gen.config.is_dissipative = diss

            # Only SES3D 4.1 has the relaxation parameters.
            if solver_format == "ses3d_4_1":
                gen.config.Q_model_relaxation_times = \
                    solver["relaxation_parameter_list"]["tau"]
                gen.config.Q_model_weights_of_relaxation_mechanisms = \
                    solver["relaxation_parameter_list"]["w"]

            # Discretization
            disc = solver["computational_setup"]
            gen.config.nx_global = disc["nx_global"]
            gen.config.ny_global = disc["ny_global"]
            gen.config.nz_global = disc["nz_global"]
            gen.config.px = disc["px_processors_in_theta_direction"]
            gen.config.py = disc["py_processors_in_phi_direction"]
            gen.config.pz = disc["pz_processors_in_r_direction"]
            gen.config.lagrange_polynomial_degree = \
                disc["lagrange_polynomial_degree"]

            # Configure the mesh.
            domain = self.comm.project.domain
            gen.config.mesh_min_latitude = domain.min_latitude
            gen.config.mesh_max_latitude = domain.max_latitude
            gen.config.mesh_min_longitude = domain.min_longitude
            gen.config.mesh_max_longitude = domain.max_longitude
            gen.config.mesh_min_depth_in_km = domain.min_depth_in_km
            gen.config.mesh_max_depth_in_km = domain.max_depth_in_km

            # Set the rotation parameters.
            gen.config.rotation_angle_in_degree = \
                domain.rotation_angle_in_degree
            gen.config.rotation_axis = domain.rotation_axis

            # Make source time function
            gen.config.source_time_function = \
                iteration.get_source_time_function()["data"]
        elif solver_format == "specfem3d_cartesian":
            gen.config.NSTEP = \
                solver["simulation_parameters"]["number_of_time_steps"]
            gen.config.DT = \
                solver["simulation_parameters"]["time_increment"]
            gen.config.NPROC = \
                solver["computational_setup"]["number_of_processors"]
            if simulation_type == "normal simulation":
                msg = ("'normal_simulate' not supported for SPECFEM3D "
                       "Cartesian. Please choose either 'adjoint_forward' or "
                       "'adjoint_reverse'.")
                raise NotImplementedError(msg)
            elif simulation_type == "adjoint forward":
                gen.config.SIMULATION_TYPE = 1
            elif simulation_type == "adjoint reverse":
                gen.config.SIMULATION_TYPE = 2
            else:
                raise NotImplementedError
            solver_format = solver_format.upper()

        elif solver_format == "specfem3d_globe_cem":
            cs = solver["computational_setup"]
            gen.config.NPROC_XI = cs["number_of_processors_xi"]
            gen.config.NPROC_ETA = cs["number_of_processors_eta"]
            gen.config.NCHUNKS = cs["number_of_chunks"]
            gen.config.NEX_XI = cs["elements_per_chunk_xi"]
            gen.config.NEX_ETA = cs["elements_per_chunk_eta"]
            gen.config.OCEANS = cs["simulate_oceans"]
            gen.config.ELLIPTICITY = cs["simulate_ellipticity"]
            gen.config.TOPOGRAPHY = cs["simulate_topography"]
            gen.config.GRAVITY = cs["simulate_gravity"]
            gen.config.ROTATION = cs["simulate_rotation"]
            gen.config.ATTENUATION = cs["simulate_attenuation"]
            gen.config.ABSORBING_CONDITIONS = True
            if cs["fast_undo_attenuation"]:
                gen.config.PARTIAL_PHYS_DISPERSION_ONLY = True
                gen.config.UNDO_ATTENUATION = False
            else:
                gen.config.PARTIAL_PHYS_DISPERSION_ONLY = False
                gen.config.UNDO_ATTENUATION = True
            gen.config.GPU_MODE = cs["use_gpu"]
            gen.config.SOURCE_TIME_FUNCTION = \
                iteration.get_source_time_function()["data"]

            if simulation_type == "normal simulation":
                gen.config.SIMULATION_TYPE = 1
                gen.config.SAVE_FORWARD = False
            elif simulation_type == "adjoint forward":
                gen.config.SIMULATION_TYPE = 1
                gen.config.SAVE_FORWARD = True
            elif simulation_type == "adjoint reverse":
                gen.config.SIMULATION_TYPE = 2
                gen.config.SAVE_FORWARD = True
            else:
                raise NotImplementedError

            # Use the current domain setting to derive the bounds in the way
            # SPECFEM specifies them.
            domain = self.comm.project.domain

            lat_range = domain.max_latitude - \
                domain.min_latitude
            lng_range = domain.max_longitude - \
                domain.min_longitude

            c_lat = \
                domain.min_latitude + lat_range / 2.0
            c_lng = \
                domain.min_longitude + lng_range / 2.0

            # Rotate the point.
            c_lat_1, c_lng_1 = rotations.rotate_lat_lon(
                c_lat, c_lng, domain.rotation_axis,
                domain.rotation_angle_in_degree)

            # SES3D rotation.
            A = rotations._get_rotation_matrix(domain.rotation_axis,
                                               domain.rotation_angle_in_degree)

            latitude_rotation = -(c_lat_1 - c_lat)
            longitude_rotation = c_lng_1 - c_lng

            # Rotate the latitude. The rotation axis is latitude 0 and
            # the center longitude + 90 degree
            B = rotations._get_rotation_matrix(
                rotations.lat_lon_radius_to_xyz(0.0, c_lng + 90, 1.0),
                latitude_rotation)
            # Rotate around the North pole.
            C = rotations._get_rotation_matrix([0.0, 0.0, 1.0],
                                               longitude_rotation)

            D = A * np.linalg.inv(C * B)

            axis, angle = rotations._get_axis_and_angle_from_rotation_matrix(D)
            rotated_axis = rotations.xyz_to_lat_lon_radius(*axis)

            # Consistency check
            if abs(rotated_axis[0] - c_lat_1) >= 0.01 or \
                    abs(rotated_axis[1] - c_lng_1) >= 0.01:
                axis *= -1.0
                angle *= -1.0
                rotated_axis = rotations.xyz_to_lat_lon_radius(*axis)

            if abs(rotated_axis[0] - c_lat_1) >= 0.01 or \
                    abs(rotated_axis[1] - c_lng_1) >= 0.01:
                msg = "Failed to describe the domain in terms that SPECFEM " \
                      "understands. The domain definition in the output " \
                      "files will NOT BE CORRECT!"
                warnings.warn(msg, LASIFWarning)

            gen.config.ANGULAR_WIDTH_XI_IN_DEGREES = lng_range
            gen.config.ANGULAR_WIDTH_ETA_IN_DEGREES = lat_range
            gen.config.CENTER_LATITUDE_IN_DEGREES = c_lat_1
            gen.config.CENTER_LONGITUDE_IN_DEGREES = c_lng_1
            gen.config.GAMMA_ROTATION_AZIMUTH = angle

            gen.config.MODEL = cs["model"]

            pp = iteration.get_process_params()
            gen.config.RECORD_LENGTH_IN_MINUTES = \
                (pp["npts"] * pp["dt"]) / 60.0
            solver_format = solver_format.upper()

        else:
            msg = "Unknown solver '%s'." % solver_format
            raise NotImplementedError(msg)

        # =================================================================
        # output
        # =================================================================
        output_dir = self.comm.project.get_output_folder(
            type="input_files",
            tag="ITERATION_%s__%s__EVENT_%s" %
            (iteration_name, simulation_type.replace(" ", "_"), event_name))

        gen.write(format=solver_format, output_dir=output_dir)
        print("Written files to '%s'." % output_dir)