def warn(message, category="default", status={}): """Trigger a warning, an error or just ignore based on the value defined in the :py:attr:`~radis.lbl.loader.DatabankLoader.warnings` dictionary. The warnings can thus be deactivated selectively by setting the SpectrumFactory :attr:`~radis.lbl.loader.DatabankLoader.warnings` attribute. All warnings can be disabled by setting it to ``False``. Parameters ---------- message: str what to print category: str one of the keys of self.warnings. status: dict status for all warning categories. Can be one of ``'warn'``, ``'ignore'``, ``'print'``, ``'error'`` Examples -------- :: if not ((df.Erotu > tol).all() and (df.Erotl > tol).all()): warn( "There are negative rotational energies in the database", "NegativeEnergiesWarning", ) """ # TODO (refactor): make it possible to run warn(NegativeEnergiesWarning("message")) # instead of warn("message, "NegativeEnergiesWarning") # ex : # if isinstance(message, Warning): # etc. if status == False: return action = status[category] WarningType = WarningClasses[category] if action in "warn": warnings.warn(WarningType(message)) elif action == "once": warnings.warn(WarningType(message)) # keep 'once' but ignore WarningType with simplefilters warnings.simplefilter("ignore", WarningType) elif action == "ignore": pass elif action == "print": # just print the message, in red printr(message) elif action == "error": raise WarningType(message) else: raise ValueError("Unexpected action for warning: {0}".format(action))
def warn(message, category="default", status={}): """Trigger a warning, an error or just ignore based on the value defined in the :py:attr:`~radis.lbl.loader.DatabankLoader.warnings` dictionary The warnings can thus be deactivated selectively by setting the SpectrumFactory :attr:`~radis.lbl.loader.DatabankLoader.warnings` attribute. All warnings can be disabled by setting it to ``False``. Parameters ---------- message: str what to print category: str one of the keys of self.warnings. status: dict status for all warning categories. Can be one of ``'warn'``, ``'ignore'``, ``'print'``, ``'error'`` """ if status == False: return action = status[category] WarningType = WarningClasses[category] if action in "warn": warnings.warn(WarningType(message)) elif action == "once": warnings.warn(WarningType(message)) # keep 'once' but ignore WarningType with simplefilters warnings.simplefilter("ignore", WarningType) elif action == "ignore": pass elif action == "print": # just print the message, in red printr(message) elif action == "error": raise WarningType(message) else: raise ValueError("Unexpected action for warning: {0}".format(action))
def warn(message, category='default', status={}): ''' Trigger a warning, an error or just ignore based on the value defined in the :py:attr:`~radis.lbl.loader.DatabankLoader.warnings` dictionary The warnings can thus be deactivated selectively by setting the SpectrumFactory :attr:`~radis.lbl.loader.DatabankLoader.warnings` attribute Parameters ---------- message: str what to print category: str one of the keys of self.warnings status: dict status for all warning categories. Can be one of ``'warn'``, ``'ignore'``, ``'print'``, ``'error'`` ''' action = status[category] WarningType = WarningClasses[category] if action == 'warn': warnings.warn(WarningType(message)) elif action == 'once': warnings.warn(WarningType(message)) # keep 'once' but ignore WarningType with simplefilters warnings.simplefilter('ignore', WarningType) elif action == 'ignore': pass elif action == 'print': # just print the message, in red printr(message) elif action == 'error': raise WarningType(message) else: raise ValueError('Unexpected action for warning: {0}'.format(action))
def IgnoreMissingDatabase(err, file='', warnings=True): ''' A function to deal with MissingDatabases errors. If :data:`~radis.test.utils.IGNORE_MISSING_DATABASES` is ``True``, just print a warning. Else, raise the error Parameters ---------- err: an Error file: str where the error occured. Use ``file=__file__`` on function call ''' # TODO: make IGNORE_MISSING_DATABASES a ~/.radis parameter if IGNORE_MISSING_DATABASES: if warnings: import sys print(sys.exc_info()) printr('In {0}: Database not defined: {1}'.format( file, err.filename) + '\n Ignoring the test') return True else: raise err
def load_h5_cache_file( cachefile, use_cached, metadata, current_version, last_compatible_version=OLDEST_COMPATIBLE_VERSION, verbose=True, ): """Function to load a h5 cache file Parameters ---------- cachefile: str cache file path use_cached: str use cache file if value is not ``False``: - if ``True``, use (and generate if doesnt exist) cache file. - if ``'regen'``, delete cache file (if exists) so it is regenerated - if ``'force'``, use cache file and raises an error if it doesnt exist if using the cache file, check if the file is deprecated. If it is deprecated, regenerate the file unless ``'force'`` was used (in that case, raise an error) metadata: dict values are compared to cache file attributes. If they dont match, the file is considered deprecated. See ``use_cached`` to know how to handle deprecated files current_version: str version is compared to cache file version (part of attributes). If current version is superior, a simple warning is triggered. last_compatible_version: str if file version is inferior to this, file is considered deprecated. See ``use_cached`` to know how to handle deprecated files. Default :data:`~radis.OLDEST_COMPATIBLE_VERSION`. Returns ------- df: pandas DataFrame, or None None if no cache file was found, or if it was deleted See Also -------- :data:`~radis.OLDEST_COMPATIBLE_VERSION` """ # 1. know if we have to load the file if not use_cached: return None elif use_cached == "regen" and exists(cachefile): os.remove(cachefile) if verbose: printm("Deleted h5 cache file : {0}".format(cachefile)) return None # 2. check the file is here if not exists(cachefile): if use_cached == "force": raise ValueError("Cache file {0} doesnt exist".format(cachefile)) else: return None # File doesn't exist. It's okay. # 3. read file attributes to know if it's deprecated try: check_not_deprecated( cachefile, metadata, current_version=current_version, last_compatible_version=last_compatible_version, ) # ... if deprecated, raise an error only if 'force' except DeprecatedFileError as err: if use_cached == "force": raise err else: if verbose: printr("File {0} deprecated:\n{1}\nDeleting it!".format( cachefile, str(err))) os.remove(cachefile) return None # 4. File is not not deprecated: read the content. else: df = None if verbose >= 2: printm("Reading cache file ({0})".format(cachefile)) try: df = pd.read_hdf(cachefile, "df") except KeyError as err: # An error happened during file reading. # Fail safe by deleting cache file (unless we explicitely wanted it # with 'force') if use_cached == "force": raise else: if verbose: printr("An error happened during cache file reading " + "{0}:\n{1}\n".format(cachefile, str(err)) + "Deleting cache file to regenerate it") os.remove(cachefile) df = None return df
def check_cache_file(fcache, use_cached=True, metadata={}, verbose=True): """Quick function that check status of cache file generated by RADIS: Parameters ---------- fcache: str cache file name use_cached: ``True``, ``False``, ``'force'``, ``'regen'`` see notes below. Default ``True``. metadata: dict attributes to check verbose: boolean print stuff Notes ----- The function first checks the existence of ``fcache``. What is does depends on the value of ``use_cached``: - if ``True``, check it exists. - if ``'regen'``, delete cache file to regenerate it later. - if ``'force'``, raise an error if file doesnt exist. Then look if it is deprecated (we just look at the attributes, the file is never fully read). Deprecation is done by :py:func:`~radis.misc.cache_files.check_not_deprecated` comparing the ``metadata=`` content. - if deprecated, deletes it to regenerate later unless 'force' was used See Also -------- :py:func:`~radis.misc.cache_files.check_not_deprecated` """ # Test existence of file: if not use_cached: return # we dont want a cache file, no need to test it elif use_cached == "regen": if exists(fcache): os.remove(fcache) if verbose: print(("Deleted h5 cache file : {0}".format(fcache))) elif use_cached == "force": if not exists(fcache): raise ValueError("Cache file {0} doesnt exist".format(fcache)) else: # use_cached == True pass # just use the file as is # If file is still here, test if it is deprecated: # (we just read the attributes, the file is never fully read) if exists(fcache): if verbose: print(("Using cache file: {0}".format(fcache))) try: check_not_deprecated( fcache, metadata=metadata, current_version=radis.__version__, last_compatible_version=OLDEST_COMPATIBLE_VERSION, ) except DeprecatedFileError as err: if use_cached == "force": raise else: # delete file to regenerate it in the end of the script if verbose: printr("File {0} deprecated:\n{1}\nDeleting it!".format( fcache, str(err))) os.remove(fcache) return
def fetch_astroquery(molecule, isotope, wmin, wmax, verbose=True, cache=True, metadata={}): ''' Wrapper to Astroquery [1]_ fetch function to download a line database Notes ----- Astroquery [1]_ is itself based on [HAPI]_ Parameters ---------- molecule: str, or int molecule name or identifier isotope: int isotope number wmin, wmax: float (cm-1) wavenumber min and max Other Parameters ---------------- verbose: boolean Default ``True`` cache: boolean if ``True``, tries to find a ``.h5`` cache file in the Astroquery :py:attr:`~astroquery.query.BaseQuery.cache_location`, that would match the requirements. If not found, downloads it and saves the line dataframe as a ``.h5`` file in the Astroquery. metadata: dict if ``cache=True``, check that the metadata in the cache file correspond to these attributes. Arguments ``molecule``, ``isotope``, ``wmin``, ``wmax`` are already added by default. References ---------- .. [1] `Astroquery <https://astroquery.readthedocs.io>`_ See Also -------- :py:func:`astroquery.hitran.reader.download_hitran`, :py:func:`astroquery.hitran.reader.read_hitran_file`, :py:attr:`~astroquery.query.BaseQuery.cache_location` ''' # Check input if not is_float(molecule): mol_id = get_molecule_identifier(molecule) else: mol_id = molecule molecule = get_molecule(mol_id) assert is_float(isotope) empty_range = False # If cache, tries to find from Astroquery: if cache: # Update metadata with physical properties from the database. metadata.update({ 'molecule': molecule, 'isotope': isotope, 'wmin': wmin, 'wmax': wmax }) fcache = join( Hitran.cache_location, CACHE_FILE_NAME.format( **{ 'molecule': molecule, 'isotope': isotope, 'wmin': wmin, 'wmax': wmax })) check_cache_file(fcache=fcache, use_cached=cache, metadata=metadata, verbose=verbose) if exists(fcache): try: return get_cache_file(fcache, verbose=verbose) except Exception as err: if verbose: printr( 'Problem reading cache file {0}:\n{1}\nDeleting it!'. format(fcache, str(err))) os.remove(fcache) # tbl = Hitran.query_lines_async(molecule_number=mol_id, # isotopologue_number=isotope, # min_frequency=wmin / u.cm, # max_frequency=wmax / u.cm) # # Download using the astroquery library try: response = Hitran.query_lines_async(molecule_number=mol_id, isotopologue_number=isotope, min_frequency=wmin / u.cm, max_frequency=wmax / u.cm) except KeyError as err: raise KeyError(str(err)+' <<w this error occured in Astroquery. Maybe these molecule '+\ '({0}) and isotope ({1}) are not supported'.format(molecule, isotope)) from err # Deal with usual errors if response.status_code == 404: # Maybe there are just no lines for this species in this range # In that case we usually end up with errors like: # (<class 'Exception'>, Exception('Query failed: 404 Client Error: # Not Found for url: http://hitran.org/lbl/api?numax=25000&numin=19000&iso_ids_list=69\n',), # <traceback object at 0x7f0967c91708>) if response.reason == 'Not Found': # Let's bet it's just that there are no lines in this range empty_range = True if verbose: print(( 'No lines for {0} (id={1}), iso={2} in range {3:.2f}-{4:.2f}cm-1. ' .format(molecule, mol_id, isotope, wmin, wmax))) else: raise ValueError( 'An error occured during the download of HITRAN files ' + 'for {0} (id={1}), iso={2} between {3:.2f}-{4:.2f}cm-1. '. format(molecule, mol_id, isotope, wmin, wmax) + 'Are you online?\n' + 'See details of the error below:\n\n {0}'.format( response.reason)) elif response.status_code == 500: raise ValueError('{0} while querying the HITRAN server: '.format(response.status_code)+\ '\n\n{0}'.format(response.text)) # Process response # Rename columns from Astroquery to RADIS format rename_columns = { 'molec_id': 'id', 'local_iso_id': 'iso', 'nu': 'wav', 'sw': 'int', 'a': 'A', 'gamma_air': 'airbrd', 'gamma_self': 'selbrd', 'elower': 'El', 'n_air': 'Tdpair', 'delta_air': 'Pshft', 'global_upper_quanta': 'globu', 'global_lower_quanta': 'globl', 'local_upper_quanta': 'locu', 'local_lower_quanta': 'locl', 'line_mixing_flag': 'lmix', 'gp': 'gp', 'gpp': 'gpp', } if not empty_range: # _fix_astroquery_file_format(filename) # Note: as of 0.9.16 we're not fixing astroquery_file_format anymore. # maybe we should. tbl = Hitran._parse_result(response) df = tbl.to_pandas() df = df.rename(columns=rename_columns) else: df = pd.DataFrame(columns=list(rename_columns.values())) # Cast type to float64 cast_type = { 'wav': np.float64, 'int': np.float64, 'A': np.float64, 'airbrd': np.float64, 'selbrd': np.float64, 'El': np.float64, 'Tdpair': np.float64, 'Pshft': np.float64, } for c, typ in cast_type.items(): df[c] = df[c].astype(typ) # cached file mode but cached file doesn't exist yet (else we had returned) if cache: if verbose: print('Generating cached file: {0}'.format(fcache)) try: save_to_hdf(df, fcache, metadata=metadata, version=radis.__version__, key='df', overwrite=True, verbose=verbose) except: if verbose: print(sys.exc_info()) print( 'An error occured in cache file generation. Lookup access rights' ) pass return df
def fetch_astroquery( molecule, isotope, wmin, wmax, verbose=True, cache=True, expected_metadata={} ): """Download a HITRAN line database to a Pandas DataFrame. Wrapper to Astroquery [1]_ fetch function Parameters ---------- molecule: str, or int molecule name or identifier isotope: int isotope number wmin, wmax: float (cm-1) wavenumber min and max Other Parameters ---------------- verbose: boolean Default ``True`` cache: boolean or ``'regen'`` if ``True``, tries to find a ``.h5`` cache file in the Astroquery :py:attr:`~astroquery.query.BaseQuery.cache_location`, that would match the requirements. If not found, downloads it and saves the line dataframe as a ``.h5`` file in the Astroquery. If ``'regen'``, delete existing cache file to regerenate it. expected_metadata: dict if ``cache=True``, check that the metadata in the cache file correspond to these attributes. Arguments ``molecule``, ``isotope``, ``wmin``, ``wmax`` are already added by default. Notes ----- The HITRAN module in Astroquery [1]_ is itself based on [HAPI]_ References ---------- .. [1] `Astroquery <https://astroquery.readthedocs.io>`_ See Also -------- :py:func:`astroquery.hitran.reader.download_hitran`, :py:func:`astroquery.hitran.reader.read_hitran_file`, :py:attr:`~astroquery.query.BaseQuery.cache_location` """ # Check input if not is_float(molecule): mol_id = get_molecule_identifier(molecule) else: mol_id = molecule molecule = get_molecule(mol_id) assert is_float(isotope) empty_range = False if cache: # Cache file location in Astroquery cache # TODO: move full HITRAN databases in ~/radisdb cache like io/hitemp/fetch_hitemp ? fcache = join( Hitran.cache_location, CACHE_FILE_NAME.format( **{"molecule": molecule, "isotope": isotope, "wmin": wmin, "wmax": wmax} ), ) # ... Update metadata with physical properties from the database. expected_metadata.update( {"molecule": molecule, "isotope": isotope, "wmin": wmin, "wmax": wmax} ) if cache == "regen": if exists(fcache): if verbose: print(f"Cache file {fcache} deleted to be regenerated") os.remove(fcache) else: # Load cache file if valid check_cache_file( fcache=fcache, use_cached=cache, expected_metadata=expected_metadata, verbose=verbose, ) if exists(fcache): try: return get_cache_file(fcache, verbose=verbose) except Exception as err: if verbose: printr( "Problem reading cache file {0}:\n{1}\nDeleting it!".format( fcache, str(err) ) ) os.remove(fcache) # Download using the astroquery library try: response = Hitran.query_lines_async( molecule_number=mol_id, isotopologue_number=isotope, min_frequency=wmin / u.cm, max_frequency=wmax / u.cm, ) except KeyError as err: raise KeyError( str(err) + " <<w this error occured in Astroquery. Maybe these molecule " + "({0}) and isotope ({1}) are not supported".format(molecule, isotope) ) from err # Deal with usual errors if response.status_code == 404: # Maybe there are just no lines for this species in this range # In that case we usually end up with errors like: # (<class 'Exception'>, Exception('Query failed: 404 Client Error: # Not Found for url: http://hitran.org/lbl/api?numax=25000&numin=19000&iso_ids_list=69\n',), # <traceback object at 0x7f0967c91708>) if response.reason == "Not Found": # Let's bet it's just that there are no lines in this range empty_range = True if verbose: print( ( "No lines for {0} (id={1}), iso={2} in range {3:.2f}-{4:.2f}cm-1. ".format( molecule, mol_id, isotope, wmin, wmax ) ) ) else: raise ValueError( "An error occured during the download of HITRAN files " + "for {0} (id={1}), iso={2} between {3:.2f}-{4:.2f}cm-1. ".format( molecule, mol_id, isotope, wmin, wmax ) + "Are you online?\n" + "See details of the error below:\n\n {0}".format(response.reason) ) elif response.status_code == 500: raise ValueError( "{0} while querying the HITRAN server: ".format(response.status_code) + "\n\n{0}".format(response.text) ) # Process response # Rename columns from Astroquery to RADIS format rename_columns = { "molec_id": "id", "local_iso_id": "iso", "nu": "wav", "sw": "int", "a": "A", "gamma_air": "airbrd", "gamma_self": "selbrd", "elower": "El", "n_air": "Tdpair", "delta_air": "Pshft", "global_upper_quanta": "globu", "global_lower_quanta": "globl", "local_upper_quanta": "locu", "local_lower_quanta": "locl", "line_mixing_flag": "lmix", "gp": "gp", "gpp": "gpp", } if not empty_range: tbl = Hitran._parse_result(response) df = tbl.to_pandas() df = df.rename(columns=rename_columns) else: df = pd.DataFrame(columns=list(rename_columns.values())) # Cast type to float64 cast_type = { "wav": np.float64, "int": np.float64, "A": np.float64, "airbrd": np.float64, "selbrd": np.float64, "El": np.float64, "Tdpair": np.float64, "Pshft": np.float64, } for c, typ in cast_type.items(): df[c] = df[c].astype(typ) # cached file mode but cached file doesn't exist yet (else we had returned) if cache: new_metadata = { "molecule": molecule, "isotope": isotope, "wmin": wmin, "wmax": wmax, } if verbose: print( "Generating cache file {0} with metadata :\n{1}".format( fcache, new_metadata ) ) try: save_to_hdf( df, fcache, metadata=new_metadata, version=radis.__version__, key="df", overwrite=True, verbose=verbose, ) except PermissionError: if verbose: print(sys.exc_info()) print("An error occured in cache file generation. Lookup access rights") pass return df
def check_cache_file(use_cached, fcache, metadata={}, verbose=True): ''' Quick function that check status of cache file generated by RADIS: First test its existence: function of the value of ``use_cached``: - if True, ok - if 'regen', delete cache file to regenerate it later. - if 'force', raise an error if file doesnt exist. Then look if it is deprecated (we just look at the attributes, the file is never fully read) - if deprecated, delete it to regenerate later unless 'force' was used Parameters ---------- use_cached: True, False, 'force', 'regen' see description above fcache: str cache file name metadata: dict attributes to check verbose: boolean print stuff ''' # Test existence of file: if not use_cached: return # we dont want a cache file, no need to test it elif use_cached == 'regen': if exists(fcache): os.remove(fcache) if verbose: print(('Deleted h5 cache file : {0}'.format(fcache))) elif use_cached == 'force': if not exists(fcache): raise ValueError('Cache file {0} doesnt exist'.format(fcache)) else: # use_cached == True pass # just use the file as is # If file is still here, test if it is deprecated: # (we just read the attributes, the file is never fully read) if exists(fcache): if verbose: print(('Using cache file: {0}'.format(fcache))) try: check_not_deprecated( fcache, metadata=metadata, current_version=radis.__version__, last_compatible_version=OLDEST_COMPATIBLE_VERSION) except DeprecatedFileError as err: if use_cached == 'force': raise else: # delete file to regenerate it in the end of the script if verbose: printr('File {0} deprecated:\n{1}\nDeleting it!'.format( fcache, str(err))) os.remove(fcache) return
def load_h5_cache_file( cachefile, use_cached, valid_if_metadata_is={}, relevant_if_metadata_above={}, relevant_if_metadata_below={}, current_version="", last_compatible_version=OLDEST_COMPATIBLE_VERSION, verbose=True, ): """Function to load a h5 cache file. Parameters ---------- cachefile: str cache file path use_cached: str use cache file if value is not ``False``: - if ``True``, use (and generate if doesnt exist) cache file. - if ``'regen'``, delete cache file (if exists) so it is regenerated - if ``'force'``, use cache file and raises an error if it doesnt exist if using the cache file, check if the file is deprecated. If it is deprecated, regenerate the file unless ``'force'`` was used (in that case, raise an error) valid_if_metadata_is: dict values are compared to cache file attributes. If they dont match, the file is considered deprecated. See ``use_cached`` to know how to handle deprecated files .. note:: if the file has extra attributes they are not compared current_version: str version is compared to cache file version (part of attributes). If current version is superior, a simple warning is triggered. last_compatible_version: str if file version is inferior to this, file is considered deprecated. See ``use_cached`` to know how to handle deprecated files. Default :data:`~radis.OLDEST_COMPATIBLE_VERSION`. relevant_if_metadata_above, relevant_if_metadata_below : dict values are compared to cache file attributes. If they don't match, the function returns a :py:class:`~radis.misc.warning.IrrelevantFileWarning`. For instance, load a line database file, only if it contains wavenumbers between 2300 and 2500 cm-1 :: load_h5_cache_file(..., relevant_if_metadata_above={'wav':2300}; relevant_if_metadata_below={'wav':2500}) Note that in such an example, the file data is not read. Only the file metadata is. If the metadata does not contain the key (e.g.: ``'wav'``) a :py:class:`~radis.misc.warning.DeprecatedFileWarning` is raised. Returns ------- df: pandas DataFrame, or None None if no cache file was found, or if it was deleted See Also -------- :data:`~radis.OLDEST_COMPATIBLE_VERSION` """ # 1. know if we have to load the file if not use_cached: return None elif use_cached == "regen" and exists(cachefile): os.remove(cachefile) if verbose: printm("Deleted h5 cache file : {0}".format(cachefile)) return None # 2. check the file is here if not exists(cachefile): if use_cached == "force": raise ValueError("Cache file {0} doesnt exist".format(cachefile)) else: return None # File doesn't exist. It's okay. # 3. read file attributes to know if it's deprecated try: check_not_deprecated( cachefile, metadata_is=valid_if_metadata_is, metadata_keys_contain=list(relevant_if_metadata_above.keys()) + list(relevant_if_metadata_below.keys()), current_version=current_version, last_compatible_version=last_compatible_version, ) # ... if deprecated, raise an error only if 'force' except DeprecatedFileWarning as err: if use_cached == "force": raise err else: if verbose: printr("File {0} deprecated:\n{1}\nDeleting it!".format( cachefile, str(err))) os.remove(cachefile) return None # 4. File is not not deprecated: read the the extremum wavenumbers. raise if relevant_if_metadata_above is not None or relevant_if_metadata_below is not None: try: check_relevancy( cachefile, relevant_if_metadata_above, relevant_if_metadata_below, ) # ... if irrelevant, raise an error only if 'force' except IrrelevantFileWarning as err: if verbose >= 2: from radis.misc.printer import printg printg("Database file {0} irrelevant and not loaded".format( cachefile)) raise err # 5. File is relevant: read the content. df = None if verbose >= 2: printm("Reading cache file ({0})".format(cachefile)) try: df = pd.read_hdf(cachefile, "df") except KeyError as err: # An error happened during file reading. # Fail safe by deleting cache file (unless we explicitely wanted it # with 'force') if use_cached == "force": raise else: if verbose: printr("An error happened during cache file reading " + "{0}:\n{1}\n".format(cachefile, str(err)) + "Deleting cache file to regenerate it") os.remove(cachefile) df = None return df