def __init__(self, file_io, model, gas, dust, source, bg_source, dust_source, detector, server, parse_args): """Initialisation of the command options. Args: file_io: Handles file input/output and all necessary paths. model: Handles the model space including various quantities such as the density distribution. gas: Handles the gas species with its parameters. dust: Handles the dust composition with its parameters. source: Handles the radiation of a radiation source such as stars or interstellar radiation fields. bg_source: Handles the radiation of a background source. dust_source: Handles the radiation of the dust grains. detector: Handles the position and wavelength at which a detector observes parse_args : Provides all parameters chosen by user when executing PolarisTools. """ self.file_io = file_io self.model = model self.gas = gas self.dust = dust self.source = source self.bg_source = bg_source self.dust_source = dust_source self.detector = detector self.server = server self.parse_args = parse_args # Get math module from polaris_tools_modules.math import Math self.math = Math()
def __init__(self, basic_plot_routines, model, file_io, parse_args): """Initialisation of plot parameters. Args: model: Handles the model space including various quantities such as the density distribution. file_io: Handles file input/output and all necessary paths. parse_args : Provides all parameters chosen by user when executing PolarisTools. """ self.basic_plots = basic_plot_routines self.model = model self.file_io = file_io self.parse_args = parse_args # Get math module from polaris_tools_modules.math import Math self.math = Math() # Stokes parameter names dependent on an index self.stokes_parameter = { 0: 'I', 1: 'Q', 2: 'U', 3: 'V', }
def __init__(self, model, file_io, parse_args): """Initialisation of all usable options. Notes: To create your own stellar source, add its name to the dictionary and write a class with its options as a derived class of class StellarSource. Args: model: Handles the model space including various quantities such as the density distribution. file_io : Handles file input/output and all necessary paths. parse_args : Provides all parameters chosen by user when executing PolarisTools. """ self.model = model self.file_io = file_io self.parse_args = parse_args # Get math module self.math = Math() #: dict: Dictionary with all usable stellar sources self.sources_dict = { 'isrf': ISRF, 'mathis_isrf': MathisISRF, 't_tauri': TTauri, 'herbig_ae': HerbigAe, 'binary': BinaryStar, 'sun': Sun, 'laser': Laser, } update_sources_dict(self.sources_dict)
def __init__(self, file_io, parse_args): """Initialisation of the dust parameters. Args: file_io : Handles file input/output and all necessary paths. """ self.file_io = file_io self.parse_args = parse_args # Get math module from polaris_tools_modules.math import Math self.math = Math()
class CustomPlots: """This class contains modified plot routines for special purposes and images. """ def __init__(self, basic_plot_routines, model, file_io, parse_args): """Initialisation of plot parameters. Args: model: Handles the model space including various quantities such as the density distribution. file_io: Handles file input/output and all necessary paths. parse_args : Provides all parameters chosen by user when executing PolarisTools. """ self.basic_plots = basic_plot_routines self.model = model self.file_io = file_io self.parse_args = parse_args # Get math module from polaris_tools_modules.math import Math self.math = Math() # Stokes parameter names dependent on an index self.stokes_parameter = { 0: 'I', 1: 'Q', 2: 'U', 3: 'V', } def plot_1(self): """Plot line spectrum from POLARIS simulations. """ # Set data input to Jy to calculate the total flux self.file_io.cmap_unit = 'total' # Read spectrum data plot_data, header = self.file_io.read_spectrum( 'line_spectrum_species_0001_line_0001') # Create pdf file if show_plot is not chosen self.file_io.init_plot_output('line_spectrum_species_0001_line_0001') velocity = [] for vch in range(header['nr_channels']): # Get velocity of current channel velocity.append(1e-3 * self.math.get_velocity( vch, header['nr_channels'], header['max_velocity'])) for i_quantity in range(4): # Create Matplotlib figure plot = Plot(self.model, self.parse_args, xlabel=r'$\mathit{v}\ [\si{\kilo\metre\per\second}]$', ylabel=self.file_io.get_quantity_labels(i_quantity), extent=[velocity[0], velocity[-1], None, None], with_cbar=False) # Plot spectrum as line plot.plot_line(velocity, plot_data[i_quantity, :], marker='.') # Save figure to pdf file or print it on screen plot.save_figure(self.file_io)
def __init__(self, file_io, parse_args, model=None): """Initialisation of all usable options. Notes: To create your own dust composition, add its name to the dictionary and write a class with its options as a derived class of class Dust. Args: file_io : Handles file input/output and all necessary paths. parse_args : Provides all parameters chosen by user when executing PolarisTools. model: Handles the model space including various quantities such as the density distribution. """ self.file_io = file_io self.parse_args = parse_args self.model = model # Get math module self.math = Math() #: dict: Dictionary with all usable dust compositions self.dust_dict = { 'none': NoDust, 'graphite_oblate': GraphiteOblate, 'graphite_perpend': GraphitePerpend, 'graphite_parallel': GraphiteParallel, 'silicate_oblate': SilicateOblate, 'silicate': Silicate, 'mrn_oblate': MrnOblate, 'mrn': Mrn, 'CM20': CM20, 'aPyM5': aPyM5, 'aOlM5': aOlM5, 'themis': Themis, 'pah_neutral': PAH0, 'pah_ion': PAH1, } update_dust_dict(self.dust_dict)
def __init__(self, parse_args): """Initialisation of all usable options. Notes: To create your own model, add its name to the dictionary and write a class with its options as a derived class of class Model. Args: parse_args (ArgumentParser) : Provides all parameters chosen by user when executing PolarisTools. """ self.parse_args = parse_args # Get math module self.math = Math() # dict: Dictionary with all usable models self.model_dict = { 'default': Model, 'disk': Disk, 'sphere': Sphere, 'globule': BokGlobule, } update_model_dict(self.model_dict)
def __init__(self, file_io, parse_args, model=None): """Initialisation of all usable options. Notes: To create your own gas species, add its name to the dictionary and write a class with its options as a derived class of class Gas. Args: model: Handles the model space including various quantities such as the density distribution. file_io : Handles file input/output and all necessary paths. parse_args : Provides all parameters chosen by user when executing PolarisTools. """ self.file_io = file_io self.parse_args = parse_args self.model = model # Get math module self.math = Math() #: dict[str: int]: Index of different level population methods self.lvl_pop = { 'LTE': 1, 'FEP': 2, 'LVG': 3, } #: dict: Dictionary with all usable gas species self.gas_dict = { 'none': NoGas, 'CO': GasCO, '13CO': Gas13CO, 'C18O': GasC18O, 'HCO': GasHCO, 'SO': GasSO, 'OH': GasOH, 'CN': GasCN, 'H1': GasH1, } update_gas_dict(self.gas_dict)
def __init__(self, file_io, parse_args, parameter): """Initialisation of the dust creator parameters. Args: file_io : Handles file input/output and all necessary paths. parameter (dict): Dictionary with parameter to create dust grain catalog. """ self.file_io = file_io self.parse_args = parse_args self.parameter = parameter # Get math module from polaris_tools_modules.math import Math self.math = Math() # Elements to create scattering matrix self.elements = np.zeros(16) self.elements[0] = 1 # S11 self.elements[1] = 2 # S12 self.elements[2] = 0 # S13 self.elements[3] = 0 # S14 self.elements[4] = 2 # S21 self.elements[5] = 1 # S22 self.elements[6] = 0 # S23 self.elements[7] = 0 # S24 self.elements[8] = 0 # S31 self.elements[9] = 0 # S32 self.elements[10] = 3 # S33 self.elements[11] = 4 # S34 self.elements[12] = 0 # S41 self.elements[13] = 0 # S42 self.elements[14] = -4 # S43 self.elements[15] = 3 # S44 self.nr_matrix_elements = 4
class CmdPolaris: """This class creates ".cmd" files which can be used to execute POLARIS. """ def __init__(self, file_io, model, gas, dust, source, bg_source, dust_source, detector, server, parse_args): """Initialisation of the command options. Args: file_io: Handles file input/output and all necessary paths. model: Handles the model space including various quantities such as the density distribution. gas: Handles the gas species with its parameters. dust: Handles the dust composition with its parameters. source: Handles the radiation of a radiation source such as stars or interstellar radiation fields. bg_source: Handles the radiation of a background source. dust_source: Handles the radiation of the dust grains. detector: Handles the position and wavelength at which a detector observes parse_args : Provides all parameters chosen by user when executing PolarisTools. """ self.file_io = file_io self.model = model self.gas = gas self.dust = dust self.source = source self.bg_source = bg_source self.dust_source = dust_source self.detector = detector self.server = server self.parse_args = parse_args # Get math module from polaris_tools_modules.math import Math self.math = Math() def write_common_part(self, cmd_file): """Writes commands into the command file. Args: cmd_file: Instance of the command file. """ # Overwrite default values with user input if self.parse_args.mass_fraction is not None: mass_fraction = self.parse_args.mass_fraction else: mass_fraction = self.model.parameter['mass_fraction'] if self.parse_args.scattering is not None: scattering = self.parse_args.scattering else: scattering = self.dust.parameter['scattering'] if self.parse_args.midplane_points is not None: midplane_points = self.parse_args.midplane_points else: midplane_points = self.model.parameter['midplane_points'] if self.parse_args.midplane_zoom is not None: midplane_zoom = self.parse_args.midplane_zoom else: midplane_zoom = self.model.parameter['midplane_zoom'] if self.parse_args.mu is not None: mu = self.parse_args.mu else: mu = self.math.const['avg_gas_mass'] if self.parse_args.nr_threads is not None: nr_threads = self.parse_args.nr_threads else: nr_threads = -1 cmd_file.write('<common>\n') cmd_file.write(self.dust.get_command()) cmd_file.write('\n') if midplane_points is not None: cmd_file.write('\t<write_inp_midplanes>\t' + str(midplane_points) + '\n') cmd_file.write('\t<write_out_midplanes>\t' + str(midplane_points) + '\n') if self.parse_args.midplane_3d_param is not None: if 0 <= len(self.parse_args.midplane_3d_param) <= 4 and \ 1 <= int(self.parse_args.midplane_3d_param[0]) <= 3: cmd_file.write('\t<write_3d_midplanes>') for i in range(len(self.parse_args.midplane_3d_param)): if 1 < i < 4: cmd_file.write( '\t' + str(self.math.parse(self.parse_args.midplane_3d_param[i], 'length'))) else: cmd_file.write( '\t' + str(self.parse_args.midplane_3d_param[i])) cmd_file.write('\n') if midplane_zoom is not None: cmd_file.write('\t<midplane_zoom>\t\t' + str(midplane_zoom) + '\n') if self.parse_args.midplane_rad_field: cmd_file.write('\t<write_radiation_field>\t1\n') cmd_file.write('\n') cmd_file.write('\t<mass_fraction>\t\t' + str(mass_fraction) + '\n') cmd_file.write('\t<mu>\t\t\t' + str(mu) + '\n') cmd_file.write('\n') cmd_file.write('\t<phase_function>\tPH_' + str(scattering) + '\n') cmd_file.write('\n') if self.parse_args.healpix_orientation is not None: cmd_file.write('\t<healpix_orientation>\t' + str(self.parse_args.healpix_orientation) + '\n') cmd_file.write('\n') cmd_file.write('\t<nr_threads>\t\t' + str(nr_threads) + '\n') cmd_file.write('</common>\n') cmd_file.write('\n') def write_temp_part(self, cmd_file, add_rat=False): """Writes commands for temperature calculation to the command file. Args: add_rat (bool): Also calculate radiative torques? """ # Overwrite default values with user input conv_dens, conv_len, conv_mag, conv_vel = self.get_conv_factors() cmd_file.write('<task> 1\n') if self.source is not None and self.source.parameter['nr_photons'] > 0: cmd_file.write(self.source.get_command()) cmd_file.write('\n') if add_rat: cmd_file.write('\t<cmd>\t\t\tCMD_TEMP_RAT\n') else: cmd_file.write('\t<cmd>\t\t\tCMD_TEMP\n') cmd_file.write('\n') if self.parse_args.grid_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_filename cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif self.parse_args.grid_cgs_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_cgs_filename cmd_file.write('\t<path_grid_cgs>\t\t"' + grid_path + '"\n') else: raise ValueError('No input grid defined/found!') cmd_file.write('\t<path_out>\t\t"' + self.file_io.path['simulation_type'] + '"\n') cmd_file.write('\n') cmd_file.write('\t<dust_offset>\t\t' + str(int(self.parse_args.dust_offset)) + '\n') cmd_file.write('\n') if self.parse_args.adj_tgas is not None: cmd_file.write('\t<adj_tgas>\t\t' + str(self.parse_args.adj_tgas) + '\n') if self.parse_args.sub_dust: cmd_file.write('\t<sub_dust>\t1\n') if self.parse_args.full_dust_temp: cmd_file.write('\t<full_dust_temp>\t1\n') if self.parse_args.radiation_field: cmd_file.write('\t<radiation_field>\t1\n') if self.parse_args.temp_a_max is not None: cmd_file.write('\t<stochastic_heating>\t' + str(self.parse_args.temp_a_max) + '\n') cmd_file.write('\n') cmd_file.write('\t<conv_dens>\t\t' + str(conv_dens) + '\n') cmd_file.write('\t<conv_len>\t\t' + str(conv_len) + '\n') cmd_file.write('\t<conv_mag>\t\t' + str(conv_mag) + '\n') cmd_file.write('\t<conv_vel>\t\t' + str(conv_vel) + '\n') cmd_file.write('</task>\n') cmd_file.write('\n') def write_rat_part(self, cmd_file): """Writes commands for radiative torque calculation to the command file. Args: cmd_file: Instance of the command file. """ # Overwrite default values with user input conv_dens, conv_len, conv_mag, conv_vel = self.get_conv_factors() cmd_file.write('<task> 1\n') if self.source is not None and self.source.parameter['nr_photons'] > 0: cmd_file.write(self.source.get_command()) cmd_file.write('\n') cmd_file.write('\t<cmd>\t\t\tCMD_RAT\n') cmd_file.write('\n') if self.parse_args.grid_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_filename cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif self.parse_args.grid_cgs_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_cgs_filename cmd_file.write('\t<path_grid_cgs>\t\t"' + grid_path + '"\n') elif os.path.isfile(self.file_io.path['simulation'] + 'temp/grid_temp.dat'): grid_path = self.file_io.path['simulation'] + 'temp/grid_temp.dat' cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif os.path.isfile(self.file_io.path['simulation'] + 'temp_rat/grid_temp.dat'): grid_path = self.file_io.path['simulation'] + \ 'temp_rat/grid_temp.dat' cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') else: raise ValueError('No input grid defined/found!') cmd_file.write('\t<path_out>\t\t"' + self.file_io.path['simulation_type'] + '"\n') cmd_file.write('\n') cmd_file.write('\t<conv_dens>\t\t' + str(conv_dens) + '\n') cmd_file.write('\t<conv_len>\t\t' + str(conv_len) + '\n') cmd_file.write('\t<conv_mag>\t\t' + str(conv_mag) + '\n') cmd_file.write('\t<conv_vel>\t\t' + str(conv_vel) + '\n') cmd_file.write('</task>\n') cmd_file.write('\n') def write_dust_mc_part(self, cmd_file): """Writes commands for Monte-Carlo radiative transfer to the command file. Args: cmd_file: Instance of the command file. """ # Overwrite default values with user input if self.parse_args.peel_off is not None: peel_off = self.parse_args.peel_off else: peel_off = self.model.parameter['peel_off'] if self.parse_args.enfsca is not None: enfsca = self.parse_args.enfsca else: enfsca = self.model.parameter['enforced_scattering'] if self.parse_args.rot_axis_1 is not None: rot_axis_1 = self.parse_args.rot_axis_1 else: rot_axis_1 = self.detector.parameter['rot_axis_1'] if self.parse_args.rot_axis_2 is not None: rot_axis_2 = self.parse_args.rot_axis_2 else: rot_axis_2 = self.detector.parameter['rot_axis_2'] conv_dens, conv_len, conv_mag, conv_vel = self.get_conv_factors() # Make rotation axis to unit length vectors if np.linalg.norm(rot_axis_1) != 1: rot_axis_1 /= np.linalg.norm(rot_axis_1) if np.linalg.norm(rot_axis_2) != 1: rot_axis_2 /= np.linalg.norm(rot_axis_2) cmd_file.write('<task> 1\n') cmd_file.write('\n') cmd_file.write(self.detector.get_dust_scattering_command()) cmd_file.write('\t<axis1>\t' + str(rot_axis_1[0]) + '\t' + str(rot_axis_1[1]) + '\t' + str(rot_axis_1[2]) + '\n') cmd_file.write('\t<axis2>\t' + str(rot_axis_2[0]) + '\t' + str(rot_axis_2[1]) + '\t' + str(rot_axis_2[2]) + '\n') cmd_file.write('\n') if self.source is not None and self.source.parameter['nr_photons'] > 0: cmd_file.write(self.source.get_command()) if self.dust_source.parameter['nr_photons'] > 0: cmd_file.write(self.dust_source.get_command()) cmd_file.write('\n') cmd_file.write('\n') cmd_file.write('\t<cmd>\t\t\tCMD_DUST_SCATTERING\n') cmd_file.write('\n') if self.parse_args.grid_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_filename cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif self.parse_args.grid_cgs_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_cgs_filename cmd_file.write('\t<path_grid_cgs>\t\t"' + grid_path + '"\n') elif os.path.isfile(self.file_io.path['simulation'] + 'temp/grid_temp.dat'): grid_path = self.file_io.path['simulation'] + 'temp/grid_temp.dat' cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif os.path.isfile(self.file_io.path['simulation'] + 'temp_rat/grid_temp.dat'): grid_path = self.file_io.path['simulation'] + \ 'temp_rat/grid_temp.dat' cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') else: raise ValueError('No input grid defined/found!') cmd_file.write('\t<path_out>\t\t"' + self.file_io.path['simulation_type'] + '"\n') cmd_file.write('\n') cmd_file.write('\t<peel_off>\t\t' + str(int(peel_off)) + '\n') cmd_file.write('\t<enfsca>\t\t' + str(int(enfsca)) + '\n') if not peel_off: cmd_file.write('\t<acceptance_angle>\t' + str(self.detector.parameter['acceptance_angle']) + '\n') cmd_file.write('\n') cmd_file.write('\t<conv_dens>\t\t' + str(conv_dens) + '\n') cmd_file.write('\t<conv_len>\t\t' + str(conv_len) + '\n') cmd_file.write('\t<conv_mag>\t\t' + str(conv_mag) + '\n') cmd_file.write('\t<conv_vel>\t\t' + str(conv_vel) + '\n') cmd_file.write('</task>\n') cmd_file.write('\n') def write_dust_part(self, cmd_file): """Writes commands for raytrace simulations to the command file. Args: cmd_file: Instance of the command file. """ # Overwrite default values with user input if self.parse_args.max_subpixel_lvl is not None: max_subpixel_lvl = self.parse_args.max_subpixel_lvl else: max_subpixel_lvl = self.detector.parameter['max_subpixel_lvl'] if self.parse_args.rot_axis_1 is not None: rot_axis_1 = self.parse_args.rot_axis_1 else: rot_axis_1 = self.detector.parameter['rot_axis_1'] if self.parse_args.rot_axis_2 is not None: rot_axis_2 = self.parse_args.rot_axis_2 else: rot_axis_2 = self.detector.parameter['rot_axis_2'] conv_dens, conv_len, conv_mag, conv_vel = self.get_conv_factors() # Make rotation axis to unit length vectors if np.linalg.norm(rot_axis_1) != 1: rot_axis_1 /= np.linalg.norm(rot_axis_1) if np.linalg.norm(rot_axis_2) != 1: rot_axis_2 /= np.linalg.norm(rot_axis_2) cmd_file.write('<task> 1\n') cmd_file.write('\n') cmd_file.write(self.detector.get_dust_emission_command()) cmd_file.write('\t<axis1>\t' + str(rot_axis_1[0]) + '\t' + str(rot_axis_1[1]) + '\t' + str(rot_axis_1[2]) + '\n') cmd_file.write('\t<axis2>\t' + str(rot_axis_2[0]) + '\t' + str(rot_axis_2[1]) + '\t' + str(rot_axis_2[2]) + '\n') cmd_file.write('\n') if self.source is not None and self.source.parameter['nr_photons'] > 0: cmd_file.write(self.source.get_command()) cmd_file.write('\n') cmd_file.write(self.bg_source.get_command()) cmd_file.write('\n') cmd_file.write('\t<cmd>\t\t\tCMD_DUST_EMISSION\n') if self.parse_args.grid_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_filename cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif self.parse_args.grid_cgs_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_cgs_filename cmd_file.write('\t<path_grid_cgs>\t\t"' + grid_path + '"\n') elif os.path.isfile(self.file_io.path['simulation'] + 'rat/grid_rat.dat'): grid_path = self.file_io.path['simulation'] + 'rat/grid_rat.dat' cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif os.path.isfile(self.file_io.path['simulation'] + 'temp/grid_temp.dat'): grid_path = self.file_io.path['simulation'] + 'temp/grid_temp.dat' cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif os.path.isfile(self.file_io.path['simulation'] + 'temp_rat/grid_temp.dat'): grid_path = self.file_io.path['simulation'] + \ 'temp_rat/grid_temp.dat' cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') else: raise ValueError('No input grid defined/found!') cmd_file.write('\t<path_out>\t\t"' + self.file_io.path['simulation_type'] + '"\n') if 'dust_' in self.parse_args.simulation_type and \ self.parse_args.simulation_type not in ['dust_mc', 'dust_full']: for align in self.parse_args.simulation_type.replace('dust_', '').split('_'): cmd_file.write('\t<align>\t\t\tALIG_' + str(align).upper() + '\n') cmd_file.write('\n') cmd_file.write('\t<max_subpixel_lvl>\t' + str(max_subpixel_lvl) + '\n') if self.parse_args.f_highj is not None: cmd_file.write('\t<f_highJ>\t\t' + str(self.parse_args.f_highj) + '\n') if self.parse_args.f_c is not None: cmd_file.write('\t<f_c>\t\t\t' + str(self.parse_args.f_c) + '\n') cmd_file.write('\n') if self.parse_args.no_rt_scattering: cmd_file.write('\t<rt_scattering>\t0\n') cmd_file.write('\n') elif self.dust_source.parameter['nr_photons'] > 0: cmd_file.write(self.dust_source.get_command()) cmd_file.write('\n') if self.parse_args.temp_a_max is not None: cmd_file.write('\t<stochastic_heating>\t' + str(self.parse_args.temp_a_max) + '\n') cmd_file.write('\n') if self.parse_args.split_dust_emission is not None: cmd_file.write('\t<split_dust_emission>\t' + str(int(self.parse_args.split_dust_emission)) + '\n') cmd_file.write('\n') cmd_file.write('\t<conv_dens>\t\t' + str(conv_dens) + '\n') cmd_file.write('\t<conv_len>\t\t' + str(conv_len) + '\n') cmd_file.write('\t<conv_mag>\t\t' + str(conv_mag) + '\n') cmd_file.write('\t<conv_vel>\t\t' + str(conv_vel) + '\n') cmd_file.write('</task>\n') cmd_file.write('\n') def write_line_part(self, cmd_file): """Writes commands for the radiative linetransfer to the command file. Args: cmd_file: Instance of the command file. """ # Overwrite default values with user input if self.parse_args.turbulent_velocity is not None: turbulent_velocity = self.math.parse( self.parse_args.turbulent_velocity, 'velocity') else: turbulent_velocity = self.model.parameter['turbulent_velocity'] if self.parse_args.max_subpixel_lvl is not None: max_subpixel_lvl = self.parse_args.max_subpixel_lvl else: max_subpixel_lvl = self.detector.parameter['max_subpixel_lvl'] if self.parse_args.rot_axis_1 is not None: rot_axis_1 = self.parse_args.rot_axis_1 else: rot_axis_1 = self.detector.parameter['rot_axis_1'] if self.parse_args.rot_axis_2 is not None: rot_axis_2 = self.parse_args.rot_axis_2 else: rot_axis_2 = self.detector.parameter['rot_axis_2'] conv_dens, conv_len, conv_mag, conv_vel = self.get_conv_factors() # Make rotation axis to unit length vectors if np.linalg.norm(rot_axis_1) != 1: rot_axis_1 /= np.linalg.norm(rot_axis_1) if np.linalg.norm(rot_axis_2) != 1: rot_axis_2 /= np.linalg.norm(rot_axis_2) cmd_file.write('<task> 1\n') cmd_file.write(self.gas.get_command()) cmd_file.write('\n') cmd_file.write(self.bg_source.get_command()) cmd_file.write('\n') cmd_file.write(self.detector.get_line_command()) cmd_file.write('\t<axis1>\t' + str(rot_axis_1[0]) + '\t' + str(rot_axis_1[1]) + '\t' + str(rot_axis_1[2]) + '\n') cmd_file.write('\t<axis2>\t' + str(rot_axis_2[0]) + '\t' + str(rot_axis_2[1]) + '\t' + str(rot_axis_2[2]) + '\n') cmd_file.write('\n') cmd_file.write('\t<cmd>\t\tCMD_LINE_EMISSION\n') cmd_file.write('\n') if self.parse_args.grid_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_filename cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif self.parse_args.grid_cgs_filename is not None: grid_path = self.file_io.path['model'] + \ self.parse_args.grid_cgs_filename cmd_file.write('\t<path_grid_cgs>\t\t"' + grid_path + '"\n') elif os.path.isfile(self.file_io.path['simulation'] + 'temp/grid_temp.dat'): grid_path = self.file_io.path['simulation'] + 'temp/grid_temp.dat' cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') elif os.path.isfile(self.file_io.path['simulation'] + 'temp_rat/grid_temp.dat'): grid_path = self.file_io.path['simulation'] + \ 'temp_rat/grid_temp.dat' cmd_file.write('\t<path_grid>\t\t"' + grid_path + '"\n') else: raise ValueError('No input grid defined/found!') cmd_file.write('\t<path_out>\t\t"' + self.file_io.path['simulation_type'] + '"\n') cmd_file.write('\n') if self.parse_args.no_vel_maps: cmd_file.write('\t<vel_maps>\t\t0\n') else: cmd_file.write('\t<vel_maps>\t\t1\n') if self.parse_args.kepler and self.source.parameter['kepler_usable']: cmd_file.write( '\t<kepler_star_mass>\t' + str(self.source.parameter['mass'] / self.math.const['M_sun']) + '\n') elif self.model.parameter['vel_is_speed_of_sound']: cmd_file.write('\t<vel_is_speed_of_sound>\t1\n') cmd_file.write('\t<max_subpixel_lvl>\t' + str(max_subpixel_lvl) + '\n') cmd_file.write( '\t<turbulent_velocity>\t' + str(turbulent_velocity) + '\n') cmd_file.write('\n') cmd_file.write('\t<conv_dens>\t\t' + str(conv_dens) + '\n') cmd_file.write('\t<conv_len>\t\t' + str(conv_len) + '\n') cmd_file.write('\t<conv_mag>\t\t' + str(conv_mag) + '\n') cmd_file.write('\t<conv_vel>\t\t' + str(conv_vel) + '\n') cmd_file.write('</task>\n') cmd_file.write('\n') def write_run_file(self, run_file): """Writes a run file to execute POLARIS directly or remotely on a server. Putting on a queue is also possible. Args: run_file: Instance of the run bash script file. """ self.server.check_for_walltime() run_file.write('#!/bin/bash\n') run_file.write(self.server.get_command()) # For MAC PCs with newest OS (they are not allowing the change of the LD library path in .bashrc) if 'darwin' in sys.platform: run_file.write('export LD_LIBRARY_PATH="' + self.file_io.path['polaris'] + 'CCfits/.libs/:' + self.file_io.path['polaris'] + 'cfitsio:${LD_LIBRARY_PATH}"\n') run_file.write('export DYLD_LIBRARY_PATH="' + self.file_io.path['polaris'] + 'CCfits/.libs/:' + self.file_io.path['polaris'] + 'cfitsio:${DYLD_LIBRARY_PATH}"\n') if self.parse_args.save_output: run_file.write(self.file_io.path['bin'] + 'polaris ' + self.file_io.path['simulation_type'] + 'POLARIS.cmd ' + '| tee ' + self.file_io.path['simulation_type'] + 'POLARIS.out\n') run_file.write(r"sed -i 's/\r$//; s/\r/\r\n/g; /\r/d' " + self.file_io.path['simulation_type'] + 'POLARIS.out\n') else: run_file.write(self.file_io.path['bin'] + 'polaris ' + self.file_io.path['simulation_type'] + 'POLARIS.cmd' + '\n') run_file.close() def execute_run_file(self): """Execute POLARIS with the command file. """ os.system('chmod +x ' + self.file_io.path['simulation'] + 'run.sh') os.system('sh ' + self.file_io.path['simulation'] + 'run.sh') def queue_run_file(self): """Push POLARIS run file to a server queue. """ os.system('chmod +x ' + self.file_io.path['simulation'] + 'run.sh') if self.server.parameter['queue_system'] == 'SBATCH': os.system('sbatch ' + self.file_io.path['simulation'] + 'run.sh') elif self.server.parameter['queue_system'] == 'PBS': os.system('qsub ' + self.file_io.path['simulation'] + 'run.sh') else: raise AttributeError('Queuing system not known!') def get_conv_factors(self): """Get the right conversion factors. """ if self.parse_args.conv_dens is not None: conv_dens = self.parse_args.conv_dens elif self.parse_args.grid_filename is not None and \ self.parse_args.grid_cgs_filename is not None: conv_dens = self.model.conv_parameter['conv_dens'] else: conv_dens = 1 if self.parse_args.conv_len is not None: conv_len = self.parse_args.conv_len elif self.parse_args.grid_filename is not None and \ self.parse_args.grid_cgs_filename is not None: conv_len = self.model.conv_parameter['conv_len'] else: conv_len = 1 if self.parse_args.conv_mag is not None: conv_mag = self.parse_args.conv_mag elif self.parse_args.grid_filename is not None and \ self.parse_args.grid_cgs_filename is not None: conv_mag = self.model.conv_parameter['conv_mag'] else: conv_mag = 1 if self.parse_args.conv_vel is not None: conv_vel = self.parse_args.conv_vel elif self.parse_args.grid_filename is not None and \ self.parse_args.grid_cgs_filename is not None: conv_vel = self.model.conv_parameter['conv_vel'] else: conv_vel = 1 return conv_dens, conv_len, conv_mag, conv_vel
class DustChooser: """The DustChooser class provides the composition of the chosen dust species. """ def __init__(self, file_io, parse_args, model=None): """Initialisation of all usable options. Notes: To create your own dust composition, add its name to the dictionary and write a class with its options as a derived class of class Dust. Args: file_io : Handles file input/output and all necessary paths. parse_args : Provides all parameters chosen by user when executing PolarisTools. model: Handles the model space including various quantities such as the density distribution. """ self.file_io = file_io self.parse_args = parse_args self.model = model # Get math module self.math = Math() #: dict: Dictionary with all usable dust compositions self.dust_dict = { 'none': NoDust, 'graphite_oblate': GraphiteOblate, 'graphite_perpend': GraphitePerpend, 'graphite_parallel': GraphiteParallel, 'silicate_oblate': SilicateOblate, 'silicate': Silicate, 'mrn_oblate': MrnOblate, 'mrn': Mrn, 'CM20': CM20, 'aPyM5': aPyM5, 'aOlM5': aOlM5, 'themis': Themis, 'pah_neutral': PAH0, 'pah_ion': PAH1, } update_dust_dict(self.dust_dict) def get_module(self): """Chooses dust class from user input Note: Parameters set by PolarisTools overwrite preset values in the separate dust classes. Returns: Instance of chosen dust composition. """ if self.parse_args.dust_composition in self.dust_dict.keys(): dust_composition = self.parse_args.dust_composition elif self.parse_args.dust_composition is None: if self.parse_args.simulation_type in ['line', 'zeeman']: dust_composition = 'none' elif self.model is not None and self.model.parameter[ 'dust_composition'] is not None: dust_composition = self.model.parameter['dust_composition'] else: dust_composition = 'none' else: raise ValueError( 'dust component not known! You can add a new component in dust.py' ) dust = self.dust_dict[dust_composition](self.file_io, self.parse_args) # Overwrite preset parameters from user input self.update_user_parameters(dust) return dust def get_module_from_name(self, dust_name, user_input=True): """Chooses dust class from user input Args: user_input (bool): Update parameters with user input? Note: Parameters set by PolarisTools overwrite preset values in the separate dust classes. Returns: Instance of chosen dust composition. """ if dust_name not in self.dust_dict.keys(): raise ValueError( 'dust component not known! You can add a new component in dust.py' ) dust = self.dust_dict[dust_name](self.file_io, self.parse_args) # Overwrite preset parameters from user input if user_input: self.update_user_parameters(dust) return dust def update_user_parameters(self, dust): """Overwrite preset parameters from user input. Args: dust : Instance of chosen dust composition. """ if self.parse_args.dust_size is not None: dust.parameter['amin'] = self.math.parse( self.parse_args.dust_size[0], 'length') dust.parameter['amax'] = self.math.parse( self.parse_args.dust_size[1], 'length') if self.parse_args.dust_size_distribution is not None: if len(self.parse_args.dust_size_distribution) < 2: raise ValueError('Defined dust size distribution not usable!') else: dust.parameter[ 'size_keyword'] = self.parse_args.dust_size_distribution[0] if 'plaw' in dust.parameter['size_keyword']: min_len = 1 if '-ed' in dust.parameter['size_keyword']: min_len += 3 if '-cv' in dust.parameter['size_keyword']: min_len += 3 elif 'logn' in dust.parameter['size_keyword']: min_len = 2 else: min_len = 0 if min_len != len(self.parse_args.dust_size_distribution) - 1: raise ValueError( 'Defined dust size distribution not usable!') else: dust.parameter['size_parameter'] = [] for i in range(min_len): dust.parameter['size_parameter'].append( self.math.parse( self.parse_args.dust_size_distribution[i + 1], 'length'))
class SourceChooser: """The StellarSourceChooser class provides the chosen stellar source such as a single star in the centre of the model space. """ def __init__(self, model, file_io, parse_args): """Initialisation of all usable options. Notes: To create your own stellar source, add its name to the dictionary and write a class with its options as a derived class of class StellarSource. Args: model: Handles the model space including various quantities such as the density distribution. file_io : Handles file input/output and all necessary paths. parse_args : Provides all parameters chosen by user when executing PolarisTools. """ self.model = model self.file_io = file_io self.parse_args = parse_args # Get math module self.math = Math() #: dict: Dictionary with all usable stellar sources self.sources_dict = { 'isrf': ISRF, 'mathis_isrf': MathisISRF, 't_tauri': TTauri, 'herbig_ae': HerbigAe, 'binary': BinaryStar, 'sun': Sun, 'laser': Laser, } update_sources_dict(self.sources_dict) def get_module(self): """Chooses radiation source from user input Note: Parameters set by PolarisTools overwrite preset values in the separate radiation source classes. Returns: Instance of chosen stellar source. """ if self.parse_args.radiation_source in self.sources_dict.keys(): source_name = self.parse_args.radiation_source elif self.parse_args.radiation_source is None: if self.model is not None and self.model.parameter[ 'radiation_source'] is not None: source_name = self.model.parameter['radiation_source'] else: return None else: raise ValueError( 'stellar source not known! You can add a new source in source.py' ) radiation_source = self.sources_dict[source_name](self.file_io, self.parse_args) # Overwrite default values with user input radiation_source.update_parameter( self.parse_args.rad_source_extra_parameter) if self.parse_args.nr_photons is not None: radiation_source.parameter['nr_photons'] = int( self.parse_args.nr_photons) if self.parse_args.rad_source_position is not None: radiation_source.parameter['position'] = [ self.math.parse(self.parse_args.rad_source_position[i], 'length') for i in range(3) ] if self.parse_args.rad_source_direction is not None: radiation_source.parameter['direction'] = [ self.math.parse(self.parse_args.rad_source_direction[i], 'length') for i in range(3) ] if self.parse_args.rad_source_temperature is not None: radiation_source.parameter[ 'temperature'] = self.parse_args.rad_source_temperature if self.parse_args.rad_source_luminosity is not None: radiation_source.parameter['luminosity'] = self.math.parse( self.parse_args.rad_source_luminosity, 'luminosity') radiation_source.parameter['radius'] = 0. elif self.parse_args.rad_source_radius is not None: radiation_source.parameter['radius'] = self.math.parse( self.parse_args.rad_source_radius, 'length') if self.parse_args.rad_source_power is not None: radiation_source.parameter['power'] = self.math.parse( self.parse_args.rad_source_power, 'luminosity') if self.parse_args.rad_source_center_wl is not None: radiation_source.parameter['center_wl'] = self.math.parse( self.parse_args.rad_source_center_wl, 'length') if self.parse_args.rad_source_fwhm is not None: radiation_source.parameter['fwhm'] = self.math.parse( self.parse_args.rad_source_fwhm, 'length') if self.parse_args.rad_source_mass is not None: radiation_source.parameter['mass'] = self.math.parse( self.parse_args.rad_source_mass, 'mass') return radiation_source def get_module_from_name(self, radiation_source_name): """Chooses radiation source from name string Args: radiation_source_name (str): Name of the stellar source. Returns: Instance of chosen radiation source . """ if radiation_source_name in self.sources_dict.keys(): radiation_source = self.sources_dict[radiation_source_name]( self.file_io, self.parse_args) else: raise ValueError('No source with the name ' + str(radiation_source_name) + '!') return radiation_source
class ModelChooser: """The ModelChooser class provides the chosen model. """ def __init__(self, parse_args): """Initialisation of all usable options. Notes: To create your own model, add its name to the dictionary and write a class with its options as a derived class of class Model. Args: parse_args (ArgumentParser) : Provides all parameters chosen by user when executing PolarisTools. """ self.parse_args = parse_args # Get math module self.math = Math() # dict: Dictionary with all usable models self.model_dict = { 'default': Model, 'disk': Disk, 'sphere': Sphere, 'globule': BokGlobule, } update_model_dict(self.model_dict) def get_module(self): """Chooses model class from user input Note: Parameters set by PolarisTools overwrite preset values in the separate model classes. Returns: Instance of chosen model. """ if self.parse_args.model_name in self.model_dict.keys(): model = self.model_dict[self.parse_args.model_name]() elif self.parse_args.model_name is not None: raise ValueError( 'Model name not known! You can add a new model in model.py.') else: model = self.model_dict['default']() # Set user input variables if 'grid_type' in vars(self.parse_args).keys(): model.update_parameter(self.parse_args.extra_parameter) if self.parse_args.grid_type is not None: model.parameter['grid_type'] = self.parse_args.grid_type if self.parse_args.gas_mass is not None: model.parameter['gas_mass'] = self.math.parse( self.parse_args.gas_mass, 'mass') if self.parse_args.sidelength is not None: model.octree_parameter['sidelength'] = self.math.parse( self.parse_args.sidelength, 'length') if self.parse_args.max_tree_level is not None: model.octree_parameter[ 'max_tree_level'] = self.parse_args.max_tree_level if self.parse_args.inner_radius is not None: model.parameter['inner_radius'] = self.math.parse( self.parse_args.inner_radius, 'length') if self.parse_args.outer_radius is not None: model.parameter['outer_radius'] = self.math.parse( self.parse_args.outer_radius, 'length') if self.parse_args.z_max is not None: model.cylindrical_parameter['z_max'] = self.math.parse( self.parse_args.z_max, 'length') if self.parse_args.n_r is not None: model.spherical_parameter['n_r'] = self.parse_args.n_r model.cylindrical_parameter['n_r'] = self.parse_args.n_r if self.parse_args.n_ph is not None: model.spherical_parameter['n_ph'] = self.parse_args.n_ph model.cylindrical_parameter['n_ph'] = self.parse_args.n_ph if self.parse_args.n_th is not None: model.spherical_parameter['n_th'] = self.parse_args.n_th if self.parse_args.n_z is not None: model.cylindrical_parameter['n_z'] = self.parse_args.n_z if self.parse_args.sf_r is not None: model.spherical_parameter['sf_r'] = self.parse_args.sf_r model.cylindrical_parameter['sf_r'] = self.parse_args.sf_r if self.parse_args.sf_ph is not None: model.spherical_parameter['sf_ph'] = self.parse_args.sf_ph model.cylindrical_parameter['sf_ph'] = self.parse_args.sf_ph if self.parse_args.sf_th is not None: model.spherical_parameter['sf_th'] = self.parse_args.sf_th if self.parse_args.sf_z is not None: model.cylindrical_parameter['sf_z'] = self.parse_args.sf_z if self.parse_args.split_first_cell is not None: model.spherical_parameter[ 'split_first_cell'] = self.parse_args.split_first_cell model.cylindrical_parameter[ 'split_first_cell'] = self.parse_args.split_first_cell elif 'distance' in vars(self.parse_args).keys(): if self.parse_args.distance is not None: model.parameter['distance'] = self.math.parse( self.parse_args.distance, 'length') # Set the grid extent if global extent is set if model.parameter['grid_type'] == 'octree' and model.parameter[ 'outer_radius'] is not None: model.octree_parameter['sidelength'] = 2. * \ model.parameter['outer_radius'] elif model.parameter['grid_type'] == 'spherical': if model.parameter['inner_radius'] is not None: model.spherical_parameter['inner_radius'] = model.parameter[ 'inner_radius'] if model.parameter['outer_radius'] is not None: model.spherical_parameter['outer_radius'] = model.parameter[ 'outer_radius'] elif model.parameter['grid_type'] == 'cylindrical': if model.parameter['inner_radius'] is not None: model.cylindrical_parameter['inner_radius'] = model.parameter[ 'inner_radius'] if model.parameter['outer_radius'] is not None: model.cylindrical_parameter['outer_radius'] = model.parameter[ 'outer_radius'] if model.cylindrical_parameter['z_max'] is None: model.cylindrical_parameter['z_max'] = model.parameter[ 'outer_radius'] # Convert radius in various units if model.parameter['grid_type'] == 'octree': model.tmp_parameter[ 'radius_x_m'] = model.octree_parameter['sidelength'] / 2. model.tmp_parameter[ 'radius_y_m'] = model.octree_parameter['sidelength'] / 2. elif model.parameter['grid_type'] == 'spherical': model.tmp_parameter['radius_x_m'] = model.spherical_parameter[ 'outer_radius'] model.tmp_parameter['radius_y_m'] = model.spherical_parameter[ 'outer_radius'] elif model.parameter['grid_type'] == 'cylindrical': model.tmp_parameter['radius_x_m'] = model.cylindrical_parameter[ 'outer_radius'] model.tmp_parameter['radius_y_m'] = model.cylindrical_parameter[ 'outer_radius'] else: raise ValueError('Grid type not known!') model.tmp_parameter['radius_x_arcsec'] = self.math.length_conv( model.tmp_parameter['radius_x_m'], 'arcsec', model.parameter['distance']) model.tmp_parameter['radius_y_arcsec'] = self.math.length_conv( model.tmp_parameter['radius_y_m'], 'arcsec', model.parameter['distance']) model.tmp_parameter['radius_x_pc'] = self.math.length_conv( model.tmp_parameter['radius_x_m'], 'pc') model.tmp_parameter['radius_y_pc'] = self.math.length_conv( model.tmp_parameter['radius_y_m'], 'pc') model.tmp_parameter['radius_x_au'] = self.math.length_conv( model.tmp_parameter['radius_x_m'], 'au') model.tmp_parameter['radius_y_au'] = self.math.length_conv( model.tmp_parameter['radius_y_m'], 'au') model.tmp_parameter['radius_x_ae'] = model.tmp_parameter['radius_x_au'] model.tmp_parameter['radius_y_ae'] = model.tmp_parameter['radius_y_au'] return model
class GasCreator: """The GasCreator class is able to create various gas species for POLARIS. """ def __init__(self, file_io, parse_args): """Initialisation of the dust parameters. Args: file_io : Handles file input/output and all necessary paths. """ self.file_io = file_io self.parse_args = parse_args # Get math module from polaris_tools_modules.math import Math self.math = Math() def convert_database(self, database, database_code): """Converts files fom the cdms/jpl database into the LAMBDA database format. Args: database (str): Name of the database from which we take the data to convert it. (Possible options: jpl, cdms) database_code (int): Unique code of the gas species/atom database file. Notes: JPL: http://spec.jpl.nasa.gov/ CDMS: http://www.astro.uni-koeln.de/cdms/catalog LAMBDA: http://home.strw.leidenuniv.nl/~moldata/ """ # Init name of species and partition function with default values species_name = '' rot_spin_partition_func = 0 if database == 'cdms': # Load partition functions from cdms database doc_cdms = open( self.file_io.path[database] + 'c' + str(database_code).zfill(6) + '.out', 'r') # Search for the entry with the partition function at T = 300 K i_line = 0 for line in doc_cdms.readlines(): if '300.000' in line: rot_spin_partition_func = float(line.split()[1]) if 'ID=' in line: species_name = line.partition(', ID=')[0] if i_line == 0: species_name = line.split()[0] i_line += 1 doc_cdms.close() elif database == 'jpl': # Load partition functions from documentation of the chosen species from jpl database doc_jpl = open( self.file_io.path[database] + 'd' + str(database_code).zfill(6) + '.cat', 'r') # Search for the entry with the partition function at T = 300 K for line in doc_jpl.readlines(): if 'Q(300.0)=' in line: rot_spin_partition_func = float(line.split()[-1]) if 'Name:' in line: species_name = line.split()[-1] doc_jpl.close() else: raise ValueError('Database: ' + database + ' not known!') # If the partition function was not loaded correctly if species_name == '' or rot_spin_partition_func == 0: raise EnvironmentError( 'Name of the species and partition function are not sufficiently loaded!' ) # Init dictionary where each entry is one energy level energy_level = dict( # Transition index [1/cm] ENERGIES=[], # Degeneracy of the level WEIGHT=[], # Related quantum numbers QN_1=[], QN_2=[], QN_3=[], QN_4=[], QN_5=[], QN_6=[], ) if os.path.isfile(self.file_io.path[database] + 'c' + str(database_code).zfill(6) + '.egy'): # Load database file data = open( self.file_io.path[database] + 'c' + str(database_code).zfill(6) + '.egy', 'r') # Get the energy levels from database for line in data.readlines(): formatted_line = line.split() # Add the energy level to the dictionary energy_level['ENERGIES'].append(abs(float(formatted_line[2]))) energy_level['WEIGHT'].append( int(formatted_line[5].replace(':', ''))) if len(formatted_line) >= 7: energy_level['QN_1'].append(int(formatted_line[6])) else: energy_level['QN_1'].append(-1) if len(formatted_line) >= 8: energy_level['QN_2'].append(int(formatted_line[7])) else: energy_level['QN_2'].append(-1) if len(formatted_line) >= 9: energy_level['QN_3'].append(int(formatted_line[8])) else: energy_level['QN_3'].append(-1) if len(formatted_line) >= 10: energy_level['QN_4'].append(int(formatted_line[9])) else: energy_level['QN_4'].append(-1) if len(formatted_line) >= 11: energy_level['QN_5'].append(int(formatted_line[10])) else: energy_level['QN_5'].append(-1) if len(formatted_line) >= 12: energy_level['QN_6'].append(int(formatted_line[11])) else: energy_level['QN_6'].append(-1) else: # Load database file data = open( self.file_io.path[database] + 'c' + str(database_code).zfill(6) + '.cat', 'r') # Per default, the quantum number are integers half_value_qn = False # Get the upper energy level from each transition for line in data.readlines(): #: float: Energy of lower transition [1/cm] energy_lower = float(line[31:41]) #: float: Energy of upper transition [1/cm] energy_upper = energy_lower + float( line[0:13]) * 1e-2 * 1e6 / self.math.const['c'] # Obtain each available quantum number and calculate the weight from it if line[55:57].rstrip( '\n') == ' ' or not line[55:57].rstrip('\n'): qn_1 = -1 else: qn_1 = int(line[55:57].rstrip('\n')) # Obtain each available quantum number and calculate the weight from it if line[57:59].rstrip( '\n') == ' ' or not line[57:59].rstrip('\n'): qn_2 = -1 else: qn_2 = int(line[57:59].rstrip('\n')) # Obtain each available quantum number and calculate the weight from it if line[59:61].rstrip( '\n') == ' ' or not line[59:61].rstrip('\n'): qn_3 = -1 else: qn_3 = int(line[59:61].rstrip('\n')) # Obtain each available quantum number and calculate the weight from it if line[61:63].rstrip( '\n') == ' ' or not line[61:63].rstrip('\n'): qn_4 = -1 else: qn_4 = int(line[61:63].rstrip('\n')) if line[63:65].rstrip( '\n') == ' ' or not line[63:65].rstrip('\n'): qn_5 = -1 else: qn_5 = int(line[63:65].rstrip('\n')) if line[65:67].rstrip( '\n') == ' ' or not line[65:67].rstrip('\n'): qn_6 = -1 else: qn_6 = int(line[65:67].rstrip('\n')) weight = self.math.get_weight_from_qn(database_code, qn_1, qn_2, qn_3, qn_4, qn_5, qn_6) # For the upper energy level, if the calculated weight is not same as the weight of the transition, # the quantum numbers are not integers but .5 values. if weight != int(line[41:44]): if weight - int(line[41:44]) == 1: half_value_qn = True else: print(weight, int(line[41:44])) raise ValueError( 'weight from quantum numbers does not fit with weight of spectral transition!' ) # Add the energy level to the dictionary energy_level['ENERGIES'].append(abs(energy_upper)) energy_level['QN_1'].append(int(qn_1)) energy_level['QN_2'].append(int(qn_2)) energy_level['QN_3'].append(int(qn_3)) energy_level['QN_4'].append(int(qn_4)) energy_level['QN_5'].append(int(qn_5)) energy_level['QN_6'].append(int(qn_6)) # Adjust the weight according to the format of the quantum numbers if half_value_qn: energy_level['WEIGHT'].append(weight - 1) else: energy_level['WEIGHT'].append(weight) data.close() # Load database file data = open( self.file_io.path[database] + 'c' + str(database_code).zfill(6) + '.cat', 'r') # Get the lower energy level from each transition for line in data.readlines(): #: float: Energy of lower transition [1/cm] energy_lower = float(line[31:41]) # Obtain each available quantum number and calculate the weight from it if line[67:69].rstrip( '\n') == ' ' or not line[67:69].rstrip('\n'): qn_1 = -1 else: qn_1 = int(line[67:69].rstrip('\n')) # Obtain each available quantum number and calculate the weight from it if line[69:71].rstrip( '\n') == ' ' or not line[69:71].rstrip('\n'): qn_2 = -1 else: qn_2 = int(line[69:71].rstrip('\n')) # Obtain each available quantum number and calculate the weight from it if line[71:73].rstrip( '\n') == ' ' or not line[71:73].rstrip('\n'): qn_3 = -1 else: qn_3 = int(line[71:73].rstrip('\n')) # Obtain each available quantum number and calculate the weight from it if line[73:75].rstrip( '\n') == ' ' or not line[73:75].rstrip('\n'): qn_4 = -1 else: qn_4 = int(line[73:75].rstrip('\n')) if line[73:75].rstrip( '\n') == ' ' or not line[73:75].rstrip('\n'): qn_5 = -1 else: qn_5 = int(line[73:75].rstrip('\n')) if line[75:77].rstrip( '\n') == ' ' or not line[75:77].rstrip('\n'): qn_6 = -1 else: qn_6 = int(line[75:77].rstrip('\n')) weight = self.math.get_weight_from_qn(database_code, qn_1, qn_2, qn_3, qn_4, qn_5, qn_6) # Add the energy level to the dictionary energy_level['ENERGIES'].append(abs(energy_lower)) energy_level['QN_1'].append(int(qn_1)) energy_level['QN_2'].append(int(qn_2)) energy_level['QN_3'].append(int(qn_3)) energy_level['QN_4'].append(int(qn_4)) energy_level['QN_5'].append(int(qn_5)) energy_level['QN_6'].append(int(qn_6)) # Adjust the weight according to the format of the quantum numbers if half_value_qn: energy_level['WEIGHT'].append(weight - 1) else: energy_level['WEIGHT'].append(weight) data.close() # Algorithm to sort the energy levels with their energy for i_level in range(len(energy_level['ENERGIES'])): print(i_level, "/", len(energy_level['ENERGIES'])) for j_level in range(i_level + 1, len(energy_level['ENERGIES'])): # If the next energy level has a higher energy than the current one, switch them if energy_level['ENERGIES'][i_level] > energy_level[ 'ENERGIES'][j_level]: # Switch each entry of the dictionary for keys in energy_level.keys(): tmp = energy_level[keys][j_level] energy_level[keys][j_level] = energy_level[keys][ i_level] energy_level[keys][i_level] = tmp # Delete duplicates of the energy levels index = len(energy_level['ENERGIES']) - 1 while index != 0: index -= 1 # If all quantum numbers are the same, the energy level is the same # However, the energy value might vary slightly if energy_level['QN_1'][index] == energy_level['QN_1'][index + 1] \ and energy_level['QN_2'][index] == energy_level['QN_2'][index + 1] \ and energy_level['QN_3'][index] == energy_level['QN_3'][index + 1] \ and energy_level['QN_4'][index] == energy_level['QN_4'][index + 1] \ and energy_level['QN_5'][index] == energy_level['QN_5'][index + 1] \ and energy_level['QN_6'][index] == energy_level['QN_6'][index + 1]: del energy_level['ENERGIES'][index + 1] del energy_level['WEIGHT'][index + 1] del energy_level['QN_1'][index + 1] del energy_level['QN_2'][index + 1] del energy_level['QN_3'][index + 1] del energy_level['QN_4'][index + 1] del energy_level['QN_5'][index + 1] del energy_level['QN_6'][index + 1] # Create a new entry for the index of the energy levels energy_level['LEVEL'] = [] for i_level in range(len(energy_level['ENERGIES'])): energy_level['LEVEL'].append(i_level + 1) # Init dictionary where each entry is one spectral line transition radiative_transitions = dict( # Transition index TRANS=[], # Index of upper transition UP=[], # Index of lower transition LOW=[], # Einstein coefficient A [1/s] EINSTEINA=[], # Frequency of transition [GHz] FREQ=[], # Energy of transition [K] E=[], ) # Load database file data = open( self.file_io.path[database] + 'c' + str(database_code).zfill(6) + '.cat', 'r') # Go through each transition again i_trans = 0 for line in data.readlines(): i_trans += 1 # Set index of transition radiative_transitions['TRANS'].append(i_trans) #: float: Energy of lower transition [1/cm] energy_lower = float(line[31:41]) #: float: Energy of upper transition [1/cm] energy_upper = energy_lower + float( line[0:13]) * 1e-2 * 1e6 / self.math.const['c'] # Find index of lower energy level if energy_lower == 0.: # If the lower energy level is the zero level, it has the index 1 radiative_transitions['LOW'].append(1) else: # Init comparison values min_value_lower = 999.9 min_index_lower = 0 # The lower energy level is found by taking the one with the closest energy for i_level in range(len(energy_level['LEVEL'])): if abs(energy_level['ENERGIES'][i_level] - energy_lower) < min_value_lower: min_value_lower = abs( energy_level['ENERGIES'][i_level] - energy_lower) min_index_lower = i_level # Set index of lower transition radiative_transitions['LOW'].append( energy_level['LEVEL'][min_index_lower]) # Init comparison values min_value_upper = 999.9 min_index_upper = 0 # The upper energy level is found by taking the one with the closest energy for i_level in range(len(energy_level['LEVEL'])): if abs(energy_level['ENERGIES'][i_level] - energy_upper) < min_value_upper: min_value_upper = abs(energy_level['ENERGIES'][i_level] - energy_upper) min_index_upper = i_level # Set index of upper transition radiative_transitions['UP'].append( energy_level['LEVEL'][min_index_upper]) #: float: Frequency of lower transition [Hz] freq_lower = energy_lower * 1e2 * self.math.const['c'] #: float: Frequency of upper transition [Hz] freq_upper = freq_lower + float(line[0:13]) * 1e6 #: float: Energy of lower transition energy_lower = freq_lower * self.math.const['h'] exp_val_lower = np.exp(-energy_lower / (self.math.const['k_B'] * 300)) #: float: Energy of upper transition energy_upper = freq_upper * self.math.const['h'] exp_val_upper = np.exp(-energy_upper / (self.math.const['k_B'] * 300)) #: float: Line intensity at 300K line_int = 10**float(line[21:29]) #: float: Squared value of the transition frequency [MHz] trans_freq_sq = float(line[0:13])**2 #: float: Upper state degeneracy upper_deg = float(line[41:44]) #: float: Einstein coefficient A einstein_a = line_int * trans_freq_sq * ( rot_spin_partition_func / upper_deg) * 1 / (exp_val_lower - exp_val_upper) * 2.7964e-16 # Set einstein coefficient A radiative_transitions['EINSTEINA'].append(einstein_a) # Set frequency of transition [GHz] radiative_transitions['FREQ'].append(float(line[0:13]) * 1e-3) #: float: Transition frequency [Hz] trans_freq = float(line[0:13]) * 1e6 #: float: Brightness temperature bright_temp = self.math.const['h'] * trans_freq / self.math.const[ 'k_B'] # Set Energy of transition in Kelvin radiative_transitions['E'].append(bright_temp) data.close() # Write the data as Leiden database file leiden_file = open( self.file_io.path['gas'] + species_name + '_' + database + '.dat', 'w') leiden_file.write('!GAS_SPECIES\n') leiden_file.write(species_name + '\n') leiden_file.write('!MOLECULAR WEIGHT\n') leiden_file.write(str(database_code).zfill(6)[0:3].upper() + '\n') leiden_file.write('!NUMBER OF ENERGY LEVELS\n') leiden_file.write(str(len(energy_level['LEVEL'])) + '\n') leiden_file.write('!LEVEL + ENERGIES[cm^-1] + WEIGHT + QNUM\n') for i_level in range(len(energy_level['LEVEL'])): qn_string = '' if energy_level['QN_1'][i_level] != -1: qn_string += str(energy_level['QN_1'][i_level]).rjust(2) if energy_level['QN_2'][i_level] != -1: qn_string += str(energy_level['QN_2'][i_level]).rjust(2) if energy_level['QN_3'][i_level] != -1: qn_string += str(energy_level['QN_3'][i_level]).rjust(2) if energy_level['QN_4'][i_level] != -1: qn_string += str( energy_level['QN_4'][i_level]).rjust(2) + ' ' if energy_level['QN_5'][i_level] != -1: qn_string += str( energy_level['QN_5'][i_level]).rjust(2) + ' ' if energy_level['QN_6'][i_level] != -1: qn_string += str( energy_level['QN_6'][i_level]).rjust(2) + ' ' leiden_file.write( '{:5d}'.format(energy_level['LEVEL'][i_level]) + '{:16.8f}'.format(energy_level['ENERGIES'][i_level]) + '{:9.1f}'.format(energy_level['WEIGHT'][i_level]) + ' ' + qn_string + '\n') leiden_file.write('!NUMBER OF RADIATIVE TRANSITIONS\n') leiden_file.write(str(len(radiative_transitions['TRANS'])) + '\n') leiden_file.write( '!TRANS + UP + LOW + EINSTEINA[s^-1] + FREQ[GHz] + E_u[K]\n') for i_trans in range(len(radiative_transitions['TRANS'])): leiden_file.write( '{:5d}'.format(radiative_transitions['TRANS'][i_trans]) + '{:6d}'.format(radiative_transitions['UP'][i_trans]) + '{:6d}'.format(radiative_transitions['LOW'][i_trans]) + '{:12.3e}'.format(radiative_transitions['EINSTEINA'][i_trans]) + '{:16.7f}'.format(radiative_transitions['FREQ'][i_trans]) + '{:10.2f}'.format(radiative_transitions['E'][i_trans]) + '\n') leiden_file.close() def create_zeeman_file(self): """Create Zeeman database file which can be used by POLARIS. """ if self.parse_args.gas_species.upper() == 'CN': j_upper_list = [0.5, 0.5, 0.5, 1.5, 1.5, 1.5, 1.5] f_upper_list = [0.5, 1.5, 1.5, 1.5, 2.5, 0.5, 1.5] j_lower_list = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5] f_lower_list = [1.5, 0.5, 1.5, 0.5, 1.5, 0.5, 1.5] transition_index = [11, 12, 13, 14, 15, 16, 17] zeeman_file = open(self.file_io.path['gas'] + 'cn_zeeman.dat', 'w') zeeman_file.write('!Gas species name\n') zeeman_file.write('CN\n') zeeman_file.write( '!Gas species radius for collision calculations\n') # The gas_species radius is calculated by adding the covalent # radii of all atoms together. zeeman_file.write( str(self.math.covalent_radii['C'] + self.math.covalent_radii['N']) + '\n') zeeman_file.write('!Number of transitions with Zeeman effect\n') zeeman_file.write(str(len(j_upper_list)) + '\n') zeeman_shifts = np.zeros(len(j_upper_list)) for i_trans in range(len(j_upper_list)): lande_upper = self.math.lande_g_cn(1, j_upper_list[i_trans], f_upper_list[i_trans]) lande_lower = self.math.lande_g_cn(0, j_lower_list[i_trans], f_lower_list[i_trans]) zeeman_file.write('!Transition index in Leiden database\n') zeeman_file.write(str(transition_index[i_trans]) + '\n') zeeman_file.write('!Lande factor of upper level\n') zeeman_file.write(str(lande_upper) + '\n') zeeman_file.write('!Lande factor of lower level\n') zeeman_file.write(str(lande_lower) + '\n') zeeman_file.write( '!Number of Zeeman sublevels in the upper level\n') zeeman_file.write(str((f_upper_list[i_trans] * 2) + 1) + '\n') zeeman_file.write( '!Number of Zeeman sublevels in the lower level\n') zeeman_file.write(str((f_lower_list[i_trans] * 2) + 1) + '\n') if f_lower_list[i_trans] == f_upper_list[i_trans]: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_zero( f, m_f, transition) elif (f_lower_list[i_trans] - f_upper_list[i_trans]) == +1: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_plus( f, m_f, transition) elif (f_lower_list[i_trans] - f_upper_list[i_trans]) == -1: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_minus( f, m_f, transition) for i_upper_trans in np.arange( -float(f_upper_list[i_trans]), float(f_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(f_lower_list[i_trans]), float(f_lower_list[i_trans] + 1), 1): if i_lower_trans == i_upper_trans: zeeman_file.write( '!Line strength of pi transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans) + ')\n') rel_str_pi = 2.0 * relative_strength( f_upper_list[i_trans], i_upper_trans, 0) zeeman_file.write(str(rel_str_pi) + '\n') for i_upper_trans in np.arange( -float(f_upper_list[i_trans]), float(f_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(f_lower_list[i_trans]), float(f_lower_list[i_trans] + 1), 1): if i_lower_trans - i_upper_trans == +1: zeeman_file.write( '!Line strength of sigma_+ transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans + 1) + ')\n') rel_str_sigma_p = relative_strength( f_upper_list[i_trans], i_upper_trans, +1) zeeman_file.write(str(rel_str_sigma_p) + '\n') for i_upper_trans in np.arange( -float(f_upper_list[i_trans]), float(f_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(f_lower_list[i_trans]), float(f_lower_list[i_trans] + 1), 1): if i_lower_trans - i_upper_trans == -1: zeeman_file.write( '!Line strength of sigma_- transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans - 1) + ')\n') rel_str_sigma_m = relative_strength( f_upper_list[i_trans], i_upper_trans, -1) zeeman_file.write(str(rel_str_sigma_m) + '\n') zeeman_shifts[i_trans] += 2.0 * rel_str_sigma_m * ( (lande_upper * i_upper_trans) - (lande_lower * i_lower_trans)) zeeman_file.close() zeeman_shifts = np.multiply( zeeman_shifts, self.math.const['mu_B'] / self.math.const['h'] * 1e-10 * 2.0) print('2*nu/B =', zeeman_shifts[0], 'Hz/muG') elif self.parse_args.gas_species.upper() == 'SO': n_upper_list = [2, 2, 3, 4, 5] j_upper_list = [3, 1, 4, 3, 6] n_lower_list = [1, 1, 2, 3, 4] j_lower_list = [2, 2, 3, 2, 5] transition_index = [3, 8, 10, 28, 34] zeeman_file = open(self.file_io.path['gas'] + 'so_zeeman.dat', 'w') zeeman_file.write('!Gas species name\n') zeeman_file.write('SO\n') zeeman_file.write( '!Gas species radius for collision calculations\n') # The gas_species radius is calculated by adding the covalent # radii of all atoms together. zeeman_file.write( str(self.math.covalent_radii['S'] + self.math.covalent_radii['O']) + '\n') zeeman_file.write('!Number of transitions with Zeeman effect\n') zeeman_file.write(str(len(j_upper_list)) + '\n') zeeman_shifts = np.zeros(len(j_upper_list)) for i_trans in range(len(j_upper_list)): lande_upper = self.math.lande_g_so(n_upper_list[i_trans], j_upper_list[i_trans]) lande_lower = self.math.lande_g_so(n_lower_list[i_trans], j_lower_list[i_trans]) zeeman_file.write('!Transition index in Leiden database\n') zeeman_file.write(str(transition_index[i_trans]) + '\n') zeeman_file.write('!Lande factor of upper level\n') zeeman_file.write(str(lande_upper) + '\n') zeeman_file.write('!Lande factor of lower level\n') zeeman_file.write(str(lande_lower) + '\n') zeeman_file.write( '!Number of Zeeman sublevels in the upper level\n') zeeman_file.write(str((j_upper_list[i_trans] * 2) + 1) + '\n') zeeman_file.write( '!Number of Zeeman sublevels in the lower level\n') zeeman_file.write(str((j_lower_list[i_trans] * 2) + 1) + '\n') if j_lower_list[i_trans] == j_upper_list[i_trans]: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_zero( f, m_f, transition) elif (j_lower_list[i_trans] - j_upper_list[i_trans]) == +1: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_plus( f, m_f, transition) elif (j_lower_list[i_trans] - j_upper_list[i_trans]) == -1: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_minus( f, m_f, transition) for i_upper_trans in np.arange( -float(j_upper_list[i_trans]), float(j_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(j_lower_list[i_trans]), float(j_lower_list[i_trans] + 1), 1): if i_lower_trans == i_upper_trans: zeeman_file.write( '!Line strength of pi transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans) + ')\n') rel_str_pi = 2.0 * relative_strength( j_upper_list[i_trans], i_upper_trans, 0) zeeman_file.write(str(rel_str_pi) + '\n') for i_upper_trans in np.arange( -float(j_upper_list[i_trans]), float(j_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(j_lower_list[i_trans]), float(j_lower_list[i_trans] + 1), 1): if i_lower_trans - i_upper_trans == +1: zeeman_file.write( '!Line strength of sigma_+ transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans + 1) + ')\n') rel_str_sigma_p = relative_strength( j_upper_list[i_trans], i_upper_trans, +1) zeeman_file.write(str(rel_str_sigma_p) + '\n') for i_upper_trans in np.arange( -float(j_upper_list[i_trans]), float(j_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(j_lower_list[i_trans]), float(j_lower_list[i_trans] + 1), 1): if i_lower_trans - i_upper_trans == -1: zeeman_file.write( '!Line strength of sigma_- transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans - 1) + ')\n') rel_str_sigma_m = relative_strength( j_upper_list[i_trans], i_upper_trans, -1) zeeman_file.write(str(rel_str_sigma_m) + '\n') zeeman_shifts[i_trans] += 2.0 * rel_str_sigma_m * ( (lande_upper * i_upper_trans) - (lande_lower * i_lower_trans)) zeeman_file.close() zeeman_shifts = np.multiply( zeeman_shifts, self.math.const['mu_B'] / self.math.const['h'] * 1e-10 * 2.0) print('2*nu/B =', zeeman_shifts[0], 'Hz/muG') elif self.parse_args.gas_species.upper() == 'CCS': n_upper_list = [0, 1, 2, 3] j_upper_list = [1, 2, 3, 4] n_lower_list = [1, 0, 1, 2] j_lower_list = [0, 1, 2, 3] transition_index = [4, 6, 11, 15] zeeman_file = open(self.file_io.path['gas'] + 'ccs_zeeman.dat', 'w') zeeman_file.write('!Gas species name\n') zeeman_file.write('CCS\n') zeeman_file.write( '!Gas species radius for collision calculations\n') # The gas_species radius is calculated by adding the covalent # radii of all atoms together. zeeman_file.write( str(2 * self.math.covalent_radii['C'] + self.math.covalent_radii['S']) + '\n') zeeman_file.write('!Number of transitions with Zeeman effect\n') zeeman_file.write(str(len(j_upper_list)) + '\n') zeeman_shifts = np.zeros(len(j_upper_list)) for i_trans in range(len(j_upper_list)): lande_upper = self.math.lande_g_ccs(n_upper_list[i_trans], j_upper_list[i_trans]) lande_lower = self.math.lande_g_ccs(n_lower_list[i_trans], j_lower_list[i_trans]) zeeman_file.write('!Transition index in Leiden database\n') zeeman_file.write(str(transition_index[i_trans]) + '\n') zeeman_file.write('!Lande factor of upper level\n') zeeman_file.write(str(lande_upper) + '\n') zeeman_file.write('!Lande factor of lower level\n') zeeman_file.write(str(lande_lower) + '\n') zeeman_file.write( '!Number of Zeeman sublevels in the upper level\n') zeeman_file.write(str((j_upper_list[i_trans] * 2) + 1) + '\n') zeeman_file.write( '!Number of Zeeman sublevels in the lower level\n') zeeman_file.write(str((j_lower_list[i_trans] * 2) + 1) + '\n') if j_lower_list[i_trans] == j_upper_list[i_trans]: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_zero( f, m_f, transition) elif (j_lower_list[i_trans] - j_upper_list[i_trans]) == +1: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_plus( f, m_f, transition) elif (j_lower_list[i_trans] - j_upper_list[i_trans]) == -1: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_minus( f, m_f, transition) for i_upper_trans in np.arange( -float(j_upper_list[i_trans]), float(j_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(j_lower_list[i_trans]), float(j_lower_list[i_trans] + 1), 1): if i_lower_trans == i_upper_trans: zeeman_file.write( '!Line strength of pi transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans) + ')\n') rel_str_pi = 2.0 * relative_strength( j_upper_list[i_trans], i_upper_trans, 0) zeeman_file.write(str(rel_str_pi) + '\n') for i_upper_trans in np.arange( -float(j_upper_list[i_trans]), float(j_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(j_lower_list[i_trans]), float(j_lower_list[i_trans] + 1), 1): if i_lower_trans - i_upper_trans == +1: zeeman_file.write( '!Line strength of sigma_+ transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans + 1) + ')\n') rel_str_sigma_p = relative_strength( j_upper_list[i_trans], i_upper_trans, +1) zeeman_file.write(str(rel_str_sigma_p) + '\n') for i_upper_trans in np.arange( -float(j_upper_list[i_trans]), float(j_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(j_lower_list[i_trans]), float(j_lower_list[i_trans] + 1), 1): if i_lower_trans - i_upper_trans == -1: zeeman_file.write( '!Line strength of sigma_- transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans - 1) + ')\n') rel_str_sigma_m = relative_strength( j_upper_list[i_trans], i_upper_trans, -1) zeeman_file.write(str(rel_str_sigma_m) + '\n') zeeman_shifts[i_trans] += 2.0 * rel_str_sigma_m * ( (lande_upper * i_upper_trans) - (lande_lower * i_lower_trans)) zeeman_file.close() zeeman_shifts = np.multiply( zeeman_shifts, self.math.const['mu_B'] / self.math.const['h'] * 1e-10 * 2.0) print('2*nu/B =', zeeman_shifts[0], 'Hz/muG') elif self.parse_args.gas_species.upper() == 'H1': l_upper_list = [0] f_upper_list = [1] l_lower_list = [0] f_lower_list = [0] transition_index = [1] zeeman_file = open(self.file_io.path['gas'] + 'h1_zeeman.dat', 'w') zeeman_file.write('!Gas species name\n') zeeman_file.write('H1\n') zeeman_file.write( '!Gas species radius for collision calculations\n') # The gas_species radius is calculated by adding the covalent # radii of all atoms together. zeeman_file.write(str(self.math.covalent_radii['H']) + '\n') zeeman_file.write('!Number of transitions with Zeeman effect\n') zeeman_file.write(str(len(l_upper_list)) + '\n') zeeman_shifts = np.zeros(len(l_upper_list)) for i_trans in range(len(l_upper_list)): lande_upper = self.math.lande_g_h1(l_upper_list[i_trans], f_upper_list[i_trans]) lande_lower = self.math.lande_g_h1(l_lower_list[i_trans], f_lower_list[i_trans]) zeeman_file.write('!Transition index in Leiden database\n') zeeman_file.write(str(transition_index[i_trans]) + '\n') zeeman_file.write('!Lande factor of upper level\n') zeeman_file.write(str(lande_upper) + '\n') zeeman_file.write('!Lande factor of lower level\n') zeeman_file.write(str(lande_lower) + '\n') zeeman_file.write( '!Number of Zeeman sublevels in the upper level\n') zeeman_file.write(str((f_upper_list[i_trans] * 2) + 1) + '\n') zeeman_file.write( '!Number of Zeeman sublevels in the lower level\n') zeeman_file.write(str((f_lower_list[i_trans] * 2) + 1) + '\n') if f_lower_list[i_trans] == f_upper_list[i_trans]: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_zero( f, m_f, transition) elif (f_lower_list[i_trans] - f_upper_list[i_trans]) == +1: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_plus( f, m_f, transition) elif (f_lower_list[i_trans] - f_upper_list[i_trans]) == -1: def relative_strength(f, m_f, transition): return self.math.relative_line_strength_minus( f, m_f, transition) for i_upper_trans in np.arange( -float(f_upper_list[i_trans]), float(f_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(f_lower_list[i_trans]), float(f_lower_list[i_trans] + 1), 1): if i_lower_trans == i_upper_trans: zeeman_file.write( '!Line strength of pi transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans) + ')\n') rel_str_pi = 2.0 * relative_strength( f_upper_list[i_trans], i_upper_trans, 0) zeeman_file.write(str(rel_str_pi) + '\n') for i_upper_trans in np.arange( -float(f_upper_list[i_trans]), float(f_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(f_lower_list[i_trans]), float(f_lower_list[i_trans] + 1), 1): if i_lower_trans - i_upper_trans == +1: zeeman_file.write( '!Line strength of sigma_+ transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans + 1) + ')\n') rel_str_sigma_p = relative_strength( f_upper_list[i_trans], i_upper_trans, +1) zeeman_file.write(str(rel_str_sigma_p) + '\n') for i_upper_trans in np.arange( -float(f_upper_list[i_trans]), float(f_upper_list[i_trans] + 1), 1): for i_lower_trans in np.arange( -float(f_lower_list[i_trans]), float(f_lower_list[i_trans] + 1), 1): if i_lower_trans - i_upper_trans == -1: zeeman_file.write( '!Line strength of sigma_- transition (M\'=' + str(i_upper_trans) + ' -> M\'\'=' + str(i_upper_trans - 1) + ')\n') rel_str_sigma_m = relative_strength( f_upper_list[i_trans], i_upper_trans, -1) zeeman_file.write(str(rel_str_sigma_m) + '\n') zeeman_shifts[i_trans] += 2.0 * rel_str_sigma_m * ( (lande_upper * i_upper_trans) - (lande_lower * i_lower_trans)) zeeman_file.close() zeeman_shifts = np.multiply( zeeman_shifts, self.math.const['mu_B'] / self.math.const['h'] * 1e-10 * 2.0) print('2*nu/B =', zeeman_shifts[0], 'Hz/muG') def create_hydrogen_file(self): """Create LAMBDA database file for H1. """ n_max = 20 alpha = self.math.const['e']**2 / ( 4 * np.pi * self.math.const['epsilon_0'] * self.math.const['hbar'] * self.math.const['c']) energy_level = dict( # ID of the energy level LEVEL=[], # Transition index [1/cm] ENERGIES=[], # Degeneracy of the level WEIGHT=[], # Related quantum numbers QN=[], ) energy_level_zero = \ -self.math.const['m_e'] * self.math.const['c'] ** 2 * ( 1 - (1 + (alpha / (1 - 0.5 - 0.5 + np.sqrt((0.5 + 0.5) ** 2 - alpha ** 2))) ** 2) ** (-0.5)) gamma = 2.7928 energy_level_zero += (self.math.const['m_e'] / self.math.const['m_p']) * alpha ** 4 * self.math.const['m_e'] * \ self.math.const['c'] ** 2 * 4 * gamma / 3 * (- 3 / 2.) i_level = 0 for n in range(1, n_max + 1): j_max = (n - 1) + 0.5 for j in np.arange(0.5, j_max + 1): energy_level_tmp = -self.math.const['m_e'] * self.math.const[ 'c']**2 * (1 - (1 + (alpha / (n - j - 0.5 + np.sqrt( (j + 0.5)**2 - alpha**2)))**2)**(-0.5)) if n == 1 and j == 0.5: n_extra = 2 else: n_extra = 1 for f in range(n_extra): i_level += 1 if n_extra == 2: gamma = 2.7928 hf_energy = (self.math.const['m_e'] / self.math.const['m_p']) * alpha ** 4 * \ self.math.const['m_e'] * self.math.const['c'] ** 2 * 4 * gamma / \ (3 * n ** 3) * (f * (f + 1) - 3 / 2.) energy_level['QN'].append('{:2d}'.format(n) + '{:2d}'.format(int(j)) + '{:2d}'.format(int(f))) else: hf_energy = 0. energy_level['QN'].append('{:2d}'.format(n) + '{:2d}'.format(int(j)) + ' -') wave_number = ( (energy_level_tmp + hf_energy - energy_level_zero) * 1e-2 / (self.math.const['h'] * self.math.const['c'])) energy_level['ENERGIES'].append(wave_number) energy_level['WEIGHT'].append(((j * 2) + 1) * 2) energy_level['LEVEL'].append(i_level) radiative_transitions = dict( # Transition index TRANS=[], # Index of upper transition UP=[], # Index of lower transition LOW=[], # Einstein coefficient A [1/s] EINSTEINA=[], # Frequency of transition [GHz] FREQ=[], # Energy of transition [K] E=[], ) radiative_transitions['TRANS'].append(1) radiative_transitions['LOW'].append(1) radiative_transitions['UP'].append(2) radiative_transitions['EINSTEINA'].append(2.876e-15) radiative_transitions['FREQ'].append(1.4204058) radiative_transitions['E'].append(0.07) leiden_file = open(self.file_io.path['gas'] + 'H1_converted.dat', 'w') leiden_file.write('!GAS SPECIES\n') leiden_file.write('H1' + '\n') leiden_file.write('!MOLECULAR WEIGHT\n') leiden_file.write('1\n') leiden_file.write('!NUMBER OF ENERGY LEVELS\n') leiden_file.write(str(len(energy_level['LEVEL'])) + '\n') leiden_file.write('!LEVEL + ENERGIES[cm^-1] + WEIGHT + QNUM\n') for i_level in range(len(energy_level['LEVEL'])): leiden_file.write( '{:5d}'.format(energy_level['LEVEL'][i_level]) + '{:16.8f}'.format(energy_level['ENERGIES'][i_level]) + '{:9.1f}'.format(energy_level['WEIGHT'][i_level]) + ' ' + str(energy_level['QN'][i_level]) + '\n') leiden_file.write('!NUMBER OF RADIATIVE TRANSITIONS\n') leiden_file.write(str(len(radiative_transitions['TRANS'])) + '\n') leiden_file.write( '!TRANS + UP + LOW + EINSTEINA[s^-1] + FREQ[GHz] + E_u[K]\n') for i_trans in range(len(radiative_transitions['TRANS'])): leiden_file.write( '{:5d}'.format(radiative_transitions['TRANS'][i_trans]) + '{:5d}'.format(radiative_transitions['UP'][i_trans]) + '{:5d}'.format(radiative_transitions['LOW'][i_trans]) + '{:12.3e}'.format(radiative_transitions['EINSTEINA'][i_trans]) + '{:17.7f}'.format(radiative_transitions['FREQ'][i_trans]) + '{:10.2f}'.format(radiative_transitions['E'][i_trans]) + '\n') leiden_file.close()