Abundance Map Interpolation for Astronomical Data Analysis

This Python script is designed for astronomers and astrophysicists working with stellar abundance data. It involves reading an abundance map, interpolating data across a specified grid dimension, and outputting statistics regarding element mass fractions and changes in target values versus new calculations. The script leverages NumPy for numerical operations, SciPy for interpolation, argparse for command-line argument parsing, and pickle for object serialization. Its primary function is to aid in the analysis of stellar compositions by converting abundance ratios to mass fractions and vice versa, ultimately saving the calculated statistics for further analysis.

from generateTOPStables import convert_XYZ_FeHAlphaHaHe, call_num_frac, parse_abundance_file, format_TOPS_string
import argparse
import os
from scipy.interpolate import RegularGridInterpolator, interp2d
import pickle
import numpy as np
 
def get_stats(interpF, gridDim, FeHb, AlphaHb, aHeb, numFracProgram, abunTable, inputDir, abundanceMap):
    if interpF:
        MetalAbunMap = pickle.load(open(interpF, 'rb'))
    else:
        # This run numfrac gridDim^3 times for a variety of compositions,
        #  at each it finds the X, Y, and Z values and uses these to build
        #  a 3D interpolated vector field function which can take X, Y, and Z, 
        #  and return [Fe/H], [alpha/H], and a(He) 
        A = np.empty(shape=(gridDim, gridDim, gridDim, 3))
        FeH = np.linspace(FeHb[0], FeHb[1], gridDim)
        AlphaH = np.linspace(AlphaHb[0], AlphaHb[1], gridDim)
        aHe = np.linspace(aHeb[0], aHeb[1], gridDim)
        if not abunTable:
            saveName = "{}2XYZ.p".format(numFracProgram.split('/')[-1])
            for i, feh in enumerate(FeH):
                for j, alphah in enumerate(AlphaH):
                    for k, ahe in enumerate(aHe):
                        # this callibrate.tmp file is to store the output of one
                        #  run, it will be overwritten each time and eventually
                        #  deleted to clean up
                        status = call_num_frac(numFracProgram,
                                "calibrate.tmp", feh, alphah, ahe, force=True)
                        _, oX, oY, oZ = parse_abundance_file("calibrate.tmp")
                        A[i, j, k] = np.array([oX, oY, oZ])
        else:
            saveName = f"{abunTable.split('/')[-1].split('.')[0]}2XYZ.p"
            FeH_tup = (FeH.min(), FeH.max(), FeH.shape[0])
            AlphaH_tup = (AlphaH.min(), AlphaH.max(), AlphaH.shape[0])
            aHe_tup = (aHe.min(), aHe.max(), aHe.shape[0])
 
            status = call_num_frac(numFracProgram, "calibrate.tmp",
                                   FeH_tup, AlphaH_tup, aHe_tup, force=True,
                                   abunTable=abunTable)
            _, oX, oY, oZ = parse_abundance_file("calibrate.tmp", big=True)
            A[:, :, :, 0] = oX
            A[:, :, :, 1] = oY
            A[:, :, :, 2] = oZ
 
        os.remove("calibrate.tmp")
        MetalAbunMap = RegularGridInterpolator((FeH, AlphaH, aHe), A)
 
        # force this to save to disk so we don't have to do this every time
        pickle.dump(MetalAbunMap,
                    open(os.path.join(inputDir, saveName), 'wb'))
    with open(abundanceMap, 'r') as f:
        contents = f.read().split('\n')
        pContents = [[float(y) for y in x.split(',')]
                          for x in contents
                          if x != '' and x.lstrip()[0] != '#']
    stats = np.zeros(shape=(len(pContents), 4))
    for i, composition in enumerate(pContents):
        results = convert_XYZ_FeHAlphaHaHe(MetalAbunMap,composition[0], composition[2])
        status = call_num_frac(numFracProgram, "ValidationOutput.tmp", results[0], 0.0, results[1], force=True, abunTable=abunTable)
        if int(status) == 0:
            massFraction, X, Y, Z = parse_abundance_file("ValidationOutput.tmp")
        if os.path.exists("ValidationOutput.tmp"):
            os.remove("ValidationOutput.tmp")
        stats[i] = np.array([composition[0], composition[2], X, Z])
    return stats
 
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Given an abundance map find the bounds to test over")
    parser.add_argument("numFracProgram", help="path to program to generate"
                        " Number and Mass fractions", type=str)
    parser.add_argument("abundanceMap", help="Path to abundace map csv",
                        type=str)
    parser.add_argument("FeHb", help="lower/upper bound for"
                        " FeH interpolation", type=float, nargs=2)
    parser.add_argument("AlphaHb", help="lower/upper bound for"
                        " AlphaH interpolation", type=float, nargs=2)
    parser.add_argument("aHeb", help="lower/upper bound for"
                        " aHe interpolation", type=float, nargs=2)
    parser.add_argument("--interpF", help="Pickeled Interpolation File to go"
                        " between [Fe/H], [alpha/H], a(H) and X, Y, Z",
                        type=str)
    parser.add_argument("--gridDim", type=int, help="Grid dimension to use"
                        " when generating the enhancmnet -> abundence table.",
                        default=50)
    parser.add_argument("--inputDir", type=str, help="Directory to save input"
                        " file to (such as interpolation function)",
                        default=".")
    parser.add_argument("--abunTable", type=str, help="Table to pull abundances"
                        " from. If set numFracProgram must point to executable"
                        " expedciting table input")
 
    args = parser.parse_args()
 
    stats = get_stats(args.interpF, args.gridDim, args.FeHb, args.AlphaHb, args.aHeb, args.numFracProgram, args.abunTable, args.inputDir, args.abundanceMap)
    np.save("stats.npy", stats)
 
    diff = stats[:, :2] - stats[:, 2:]
    fractionalChange = diff/stats[:, :2]
    for comp in stats:
        print(f"target X: {comp[0]}, target Z: {comp[1]}, new X: {comp[2]}, new Z: {comp[3]}")