def _notation_adf11_adas2cherab(rate_adas, filetype): """ Converts adas unit, charge and numeric notation to cherab notation :param rate_adas: Nested dictionary of shape rate_adas[element][charge][te, ne, rates] :param filetype: string denoting adas adf11 file type to decide whether charge conversion is to be applied. Will be applied for file types: "scd", "ccd", "plt", "pls" :return: nested dictionary with cherab rates and units notation """ # Charge correction will be applied if there is difference between adas and cherab charge notation if filetype in ["scd", "plt", "pls"]: charge_correction = int(-1) else: charge_correction = int(0) # adas units, charge and number notation to be changed to cherab notation rate_cherab = RecursiveDict() for i in rate_adas.keys(): for j in rate_adas[i].keys(): # convert from adas log10 in [cm**-3] notation to cherab [m**-3] electron density notation rate_cherab[i][j + charge_correction]["ne"] = PerCm3ToPerM3.to( 10**rate_adas[i][j]["ne"]) # convert from adas log10 to cherab electron temperature notation rate_cherab[i][j + charge_correction]["te"] = 10**rate_adas[i][j]["te"] rate_cherab[i][j + charge_correction]["rates"] = Cm3ToM3.to( 10**rate_adas[i][j]["rates"]) return rate_cherab
def install_adf11ccd(donor_element, donor_charge, receiver_element, file_path, download=False, repository_path=None, adas_path=None): """ Adds the thermal charge exchange rate defined in an ADF11 file to the repository. :param donor_element: Element donating the electron, for the case of ADF11 files it is neutral hydrogen. :param donor_charge: Charge of the donor atom/ion. :param receiver_element: Element receiving the electron. :param file_path: Path relative to ADAS root. :param download: Attempt to download file if not present (Default=True). :param repository_path: Path to the repository in which to install the rates (optional). :param adas_path: Path to ADAS files repository (optional). """ print('Installing {}...'.format(file_path)) path = _locate_adas_file(file_path, download, adas_path) if not path: raise ValueError('Could not locate the specified ADAS file.') # decode file and write out rates rate_adas = parse_adf11(receiver_element, path) rate_cherab = _notation_adf11_adas2cherab( rate_adas, "ccd") # convert from adas to cherab notation # reshape rate dictionary to match cherab convention rate_cherab_ccd = RecursiveDict() rate_cherab_ccd[donor_element][donor_charge] = rate_cherab repository.update_thermal_cx_rates(rate_cherab_ccd, repository_path)
def parse_adf21(beam_species, target_ion, target_charge, adf_file_path): """ Opens and parses ADAS ADF21 data files. :param beam_species: Element object describing the beam species. :param target_ion: Element object describing the target ion species. :param target_charge: Ionisation level of the target species. :param adf_file_path: Path to ADF15 file from ADAS root. :return: Dictionary containing rates. """ rate = RecursiveDict() with open(adf_file_path, 'r') as file: rate[beam_species][target_ion][target_charge] = parse_adas2x_rate(file) return rate
def parse_adf22bme(beam_species, target_ion, target_ionisation, transition, adf_file_path): """ Opens and parses ADAS ADF22 BME data files. :param beam_species: Element object describing the beam species. :param target_ion: Element object describing the target ion species. :param target_ionisation: Ionisation level of the target species. :param transition: Atomic transition tuple (upper level, lower level). :param adf_file_path: Path to ADF15 file from ADAS root. :return: Dictionary containing rates. """ rate = RecursiveDict() with open(adf_file_path, 'r') as file: rate[beam_species][target_ion][target_ionisation][transition] = parse_adas2x_rate(file) return rate
def _update_and_write_adf11(species, rate_data, path): # read in any existing rates try: with open(path, 'r') as f: content = RecursiveDict.from_dict(json.load(f)) except FileNotFoundError: content = RecursiveDict() for charge, rates in rate_data.items(): if not valid_charge(species, charge): raise ValueError( 'The charge state is larger than the number of protons in the specified species.' ) # sanitise and validate rate data te = np.array(rates['te'], np.float64) ne = np.array(rates['ne'], np.float64) rate_table = np.array(rates['rates'], np.float64) if ne.ndim != 1: raise ValueError('Density array must be a 1D array.') if te.ndim != 1: raise ValueError('Temperature array must be a 1D array.') if (ne.shape[0], te.shape[0]) != rate_table.shape: raise ValueError( 'Electron temperature, density and rate data arrays have inconsistent sizes.' ) # update file content with new rate content[charge] = { 'te': te.tolist(), 'ne': ne.tolist(), 'rate': rate_table.tolist(), } # create directory structure if missing directory = os.path.dirname(path) if not os.path.isdir(directory): os.makedirs(directory) # write new data with open(path, 'w') as f: json.dump(content, f, indent=2, sort_keys=True)
def parse_adf12(donor_ion, donor_metastable, receiver_ion, receiver_charge, adf_file_path): """ Opens and parses ADAS ADF12 data files. :param donor_ion: The donor ion element described by the rate file. :param donor_metastable: The donor ion metastable level. :param receiver_ion: The receiver ion element described by the rate file. :param receiver_charge: The receiver ion charge state described by the rate file. :param adf_file_path: Path to ADF15 file from ADAS root. :return: Dictionary containing rates. """ rates = RecursiveDict() with open(adf_file_path, 'r') as file: rate_count = int(file.readline()[3:5]) for i in range(rate_count): # parse block transition, rate = _parse_block(file) # add to repository update dictionary, converting density from cm^-3 to m^-3 rates[donor_ion][receiver_ion][receiver_charge][transition][ donor_metastable] = { 'eb': np.array(rate['ENER'], np.float64), 'ti': np.array(rate['TIEV'], np.float64), 'ni': PerCm3ToPerM3.to(np.array(rate['DENSI'], np.float64)), 'z': np.array(rate['ZEFF'], np.float64), 'b': np.array(rate['BMAG'], np.float64), 'qeb': Cm3ToM3.to(np.array(rate['QENER'], np.float64)), 'qti': Cm3ToM3.to(np.array(rate['QTIEV'], np.float64)), 'qni': Cm3ToM3.to(np.array(rate['QDENSI'], np.float64)), 'qz': Cm3ToM3.to(np.array(rate['QZEFF'], np.float64)), 'qb': Cm3ToM3.to(np.array(rate['QBMAG'], np.float64)), 'ebref': rate['EBREF'], 'tiref': rate['TIREF'], 'niref': PerCm3ToPerM3.to(rate['NIREF']), 'zref': rate['ZEREF'], 'bref': rate['BREF'], 'qref': Cm3ToM3.to(rate['QEFREF']) } return rates
def _scrape_metadata_hydrogen(file, element, charge): """ Scrapes transition and block information from the comments. """ config = RecursiveDict() # start parsing from the beginning file.seek(0) lines = file.readlines() pec_index_header_match = '^C\s*ISEL\s*WAVELENGTH\s*TRANSITION\s*TYPE' while not re.match(pec_index_header_match, lines[0], re.IGNORECASE): lines.pop(0) index_lines = lines for i in range(len(index_lines)): pec_hydrogen_transition_match = '^C\s*([0-9]*)\.\s*([0-9]*\.[0-9]*)\s*N=\s*([0-9]*) - N=\s*([0-9]*)\s*([A-Z]*)' match = re.match(pec_hydrogen_transition_match, index_lines[i], re.IGNORECASE) if not match: continue block_num = int(match.groups()[0]) wavelength = float(match.groups()[1]) / 10 # convert Angstroms to nm upper_level = int(match.groups()[2]) lower_level = int(match.groups()[3]) rate_type_adas = match.groups()[4] if rate_type_adas == 'EXCIT': rate_type = 'excitation' elif rate_type_adas == 'RECOM': rate_type = 'recombination' elif rate_type_adas == 'CHEXC': rate_type = 'cx_thermal' else: raise ValueError( "Unrecognised rate type - {}".format(rate_type_adas)) config[rate_type][element][charge][(upper_level, lower_level)] = block_num config["wavelength"][element][charge][(upper_level, lower_level)] = wavelength return config
def parse_adf15(element, charge, adf_file_path, header_format=None): """ Opens and parses ADAS ADF15 data files. :param element: Element described by ADF file. :param charge: Charge state described by ADF file. :param adf_file_path: Path to ADF15 file from ADAS root. :return: Dictionary containing rates. """ if not isinstance(element, Element): raise TypeError('The element must be an Element object.') charge = int(charge) with open(adf_file_path, "r") as file: # for check header line header = file.readline() if not re.match('^\s*(\d*) {4}/(.*)/?\s*$', header): raise ValueError( 'The specified path does not point to a valid ADF15 file.') # scrape transition information and wavelength # use simple electron configuration structure for hydrogen-like ions if header_format == 'hydrogen' or element == hydrogen: config = _scrape_metadata_hydrogen(file, element, charge) elif header_format == 'hydrogen-like' or element.atomic_number - charge == 1: config = _scrape_metadata_hydrogen_like(file, element, charge) else: config = _scrape_metadata_full(file, element, charge) # process rate data rates = RecursiveDict() for cls in ('excitation', 'recombination', 'thermalcx'): for element, charge_states in config[cls].items(): for charge, transitions in charge_states.items(): for transition in transitions.keys(): block_num = config[cls][element][charge][transition] rates[cls][element][charge][ transition] = _extract_rate(file, block_num) wavelengths = config['wavelength'] return rates, wavelengths
def parse_adf15(element, ionisation, adf_file_path): """ Opens and parses ADAS ADF15 data files. :param element: Element described by ADF file. :param ionisation: Ionisation described by ADF file. :param adf_file_path: Path to ADF15 file from ADAS root. :return: Dictionary containing rates. """ if not isinstance(element, Element): raise TypeError('The element must be an Element object.') ionisation = int(ionisation) with open(adf_file_path, "r") as file: # for check header line header = file.readline() if not re.match('^\s*(\d*) {4}/(.*)/?\s*$', header): raise ValueError( 'The specified path does not point to a valid ADF15 file.') # scrape transition information and wavelength config = _scrape_metadata(file, element, ionisation) # process rate data rates = RecursiveDict() for cls in ('excitation', 'recombination', 'thermalcx'): for element, ionisations in config[cls].items(): for ionisation, transitions in ionisations.items(): for transition in transitions.keys(): block_num = config[cls][element][ionisation][ transition] rates[cls][element][ionisation][ transition] = _extract_rate(file, block_num) wavelengths = config['wavelength'] return rates, wavelengths
def update_wavelengths(wavelengths, repository_path=None): repository_path = repository_path or DEFAULT_REPOSITORY_PATH for element, ionisations in wavelengths.items(): for ionisation, transitions in ionisations.items(): # sanitise and validate if not isinstance(element, Element): raise TypeError('The element must be an Element object.') if not valid_ionisation(element, ionisation): raise ValueError('Ionisation level is larger than the number of protons in the element.') path = os.path.join(repository_path, 'wavelength/{}/{}.json'.format(element.symbol.lower(), ionisation)) # read in any existing wavelengths try: with open(path, 'r') as f: content = RecursiveDict.from_dict(json.load(f)) except FileNotFoundError: content = RecursiveDict() # add/replace data for a transition for transition in transitions: key = encode_transition(transition) content[key] = float(wavelengths[element][ionisation][transition]) # create directory structure if missing directory = os.path.dirname(path) if not os.path.isdir(directory): os.makedirs(directory) # write new data with open(path, 'w') as f: json.dump(content, f, indent=2, sort_keys=True)
def add_thermal_cx_rate(donor_element, donor_charge, receiver_element, rate, repository_path=None): """ Adds a single thermal charge exchange rate to the repository. If adding multiple rates, consider using the update_recombination_rates() function instead. The update function avoids repeatedly opening and closing the rate files. :param donor_element: Element donating the electron. :param donor_charge: Charge of the donating atom/ion :param receiver_element: Element receiving the electron :param rate: rates :param repository_path: :return: """ rates2update = RecursiveDict() rates2update[donor_element][donor_charge][receiver_element] = rate update_thermal_cx_rates(rates2update, repository_path)
def update_pec_rates(rates, repository_path=None): """ PEC rate file structure /pec/<class>/<element>/<ionisation>.json """ valid_classes = ['excitation', 'recombination', 'thermalcx'] repository_path = repository_path or DEFAULT_REPOSITORY_PATH for cls, elements in rates.items(): for element, ionisations in elements.items(): for ionisation, transitions in ionisations.items(): # sanitise and validate cls = cls.lower() if cls not in valid_classes: raise ValueError( 'Unrecognised pec rate class \'{}\'.'.format(cls)) if not isinstance(element, Element): raise TypeError('The element must be an Element object.') if not valid_ionisation(element, ionisation): raise ValueError( 'Ionisation level is larger than the number of protons in the element.' ) path = os.path.join( repository_path, 'pec/{}/{}/{}.json'.format(cls, element.symbol.lower(), ionisation)) # read in any existing rates try: with open(path, 'r') as f: content = RecursiveDict.from_dict(json.load(f)) except FileNotFoundError: content = RecursiveDict() # add/replace data for a transition for transition in transitions: key = encode_transition(transition) data = rates[cls][element][ionisation][transition] # sanitise/validate data data['ne'] = np.array(data['ne'], np.float64) data['te'] = np.array(data['te'], np.float64) data['rate'] = np.array(data['rate'], np.float64) if data['ne'].ndim != 1: raise ValueError('Density array must be a 1D array.') if data['te'].ndim != 1: raise ValueError( 'Temperature array must be a 1D array.') if (data['ne'].shape[0], data['te'].shape[0]) != data['rate'].shape: raise ValueError( 'Density, temperature and rate data arrays have inconsistent sizes.' ) content[key] = { 'ne': data['ne'].tolist(), 'te': data['te'].tolist(), 'rate': data['rate'].tolist() } # create directory structure if missing directory = os.path.dirname(path) if not os.path.isdir(directory): os.makedirs(directory) # write new data with open(path, 'w') as f: json.dump(content, f, indent=2, sort_keys=True)
def update_beam_emission_rates(rates, repository_path=None): """ Beam emission rate file structure /beam/emission/<beam species>/<target ion>/<target_charge>.json File contains multiple rates, indexed by transition. """ repository_path = repository_path or DEFAULT_REPOSITORY_PATH for beam_species, target_ions in rates.items(): for target_ion, target_charge_states in target_ions.items(): for target_charge, transitions in target_charge_states.items(): # sanitise and validate arguments if not isinstance(beam_species, Element): raise TypeError( 'The beam_species must be an Element object.') if not isinstance(target_ion, Element): raise TypeError( 'The beam_species must be an Element object.') if not valid_charge(target_ion, target_charge): raise ValueError( 'Charge state is larger than the number of protons in the target ion.' ) path = os.path.join( repository_path, 'beam/emission/{}/{}/{}.json'.format( beam_species.symbol.lower(), target_ion.symbol.lower(), target_charge)) # read in any existing rates try: with open(path, 'r') as f: content = RecursiveDict.from_dict(json.load(f)) except FileNotFoundError: content = RecursiveDict() # add/replace data for a transition for transition in transitions: key = encode_transition(transition) rate = rates[beam_species][target_ion][target_charge][ transition] # sanitise and validate rate data e = np.array(rate['e'], np.float64) n = np.array(rate['n'], np.float64) t = np.array(rate['t'], np.float64) sen = np.array(rate['sen'], np.float64) st = np.array(rate['st'], np.float64) if e.ndim != 1: raise ValueError( 'Beam energy array must be a 1D array.') if n.ndim != 1: raise ValueError('Density array must be a 1D array.') if t.ndim != 1: raise ValueError( 'Temperature array must be a 1D array.') if (e.shape[0], n.shape[0]) != sen.shape: raise ValueError( 'Beam energy, density and combined rate data arrays have inconsistent sizes.' ) if t.shape != st.shape: raise ValueError( 'Temperature and temperature rate data arrays have inconsistent sizes.' ) # update file content with new rate content[key] = { 'e': e.tolist(), 'n': n.tolist(), 't': t.tolist(), 'sen': sen.tolist(), 'st': st.tolist(), 'eref': float(rate['eref']), 'nref': float(rate['nref']), 'tref': float(rate['tref']), 'sref': float(rate['sref']) } # create directory structure if missing directory = os.path.dirname(path) if not os.path.isdir(directory): os.makedirs(directory) # write new data with open(path, 'w') as f: json.dump(content, f, indent=2, sort_keys=True)
def update_beam_cx_rates(rates, repository_path=None): # organisation in repository: # beam/cx/donor_ion/receiver_ion/receiver_ionisation.json # inside json file: # transition: [list of donor_metastables with rates] def sanitise_and_validate(data, x_key, x_name, y_key, y_name): """ Sanitises and validates pairs of rate data arrays. Converts arrays to numpy arrays and check that the dimensions of the supplied data are consistent. Arrays are converted in place, the supplied dictionary is modified. :param data: Rate data dictionary. :param x_key: Key of independent variable data. :param x_name: Name of data array for error reporting. :param y_key: Key of dependent variable data. :param y_name: Name of data array for error reporting. """ # convert to numpy arrays data[x_key] = np.array(data[x_key], np.float64) data[y_key] = np.array(data[y_key], np.float64) # check dimensions if data[x_key].ndim != 1: raise ValueError('The {} array must be a 1D array.'.format(x_name)) if data[y_key].ndim != 1: raise ValueError('The {} array must be a 1D array.'.format(y_name)) if data[x_key].shape != data[y_key].shape: raise ValueError( 'The {} and {} arrays have inconsistent lengths.'.format( x_name, y_name)) repository_path = repository_path or DEFAULT_REPOSITORY_PATH for donor, receivers in rates.items(): for receiver, ionisations in receivers.items(): for ionisation, transitions in ionisations.items(): # sanitise and validate if not isinstance(donor, Element): raise TypeError('The element must be an Element object.') if not isinstance(receiver, Element): raise TypeError('The element must be an Element object.') if not valid_ionisation(receiver, ionisation): raise ValueError( 'Ionisation level is larger than the number of protons in the element.' ) path = os.path.join( repository_path, 'beam/cx/{}/{}/{}.json'.format(donor.symbol.lower(), receiver.symbol.lower(), ionisation)) # read in any existing rates try: with open(path, 'r') as f: content = RecursiveDict.from_dict(json.load(f)) except FileNotFoundError: content = RecursiveDict() # json keys are strings, must convert metastable key to integer for transition, metastables in content.items(): content[transition] = RecursiveDict({ int(metastable): rate for metastable, rate in metastables.items() }) # update content for transition, metastables in transitions.items(): # add/replace data for each metastable transition_key = encode_transition(transition) for metastable in metastables: if not metastable >= 0: raise ValueError( 'Donor metastable level cannot be less than zero.' ) data = rates[donor][receiver][ionisation][transition][ metastable] # sanitise/validate data data['qref'] = float(data['qref']) sanitise_and_validate(data, 'eb', 'beam energy', 'qeb', 'beam energy effective rate') sanitise_and_validate( data, 'ti', 'ion temperature', 'qti', 'ion temperature effective rate') sanitise_and_validate(data, 'ni', 'ion density', 'qni', 'ion density effective rate') sanitise_and_validate(data, 'z', 'Zeff', 'qz', 'Zeff effective rate') sanitise_and_validate( data, 'b', 'B-field magnitude', 'qb', 'B-field magnitude effective rate') content[transition_key][metastable] = { 'eb': data['eb'].tolist(), 'ti': data['ti'].tolist(), 'ni': data['ni'].tolist(), 'z': data['z'].tolist(), 'b': data['b'].tolist(), 'qref': data['qref'], 'qeb': data['qeb'].tolist(), 'qti': data['qti'].tolist(), 'qni': data['qni'].tolist(), 'qz': data['qz'].tolist(), 'qb': data['qb'].tolist(), } # create directory structure if missing directory = os.path.dirname(path) if not os.path.isdir(directory): os.makedirs(directory) # write new data with open(path, 'w') as f: json.dump(content, f, indent=2, sort_keys=True)
def _scrape_metadata(file, element, ionisation): """ Scrapes transition and block information from the comments. """ config = RecursiveDict() # start parsing from the beginning file.seek(0) lines = file.readlines() # Use simple electron configuration structure for hydrogen if element == hydrogen: pec_index_header_match = '^C\s*ISEL\s*WAVELENGTH\s*TRANSITION\s*TYPE' while not re.match(pec_index_header_match, lines[0], re.IGNORECASE): lines.pop(0) index_lines = lines for i in range(len(index_lines)): pec_hydrogen_transition_match = '^C\s*([0-9]*)\.\s*([0-9]*\.[0-9]*)\s*N=\s*([0-9]*) - N=\s*([0-9]*)\s*([A-Z]*)' match = re.match(pec_hydrogen_transition_match, index_lines[i], re.IGNORECASE) if not match: continue block_num = int(match.groups()[0]) wavelength = float( match.groups()[1]) / 10 # convert Angstroms to nm upper_level = int(match.groups()[2]) lower_level = int(match.groups()[3]) rate_type_adas = match.groups()[4] if rate_type_adas == 'EXCIT': rate_type = 'excitation' elif rate_type_adas == 'RECOM': rate_type = 'recombination' elif rate_type_adas == 'CHEXC': rate_type = 'cx_thermal' else: raise ValueError( "Unrecognised rate type - {}".format(rate_type_adas)) config[rate_type][element][ionisation][(upper_level, lower_level)] = block_num config["wavelength"][element][ionisation][( upper_level, lower_level)] = wavelength # Use simple electron configuration structure for hydrogen-like ions elif element.atomic_number - ionisation == 1: pec_index_header_match = '^C\s*ISEL\s*WAVELENGTH\s*TRANSITION\s*TYPE' while not re.match(pec_index_header_match, lines[0], re.IGNORECASE): lines.pop(0) index_lines = lines for i in range(len(index_lines)): pec_full_transition_match = '^C\s*([0-9]*)\.\s*([0-9]*\.[0-9]*)\s*([0-9]*)[\(\)\.0-9\s]*-\s*([0-9]*)[\(\)\.0-9\s]*([A-Z]*)' match = re.match(pec_full_transition_match, index_lines[i], re.IGNORECASE) if not match: continue block_num = int(match.groups()[0]) wavelength = float( match.groups()[1]) / 10 # convert Angstroms to nm upper_level = int(match.groups()[2]) lower_level = int(match.groups()[3]) rate_type_adas = match.groups()[4] if rate_type_adas == 'EXCIT': rate_type = 'excitation' elif rate_type_adas == 'RECOM': rate_type = 'recombination' elif rate_type_adas == 'CHEXC': rate_type = 'cx_thermal' else: raise ValueError( "Unrecognised rate type - {}".format(rate_type_adas)) config[rate_type][element][ionisation][(upper_level, lower_level)] = block_num config["wavelength"][element][ionisation][( upper_level, lower_level)] = wavelength # Use full electron configuration structure for anything else else: configuration_lines = [] configuration_dict = {} configuration_header_match = '^C\s*Configuration\s*\(2S\+1\)L\(w-1/2\)\s*Energy \(cm\*\*-1\)$' while not re.match(configuration_header_match, lines[0], re.IGNORECASE): lines.pop(0) pec_index_header_match = '^C\s*ISEL\s*WAVELENGTH\s*TRANSITION\s*TYPE' while not re.match(pec_index_header_match, lines[0], re.IGNORECASE): configuration_lines.append(lines[0]) lines.pop(0) index_lines = lines for i in range(len(configuration_lines)): configuration_string_match = "^C\s*([0-9]*)\s*((?:[0-9][SPDFG][0-9]\s)*)\s*\(([0-9]*\.?[0-9]*)\)([0-9]*)\(\s*([0-9]*\.?[0-9]*)\)" match = re.match(configuration_string_match, configuration_lines[i], re.IGNORECASE) if not match: continue config_id = int(match.groups()[0]) electron_configuration = match.groups()[1].rstrip().lower() spin_multiplicity = match.groups()[2] # (2S+1) total_orbital_quantum_number = _L_LOOKUP[int( match.groups()[3])] # L total_angular_momentum_quantum_number = match.groups()[4] # J configuration_dict[config_id] = ( electron_configuration + " " + spin_multiplicity + total_orbital_quantum_number + total_angular_momentum_quantum_number) for i in range(len(index_lines)): pec_full_transition_match = '^C\s*([0-9]*)\.?\s*([0-9]*\.[0-9]*)\s*([0-9]*)[\(\)\.0-9\s]*-\s*([0-9]*)[\(\)\.0-9\s]*([A-Z]*)' match = re.match(pec_full_transition_match, index_lines[i], re.IGNORECASE) if not match: continue block_num = int(match.groups()[0]) wavelength = float( match.groups()[1]) / 10 # convert Angstroms to nm upper_level_id = int(match.groups()[2]) upper_level = configuration_dict[upper_level_id] lower_level_id = int(match.groups()[3]) lower_level = configuration_dict[lower_level_id] rate_type_adas = match.groups()[4] if rate_type_adas == 'EXCIT': rate_type = 'excitation' elif rate_type_adas == 'RECOM': rate_type = 'recombination' elif rate_type_adas == 'CHEXC': rate_type = 'cx_thermal' else: raise ValueError( "Unrecognised rate type - {}".format(rate_type_adas)) config[rate_type][element][ionisation][(upper_level, lower_level)] = block_num config["wavelength"][element][ionisation][( upper_level, lower_level)] = wavelength return config
def populate(download=True, repository_path=None, adas_path=None): """ Populates the OpenADAS repository with a typical set of rates and wavelengths. If an ADAS file is not note found an attempt will be made to download the file from the OpenADAS website. This behaviour can be disabled by setting the download argument to False. :param download: Attempt to download the ADAS files if missing (default=True). :param repository_path: Alternate path for the OpenADAS repository (default=None). :param adas_path: Alternate path in which to search for ADAS files (default=None) . """ # install a common selection of open adas files rates = { # 'adf11plt': ( # (hydrogen, "adf11/plt12/plt12_h.dat"), # (helium, "adf11/plt96/plt96_he.dat"), # (lithium, "adf11/plt96/plt96_li.dat"), # (beryllium, "adf11/plt96/plt96_be.dat"), # (carbon, "adf11/plt96/plt96_c.dat"), # (nitrogen, "adf11/plt96/plt96_n.dat"), # (oxygen, "adf11/plt96/plt96_o.dat"), # (neon, "adf11/plt96/plt96_ne.dat"), # (argon, "adf11/plt40/plt40_ar.dat"), # (krypton, "adf11/plt89/plt89_kr.dat") # ), # 'adf11prb': ( # (hydrogen, "adf11/prb12/prb12_h.dat"), # (helium, "adf11/prb96/prb96_he.dat"), # (lithium, "adf11/prb96/prb96_li.dat"), # (beryllium, "adf11/prb96/prb96_be.dat"), # (carbon, "adf11/prb96/prb96_c.dat"), # (nitrogen, "adf11/prb96/prb96_n.dat"), # (oxygen, "adf11/prb96/prb96_o.dat"), # (neon, "adf11/prb96/prb96_ne.dat"), # (argon, "adf11/prb89/prb89_ar.dat"), # (krypton, "adf11/prb89/prb89_kr.dat") # ), 'adf12': ( # (donor, receiver, ionisation, donor_metastable, rate file) (hydrogen, 1, hydrogen, 1, 'adf12/qef93#h/qef93#h_h1.dat'), (hydrogen, 1, helium, 2, "adf12/qef93#h/qef93#h_he2.dat"), (hydrogen, 2, helium, 2, "adf12/qef97#h/qef97#h_en2_kvi#he2.dat"), (hydrogen, 1, beryllium, 4, "adf12/qef93#h/qef93#h_be4.dat"), (hydrogen, 2, beryllium, 4, "adf12/qef97#h/qef97#h_en2_kvi#be4.dat"), (hydrogen, 1, boron, 5, "adf12/qef93#h/qef93#h_b5.dat"), (hydrogen, 2, boron, 5, "adf12/qef97#h/qef97#h_en2_kvi#b5.dat"), (hydrogen, 1, carbon, 6, "adf12/qef93#h/qef93#h_c6.dat"), (hydrogen, 2, carbon, 6, "adf12/qef97#h/qef97#h_en2_kvi#c6.dat"), (hydrogen, 1, neon, 10, "adf12/qef93#h/qef93#h_ne10.dat"), (hydrogen, 2, neon, 10, "adf12/qef97#h/qef97#h_en2_kvi#ne10.dat") ), 'adf15': ( (hydrogen, 0, 'adf15/pec12#h/pec12#h_pju#h0.dat'), (helium, 0, 'adf15/pec96#he/pec96#he_pju#he0.dat'), (helium, 1, 'adf15/pec96#he/pec96#he_pju#he1.dat'), (beryllium, 0, 'adf15/pec96#be/pec96#be_pju#be0.dat'), (beryllium, 1, 'adf15/pec96#be/pec96#be_pju#be1.dat'), (beryllium, 2, 'adf15/pec96#be/pec96#be_pju#be2.dat'), (beryllium, 3, 'adf15/pec96#be/pec96#be_pju#be3.dat'), (carbon, 0, 'adf15/pec96#c/pec96#c_vsu#c0.dat'), (carbon, 1, 'adf15/pec96#c/pec96#c_vsu#c1.dat'), (carbon, 2, 'adf15/pec96#c/pec96#c_vsu#c2.dat'), # (neon, 0, 'adf15/pec96#ne/pec96#ne_pju#ne0.dat'), #TODO: OPENADAS DATA CORRUPT # (neon, 1, 'adf15/pec96#ne/pec96#ne_pju#ne1.dat'), #TODO: OPENADAS DATA CORRUPT (nitrogen, 0, 'adf15/pec96#n/pec96#n_vsu#n0.dat'), (nitrogen, 1, 'adf15/pec96#n/pec96#n_vsu#n1.dat'), # (nitrogen, 2, 'adf15/pec96#n/pec96#n_vsu#n2.dat'), #TODO: OPENADAS DATA CORRUPT ), 'adf21': ( # (beam_species, target_ion, target_ionisation, rate file) (hydrogen, hydrogen, 1, "adf21/bms97#h/bms97#h_h1.dat"), (hydrogen, helium, 2, "adf21/bms97#h/bms97#h_he2.dat"), (hydrogen, lithium, 3, "adf21/bms97#h/bms97#h_li3.dat"), (hydrogen, beryllium, 4, "adf21/bms97#h/bms97#h_be4.dat"), (hydrogen, boron, 5, "adf21/bms97#h/bms97#h_b5.dat"), (hydrogen, carbon, 6, "adf21/bms97#h/bms97#h_c6.dat"), (hydrogen, nitrogen, 7, "adf21/bms97#h/bms97#h_n7.dat"), (hydrogen, oxygen, 8, "adf21/bms97#h/bms97#h_o8.dat"), (hydrogen, fluorine, 9, "adf21/bms97#h/bms97#h_f9.dat"), (hydrogen, neon, 10, "adf21/bms97#h/bms97#h_ne10.dat"), ), 'adf22bmp': ( # (beam species, beam metastable, target ion, target ionisation, rate file) (hydrogen, 2, hydrogen, 1, "adf22/bmp97#h/bmp97#h_2_h1.dat"), (hydrogen, 3, hydrogen, 1, "adf22/bmp97#h/bmp97#h_3_h1.dat"), (hydrogen, 4, hydrogen, 1, "adf22/bmp97#h/bmp97#h_4_h1.dat"), (hydrogen, 2, helium, 2, "adf22/bmp97#h/bmp97#h_2_he2.dat"), (hydrogen, 3, helium, 2, "adf22/bmp97#h/bmp97#h_3_he2.dat"), (hydrogen, 4, helium, 2, "adf22/bmp97#h/bmp97#h_4_he2.dat"), (hydrogen, 2, lithium, 3, "adf22/bmp97#h/bmp97#h_2_li3.dat"), (hydrogen, 3, lithium, 3, "adf22/bmp97#h/bmp97#h_3_li3.dat"), (hydrogen, 4, lithium, 3, "adf22/bmp97#h/bmp97#h_4_li3.dat"), (hydrogen, 2, beryllium, 4, "adf22/bmp97#h/bmp97#h_2_be4.dat"), (hydrogen, 3, beryllium, 4, "adf22/bmp97#h/bmp97#h_3_be4.dat"), (hydrogen, 4, beryllium, 4, "adf22/bmp97#h/bmp97#h_4_be4.dat"), (hydrogen, 2, boron, 5, "adf22/bmp97#h/bmp97#h_2_b5.dat"), (hydrogen, 3, boron, 5, "adf22/bmp97#h/bmp97#h_3_b5.dat"), (hydrogen, 4, boron, 5, "adf22/bmp97#h/bmp97#h_4_b5.dat"), (hydrogen, 2, carbon, 6, "adf22/bmp97#h/bmp97#h_2_c6.dat"), (hydrogen, 3, carbon, 6, "adf22/bmp97#h/bmp97#h_3_c6.dat"), (hydrogen, 4, carbon, 6, "adf22/bmp97#h/bmp97#h_4_c6.dat"), (hydrogen, 2, nitrogen, 7, "adf22/bmp97#h/bmp97#h_2_n7.dat"), (hydrogen, 3, nitrogen, 7, "adf22/bmp97#h/bmp97#h_3_n7.dat"), (hydrogen, 4, nitrogen, 7, "adf22/bmp97#h/bmp97#h_4_n7.dat"), (hydrogen, 2, oxygen, 8, "adf22/bmp97#h/bmp97#h_2_o8.dat"), (hydrogen, 3, oxygen, 8, "adf22/bmp97#h/bmp97#h_3_o8.dat"), (hydrogen, 4, oxygen, 8, "adf22/bmp97#h/bmp97#h_4_o8.dat"), (hydrogen, 2, fluorine, 9, "adf22/bmp97#h/bmp97#h_2_f9.dat"), (hydrogen, 3, fluorine, 9, "adf22/bmp97#h/bmp97#h_3_f9.dat"), (hydrogen, 4, fluorine, 9, "adf22/bmp97#h/bmp97#h_4_f9.dat"), (hydrogen, 2, neon, 10, "adf22/bmp97#h/bmp97#h_2_ne10.dat"), (hydrogen, 3, neon, 10, "adf22/bmp97#h/bmp97#h_3_ne10.dat"), (hydrogen, 4, neon, 10, "adf22/bmp97#h/bmp97#h_4_ne10.dat"), ), 'adf22bme': ( # (beam species, target_ion, target_ionisation, (initial_level, final_level), rate file) (hydrogen, hydrogen, 1, (3, 2), "adf22/bme10#h/bme10#h_h1.dat"), (hydrogen, helium, 2, (3, 2), "adf22/bme97#h/bme97#h_he2.dat"), (hydrogen, lithium, 3, (3, 2), "adf22/bme97#h/bme97#h_li3.dat"), (hydrogen, beryllium, 4, (3, 2), "adf22/bme97#h/bme97#h_be4.dat"), (hydrogen, boron, 5, (3, 2), "adf22/bme97#h/bme97#h_b5.dat"), (hydrogen, carbon, 6, (3, 2), "adf22/bme97#h/bme97#h_c6.dat"), (hydrogen, nitrogen, 7, (3, 2), "adf22/bme97#h/bme97#h_n7.dat"), (hydrogen, oxygen, 8, (3, 2), "adf22/bme97#h/bme97#h_o8.dat"), (hydrogen, fluorine, 9, (3, 2), "adf22/bme97#h/bme97#h_f9.dat"), (hydrogen, neon, 10, (3, 2), "adf22/bme97#h/bme97#h_ne10.dat"), (hydrogen, argon, 18, (3, 2), "adf22/bme99#h/bme99#h_ar18.dat"), ) } # add common wavelengths to the repository wavelengths = RecursiveDict() # H0 wavelengths[hydrogen][0] = { (2, 1): 121.52, (3, 1): 102.53, (3, 2): 656.19, (4, 1): 97.21, (4, 2): 486.06, (4, 3): 1874.82, (5, 1): 94.93, (5, 2): 433.99, (5, 3): 1281.61, (5, 4): 4050.53, (6, 1): 93.74, (6, 2): 410.12, (6, 3): 1093.64, (6, 4): 2624.75, (6, 5): 7456.66, (7, 1): 93.04, (7, 2): 396.95, (7, 3): 1004.79, (7, 4): 2165.19, (7, 5): 4651.78, (7, 6): 12366.59, (8, 1): 92.58, (8, 2): 388.85, (8, 3): 954.45, (8, 4): 1944.26, (8, 5): 3738.95, (8, 6): 7499.27, (8, 7): 19053.71, (9, 1): 92.28, (9, 2): 383.49, (9, 3): 922.76, (9, 4): 1817.13, (9, 5): 3295.58, (9, 6): 5905.68, (9, 7): 11303.84, (9, 8): 27791.42, (10, 1): 92.06, (10, 2): 379.74, (10, 3): 901.35, (10, 4): 1735.94, (10, 5): 3037.9, (10, 6): 5126.46, (10, 7): 8756.3, (10, 8): 16202.13, (10, 9): 38853.14, (11, 1): 91.9, (11, 2): 377.01, (11, 3): 886.14, (11, 4): 1680.39, (11, 5): 2871.76, (11, 6): 4670.5, (11, 7): 7504.88, (11, 8): 12381.84, (11, 9): 22330.84, (11, 10): 52512.27, (12, 1): 91.77, (12, 2): 374.96, (12, 3): 874.92, (12, 4): 1640.47, (12, 5): 2757.09, (12, 6): 4374.58, (12, 7): 6769.08, (12, 8): 10498.98, (12, 9): 16873.36, (12, 10): 29826.65, (12, 11): 69042.22, } # todo: temporary - add more accurate values wavelengths[deuterium][0] = wavelengths[hydrogen][0] wavelengths[tritium][0] = wavelengths[hydrogen][0] # He1+ wavelengths[helium][1] = { (2, 1): 30.378, # 2p -> 1s (3, 1): 25.632, # 3p -> 1s (3, 2): 164.04, # 3d -> 2p (4, 2): 121.51, # 4d -> 2p (4, 3): 468.71, # 4f -> 3d (5, 3): 320.28, # 5f -> 3d (5, 4): 1012.65, # 5g -> 4f (6, 4): 656.20, # 6g -> 4f (6, 5): 1864.20, # 6h -> 5g (7, 5): 1162.53, # from ADAS comment, unknown source (7, 6): 3090.55 # from ADAS comment, unknown source } # Be3+ wavelengths[beryllium][3] = { (3, 1): 6.4065, # 3p -> 1s (3, 2): 41.002, # 3d -> 2p (4, 2): 30.373, # 4d -> 2p (4, 3): 117.16, # 4f -> 3d (5, 3): 80.092, # 5f -> 3d (5, 4): 253.14, # 5g -> 4f (6, 4): 164.03, # 6g -> 4f (6, 5): 466.01, # 6h -> 5g (7, 5): 290.62, # from ADAS comment, unknown source (7, 6): 772.62, # from ADAS comment, unknown source (8, 6): 468.53, # from ADAS comment, unknown source (8, 7): 1190.42 # from ADAS comment, unknown source } # B4+ wavelengths[boron][4] = { (3, 1): 4.0996, # 3p -> 1s (3, 2): 26.238, # 3d -> 2p (4, 2): 19.437, # 4d -> 2p (4, 3): 74.980, # 4f -> 3d (5, 3): 51.257, # 5f -> 3d (5, 4): 162.00, # 5g -> 4f (6, 4): 104.98, # 6g -> 4f (6, 5): 298.24, # 6h -> 5g (7, 5): 186.05, # 7h -> 5g (7, 6): 494.48, # 7i -> 6h (8, 6): 299.86, # 8i -> 6h (8, 7): 761.87, # 8k -> 7i (9, 7): 451.99, # 9k -> 7i (9, 8): 1111.25 # from ADAS comment, unknown source } # C5+ wavelengths[carbon][5] = { (4, 2): 13.496, # 4d -> 2p (4, 3): 52.067, # 4f -> 3d (5, 3): 35.594, # 5f -> 3d (5, 4): 112.50, # 5g -> 4f (6, 4): 72.900, # 6g -> 4f (6, 5): 207.11, # 6h -> 5g (7, 5): 129.20, # from ADAS comment, unknown source (7, 6): 343.38, # from ADAS comment, unknown source (8, 6): 208.23, # from ADAS comment, unknown source (8, 7): 529.07, # from ADAS comment, unknown source (9, 7): 313.87, # from ADAS comment, unknown source (9, 8): 771.69, # from ADAS comment, unknown source (10, 8): 449.89, # from ADAS comment, unknown source (10, 9): 1078.86 # from ADAS comment, unknown source } # Ne9+ wavelengths[neon][9] = { (6, 5): 74.54, # from ADAS comment, unknown source (7, 6): 123.64, # from ADAS comment, unknown source (8, 7): 190.50, # from ADAS comment, unknown source (9, 8): 277.79, # from ADAS comment, unknown source (10, 9): 388.37, # from ADAS comment, unknown source (11, 10): 524.92, # from ADAS comment, unknown source (12, 11): 690.16, # from ADAS comment, unknown source (13, 12): 886.83, # from ADAS comment, unknown source (6, 4): 26.24, # from ADAS comment, unknown source (7, 5): 46.51, # from ADAS comment, unknown source (8, 6): 74.98, # from ADAS comment, unknown source (9, 7): 113.02, # from ADAS comment, unknown source (10, 8): 162.00, # from ADAS comment, unknown source (11, 9): 223.22, # from ADAS comment, unknown source (12, 10): 298.15, # from ADAS comment, unknown source (13, 11): 388.12 # from ADAS comment, unknown source } install_files(rates, download=download, repository_path=repository_path, adas_path=adas_path) repository.update_wavelengths(wavelengths, repository_path=repository_path)
def parse_adf11(element, adf_file_path): """ Reads contents of open adas adf11 files :param element: Element described by ADF file. :param adf_file_path: Path to ADF11 file from ADAS root. :return: temperature, density, rates as numpy array """ if not isinstance(element, Element): raise TypeError('The element must be an Element object.') with open(adf_file_path, "r") as source_file: lines = source_file.readlines() # read file contents by lines tmp = re.split("\s{2,}", lines[0].strip()) # split into relevant variables # exctract variables z_nuclear = int(tmp[0]) n_densities = int(tmp[1]) n_temperatures = int(tmp[2]) z_min = int(tmp[3]) z_max = int(tmp[4]) element_name = tmp[5].strip('/').lower() projectname = tmp[6] if element.atomic_number != z_nuclear or element.name != element_name: raise ValueError( "The requested element '{}' does not match the element description in the" "specified ADF11 file, '{}'.".format(element.name, element_name)) # check if it is a resolved file if re.match("\s*[0-9]+", lines[3]): # is it unresolved? startsearch = 2 else: startsearch = 4 # skip vectors with info about resolved states # get temperature and density vectors for i in range(startsearch, len(lines)): if re.match("^\s*C{0}-{2,}", lines[i]): tmp = re.sub("\n*\s+", "\t", "".join( lines[startsearch:i]).strip()) # replace unwanted chars tmp = np.fromstring(tmp, sep="\t", dtype=float) # put into nunpy array densities = tmp[:n_densities] # read density values temperatures = tmp[n_densities:] # read temperature values startsearch = i break # process rate data rates = RecursiveDict() # get beginnig and end of requested rates data block and add it to xarray blockrates_start = None blockrates_stop = None for i in range(startsearch, len(lines)): if re.match("^\s*C*-{2,}", lines[i]): # is it a rates block header? # is it a first data block found? if not blockrates_start is None: blockrates_stop = i # end of the requested block rates_table = re.sub("\n*\s+", "\t", "".join( lines[blockrates_start:blockrates_stop]).strip() ) # replace unwanted chars rates_table = np.fromstring( rates_table, sep="\t", dtype=float).reshape( (n_temperatures, n_densities)) # transform into an array rates[element][ion_charge]['ne'] = densities rates[element][ion_charge]['te'] = temperatures rates[element][ion_charge]['rates'] = np.swapaxes( rates_table, 0, 1) # if end of data block beak the loop or reassign start of data block for next stage if re.match("^\s*C{1}-{2,}", lines[i]) or re.match("^\s*C{0,1}-{2,}", lines[i]) and \ re.match("^\s*C\n", lines[i + 1]): break z1_pos = re.search("Z1\s*=*\s*[0-9]+\s*", lines[i]).group() # get Z1 part ion_charge = int( re.sub("Z1[\s*=]", "", z1_pos)) # remove Z1 to avoid getting 1 later if not re.search("IGRD\s*=*\s*[0-9]+\s*", lines[i]) is None: # get the IGRD part igrd_pos = re.search("IGRD\s*=*\s*[0-9]+\s*", lines[i]).group() # get the IGRD part else: igrd_pos = "No spec" if not re.search("IPRT\s*=*\s*[0-9]+\s*", lines[i]) is None: iptr_pos = re.search("IPRT\s*=*\s*[0-9]+\s*", lines[i]).group() # get the IPRT part else: iptr_pos = "No spec" blockrates_start = i + 1 # if block start not known, check if we are at the right position return rates