def smooth(self, input_path, **kwargs): """ Specfem2D requires additional model parameters in directory to perform the xsmooth_sem task. This function will copy these files into the directory before performing the base smooth operations. Kwargs should match arguments of solver.base.smooth() .. note:: This operation is usually run with run(single=True) so only one task will be performing these operations. :type input_path: str :param input_path: path to data """ # Redundant to 'base' class but necessary if not exists(input_path): unix.mkdir(input_path) unix.cd(self.cwd) unix.cd("DATA") # Copy over only the files that are required. Won't execute if no match files = [] for tag in ["jacobian", "NSPEC_ibool", "x", "y", "z"]: files += glob(f"*_{tag}.bin") for src in files: unix.cp(src=src, dst=input_path) super().smooth(input_path=input_path, **kwargs)
def generate_data(self, **model_kwargs): """ Generates data using the True model, exports traces to `traces/obs` :param model_kwargs: keyword arguments to pass to `generate_mesh` """ # Create the mesh self.generate_mesh(**model_kwargs) # Run the Forward simulation unix.cd(self.cwd) setpar(key="SIMULATION_TYPE", val="1", file="DATA/Par_file") setpar(key="SAVE_FORWARD", val=".true.", file="DATA/Par_file") if PAR.ATTENUATION: setpar(key="ATTENUATION", val=".true.", file="DATA/Par_file") else: setpar(key="ATTENUATION", val=".false.", file="DATA/Par_file") call_solver(mpiexec=PAR.MPIEXEC, executable="bin/xspecfem3D") unix.mv(src=glob(os.path.join("OUTPUT_FILES", self.data_wildcard)), dst=os.path.join("traces", "obs")) # Export traces to disk for permanent storage if PAR.SAVETRACES: self.export_traces(os.path.join(PATH.OUTPUT, "traces", "obs"))
def generate_data(self, **model_kwargs): """ Generates data using the True model, exports traces to `traces/obs` :param model_kwargs: keyword arguments to pass to `generate_mesh` """ self.generate_mesh(**model_kwargs) unix.cd(self.cwd) setpar(key="SIMULATION_TYPE", val="1", file="DATA/Par_file") setpar(key="SAVE_FORWARD", val=".true.", file="DATA/Par_file") call_solver(PAR.MPIEXEC, "bin/xmeshfem2D", output="mesher.log") call_solver(PAR.MPIEXEC, "bin/xspecfem2D", output="solver.log") if PAR.FORMAT.upper() == "SU": # Work around SPECFEM2D's version dependent file names for tag in ["d", "v", "a", "p"]: unix.rename(old=f"single_{tag}.su", new="single.su", names=glob(os.path.join("OUTPUT_FILES", "*.su"))) unix.mv(src=glob(os.path.join("OUTPUT_FILES", self.data_wildcard)), dst=os.path.join("traces", "obs")) if PAR.SAVETRACES: self.export_traces(os.path.join(PATH.OUTPUT, "traces", "obs"))
def cleanup_xspecfem2d_run(self, choice=None): """ Do some cleanup after running the SPECFEM2D binaries to make sure files are in the correct locations, and rename the OUTPUT_FILES directory so that it does not get overwritten by subsequent runs :type choice: str :param choice: Rename the OUTPUT_FILES directory with a suffix tag msut be 'INIT' or 'TRUE'. If None, will not rename but the """ cd(self.workdir_paths.workdir) print("> Cleaning up after xspecfem2d, setting up for new run") # SPECFEM2D outputs its models in the DATA/ directory by default, # while SeisFlows3 expects this in the OUTPUT_FILES/ directory (which is # the default in SPECFEM3D) mv(glob.glob("DATA/*bin"), self.workdir_paths.output) if choice == "INIT": mv(self.workdir_paths.output, self.workdir_paths.model_init) # Create a new OUTPUT_FILES/ directory for TRUE run rm(self.workdir_paths.output) mkdir(self.workdir_paths.output) elif choice == "TRUE": mv(self.workdir_paths.output, self.workdir_paths.model_true)
def setup_seisflows_working_directory(self): """ Create and set the SeisFlows3 parameter file, making sure all required parameters are set correctly for this example problem """ cd(self.cwd) self.sf.setup(force=True) # Force will delete existing parameter file self.sf.configure() self.sf.par("ntask", self.ntask) # default 3 sources for this example self.sf.par("materials", "elastic") # how velocity model parameterized self.sf.par("density", "constant") # update density or keep constant self.sf.par("nt", 5000) # set by SPECFEM2D Par_file self.sf.par("dt", .06) # set by SPECFEM2D Par_file self.sf.par("f0", 0.084) # set by SOURCE file self.sf.par("format", "ascii") # how to output synthetic seismograms self.sf.par("begin", 1) # first iteration self.sf.par("end", self.niter) # final iteration -- we will run 2 self.sf.par("case", "synthetic") # synthetic-synthetic inversion self.sf.par("attenuation", False) self.sf.par("specfem_bin", self.workdir_paths.bin) self.sf.par("specfem_data", self.workdir_paths.data) self.sf.par("model_init", self.workdir_paths.model_init) self.sf.par("model_true", self.workdir_paths.model_true)
def generate_data(self): """ Overload seisflows.solver.base.generate_data. To be run in parallel Not used if PAR.CASE == "Data" Generates data in the synthetic-synthetic comparison case. Automatically calls generate mesh for the true model, rather than passing them in as kwargs. Also turns on attenuation for the forward model !!! attenuation could be moved into parameters.yaml? !!! """ unix.cd(self.cwd) setpar(key="SIMULATION_TYPE", val="1", file="DATA/Par_file") setpar(key="SAVE_FORWARD", val=".true.", file="DATA/Par_file") if PAR.ATTENUATION: setpar(key="ATTENUATION ", val=".true.", file="DATA/Par_file") else: setpar(key="ATTENUATION ", val=".false.", file="DATA/Par_file") call_solver(mpiexec=PAR.MPIEXEC, executable="bin/xspecfem3D") # move ASCII .sem? files into appropriate directory unix.mv(src=glob(os.path.join("OUTPUT_FILES", self.data_wildcard)), dst=os.path.join("traces", "obs")) # Export traces to permanent storage on disk if PAR.SAVETRACES: self.export_traces(os.path.join(PATH.OUTPUT, "traces", "obs"))
def write_sources(self): """ Write sources to text file """ unix.cd(self.cwd) _, h = preprocess.load(dir="traces/obs") solvertools.write_sources(PAR=vars(PAR), h=h)
def eval_func(self, path, write_residuals=True): """ High level solver interface Performs forward simulations and evaluates the misfit function :type path: str :param path: directory from which model is imported and where residuals will be exported :type write_residuals: bool :param write_residuals: calculate and export residuals """ if self.taskid == 0: self.logger.info("running forward simulations") unix.cd(self.cwd) self.import_model(path) self.forward() if write_residuals: if self.taskid == 0: self.logger.debug("calling preprocess.prepare_eval_grad()") preprocess.prepare_eval_grad(cwd=self.cwd, taskid=self.taskid, source_name=self.source_name, filenames=self.data_filenames) self.export_residuals(path)
def initialize_adjoint_traces(self): """ Setup utility: Creates the "adjoint traces" expected by SPECFEM Note: Adjoint traces are initialized by writing zeros for all channels. Channels actually in use during an inversion or migration will be overwritten with nonzero values later on. """ # Initialize adjoint traces as zeroes for all data_filenames # write to `traces/adj` super().initialize_adjoint_traces() # Rename data to work around Specfem naming convetions self.rename_data() # Workaround for Specfem3D's requirement that all components exist, # even ones not in use as adjoint traces if PAR.FORMAT.upper() == "SU": unix.cd(os.path.join(self.cwd, "traces", "adj")) for iproc in range(PAR.NPROC): for channel in ["x", "y", "z"]: dst = f"{iproc:d}_d{channel}_SU.adj" if not exists(dst): src = f"{iproc:d}_d{PAR.COMPONENTS[0]}_SU.adj" unix.cp(src, dst)
def generate_mesh(self, model_path, model_name, model_type='gll'): """ Performs meshing with internal mesher Meshfem2D and database generation :type model_path: str :param model_path: path to the model to be used for mesh generation :type model_name: str :param model_name: name of the model to be used as identification :type model_type: str :param model_type: available model types to be passed to the Specfem3D Par_file. See Specfem3D Par_file for available options. """ assert (exists(model_path)), f"model {model_path} does not exist" available_model_types = ["gll"] assert(model_type in available_model_types), \ f"{model_type} not in available types {available_model_types}" unix.cd(self.cwd) # Run mesh generation if model_type == "gll": self.check_mesh_properties(model_path) # Copy the model files (ex: proc000023_vp.bin ...) into DATA src = glob(os.path.join(model_path, "*")) dst = self.model_databases unix.cp(src, dst) # Export the model into output folder if self.taskid == 0: self.export_model(os.path.join(PATH.OUTPUT, model_name))
def initialize_solver_directories(self): """ Creates directory structure expected by SPECFEM3D (bin/, DATA/) copies executables, and prepares input files. Executables must be supplied by user as there is no mechanism for automatically compiling from source Directories will act as completely independent Specfem run directories. This allows for embarrassing parallelization while avoiding the need for intra-directory communications, at the cost of redundancy and extra files. """ if self.taskid == 0: self.logger.info(f"initializing {PAR.NTASK} solver directories") unix.mkdir(self.cwd) unix.cd(self.cwd) # Create directory structure for cwd_dir in [ "bin", "DATA", "OUTPUT_FILES/DATABASES_MPI", "traces/obs", "traces/syn", "traces/adj", self.model_databases, self.kernel_databases ]: unix.mkdir(cwd_dir) # Copy exectuables into the bin/ directory src = glob(os.path.join(PATH.SPECFEM_BIN, "*")) dst = os.path.join("bin", "") unix.cp(src, dst) # Copy all input files except source files src = glob(os.path.join(PATH.SPECFEM_DATA, "*")) src = [_ for _ in src if self.source_prefix not in _] dst = os.path.join("DATA", "") unix.cp(src, dst) # Symlink event source specifically, strip the source name as SPECFEM # just expects `source_name` src = os.path.join(PATH.SPECFEM_DATA, f"{self.source_prefix}_{self.source_name}") dst = os.path.join("DATA", self.source_prefix) unix.ln(src, dst) if self.taskid == 0: mainsolver = os.path.join(PATH.SOLVER, "mainsolver") # Symlink taskid_0 as mainsolver in solver directory for convenience if not os.path.exists(mainsolver): unix.ln(self.cwd, mainsolver) self.logger.debug(f"source {self.source_name} symlinked as " f"mainsolver") else: # Copy the initial model from mainsolver into current directory # Avoids the need to run multiple instances of xgenerate_databases src = os.path.join(PATH.SOLVER, "mainsolver", "OUTPUT_FILES", "DATABASES_MPI") dst = os.path.join(self.cwd, "OUTPUT_FILES", "DATABASES_MPI") unix.cp(src, dst) self.check_solver_parameter_files()
def clean(self): """ Clean up solver-dependent run directory by removing the OUTPUT_FILES/ directory """ unix.cd(self.cwd) unix.rm("OUTPUT_FILES") unix.mkdir("OUTPUT_FILES")
def finalize_specfem2d_par_file(self): """ Last minute changes to get the SPECFEM2D Par_file in the correct format for running SeisFlows3. Setting model type to read from .bin GLL files change number of stations etc. """ cd(self.workdir_paths.data) self.sf.sempar("model", "gll") # GLL so SPECFEM reads .bin files
def write_parameters(self): """ Write a set of parameters !!! This calls on plugins.solver.specfem3d.write_parameters() but that function doesn't exist !!! """ unix.cd(self.cwd) solvertools.write_parameters(vars(PAR))
def smooth(self, input_path, output_path, parameters=None, span_h=0., span_v=0., output="solver.log"): """ Postprocessing wrapper: xsmooth_sem Smooths kernels by convolving them with a Gaussian. .. note:: paths require a trailing `/` character when calling xsmooth_sem .. note:: It is ASSUMED that this function is being called by system.run(single=True) so that we can use the main solver directory to perform the kernel smooth task :type input_path: str :param input_path: path to data :type output_path: str :param output_path: path to export the outputs of xcombine_sem :type parameters: list :param parameters: optional list of parameters, defaults to `self.parameters` :type span_h: float :param span_h: horizontal smoothing length in meters :type span_v: float :param span_v: vertical smoothing length in meters :type output: str :param output: file to output stdout to """ if parameters is None: parameters = self.parameters if not exists(output_path): unix.mkdir(output_path) # Apply smoothing operator inside scratch/solver/* unix.cd(self.cwd) # mpiexec ./bin/xsmooth_sem SMOOTH_H SMOOTH_V name input output use_gpu for name in parameters: call_solver(mpiexec=PAR.MPIEXEC, executable=" ".join([ "bin/xsmooth_sem", str(span_h), str(span_v), f"{name}_kernel", os.path.join(input_path, ""), os.path.join(output_path, ""), ".false" ]), output=output) # Rename output files files = glob(os.path.join(output_path, "*")) unix.rename(old="_smooth", new="", names=files)
def update(self): """ Updates L-BFGS algorithm history .. note:: Because models are large, and multiple iterations of models need to be stored in memory, previous models are stored as `memmaps`, which allow for access of small segments of large files on disk, without reading the entire file. Memmaps are array like objects. .. note:: Notation for s and y taken from Liu & Nocedal 1989 iterate notation: sk = x_k+1 - x_k and yk = g_k+1 - gk :rtype s: np.memmap :return s: memory of the model differences `m_new - m_old` :rtype y: np.memmap :return y: memory of the gradient differences `g_new - g_old` """ unix.cd(PATH.OPTIMIZE) # Determine the iterates for model m and gradient g s_k = self.load(self.m_new) - self.load(self.m_old) y_k = self.load(self.g_new) - self.load(self.g_old) # Determine the shape of the memory map (length of model, length of mem) m = len(s_k) n = PAR.LBFGSMEM # Initial iteration, need to create the memory map if self.memory_used == 0: s = np.memmap(filename=self.s_file, mode="w+", dtype="float32", shape=(m, n)) y = np.memmap(filename=self.y_file, mode="w+", dtype="float32", shape=(m, n)) # Store the model and gradient differences in memmaps s[:, 0] = s_k y[:, 0] = y_k self.memory_used = 1 # Subsequent iterations will append to memory maps else: s = np.memmap(filename=self.s_file, mode="r+", dtype="float32", shape=(m, n)) y = np.memmap(filename=self.y_file, mode="r+", dtype="float32", shape=(m, n)) # Shift all stored memory by one index to make room for latest mem s[:, 1:] = s[:, :-1] y[:, 1:] = y[:, :-1] # Store the latest model and gradient in first index s[:, 0] = s_k y[:, 0] = y_k # Keep track of the memory used if self.memory_used < PAR.LBFGSMEM: self.memory_used += 1 return s, y
def setup(self): """ Set up the LBFGS optimization schema """ super().setup() # Create a separate directory for LBFGS matters unix.cd(PATH.OPTIMIZE) unix.mkdir(self.LBFGS_dir)
def setup_specfem2d_for_model_true(self): """ Make some adjustments to the parameter file to create the final velocity model. This function assumes it is running from inside the SPECFEM2D/DATA directory """ cd(self.workdir_paths.data) assert (os.path.exists("Par_file")), f"I cannot find the Par_file!" print("> Updating initial homogeneous velocity model values") new_model = "1 1 2600.d0 5900.d0 3550.0d0 0 0 10.d0 10.d0 0 0 0 0 0 0" self.sf.sempar("velocity_model", new_model)
def write_receivers(self): """ Write a list of receivers into a text file !!! This calls on plugins.solver.specfem3d.write_receivers() but incorrect number of parameters is forwarded !!! """ unix.cd(self.cwd) setpar(key="use_existing_STATIONS", val=".true", file="DATA/Par_file") _, h = preprocess.load("traces/obs") solvertools.write_receivers(h.nr, h.rx, h.rz)
def data_filenames(self): """ Returns the filenames of all data, either by the requested components or by all available files in the directory. :rtype: list :return: list of data filenames """ unix.cd(os.path.join(self.cwd, "traces", "obs")) if PAR.FORMAT.upper() == "ASCII": return sorted(glob("*.???.sem.ascii"))
def apply(self, q, s=None, y=None): """ Applies L-BFGS inverse Hessian to given vector :type q: np.array :param q: gradient direction to apply L-BFGS to :type s: np.memmap :param s: memory of model differences :param s: memory of model differences :type y: np.memmap :param y: memory of gradient direction differences :rtype r: np.array :return r: new search direction from application of L-BFGS """ unix.cd(PATH.OPTIMIZE) # If no memmaps are given as arguments, instantiate them if s is None or y is None: m = len(q) n = PAR.LBFGSMEM s = np.memmap(filename=self.s_file, mode="w+", dtype="float32", shape=(m, n)) y = np.memmap(filename=self.y_file, mode="w+", dtype="float32", shape=(m, n)) # First matrix product # Recursion step 2 from appendix A of Modrak & Tromp 2016 kk = self.memory_used rh = np.zeros(kk) al = np.zeros(kk) for ii in range(kk): rh[ii] = 1 / np.dot(y[:, ii], s[:, ii]) al[ii] = rh[ii] * np.dot(s[:, ii], q) q = q - al[ii] * y[:, ii] # Apply a preconditioner if available if self.precond: r = self.precond(q) else: r = q # Use scaling M3 proposed by Liu and Nocedal 1989 sty = np.dot(y[:, 0], s[:, 0]) yty = np.dot(y[:, 0], y[:, 0]) r *= sty/yty # Second matrix product # Recursion step 4 from appendix A of Modrak & Tromp 2016 for ii in range(kk - 1, -1, -1): be = rh[ii] * np.dot(y[:, ii], r) r = r + s[:, ii] * (al[ii] - be) return r
def finalize_search(self): """ Prepares algorithm machinery and scratch directory for next model update Removes old model/search parameters, moves current parameters to old, sets up new current parameters and writes statistic outputs """ self.logger.info(msg.sub("FINALIZING LINE SEARCH")) g = self.load(self.g_new) p = self.load(self.p_new) x = self.line_search.search_history()[0] f = self.line_search.search_history()[1] # Clean scratch directory unix.cd(PATH.OPTIMIZE) # Remove the old model parameters if self.iter > 1: self.logger.info("removing previously accepted model files (old)") for fid in [self.m_old, self.f_old, self.g_old, self.p_old]: unix.rm(fid) self.logger.info( "shifting current model (new) to previous model (old)") unix.mv(self.m_new, self.m_old) unix.mv(self.f_new, self.f_old) unix.mv(self.g_new, self.g_old) unix.mv(self.p_new, self.p_old) self.logger.info("setting accepted line search model as current model") unix.mv(self.m_try, self.m_new) self.savetxt(self.f_new, f.min()) self.logger.info(f"current misfit is {self.f_new}={f.min():.3E}") # !!! TODO Describe what stats are being written here self.logger.info(f"writing optimization stats to: {CFGPATHS.STATSDIR}") self.write_stats(self.log_factor, value=-dot(g, g)**-0.5 * (f[1] - f[0]) / (x[1] - x[0])) self.write_stats(self.log_gradient_norm_L1, value=np.linalg.norm(g, 1)) self.write_stats(self.log_gradient_norm_L2, value=np.linalg.norm(g, 2)) self.write_stats(self.log_misfit, value=f[0]) self.write_stats(self.log_restarted, value=self.restarted) self.write_stats(self.log_slope, value=(f[1] - f[0]) / (x[1] - x[0])) self.write_stats(self.log_step_count, value=self.line_search.step_count) self.write_stats(self.log_step_length, value=x[f.argmin()]) self.write_stats(self.log_theta, value=180. * np.pi**-1 * angle(p, -g)) self.logger.info("resetting line search step count to 0") self.line_search.step_count = 0
def eval_fwd(self, path=''): """ High level solver interface Performans forward simulations only, function evaluation is split off into its own function :type path: str :param path: path in the scratch directory to use for I/O """ unix.cd(self.cwd) self.import_model(path) self.forward()
def configure_specfem2d_and_make_binaries(self): """ Run ./configure within the SPECFEM2D repo directory. This function assumes it is being run from inside the repo. Should guess all the configuration options. Probably the least stable part of the example """ cd(self.sem2d_paths.repo) try: if not os.path.exists("./config.log"): cmd = "./configure" print(f"Configuring SPECFEM2D with command: {cmd}") # Ignore the configure outputs from SPECFEM subprocess.run(cmd, shell=True, check=True, stdout=subprocess.DEVNULL) else: print("SPECFEM2D already configured, skipping 'configure'") except subprocess.CalledProcessError as e: print(f"SPECFEM `configure` step has failed, please check and " f"retry. If this command repeatedly fails, you may need " f"to configure SPECFEM2D manually.\n{e}") sys.exit(-1) try: if not glob.glob("./bin/x*"): cmd = "make all" print(f"Making SPECFEM2D binaries with command: {cmd}") # Ignore the make outputs from SPECFEM subprocess.run(cmd, shell=True, check=True, stdout=subprocess.DEVNULL) else: print("executables found in SPECFEM2D/bin directory, " "skipping 'make'") except subprocess.CalledProcessError as e: print(f"SPECFEM 'make' step has failed, please check and " f"retry. If this command repeatedly fails, you may need " f"to compile SPECFEM2D manually.\n{e}") sys.exit(-1) # Symlink the finished repo into the working directory so that any # subsequent runs won't need to have the user re-type repo location if not os.path.exists(os.path.join(self.cwd, "specfem2d")): print("symlinking existing specfem2D repository to cwd") ln(self.sem2d_paths.repo, os.path.join(self.cwd, "specfem2d"))
def setup_specfem2d_for_model_true(self): """ Overwrites MODEL TRUE creation from EX1 Make some adjustments to the parameter file to create the final velocity model. This function assumes it is running from inside the SPECFEM2D/DATA directory """ cd(self.workdir_paths.data) assert (os.path.exists("Par_file")), f"I cannot find the Par_file!" print("> EX: Updating SPECFEM2D to set checkerboard model as " "MODEL_TRUE") self.sf.sempar("model", "legacy") # read model_velocity.dat_checker rm("proc000000_model_velocity.dat_input") ln("model_velocity.dat_checker", "proc000000_model_velocity.dat_input")
def initialize_solver_directories(self): """ Creates solver directories in serial using a single node. Should only be run by master job. Differs from Base initialize_solver_directories() as this serial task will create directory structures for each source, rather than having each source create its own. However the internal dir structure is the same. """ for source_name in self.source_names: cwd = os.path.join(PATH.SOLVER, source_name) # Remove any existing scratch directory unix.rm(cwd) # Create internal directory structure, change into directory to make # all actions RELATIVE path actions unix.mkdir(cwd) unix.cd(cwd) for cwd_dir in [ "bin", "DATA", "OUTPUT_FILES/DATABASES_MPI", "traces/obs", "traces/syn", "traces/adj" ]: unix.mkdir(cwd_dir) # Copy exectuables src = glob(os.path.join(PATH.SPECFEM_BIN, "*")) dst = os.path.join("bin", "") unix.cp(src, dst) # Copy all input files except source files src = glob(os.path.join(PATH.SPECFEM_DATA, "*")) src = [_ for _ in src if self.source_prefix not in _] dst = os.path.join("DATA", "") unix.cp(src, dst) # symlink event source specifically src = os.path.join(PATH.SPECFEM_DATA, f"{self.source_prefix}_{source_name}") dst = os.path.join("DATA", self.source_prefix) unix.ln(src, dst) if source_name == self.mainsolver: # Symlink taskid_0 as mainsolver in solver directory unix.ln(source_name, os.path.join(PATH.SOLVER, "mainsolver")) # Only check the solver parameters once self.check_solver_parameter_files()
def setup_specfem2d_for_model_init(self): """ Make some adjustments to the original parameter file to. This function assumes it is running from inside the SPECFEM2D/DATA dir """ cd(self.workdir_paths.data) assert (os.path.exists("Par_file")), f"I cannot find the Par_file!" print("> Setting the SPECFEM2D Par_file for SeisFlows3 compatiblility") self.sf.sempar("setup_with_binary_database", 1) # create .bin files self.sf.sempar("save_model", "binary") # output model in .bin format self.sf.sempar("save_ASCII_kernels", ".false.") # kernels also .bin rm(self.workdir_paths.output) mkdir(self.workdir_paths.output)
def run_xspecfem2d_binaries(self): """ Runs the xmeshfem2d and then xspecfem2d binaries using subprocess and then do some cleanup to get files in the correct locations. Assumes that we can run the binaries directly with './' """ cd(self.workdir_paths.workdir) cmd_mesh = f"./bin/xmeshfem2D > OUTPUT_FILES/mesher.log.txt" cmd_spec = f"./bin/xspecfem2D > OUTPUT_FILES/solver.log.txt" for cmd in [cmd_mesh, cmd_spec]: print(f"Running SPECFEM2D with command: {cmd}") subprocess.run(cmd, shell=True, check=True, stdout=subprocess.DEVNULL)
def restart(self): """ On top of base restart class, include a restart of the LBFGS internal memory and memmaps """ super().restart() self.logger.info("restarting L-BFGS optimization algorithm by clearing " "internal memory") self.LBFGS_iter = 1 self.memory_used = 0 unix.cd(PATH.OPTIMIZE) s = np.memmap(filename=self.s_file, mode="r+") y = np.memmap(filename=self.y_file, mode="r+") s[:] = 0. y[:] = 0.
def initialize_adjoint_traces(self): """ Setup utility: Creates the "adjoint traces" expected by SPECFEM. This is only done for the 'base' the Preprocess class. Note: Adjoint traces are initialized by writing zeros for all channels. Channels actually in use during an inversion or migration will be overwritten with nonzero values later on. """ super().initialize_adjoint_traces() unix.cd(self.cwd) unix.cd(os.path.join("traces", "adj")) # work around SPECFEM2D's use of different name conventions for # regular traces and 'adjoint' traces if PAR.FORMAT.upper() == "SU": files = glob("*SU") unix.rename(old="_SU", new="_SU.adj", names=files) elif PAR.FORMAT.upper() == "ASCII": files = glob("*sem?") # Get the available extensions, which are named based on unit extensions = set([os.path.splitext(_)[-1] for _ in files]) for extension in extensions: unix.rename(old=extension, new=".adj", names=files) # SPECFEM2D requires that all components exist even if ununsed components = ["x", "y", "z", "p"] if PAR.FORMAT.upper() == "SU": for comp in components: src = f"U{PAR.COMPONENTS[0]}_file_single.su.adj" dst = f"U{comp.lower()}s_file_single.su.adj" if not exists(dst): unix.cp(src, dst) elif PAR.FORMAT.upper() == "ASCII": for fid in glob("*.adj"): net, sta, cha, ext = fid.split(".") for comp in components: # Replace the last value in the channel with new component cha_check = cha[:-1] + comp.upper() fid_check = ".".join([net, sta, cha_check, ext]) if not exists(fid_check): unix.cp(fid, fid_check)