Source code for pydrad.configure.util

"""
Utilities for HYDRAD configuration
"""
import astropy.units as u
import os
import pathlib
import platform
import shutil
import stat
import subprocess
import tempfile

from pydrad import log

__all__ = ['MissingParameter',
           'HYDRADError',
           'run_shell_command',
           'get_clean_hydrad',
           'get_equilibrium_heating_rate']


class MissingParameter(Exception):
    """
    An error to raise if a parameter is missing in the configuration
    """
    pass


class HYDRADError(Exception):
    """
    An error to raise if something has gone wrong when running the HYDRAD code
    """
    pass


[docs] def run_shell_command(path, shell=True, **kwargs): """ Wrapper function for running shell commands. This is essentially a light wrapper around `subprocess.run` and includes some error handling and logging specific to running the shell commands needed to run and compile HYDRAD. Parameters ---------- cmd : `list` Command to run cwd : `str` or path-like Directory to run the command in shell : `bool`, optional See `~subprocess.run` Raises ------ HYDRADError This error is raised if HYDRAD or initial conditions code fails to compile or if there is a runtime error in the initial conditions code. """ path = pathlib.Path(path) on_windows = platform.system().lower() == 'windows' cmd = subprocess.run( path.name if on_windows else f'./{path.name}', cwd=path.parent, shell=shell, capture_output=True, env=os.environ, **kwargs, ) stdout = f"{cmd.stdout.decode('utf-8')}" stderr = f"{cmd.stderr.decode('utf-8')}" if stdout: log.info(stdout) if stderr: log.warning(stderr) hydrad_error_messages = ['segmentation fault', 'abort', 'error', 'trace trap'] if any([e in s.lower() for s in [stderr, stdout] for e in hydrad_error_messages]): raise HYDRADError(f'{stderr}\n{stdout}')
[docs] def get_clean_hydrad(output_path, base_path=None, from_github=False, overwrite=False): """ Create a clean copy of HYDRAD with only the files necessary to run the code. May be useful when making many copies. Parameters ---------- output_path : pathlike Path to the new stripped down version base_path : pathlike, optional Path to the original copy. This will not be modified. from_github : `bool`, optional If True, grab the latest copy of HYDRAD from GitHub. In this case, `base_path` is ignored. Note that this requires the GitPython package. overwrite : `bool`, optional If True, overwrite the directory at `output_path` if it exists. You may need to set this to true of the path you are writing your clean copy to HYDRAD to already exists, but is empty. """ # NOTE: This function is needed for handling permissions errors that occur on Windows # when trying to remove files. See this GH issue comment for more information: # https://github.com/python/cpython/issues/87823#issuecomment-1093908280 def _handle_readonly(func, path, exc_info): if func not in (os.unlink, os.rmdir) or exc_info[1].winerror != 5: raise exc_info[1] os.chmod(path, stat.S_IWRITE) func(path) # NOTE: this is all done in a temp directory and then copied over # so that if something fails, all the files are cleaned up with tempfile.TemporaryDirectory() as _tmpdir: tmpdir = pathlib.Path(_tmpdir) if from_github: import git git.Repo.clone_from('https://github.com/rice-solar-physics/HYDRAD', tmpdir) elif base_path: shutil.copytree(base_path, tmpdir, dirs_exist_ok=True) else: raise ValueError('Specify local path to HYDRAD or clone from GitHub') rm_dirs = [ 'Forward_Model', 'HYDRAD_GUI', 'Visualisation', '.git', ] rm_files = [ 'HYDRAD_GUI.jar', 'LICENSE', 'README.md', '.gitignore', 'hydrad-logo.png', ] # NOTE: Use glob because name of user guide PDF may change rm_files += [f.name for f in tmpdir.glob('HYDRAD_User_Guide*.pdf')] for d in rm_dirs: try: shutil.rmtree(pathlib.Path(tmpdir) / d, onerror=_handle_readonly) except FileNotFoundError: log.warning(f'Cannot remove {d}. Directory not found.') for f in rm_files: (tmpdir / f).unlink(missing_ok=True) shutil.copytree(tmpdir, output_path, dirs_exist_ok=overwrite)
[docs] def get_equilibrium_heating_rate(root_dir): """ Read equilibrium heating rate from initial conditions results Parameters ---------- root_dir : `str` or pathlike Path to HYDRAD directory """ filename = pathlib.Path(root_dir) / 'Initial_Conditions' / 'profiles' / 'initial.amr.sol' with filename.open() as f: equilibrium_heating_rate = float(f.readline()) * u.Unit('erg cm-3 s-1') return equilibrium_heating_rate