From 4229375f6243b4d973f1892611fee435ab67a7ed Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Sun, 14 May 2017 22:02:35 -0400 Subject: [PATCH 01/19] * Modified readfile() to be friendly to headers w/tab delimiters. * Added Hgamma line to list, extinction coefficients * Created "--bluedust" flag to calculate E(B-V) from Hb/Hg. * Created metscales.calcEB_Vblue() to do E(B-V) calculation. * Added dust_blue(=False) flag to metallicity.calculation(), metallicity.run() --- .../inspectionProfiles/profiles_settings.xml | 7 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/pyMCZ.iml | 12 + .idea/vcs.xml | 6 + build/lib/pyMCZ/__init__.py | 7 + build/lib/pyMCZ/mcz.py | 747 ++++++++++ build/lib/pyMCZ/metallicity.py | 245 ++++ build/lib/pyMCZ/metscales.py | 1273 +++++++++++++++++ build/lib/pyMCZ/pylabsetup.py | 41 + build/lib/pyMCZ/testcompleteness.py | 136 ++ build/scripts-2.7/mcz.py | 747 ++++++++++ build/scripts-2.7/metallicity.py | 245 ++++ build/scripts-2.7/metscales.py | 1273 +++++++++++++++++ build/scripts-2.7/pylabsetup.py | 41 + pyMCZ/mcz.py | 30 +- pyMCZ/metallicity.py | 13 +- pyMCZ/metscales.py | 17 +- 18 files changed, 4839 insertions(+), 13 deletions(-) create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/pyMCZ.iml create mode 100644 .idea/vcs.xml create mode 100644 build/lib/pyMCZ/__init__.py create mode 100644 build/lib/pyMCZ/mcz.py create mode 100644 build/lib/pyMCZ/metallicity.py create mode 100644 build/lib/pyMCZ/metscales.py create mode 100644 build/lib/pyMCZ/pylabsetup.py create mode 100644 build/lib/pyMCZ/testcompleteness.py create mode 100755 build/scripts-2.7/mcz.py create mode 100755 build/scripts-2.7/metallicity.py create mode 100755 build/scripts-2.7/metscales.py create mode 100755 build/scripts-2.7/pylabsetup.py diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..c23ecac --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7900cb6 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c51f2e6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pyMCZ.iml b/.idea/pyMCZ.iml new file mode 100644 index 0000000..6f63a63 --- /dev/null +++ b/.idea/pyMCZ.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/build/lib/pyMCZ/__init__.py b/build/lib/pyMCZ/__init__.py new file mode 100644 index 0000000..e17c7f3 --- /dev/null +++ b/build/lib/pyMCZ/__init__.py @@ -0,0 +1,7 @@ +import os + +p = os.path.abspath('./') +if not os.path.exists(p + '/output'): + os.makedirs(p + '/output') + +__all__ = ["mcz", "metallicity", "metscales", "testcompleteness"] diff --git a/build/lib/pyMCZ/mcz.py b/build/lib/pyMCZ/mcz.py new file mode 100644 index 0000000..3b39ed6 --- /dev/null +++ b/build/lib/pyMCZ/mcz.py @@ -0,0 +1,747 @@ +#!/usr/bin/env python +import os +import sys +import argparse +import warnings + +import numpy as np +import scipy.stats as stats +from scipy.special import gammaln +from scipy import optimize +import matplotlib.pyplot as plt +from matplotlib.ticker import FormatStrFormatter +import csv as csv + +#modules of this package +import pylabsetup + +#import metallicity_save2 as metallicity +import metallicity as metallicity + +import itertools +import multiprocessing as mpc + +###versin 1.3 February 2016 +#fixed compatyibiity issue with numphy >1.9 in the val to np.histogram https://github.com/numpy/numpy/issues/6469 + +###version 1.3.1 April 2016 +#works with version 0.7 of pyqz (current version) +#previous versions only worked with version 0.5. +#a check for the version of pyqz is implememted and a different call is issued for version >0.5 and <=0.5 + + + +# Define the version of +__version__ = '1.3.1' + +NM0 = 0 # setting this to say N>0 starts the calculation at measurement N. +#this is only for exploratory purposes as the code bugs out before +#plotting and printing the results + +PROFILING = True +PROFILING = False + +alllines = ['[OII]3727', 'Hb', '[OIII]4959', '[OIII]5007', '[OI]6300', 'Ha', '[NII]6584', '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532'] +morelines = ['E(B-V)', 'dE(B-V)', 'scale_blue', 'd scale_blue'] + +MAXPROCESSES = 10 + +#pickle may not be installed +NOPICKLE = False +try: + import pickle +except ImportError: + NOPICKLE = True + +CLOBBER = False +VERBOSE = False +UNPICKLE = False +ASCIIOUTPUT = False +ASCIIDISTRIB = False +RUNSIM = True +NOPLOT = False +BINMODE = 'k' +binning = {'bb': 'Bayesian blocks', 'k': "Knuth's rule", 'd': "Doane's formula", 's': r'$\sqrt{N}$', 't': r'$2 N^{1/3}$', 'kd': 'Kernel Density'} + +MP = False + + +def is_number(s): + if not type(s) is np.string_: + try: + float(s) + return True + except ValueError: + return False + return False + + +def smart_open(logfile=None): + if logfile and logfile != '-': + fh = open(logfile, 'w') + else: + fh = sys.stdout + return fh + + +def getknuth(m, data, N): + m = int(m) + if m > N: + return [-1] + bins = np.linspace(min(data), max(data), int(m) + 1) + try: + nk, bins = np.histogram(data, bins) + return -(N * np.log(m) + gammaln(0.5 * m) - m * gammaln(0.5) - gammaln(N + 0.5 * m) + np.sum(gammaln(nk + 0.5))) + except: + return [-1] + + +def knuthn(data, maxM=None): + assert data.ndim == 1, "data must be 1D array to calculate Knuth's number of bins" + N = data.size + if not maxM: + maxM = 5 * np.sqrt(N) + m0 = 2.0 * N ** (1. / 3.) + gk = getknuth + if gk == [-1]: + return m0, 't' + mkall = optimize.fmin(gk, m0, args=(data, N), disp=VERBOSE, maxiter=30) # , maxfun=1000)#[0] + mk = mkall[0] + if mk > maxM or mk < 0.3 * np.sqrt(N): + mk = m0 + return mk, 't' + return mk, 0 + + +############################################################################## +##The input data +############################################################################## +##Reads the flux file and returns it as an array. +##Ignores non-numeric lines +##Returns (flux array,num) +############################################################################## +def readfile(filename): + noheader = 1 + findex = -1 + f = open(filename, 'r') + l0 = f.readline().replace(' ', '') + l1 = f.readline().split() + if l0.startswith('#') or l0.startswith(';'): + header = l0.strip().replace(";", '').replace("#", '').split(',') + header[0] = header[0].replace(' ', '') + header = header[:len(l1)] + else: + noheader = 0 + header = ['galnum'] + alllines + ['flag'] + morelines + header = header[:len(l1)] + + formats = ['S10'] + ['f'] * (len(header) - 1) + if 'flag' in header: + findex = header.index('flag') + formats[findex] = 'S10' + + bstruct = {} + for i, k in enumerate(header): + bstruct[k] = [i, 0] + b = np.loadtxt(filename, skiprows=noheader, dtype={'names': header, 'formats': formats}, comments=';') + if b.size == 1: + b = np.atleast_1d(b) + + for i, k in enumerate(header): + if not k == 'flag' and is_number(b[k][0]): + bstruct[k][1] = np.count_nonzero(b[k]) + sum(np.isnan(b[k])) + j = len(b['galnum']) + return b, j, bstruct + + +def ingest_data(filename, path): + ###Initialize### + measfile = os.path.join(path, filename + "_meas.txt") + errfile = os.path.join(path, filename + "_err.txt") + + ###read the max, meas, min flux files### + meas, nm, bsmeas = readfile(measfile) + err, nm, bserr = readfile(errfile) + try: + snr = (meas[: + ,1:].view(np.float32).reshape(meas[: + ,1:].shape + (-1, ))[: + ,1:]) / (err[: + ,1:].view(np.float32).reshape(err[: + ,1:].shape + (-1, ))[: + ,1:]) + if snr[~np.isnan(snr)].any() < 3: + raw_input('''WARNING: signal to noise ratio smaller than 3 + for at least some lines! You should only use SNR>3 + measurements (return to proceed)''') + except (IndexError, TypeError): + pass + return (filename, meas, err, nm, path, (bsmeas, bserr)) + + +def input_data(filename, path): + p = os.path.join(path, "input") + assert os.path.isdir(p), "bad data directory %s" % p + if os.path.isfile(os.path.join(p, filename + '_err.txt')): + if os.path.isfile(os.path.join(p, filename + '_meas.txt')): + return ingest_data(filename, path=p) + print "Unable to find _meas and _err files ", filename + '_meas.txt', filename + '_err.txt', "in directory ", p + return -1 + + +############################################################################## +##returns a random distribution. In the deployed version of the code this is a gaussian distribution, but the user can include her or his distribution. +##return a random sample of size n +############################################################################## +def errordistrib(distrargs, n, distype='normal'): + if distype == 'normal': + try: + mu, sigma = distrargs + except: + print "for normal distribution distargs must be a 2 element tuple" + return -1 + return np.random.normal(mu, sigma, n) + else: + print "distribution not supported" + return -1 + + +############################################################################## +##returns appropriate bin size for the number of data +##mode 'k' calculates this based on Knuth's rule +##mode 'd' calculates this based on Doane's formula +##mode 's' calculates this based on sqrt of number of data +##mode 't' calculates this based on 2*n**1/3 (default) +############################################################################## +def getbinsize(n, data, ): + if BINMODE == 'd': + g1 = np.abs(stats.mstats.moment(data, moment=3)) # /data.std()) + s1 = np.sqrt(float(n) / 6.0) + #s1=1.0/np.sqrt(6.*(n-2.)/((n+1.)*(n+3.))) + k = 1 + np.log2(n) + np.log2(1 + (g1 * s1)), 0 + elif BINMODE == 's': + k = np.sqrt(n), 0 + elif BINMODE == 't': + k = 2. * n ** (1. / 3.), 0 + else: + k = knuthn(data) + return k + + +############################################################################## +##Check if hist files already exist and need to be replaced +############################################################################## +def checkhist(snname, Zs, nsample, i, path): + global CLOBBER + + name = '%s_n%d_%s_%d' % ((snname, nsample, Zs, i + 1)) + outdir = os.path.join(path, 'hist') + outfile = os.path.join(outdir, name + ".pdf") + if os.path.isfile(outfile) and not CLOBBER: + replace = raw_input("replacing existing image files, starting with: %s ? [Y/n]\n" % outfile).lower() + assert(not (replace.startswith('n'))), "save your existing output directory under another name first" + CLOBBER = True + + +############################################################################## +##Save the result as histogram as name +############################################################################## +#@profile +def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False, fs=24, reserr=None): + global BINMODE + #global NOPLOT + + name = '%s_n%d_%s_%d' % ((snname, nsample, Zs, i + 1)) + outdir = os.path.join(path, 'hist') + outfile = os.path.join(outdir, name + ".pdf") + if not NOPLOT: + fig = plt.figure(figsize=(11, 8)) + plt.clf() + + ####kill outliers, infinities, and bad distributions### + data = data[np.isfinite(data)] + + n = data.shape[0] + kde = None + if not n > 0: + if verbose: + print "data must be an actual distribution (n>0 elements!, %s)" % Zs + return "-1,-1,_1", [], kde + + if data.shape[0] <= 0 or np.sum(data) <= 0: + print '{0:15} {1:20} {2:>13d} {3:>7d} {4:>7d} '.format(snname, Zs, -1, -1, -1) + return "-1, -1, -1", [], kde + try: + ###find C.I.### + median, pc16, pc84 = np.percentile(data, [50, 16, 84]) + std = np.std(data) + left = pc16 + right = pc84 + maxleft = median - std * 5 + maxright = median + std * 5 + if "%2f" % maxright == "%2f" % maxleft: + maxleft = median - 1 + maxright = median + 1 + if round(right, 6) == round(left, 6) and round(left, 6) == round(median, 6): + print '{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(snname, Zs, median, 0, 0) + if reserr: + print '+/- {0:.3f}'.format(reserr) + return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde # "-1,-1,-1",[] + ###print out the confidence interval### + print '{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f}'.format(snname, Zs, round(median, 3), round(median - left, 3), round(right - median, 3)) + if reserr: + print '+/- {0:.3f}'.format(reserr) + alpha = 1.0 + + ######histogram###### + if BINMODE == 'kd': + ##if sklearn is available use it to get Kernel Density + try: + from sklearn.neighbors import KernelDensity + except ImportError: + print '''sklearn is not available, + thus we cannot compute kernel density. + switching to bayesian blocks''' + BINMODE = 'bb' + if BINMODE == 'kd': + ##bw is chosen according to Silverman 1986 + bw = 1.06 * std * n ** (-0.2) + numbin, bm = getbinsize(data.shape[0], data) + distrib = np.histogram(data, bins=int(numbin), density=True) + ###make hist### + counts, bins = distrib[0], distrib[1] + widths = np.diff(bins) + countsnorm = counts / np.max(counts) + + if bw > 0: + kde = KernelDensity(kernel='gaussian', bandwidth=bw).fit(data[: + , np.newaxis]) + kdebins = np.linspace(maxleft, maxright, 1000)[: + , np.newaxis] + log_dens = kde.score_samples(kdebins) + dens = np.exp(log_dens) + norm = countsnorm.sum() * (bins[1] - bins[0]) / dens.sum() / (kdebins[1] - kdebins[0]) + if not NOPLOT: + plt.fill(kdebins[: + ,0], dens * norm, fc='#7570b3', alpha=0.8) + alpha = 0.5 + + ###find appropriate bin size### + else: + if BINMODE == 'bb': + ##if astroML is available use it to get Bayesian blocks + bm = 0 + try: + from astroML.plotting import hist as amlhist + if BINMODE == 'bb': + distrib = amlhist(data, bins='blocks', normed=True) + if not NOPLOT: + plt.clf() + except ImportError: + print "bayesian blocks for histogram requires astroML to be installed" + print "defaulting to Knuth's rule " + ##otherwise + numbin, bm = getbinsize(data.shape[0], data) + distrib = np.histogram(data, bins=int(numbin), density=True) + else: + numbin, bm = getbinsize(data.shape[0], data) + distrib = np.histogram(data, bins=int(numbin), density=True) + + ###make hist### + counts, bins = distrib[0], distrib[1] + widths = np.diff(bins) + countsnorm = counts / np.max(counts) + + ###plot hist### + if NOPLOT: + return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde + + plt.bar(bins[:-1], countsnorm, widths, color=['gray'], alpha=alpha) + plt.minorticks_on() + plt.gca().xaxis.set_major_formatter(FormatStrFormatter('%.2f')) + plt.xlim(maxleft, maxright) + + #the following lines assure the x tick label is + #within the length of the x axis + xticks = plt.xticks()[0] + dx = xticks[-1] - xticks[-2] + xticks = xticks[(xticks < maxright) * (xticks > maxleft)] + if (maxright - xticks[-1]) < 0.25 * dx: + maxright = maxright + 0.25 * dx + maxleft = maxleft - 0.25 * dx + plt.xlim(maxleft, maxright) + plt.xticks(xticks, ['%.2f' % s for s in xticks]) + + plt.ylim(0, 1.15) + plt.yticks(np.arange(0.2, 1.3, 0.2), ["%.1f" % x for x in np.arange(0.2, 1.1, 0.2)]) + plt.axvspan(left, right, color='DarkOrange', alpha=0.4) + plt.axvline(x=median, linewidth=2, color='white', ls='--') + + #labels and legends + st = '%s ' % (snname) + plt.annotate(st, xy=(0.13, 0.6), xycoords='axes fraction', size=fs, fontweight='bold') + st = '%s ' % (Zs.replace('_', ' ')) + plt.annotate(st, xy=(0.61, 0.93), xycoords='axes fraction', fontsize=fs, fontweight='bold') + st = 'measurement %d of %d\n %s\nmedian: %.3f\n16th Percentile: %.3f\n84th Percentile: %.3f' % (i + 1, nmeas, measnames[i], round(median, 3), round(left, 3), round(right, 3)) + plt.annotate(st, xy=(0.61, 0.65), xycoords='axes fraction', fontsize=fs) + effectiven = len(data[~np.isnan(data)]) + if effectiven: + st = 'MC sample size %d (%d)\nhistogram rule: %s' % (effectiven, nsample, binning[BINMODE]) + if bm: + if effectiven < nsample: + st = 'MC sample size %d (%d)\nhistogram rule: %s' % (effectiven, nsample, binning[bm]) + else: + st = 'MC sample size %d\nhistogram rule: %s' % (nsample, binning[bm]) + plt.annotate(st, xy=(0.61, 0.55), xycoords='axes fraction', fontsize=fs - 5) + if "E(B-V)" in Zs: + plt.xlabel('E(B-V) [mag]') + outfile = outfile.replace('(', '').replace(')', '') + elif "logR23" in Zs: + plt.xlabel('logR23') + else: + plt.xlabel('12+log(O/H)') + plt.ylabel('relative counts') + plt.savefig(outfile, format='pdf') + plt.close(fig) + + return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde + + except (OverflowError, AttributeError, ValueError): + if VERBOSE: + print data + print name, 'had infinities (or something in plotting went wrong)' + return "-1, -1,-1", [], None + + +def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr, verbose, res, scales, nps, logf))): + logf = sys.stdout + print >> logf, "\n\nreading in measurements ", i + 1 + fluxi = {} # np.zeros((len(bss[0]),nm),float) + for k in bss[0].iterkeys(): + print >> logf, '{0:15} '.format(k), + print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) + fluxi[k] = flux[k][i] * np.ones(len(sample[i])) + err[k][i] * sample[i] + warnings.filterwarnings("ignore") + success = metallicity.calculation(scales[i], fluxi, nm, mds, nps, logf, disp=disp, dust_corr=dust_corr, verbose=verbose) + if success == -1: + print >> logf, "MINIMUM REQUIRED LINES: '[OII]3727','[OIII]5007','[NII]6584','[SII]6717'" + + for key in scales[i].mds.iterkeys(): + if key in res.keys(): + res[key][i] = scales[i].mds[key] + if res[key][i] is None: + res[key][i] = [float('NaN')] * len(sample) + return res + + +############################################################################## +## The main function. takes the flux and its error as input. +## filename - a string 'filename' common to the three flux files +## flux - np array of the fluxes +## err - the flux errors, must be the same dimension as flux +## nsample - the number of samples the code will generate. Default is 100 +## errmode - determines which method to choose the bin size. +## mode 'k' calculates this based on Knuth's rule (default) +## mode 'd' calculates this based on Doane's formula +## mode 's' calculates this based on sqrt of number of data +## mode 't' calculates this based on 2*n**1/3 +############################################################################## +#@profile +def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickle=False, dust_corr=True, verbose=False, fs=24): + global RUNSIM # ,BINMODE#,NOPLOT + assert(len(flux[0]) == len(err[0])), "flux and err must be same dimensions" + assert(len(flux['galnum']) == nm), "flux and err must be of declaired size" + assert(len(err['galnum']) == nm), "flux and err must be same dimensions" + + #increasing sample by 10% to assure robustness against rejected samples + newnsample = nsample + if nsample > 1: + newnsample = int(nsample + 0.1 * nsample) + + p = os.path.join(path, '..') + + ###retrieve the metallicity keys + Zs = metallicity.get_keys() + Zserr = metallicity.get_errkeys() + + ###make necessary paths for output files + if not os.path.exists(os.path.join(p, 'output', '%s' % name)): + os.makedirs(os.path.join(p, 'output', '%s' % name)) + if not os.path.exists(os.path.join(p, 'output', '%s' % name, 'hist')): + os.makedirs(os.path.join(p, 'output', '%s' % name, 'hist')) + binp = os.path.join(p, 'output', '%s' % name) + picklefile = os.path.join(binp, '%s_n%d.pkl' % (name, nsample)) + if VERBOSE: + print "output files will be stored in ", binp + if not CLOBBER and not NOPLOT: + for key in Zs: + for i in range(NM0, nm): + checkhist(name, key, nsample, i, binp) + if unpickle: + RUNSIM = False + if not os.path.isfile(picklefile): + raw_input("missing pickled file for this simulation: name, nsample.\nrun the MonteCarlo? Ctr-C to exit, Return to continue?\n") + RUNSIM = True + else: + pklfile = open(picklefile, 'rb') + res = pickle.load(pklfile) + + if RUNSIM: + ###Sample 'nsample' points from a gaussian centered on 0 with std 1 + mu = 0 + sigma = 1 + dargs = (mu, sigma) + if nsample == 1: + sample = [np.array([mu]) for i in range(NM0, nm)] + else: + sample = [errordistrib(dargs, newnsample) for i in range(NM0, nm)] + if sample == -1: + return -1 + ###Start calculation### + ## the flux to be feed to the calculation will be + ## flux + error*i + ## where i is the sampled gaussian + if VERBOSE: + print "Starting iteration" + + #initialize the dictionary + res = {} + reserr = {} + for key in Zs: + res[key] = [[] for i in range(NM0, nm)] + for key in Zserr: + reserr[key] = [[] for i in range(NM0, nm)] + #use only valid inputs + delkeys = [] + for k in bss[0].iterkeys(): + if k == 'flag' or k == 'galnum' or bss[0][k][1] == float('nan'): # or bss[1][k][1]==bss[0][k][1]: + delkeys.append(k) + for k in delkeys: + del bss[0][k] + del bss[1][k] + + import metscales as ms + nps = min(mpc.cpu_count() - 1 or 1, MAXPROCESSES) + + if multiproc and nps > 1: + scales = [ms.diagnostics(newnsample, logf, nps) for i in range(nm)] + + print >> logf, "\n\n\nrunning on %d threads\n\n\n" % nps + second_args = [sample, flux, err, nm, bss, mds, VERBOSE, dust_corr, VERBOSE, res, scales, nps, logf] + pool = mpc.Pool(processes=nps) # depends on available cores + rr = pool.map(calc, itertools.izip(range(NM0, nm), itertools.repeat(second_args))) # for i in range(nm): result[i] = f(i, second_args) + for ri, r in enumerate(rr): + for kk in r.iterkeys(): + res[kk][ri] = r[kk][ri] + + for ri, r in enumerate(rr): + for kk in r.iterkeys(): + res[kk][ri] = r[kk][ri] + pool.close() # not optimal! but easy + pool.join() + for key in scales[i].mds.iterkeys(): + if key in Zs: + res[key] = np.array(res[key]).T + elif key in reserr.keys(): + reserr[key] = np.array(reserr[key]).T + if res[key][i] is None: + res[key][i] = [float('NaN')] * len(sample) + + if VERBOSE: + print "Iteration Complete" + else: + #looping over nm spectra + for i in range(NM0, nm): + scales = ms.diagnostics(newnsample, logf, nps) + print >> logf, "\n\n measurements ", i + 1 + fluxi = {} + + for k in bss[0].iterkeys(): + print >> logf, '{0:15} '.format(k), + print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) + fluxi[k] = flux[k][i] * np.ones(len(sample[i])) + err[k][i] * sample[i] + warnings.filterwarnings("ignore") + print >> logf, "" + + success = metallicity.calculation(scales, fluxi, nm, mds, 1, logf, disp=VERBOSE, dust_corr=dust_corr, verbose=VERBOSE) + if success == -1: + print "MINIMUM REQUIRED LINES: [OII]3727 & [OIII]5007, or [NII]6584, and Ha & Hb if you want dereddening" + #continue + + + for key in scales.mds.iterkeys(): + if not key in Zs: + continue + res[key][i] = scales.mds[key] + if res[key][i] is None: + res[key][i] = [float('NaN')] * newnsample + elif len(res[key][i]) < newnsample: + res[key][i] = res[key][i] + [float('NaN')] * (newnsample - len(res[key][i])) + + for key in scales.mds.iterkeys(): + if key in Zs: + res[key] = np.array(res[key]).T + if VERBOSE: + print "Iteration Complete" + + #"WE CAN PICKLE THIS!" + #pickle this realization + if not NOPICKLE: + pickle.dump(res, open(picklefile, 'wb')) + + from matplotlib.font_manager import findfont, FontProperties + + if 'Time' not in findfont(FontProperties()): + fs = 20 + if VERBOSE: + print "FONT: %s, %d" % (findfont(FontProperties()), fs) + + ###Bin the results and save### + print "\n\n" + print '{0:15} {1:20} {2:>13} -{3:>5} +{4:>5} {5:11} {6:>7}'.format("SN", "diagnostic", "metallicity", "34%", "34%", "(sample size:", '%d)' % nsample) + for i in range(NM0, nm): + if ASCIIOUTPUT: + fi = open(os.path.join(binp, '%s_n%d_%d.txt' % (name, nsample, i + 1)), 'w') + fi.write("%s\t Median Oxygen abundance (12+log(O/H))\t 16th percentile\t 84th percentile\n" % name) + + boxlabels = [] + datas = [] + print "\n\nmeasurement %d : %s-------------------------------------------------------------" % (i + 1, flux[i]['galnum']) + for key in Zs: + if nsample == -1: + try: + if ~np.isnan(res[key][i][0]): + print '{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(name + ' %d' % (i + 1), key, res[key][i][0], 0, 0) + except IndexError: + pass + else: + reserr = None + try: + if sum(~np.isnan(res[key][: + ,i])) > 0: + if ASCIIDISTRIB: + with open(os.path.join(binp, '%s_n%d_%s_%d.csv' % (name, nsample, key, i + 1)), "wb") as fidist: + writer = csv.writer(fidist) + writer.writerow(res[key][: + ,i]) + if 'PM14' in key: + print reserr['PM14err'] + reserr = np.sqrt(~np.nansum(reserr['PM14err'][: + ,i] ** 2)) + sh, data, kde = savehist(res[key][: + ,i], name, key, nsample, i, binp, nm, flux[:]['galnum'], verbose=verbose, fs=fs, reserr=reserr) + s = key + "\t " + sh + '\n' + if ASCIIOUTPUT: + fi.write(s) + if key not in ["E(B-V)", "logR23"]: + boxlabels.append(key.replace('_', ' ')) + datas.append(data) + if BINMODE == 'kd' and not NOPICKLE: + pickleKDEfile = os.path.join(binp + '/%s_n%d_%s_%d_KDE.pkl' % (name, nsample, key, i + 1)) + if VERBOSE: + print "KDE files will be stored in ", pickleKDEfile + pickle.dump(kde, open(pickleKDEfile, 'wb')) + except (IndexError, TypeError): + pass + #make box_and_whiskers plot + fig = plt.figure(figsize=(8, 15)) + fig.subplots_adjust(bottom=0.18, left=0.18) + ax = fig.add_subplot(111) + plt.grid() + if len(datas) == 0: + continue + + bp = ax.boxplot(datas, patch_artist=True) + for box in bp['boxes']: + box.set(color='#7570b3', linewidth=2) + box.set(facecolor='DarkOrange', alpha=0.4) + for whisker in bp['whiskers']: + whisker.set(color='#7570b3', linewidth=2) + for cap in bp['caps']: + cap.set(color='#7570b3', linewidth=2) + for median in bp['medians']: + median.set(color='k', linewidth=2) + for flier in bp['fliers']: + flier.set(marker='o', color='#7570b3', alpha=0.4) + plt.title("measurement %d: %s" % (i + 1, flux[i]['galnum'])) + plt.xticks(range(1, len(boxlabels) + 1), boxlabels, rotation=90, fontsize=fs - 5) + plt.fill_between(range(1, len(boxlabels) + 1), [8.76] * len(boxlabels), [8.69] * len(boxlabels), facecolor='black', alpha=0.3) + plt.text(1.2, 8.705, "Solar Oxygen Abundance", alpha=0.7) + plt.gca().yaxis.set_major_formatter(FormatStrFormatter('%.2f')) + plt.ylabel('12+log(O/H)', fontsize=fs) + plt.savefig(binp + "/" + name + "_boxplot_n%d_%d.pdf" % (nsample, i + 1), format='pdf') + if ASCIIOUTPUT: + fi.close() + if VERBOSE: + print "uncertainty calculation complete" + + #del datas + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('name', metavar='', type=str, help="the SN file name (root of the _min,_max file names") + parser.add_argument('nsample', metavar='N', type=int, help="number of iterations, minimum 100 (or 0 for no MC sampling)") + parser.add_argument('--clobber', default=False, action='store_true', help="replace existing output") + parser.add_argument('--binmode', default='k', type=str, choices=['d', 's', 'k', 't', 'bb', 'kd'], help='''method to determine bin size + {d: Duanes formula, s: n^1/2, t: 2*n**1/3(default), k: Knuth's rule, + bb: Bayesian blocks, kd: Kernel Density}''') + parser.add_argument('--path', default=None, type=str, help='''input/output path (must contain the input _max.txt and + _min.txt files in a subdirectory sn_data)''') + parser.add_argument('--unpickle', default=False, action='store_true', help="read the pickled realization instead of making a new one") + + parser.add_argument('--verbose', default=False, action='store_true', help="verbose mode") + parser.add_argument('--log', default=None, type=str, help="log file, if not passed defaults to standard output") + parser.add_argument('--nodust', default=False, action='store_true', help=" don't do dust corrections (default is to do it)") + parser.add_argument('--noplot', default=False, action='store_true', help=" don't plot individual distributions (default is to plot all distributions)") + parser.add_argument('--asciiout', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") + parser.add_argument('--asciidistrib', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") + parser.add_argument('--md', default='all', type=str, help='''metallicity diagnostic to calculate. + default is 'all', options are: + D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, KD02, DP00 (deprecated), P01''') + parser.add_argument('--multiproc', default=False, action='store_true', help=" multiprocess, with number of threads max(available cores-1, MAXPROCESSES)") + args = parser.parse_args() + + global CLOBBER + global VERBOSE + global BINMODE + global ASCIIOUTPUT + global ASCIIDISTRIB + global NOPLOT + CLOBBER = args.clobber + VERBOSE = args.verbose + BINMODE = args.binmode + NOPLOT = args.noplot + ASCIIOUTPUT = args.asciiout + ASCIIDISTRIB = args.asciidistrib + + if args.unpickle and NOPICKLE: + args.unpickle = False + raw_input("cannot use pickle on this machine, we won't save and won't read saved pickled realizations. Ctr-C to exit, Return to continue?\n") + + if args.path: + path = args.path + else: + assert (os.getenv("MCMetdata")), ''' the _max, _min (and _med) data must live in a folder named sn_data. + pass a path to the sn_data folder, or set up the environmental variable + MCMetdata pointing to the path where sn_data lives ''' + path = os.getenv("MCMetdata") + assert(os.path.isdir(path)), "pass a path or set up the environmental variable MCMetdata pointing to the path where the _min _max _med files live" + if args.nsample == 1: + print "CALCULATING METALLICITY WITHOUT GENERATING MC DISTRIBUTIONS" + if args.nsample == 1 or args.nsample >= 10: + fi = input_data(args.name, path=path) + if fi != -1: + logf = smart_open(args.log) + run(fi, args.nsample, args.md, args.multiproc, logf, unpickle=args.unpickle, dust_corr=(not args.nodust), verbose=VERBOSE) + if args.log: + logf.close() + else: + print "nsample must be at least 100" + + +if __name__ == "__main__": + if PROFILING: + import cProfile + cProfile.run("main()") + else: + main() diff --git a/build/lib/pyMCZ/metallicity.py b/build/lib/pyMCZ/metallicity.py new file mode 100644 index 0000000..afc7ec1 --- /dev/null +++ b/build/lib/pyMCZ/metallicity.py @@ -0,0 +1,245 @@ +############################################################################## +## Calculates oxygen abundance (here called metalicity) based on strong emission lines, +## based on code originally written in IDL by Lisa Kewley (Kewley & Ellison 2008). Outputs +## oxygen abundance in many different diagnostics (see Bianco et al. 2016). +## +##new calculation based on the most recent version of the .pro file. +## +##inputs: +## measured - flux data, must be the format returned by readfile() +## num - number of spectra for which to calculate metallicity, also returned by readfile() +## outfilename - the name of the file the results will be appended to +## red_corr - reddening correction flag - True by default +## disp - if True prints the results, default False +############################################################################## + +import sys +import os +import numpy as np + +IGNOREDUST = False +MP = True +FIXNEGATIVES = True # set to true if no negative flux measurements should be allowed. all negative flux measurements are set to 0 + +##list of metallicity methods, in order calculated +Zs = ["E(B-V)", # based on Halpha, Hbeta + "logR23", # Hbeta, [OII]3727, [OIII]5007, [OIII]4959 + + "D02", # Halpha, [NII]6584 + "Z94", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "M91", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "C01_N2S2", # [OII]3727, [OIII]5007, [NII]6584, [SII]6717 + "C01_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + + "P05", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "P01", # available but deprecated + "PP04_N2Ha", # Halpha, [NII]6584 + "PP04_O3N2", # Halpha, Hbeta,[OIII]5007, [NII]6584 + "DP00", # S23 available but deprecated + "P10_ONS", "P10_ON", + "M08_R23", "M08_N2Ha", "M08_O3Hb", "M08_O2Hb", "M08_O3O2", "M08_O3N2", + "M13_O3N2", "M13_N2", + "D13_N2S2_O3S2", "D13_N2S2_O3Hb", + "D13_N2S2_O3O2", "D13_N2O2_O3S2", + "D13_N2O2_O3Hb", "D13_N2O2_O3O2", + "D13_N2Ha_O3Hb", "D13_N2Ha_O3O2", + "KD02_N2O2", # Halpha, Hbeta, [OII]3727, [NII]6584 + "KD02_N2S2", + "KK04_N2Ha", # Halpha, Hbeta, [OII]3727, [NII]6584 + "KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "KD02comb", + "PM14"] # ,"KK04comb"] +#'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' + +Zserr = ['PM14err'] # ,"KK04comb"] +#'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' + + +def get_keys(): + return Zs + + +def get_errkeys(): + return Zserr + + +def printsafemulti(string, logf, nps): + #this is needed because dealing with a log output with multiprocessing + #is painful. but it introduces a bunch of if checks. + #if anyone has a better solution please let me know! + if nps == 1: + print >> logf, string + else: + print string + + +############################################################################## +##fz_roots function as used in the IDL code FED:reference the code here! +############################################################################## + +#@profile +def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=False, verbose=False): + + global IGNOREDUST + mscales.setdustcorrect() + raw_lines = {} + raw_lines['[OIII]5007'] = np.array([float('NaN')]) + raw_lines['Hb'] = np.array([float('NaN')]) + raw_lines['Hz'] = np.array([float('NaN')]) + for k in measured.iterkeys(): + #kills all non-finite terms + measured[k][~(np.isfinite(measured[k][:]))] = 0.0 + if FIXNEGATIVES: + measured[k][measured[k] < 0] = 0.0 + raw_lines[k] = measured[k] + + ######we trust QM better than we trust the measurement of the [OIII]4959 + ######which is typical low S/N so we set it to [OIII]5007/3. + ######change this only if youre spectra are very high SNR + raw_lines['[OIII]4959'] = raw_lines['[OIII]5007'] / 3. + raw_lines['[OIII]49595007'] = raw_lines['[OIII]4959'] + raw_lines['[OIII]5007'] + mscales.setHab(raw_lines['Ha'], raw_lines['Hb']) + + #if Ha or Hb is zero, cannot do red correction + if dust_corr and mscales.hasHa and mscales.hasHb: + with np.errstate(invalid='ignore'): + mscales.calcEB_V() + elif dust_corr and not IGNOREDUST: + + if nps > 1: + print '''WARNING: reddening correction cannot be done + without both H_alpha and H_beta measurement!!''' + + else: + response = raw_input('''WARNING: reddening correction cannot be done without both H_alpha and H_beta measurement!! + Continuing without reddening correction? [Y/n]\n''').lower() + assert(not (response.startswith('n'))), "please fix the input file to include Ha and Hb measurements" + + IGNOREDUST = True + dust_corr = False + mscales.mds['E(B-V)'] = np.ones(len(raw_lines['Ha'])) * 1e-5 + else: + mscales.unsetdustcorrect() + mscales.mds['E(B-V)'] = np.ones(len(raw_lines['Ha'])) * 1e-5 + + for k in ['[OII]3727', '[OIII]5007', '[OI]6300', '[OIII]4959', + '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532' + '[OII]3727', '[OIII]5007', '[OI]6300', '[OIII]4959', + '[NII]6584', '[SIII]9532']: + if k not in raw_lines or (len(raw_lines[k]) == 1 and np.isnan(raw_lines[k][0])): + raw_lines[k] = np.array([0.] * num) + + mscales.setOlines(raw_lines['[OII]3727'], raw_lines['[OIII]5007'], raw_lines['[OI]6300'], raw_lines['[OIII]4959']) + mscales.setSII(raw_lines['[SII]6717'], raw_lines['[SII]6731'], raw_lines['[SIII]9069'], raw_lines['[SIII]9532']) + mscales.setNII(raw_lines['[NII]6584']) + + if mscales.checkminimumreq(dust_corr, IGNOREDUST) == -1: + return -1 + + mscales.calcNIIOII() + mscales.calcNIISII() + + mscales.calcR23() + #mscales.calcS23() + + mscales.initialguess() + mds = mds.split(',') + + + #mscales.printme() + if verbose: + print "calculating metallicity diagnostic scales: ", mds + if 'all' in mds: + mscales.calcD02() + if os.getenv("PYQZ_DIR"): + cmd_folder = os.getenv("PYQZ_DIR") + '/' + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + mscales.calcpyqz() + else: + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable : + export PYQZ_DIR="your/path/where/pyqz/resides/ in bash, for example, if you want this scale. ''', logf, nps) + + mscales.calcZ94() + mscales.calcM91() + + mscales.calcPP04() + + #mscales.calcP05() + mscales.calcP10() + + mscales.calcM08() + mscales.calcM13() + + mscales.calcKD02_N2O2() + mscales.calcKK04_N2Ha() + + mscales.calcKK04_R23() + mscales.calcKDcombined() + + if 'DP00' in mds: + mscales.calcDP00() + if 'P01' in mds: + mscales.calcP01() + + if 'D02' in mds: + mscales.calcD02() + if 'D13' in mds: + if os.getenv("PYQZ_DIR"): + cmd_folder = os.getenv("PYQZ_DIR") + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + #mscales.calcpyqz() + #in order to see the original pyqz plots + #call pyqz with option plot=True by + #using the commented line below instead + mscales.calcpyqz(plot=disp) + else: + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable + PYQZ_DIR if you want this scale. ''', logf, nps) + + if 'D13all' in mds: + if os.getenv("PYQZ_DIR"): + cmd_folder = os.getenv("PYQZ_DIR") + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + #mscales.calcpyqz() + #in order to see the original pyqz plots + #call pyqz with option plot=True by + #using the commented line below instead + mscales.calcpyqz(plot=disp, allD13=True) + else: + printsafemulti('''set path to pyqz as environmental variable +PYQZ_DIR if you want this scale. ''', logf, nps) + + if 'PM14' in mds: + if os.getenv("HIICHI_DIR"): + cmd_folder = os.getenv("HIICHI_DIR") + '/' + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + mscales.calcPM14() + if 'PP04' in mds: + mscales.calcPP04() + if 'Z94' in mds: + mscales.calcZ94() + if 'M91' in mds: + mscales.calcM91() + if 'P10' in mds: + mscales.calcP10() + if 'M13' in mds: + mscales.calcM13() + if 'M08all' in mds: + mscales.calcM08(allM08=True) + elif 'M08' in mds: + mscales.calcM08() + if 'P05' in mds: + mscales.calcP05() + if 'C01' in mds: + mscales.calcC01_ZR23() + if 'KD02' in mds: + mscales.calcKD02_N2O2() + mscales.calcKK04_N2Ha() + mscales.calcKK04_R23() + mscales.calcKDcombined() diff --git a/build/lib/pyMCZ/metscales.py b/build/lib/pyMCZ/metscales.py new file mode 100644 index 0000000..bb059ed --- /dev/null +++ b/build/lib/pyMCZ/metscales.py @@ -0,0 +1,1273 @@ +import numpy as np +#import sys +import scipy.stats as stats +import numpy.polynomial.polynomial as nppoly +from metallicity import get_keys, printsafemulti + +niter = 5 # number of iteations+1 for KD02 methods + +k_Ha = 2.535 # CCM Rv=3.1 +k_Hb = 3.609 # CCM Rv=3.1 + +#k_O1=2.661 # CCM Rv=3.1 +k_O2 = 4.771 # CCM Rv=3.1 +k_O35007 = 3.341 # CCM Rv=3.1 +k_O34959 = 3.384 # CCM Rv=3.1 +k_O3 = (k_O35007 + k_O34959) / 2. + +k_N2 = 2.443 # CCM Rv=3.1 +k_S2 = 2.381 # CCM Rv=3.1 + +k_S3 = 1 # guess for CCM Rv=3.1 + +global DUSTCORRECT +DUSTCORRECT = True + +''' +R23_coef=np.zeros((5,7)) # coefficients from model grid fits +R23c0=[-3267,-3727.42,-4282.30,-4745.18,-4516.46,-3509.63,-1550.53] +R23_coef[:,0]=[-3267.93,1611.04,-298.187,24.5508,-0.758310] # q=5e6 +R23_coef[:,1]=[-3727.42,1827.45,-336.340,27.5367,-0.845876] # q=1e7 +R23_coef[:,2]=[-4282.30,2090.55,-383.039,31.2159,-0.954473] # q=2e7 +R23_coef[:,3]=[-4745.18,2309.42,-421.778,34.2598,-1.04411] # q=4e7 +R23_coef[:,4]=[-4516.46,2199.09,-401.868,32.6686,-0.996645] # q=8e7 +R23_coef[:,5]=[-3509.63,1718.64,-316.057,25.8717,-0.795242] # q=1.5e8 +R23_coef[:,6]=[-1550.53,784.262,-149.245,12.6618,-0.403774] # q=3e8 + + +N2S2_coef=np.zeros((5,7)) # coefficients from model grid fits +N2S2c0=[-1042.47,-1879.46,-2027.82,-2080.31,-2162.93,-2368.56,-2910.63] +N2S2_coef[:,0]=[-1042.47,521.076,-97.1578,8.00058,-0.245356] +N2S2_coef[:,1]=[-1879.46,918.362,-167.764,13.5700,-0.409872] +N2S2_coef[:,2]=[-2027.82,988.218,-180.097,14.5377,-0.438345] +N2S2_coef[:,3]=[-2080.31,1012.26,-184.215,14.8502,-0.447182] +N2S2_coef[:,4]=[-2162.93,1048.97,-190.260,15.2859,-0.458717] +N2S2_coef[:,5]=[-2368.56,1141.97,-205.908,16.4451,-0.490553] +N2S2_coef[:,6]=[-2910.63,1392.18,-249.012,19.7280,-0.583763] + +O3O2_coef=np.zeros((4,8)) # coefficients from model grid fits +O3O2c0=[-36.9772,-74.2814,-36.7948,-81.1880,-52.6367,-86.8674,-24.4044,49.4728] +O3O2_coef[:,0]=[-36.9772,10.2838,-0.957421,0.0328614] #z=0.05 +O3O2_coef[:,1]=[-74.2814,24.6206,-2.79194,0.110773] # z=0.1 +O3O2_coef[:,2]=[-36.7948,10.0581,-0.914212,0.0300472] # z=0.2 +O3O2_coef[:,3]=[-81.1880,27.5082,-3.19126,0.128252] # z=0.5 +O3O2_coef[:,4]=[-52.6367,16.0880,-1.67443,0.0608004] # z=1.0 +O3O2_coef[:,5]=[-86.8674,28.0455,-3.01747,0.108311] # z=1.5 +O3O2_coef[:,6]=[-24.4044,2.51913,0.452486,-0.0491711] # z=2.0 +O3O2_coef[:,7]=[49.4728,-27.4711,4.50304,-0.232228] # z=3.0 +''' + +M08_coefs = {'R23': [0.7462, -0.7149, -0.9401, -0.6154, -0.2524], + 'N2Ha': [-0.7732, 1.2357, -0.2811, -0.7201, -0.3330], + 'O3Hb': [0.1549, -1.5031, -0.9790, -0.0297], + 'O3O2': [-0.2839, -1.3881, -0.3172], + 'O2Hb': [0.5603, 0.0450, -1.8017, -1.8434, -0.6549], + 'O3N2': [0.4520, -2.6096, -0.7170, 0.1347]} + +#this is to check the Maiolino coefficients and find the split maximum of the cirves with degeneracy +''' +import pylab as pl + +x=np.arange(7.0,9.5,0.1) + +for k in M08_coefs.iterkeys(): + print k,max(nppoly.polyval(x-8.69,M08_coefs[k])),x[nppoly.polyval(x-8.69,M08_coefs[k])==max(nppoly.polyval(x-8.69,M08_coefs[k]))] + pl.plot(x,nppoly.polyval(x-8.69,M08_coefs[k]), label=k+' max:%.1f'%x[nppoly.polyval(x-8.69,M08_coefs[k])==max(nppoly.polyval(x-8.69,M08_coefs[k]))]) + +print nppoly.polyval(8.4-8.69,M08_coefs['N2Ha']) +pl.ylabel("log R") +pl.xlabel("12+log(O/H)") +pl.legend(loc=3) +pl.show() +''' + + +class diagnostics: + def __init__(self, num, logf, nps): + self.nm = num + self.Ha = None + self.Hb = None + + self.hasHa, self.hasHb = False, False + self.hasO2, self.hasO3 = False, False + self.hasS2, self.hasN2 = False, False + + self.hasO3Hb = False + self.hasO3O2 = False + + self.hasN2O2 = False + self.hasN2S2 = False + + self.hasS26731 = False + self.hasS39532 = False + self.hasS39069 = False + self.hasS2Hb = False + + self.N2O2_roots = None + #other lines calculated and repeatedly used + self.P = None + self.R2 = None + self.R3 = None + self.R23 = None + self.S2Hb = None + self.N2 = None + self.N2S2 = None + self.O23727 = None + self.O35007 = None + self.N26584 = None + self.S26717 = None + self.S26731 = None + self.S39069 = None + self.S39532 = None + + self.O34959p5007 = None + self.O35007O2 = None + self.O2O35007 = None + + self.logR23 = None + self.logN2O2 = None + self.logN2S2 = None + self.logO3O2 = None + self.logS23 = None + #self.logS3S2=None + + self.logO2Hb = None + self.logO3Hb = None + self.logN2Ha = None + self.logS2Ha = None + + self.logO35007O2 = None + self.logO2O35007 = None + + self.logO3O2sq = None + self.logq = None + self.Z_init_guess = None + self.N2O2_coef0 = 1106.8660 + + self.OIII_OII = None + self.OIII_Hb = None + self.OIII_SII = None + + self.NII_OII = None + self.NII_SII = None + #metallicity diagnostics to be returned + + self.mds = {} + for Z in get_keys(): + self.mds[Z] = None + + #setting output file + self.logf = logf + self.nps = nps + + def printme(self, verbose=False): + try: + print "\nHa", np.mean(self.Ha) + if verbose: + print self.Ha + except (IndexError, TypeError): + pass + try: + print "\nHb", np.mean(self.Hb) + if verbose: + print self.Hb + except (IndexError, TypeError): + pass + try: + print "\nO2", np.mean(self.O23727) + if verbose: + print self.O23727 + except (IndexError, TypeError): + pass + try: + print "\nO3", np.mean(self.O35007) + if verbose: + print self.O35007 + except (IndexError, TypeError): + pass + try: + print "\nO34959", np.mean(self.O34959) + if verbose: + print self.O34959 + except (IndexError, TypeError): + pass + try: + print "\nZ94", np.mean(self.mds['Z94']) + if verbose: + print self.mds['Z94'] + except (IndexError, TypeError): + pass + try: + print "\nR23", np.mean(self.R23) + if verbose: + print self.R23 + except (IndexError, TypeError): + pass + try: + print "\nlog(R23)", np.mean(self.logR23) + if verbose: + print self.logR23 + except (TypeError, IndexError): + pass + try: + print "\nlog([NII][OII])", stats.nanmean(self.logN2O2) + if verbose: + print self.logN2O2 + except (TypeError, IndexError): + pass + try: + print "\nlog([OIII][OII])", stats.nanmean(self.logO3O2) + if verbose: + print self.logO3O2 + except (TypeError, IndexError): + pass + for k in self.mds.iterkeys(): + print "\n", k, + try: + print stats.nanmean(self.mds[k]), np.stdev(self.mds[k]) + except (IndexError, TypeError): + if verbose: + print self.mds[k] + + def checkminimumreq(self, red_corr, ignoredust): + if red_corr and not ignoredust: + if not self.hasHa: + return -1 + if not self.hasHb: + return -1 + #if not self.hasO2 : + # return -1 + #if not self.hasO3 : + # return -1 + #if not self.hasS2 : + # return -1 + if not self.hasN2 and not (self.hasO2 and self.hasO3): + return -1 + + def fz_roots(self, coef): + if len(coef.shape) == 1: + coef[~(np.isfinite(coef))] = 0.0 + rts = np.roots(coef[::-1]) + if rts.size == 0: + printsafemulti('WARNING: fz_roots failed', self.logf, self.nps) + rts = np.zeros(coef.size - 1) + return rts + + else: + rts = np.zeros((coef.shape[0], coef.shape[1] - 1), dtype=complex) + coef[~(np.isfinite(coef))] = 0.0 + + for i in range(coef.shape[0]): + rts[i] = np.roots(coef[i][::-1]) # ::-1][0]) + return rts + + def setdustcorrect(self): + global DUSTCORRECT + DUSTCORRECT = True + + def unsetdustcorrect(self): + global DUSTCORRECT + DUSTCORRECT = False + + def dustcorrect(self, l1, l2, flux=False): + #global DUSTCORRECT + if DUSTCORRECT: + if not flux: + return 0.4 * self.mds['E(B-V)'] * (l1 - l2) + return 10 ** (0.4 * self.mds['E(B-V)'] * (l1 - l2)) + else: + if not flux: + return 0 + return 1.0 + + def setHab(self, Ha, Hb): + self.Ha = Ha + self.Hb = Hb + if sum(self.Ha > 0): + self.hasHa = True + if sum(self.Hb > 0): + self.hasHb = True + + def setOlines(self, O23727, O35007, O16300, O34959): + self.O23727 = O23727 + self.O35007 = O35007 + self.O16300 = O16300 + + if sum(self.O35007 > 0): + self.hasO3 = True + if sum(self.O23727 > 0): + self.hasO2 = True + + if self.hasO2 and self.hasO3: + self.O35007O2 = (self.O35007 / self.O23727) * self.dustcorrect(k_O3, k_O2, flux=True) + self.O2O35007 = (self.O23727 / self.O35007) * self.dustcorrect(k_O2, k_O3, flux=True) + + self.logO35007O2 = np.log10(self.O35007O2) + self.logO2O35007 = np.log10(self.O2O35007) + + #self.logO2O35007Hb=np.log10((self.O23727+self.O35007)/self.Hb) + # ratios for other diagnostics - slightly different ratios needed + if self.hasHb: + self.logO2O35007Hb = np.log10((self.O23727 / self.Hb) * self.dustcorrect(k_O2, k_Hb, flux=True)) + \ + (self.O35007 / self.Hb) * self.dustcorrect(k_O35007, k_Hb, flux=True) + + else: + printsafemulti("WARNING: needs O lines and and Ha/b: did you run setHab()?", self.logf, self.nps) + if self.hasHb: + if self.hasO2: + self.logO2Hb = np.log10(self.O23727 / self.Hb) + self.dustcorrect(k_O2, k_Hb) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + if self.hasO3: + self.O3Hb = (self.O35007 / self.Hb) + self.dustcorrect(k_O35007, k_Hb, flux=True) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + self.logO3Hb = np.log10(self.O3Hb) + self.hasO3Hb = True + + + if self.hasO2 and self.hasO3: + self.OIII_OII = np.log10(self.O35007 / self.O23727 + self.dustcorrect(k_O35007, k_O2, flux=True)) + if O34959 is not None and sum(O34959 > 0) > 0: + self.O34959p5007 = (O34959 + self.O35007) + self.logO3O2 = np.log10((self.O34959p5007) / self.O23727) + self.dustcorrect(k_O3, k_O2) + #this is useful when we get logq + self.hasO3O2 = True + if self.hasHb: + self.OIII_Hb = np.log10(self.O35007 / self.Hb + self.dustcorrect(k_O35007, k_Hb, flux=True)) + + def setNII(self, N26584): + if N26584 is not None and sum(N26584 > 0): + self.N26584 = N26584 + self.hasN2 = True + if self.hasHa: + self.logN2Ha = np.log10(self.N26584 / self.Ha) # +self.dustcorrect(k_N2,k_Ha,flux=True) + #lines are very close: no dust correction + #Note: no dust correction cause the lies are really close! + else: + printsafemulti("WARNING: needs NII6584 and Ha to calculate NIIHa: did you run setHab()?", self.logf, self.nps) + if self.hasS2 and self.hasS26731 and self.hasN2: + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_S2,flux=True) + #lines are very close: no dust correction + if self.hasO2 and self.hasN2: + self.NII_OII = np.log10(self.N26584 / self.O23727 + self.dustcorrect(k_N2, k_O2, flux=True)) + + def setSII(self, S26717, S26731, S39069, S39532): + if S26717 is not None and sum(S26717 > 0) > 0: + self.S26717 = S26717 + self.hasS2 = True + + if self.hasHa: + self.logS2Ha = np.log10(self.S26717 / self.Ha) + self.dustcorrect(k_S2, k_Ha) + else: + printsafemulti("WARNING: needs SII6717 and Ha to calculate SIIHa: did you run setHab() and setS()?", self.logf, self.nps) + if S26731 is not None and sum(S26731 > 1e-9) > 0: + self.S26731 = S26731 + self.hasS26731 = True + if S39069 is not None and sum(S39069 > 1e-9) > 0: + self.S39069 = S39069 + self.hasS39069 = True + if S39532 is not None and sum(S39532 > 1e-9) > 0: + self.S39532 = S39532 + self.hasS39532 = True + if self.hasS2: + if self.hasN2 and self.NII_SII is None and self.hasS26731: + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_O2,flux=True) + #lines are very close: no dust correction + if self.hasO3 and self.OIII_SII is None and self.hasS26731: + self.OIII_SII = np.log10(self.O35007 / (self.S26717 + self.S26731) + self.dustcorrect(k_O3, k_S2, flux=True)) + + #@profile + def calcEB_V(self): + printsafemulti("calculating E(B-V)", self.logf, self.nps) + self.mds['E(B-V)'] = np.log10(2.86 * self.Hb / self.Ha) / (0.4 * (k_Ha - k_Hb)) # E(B-V) + self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 + + #@profile + def calcNIISII(self): + if self.hasS2 and self.hasN2: + self.N2S2 = self.N26584 / self.S26717 + self.dustcorrect(k_N2, k_S2, flux=True) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) + self.logN2S2 = np.log10(self.N26584 / self.S26717) + self.dustcorrect(k_N2, k_S2) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) + self.hasN2S2 = True + else: + printsafemulti("WARNING: needs SII6717 and NII6584 to calculate NIISII: did you run setN2() and setS?", self.logf, self.nps) + + #@profile + def calcNIIOII(self): + if self.hasN2 and self.hasO2: + self.logN2O2 = np.log10(self.N26584 / self.O23727) + self.dustcorrect(k_N2, k_O2) + self.hasN2O2 = True + if not self.hasN2O2 or np.mean(self.logN2O2) < 1.2: + + try: + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods should only be used for log([NII]6564/[OII]3727) >1.2, + the mean log([NII]6564/[OII]3727)= %f''' % np.mean(self.logN2O2), self.logf, self.nps) + except TypeError: + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods + should only be used for log([NII]6564/[OII]3727) >1.2, + the mean log([NII]6564/[OII]3727)= %s''' % self.logN2O2, self.logf, self.nps) + + if not self.hasN2O2: + self.N2O2_roots = np.zeros(self.nm) + float('NaN') + else: + N2O2_coef = np.array([[self.N2O2_coef0, -532.15451, 96.373260, -7.8106123, 0.23928247]] * self.nm).T # q=2e7 line (approx average) + N2O2_coef[0] -= self.logN2O2 + N2O2_coef = N2O2_coef.T + # finding roots for == (4) + self.N2O2_roots = np.array([self.fz_roots(N2O2_coef)])[0] + + #@profile + def calcR23(self): + printsafemulti("calculating R23", self.logf, self.nps) + + #R23 NEW Comb, [NII]/Ha: KK04 = Kobulnicky & Kewley, 2004, submitted' + if self.hasO3 and self.hasO2 and self.hasHb: + self.R2 = (self.O23727 / self.Hb) * self.dustcorrect(k_O2, k_Hb, flux=True) + self.R3 = (self.O34959p5007 / self.Hb) * self.dustcorrect(k_O3, k_Hb, flux=True) + self.R23 = self.R2 + self.R3 + self.logR23 = np.log10(self.R23) + self.mds['logR23'] = self.logR23 + #note that values of logR23 > 0.95 are unphysical. + #you may choose to uncomment the line below + #self.logR23[self.logR23>0.95]=0.95 + else: + printsafemulti("WARNING: need O3, O2, Hb", self.logf, self.nps) + + #@profile + def calcS23(self): + printsafemulti("calculating S23", self.logf, self.nps) + #the original code here uses S267176731, + #which is however set to 6717 as default + #Vilchez & Esteban (1996) + if self.hasS2: + if self.hasS39069 and self.hasHb: + self.logS23 = np.log10((self.S26717 / self.Hb) * + self.dustcorrect(k_S2, k_Hb, flux=True) + + (self.S39069 / self.Hb) * + self.dustcorrect(k_S3, k_Hb, flux=True)) + + #self.logS3S2=np.log10(S39069/self.S26717)+self.dustcorrect(k_S3,k_S2) + + ##@profile + def calclogq(self, Z): + if not self.hasO3O2: + printsafemulti("WARNING: needs O3,O2,Hb to calculate logq properly.", self.logf, self.nps) + return -1 + if self.logO3O2sq is None: + self.logO3O2sq = self.logO3O2 ** 2 + return (32.81 - 1.153 * self.logO3O2sq + Z * (-3.396 - 0.025 * self.logO3O2 + 0.1444 * self.logO3O2sq)) / (4.603 - 0.3119 * self.logO3O2 - \ + 0.163 * self.logO3O2sq + Z * (-0.48 + 0.0271 * self.logO3O2 + 0.02037 * self.logO3O2sq)) + + ##@profile + def initialguess(self): + # Initial Guess - appearing in LK code as of Nov 2006 + # upper branch: if no lines are available, metallicity is set to 8.7 + self.Z_init_guess = np.zeros(self.nm) + 8.7 + # use [N2]/Ha + if self.hasHa and self.hasN2: + self.Z_init_guess[(self.logN2Ha < -1.3) & (self.N26584 != 0.0)] = 8.2 + self.Z_init_guess[(self.logN2Ha < -1.1) & (self.logN2Ha >= -1.3) & (self.N26584 != 0.0)] = 8.4 + #A1 KE08 + self.Z_init_guess[(self.logN2Ha >= -1.1) & (self.N26584 != 0.0)] = 8.7 + #use [N2]/[O2] + if self.hasN2 and self.hasO2: + N2O2 = np.zeros(self.nm) + float('nan') + if self.hasHb: + ###FED CHECK THIS! + N2O2 = self.N26584 * self.Ha * self.Hb * self.O23727 + if not self.hasN2O2: + printsafemulti("WARNING: must calculate logN2O2 first", self.logf, self.nps) + self.calcNIIOII() + self.Z_init_guess[(self.logN2O2 < -1.2) & (N2O2 != 0.0)] = 8.2 + # at logN2O2<-1.2 using low-Z gals, A1 KE08 + self.Z_init_guess[(self.logN2O2 >= -1.2) & (N2O2 != 0.0)] = 8.7 + + # at logN2O2>-1.2 using HII regions + + + + #######################these are the metallicity diagnostics################## + #@profile + def calcpyqz(self, plot=False, allD13=False): + printsafemulti("calculating D13", self.logf, self.nps) + + # initializing variable pyqz to avoid style issues + # (pyqz not defined is reported as error by Landscape.io w/ import in func + pyqz = None + try: + import pyqz + except ImportError: + return -1 + + #check version of pyqz + from distutils.version import StrictVersion + oldpyqz = False + if StrictVersion(pyqz.__version__) <= StrictVersion('0.5.0'): + oldpyqz = True + + if self.NII_SII is not None and allD13: + if self.OIII_SII is not None: + + if oldpyqz: + self.mds['D13_N2S2_O3S2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_SII]), 'NII/SII', 'OIII/SII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + #pyqz.get_grid_fn(Pk=5.0,calibs='GCZO', kappa =20, struct='pp') + self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_SII])], \ + '[NII]/[SII]+;[OIII]/[SII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + if self.OIII_Hb is not None: + if oldpyqz: + self.mds['D13_N2S2_O3Hb'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_Hb]), 'NII/SII', 'OIII/Hb', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2S2_O3SHb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/[SII]+;[OIII]/Hb', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + + + if self.OIII_OII is not None: + if oldpyqz: + self.mds['D13_N2S2_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_OII]), 'NII/SII', 'OIII/OII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2S2_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_OII])], \ + '[NII]/[SII]+;[OIII]/[OII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + + if self.NII_OII is not None and allD13: + if self.OIII_SII is not None: + if oldpyqz: + self.mds['D13_N2O2_O3S2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_SII]), 'NII/OII', 'OIII/SII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2O2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_SII])], \ + '[NII]/[OII]+;[OIII]/[SII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + + if self.OIII_Hb is not None: + if oldpyqz: + self.mds['D13_N2O2_O3Hb'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_Hb]), 'NII/OII', 'OIII/Hb', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2O2_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/[OII]+;[OIII]/Hb', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + if self.OIII_OII is not None: + if oldpyqz: + self.mds['D13_N2O2_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_OII]), 'NII/OII', 'OIII/OII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2O2_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_OII])], \ + '[NII]/[OII]+;[OIII]/[OII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + if self.logN2Ha is not None: + if self.OIII_Hb is not None: + if oldpyqz: + self.mds['D13_N2Ha_O3Hb'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_Hb]), 'NII/Ha', 'OIII/Hb', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2Ha_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/Ha;[OIII]/Hb', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + if self.OIII_OII is not None: + if oldpyqz: + self.mds['D13_N2Ha_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_OII]), 'NII/Ha', 'OIII/OII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + + else: + self.mds['D13_N2Ha_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/Ha;[OIII]/[OII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + #@profile + def calcDP00(self): + # Diaz, A. I., & Perez-Montero, E. 2000, MNRAS, 312, 130 + # As per KD02: DP00 diagnostic systematically underestimates the + # abundance relative to the comparison abundance. + # A term is added to improve the fit according to KD02 Eq. 6 + # AVAILABLE BUT DEPRECATED + printsafemulti("calculating DP00", self.logf, self.nps) + + if self.logS23 is None: + self.calcS23() + if self.logS23 is None: + printsafemulti("WARNING: Cannot compute this without S23", self.logf, self.nps) + return -1 + self.mds['DP00'] = 1.53 * self.logS23 + 8.27 + 1.0 / (2.0 - 9.0 * self.logS23 ** 3) + + #@profile + def calcD02(self): + # [NII]/Ha Denicolo, Terlevich & Terlevich (2002), MNRAS, 330, 69 + #FED:added uncertainties + printsafemulti("calculating D02", self.logf, self.nps) + + e1 = np.random.normal(0, 0.05, self.nm) + e2 = np.random.normal(0, 0.1, self.nm) + if self.hasN2 and self.hasHa: + self.mds['D02'] = 9.12 + e1 + (0.73 + e2) * self.logN2Ha + else: + printsafemulti("WARNING: need N2Ha to do this. did you run setHab and setNII", self.logf, self.nps) + + #@profile + def calcPP04(self): + ### PP04_N2_Z, PP04_O3N2_Z Pettini & Pagel diagnostics - + ### Pettini & Pagel (2004), MNRAS, 348, L59 + # [NII]/Ha Pettini & Pagel (2004), MNRAS, 348, L59 + #discriminating lower and upper branch using [NII]/[OII] or [NII]/Ha + printsafemulti("calculating PP04", self.logf, self.nps) + if self.hasN2 and self.hasHa: + self.mds['PP04_N2Ha'] = nppoly.polyval(self.logN2Ha, [9.37, 2.03, 1.26, 0.32]) + + #FED: restricting the range as per paper + index = (self.logN2Ha > -2.5) * (self.logN2Ha < -0.3) + self.mds['PP04_N2Ha'][~index] = float('NaN') + if self.hasO3Hb: + self.mds['PP04_O3N2'] = 8.73 - 0.32 * (self.logO3Hb - self.logN2Ha) + index = (self.logO3Hb > 2) + self.mds['PP04_O3N2'][index] = float('NaN') + else: + printsafemulti("WARNING: need O3Hb for PP04_O3N2", self.logf, self.nps) + else: + printsafemulti("WARNING: need N2Ha to do this. did you run setHab and setNII", self.logf, self.nps) + + #@profile + def calcZ94(self): + ### calculating z from Kobulnicky,Kennicutt,Pizagno (1998) + ### parameterization of Zaritzky et al. (1994) + ###Z94 = Zaritsky, D., Kennicutt, R. C., & Huchra, J. P. 1994, + ###ApJ, 420, 87 + ### only valid on the upper branch of R23 (KE08 A2.4) + + printsafemulti("calculating Z94", self.logf, self.nps) + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + self.mds['Z94'] = nppoly.polyval(self.logR23, [9.265, -0.33, -0.202, -0.207, -0.333]) + self.mds['Z94'][(self.logR23 > 0.9)] = None + ## 0.9 is a conservative constraint to make sure that we are + ## only using the upper branch (i.e. 12+log(O/H)>8.4) + + def calcP(self): + if self.P is None: + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + #R3=10**self.logO349595007Hb + #R2=10**self.logO2Hb + #P = R3/(R2+R3) + self.P = self.R3 / self.R23 + + #@profile + def calcP05(self): + # #### P-method ##### + ##Pilyugin+ 2005 method. Based on [OIII],[OII], Hbeta + ##calibrated from Te method + # make sure you run setOlines() first + printsafemulti("calculating P05", self.logf, self.nps) + + if self.calcP() == -1: + return -1 + if self.Z_init_guess is None: + self.initialguess() + + Psq = self.P * self.P + + P_abund_up = (self.R23 + 726.1 + 842.2 * self.P + 337.5 * Psq) / (85.96 + 82.76 * self.P + 43.98 * Psq + 1.793 * self.R23) + P_abund_low = (self.R23 + 106.4 + 106.8 * self.P - 3.40 * Psq) / (17.72 + 6.60 * self.P + 6.95 * Psq - 0.302 * self.R23) + + self.mds['P05'] = P_abund_up + self.mds['P05'][self.Z_init_guess < 8.4] = P_abund_low[self.Z_init_guess < 8.4] + + #@profile + def calcP10(self): + # #### P-method ##### + ##Pilyugin+ 2010 method. + ##calibrated from Te method + # need Hb + #The Astrophysical Journal, Volume 720, Issue 2, pp. 1738-1751 (2010). + #Published in Sep 2010 + + printsafemulti("calculating P10", self.logf, self.nps) + + if not self.hasHb: + printsafemulti("this method needs Hb", self.logf, self.nps) + return -1 + self.mds['P10_ONS'] = np.zeros(self.nm) + float('NaN') + self.mds['P10_ON'] = np.zeros(self.nm) + float('NaN') + #P10N2=np.zeros(self.nm)+float('NaN') + #P10S2=np.zeros(self.nm)+float('NaN') + P10logR3 = np.zeros(self.nm) + float('NaN') + P10logR2 = np.zeros(self.nm) + float('NaN') + P10logN2 = np.zeros(self.nm) + float('NaN') + P10logS2 = np.zeros(self.nm) + float('NaN') + + self.calcP() + if self.R2 is not None: + P10logR2 = np.log(self.R2) + + if self.R3 is not None: + P10logR3 = np.log(self.R3) + + if self.hasN2: + #the ratio of N26548 and N26548 is N26584/N26548 = 3 + #independent on physical conditions + #The Physics and Dynamics of Planetary Nebulae + # By Grigor A. Gurzadyan + P10logN2 = np.log((self.N26584 * 1.33) / self.Hb) + self.dustcorrect(k_N2, k_Hb) + + if self.hasS2 and self.hasS26731: + self.S2Hb = ((self.S26717 + self.S26731) / self.Hb) + self.dustcorrect(k_S2, k_Hb, flux=True) + self.hasS2Hb = True + P10logS2 = np.log10(self.S2Hb) + + P10logN2S2 = P10logN2 - P10logS2 + P10logN2R2 = P10logN2 - P10logR2 + P10logS2R2 = P10logS2 - P10logR2 + + coefsONS0 = np.array([8.277, 0.657, -0.399, -0.061, 0.005]) + coefsONS1 = np.array([8.816, -0.733, 0.454, 0.710, -0.337]) + coefsONS2 = np.array([8.774, -1.855, 1.517, 0.304, 0.328]) + + vsONS = np.array([np.ones(self.nm), self.P, P10logR3, P10logN2R2, P10logS2R2]).T + + coefsON0 = np.array([8.606, -0.105, -0.410, -0.150]) + coefsON1 = np.array([8.642, 0.077, 0.411, 0.601]) + coefsON2 = np.array([8.013, 0.905, 0.602, 0.751]) + + vsON = np.array([np.ones(self.nm), P10logR3, P10logR2, P10logN2R2]).T + + indx = P10logN2 > -0.1 + if self.P is not None: + self.mds['P10_ONS'][indx] = np.dot(vsONS[indx], coefsONS0) + self.mds['P10_ON'][indx] = np.dot(vsON[indx], coefsON0) + + indx = (P10logN2 < -0.1) * (P10logN2S2 > -0.25) + if self.P is not None: + self.mds['P10_ONS'][indx] = np.dot(vsONS[indx], coefsONS1) + self.mds['P10_ON'][indx] = np.dot(vsON[indx], coefsON1) + + indx = (P10logN2 < -0.1) * (P10logN2S2 < -0.25) + if self.P is not None: + self.mds['P10_ONS'][indx] = np.dot(vsONS[indx], coefsONS2) + self.mds['P10_ON'][indx] = np.dot(vsON[indx], coefsON2) + + indx = ~((self.mds['P10_ONS'] > 7.1) * (self.mds['P10_ON'] > 7.1) * (self.mds['P10_ONS'] < 9.4) * (self.mds['P10_ON'] < 9.4)) + if self.P is not None: + self.mds['P10_ONS'][indx] = float('NaN') + self.mds['P10_ON'][indx] = float('NaN') + + #@profile + def calcP01(self): + # P-method 2001 upper branch (derecated and commented out) + # Pilyugin 2001 + # available but deprecated + printsafemulti("calculating old P05", self.logf, self.nps) + + if self.Z_init_guess is None: + self.initialguess() + if self.hasO3O2 and self.hasO3 and self.hasO2: + P = 10 ** self.logO3O2 / (1 + 10 ** self.logO3O2) + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + Psq = P ** 2 + P_abund_old = (self.R23 + 54.2 + 59.45 * P + 7.31 * Psq) / (6.07 + 6.71 * P + 0.371 * Psq + 0.243 * self.R23) + self.mds['P01'] = np.zeros(self.nm) + float('NaN') + self.mds['P01'][self.Z_init_guess >= 8.4] = P_abund_old[self.Z_init_guess >= 8.4] + else: + printsafemulti("WARNING: need OIIIOII to calculate P01, did you set them up with setOlines()?", self.logf, self.nps) + + #@profile + def calcC01_ZR23(self): + # C01 = Charlot, S., & Longhetti, M., 2001, MNRAS, 323, 887 + # Charlot 01 R23 calibration: (case F) ## + # available but deprecated + printsafemulti("calculating C01", self.logf, self.nps) + + if self.hasO3 and self.hasO2 and self.hasO3Hb: + x2 = self.O2O35007 / 1.5 + x3 = (10 ** self.logO3Hb) * 0.5 + self.mds['C01_R23'] = np.zeros(self.nm) + float('NaN') + self.mds['C01_R23'][self.O2O35007 < 0.8] = np.log10(3.78e-4 * (x2[self.O2O35007 < 0.8]) ** 0.17 * x3[self.O2O35007 < 0.8] ** (-0.44)) + 12.0 + + self.mds['C01_R23'][self.O2O35007 >= 0.8] = np.log10(3.96e-4 * x3[self.O2O35007 >= 0.8] ** (-0.46)) + 12.0 + else: + printsafemulti('''WARNING: need [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, +did you set them up with setOlines()?''', self.logf, self.nps) + + # Charlot 01 calibration: (case A) based on [N2]/[SII]## + # available but deprecated + if not self.hasN2S2: + printsafemulti("WARNING: trying to calculate logNIISII", self.logf, self.nps) + self.calcNIISII() + if self.hasN2S2 and self.hasO3 and self.hasO2 and self.hasO3Hb: + self.mds['C01_N2S2'] = np.log10(5.09e-4 * (x2 ** 0.17) * ((self.N2S2 / 0.85) ** 1.17)) + 12 + else: + printsafemulti('''WARNING: needs [NII]6584, [SII]6717, [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, +did you set them up with setOlines() and ?''', self.logf, self.nps) + + #@profile + def calcM91(self): + # ## calculating McGaugh (1991) + # McGaugh, S.S., 1991, ApJ, 380, 140' + # M91 calibration using [N2O2] as + # initial estimate of abundance: + # this initial estimate can be + # changed by replacing + # OH_init by another guess, eg C01_Z + # NOTE: occasionally the M91 + # 'upper branch' will give a metallicity + # that is lower than the 'lower branch'. + # Happens for very high R23 values. + # If R23 is higher than the intersection + # (calculate the intersection), then + # the metallicity is likely to be around + # the R23 maximum = 8.4 + + printsafemulti("calculating M91", self.logf, self.nps) + self.mds['M91'] = np.zeros(self.nm) + float('NaN') + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + + if self.Z_init_guess is None: + self.initialguess() + + M91_Z_low = nppoly.polyval(self.logR23, [12.0 - 4.944, 0.767, 0.602]) - \ + self.logO3O2 * nppoly.polyval(self.logR23, [0.29, 0.332, -0.331]) + M91_Z_up = nppoly.polyval(self.logR23, [12.0 - 2.939, -0.2, -0.237, -0.305, -0.0283]) - \ + self.logO3O2 * nppoly.polyval(self.logR23, [0.0047, -0.0221, -0.102, -0.0817, -0.00717]) + + indx = (np.abs(self.logO3O2) > 0) * (np.abs(self.logR23) > 0) * (self.Z_init_guess < 8.4) + self.mds['M91'][indx] = M91_Z_low[indx] + indx = (np.abs(self.logO3O2) > 0) * (np.abs(self.logR23) > 0) * (self.Z_init_guess >= 8.4) + self.mds['M91'][indx] = M91_Z_up[indx] + self.mds['M91'][(M91_Z_up < M91_Z_low)] = float('NaN') + + #@profile + def calcM13(self): + #Marino+ 2013 + printsafemulti("calculating M13", self.logf, self.nps) + + if not self.hasHa or not self.hasN2: + printsafemulti("WARNING: need O3, N2, Ha and Hb, or at least N2 and Ha", self.logf, self.nps) + return -1 + else: + e1 = np.random.normal(0, 0.027, self.nm) + e2 = np.random.normal(0, 0.024, self.nm) + self.mds["M13_N2"] = 8.743 + e1 + (0.462 + e2) * self.logN2Ha + if self.hasHb and self.hasO3: + e1 = np.random.normal(0, 0.012, self.nm) + e2 = np.random.normal(0, 0.012, self.nm) + O3N2 = self.logO3Hb - self.logN2Ha + self.mds["M13_O3N2"] = 8.533 + e1 - (0.214 + e1) * O3N2 + index = (self.logO3Hb > 1.7) + self.mds["M13_O3N2"][index] = float('NaN') + + #@profile + def calcM08(self, allM08=False): + #Maiolino+ 2008 + #Astronomy and Astrophysics, Volume 488, Issue 2, 2008, pp.463-479 + #Published in Sep 2008 + printsafemulti("calculating M08", self.logf, self.nps) + highZ = None + if self.logO35007O2 is not None: + self.mds['M08_O3O2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O3O2']] * self.nm).T + coefs[0] = coefs[0] - self.logO35007O2 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + #the two cumsum assure that if the condition for the ith element + #of indx is [False, False] then after the first cumsum(1) is [0,0] + #[False, True] is [0,1] + #[True, True] is [1,2] + #but (here is the kicker) [True, False] is [1,1]. + #Because i want only one solution + #(i'll settle for the first one occurring) [1,1] is ambiguous. + #The second cumsum(1) makes + #[0,0]->[0,0], [0,1]->[0,1], [1,2]->[1,3] and finally [1,1]->[1,2] + + self.mds['M08_O3O2'][(indx.sum(1)) > 0] = sols[indx].real + highZ = np.median(self.logO35007O2) < 0 + if self.logN2Ha is not None: + self.mds['M08_N2Ha'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['N2Ha']] * self.nm).T + coefs[0] = coefs[0] - self.logN2Ha + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_N2Ha'][(indx.sum(1)) > 0] = sols[indx].real + if highZ is None: + highZ = np.median(self.logN2Ha) > -1.3 + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute M08_R23 without R23", self.logf, self.nps) + else: + self.mds['M08_R23'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['R23']] * self.nm).T + coefs[0] = coefs[0] - self.logR23 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real >= 8.0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_R23'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real <= 8.0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_R23'][(indx.sum(1)) > 0] = sols[indx].real + if not allM08: + return + else: + printsafemulti("calculating other M08s", self.logf, self.nps) + + if self.logO3Hb is not None: + + self.mds['M08_O3Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O3Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO3Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real >= 7.9)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real <= 7.9)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + + if self.logO2Hb is not None: + self.mds['M08_O2Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O2Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO2Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real >= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real <= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + + + + if self.hasO3 and self.hasN2: + self.mds['M08_O3N2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O3N2']] * self.nm).T + coefs[0] = coefs[0] - np.log(self.O35007 / self.N26584) * self.dustcorrect(k_O35007, k_N2) + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O3N2'][(indx.sum(1)) > 0] = sols[indx].real + + #@profile + def calcKD02_N2O2(self): + ## Kewley & Dopita (2002) estimates of abundance + ## KD02 + # KD02 [N2]/[O2] estimate (can be used for whole log(O/H)+12 range, + # but rms scatter increases to 0.11 rms for log(O/H)+12 < 8.6 + # rms = 0.04 for + # log(O/H)+12 > 8.6 + # uses equation (4) from KD02 paper + # FED: i vectorized the hell out of this function!!! + # from a 7 dimensional if/for loop to 1 if and 1 for :-D + #vectorizing makes fed happy ... + + printsafemulti("calculating KD02_N2O2", self.logf, self.nps) + + if self.hasN2 and self.hasO2 and self.hasHa and self.hasHb: + self.mds['KD02_N2O2'] = np.zeros(self.nm) + float('NaN') + if not self.hasN2O2: + printsafemulti("WARNING: must calculate logN2O2 first", self.logf, self.nps) + self.calcNIIOII() + if not self.hasN2O2 or self.N2O2_roots is None or sum(np.isnan(self.N2O2_roots.flatten())) == len(self.N2O2_roots.flatten()): + printsafemulti("WARNING: cannot calculate N2O2", self.logf, self.nps) + return -1 + roots = self.N2O2_roots.T + for k in range(4): + indx = (abs(roots[k]) >= 7.5) * (abs(roots[k]) <= 9.4) * (roots[k][:].imag == 0.0) + self.mds['KD02_N2O2'][indx] = abs(roots[k][indx]) + else: + printsafemulti("WARNING: need NII6584 and OII3727 and Ha and Hb to calculate this. did you run setO() setHab() and setNII()?", self.logf, self.nps) + return 1 + + #@profile + def calcKK04_N2Ha(self): + # calculating [N2]/Ha abundance estimates using [O3]/[O2] also + printsafemulti("calculating KK04_N2Ha", self.logf, self.nps) + + if self.mds['KD02_N2O2'] is None: + self.calcKD02_N2O2() + if self.mds['KD02_N2O2'] is None or sum(np.isnan(self.mds['KD02_N2O2'])) == self.nm: + printsafemulti("WARNING: without KD02_N2O2 cannot calculate KK04_N2Ha properly, but we will do our best...", self.logf, self.nps) + Z_new_N2Ha = np.zeros(self.nm) + 8.6 + else: + Z_new_N2Ha = self.mds['KD02_N2O2'].copy() # was 8.6 + + if self.hasN2 and self.hasHa: + logq_save = np.zeros(self.nm) + convergence, tol, ii = 100, 1.0e-3, 0 + if self.hasO3O2: + # calculating logq using the [N2]/[O2] + # metallicities for comparison + while convergence > tol and ii < 100: + ii += 1 + self.logq = self.calclogq(Z_new_N2Ha) + Z_new_N2Ha = nppoly.polyval(self.logN2Ha, [7.04, 5.28, 6.28, 2.37]) - \ + self.logq * nppoly.polyval(self.logN2Ha, [-2.44, -2.01, -0.325, +0.128]) + \ + 10 ** (self.logN2Ha - 0.2) * self.logq * (-3.16 + 4.65 * self.logN2Ha) + convergence = np.abs(self.logq - logq_save).mean() + logq_save = self.logq.copy() + if ii >= 100: + printsafemulti("WARNING: loop did not converge", self.logf, self.nps) + Z_new_N2Ha = np.zeros(self.nm) + float('NaN') + else: + self.logq = 7.37177 * np.ones(self.nm) + Z_new_N2Ha = nppoly.polyval(self.logN2Ha, [7.04, 5.28, 6.28, 2.37]) - \ + self.logq * nppoly.polyval(self.logN2Ha, [-2.44, -2.01, -0.325, +0.128]) + \ + 10 ** (self.logN2Ha - 0.2) * self.logq * (-3.16 + 4.65 * self.logN2Ha) + self.mds['KK04_N2Ha'] = Z_new_N2Ha + indx = self.logN2Ha > 0.8 + self.mds['KK04_N2Ha'][indx] = float('NaN') + else: + printsafemulti("WARNING: need NII6584 and Ha to calculate this. did you run setHab() and setNII()?", self.logf, self.nps) + + #@profile + def calcKK04_R23(self): + # Kobulnicky & Kewley 2004 + # calculating upper and lower metallicities for objects without + # Hb and for objects without O3 and/or O2 + + printsafemulti("calculating KK04_R23", self.logf, self.nps) + #this is in the original code but not used :( + #if self.hasN2 and self.hasHa: + #logq_lims=[6.9,8.38] + #logN2Ha=np.log10(self.N26584/self.Ha) CHECK!! why remove dust correction?? + #Z_new_N2Ha_lims= np.atleast_2d([1.0,1.0]).T*nppoly.polyval(self.logN2Ha,[7.04, 5.28,6.28,2.37])- + #np.atleast_2d( logq_lims).T*nppoly.polyval(self.logN2Ha,[-2.44,-2.01,-0.325,0.128])+ + #np.atleast_2d(logq_lims).T*(10**(self.logN2Ha-0.2)*(-3.16+4.65*self.logN2Ha)) + # R23 diagnostics from Kobulnicky & Kewley 2004 + + Zmax = np.zeros(self.nm) + # ionization parameter form logR23 + if not self.hasO3O2: + logq = np.zeros(self.nm) + else: + if self.Z_init_guess is None: + self.initialguess() + Z_new = self.Z_init_guess.copy() + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + else: + logqold, convergence, ii = np.zeros(self.nm) + 100, 100, 0 + tol = 1e-4 + #3 iterations are typically enought to achieve convergence KE08 A2.3 + while convergence > tol and ii < 100: + Zmax = Zmax * 0.0 + ii += 1 + logq = self.calclogq(Z_new) + Zmax[(logq >= 6.7) * (logq < 8.3)] = 8.4 + # maximum of R23 curve: + Z_new = nppoly.polyval(self.logR23, [9.72, -0.777, -0.951, -0.072, -0.811]) - \ + logq * nppoly.polyval(self.logR23, [0.0737, -0.0713, -0.141, 0.0373, -0.058]) + indx = self.Z_init_guess <= Zmax + Z_new[indx] = nppoly.polyval(self.logR23[indx], [9.40, 4.65, -3.17]) - \ + logq[indx] * nppoly.polyval(self.logR23[indx], [0.272, 0.547, -0.513]) + convergence = np.abs((logqold - logq).mean()) + logqold = logq.copy() + if ii >= 100: + printsafemulti("WARNING: loop did not converge", self.logf, self.nps) + Z_new = np.zeros(self.nm) + float('NaN') + Z_new_lims = [nppoly.polyval(self.logR23, [9.40, 4.65, -3.17]) - \ + logq * nppoly.polyval(self.logR23, [0.272, 0.547, -0.513]), + nppoly.polyval(self.logR23, [9.72, -0.777, -0.951, -0.072, -0.811]) - \ + logq * nppoly.polyval(self.logR23, [0.0737, -0.0713, -0.141, 0.0373, -0.058])] + Z_new[(Z_new_lims[0] > Z_new_lims[1])] = None + self.mds['KK04_R23'] = Z_new + + #@profile + def calcKDcombined(self): + # KD02comb Kewley, L. J., & Dopita, M. A., 2002, ApJ + # updated in KE08 + # ### KD02 [NII]/[OII] estimate ### + # (can be used for log(O/H)+12 > 8.6 only) + + printsafemulti("calculating KD_combined", self.logf, self.nps) + + #We first use the + #[N ii]/[O ii] ratio to determine whether it lies on the upper + #or lower R23 branch + + if self.mds['KD02_N2O2'] is None: + self.calcKD02_N2O2() + if self.mds['KK04_N2Ha'] is None: + self.calcKK04_N2Ha() + if self.logR23 is None: + self.calcR23() + if self.mds['M91'] is None: + printsafemulti("WARNING: Must first calculate M91", self.logf, self.nps) + self.calcM91() +# if self.mds['Z94'] is None: +# printsafemulti( "WARNING: Must first calculate Z94",self.logf,self.nps) +# self.calcZ94() + if self.mds['KK04_R23'] is None: + printsafemulti("WARNING: Must first calculate KK04_R23", self.logf, self.nps) + self.calcKK04_R23() + if not self.hasHa and not self.hasHb: + printsafemulti("WARNING: need Ha and Hb for this. did you run setHab()?", self.logf, self.nps) + + #alternative way to calculate KD02_N2O2, stated in the paper KD02, + #valid in high Z regimes (Z>8.4) + #but we forego it + #if not self.logN2O2 is None: + # self.mds['KD02_N2O2']=np.log10(8.511e-4*(1.54020+1.26602*self.logN2O2+0.167977*self.logN2O2**2))+12. + #else: self.mds['KD02_N2O2']=np.zeros(self.nm)+float('NaN') + + # ionization parameter + # calculate an initial ionization parameter by assuming + # a nominal lower branch [12 + log (O/H ) = 8.2] + # or upper branch [12 + log (O/H ) = 8.7] metallicity using + # equation (13) from KK04 + logq = np.zeros(self.nm) + if self.hasN2 and self.hasO2 and self.hasHb and self.hasHa and self.hasO3O2: + logq = self.calclogq(self.mds['KD02_N2O2']) + logq[self.mds['KD02_N2O2'] >= 8.4] = self.logq[self.mds['KD02_N2O2'] >= 8.4] + else: + if self.Z_init_guess is None: + self.initialguess() + logq = self.calclogq(self.Z_init_guess) + #FED: CHECK: the paragraph below makes sense in words but i dont see whereit ie enforced. + # if log([NII]/[OII]) after extinction correction is <-1.5, then check the data. + # if it is only slightly less than 1.5, then this can be a result of either noisy + # data, inaccurate fluxes or extinction correction, or a higher ionization parameter + # than modelled. + # For these cases, the average of the M91,Z94 and C01 should be used. + + # KD02 R23 estimate (not reliable for 8.4 < log(O/H)+12 < 8.8) + # uses [NII]/[OII] estimate as initial guess - this can be changed below + + self.mds['KD02comb'] = np.zeros(self.nm) + float('NaN') + + indx_ig = self.Z_init_guess > 8.4 + if self.mds['KD02_N2O2'] is not None: + self.mds['KD02comb'][indx_ig] = self.mds['KD02_N2O2'][indx_ig].copy() + if self.mds['KK04_N2Ha'] is not None: + self.mds['KD02comb'][~indx_ig] = self.mds['KK04_N2Ha'][~indx_ig].copy() + if self.mds['KK04_R23'] is not None and self.mds['M91'] is not None: + # if [NII]/[OII] abundance available + # and [NII]/Ha abundance < 8.4, then use R23. + indx = (~np.isnan(self.mds['KK04_R23'])) * (~np.isnan(self.mds['M91'])) * (~indx_ig) + self.mds['KD02comb'][indx] = 0.5 * (self.mds['KK04_R23'][indx].copy() + self.mds['M91'][indx].copy()) + + else: + printsafemulti("WARNING: cannot calculate KK04comb because KK04_R23 or M91, failed", self.logf, self.nps) + +#######################these are the metallicity diagnostics################## +#@profile + def calcPM14(self): + # Perez-Montero 2014 + # (can be used for for log(O/H)+12 > 8.6 only) + import os + from subprocess import Popen, PIPE, STDOUT + from StringIO import StringIO + + printsafemulti("calculating HIICHI", self.logf, self.nps) + fin_hii_chi = open(os.getenv('HIICHI_DIR') + '/in.tmp', 'w') + + if not self.hasHb: + printsafemulti("cannot calculate HIICHI without Hbeta", self.logf, self.nps) + return -1 + + ratios = np.zeros((5, self.nm)) + + if self.R2 is not None: + ratios[0] = self.R2 + elif self.hasO2: + ratios[0] = ((self.O23727 / self.Hb) * self.dustcorrect(k_O2, k_Hb, flux=True)) + else: + ratios[0] = np.array(['0 '] * self.nm) + + #we will never have 4363... + ratios[1] = np.zeros(self.nm) + + if self.hasO3Hb: + ratios[2] = self.O3Hb + elif self.hasO3: + ratios[2] = ((self.O35007 / self.Hb) + self.dustcorrect(k_O35007, k_Hb, flux=True)) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + else: + ratios[2] = np.zeros(self.nm) + + if self.hasN2: + ratios[3] = self.N26584 / self.Hb + else: + ratios[3] = np.zeros(self.nm) + + if self.hasS2Hb: + ratios[4] = self.S2Hb + elif self.hasS2 and self.hasS26731: + ratios[4] = (((self.S26717 + self.S26731) / self.Hb) + self.dustcorrect(k_S2, k_Hb, flux=True)) + else: + ratios[4] = np.zeros(self.nm) + + + for ni in range(self.nm): + fin_hii_chi.write('%f %f %f %f %f\n' % (ratios[0][ni], ratios[1][ni], ratios[2][ni], ratios[3][ni], ratios[4][ni])) + fin_hii_chi.close() + os.system("ln -s %s/C13*dat . " % os.getenv('HIICHI_DIR')) + print "\n\n\n\n\n" + #os.system("python %s/HII-CHI-mistry_v01.2.py in.tmp"%os.getenv('HIICHI_DIR')) + #os.system("python %s/HII-CHI-mistry_v01.2.py %s/in.tmp"%(os.getenv('HIICHI_DIR'),os.getenv('HIICHI_DIR'))) + p = Popen(['python', '%s/HII-CHI-mistry_v01.2.py' % os.getenv('HIICHI_DIR'), '%s/in.tmp' % os.getenv('HIICHI_DIR')], stdout=PIPE, stdin=PIPE, stderr=STDOUT) + out, err = p.communicate(input='%s/in.tmp' % os.getenv('HIICHI_DIR')) + print "\n\n\n\n\n" + out = StringIO(out) + # for l in enumerate(out): + # if l[0].isdigit(): + # break + out = out.readlines()[12:] + self.mds['PM14'] = np.zeros((self.nm)) + self.mds['PM14err'] = np.zeros((self.nm)) + for i, l in enumerate(out): + self.mds['PM14'][i], self.mds['PM14err'][i] = map(float, l.split()[3:5]) + #data = np.loadtxt(out, skiprows=12, usecols=(3,4))#, dtype=[('lOH','f'),('elOH','f')], delimiter=",", unpack = True) + print self.mds + os.system('rm -r C13*dat') diff --git a/build/lib/pyMCZ/pylabsetup.py b/build/lib/pyMCZ/pylabsetup.py new file mode 100644 index 0000000..5ffef05 --- /dev/null +++ b/build/lib/pyMCZ/pylabsetup.py @@ -0,0 +1,41 @@ +import matplotlib as mpl +import pylab as plt + +mpl.rcParams.update(mpl.rcParamsDefault) +mpl.rcParams['font.size'] = 26. +mpl.rcParams['font.family'] = 'serif' +#mpl.rcParams['font.family'] = 'serif' +mpl.rcParams['font.serif'] = ['Times New Roman', 'Times', 'Palatino', 'Charter', 'serif'] +mpl.rcParams['font.sans-serif'] = ['Helvetica'] +mpl.rcParams['axes.labelsize'] = 24 +mpl.rcParams['xtick.labelsize'] = 22. +mpl.rcParams['ytick.labelsize'] = 22. +mpl.rcParams['xtick.major.size'] = 15. +mpl.rcParams['xtick.minor.size'] = 10. +mpl.rcParams['ytick.major.size'] = 15. +mpl.rcParams['ytick.minor.size'] = 10. +#mpl.rcParams['figure.autolayout']= True + +#fontsize=26 +#mpl.rc('axes', titlesize=fontsize) +#mpl.rc('axes', labelsize=fontsize) +#mpl.rc('xtick', labelsize=fontsize) +#mpl.rc('ytick', labelsize=fontsize) +#mpl.rc('font', size=fontsize, family='serif', serif='Utopia', +# style='normal', variant='normal', +# stretch='normal', weight='normal') +#mpl.rc('font',**{'family':'serif','serif':[ 'Times New Roman', 'Times', 'serif'], +# 'sans-serif':['Helvetica'], 'size':19, +# 'weight':'normal'}) +mpl.rc('axes', **{'linewidth' : 1.2}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('ytick', **{'major.pad': 5, 'color': 'k'}) +mpl.rc('xtick', **{'major.pad': 5, 'color': 'k'}) +params = {'legend.fontsize': 24, + 'legend.numpoints': 1, + 'legend.handletextpad': 1 + } + +plt.rcParams.update(params) +plt.minorticks_on() diff --git a/build/lib/pyMCZ/testcompleteness.py b/build/lib/pyMCZ/testcompleteness.py new file mode 100644 index 0000000..43a317b --- /dev/null +++ b/build/lib/pyMCZ/testcompleteness.py @@ -0,0 +1,136 @@ +import os +import sys +import numpy as np +import pylab as pl + +from scipy import stats # , interpolate +import pylabsetup + +#pickle may not be installed +NOPICKLE = False +try: + import pickle +except ImportError: + NOPICKLE = True + + +def fitdistrib(picklefile, scales=None): + from matplotlib.font_manager import findfont, FontProperties + from matplotlib.ticker import FormatStrFormatter + majorFormatter = FormatStrFormatter('%.2f') + + fs = 20 # FontProperties.get_size(FontProperties()) + if 'Time' not in findfont(FontProperties()): + fs = 20 + print "FONT: %s, %d" % (findfont(FontProperties()), fs) + fs = fs - 2 + params = {'axes.labelsize': fs, + 'xtick.labelsize': fs - 3, + 'ytick.labelsize': fs - 3, + 'legend.fontsize': fs - 2, + 'legend.handletextpad': 0.2 + } + pl.rcParams.update(params) + + + if NOPICKLE: + print "you must install pickle to read in the distributions and fit them" + return -1 + assert(os.path.isfile(picklefile)), "missing pickled file %s" % picklefile + pklfile = open(picklefile, 'rb') + res = pickle.load(pklfile) + print scales + if scales: + testingdiags = scales.split(',') + else: + testingdiags = ['E(B-V)', 'D02', 'M13_N2', 'KD02comb'] + + print "\n\n###testing with scales: ", testingdiags, + print "###\n\n" + ebvs = res['E(B-V)'].T + nm = len(ebvs) + Ndata = len(ebvs[0]) + assert(Ndata > 0), "something is wrong with your distribution" + invalids = [sum(np.isnan(res[testingdiags[1]].T[i])) + sum(np.isnan(res[testingdiags[2]].T[i])) + sum(np.isnan(res[testingdiags[3]].T[i])) for i in range(nm)] + myi, = np.where(invalids == min(invalids)) + try: + myi = myi[1] # in case there are more than one solutions + except IndexError: + try: + myi = myi[0] + except IndexError: + pass + xold = np.zeros(10) + assert(Ndata - invalids[myi] > 0), "something is wrong with your distribution. Perhaps one of the default scale has all invalid values? try select different scales" + # -- plotting utils + fwid = 10. # fig width + rat = 1. # fig aspect ratio + offx = 0.08 # x offset in figure coords + offy = 0.05 # y offset in figure coords + psz = 0.4 # size of the plot in figure width coords + nr = 2 # number of row plots + nc = 2 # number of col plots + + fig, axx = pl.subplots(nr, nc, figsize=(fwid, fwid / rat)) + print fig + + for i, d in enumerate(testingdiags): + ii, jj = int(i / 2), int((i + 1) / 2) - int(i / 2) + ax = axx[ii][jj] + ax.set_position([jj / float(nc) + offx, 1.0 - (ii + 1) / float(nr) + offy, psz, psz * rat]) + for f in [0.1, 0.25, 0.5, 0.75, 1]: + n0 = int(f * Ndata) + x = np.random.choice(res[d].T[myi], n0, replace=False) + #print res[d].T[myi] + x = x[x > 0] + + x.sort() + #print x + n = len(x) + + Px_cuml = np.linspace(0, 1, n) + # set up an interpolation of the inverse cumulative distribution + #tck = interpolate.splrep(Px_cuml, x) + + # sample evenly along the cumulative distribution, and interpolate + #Px_cuml_sample = np.linspace(0, 1, 10 * n) + + #x_sample = interpolate.splev(Px_cuml_sample, tck) + + indices = np.linspace(0, n - 1, 20).astype(int) + ax.set_ylim(0, 1.05) + ax.plot(x[indices], Px_cuml[indices], 'o', lw=0, label="%d" % n0) + ax.plot(x, Px_cuml, '-k') + maxleft, maxright = min(x), max(x) + D, p = stats.ks_2samp(x, xold) + print "KS test for %s n = %d D = %.2g; p = %.2g" % (d, n0, D, p) + xold = x.copy() + lims = ax.set_xlim((maxleft, maxright)) + axratio = (lims[1] - lims[0]) / 1.05 + ax.set_aspect(aspect=axratio) + + ax.set_title('%s Cumulative Distribution' % d.replace('_', ' '), fontsize=fs) + ax.set_xlabel('$x$') + ax.set_ylabel('$p( maxleft)] + while (maxright - xticks[-1]) < 0.25 * dx: + xticks = xticks[:-1] + pl.xticks(xticks, ['%.2f' % s for s in xticks]) + pl.savefig(picklefile.replace('.pkl', '_testcomplete.pdf')) + pl.show() + +if __name__ == "__main__": + if len(sys.argv) > 2: + print '''only argument allowed: name of the pickle file containing the distribution + default is '../output/exampledata/exampledata_n2000.pkl' + ''' + sys.exit() + infile = '../output/exampledata/exampledata_n2000.pkl' + if len(sys.argv) > 1: + infile = sys.argv[1] + fitdistrib(infile) diff --git a/build/scripts-2.7/mcz.py b/build/scripts-2.7/mcz.py new file mode 100755 index 0000000..11992a1 --- /dev/null +++ b/build/scripts-2.7/mcz.py @@ -0,0 +1,747 @@ +#!/Users/howk/anaconda/bin/python +import os +import sys +import argparse +import warnings + +import numpy as np +import scipy.stats as stats +from scipy.special import gammaln +from scipy import optimize +import matplotlib.pyplot as plt +from matplotlib.ticker import FormatStrFormatter +import csv as csv + +#modules of this package +import pylabsetup + +#import metallicity_save2 as metallicity +import metallicity as metallicity + +import itertools +import multiprocessing as mpc + +###versin 1.3 February 2016 +#fixed compatyibiity issue with numphy >1.9 in the val to np.histogram https://github.com/numpy/numpy/issues/6469 + +###version 1.3.1 April 2016 +#works with version 0.7 of pyqz (current version) +#previous versions only worked with version 0.5. +#a check for the version of pyqz is implememted and a different call is issued for version >0.5 and <=0.5 + + + +# Define the version of +__version__ = '1.3.1' + +NM0 = 0 # setting this to say N>0 starts the calculation at measurement N. +#this is only for exploratory purposes as the code bugs out before +#plotting and printing the results + +PROFILING = True +PROFILING = False + +alllines = ['[OII]3727', 'Hb', '[OIII]4959', '[OIII]5007', '[OI]6300', 'Ha', '[NII]6584', '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532'] +morelines = ['E(B-V)', 'dE(B-V)', 'scale_blue', 'd scale_blue'] + +MAXPROCESSES = 10 + +#pickle may not be installed +NOPICKLE = False +try: + import pickle +except ImportError: + NOPICKLE = True + +CLOBBER = False +VERBOSE = False +UNPICKLE = False +ASCIIOUTPUT = False +ASCIIDISTRIB = False +RUNSIM = True +NOPLOT = False +BINMODE = 'k' +binning = {'bb': 'Bayesian blocks', 'k': "Knuth's rule", 'd': "Doane's formula", 's': r'$\sqrt{N}$', 't': r'$2 N^{1/3}$', 'kd': 'Kernel Density'} + +MP = False + + +def is_number(s): + if not type(s) is np.string_: + try: + float(s) + return True + except ValueError: + return False + return False + + +def smart_open(logfile=None): + if logfile and logfile != '-': + fh = open(logfile, 'w') + else: + fh = sys.stdout + return fh + + +def getknuth(m, data, N): + m = int(m) + if m > N: + return [-1] + bins = np.linspace(min(data), max(data), int(m) + 1) + try: + nk, bins = np.histogram(data, bins) + return -(N * np.log(m) + gammaln(0.5 * m) - m * gammaln(0.5) - gammaln(N + 0.5 * m) + np.sum(gammaln(nk + 0.5))) + except: + return [-1] + + +def knuthn(data, maxM=None): + assert data.ndim == 1, "data must be 1D array to calculate Knuth's number of bins" + N = data.size + if not maxM: + maxM = 5 * np.sqrt(N) + m0 = 2.0 * N ** (1. / 3.) + gk = getknuth + if gk == [-1]: + return m0, 't' + mkall = optimize.fmin(gk, m0, args=(data, N), disp=VERBOSE, maxiter=30) # , maxfun=1000)#[0] + mk = mkall[0] + if mk > maxM or mk < 0.3 * np.sqrt(N): + mk = m0 + return mk, 't' + return mk, 0 + + +############################################################################## +##The input data +############################################################################## +##Reads the flux file and returns it as an array. +##Ignores non-numeric lines +##Returns (flux array,num) +############################################################################## +def readfile(filename): + noheader = 1 + findex = -1 + f = open(filename, 'r') + l0 = f.readline().replace(' ', '') + l1 = f.readline().split() + if l0.startswith('#') or l0.startswith(';'): + header = l0.strip().replace(";", '').replace("#", '').split(',') + header[0] = header[0].replace(' ', '') + header = header[:len(l1)] + else: + noheader = 0 + header = ['galnum'] + alllines + ['flag'] + morelines + header = header[:len(l1)] + + formats = ['S10'] + ['f'] * (len(header) - 1) + if 'flag' in header: + findex = header.index('flag') + formats[findex] = 'S10' + + bstruct = {} + for i, k in enumerate(header): + bstruct[k] = [i, 0] + b = np.loadtxt(filename, skiprows=noheader, dtype={'names': header, 'formats': formats}, comments=';') + if b.size == 1: + b = np.atleast_1d(b) + + for i, k in enumerate(header): + if not k == 'flag' and is_number(b[k][0]): + bstruct[k][1] = np.count_nonzero(b[k]) + sum(np.isnan(b[k])) + j = len(b['galnum']) + return b, j, bstruct + + +def ingest_data(filename, path): + ###Initialize### + measfile = os.path.join(path, filename + "_meas.txt") + errfile = os.path.join(path, filename + "_err.txt") + + ###read the max, meas, min flux files### + meas, nm, bsmeas = readfile(measfile) + err, nm, bserr = readfile(errfile) + try: + snr = (meas[: + ,1:].view(np.float32).reshape(meas[: + ,1:].shape + (-1, ))[: + ,1:]) / (err[: + ,1:].view(np.float32).reshape(err[: + ,1:].shape + (-1, ))[: + ,1:]) + if snr[~np.isnan(snr)].any() < 3: + raw_input('''WARNING: signal to noise ratio smaller than 3 + for at least some lines! You should only use SNR>3 + measurements (return to proceed)''') + except (IndexError, TypeError): + pass + return (filename, meas, err, nm, path, (bsmeas, bserr)) + + +def input_data(filename, path): + p = os.path.join(path, "input") + assert os.path.isdir(p), "bad data directory %s" % p + if os.path.isfile(os.path.join(p, filename + '_err.txt')): + if os.path.isfile(os.path.join(p, filename + '_meas.txt')): + return ingest_data(filename, path=p) + print "Unable to find _meas and _err files ", filename + '_meas.txt', filename + '_err.txt', "in directory ", p + return -1 + + +############################################################################## +##returns a random distribution. In the deployed version of the code this is a gaussian distribution, but the user can include her or his distribution. +##return a random sample of size n +############################################################################## +def errordistrib(distrargs, n, distype='normal'): + if distype == 'normal': + try: + mu, sigma = distrargs + except: + print "for normal distribution distargs must be a 2 element tuple" + return -1 + return np.random.normal(mu, sigma, n) + else: + print "distribution not supported" + return -1 + + +############################################################################## +##returns appropriate bin size for the number of data +##mode 'k' calculates this based on Knuth's rule +##mode 'd' calculates this based on Doane's formula +##mode 's' calculates this based on sqrt of number of data +##mode 't' calculates this based on 2*n**1/3 (default) +############################################################################## +def getbinsize(n, data, ): + if BINMODE == 'd': + g1 = np.abs(stats.mstats.moment(data, moment=3)) # /data.std()) + s1 = np.sqrt(float(n) / 6.0) + #s1=1.0/np.sqrt(6.*(n-2.)/((n+1.)*(n+3.))) + k = 1 + np.log2(n) + np.log2(1 + (g1 * s1)), 0 + elif BINMODE == 's': + k = np.sqrt(n), 0 + elif BINMODE == 't': + k = 2. * n ** (1. / 3.), 0 + else: + k = knuthn(data) + return k + + +############################################################################## +##Check if hist files already exist and need to be replaced +############################################################################## +def checkhist(snname, Zs, nsample, i, path): + global CLOBBER + + name = '%s_n%d_%s_%d' % ((snname, nsample, Zs, i + 1)) + outdir = os.path.join(path, 'hist') + outfile = os.path.join(outdir, name + ".pdf") + if os.path.isfile(outfile) and not CLOBBER: + replace = raw_input("replacing existing image files, starting with: %s ? [Y/n]\n" % outfile).lower() + assert(not (replace.startswith('n'))), "save your existing output directory under another name first" + CLOBBER = True + + +############################################################################## +##Save the result as histogram as name +############################################################################## +#@profile +def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False, fs=24, reserr=None): + global BINMODE + #global NOPLOT + + name = '%s_n%d_%s_%d' % ((snname, nsample, Zs, i + 1)) + outdir = os.path.join(path, 'hist') + outfile = os.path.join(outdir, name + ".pdf") + if not NOPLOT: + fig = plt.figure(figsize=(11, 8)) + plt.clf() + + ####kill outliers, infinities, and bad distributions### + data = data[np.isfinite(data)] + + n = data.shape[0] + kde = None + if not n > 0: + if verbose: + print "data must be an actual distribution (n>0 elements!, %s)" % Zs + return "-1,-1,_1", [], kde + + if data.shape[0] <= 0 or np.sum(data) <= 0: + print '{0:15} {1:20} {2:>13d} {3:>7d} {4:>7d} '.format(snname, Zs, -1, -1, -1) + return "-1, -1, -1", [], kde + try: + ###find C.I.### + median, pc16, pc84 = np.percentile(data, [50, 16, 84]) + std = np.std(data) + left = pc16 + right = pc84 + maxleft = median - std * 5 + maxright = median + std * 5 + if "%2f" % maxright == "%2f" % maxleft: + maxleft = median - 1 + maxright = median + 1 + if round(right, 6) == round(left, 6) and round(left, 6) == round(median, 6): + print '{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(snname, Zs, median, 0, 0) + if reserr: + print '+/- {0:.3f}'.format(reserr) + return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde # "-1,-1,-1",[] + ###print out the confidence interval### + print '{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f}'.format(snname, Zs, round(median, 3), round(median - left, 3), round(right - median, 3)) + if reserr: + print '+/- {0:.3f}'.format(reserr) + alpha = 1.0 + + ######histogram###### + if BINMODE == 'kd': + ##if sklearn is available use it to get Kernel Density + try: + from sklearn.neighbors import KernelDensity + except ImportError: + print '''sklearn is not available, + thus we cannot compute kernel density. + switching to bayesian blocks''' + BINMODE = 'bb' + if BINMODE == 'kd': + ##bw is chosen according to Silverman 1986 + bw = 1.06 * std * n ** (-0.2) + numbin, bm = getbinsize(data.shape[0], data) + distrib = np.histogram(data, bins=int(numbin), density=True) + ###make hist### + counts, bins = distrib[0], distrib[1] + widths = np.diff(bins) + countsnorm = counts / np.max(counts) + + if bw > 0: + kde = KernelDensity(kernel='gaussian', bandwidth=bw).fit(data[: + , np.newaxis]) + kdebins = np.linspace(maxleft, maxright, 1000)[: + , np.newaxis] + log_dens = kde.score_samples(kdebins) + dens = np.exp(log_dens) + norm = countsnorm.sum() * (bins[1] - bins[0]) / dens.sum() / (kdebins[1] - kdebins[0]) + if not NOPLOT: + plt.fill(kdebins[: + ,0], dens * norm, fc='#7570b3', alpha=0.8) + alpha = 0.5 + + ###find appropriate bin size### + else: + if BINMODE == 'bb': + ##if astroML is available use it to get Bayesian blocks + bm = 0 + try: + from astroML.plotting import hist as amlhist + if BINMODE == 'bb': + distrib = amlhist(data, bins='blocks', normed=True) + if not NOPLOT: + plt.clf() + except ImportError: + print "bayesian blocks for histogram requires astroML to be installed" + print "defaulting to Knuth's rule " + ##otherwise + numbin, bm = getbinsize(data.shape[0], data) + distrib = np.histogram(data, bins=int(numbin), density=True) + else: + numbin, bm = getbinsize(data.shape[0], data) + distrib = np.histogram(data, bins=int(numbin), density=True) + + ###make hist### + counts, bins = distrib[0], distrib[1] + widths = np.diff(bins) + countsnorm = counts / np.max(counts) + + ###plot hist### + if NOPLOT: + return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde + + plt.bar(bins[:-1], countsnorm, widths, color=['gray'], alpha=alpha) + plt.minorticks_on() + plt.gca().xaxis.set_major_formatter(FormatStrFormatter('%.2f')) + plt.xlim(maxleft, maxright) + + #the following lines assure the x tick label is + #within the length of the x axis + xticks = plt.xticks()[0] + dx = xticks[-1] - xticks[-2] + xticks = xticks[(xticks < maxright) * (xticks > maxleft)] + if (maxright - xticks[-1]) < 0.25 * dx: + maxright = maxright + 0.25 * dx + maxleft = maxleft - 0.25 * dx + plt.xlim(maxleft, maxright) + plt.xticks(xticks, ['%.2f' % s for s in xticks]) + + plt.ylim(0, 1.15) + plt.yticks(np.arange(0.2, 1.3, 0.2), ["%.1f" % x for x in np.arange(0.2, 1.1, 0.2)]) + plt.axvspan(left, right, color='DarkOrange', alpha=0.4) + plt.axvline(x=median, linewidth=2, color='white', ls='--') + + #labels and legends + st = '%s ' % (snname) + plt.annotate(st, xy=(0.13, 0.6), xycoords='axes fraction', size=fs, fontweight='bold') + st = '%s ' % (Zs.replace('_', ' ')) + plt.annotate(st, xy=(0.61, 0.93), xycoords='axes fraction', fontsize=fs, fontweight='bold') + st = 'measurement %d of %d\n %s\nmedian: %.3f\n16th Percentile: %.3f\n84th Percentile: %.3f' % (i + 1, nmeas, measnames[i], round(median, 3), round(left, 3), round(right, 3)) + plt.annotate(st, xy=(0.61, 0.65), xycoords='axes fraction', fontsize=fs) + effectiven = len(data[~np.isnan(data)]) + if effectiven: + st = 'MC sample size %d (%d)\nhistogram rule: %s' % (effectiven, nsample, binning[BINMODE]) + if bm: + if effectiven < nsample: + st = 'MC sample size %d (%d)\nhistogram rule: %s' % (effectiven, nsample, binning[bm]) + else: + st = 'MC sample size %d\nhistogram rule: %s' % (nsample, binning[bm]) + plt.annotate(st, xy=(0.61, 0.55), xycoords='axes fraction', fontsize=fs - 5) + if "E(B-V)" in Zs: + plt.xlabel('E(B-V) [mag]') + outfile = outfile.replace('(', '').replace(')', '') + elif "logR23" in Zs: + plt.xlabel('logR23') + else: + plt.xlabel('12+log(O/H)') + plt.ylabel('relative counts') + plt.savefig(outfile, format='pdf') + plt.close(fig) + + return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde + + except (OverflowError, AttributeError, ValueError): + if VERBOSE: + print data + print name, 'had infinities (or something in plotting went wrong)' + return "-1, -1,-1", [], None + + +def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr, verbose, res, scales, nps, logf))): + logf = sys.stdout + print >> logf, "\n\nreading in measurements ", i + 1 + fluxi = {} # np.zeros((len(bss[0]),nm),float) + for k in bss[0].iterkeys(): + print >> logf, '{0:15} '.format(k), + print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) + fluxi[k] = flux[k][i] * np.ones(len(sample[i])) + err[k][i] * sample[i] + warnings.filterwarnings("ignore") + success = metallicity.calculation(scales[i], fluxi, nm, mds, nps, logf, disp=disp, dust_corr=dust_corr, verbose=verbose) + if success == -1: + print >> logf, "MINIMUM REQUIRED LINES: '[OII]3727','[OIII]5007','[NII]6584','[SII]6717'" + + for key in scales[i].mds.iterkeys(): + if key in res.keys(): + res[key][i] = scales[i].mds[key] + if res[key][i] is None: + res[key][i] = [float('NaN')] * len(sample) + return res + + +############################################################################## +## The main function. takes the flux and its error as input. +## filename - a string 'filename' common to the three flux files +## flux - np array of the fluxes +## err - the flux errors, must be the same dimension as flux +## nsample - the number of samples the code will generate. Default is 100 +## errmode - determines which method to choose the bin size. +## mode 'k' calculates this based on Knuth's rule (default) +## mode 'd' calculates this based on Doane's formula +## mode 's' calculates this based on sqrt of number of data +## mode 't' calculates this based on 2*n**1/3 +############################################################################## +#@profile +def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickle=False, dust_corr=True, verbose=False, fs=24): + global RUNSIM # ,BINMODE#,NOPLOT + assert(len(flux[0]) == len(err[0])), "flux and err must be same dimensions" + assert(len(flux['galnum']) == nm), "flux and err must be of declaired size" + assert(len(err['galnum']) == nm), "flux and err must be same dimensions" + + #increasing sample by 10% to assure robustness against rejected samples + newnsample = nsample + if nsample > 1: + newnsample = int(nsample + 0.1 * nsample) + + p = os.path.join(path, '..') + + ###retrieve the metallicity keys + Zs = metallicity.get_keys() + Zserr = metallicity.get_errkeys() + + ###make necessary paths for output files + if not os.path.exists(os.path.join(p, 'output', '%s' % name)): + os.makedirs(os.path.join(p, 'output', '%s' % name)) + if not os.path.exists(os.path.join(p, 'output', '%s' % name, 'hist')): + os.makedirs(os.path.join(p, 'output', '%s' % name, 'hist')) + binp = os.path.join(p, 'output', '%s' % name) + picklefile = os.path.join(binp, '%s_n%d.pkl' % (name, nsample)) + if VERBOSE: + print "output files will be stored in ", binp + if not CLOBBER and not NOPLOT: + for key in Zs: + for i in range(NM0, nm): + checkhist(name, key, nsample, i, binp) + if unpickle: + RUNSIM = False + if not os.path.isfile(picklefile): + raw_input("missing pickled file for this simulation: name, nsample.\nrun the MonteCarlo? Ctr-C to exit, Return to continue?\n") + RUNSIM = True + else: + pklfile = open(picklefile, 'rb') + res = pickle.load(pklfile) + + if RUNSIM: + ###Sample 'nsample' points from a gaussian centered on 0 with std 1 + mu = 0 + sigma = 1 + dargs = (mu, sigma) + if nsample == 1: + sample = [np.array([mu]) for i in range(NM0, nm)] + else: + sample = [errordistrib(dargs, newnsample) for i in range(NM0, nm)] + if sample == -1: + return -1 + ###Start calculation### + ## the flux to be feed to the calculation will be + ## flux + error*i + ## where i is the sampled gaussian + if VERBOSE: + print "Starting iteration" + + #initialize the dictionary + res = {} + reserr = {} + for key in Zs: + res[key] = [[] for i in range(NM0, nm)] + for key in Zserr: + reserr[key] = [[] for i in range(NM0, nm)] + #use only valid inputs + delkeys = [] + for k in bss[0].iterkeys(): + if k == 'flag' or k == 'galnum' or bss[0][k][1] == float('nan'): # or bss[1][k][1]==bss[0][k][1]: + delkeys.append(k) + for k in delkeys: + del bss[0][k] + del bss[1][k] + + import metscales as ms + nps = min(mpc.cpu_count() - 1 or 1, MAXPROCESSES) + + if multiproc and nps > 1: + scales = [ms.diagnostics(newnsample, logf, nps) for i in range(nm)] + + print >> logf, "\n\n\nrunning on %d threads\n\n\n" % nps + second_args = [sample, flux, err, nm, bss, mds, VERBOSE, dust_corr, VERBOSE, res, scales, nps, logf] + pool = mpc.Pool(processes=nps) # depends on available cores + rr = pool.map(calc, itertools.izip(range(NM0, nm), itertools.repeat(second_args))) # for i in range(nm): result[i] = f(i, second_args) + for ri, r in enumerate(rr): + for kk in r.iterkeys(): + res[kk][ri] = r[kk][ri] + + for ri, r in enumerate(rr): + for kk in r.iterkeys(): + res[kk][ri] = r[kk][ri] + pool.close() # not optimal! but easy + pool.join() + for key in scales[i].mds.iterkeys(): + if key in Zs: + res[key] = np.array(res[key]).T + elif key in reserr.keys(): + reserr[key] = np.array(reserr[key]).T + if res[key][i] is None: + res[key][i] = [float('NaN')] * len(sample) + + if VERBOSE: + print "Iteration Complete" + else: + #looping over nm spectra + for i in range(NM0, nm): + scales = ms.diagnostics(newnsample, logf, nps) + print >> logf, "\n\n measurements ", i + 1 + fluxi = {} + + for k in bss[0].iterkeys(): + print >> logf, '{0:15} '.format(k), + print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) + fluxi[k] = flux[k][i] * np.ones(len(sample[i])) + err[k][i] * sample[i] + warnings.filterwarnings("ignore") + print >> logf, "" + + success = metallicity.calculation(scales, fluxi, nm, mds, 1, logf, disp=VERBOSE, dust_corr=dust_corr, verbose=VERBOSE) + if success == -1: + print "MINIMUM REQUIRED LINES: [OII]3727 & [OIII]5007, or [NII]6584, and Ha & Hb if you want dereddening" + #continue + + + for key in scales.mds.iterkeys(): + if not key in Zs: + continue + res[key][i] = scales.mds[key] + if res[key][i] is None: + res[key][i] = [float('NaN')] * newnsample + elif len(res[key][i]) < newnsample: + res[key][i] = res[key][i] + [float('NaN')] * (newnsample - len(res[key][i])) + + for key in scales.mds.iterkeys(): + if key in Zs: + res[key] = np.array(res[key]).T + if VERBOSE: + print "Iteration Complete" + + #"WE CAN PICKLE THIS!" + #pickle this realization + if not NOPICKLE: + pickle.dump(res, open(picklefile, 'wb')) + + from matplotlib.font_manager import findfont, FontProperties + + if 'Time' not in findfont(FontProperties()): + fs = 20 + if VERBOSE: + print "FONT: %s, %d" % (findfont(FontProperties()), fs) + + ###Bin the results and save### + print "\n\n" + print '{0:15} {1:20} {2:>13} -{3:>5} +{4:>5} {5:11} {6:>7}'.format("SN", "diagnostic", "metallicity", "34%", "34%", "(sample size:", '%d)' % nsample) + for i in range(NM0, nm): + if ASCIIOUTPUT: + fi = open(os.path.join(binp, '%s_n%d_%d.txt' % (name, nsample, i + 1)), 'w') + fi.write("%s\t Median Oxygen abundance (12+log(O/H))\t 16th percentile\t 84th percentile\n" % name) + + boxlabels = [] + datas = [] + print "\n\nmeasurement %d : %s-------------------------------------------------------------" % (i + 1, flux[i]['galnum']) + for key in Zs: + if nsample == -1: + try: + if ~np.isnan(res[key][i][0]): + print '{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(name + ' %d' % (i + 1), key, res[key][i][0], 0, 0) + except IndexError: + pass + else: + reserr = None + try: + if sum(~np.isnan(res[key][: + ,i])) > 0: + if ASCIIDISTRIB: + with open(os.path.join(binp, '%s_n%d_%s_%d.csv' % (name, nsample, key, i + 1)), "wb") as fidist: + writer = csv.writer(fidist) + writer.writerow(res[key][: + ,i]) + if 'PM14' in key: + print reserr['PM14err'] + reserr = np.sqrt(~np.nansum(reserr['PM14err'][: + ,i] ** 2)) + sh, data, kde = savehist(res[key][: + ,i], name, key, nsample, i, binp, nm, flux[:]['galnum'], verbose=verbose, fs=fs, reserr=reserr) + s = key + "\t " + sh + '\n' + if ASCIIOUTPUT: + fi.write(s) + if key not in ["E(B-V)", "logR23"]: + boxlabels.append(key.replace('_', ' ')) + datas.append(data) + if BINMODE == 'kd' and not NOPICKLE: + pickleKDEfile = os.path.join(binp + '/%s_n%d_%s_%d_KDE.pkl' % (name, nsample, key, i + 1)) + if VERBOSE: + print "KDE files will be stored in ", pickleKDEfile + pickle.dump(kde, open(pickleKDEfile, 'wb')) + except (IndexError, TypeError): + pass + #make box_and_whiskers plot + fig = plt.figure(figsize=(8, 15)) + fig.subplots_adjust(bottom=0.18, left=0.18) + ax = fig.add_subplot(111) + plt.grid() + if len(datas) == 0: + continue + + bp = ax.boxplot(datas, patch_artist=True) + for box in bp['boxes']: + box.set(color='#7570b3', linewidth=2) + box.set(facecolor='DarkOrange', alpha=0.4) + for whisker in bp['whiskers']: + whisker.set(color='#7570b3', linewidth=2) + for cap in bp['caps']: + cap.set(color='#7570b3', linewidth=2) + for median in bp['medians']: + median.set(color='k', linewidth=2) + for flier in bp['fliers']: + flier.set(marker='o', color='#7570b3', alpha=0.4) + plt.title("measurement %d: %s" % (i + 1, flux[i]['galnum'])) + plt.xticks(range(1, len(boxlabels) + 1), boxlabels, rotation=90, fontsize=fs - 5) + plt.fill_between(range(1, len(boxlabels) + 1), [8.76] * len(boxlabels), [8.69] * len(boxlabels), facecolor='black', alpha=0.3) + plt.text(1.2, 8.705, "Solar Oxygen Abundance", alpha=0.7) + plt.gca().yaxis.set_major_formatter(FormatStrFormatter('%.2f')) + plt.ylabel('12+log(O/H)', fontsize=fs) + plt.savefig(binp + "/" + name + "_boxplot_n%d_%d.pdf" % (nsample, i + 1), format='pdf') + if ASCIIOUTPUT: + fi.close() + if VERBOSE: + print "uncertainty calculation complete" + + #del datas + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('name', metavar='', type=str, help="the SN file name (root of the _min,_max file names") + parser.add_argument('nsample', metavar='N', type=int, help="number of iterations, minimum 100 (or 0 for no MC sampling)") + parser.add_argument('--clobber', default=False, action='store_true', help="replace existing output") + parser.add_argument('--binmode', default='k', type=str, choices=['d', 's', 'k', 't', 'bb', 'kd'], help='''method to determine bin size + {d: Duanes formula, s: n^1/2, t: 2*n**1/3(default), k: Knuth's rule, + bb: Bayesian blocks, kd: Kernel Density}''') + parser.add_argument('--path', default=None, type=str, help='''input/output path (must contain the input _max.txt and + _min.txt files in a subdirectory sn_data)''') + parser.add_argument('--unpickle', default=False, action='store_true', help="read the pickled realization instead of making a new one") + + parser.add_argument('--verbose', default=False, action='store_true', help="verbose mode") + parser.add_argument('--log', default=None, type=str, help="log file, if not passed defaults to standard output") + parser.add_argument('--nodust', default=False, action='store_true', help=" don't do dust corrections (default is to do it)") + parser.add_argument('--noplot', default=False, action='store_true', help=" don't plot individual distributions (default is to plot all distributions)") + parser.add_argument('--asciiout', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") + parser.add_argument('--asciidistrib', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") + parser.add_argument('--md', default='all', type=str, help='''metallicity diagnostic to calculate. + default is 'all', options are: + D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, KD02, DP00 (deprecated), P01''') + parser.add_argument('--multiproc', default=False, action='store_true', help=" multiprocess, with number of threads max(available cores-1, MAXPROCESSES)") + args = parser.parse_args() + + global CLOBBER + global VERBOSE + global BINMODE + global ASCIIOUTPUT + global ASCIIDISTRIB + global NOPLOT + CLOBBER = args.clobber + VERBOSE = args.verbose + BINMODE = args.binmode + NOPLOT = args.noplot + ASCIIOUTPUT = args.asciiout + ASCIIDISTRIB = args.asciidistrib + + if args.unpickle and NOPICKLE: + args.unpickle = False + raw_input("cannot use pickle on this machine, we won't save and won't read saved pickled realizations. Ctr-C to exit, Return to continue?\n") + + if args.path: + path = args.path + else: + assert (os.getenv("MCMetdata")), ''' the _max, _min (and _med) data must live in a folder named sn_data. + pass a path to the sn_data folder, or set up the environmental variable + MCMetdata pointing to the path where sn_data lives ''' + path = os.getenv("MCMetdata") + assert(os.path.isdir(path)), "pass a path or set up the environmental variable MCMetdata pointing to the path where the _min _max _med files live" + if args.nsample == 1: + print "CALCULATING METALLICITY WITHOUT GENERATING MC DISTRIBUTIONS" + if args.nsample == 1 or args.nsample >= 10: + fi = input_data(args.name, path=path) + if fi != -1: + logf = smart_open(args.log) + run(fi, args.nsample, args.md, args.multiproc, logf, unpickle=args.unpickle, dust_corr=(not args.nodust), verbose=VERBOSE) + if args.log: + logf.close() + else: + print "nsample must be at least 100" + + +if __name__ == "__main__": + if PROFILING: + import cProfile + cProfile.run("main()") + else: + main() diff --git a/build/scripts-2.7/metallicity.py b/build/scripts-2.7/metallicity.py new file mode 100755 index 0000000..afc7ec1 --- /dev/null +++ b/build/scripts-2.7/metallicity.py @@ -0,0 +1,245 @@ +############################################################################## +## Calculates oxygen abundance (here called metalicity) based on strong emission lines, +## based on code originally written in IDL by Lisa Kewley (Kewley & Ellison 2008). Outputs +## oxygen abundance in many different diagnostics (see Bianco et al. 2016). +## +##new calculation based on the most recent version of the .pro file. +## +##inputs: +## measured - flux data, must be the format returned by readfile() +## num - number of spectra for which to calculate metallicity, also returned by readfile() +## outfilename - the name of the file the results will be appended to +## red_corr - reddening correction flag - True by default +## disp - if True prints the results, default False +############################################################################## + +import sys +import os +import numpy as np + +IGNOREDUST = False +MP = True +FIXNEGATIVES = True # set to true if no negative flux measurements should be allowed. all negative flux measurements are set to 0 + +##list of metallicity methods, in order calculated +Zs = ["E(B-V)", # based on Halpha, Hbeta + "logR23", # Hbeta, [OII]3727, [OIII]5007, [OIII]4959 + + "D02", # Halpha, [NII]6584 + "Z94", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "M91", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "C01_N2S2", # [OII]3727, [OIII]5007, [NII]6584, [SII]6717 + "C01_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + + "P05", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "P01", # available but deprecated + "PP04_N2Ha", # Halpha, [NII]6584 + "PP04_O3N2", # Halpha, Hbeta,[OIII]5007, [NII]6584 + "DP00", # S23 available but deprecated + "P10_ONS", "P10_ON", + "M08_R23", "M08_N2Ha", "M08_O3Hb", "M08_O2Hb", "M08_O3O2", "M08_O3N2", + "M13_O3N2", "M13_N2", + "D13_N2S2_O3S2", "D13_N2S2_O3Hb", + "D13_N2S2_O3O2", "D13_N2O2_O3S2", + "D13_N2O2_O3Hb", "D13_N2O2_O3O2", + "D13_N2Ha_O3Hb", "D13_N2Ha_O3O2", + "KD02_N2O2", # Halpha, Hbeta, [OII]3727, [NII]6584 + "KD02_N2S2", + "KK04_N2Ha", # Halpha, Hbeta, [OII]3727, [NII]6584 + "KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "KD02comb", + "PM14"] # ,"KK04comb"] +#'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' + +Zserr = ['PM14err'] # ,"KK04comb"] +#'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' + + +def get_keys(): + return Zs + + +def get_errkeys(): + return Zserr + + +def printsafemulti(string, logf, nps): + #this is needed because dealing with a log output with multiprocessing + #is painful. but it introduces a bunch of if checks. + #if anyone has a better solution please let me know! + if nps == 1: + print >> logf, string + else: + print string + + +############################################################################## +##fz_roots function as used in the IDL code FED:reference the code here! +############################################################################## + +#@profile +def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=False, verbose=False): + + global IGNOREDUST + mscales.setdustcorrect() + raw_lines = {} + raw_lines['[OIII]5007'] = np.array([float('NaN')]) + raw_lines['Hb'] = np.array([float('NaN')]) + raw_lines['Hz'] = np.array([float('NaN')]) + for k in measured.iterkeys(): + #kills all non-finite terms + measured[k][~(np.isfinite(measured[k][:]))] = 0.0 + if FIXNEGATIVES: + measured[k][measured[k] < 0] = 0.0 + raw_lines[k] = measured[k] + + ######we trust QM better than we trust the measurement of the [OIII]4959 + ######which is typical low S/N so we set it to [OIII]5007/3. + ######change this only if youre spectra are very high SNR + raw_lines['[OIII]4959'] = raw_lines['[OIII]5007'] / 3. + raw_lines['[OIII]49595007'] = raw_lines['[OIII]4959'] + raw_lines['[OIII]5007'] + mscales.setHab(raw_lines['Ha'], raw_lines['Hb']) + + #if Ha or Hb is zero, cannot do red correction + if dust_corr and mscales.hasHa and mscales.hasHb: + with np.errstate(invalid='ignore'): + mscales.calcEB_V() + elif dust_corr and not IGNOREDUST: + + if nps > 1: + print '''WARNING: reddening correction cannot be done + without both H_alpha and H_beta measurement!!''' + + else: + response = raw_input('''WARNING: reddening correction cannot be done without both H_alpha and H_beta measurement!! + Continuing without reddening correction? [Y/n]\n''').lower() + assert(not (response.startswith('n'))), "please fix the input file to include Ha and Hb measurements" + + IGNOREDUST = True + dust_corr = False + mscales.mds['E(B-V)'] = np.ones(len(raw_lines['Ha'])) * 1e-5 + else: + mscales.unsetdustcorrect() + mscales.mds['E(B-V)'] = np.ones(len(raw_lines['Ha'])) * 1e-5 + + for k in ['[OII]3727', '[OIII]5007', '[OI]6300', '[OIII]4959', + '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532' + '[OII]3727', '[OIII]5007', '[OI]6300', '[OIII]4959', + '[NII]6584', '[SIII]9532']: + if k not in raw_lines or (len(raw_lines[k]) == 1 and np.isnan(raw_lines[k][0])): + raw_lines[k] = np.array([0.] * num) + + mscales.setOlines(raw_lines['[OII]3727'], raw_lines['[OIII]5007'], raw_lines['[OI]6300'], raw_lines['[OIII]4959']) + mscales.setSII(raw_lines['[SII]6717'], raw_lines['[SII]6731'], raw_lines['[SIII]9069'], raw_lines['[SIII]9532']) + mscales.setNII(raw_lines['[NII]6584']) + + if mscales.checkminimumreq(dust_corr, IGNOREDUST) == -1: + return -1 + + mscales.calcNIIOII() + mscales.calcNIISII() + + mscales.calcR23() + #mscales.calcS23() + + mscales.initialguess() + mds = mds.split(',') + + + #mscales.printme() + if verbose: + print "calculating metallicity diagnostic scales: ", mds + if 'all' in mds: + mscales.calcD02() + if os.getenv("PYQZ_DIR"): + cmd_folder = os.getenv("PYQZ_DIR") + '/' + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + mscales.calcpyqz() + else: + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable : + export PYQZ_DIR="your/path/where/pyqz/resides/ in bash, for example, if you want this scale. ''', logf, nps) + + mscales.calcZ94() + mscales.calcM91() + + mscales.calcPP04() + + #mscales.calcP05() + mscales.calcP10() + + mscales.calcM08() + mscales.calcM13() + + mscales.calcKD02_N2O2() + mscales.calcKK04_N2Ha() + + mscales.calcKK04_R23() + mscales.calcKDcombined() + + if 'DP00' in mds: + mscales.calcDP00() + if 'P01' in mds: + mscales.calcP01() + + if 'D02' in mds: + mscales.calcD02() + if 'D13' in mds: + if os.getenv("PYQZ_DIR"): + cmd_folder = os.getenv("PYQZ_DIR") + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + #mscales.calcpyqz() + #in order to see the original pyqz plots + #call pyqz with option plot=True by + #using the commented line below instead + mscales.calcpyqz(plot=disp) + else: + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable + PYQZ_DIR if you want this scale. ''', logf, nps) + + if 'D13all' in mds: + if os.getenv("PYQZ_DIR"): + cmd_folder = os.getenv("PYQZ_DIR") + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + #mscales.calcpyqz() + #in order to see the original pyqz plots + #call pyqz with option plot=True by + #using the commented line below instead + mscales.calcpyqz(plot=disp, allD13=True) + else: + printsafemulti('''set path to pyqz as environmental variable +PYQZ_DIR if you want this scale. ''', logf, nps) + + if 'PM14' in mds: + if os.getenv("HIICHI_DIR"): + cmd_folder = os.getenv("HIICHI_DIR") + '/' + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + mscales.calcPM14() + if 'PP04' in mds: + mscales.calcPP04() + if 'Z94' in mds: + mscales.calcZ94() + if 'M91' in mds: + mscales.calcM91() + if 'P10' in mds: + mscales.calcP10() + if 'M13' in mds: + mscales.calcM13() + if 'M08all' in mds: + mscales.calcM08(allM08=True) + elif 'M08' in mds: + mscales.calcM08() + if 'P05' in mds: + mscales.calcP05() + if 'C01' in mds: + mscales.calcC01_ZR23() + if 'KD02' in mds: + mscales.calcKD02_N2O2() + mscales.calcKK04_N2Ha() + mscales.calcKK04_R23() + mscales.calcKDcombined() diff --git a/build/scripts-2.7/metscales.py b/build/scripts-2.7/metscales.py new file mode 100755 index 0000000..bb059ed --- /dev/null +++ b/build/scripts-2.7/metscales.py @@ -0,0 +1,1273 @@ +import numpy as np +#import sys +import scipy.stats as stats +import numpy.polynomial.polynomial as nppoly +from metallicity import get_keys, printsafemulti + +niter = 5 # number of iteations+1 for KD02 methods + +k_Ha = 2.535 # CCM Rv=3.1 +k_Hb = 3.609 # CCM Rv=3.1 + +#k_O1=2.661 # CCM Rv=3.1 +k_O2 = 4.771 # CCM Rv=3.1 +k_O35007 = 3.341 # CCM Rv=3.1 +k_O34959 = 3.384 # CCM Rv=3.1 +k_O3 = (k_O35007 + k_O34959) / 2. + +k_N2 = 2.443 # CCM Rv=3.1 +k_S2 = 2.381 # CCM Rv=3.1 + +k_S3 = 1 # guess for CCM Rv=3.1 + +global DUSTCORRECT +DUSTCORRECT = True + +''' +R23_coef=np.zeros((5,7)) # coefficients from model grid fits +R23c0=[-3267,-3727.42,-4282.30,-4745.18,-4516.46,-3509.63,-1550.53] +R23_coef[:,0]=[-3267.93,1611.04,-298.187,24.5508,-0.758310] # q=5e6 +R23_coef[:,1]=[-3727.42,1827.45,-336.340,27.5367,-0.845876] # q=1e7 +R23_coef[:,2]=[-4282.30,2090.55,-383.039,31.2159,-0.954473] # q=2e7 +R23_coef[:,3]=[-4745.18,2309.42,-421.778,34.2598,-1.04411] # q=4e7 +R23_coef[:,4]=[-4516.46,2199.09,-401.868,32.6686,-0.996645] # q=8e7 +R23_coef[:,5]=[-3509.63,1718.64,-316.057,25.8717,-0.795242] # q=1.5e8 +R23_coef[:,6]=[-1550.53,784.262,-149.245,12.6618,-0.403774] # q=3e8 + + +N2S2_coef=np.zeros((5,7)) # coefficients from model grid fits +N2S2c0=[-1042.47,-1879.46,-2027.82,-2080.31,-2162.93,-2368.56,-2910.63] +N2S2_coef[:,0]=[-1042.47,521.076,-97.1578,8.00058,-0.245356] +N2S2_coef[:,1]=[-1879.46,918.362,-167.764,13.5700,-0.409872] +N2S2_coef[:,2]=[-2027.82,988.218,-180.097,14.5377,-0.438345] +N2S2_coef[:,3]=[-2080.31,1012.26,-184.215,14.8502,-0.447182] +N2S2_coef[:,4]=[-2162.93,1048.97,-190.260,15.2859,-0.458717] +N2S2_coef[:,5]=[-2368.56,1141.97,-205.908,16.4451,-0.490553] +N2S2_coef[:,6]=[-2910.63,1392.18,-249.012,19.7280,-0.583763] + +O3O2_coef=np.zeros((4,8)) # coefficients from model grid fits +O3O2c0=[-36.9772,-74.2814,-36.7948,-81.1880,-52.6367,-86.8674,-24.4044,49.4728] +O3O2_coef[:,0]=[-36.9772,10.2838,-0.957421,0.0328614] #z=0.05 +O3O2_coef[:,1]=[-74.2814,24.6206,-2.79194,0.110773] # z=0.1 +O3O2_coef[:,2]=[-36.7948,10.0581,-0.914212,0.0300472] # z=0.2 +O3O2_coef[:,3]=[-81.1880,27.5082,-3.19126,0.128252] # z=0.5 +O3O2_coef[:,4]=[-52.6367,16.0880,-1.67443,0.0608004] # z=1.0 +O3O2_coef[:,5]=[-86.8674,28.0455,-3.01747,0.108311] # z=1.5 +O3O2_coef[:,6]=[-24.4044,2.51913,0.452486,-0.0491711] # z=2.0 +O3O2_coef[:,7]=[49.4728,-27.4711,4.50304,-0.232228] # z=3.0 +''' + +M08_coefs = {'R23': [0.7462, -0.7149, -0.9401, -0.6154, -0.2524], + 'N2Ha': [-0.7732, 1.2357, -0.2811, -0.7201, -0.3330], + 'O3Hb': [0.1549, -1.5031, -0.9790, -0.0297], + 'O3O2': [-0.2839, -1.3881, -0.3172], + 'O2Hb': [0.5603, 0.0450, -1.8017, -1.8434, -0.6549], + 'O3N2': [0.4520, -2.6096, -0.7170, 0.1347]} + +#this is to check the Maiolino coefficients and find the split maximum of the cirves with degeneracy +''' +import pylab as pl + +x=np.arange(7.0,9.5,0.1) + +for k in M08_coefs.iterkeys(): + print k,max(nppoly.polyval(x-8.69,M08_coefs[k])),x[nppoly.polyval(x-8.69,M08_coefs[k])==max(nppoly.polyval(x-8.69,M08_coefs[k]))] + pl.plot(x,nppoly.polyval(x-8.69,M08_coefs[k]), label=k+' max:%.1f'%x[nppoly.polyval(x-8.69,M08_coefs[k])==max(nppoly.polyval(x-8.69,M08_coefs[k]))]) + +print nppoly.polyval(8.4-8.69,M08_coefs['N2Ha']) +pl.ylabel("log R") +pl.xlabel("12+log(O/H)") +pl.legend(loc=3) +pl.show() +''' + + +class diagnostics: + def __init__(self, num, logf, nps): + self.nm = num + self.Ha = None + self.Hb = None + + self.hasHa, self.hasHb = False, False + self.hasO2, self.hasO3 = False, False + self.hasS2, self.hasN2 = False, False + + self.hasO3Hb = False + self.hasO3O2 = False + + self.hasN2O2 = False + self.hasN2S2 = False + + self.hasS26731 = False + self.hasS39532 = False + self.hasS39069 = False + self.hasS2Hb = False + + self.N2O2_roots = None + #other lines calculated and repeatedly used + self.P = None + self.R2 = None + self.R3 = None + self.R23 = None + self.S2Hb = None + self.N2 = None + self.N2S2 = None + self.O23727 = None + self.O35007 = None + self.N26584 = None + self.S26717 = None + self.S26731 = None + self.S39069 = None + self.S39532 = None + + self.O34959p5007 = None + self.O35007O2 = None + self.O2O35007 = None + + self.logR23 = None + self.logN2O2 = None + self.logN2S2 = None + self.logO3O2 = None + self.logS23 = None + #self.logS3S2=None + + self.logO2Hb = None + self.logO3Hb = None + self.logN2Ha = None + self.logS2Ha = None + + self.logO35007O2 = None + self.logO2O35007 = None + + self.logO3O2sq = None + self.logq = None + self.Z_init_guess = None + self.N2O2_coef0 = 1106.8660 + + self.OIII_OII = None + self.OIII_Hb = None + self.OIII_SII = None + + self.NII_OII = None + self.NII_SII = None + #metallicity diagnostics to be returned + + self.mds = {} + for Z in get_keys(): + self.mds[Z] = None + + #setting output file + self.logf = logf + self.nps = nps + + def printme(self, verbose=False): + try: + print "\nHa", np.mean(self.Ha) + if verbose: + print self.Ha + except (IndexError, TypeError): + pass + try: + print "\nHb", np.mean(self.Hb) + if verbose: + print self.Hb + except (IndexError, TypeError): + pass + try: + print "\nO2", np.mean(self.O23727) + if verbose: + print self.O23727 + except (IndexError, TypeError): + pass + try: + print "\nO3", np.mean(self.O35007) + if verbose: + print self.O35007 + except (IndexError, TypeError): + pass + try: + print "\nO34959", np.mean(self.O34959) + if verbose: + print self.O34959 + except (IndexError, TypeError): + pass + try: + print "\nZ94", np.mean(self.mds['Z94']) + if verbose: + print self.mds['Z94'] + except (IndexError, TypeError): + pass + try: + print "\nR23", np.mean(self.R23) + if verbose: + print self.R23 + except (IndexError, TypeError): + pass + try: + print "\nlog(R23)", np.mean(self.logR23) + if verbose: + print self.logR23 + except (TypeError, IndexError): + pass + try: + print "\nlog([NII][OII])", stats.nanmean(self.logN2O2) + if verbose: + print self.logN2O2 + except (TypeError, IndexError): + pass + try: + print "\nlog([OIII][OII])", stats.nanmean(self.logO3O2) + if verbose: + print self.logO3O2 + except (TypeError, IndexError): + pass + for k in self.mds.iterkeys(): + print "\n", k, + try: + print stats.nanmean(self.mds[k]), np.stdev(self.mds[k]) + except (IndexError, TypeError): + if verbose: + print self.mds[k] + + def checkminimumreq(self, red_corr, ignoredust): + if red_corr and not ignoredust: + if not self.hasHa: + return -1 + if not self.hasHb: + return -1 + #if not self.hasO2 : + # return -1 + #if not self.hasO3 : + # return -1 + #if not self.hasS2 : + # return -1 + if not self.hasN2 and not (self.hasO2 and self.hasO3): + return -1 + + def fz_roots(self, coef): + if len(coef.shape) == 1: + coef[~(np.isfinite(coef))] = 0.0 + rts = np.roots(coef[::-1]) + if rts.size == 0: + printsafemulti('WARNING: fz_roots failed', self.logf, self.nps) + rts = np.zeros(coef.size - 1) + return rts + + else: + rts = np.zeros((coef.shape[0], coef.shape[1] - 1), dtype=complex) + coef[~(np.isfinite(coef))] = 0.0 + + for i in range(coef.shape[0]): + rts[i] = np.roots(coef[i][::-1]) # ::-1][0]) + return rts + + def setdustcorrect(self): + global DUSTCORRECT + DUSTCORRECT = True + + def unsetdustcorrect(self): + global DUSTCORRECT + DUSTCORRECT = False + + def dustcorrect(self, l1, l2, flux=False): + #global DUSTCORRECT + if DUSTCORRECT: + if not flux: + return 0.4 * self.mds['E(B-V)'] * (l1 - l2) + return 10 ** (0.4 * self.mds['E(B-V)'] * (l1 - l2)) + else: + if not flux: + return 0 + return 1.0 + + def setHab(self, Ha, Hb): + self.Ha = Ha + self.Hb = Hb + if sum(self.Ha > 0): + self.hasHa = True + if sum(self.Hb > 0): + self.hasHb = True + + def setOlines(self, O23727, O35007, O16300, O34959): + self.O23727 = O23727 + self.O35007 = O35007 + self.O16300 = O16300 + + if sum(self.O35007 > 0): + self.hasO3 = True + if sum(self.O23727 > 0): + self.hasO2 = True + + if self.hasO2 and self.hasO3: + self.O35007O2 = (self.O35007 / self.O23727) * self.dustcorrect(k_O3, k_O2, flux=True) + self.O2O35007 = (self.O23727 / self.O35007) * self.dustcorrect(k_O2, k_O3, flux=True) + + self.logO35007O2 = np.log10(self.O35007O2) + self.logO2O35007 = np.log10(self.O2O35007) + + #self.logO2O35007Hb=np.log10((self.O23727+self.O35007)/self.Hb) + # ratios for other diagnostics - slightly different ratios needed + if self.hasHb: + self.logO2O35007Hb = np.log10((self.O23727 / self.Hb) * self.dustcorrect(k_O2, k_Hb, flux=True)) + \ + (self.O35007 / self.Hb) * self.dustcorrect(k_O35007, k_Hb, flux=True) + + else: + printsafemulti("WARNING: needs O lines and and Ha/b: did you run setHab()?", self.logf, self.nps) + if self.hasHb: + if self.hasO2: + self.logO2Hb = np.log10(self.O23727 / self.Hb) + self.dustcorrect(k_O2, k_Hb) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + if self.hasO3: + self.O3Hb = (self.O35007 / self.Hb) + self.dustcorrect(k_O35007, k_Hb, flux=True) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + self.logO3Hb = np.log10(self.O3Hb) + self.hasO3Hb = True + + + if self.hasO2 and self.hasO3: + self.OIII_OII = np.log10(self.O35007 / self.O23727 + self.dustcorrect(k_O35007, k_O2, flux=True)) + if O34959 is not None and sum(O34959 > 0) > 0: + self.O34959p5007 = (O34959 + self.O35007) + self.logO3O2 = np.log10((self.O34959p5007) / self.O23727) + self.dustcorrect(k_O3, k_O2) + #this is useful when we get logq + self.hasO3O2 = True + if self.hasHb: + self.OIII_Hb = np.log10(self.O35007 / self.Hb + self.dustcorrect(k_O35007, k_Hb, flux=True)) + + def setNII(self, N26584): + if N26584 is not None and sum(N26584 > 0): + self.N26584 = N26584 + self.hasN2 = True + if self.hasHa: + self.logN2Ha = np.log10(self.N26584 / self.Ha) # +self.dustcorrect(k_N2,k_Ha,flux=True) + #lines are very close: no dust correction + #Note: no dust correction cause the lies are really close! + else: + printsafemulti("WARNING: needs NII6584 and Ha to calculate NIIHa: did you run setHab()?", self.logf, self.nps) + if self.hasS2 and self.hasS26731 and self.hasN2: + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_S2,flux=True) + #lines are very close: no dust correction + if self.hasO2 and self.hasN2: + self.NII_OII = np.log10(self.N26584 / self.O23727 + self.dustcorrect(k_N2, k_O2, flux=True)) + + def setSII(self, S26717, S26731, S39069, S39532): + if S26717 is not None and sum(S26717 > 0) > 0: + self.S26717 = S26717 + self.hasS2 = True + + if self.hasHa: + self.logS2Ha = np.log10(self.S26717 / self.Ha) + self.dustcorrect(k_S2, k_Ha) + else: + printsafemulti("WARNING: needs SII6717 and Ha to calculate SIIHa: did you run setHab() and setS()?", self.logf, self.nps) + if S26731 is not None and sum(S26731 > 1e-9) > 0: + self.S26731 = S26731 + self.hasS26731 = True + if S39069 is not None and sum(S39069 > 1e-9) > 0: + self.S39069 = S39069 + self.hasS39069 = True + if S39532 is not None and sum(S39532 > 1e-9) > 0: + self.S39532 = S39532 + self.hasS39532 = True + if self.hasS2: + if self.hasN2 and self.NII_SII is None and self.hasS26731: + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_O2,flux=True) + #lines are very close: no dust correction + if self.hasO3 and self.OIII_SII is None and self.hasS26731: + self.OIII_SII = np.log10(self.O35007 / (self.S26717 + self.S26731) + self.dustcorrect(k_O3, k_S2, flux=True)) + + #@profile + def calcEB_V(self): + printsafemulti("calculating E(B-V)", self.logf, self.nps) + self.mds['E(B-V)'] = np.log10(2.86 * self.Hb / self.Ha) / (0.4 * (k_Ha - k_Hb)) # E(B-V) + self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 + + #@profile + def calcNIISII(self): + if self.hasS2 and self.hasN2: + self.N2S2 = self.N26584 / self.S26717 + self.dustcorrect(k_N2, k_S2, flux=True) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) + self.logN2S2 = np.log10(self.N26584 / self.S26717) + self.dustcorrect(k_N2, k_S2) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) + self.hasN2S2 = True + else: + printsafemulti("WARNING: needs SII6717 and NII6584 to calculate NIISII: did you run setN2() and setS?", self.logf, self.nps) + + #@profile + def calcNIIOII(self): + if self.hasN2 and self.hasO2: + self.logN2O2 = np.log10(self.N26584 / self.O23727) + self.dustcorrect(k_N2, k_O2) + self.hasN2O2 = True + if not self.hasN2O2 or np.mean(self.logN2O2) < 1.2: + + try: + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods should only be used for log([NII]6564/[OII]3727) >1.2, + the mean log([NII]6564/[OII]3727)= %f''' % np.mean(self.logN2O2), self.logf, self.nps) + except TypeError: + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods + should only be used for log([NII]6564/[OII]3727) >1.2, + the mean log([NII]6564/[OII]3727)= %s''' % self.logN2O2, self.logf, self.nps) + + if not self.hasN2O2: + self.N2O2_roots = np.zeros(self.nm) + float('NaN') + else: + N2O2_coef = np.array([[self.N2O2_coef0, -532.15451, 96.373260, -7.8106123, 0.23928247]] * self.nm).T # q=2e7 line (approx average) + N2O2_coef[0] -= self.logN2O2 + N2O2_coef = N2O2_coef.T + # finding roots for == (4) + self.N2O2_roots = np.array([self.fz_roots(N2O2_coef)])[0] + + #@profile + def calcR23(self): + printsafemulti("calculating R23", self.logf, self.nps) + + #R23 NEW Comb, [NII]/Ha: KK04 = Kobulnicky & Kewley, 2004, submitted' + if self.hasO3 and self.hasO2 and self.hasHb: + self.R2 = (self.O23727 / self.Hb) * self.dustcorrect(k_O2, k_Hb, flux=True) + self.R3 = (self.O34959p5007 / self.Hb) * self.dustcorrect(k_O3, k_Hb, flux=True) + self.R23 = self.R2 + self.R3 + self.logR23 = np.log10(self.R23) + self.mds['logR23'] = self.logR23 + #note that values of logR23 > 0.95 are unphysical. + #you may choose to uncomment the line below + #self.logR23[self.logR23>0.95]=0.95 + else: + printsafemulti("WARNING: need O3, O2, Hb", self.logf, self.nps) + + #@profile + def calcS23(self): + printsafemulti("calculating S23", self.logf, self.nps) + #the original code here uses S267176731, + #which is however set to 6717 as default + #Vilchez & Esteban (1996) + if self.hasS2: + if self.hasS39069 and self.hasHb: + self.logS23 = np.log10((self.S26717 / self.Hb) * + self.dustcorrect(k_S2, k_Hb, flux=True) + + (self.S39069 / self.Hb) * + self.dustcorrect(k_S3, k_Hb, flux=True)) + + #self.logS3S2=np.log10(S39069/self.S26717)+self.dustcorrect(k_S3,k_S2) + + ##@profile + def calclogq(self, Z): + if not self.hasO3O2: + printsafemulti("WARNING: needs O3,O2,Hb to calculate logq properly.", self.logf, self.nps) + return -1 + if self.logO3O2sq is None: + self.logO3O2sq = self.logO3O2 ** 2 + return (32.81 - 1.153 * self.logO3O2sq + Z * (-3.396 - 0.025 * self.logO3O2 + 0.1444 * self.logO3O2sq)) / (4.603 - 0.3119 * self.logO3O2 - \ + 0.163 * self.logO3O2sq + Z * (-0.48 + 0.0271 * self.logO3O2 + 0.02037 * self.logO3O2sq)) + + ##@profile + def initialguess(self): + # Initial Guess - appearing in LK code as of Nov 2006 + # upper branch: if no lines are available, metallicity is set to 8.7 + self.Z_init_guess = np.zeros(self.nm) + 8.7 + # use [N2]/Ha + if self.hasHa and self.hasN2: + self.Z_init_guess[(self.logN2Ha < -1.3) & (self.N26584 != 0.0)] = 8.2 + self.Z_init_guess[(self.logN2Ha < -1.1) & (self.logN2Ha >= -1.3) & (self.N26584 != 0.0)] = 8.4 + #A1 KE08 + self.Z_init_guess[(self.logN2Ha >= -1.1) & (self.N26584 != 0.0)] = 8.7 + #use [N2]/[O2] + if self.hasN2 and self.hasO2: + N2O2 = np.zeros(self.nm) + float('nan') + if self.hasHb: + ###FED CHECK THIS! + N2O2 = self.N26584 * self.Ha * self.Hb * self.O23727 + if not self.hasN2O2: + printsafemulti("WARNING: must calculate logN2O2 first", self.logf, self.nps) + self.calcNIIOII() + self.Z_init_guess[(self.logN2O2 < -1.2) & (N2O2 != 0.0)] = 8.2 + # at logN2O2<-1.2 using low-Z gals, A1 KE08 + self.Z_init_guess[(self.logN2O2 >= -1.2) & (N2O2 != 0.0)] = 8.7 + + # at logN2O2>-1.2 using HII regions + + + + #######################these are the metallicity diagnostics################## + #@profile + def calcpyqz(self, plot=False, allD13=False): + printsafemulti("calculating D13", self.logf, self.nps) + + # initializing variable pyqz to avoid style issues + # (pyqz not defined is reported as error by Landscape.io w/ import in func + pyqz = None + try: + import pyqz + except ImportError: + return -1 + + #check version of pyqz + from distutils.version import StrictVersion + oldpyqz = False + if StrictVersion(pyqz.__version__) <= StrictVersion('0.5.0'): + oldpyqz = True + + if self.NII_SII is not None and allD13: + if self.OIII_SII is not None: + + if oldpyqz: + self.mds['D13_N2S2_O3S2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_SII]), 'NII/SII', 'OIII/SII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + #pyqz.get_grid_fn(Pk=5.0,calibs='GCZO', kappa =20, struct='pp') + self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_SII])], \ + '[NII]/[SII]+;[OIII]/[SII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + if self.OIII_Hb is not None: + if oldpyqz: + self.mds['D13_N2S2_O3Hb'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_Hb]), 'NII/SII', 'OIII/Hb', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2S2_O3SHb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/[SII]+;[OIII]/Hb', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + + + if self.OIII_OII is not None: + if oldpyqz: + self.mds['D13_N2S2_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_OII]), 'NII/SII', 'OIII/OII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2S2_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_OII])], \ + '[NII]/[SII]+;[OIII]/[OII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + + if self.NII_OII is not None and allD13: + if self.OIII_SII is not None: + if oldpyqz: + self.mds['D13_N2O2_O3S2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_SII]), 'NII/OII', 'OIII/SII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2O2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_SII])], \ + '[NII]/[OII]+;[OIII]/[SII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + + if self.OIII_Hb is not None: + if oldpyqz: + self.mds['D13_N2O2_O3Hb'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_Hb]), 'NII/OII', 'OIII/Hb', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2O2_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/[OII]+;[OIII]/Hb', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + if self.OIII_OII is not None: + if oldpyqz: + self.mds['D13_N2O2_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_OII]), 'NII/OII', 'OIII/OII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2O2_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_OII])], \ + '[NII]/[OII]+;[OIII]/[OII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + if self.logN2Ha is not None: + if self.OIII_Hb is not None: + if oldpyqz: + self.mds['D13_N2Ha_O3Hb'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_Hb]), 'NII/Ha', 'OIII/Hb', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2Ha_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/Ha;[OIII]/Hb', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + if self.OIII_OII is not None: + if oldpyqz: + self.mds['D13_N2Ha_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_OII]), 'NII/Ha', 'OIII/OII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + + else: + self.mds['D13_N2Ha_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/Ha;[OIII]/[OII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + #@profile + def calcDP00(self): + # Diaz, A. I., & Perez-Montero, E. 2000, MNRAS, 312, 130 + # As per KD02: DP00 diagnostic systematically underestimates the + # abundance relative to the comparison abundance. + # A term is added to improve the fit according to KD02 Eq. 6 + # AVAILABLE BUT DEPRECATED + printsafemulti("calculating DP00", self.logf, self.nps) + + if self.logS23 is None: + self.calcS23() + if self.logS23 is None: + printsafemulti("WARNING: Cannot compute this without S23", self.logf, self.nps) + return -1 + self.mds['DP00'] = 1.53 * self.logS23 + 8.27 + 1.0 / (2.0 - 9.0 * self.logS23 ** 3) + + #@profile + def calcD02(self): + # [NII]/Ha Denicolo, Terlevich & Terlevich (2002), MNRAS, 330, 69 + #FED:added uncertainties + printsafemulti("calculating D02", self.logf, self.nps) + + e1 = np.random.normal(0, 0.05, self.nm) + e2 = np.random.normal(0, 0.1, self.nm) + if self.hasN2 and self.hasHa: + self.mds['D02'] = 9.12 + e1 + (0.73 + e2) * self.logN2Ha + else: + printsafemulti("WARNING: need N2Ha to do this. did you run setHab and setNII", self.logf, self.nps) + + #@profile + def calcPP04(self): + ### PP04_N2_Z, PP04_O3N2_Z Pettini & Pagel diagnostics - + ### Pettini & Pagel (2004), MNRAS, 348, L59 + # [NII]/Ha Pettini & Pagel (2004), MNRAS, 348, L59 + #discriminating lower and upper branch using [NII]/[OII] or [NII]/Ha + printsafemulti("calculating PP04", self.logf, self.nps) + if self.hasN2 and self.hasHa: + self.mds['PP04_N2Ha'] = nppoly.polyval(self.logN2Ha, [9.37, 2.03, 1.26, 0.32]) + + #FED: restricting the range as per paper + index = (self.logN2Ha > -2.5) * (self.logN2Ha < -0.3) + self.mds['PP04_N2Ha'][~index] = float('NaN') + if self.hasO3Hb: + self.mds['PP04_O3N2'] = 8.73 - 0.32 * (self.logO3Hb - self.logN2Ha) + index = (self.logO3Hb > 2) + self.mds['PP04_O3N2'][index] = float('NaN') + else: + printsafemulti("WARNING: need O3Hb for PP04_O3N2", self.logf, self.nps) + else: + printsafemulti("WARNING: need N2Ha to do this. did you run setHab and setNII", self.logf, self.nps) + + #@profile + def calcZ94(self): + ### calculating z from Kobulnicky,Kennicutt,Pizagno (1998) + ### parameterization of Zaritzky et al. (1994) + ###Z94 = Zaritsky, D., Kennicutt, R. C., & Huchra, J. P. 1994, + ###ApJ, 420, 87 + ### only valid on the upper branch of R23 (KE08 A2.4) + + printsafemulti("calculating Z94", self.logf, self.nps) + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + self.mds['Z94'] = nppoly.polyval(self.logR23, [9.265, -0.33, -0.202, -0.207, -0.333]) + self.mds['Z94'][(self.logR23 > 0.9)] = None + ## 0.9 is a conservative constraint to make sure that we are + ## only using the upper branch (i.e. 12+log(O/H)>8.4) + + def calcP(self): + if self.P is None: + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + #R3=10**self.logO349595007Hb + #R2=10**self.logO2Hb + #P = R3/(R2+R3) + self.P = self.R3 / self.R23 + + #@profile + def calcP05(self): + # #### P-method ##### + ##Pilyugin+ 2005 method. Based on [OIII],[OII], Hbeta + ##calibrated from Te method + # make sure you run setOlines() first + printsafemulti("calculating P05", self.logf, self.nps) + + if self.calcP() == -1: + return -1 + if self.Z_init_guess is None: + self.initialguess() + + Psq = self.P * self.P + + P_abund_up = (self.R23 + 726.1 + 842.2 * self.P + 337.5 * Psq) / (85.96 + 82.76 * self.P + 43.98 * Psq + 1.793 * self.R23) + P_abund_low = (self.R23 + 106.4 + 106.8 * self.P - 3.40 * Psq) / (17.72 + 6.60 * self.P + 6.95 * Psq - 0.302 * self.R23) + + self.mds['P05'] = P_abund_up + self.mds['P05'][self.Z_init_guess < 8.4] = P_abund_low[self.Z_init_guess < 8.4] + + #@profile + def calcP10(self): + # #### P-method ##### + ##Pilyugin+ 2010 method. + ##calibrated from Te method + # need Hb + #The Astrophysical Journal, Volume 720, Issue 2, pp. 1738-1751 (2010). + #Published in Sep 2010 + + printsafemulti("calculating P10", self.logf, self.nps) + + if not self.hasHb: + printsafemulti("this method needs Hb", self.logf, self.nps) + return -1 + self.mds['P10_ONS'] = np.zeros(self.nm) + float('NaN') + self.mds['P10_ON'] = np.zeros(self.nm) + float('NaN') + #P10N2=np.zeros(self.nm)+float('NaN') + #P10S2=np.zeros(self.nm)+float('NaN') + P10logR3 = np.zeros(self.nm) + float('NaN') + P10logR2 = np.zeros(self.nm) + float('NaN') + P10logN2 = np.zeros(self.nm) + float('NaN') + P10logS2 = np.zeros(self.nm) + float('NaN') + + self.calcP() + if self.R2 is not None: + P10logR2 = np.log(self.R2) + + if self.R3 is not None: + P10logR3 = np.log(self.R3) + + if self.hasN2: + #the ratio of N26548 and N26548 is N26584/N26548 = 3 + #independent on physical conditions + #The Physics and Dynamics of Planetary Nebulae + # By Grigor A. Gurzadyan + P10logN2 = np.log((self.N26584 * 1.33) / self.Hb) + self.dustcorrect(k_N2, k_Hb) + + if self.hasS2 and self.hasS26731: + self.S2Hb = ((self.S26717 + self.S26731) / self.Hb) + self.dustcorrect(k_S2, k_Hb, flux=True) + self.hasS2Hb = True + P10logS2 = np.log10(self.S2Hb) + + P10logN2S2 = P10logN2 - P10logS2 + P10logN2R2 = P10logN2 - P10logR2 + P10logS2R2 = P10logS2 - P10logR2 + + coefsONS0 = np.array([8.277, 0.657, -0.399, -0.061, 0.005]) + coefsONS1 = np.array([8.816, -0.733, 0.454, 0.710, -0.337]) + coefsONS2 = np.array([8.774, -1.855, 1.517, 0.304, 0.328]) + + vsONS = np.array([np.ones(self.nm), self.P, P10logR3, P10logN2R2, P10logS2R2]).T + + coefsON0 = np.array([8.606, -0.105, -0.410, -0.150]) + coefsON1 = np.array([8.642, 0.077, 0.411, 0.601]) + coefsON2 = np.array([8.013, 0.905, 0.602, 0.751]) + + vsON = np.array([np.ones(self.nm), P10logR3, P10logR2, P10logN2R2]).T + + indx = P10logN2 > -0.1 + if self.P is not None: + self.mds['P10_ONS'][indx] = np.dot(vsONS[indx], coefsONS0) + self.mds['P10_ON'][indx] = np.dot(vsON[indx], coefsON0) + + indx = (P10logN2 < -0.1) * (P10logN2S2 > -0.25) + if self.P is not None: + self.mds['P10_ONS'][indx] = np.dot(vsONS[indx], coefsONS1) + self.mds['P10_ON'][indx] = np.dot(vsON[indx], coefsON1) + + indx = (P10logN2 < -0.1) * (P10logN2S2 < -0.25) + if self.P is not None: + self.mds['P10_ONS'][indx] = np.dot(vsONS[indx], coefsONS2) + self.mds['P10_ON'][indx] = np.dot(vsON[indx], coefsON2) + + indx = ~((self.mds['P10_ONS'] > 7.1) * (self.mds['P10_ON'] > 7.1) * (self.mds['P10_ONS'] < 9.4) * (self.mds['P10_ON'] < 9.4)) + if self.P is not None: + self.mds['P10_ONS'][indx] = float('NaN') + self.mds['P10_ON'][indx] = float('NaN') + + #@profile + def calcP01(self): + # P-method 2001 upper branch (derecated and commented out) + # Pilyugin 2001 + # available but deprecated + printsafemulti("calculating old P05", self.logf, self.nps) + + if self.Z_init_guess is None: + self.initialguess() + if self.hasO3O2 and self.hasO3 and self.hasO2: + P = 10 ** self.logO3O2 / (1 + 10 ** self.logO3O2) + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + Psq = P ** 2 + P_abund_old = (self.R23 + 54.2 + 59.45 * P + 7.31 * Psq) / (6.07 + 6.71 * P + 0.371 * Psq + 0.243 * self.R23) + self.mds['P01'] = np.zeros(self.nm) + float('NaN') + self.mds['P01'][self.Z_init_guess >= 8.4] = P_abund_old[self.Z_init_guess >= 8.4] + else: + printsafemulti("WARNING: need OIIIOII to calculate P01, did you set them up with setOlines()?", self.logf, self.nps) + + #@profile + def calcC01_ZR23(self): + # C01 = Charlot, S., & Longhetti, M., 2001, MNRAS, 323, 887 + # Charlot 01 R23 calibration: (case F) ## + # available but deprecated + printsafemulti("calculating C01", self.logf, self.nps) + + if self.hasO3 and self.hasO2 and self.hasO3Hb: + x2 = self.O2O35007 / 1.5 + x3 = (10 ** self.logO3Hb) * 0.5 + self.mds['C01_R23'] = np.zeros(self.nm) + float('NaN') + self.mds['C01_R23'][self.O2O35007 < 0.8] = np.log10(3.78e-4 * (x2[self.O2O35007 < 0.8]) ** 0.17 * x3[self.O2O35007 < 0.8] ** (-0.44)) + 12.0 + + self.mds['C01_R23'][self.O2O35007 >= 0.8] = np.log10(3.96e-4 * x3[self.O2O35007 >= 0.8] ** (-0.46)) + 12.0 + else: + printsafemulti('''WARNING: need [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, +did you set them up with setOlines()?''', self.logf, self.nps) + + # Charlot 01 calibration: (case A) based on [N2]/[SII]## + # available but deprecated + if not self.hasN2S2: + printsafemulti("WARNING: trying to calculate logNIISII", self.logf, self.nps) + self.calcNIISII() + if self.hasN2S2 and self.hasO3 and self.hasO2 and self.hasO3Hb: + self.mds['C01_N2S2'] = np.log10(5.09e-4 * (x2 ** 0.17) * ((self.N2S2 / 0.85) ** 1.17)) + 12 + else: + printsafemulti('''WARNING: needs [NII]6584, [SII]6717, [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, +did you set them up with setOlines() and ?''', self.logf, self.nps) + + #@profile + def calcM91(self): + # ## calculating McGaugh (1991) + # McGaugh, S.S., 1991, ApJ, 380, 140' + # M91 calibration using [N2O2] as + # initial estimate of abundance: + # this initial estimate can be + # changed by replacing + # OH_init by another guess, eg C01_Z + # NOTE: occasionally the M91 + # 'upper branch' will give a metallicity + # that is lower than the 'lower branch'. + # Happens for very high R23 values. + # If R23 is higher than the intersection + # (calculate the intersection), then + # the metallicity is likely to be around + # the R23 maximum = 8.4 + + printsafemulti("calculating M91", self.logf, self.nps) + self.mds['M91'] = np.zeros(self.nm) + float('NaN') + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + + if self.Z_init_guess is None: + self.initialguess() + + M91_Z_low = nppoly.polyval(self.logR23, [12.0 - 4.944, 0.767, 0.602]) - \ + self.logO3O2 * nppoly.polyval(self.logR23, [0.29, 0.332, -0.331]) + M91_Z_up = nppoly.polyval(self.logR23, [12.0 - 2.939, -0.2, -0.237, -0.305, -0.0283]) - \ + self.logO3O2 * nppoly.polyval(self.logR23, [0.0047, -0.0221, -0.102, -0.0817, -0.00717]) + + indx = (np.abs(self.logO3O2) > 0) * (np.abs(self.logR23) > 0) * (self.Z_init_guess < 8.4) + self.mds['M91'][indx] = M91_Z_low[indx] + indx = (np.abs(self.logO3O2) > 0) * (np.abs(self.logR23) > 0) * (self.Z_init_guess >= 8.4) + self.mds['M91'][indx] = M91_Z_up[indx] + self.mds['M91'][(M91_Z_up < M91_Z_low)] = float('NaN') + + #@profile + def calcM13(self): + #Marino+ 2013 + printsafemulti("calculating M13", self.logf, self.nps) + + if not self.hasHa or not self.hasN2: + printsafemulti("WARNING: need O3, N2, Ha and Hb, or at least N2 and Ha", self.logf, self.nps) + return -1 + else: + e1 = np.random.normal(0, 0.027, self.nm) + e2 = np.random.normal(0, 0.024, self.nm) + self.mds["M13_N2"] = 8.743 + e1 + (0.462 + e2) * self.logN2Ha + if self.hasHb and self.hasO3: + e1 = np.random.normal(0, 0.012, self.nm) + e2 = np.random.normal(0, 0.012, self.nm) + O3N2 = self.logO3Hb - self.logN2Ha + self.mds["M13_O3N2"] = 8.533 + e1 - (0.214 + e1) * O3N2 + index = (self.logO3Hb > 1.7) + self.mds["M13_O3N2"][index] = float('NaN') + + #@profile + def calcM08(self, allM08=False): + #Maiolino+ 2008 + #Astronomy and Astrophysics, Volume 488, Issue 2, 2008, pp.463-479 + #Published in Sep 2008 + printsafemulti("calculating M08", self.logf, self.nps) + highZ = None + if self.logO35007O2 is not None: + self.mds['M08_O3O2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O3O2']] * self.nm).T + coefs[0] = coefs[0] - self.logO35007O2 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + #the two cumsum assure that if the condition for the ith element + #of indx is [False, False] then after the first cumsum(1) is [0,0] + #[False, True] is [0,1] + #[True, True] is [1,2] + #but (here is the kicker) [True, False] is [1,1]. + #Because i want only one solution + #(i'll settle for the first one occurring) [1,1] is ambiguous. + #The second cumsum(1) makes + #[0,0]->[0,0], [0,1]->[0,1], [1,2]->[1,3] and finally [1,1]->[1,2] + + self.mds['M08_O3O2'][(indx.sum(1)) > 0] = sols[indx].real + highZ = np.median(self.logO35007O2) < 0 + if self.logN2Ha is not None: + self.mds['M08_N2Ha'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['N2Ha']] * self.nm).T + coefs[0] = coefs[0] - self.logN2Ha + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_N2Ha'][(indx.sum(1)) > 0] = sols[indx].real + if highZ is None: + highZ = np.median(self.logN2Ha) > -1.3 + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute M08_R23 without R23", self.logf, self.nps) + else: + self.mds['M08_R23'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['R23']] * self.nm).T + coefs[0] = coefs[0] - self.logR23 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real >= 8.0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_R23'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real <= 8.0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_R23'][(indx.sum(1)) > 0] = sols[indx].real + if not allM08: + return + else: + printsafemulti("calculating other M08s", self.logf, self.nps) + + if self.logO3Hb is not None: + + self.mds['M08_O3Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O3Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO3Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real >= 7.9)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real <= 7.9)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + + if self.logO2Hb is not None: + self.mds['M08_O2Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O2Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO2Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real >= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real <= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + + + + if self.hasO3 and self.hasN2: + self.mds['M08_O3N2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O3N2']] * self.nm).T + coefs[0] = coefs[0] - np.log(self.O35007 / self.N26584) * self.dustcorrect(k_O35007, k_N2) + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O3N2'][(indx.sum(1)) > 0] = sols[indx].real + + #@profile + def calcKD02_N2O2(self): + ## Kewley & Dopita (2002) estimates of abundance + ## KD02 + # KD02 [N2]/[O2] estimate (can be used for whole log(O/H)+12 range, + # but rms scatter increases to 0.11 rms for log(O/H)+12 < 8.6 + # rms = 0.04 for + # log(O/H)+12 > 8.6 + # uses equation (4) from KD02 paper + # FED: i vectorized the hell out of this function!!! + # from a 7 dimensional if/for loop to 1 if and 1 for :-D + #vectorizing makes fed happy ... + + printsafemulti("calculating KD02_N2O2", self.logf, self.nps) + + if self.hasN2 and self.hasO2 and self.hasHa and self.hasHb: + self.mds['KD02_N2O2'] = np.zeros(self.nm) + float('NaN') + if not self.hasN2O2: + printsafemulti("WARNING: must calculate logN2O2 first", self.logf, self.nps) + self.calcNIIOII() + if not self.hasN2O2 or self.N2O2_roots is None or sum(np.isnan(self.N2O2_roots.flatten())) == len(self.N2O2_roots.flatten()): + printsafemulti("WARNING: cannot calculate N2O2", self.logf, self.nps) + return -1 + roots = self.N2O2_roots.T + for k in range(4): + indx = (abs(roots[k]) >= 7.5) * (abs(roots[k]) <= 9.4) * (roots[k][:].imag == 0.0) + self.mds['KD02_N2O2'][indx] = abs(roots[k][indx]) + else: + printsafemulti("WARNING: need NII6584 and OII3727 and Ha and Hb to calculate this. did you run setO() setHab() and setNII()?", self.logf, self.nps) + return 1 + + #@profile + def calcKK04_N2Ha(self): + # calculating [N2]/Ha abundance estimates using [O3]/[O2] also + printsafemulti("calculating KK04_N2Ha", self.logf, self.nps) + + if self.mds['KD02_N2O2'] is None: + self.calcKD02_N2O2() + if self.mds['KD02_N2O2'] is None or sum(np.isnan(self.mds['KD02_N2O2'])) == self.nm: + printsafemulti("WARNING: without KD02_N2O2 cannot calculate KK04_N2Ha properly, but we will do our best...", self.logf, self.nps) + Z_new_N2Ha = np.zeros(self.nm) + 8.6 + else: + Z_new_N2Ha = self.mds['KD02_N2O2'].copy() # was 8.6 + + if self.hasN2 and self.hasHa: + logq_save = np.zeros(self.nm) + convergence, tol, ii = 100, 1.0e-3, 0 + if self.hasO3O2: + # calculating logq using the [N2]/[O2] + # metallicities for comparison + while convergence > tol and ii < 100: + ii += 1 + self.logq = self.calclogq(Z_new_N2Ha) + Z_new_N2Ha = nppoly.polyval(self.logN2Ha, [7.04, 5.28, 6.28, 2.37]) - \ + self.logq * nppoly.polyval(self.logN2Ha, [-2.44, -2.01, -0.325, +0.128]) + \ + 10 ** (self.logN2Ha - 0.2) * self.logq * (-3.16 + 4.65 * self.logN2Ha) + convergence = np.abs(self.logq - logq_save).mean() + logq_save = self.logq.copy() + if ii >= 100: + printsafemulti("WARNING: loop did not converge", self.logf, self.nps) + Z_new_N2Ha = np.zeros(self.nm) + float('NaN') + else: + self.logq = 7.37177 * np.ones(self.nm) + Z_new_N2Ha = nppoly.polyval(self.logN2Ha, [7.04, 5.28, 6.28, 2.37]) - \ + self.logq * nppoly.polyval(self.logN2Ha, [-2.44, -2.01, -0.325, +0.128]) + \ + 10 ** (self.logN2Ha - 0.2) * self.logq * (-3.16 + 4.65 * self.logN2Ha) + self.mds['KK04_N2Ha'] = Z_new_N2Ha + indx = self.logN2Ha > 0.8 + self.mds['KK04_N2Ha'][indx] = float('NaN') + else: + printsafemulti("WARNING: need NII6584 and Ha to calculate this. did you run setHab() and setNII()?", self.logf, self.nps) + + #@profile + def calcKK04_R23(self): + # Kobulnicky & Kewley 2004 + # calculating upper and lower metallicities for objects without + # Hb and for objects without O3 and/or O2 + + printsafemulti("calculating KK04_R23", self.logf, self.nps) + #this is in the original code but not used :( + #if self.hasN2 and self.hasHa: + #logq_lims=[6.9,8.38] + #logN2Ha=np.log10(self.N26584/self.Ha) CHECK!! why remove dust correction?? + #Z_new_N2Ha_lims= np.atleast_2d([1.0,1.0]).T*nppoly.polyval(self.logN2Ha,[7.04, 5.28,6.28,2.37])- + #np.atleast_2d( logq_lims).T*nppoly.polyval(self.logN2Ha,[-2.44,-2.01,-0.325,0.128])+ + #np.atleast_2d(logq_lims).T*(10**(self.logN2Ha-0.2)*(-3.16+4.65*self.logN2Ha)) + # R23 diagnostics from Kobulnicky & Kewley 2004 + + Zmax = np.zeros(self.nm) + # ionization parameter form logR23 + if not self.hasO3O2: + logq = np.zeros(self.nm) + else: + if self.Z_init_guess is None: + self.initialguess() + Z_new = self.Z_init_guess.copy() + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + else: + logqold, convergence, ii = np.zeros(self.nm) + 100, 100, 0 + tol = 1e-4 + #3 iterations are typically enought to achieve convergence KE08 A2.3 + while convergence > tol and ii < 100: + Zmax = Zmax * 0.0 + ii += 1 + logq = self.calclogq(Z_new) + Zmax[(logq >= 6.7) * (logq < 8.3)] = 8.4 + # maximum of R23 curve: + Z_new = nppoly.polyval(self.logR23, [9.72, -0.777, -0.951, -0.072, -0.811]) - \ + logq * nppoly.polyval(self.logR23, [0.0737, -0.0713, -0.141, 0.0373, -0.058]) + indx = self.Z_init_guess <= Zmax + Z_new[indx] = nppoly.polyval(self.logR23[indx], [9.40, 4.65, -3.17]) - \ + logq[indx] * nppoly.polyval(self.logR23[indx], [0.272, 0.547, -0.513]) + convergence = np.abs((logqold - logq).mean()) + logqold = logq.copy() + if ii >= 100: + printsafemulti("WARNING: loop did not converge", self.logf, self.nps) + Z_new = np.zeros(self.nm) + float('NaN') + Z_new_lims = [nppoly.polyval(self.logR23, [9.40, 4.65, -3.17]) - \ + logq * nppoly.polyval(self.logR23, [0.272, 0.547, -0.513]), + nppoly.polyval(self.logR23, [9.72, -0.777, -0.951, -0.072, -0.811]) - \ + logq * nppoly.polyval(self.logR23, [0.0737, -0.0713, -0.141, 0.0373, -0.058])] + Z_new[(Z_new_lims[0] > Z_new_lims[1])] = None + self.mds['KK04_R23'] = Z_new + + #@profile + def calcKDcombined(self): + # KD02comb Kewley, L. J., & Dopita, M. A., 2002, ApJ + # updated in KE08 + # ### KD02 [NII]/[OII] estimate ### + # (can be used for log(O/H)+12 > 8.6 only) + + printsafemulti("calculating KD_combined", self.logf, self.nps) + + #We first use the + #[N ii]/[O ii] ratio to determine whether it lies on the upper + #or lower R23 branch + + if self.mds['KD02_N2O2'] is None: + self.calcKD02_N2O2() + if self.mds['KK04_N2Ha'] is None: + self.calcKK04_N2Ha() + if self.logR23 is None: + self.calcR23() + if self.mds['M91'] is None: + printsafemulti("WARNING: Must first calculate M91", self.logf, self.nps) + self.calcM91() +# if self.mds['Z94'] is None: +# printsafemulti( "WARNING: Must first calculate Z94",self.logf,self.nps) +# self.calcZ94() + if self.mds['KK04_R23'] is None: + printsafemulti("WARNING: Must first calculate KK04_R23", self.logf, self.nps) + self.calcKK04_R23() + if not self.hasHa and not self.hasHb: + printsafemulti("WARNING: need Ha and Hb for this. did you run setHab()?", self.logf, self.nps) + + #alternative way to calculate KD02_N2O2, stated in the paper KD02, + #valid in high Z regimes (Z>8.4) + #but we forego it + #if not self.logN2O2 is None: + # self.mds['KD02_N2O2']=np.log10(8.511e-4*(1.54020+1.26602*self.logN2O2+0.167977*self.logN2O2**2))+12. + #else: self.mds['KD02_N2O2']=np.zeros(self.nm)+float('NaN') + + # ionization parameter + # calculate an initial ionization parameter by assuming + # a nominal lower branch [12 + log (O/H ) = 8.2] + # or upper branch [12 + log (O/H ) = 8.7] metallicity using + # equation (13) from KK04 + logq = np.zeros(self.nm) + if self.hasN2 and self.hasO2 and self.hasHb and self.hasHa and self.hasO3O2: + logq = self.calclogq(self.mds['KD02_N2O2']) + logq[self.mds['KD02_N2O2'] >= 8.4] = self.logq[self.mds['KD02_N2O2'] >= 8.4] + else: + if self.Z_init_guess is None: + self.initialguess() + logq = self.calclogq(self.Z_init_guess) + #FED: CHECK: the paragraph below makes sense in words but i dont see whereit ie enforced. + # if log([NII]/[OII]) after extinction correction is <-1.5, then check the data. + # if it is only slightly less than 1.5, then this can be a result of either noisy + # data, inaccurate fluxes or extinction correction, or a higher ionization parameter + # than modelled. + # For these cases, the average of the M91,Z94 and C01 should be used. + + # KD02 R23 estimate (not reliable for 8.4 < log(O/H)+12 < 8.8) + # uses [NII]/[OII] estimate as initial guess - this can be changed below + + self.mds['KD02comb'] = np.zeros(self.nm) + float('NaN') + + indx_ig = self.Z_init_guess > 8.4 + if self.mds['KD02_N2O2'] is not None: + self.mds['KD02comb'][indx_ig] = self.mds['KD02_N2O2'][indx_ig].copy() + if self.mds['KK04_N2Ha'] is not None: + self.mds['KD02comb'][~indx_ig] = self.mds['KK04_N2Ha'][~indx_ig].copy() + if self.mds['KK04_R23'] is not None and self.mds['M91'] is not None: + # if [NII]/[OII] abundance available + # and [NII]/Ha abundance < 8.4, then use R23. + indx = (~np.isnan(self.mds['KK04_R23'])) * (~np.isnan(self.mds['M91'])) * (~indx_ig) + self.mds['KD02comb'][indx] = 0.5 * (self.mds['KK04_R23'][indx].copy() + self.mds['M91'][indx].copy()) + + else: + printsafemulti("WARNING: cannot calculate KK04comb because KK04_R23 or M91, failed", self.logf, self.nps) + +#######################these are the metallicity diagnostics################## +#@profile + def calcPM14(self): + # Perez-Montero 2014 + # (can be used for for log(O/H)+12 > 8.6 only) + import os + from subprocess import Popen, PIPE, STDOUT + from StringIO import StringIO + + printsafemulti("calculating HIICHI", self.logf, self.nps) + fin_hii_chi = open(os.getenv('HIICHI_DIR') + '/in.tmp', 'w') + + if not self.hasHb: + printsafemulti("cannot calculate HIICHI without Hbeta", self.logf, self.nps) + return -1 + + ratios = np.zeros((5, self.nm)) + + if self.R2 is not None: + ratios[0] = self.R2 + elif self.hasO2: + ratios[0] = ((self.O23727 / self.Hb) * self.dustcorrect(k_O2, k_Hb, flux=True)) + else: + ratios[0] = np.array(['0 '] * self.nm) + + #we will never have 4363... + ratios[1] = np.zeros(self.nm) + + if self.hasO3Hb: + ratios[2] = self.O3Hb + elif self.hasO3: + ratios[2] = ((self.O35007 / self.Hb) + self.dustcorrect(k_O35007, k_Hb, flux=True)) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + else: + ratios[2] = np.zeros(self.nm) + + if self.hasN2: + ratios[3] = self.N26584 / self.Hb + else: + ratios[3] = np.zeros(self.nm) + + if self.hasS2Hb: + ratios[4] = self.S2Hb + elif self.hasS2 and self.hasS26731: + ratios[4] = (((self.S26717 + self.S26731) / self.Hb) + self.dustcorrect(k_S2, k_Hb, flux=True)) + else: + ratios[4] = np.zeros(self.nm) + + + for ni in range(self.nm): + fin_hii_chi.write('%f %f %f %f %f\n' % (ratios[0][ni], ratios[1][ni], ratios[2][ni], ratios[3][ni], ratios[4][ni])) + fin_hii_chi.close() + os.system("ln -s %s/C13*dat . " % os.getenv('HIICHI_DIR')) + print "\n\n\n\n\n" + #os.system("python %s/HII-CHI-mistry_v01.2.py in.tmp"%os.getenv('HIICHI_DIR')) + #os.system("python %s/HII-CHI-mistry_v01.2.py %s/in.tmp"%(os.getenv('HIICHI_DIR'),os.getenv('HIICHI_DIR'))) + p = Popen(['python', '%s/HII-CHI-mistry_v01.2.py' % os.getenv('HIICHI_DIR'), '%s/in.tmp' % os.getenv('HIICHI_DIR')], stdout=PIPE, stdin=PIPE, stderr=STDOUT) + out, err = p.communicate(input='%s/in.tmp' % os.getenv('HIICHI_DIR')) + print "\n\n\n\n\n" + out = StringIO(out) + # for l in enumerate(out): + # if l[0].isdigit(): + # break + out = out.readlines()[12:] + self.mds['PM14'] = np.zeros((self.nm)) + self.mds['PM14err'] = np.zeros((self.nm)) + for i, l in enumerate(out): + self.mds['PM14'][i], self.mds['PM14err'][i] = map(float, l.split()[3:5]) + #data = np.loadtxt(out, skiprows=12, usecols=(3,4))#, dtype=[('lOH','f'),('elOH','f')], delimiter=",", unpack = True) + print self.mds + os.system('rm -r C13*dat') diff --git a/build/scripts-2.7/pylabsetup.py b/build/scripts-2.7/pylabsetup.py new file mode 100755 index 0000000..5ffef05 --- /dev/null +++ b/build/scripts-2.7/pylabsetup.py @@ -0,0 +1,41 @@ +import matplotlib as mpl +import pylab as plt + +mpl.rcParams.update(mpl.rcParamsDefault) +mpl.rcParams['font.size'] = 26. +mpl.rcParams['font.family'] = 'serif' +#mpl.rcParams['font.family'] = 'serif' +mpl.rcParams['font.serif'] = ['Times New Roman', 'Times', 'Palatino', 'Charter', 'serif'] +mpl.rcParams['font.sans-serif'] = ['Helvetica'] +mpl.rcParams['axes.labelsize'] = 24 +mpl.rcParams['xtick.labelsize'] = 22. +mpl.rcParams['ytick.labelsize'] = 22. +mpl.rcParams['xtick.major.size'] = 15. +mpl.rcParams['xtick.minor.size'] = 10. +mpl.rcParams['ytick.major.size'] = 15. +mpl.rcParams['ytick.minor.size'] = 10. +#mpl.rcParams['figure.autolayout']= True + +#fontsize=26 +#mpl.rc('axes', titlesize=fontsize) +#mpl.rc('axes', labelsize=fontsize) +#mpl.rc('xtick', labelsize=fontsize) +#mpl.rc('ytick', labelsize=fontsize) +#mpl.rc('font', size=fontsize, family='serif', serif='Utopia', +# style='normal', variant='normal', +# stretch='normal', weight='normal') +#mpl.rc('font',**{'family':'serif','serif':[ 'Times New Roman', 'Times', 'serif'], +# 'sans-serif':['Helvetica'], 'size':19, +# 'weight':'normal'}) +mpl.rc('axes', **{'linewidth' : 1.2}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('ytick', **{'major.pad': 5, 'color': 'k'}) +mpl.rc('xtick', **{'major.pad': 5, 'color': 'k'}) +params = {'legend.fontsize': 24, + 'legend.numpoints': 1, + 'legend.handletextpad': 1 + } + +plt.rcParams.update(params) +plt.minorticks_on() diff --git a/pyMCZ/mcz.py b/pyMCZ/mcz.py index edd3c4d..10ff4f3 100644 --- a/pyMCZ/mcz.py +++ b/pyMCZ/mcz.py @@ -42,7 +42,7 @@ PROFILING = True PROFILING = False -alllines = ['[OII]3727', 'Hb', '[OIII]4959', '[OIII]5007', '[OI]6300', 'Ha', '[NII]6584', '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532'] +alllines = ['[OII]3727', 'Hg','Hb', '[OIII]4959', '[OIII]5007', '[OI]6300', 'Ha', '[NII]6584', '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532'] morelines = ['E(B-V)', 'dE(B-V)', 'scale_blue', 'd scale_blue'] MAXPROCESSES = 10 @@ -130,7 +130,12 @@ def readfile(filename): l0 = f.readline().replace(' ', '') l1 = f.readline().split() if l0.startswith('#') or l0.startswith(';'): - header = l0.strip().replace(";", '').replace("#", '').split(',') + # Modified to account for non-standard header (\t instead of , separated) jch + temp_header = l0.strip().replace(";", '').replace("#", '') + if temp_header.find(',') == -1: + header = temp_header.split('\t') + else: + header = temp_header.split(',') header[0] = header[0].replace(' ', '') header = header[:len(l1)] else: @@ -429,7 +434,7 @@ def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False return "-1, -1,-1", [], None -def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr, +def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr,dust_blue, verbose, res, scales, nps, logf))): logf = sys.stdout logf.write("\n\nreading in measurements %d\n"%(i + 1)) @@ -441,7 +446,9 @@ def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr, #print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) fluxi[k] = flux[k][i] * np.ones(len(sample[i])) + err[k][i] * sample[i] warnings.filterwarnings("ignore") - success = metallicity.calculation(scales[i], fluxi, nm, mds, nps, logf, disp=disp, dust_corr=dust_corr, verbose=verbose) + success = metallicity.calculation(scales[i], fluxi, nm, mds, nps, logf, + disp=disp, verbose=verbose, + dust_corr=dust_corr,dust_blue=dust_blue) if success == -1: logf.write("MINIMUM REQUIRED LINES: '[OII]3727','[OIII]5007','[NII]6584','[SII]6717'\n") #print >> logf, "MINIMUM REQUIRED LINES: '[OII]3727','[OIII]5007','[NII]6584','[SII]6717'" @@ -467,7 +474,8 @@ def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr, ## mode 't' calculates this based on 2*n**1/3 ############################################################################## #@profile -def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickle=False, dust_corr=True, verbose=False, fs=24): +def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, + unpickle=False, dust_corr=True, dust_blue=False, verbose=False, fs=24): global RUNSIM # ,BINMODE#,NOPLOT assert(len(flux[0]) == len(err[0])), "flux and err must be same dimensions" assert(len(flux['galnum']) == nm), "flux and err must be of declaired size" @@ -549,7 +557,7 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl logf.write( "\n\n\nrunning on %d threads\n\n\n" % nps) #print >> logf, "\n\n\nrunning on %d threads\n\n\n" % nps - second_args = [sample, flux, err, nm, bss, mds, VERBOSE, dust_corr, VERBOSE, res, scales, nps, logf] + second_args = [sample, flux, err, nm, bss, mds, VERBOSE, dust_corr, dust_blue, VERBOSE, res, scales, nps, logf] pool = mpc.Pool(processes=nps) # depends on available cores rr = pool.map(calc, itertools.izip(range(NM0, nm), itertools.repeat(second_args))) # for i in range(nm): result[i] = f(i, second_args) @@ -591,7 +599,9 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl logf.write("\n") #print >> logf, "" - success = metallicity.calculation(scales, fluxi, nm, mds, 1, logf, disp=VERBOSE, dust_corr=dust_corr, verbose=VERBOSE) + success = metallicity.calculation(scales, fluxi, nm, mds, 1, logf, + disp=VERBOSE, verbose=VERBOSE, + dust_corr=dust_corr, dust_blue=dust_blue) if success == -1: print ("MINIMUM REQUIRED LINES: [OII]3727 & [OIII]5007, or [NII]6584, and Ha & Hb if you want dereddening") #continue @@ -723,6 +733,7 @@ def main(): parser.add_argument('--verbose', default=False, action='store_true', help="verbose mode") parser.add_argument('--log', default=None, type=str, help="log file, if not passed defaults to standard output") parser.add_argument('--nodust', default=False, action='store_true', help=" don't do dust corrections (default is to do it)") + parser.add_argument('--bluedust', default=False, action='store_true', help=" use Hg/Hb for dust correction (default is Hb/Ha)") parser.add_argument('--noplot', default=False, action='store_true', help=" don't plot individual distributions (default is to plot all distributions)") parser.add_argument('--asciiout', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") parser.add_argument('--asciidistrib', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") @@ -763,7 +774,10 @@ def main(): fi = input_data(args.name, path=path) if fi != -1: logf = smart_open(args.log) - run(fi, args.nsample, args.md, args.multiproc, logf, unpickle=args.unpickle, dust_corr=(not args.nodust), verbose=VERBOSE) + run(fi, args.nsample, args.md, args.multiproc, logf, + unpickle=args.unpickle, + dust_corr=(not args.nodust), dust_blue=(args.bluedust), + verbose=VERBOSE) if args.log: logf.close() else: diff --git a/pyMCZ/metallicity.py b/pyMCZ/metallicity.py index d2b0f4a..c42ef79 100644 --- a/pyMCZ/metallicity.py +++ b/pyMCZ/metallicity.py @@ -27,6 +27,7 @@ ##list of metallicity methods, in order calculated Zs = ["E(B-V)", # based on Halpha, Hbeta + "E(B-V)blue", # based on Hbeta, Hgamma "logR23", # Hbeta, [OII]3727, [OIII]5007, [OIII]4959 "D02", # Halpha, [NII]6584 @@ -83,13 +84,15 @@ def printsafemulti(string, logf, nps): ############################################################################## #@profile -def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=False, verbose=False): +def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, + dust_blue=False, disp=False, verbose=False): global IGNOREDUST mscales.setdustcorrect() raw_lines = {} raw_lines['[OIII]5007'] = np.array([float('NaN')]) raw_lines['Hb'] = np.array([float('NaN')]) + raw_lines['Hg'] = np.array([float('NaN')]) raw_lines['Hz'] = np.array([float('NaN')]) for k in measured.iterkeys(): #kills all non-finite terms @@ -107,12 +110,16 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=Fal ######change this only if youre spectra are very high SNR raw_lines['[OIII]4959'] = raw_lines['[OIII]5007'] / 3. raw_lines['[OIII]49595007'] = raw_lines['[OIII]4959'] + raw_lines['[OIII]5007'] - mscales.setHab(raw_lines['Ha'], raw_lines['Hb']) + mscales.setHabg(raw_lines['Ha'], raw_lines['Hb'], raw_lines['Hg']) #if Ha or Hb is zero, cannot do red correction - if dust_corr and mscales.hasHa and mscales.hasHb: + if dust_corr and mscales.hasHa and mscales.hasHb and not dust_blue: with np.errstate(invalid='ignore'): mscales.calcEB_V() + elif dust_blue and mscales.hasHg and mscales.hasHb: + with np.errstate(invalid='ignore'): + mscales.calcEB_Vblue() + print('----- Using Hg/Hb for E(B-V)_blue estimate. -----') elif dust_corr and not IGNOREDUST: if nps > 1: diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index d7f0056..23cf19b 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -9,6 +9,7 @@ k_Ha = 2.535 # CCM Rv=3.1 k_Hb = 3.609 # CCM Rv=3.1 +k_Hg = 4.400 # CCM Rv=3.1 #k_O1=2.661 # CCM Rv=3.1 k_O2 = 4.771 # CCM Rv=3.1 @@ -88,8 +89,9 @@ def __init__(self, num, logf, nps): self.nm = num self.Ha = None self.Hb = None + self.Hg = None - self.hasHa, self.hasHb = False, False + self.hasHa, self.hasHb, self.hasHg = False, False, False self.hasO2, self.hasO3 = False, False self.hasS2, self.hasN2 = False, False @@ -281,13 +283,16 @@ def dustcorrect(self, l1, l2, flux=False): return 0 return 1.0 - def setHab(self, Ha, Hb): + def setHabg(self, Ha, Hb, Hg): self.Ha = Ha self.Hb = Hb + self.Hg = Hg if sum(self.Ha > 0): self.hasHa = True if sum(self.Hb > 0): self.hasHb = True + if sum(self.Hg > 0): + self.hasHg = True def setOlines(self, O23727, O35007, O16300, O34959): self.O23727 = O23727 @@ -389,6 +394,14 @@ def calcEB_V(self): self.mds['E(B-V)'] = np.log10(2.86 * self.Hb / self.Ha) / (0.4 * (k_Ha - k_Hb)) # E(B-V) self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 + #@profile + def calcEB_Vblue(self): + printsafemulti("calculating E(B-V)_blue", self.logf, self.nps) + gamma_beta_intrinsic = 2.145 + self.mds['E(B-V)'] = np.log10(gamma_beta_intrinsic + * self.Hg / self.Hb) / (0.4 * (k_Hb - k_Hg)) # E(B-V) + self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 + #@profile def calcNIISII(self): if self.hasS2 and self.hasN2: From 12fc65249cd7c53041429641dfb2ef148f4a446f Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Mon, 15 May 2017 20:29:25 -0400 Subject: [PATCH 02/19] * Housekeeping... --- .idea/misc.xml | 2 +- .idea/pyMCZ.iml | 2 +- .idea/workspace.xml | 436 ++++++++++++++++++++++++++++++++ build/lib/pyMCZ/__init__.py | 7 - build/lib/pyMCZ/pylabsetup.py | 41 --- build/scripts-2.7/pylabsetup.py | 41 --- 6 files changed, 438 insertions(+), 91 deletions(-) create mode 100644 .idea/workspace.xml delete mode 100644 build/lib/pyMCZ/__init__.py delete mode 100644 build/lib/pyMCZ/pylabsetup.py delete mode 100755 build/scripts-2.7/pylabsetup.py diff --git a/.idea/misc.xml b/.idea/misc.xml index 7900cb6..75213d3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/.idea/pyMCZ.iml b/.idea/pyMCZ.iml index 6f63a63..49ae6b6 100644 --- a/.idea/pyMCZ.iml +++ b/.idea/pyMCZ.iml @@ -2,7 +2,7 @@ - + diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..a24e286 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + morelines + diagnostics( + calculating E(B-V) + E(B-V) + class + def dustcorrect + calculation( + calc( + calculating + Zs + red_corr + mds + mds['E(B + ignoredust + dustcorrect + bluedust + ingest_data + input_data + run( + NM0 + checkhist + get_keys + dust + res + diagnostics + print( + blue + -- + M13 + M13_O3N2 + + + ** + x[j] + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1494772658568 + + + 1494813755291 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/lib/pyMCZ/__init__.py b/build/lib/pyMCZ/__init__.py deleted file mode 100644 index e17c7f3..0000000 --- a/build/lib/pyMCZ/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -import os - -p = os.path.abspath('./') -if not os.path.exists(p + '/output'): - os.makedirs(p + '/output') - -__all__ = ["mcz", "metallicity", "metscales", "testcompleteness"] diff --git a/build/lib/pyMCZ/pylabsetup.py b/build/lib/pyMCZ/pylabsetup.py deleted file mode 100644 index 5ffef05..0000000 --- a/build/lib/pyMCZ/pylabsetup.py +++ /dev/null @@ -1,41 +0,0 @@ -import matplotlib as mpl -import pylab as plt - -mpl.rcParams.update(mpl.rcParamsDefault) -mpl.rcParams['font.size'] = 26. -mpl.rcParams['font.family'] = 'serif' -#mpl.rcParams['font.family'] = 'serif' -mpl.rcParams['font.serif'] = ['Times New Roman', 'Times', 'Palatino', 'Charter', 'serif'] -mpl.rcParams['font.sans-serif'] = ['Helvetica'] -mpl.rcParams['axes.labelsize'] = 24 -mpl.rcParams['xtick.labelsize'] = 22. -mpl.rcParams['ytick.labelsize'] = 22. -mpl.rcParams['xtick.major.size'] = 15. -mpl.rcParams['xtick.minor.size'] = 10. -mpl.rcParams['ytick.major.size'] = 15. -mpl.rcParams['ytick.minor.size'] = 10. -#mpl.rcParams['figure.autolayout']= True - -#fontsize=26 -#mpl.rc('axes', titlesize=fontsize) -#mpl.rc('axes', labelsize=fontsize) -#mpl.rc('xtick', labelsize=fontsize) -#mpl.rc('ytick', labelsize=fontsize) -#mpl.rc('font', size=fontsize, family='serif', serif='Utopia', -# style='normal', variant='normal', -# stretch='normal', weight='normal') -#mpl.rc('font',**{'family':'serif','serif':[ 'Times New Roman', 'Times', 'serif'], -# 'sans-serif':['Helvetica'], 'size':19, -# 'weight':'normal'}) -mpl.rc('axes', **{'linewidth' : 1.2}) -mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) -mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) -mpl.rc('ytick', **{'major.pad': 5, 'color': 'k'}) -mpl.rc('xtick', **{'major.pad': 5, 'color': 'k'}) -params = {'legend.fontsize': 24, - 'legend.numpoints': 1, - 'legend.handletextpad': 1 - } - -plt.rcParams.update(params) -plt.minorticks_on() diff --git a/build/scripts-2.7/pylabsetup.py b/build/scripts-2.7/pylabsetup.py deleted file mode 100755 index 5ffef05..0000000 --- a/build/scripts-2.7/pylabsetup.py +++ /dev/null @@ -1,41 +0,0 @@ -import matplotlib as mpl -import pylab as plt - -mpl.rcParams.update(mpl.rcParamsDefault) -mpl.rcParams['font.size'] = 26. -mpl.rcParams['font.family'] = 'serif' -#mpl.rcParams['font.family'] = 'serif' -mpl.rcParams['font.serif'] = ['Times New Roman', 'Times', 'Palatino', 'Charter', 'serif'] -mpl.rcParams['font.sans-serif'] = ['Helvetica'] -mpl.rcParams['axes.labelsize'] = 24 -mpl.rcParams['xtick.labelsize'] = 22. -mpl.rcParams['ytick.labelsize'] = 22. -mpl.rcParams['xtick.major.size'] = 15. -mpl.rcParams['xtick.minor.size'] = 10. -mpl.rcParams['ytick.major.size'] = 15. -mpl.rcParams['ytick.minor.size'] = 10. -#mpl.rcParams['figure.autolayout']= True - -#fontsize=26 -#mpl.rc('axes', titlesize=fontsize) -#mpl.rc('axes', labelsize=fontsize) -#mpl.rc('xtick', labelsize=fontsize) -#mpl.rc('ytick', labelsize=fontsize) -#mpl.rc('font', size=fontsize, family='serif', serif='Utopia', -# style='normal', variant='normal', -# stretch='normal', weight='normal') -#mpl.rc('font',**{'family':'serif','serif':[ 'Times New Roman', 'Times', 'serif'], -# 'sans-serif':['Helvetica'], 'size':19, -# 'weight':'normal'}) -mpl.rc('axes', **{'linewidth' : 1.2}) -mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) -mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) -mpl.rc('ytick', **{'major.pad': 5, 'color': 'k'}) -mpl.rc('xtick', **{'major.pad': 5, 'color': 'k'}) -params = {'legend.fontsize': 24, - 'legend.numpoints': 1, - 'legend.handletextpad': 1 - } - -plt.rcParams.update(params) -plt.minorticks_on() From 174915d070e26f21a6964612bb27033f26bd6f7b Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Mon, 15 May 2017 20:34:59 -0400 Subject: [PATCH 03/19] *Housekeeping --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 1 + .gitignore~ | 1 + .../inspectionProfiles/profiles_settings.xml | 7 - .idea/misc.xml | 4 - .idea/modules.xml | 8 - .idea/pyMCZ.iml | 12 - .idea/vcs.xml | 6 - .idea/workspace.xml | 436 ------------------ pyMCZ/metallicity.pyc | Bin 0 -> 6178 bytes pyMCZ/metscales.pyc | Bin 0 -> 38001 bytes pyMCZ/pylabsetup.pyc | Bin 0 -> 1224 bytes 12 files changed, 2 insertions(+), 473 deletions(-) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 .gitignore~ delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/pyMCZ.iml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml create mode 100644 pyMCZ/metallicity.pyc create mode 100644 pyMCZ/metscales.pyc create mode 100644 pyMCZ/pylabsetup.pyc diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..af3cea9ec33c0384a5c237efd88bf064c37d34f1 GIT binary patch literal 6148 zcmeHLJ5B>J5FLjNB+x`kLxuJPNU5M)B};{nkm#sD`GG{(C?o|`J_QXY$O*Uz5)DXn za13VbA@O9L79lhv*>B_L@w1<@b{qgO?OCq{&;WoM7Q$p3t4I(}r4$uz*+n!o#?7#G z)-6WkQcrDA2h@ST=m5RD3GBlVu3;L!zm>OsC*(#&p7&H$6pyEGJ};K;XI&uO7p1VEN)?p^AIb? z&FoXM5&a-Hq8+e87gEyQgn~39%Jso~m5*qP{D`{Xg?`DDa z@!48h9G$f(mJ2Kt)X#c!DOmAw>>lVSrvDXOAeYhuV#a7aVhe`;5O6kVr4Ib61K;}V BwgLbE literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/.gitignore~ b/.gitignore~ new file mode 100644 index 0000000..62c8935 --- /dev/null +++ b/.gitignore~ @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index c23ecac..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 75213d3..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index c51f2e6..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/pyMCZ.iml b/.idea/pyMCZ.iml deleted file mode 100644 index 49ae6b6..0000000 --- a/.idea/pyMCZ.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index a24e286..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,436 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - morelines - diagnostics( - calculating E(B-V) - E(B-V) - class - def dustcorrect - calculation( - calc( - calculating - Zs - red_corr - mds - mds['E(B - ignoredust - dustcorrect - bluedust - ingest_data - input_data - run( - NM0 - checkhist - get_keys - dust - res - diagnostics - print( - blue - -- - M13 - M13_O3N2 - - - ** - x[j] - - - - - - - - - - - true - DEFINITION_ORDER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - project - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1494772658568 - - - 1494813755291 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pyMCZ/metallicity.pyc b/pyMCZ/metallicity.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9eefe0460a25b50d0c7f529f94ef6afc54c4fd6e GIT binary patch literal 6178 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 literal 0 HcmV?d00001 diff --git a/pyMCZ/metscales.pyc b/pyMCZ/metscales.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10c470bf500c798ea17ef8ab38554bd22f6f2130 GIT binary patch literal 38001 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 T?$`OZvBUPWufkSYC8)E%m;4p$ literal 0 HcmV?d00001 From 622a5f6d53604baca3d63957ddf212f2b01a9bb9 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Tue, 16 May 2017 15:16:46 -0400 Subject: [PATCH 04/19] Added calcC17 module to use the SEL methods calibrated by Curti+ (2017, MNRAS, 465, 1384). --- README.md | 3 ++ pyMCZ/mcz.py | 2 +- pyMCZ/metallicity.py | 13 ++++- pyMCZ/metscales.py | 118 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fee642a..36c5d88 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ additional command line arguments --md MD metallicity scales to calculate. default is 'all', options are: D02, Z94, M91, M08, P05,P10, PP04, M13, D13, KD02, KD02comb, DP00 (deprecated), P01 (deprecated), C01 (deprecated) + --unpickle if it exists, a pickle file generated in a previous run for this \ and this number of samples is read in instead of recalculating the metalicity @@ -71,6 +72,8 @@ additional command line arguments --nodust don't do dust corrections (default is to do it) + --bluedust Calculate extinction based on Hg/Hb (default is to do Hb/Ha) + --noplot don't plot individual distributions (default is to plot all distributions) diff --git a/pyMCZ/mcz.py b/pyMCZ/mcz.py index 10ff4f3..1d1b2e9 100644 --- a/pyMCZ/mcz.py +++ b/pyMCZ/mcz.py @@ -739,7 +739,7 @@ def main(): parser.add_argument('--asciidistrib', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") parser.add_argument('--md', default='all', type=str, help='''metallicity diagnostic to calculate. default is 'all', options are: - D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, D13all, KD02, DP00 (deprecated), P01, D16''') + D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, D13all, KD02, DP00 (deprecated), P01, D16, C17''') parser.add_argument('--multiproc', default=False, action='store_true', help=" multiprocess, with number of threads max(available cores-1, MAXPROCESSES)") args = parser.parse_args() diff --git a/pyMCZ/metallicity.py b/pyMCZ/metallicity.py index c42ef79..d49627a 100644 --- a/pyMCZ/metallicity.py +++ b/pyMCZ/metallicity.py @@ -54,9 +54,11 @@ "KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) "KD02comb", "PM14", - "D16"] # ,"KK04comb"] + "D16", + "C17_O3O2", "C17_O3N2","C17_N2Ha","C17_O2Hb","C17_O3Hb","C17_R23"] # ,"KK04comb"] #'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' + Zserr = ['PM14err'] # ,"KK04comb"] #'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' @@ -194,6 +196,9 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, mscales.calcKK04_R23() mscales.calcKDcombined() + mscales.calcD16() + mscales.calcC17() + if 'DP00' in mds: mscales.calcDP00() if 'P01' in mds: @@ -260,5 +265,11 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, mscales.calcKK04_N2Ha() mscales.calcKK04_R23() mscales.calcKDcombined() + + # Modifications by jch [5/2017, Santiago] to include newer scales. if 'D16' in mds: + # Dopita+ 2016 mscales.calcD16() + if 'C17' in mds: + # Curti+ 2017 + mscales.calcC17() diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index 23cf19b..14ac271 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -66,6 +66,13 @@ 'O2Hb': [0.5603, 0.0450, -1.8017, -1.8434, -0.6549], 'O3N2': [0.4520, -2.6096, -0.7170, 0.1347]} +C17_coefs = {'N2Ha': [-0.489, 1.513,-2.554,-5.293,-2.867], + 'O3O2': [-0.691,-2.944,-1.308], + 'O3N2': [0.281,-4.765,-2.268], + 'O2Hb': [0.418,-0.961,-3.505,-1.949], + 'O3Hb': [-0.277,-3.549,-3.593,-0.981], + 'R23': [0.527,-1.569,-1.652,-0.421]} + #this is to check the Maiolino coefficients and find the split maximum of the cirves with degeneracy ''' import pylab as pl @@ -867,6 +874,115 @@ def calcC01_ZR23(self): printsafemulti('''WARNING: needs [NII]6584, [SII]6717, [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, did you set them up with setOlines() and ?''', self.logf, self.nps) + def calcC17(self, allC17=False): + # Curti+ 2017 + # Monthly Notices Royal Astronomical Society, vol. 465, pp 1384-1400 + # Published in October 2016 + # + # Added 5/16/17 by jch, Santiago + + printsafemulti("calculating C17", self.logf, self.nps) + highZ = None + + if self.logO35007O2 is not None: + self.mds['C17_O3O2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3O2']] * self.nm).T + coefs[0] = coefs[0] - self.logO35007O2 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + # the two cumsum assure that if the condition for the ith element + # of indx is [False, False] then after the first cumsum(1) is [0,0] + # [False, True] is [0,1] + # [True, True] is [1,2] + # but (here is the kicker) [True, False] is [1,1]. + # Because i want only one solution + # (i'll settle for the first one occurring) [1,1] is ambiguous. + # The second cumsum(1) makes + # [0,0]->[0,0], [0,1]->[0,1], [1,2]->[1,3] and finally [1,1]->[1,2] + + self.mds['C17_O3O2'][(indx.sum(1)) > 0] = sols[indx].real + highZ = np.median(self.logO35007O2) < 0 + + if self.logN2Ha is not None: + self.mds['C17_N2Ha'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['N2Ha']] * self.nm).T + coefs[0] = coefs[0] - self.logN2Ha + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + + self.mds['C17_N2Ha'][(indx.sum(1)) > 0] = sols[indx].real + if highZ is None: + highZ = np.median(self.logN2Ha) > -1.3 + + if self.hasO3 and self.hasN2: + self.mds['C17_O3N2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3N2']] * self.nm).T + coefs[0] = coefs[0] - np.log(self.O35007 / self.N26584) \ + + self.dustcorrect(k_O35007, k_N2, flux=False) + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) \ + * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3N2'][(indx.sum(1)) > 0] = sols[indx].real + + # Require allC17 flag if we want everything. + if not allC17: + return + else: + printsafemulti("calculating other C17s", self.logf, self.nps) + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute C17_R23 without R23", self.logf, self.nps) + else: + self.mds['C17_R23'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['R23']] * self.nm).T + coefs[0] = coefs[0] - self.logR23 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * + (sols.real >= 8.0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * + (sols.real <= 8.0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real + + if self.logO3Hb is not None: + + self.mds['C17_O3Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO3Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * + (sols.real >= 7.9)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * + (sols.real <= 7.9)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + + if self.logO2Hb is not None: + self.mds['C17_O2Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O2Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO2Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * + (sols.real >= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * + (sols.real <= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + + #@profile def calcM91(self): # ## calculating McGaugh (1991) @@ -1317,7 +1433,7 @@ def calcPM14(self): def calcD16(self): #Dopita+ 2016 printsafemulti("calculating D16", self.logf, self.nps) - + if not self.hasHa or not self.hasN2 or not self.hasS2: printsafemulti("WARNING: need N2, Ha and SII, ", self.logf, self.nps) From c98f294560e74241d7ccacf16a60d6a0dd3b0a28 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Tue, 16 May 2017 17:35:13 -0400 Subject: [PATCH 05/19] 00ToDo edits. --- 00ToDo | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 00ToDo diff --git a/00ToDo b/00ToDo new file mode 100644 index 0000000..e69de29 From a5c58f9a8fa0cc8160c81bea1fd459117abebff5 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Thu, 18 May 2017 21:47:32 -0400 Subject: [PATCH 06/19] *Started pyqz fixes, but these will take some doing. *Fixed bug: C17 O3N2 ratio defined differently than M08. *Fixed bug: C17 was looking at roots over too broad a metallicity. *Moved C17 O3Hb method into standard set. --- .gitignore | 1 + pyMCZ/metallicity.py | 4 ++ pyMCZ/metscales.py | 124 ++++++++++++++++++++++++++----------------- 3 files changed, 79 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 9f11b75..eddf617 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea/ +*~ diff --git a/pyMCZ/metallicity.py b/pyMCZ/metallicity.py index d49627a..544f299 100644 --- a/pyMCZ/metallicity.py +++ b/pyMCZ/metallicity.py @@ -273,3 +273,7 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, if 'C17' in mds: # Curti+ 2017 mscales.calcC17() + if 'C17all' in mds: + # Curti+ 2017 + mscales.calcC17(allC17=True) + diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index 14ac271..7ebf457 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -540,11 +540,15 @@ def calcpyqz(self, plot=False, allD13=False): method='default', plot=plot, n_plot=False, savefig=False)[0].T else: #pyqz.get_grid_fn(Pk=5.0,calibs='GCZO', kappa =20, struct='pp') - self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ - np.atleast_1d([self.OIII_SII])], \ - '[NII]/[SII]+;[OIII]/[SII]+', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + # self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + # np.atleast_1d([self.OIII_SII])], \ + # '[NII]/[SII]+;[OIII]/[SII]+', \ + # show_plot=plot, n_plot=False, \ + # save_plot=False, verbose=False)[0].T + self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', + [np.atleast_1d([self.NII_SII]), + np.atleast_1d([self.OIII_SII])], + '[NII]/[SII]+;[OIII]/[SII]+')[0].T if self.OIII_Hb is not None: if oldpyqz: @@ -552,11 +556,14 @@ def calcpyqz(self, plot=False, allD13=False): np.atleast_1d([self.OIII_Hb]), 'NII/SII', 'OIII/Hb', \ method='default', plot=plot, n_plot=False, savefig=False)[0].T else: + # self.mds['D13_N2S2_O3SHb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + # np.atleast_1d([self.OIII_Hb])], \ + # '[NII]/[SII]+;[OIII]/Hb', \ + # show_plot=plot, n_plot=False, \ + # save_plot=False, verbose=False)[0].T self.mds['D13_N2S2_O3SHb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ np.atleast_1d([self.OIII_Hb])], \ - '[NII]/[SII]+;[OIII]/Hb', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + '[NII]/[SII]+;[OIII]/Hb')[0].T @@ -595,9 +602,7 @@ def calcpyqz(self, plot=False, allD13=False): else: self.mds['D13_N2O2_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ np.atleast_1d([self.OIII_Hb])], \ - '[NII]/[OII]+;[OIII]/Hb', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + '[NII]/[OII]+;[OIII]/Hb')[0].T if self.OIII_OII is not None: if oldpyqz: @@ -607,9 +612,7 @@ def calcpyqz(self, plot=False, allD13=False): else: self.mds['D13_N2O2_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ np.atleast_1d([self.OIII_OII])], \ - '[NII]/[OII]+;[OIII]/[OII]+', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + '[NII]/[OII]+;[OIII]/[OII]+')[0].T if self.logN2Ha is not None: if self.OIII_Hb is not None: if oldpyqz: @@ -619,9 +622,7 @@ def calcpyqz(self, plot=False, allD13=False): else: self.mds['D13_N2Ha_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ np.atleast_1d([self.OIII_Hb])], \ - '[NII]/Ha;[OIII]/Hb', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + '[NII]/Ha;[OIII]/Hb')[0].T if self.OIII_OII is not None: if oldpyqz: self.mds['D13_N2Ha_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.logN2Ha]), \ @@ -631,9 +632,7 @@ def calcpyqz(self, plot=False, allD13=False): else: self.mds['D13_N2Ha_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ np.atleast_1d([self.OIII_Hb])], \ - '[NII]/Ha;[OIII]/[OII]+', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + '[NII]/Ha;[OIII]/[OII]+')[0].T #@profile def calcDP00(self): @@ -880,18 +879,27 @@ def calcC17(self, allC17=False): # Published in October 2016 # # Added 5/16/17 by jch, Santiago + import pdb + + valid_upper = 8.85 + valid_lower = 7.6 printsafemulti("calculating C17", self.logf, self.nps) highZ = None if self.logO35007O2 is not None: + # C17 O3O2 self.mds['C17_O3O2'] = np.zeros(self.nm) + float('NaN') coefs = np.array([C17_coefs['O3O2']] * self.nm).T coefs[0] = coefs[0] - self.logO35007O2 + + # sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * + # Find valid solutions + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + # the two cumsum assure that if the condition for the ith element # of indx is [False, False] then after the first cumsum(1) is [0,0] # [False, True] is [0,1] @@ -902,16 +910,22 @@ def calcC17(self, allC17=False): # The second cumsum(1) makes # [0,0]->[0,0], [0,1]->[0,1], [1,2]->[1,3] and finally [1,1]->[1,2] + # Set the results where appropriate self.mds['C17_O3O2'][(indx.sum(1)) > 0] = sols[indx].real + + # Now use this to see if we're on the high-Z branch of R23 highZ = np.median(self.logO35007O2) < 0 if self.logN2Ha is not None: + # C17 N2Ha self.mds['C17_N2Ha'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['N2Ha']] * self.nm).T coefs[0] = coefs[0] - self.logN2Ha + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 self.mds['C17_N2Ha'][(indx.sum(1)) > 0] = sols[indx].real @@ -919,15 +933,33 @@ def calcC17(self, allC17=False): highZ = np.median(self.logN2Ha) > -1.3 if self.hasO3 and self.hasN2: + # C17 O3N2 self.mds['C17_O3N2'] = np.zeros(self.nm) + float('NaN') coefs = np.array([C17_coefs['O3N2']] * self.nm).T - coefs[0] = coefs[0] - np.log(self.O35007 / self.N26584) \ - + self.dustcorrect(k_O35007, k_N2, flux=False) + o3n2_value = self.logO3Hb - self.logN2Ha + coefs[0] = coefs[0] - o3n2_value + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) \ + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) \ * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 self.mds['C17_O3N2'][(indx.sum(1)) > 0] = sols[indx].real + if self.logO3Hb is not None: + # C17 O3Hb + self.mds['C17_O3Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO3Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real >= 8.3)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 8.3) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real <= 7.9)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real # Require allC17 flag if we want everything. if not allC17: return @@ -945,28 +977,16 @@ def calcC17(self, allC17=False): coefs[0] = coefs[0] - self.logR23 sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 if highZ is True: - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * - (sols.real >= 8.0)).cumsum(1).cumsum(1) == 1 + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real >= 8.4)).cumsum(1).cumsum(1) == 1 self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real elif highZ is False: - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * - (sols.real <= 8.0)).cumsum(1).cumsum(1) == 1 + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real < 8.4)).cumsum(1).cumsum(1) == 1 self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real - if self.logO3Hb is not None: - - self.mds['C17_O3Hb'] = np.zeros(self.nm) + float('NaN') - coefs = np.array([C17_coefs['O3Hb']] * self.nm).T - coefs[0] = coefs[0] - self.logO3Hb - sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - if highZ is True: - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * - (sols.real >= 7.9)).cumsum(1).cumsum(1) == 1 - self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real - elif highZ is False: - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * - (sols.real <= 7.9)).cumsum(1).cumsum(1) == 1 - self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real if self.logO2Hb is not None: self.mds['C17_O2Hb'] = np.zeros(self.nm) + float('NaN') @@ -974,12 +994,14 @@ def calcC17(self, allC17=False): coefs[0] = coefs[0] - self.logO2Hb sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 if highZ is True: - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * + indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * + (sols.imag == 0) * (sols.real >= 8.7)).cumsum(1).cumsum(1) == 1 self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real elif highZ is False: - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * - (sols.real <= 8.7)).cumsum(1).cumsum(1) == 1 + indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * + (sols.imag == 0) * + (sols.real <= valid_upper)).cumsum(1).cumsum(1) == 1 self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real @@ -1207,7 +1229,7 @@ def calcKK04_N2Ha(self): convergence = np.abs(self.logq - logq_save).mean() logq_save = self.logq.copy() if ii >= 100: - printsafemulti("WARNING: loop did not converge", self.logf, self.nps) + printsafemulti("WARNING [KK04_N2Ha]: loop did not converge", self.logf, self.nps) Z_new_N2Ha = np.zeros(self.nm) + float('NaN') else: self.logq = 7.37177 * np.ones(self.nm) @@ -1267,7 +1289,7 @@ def calcKK04_R23(self): convergence = np.abs((logqold - logq).mean()) logqold = logq.copy() if ii >= 100: - printsafemulti("WARNING: loop did not converge", self.logf, self.nps) + printsafemulti("WARNING [KK04_R23]: loop did not converge", self.logf, self.nps) Z_new = np.zeros(self.nm) + float('NaN') Z_new_lims = [nppoly.polyval(self.logR23, [9.40, 4.65, -3.17]) - \ logq * nppoly.polyval(self.logR23, [0.272, 0.547, -0.513]), @@ -1438,8 +1460,10 @@ def calcD16(self): printsafemulti("WARNING: need N2, Ha and SII, ", self.logf, self.nps) return -1 - y = self.logN2S2 + 0.264 * self.logN2Ha - self.mds["D16"] = 8.77 + y - 0.45 * pow(y + 0.3, 5) + + d16_n2s2 = np.log10(self.N26584 / (self.S26717+self.S26731)) + self.dustcorrect(k_N2, k_S2, flux=False) + y = d16_n2s2 + 0.264 * self.logN2Ha + self.mds["D16"] = 8.77 + y# + 0.45 * pow(y + 0.3, 5) index = (y < -1.) self.mds["D16"][index] = float('NaN') index = (y > 0.5) From 13dfce1146e2820c9f337047a77d5dfbdaced789 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Thu, 18 May 2017 22:21:13 -0400 Subject: [PATCH 07/19] *C17_O3Hb: only use the high-metal branch. --- pyMCZ/metscales.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index 7ebf457..a13adac 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -950,16 +950,12 @@ def calcC17(self, allC17=False): coefs = np.array([C17_coefs['O3Hb']] * self.nm).T coefs[0] = coefs[0] - self.logO3Hb sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - if highZ is True: - indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * - (sols.imag == 0) * - (sols.real >= 8.3)).cumsum(1).cumsum(1) == 1 - self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real - elif highZ is False: - indx = ((sols.real >= 8.3) * (sols.real <= valid_upper) * - (sols.imag == 0) * - (sols.real <= 7.9)).cumsum(1).cumsum(1) == 1 - self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + + # C17 define this only for the high-metal branch. + indx = ((sols.real >= 8.2) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + # Require allC17 flag if we want everything. if not allC17: return From a22ed16bb1beba62343ceb515528828e56caa282 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Sun, 21 May 2017 09:17:20 -0400 Subject: [PATCH 08/19] *Added Pilyugin & Grebel scale. --- 00ToDo | 1 + pyMCZ/mcz.py | 2 +- pyMCZ/metallicity.py | 9 +- pyMCZ/metscales.py | 311 +++++++++++++++++++++++++------------------ 4 files changed, 192 insertions(+), 131 deletions(-) diff --git a/00ToDo b/00ToDo index e69de29..cb767a0 100644 --- a/00ToDo +++ b/00ToDo @@ -0,0 +1 @@ +# TODO Include estimates of systematic uncertainties in output? \ No newline at end of file diff --git a/pyMCZ/mcz.py b/pyMCZ/mcz.py index 1d1b2e9..603f121 100644 --- a/pyMCZ/mcz.py +++ b/pyMCZ/mcz.py @@ -739,7 +739,7 @@ def main(): parser.add_argument('--asciidistrib', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") parser.add_argument('--md', default='all', type=str, help='''metallicity diagnostic to calculate. default is 'all', options are: - D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, D13all, KD02, DP00 (deprecated), P01, D16, C17''') + D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, D13all, KD02, DP00 (deprecated), P01, D16, PG16, C17''') parser.add_argument('--multiproc', default=False, action='store_true', help=" multiprocess, with number of threads max(available cores-1, MAXPROCESSES)") args = parser.parse_args() diff --git a/pyMCZ/metallicity.py b/pyMCZ/metallicity.py index 544f299..bbafdb2 100644 --- a/pyMCZ/metallicity.py +++ b/pyMCZ/metallicity.py @@ -54,7 +54,8 @@ "KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) "KD02comb", "PM14", - "D16", + "D16", # Dopita (2016) + "PG16_R","PG16_S", # Pilyugin & Grebel (2016) "C17_O3O2", "C17_O3N2","C17_N2Ha","C17_O2Hb","C17_O3Hb","C17_R23"] # ,"KK04comb"] #'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' @@ -185,7 +186,7 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, mscales.calcPP04() #mscales.calcP05() - mscales.calcP10() + #mscales.calcP10() mscales.calcM08() mscales.calcM13() @@ -197,6 +198,7 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, mscales.calcKDcombined() mscales.calcD16() + mscales.calcPG16() mscales.calcC17() if 'DP00' in mds: @@ -270,6 +272,9 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, if 'D16' in mds: # Dopita+ 2016 mscales.calcD16() + if 'PG16' in mds: + # Pilyugin & Grebel 2016 + mscales.calcPG16() if 'C17' in mds: # Curti+ 2017 mscales.calcC17() diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index a13adac..8c04a2b 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -5,6 +5,8 @@ import numpy.polynomial.polynomial as nppoly from metallicity import get_keys, printsafemulti +import pdb + niter = 5 # number of iteations+1 for KD02 methods k_Ha = 2.535 # CCM Rv=3.1 @@ -873,134 +875,6 @@ def calcC01_ZR23(self): printsafemulti('''WARNING: needs [NII]6584, [SII]6717, [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, did you set them up with setOlines() and ?''', self.logf, self.nps) - def calcC17(self, allC17=False): - # Curti+ 2017 - # Monthly Notices Royal Astronomical Society, vol. 465, pp 1384-1400 - # Published in October 2016 - # - # Added 5/16/17 by jch, Santiago - import pdb - - valid_upper = 8.85 - valid_lower = 7.6 - - printsafemulti("calculating C17", self.logf, self.nps) - highZ = None - - if self.logO35007O2 is not None: - # C17 O3O2 - self.mds['C17_O3O2'] = np.zeros(self.nm) + float('NaN') - coefs = np.array([C17_coefs['O3O2']] * self.nm).T - coefs[0] = coefs[0] - self.logO35007O2 - - # sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - - # Find valid solutions - indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * - (sols.imag == 0)).cumsum(1).cumsum(1) == 1 - - # the two cumsum assure that if the condition for the ith element - # of indx is [False, False] then after the first cumsum(1) is [0,0] - # [False, True] is [0,1] - # [True, True] is [1,2] - # but (here is the kicker) [True, False] is [1,1]. - # Because i want only one solution - # (i'll settle for the first one occurring) [1,1] is ambiguous. - # The second cumsum(1) makes - # [0,0]->[0,0], [0,1]->[0,1], [1,2]->[1,3] and finally [1,1]->[1,2] - - # Set the results where appropriate - self.mds['C17_O3O2'][(indx.sum(1)) > 0] = sols[indx].real - - # Now use this to see if we're on the high-Z branch of R23 - highZ = np.median(self.logO35007O2) < 0 - - if self.logN2Ha is not None: - # C17 N2Ha - self.mds['C17_N2Ha'] = np.zeros(self.nm) + float('NaN') - - coefs = np.array([C17_coefs['N2Ha']] * self.nm).T - coefs[0] = coefs[0] - self.logN2Ha - - sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - - indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * - (sols.imag == 0)).cumsum(1).cumsum(1) == 1 - - self.mds['C17_N2Ha'][(indx.sum(1)) > 0] = sols[indx].real - if highZ is None: - highZ = np.median(self.logN2Ha) > -1.3 - - if self.hasO3 and self.hasN2: - # C17 O3N2 - self.mds['C17_O3N2'] = np.zeros(self.nm) + float('NaN') - coefs = np.array([C17_coefs['O3N2']] * self.nm).T - o3n2_value = self.logO3Hb - self.logN2Ha - coefs[0] = coefs[0] - o3n2_value - - sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) \ - * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 - self.mds['C17_O3N2'][(indx.sum(1)) > 0] = sols[indx].real - - if self.logO3Hb is not None: - # C17 O3Hb - self.mds['C17_O3Hb'] = np.zeros(self.nm) + float('NaN') - coefs = np.array([C17_coefs['O3Hb']] * self.nm).T - coefs[0] = coefs[0] - self.logO3Hb - sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - - # C17 define this only for the high-metal branch. - indx = ((sols.real >= 8.2) * (sols.real <= valid_upper) * - (sols.imag == 0)).cumsum(1).cumsum(1) == 1 - self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real - - # Require allC17 flag if we want everything. - if not allC17: - return - else: - printsafemulti("calculating other C17s", self.logf, self.nps) - - if self.logR23 is None: - printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) - self.calcR23() - if self.logR23 is None: - printsafemulti("WARNING: Cannot compute C17_R23 without R23", self.logf, self.nps) - else: - self.mds['C17_R23'] = np.zeros(self.nm) + float('NaN') - coefs = np.array([C17_coefs['R23']] * self.nm).T - coefs[0] = coefs[0] - self.logR23 - sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - if highZ is True: - indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * - (sols.imag == 0) * - (sols.real >= 8.4)).cumsum(1).cumsum(1) == 1 - self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real - elif highZ is False: - indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * - (sols.imag == 0) * - (sols.real < 8.4)).cumsum(1).cumsum(1) == 1 - self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real - - - if self.logO2Hb is not None: - self.mds['C17_O2Hb'] = np.zeros(self.nm) + float('NaN') - coefs = np.array([C17_coefs['O2Hb']] * self.nm).T - coefs[0] = coefs[0] - self.logO2Hb - sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - if highZ is True: - indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * - (sols.imag == 0) * - (sols.real >= 8.7)).cumsum(1).cumsum(1) == 1 - self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real - elif highZ is False: - indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * - (sols.imag == 0) * - (sols.real <= valid_upper)).cumsum(1).cumsum(1) == 1 - self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real - - #@profile def calcM91(self): # ## calculating McGaugh (1991) @@ -1447,6 +1321,9 @@ def calcPM14(self): print (self.mds) os.system('rm -r C13*dat') + +########## jch additions + #@profile def calcD16(self): #Dopita+ 2016 @@ -1466,3 +1343,181 @@ def calcD16(self): self.mds["D16"][index] = float('NaN') + def calcC17(self, allC17=False): + # Curti+ 2017 + # Monthly Notices Royal Astronomical Society, vol. 465, pp 1384-1400 + # Published in October 2016 + # + # Added 5/16/17 by jch, Santiago + import pdb + + valid_upper = 8.85 + valid_lower = 7.6 + + printsafemulti("calculating C17", self.logf, self.nps) + highZ = None + + if self.logO35007O2 is not None: + # C17 O3O2 + self.mds['C17_O3O2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3O2']] * self.nm).T + coefs[0] = coefs[0] - self.logO35007O2 + + # sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + # Find valid solutions + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + + # the two cumsum assure that if the condition for the ith element + # of indx is [False, False] then after the first cumsum(1) is [0,0] + # [False, True] is [0,1] + # [True, True] is [1,2] + # but (here is the kicker) [True, False] is [1,1]. + # Because i want only one solution + # (i'll settle for the first one occurring) [1,1] is ambiguous. + # The second cumsum(1) makes + # [0,0]->[0,0], [0,1]->[0,1], [1,2]->[1,3] and finally [1,1]->[1,2] + + # Set the results where appropriate + self.mds['C17_O3O2'][(indx.sum(1)) > 0] = sols[indx].real + + # Now use this to see if we're on the high-Z branch of R23 + highZ = np.median(self.logO35007O2) < 0 + + if self.logN2Ha is not None: + # C17 N2Ha + self.mds['C17_N2Ha'] = np.zeros(self.nm) + float('NaN') + + coefs = np.array([C17_coefs['N2Ha']] * self.nm).T + coefs[0] = coefs[0] - self.logN2Ha + + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + + self.mds['C17_N2Ha'][(indx.sum(1)) > 0] = sols[indx].real + if highZ is None: + highZ = np.median(self.logN2Ha) > -1.3 + + if self.hasO3 and self.hasN2: + # C17 O3N2 + self.mds['C17_O3N2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3N2']] * self.nm).T + o3n2_value = self.logO3Hb - self.logN2Ha + coefs[0] = coefs[0] - o3n2_value + + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) \ + * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3N2'][(indx.sum(1)) > 0] = sols[indx].real + + if self.logO3Hb is not None: + # C17 O3Hb + self.mds['C17_O3Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO3Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + # C17 define this only for the high-metal branch. + indx = ((sols.real >= 8.2) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + + # Require allC17 flag if we want everything. + if not allC17: + return + else: + printsafemulti("calculating other C17s", self.logf, self.nps) + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute C17_R23 without R23", self.logf, self.nps) + else: + self.mds['C17_R23'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['R23']] * self.nm).T + coefs[0] = coefs[0] - self.logR23 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real >= 8.4)).cumsum(1).cumsum(1) == 1 + self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real < 8.4)).cumsum(1).cumsum(1) == 1 + self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real + + + if self.logO2Hb is not None: + self.mds['C17_O2Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O2Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO2Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * + (sols.imag == 0) * + (sols.real >= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * + (sols.imag == 0) * + (sols.real <= valid_upper)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + + + + def calcPG16(self): + # Pilyugin & Grebel (2016, MNRAS, 457, 3678) + printsafemulti("calculating PG16", self.logf, self.nps) + + # Initialize the PG16 results + self.mds['PG16'] = np.zeros(self.nm) + float('NaN') + + # Make sure the lines are all here... + if not self.hasHa or not self.hasN2 or not self.hasO2 or not self.hasO3 or not self.hasS2: + printsafemulti("WARNING: need O2, O3, N2, S2, and Hb", + self.logf, self.nps) + return -1 + + pilR2 = self.logO2Hb + pilR3 = self.logO3Hb+np.log10(1.33) # Both members of doublet + pilR3R2 = (pilR3-pilR2) + + # Calculate S2/Hb = S2/Ha * Ha/Hb = S2/Ha*2.85 + pilS2 = np.log10((self.S26731 + self.S26731) / self.Ha * 2.85) + pilS2 += np.log10(self.dustcorrect(k_S2,k_Ha,flux=True)) + pilR3S2 = (pilR3-pilS2) + + # Calculate N2/Hb = N2/Ha * Ha/Hb = N2/Ha*2.85 + pilN2 = np.log10((self.N26584*1.33 / self.Ha)*2.85) + # pilN2 += np.log10(self.dustcorrect(k_N2,k_Hb,flux=True)) + + # Determine which branch we're on + highZ = (np.median(pilN2) >= -0.6) + print(np.median(pilN2)) + + # Calculate R = O2O3N2 abundances + if highZ == True: + print('PG16 high metal branch') + self.mds['PG16_R'] = 8.589+0.022*pilR3R2+0.399*pilN2 + self.mds['PG16_R'] += (-0.137+0.164*pilR3R2+0.589*pilN2)*pilR2 + else: + print('PG16 low metal branch') + self.mds['PG16_R'] = 7.932+0.944*pilR3R2+0.695*pilN2 + self.mds['PG16_R'] += (0.970-0.291*pilR3R2-0.019*pilN2)*pilR2 + + # Calculate S = S2O3N2 abundances + if highZ == True: + print('PG16 high metal branch') + self.mds['PG16_S'] = 8.424+0.030*pilR3S2+0.751*pilN2 + self.mds['PG16_S'] += (-0.349+0.182*pilR3S2+0.508*pilN2)*pilS2 + else: + print('PG16 low metal branch') + self.mds['PG16_S'] = 8.072+0.789*pilR3S2+0.726*pilN2 + self.mds['PG16_S'] += (1.069-0.17*pilR3S2+0.022*pilN2)*pilS2 From b0b5ee28ccc13d7129f80b5394da57dd19250334 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Sun, 21 May 2017 11:20:48 -0400 Subject: [PATCH 09/19] *Added Bresolin (2007) O2N2 scale. --- pyMCZ/metallicity.py | 11 +++++++---- pyMCZ/metscales.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/pyMCZ/metallicity.py b/pyMCZ/metallicity.py index bbafdb2..3a076e0 100644 --- a/pyMCZ/metallicity.py +++ b/pyMCZ/metallicity.py @@ -54,10 +54,10 @@ "KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) "KD02comb", "PM14", - "D16", # Dopita (2016) + "B07", # Bresolin (2007) N2O2 + "D16", # Dopita (2016) ONS "PG16_R","PG16_S", # Pilyugin & Grebel (2016) - "C17_O3O2", "C17_O3N2","C17_N2Ha","C17_O2Hb","C17_O3Hb","C17_R23"] # ,"KK04comb"] -#'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' + "C17_O3O2", "C17_O3N2","C17_N2Ha","C17_O2Hb","C17_O3Hb","C17_R23"] Zserr = ['PM14err'] # ,"KK04comb"] @@ -269,8 +269,11 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, mscales.calcKDcombined() # Modifications by jch [5/2017, Santiago] to include newer scales. + if 'B07' in mds: + # Bresolin 2007 N2O2 + mscales.calcB07() if 'D16' in mds: - # Dopita+ 2016 + # Dopita+ 2016 ONS mscales.calcD16() if 'PG16' in mds: # Pilyugin & Grebel 2016 diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index 8c04a2b..d5e9c10 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -1324,6 +1324,25 @@ def calcPM14(self): ########## jch additions + + #@profile + def calcB07(self): + #Bresolin 2007 - N2O2 + printsafemulti("calculating B07", self.logf, self.nps) + + if not self.hasHa or not self.hasN2 or not self.hasO2: + printsafemulti("WARNING: need N2, O2 ", + self.logf, self.nps) + return -1 + + y = 8.66 + 0.36*self.logN2O2 - 0.17*self.logN2O2**2 + self.mds["B07"] = y + index = (self.logN2O2 < -1.2) + self.mds["B07"][index] = float('NaN') + index = (self.logN2O2 > 0.6) + self.mds["B07"][index] = float('NaN') + + #@profile def calcD16(self): #Dopita+ 2016 From 4f942b7b45f7a5f55679c39daf89c918757063eb Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Wed, 31 May 2017 11:19:00 -0400 Subject: [PATCH 10/19] *New ToDo --- 00ToDo | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/00ToDo b/00ToDo index cb767a0..20b10d8 100644 --- a/00ToDo +++ b/00ToDo @@ -1 +1,2 @@ -# TODO Include estimates of systematic uncertainties in output? \ No newline at end of file +# TODO Include estimates of systematic uncertainties in output? +# TODO For n iterations extract only n results, excluding NaN runs. \ No newline at end of file From e74a653c929dd96aff69451ed0b20a612b4a3cf3 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Sat, 3 Jun 2017 09:19:33 -0400 Subject: [PATCH 11/19] *Minor update --- pyMCZ/metscales.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index d5e9c10..38c9534 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -400,15 +400,15 @@ def setSII(self, S26717, S26731, S39069, S39532): #@profile def calcEB_V(self): printsafemulti("calculating E(B-V)", self.logf, self.nps) - self.mds['E(B-V)'] = np.log10(2.86 * self.Hb / self.Ha) / (0.4 * (k_Ha - k_Hb)) # E(B-V) + a2b_intrinsic = 2.86 + self.mds['E(B-V)'] = np.log10(a2b_intrinsic * self.Hb / self.Ha) / (0.4 * (k_Ha - k_Hb)) # E(B-V) self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 #@profile def calcEB_Vblue(self): printsafemulti("calculating E(B-V)_blue", self.logf, self.nps) - gamma_beta_intrinsic = 2.145 - self.mds['E(B-V)'] = np.log10(gamma_beta_intrinsic - * self.Hg / self.Hb) / (0.4 * (k_Hb - k_Hg)) # E(B-V) + b2g_intrinsic = 2.145 + self.mds['E(B-V)'] = np.log10(b2g_intrinsic * self.Hg / self.Hb) / (0.4 * (k_Hb - k_Hg)) # E(B-V) self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 #@profile From 38b481240fa50a780f33f455f60a38891f3c7893 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Thu, 24 Aug 2017 15:44:38 -0300 Subject: [PATCH 12/19] * Formatting clean up. * Fixed bug in M08_O3N2 calculation: np.log() --> np.log10(). --- pyMCZ/metallicity.py | 1 + pyMCZ/metscales.py | 50 ++++++++++++++++++++++---------------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/pyMCZ/metallicity.py b/pyMCZ/metallicity.py index 3a076e0..610b07c 100644 --- a/pyMCZ/metallicity.py +++ b/pyMCZ/metallicity.py @@ -28,6 +28,7 @@ ##list of metallicity methods, in order calculated Zs = ["E(B-V)", # based on Halpha, Hbeta "E(B-V)blue", # based on Hbeta, Hgamma + "C(Hbeta)", "logR23", # Hbeta, [OII]3727, [OIII]5007, [OIII]4959 "D02", # Halpha, [NII]6584 diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index 38c9534..57808ac 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -51,7 +51,7 @@ O3O2_coef=np.zeros((4,8)) # coefficients from model grid fits O3O2c0=[-36.9772,-74.2814,-36.7948,-81.1880,-52.6367,-86.8674,-24.4044,49.4728] -O3O2_coef[:,0]=[-36.9772,10.2838,-0.957421,0.0328614] #z=0.05 +O3O2_coef[:,0]=[-36.9772,10.2838,-0.957421,0.0328614] #z=0.05 O3O2_coef[:,1]=[-74.2814,24.6206,-2.79194,0.110773] # z=0.1 O3O2_coef[:,2]=[-36.7948,10.0581,-0.914212,0.0300472] # z=0.2 O3O2_coef[:,3]=[-81.1880,27.5082,-3.19126,0.128252] # z=0.5 @@ -240,7 +240,7 @@ def printme(self, verbose=False): except (IndexError, TypeError): if verbose: print (self.mds[k]) - + def checkminimumreq(self, red_corr, ignoredust): if red_corr and not ignoredust: if not self.hasHa: @@ -336,7 +336,7 @@ def setOlines(self, O23727, O35007, O16300, O34959): if self.hasO2: self.logO2Hb = np.log10(self.O23727 / self.Hb) + \ self.dustcorrect(k_O2, k_Hb, flux=False) - # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) if self.hasO3: self.O3Hb = (self.O35007 / self.Hb) * \ self.dustcorrect(k_O35007, k_Hb, flux=True) @@ -361,13 +361,13 @@ def setNII(self, N26584): self.N26584 = N26584 self.hasN2 = True if self.hasHa: - self.logN2Ha = np.log10(self.N26584 / self.Ha) # +self.dustcorrect(k_N2,k_Ha,flux=True) + self.logN2Ha = np.log10(self.N26584 / self.Ha) # +self.dustcorrect(k_N2,k_Ha,flux=True) #lines are very close: no dust correction #Note: no dust correction cause the lies are really close! else: printsafemulti("WARNING: needs NII6584 and Ha to calculate NIIHa: did you run setHab()?", self.logf, self.nps) if self.hasS2 and self.hasS26731 and self.hasN2: - self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_S2,flux=True) + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_S2,flux=True) #lines are very close: no dust correction if self.hasO2 and self.hasN2: self.NII_OII = np.log10(self.N26584 / self.O23727 * self.dustcorrect(k_N2, k_O2, flux=True)) @@ -392,7 +392,7 @@ def setSII(self, S26717, S26731, S39069, S39532): self.hasS39532 = True if self.hasS2: if self.hasN2 and self.NII_SII is None and self.hasS26731: - self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_O2,flux=True) + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_O2,flux=True) #lines are very close: no dust correction if self.hasO3 and self.OIII_SII is None and self.hasS26731: self.OIII_SII = np.log10(self.O35007 / (self.S26717 + self.S26731) * self.dustcorrect(k_O3, k_S2, flux=True)) @@ -428,11 +428,11 @@ def calcNIIOII(self): if not self.hasN2O2 or np.mean(self.logN2O2) < -1.2: try: - printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods should only be used for log([NII]6564/[OII]3727) > -1.2, + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods should only be used for log([NII]6564/[OII]3727) > -1.2, the mean log([NII]6564/[OII]3727)= %f''' % np.mean(self.logN2O2), self.logf, self.nps) except TypeError: - printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods - should only be used for log([NII]6564/[OII]3727) >-1.2, + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods + should only be used for log([NII]6564/[OII]3727) >-1.2, the mean log([NII]6564/[OII]3727)= %s''' % self.logN2O2, self.logf, self.nps) if not self.hasN2O2: @@ -581,7 +581,7 @@ def calcpyqz(self, plot=False, allD13=False): show_plot=plot, n_plot=False, \ save_plot=False, verbose=False)[0].T - + if self.NII_OII is not None and allD13: if self.OIII_SII is not None: if oldpyqz: @@ -777,7 +777,7 @@ def calcP10(self): #independent on physical conditions #The Physics and Dynamics of Planetary Nebulae # By Grigor A. Gurzadyan - P10logN2 = (np.log((self.N26584 * 1.33) / self.Hb) + P10logN2 = (np.log((self.N26584 * 1.33) / self.Hb) + self.dustcorrect(k_N2, k_Hb, flux=False)) if self.hasS2 and self.hasS26731: @@ -861,7 +861,7 @@ def calcC01_ZR23(self): self.mds['C01_R23'][self.O2O35007 >= 0.8] = np.log10(3.96e-4 * x3[self.O2O35007 >= 0.8] ** (-0.46)) + 12.0 else: - printsafemulti('''WARNING: need [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, + printsafemulti('''WARNING: need [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, did you set them up with setOlines()?''', self.logf, self.nps) # Charlot 01 calibration: (case A) based on [N2]/[SII]## @@ -872,7 +872,7 @@ def calcC01_ZR23(self): if self.hasN2S2 and self.hasO3 and self.hasO2 and self.hasO3Hb: self.mds['C01_N2S2'] = np.log10(5.09e-4 * (x2 ** 0.17) * ((self.N2S2 / 0.85) ** 1.17)) + 12 else: - printsafemulti('''WARNING: needs [NII]6584, [SII]6717, [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, + printsafemulti('''WARNING: needs [NII]6584, [SII]6717, [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, did you set them up with setOlines() and ?''', self.logf, self.nps) #@profile @@ -923,7 +923,7 @@ def calcM13(self): printsafemulti("calculating M13", self.logf, self.nps) if not self.hasHa or not self.hasN2: - printsafemulti("WARNING: need O3, N2, Ha and Hb, " + + printsafemulti("WARNING: need O3, N2, Ha and Hb, " + "or at least N2 and Ha", self.logf, self.nps) return -1 else: @@ -935,18 +935,18 @@ def calcM13(self): e2 = np.random.normal(0, 0.012, self.nm) O3N2 = self.logO3Hb - self.logN2Ha self.mds["M13_O3N2"] = 8.533 + e1 - (0.214 + e1) * O3N2 - index = (O3N2 > 1.7) + index = (O3N2 > 1.7) self.mds["M13_O3N2"][index] = float('NaN') index = (O3N2 < -1.1) self.mds["M13_O3N2"][index] = float('NaN') - + #for i in range(len(self.mds["M13_O3N2"])): #print ("here O3N2",#self.O35007[i], self.Hb[i], - #self.O3Hb[i], + #self.O3Hb[i], #self.logO3Hb[i], self.logN2Ha[i], - # O3N2[i], + # O3N2[i], # self.mds["M13_O3N2"][i]) - + #@profile def calcM08(self, allM08=False): #Maiolino+ 2008 @@ -1033,7 +1033,7 @@ def calcM08(self, allM08=False): if self.hasO3 and self.hasN2: self.mds['M08_O3N2'] = np.zeros(self.nm) + float('NaN') coefs = np.array([M08_coefs['O3N2']] * self.nm).T - coefs[0] = coefs[0] - np.log(self.O35007 / self.N26584) \ + coefs[0] = coefs[0] - np.log10(self.O35007 / self.N26584) \ + self.dustcorrect(k_O35007, k_N2, flux=False) sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 indx = ((sols.real >= 7.1) * (sols.real <= 9.4)\ @@ -1278,7 +1278,7 @@ def calcPM14(self): ratios[2] = self.O3Hb elif self.hasO3: ratios[2] = ((self.O35007 / self.Hb) - * self.dustcorrect(k_O35007, k_Hb, flux=True)) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + * self.dustcorrect(k_O35007, k_Hb, flux=True)) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) else: ratios[2] = np.zeros(self.nm) @@ -1295,7 +1295,7 @@ def calcPM14(self): else: ratios[4] = np.zeros(self.nm) - + for ni in range(self.nm): fin_hii_chi.write('%f %f %f %f %f\n' % (ratios[0][ni], ratios[1][ni], ratios[2][ni], ratios[3][ni], ratios[4][ni])) fin_hii_chi.close() @@ -1357,11 +1357,11 @@ def calcD16(self): y = d16_n2s2 + 0.264 * self.logN2Ha self.mds["D16"] = 8.77 + y# + 0.45 * pow(y + 0.3, 5) index = (y < -1.) - self.mds["D16"][index] = float('NaN') + self.mds["D16"][index] = float('NaN') index = (y > 0.5) - self.mds["D16"][index] = float('NaN') + self.mds["D16"][index] = float('NaN') + - def calcC17(self, allC17=False): # Curti+ 2017 # Monthly Notices Royal Astronomical Society, vol. 465, pp 1384-1400 From cf3c0c747fdd87ffe54a1e99d28f915541187335 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Fri, 25 Aug 2017 14:40:39 -0300 Subject: [PATCH 13/19] *Changed code for Bresolin+ (2007): now B07_N2O2 (was B07). --- pyMCZ/mcz.py | 30 +++++++++++++++--------------- pyMCZ/metallicity.py | 25 ++++++++++++------------- pyMCZ/metscales.py | 6 +++--- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/pyMCZ/mcz.py b/pyMCZ/mcz.py index 603f121..8fa30cd 100644 --- a/pyMCZ/mcz.py +++ b/pyMCZ/mcz.py @@ -35,7 +35,7 @@ # Define the version of __version__ = '1.3.1' -NM0 = 0 # setting this to say N>0 starts the calculation at measurement N. +NM0 = 0 # setting this to say N>0 starts the calculation at measurement N. #this is only for exploratory purposes as the code bugs out before #plotting and printing the results @@ -151,7 +151,7 @@ def readfile(filename): newheader.append(h) header = newheader - + formats = ['S10'] + ['f'] * (len(header) - 1) if 'flag' in header: @@ -188,8 +188,8 @@ def ingest_data(filename, path): ,1:].shape + (-1, ))[: ,1:]) if snr[~np.isnan(snr)].any() < 3: - raw_input('''WARNING: signal to noise ratio smaller than 3 - for at least some lines! You should only use SNR>3 + raw_input('''WARNING: signal to noise ratio smaller than 3 + for at least some lines! You should only use SNR>3 measurements (return to proceed)''') except (IndexError, TypeError): pass @@ -319,8 +319,8 @@ def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False try: from sklearn.neighbors import KernelDensity except ImportError: - print ('''sklearn is not available, - thus we cannot compute kernel density. + print ('''sklearn is not available, + thus we cannot compute kernel density. switching to bayesian blocks''') BINMODE = 'bb' if BINMODE == 'kd': @@ -511,7 +511,7 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, raw_input("missing pickled file for this simulation: name, nsample.\nrun the MonteCarlo? Ctr-C to exit, Return to continue?\n") RUNSIM = True else: - pklfile = open(picklefile, 'rb') + pklfile = open(picklefile, 'rb') res = pickle.load(pklfile) if RUNSIM: @@ -525,7 +525,7 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, sample = [errordistrib(dargs, newnsample) for i in range(NM0, nm)] if sample == -1: return -1 - + ###Start calculation### ## the flux to be feed to the calculation will be ## flux + error*i @@ -723,10 +723,10 @@ def main(): parser.add_argument('name', metavar='', type=str, help="the SN file name (root of the _min,_max file names") parser.add_argument('nsample', metavar='N', type=int, help="number of iterations, minimum 100 (or 1 for no MC sampling)") parser.add_argument('--clobber', default=False, action='store_true', help="replace existing output") - parser.add_argument('--binmode', default='k', type=str, choices=['d', 's', 'k', 't', 'bb', 'kd'], help='''method to determine bin size - {d: Duanes formula, s: n^1/2, t: 2*n**1/3(default), k: Knuth's rule, + parser.add_argument('--binmode', default='k', type=str, choices=['d', 's', 'k', 't', 'bb', 'kd'], help='''method to determine bin size + {d: Duanes formula, s: n^1/2, t: 2*n**1/3(default), k: Knuth's rule, bb: Bayesian blocks, kd: Kernel Density}''') - parser.add_argument('--path', default=None, type=str, help='''input/output path (must contain the input _max.txt and + parser.add_argument('--path', default=None, type=str, help='''input/output path (must contain the input _max.txt and _min.txt files in a subdirectory sn_data)''') parser.add_argument('--unpickle', default=False, action='store_true', help="read the pickled realization instead of making a new one") @@ -737,8 +737,8 @@ def main(): parser.add_argument('--noplot', default=False, action='store_true', help=" don't plot individual distributions (default is to plot all distributions)") parser.add_argument('--asciiout', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") parser.add_argument('--asciidistrib', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") - parser.add_argument('--md', default='all', type=str, help='''metallicity diagnostic to calculate. - default is 'all', options are: + parser.add_argument('--md', default='all', type=str, help='''metallicity diagnostic to calculate. + default is 'all', options are: D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, D13all, KD02, DP00 (deprecated), P01, D16, PG16, C17''') parser.add_argument('--multiproc', default=False, action='store_true', help=" multiprocess, with number of threads max(available cores-1, MAXPROCESSES)") args = parser.parse_args() @@ -763,8 +763,8 @@ def main(): if args.path: path = args.path else: - assert (os.getenv("MCMetdata")), ''' the _max, _min (and _med) data must live in a folder named sn_data. - pass a path to the sn_data folder, or set up the environmental variable + assert (os.getenv("MCMetdata")), ''' the _max, _min (and _med) data must live in a folder named sn_data. + pass a path to the sn_data folder, or set up the environmental variable MCMetdata pointing to the path where sn_data lives ''' path = os.getenv("MCMetdata") assert(os.path.isdir(path)), "pass a path or set up the environmental variable MCMetdata pointing to the path where the _min _max _med files live" diff --git a/pyMCZ/metallicity.py b/pyMCZ/metallicity.py index 610b07c..e9f1031 100644 --- a/pyMCZ/metallicity.py +++ b/pyMCZ/metallicity.py @@ -1,6 +1,6 @@ ############################################################################## -## Calculates oxygen abundance (here called metalicity) based on strong emission lines, -## based on code originally written in IDL by Lisa Kewley (Kewley & Ellison 2008). Outputs +## Calculates oxygen abundance (here called metalicity) based on strong emission lines, +## based on code originally written in IDL by Lisa Kewley (Kewley & Ellison 2008). Outputs ## oxygen abundance in many different diagnostics (see Bianco et al. 2016). ## ##new calculation based on the most recent version of the .pro file. @@ -24,7 +24,7 @@ DROPNEGATIVES = False # set to true if no negative flux measurements should be allowed. all negative flux measurements are dropped if DROPNEGATIVES: FIXNEGATIVES = False - + ##list of metallicity methods, in order calculated Zs = ["E(B-V)", # based on Halpha, Hbeta "E(B-V)blue", # based on Hbeta, Hgamma @@ -55,7 +55,7 @@ "KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) "KD02comb", "PM14", - "B07", # Bresolin (2007) N2O2 + "B07_N2O2", # Bresolin (2007) N2O2 "D16", # Dopita (2016) ONS "PG16_R","PG16_S", # Pilyugin & Grebel (2016) "C17_O3O2", "C17_O3N2","C17_N2Ha","C17_O2Hb","C17_O3Hb","C17_R23"] @@ -84,7 +84,7 @@ def printsafemulti(string, logf, nps): ############################################################################## -##fz_roots function as used in the IDL code +##fz_roots function as used in the IDL code ############################################################################## #@profile @@ -127,11 +127,11 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, elif dust_corr and not IGNOREDUST: if nps > 1: - print ('''WARNING: reddening correction cannot be done + print ('''WARNING: reddening correction cannot be done without both H_alpha and H_beta measurement!!''') else: - response = raw_input('''WARNING: reddening correction cannot be done without both H_alpha and H_beta measurement!! + response = raw_input('''WARNING: reddening correction cannot be done without both H_alpha and H_beta measurement!! Continuing without reddening correction? [Y/n]\n''').lower() assert(not (response.startswith('n'))), "please fix the input file to include Ha and Hb measurements" @@ -177,7 +177,7 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, sys.path.insert(0, cmd_folder) mscales.calcpyqz() else: - printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: set path to pyqz as environmental variable : export PYQZ_DIR="your/path/where/pyqz/resides/ in bash, for example, if you want this scale. \n''', logf, nps) @@ -220,8 +220,8 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, #using the commented line below instead mscales.calcpyqz(plot=disp) else: - printsafemulti('''WARNING: CANNOT CALCULATE pyqz: - set path to pyqz as environmental variable + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable PYQZ_DIR if you want this scale. ''', logf, nps) if 'D13all' in mds: @@ -235,8 +235,8 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, #using the commented line below instead mscales.calcpyqz(plot=disp, allD13=True) else: - printsafemulti('''WARNING: CANNOT CALCULATE pyqz: - set path to pyqz as environmental variable + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable PYQZ_DIR if you want this scale. ''', logf, nps) if 'PM14' in mds: @@ -285,4 +285,3 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, if 'C17all' in mds: # Curti+ 2017 mscales.calcC17(allC17=True) - diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index 57808ac..0622ce7 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -1336,11 +1336,11 @@ def calcB07(self): return -1 y = 8.66 + 0.36*self.logN2O2 - 0.17*self.logN2O2**2 - self.mds["B07"] = y + self.mds["B07_N2O2"] = y index = (self.logN2O2 < -1.2) - self.mds["B07"][index] = float('NaN') + self.mds["B07_N2O2"][index] = float('NaN') index = (self.logN2O2 > 0.6) - self.mds["B07"][index] = float('NaN') + self.mds["B07_N2O2"][index] = float('NaN') #@profile From 94d91e60a1ef03a4009e274384f1d35af848a09e Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Fri, 25 Aug 2017 15:25:56 -0300 Subject: [PATCH 14/19] * Added PMC09_N2Ha method. * Changed B07 --> B07_N2Ha * Allowed KK04_N2Ha to be called on its own. --- pyMCZ/metallicity.py | 12 +++++++++++- pyMCZ/metallicity.pyc | Bin 6178 -> 6631 bytes pyMCZ/metscales.py | 12 ++++++++++++ pyMCZ/metscales.pyc | Bin 38001 -> 43807 bytes 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pyMCZ/metallicity.py b/pyMCZ/metallicity.py index e9f1031..c91cc17 100644 --- a/pyMCZ/metallicity.py +++ b/pyMCZ/metallicity.py @@ -55,6 +55,7 @@ "KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) "KD02comb", "PM14", + "PMC09_N2Ha", "B07_N2O2", # Bresolin (2007) N2O2 "D16", # Dopita (2016) ONS "PG16_R","PG16_S", # Pilyugin & Grebel (2016) @@ -270,7 +271,7 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, mscales.calcKDcombined() # Modifications by jch [5/2017, Santiago] to include newer scales. - if 'B07' in mds: + if 'B07_N2O2' in mds: # Bresolin 2007 N2O2 mscales.calcB07() if 'D16' in mds: @@ -285,3 +286,12 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, if 'C17all' in mds: # Curti+ 2017 mscales.calcC17(allC17=True) + if 'PMC09_N2Ha' in mds: + # Perez-Montero & Contini (2009) + mscales.calcPMC09() + + if 'KK04_N2Ha' in mds: + mscales.calcKD02_N2O2() + mscales.calcKK04_N2Ha() + else if 'KD02_N2O2' in mds: + mscales.calcKD02_N2O2() diff --git a/pyMCZ/metallicity.pyc b/pyMCZ/metallicity.pyc index 9eefe0460a25b50d0c7f529f94ef6afc54c4fd6e..d4dbc8884e3b17fd5d0ad0f7a6293d1eefce4f76 100644 GIT binary patch delta 1376 zcmY+EO>7fa5XWb{UfZ#8zStx*K~+hEK z?#o=8^(GnbSo!YK0#tt3!MubV+rAIsxh=s8gU$gX#vA0p)`7KxIMYVA+G& zL!i#UGQw;UR1a(e%+`YH)kYntyf*4#F$Rl~>Rrr`&4*v(1Wnl=g?eIp8}@4p zf2D<*=aoAEt9@`60L3b3YaKuxhdUOmMPTFauz<4xNEwi@1@s4?(|`^QQUORC@WA#v zVKoh^9quA*++&x%kGJ9`d5Mqd_qZK;U{fjKw4O98oXC4CFhbwPkK!wi??#8NCO)j+ zG7;T6=N9cGj^Q#rOte)7^bT%qMdUmglk$EGNbaKEa;7XN{K;3zTs@DmybB z(mD&J@`7}yw4T_~p}MEU;RLkrxFOG_znoM9x3phl;-uTDv8gk0lV^&3B6%8X`YC#u z&CNz6tNDvMJC5pskh*cBTSDl6L09i>9 A=>Px# delta 918 zcmY+DUr19?9LImZ-R-VzS9e#rwltfowrl48sOfYS5(Z%mZ3gKG2kpwtNSn?FX%V4| zo}~H_Q4a|{_!z+`qIxUop&o>KjvfLb2!tMjAnKf>=R)_~`@KJYpYQLS-?^N-(fK6N zsQj#R_jrP11=zkK?A;FtbNb!J^;5+N}R@_yrT^@_^dN?2_rB)Cs+w7`w00>>@fA@O?wk{0Nc;Hv){~9zjacOv_(>~WKe)(7U-4WrjuT~jWFM6pUgcH=Hr$b z*hxs!X#HC(Qj<51Av)tt;{zVk3vUcVwQt@@#CMeOg&Cajt>G;zPSNttXX-(AdW;h^ zzw0Bm)lTldfEcHhh9q6{YqZ`lN*RBgZu^@Qe!`ynqn?Y*pN(;8rw#v0=Xn;+{RUdy zQ}rlrn;W+t>I+;_P(ykZ*gI0<_8BH+2b1afD_0k?gFKh#GR9?`N!i9EGn^gbiD54M qzsVY0qFh?J#JSv|XmC7mj)gi~gbkaDa<~*)33h2N#bp=6tKdIM%DVLc diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index 0622ce7..9e2c112 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -1324,6 +1324,18 @@ def calcPM14(self): ########## jch additions + def calcPMC09(self): + #Perez-Montero & Contini (2009) - N2Ha + printsafemulti("calculating PMC09", self.logf, self.nps) + + if not self.hasHa or not self.hasN2 or not self.hasHa: + printsafemulti("WARNING: need N2, Ha ", + self.logf, self.nps) + return -1 + PMC09_N2 = lambda x: 9.07+0.79*x + + y = 9.07 + 0.78*self.logN2Ha + self.mds["PMC09_N2Ha"] = y #@profile def calcB07(self): diff --git a/pyMCZ/metscales.pyc b/pyMCZ/metscales.pyc index 10c470bf500c798ea17ef8ab38554bd22f6f2130..d380ef16489b1db8cc8ed6b1557f3835c9856177 100644 GIT binary patch delta 8023 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 P8F)cC@HYp4-LC%y2`0iP delta 2943 zcma)8dr(y875~oNyYB^-1(t;kuqfanNC4%r2w{yNqJZg=1lcMN+2t)DkcY1jqCzwz zvN@7CY11TQYa-(`NpB~%)G^UX+D=DXr%tuaWTNRb?H}o6=u}7ZY|pu@S!a?s-kts3 zbHDF==lst3&i8ofqV%8FB&X)TQKlJ7Qgeq0KRSAQ1&EQ-kh7^l7JiEeXoWvY1a!h5 zEdqMsj}ZZb@LNT|DEzS^U=n_t2*!$_O(X`)BEcUg0v6$q7lA0@PY{7<;kS!mqG)%7 z&hB<5iNM_=P#^;NVy_T;glP6Bi-05=-G$2E;2pqjc@buRjE(fC_}b#H04whnZ#RIx637vMSNFYea>Uckqec7h8- zIYpYzky-j$hHG9-jIv$?5-{DfRA(3E+ty0o z*m|SfvL;d7#v09bQm<7oBPtDtC?62&2UWuYJz`6q&PX^^>_%6q1(Mj0Hktfn;C-HW zqMX{|ymJ>kaET`Dg{p1A%LOfGhVa(RIAnJHmWEv0@k5Ud<2@@ShfKk(i2IKG;9-y3 za8(`itQ;Pb3JYmTBd=F^WF}HdQe3y=iydX@dZI$U2e5r*qUzdJYLxF1fh0U#mIvo? zzHG&VcbwI9a(+P@4%vP)cX_?vdGR0`mh~W=^cl_<6hKL2*kG0?Z(hdB3>*1Emq%? zh3{1CQ#$Xd1_P6;`;L(43t^?VMfWu?z&SkP%}CWa zA%Rw)1zb`Ly;H#nxzaanc-iY&^?kjIA%=e68WU-|XPqIb@gPCC(e^wuYb4p3WGA-;?f{oo^-p3tP3@7og_xHt5kT@z63=RZGd@k;o zRGJ)v3^H}QIKQ5u_cYx%o| z;TH@FgMs1K5fcT=L&|~9FAT(GV(^EM8sVZQ%t-3>`K7HLgTc-b-z5HT$V+j0w=IHq zv18kYTgl{?zOHD8>p7~%(L%6BEG4ne{W$YbUiKu5RL8>i5yL!znF781!)34_X_fhCphdw6W;x^PfsyJ8_nsoV8a`8zzNjjzsAC*~_2 zo6n%mnCd9v;}-KcJzip^V^sJg24J{|m&Su|zf$qUa~ksvHu5FI75s2t4#;Slc!+{} zd}2IV4NAj9BCh_ysz!89=B7o$i&TsnkIL>d?3_#s2X%JR22;xANrTQB#pd{g_r)++ z@%q!*22db8j)kL})(OJp|te!sTRFy=$gY}JkM=IwIljsv-wFr zK((TFC(%izs#rvy{QWBWaw@S#cNA@v-gRAdCHq&keSE&M%XI0;P{o0CZ`ZlA{CrT^ zwOoBJRQ7NzqZ)#$g5oZE(KG+HZ!f*VUso#KW-J(w!^ILUCD;S$6943`>brrLj-3*u&7v@TtJD*|w-WlC5B&Gsy;a{B$-0 G;{Oi~D6}>J From 067c3b6b93a5fd458deae47baff34174ab5ccb9c Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Thu, 27 Sep 2018 10:55:38 -0400 Subject: [PATCH 15/19] Clean-up, consistency in naming scales - Changed M13_N2 --> M13_N2Ha. - Calculate N2/O2 from (N2/Ha)*(2.86*Hb/O2). --- pyMCZ/metallicity.py | 4 ++-- pyMCZ/metscales.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyMCZ/metallicity.py b/pyMCZ/metallicity.py index c91cc17..d5d2e79 100644 --- a/pyMCZ/metallicity.py +++ b/pyMCZ/metallicity.py @@ -44,7 +44,7 @@ "DP00", # S23 available but deprecated "P10_ONS", "P10_ON", "M08_R23", "M08_N2Ha", "M08_O3Hb", "M08_O2Hb", "M08_O3O2", "M08_O3N2", - "M13_O3N2", "M13_N2", + "M13_O3N2", "M13_N2Ha", "D13_N2S2_O3S2", "D13_N2S2_O3Hb", "D13_N2S2_O3O2", "D13_N2O2_O3S2", "D13_N2O2_O3Hb", "D13_N2O2_O3O2", @@ -293,5 +293,5 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, if 'KK04_N2Ha' in mds: mscales.calcKD02_N2O2() mscales.calcKK04_N2Ha() - else if 'KD02_N2O2' in mds: + elif 'KD02_N2O2' in mds: mscales.calcKD02_N2O2() diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index 9e2c112..6a57478 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -424,6 +424,7 @@ def calcNIISII(self): def calcNIIOII(self): if self.hasN2 and self.hasO2: self.logN2O2 = np.log10(self.N26584 / self.O23727) + self.dustcorrect(k_N2, k_O2, flux=False) + self.hasN2O2 = True if not self.hasN2O2 or np.mean(self.logN2O2) < -1.2: @@ -929,7 +930,7 @@ def calcM13(self): else: e1 = np.random.normal(0, 0.027, self.nm) e2 = np.random.normal(0, 0.024, self.nm) - self.mds["M13_N2"] = 8.743 + e1 + (0.462 + e2) * self.logN2Ha + self.mds["M13_N2Ha"] = 8.743 + e1 + (0.462 + e2) * self.logN2Ha if self.hasHb and self.hasO3: e1 = np.random.normal(0, 0.012, self.nm) e2 = np.random.normal(0, 0.012, self.nm) @@ -1347,6 +1348,9 @@ def calcB07(self): self.logf, self.nps) return -1 + # Use [N2/Ha * Ha/Hb] / [O2/Hb] + x = (self.logN2Ha+np.log10(2.86)) - self.logO2Hb + y = 8.66 + 0.36*self.logN2O2 - 0.17*self.logN2O2**2 self.mds["B07_N2O2"] = y index = (self.logN2O2 < -1.2) From 0a658630c4991b782f18e17d8dd2c288ef30d7be Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Thu, 27 Sep 2018 11:16:28 -0400 Subject: [PATCH 16/19] Cleanup --- .DS_Store | Bin 6148 -> 6148 bytes .gitignore | 1 + build/lib/pyMCZ/__init__.py | 7 + build/lib/pyMCZ/mcz.py | 190 ++-- build/lib/pyMCZ/metallicity.py | 100 +- build/lib/pyMCZ/metscales.py | 475 +++++++-- build/lib/pyMCZ/pylabsetup.py | 41 + build/scripts-2.7/pylabsetup.py | 41 + build/scripts-3.6/mcz.py | 791 +++++++++++++++ build/scripts-3.6/metallicity.py | 297 ++++++ build/scripts-3.6/metscales.py | 1558 ++++++++++++++++++++++++++++++ build/scripts-3.6/pylabsetup.py | 41 + pyMCZ/metallicity.pyc | Bin 6631 -> 0 bytes pyMCZ/metscales.pyc | Bin 43807 -> 0 bytes pyMCZ/pylabsetup.pyc | Bin 1224 -> 0 bytes 15 files changed, 3350 insertions(+), 192 deletions(-) create mode 100644 build/lib/pyMCZ/__init__.py create mode 100644 build/lib/pyMCZ/pylabsetup.py create mode 100755 build/scripts-2.7/pylabsetup.py create mode 100755 build/scripts-3.6/mcz.py create mode 100755 build/scripts-3.6/metallicity.py create mode 100755 build/scripts-3.6/metscales.py create mode 100755 build/scripts-3.6/pylabsetup.py delete mode 100644 pyMCZ/metallicity.pyc delete mode 100644 pyMCZ/metscales.pyc delete mode 100644 pyMCZ/pylabsetup.pyc diff --git a/.DS_Store b/.DS_Store index af3cea9ec33c0384a5c237efd88bf064c37d34f1..9a7b632b7baafc1777da3ad84cab91a2e4768600 100644 GIT binary patch literal 6148 zcmeHKu};H441JfjQY%2}gcu{ljOfk~l`t}OV*(l!Dz$2dc3{g#FeC8~d;l{8Tf4Eq z$QLky?K_e?HC+)xwq(E5KIiFPsyGM0^cPtV=mKbyGe#W>HO6)JHH%z^n+@M%oc9L9 z>14LHQWJkw0e*H(93saEbN~58@5gt3Z8A;MY%w1KtIqwtY^Us-OtX!(*-iDIh(4GO zE#ey?!!<5_M&Ru+tX7=M*VVRgdc75EUDfA>IkLQ0SP=6VS-{&m*cINgTJf$LFJF$= ztCqX1sRq2QC(Ap-EzWR)^ME&c_!Qoo6NdcPK zZ0#+J8dU*RKouwz;P*pJ&X_nXEXu2cf*b(|o0Jx#Yx%|_j5$G=I4mquhRoPjLfdNG z5yRMa%ExY9;;^u2+hK&6?&Fw^JE0h1r+h5SVG@fPRRL8XRKT4Qx;+0+H=qB*CjF%f zr~>~=0nJ5FLjNB+x`kLxuJPNU5M)B};{nkm#sD`GG{(C?o|`J_QXY$O*Uz5)DXn za13VbA@O9L79lhv*>B_L@w1<@b{qgO?OCq{&;WoM7Q$p3t4I(}r4$uz*+n!o#?7#G z)-6WkQcrDA2h@ST=m5RD3GBlVu3;L!zm>OsC*(#&p7&H$6pyEGJ};K;XI&uO7p1VEN)?p^AIb? z&FoXM5&a-Hq8+e87gEyQgn~39%Jso~m5*qP{D`{Xg?`DDa z@!48h9G$f(mJ2Kt)X#c!DOmAw>>lVSrvDXOAeYhuV#a7aVhe`;5O6kVr4Ib61K;}V BwgLbE diff --git a/.gitignore b/.gitignore index eddf617..7fab245 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ *~ +*.pyc diff --git a/build/lib/pyMCZ/__init__.py b/build/lib/pyMCZ/__init__.py new file mode 100644 index 0000000..e17c7f3 --- /dev/null +++ b/build/lib/pyMCZ/__init__.py @@ -0,0 +1,7 @@ +import os + +p = os.path.abspath('./') +if not os.path.exists(p + '/output'): + os.makedirs(p + '/output') + +__all__ = ["mcz", "metallicity", "metscales", "testcompleteness"] diff --git a/build/lib/pyMCZ/mcz.py b/build/lib/pyMCZ/mcz.py index 3b39ed6..8fa30cd 100644 --- a/build/lib/pyMCZ/mcz.py +++ b/build/lib/pyMCZ/mcz.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import print_function import os import sys import argparse @@ -34,14 +35,14 @@ # Define the version of __version__ = '1.3.1' -NM0 = 0 # setting this to say N>0 starts the calculation at measurement N. +NM0 = 0 # setting this to say N>0 starts the calculation at measurement N. #this is only for exploratory purposes as the code bugs out before #plotting and printing the results PROFILING = True PROFILING = False -alllines = ['[OII]3727', 'Hb', '[OIII]4959', '[OIII]5007', '[OI]6300', 'Ha', '[NII]6584', '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532'] +alllines = ['[OII]3727', 'Hg','Hb', '[OIII]4959', '[OIII]5007', '[OI]6300', 'Ha', '[NII]6584', '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532'] morelines = ['E(B-V)', 'dE(B-V)', 'scale_blue', 'd scale_blue'] MAXPROCESSES = 10 @@ -61,7 +62,9 @@ RUNSIM = True NOPLOT = False BINMODE = 'k' -binning = {'bb': 'Bayesian blocks', 'k': "Knuth's rule", 'd': "Doane's formula", 's': r'$\sqrt{N}$', 't': r'$2 N^{1/3}$', 'kd': 'Kernel Density'} +binning = {'bb': 'Bayesian blocks', 'k': "Knuth's rule", + 'd': "Doane's formula", 's': r'$\sqrt{N}$', + 't': r'$2 N^{1/3}$', 'kd': 'Kernel Density'} MP = False @@ -127,7 +130,12 @@ def readfile(filename): l0 = f.readline().replace(' ', '') l1 = f.readline().split() if l0.startswith('#') or l0.startswith(';'): - header = l0.strip().replace(";", '').replace("#", '').split(',') + # Modified to account for non-standard header (\t instead of , separated) jch + temp_header = l0.strip().replace(";", '').replace("#", '') + if temp_header.find(',') == -1: + header = temp_header.split('\t') + else: + header = temp_header.split(',') header[0] = header[0].replace(' ', '') header = header[:len(l1)] else: @@ -135,7 +143,17 @@ def readfile(filename): header = ['galnum'] + alllines + ['flag'] + morelines header = header[:len(l1)] + usecols = [] + newheader = [] + for i,h in enumerate(header): + if h in alllines + ['galnum']: + usecols.append(i) + newheader.append(h) + + header = newheader + formats = ['S10'] + ['f'] * (len(header) - 1) + if 'flag' in header: findex = header.index('flag') formats[findex] = 'S10' @@ -143,10 +161,9 @@ def readfile(filename): bstruct = {} for i, k in enumerate(header): bstruct[k] = [i, 0] - b = np.loadtxt(filename, skiprows=noheader, dtype={'names': header, 'formats': formats}, comments=';') + b = np.loadtxt(filename, skiprows=noheader, dtype={'names': header, 'formats': formats}, comments=';',usecols=usecols) if b.size == 1: b = np.atleast_1d(b) - for i, k in enumerate(header): if not k == 'flag' and is_number(b[k][0]): bstruct[k][1] = np.count_nonzero(b[k]) + sum(np.isnan(b[k])) @@ -171,24 +188,23 @@ def ingest_data(filename, path): ,1:].shape + (-1, ))[: ,1:]) if snr[~np.isnan(snr)].any() < 3: - raw_input('''WARNING: signal to noise ratio smaller than 3 - for at least some lines! You should only use SNR>3 + raw_input('''WARNING: signal to noise ratio smaller than 3 + for at least some lines! You should only use SNR>3 measurements (return to proceed)''') except (IndexError, TypeError): pass return (filename, meas, err, nm, path, (bsmeas, bserr)) - def input_data(filename, path): p = os.path.join(path, "input") assert os.path.isdir(p), "bad data directory %s" % p if os.path.isfile(os.path.join(p, filename + '_err.txt')): if os.path.isfile(os.path.join(p, filename + '_meas.txt')): return ingest_data(filename, path=p) - print "Unable to find _meas and _err files ", filename + '_meas.txt', filename + '_err.txt', "in directory ", p + print ("Unable to find _meas and _err files ", filename + + '_meas.txt', filename + '_err.txt', "in directory ", p) return -1 - ############################################################################## ##returns a random distribution. In the deployed version of the code this is a gaussian distribution, but the user can include her or his distribution. ##return a random sample of size n @@ -198,11 +214,11 @@ def errordistrib(distrargs, n, distype='normal'): try: mu, sigma = distrargs except: - print "for normal distribution distargs must be a 2 element tuple" + print ("for normal distribution distargs must be a 2 element tuple") return -1 return np.random.normal(mu, sigma, n) else: - print "distribution not supported" + print ("distribution not supported") return -1 @@ -265,11 +281,12 @@ def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False kde = None if not n > 0: if verbose: - print "data must be an actual distribution (n>0 elements!, %s)" % Zs + print ("data must be an actual distribution (n>0 elements!, %s)" % Zs) return "-1,-1,_1", [], kde if data.shape[0] <= 0 or np.sum(data) <= 0: - print '{0:15} {1:20} {2:>13d} {3:>7d} {4:>7d} '.format(snname, Zs, -1, -1, -1) + print ('{0:15} {1:20} {2:>13d} {3:>7d} {4:>7d} '\ + .format(snname, Zs, -1, -1, -1)) return "-1, -1, -1", [], kde try: ###find C.I.### @@ -283,14 +300,17 @@ def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False maxleft = median - 1 maxright = median + 1 if round(right, 6) == round(left, 6) and round(left, 6) == round(median, 6): - print '{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(snname, Zs, median, 0, 0) + print ('{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(snname, Zs, median, 0, 0)) if reserr: - print '+/- {0:.3f}'.format(reserr) + print ('+/- {0:.3f}'.format(reserr)) return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde # "-1,-1,-1",[] ###print out the confidence interval### - print '{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f}'.format(snname, Zs, round(median, 3), round(median - left, 3), round(right - median, 3)) + print ('{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f}'.format(snname, + Zs, round(median, 3), + round(median - left, 3), + round(right - median, 3))) if reserr: - print '+/- {0:.3f}'.format(reserr) + print ('+/- {0:.3f}'.format(reserr)) alpha = 1.0 ######histogram###### @@ -299,9 +319,9 @@ def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False try: from sklearn.neighbors import KernelDensity except ImportError: - print '''sklearn is not available, - thus we cannot compute kernel density. - switching to bayesian blocks''' + print ('''sklearn is not available, + thus we cannot compute kernel density. + switching to bayesian blocks''') BINMODE = 'bb' if BINMODE == 'kd': ##bw is chosen according to Silverman 1986 @@ -338,8 +358,8 @@ def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False if not NOPLOT: plt.clf() except ImportError: - print "bayesian blocks for histogram requires astroML to be installed" - print "defaulting to Knuth's rule " + print ("bayesian blocks for histogram requires astroML to be installed") + print ("defaulting to Knuth's rule ") ##otherwise numbin, bm = getbinsize(data.shape[0], data) distrib = np.histogram(data, bins=int(numbin), density=True) @@ -382,7 +402,8 @@ def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False plt.annotate(st, xy=(0.13, 0.6), xycoords='axes fraction', size=fs, fontweight='bold') st = '%s ' % (Zs.replace('_', ' ')) plt.annotate(st, xy=(0.61, 0.93), xycoords='axes fraction', fontsize=fs, fontweight='bold') - st = 'measurement %d of %d\n %s\nmedian: %.3f\n16th Percentile: %.3f\n84th Percentile: %.3f' % (i + 1, nmeas, measnames[i], round(median, 3), round(left, 3), round(right, 3)) + st = 'measurement %d of %d\n %s\nmedian: %.3f\n16th Percentile: %.3f\n84th Percentile: %.3f' \ + % (i + 1, nmeas, measnames[i], round(median, 3), round(left, 3), round(right, 3)) plt.annotate(st, xy=(0.61, 0.65), xycoords='axes fraction', fontsize=fs) effectiven = len(data[~np.isnan(data)]) if effectiven: @@ -408,23 +429,29 @@ def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False except (OverflowError, AttributeError, ValueError): if VERBOSE: - print data - print name, 'had infinities (or something in plotting went wrong)' + print (data) + print (name, 'had infinities (or something in plotting went wrong)') return "-1, -1,-1", [], None -def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr, verbose, res, scales, nps, logf))): +def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr,dust_blue, + verbose, res, scales, nps, logf))): logf = sys.stdout - print >> logf, "\n\nreading in measurements ", i + 1 + logf.write("\n\nreading in measurements %d\n"%(i + 1)) fluxi = {} # np.zeros((len(bss[0]),nm),float) for k in bss[0].iterkeys(): - print >> logf, '{0:15} '.format(k), - print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) + logf.write('{0:15} '.format(k)) + logf.write('{0:0.2} +/- {1:0.2}\n'.format(flux[k][i], err[k][i])) + #print >> logf, '{0:15} '.format(k), + #print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) fluxi[k] = flux[k][i] * np.ones(len(sample[i])) + err[k][i] * sample[i] warnings.filterwarnings("ignore") - success = metallicity.calculation(scales[i], fluxi, nm, mds, nps, logf, disp=disp, dust_corr=dust_corr, verbose=verbose) + success = metallicity.calculation(scales[i], fluxi, nm, mds, nps, logf, + disp=disp, verbose=verbose, + dust_corr=dust_corr,dust_blue=dust_blue) if success == -1: - print >> logf, "MINIMUM REQUIRED LINES: '[OII]3727','[OIII]5007','[NII]6584','[SII]6717'" + logf.write("MINIMUM REQUIRED LINES: '[OII]3727','[OIII]5007','[NII]6584','[SII]6717'\n") + #print >> logf, "MINIMUM REQUIRED LINES: '[OII]3727','[OIII]5007','[NII]6584','[SII]6717'" for key in scales[i].mds.iterkeys(): if key in res.keys(): @@ -447,7 +474,8 @@ def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr, verbose, res, sc ## mode 't' calculates this based on 2*n**1/3 ############################################################################## #@profile -def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickle=False, dust_corr=True, verbose=False, fs=24): +def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, + unpickle=False, dust_corr=True, dust_blue=False, verbose=False, fs=24): global RUNSIM # ,BINMODE#,NOPLOT assert(len(flux[0]) == len(err[0])), "flux and err must be same dimensions" assert(len(flux['galnum']) == nm), "flux and err must be of declaired size" @@ -472,7 +500,7 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl binp = os.path.join(p, 'output', '%s' % name) picklefile = os.path.join(binp, '%s_n%d.pkl' % (name, nsample)) if VERBOSE: - print "output files will be stored in ", binp + print ("output files will be stored in ", binp) if not CLOBBER and not NOPLOT: for key in Zs: for i in range(NM0, nm): @@ -497,12 +525,13 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl sample = [errordistrib(dargs, newnsample) for i in range(NM0, nm)] if sample == -1: return -1 + ###Start calculation### ## the flux to be feed to the calculation will be ## flux + error*i ## where i is the sampled gaussian if VERBOSE: - print "Starting iteration" + print ("Starting iteration") #initialize the dictionary res = {} @@ -526,17 +555,17 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl if multiproc and nps > 1: scales = [ms.diagnostics(newnsample, logf, nps) for i in range(nm)] - print >> logf, "\n\n\nrunning on %d threads\n\n\n" % nps - second_args = [sample, flux, err, nm, bss, mds, VERBOSE, dust_corr, VERBOSE, res, scales, nps, logf] + logf.write( "\n\n\nrunning on %d threads\n\n\n" % nps) + #print >> logf, "\n\n\nrunning on %d threads\n\n\n" % nps + second_args = [sample, flux, err, nm, bss, mds, VERBOSE, dust_corr, dust_blue, VERBOSE, res, scales, nps, logf] pool = mpc.Pool(processes=nps) # depends on available cores rr = pool.map(calc, itertools.izip(range(NM0, nm), itertools.repeat(second_args))) # for i in range(nm): result[i] = f(i, second_args) - for ri, r in enumerate(rr): - for kk in r.iterkeys(): - res[kk][ri] = r[kk][ri] + for ri, r in enumerate(rr): for kk in r.iterkeys(): res[kk][ri] = r[kk][ri] + pool.close() # not optimal! but easy pool.join() for key in scales[i].mds.iterkeys(): @@ -548,29 +577,38 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl res[key][i] = [float('NaN')] * len(sample) if VERBOSE: - print "Iteration Complete" + print ("Iteration Complete") else: #looping over nm spectra for i in range(NM0, nm): scales = ms.diagnostics(newnsample, logf, nps) - print >> logf, "\n\n measurements ", i + 1 + logf.write("\n\n measurements %d\n"%(i + 1)) + #print >> logf, "\n\n measurements ", i + 1 fluxi = {} - for k in bss[0].iterkeys(): - print >> logf, '{0:15} '.format(k), - print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) + #shuffling the distribution each time + np.random.shuffle(sample[i]) + + logf.write('{0:15} '.format(k)) + logf.write('{0:0.2} +/- {1:0.2}\n'.format(flux[k][i], + err[k][i])) + #print >> logf, '{0:15} '.format(k), + #print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) fluxi[k] = flux[k][i] * np.ones(len(sample[i])) + err[k][i] * sample[i] warnings.filterwarnings("ignore") - print >> logf, "" + logf.write("\n") + #print >> logf, "" - success = metallicity.calculation(scales, fluxi, nm, mds, 1, logf, disp=VERBOSE, dust_corr=dust_corr, verbose=VERBOSE) + success = metallicity.calculation(scales, fluxi, nm, mds, 1, logf, + disp=VERBOSE, verbose=VERBOSE, + dust_corr=dust_corr, dust_blue=dust_blue) if success == -1: - print "MINIMUM REQUIRED LINES: [OII]3727 & [OIII]5007, or [NII]6584, and Ha & Hb if you want dereddening" + print ("MINIMUM REQUIRED LINES: [OII]3727 & [OIII]5007, or [NII]6584, and Ha & Hb if you want dereddening") #continue for key in scales.mds.iterkeys(): - if not key in Zs: + if key not in Zs: continue res[key][i] = scales.mds[key] if res[key][i] is None: @@ -582,7 +620,7 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl if key in Zs: res[key] = np.array(res[key]).T if VERBOSE: - print "Iteration Complete" + print ("Iteration Complete") #"WE CAN PICKLE THIS!" #pickle this realization @@ -594,11 +632,13 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl if 'Time' not in findfont(FontProperties()): fs = 20 if VERBOSE: - print "FONT: %s, %d" % (findfont(FontProperties()), fs) + print ("FONT: %s, %d" % (findfont(FontProperties()), fs)) ###Bin the results and save### - print "\n\n" - print '{0:15} {1:20} {2:>13} -{3:>5} +{4:>5} {5:11} {6:>7}'.format("SN", "diagnostic", "metallicity", "34%", "34%", "(sample size:", '%d)' % nsample) + print ("\n\n") + print ('{0:15} {1:20} {2:>13} -{3:>5} +{4:>5} {5:11} {6:>7}'\ + .format("SN", "diagnostic", "metallicity", "34%", "34%", + "(sample size:", '%d)' % nsample)) for i in range(NM0, nm): if ASCIIOUTPUT: fi = open(os.path.join(binp, '%s_n%d_%d.txt' % (name, nsample, i + 1)), 'w') @@ -606,12 +646,13 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl boxlabels = [] datas = [] - print "\n\nmeasurement %d : %s-------------------------------------------------------------" % (i + 1, flux[i]['galnum']) + print ("\n\nmeasurement ", + "%d/%d : %s-------------------------------------------------------------" % (i + 1, nm, flux[i]['galnum'])) for key in Zs: if nsample == -1: try: if ~np.isnan(res[key][i][0]): - print '{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(name + ' %d' % (i + 1), key, res[key][i][0], 0, 0) + print ('{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(name + ' %d' % (i + 1), key, res[key][i][0], 0, 0)) except IndexError: pass else: @@ -625,7 +666,7 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl writer.writerow(res[key][: ,i]) if 'PM14' in key: - print reserr['PM14err'] + print (reserr['PM14err']) reserr = np.sqrt(~np.nansum(reserr['PM14err'][: ,i] ** 2)) sh, data, kde = savehist(res[key][: @@ -639,7 +680,7 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl if BINMODE == 'kd' and not NOPICKLE: pickleKDEfile = os.path.join(binp + '/%s_n%d_%s_%d_KDE.pkl' % (name, nsample, key, i + 1)) if VERBOSE: - print "KDE files will be stored in ", pickleKDEfile + print ("KDE files will be stored in ", pickleKDEfile) pickle.dump(kde, open(pickleKDEfile, 'wb')) except (IndexError, TypeError): pass @@ -662,7 +703,7 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl for median in bp['medians']: median.set(color='k', linewidth=2) for flier in bp['fliers']: - flier.set(marker='o', color='#7570b3', alpha=0.4) + flier.set(marker='o', color='#7570b3', alpha=0.4, mfc='#7570b3') plt.title("measurement %d: %s" % (i + 1, flux[i]['galnum'])) plt.xticks(range(1, len(boxlabels) + 1), boxlabels, rotation=90, fontsize=fs - 5) plt.fill_between(range(1, len(boxlabels) + 1), [8.76] * len(boxlabels), [8.69] * len(boxlabels), facecolor='black', alpha=0.3) @@ -673,31 +714,32 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl if ASCIIOUTPUT: fi.close() if VERBOSE: - print "uncertainty calculation complete" + print ("uncertainty calculation complete") #del datas def main(): parser = argparse.ArgumentParser() parser.add_argument('name', metavar='', type=str, help="the SN file name (root of the _min,_max file names") - parser.add_argument('nsample', metavar='N', type=int, help="number of iterations, minimum 100 (or 0 for no MC sampling)") + parser.add_argument('nsample', metavar='N', type=int, help="number of iterations, minimum 100 (or 1 for no MC sampling)") parser.add_argument('--clobber', default=False, action='store_true', help="replace existing output") - parser.add_argument('--binmode', default='k', type=str, choices=['d', 's', 'k', 't', 'bb', 'kd'], help='''method to determine bin size - {d: Duanes formula, s: n^1/2, t: 2*n**1/3(default), k: Knuth's rule, + parser.add_argument('--binmode', default='k', type=str, choices=['d', 's', 'k', 't', 'bb', 'kd'], help='''method to determine bin size + {d: Duanes formula, s: n^1/2, t: 2*n**1/3(default), k: Knuth's rule, bb: Bayesian blocks, kd: Kernel Density}''') - parser.add_argument('--path', default=None, type=str, help='''input/output path (must contain the input _max.txt and + parser.add_argument('--path', default=None, type=str, help='''input/output path (must contain the input _max.txt and _min.txt files in a subdirectory sn_data)''') parser.add_argument('--unpickle', default=False, action='store_true', help="read the pickled realization instead of making a new one") parser.add_argument('--verbose', default=False, action='store_true', help="verbose mode") parser.add_argument('--log', default=None, type=str, help="log file, if not passed defaults to standard output") parser.add_argument('--nodust', default=False, action='store_true', help=" don't do dust corrections (default is to do it)") + parser.add_argument('--bluedust', default=False, action='store_true', help=" use Hg/Hb for dust correction (default is Hb/Ha)") parser.add_argument('--noplot', default=False, action='store_true', help=" don't plot individual distributions (default is to plot all distributions)") parser.add_argument('--asciiout', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") parser.add_argument('--asciidistrib', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") - parser.add_argument('--md', default='all', type=str, help='''metallicity diagnostic to calculate. - default is 'all', options are: - D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, KD02, DP00 (deprecated), P01''') + parser.add_argument('--md', default='all', type=str, help='''metallicity diagnostic to calculate. + default is 'all', options are: + D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, D13all, KD02, DP00 (deprecated), P01, D16, PG16, C17''') parser.add_argument('--multiproc', default=False, action='store_true', help=" multiprocess, with number of threads max(available cores-1, MAXPROCESSES)") args = parser.parse_args() @@ -721,23 +763,25 @@ def main(): if args.path: path = args.path else: - assert (os.getenv("MCMetdata")), ''' the _max, _min (and _med) data must live in a folder named sn_data. - pass a path to the sn_data folder, or set up the environmental variable + assert (os.getenv("MCMetdata")), ''' the _max, _min (and _med) data must live in a folder named sn_data. + pass a path to the sn_data folder, or set up the environmental variable MCMetdata pointing to the path where sn_data lives ''' path = os.getenv("MCMetdata") assert(os.path.isdir(path)), "pass a path or set up the environmental variable MCMetdata pointing to the path where the _min _max _med files live" if args.nsample == 1: - print "CALCULATING METALLICITY WITHOUT GENERATING MC DISTRIBUTIONS" + print ("CALCULATING METALLICITY WITHOUT GENERATING MC DISTRIBUTIONS") if args.nsample == 1 or args.nsample >= 10: fi = input_data(args.name, path=path) if fi != -1: logf = smart_open(args.log) - run(fi, args.nsample, args.md, args.multiproc, logf, unpickle=args.unpickle, dust_corr=(not args.nodust), verbose=VERBOSE) + run(fi, args.nsample, args.md, args.multiproc, logf, + unpickle=args.unpickle, + dust_corr=(not args.nodust), dust_blue=(args.bluedust), + verbose=VERBOSE) if args.log: logf.close() else: - print "nsample must be at least 100" - + print ("nsample must be at least 100") if __name__ == "__main__": if PROFILING: diff --git a/build/lib/pyMCZ/metallicity.py b/build/lib/pyMCZ/metallicity.py index afc7ec1..d5d2e79 100644 --- a/build/lib/pyMCZ/metallicity.py +++ b/build/lib/pyMCZ/metallicity.py @@ -1,6 +1,6 @@ ############################################################################## -## Calculates oxygen abundance (here called metalicity) based on strong emission lines, -## based on code originally written in IDL by Lisa Kewley (Kewley & Ellison 2008). Outputs +## Calculates oxygen abundance (here called metalicity) based on strong emission lines, +## based on code originally written in IDL by Lisa Kewley (Kewley & Ellison 2008). Outputs ## oxygen abundance in many different diagnostics (see Bianco et al. 2016). ## ##new calculation based on the most recent version of the .pro file. @@ -13,6 +13,7 @@ ## disp - if True prints the results, default False ############################################################################## +from __future__ import print_function import sys import os import numpy as np @@ -20,9 +21,14 @@ IGNOREDUST = False MP = True FIXNEGATIVES = True # set to true if no negative flux measurements should be allowed. all negative flux measurements are set to 0 +DROPNEGATIVES = False # set to true if no negative flux measurements should be allowed. all negative flux measurements are dropped +if DROPNEGATIVES: + FIXNEGATIVES = False ##list of metallicity methods, in order calculated Zs = ["E(B-V)", # based on Halpha, Hbeta + "E(B-V)blue", # based on Hbeta, Hgamma + "C(Hbeta)", "logR23", # Hbeta, [OII]3727, [OIII]5007, [OIII]4959 "D02", # Halpha, [NII]6584 @@ -38,7 +44,7 @@ "DP00", # S23 available but deprecated "P10_ONS", "P10_ON", "M08_R23", "M08_N2Ha", "M08_O3Hb", "M08_O2Hb", "M08_O3O2", "M08_O3N2", - "M13_O3N2", "M13_N2", + "M13_O3N2", "M13_N2Ha", "D13_N2S2_O3S2", "D13_N2S2_O3Hb", "D13_N2S2_O3O2", "D13_N2O2_O3S2", "D13_N2O2_O3Hb", "D13_N2O2_O3O2", @@ -48,8 +54,13 @@ "KK04_N2Ha", # Halpha, Hbeta, [OII]3727, [NII]6584 "KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) "KD02comb", - "PM14"] # ,"KK04comb"] -#'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' + "PM14", + "PMC09_N2Ha", + "B07_N2O2", # Bresolin (2007) N2O2 + "D16", # Dopita (2016) ONS + "PG16_R","PG16_S", # Pilyugin & Grebel (2016) + "C17_O3O2", "C17_O3N2","C17_N2Ha","C17_O2Hb","C17_O3Hb","C17_R23"] + Zserr = ['PM14err'] # ,"KK04comb"] #'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' @@ -66,52 +77,62 @@ def get_errkeys(): def printsafemulti(string, logf, nps): #this is needed because dealing with a log output with multiprocessing #is painful. but it introduces a bunch of if checks. - #if anyone has a better solution please let me know! if nps == 1: - print >> logf, string + logf.write(string + "\n") + #print >> logf, string else: - print string + print (string) ############################################################################## -##fz_roots function as used in the IDL code FED:reference the code here! +##fz_roots function as used in the IDL code ############################################################################## #@profile -def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=False, verbose=False): +def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, + dust_blue=False, disp=False, verbose=False): global IGNOREDUST mscales.setdustcorrect() raw_lines = {} raw_lines['[OIII]5007'] = np.array([float('NaN')]) raw_lines['Hb'] = np.array([float('NaN')]) + raw_lines['Hg'] = np.array([float('NaN')]) raw_lines['Hz'] = np.array([float('NaN')]) for k in measured.iterkeys(): #kills all non-finite terms measured[k][~(np.isfinite(measured[k][:]))] = 0.0 + if FIXNEGATIVES: measured[k][measured[k] < 0] = 0.0 - raw_lines[k] = measured[k] + if DROPNEGATIVES: + measured[k][measured[k] < 0] = np.nan + + raw_lines[k] = measured[k] ######we trust QM better than we trust the measurement of the [OIII]4959 ######which is typical low S/N so we set it to [OIII]5007/3. ######change this only if youre spectra are very high SNR raw_lines['[OIII]4959'] = raw_lines['[OIII]5007'] / 3. raw_lines['[OIII]49595007'] = raw_lines['[OIII]4959'] + raw_lines['[OIII]5007'] - mscales.setHab(raw_lines['Ha'], raw_lines['Hb']) + mscales.setHabg(raw_lines['Ha'], raw_lines['Hb'], raw_lines['Hg']) #if Ha or Hb is zero, cannot do red correction - if dust_corr and mscales.hasHa and mscales.hasHb: + if dust_corr and mscales.hasHa and mscales.hasHb and not dust_blue: with np.errstate(invalid='ignore'): mscales.calcEB_V() + elif dust_blue and mscales.hasHg and mscales.hasHb: + with np.errstate(invalid='ignore'): + mscales.calcEB_Vblue() + print('----- Using Hg/Hb for E(B-V)_blue estimate. -----') elif dust_corr and not IGNOREDUST: if nps > 1: - print '''WARNING: reddening correction cannot be done - without both H_alpha and H_beta measurement!!''' + print ('''WARNING: reddening correction cannot be done + without both H_alpha and H_beta measurement!!''') else: - response = raw_input('''WARNING: reddening correction cannot be done without both H_alpha and H_beta measurement!! + response = raw_input('''WARNING: reddening correction cannot be done without both H_alpha and H_beta measurement!! Continuing without reddening correction? [Y/n]\n''').lower() assert(not (response.startswith('n'))), "please fix the input file to include Ha and Hb measurements" @@ -148,7 +169,7 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=Fal #mscales.printme() if verbose: - print "calculating metallicity diagnostic scales: ", mds + print ("calculating metallicity diagnostic scales: ", mds) if 'all' in mds: mscales.calcD02() if os.getenv("PYQZ_DIR"): @@ -157,9 +178,9 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=Fal sys.path.insert(0, cmd_folder) mscales.calcpyqz() else: - printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: set path to pyqz as environmental variable : - export PYQZ_DIR="your/path/where/pyqz/resides/ in bash, for example, if you want this scale. ''', logf, nps) + export PYQZ_DIR="your/path/where/pyqz/resides/ in bash, for example, if you want this scale. \n''', logf, nps) mscales.calcZ94() mscales.calcM91() @@ -167,7 +188,7 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=Fal mscales.calcPP04() #mscales.calcP05() - mscales.calcP10() + #mscales.calcP10() mscales.calcM08() mscales.calcM13() @@ -178,6 +199,10 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=Fal mscales.calcKK04_R23() mscales.calcKDcombined() + mscales.calcD16() + mscales.calcPG16() + mscales.calcC17() + if 'DP00' in mds: mscales.calcDP00() if 'P01' in mds: @@ -196,8 +221,8 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=Fal #using the commented line below instead mscales.calcpyqz(plot=disp) else: - printsafemulti('''WARNING: CANNOT CALCULATE pyqz: - set path to pyqz as environmental variable + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable PYQZ_DIR if you want this scale. ''', logf, nps) if 'D13all' in mds: @@ -211,8 +236,9 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=Fal #using the commented line below instead mscales.calcpyqz(plot=disp, allD13=True) else: - printsafemulti('''set path to pyqz as environmental variable -PYQZ_DIR if you want this scale. ''', logf, nps) + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable + PYQZ_DIR if you want this scale. ''', logf, nps) if 'PM14' in mds: if os.getenv("HIICHI_DIR"): @@ -243,3 +269,29 @@ def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, disp=Fal mscales.calcKK04_N2Ha() mscales.calcKK04_R23() mscales.calcKDcombined() + + # Modifications by jch [5/2017, Santiago] to include newer scales. + if 'B07_N2O2' in mds: + # Bresolin 2007 N2O2 + mscales.calcB07() + if 'D16' in mds: + # Dopita+ 2016 ONS + mscales.calcD16() + if 'PG16' in mds: + # Pilyugin & Grebel 2016 + mscales.calcPG16() + if 'C17' in mds: + # Curti+ 2017 + mscales.calcC17() + if 'C17all' in mds: + # Curti+ 2017 + mscales.calcC17(allC17=True) + if 'PMC09_N2Ha' in mds: + # Perez-Montero & Contini (2009) + mscales.calcPMC09() + + if 'KK04_N2Ha' in mds: + mscales.calcKD02_N2O2() + mscales.calcKK04_N2Ha() + elif 'KD02_N2O2' in mds: + mscales.calcKD02_N2O2() diff --git a/build/lib/pyMCZ/metscales.py b/build/lib/pyMCZ/metscales.py index bb059ed..6a57478 100644 --- a/build/lib/pyMCZ/metscales.py +++ b/build/lib/pyMCZ/metscales.py @@ -1,13 +1,17 @@ +from __future__ import print_function import numpy as np #import sys import scipy.stats as stats import numpy.polynomial.polynomial as nppoly from metallicity import get_keys, printsafemulti +import pdb + niter = 5 # number of iteations+1 for KD02 methods k_Ha = 2.535 # CCM Rv=3.1 k_Hb = 3.609 # CCM Rv=3.1 +k_Hg = 4.400 # CCM Rv=3.1 #k_O1=2.661 # CCM Rv=3.1 k_O2 = 4.771 # CCM Rv=3.1 @@ -47,7 +51,7 @@ O3O2_coef=np.zeros((4,8)) # coefficients from model grid fits O3O2c0=[-36.9772,-74.2814,-36.7948,-81.1880,-52.6367,-86.8674,-24.4044,49.4728] -O3O2_coef[:,0]=[-36.9772,10.2838,-0.957421,0.0328614] #z=0.05 +O3O2_coef[:,0]=[-36.9772,10.2838,-0.957421,0.0328614] #z=0.05 O3O2_coef[:,1]=[-74.2814,24.6206,-2.79194,0.110773] # z=0.1 O3O2_coef[:,2]=[-36.7948,10.0581,-0.914212,0.0300472] # z=0.2 O3O2_coef[:,3]=[-81.1880,27.5082,-3.19126,0.128252] # z=0.5 @@ -64,6 +68,13 @@ 'O2Hb': [0.5603, 0.0450, -1.8017, -1.8434, -0.6549], 'O3N2': [0.4520, -2.6096, -0.7170, 0.1347]} +C17_coefs = {'N2Ha': [-0.489, 1.513,-2.554,-5.293,-2.867], + 'O3O2': [-0.691,-2.944,-1.308], + 'O3N2': [0.281,-4.765,-2.268], + 'O2Hb': [0.418,-0.961,-3.505,-1.949], + 'O3Hb': [-0.277,-3.549,-3.593,-0.981], + 'R23': [0.527,-1.569,-1.652,-0.421]} + #this is to check the Maiolino coefficients and find the split maximum of the cirves with degeneracy ''' import pylab as pl @@ -87,8 +98,9 @@ def __init__(self, num, logf, nps): self.nm = num self.Ha = None self.Hb = None + self.Hg = None - self.hasHa, self.hasHb = False, False + self.hasHa, self.hasHb, self.hasHg = False, False, False self.hasO2, self.hasO3 = False, False self.hasS2, self.hasN2 = False, False @@ -162,73 +174,73 @@ def __init__(self, num, logf, nps): def printme(self, verbose=False): try: - print "\nHa", np.mean(self.Ha) + print ("\nHa", np.mean(self.Ha)) if verbose: - print self.Ha + print (self.Ha) except (IndexError, TypeError): pass try: - print "\nHb", np.mean(self.Hb) + print ("\nHb", np.mean(self.Hb)) if verbose: - print self.Hb + print (self.Hb) except (IndexError, TypeError): pass try: - print "\nO2", np.mean(self.O23727) + print ("\nO2", np.mean(self.O23727)) if verbose: - print self.O23727 + print (self.O23727) except (IndexError, TypeError): pass try: - print "\nO3", np.mean(self.O35007) + print ("\nO3", np.mean(self.O35007)) if verbose: - print self.O35007 + print (self.O35007) except (IndexError, TypeError): pass try: - print "\nO34959", np.mean(self.O34959) + print ("\nO34959", np.mean(self.O34959)) if verbose: - print self.O34959 + print (self.O34959) except (IndexError, TypeError): pass try: - print "\nZ94", np.mean(self.mds['Z94']) + print ("\nZ94", np.mean(self.mds['Z94'])) if verbose: - print self.mds['Z94'] + print (self.mds['Z94']) except (IndexError, TypeError): pass try: - print "\nR23", np.mean(self.R23) + print ("\nR23", np.mean(self.R23)) if verbose: - print self.R23 + print (self.R23) except (IndexError, TypeError): pass try: - print "\nlog(R23)", np.mean(self.logR23) + print ("\nlog(R23)", np.mean(self.logR23)) if verbose: - print self.logR23 + print (self.logR23) except (TypeError, IndexError): pass try: - print "\nlog([NII][OII])", stats.nanmean(self.logN2O2) + print ("\nlog([NII][OII])", stats.nanmean(self.logN2O2)) if verbose: - print self.logN2O2 + print (self.logN2O2) except (TypeError, IndexError): pass try: - print "\nlog([OIII][OII])", stats.nanmean(self.logO3O2) + print ("\nlog([OIII][OII])", stats.nanmean(self.logO3O2)) if verbose: - print self.logO3O2 + print (self.logO3O2) except (TypeError, IndexError): pass for k in self.mds.iterkeys(): - print "\n", k, + print ("\n", k) try: - print stats.nanmean(self.mds[k]), np.stdev(self.mds[k]) + print (stats.nanmean(self.mds[k]), np.stdev(self.mds[k])) except (IndexError, TypeError): if verbose: - print self.mds[k] - + print (self.mds[k]) + def checkminimumreq(self, red_corr, ignoredust): if red_corr and not ignoredust: if not self.hasHa: @@ -280,13 +292,16 @@ def dustcorrect(self, l1, l2, flux=False): return 0 return 1.0 - def setHab(self, Ha, Hb): + def setHabg(self, Ha, Hb, Hg): self.Ha = Ha self.Hb = Hb + self.Hg = Hg if sum(self.Ha > 0): self.hasHa = True if sum(self.Hb > 0): self.hasHb = True + if sum(self.Hg > 0): + self.hasHg = True def setOlines(self, O23727, O35007, O16300, O34959): self.O23727 = O23727 @@ -307,46 +322,55 @@ def setOlines(self, O23727, O35007, O16300, O34959): #self.logO2O35007Hb=np.log10((self.O23727+self.O35007)/self.Hb) # ratios for other diagnostics - slightly different ratios needed - if self.hasHb: - self.logO2O35007Hb = np.log10((self.O23727 / self.Hb) * self.dustcorrect(k_O2, k_Hb, flux=True)) + \ - (self.O35007 / self.Hb) * self.dustcorrect(k_O35007, k_Hb, flux=True) + # commenting since it is not used + #if self.hasHb: + # self.logO2O35007Hb = np.log10( + # (self.O23727 / self.Hb) * self.dustcorrect(k_O2, + # k_Hb, flux=True) + # + (self.O35007 / self.Hb) * self.dustcorrect(k_O35007, + # k_Hb, flux=True)) else: printsafemulti("WARNING: needs O lines and and Ha/b: did you run setHab()?", self.logf, self.nps) if self.hasHb: if self.hasO2: - self.logO2Hb = np.log10(self.O23727 / self.Hb) + self.dustcorrect(k_O2, k_Hb) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + self.logO2Hb = np.log10(self.O23727 / self.Hb) + \ + self.dustcorrect(k_O2, k_Hb, flux=False) + # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) if self.hasO3: - self.O3Hb = (self.O35007 / self.Hb) + self.dustcorrect(k_O35007, k_Hb, flux=True) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + self.O3Hb = (self.O35007 / self.Hb) * \ + self.dustcorrect(k_O35007, k_Hb, flux=True) + # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) self.logO3Hb = np.log10(self.O3Hb) self.hasO3Hb = True if self.hasO2 and self.hasO3: - self.OIII_OII = np.log10(self.O35007 / self.O23727 + self.dustcorrect(k_O35007, k_O2, flux=True)) + self.OIII_OII = np.log10(self.O35007 / self.O23727 * self.dustcorrect(k_O35007, k_O2, flux=True)) if O34959 is not None and sum(O34959 > 0) > 0: self.O34959p5007 = (O34959 + self.O35007) - self.logO3O2 = np.log10((self.O34959p5007) / self.O23727) + self.dustcorrect(k_O3, k_O2) + self.logO3O2 = np.log10((self.O34959p5007) / self.O23727)\ + + self.dustcorrect(k_O3, k_O2, flux=False) #this is useful when we get logq self.hasO3O2 = True if self.hasHb: - self.OIII_Hb = np.log10(self.O35007 / self.Hb + self.dustcorrect(k_O35007, k_Hb, flux=True)) + self.OIII_Hb = np.log10(self.O35007 / self.Hb * self.dustcorrect(k_O35007, k_Hb, flux=True)) def setNII(self, N26584): if N26584 is not None and sum(N26584 > 0): self.N26584 = N26584 self.hasN2 = True if self.hasHa: - self.logN2Ha = np.log10(self.N26584 / self.Ha) # +self.dustcorrect(k_N2,k_Ha,flux=True) + self.logN2Ha = np.log10(self.N26584 / self.Ha) # +self.dustcorrect(k_N2,k_Ha,flux=True) #lines are very close: no dust correction #Note: no dust correction cause the lies are really close! else: printsafemulti("WARNING: needs NII6584 and Ha to calculate NIIHa: did you run setHab()?", self.logf, self.nps) if self.hasS2 and self.hasS26731 and self.hasN2: - self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_S2,flux=True) + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_S2,flux=True) #lines are very close: no dust correction if self.hasO2 and self.hasN2: - self.NII_OII = np.log10(self.N26584 / self.O23727 + self.dustcorrect(k_N2, k_O2, flux=True)) + self.NII_OII = np.log10(self.N26584 / self.O23727 * self.dustcorrect(k_N2, k_O2, flux=True)) def setSII(self, S26717, S26731, S39069, S39532): if S26717 is not None and sum(S26717 > 0) > 0: @@ -354,7 +378,7 @@ def setSII(self, S26717, S26731, S39069, S39532): self.hasS2 = True if self.hasHa: - self.logS2Ha = np.log10(self.S26717 / self.Ha) + self.dustcorrect(k_S2, k_Ha) + self.logS2Ha = np.log10(self.S26717 / self.Ha) + self.dustcorrect(k_S2, k_Ha, flux=False) else: printsafemulti("WARNING: needs SII6717 and Ha to calculate SIIHa: did you run setHab() and setS()?", self.logf, self.nps) if S26731 is not None and sum(S26731 > 1e-9) > 0: @@ -368,22 +392,30 @@ def setSII(self, S26717, S26731, S39069, S39532): self.hasS39532 = True if self.hasS2: if self.hasN2 and self.NII_SII is None and self.hasS26731: - self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_O2,flux=True) + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_O2,flux=True) #lines are very close: no dust correction if self.hasO3 and self.OIII_SII is None and self.hasS26731: - self.OIII_SII = np.log10(self.O35007 / (self.S26717 + self.S26731) + self.dustcorrect(k_O3, k_S2, flux=True)) + self.OIII_SII = np.log10(self.O35007 / (self.S26717 + self.S26731) * self.dustcorrect(k_O3, k_S2, flux=True)) #@profile def calcEB_V(self): printsafemulti("calculating E(B-V)", self.logf, self.nps) - self.mds['E(B-V)'] = np.log10(2.86 * self.Hb / self.Ha) / (0.4 * (k_Ha - k_Hb)) # E(B-V) + a2b_intrinsic = 2.86 + self.mds['E(B-V)'] = np.log10(a2b_intrinsic * self.Hb / self.Ha) / (0.4 * (k_Ha - k_Hb)) # E(B-V) + self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 + + #@profile + def calcEB_Vblue(self): + printsafemulti("calculating E(B-V)_blue", self.logf, self.nps) + b2g_intrinsic = 2.145 + self.mds['E(B-V)'] = np.log10(b2g_intrinsic * self.Hg / self.Hb) / (0.4 * (k_Hb - k_Hg)) # E(B-V) self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 #@profile def calcNIISII(self): if self.hasS2 and self.hasN2: - self.N2S2 = self.N26584 / self.S26717 + self.dustcorrect(k_N2, k_S2, flux=True) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) - self.logN2S2 = np.log10(self.N26584 / self.S26717) + self.dustcorrect(k_N2, k_S2) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) + self.N2S2 = self.N26584 / self.S26717 * self.dustcorrect(k_N2, k_S2, flux=True) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) + self.logN2S2 = np.log10(self.N26584 / self.S26717) + self.dustcorrect(k_N2, k_S2, flux=False) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) self.hasN2S2 = True else: printsafemulti("WARNING: needs SII6717 and NII6584 to calculate NIISII: did you run setN2() and setS?", self.logf, self.nps) @@ -391,16 +423,17 @@ def calcNIISII(self): #@profile def calcNIIOII(self): if self.hasN2 and self.hasO2: - self.logN2O2 = np.log10(self.N26584 / self.O23727) + self.dustcorrect(k_N2, k_O2) + self.logN2O2 = np.log10(self.N26584 / self.O23727) + self.dustcorrect(k_N2, k_O2, flux=False) + self.hasN2O2 = True - if not self.hasN2O2 or np.mean(self.logN2O2) < 1.2: + if not self.hasN2O2 or np.mean(self.logN2O2) < -1.2: try: - printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods should only be used for log([NII]6564/[OII]3727) >1.2, + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods should only be used for log([NII]6564/[OII]3727) > -1.2, the mean log([NII]6564/[OII]3727)= %f''' % np.mean(self.logN2O2), self.logf, self.nps) except TypeError: - printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods - should only be used for log([NII]6564/[OII]3727) >1.2, + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods + should only be used for log([NII]6564/[OII]3727) >-1.2, the mean log([NII]6564/[OII]3727)= %s''' % self.logN2O2, self.logf, self.nps) if not self.hasN2O2: @@ -489,12 +522,12 @@ def calcpyqz(self, plot=False, allD13=False): # initializing variable pyqz to avoid style issues # (pyqz not defined is reported as error by Landscape.io w/ import in func - pyqz = None + pyqz = None try: import pyqz except ImportError: return -1 - + #check version of pyqz from distutils.version import StrictVersion oldpyqz = False @@ -510,11 +543,15 @@ def calcpyqz(self, plot=False, allD13=False): method='default', plot=plot, n_plot=False, savefig=False)[0].T else: #pyqz.get_grid_fn(Pk=5.0,calibs='GCZO', kappa =20, struct='pp') - self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ - np.atleast_1d([self.OIII_SII])], \ - '[NII]/[SII]+;[OIII]/[SII]+', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + # self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + # np.atleast_1d([self.OIII_SII])], \ + # '[NII]/[SII]+;[OIII]/[SII]+', \ + # show_plot=plot, n_plot=False, \ + # save_plot=False, verbose=False)[0].T + self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', + [np.atleast_1d([self.NII_SII]), + np.atleast_1d([self.OIII_SII])], + '[NII]/[SII]+;[OIII]/[SII]+')[0].T if self.OIII_Hb is not None: if oldpyqz: @@ -522,11 +559,14 @@ def calcpyqz(self, plot=False, allD13=False): np.atleast_1d([self.OIII_Hb]), 'NII/SII', 'OIII/Hb', \ method='default', plot=plot, n_plot=False, savefig=False)[0].T else: + # self.mds['D13_N2S2_O3SHb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + # np.atleast_1d([self.OIII_Hb])], \ + # '[NII]/[SII]+;[OIII]/Hb', \ + # show_plot=plot, n_plot=False, \ + # save_plot=False, verbose=False)[0].T self.mds['D13_N2S2_O3SHb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ np.atleast_1d([self.OIII_Hb])], \ - '[NII]/[SII]+;[OIII]/Hb', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + '[NII]/[SII]+;[OIII]/Hb')[0].T @@ -542,7 +582,7 @@ def calcpyqz(self, plot=False, allD13=False): show_plot=plot, n_plot=False, \ save_plot=False, verbose=False)[0].T - + if self.NII_OII is not None and allD13: if self.OIII_SII is not None: if oldpyqz: @@ -565,9 +605,7 @@ def calcpyqz(self, plot=False, allD13=False): else: self.mds['D13_N2O2_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ np.atleast_1d([self.OIII_Hb])], \ - '[NII]/[OII]+;[OIII]/Hb', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + '[NII]/[OII]+;[OIII]/Hb')[0].T if self.OIII_OII is not None: if oldpyqz: @@ -577,10 +615,7 @@ def calcpyqz(self, plot=False, allD13=False): else: self.mds['D13_N2O2_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ np.atleast_1d([self.OIII_OII])], \ - '[NII]/[OII]+;[OIII]/[OII]+', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T - + '[NII]/[OII]+;[OIII]/[OII]+')[0].T if self.logN2Ha is not None: if self.OIII_Hb is not None: if oldpyqz: @@ -590,10 +625,7 @@ def calcpyqz(self, plot=False, allD13=False): else: self.mds['D13_N2Ha_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ np.atleast_1d([self.OIII_Hb])], \ - '[NII]/Ha;[OIII]/Hb', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T - + '[NII]/Ha;[OIII]/Hb')[0].T if self.OIII_OII is not None: if oldpyqz: self.mds['D13_N2Ha_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.logN2Ha]), \ @@ -603,9 +635,7 @@ def calcpyqz(self, plot=False, allD13=False): else: self.mds['D13_N2Ha_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ np.atleast_1d([self.OIII_Hb])], \ - '[NII]/Ha;[OIII]/[OII]+', \ - show_plot=plot, n_plot=False, \ - save_plot=False, verbose=False)[0].T + '[NII]/Ha;[OIII]/[OII]+')[0].T #@profile def calcDP00(self): @@ -634,7 +664,8 @@ def calcD02(self): if self.hasN2 and self.hasHa: self.mds['D02'] = 9.12 + e1 + (0.73 + e2) * self.logN2Ha else: - printsafemulti("WARNING: need N2Ha to do this. did you run setHab and setNII", self.logf, self.nps) + printsafemulti("WARNING: need N2Ha to do this. " + + "did you run setHab and setNII", self.logf, self.nps) #@profile def calcPP04(self): @@ -747,10 +778,12 @@ def calcP10(self): #independent on physical conditions #The Physics and Dynamics of Planetary Nebulae # By Grigor A. Gurzadyan - P10logN2 = np.log((self.N26584 * 1.33) / self.Hb) + self.dustcorrect(k_N2, k_Hb) + P10logN2 = (np.log((self.N26584 * 1.33) / self.Hb) + + self.dustcorrect(k_N2, k_Hb, flux=False)) if self.hasS2 and self.hasS26731: - self.S2Hb = ((self.S26717 + self.S26731) / self.Hb) + self.dustcorrect(k_S2, k_Hb, flux=True) + self.S2Hb = ((self.S26717 + self.S26731) / self.Hb + * self.dustcorrect(k_S2, k_Hb, flux=True)) self.hasS2Hb = True P10logS2 = np.log10(self.S2Hb) @@ -829,7 +862,7 @@ def calcC01_ZR23(self): self.mds['C01_R23'][self.O2O35007 >= 0.8] = np.log10(3.96e-4 * x3[self.O2O35007 >= 0.8] ** (-0.46)) + 12.0 else: - printsafemulti('''WARNING: need [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, + printsafemulti('''WARNING: need [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, did you set them up with setOlines()?''', self.logf, self.nps) # Charlot 01 calibration: (case A) based on [N2]/[SII]## @@ -840,7 +873,7 @@ def calcC01_ZR23(self): if self.hasN2S2 and self.hasO3 and self.hasO2 and self.hasO3Hb: self.mds['C01_N2S2'] = np.log10(5.09e-4 * (x2 ** 0.17) * ((self.N2S2 / 0.85) ** 1.17)) + 12 else: - printsafemulti('''WARNING: needs [NII]6584, [SII]6717, [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, + printsafemulti('''WARNING: needs [NII]6584, [SII]6717, [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, did you set them up with setOlines() and ?''', self.logf, self.nps) #@profile @@ -891,19 +924,29 @@ def calcM13(self): printsafemulti("calculating M13", self.logf, self.nps) if not self.hasHa or not self.hasN2: - printsafemulti("WARNING: need O3, N2, Ha and Hb, or at least N2 and Ha", self.logf, self.nps) + printsafemulti("WARNING: need O3, N2, Ha and Hb, " + + "or at least N2 and Ha", self.logf, self.nps) return -1 else: e1 = np.random.normal(0, 0.027, self.nm) e2 = np.random.normal(0, 0.024, self.nm) - self.mds["M13_N2"] = 8.743 + e1 + (0.462 + e2) * self.logN2Ha + self.mds["M13_N2Ha"] = 8.743 + e1 + (0.462 + e2) * self.logN2Ha if self.hasHb and self.hasO3: e1 = np.random.normal(0, 0.012, self.nm) e2 = np.random.normal(0, 0.012, self.nm) O3N2 = self.logO3Hb - self.logN2Ha self.mds["M13_O3N2"] = 8.533 + e1 - (0.214 + e1) * O3N2 - index = (self.logO3Hb > 1.7) + index = (O3N2 > 1.7) self.mds["M13_O3N2"][index] = float('NaN') + index = (O3N2 < -1.1) + self.mds["M13_O3N2"][index] = float('NaN') + + #for i in range(len(self.mds["M13_O3N2"])): + #print ("here O3N2",#self.O35007[i], self.Hb[i], + #self.O3Hb[i], + #self.logO3Hb[i], self.logN2Ha[i], + # O3N2[i], + # self.mds["M13_O3N2"][i]) #@profile def calcM08(self, allM08=False): @@ -991,9 +1034,11 @@ def calcM08(self, allM08=False): if self.hasO3 and self.hasN2: self.mds['M08_O3N2'] = np.zeros(self.nm) + float('NaN') coefs = np.array([M08_coefs['O3N2']] * self.nm).T - coefs[0] = coefs[0] - np.log(self.O35007 / self.N26584) * self.dustcorrect(k_O35007, k_N2) + coefs[0] = coefs[0] - np.log10(self.O35007 / self.N26584) \ + + self.dustcorrect(k_O35007, k_N2, flux=False) sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 - indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4)\ + * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 self.mds['M08_O3N2'][(indx.sum(1)) > 0] = sols[indx].real #@profile @@ -1055,7 +1100,7 @@ def calcKK04_N2Ha(self): convergence = np.abs(self.logq - logq_save).mean() logq_save = self.logq.copy() if ii >= 100: - printsafemulti("WARNING: loop did not converge", self.logf, self.nps) + printsafemulti("WARNING [KK04_N2Ha]: loop did not converge", self.logf, self.nps) Z_new_N2Ha = np.zeros(self.nm) + float('NaN') else: self.logq = 7.37177 * np.ones(self.nm) @@ -1115,7 +1160,7 @@ def calcKK04_R23(self): convergence = np.abs((logqold - logq).mean()) logqold = logq.copy() if ii >= 100: - printsafemulti("WARNING: loop did not converge", self.logf, self.nps) + printsafemulti("WARNING [KK04_R23]: loop did not converge", self.logf, self.nps) Z_new = np.zeros(self.nm) + float('NaN') Z_new_lims = [nppoly.polyval(self.logR23, [9.40, 4.65, -3.17]) - \ logq * nppoly.polyval(self.logR23, [0.272, 0.547, -0.513]), @@ -1175,7 +1220,7 @@ def calcKDcombined(self): if self.Z_init_guess is None: self.initialguess() logq = self.calclogq(self.Z_init_guess) - #FED: CHECK: the paragraph below makes sense in words but i dont see whereit ie enforced. + #FED: CHECK: the paragraph below makes sense in words but i dont see where it is enforced. # if log([NII]/[OII]) after extinction correction is <-1.5, then check the data. # if it is only slightly less than 1.5, then this can be a result of either noisy # data, inaccurate fluxes or extinction correction, or a higher ionization parameter @@ -1222,7 +1267,8 @@ def calcPM14(self): if self.R2 is not None: ratios[0] = self.R2 elif self.hasO2: - ratios[0] = ((self.O23727 / self.Hb) * self.dustcorrect(k_O2, k_Hb, flux=True)) + ratios[0] = ((self.O23727 / self.Hb) + * self.dustcorrect(k_O2, k_Hb, flux=True)) else: ratios[0] = np.array(['0 '] * self.nm) @@ -1232,7 +1278,8 @@ def calcPM14(self): if self.hasO3Hb: ratios[2] = self.O3Hb elif self.hasO3: - ratios[2] = ((self.O35007 / self.Hb) + self.dustcorrect(k_O35007, k_Hb, flux=True)) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + ratios[2] = ((self.O35007 / self.Hb) + * self.dustcorrect(k_O35007, k_Hb, flux=True)) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) else: ratios[2] = np.zeros(self.nm) @@ -1244,21 +1291,24 @@ def calcPM14(self): if self.hasS2Hb: ratios[4] = self.S2Hb elif self.hasS2 and self.hasS26731: - ratios[4] = (((self.S26717 + self.S26731) / self.Hb) + self.dustcorrect(k_S2, k_Hb, flux=True)) + ratios[4] = (((self.S26717 + self.S26731) / self.Hb) * + self.dustcorrect(k_S2, k_Hb, flux=True)) else: ratios[4] = np.zeros(self.nm) - + for ni in range(self.nm): fin_hii_chi.write('%f %f %f %f %f\n' % (ratios[0][ni], ratios[1][ni], ratios[2][ni], ratios[3][ni], ratios[4][ni])) fin_hii_chi.close() os.system("ln -s %s/C13*dat . " % os.getenv('HIICHI_DIR')) - print "\n\n\n\n\n" + print ("\n\n\n\n\n") #os.system("python %s/HII-CHI-mistry_v01.2.py in.tmp"%os.getenv('HIICHI_DIR')) #os.system("python %s/HII-CHI-mistry_v01.2.py %s/in.tmp"%(os.getenv('HIICHI_DIR'),os.getenv('HIICHI_DIR'))) - p = Popen(['python', '%s/HII-CHI-mistry_v01.2.py' % os.getenv('HIICHI_DIR'), '%s/in.tmp' % os.getenv('HIICHI_DIR')], stdout=PIPE, stdin=PIPE, stderr=STDOUT) + p = Popen(['python', '%s/HII-CHI-mistry_v01.2.py' % os.getenv('HIICHI_DIR'), + '%s/in.tmp' % os.getenv('HIICHI_DIR')], + stdout=PIPE, stdin=PIPE, stderr=STDOUT) out, err = p.communicate(input='%s/in.tmp' % os.getenv('HIICHI_DIR')) - print "\n\n\n\n\n" + print ("\n\n\n\n\n") out = StringIO(out) # for l in enumerate(out): # if l[0].isdigit(): @@ -1269,5 +1319,240 @@ def calcPM14(self): for i, l in enumerate(out): self.mds['PM14'][i], self.mds['PM14err'][i] = map(float, l.split()[3:5]) #data = np.loadtxt(out, skiprows=12, usecols=(3,4))#, dtype=[('lOH','f'),('elOH','f')], delimiter=",", unpack = True) - print self.mds + print (self.mds) os.system('rm -r C13*dat') + + +########## jch additions + + def calcPMC09(self): + #Perez-Montero & Contini (2009) - N2Ha + printsafemulti("calculating PMC09", self.logf, self.nps) + + if not self.hasHa or not self.hasN2 or not self.hasHa: + printsafemulti("WARNING: need N2, Ha ", + self.logf, self.nps) + return -1 + PMC09_N2 = lambda x: 9.07+0.79*x + + y = 9.07 + 0.78*self.logN2Ha + self.mds["PMC09_N2Ha"] = y + + #@profile + def calcB07(self): + #Bresolin 2007 - N2O2 + printsafemulti("calculating B07", self.logf, self.nps) + + if not self.hasHa or not self.hasN2 or not self.hasO2: + printsafemulti("WARNING: need N2, O2 ", + self.logf, self.nps) + return -1 + + # Use [N2/Ha * Ha/Hb] / [O2/Hb] + x = (self.logN2Ha+np.log10(2.86)) - self.logO2Hb + + y = 8.66 + 0.36*self.logN2O2 - 0.17*self.logN2O2**2 + self.mds["B07_N2O2"] = y + index = (self.logN2O2 < -1.2) + self.mds["B07_N2O2"][index] = float('NaN') + index = (self.logN2O2 > 0.6) + self.mds["B07_N2O2"][index] = float('NaN') + + + #@profile + def calcD16(self): + #Dopita+ 2016 + printsafemulti("calculating D16", self.logf, self.nps) + + if not self.hasHa or not self.hasN2 or not self.hasS2: + printsafemulti("WARNING: need N2, Ha and SII, ", + self.logf, self.nps) + return -1 + + d16_n2s2 = np.log10(self.N26584 / (self.S26717+self.S26731)) + self.dustcorrect(k_N2, k_S2, flux=False) + y = d16_n2s2 + 0.264 * self.logN2Ha + self.mds["D16"] = 8.77 + y# + 0.45 * pow(y + 0.3, 5) + index = (y < -1.) + self.mds["D16"][index] = float('NaN') + index = (y > 0.5) + self.mds["D16"][index] = float('NaN') + + + def calcC17(self, allC17=False): + # Curti+ 2017 + # Monthly Notices Royal Astronomical Society, vol. 465, pp 1384-1400 + # Published in October 2016 + # + # Added 5/16/17 by jch, Santiago + import pdb + + valid_upper = 8.85 + valid_lower = 7.6 + + printsafemulti("calculating C17", self.logf, self.nps) + highZ = None + + if self.logO35007O2 is not None: + # C17 O3O2 + self.mds['C17_O3O2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3O2']] * self.nm).T + coefs[0] = coefs[0] - self.logO35007O2 + + # sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + # Find valid solutions + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + + # the two cumsum assure that if the condition for the ith element + # of indx is [False, False] then after the first cumsum(1) is [0,0] + # [False, True] is [0,1] + # [True, True] is [1,2] + # but (here is the kicker) [True, False] is [1,1]. + # Because i want only one solution + # (i'll settle for the first one occurring) [1,1] is ambiguous. + # The second cumsum(1) makes + # [0,0]->[0,0], [0,1]->[0,1], [1,2]->[1,3] and finally [1,1]->[1,2] + + # Set the results where appropriate + self.mds['C17_O3O2'][(indx.sum(1)) > 0] = sols[indx].real + + # Now use this to see if we're on the high-Z branch of R23 + highZ = np.median(self.logO35007O2) < 0 + + if self.logN2Ha is not None: + # C17 N2Ha + self.mds['C17_N2Ha'] = np.zeros(self.nm) + float('NaN') + + coefs = np.array([C17_coefs['N2Ha']] * self.nm).T + coefs[0] = coefs[0] - self.logN2Ha + + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + + self.mds['C17_N2Ha'][(indx.sum(1)) > 0] = sols[indx].real + if highZ is None: + highZ = np.median(self.logN2Ha) > -1.3 + + if self.hasO3 and self.hasN2: + # C17 O3N2 + self.mds['C17_O3N2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3N2']] * self.nm).T + o3n2_value = self.logO3Hb - self.logN2Ha + coefs[0] = coefs[0] - o3n2_value + + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) \ + * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3N2'][(indx.sum(1)) > 0] = sols[indx].real + + if self.logO3Hb is not None: + # C17 O3Hb + self.mds['C17_O3Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO3Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + # C17 define this only for the high-metal branch. + indx = ((sols.real >= 8.2) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + + # Require allC17 flag if we want everything. + if not allC17: + return + else: + printsafemulti("calculating other C17s", self.logf, self.nps) + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute C17_R23 without R23", self.logf, self.nps) + else: + self.mds['C17_R23'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['R23']] * self.nm).T + coefs[0] = coefs[0] - self.logR23 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real >= 8.4)).cumsum(1).cumsum(1) == 1 + self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real < 8.4)).cumsum(1).cumsum(1) == 1 + self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real + + + if self.logO2Hb is not None: + self.mds['C17_O2Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O2Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO2Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * + (sols.imag == 0) * + (sols.real >= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * + (sols.imag == 0) * + (sols.real <= valid_upper)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + + + + def calcPG16(self): + # Pilyugin & Grebel (2016, MNRAS, 457, 3678) + printsafemulti("calculating PG16", self.logf, self.nps) + + # Initialize the PG16 results + self.mds['PG16'] = np.zeros(self.nm) + float('NaN') + + # Make sure the lines are all here... + if not self.hasHa or not self.hasN2 or not self.hasO2 or not self.hasO3 or not self.hasS2: + printsafemulti("WARNING: need O2, O3, N2, S2, and Hb", + self.logf, self.nps) + return -1 + + pilR2 = self.logO2Hb + pilR3 = self.logO3Hb+np.log10(1.33) # Both members of doublet + pilR3R2 = (pilR3-pilR2) + + # Calculate S2/Hb = S2/Ha * Ha/Hb = S2/Ha*2.85 + pilS2 = np.log10((self.S26731 + self.S26731) / self.Ha * 2.85) + pilS2 += np.log10(self.dustcorrect(k_S2,k_Ha,flux=True)) + pilR3S2 = (pilR3-pilS2) + + # Calculate N2/Hb = N2/Ha * Ha/Hb = N2/Ha*2.85 + pilN2 = np.log10((self.N26584*1.33 / self.Ha)*2.85) + # pilN2 += np.log10(self.dustcorrect(k_N2,k_Hb,flux=True)) + + # Determine which branch we're on + highZ = (np.median(pilN2) >= -0.6) + print(np.median(pilN2)) + + # Calculate R = O2O3N2 abundances + if highZ == True: + print('PG16 high metal branch') + self.mds['PG16_R'] = 8.589+0.022*pilR3R2+0.399*pilN2 + self.mds['PG16_R'] += (-0.137+0.164*pilR3R2+0.589*pilN2)*pilR2 + else: + print('PG16 low metal branch') + self.mds['PG16_R'] = 7.932+0.944*pilR3R2+0.695*pilN2 + self.mds['PG16_R'] += (0.970-0.291*pilR3R2-0.019*pilN2)*pilR2 + + # Calculate S = S2O3N2 abundances + if highZ == True: + print('PG16 high metal branch') + self.mds['PG16_S'] = 8.424+0.030*pilR3S2+0.751*pilN2 + self.mds['PG16_S'] += (-0.349+0.182*pilR3S2+0.508*pilN2)*pilS2 + else: + print('PG16 low metal branch') + self.mds['PG16_S'] = 8.072+0.789*pilR3S2+0.726*pilN2 + self.mds['PG16_S'] += (1.069-0.17*pilR3S2+0.022*pilN2)*pilS2 diff --git a/build/lib/pyMCZ/pylabsetup.py b/build/lib/pyMCZ/pylabsetup.py new file mode 100644 index 0000000..5ffef05 --- /dev/null +++ b/build/lib/pyMCZ/pylabsetup.py @@ -0,0 +1,41 @@ +import matplotlib as mpl +import pylab as plt + +mpl.rcParams.update(mpl.rcParamsDefault) +mpl.rcParams['font.size'] = 26. +mpl.rcParams['font.family'] = 'serif' +#mpl.rcParams['font.family'] = 'serif' +mpl.rcParams['font.serif'] = ['Times New Roman', 'Times', 'Palatino', 'Charter', 'serif'] +mpl.rcParams['font.sans-serif'] = ['Helvetica'] +mpl.rcParams['axes.labelsize'] = 24 +mpl.rcParams['xtick.labelsize'] = 22. +mpl.rcParams['ytick.labelsize'] = 22. +mpl.rcParams['xtick.major.size'] = 15. +mpl.rcParams['xtick.minor.size'] = 10. +mpl.rcParams['ytick.major.size'] = 15. +mpl.rcParams['ytick.minor.size'] = 10. +#mpl.rcParams['figure.autolayout']= True + +#fontsize=26 +#mpl.rc('axes', titlesize=fontsize) +#mpl.rc('axes', labelsize=fontsize) +#mpl.rc('xtick', labelsize=fontsize) +#mpl.rc('ytick', labelsize=fontsize) +#mpl.rc('font', size=fontsize, family='serif', serif='Utopia', +# style='normal', variant='normal', +# stretch='normal', weight='normal') +#mpl.rc('font',**{'family':'serif','serif':[ 'Times New Roman', 'Times', 'serif'], +# 'sans-serif':['Helvetica'], 'size':19, +# 'weight':'normal'}) +mpl.rc('axes', **{'linewidth' : 1.2}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('ytick', **{'major.pad': 5, 'color': 'k'}) +mpl.rc('xtick', **{'major.pad': 5, 'color': 'k'}) +params = {'legend.fontsize': 24, + 'legend.numpoints': 1, + 'legend.handletextpad': 1 + } + +plt.rcParams.update(params) +plt.minorticks_on() diff --git a/build/scripts-2.7/pylabsetup.py b/build/scripts-2.7/pylabsetup.py new file mode 100755 index 0000000..5ffef05 --- /dev/null +++ b/build/scripts-2.7/pylabsetup.py @@ -0,0 +1,41 @@ +import matplotlib as mpl +import pylab as plt + +mpl.rcParams.update(mpl.rcParamsDefault) +mpl.rcParams['font.size'] = 26. +mpl.rcParams['font.family'] = 'serif' +#mpl.rcParams['font.family'] = 'serif' +mpl.rcParams['font.serif'] = ['Times New Roman', 'Times', 'Palatino', 'Charter', 'serif'] +mpl.rcParams['font.sans-serif'] = ['Helvetica'] +mpl.rcParams['axes.labelsize'] = 24 +mpl.rcParams['xtick.labelsize'] = 22. +mpl.rcParams['ytick.labelsize'] = 22. +mpl.rcParams['xtick.major.size'] = 15. +mpl.rcParams['xtick.minor.size'] = 10. +mpl.rcParams['ytick.major.size'] = 15. +mpl.rcParams['ytick.minor.size'] = 10. +#mpl.rcParams['figure.autolayout']= True + +#fontsize=26 +#mpl.rc('axes', titlesize=fontsize) +#mpl.rc('axes', labelsize=fontsize) +#mpl.rc('xtick', labelsize=fontsize) +#mpl.rc('ytick', labelsize=fontsize) +#mpl.rc('font', size=fontsize, family='serif', serif='Utopia', +# style='normal', variant='normal', +# stretch='normal', weight='normal') +#mpl.rc('font',**{'family':'serif','serif':[ 'Times New Roman', 'Times', 'serif'], +# 'sans-serif':['Helvetica'], 'size':19, +# 'weight':'normal'}) +mpl.rc('axes', **{'linewidth' : 1.2}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('ytick', **{'major.pad': 5, 'color': 'k'}) +mpl.rc('xtick', **{'major.pad': 5, 'color': 'k'}) +params = {'legend.fontsize': 24, + 'legend.numpoints': 1, + 'legend.handletextpad': 1 + } + +plt.rcParams.update(params) +plt.minorticks_on() diff --git a/build/scripts-3.6/mcz.py b/build/scripts-3.6/mcz.py new file mode 100755 index 0000000..f429d37 --- /dev/null +++ b/build/scripts-3.6/mcz.py @@ -0,0 +1,791 @@ +#!/Users/howk/miniconda3/bin/python +from __future__ import print_function +import os +import sys +import argparse +import warnings + +import numpy as np +import scipy.stats as stats +from scipy.special import gammaln +from scipy import optimize +import matplotlib.pyplot as plt +from matplotlib.ticker import FormatStrFormatter +import csv as csv + +#modules of this package +import pylabsetup + +#import metallicity_save2 as metallicity +import metallicity as metallicity + +import itertools +import multiprocessing as mpc + +###versin 1.3 February 2016 +#fixed compatyibiity issue with numphy >1.9 in the val to np.histogram https://github.com/numpy/numpy/issues/6469 + +###version 1.3.1 April 2016 +#works with version 0.7 of pyqz (current version) +#previous versions only worked with version 0.5. +#a check for the version of pyqz is implememted and a different call is issued for version >0.5 and <=0.5 + + + +# Define the version of +__version__ = '1.3.1' + +NM0 = 0 # setting this to say N>0 starts the calculation at measurement N. +#this is only for exploratory purposes as the code bugs out before +#plotting and printing the results + +PROFILING = True +PROFILING = False + +alllines = ['[OII]3727', 'Hg','Hb', '[OIII]4959', '[OIII]5007', '[OI]6300', 'Ha', '[NII]6584', '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532'] +morelines = ['E(B-V)', 'dE(B-V)', 'scale_blue', 'd scale_blue'] + +MAXPROCESSES = 10 + +#pickle may not be installed +NOPICKLE = False +try: + import pickle +except ImportError: + NOPICKLE = True + +CLOBBER = False +VERBOSE = False +UNPICKLE = False +ASCIIOUTPUT = False +ASCIIDISTRIB = False +RUNSIM = True +NOPLOT = False +BINMODE = 'k' +binning = {'bb': 'Bayesian blocks', 'k': "Knuth's rule", + 'd': "Doane's formula", 's': r'$\sqrt{N}$', + 't': r'$2 N^{1/3}$', 'kd': 'Kernel Density'} + +MP = False + + +def is_number(s): + if not type(s) is np.string_: + try: + float(s) + return True + except ValueError: + return False + return False + + +def smart_open(logfile=None): + if logfile and logfile != '-': + fh = open(logfile, 'w') + else: + fh = sys.stdout + return fh + + +def getknuth(m, data, N): + m = int(m) + if m > N: + return [-1] + bins = np.linspace(min(data), max(data), int(m) + 1) + try: + nk, bins = np.histogram(data, bins) + return -(N * np.log(m) + gammaln(0.5 * m) - m * gammaln(0.5) - gammaln(N + 0.5 * m) + np.sum(gammaln(nk + 0.5))) + except: + return [-1] + + +def knuthn(data, maxM=None): + assert data.ndim == 1, "data must be 1D array to calculate Knuth's number of bins" + N = data.size + if not maxM: + maxM = 5 * np.sqrt(N) + m0 = 2.0 * N ** (1. / 3.) + gk = getknuth + if gk == [-1]: + return m0, 't' + mkall = optimize.fmin(gk, m0, args=(data, N), disp=VERBOSE, maxiter=30) # , maxfun=1000)#[0] + mk = mkall[0] + if mk > maxM or mk < 0.3 * np.sqrt(N): + mk = m0 + return mk, 't' + return mk, 0 + + +############################################################################## +##The input data +############################################################################## +##Reads the flux file and returns it as an array. +##Ignores non-numeric lines +##Returns (flux array,num) +############################################################################## +def readfile(filename): + noheader = 1 + findex = -1 + f = open(filename, 'r') + l0 = f.readline().replace(' ', '') + l1 = f.readline().split() + if l0.startswith('#') or l0.startswith(';'): + # Modified to account for non-standard header (\t instead of , separated) jch + temp_header = l0.strip().replace(";", '').replace("#", '') + if temp_header.find(',') == -1: + header = temp_header.split('\t') + else: + header = temp_header.split(',') + header[0] = header[0].replace(' ', '') + header = header[:len(l1)] + else: + noheader = 0 + header = ['galnum'] + alllines + ['flag'] + morelines + header = header[:len(l1)] + + usecols = [] + newheader = [] + for i,h in enumerate(header): + if h in alllines + ['galnum']: + usecols.append(i) + newheader.append(h) + + header = newheader + + formats = ['S10'] + ['f'] * (len(header) - 1) + + if 'flag' in header: + findex = header.index('flag') + formats[findex] = 'S10' + + bstruct = {} + for i, k in enumerate(header): + bstruct[k] = [i, 0] + b = np.loadtxt(filename, skiprows=noheader, dtype={'names': header, 'formats': formats}, comments=';',usecols=usecols) + if b.size == 1: + b = np.atleast_1d(b) + for i, k in enumerate(header): + if not k == 'flag' and is_number(b[k][0]): + bstruct[k][1] = np.count_nonzero(b[k]) + sum(np.isnan(b[k])) + j = len(b['galnum']) + return b, j, bstruct + + +def ingest_data(filename, path): + ###Initialize### + measfile = os.path.join(path, filename + "_meas.txt") + errfile = os.path.join(path, filename + "_err.txt") + + ###read the max, meas, min flux files### + meas, nm, bsmeas = readfile(measfile) + err, nm, bserr = readfile(errfile) + try: + snr = (meas[: + ,1:].view(np.float32).reshape(meas[: + ,1:].shape + (-1, ))[: + ,1:]) / (err[: + ,1:].view(np.float32).reshape(err[: + ,1:].shape + (-1, ))[: + ,1:]) + if snr[~np.isnan(snr)].any() < 3: + raw_input('''WARNING: signal to noise ratio smaller than 3 + for at least some lines! You should only use SNR>3 + measurements (return to proceed)''') + except (IndexError, TypeError): + pass + return (filename, meas, err, nm, path, (bsmeas, bserr)) + +def input_data(filename, path): + p = os.path.join(path, "input") + assert os.path.isdir(p), "bad data directory %s" % p + if os.path.isfile(os.path.join(p, filename + '_err.txt')): + if os.path.isfile(os.path.join(p, filename + '_meas.txt')): + return ingest_data(filename, path=p) + print ("Unable to find _meas and _err files ", filename + + '_meas.txt', filename + '_err.txt', "in directory ", p) + return -1 + +############################################################################## +##returns a random distribution. In the deployed version of the code this is a gaussian distribution, but the user can include her or his distribution. +##return a random sample of size n +############################################################################## +def errordistrib(distrargs, n, distype='normal'): + if distype == 'normal': + try: + mu, sigma = distrargs + except: + print ("for normal distribution distargs must be a 2 element tuple") + return -1 + return np.random.normal(mu, sigma, n) + else: + print ("distribution not supported") + return -1 + + +############################################################################## +##returns appropriate bin size for the number of data +##mode 'k' calculates this based on Knuth's rule +##mode 'd' calculates this based on Doane's formula +##mode 's' calculates this based on sqrt of number of data +##mode 't' calculates this based on 2*n**1/3 (default) +############################################################################## +def getbinsize(n, data, ): + if BINMODE == 'd': + g1 = np.abs(stats.mstats.moment(data, moment=3)) # /data.std()) + s1 = np.sqrt(float(n) / 6.0) + #s1=1.0/np.sqrt(6.*(n-2.)/((n+1.)*(n+3.))) + k = 1 + np.log2(n) + np.log2(1 + (g1 * s1)), 0 + elif BINMODE == 's': + k = np.sqrt(n), 0 + elif BINMODE == 't': + k = 2. * n ** (1. / 3.), 0 + else: + k = knuthn(data) + return k + + +############################################################################## +##Check if hist files already exist and need to be replaced +############################################################################## +def checkhist(snname, Zs, nsample, i, path): + global CLOBBER + + name = '%s_n%d_%s_%d' % ((snname, nsample, Zs, i + 1)) + outdir = os.path.join(path, 'hist') + outfile = os.path.join(outdir, name + ".pdf") + if os.path.isfile(outfile) and not CLOBBER: + replace = raw_input("replacing existing image files, starting with: %s ? [Y/n]\n" % outfile).lower() + assert(not (replace.startswith('n'))), "save your existing output directory under another name first" + CLOBBER = True + + +############################################################################## +##Save the result as histogram as name +############################################################################## +#@profile +def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False, fs=24, reserr=None): + global BINMODE + #global NOPLOT + + name = '%s_n%d_%s_%d' % ((snname, nsample, Zs, i + 1)) + outdir = os.path.join(path, 'hist') + outfile = os.path.join(outdir, name + ".pdf") + if not NOPLOT: + fig = plt.figure(figsize=(11, 8)) + plt.clf() + + ####kill outliers, infinities, and bad distributions### + data = data[np.isfinite(data)] + + n = data.shape[0] + kde = None + if not n > 0: + if verbose: + print ("data must be an actual distribution (n>0 elements!, %s)" % Zs) + return "-1,-1,_1", [], kde + + if data.shape[0] <= 0 or np.sum(data) <= 0: + print ('{0:15} {1:20} {2:>13d} {3:>7d} {4:>7d} '\ + .format(snname, Zs, -1, -1, -1)) + return "-1, -1, -1", [], kde + try: + ###find C.I.### + median, pc16, pc84 = np.percentile(data, [50, 16, 84]) + std = np.std(data) + left = pc16 + right = pc84 + maxleft = median - std * 5 + maxright = median + std * 5 + if "%2f" % maxright == "%2f" % maxleft: + maxleft = median - 1 + maxright = median + 1 + if round(right, 6) == round(left, 6) and round(left, 6) == round(median, 6): + print ('{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(snname, Zs, median, 0, 0)) + if reserr: + print ('+/- {0:.3f}'.format(reserr)) + return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde # "-1,-1,-1",[] + ###print out the confidence interval### + print ('{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f}'.format(snname, + Zs, round(median, 3), + round(median - left, 3), + round(right - median, 3))) + if reserr: + print ('+/- {0:.3f}'.format(reserr)) + alpha = 1.0 + + ######histogram###### + if BINMODE == 'kd': + ##if sklearn is available use it to get Kernel Density + try: + from sklearn.neighbors import KernelDensity + except ImportError: + print ('''sklearn is not available, + thus we cannot compute kernel density. + switching to bayesian blocks''') + BINMODE = 'bb' + if BINMODE == 'kd': + ##bw is chosen according to Silverman 1986 + bw = 1.06 * std * n ** (-0.2) + numbin, bm = getbinsize(data.shape[0], data) + distrib = np.histogram(data, bins=int(numbin), density=True) + ###make hist### + counts, bins = distrib[0], distrib[1] + widths = np.diff(bins) + countsnorm = counts / np.max(counts) + + if bw > 0: + kde = KernelDensity(kernel='gaussian', bandwidth=bw).fit(data[: + , np.newaxis]) + kdebins = np.linspace(maxleft, maxright, 1000)[: + , np.newaxis] + log_dens = kde.score_samples(kdebins) + dens = np.exp(log_dens) + norm = countsnorm.sum() * (bins[1] - bins[0]) / dens.sum() / (kdebins[1] - kdebins[0]) + if not NOPLOT: + plt.fill(kdebins[: + ,0], dens * norm, fc='#7570b3', alpha=0.8) + alpha = 0.5 + + ###find appropriate bin size### + else: + if BINMODE == 'bb': + ##if astroML is available use it to get Bayesian blocks + bm = 0 + try: + from astroML.plotting import hist as amlhist + if BINMODE == 'bb': + distrib = amlhist(data, bins='blocks', normed=True) + if not NOPLOT: + plt.clf() + except ImportError: + print ("bayesian blocks for histogram requires astroML to be installed") + print ("defaulting to Knuth's rule ") + ##otherwise + numbin, bm = getbinsize(data.shape[0], data) + distrib = np.histogram(data, bins=int(numbin), density=True) + else: + numbin, bm = getbinsize(data.shape[0], data) + distrib = np.histogram(data, bins=int(numbin), density=True) + + ###make hist### + counts, bins = distrib[0], distrib[1] + widths = np.diff(bins) + countsnorm = counts / np.max(counts) + + ###plot hist### + if NOPLOT: + return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde + + plt.bar(bins[:-1], countsnorm, widths, color=['gray'], alpha=alpha) + plt.minorticks_on() + plt.gca().xaxis.set_major_formatter(FormatStrFormatter('%.2f')) + plt.xlim(maxleft, maxright) + + #the following lines assure the x tick label is + #within the length of the x axis + xticks = plt.xticks()[0] + dx = xticks[-1] - xticks[-2] + xticks = xticks[(xticks < maxright) * (xticks > maxleft)] + if (maxright - xticks[-1]) < 0.25 * dx: + maxright = maxright + 0.25 * dx + maxleft = maxleft - 0.25 * dx + plt.xlim(maxleft, maxright) + plt.xticks(xticks, ['%.2f' % s for s in xticks]) + + plt.ylim(0, 1.15) + plt.yticks(np.arange(0.2, 1.3, 0.2), ["%.1f" % x for x in np.arange(0.2, 1.1, 0.2)]) + plt.axvspan(left, right, color='DarkOrange', alpha=0.4) + plt.axvline(x=median, linewidth=2, color='white', ls='--') + + #labels and legends + st = '%s ' % (snname) + plt.annotate(st, xy=(0.13, 0.6), xycoords='axes fraction', size=fs, fontweight='bold') + st = '%s ' % (Zs.replace('_', ' ')) + plt.annotate(st, xy=(0.61, 0.93), xycoords='axes fraction', fontsize=fs, fontweight='bold') + st = 'measurement %d of %d\n %s\nmedian: %.3f\n16th Percentile: %.3f\n84th Percentile: %.3f' \ + % (i + 1, nmeas, measnames[i], round(median, 3), round(left, 3), round(right, 3)) + plt.annotate(st, xy=(0.61, 0.65), xycoords='axes fraction', fontsize=fs) + effectiven = len(data[~np.isnan(data)]) + if effectiven: + st = 'MC sample size %d (%d)\nhistogram rule: %s' % (effectiven, nsample, binning[BINMODE]) + if bm: + if effectiven < nsample: + st = 'MC sample size %d (%d)\nhistogram rule: %s' % (effectiven, nsample, binning[bm]) + else: + st = 'MC sample size %d\nhistogram rule: %s' % (nsample, binning[bm]) + plt.annotate(st, xy=(0.61, 0.55), xycoords='axes fraction', fontsize=fs - 5) + if "E(B-V)" in Zs: + plt.xlabel('E(B-V) [mag]') + outfile = outfile.replace('(', '').replace(')', '') + elif "logR23" in Zs: + plt.xlabel('logR23') + else: + plt.xlabel('12+log(O/H)') + plt.ylabel('relative counts') + plt.savefig(outfile, format='pdf') + plt.close(fig) + + return "%f\t %f\t %f" % (round(median, 3), round(median - left, 3), round(right - median, 3)), data, kde + + except (OverflowError, AttributeError, ValueError): + if VERBOSE: + print (data) + print (name, 'had infinities (or something in plotting went wrong)') + return "-1, -1,-1", [], None + + +def calc((i, (sample, flux, err, nm, bss, mds, disp, dust_corr,dust_blue, + verbose, res, scales, nps, logf))): + logf = sys.stdout + logf.write("\n\nreading in measurements %d\n"%(i + 1)) + fluxi = {} # np.zeros((len(bss[0]),nm),float) + for k in bss[0].iterkeys(): + logf.write('{0:15} '.format(k)) + logf.write('{0:0.2} +/- {1:0.2}\n'.format(flux[k][i], err[k][i])) + #print >> logf, '{0:15} '.format(k), + #print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) + fluxi[k] = flux[k][i] * np.ones(len(sample[i])) + err[k][i] * sample[i] + warnings.filterwarnings("ignore") + success = metallicity.calculation(scales[i], fluxi, nm, mds, nps, logf, + disp=disp, verbose=verbose, + dust_corr=dust_corr,dust_blue=dust_blue) + if success == -1: + logf.write("MINIMUM REQUIRED LINES: '[OII]3727','[OIII]5007','[NII]6584','[SII]6717'\n") + #print >> logf, "MINIMUM REQUIRED LINES: '[OII]3727','[OIII]5007','[NII]6584','[SII]6717'" + + for key in scales[i].mds.iterkeys(): + if key in res.keys(): + res[key][i] = scales[i].mds[key] + if res[key][i] is None: + res[key][i] = [float('NaN')] * len(sample) + return res + + +############################################################################## +## The main function. takes the flux and its error as input. +## filename - a string 'filename' common to the three flux files +## flux - np array of the fluxes +## err - the flux errors, must be the same dimension as flux +## nsample - the number of samples the code will generate. Default is 100 +## errmode - determines which method to choose the bin size. +## mode 'k' calculates this based on Knuth's rule (default) +## mode 'd' calculates this based on Doane's formula +## mode 's' calculates this based on sqrt of number of data +## mode 't' calculates this based on 2*n**1/3 +############################################################################## +#@profile +def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, + unpickle=False, dust_corr=True, dust_blue=False, verbose=False, fs=24): + global RUNSIM # ,BINMODE#,NOPLOT + assert(len(flux[0]) == len(err[0])), "flux and err must be same dimensions" + assert(len(flux['galnum']) == nm), "flux and err must be of declaired size" + assert(len(err['galnum']) == nm), "flux and err must be same dimensions" + + #increasing sample by 10% to assure robustness against rejected samples + newnsample = nsample + if nsample > 1: + newnsample = int(nsample + 0.1 * nsample) + + p = os.path.join(path, '..') + + ###retrieve the metallicity keys + Zs = metallicity.get_keys() + Zserr = metallicity.get_errkeys() + + ###make necessary paths for output files + if not os.path.exists(os.path.join(p, 'output', '%s' % name)): + os.makedirs(os.path.join(p, 'output', '%s' % name)) + if not os.path.exists(os.path.join(p, 'output', '%s' % name, 'hist')): + os.makedirs(os.path.join(p, 'output', '%s' % name, 'hist')) + binp = os.path.join(p, 'output', '%s' % name) + picklefile = os.path.join(binp, '%s_n%d.pkl' % (name, nsample)) + if VERBOSE: + print ("output files will be stored in ", binp) + if not CLOBBER and not NOPLOT: + for key in Zs: + for i in range(NM0, nm): + checkhist(name, key, nsample, i, binp) + if unpickle: + RUNSIM = False + if not os.path.isfile(picklefile): + raw_input("missing pickled file for this simulation: name, nsample.\nrun the MonteCarlo? Ctr-C to exit, Return to continue?\n") + RUNSIM = True + else: + pklfile = open(picklefile, 'rb') + res = pickle.load(pklfile) + + if RUNSIM: + ###Sample 'nsample' points from a gaussian centered on 0 with std 1 + mu = 0 + sigma = 1 + dargs = (mu, sigma) + if nsample == 1: + sample = [np.array([mu]) for i in range(NM0, nm)] + else: + sample = [errordistrib(dargs, newnsample) for i in range(NM0, nm)] + if sample == -1: + return -1 + + ###Start calculation### + ## the flux to be feed to the calculation will be + ## flux + error*i + ## where i is the sampled gaussian + if VERBOSE: + print ("Starting iteration") + + #initialize the dictionary + res = {} + reserr = {} + for key in Zs: + res[key] = [[] for i in range(NM0, nm)] + for key in Zserr: + reserr[key] = [[] for i in range(NM0, nm)] + #use only valid inputs + delkeys = [] + for k in bss[0].iterkeys(): + if k == 'flag' or k == 'galnum' or bss[0][k][1] == float('nan'): # or bss[1][k][1]==bss[0][k][1]: + delkeys.append(k) + for k in delkeys: + del bss[0][k] + del bss[1][k] + + import metscales as ms + nps = min(mpc.cpu_count() - 1 or 1, MAXPROCESSES) + + if multiproc and nps > 1: + scales = [ms.diagnostics(newnsample, logf, nps) for i in range(nm)] + + logf.write( "\n\n\nrunning on %d threads\n\n\n" % nps) + #print >> logf, "\n\n\nrunning on %d threads\n\n\n" % nps + second_args = [sample, flux, err, nm, bss, mds, VERBOSE, dust_corr, dust_blue, VERBOSE, res, scales, nps, logf] + pool = mpc.Pool(processes=nps) # depends on available cores + rr = pool.map(calc, itertools.izip(range(NM0, nm), itertools.repeat(second_args))) # for i in range(nm): result[i] = f(i, second_args) + + + for ri, r in enumerate(rr): + for kk in r.iterkeys(): + res[kk][ri] = r[kk][ri] + + pool.close() # not optimal! but easy + pool.join() + for key in scales[i].mds.iterkeys(): + if key in Zs: + res[key] = np.array(res[key]).T + elif key in reserr.keys(): + reserr[key] = np.array(reserr[key]).T + if res[key][i] is None: + res[key][i] = [float('NaN')] * len(sample) + + if VERBOSE: + print ("Iteration Complete") + else: + #looping over nm spectra + for i in range(NM0, nm): + scales = ms.diagnostics(newnsample, logf, nps) + logf.write("\n\n measurements %d\n"%(i + 1)) + #print >> logf, "\n\n measurements ", i + 1 + fluxi = {} + for k in bss[0].iterkeys(): + #shuffling the distribution each time + np.random.shuffle(sample[i]) + + logf.write('{0:15} '.format(k)) + logf.write('{0:0.2} +/- {1:0.2}\n'.format(flux[k][i], + err[k][i])) + #print >> logf, '{0:15} '.format(k), + #print >> logf, '{0:0.2} +/- {1:0.2}'.format(flux[k][i], err[k][i]) + fluxi[k] = flux[k][i] * np.ones(len(sample[i])) + err[k][i] * sample[i] + warnings.filterwarnings("ignore") + logf.write("\n") + #print >> logf, "" + + success = metallicity.calculation(scales, fluxi, nm, mds, 1, logf, + disp=VERBOSE, verbose=VERBOSE, + dust_corr=dust_corr, dust_blue=dust_blue) + if success == -1: + print ("MINIMUM REQUIRED LINES: [OII]3727 & [OIII]5007, or [NII]6584, and Ha & Hb if you want dereddening") + #continue + + + for key in scales.mds.iterkeys(): + if key not in Zs: + continue + res[key][i] = scales.mds[key] + if res[key][i] is None: + res[key][i] = [float('NaN')] * newnsample + elif len(res[key][i]) < newnsample: + res[key][i] = res[key][i] + [float('NaN')] * (newnsample - len(res[key][i])) + + for key in scales.mds.iterkeys(): + if key in Zs: + res[key] = np.array(res[key]).T + if VERBOSE: + print ("Iteration Complete") + + #"WE CAN PICKLE THIS!" + #pickle this realization + if not NOPICKLE: + pickle.dump(res, open(picklefile, 'wb')) + + from matplotlib.font_manager import findfont, FontProperties + + if 'Time' not in findfont(FontProperties()): + fs = 20 + if VERBOSE: + print ("FONT: %s, %d" % (findfont(FontProperties()), fs)) + + ###Bin the results and save### + print ("\n\n") + print ('{0:15} {1:20} {2:>13} -{3:>5} +{4:>5} {5:11} {6:>7}'\ + .format("SN", "diagnostic", "metallicity", "34%", "34%", + "(sample size:", '%d)' % nsample)) + for i in range(NM0, nm): + if ASCIIOUTPUT: + fi = open(os.path.join(binp, '%s_n%d_%d.txt' % (name, nsample, i + 1)), 'w') + fi.write("%s\t Median Oxygen abundance (12+log(O/H))\t 16th percentile\t 84th percentile\n" % name) + + boxlabels = [] + datas = [] + print ("\n\nmeasurement ", + "%d/%d : %s-------------------------------------------------------------" % (i + 1, nm, flux[i]['galnum'])) + for key in Zs: + if nsample == -1: + try: + if ~np.isnan(res[key][i][0]): + print ('{0:15} {1:20} {2:>13.3f} -{3:>7.3f} +{4:>7.3f} (no distribution)'.format(name + ' %d' % (i + 1), key, res[key][i][0], 0, 0)) + except IndexError: + pass + else: + reserr = None + try: + if sum(~np.isnan(res[key][: + ,i])) > 0: + if ASCIIDISTRIB: + with open(os.path.join(binp, '%s_n%d_%s_%d.csv' % (name, nsample, key, i + 1)), "wb") as fidist: + writer = csv.writer(fidist) + writer.writerow(res[key][: + ,i]) + if 'PM14' in key: + print (reserr['PM14err']) + reserr = np.sqrt(~np.nansum(reserr['PM14err'][: + ,i] ** 2)) + sh, data, kde = savehist(res[key][: + ,i], name, key, nsample, i, binp, nm, flux[:]['galnum'], verbose=verbose, fs=fs, reserr=reserr) + s = key + "\t " + sh + '\n' + if ASCIIOUTPUT: + fi.write(s) + if key not in ["E(B-V)", "logR23"]: + boxlabels.append(key.replace('_', ' ')) + datas.append(data) + if BINMODE == 'kd' and not NOPICKLE: + pickleKDEfile = os.path.join(binp + '/%s_n%d_%s_%d_KDE.pkl' % (name, nsample, key, i + 1)) + if VERBOSE: + print ("KDE files will be stored in ", pickleKDEfile) + pickle.dump(kde, open(pickleKDEfile, 'wb')) + except (IndexError, TypeError): + pass + #make box_and_whiskers plot + fig = plt.figure(figsize=(8, 15)) + fig.subplots_adjust(bottom=0.18, left=0.18) + ax = fig.add_subplot(111) + plt.grid() + if len(datas) == 0: + continue + + bp = ax.boxplot(datas, patch_artist=True) + for box in bp['boxes']: + box.set(color='#7570b3', linewidth=2) + box.set(facecolor='DarkOrange', alpha=0.4) + for whisker in bp['whiskers']: + whisker.set(color='#7570b3', linewidth=2) + for cap in bp['caps']: + cap.set(color='#7570b3', linewidth=2) + for median in bp['medians']: + median.set(color='k', linewidth=2) + for flier in bp['fliers']: + flier.set(marker='o', color='#7570b3', alpha=0.4, mfc='#7570b3') + plt.title("measurement %d: %s" % (i + 1, flux[i]['galnum'])) + plt.xticks(range(1, len(boxlabels) + 1), boxlabels, rotation=90, fontsize=fs - 5) + plt.fill_between(range(1, len(boxlabels) + 1), [8.76] * len(boxlabels), [8.69] * len(boxlabels), facecolor='black', alpha=0.3) + plt.text(1.2, 8.705, "Solar Oxygen Abundance", alpha=0.7) + plt.gca().yaxis.set_major_formatter(FormatStrFormatter('%.2f')) + plt.ylabel('12+log(O/H)', fontsize=fs) + plt.savefig(binp + "/" + name + "_boxplot_n%d_%d.pdf" % (nsample, i + 1), format='pdf') + if ASCIIOUTPUT: + fi.close() + if VERBOSE: + print ("uncertainty calculation complete") + + #del datas + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('name', metavar='', type=str, help="the SN file name (root of the _min,_max file names") + parser.add_argument('nsample', metavar='N', type=int, help="number of iterations, minimum 100 (or 1 for no MC sampling)") + parser.add_argument('--clobber', default=False, action='store_true', help="replace existing output") + parser.add_argument('--binmode', default='k', type=str, choices=['d', 's', 'k', 't', 'bb', 'kd'], help='''method to determine bin size + {d: Duanes formula, s: n^1/2, t: 2*n**1/3(default), k: Knuth's rule, + bb: Bayesian blocks, kd: Kernel Density}''') + parser.add_argument('--path', default=None, type=str, help='''input/output path (must contain the input _max.txt and + _min.txt files in a subdirectory sn_data)''') + parser.add_argument('--unpickle', default=False, action='store_true', help="read the pickled realization instead of making a new one") + + parser.add_argument('--verbose', default=False, action='store_true', help="verbose mode") + parser.add_argument('--log', default=None, type=str, help="log file, if not passed defaults to standard output") + parser.add_argument('--nodust', default=False, action='store_true', help=" don't do dust corrections (default is to do it)") + parser.add_argument('--bluedust', default=False, action='store_true', help=" use Hg/Hb for dust correction (default is Hb/Ha)") + parser.add_argument('--noplot', default=False, action='store_true', help=" don't plot individual distributions (default is to plot all distributions)") + parser.add_argument('--asciiout', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") + parser.add_argument('--asciidistrib', default=False, action='store_true', help=" write distribution in an ascii output (default is not to)") + parser.add_argument('--md', default='all', type=str, help='''metallicity diagnostic to calculate. + default is 'all', options are: + D02, Z94, M91, C01, P05, M08, M08all, M13, PP04, D13, D13all, KD02, DP00 (deprecated), P01, D16, PG16, C17''') + parser.add_argument('--multiproc', default=False, action='store_true', help=" multiprocess, with number of threads max(available cores-1, MAXPROCESSES)") + args = parser.parse_args() + + global CLOBBER + global VERBOSE + global BINMODE + global ASCIIOUTPUT + global ASCIIDISTRIB + global NOPLOT + CLOBBER = args.clobber + VERBOSE = args.verbose + BINMODE = args.binmode + NOPLOT = args.noplot + ASCIIOUTPUT = args.asciiout + ASCIIDISTRIB = args.asciidistrib + + if args.unpickle and NOPICKLE: + args.unpickle = False + raw_input("cannot use pickle on this machine, we won't save and won't read saved pickled realizations. Ctr-C to exit, Return to continue?\n") + + if args.path: + path = args.path + else: + assert (os.getenv("MCMetdata")), ''' the _max, _min (and _med) data must live in a folder named sn_data. + pass a path to the sn_data folder, or set up the environmental variable + MCMetdata pointing to the path where sn_data lives ''' + path = os.getenv("MCMetdata") + assert(os.path.isdir(path)), "pass a path or set up the environmental variable MCMetdata pointing to the path where the _min _max _med files live" + if args.nsample == 1: + print ("CALCULATING METALLICITY WITHOUT GENERATING MC DISTRIBUTIONS") + if args.nsample == 1 or args.nsample >= 10: + fi = input_data(args.name, path=path) + if fi != -1: + logf = smart_open(args.log) + run(fi, args.nsample, args.md, args.multiproc, logf, + unpickle=args.unpickle, + dust_corr=(not args.nodust), dust_blue=(args.bluedust), + verbose=VERBOSE) + if args.log: + logf.close() + else: + print ("nsample must be at least 100") + +if __name__ == "__main__": + if PROFILING: + import cProfile + cProfile.run("main()") + else: + main() diff --git a/build/scripts-3.6/metallicity.py b/build/scripts-3.6/metallicity.py new file mode 100755 index 0000000..d5d2e79 --- /dev/null +++ b/build/scripts-3.6/metallicity.py @@ -0,0 +1,297 @@ +############################################################################## +## Calculates oxygen abundance (here called metalicity) based on strong emission lines, +## based on code originally written in IDL by Lisa Kewley (Kewley & Ellison 2008). Outputs +## oxygen abundance in many different diagnostics (see Bianco et al. 2016). +## +##new calculation based on the most recent version of the .pro file. +## +##inputs: +## measured - flux data, must be the format returned by readfile() +## num - number of spectra for which to calculate metallicity, also returned by readfile() +## outfilename - the name of the file the results will be appended to +## red_corr - reddening correction flag - True by default +## disp - if True prints the results, default False +############################################################################## + +from __future__ import print_function +import sys +import os +import numpy as np + +IGNOREDUST = False +MP = True +FIXNEGATIVES = True # set to true if no negative flux measurements should be allowed. all negative flux measurements are set to 0 +DROPNEGATIVES = False # set to true if no negative flux measurements should be allowed. all negative flux measurements are dropped +if DROPNEGATIVES: + FIXNEGATIVES = False + +##list of metallicity methods, in order calculated +Zs = ["E(B-V)", # based on Halpha, Hbeta + "E(B-V)blue", # based on Hbeta, Hgamma + "C(Hbeta)", + "logR23", # Hbeta, [OII]3727, [OIII]5007, [OIII]4959 + + "D02", # Halpha, [NII]6584 + "Z94", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "M91", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "C01_N2S2", # [OII]3727, [OIII]5007, [NII]6584, [SII]6717 + "C01_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + + "P05", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "P01", # available but deprecated + "PP04_N2Ha", # Halpha, [NII]6584 + "PP04_O3N2", # Halpha, Hbeta,[OIII]5007, [NII]6584 + "DP00", # S23 available but deprecated + "P10_ONS", "P10_ON", + "M08_R23", "M08_N2Ha", "M08_O3Hb", "M08_O2Hb", "M08_O3O2", "M08_O3N2", + "M13_O3N2", "M13_N2Ha", + "D13_N2S2_O3S2", "D13_N2S2_O3Hb", + "D13_N2S2_O3O2", "D13_N2O2_O3S2", + "D13_N2O2_O3Hb", "D13_N2O2_O3O2", + "D13_N2Ha_O3Hb", "D13_N2Ha_O3O2", + "KD02_N2O2", # Halpha, Hbeta, [OII]3727, [NII]6584 + "KD02_N2S2", + "KK04_N2Ha", # Halpha, Hbeta, [OII]3727, [NII]6584 + "KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 ) + "KD02comb", + "PM14", + "PMC09_N2Ha", + "B07_N2O2", # Bresolin (2007) N2O2 + "D16", # Dopita (2016) ONS + "PG16_R","PG16_S", # Pilyugin & Grebel (2016) + "C17_O3O2", "C17_O3N2","C17_N2Ha","C17_O2Hb","C17_O3Hb","C17_R23"] + + +Zserr = ['PM14err'] # ,"KK04comb"] +#'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha' + + +def get_keys(): + return Zs + + +def get_errkeys(): + return Zserr + + +def printsafemulti(string, logf, nps): + #this is needed because dealing with a log output with multiprocessing + #is painful. but it introduces a bunch of if checks. + if nps == 1: + logf.write(string + "\n") + #print >> logf, string + else: + print (string) + + +############################################################################## +##fz_roots function as used in the IDL code +############################################################################## + +#@profile +def calculation(mscales, measured, num, mds, nps, logf, dust_corr=True, + dust_blue=False, disp=False, verbose=False): + + global IGNOREDUST + mscales.setdustcorrect() + raw_lines = {} + raw_lines['[OIII]5007'] = np.array([float('NaN')]) + raw_lines['Hb'] = np.array([float('NaN')]) + raw_lines['Hg'] = np.array([float('NaN')]) + raw_lines['Hz'] = np.array([float('NaN')]) + for k in measured.iterkeys(): + #kills all non-finite terms + measured[k][~(np.isfinite(measured[k][:]))] = 0.0 + + if FIXNEGATIVES: + measured[k][measured[k] < 0] = 0.0 + + if DROPNEGATIVES: + measured[k][measured[k] < 0] = np.nan + + raw_lines[k] = measured[k] + ######we trust QM better than we trust the measurement of the [OIII]4959 + ######which is typical low S/N so we set it to [OIII]5007/3. + ######change this only if youre spectra are very high SNR + raw_lines['[OIII]4959'] = raw_lines['[OIII]5007'] / 3. + raw_lines['[OIII]49595007'] = raw_lines['[OIII]4959'] + raw_lines['[OIII]5007'] + mscales.setHabg(raw_lines['Ha'], raw_lines['Hb'], raw_lines['Hg']) + + #if Ha or Hb is zero, cannot do red correction + if dust_corr and mscales.hasHa and mscales.hasHb and not dust_blue: + with np.errstate(invalid='ignore'): + mscales.calcEB_V() + elif dust_blue and mscales.hasHg and mscales.hasHb: + with np.errstate(invalid='ignore'): + mscales.calcEB_Vblue() + print('----- Using Hg/Hb for E(B-V)_blue estimate. -----') + elif dust_corr and not IGNOREDUST: + + if nps > 1: + print ('''WARNING: reddening correction cannot be done + without both H_alpha and H_beta measurement!!''') + + else: + response = raw_input('''WARNING: reddening correction cannot be done without both H_alpha and H_beta measurement!! + Continuing without reddening correction? [Y/n]\n''').lower() + assert(not (response.startswith('n'))), "please fix the input file to include Ha and Hb measurements" + + IGNOREDUST = True + dust_corr = False + mscales.mds['E(B-V)'] = np.ones(len(raw_lines['Ha'])) * 1e-5 + else: + mscales.unsetdustcorrect() + mscales.mds['E(B-V)'] = np.ones(len(raw_lines['Ha'])) * 1e-5 + + for k in ['[OII]3727', '[OIII]5007', '[OI]6300', '[OIII]4959', + '[SII]6717', '[SII]6731', '[SIII]9069', '[SIII]9532' + '[OII]3727', '[OIII]5007', '[OI]6300', '[OIII]4959', + '[NII]6584', '[SIII]9532']: + if k not in raw_lines or (len(raw_lines[k]) == 1 and np.isnan(raw_lines[k][0])): + raw_lines[k] = np.array([0.] * num) + + mscales.setOlines(raw_lines['[OII]3727'], raw_lines['[OIII]5007'], raw_lines['[OI]6300'], raw_lines['[OIII]4959']) + mscales.setSII(raw_lines['[SII]6717'], raw_lines['[SII]6731'], raw_lines['[SIII]9069'], raw_lines['[SIII]9532']) + mscales.setNII(raw_lines['[NII]6584']) + + if mscales.checkminimumreq(dust_corr, IGNOREDUST) == -1: + return -1 + + mscales.calcNIIOII() + mscales.calcNIISII() + + mscales.calcR23() + #mscales.calcS23() + + mscales.initialguess() + mds = mds.split(',') + + + #mscales.printme() + if verbose: + print ("calculating metallicity diagnostic scales: ", mds) + if 'all' in mds: + mscales.calcD02() + if os.getenv("PYQZ_DIR"): + cmd_folder = os.getenv("PYQZ_DIR") + '/' + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + mscales.calcpyqz() + else: + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable : + export PYQZ_DIR="your/path/where/pyqz/resides/ in bash, for example, if you want this scale. \n''', logf, nps) + + mscales.calcZ94() + mscales.calcM91() + + mscales.calcPP04() + + #mscales.calcP05() + #mscales.calcP10() + + mscales.calcM08() + mscales.calcM13() + + mscales.calcKD02_N2O2() + mscales.calcKK04_N2Ha() + + mscales.calcKK04_R23() + mscales.calcKDcombined() + + mscales.calcD16() + mscales.calcPG16() + mscales.calcC17() + + if 'DP00' in mds: + mscales.calcDP00() + if 'P01' in mds: + mscales.calcP01() + + if 'D02' in mds: + mscales.calcD02() + if 'D13' in mds: + if os.getenv("PYQZ_DIR"): + cmd_folder = os.getenv("PYQZ_DIR") + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + #mscales.calcpyqz() + #in order to see the original pyqz plots + #call pyqz with option plot=True by + #using the commented line below instead + mscales.calcpyqz(plot=disp) + else: + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable + PYQZ_DIR if you want this scale. ''', logf, nps) + + if 'D13all' in mds: + if os.getenv("PYQZ_DIR"): + cmd_folder = os.getenv("PYQZ_DIR") + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + #mscales.calcpyqz() + #in order to see the original pyqz plots + #call pyqz with option plot=True by + #using the commented line below instead + mscales.calcpyqz(plot=disp, allD13=True) + else: + printsafemulti('''WARNING: CANNOT CALCULATE pyqz: + set path to pyqz as environmental variable + PYQZ_DIR if you want this scale. ''', logf, nps) + + if 'PM14' in mds: + if os.getenv("HIICHI_DIR"): + cmd_folder = os.getenv("HIICHI_DIR") + '/' + if cmd_folder not in sys.path: + sys.path.insert(0, cmd_folder) + mscales.calcPM14() + if 'PP04' in mds: + mscales.calcPP04() + if 'Z94' in mds: + mscales.calcZ94() + if 'M91' in mds: + mscales.calcM91() + if 'P10' in mds: + mscales.calcP10() + if 'M13' in mds: + mscales.calcM13() + if 'M08all' in mds: + mscales.calcM08(allM08=True) + elif 'M08' in mds: + mscales.calcM08() + if 'P05' in mds: + mscales.calcP05() + if 'C01' in mds: + mscales.calcC01_ZR23() + if 'KD02' in mds: + mscales.calcKD02_N2O2() + mscales.calcKK04_N2Ha() + mscales.calcKK04_R23() + mscales.calcKDcombined() + + # Modifications by jch [5/2017, Santiago] to include newer scales. + if 'B07_N2O2' in mds: + # Bresolin 2007 N2O2 + mscales.calcB07() + if 'D16' in mds: + # Dopita+ 2016 ONS + mscales.calcD16() + if 'PG16' in mds: + # Pilyugin & Grebel 2016 + mscales.calcPG16() + if 'C17' in mds: + # Curti+ 2017 + mscales.calcC17() + if 'C17all' in mds: + # Curti+ 2017 + mscales.calcC17(allC17=True) + if 'PMC09_N2Ha' in mds: + # Perez-Montero & Contini (2009) + mscales.calcPMC09() + + if 'KK04_N2Ha' in mds: + mscales.calcKD02_N2O2() + mscales.calcKK04_N2Ha() + elif 'KD02_N2O2' in mds: + mscales.calcKD02_N2O2() diff --git a/build/scripts-3.6/metscales.py b/build/scripts-3.6/metscales.py new file mode 100755 index 0000000..6a57478 --- /dev/null +++ b/build/scripts-3.6/metscales.py @@ -0,0 +1,1558 @@ +from __future__ import print_function +import numpy as np +#import sys +import scipy.stats as stats +import numpy.polynomial.polynomial as nppoly +from metallicity import get_keys, printsafemulti + +import pdb + +niter = 5 # number of iteations+1 for KD02 methods + +k_Ha = 2.535 # CCM Rv=3.1 +k_Hb = 3.609 # CCM Rv=3.1 +k_Hg = 4.400 # CCM Rv=3.1 + +#k_O1=2.661 # CCM Rv=3.1 +k_O2 = 4.771 # CCM Rv=3.1 +k_O35007 = 3.341 # CCM Rv=3.1 +k_O34959 = 3.384 # CCM Rv=3.1 +k_O3 = (k_O35007 + k_O34959) / 2. + +k_N2 = 2.443 # CCM Rv=3.1 +k_S2 = 2.381 # CCM Rv=3.1 + +k_S3 = 1 # guess for CCM Rv=3.1 + +global DUSTCORRECT +DUSTCORRECT = True + +''' +R23_coef=np.zeros((5,7)) # coefficients from model grid fits +R23c0=[-3267,-3727.42,-4282.30,-4745.18,-4516.46,-3509.63,-1550.53] +R23_coef[:,0]=[-3267.93,1611.04,-298.187,24.5508,-0.758310] # q=5e6 +R23_coef[:,1]=[-3727.42,1827.45,-336.340,27.5367,-0.845876] # q=1e7 +R23_coef[:,2]=[-4282.30,2090.55,-383.039,31.2159,-0.954473] # q=2e7 +R23_coef[:,3]=[-4745.18,2309.42,-421.778,34.2598,-1.04411] # q=4e7 +R23_coef[:,4]=[-4516.46,2199.09,-401.868,32.6686,-0.996645] # q=8e7 +R23_coef[:,5]=[-3509.63,1718.64,-316.057,25.8717,-0.795242] # q=1.5e8 +R23_coef[:,6]=[-1550.53,784.262,-149.245,12.6618,-0.403774] # q=3e8 + + +N2S2_coef=np.zeros((5,7)) # coefficients from model grid fits +N2S2c0=[-1042.47,-1879.46,-2027.82,-2080.31,-2162.93,-2368.56,-2910.63] +N2S2_coef[:,0]=[-1042.47,521.076,-97.1578,8.00058,-0.245356] +N2S2_coef[:,1]=[-1879.46,918.362,-167.764,13.5700,-0.409872] +N2S2_coef[:,2]=[-2027.82,988.218,-180.097,14.5377,-0.438345] +N2S2_coef[:,3]=[-2080.31,1012.26,-184.215,14.8502,-0.447182] +N2S2_coef[:,4]=[-2162.93,1048.97,-190.260,15.2859,-0.458717] +N2S2_coef[:,5]=[-2368.56,1141.97,-205.908,16.4451,-0.490553] +N2S2_coef[:,6]=[-2910.63,1392.18,-249.012,19.7280,-0.583763] + +O3O2_coef=np.zeros((4,8)) # coefficients from model grid fits +O3O2c0=[-36.9772,-74.2814,-36.7948,-81.1880,-52.6367,-86.8674,-24.4044,49.4728] +O3O2_coef[:,0]=[-36.9772,10.2838,-0.957421,0.0328614] #z=0.05 +O3O2_coef[:,1]=[-74.2814,24.6206,-2.79194,0.110773] # z=0.1 +O3O2_coef[:,2]=[-36.7948,10.0581,-0.914212,0.0300472] # z=0.2 +O3O2_coef[:,3]=[-81.1880,27.5082,-3.19126,0.128252] # z=0.5 +O3O2_coef[:,4]=[-52.6367,16.0880,-1.67443,0.0608004] # z=1.0 +O3O2_coef[:,5]=[-86.8674,28.0455,-3.01747,0.108311] # z=1.5 +O3O2_coef[:,6]=[-24.4044,2.51913,0.452486,-0.0491711] # z=2.0 +O3O2_coef[:,7]=[49.4728,-27.4711,4.50304,-0.232228] # z=3.0 +''' + +M08_coefs = {'R23': [0.7462, -0.7149, -0.9401, -0.6154, -0.2524], + 'N2Ha': [-0.7732, 1.2357, -0.2811, -0.7201, -0.3330], + 'O3Hb': [0.1549, -1.5031, -0.9790, -0.0297], + 'O3O2': [-0.2839, -1.3881, -0.3172], + 'O2Hb': [0.5603, 0.0450, -1.8017, -1.8434, -0.6549], + 'O3N2': [0.4520, -2.6096, -0.7170, 0.1347]} + +C17_coefs = {'N2Ha': [-0.489, 1.513,-2.554,-5.293,-2.867], + 'O3O2': [-0.691,-2.944,-1.308], + 'O3N2': [0.281,-4.765,-2.268], + 'O2Hb': [0.418,-0.961,-3.505,-1.949], + 'O3Hb': [-0.277,-3.549,-3.593,-0.981], + 'R23': [0.527,-1.569,-1.652,-0.421]} + +#this is to check the Maiolino coefficients and find the split maximum of the cirves with degeneracy +''' +import pylab as pl + +x=np.arange(7.0,9.5,0.1) + +for k in M08_coefs.iterkeys(): + print k,max(nppoly.polyval(x-8.69,M08_coefs[k])),x[nppoly.polyval(x-8.69,M08_coefs[k])==max(nppoly.polyval(x-8.69,M08_coefs[k]))] + pl.plot(x,nppoly.polyval(x-8.69,M08_coefs[k]), label=k+' max:%.1f'%x[nppoly.polyval(x-8.69,M08_coefs[k])==max(nppoly.polyval(x-8.69,M08_coefs[k]))]) + +print nppoly.polyval(8.4-8.69,M08_coefs['N2Ha']) +pl.ylabel("log R") +pl.xlabel("12+log(O/H)") +pl.legend(loc=3) +pl.show() +''' + + +class diagnostics: + def __init__(self, num, logf, nps): + self.nm = num + self.Ha = None + self.Hb = None + self.Hg = None + + self.hasHa, self.hasHb, self.hasHg = False, False, False + self.hasO2, self.hasO3 = False, False + self.hasS2, self.hasN2 = False, False + + self.hasO3Hb = False + self.hasO3O2 = False + + self.hasN2O2 = False + self.hasN2S2 = False + + self.hasS26731 = False + self.hasS39532 = False + self.hasS39069 = False + self.hasS2Hb = False + + self.N2O2_roots = None + #other lines calculated and repeatedly used + self.P = None + self.R2 = None + self.R3 = None + self.R23 = None + self.S2Hb = None + self.N2 = None + self.N2S2 = None + self.O23727 = None + self.O35007 = None + self.N26584 = None + self.S26717 = None + self.S26731 = None + self.S39069 = None + self.S39532 = None + + self.O34959p5007 = None + self.O35007O2 = None + self.O2O35007 = None + + self.logR23 = None + self.logN2O2 = None + self.logN2S2 = None + self.logO3O2 = None + self.logS23 = None + #self.logS3S2=None + + self.logO2Hb = None + self.logO3Hb = None + self.logN2Ha = None + self.logS2Ha = None + + self.logO35007O2 = None + self.logO2O35007 = None + + self.logO3O2sq = None + self.logq = None + self.Z_init_guess = None + self.N2O2_coef0 = 1106.8660 + + self.OIII_OII = None + self.OIII_Hb = None + self.OIII_SII = None + + self.NII_OII = None + self.NII_SII = None + #metallicity diagnostics to be returned + + self.mds = {} + for Z in get_keys(): + self.mds[Z] = None + + #setting output file + self.logf = logf + self.nps = nps + + def printme(self, verbose=False): + try: + print ("\nHa", np.mean(self.Ha)) + if verbose: + print (self.Ha) + except (IndexError, TypeError): + pass + try: + print ("\nHb", np.mean(self.Hb)) + if verbose: + print (self.Hb) + except (IndexError, TypeError): + pass + try: + print ("\nO2", np.mean(self.O23727)) + if verbose: + print (self.O23727) + except (IndexError, TypeError): + pass + try: + print ("\nO3", np.mean(self.O35007)) + if verbose: + print (self.O35007) + except (IndexError, TypeError): + pass + try: + print ("\nO34959", np.mean(self.O34959)) + if verbose: + print (self.O34959) + except (IndexError, TypeError): + pass + try: + print ("\nZ94", np.mean(self.mds['Z94'])) + if verbose: + print (self.mds['Z94']) + except (IndexError, TypeError): + pass + try: + print ("\nR23", np.mean(self.R23)) + if verbose: + print (self.R23) + except (IndexError, TypeError): + pass + try: + print ("\nlog(R23)", np.mean(self.logR23)) + if verbose: + print (self.logR23) + except (TypeError, IndexError): + pass + try: + print ("\nlog([NII][OII])", stats.nanmean(self.logN2O2)) + if verbose: + print (self.logN2O2) + except (TypeError, IndexError): + pass + try: + print ("\nlog([OIII][OII])", stats.nanmean(self.logO3O2)) + if verbose: + print (self.logO3O2) + except (TypeError, IndexError): + pass + for k in self.mds.iterkeys(): + print ("\n", k) + try: + print (stats.nanmean(self.mds[k]), np.stdev(self.mds[k])) + except (IndexError, TypeError): + if verbose: + print (self.mds[k]) + + def checkminimumreq(self, red_corr, ignoredust): + if red_corr and not ignoredust: + if not self.hasHa: + return -1 + if not self.hasHb: + return -1 + #if not self.hasO2 : + # return -1 + #if not self.hasO3 : + # return -1 + #if not self.hasS2 : + # return -1 + if not self.hasN2 and not (self.hasO2 and self.hasO3): + return -1 + + def fz_roots(self, coef): + if len(coef.shape) == 1: + coef[~(np.isfinite(coef))] = 0.0 + rts = np.roots(coef[::-1]) + if rts.size == 0: + printsafemulti('WARNING: fz_roots failed', self.logf, self.nps) + rts = np.zeros(coef.size - 1) + return rts + + else: + rts = np.zeros((coef.shape[0], coef.shape[1] - 1), dtype=complex) + coef[~(np.isfinite(coef))] = 0.0 + + for i in range(coef.shape[0]): + rts[i] = np.roots(coef[i][::-1]) # ::-1][0]) + return rts + + def setdustcorrect(self): + global DUSTCORRECT + DUSTCORRECT = True + + def unsetdustcorrect(self): + global DUSTCORRECT + DUSTCORRECT = False + + def dustcorrect(self, l1, l2, flux=False): + #global DUSTCORRECT + if DUSTCORRECT: + if not flux: + return 0.4 * self.mds['E(B-V)'] * (l1 - l2) + return 10 ** (0.4 * self.mds['E(B-V)'] * (l1 - l2)) + else: + if not flux: + return 0 + return 1.0 + + def setHabg(self, Ha, Hb, Hg): + self.Ha = Ha + self.Hb = Hb + self.Hg = Hg + if sum(self.Ha > 0): + self.hasHa = True + if sum(self.Hb > 0): + self.hasHb = True + if sum(self.Hg > 0): + self.hasHg = True + + def setOlines(self, O23727, O35007, O16300, O34959): + self.O23727 = O23727 + self.O35007 = O35007 + self.O16300 = O16300 + + if sum(self.O35007 > 0): + self.hasO3 = True + if sum(self.O23727 > 0): + self.hasO2 = True + + if self.hasO2 and self.hasO3: + self.O35007O2 = (self.O35007 / self.O23727) * self.dustcorrect(k_O3, k_O2, flux=True) + self.O2O35007 = (self.O23727 / self.O35007) * self.dustcorrect(k_O2, k_O3, flux=True) + + self.logO35007O2 = np.log10(self.O35007O2) + self.logO2O35007 = np.log10(self.O2O35007) + + #self.logO2O35007Hb=np.log10((self.O23727+self.O35007)/self.Hb) + # ratios for other diagnostics - slightly different ratios needed + # commenting since it is not used + #if self.hasHb: + # self.logO2O35007Hb = np.log10( + # (self.O23727 / self.Hb) * self.dustcorrect(k_O2, + # k_Hb, flux=True) + # + (self.O35007 / self.Hb) * self.dustcorrect(k_O35007, + # k_Hb, flux=True)) + + else: + printsafemulti("WARNING: needs O lines and and Ha/b: did you run setHab()?", self.logf, self.nps) + if self.hasHb: + if self.hasO2: + self.logO2Hb = np.log10(self.O23727 / self.Hb) + \ + self.dustcorrect(k_O2, k_Hb, flux=False) + # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + if self.hasO3: + self.O3Hb = (self.O35007 / self.Hb) * \ + self.dustcorrect(k_O35007, k_Hb, flux=True) + # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + self.logO3Hb = np.log10(self.O3Hb) + self.hasO3Hb = True + + + if self.hasO2 and self.hasO3: + self.OIII_OII = np.log10(self.O35007 / self.O23727 * self.dustcorrect(k_O35007, k_O2, flux=True)) + if O34959 is not None and sum(O34959 > 0) > 0: + self.O34959p5007 = (O34959 + self.O35007) + self.logO3O2 = np.log10((self.O34959p5007) / self.O23727)\ + + self.dustcorrect(k_O3, k_O2, flux=False) + #this is useful when we get logq + self.hasO3O2 = True + if self.hasHb: + self.OIII_Hb = np.log10(self.O35007 / self.Hb * self.dustcorrect(k_O35007, k_Hb, flux=True)) + + def setNII(self, N26584): + if N26584 is not None and sum(N26584 > 0): + self.N26584 = N26584 + self.hasN2 = True + if self.hasHa: + self.logN2Ha = np.log10(self.N26584 / self.Ha) # +self.dustcorrect(k_N2,k_Ha,flux=True) + #lines are very close: no dust correction + #Note: no dust correction cause the lies are really close! + else: + printsafemulti("WARNING: needs NII6584 and Ha to calculate NIIHa: did you run setHab()?", self.logf, self.nps) + if self.hasS2 and self.hasS26731 and self.hasN2: + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_S2,flux=True) + #lines are very close: no dust correction + if self.hasO2 and self.hasN2: + self.NII_OII = np.log10(self.N26584 / self.O23727 * self.dustcorrect(k_N2, k_O2, flux=True)) + + def setSII(self, S26717, S26731, S39069, S39532): + if S26717 is not None and sum(S26717 > 0) > 0: + self.S26717 = S26717 + self.hasS2 = True + + if self.hasHa: + self.logS2Ha = np.log10(self.S26717 / self.Ha) + self.dustcorrect(k_S2, k_Ha, flux=False) + else: + printsafemulti("WARNING: needs SII6717 and Ha to calculate SIIHa: did you run setHab() and setS()?", self.logf, self.nps) + if S26731 is not None and sum(S26731 > 1e-9) > 0: + self.S26731 = S26731 + self.hasS26731 = True + if S39069 is not None and sum(S39069 > 1e-9) > 0: + self.S39069 = S39069 + self.hasS39069 = True + if S39532 is not None and sum(S39532 > 1e-9) > 0: + self.S39532 = S39532 + self.hasS39532 = True + if self.hasS2: + if self.hasN2 and self.NII_SII is None and self.hasS26731: + self.NII_SII = np.log10(self.N26584 / (self.S26717 + self.S26731)) # +self.dustcorrect(k_N2,k_O2,flux=True) + #lines are very close: no dust correction + if self.hasO3 and self.OIII_SII is None and self.hasS26731: + self.OIII_SII = np.log10(self.O35007 / (self.S26717 + self.S26731) * self.dustcorrect(k_O3, k_S2, flux=True)) + + #@profile + def calcEB_V(self): + printsafemulti("calculating E(B-V)", self.logf, self.nps) + a2b_intrinsic = 2.86 + self.mds['E(B-V)'] = np.log10(a2b_intrinsic * self.Hb / self.Ha) / (0.4 * (k_Ha - k_Hb)) # E(B-V) + self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 + + #@profile + def calcEB_Vblue(self): + printsafemulti("calculating E(B-V)_blue", self.logf, self.nps) + b2g_intrinsic = 2.145 + self.mds['E(B-V)'] = np.log10(b2g_intrinsic * self.Hg / self.Hb) / (0.4 * (k_Hb - k_Hg)) # E(B-V) + self.mds['E(B-V)'][self.mds['E(B-V)'] <= 0] = 1e-5 + + #@profile + def calcNIISII(self): + if self.hasS2 and self.hasN2: + self.N2S2 = self.N26584 / self.S26717 * self.dustcorrect(k_N2, k_S2, flux=True) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) + self.logN2S2 = np.log10(self.N26584 / self.S26717) + self.dustcorrect(k_N2, k_S2, flux=False) # 0.4*self.mds['E(B-V)']*(k_N2-k_S2) + self.hasN2S2 = True + else: + printsafemulti("WARNING: needs SII6717 and NII6584 to calculate NIISII: did you run setN2() and setS?", self.logf, self.nps) + + #@profile + def calcNIIOII(self): + if self.hasN2 and self.hasO2: + self.logN2O2 = np.log10(self.N26584 / self.O23727) + self.dustcorrect(k_N2, k_O2, flux=False) + + self.hasN2O2 = True + if not self.hasN2O2 or np.mean(self.logN2O2) < -1.2: + + try: + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods should only be used for log([NII]6564/[OII]3727) > -1.2, + the mean log([NII]6564/[OII]3727)= %f''' % np.mean(self.logN2O2), self.logf, self.nps) + except TypeError: + printsafemulti('''WARNING: the KD02 and KK04 (+M08) methods + should only be used for log([NII]6564/[OII]3727) >-1.2, + the mean log([NII]6564/[OII]3727)= %s''' % self.logN2O2, self.logf, self.nps) + + if not self.hasN2O2: + self.N2O2_roots = np.zeros(self.nm) + float('NaN') + else: + N2O2_coef = np.array([[self.N2O2_coef0, -532.15451, 96.373260, -7.8106123, 0.23928247]] * self.nm).T # q=2e7 line (approx average) + N2O2_coef[0] -= self.logN2O2 + N2O2_coef = N2O2_coef.T + # finding roots for == (4) + self.N2O2_roots = np.array([self.fz_roots(N2O2_coef)])[0] + + #@profile + def calcR23(self): + printsafemulti("calculating R23", self.logf, self.nps) + + #R23 NEW Comb, [NII]/Ha: KK04 = Kobulnicky & Kewley, 2004, submitted' + if self.hasO3 and self.hasO2 and self.hasHb: + self.R2 = (self.O23727 / self.Hb) * self.dustcorrect(k_O2, k_Hb, flux=True) + self.R3 = (self.O34959p5007 / self.Hb) * self.dustcorrect(k_O3, k_Hb, flux=True) + self.R23 = self.R2 + self.R3 + self.logR23 = np.log10(self.R23) + self.mds['logR23'] = self.logR23 + #note that values of logR23 > 0.95 are unphysical. + #you may choose to uncomment the line below + #self.logR23[self.logR23>0.95]=0.95 + else: + printsafemulti("WARNING: need O3, O2, Hb", self.logf, self.nps) + + #@profile + def calcS23(self): + printsafemulti("calculating S23", self.logf, self.nps) + #the original code here uses S267176731, + #which is however set to 6717 as default + #Vilchez & Esteban (1996) + if self.hasS2: + if self.hasS39069 and self.hasHb: + self.logS23 = np.log10((self.S26717 / self.Hb) * + self.dustcorrect(k_S2, k_Hb, flux=True) + + (self.S39069 / self.Hb) * + self.dustcorrect(k_S3, k_Hb, flux=True)) + + #self.logS3S2=np.log10(S39069/self.S26717)+self.dustcorrect(k_S3,k_S2) + + ##@profile + def calclogq(self, Z): + if not self.hasO3O2: + printsafemulti("WARNING: needs O3,O2,Hb to calculate logq properly.", self.logf, self.nps) + return -1 + if self.logO3O2sq is None: + self.logO3O2sq = self.logO3O2 ** 2 + return (32.81 - 1.153 * self.logO3O2sq + Z * (-3.396 - 0.025 * self.logO3O2 + 0.1444 * self.logO3O2sq)) / (4.603 - 0.3119 * self.logO3O2 - \ + 0.163 * self.logO3O2sq + Z * (-0.48 + 0.0271 * self.logO3O2 + 0.02037 * self.logO3O2sq)) + + ##@profile + def initialguess(self): + # Initial Guess - appearing in LK code as of Nov 2006 + # upper branch: if no lines are available, metallicity is set to 8.7 + self.Z_init_guess = np.zeros(self.nm) + 8.7 + # use [N2]/Ha + if self.hasHa and self.hasN2: + self.Z_init_guess[(self.logN2Ha < -1.3) & (self.N26584 != 0.0)] = 8.2 + self.Z_init_guess[(self.logN2Ha < -1.1) & (self.logN2Ha >= -1.3) & (self.N26584 != 0.0)] = 8.4 + #A1 KE08 + self.Z_init_guess[(self.logN2Ha >= -1.1) & (self.N26584 != 0.0)] = 8.7 + #use [N2]/[O2] + if self.hasN2 and self.hasO2: + N2O2 = np.zeros(self.nm) + float('nan') + if self.hasHb: + ###FED CHECK THIS! + N2O2 = self.N26584 * self.Ha * self.Hb * self.O23727 + if not self.hasN2O2: + printsafemulti("WARNING: must calculate logN2O2 first", self.logf, self.nps) + self.calcNIIOII() + self.Z_init_guess[(self.logN2O2 < -1.2) & (N2O2 != 0.0)] = 8.2 + # at logN2O2<-1.2 using low-Z gals, A1 KE08 + self.Z_init_guess[(self.logN2O2 >= -1.2) & (N2O2 != 0.0)] = 8.7 + + # at logN2O2>-1.2 using HII regions + + + + #######################these are the metallicity diagnostics################## + #@profile + def calcpyqz(self, plot=False, allD13=False): + printsafemulti("calculating D13", self.logf, self.nps) + + # initializing variable pyqz to avoid style issues + # (pyqz not defined is reported as error by Landscape.io w/ import in func + pyqz = None + try: + import pyqz + except ImportError: + return -1 + + #check version of pyqz + from distutils.version import StrictVersion + oldpyqz = False + if StrictVersion(pyqz.__version__) <= StrictVersion('0.5.0'): + oldpyqz = True + + if self.NII_SII is not None and allD13: + if self.OIII_SII is not None: + + if oldpyqz: + self.mds['D13_N2S2_O3S2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_SII]), 'NII/SII', 'OIII/SII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + #pyqz.get_grid_fn(Pk=5.0,calibs='GCZO', kappa =20, struct='pp') + # self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + # np.atleast_1d([self.OIII_SII])], \ + # '[NII]/[SII]+;[OIII]/[SII]+', \ + # show_plot=plot, n_plot=False, \ + # save_plot=False, verbose=False)[0].T + self.mds['D13_N2S2_O3S2'] = pyqz.interp_qz('Tot[O]+12', + [np.atleast_1d([self.NII_SII]), + np.atleast_1d([self.OIII_SII])], + '[NII]/[SII]+;[OIII]/[SII]+')[0].T + + if self.OIII_Hb is not None: + if oldpyqz: + self.mds['D13_N2S2_O3Hb'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_Hb]), 'NII/SII', 'OIII/Hb', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + # self.mds['D13_N2S2_O3SHb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + # np.atleast_1d([self.OIII_Hb])], \ + # '[NII]/[SII]+;[OIII]/Hb', \ + # show_plot=plot, n_plot=False, \ + # save_plot=False, verbose=False)[0].T + self.mds['D13_N2S2_O3SHb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/[SII]+;[OIII]/Hb')[0].T + + + + if self.OIII_OII is not None: + if oldpyqz: + self.mds['D13_N2S2_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_OII]), 'NII/SII', 'OIII/OII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2S2_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_SII]), \ + np.atleast_1d([self.OIII_OII])], \ + '[NII]/[SII]+;[OIII]/[OII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + + if self.NII_OII is not None and allD13: + if self.OIII_SII is not None: + if oldpyqz: + self.mds['D13_N2O2_O3S2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_SII]), 'NII/OII', 'OIII/SII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2O2_O3S2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_SII])], \ + '[NII]/[OII]+;[OIII]/[SII]+', \ + show_plot=plot, n_plot=False, \ + save_plot=False, verbose=False)[0].T + + + if self.OIII_Hb is not None: + if oldpyqz: + self.mds['D13_N2O2_O3Hb'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_Hb]), 'NII/OII', 'OIII/Hb', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2O2_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/[OII]+;[OIII]/Hb')[0].T + + if self.OIII_OII is not None: + if oldpyqz: + self.mds['D13_N2O2_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_OII]), 'NII/OII', 'OIII/OII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2O2_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.NII_OII]), \ + np.atleast_1d([self.OIII_OII])], \ + '[NII]/[OII]+;[OIII]/[OII]+')[0].T + if self.logN2Ha is not None: + if self.OIII_Hb is not None: + if oldpyqz: + self.mds['D13_N2Ha_O3Hb'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_Hb]), 'NII/Ha', 'OIII/Hb', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + else: + self.mds['D13_N2Ha_O3Hb'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/Ha;[OIII]/Hb')[0].T + if self.OIII_OII is not None: + if oldpyqz: + self.mds['D13_N2Ha_O3O2'] = pyqz.get_qz(20, 'z', np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_OII]), 'NII/Ha', 'OIII/OII', \ + method='default', plot=plot, n_plot=False, savefig=False)[0].T + + else: + self.mds['D13_N2Ha_O3O2'] = pyqz.interp_qz('Tot[O]+12', [np.atleast_1d([self.logN2Ha]), \ + np.atleast_1d([self.OIII_Hb])], \ + '[NII]/Ha;[OIII]/[OII]+')[0].T + + #@profile + def calcDP00(self): + # Diaz, A. I., & Perez-Montero, E. 2000, MNRAS, 312, 130 + # As per KD02: DP00 diagnostic systematically underestimates the + # abundance relative to the comparison abundance. + # A term is added to improve the fit according to KD02 Eq. 6 + # AVAILABLE BUT DEPRECATED + printsafemulti("calculating DP00", self.logf, self.nps) + + if self.logS23 is None: + self.calcS23() + if self.logS23 is None: + printsafemulti("WARNING: Cannot compute this without S23", self.logf, self.nps) + return -1 + self.mds['DP00'] = 1.53 * self.logS23 + 8.27 + 1.0 / (2.0 - 9.0 * self.logS23 ** 3) + + #@profile + def calcD02(self): + # [NII]/Ha Denicolo, Terlevich & Terlevich (2002), MNRAS, 330, 69 + #FED:added uncertainties + printsafemulti("calculating D02", self.logf, self.nps) + + e1 = np.random.normal(0, 0.05, self.nm) + e2 = np.random.normal(0, 0.1, self.nm) + if self.hasN2 and self.hasHa: + self.mds['D02'] = 9.12 + e1 + (0.73 + e2) * self.logN2Ha + else: + printsafemulti("WARNING: need N2Ha to do this. " + + "did you run setHab and setNII", self.logf, self.nps) + + #@profile + def calcPP04(self): + ### PP04_N2_Z, PP04_O3N2_Z Pettini & Pagel diagnostics - + ### Pettini & Pagel (2004), MNRAS, 348, L59 + # [NII]/Ha Pettini & Pagel (2004), MNRAS, 348, L59 + #discriminating lower and upper branch using [NII]/[OII] or [NII]/Ha + printsafemulti("calculating PP04", self.logf, self.nps) + if self.hasN2 and self.hasHa: + self.mds['PP04_N2Ha'] = nppoly.polyval(self.logN2Ha, [9.37, 2.03, 1.26, 0.32]) + + #FED: restricting the range as per paper + index = (self.logN2Ha > -2.5) * (self.logN2Ha < -0.3) + self.mds['PP04_N2Ha'][~index] = float('NaN') + if self.hasO3Hb: + self.mds['PP04_O3N2'] = 8.73 - 0.32 * (self.logO3Hb - self.logN2Ha) + index = (self.logO3Hb > 2) + self.mds['PP04_O3N2'][index] = float('NaN') + else: + printsafemulti("WARNING: need O3Hb for PP04_O3N2", self.logf, self.nps) + else: + printsafemulti("WARNING: need N2Ha to do this. did you run setHab and setNII", self.logf, self.nps) + + #@profile + def calcZ94(self): + ### calculating z from Kobulnicky,Kennicutt,Pizagno (1998) + ### parameterization of Zaritzky et al. (1994) + ###Z94 = Zaritsky, D., Kennicutt, R. C., & Huchra, J. P. 1994, + ###ApJ, 420, 87 + ### only valid on the upper branch of R23 (KE08 A2.4) + + printsafemulti("calculating Z94", self.logf, self.nps) + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + self.mds['Z94'] = nppoly.polyval(self.logR23, [9.265, -0.33, -0.202, -0.207, -0.333]) + self.mds['Z94'][(self.logR23 > 0.9)] = None + ## 0.9 is a conservative constraint to make sure that we are + ## only using the upper branch (i.e. 12+log(O/H)>8.4) + + def calcP(self): + if self.P is None: + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + #R3=10**self.logO349595007Hb + #R2=10**self.logO2Hb + #P = R3/(R2+R3) + self.P = self.R3 / self.R23 + + #@profile + def calcP05(self): + # #### P-method ##### + ##Pilyugin+ 2005 method. Based on [OIII],[OII], Hbeta + ##calibrated from Te method + # make sure you run setOlines() first + printsafemulti("calculating P05", self.logf, self.nps) + + if self.calcP() == -1: + return -1 + if self.Z_init_guess is None: + self.initialguess() + + Psq = self.P * self.P + + P_abund_up = (self.R23 + 726.1 + 842.2 * self.P + 337.5 * Psq) / (85.96 + 82.76 * self.P + 43.98 * Psq + 1.793 * self.R23) + P_abund_low = (self.R23 + 106.4 + 106.8 * self.P - 3.40 * Psq) / (17.72 + 6.60 * self.P + 6.95 * Psq - 0.302 * self.R23) + + self.mds['P05'] = P_abund_up + self.mds['P05'][self.Z_init_guess < 8.4] = P_abund_low[self.Z_init_guess < 8.4] + + #@profile + def calcP10(self): + # #### P-method ##### + ##Pilyugin+ 2010 method. + ##calibrated from Te method + # need Hb + #The Astrophysical Journal, Volume 720, Issue 2, pp. 1738-1751 (2010). + #Published in Sep 2010 + + printsafemulti("calculating P10", self.logf, self.nps) + + if not self.hasHb: + printsafemulti("this method needs Hb", self.logf, self.nps) + return -1 + self.mds['P10_ONS'] = np.zeros(self.nm) + float('NaN') + self.mds['P10_ON'] = np.zeros(self.nm) + float('NaN') + #P10N2=np.zeros(self.nm)+float('NaN') + #P10S2=np.zeros(self.nm)+float('NaN') + P10logR3 = np.zeros(self.nm) + float('NaN') + P10logR2 = np.zeros(self.nm) + float('NaN') + P10logN2 = np.zeros(self.nm) + float('NaN') + P10logS2 = np.zeros(self.nm) + float('NaN') + + self.calcP() + if self.R2 is not None: + P10logR2 = np.log(self.R2) + + if self.R3 is not None: + P10logR3 = np.log(self.R3) + + if self.hasN2: + #the ratio of N26548 and N26548 is N26584/N26548 = 3 + #independent on physical conditions + #The Physics and Dynamics of Planetary Nebulae + # By Grigor A. Gurzadyan + P10logN2 = (np.log((self.N26584 * 1.33) / self.Hb) + + self.dustcorrect(k_N2, k_Hb, flux=False)) + + if self.hasS2 and self.hasS26731: + self.S2Hb = ((self.S26717 + self.S26731) / self.Hb + * self.dustcorrect(k_S2, k_Hb, flux=True)) + self.hasS2Hb = True + P10logS2 = np.log10(self.S2Hb) + + P10logN2S2 = P10logN2 - P10logS2 + P10logN2R2 = P10logN2 - P10logR2 + P10logS2R2 = P10logS2 - P10logR2 + + coefsONS0 = np.array([8.277, 0.657, -0.399, -0.061, 0.005]) + coefsONS1 = np.array([8.816, -0.733, 0.454, 0.710, -0.337]) + coefsONS2 = np.array([8.774, -1.855, 1.517, 0.304, 0.328]) + + vsONS = np.array([np.ones(self.nm), self.P, P10logR3, P10logN2R2, P10logS2R2]).T + + coefsON0 = np.array([8.606, -0.105, -0.410, -0.150]) + coefsON1 = np.array([8.642, 0.077, 0.411, 0.601]) + coefsON2 = np.array([8.013, 0.905, 0.602, 0.751]) + + vsON = np.array([np.ones(self.nm), P10logR3, P10logR2, P10logN2R2]).T + + indx = P10logN2 > -0.1 + if self.P is not None: + self.mds['P10_ONS'][indx] = np.dot(vsONS[indx], coefsONS0) + self.mds['P10_ON'][indx] = np.dot(vsON[indx], coefsON0) + + indx = (P10logN2 < -0.1) * (P10logN2S2 > -0.25) + if self.P is not None: + self.mds['P10_ONS'][indx] = np.dot(vsONS[indx], coefsONS1) + self.mds['P10_ON'][indx] = np.dot(vsON[indx], coefsON1) + + indx = (P10logN2 < -0.1) * (P10logN2S2 < -0.25) + if self.P is not None: + self.mds['P10_ONS'][indx] = np.dot(vsONS[indx], coefsONS2) + self.mds['P10_ON'][indx] = np.dot(vsON[indx], coefsON2) + + indx = ~((self.mds['P10_ONS'] > 7.1) * (self.mds['P10_ON'] > 7.1) * (self.mds['P10_ONS'] < 9.4) * (self.mds['P10_ON'] < 9.4)) + if self.P is not None: + self.mds['P10_ONS'][indx] = float('NaN') + self.mds['P10_ON'][indx] = float('NaN') + + #@profile + def calcP01(self): + # P-method 2001 upper branch (derecated and commented out) + # Pilyugin 2001 + # available but deprecated + printsafemulti("calculating old P05", self.logf, self.nps) + + if self.Z_init_guess is None: + self.initialguess() + if self.hasO3O2 and self.hasO3 and self.hasO2: + P = 10 ** self.logO3O2 / (1 + 10 ** self.logO3O2) + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + Psq = P ** 2 + P_abund_old = (self.R23 + 54.2 + 59.45 * P + 7.31 * Psq) / (6.07 + 6.71 * P + 0.371 * Psq + 0.243 * self.R23) + self.mds['P01'] = np.zeros(self.nm) + float('NaN') + self.mds['P01'][self.Z_init_guess >= 8.4] = P_abund_old[self.Z_init_guess >= 8.4] + else: + printsafemulti("WARNING: need OIIIOII to calculate P01, did you set them up with setOlines()?", self.logf, self.nps) + + #@profile + def calcC01_ZR23(self): + # C01 = Charlot, S., & Longhetti, M., 2001, MNRAS, 323, 887 + # Charlot 01 R23 calibration: (case F) ## + # available but deprecated + printsafemulti("calculating C01", self.logf, self.nps) + + if self.hasO3 and self.hasO2 and self.hasO3Hb: + x2 = self.O2O35007 / 1.5 + x3 = (10 ** self.logO3Hb) * 0.5 + self.mds['C01_R23'] = np.zeros(self.nm) + float('NaN') + self.mds['C01_R23'][self.O2O35007 < 0.8] = np.log10(3.78e-4 * (x2[self.O2O35007 < 0.8]) ** 0.17 * x3[self.O2O35007 < 0.8] ** (-0.44)) + 12.0 + + self.mds['C01_R23'][self.O2O35007 >= 0.8] = np.log10(3.96e-4 * x3[self.O2O35007 >= 0.8] ** (-0.46)) + 12.0 + else: + printsafemulti('''WARNING: need [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, +did you set them up with setOlines()?''', self.logf, self.nps) + + # Charlot 01 calibration: (case A) based on [N2]/[SII]## + # available but deprecated + if not self.hasN2S2: + printsafemulti("WARNING: trying to calculate logNIISII", self.logf, self.nps) + self.calcNIISII() + if self.hasN2S2 and self.hasO3 and self.hasO2 and self.hasO3Hb: + self.mds['C01_N2S2'] = np.log10(5.09e-4 * (x2 ** 0.17) * ((self.N2S2 / 0.85) ** 1.17)) + 12 + else: + printsafemulti('''WARNING: needs [NII]6584, [SII]6717, [OIII]5700, [OII]3727, and Ha to calculate calcC01_ZR23, +did you set them up with setOlines() and ?''', self.logf, self.nps) + + #@profile + def calcM91(self): + # ## calculating McGaugh (1991) + # McGaugh, S.S., 1991, ApJ, 380, 140' + # M91 calibration using [N2O2] as + # initial estimate of abundance: + # this initial estimate can be + # changed by replacing + # OH_init by another guess, eg C01_Z + # NOTE: occasionally the M91 + # 'upper branch' will give a metallicity + # that is lower than the 'lower branch'. + # Happens for very high R23 values. + # If R23 is higher than the intersection + # (calculate the intersection), then + # the metallicity is likely to be around + # the R23 maximum = 8.4 + + printsafemulti("calculating M91", self.logf, self.nps) + self.mds['M91'] = np.zeros(self.nm) + float('NaN') + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + return -1 + + if self.Z_init_guess is None: + self.initialguess() + + M91_Z_low = nppoly.polyval(self.logR23, [12.0 - 4.944, 0.767, 0.602]) - \ + self.logO3O2 * nppoly.polyval(self.logR23, [0.29, 0.332, -0.331]) + M91_Z_up = nppoly.polyval(self.logR23, [12.0 - 2.939, -0.2, -0.237, -0.305, -0.0283]) - \ + self.logO3O2 * nppoly.polyval(self.logR23, [0.0047, -0.0221, -0.102, -0.0817, -0.00717]) + + indx = (np.abs(self.logO3O2) > 0) * (np.abs(self.logR23) > 0) * (self.Z_init_guess < 8.4) + self.mds['M91'][indx] = M91_Z_low[indx] + indx = (np.abs(self.logO3O2) > 0) * (np.abs(self.logR23) > 0) * (self.Z_init_guess >= 8.4) + self.mds['M91'][indx] = M91_Z_up[indx] + self.mds['M91'][(M91_Z_up < M91_Z_low)] = float('NaN') + + #@profile + def calcM13(self): + #Marino+ 2013 + printsafemulti("calculating M13", self.logf, self.nps) + + if not self.hasHa or not self.hasN2: + printsafemulti("WARNING: need O3, N2, Ha and Hb, " + + "or at least N2 and Ha", self.logf, self.nps) + return -1 + else: + e1 = np.random.normal(0, 0.027, self.nm) + e2 = np.random.normal(0, 0.024, self.nm) + self.mds["M13_N2Ha"] = 8.743 + e1 + (0.462 + e2) * self.logN2Ha + if self.hasHb and self.hasO3: + e1 = np.random.normal(0, 0.012, self.nm) + e2 = np.random.normal(0, 0.012, self.nm) + O3N2 = self.logO3Hb - self.logN2Ha + self.mds["M13_O3N2"] = 8.533 + e1 - (0.214 + e1) * O3N2 + index = (O3N2 > 1.7) + self.mds["M13_O3N2"][index] = float('NaN') + index = (O3N2 < -1.1) + self.mds["M13_O3N2"][index] = float('NaN') + + #for i in range(len(self.mds["M13_O3N2"])): + #print ("here O3N2",#self.O35007[i], self.Hb[i], + #self.O3Hb[i], + #self.logO3Hb[i], self.logN2Ha[i], + # O3N2[i], + # self.mds["M13_O3N2"][i]) + + #@profile + def calcM08(self, allM08=False): + #Maiolino+ 2008 + #Astronomy and Astrophysics, Volume 488, Issue 2, 2008, pp.463-479 + #Published in Sep 2008 + printsafemulti("calculating M08", self.logf, self.nps) + highZ = None + if self.logO35007O2 is not None: + self.mds['M08_O3O2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O3O2']] * self.nm).T + coefs[0] = coefs[0] - self.logO35007O2 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + #the two cumsum assure that if the condition for the ith element + #of indx is [False, False] then after the first cumsum(1) is [0,0] + #[False, True] is [0,1] + #[True, True] is [1,2] + #but (here is the kicker) [True, False] is [1,1]. + #Because i want only one solution + #(i'll settle for the first one occurring) [1,1] is ambiguous. + #The second cumsum(1) makes + #[0,0]->[0,0], [0,1]->[0,1], [1,2]->[1,3] and finally [1,1]->[1,2] + + self.mds['M08_O3O2'][(indx.sum(1)) > 0] = sols[indx].real + highZ = np.median(self.logO35007O2) < 0 + if self.logN2Ha is not None: + self.mds['M08_N2Ha'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['N2Ha']] * self.nm).T + coefs[0] = coefs[0] - self.logN2Ha + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_N2Ha'][(indx.sum(1)) > 0] = sols[indx].real + if highZ is None: + highZ = np.median(self.logN2Ha) > -1.3 + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute M08_R23 without R23", self.logf, self.nps) + else: + self.mds['M08_R23'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['R23']] * self.nm).T + coefs[0] = coefs[0] - self.logR23 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real >= 8.0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_R23'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real <= 8.0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_R23'][(indx.sum(1)) > 0] = sols[indx].real + if not allM08: + return + else: + printsafemulti("calculating other M08s", self.logf, self.nps) + + if self.logO3Hb is not None: + + self.mds['M08_O3Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O3Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO3Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real >= 7.9)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real <= 7.9)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + + if self.logO2Hb is not None: + self.mds['M08_O2Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O2Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO2Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real >= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= 7.1) * (sols.real <= 9.4) * (sols.imag == 0) * (sols.real <= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + + + + if self.hasO3 and self.hasN2: + self.mds['M08_O3N2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([M08_coefs['O3N2']] * self.nm).T + coefs[0] = coefs[0] - np.log10(self.O35007 / self.N26584) \ + + self.dustcorrect(k_O35007, k_N2, flux=False) + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= 7.1) * (sols.real <= 9.4)\ + * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['M08_O3N2'][(indx.sum(1)) > 0] = sols[indx].real + + #@profile + def calcKD02_N2O2(self): + ## Kewley & Dopita (2002) estimates of abundance + ## KD02 + # KD02 [N2]/[O2] estimate (can be used for whole log(O/H)+12 range, + # but rms scatter increases to 0.11 rms for log(O/H)+12 < 8.6 + # rms = 0.04 for + # log(O/H)+12 > 8.6 + # uses equation (4) from KD02 paper + # FED: i vectorized the hell out of this function!!! + # from a 7 dimensional if/for loop to 1 if and 1 for :-D + #vectorizing makes fed happy ... + + printsafemulti("calculating KD02_N2O2", self.logf, self.nps) + + if self.hasN2 and self.hasO2 and self.hasHa and self.hasHb: + self.mds['KD02_N2O2'] = np.zeros(self.nm) + float('NaN') + if not self.hasN2O2: + printsafemulti("WARNING: must calculate logN2O2 first", self.logf, self.nps) + self.calcNIIOII() + if not self.hasN2O2 or self.N2O2_roots is None or sum(np.isnan(self.N2O2_roots.flatten())) == len(self.N2O2_roots.flatten()): + printsafemulti("WARNING: cannot calculate N2O2", self.logf, self.nps) + return -1 + roots = self.N2O2_roots.T + for k in range(4): + indx = (abs(roots[k]) >= 7.5) * (abs(roots[k]) <= 9.4) * (roots[k][:].imag == 0.0) + self.mds['KD02_N2O2'][indx] = abs(roots[k][indx]) + else: + printsafemulti("WARNING: need NII6584 and OII3727 and Ha and Hb to calculate this. did you run setO() setHab() and setNII()?", self.logf, self.nps) + return 1 + + #@profile + def calcKK04_N2Ha(self): + # calculating [N2]/Ha abundance estimates using [O3]/[O2] also + printsafemulti("calculating KK04_N2Ha", self.logf, self.nps) + + if self.mds['KD02_N2O2'] is None: + self.calcKD02_N2O2() + if self.mds['KD02_N2O2'] is None or sum(np.isnan(self.mds['KD02_N2O2'])) == self.nm: + printsafemulti("WARNING: without KD02_N2O2 cannot calculate KK04_N2Ha properly, but we will do our best...", self.logf, self.nps) + Z_new_N2Ha = np.zeros(self.nm) + 8.6 + else: + Z_new_N2Ha = self.mds['KD02_N2O2'].copy() # was 8.6 + + if self.hasN2 and self.hasHa: + logq_save = np.zeros(self.nm) + convergence, tol, ii = 100, 1.0e-3, 0 + if self.hasO3O2: + # calculating logq using the [N2]/[O2] + # metallicities for comparison + while convergence > tol and ii < 100: + ii += 1 + self.logq = self.calclogq(Z_new_N2Ha) + Z_new_N2Ha = nppoly.polyval(self.logN2Ha, [7.04, 5.28, 6.28, 2.37]) - \ + self.logq * nppoly.polyval(self.logN2Ha, [-2.44, -2.01, -0.325, +0.128]) + \ + 10 ** (self.logN2Ha - 0.2) * self.logq * (-3.16 + 4.65 * self.logN2Ha) + convergence = np.abs(self.logq - logq_save).mean() + logq_save = self.logq.copy() + if ii >= 100: + printsafemulti("WARNING [KK04_N2Ha]: loop did not converge", self.logf, self.nps) + Z_new_N2Ha = np.zeros(self.nm) + float('NaN') + else: + self.logq = 7.37177 * np.ones(self.nm) + Z_new_N2Ha = nppoly.polyval(self.logN2Ha, [7.04, 5.28, 6.28, 2.37]) - \ + self.logq * nppoly.polyval(self.logN2Ha, [-2.44, -2.01, -0.325, +0.128]) + \ + 10 ** (self.logN2Ha - 0.2) * self.logq * (-3.16 + 4.65 * self.logN2Ha) + self.mds['KK04_N2Ha'] = Z_new_N2Ha + indx = self.logN2Ha > 0.8 + self.mds['KK04_N2Ha'][indx] = float('NaN') + else: + printsafemulti("WARNING: need NII6584 and Ha to calculate this. did you run setHab() and setNII()?", self.logf, self.nps) + + #@profile + def calcKK04_R23(self): + # Kobulnicky & Kewley 2004 + # calculating upper and lower metallicities for objects without + # Hb and for objects without O3 and/or O2 + + printsafemulti("calculating KK04_R23", self.logf, self.nps) + #this is in the original code but not used :( + #if self.hasN2 and self.hasHa: + #logq_lims=[6.9,8.38] + #logN2Ha=np.log10(self.N26584/self.Ha) CHECK!! why remove dust correction?? + #Z_new_N2Ha_lims= np.atleast_2d([1.0,1.0]).T*nppoly.polyval(self.logN2Ha,[7.04, 5.28,6.28,2.37])- + #np.atleast_2d( logq_lims).T*nppoly.polyval(self.logN2Ha,[-2.44,-2.01,-0.325,0.128])+ + #np.atleast_2d(logq_lims).T*(10**(self.logN2Ha-0.2)*(-3.16+4.65*self.logN2Ha)) + # R23 diagnostics from Kobulnicky & Kewley 2004 + + Zmax = np.zeros(self.nm) + # ionization parameter form logR23 + if not self.hasO3O2: + logq = np.zeros(self.nm) + else: + if self.Z_init_guess is None: + self.initialguess() + Z_new = self.Z_init_guess.copy() + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute this without R23", self.logf, self.nps) + else: + logqold, convergence, ii = np.zeros(self.nm) + 100, 100, 0 + tol = 1e-4 + #3 iterations are typically enought to achieve convergence KE08 A2.3 + while convergence > tol and ii < 100: + Zmax = Zmax * 0.0 + ii += 1 + logq = self.calclogq(Z_new) + Zmax[(logq >= 6.7) * (logq < 8.3)] = 8.4 + # maximum of R23 curve: + Z_new = nppoly.polyval(self.logR23, [9.72, -0.777, -0.951, -0.072, -0.811]) - \ + logq * nppoly.polyval(self.logR23, [0.0737, -0.0713, -0.141, 0.0373, -0.058]) + indx = self.Z_init_guess <= Zmax + Z_new[indx] = nppoly.polyval(self.logR23[indx], [9.40, 4.65, -3.17]) - \ + logq[indx] * nppoly.polyval(self.logR23[indx], [0.272, 0.547, -0.513]) + convergence = np.abs((logqold - logq).mean()) + logqold = logq.copy() + if ii >= 100: + printsafemulti("WARNING [KK04_R23]: loop did not converge", self.logf, self.nps) + Z_new = np.zeros(self.nm) + float('NaN') + Z_new_lims = [nppoly.polyval(self.logR23, [9.40, 4.65, -3.17]) - \ + logq * nppoly.polyval(self.logR23, [0.272, 0.547, -0.513]), + nppoly.polyval(self.logR23, [9.72, -0.777, -0.951, -0.072, -0.811]) - \ + logq * nppoly.polyval(self.logR23, [0.0737, -0.0713, -0.141, 0.0373, -0.058])] + Z_new[(Z_new_lims[0] > Z_new_lims[1])] = None + self.mds['KK04_R23'] = Z_new + + #@profile + def calcKDcombined(self): + # KD02comb Kewley, L. J., & Dopita, M. A., 2002, ApJ + # updated in KE08 + # ### KD02 [NII]/[OII] estimate ### + # (can be used for log(O/H)+12 > 8.6 only) + + printsafemulti("calculating KD_combined", self.logf, self.nps) + + #We first use the + #[N ii]/[O ii] ratio to determine whether it lies on the upper + #or lower R23 branch + + if self.mds['KD02_N2O2'] is None: + self.calcKD02_N2O2() + if self.mds['KK04_N2Ha'] is None: + self.calcKK04_N2Ha() + if self.logR23 is None: + self.calcR23() + if self.mds['M91'] is None: + printsafemulti("WARNING: Must first calculate M91", self.logf, self.nps) + self.calcM91() +# if self.mds['Z94'] is None: +# printsafemulti( "WARNING: Must first calculate Z94",self.logf,self.nps) +# self.calcZ94() + if self.mds['KK04_R23'] is None: + printsafemulti("WARNING: Must first calculate KK04_R23", self.logf, self.nps) + self.calcKK04_R23() + if not self.hasHa and not self.hasHb: + printsafemulti("WARNING: need Ha and Hb for this. did you run setHab()?", self.logf, self.nps) + + #alternative way to calculate KD02_N2O2, stated in the paper KD02, + #valid in high Z regimes (Z>8.4) + #but we forego it + #if not self.logN2O2 is None: + # self.mds['KD02_N2O2']=np.log10(8.511e-4*(1.54020+1.26602*self.logN2O2+0.167977*self.logN2O2**2))+12. + #else: self.mds['KD02_N2O2']=np.zeros(self.nm)+float('NaN') + + # ionization parameter + # calculate an initial ionization parameter by assuming + # a nominal lower branch [12 + log (O/H ) = 8.2] + # or upper branch [12 + log (O/H ) = 8.7] metallicity using + # equation (13) from KK04 + logq = np.zeros(self.nm) + if self.hasN2 and self.hasO2 and self.hasHb and self.hasHa and self.hasO3O2: + logq = self.calclogq(self.mds['KD02_N2O2']) + logq[self.mds['KD02_N2O2'] >= 8.4] = self.logq[self.mds['KD02_N2O2'] >= 8.4] + else: + if self.Z_init_guess is None: + self.initialguess() + logq = self.calclogq(self.Z_init_guess) + #FED: CHECK: the paragraph below makes sense in words but i dont see where it is enforced. + # if log([NII]/[OII]) after extinction correction is <-1.5, then check the data. + # if it is only slightly less than 1.5, then this can be a result of either noisy + # data, inaccurate fluxes or extinction correction, or a higher ionization parameter + # than modelled. + # For these cases, the average of the M91,Z94 and C01 should be used. + + # KD02 R23 estimate (not reliable for 8.4 < log(O/H)+12 < 8.8) + # uses [NII]/[OII] estimate as initial guess - this can be changed below + + self.mds['KD02comb'] = np.zeros(self.nm) + float('NaN') + + indx_ig = self.Z_init_guess > 8.4 + if self.mds['KD02_N2O2'] is not None: + self.mds['KD02comb'][indx_ig] = self.mds['KD02_N2O2'][indx_ig].copy() + if self.mds['KK04_N2Ha'] is not None: + self.mds['KD02comb'][~indx_ig] = self.mds['KK04_N2Ha'][~indx_ig].copy() + if self.mds['KK04_R23'] is not None and self.mds['M91'] is not None: + # if [NII]/[OII] abundance available + # and [NII]/Ha abundance < 8.4, then use R23. + indx = (~np.isnan(self.mds['KK04_R23'])) * (~np.isnan(self.mds['M91'])) * (~indx_ig) + self.mds['KD02comb'][indx] = 0.5 * (self.mds['KK04_R23'][indx].copy() + self.mds['M91'][indx].copy()) + + else: + printsafemulti("WARNING: cannot calculate KK04comb because KK04_R23 or M91, failed", self.logf, self.nps) + +#######################these are the metallicity diagnostics################## +#@profile + def calcPM14(self): + # Perez-Montero 2014 + # (can be used for for log(O/H)+12 > 8.6 only) + import os + from subprocess import Popen, PIPE, STDOUT + from StringIO import StringIO + + printsafemulti("calculating HIICHI", self.logf, self.nps) + fin_hii_chi = open(os.getenv('HIICHI_DIR') + '/in.tmp', 'w') + + if not self.hasHb: + printsafemulti("cannot calculate HIICHI without Hbeta", self.logf, self.nps) + return -1 + + ratios = np.zeros((5, self.nm)) + + if self.R2 is not None: + ratios[0] = self.R2 + elif self.hasO2: + ratios[0] = ((self.O23727 / self.Hb) + * self.dustcorrect(k_O2, k_Hb, flux=True)) + else: + ratios[0] = np.array(['0 '] * self.nm) + + #we will never have 4363... + ratios[1] = np.zeros(self.nm) + + if self.hasO3Hb: + ratios[2] = self.O3Hb + elif self.hasO3: + ratios[2] = ((self.O35007 / self.Hb) + * self.dustcorrect(k_O35007, k_Hb, flux=True)) # 0.4*self.mds['E(B-V)']*(k_O2-k_Hb) + else: + ratios[2] = np.zeros(self.nm) + + if self.hasN2: + ratios[3] = self.N26584 / self.Hb + else: + ratios[3] = np.zeros(self.nm) + + if self.hasS2Hb: + ratios[4] = self.S2Hb + elif self.hasS2 and self.hasS26731: + ratios[4] = (((self.S26717 + self.S26731) / self.Hb) * + self.dustcorrect(k_S2, k_Hb, flux=True)) + else: + ratios[4] = np.zeros(self.nm) + + + for ni in range(self.nm): + fin_hii_chi.write('%f %f %f %f %f\n' % (ratios[0][ni], ratios[1][ni], ratios[2][ni], ratios[3][ni], ratios[4][ni])) + fin_hii_chi.close() + os.system("ln -s %s/C13*dat . " % os.getenv('HIICHI_DIR')) + print ("\n\n\n\n\n") + #os.system("python %s/HII-CHI-mistry_v01.2.py in.tmp"%os.getenv('HIICHI_DIR')) + #os.system("python %s/HII-CHI-mistry_v01.2.py %s/in.tmp"%(os.getenv('HIICHI_DIR'),os.getenv('HIICHI_DIR'))) + p = Popen(['python', '%s/HII-CHI-mistry_v01.2.py' % os.getenv('HIICHI_DIR'), + '%s/in.tmp' % os.getenv('HIICHI_DIR')], + stdout=PIPE, stdin=PIPE, stderr=STDOUT) + out, err = p.communicate(input='%s/in.tmp' % os.getenv('HIICHI_DIR')) + print ("\n\n\n\n\n") + out = StringIO(out) + # for l in enumerate(out): + # if l[0].isdigit(): + # break + out = out.readlines()[12:] + self.mds['PM14'] = np.zeros((self.nm)) + self.mds['PM14err'] = np.zeros((self.nm)) + for i, l in enumerate(out): + self.mds['PM14'][i], self.mds['PM14err'][i] = map(float, l.split()[3:5]) + #data = np.loadtxt(out, skiprows=12, usecols=(3,4))#, dtype=[('lOH','f'),('elOH','f')], delimiter=",", unpack = True) + print (self.mds) + os.system('rm -r C13*dat') + + +########## jch additions + + def calcPMC09(self): + #Perez-Montero & Contini (2009) - N2Ha + printsafemulti("calculating PMC09", self.logf, self.nps) + + if not self.hasHa or not self.hasN2 or not self.hasHa: + printsafemulti("WARNING: need N2, Ha ", + self.logf, self.nps) + return -1 + PMC09_N2 = lambda x: 9.07+0.79*x + + y = 9.07 + 0.78*self.logN2Ha + self.mds["PMC09_N2Ha"] = y + + #@profile + def calcB07(self): + #Bresolin 2007 - N2O2 + printsafemulti("calculating B07", self.logf, self.nps) + + if not self.hasHa or not self.hasN2 or not self.hasO2: + printsafemulti("WARNING: need N2, O2 ", + self.logf, self.nps) + return -1 + + # Use [N2/Ha * Ha/Hb] / [O2/Hb] + x = (self.logN2Ha+np.log10(2.86)) - self.logO2Hb + + y = 8.66 + 0.36*self.logN2O2 - 0.17*self.logN2O2**2 + self.mds["B07_N2O2"] = y + index = (self.logN2O2 < -1.2) + self.mds["B07_N2O2"][index] = float('NaN') + index = (self.logN2O2 > 0.6) + self.mds["B07_N2O2"][index] = float('NaN') + + + #@profile + def calcD16(self): + #Dopita+ 2016 + printsafemulti("calculating D16", self.logf, self.nps) + + if not self.hasHa or not self.hasN2 or not self.hasS2: + printsafemulti("WARNING: need N2, Ha and SII, ", + self.logf, self.nps) + return -1 + + d16_n2s2 = np.log10(self.N26584 / (self.S26717+self.S26731)) + self.dustcorrect(k_N2, k_S2, flux=False) + y = d16_n2s2 + 0.264 * self.logN2Ha + self.mds["D16"] = 8.77 + y# + 0.45 * pow(y + 0.3, 5) + index = (y < -1.) + self.mds["D16"][index] = float('NaN') + index = (y > 0.5) + self.mds["D16"][index] = float('NaN') + + + def calcC17(self, allC17=False): + # Curti+ 2017 + # Monthly Notices Royal Astronomical Society, vol. 465, pp 1384-1400 + # Published in October 2016 + # + # Added 5/16/17 by jch, Santiago + import pdb + + valid_upper = 8.85 + valid_lower = 7.6 + + printsafemulti("calculating C17", self.logf, self.nps) + highZ = None + + if self.logO35007O2 is not None: + # C17 O3O2 + self.mds['C17_O3O2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3O2']] * self.nm).T + coefs[0] = coefs[0] - self.logO35007O2 + + # sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + # Find valid solutions + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + + # the two cumsum assure that if the condition for the ith element + # of indx is [False, False] then after the first cumsum(1) is [0,0] + # [False, True] is [0,1] + # [True, True] is [1,2] + # but (here is the kicker) [True, False] is [1,1]. + # Because i want only one solution + # (i'll settle for the first one occurring) [1,1] is ambiguous. + # The second cumsum(1) makes + # [0,0]->[0,0], [0,1]->[0,1], [1,2]->[1,3] and finally [1,1]->[1,2] + + # Set the results where appropriate + self.mds['C17_O3O2'][(indx.sum(1)) > 0] = sols[indx].real + + # Now use this to see if we're on the high-Z branch of R23 + highZ = np.median(self.logO35007O2) < 0 + + if self.logN2Ha is not None: + # C17 N2Ha + self.mds['C17_N2Ha'] = np.zeros(self.nm) + float('NaN') + + coefs = np.array([C17_coefs['N2Ha']] * self.nm).T + coefs[0] = coefs[0] - self.logN2Ha + + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + + self.mds['C17_N2Ha'][(indx.sum(1)) > 0] = sols[indx].real + if highZ is None: + highZ = np.median(self.logN2Ha) > -1.3 + + if self.hasO3 and self.hasN2: + # C17 O3N2 + self.mds['C17_O3N2'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3N2']] * self.nm).T + o3n2_value = self.logO3Hb - self.logN2Ha + coefs[0] = coefs[0] - o3n2_value + + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) \ + * (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3N2'][(indx.sum(1)) > 0] = sols[indx].real + + if self.logO3Hb is not None: + # C17 O3Hb + self.mds['C17_O3Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O3Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO3Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + + # C17 define this only for the high-metal branch. + indx = ((sols.real >= 8.2) * (sols.real <= valid_upper) * + (sols.imag == 0)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O3Hb'][(indx.sum(1)) > 0] = sols[indx].real + + # Require allC17 flag if we want everything. + if not allC17: + return + else: + printsafemulti("calculating other C17s", self.logf, self.nps) + + if self.logR23 is None: + printsafemulti("WARNING: Must first calculate R23", self.logf, self.nps) + self.calcR23() + if self.logR23 is None: + printsafemulti("WARNING: Cannot compute C17_R23 without R23", self.logf, self.nps) + else: + self.mds['C17_R23'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['R23']] * self.nm).T + coefs[0] = coefs[0] - self.logR23 + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real >= 8.4)).cumsum(1).cumsum(1) == 1 + self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= valid_lower) * (sols.real <= valid_upper) * + (sols.imag == 0) * + (sols.real < 8.4)).cumsum(1).cumsum(1) == 1 + self.mds['C17_R23'][(indx.sum(1)) > 0] = sols[indx].real + + + if self.logO2Hb is not None: + self.mds['C17_O2Hb'] = np.zeros(self.nm) + float('NaN') + coefs = np.array([C17_coefs['O2Hb']] * self.nm).T + coefs[0] = coefs[0] - self.logO2Hb + sols = np.array([self.fz_roots(coefs.T)])[0] + 8.69 + if highZ is True: + indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * + (sols.imag == 0) * + (sols.real >= 8.7)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + elif highZ is False: + indx = ((sols.real >= valid_lower) * (sols.real <= 8.3) * + (sols.imag == 0) * + (sols.real <= valid_upper)).cumsum(1).cumsum(1) == 1 + self.mds['C17_O2Hb'][(indx.sum(1)) > 0] = sols[indx].real + + + + def calcPG16(self): + # Pilyugin & Grebel (2016, MNRAS, 457, 3678) + printsafemulti("calculating PG16", self.logf, self.nps) + + # Initialize the PG16 results + self.mds['PG16'] = np.zeros(self.nm) + float('NaN') + + # Make sure the lines are all here... + if not self.hasHa or not self.hasN2 or not self.hasO2 or not self.hasO3 or not self.hasS2: + printsafemulti("WARNING: need O2, O3, N2, S2, and Hb", + self.logf, self.nps) + return -1 + + pilR2 = self.logO2Hb + pilR3 = self.logO3Hb+np.log10(1.33) # Both members of doublet + pilR3R2 = (pilR3-pilR2) + + # Calculate S2/Hb = S2/Ha * Ha/Hb = S2/Ha*2.85 + pilS2 = np.log10((self.S26731 + self.S26731) / self.Ha * 2.85) + pilS2 += np.log10(self.dustcorrect(k_S2,k_Ha,flux=True)) + pilR3S2 = (pilR3-pilS2) + + # Calculate N2/Hb = N2/Ha * Ha/Hb = N2/Ha*2.85 + pilN2 = np.log10((self.N26584*1.33 / self.Ha)*2.85) + # pilN2 += np.log10(self.dustcorrect(k_N2,k_Hb,flux=True)) + + # Determine which branch we're on + highZ = (np.median(pilN2) >= -0.6) + print(np.median(pilN2)) + + # Calculate R = O2O3N2 abundances + if highZ == True: + print('PG16 high metal branch') + self.mds['PG16_R'] = 8.589+0.022*pilR3R2+0.399*pilN2 + self.mds['PG16_R'] += (-0.137+0.164*pilR3R2+0.589*pilN2)*pilR2 + else: + print('PG16 low metal branch') + self.mds['PG16_R'] = 7.932+0.944*pilR3R2+0.695*pilN2 + self.mds['PG16_R'] += (0.970-0.291*pilR3R2-0.019*pilN2)*pilR2 + + # Calculate S = S2O3N2 abundances + if highZ == True: + print('PG16 high metal branch') + self.mds['PG16_S'] = 8.424+0.030*pilR3S2+0.751*pilN2 + self.mds['PG16_S'] += (-0.349+0.182*pilR3S2+0.508*pilN2)*pilS2 + else: + print('PG16 low metal branch') + self.mds['PG16_S'] = 8.072+0.789*pilR3S2+0.726*pilN2 + self.mds['PG16_S'] += (1.069-0.17*pilR3S2+0.022*pilN2)*pilS2 diff --git a/build/scripts-3.6/pylabsetup.py b/build/scripts-3.6/pylabsetup.py new file mode 100755 index 0000000..5ffef05 --- /dev/null +++ b/build/scripts-3.6/pylabsetup.py @@ -0,0 +1,41 @@ +import matplotlib as mpl +import pylab as plt + +mpl.rcParams.update(mpl.rcParamsDefault) +mpl.rcParams['font.size'] = 26. +mpl.rcParams['font.family'] = 'serif' +#mpl.rcParams['font.family'] = 'serif' +mpl.rcParams['font.serif'] = ['Times New Roman', 'Times', 'Palatino', 'Charter', 'serif'] +mpl.rcParams['font.sans-serif'] = ['Helvetica'] +mpl.rcParams['axes.labelsize'] = 24 +mpl.rcParams['xtick.labelsize'] = 22. +mpl.rcParams['ytick.labelsize'] = 22. +mpl.rcParams['xtick.major.size'] = 15. +mpl.rcParams['xtick.minor.size'] = 10. +mpl.rcParams['ytick.major.size'] = 15. +mpl.rcParams['ytick.minor.size'] = 10. +#mpl.rcParams['figure.autolayout']= True + +#fontsize=26 +#mpl.rc('axes', titlesize=fontsize) +#mpl.rc('axes', labelsize=fontsize) +#mpl.rc('xtick', labelsize=fontsize) +#mpl.rc('ytick', labelsize=fontsize) +#mpl.rc('font', size=fontsize, family='serif', serif='Utopia', +# style='normal', variant='normal', +# stretch='normal', weight='normal') +#mpl.rc('font',**{'family':'serif','serif':[ 'Times New Roman', 'Times', 'serif'], +# 'sans-serif':['Helvetica'], 'size':19, +# 'weight':'normal'}) +mpl.rc('axes', **{'linewidth' : 1.2}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('axes', **{'labelweight': 'normal', 'linewidth': 1}) +mpl.rc('ytick', **{'major.pad': 5, 'color': 'k'}) +mpl.rc('xtick', **{'major.pad': 5, 'color': 'k'}) +params = {'legend.fontsize': 24, + 'legend.numpoints': 1, + 'legend.handletextpad': 1 + } + +plt.rcParams.update(params) +plt.minorticks_on() diff --git a/pyMCZ/metallicity.pyc b/pyMCZ/metallicity.pyc deleted file mode 100644 index d4dbc8884e3b17fd5d0ad0f7a6293d1eefce4f76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6631 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 UfH&d@B+w?bfmml;zP&N+zZ_RsaR2}S diff --git a/pyMCZ/metscales.pyc b/pyMCZ/metscales.pyc deleted file mode 100644 index d380ef16489b1db8cc8ed6b1557f3835c9856177..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43807 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 Urw+ym7`wnXEx^Az{A;ZLzbBFB<^TWy diff --git a/pyMCZ/pylabsetup.pyc b/pyMCZ/pylabsetup.pyc deleted file mode 100644 index 308066a7bce621d5eaf9f377ff9df5d0d1b7a9c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1224 zcmb7CO>Yx15FKZeHcgW@&_WB8QlLODBnMCqD1u1sfeTf|hlFxLPV6LZz3bK5Aqm%> zxpCs>@Rxey0^`};CXu+{O>DpSX2vsP2UoSq@1MUu_aPf4Ts!!;KNx_(9|H~0W%<4U z-2fzm^CFy=XtM&B445cntO%ejia97DSOKvLq6}gUP#C6OMGzI#)?vtU+bZH(4mJ>M z=Aga^ZXmdsD_aP*b8u@>xxJ|DK-=exY3zc+TJC_j3$5>%_na<(xCdenTH^lysr#VN z_W_6l^nD28(Ng5c|5jB1Sgbfqk9(3XPodQYH@_4y{boz^>=~fQL%Y>?Y!mDwOu%R89`wn~+CjO`I#Edd4+( zDl%>r!SRSEPA0aJhoPv>pY~SjuhOYLr zBqvI^ky}9>!-iZt-%+Y(BGqu@@G${U3+W_9t@r&&kBqr+-r|Bx`bL|Cc2o%!tmRNf zqTw6pJ4E?qTr>`2qmZ}T{?OEL#3P}llT(MWkNm|rKZV@I+BmIxF7PncbWS|OT!}$~ z?lQM7>eb4+A*)JwKLIUQKeTlI72_JxM zj?Cnud6Jm8XQs_~=0+yM<=t`jdd9r0bmO>zs(*Lw(1@{?uYI>{1E(sh7PeT8)e3v8 T?$`OZvBUPWufkSYC8)E%m;4p$ From 38a369ba789c18b67d43ceb8618c550908e209d3 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Thu, 29 May 2025 15:59:10 -0400 Subject: [PATCH 17/19] .gitignore update. --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 1 + 2 files changed, 1 insertion(+) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 9a7b632b7baafc1777da3ad84cab91a2e4768600..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKu};H441JfjQY%2}gcu{ljOfk~l`t}OV*(l!Dz$2dc3{g#FeC8~d;l{8Tf4Eq z$QLky?K_e?HC+)xwq(E5KIiFPsyGM0^cPtV=mKbyGe#W>HO6)JHH%z^n+@M%oc9L9 z>14LHQWJkw0e*H(93saEbN~58@5gt3Z8A;MY%w1KtIqwtY^Us-OtX!(*-iDIh(4GO zE#ey?!!<5_M&Ru+tX7=M*VVRgdc75EUDfA>IkLQ0SP=6VS-{&m*cINgTJf$LFJF$= ztCqX1sRq2QC(Ap-EzWR)^ME&c_!Qoo6NdcPK zZ0#+J8dU*RKouwz;P*pJ&X_nXEXu2cf*b(|o0Jx#Yx%|_j5$G=I4mquhRoPjLfdNG z5yRMa%ExY9;;^u2+hK&6?&Fw^JE0h1r+h5SVG@fPRRL8XRKT4Qx;+0+H=qB*CjF%f zr~>~=0n Date: Thu, 29 May 2025 16:05:47 -0400 Subject: [PATCH 18/19] Removed 00ToDo file (jch specific). --- 00ToDo | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 00ToDo diff --git a/00ToDo b/00ToDo deleted file mode 100644 index 20b10d8..0000000 --- a/00ToDo +++ /dev/null @@ -1,2 +0,0 @@ -# TODO Include estimates of systematic uncertainties in output? -# TODO For n iterations extract only n results, excluding NaN runs. \ No newline at end of file From 77b572b323687f0b0346b7289746c743487ccb13 Mon Sep 17 00:00:00 2001 From: Chris Howk Date: Thu, 29 May 2025 16:36:46 -0400 Subject: [PATCH 19/19] Clean up documenting which scales are from jch. --- .gitignore | 3 ++- pyMCZ/metscales.py | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 9560c27..2f71a56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ *~ *.pyc -.DS_Store \ No newline at end of file +.DS_Store +00ToDo \ No newline at end of file diff --git a/pyMCZ/metscales.py b/pyMCZ/metscales.py index 62d706a..607ac6e 100644 --- a/pyMCZ/metscales.py +++ b/pyMCZ/metscales.py @@ -7,7 +7,7 @@ import pdb -niter = 5 # number of iteations+1 for KD02 methods +niter = 5 # number of iterations+1 for KD02 methods k_Ha = 2.535 # CCM Rv=3.1 k_Hb = 3.609 # CCM Rv=3.1 @@ -1357,10 +1357,9 @@ def calcPM14(self): os.system('rm -r C13*dat') -########## jch additions - def calcPMC09(self): - #Perez-Montero & Contini (2009) - N2Ha + #Perez-Montero & Contini (2009) - N2Ha + # Added 5/2017 by jch, Santiago printsafemulti("calculating PMC09", self.logf, self.nps) if not self.hasHa or not self.hasN2 or not self.hasHa: @@ -1375,6 +1374,7 @@ def calcPMC09(self): #@profile def calcB07(self): #Bresolin 2007 - N2O2 + # Added 5/2017 by jch, Santiago printsafemulti("calculating B07", self.logf, self.nps) if not self.hasHa or not self.hasN2 or not self.hasO2: @@ -1396,6 +1396,7 @@ def calcB07(self): #@profile def calcD16(self): #Dopita+ 2016 + # Added 5/2017 by jch, Santiago printsafemulti("calculating D16", self.logf, self.nps) if not self.hasHa or not self.hasN2 or not self.hasS2: @@ -1415,9 +1416,8 @@ def calcD16(self): def calcC17(self, allC17=False): # Curti+ 2017 # Monthly Notices Royal Astronomical Society, vol. 465, pp 1384-1400 - # Published in October 2016 # - # Added 5/16/17 by jch, Santiago + # Added 5/2017 by jch, Santiago import pdb valid_upper = 8.85 @@ -1543,6 +1543,7 @@ def calcC17(self, allC17=False): def calcPG16(self): # Pilyugin & Grebel (2016, MNRAS, 457, 3678) + # Added 5/2017 by jch, Santiago printsafemulti("calculating PG16", self.logf, self.nps) # Initialize the PG16 results