"""Shared paths, constants and small helpers for the dyson100 study.""" import io import os import time import uuid import urllib.parse import urllib.request import numpy as np STUDY = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) RAW = os.environ.get("DYSON100_RAW", "fermi_raw/dyson100") DATA = os.path.join(STUDY, "data") FIG = os.path.join(STUDY, "figures") for _d in (RAW, DATA, FIG): os.makedirs(_d, exist_ok=True) VIZIER_TAP = "https://tapvizier.cds.unistra.fr/TAPVizieR/tap/sync" GAIA_TAP = "https://gea.esac.esa.int/tap-server/tap/sync" IRSA_TAP = "https://irsa.ipac.caltech.edu/TAP/sync" SIMBAD_TAP = "https://simbad.cds.unistra.fr/simbad/sim-tap/sync" # ---------------------------------------------------------------- photometry # Zero-magnitude flux densities (Jy). WISE: Jarrett et al. 2011 / Explanatory # Supplement (W4 uses the standard 8.363 Jy; no red-source correction applied # to data, the same convention is used for injected models). 2MASS: Cohen+03. F0 = dict(J=1594.0, H=1024.0, Ks=666.7, W1=309.540, W2=171.787, W3=31.674, W4=8.363) # Isophotal wavelengths in micron LAM = dict(J=1.235, H=1.662, Ks=2.159, W1=3.3526, W2=4.6028, W3=11.5608, W4=22.0883) # AllWISE instrumental zero points: mag = ZP - 2.5 log10(flux_DN) ZP_DN = dict(W1=20.5, W2=19.5, W3=18.0, W4=13.0) H_PLANCK = 6.62607015e-34 C_LIGHT = 2.99792458e8 K_B = 1.380649e-23 SIGMA_SB = 5.670374419e-8 LSUN = 3.828e26 PC = 3.0856775814913673e16 MBOL_SUN = 4.74 T_GRID = [100, 150, 200, 300, 400, 600] F_GRID = [1e-4, 2e-4, 5e-4, 1e-3, 2e-3, 5e-3, 1e-2, 2e-2, 3e-2, 5e-2, 7e-2, 0.1, 0.2, 0.3, 0.5, 0.7, 0.9] def mag2jy(mag, band): return F0[band] * 10.0 ** (-0.4 * np.asarray(mag, dtype=float)) def jy2mag(f, band): f = np.asarray(f, dtype=float) with np.errstate(invalid="ignore", divide="ignore"): return -2.5 * np.log10(f / F0[band]) def planck_nu(lam_um, T): """B_nu in W m^-2 Hz^-1 sr^-1.""" nu = C_LIGHT / (np.asarray(lam_um, dtype=float) * 1e-6) x = H_PLANCK * nu / (K_B * T) return 2 * H_PLANCK * nu ** 3 / C_LIGHT ** 2 / np.expm1(x) def http_get(url, params=None, timeout=600, retries=4, post=True): data = urllib.parse.urlencode(params).encode() if params else None last = None for k in range(retries): try: if post or data is None: req = urllib.request.Request(url, data=data) else: req = urllib.request.Request(url + "?" + data.decode()) with urllib.request.urlopen(req, timeout=timeout) as r: return r.read() except Exception as e: # noqa last = e time.sleep(5 * (k + 1)) raise RuntimeError(f"GET failed {url}: {last}") def tap_sync(url, query, fmt="csv", timeout=900, retries=4): params = {"REQUEST": "doQuery", "LANG": "ADQL", "FORMAT": fmt, "QUERY": query} return http_get(url, params, timeout=timeout, retries=retries).decode("utf-8", "replace") def tap_upload(url, query, table, name="mytable", fmt="csv", timeout=900, retries=4): """TAP sync query with an uploaded astropy Table (multipart POST, stdlib only).""" buf = io.BytesIO() table.write(buf, format="votable") payload = buf.getvalue() b = "----" + uuid.uuid4().hex def part(k, v): return f'--{b}\r\nContent-Disposition: form-data; name="{k}"\r\n\r\n{v}\r\n'.encode() body = b"".join([part("REQUEST", "doQuery"), part("LANG", "ADQL"), part("QUERY", query), part("FORMAT", fmt), part("UPLOAD", f"{name},param:table")]) body += (f'--{b}\r\nContent-Disposition: form-data; name="table"; filename="t.xml"\r\n' f"Content-Type: application/x-votable+xml\r\n\r\n").encode() + payload + f"\r\n--{b}--\r\n".encode() last = None for k in range(retries): try: req = urllib.request.Request(url, data=body, headers={"Content-Type": "multipart/form-data; boundary=" + b}) with urllib.request.urlopen(req, timeout=timeout) as r: return r.read().decode("utf-8", "replace") except Exception as e: # noqa last = e time.sleep(10 * (k + 1)) raise RuntimeError(f"TAP upload failed {url}: {last}") XMATCH_URL = "https://cdsxmatch.u-strasbg.fr/xmatch/api/v1/sync" def cds_xmatch(csv_text, cat2, radius_arcsec, ra="ra", dec="dec", timeout=600, retries=4): """CDS XMatch of an uploaded CSV table against a VizieR table or 'simbad' (stdlib only).""" b = "----" + uuid.uuid4().hex fields = dict(request="xmatch", distMaxArcsec=str(radius_arcsec), RESPONSEFORMAT="csv", colRA1=ra, colDec1=dec, cat2=cat2) body = b"".join(f'--{b}\r\nContent-Disposition: form-data; name="{k}"\r\n\r\n{v}\r\n'.encode() for k, v in fields.items()) body += (f'--{b}\r\nContent-Disposition: form-data; name="cat1"; filename="t.csv"\r\n' f"Content-Type: text/csv\r\n\r\n").encode() + csv_text.encode() + f"\r\n--{b}--\r\n".encode() last = None for k in range(retries): try: req = urllib.request.Request(XMATCH_URL, data=body, headers={"Content-Type": "multipart/form-data; boundary=" + b}) with urllib.request.urlopen(req, timeout=timeout) as r: return r.read().decode("utf-8", "replace") except Exception as e: # noqa last = e time.sleep(5 * (k + 1)) raise RuntimeError(f"XMatch failed for {cat2}: {last}")