class Dotter2016(Isochrone): """ MESA isochrones from Dotter 2016: http://waps.cfa.harvard.edu/MIST/interp_isos.html """ _dirname = os.path.join(get_iso_dir(), '{survey}', 'dotter2016') defaults = (Isochrone.defaults) + ( ('dirname', _dirname, 'Directory name for isochrone files'), ('hb_stage', 3, 'Horizontal branch stage name'), ('hb_spread', 0.1, 'Intrinisic spread added to horizontal branch'), ) download_url = 'http://waps.cfa.harvard.edu/MIST' download_defaults = copy.deepcopy(mesa_defaults_10) abins = np.arange(1., 13.5 + 0.1, 0.1) zbins = np.arange(1e-5, 1e-3 + 1e-5, 1e-5) columns = dict( des=odict([(2, ('mass_init', float)), (3, ('mass_act', float)), (6, ('log_lum', float)), (9, ('u', float)), (10, ('g', float)), (11, ('r', float)), (12, ('i', float)), (13, ('z', float)), (14, ('Y', float)), (15, ('stage', float))]), sdss=odict([(2, ('mass_init', float)), (3, ('mass_act', float)), (6, ('log_lum', float)), (9, ('u', float)), (10, ('g', float)), (11, ('r', float)), (12, ('i', float)), (13, ('z', float)), (14, ('stage', float))]), ps1=odict([(2, ('mass_init', float)), (3, ('mass_act', float)), (6, ('log_lum', float)), (9, ('g', float)), (10, ('r', float)), (11, ('i', float)), (12, ('z', float)), (13, ('y', float)), (16, ('stage', float))]), lsst=odict([(2, ('mass_init', float)), (3, ('mass_act', float)), (6, ('log_lum', float)), (9, ('u', float)), (10, ('g', float)), (11, ('r', float)), (12, ('i', float)), (13, ('z', float)), (14, ('Y', float)), (15, ('stage', float))]), ) def _parse(self, filename): """ Reads an isochrone in the Dotter 2016 format and determines the age (Gyr), metallicity (Z), and creates arrays with the initial stellar mass and corresponding magnitudes for each step along the isochrone. """ try: columns = self.columns[self.survey.lower()] except KeyError as e: logger.warning('Unrecognized survey: %s' % (self.survey)) raise (e) kwargs = dict(comments='#', usecols=list(columns.keys()), dtype=list(columns.values())) data = np.genfromtxt(filename, **kwargs) self.mass_init = data['mass_init'] self.mass_act = data['mass_act'] self.luminosity = 10**data['log_lum'] self.mag_1 = data[self.band_1] self.mag_2 = data[self.band_2] self.stage = data['stage'] # Check where post-AGB isochrone data points begin self.mass_init_upper_bound = np.max(self.mass_init) self.index = np.nonzero(self.stage >= 4)[0][0] self.mag = self.mag_1 if self.band_1_detection else self.mag_2 self.color = self.mag_1 - self.mag_2 @classmethod def z2feh(cls, z): # Section 3.1 of Choi et al. 2016 (https://arxiv.org/abs/1604.08592) Z_init = z # Initial metal abundance Y_p = 0.249 # Primordial He abundance (Planck 2015) c = 1.5 # He enrichment ratio Y_init = Y_p + c * Z_init X_init = 1 - Y_init - Z_init Z_solar = 0.0142 # Solar metal abundance Y_solar = 0.2703 # Solar He abundance (Asplund 2009) X_solar = 1 - Y_solar - Z_solar return np.log10(Z_init / Z_solar * X_solar / X_init) @classmethod def feh2z(cls, feh): # Section 3.1 of Choi et al. 2016 (https://arxiv.org/abs/1604.08592) Y_p = 0.249 # Primordial He abundance (Planck 2015) c = 1.5 # He enrichment ratio Z_solar = 0.0142 # Solar metal abundance Y_solar = 0.2703 # Solar He abundance (Asplund 2009) X_solar = 1 - Y_solar - Z_solar return (1 - Y_p) / ((1 + c) + (X_solar / Z_solar) * 10**(-feh)) def query_server(self, outfile, age, metallicity): z = metallicity feh = self.z2feh(z) params = dict(self.download_defaults) params['output'] = dict_output[self.survey] params['FeH_value'] = feh params['age_value'] = age * 1e9 if params['age_scale'] == 'log10': params['age_value'] = np.log10(params['age_value']) server = self.download_url url = server + '/iso_form.php' # First check that the server is alive logger.debug("Accessing %s..." % url) urlopen(url, timeout=2) #response = requests.post(url,data=params) q = urlencode(params).encode('utf-8') request = Request(url, data=q) response = urlopen(request) try: fname = os.path.basename(str(response.read()).split('"')[1]) except Exception as e: logger.debug(str(e)) msg = 'Output filename not found' raise RuntimeError(msg) tmpdir = os.path.dirname(tempfile.NamedTemporaryFile().name) tmpfile = os.path.join(tmpdir, fname) out = '{0}/tmp/{1}'.format(server, fname) cmd = 'wget --progress dot:binary %s -P %s' % (out, tmpdir) logger.debug(cmd) stdout = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) logger.debug(stdout) cmd = 'unzip %s -d %s' % (tmpfile, tmpdir) logger.debug(cmd) stdout = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) logger.debug(stdout) logger.debug("Creating %s..." % outfile) shutil.move(tmpfile.replace('.zip', '.cmd'), outfile) os.remove(tmpfile) return outfile @classmethod def verify(cls, filename, survey, age, metallicity): age = age * 1e9 nlines = 14 with open(filename, 'r') as f: lines = [f.readline() for i in range(nlines)] if len(lines) < nlines: msg = "Incorrect file size" raise Exception(msg) try: s = lines[2].split()[-2] assert dict_output[survey][:4] in s except: msg = "Incorrect survey:\n" + lines[2] raise Exception(msg) try: z = lines[5].split()[2] assert np.allclose(metallicity, float(z), atol=1e-3) except: msg = "Metallicity does not match:\n" + lines[5] raise Exception(msg) try: a = lines[13].split()[1] assert np.allclose(age, float(a), atol=1e-5) except: msg = "Age does not match:\n" + lines[13] raise Exception(msg)
class Bressan2012(ParsecIsochrone): _dirname = os.path.join(get_iso_dir(), '{survey}', 'bressan2012') defaults = (Isochrone.defaults) + ( ('dirname', _dirname, 'Directory name for isochrone files'), ('hb_stage', 4, 'Horizontal branch stage name'), ('hb_spread', 0.1, 'Intrinisic spread added to horizontal branch'), ) #download_defaults = copy.deepcopy(defaults_27) download_defaults = copy.deepcopy(defaults_31) download_defaults['isoc_kind'] = 'parsec_CAF09_v1.2S' columns = dict( des=odict([ (3, ('mass_init', float)), (4, ('mass_act', float)), (5, ('log_lum', float)), (10, ('g', float)), (11, ('r', float)), (12, ('i', float)), (13, ('z', float)), (14, ('Y', float)), (16, ('stage', int)), ]), sdss=odict([ (3, ('mass_init', float)), (4, ('mass_act', float)), (5, ('log_lum', float)), (9, ('u', float)), (10, ('g', float)), (11, ('r', float)), (12, ('i', float)), (13, ('z', float)), (15, ('stage', int)), ]), ps1=odict([ (3, ('mass_init', float)), (4, ('mass_act', float)), (5, ('log_lum', float)), (9, ('g', float)), (10, ('r', float)), (11, ('i', float)), (12, ('z', float)), (13, ('y', float)), (16, ('stage', int)), ]), lsst=odict([(3, ('mass_init', float)), (4, ('mass_act', float)), (5, ('log_lum', float)), (9, ('u', float)), (10, ('g', float)), (11, ('r', float)), (12, ('i', float)), (13, ('z', float)), (14, ('Y', float)), (16, ('stage', float))]), ) def _parse(self, filename): """Reads an isochrone file in the Padova (Bressan et al. 2012) format. Creates arrays with the initial stellar mass and corresponding magnitudes for each step along the isochrone. """ #http://stev.oapd.inaf.it/cgi-bin/cmd_2.7 try: columns = self.columns[self.survey.lower()] except KeyError as e: logger.warning('Unrecognized survey: %s' % (self.survey)) raise (e) # delimiter='\t' is used to be compatible with OldPadova... # ADW: This should be updated, but be careful of column numbering kwargs = dict(delimiter='\t', usecols=list(columns.keys()), dtype=list(columns.values())) self.data = np.genfromtxt(filename, **kwargs) self.mass_init = self.data['mass_init'] self.mass_act = self.data['mass_act'] self.luminosity = 10**self.data['log_lum'] self.mag_1 = self.data[self.band_1] self.mag_2 = self.data[self.band_2] self.stage = self.data['stage'] self.mass_init_upper_bound = np.max(self.mass_init) self.index = len(self.mass_init) self.mag = self.mag_1 if self.band_1_detection else self.mag_2 self.color = self.mag_1 - self.mag_2
class Marigo2017(ParsecIsochrone): #http://stev.oapd.inaf.it/cgi-bin/cmd_31 #_dirname = '/u/ki/kadrlica/des/isochrones/v4/' _dirname = os.path.join(get_iso_dir(), '{survey}', 'marigo2017') defaults = (Isochrone.defaults) + ( ('dirname', _dirname, 'Directory name for isochrone files'), ('hb_stage', 4, 'Horizontal branch stage name'), ('hb_spread', 0.1, 'Intrinisic spread added to horizontal branch'), ) download_defaults = copy.deepcopy(defaults_31) download_defaults['isoc_kind'] = 'parsec_CAF09_v1.2S_NOV13' columns = dict( des=odict([ (2, ('mass_init', float)), (3, ('mass_act', float)), (4, ('log_lum', float)), (7, ('stage', int)), (23, ('u', float)), (24, ('g', float)), (25, ('r', float)), (26, ('i', float)), (27, ('z', float)), (28, ('Y', float)), ]), sdss=odict([ (2, ('mass_init', float)), (3, ('mass_act', float)), (4, ('log_lum', float)), (7, ('stage', int)), (23, ('u', float)), (24, ('g', float)), (25, ('r', float)), (26, ('i', float)), (27, ('z', float)), ]), ps1=odict([ (2, ('mass_init', float)), (3, ('mass_act', float)), (4, ('log_lum', float)), (7, ('stage', int)), (23, ('g', float)), (24, ('r', float)), (25, ('i', float)), (26, ('z', float)), (27, ('y', float)), (28, ('w', float)), ]), lsst=odict([ (2, ('mass_init', float)), (3, ('mass_act', float)), (4, ('log_lum', float)), (7, ('stage', int)), (23, ('u', float)), (24, ('g', float)), (25, ('r', float)), (26, ('i', float)), (27, ('z', float)), (28, ('Y', float)), ]), ) def _parse(self, filename): """Reads an isochrone file in the Padova (Marigo et al. 2017) format. Creates arrays with the initial stellar mass and corresponding magnitudes for each step along the isochrone. """ try: columns = self.columns[self.survey.lower()] except KeyError as e: logger.warning('Unrecognized survey: %s' % (self.survey)) raise (e) kwargs = dict(usecols=list(columns.keys()), dtype=list(columns.values())) self.data = np.genfromtxt(filename, **kwargs) # cut out anomalous point: # https://github.com/DarkEnergySurvey/ugali/issues/29 self.data = self.data[self.data['stage'] != 9] self.mass_init = self.data['mass_init'] self.mass_act = self.data['mass_act'] self.luminosity = 10**self.data['log_lum'] self.mag_1 = self.data[self.band_1] self.mag_2 = self.data[self.band_2] self.stage = self.data['stage'] self.mass_init_upper_bound = np.max(self.mass_init) self.index = len(self.mass_init) self.mag = self.mag_1 if self.band_1_detection else self.mag_2 self.color = self.mag_1 - self.mag_2
class Dotter2008(Isochrone): """ KCB: currently inheriting from PadovaIsochrone because there are several useful functions where we would basically be copying code. """ _dirname = os.path.join(get_iso_dir(), '{survey}', 'dotter2008') #_zsolar = 0.0163 _zsolar = 0.0180 # Grevesse & Sauval, 1998 # KCB: What to do about horizontal branch? defaults = (Isochrone.defaults) + ( ('dirname', _dirname, 'Directory name for isochrone files'), ('hb_stage', 'BHeb', 'Horizontal branch stage name'), ('hb_spread', 0.1, 'Intrinisic spread added to horizontal branch'), ) abins = np.arange(1., 13.5 + 0.1, 0.1) zbins = np.arange(7e-5, 1e-3 + 1e-5, 1e-5) download_defaults = copy.deepcopy(dartmouth_defaults) columns = dict( des=odict([ (1, ('mass', float)), (4, ('log_lum', float)), (5, ('u', float)), (6, ('g', float)), (7, ('r', float)), (8, ('i', float)), (9, ('z', float)), ]), sdss=odict([ (1, ('mass', float)), (4, ('log_lum', float)), (5, ('u', float)), (6, ('g', float)), (7, ('r', float)), (8, ('i', float)), (9, ('z', float)), ]), ps1=odict([ (1, ('mass', float)), (4, ('log_lum', float)), (6, ('g', float)), (7, ('r', float)), (8, ('i', float)), (9, ('z', float)), (10, ('y', float)), ]), ) def _parse(self, filename): """ Reads an isochrone in the Dotter format and determines the age (log10 yrs and Gyr), metallicity (Z and [Fe/H]), and creates arrays with the initial stellar mass and corresponding magnitudes for each step along the isochrone. http://stellar.dartmouth.edu/models/isolf_new.html """ try: columns = self.columns[self.survey.lower()] except KeyError as e: logger.warning('Unrecognized survey: %s' % (survey)) raise (e) kwargs = dict(comments='#', usecols=list(columns.keys()), dtype=list(columns.values())) self.data = np.genfromtxt(filename, **kwargs) # KCB: Not sure whether the mass in Dotter isochrone output # files is initial mass or current mass self.mass_init = self.data['mass'] self.mass_act = self.data['mass'] self.luminosity = 10**self.data['log_lum'] self.mag_1 = self.data[self.band_1] self.mag_2 = self.data[self.band_2] self.stage = np.tile('Main', len(self.data)) # KCB: No post-AGB isochrone data points, right? self.mass_init_upper_bound = np.max(self.mass_init) self.mag = self.mag_1 if self.band_1_detection else self.mag_2 self.color = self.mag_1 - self.mag_2 @classmethod def z2feh(cls, z): # Section 3 of Dotter et al. 2008 # Section 2 of Dotter et al. 2007 (0706.0847) Z_init = z # Initial metal abundance Y_p = 0.245 # Primordial He abundance (WMAP, 2003) c = 1.54 # He enrichment ratio Y_init = Y_p + c * Z_init X_init = 1 - Y_init - Z_init # This is not well defined... #Z_solar/X_solar = 0.0229 # Solar metal fraction (Grevesse 1998) ZX_solar = 0.0229 return np.log10(Z_init / X_init * 1 / ZX_solar) @classmethod def feh2z(cls, feh): # Section 3 of Dotter et al. 2008 Y_p = 0.245 # Primordial He abundance (WMAP, 2003) c = 1.54 # He enrichment ratio # This is not well defined... #Z_solar/X_solar = 0.0229 # Solar metal fraction (Grevesse 1998) ZX_solar = 0.0229 return (1 - Y_p) / ((1 + c) + (1 / ZX_solar) * 10**(-feh)) def query_server(self, outfile, age, metallicity): z = metallicity feh = self.z2feh(z) params = copy.deepcopy(self.download_defaults) params['age'] = age params['feh'] = '%.6f' % feh params['clr'] = dict_clr[self.survey] url = 'http://stellar.dartmouth.edu/models/isolf_new.php' query = url + '?' + urlencode(params) logger.debug(query) response = urlopen(query) page_source = response.read() try: file_id = int(page_source.split('tmp/tmp')[-1].split('.iso')[0]) except Exception as e: logger.debug(str(e)) msg = 'Output filename not found' raise RuntimeError(msg) infile = 'http://stellar.dartmouth.edu/models/tmp/tmp%s.iso' % ( file_id) command = 'wget -q %s -O %s' % (infile, outfile) subprocess.call(command, shell=True) ## ADW: Old code to rename the output file based on Zeff ([a/Fe] corrected) #tmpfile = tempfile.NamedTemporaryFile().name #tmp = open(tmpfile,'r') #lines = [tmp.readline() for i in range(4)] #z_eff = float(lines[3].split()[4]) #basename = self.params2filename(age,z_eff) #logger.info("Writing %s..."%outfile) #mkdir(outdir) #shutil.move(tmpfile,outfile) @classmethod def verify(cls, filename, survey, age, metallicity): nlines = 8 with open(filename, 'r') as f: lines = [f.readline() for i in range(nlines)] if len(lines) < nlines: msg = "Incorrect file size" raise Exception(msg) try: z = lines[3].split()[4] assert np.allclose(metallicity, float(z), atol=1e-3) except: msg = "Metallicity does not match:\n" + lines[3] raise Exception(msg) try: s = lines[5].split()[2] assert dict_output[survey][:4] in s except: msg = "Incorrect survey:\n" + lines[5] raise Exception(msg) try: a = lines[7].split('=')[1].strip().split()[0] assert np.allclose(age, float(a), atol=1e-5) except: msg = "Age does not match:\n" + lines[7] raise Exception(msg)