def test_calculatedQ_match_HAPI(plot=False, verbose=True, *args, **kwargs): """Tested that Q ab_initio (Dunham) match HAPI for different molecules and isotopes""" # molecule, isotope, temperature, absolute tolerance for molecule, iso, T, atol, rtol in [ ("CO2", 1, 300, 0.9, 0.02), ("CO2", 1, 1000, 5.0, 0.02), ("CO2", 1, 3000, 2150, 0.02), ("CO2", 2, 300, 1.8, 0.02), ("CO2", 2, 1000, 25, 0.02), ("CO2", 2, 3000, 4962, 0.02), ("CO", 1, 300, 0.16, 0.02), ("CO", 1, 1000, 1.9, 0.02), ("CO", 1, 3000, 27, 0.02), ("CO", 2, 300, 0.31, 0.02), ("CO", 2, 1000, 4.1, 0.02), ("CO", 2, 3000, 56.9, 0.02), ("CO", 3, 300, 0.16, 0.02), ("CO", 3, 1000, 2.1, 0.02), ("CO", 3, 3000, 28.6, 0.02), ]: S = Molecules[molecule][iso]["X"] # Dont use cached: force recalculating db = PartFunc_Dunham(S) from radis.db.classes import get_molecule_identifier hapi = PartFuncHAPI(M=get_molecule_identifier(molecule), I=iso) Q_radis = db.at(T) Q_hapi = hapi.at(T) if verbose: print("Q({0},iso={1},{2}K)\tRADIS: {3:.2f}\tHAPI={4:.2f}".format( molecule, iso, T, Q_radis, Q_hapi)) try: assert np.isclose(Q_radis, Q_hapi, atol=atol) assert np.isclose(Q_radis, Q_hapi, atol=atol) except AssertionError: raise AssertionError( "Partition function for {0}, iso={1} ".format(molecule, iso) + "at {0}K doesnt match in RADIS ".format(T) + "({0:.4f}) and HAPI ({1:.4f})".format(Q_radis, Q_hapi)) if verbose: printm("Tested Q ab_initio (Dunham) matches HAPI: OK") return True
def test_line_broadening(rtol=1e-3, verbose=True, plot=False, *args, **kwargs): r""" Plot absorption coefficient (cm-1) of CO at high temperature (2000 K) with RADIS, and compare with calculations from HAPI using the HITRAN database Notes ----- In this example no data is needed. Everything is downloaded from the HITRAN database directly using either the HAPI ``fetch`` function, or the RADIS :meth:`~neq.spec.factory.fetch_databank` method. """ from hapi import ( absorptionCoefficient_Voigt, db_begin, fetch, tableList, transmittanceSpectrum, ) setup_test_line_databases() # add HITRAN-CO-TEST in ~/.radis if not there # Conditions molecule = "CO2" mol_id = get_molecule_identifier(molecule) iso = 1 T = 1500 p = 0.1 L = 0.1 # M = 0.001 # mole fraction (dont know where to put that ) dnu = 0.0001 wmin = nm2cm(4372.69 + 0.2) # cm-1 wmax = nm2cm(4372.69 - 0.2) # cm-1 # broadening_max_width = 6 # cm-1 broadening_max_width = 0.5 # cm-1 # %% HITRAN calculation # ----------- # Generate HAPI database locally HAPIdb = join(dirname(__file__), __file__.replace(".py", "_HAPIdata")) def calc_hapi(): """ Calc spectrum under HAPI """ clean_after_run = not exists(HAPIdb) and False try: db_begin(HAPIdb) if not molecule in tableList( ): # only if data not downloaded already fetch( molecule, mol_id, iso, wmin - broadening_max_width / 2, wmax + broadening_max_width / 2, ) # HAPI doesnt correct for side effects # Calculate with HAPI nu, coef = absorptionCoefficient_Voigt( SourceTables="CO2", Environment={ "T": T, "p": p / 1.01325, }, # K # atm WavenumberStep=dnu, HITRAN_units=False, GammaL="gamma_self", ) nu, trans = transmittanceSpectrum( nu, coef, Environment={ "l": L, }, ) # cm s_hapi = Spectrum.from_array( nu, trans, "transmittance_noslit", "cm-1", "1", conditions={"Tgas": T}, name="HAPI", ) except: raise finally: if clean_after_run: shutil.rmtree(HAPIdb) return s_hapi s_hapi = calc_hapi() def calc_radis(): # %% Calculate with RADIS # ---------- pl = SpectrumFactory( wavenum_min=wmin, wavenum_max=wmax, mole_fraction=1, path_length=L, wstep=dnu, molecule=molecule, pressure=p, broadening_max_width=broadening_max_width, cutoff=1e-23, isotope=iso, ) pl.warnings["MissingSelfBroadeningWarning"] = "ignore" pl.warnings["HighTemperatureWarning"] = "ignore" pl.fetch_databank( source="hitran", load_energies=False, db_use_cached=True, ) s = pl.eq_spectrum(Tgas=T) # , Ttrans=300) s.name = "RADIS" if plot: pl.plot_broadening() return s s = calc_radis() # %% Compare # also shrinks HAPI range to the valid one s_hapi.resample(s.get_wavenumber(), unit="cm-1", energy_threshold=0.1) save = False # just used in the article if plot or save: from radis import plot_diff # title = '{0} bar, {1} K, {2} cm'.format(p, T, L) if save else None fig, [ax0, ax1] = plot_diff(s, s_hapi, var="transmittance_noslit", method="ratio", show=plot) ax0.annotate( r"[P64](00$^\mathregular{0}$0)$\rightarrow $(00$^\mathregular{0}$1)", (2286.945, 0.76), (2286.94, 0.8), arrowprops=dict(arrowstyle="->", facecolor="black"), ) ax0.annotate( r"[P53](01$^\mathregular{1}$0)$\rightarrow $(01$^\mathregular{1}$1)", (2286.9, 0.78), (2286.9, 0.82), arrowprops=dict(arrowstyle="->", facecolor="black"), horizontalalignment="right", ) ax1.set_ylim(0.95, 1.05) if save: fig.savefig("out/test_RADIS_vs_HAPI_line_broadening.pdf") # Compare integrals diff = abs( s.get_integral("transmittance_noslit") / s_hapi.get_integral("transmittance_noslit") - 1) b = diff < rtol if verbose: printm("Integral difference ({0:.2f}%) < {1:.2f}%: {2}".format( diff * 100, rtol * 100, b)) return b
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