diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2f71a56
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.idea/
+*~
+*.pyc
+.DS_Store
+00ToDo
\ No newline at end of file
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/README.md b/README.md
index 6f6c895..8ff3c3d 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,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
@@ -72,6 +73,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/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..8fa30cd
--- /dev/null
+++ b/build/lib/pyMCZ/mcz.py
@@ -0,0 +1,791 @@
+#!/usr/bin/env 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/lib/pyMCZ/metallicity.py b/build/lib/pyMCZ/metallicity.py
new file mode 100644
index 0000000..d5d2e79
--- /dev/null
+++ b/build/lib/pyMCZ/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/lib/pyMCZ/metscales.py b/build/lib/pyMCZ/metscales.py
new file mode 100644
index 0000000..6a57478
--- /dev/null
+++ b/build/lib/pyMCZ/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/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/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/mcz.py b/pyMCZ/mcz.py
index db024ec..52df3f5 100644
--- a/pyMCZ/mcz.py
+++ b/pyMCZ/mcz.py
@@ -35,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
@@ -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:
@@ -146,7 +151,7 @@ def readfile(filename):
newheader.append(h)
header = newheader
-
+
formats = ['S10'] + ['f'] * (len(header) - 1)
if 'flag' in header:
@@ -183,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
@@ -314,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':
@@ -429,7 +434,7 @@ def savehist(data, snname, Zs, nsample, i, path, nmeas, measnames, verbose=False
return "-1\t -1\t -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"
@@ -503,7 +511,7 @@ def run((name, flux, err, nm, path, bss), nsample, mds, multiproc, logf, unpickl
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:
@@ -517,7 +525,7 @@ 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
@@ -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
@@ -713,22 +723,23 @@ 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")
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''')
+ 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()
@@ -752,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"
@@ -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..d5d2e79 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,9 +24,11 @@
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
@@ -42,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",
@@ -53,8 +55,12 @@
"KK04_R23", # Hbeta, [OII]3727, [OIII]5007, ([OIII]4959 )
"KD02comb",
"PM14",
- "D16"] # ,"KK04comb"]
-#'KD02_N2O2', 'KD03new_R23', 'M91', 'KD03_N2Ha'
+ "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'
@@ -79,17 +85,19 @@ def printsafemulti(string, logf, nps):
##############################################################################
-##fz_roots function as used in the IDL code
+##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
@@ -107,20 +115,24 @@ 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:
- 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"
@@ -166,7 +178,7 @@ 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. \n''', logf, nps)
@@ -176,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()
@@ -187,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:
@@ -205,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:
@@ -220,8 +236,8 @@ 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('''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:
@@ -253,5 +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/pyMCZ/metscales.py b/pyMCZ/metscales.py
index 04e2e2d..607ac6e 100644
--- a/pyMCZ/metscales.py
+++ b/pyMCZ/metscales.py
@@ -5,10 +5,13 @@
import numpy.polynomial.polynomial as nppoly
from metallicity import get_keys, printsafemulti
-niter = 5 # number of iteations+1 for KD02 methods
+import pdb
+
+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
+k_Hg = 4.400 # CCM Rv=3.1
#k_O1=2.661 # CCM Rv=3.1
k_O2 = 4.771 # CCM Rv=3.1
@@ -48,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
@@ -65,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
@@ -88,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
@@ -229,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:
@@ -281,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
@@ -322,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)
@@ -347,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))
@@ -378,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))
@@ -386,7 +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)
+ 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
@@ -402,15 +424,16 @@ 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:
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:
@@ -529,11 +552,10 @@ def calcpyqz(self, plot=False, allD13=False):
np.atleast_1d([self.OIII_SII])], \
'[NII]/[SII]+;[OIII]/[SII]+')[0].T
else:
- 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:
@@ -547,11 +569,14 @@ def calcpyqz(self, plot=False, allD13=False):
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
@@ -571,7 +596,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:
@@ -602,9 +627,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:
@@ -618,9 +641,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:
@@ -634,9 +655,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]), \
@@ -650,9 +669,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):
@@ -795,7 +812,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:
@@ -879,7 +896,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]##
@@ -890,7 +907,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
@@ -941,30 +958,30 @@ 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:
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 = (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
@@ -1117,7 +1134,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)
@@ -1177,7 +1194,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]),
@@ -1296,7 +1313,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)
@@ -1313,7 +1330,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()
@@ -1339,20 +1356,238 @@ def calcPM14(self):
print (self.mds)
os.system('rm -r C13*dat')
+
+ def calcPMC09(self):
+ #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:
+ 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
+ # 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:
+ 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
+ # 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:
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')
+ 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
+ #
+ # Added 5/2017 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)
+ # Added 5/2017 by jch, Santiago
+ 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