Source code for madcubapy.io.slim
# import astropy
import astropy
from astropy.table import Table
from astropy.table import Column
import numpy as np
from madcubapy.utils.numeric import _return_significant_value
__all__ = [
'import_molecular_parameters_table',
'format_molecular_parameters_columns',
'output_latex_molecular_parameters_table',
]
[docs]
def import_molecular_parameters_table(filein=None, format='ascii'):
"""Import a SLIM Molecular parameters table exported from MADCUBA as a
`~astropy.table.Table`.
Parameters
----------
filein : `str` or `~pathlib.Path`
File name of the exported MADCUBA transitions table.
format : `str`
File format exported by MADCUBA. Can be either 'ascii' or 'csv'.
Returns
-------
table_out : `~astropy.table.Table`
Molecular Parameters Table.
"""
if format=='csv':
table_in = Table.read(filein, format='csv', delimiter=',')
elif format=='ascii':
table_in = Table.read(filein, format='ascii', delimiter='\t')
# Select subtable
table_trim = table_in[
"formula",
"Comp.",
"FWHM",
"f_3",
"delta_3",
"VLSR_",
"f_2",
"delta_2",
"logN|EM",
"f",
"delta",
"Tex|Te*",
"f_1",
"delta_1",
"Noise",
"AutofitNoise",
"UpperLimits",
"AutofitTau",
"Autofit",
"Simulate",
"IsUseAllSpecies",
"ChangeSimulate",
"UpperLimitsTex",
"TMB",
"ApplyAllSpecies",
"ApplyOnlyCheck",
]
# create indexes to help ordering later
order_index = np.arange(len(table_trim))+1
table_trim.add_column(order_index, name='Order_index', index=0)
# Get unique rows from table
table_out = astropy.table.unique(table_trim, keys=['formula', 'Comp.'],
keep='first')
table_out.sort('Order_index') # may need to order by velocity after this
# Create short names for molecules important for this work
short_names = []
for name in table_out['formula']:
if name == 'PN,v=0-5,hfs':
short_name = 'PN,hfs'
elif name == 'PN,v=0-5':
short_name = 'PN'
elif name == 'PO,v=0':
short_name = 'PO'
elif name == 'SO,v=0':
short_name = 'SO'
elif name == 'SO2,v=0':
short_name = 'SO2'
elif name == 'SiO,v=0-10':
short_name = 'SiO'
elif name == 'CH3OH,vt=0-2':
short_name = 'CH3OH'
elif name == 'CH2DOH':
short_name = 'CH2DOH'
elif name == 'CHD2OH,vt=0':
short_name = 'CHD2OH'
elif name == 'CH3OCH3,v=0':
short_name = 'CH3OCH3'
elif name == 'CH3OCHO':
short_name = 'CH3OCHO'
elif name == 't-HC-13-OOH':
short_name = 't-HC-13-OOH'
else:
short_name = f'DEL---{name}'
short_names.append(short_name)
# Create latex labels
label_names = []
for name in table_out['formula']:
if name == 'PN,v=0-5,hfs':
label_name = 'PN (HFS)'
elif name == 'PN,v=0-5':
label_name = 'PN'
elif name == 'PO,v=0':
label_name = 'PO'
elif name == 'SO,v=0':
label_name = 'SO'
elif name == 'SO2,v=0':
label_name = 'SO$_{2}$'
elif name == 'SiO,v=0-10':
label_name = 'SiO'
elif name == 'CH3OH,vt=0-2':
label_name = 'CH$_{3}$OH'
elif name == 'CH2DOH':
label_name = 'CH$_{2}$DOH'
elif name == 'CHD2OH,vt=0':
label_name = 'CHD$_{2}$OH'
elif name == 'CH3OCH3,v=0':
label_name = 'CH$_{3}$OCH$_{3}$'
elif name == 'CH3OCHO':
label_name = 'CH$_{3}$OCHO'
elif name == 't-HC-13-OOH':
label_name = '$t$-HC$^{13}$OOH'
else:
label_name = name
label_names.append(label_name)
# Rename table columns to be the same as SLIM Tables
table_out['formula'].name = 'Formula'
table_out['Comp.'].name = 'C'
table_out['FWHM'].name = 'Width'
table_out['delta_3'].name = 'delta Width'
table_out['f_3'].name = 'f Width'
table_out['VLSR_'].name = 'Velocity'
table_out['delta_2'].name = 'delta Velocity'
table_out['f_2'].name = 'f Velocity'
table_out['logN|EM'].name = 'N/EM'
table_out['delta'].name = 'delta N/EM'
table_out['f'].name = 'f N/EM'
table_out['Tex|Te*'].name = 'Tex/Te'
table_out['delta_1'].name = 'delta Tex/Te'
table_out['f_1'].name = 'f Tex/Te'
table_out.add_column(label_names, name='Label', index=1)
table_out.add_column(short_names, name='Formula_short', index=2)
return table_out
[docs]
def format_molecular_parameters_columns(table):
"""Add new columns to a Molecular Parameters Table with LaTeX formatted
strings for the physical parameters column density, excitation temperature,
width, and velocity; using significant values and previous related values
from other components or molecules.
Parameters
----------
table : `~astropy.table.Table`
Input Molecular Parameters Table.
Returns
-------
table_out : `~astropy.table.Table`
Formatted Molecular Parameters Table.
"""
formattable_params = ['N/EM', 'Tex/Te', 'Velocity', 'Width']
table_out = table.copy(copy_data=True)
for param in formattable_params:
# Prepare column names
value_col = param
delta_col = f'delta {param}'
f_col = f'f {param}'
# Create new formatted column
new_column_empty_vals = np.array(["--" for row in table_out], dtype="U50")
formatted_col = f'formatted {param}'
new_column = Column(new_column_empty_vals, name=formatted_col)
pos = table_out.colnames.index(delta_col) + 1
if formatted_col in table_out.colnames:
table_out.remove_column(formatted_col)
table_out.add_column(new_column, index=pos)
# Special case. For column density just measure significant values
if param == 'N/EM':
for row in table_out:
value = (10**row[value_col]) / (10**13)
unc = (10**row[delta_col]) / (10**13)
if row['Autofit'] == 'true':
dummy, unc_formatted, round_num = _return_significant_value(unc)
value_formatted = round(value, round_num)
row[formatted_col] = f"{value_formatted} +/- {unc_formatted}"
elif row['UpperLimits'] == 'true':
dummy, value_formatted, round_num = _return_significant_value(value)
row[formatted_col] = f"<{value_formatted}"
else:
row[formatted_col] = f"Norun-{value}"
continue
# first run: format only autofit rows
for row in table_out:
if row['Autofit'] == 'true' and row[f_col] == 'false':
unc = row[delta_col]
dummy, unc_formatted, round_num = _return_significant_value(unc)
value_formatted = round(row[value_col], round_num)
row[formatted_col] = f"{value_formatted} +/- {unc_formatted}"
# second run: format inside molecule groups that have at last one component of the same value and one autofit component
formula_groups = table_out.group_by("Formula")
for formula_group in formula_groups.groups:
formula = formula_group["Formula"][0]
# skip groups with one row
if len(formula_group) == 1:
continue
# skip if there are no autofitted and formatted values in group
formatted_filter = (formula_group["Autofit"] == 'true') & (formula_group[formatted_col] != '--')
unformatted_filter = formula_group[formatted_col] == '--'
if len(formula_group[formatted_filter]) == 0:
continue
for row in formula_group[unformatted_filter]:
comp = row["C"]
found_value_filter = np.isclose(formula_group[value_col], row[value_col])
if len(formula_group[formatted_filter & found_value_filter]) == 0:
continue
elif len(formula_group[formatted_filter & found_value_filter]) == 1:
found_comp_row = formula_group[formatted_filter & found_value_filter][0]
unc = found_comp_row[delta_col]
dummy, unc_formatted, round_num = _return_significant_value(unc)
value_formatted = round(row[value_col], round_num)
# cannot modify directly the row from a masked table. It is a mini table copied from the main one
# row[formatted_col] = f"{value_formatted}"
# use the index value instead
index = row["Order_index"] - 1
table_out[index][formatted_col] = f"{value_formatted}"
else:
row[formatted_col] = "error"
# FIND OUT WHAT CAN I DO HERE. THE OTHER VERSION COULD USE OLDER CODE FOR THIS PART
# third run: format comps that have same value and autofit (and formatted value) in the same comp of other molecules
comp_groups = table_out.group_by("C")
for comp_group in comp_groups.groups:
comp = comp_group["C"][0]
# skip groups with one row
if len(comp_group) == 1:
continue
# skip if there are no autofitted and formatted values in group
formatted_filter = (comp_group["Autofit"] == 'true') & (comp_group[formatted_col] != '--')
unformatted_filter = comp_group[formatted_col] == '--'
if len(comp_group[formatted_filter]) == 0:
continue
for row in comp_group[unformatted_filter]:
formula = row['Formula']
found_value_filter = np.isclose(comp_group[value_col], row[value_col])
if len(comp_group[formatted_filter & found_value_filter]) == 0:
continue
elif len(comp_group[formatted_filter & found_value_filter]) == 1:
found_comp_row = comp_group[formatted_filter & found_value_filter][0]
unc = found_comp_row[delta_col]
dummy, unc_formatted, round_num = _return_significant_value(unc)
value_formatted = round(row[value_col], round_num)
index = row["Order_index"] - 1
table_out[index][formatted_col] = f"{value_formatted}"
else:
row[formatted_col] = "error"
# fourth run: format inside molecule groups that have at last one component of the same value
formula_groups = table_out.group_by("Formula")
for formula_group in formula_groups.groups:
formula = formula_group["Formula"][0]
# skip groups with one row
if len(formula_group) == 1:
continue
# skip if there are no autofitted and formatted values in group
formatted_filter = formula_group[formatted_col] != '--'
unformatted_filter = formula_group[formatted_col] == '--'
if len(formula_group[formatted_filter]) == 0:
continue
for row in formula_group[unformatted_filter]:
comp = row["C"]
found_value_filter = np.isclose(formula_group[value_col], row[value_col])
if len(formula_group[formatted_filter & found_value_filter]) == 0:
continue
elif len(formula_group[formatted_filter & found_value_filter]) == 1:
found_comp_row = formula_group[formatted_filter & found_value_filter][0]
# Now there is no need to get the significant values. THis is straight copy paste like I did in MADCUBA
index = row["Order_index"] - 1
table_out[index][formatted_col] = found_comp_row[formatted_col]
else:
row[formatted_col] = "error"
# FIND OUT WHAT CAN I DO HERE. THE OTHER VERSION COULD USE OLDER CODE FOR THIS PART
# fifth run: format comps that have same value and autofit (and formatted value) in the same comp of other molecules (autofit not necessary)
comp_groups = table_out.group_by("C")
for comp_group in comp_groups.groups:
comp = comp_group["C"][0]
# skip groups with one row
if len(comp_group) == 1:
continue
# skip if there are no autofitted and formatted values in group
formatted_filter = comp_group[formatted_col] != '--'
unformatted_filter = comp_group[formatted_col] == '--'
if len(comp_group[formatted_filter]) == 0:
continue
for row in comp_group[unformatted_filter]:
formula = row['Formula']
found_value_filter = np.isclose(comp_group[value_col], row[value_col])
if len(comp_group[formatted_filter & found_value_filter]) == 0:
continue
elif len(comp_group[formatted_filter & found_value_filter]) == 1:
found_comp_row = comp_group[formatted_filter & found_value_filter][0]
# Now there is no need to get the significant values. THis is straight copy paste like I did in MADCUBA
index = row["Order_index"] - 1
table_out[index][formatted_col] = found_comp_row[formatted_col]
else:
row[formatted_col] = "error"
# FIND OUT WHAT CAN I DO HERE. THE OTHER VERSION COULD USE OLDER CODE FOR THIS PART
# sixth run: remaining values with no relation to other rows
unformatted_filter = table_out[formatted_col] == '--'
for row in table_out[unformatted_filter]:
formula = row['Formula']
comp = row['C']
value = row[value_col]
dummy, value_formatted, round_num = _return_significant_value(value)
index = row["Order_index"] - 1
table_out[index][formatted_col] = f"{value_formatted}"
return table_out
[docs]
def output_latex_molecular_parameters_table(table, fileout, mol_list='all'):
"""Output a Molecular Parameters Table as a filetext with code for a
LaTeX table.
Parameters
----------
table : `~astropy.table.Table`
Molecular Parameters Table.
fileout : `str` or `pathlib.Path`
Output file with the LaTeX table code.
mol_list : `list`, optional
List of molecule formulas to select for the LaTeX table.
"""
# Format table columns if not pre-formatted
if "formatted Width" not in table.colnames:
fmt_table = format_molecular_parameters_columns(table)
else:
fmt_table = table
# Select every molecule for 'all'
if mol_list == 'all':
mol_list = list(dict.fromkeys(fmt_table['Formula']))
space_between_molecules = " \\noalign{\\vskip 4pt}\n"
# Write ascii file with the latex table text.
with open(fileout, 'w') as f:
# f.write('Molecule & N & Tex & v & width \n')
# Loop through molecule formulas
for i, formula in enumerate(mol_list):
if i != 0:
f.write(space_between_molecules)
formula_table = fmt_table[fmt_table["Formula"] == formula]
nrows = len(formula_table)
previous_row_name_len = 0
for j, row in enumerate(formula_table):
# comp = row['C']
# autofit = row['Autofit']
# upper_limits_N = row['UpperLimits']
# upper_limits_Tex = row['UpperLimitsTex']
current_name = row['Label']
if nrows > 1:
if j == 0:
label = f"\\multirow{{{nrows}}}{{*}}{{{current_name}}}"
previous_row_name_len = len(label)
name = label
else:
name = ' ' * previous_row_name_len
else:
name = current_name
# In each row write the following information sequentially
f.write(f' {name} &')
# Column density
formatted_N = row['formatted N/EM']
if '<' in formatted_N:
formatted_N_latex = formatted_N.replace("<", "$<$")
f.write(f' {formatted_N_latex} &')
elif'+/-' in formatted_N:
formatted_N_latex = formatted_N.replace(" +/- ", "$\\pm$")
f.write(f' {formatted_N_latex} &')
else:
f.write(f' {formatted_N} &')
# Tex
formatted_Tex = row['formatted Tex/Te']
if'+/-' in formatted_Tex:
formatted_Tex_latex = formatted_Tex.replace(" +/- ", "$\\pm$")
f.write(f' {formatted_Tex_latex} &')
else:
f.write(f' {formatted_Tex} &')
# Velocity
formatted_vel = row['formatted Velocity']
if'+/-' in formatted_vel:
formatted_vel_latex = formatted_vel.replace(" +/- ", "$\\pm$")
f.write(f' {formatted_vel_latex} &')
else:
f.write(f' {formatted_vel} &')
# Width
formatted_width = row['formatted Width']
if'+/-' in formatted_width:
formatted_width_latex = formatted_width.replace(" +/- ", "$\\pm$")
f.write(f' {formatted_width_latex} \\\\')
else:
f.write(f' {formatted_width} \\\\')
# Jump to the next line
f.write('\n')