Source code for pandexo.engine.run_online

import asyncio
import json
import logging
import os
import copy
import uuid
from collections import namedtuple, OrderedDict
from tornado.httpclient import AsyncHTTPClient
import tornado.escape
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
from bokeh.resources import CDN

import traceback
from sqlalchemy import *
from concurrent.futures import ProcessPoolExecutor
import pickle
import pandas as pd 
import numpy as np
from astroquery.simbad import Simbad
import astropy.units as u

from .pandexo import wrapper
from .jwst import validate_miri_lrs_subarray, validate_nirspec_prism_subarray
from .utils.plotters import create_component_jwst, create_component_hst
from .logs import jwst_log, hst_log
from .exomast import async_get_target_data

logger = logging.getLogger(__name__)

#grab all planets for folks 
#all_planets =  pd.read_csv('https://exoplanetarchive.ipac.caltech.edu/TAP/sync?query=select+pl_name+from+PSCompPars&format=csv')
#all_planets = sorted(all_planets['pl_name'].values)

# define location of temp files
__TEMP__ = os.environ.get("PANDEXO_TEMP", os.path.join(os.path.dirname(__file__), "temp"))
PLANET_LIST_URL = "https://exoplanetarchive.ipac.caltech.edu/TAP/sync?query=select+pl_name+from+PSCompPars&format=csv"
PLANET_LIST_CACHE = os.environ.get(
    "PANDEXO_PLANET_LIST_CACHE",
    os.path.join(__TEMP__, "planets.csv"),
)
MIRI_LRS_ALLOWED_SUBARRAYS = {
    "lrsslitless": ("slitlessprism", "slitlessprism_ip", "slitlessprism_ips"),
    "lrsslit": ("subslit", "full"),
}
NIRSPEC_PRISM_MULTISTRIPE_SUBARRAYS = (
    "s256m2_prm",
    "s128m4_prm",
    "s64m8_prm",
    "s32m16_prm",
)
NIRSPEC_WEB_MODES = (
    "g140mf070lp", "g140hf070lp", "g140mf100lp", "g140hf100lp",
    "g235mf170lp", "g235hf170lp", "g395mf290lp", "g395hf290lp",
    "prismclear",
)
NIRSPEC_STANDARD_SUBARRAYS = ("sub2048", "sub1024a", "sub1024b", "sub512")
NIRCAM_WEB_FILTERS = ("f322w2", "f444w")
NIRCAM_WEB_SUBARRAYS = (
    "subgrism64", "subgrism128", "subgrism256",
    "subgrism64 (noutputs=1)", "subgrism128 (noutputs=1)",
    "subgrism256 (noutputs=1)",
)
NIRCAM_WEB_READOUTS = (
    "optimize", "rapid", "bright1", "bright2", "shallow2", "shallow4",
    "medium2", "medium8", "mediumdeep2", "mediumdeep8", "deep2", "deep8",
)
NIRCAM_DHS_WEB_FILTERS = ("f070w", "f090w", "f115w", "f150w2", "f200w")
NIRCAM_DHS_WEB_SUBARRAYS = (
    "sub41s1_2-spectra", "sub82s2_4-spectra", "sub164s4_8-spectra",
    "sub260s4_8-spectra",
)
NIRCAM_DHS_WEB_READOUTS = (
    "optimize", "rapid", "bright1", "dhs3", "dhs4", "dhs5", "dhs6", "dhs7",
)
NIRISS_WEB_SUBARRAYS = (
    "substrip256", "substrip96", "sub17stripe_soss", "sub60stripe_soss",
    "sub204stripe_soss", "sub680stripe_soss",
)


[docs] def validate_online_instrument_configuration(conf): """Reject incomplete or unsupported instrument selections before queuing work.""" instrument = conf.get("instrument", {}) detector = conf.get("detector", {}) name = str(instrument.get("instrument", "")).lower() subarray = str(detector.get("subarray", "")).lower() readout = str(detector.get("readout_pattern", "")).lower() if name == "miri": mode = str(instrument.get("mode", "")).lower() if mode not in MIRI_LRS_ALLOWED_SUBARRAYS: raise ValueError(f"Unsupported MIRI LRS mode: {mode}.") validate_miri_lrs_subarray(conf) elif name == "nirspec": mode = f"{instrument.get('disperser', '')}{instrument.get('filter', '')}".lower() if mode not in NIRSPEC_WEB_MODES: raise ValueError(f"Unsupported NIRSpec BOTS mode: {mode}.") allowed = NIRSPEC_STANDARD_SUBARRAYS if mode == "prismclear": allowed = tuple( item for item in NIRSPEC_STANDARD_SUBARRAYS if item != "sub1024a" ) + NIRSPEC_PRISM_MULTISTRIPE_SUBARRAYS if subarray not in allowed: raise ValueError( f"NIRSpec mode {mode.upper()} does not support subarray " f"{subarray.upper()}." ) validate_nirspec_prism_subarray(conf) elif name == "nircam" and subarray.endswith("-spectra"): mode = str(instrument.get("mode", "")).lower() filt = str(instrument.get("filter", "")).lower() pair = str(instrument.get("pandexofilterpair", "")).lower() if mode == "sw_tsgrism": valid_filters = filt in NIRCAM_DHS_WEB_FILTERS and pair in NIRCAM_WEB_FILTERS elif mode == "lw_tsgrism": valid_filters = filt in NIRCAM_WEB_FILTERS and pair in NIRCAM_DHS_WEB_FILTERS else: valid_filters = False if not valid_filters: raise ValueError("Choose a valid paired NIRCam DHS filter configuration.") if subarray not in NIRCAM_DHS_WEB_SUBARRAYS: raise ValueError("Choose a valid NIRCam DHS subarray.") if readout not in NIRCAM_DHS_WEB_READOUTS: raise ValueError("Choose a valid NIRCam DHS readout pattern.") elif name == "nircam": if instrument.get("filter") not in NIRCAM_WEB_FILTERS: raise ValueError("Choose F322W2 or F444W for NIRCam grism time series.") if subarray not in NIRCAM_WEB_SUBARRAYS: raise ValueError("Choose a valid NIRCam grism subarray.") if readout not in NIRCAM_WEB_READOUTS: raise ValueError("Choose a valid NIRCam grism readout pattern.") elif name == "niriss" and subarray not in NIRISS_WEB_SUBARRAYS: raise ValueError("Choose a valid NIRISS SOSS subarray.")
#define location of fort grids try: __FORT__ = os.environ.get('FORTGRID_DIR') db_fort = create_engine('sqlite:///'+__FORT__) except: print('FORTNEY DATABASE NOT INSTALLED') # SIMBAD field registration may require a network request. The target resolver # already handles lookup failures, so an outage must not prevent web startup. try: Simbad.add_votable_fields('flux(H)') Simbad.add_votable_fields('flux(J)') except Exception: logger.warning("Unable to configure SIMBAD magnitude fields at startup") define("port", default=1111, help="run on the given port", type=int) define("debug", default=False, help="automatically detect code changes in development") define("workers", default=4, help="maximum number of simultaneous async tasks") # Define a simple named tuple to keep track for submitted calculations CalculationTask = namedtuple('CalculationTask', ['id', 'name', 'task', 'cookie', 'count', 'form_data'])
[docs] async def retrieve_url_to_file(url, filename): """Fetch a URL without blocking Tornado and atomically cache it on disk.""" os.makedirs(os.path.dirname(filename), exist_ok=True) response = await AsyncHTTPClient().fetch(url) tmp_filename = "{}.{}.tmp".format(filename, uuid.uuid4().hex) try: with open(tmp_filename, "wb") as write_file: write_file.write(response.body) os.replace(tmp_filename, filename) finally: if os.path.exists(tmp_filename): os.remove(tmp_filename)
[docs] async def get_cached_planet_names(): """Return the Exoplanet Archive planet-name list, caching it between users.""" if not os.path.exists(PLANET_LIST_CACHE): await retrieve_url_to_file(PLANET_LIST_URL, PLANET_LIST_CACHE) all_planets = pd.read_csv(PLANET_LIST_CACHE) return sorted(all_planets["pl_name"].dropna().values)
[docs] def getStarName(planet_name): """ Given a string with a (supposed) planet name, this function returns the star name. For example: - If `planet_name` is 'HATS-5b' this returns 'HATS-5'. - If `planet_name` is 'Kepler-12Ab' this returns 'Kepler-12A'. It also handles the corner case in which `planet_name` is *not* a planet name, but a star name itself, e.g.: - If `planet_name` is 'HAT-P-1' it returns 'HAT-P-1'. - If `planet_name` is 'HAT-P-1 ' it returns 'HAT-P-1'. """ star_name = planet_name.strip() # Check if last character is a letter: if str.isalpha(star_name[-1]): if star_name[-1] == star_name[-1].lower(): star_name = star_name[:-1] # Return trimmed string: return star_name.strip()
def _float_or_none(value): if np.ma.is_masked(value): return None if hasattr(value, "value"): value = value.value try: value = float(value) except (TypeError, ValueError): return None if not np.isfinite(value): return None return value def _simbad_magnitude(simbad_data, *column_names): if simbad_data is None or len(simbad_data) == 0: return None for column_name in column_names: if column_name in simbad_data.colnames: magnitude = _float_or_none(simbad_data[column_name][0]) if magnitude is not None: return magnitude return None
[docs] def get_simbad_magnitudes(star_name): """ Query Simbad for stellar magnitudes used by the online target resolver. ExoMAST often lacks near-infrared stellar magnitudes, so the historical web interface intentionally used Simbad for these fields. """ simbad_data = Simbad.query_object(star_name) return { "Jmag": _simbad_magnitude(simbad_data, "FLUX_J", "J"), "Hmag": _simbad_magnitude(simbad_data, "FLUX_H", "H"), }
[docs] async def add_simbad_magnitudes(planet_data, target_name): if planet_data is None: return None star_name = getStarName(planet_data.get("canonical_name") or target_name) try: magnitudes = await asyncio.to_thread(get_simbad_magnitudes, star_name) except Exception: logger.exception("Unable to resolve Simbad magnitudes for %s", star_name) return planet_data for key, value in magnitudes.items(): if value is not None: planet_data[key] = value return planet_data
[docs] class Application(tornado.web.Application): """Gobal settings of the server This defines the global settings of the server. This parses out the handlers, and includes settings for if ever we want to tie this to a database. """ def __init__(self): handlers = [ (r"/", HomeHandler), (r"/about", AboutHandler), (r"/dashboard", DashboardHandler), (r"/dashboardhst", DashboardHSTHandler), # Relative redirects preserve a reverse-proxy prefix such as # ``/pandexo`` while still working from the local application root. (r"/calculation", tornado.web.RedirectHandler, {"url": "dashboard"}), (r"/calculation/", tornado.web.RedirectHandler, {"url": "../dashboard"}), (r"/tables", TablesHandler), (r"/helpfulplots", HelpfulPlotsHandler), (r"/calculation/new", CalculationNewHandler), (r"/calculation/new/([^/]+)", CalculationNewHandler), (r"/calculation/newHST", CalculationNewHSTHandler), (r"/calculation/newHST/([^/]+)", CalculationNewHSTHandler), (r"/resolve", ResolveHandler), (r"/calculation/status/([^/]+)", CalculationStatusHandler), (r"/calculation/statushst/([^/]+)", CalculationStatusHSTHandler), (r"/calculation/view/([^/]+)", CalculationViewHandler), (r"/calculation/viewhst/([^/]+)", CalculationViewHSTHandler), (r"/calculation/download/([^/]+)", CalculationDownloadHandler), (r"/calculation/downloadtext/([^/]+)", CalculationDownloadTextHandler), (r"/calculation/downloadpandin/([^/]+)", CalculationDownloadPandInHandler) ] settings = dict( blog_title="Pandexo", template_path=os.path.join(os.path.dirname(__file__), "templates"), static_path=os.path.join(os.path.dirname(__file__), "static"), xsrf_cookies=True, cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__", debug=True, ) super(Application, self).__init__(handlers, **settings)
[docs] class BaseHandler(tornado.web.RequestHandler): """ Logic to handle user information and database access might go here. """ executor = None buffer = OrderedDict() @staticmethod def _bokeh_resources(): return { 'bokeh_css': CDN.render_css(), 'bokeh_js': CDN.render_js(), }
[docs] def get_template_namespace(self): namespace = super(BaseHandler, self).get_template_namespace() namespace.update(self._bokeh_resources()) return namespace
[docs] def render(self, template_name, **kwargs): for key, value in self._bokeh_resources().items(): kwargs.setdefault(key, value) return super(BaseHandler, self).render(template_name, **kwargs)
def _get_task_response(self, id): """ Simple function to grab a calculation that's stored in the buffer, and return a dictionary/json-like response to the front-end. """ calc_task = self.buffer.get(id) task = calc_task.task response = {'id': id, 'name': calc_task.name, 'count': calc_task.count} if task.running(): response['state'] = 'running' response['code'] = 202 elif task.done(): response['state'] = 'finished' elif task.cancelled(): response['state'] = 'cancelled' else: response['state'] = 'pending' response['html'] = tornado.escape.to_basestring( self.render_string("calc_row.html", response=response)) return response def _get_task_response_hst(self, id): """ Simple function to grab a calculation that's stored in the buffer, and return a dictionary/json-like response to the front-end. """ calc_task = self.buffer.get(id) task = calc_task.task response = {'id': id, 'name': calc_task.name, 'count': calc_task.count} if task.running(): response['state'] = 'running' response['code'] = 202 elif task.done(): response['state'] = 'finished' elif task.cancelled(): response['state'] = 'cancelled' else: response['state'] = 'pending' response['html'] = tornado.escape.to_basestring( self.render_string("calc_rowhst.html", response=response)) return response
[docs] def write_error(self, status_code, **kwargs): """ This renders a customized error page """ reason = self._reason error_info = '' trace_print = '' if 'exc_info' in kwargs: error_info = kwargs['exc_info'] try: trace_print = traceback.format_exception(*error_info) trace_print = "\n".join(map(str,trace_print)) except: pass self.render('errors.html',page=None, status_code=status_code, reason=reason, error_log=trace_print)
def _get_task_result(self, id): """ This method grabs only the result returned from the python `Future` object. This contains the stuff that Pandeia returns. """ calc_task = self.buffer.get(id) task = calc_task.task return task.result() def _add_task(self, id, name, task, form_data=None): """ This creates the task and adds it to the buffer. """ self.buffer[id] = CalculationTask(id=id, name=name, task=task, count=len(self.buffer)+1, cookie=self.get_cookie("pandexo_user"), form_data=form_data) # Only allow 100 tasks **globally**. This will delete old tasks first. if len(self.buffer) > 100: self.buffer.popitem(last=False)
[docs] class HomeHandler(BaseHandler):
[docs] def get(self): """ This sets an **unsecured** cookie. If user accounts gets implemented, this must be changed to a secure cookie. """ if not self.get_cookie("pandexo_user"): self.set_cookie("pandexo_user", str(uuid.uuid4())) self.render("home.html")
[docs] class AboutHandler(BaseHandler):
[docs] def get(self): """ Render about PandExo Page """ self.render("about.html")
[docs] class TablesHandler(BaseHandler):
[docs] def get(self): """ Render tables with confirmed candidates """ self.render("tables.html")
[docs] class HelpfulPlotsHandler(BaseHandler):
[docs] def get(self): """ Renders helpful bokeh plots """ self.render("helpfulplots.html")
[docs] class DashboardHandler(BaseHandler): """ Request handler for the dashboard page. This will retrieve and render the html template, along with the list of current task objects. """
[docs] def get(self): task_responses = [self._get_task_response(id) for id, nt in list(self.buffer.items()) if ((nt.cookie == self.get_cookie("pandexo_user")) & (id[len(id)-1]=='e'))] self.render("dashboard.html", calculations=task_responses[::-1])
[docs] class DashboardHSTHandler(BaseHandler): """ Request handler for the dashboard page. This will retrieve and render the html template, along with the list of current task objects. """
[docs] def get(self): task_responses = [self._get_task_response_hst(id) for id, nt in list(self.buffer.items()) if ((nt.cookie == self.get_cookie("pandexo_user")) & (id[len(id)-1]=='h'))] self.render("dashboardhst.html", calculations=task_responses[::-1])
[docs] class CalculationNewHandler(BaseHandler): """ This request handler deals with processing the form data and submitting a new calculation task to the parallelized workers. """
[docs] async def get(self, id=None): try: header= pd.read_sql_table('header',db_fort) except: header = pd.DataFrame({ 'temp': ['NO GRID DB FOUND'], 'ray' : ['NO GRID DB FOUND'], 'flat':['NO GRID DB FOUND']}) with open(os.path.join(os.path.dirname(__file__), "reference", "exo_input.json")) as data_file: exodata = json.load(data_file) form_data = None if id is not None: form_data = self.buffer[id].form_data all_planets = await get_cached_planet_names() unique_temps = sorted(header.temp.unique()) self.render("new.html", id=id, temp=list(map(str, unique_temps)), planets=all_planets, data=exodata, data_json=json.dumps(form_data))
[docs] def post(self): """ The post method contains the returned data from the form data ( accessed by using `self.get_argument(...)` for specific arguments, or `self.request.body` to grab the entire returned object. """ #print(self.request.body) form_data = {} for key in self.request.arguments: form_data[key] = self.get_argument(key) id = str(uuid.uuid4())+'e' with open(os.path.join(os.path.dirname(__file__), "reference", "exo_input.json")) as data_file: exodata = json.load(data_file) exodata["telescope"] = 'jwst' # Retain the public input field; jwst.compute_full_sim selects the # actual transit noise method from the detector readout mode. exodata["calculation"] = 'fml' #star exodata["star"]["temp"] = float(self.get_argument("temp")) exodata["star"]["logg"] = float(self.get_argument("logg")) exodata["star"]["metal"] = float(self.get_argument("metal")) exodata["star"]["mag"] = float(self.get_argument("mag")) exodata["star"]["ref_wave"] = float(self.get_argument("ref_wave")) #optinoal star radius exodata["star"]["radius"] = float(self.get_argument("rstarc")) exodata["star"]["r_unit"] = str(self.get_argument("rstar_unitc")) #optional planet radius exodata["planet"]["radius"] = float(self.get_argument("refradc")) exodata["planet"]["r_unit"] = str(self.get_argument("r_unitc")) #transit duration # for phase curves user doesn't necessarily have to input a transit duration try: exodata["planet"]["transit_duration"] = float(self.get_argument("transit_duration")) exodata["planet"]["td_unit"] = str(self.get_argument("td_unit")) except: # but if they don't.. make sure that the planet units are in seconds... if self.get_argument("planwunits") == 'sec': exodata["planet"]["transit_duration"] = 0.0 else: raise Exception("Need transit duraiton or input phase curve file") # stellar model exodata["star"]["type"] = self.get_argument("stellarModel") if exodata["star"]["type"] == "user": # process star file fileinfo_star = self.request.files['starFile'][0] fname_star = fileinfo_star['filename'] extn_star = os.path.splitext(fname_star)[1] cname_star = id+'star' + extn_star with open(os.path.join(__TEMP__, cname_star), 'wb') as fh_star: fh_star.write(fileinfo_star['body']) exodata["star"]["starpath"] = os.path.join(__TEMP__, cname_star) exodata["star"]["f_unit"] = self.get_argument("starfunits") exodata["star"]["w_unit"] = self.get_argument("starwunits") # planet model exodata["planet"]["type"] = self.get_argument("planetModel") if exodata["planet"]["type"] == "user": # process planet file fileinfo_plan = self.request.files['planFile'][0] fname_plan = fileinfo_plan['filename'] extn_plan = os.path.splitext(fname_plan)[1] cname_plan = id+'planet' + extn_plan with open(os.path.join(__TEMP__, cname_plan), 'wb') as fh_plan: fh_plan.write(fileinfo_plan['body']) exodata["planet"]["exopath"] = os.path.join(__TEMP__, cname_plan) exodata["planet"]["w_unit"] = self.get_argument("planwunits") exodata["planet"]["f_unit"] = self.get_argument("planfunits") elif exodata["planet"]["type"] == "constant": if self.get_argument("constant_unit") == 'fp/f*': exodata["planet"]["temp"] = float(self.get_argument("ptempc")) exodata["planet"]["f_unit"] = 'fp/f*' elif self.get_argument("constant_unit") == 'rp^2/r*^2': exodata["planet"]["f_unit"] = 'rp^2/r*^2' elif exodata["planet"]["type"] == "grid": exodata["planet"]["temp"] = float(self.get_argument("ptempg")) exodata["planet"]["chem"] = str(self.get_argument("pchem")) exodata["planet"]["cloud"] = self.get_argument("cloud") exodata["planet"]["mass"] = float(self.get_argument("pmass")) exodata["planet"]["m_unit"] = str(self.get_argument("m_unit")) #baseline exodata["observation"]["baseline"] = float(self.get_argument("baseline")) exodata["observation"]["baseline_unit"] = self.get_argument("baseline_unit") try: exodata["observation"]["target_acq"] = self.get_argument("TA") == 'on' except: exodata["observation"]["target_acq"] = False exodata["observation"]["noccultations"] = float(self.get_argument("numtrans")) exodata["observation"]["sat_level"] = float(self.get_argument("satlevel")) exodata["observation"]["sat_unit"] = self.get_argument("sat_unit") # noise floor, set to 0.0 of no values are input try: observation_type = self.get_argument("noiseModel") if observation_type == "user": # process noise file fileinfo_noise = self.request.files['noiseFile'][0] fname_noise = fileinfo_noise['filename'] extn_noise = os.path.splitext(fname_noise)[1] cname_noise = id + 'noise' + extn_noise with open(os.path.join(__TEMP__, cname_noise), 'wb') as fh_noise: fh_noise.write(fileinfo_noise['body']) exodata["observation"]["noise_floor"] = os.path.join(__TEMP__, cname_noise) else: exodata["observation"]["noise_floor"] = float(self.get_argument("noisefloor")) except: exodata["observation"]["noise_floor"] = 0.0 instrument = self.get_argument("instrument").lower() if instrument == "miri": with open(os.path.join(os.path.dirname(__file__), "reference", "miri_input.json")) as data_file: pandata = json.load(data_file) mirimode = self.get_argument("mirimode") if mirimode not in MIRI_LRS_ALLOWED_SUBARRAYS: raise tornado.web.HTTPError( 400, reason=f"Unsupported MIRI LRS mode: {mirimode}." ) mirisubarray = self.get_argument( "mirisubarray", MIRI_LRS_ALLOWED_SUBARRAYS[mirimode][0] ) if mirisubarray not in MIRI_LRS_ALLOWED_SUBARRAYS[mirimode]: allowed = ", ".join( item.upper() for item in MIRI_LRS_ALLOWED_SUBARRAYS[mirimode] ) raise tornado.web.HTTPError( 400, reason=( f"MIRI LRS {mirimode.upper()} supports only these " f"subarrays: {allowed}." ), ) if (mirimode == "lrsslit"): pandata["configuration"]["instrument"]["mode"] = mirimode pandata["configuration"]["instrument"]["aperture"] = "lrsslit" pandata["configuration"]["detector"]["subarray"] = mirisubarray if instrument == "nirspec": with open(os.path.join(os.path.dirname(__file__), "reference", "nirspec_input.json")) as data_file: pandata = json.load(data_file) nirspecmode = self.get_argument("nirspecmode") nirspecsubarray = self.get_argument("nirspecsubarray") if ( nirspecmode != "prismclear" and nirspecsubarray in NIRSPEC_PRISM_MULTISTRIPE_SUBARRAYS ): raise tornado.web.HTTPError( 400, reason=( "NIRSpec PRISM multistripe subarrays are only " "available with Prism R=100 No filter." ), ) pandata["configuration"]["instrument"]["disperser"] = nirspecmode[0:5] pandata["configuration"]["instrument"]["filter"] = nirspecmode[5:11] pandata["configuration"]["detector"]["subarray"] = nirspecsubarray if instrument == "nircam": with open(os.path.join(os.path.dirname(__file__), "reference", "nircam_input.json")) as data_file: pandata = json.load(data_file) pandata["configuration"]["instrument"]["filter"] = self.get_argument("nircamfilter") pandata["configuration"]["detector"]["subarray"] = self.get_argument("nircamsubarray") pandata["configuration"]["detector"]["readout_pattern"] = ( self.get_argument("nircamreadout", "optimize") ) if instrument == "nircamdhs": with open(os.path.join(os.path.dirname(__file__), "reference", "nircam_dhs_input.json")) as data_file: pandata = json.load(data_file) # SW and LW are observed together, but PandExo displays one at a time. sw_or_lw = self.get_argument("nircammode") if sw_or_lw not in ("sw", "lw"): raise tornado.web.HTTPError( 400, reason="Choose the NIRCam DHS short- or long-wave channel." ) filter_to_sim = f"nircam{sw_or_lw}" if "sw" in filter_to_sim: pair_filter = "nircamlw" else: pair_filter = "nircamsw" pandata["configuration"]["instrument"]["filter"] = self.get_argument(filter_to_sim) pandata["configuration"]["instrument"]["pandexofilterpair"] = self.get_argument(pair_filter) pandata["configuration"]["detector"]["subarray"] = self.get_argument("nircamsubarraydhs") pandata["configuration"]["detector"]["readout_pattern"] = ( self.get_argument("nircamdhsreadout", "optimize") ) if instrument == "niriss": with open(os.path.join(os.path.dirname(__file__), "reference", "niriss_input.json")) as data_file: pandata = json.load(data_file) nirissmode = self.get_argument("nirissmode") pandata["configuration"]["detector"]["subarray"] = nirissmode if nirissmode == "substrip256": try: order = int(self.get_argument("nirissorders")) except ValueError: raise tornado.web.HTTPError( 400, reason="NIRISS SOSS order must be 1 or 2." ) if order not in (1, 2): raise tornado.web.HTTPError( 400, reason="NIRISS SOSS order must be 1 or 2." ) pandata["strategy"]["order"] = order pandata['configuration']['instrument']['instrument'] = instrument.replace('dhs', '') try: validate_online_instrument_configuration(pandata["configuration"]) except ValueError as exc: raise tornado.web.HTTPError(400, reason=str(exc)) # write in optimal groups or set a number try: pandata["configuration"]["detector"]["ngroup"] = int(self.get_argument("optimize")) except: pandata["configuration"]["detector"]["ngroup"] = self.get_argument("optimize") finaldata = {"pandeia_input": pandata, "pandexo_input": exodata} #PandExo stats try: jwst_log(finaldata) except: pass task = self.executor.submit(wrapper, finaldata) self._add_task(id, self.get_argument("calcName"), task, form_data) response = self._get_task_response(id) response['info'] = {} response['location'] = '/calculation/status/{}'.format(id) self.write(dict(response)) self.redirect("../dashboard")
[docs] class ResolveHandler(tornado.web.RequestHandler): """ Resolves a planet by name and returns data on its system """
[docs] async def get(self): name = self.get_argument("name") try: planet_data = (await async_get_target_data(name))[0] planet_data = await add_simbad_magnitudes(planet_data, name) except Exception: logger.exception("Unable to resolve target %s", name) planet_data = None self.write(json.dumps(planet_data))
[docs] class CalculationNewHSTHandler(BaseHandler): """ This request handler deals with processing the form data and submitting a new HST calculation task to the parallelized workers. """
[docs] async def get(self, id=None): try: self.header= pd.read_sql_table('header',db_fort) except: self.header = pd.DataFrame({ 'temp': ['NO GRID DB FOUND'], 'ray' : ['NO GRID DB FOUND'], 'flat':['NO GRID DB FOUND']}) with open(os.path.join(os.path.dirname(__file__), "reference", "exo_input.json")) as data_file: exodata = json.load(data_file) form_data = None if id is not None: form_data = self.buffer[id].form_data all_planets = await get_cached_planet_names() unique_temps = sorted(self.header.temp.unique()) self.render("newHST.html", id=id, temp=list(map(str, unique_temps)), data=exodata, data_json=json.dumps(form_data), planets=all_planets)
[docs] def post(self): """ The post method contains the retured data from the form data ( accessed by using `self.get_argument(...)` for specific arguments, or `self.request.body` to grab the entire returned object. """ form_data = {} for key in self.request.arguments: form_data[key] = self.get_argument(key) id = str(uuid.uuid4())+'h' with open(os.path.join(os.path.dirname(__file__), "reference", "exo_input.json")) as data_file: exodata = json.load(data_file) exodata["telescope"] = 'hst' #star exodata["star"]["jmag"] = float(self.get_argument("Jmag")) try: #only needed for higher accuracy exodata["star"]["hmag"] = float(self.get_argument("Hmag")) except: exodata["star"]["hmag"] = None exodata["star"]["radius"] = float(self.get_argument("rstarc")) exodata["star"]["r_unit"] = str(self.get_argument("rstar_unitc")) try: #only needed for secondary eclipse exodata["star"]["temp"] = float(self.get_argument("stempc")) except: exodata["star"]["temp"] = None #planet exodata["planet"]["radius"] = float(self.get_argument("refradc")) exodata["planet"]["r_unit"] = str(self.get_argument("r_unitc")) depth = exodata["planet"]["radius"]**2 / ((exodata["star"]["radius"] *u.Unit(exodata["star"]["r_unit"]) ) .to(u.Unit(exodata["planet"]["r_unit"]))).value**2 exodata["planet"]["depth"] = depth exodata["planet"]["i"] = float(self.get_argument("i")) exodata["planet"]["ars"] = float(self.get_argument("ars")) exodata["planet"]["period"] = float(self.get_argument("period")) exodata["planet"]["ecc"] = float(self.get_argument("ecc")) try: exodata["planet"]["w"] = float(self.get_argument("w")) except: exodata["planet"]["w"] = 90. exodata["planet"]["transit_duration"] = float(self.get_argument("transit_duration")) # planet model exodata["planet"]["type"] = self.get_argument("planetModel") if exodata["planet"]["type"] == "user": # process planet file fileinfo_plan = self.request.files['planFile'][0] fname_plan = fileinfo_plan['filename'] extn_plan = os.path.splitext(fname_plan)[1] cname_plan = id+'planet' + extn_plan with open(os.path.join(__TEMP__, cname_plan), 'wb') as fh_plan: fh_plan.write(fileinfo_plan['body']) exodata["planet"]["exopath"] = os.path.join(__TEMP__, cname_plan) exodata["planet"]["w_unit"] = self.get_argument("planwunits") exodata["planet"]["f_unit"] = self.get_argument("planfunits") elif exodata["planet"]["type"] == "constant": if self.get_argument("constant_unit") == 'fp/f*': exodata["planet"]["temp"] = float(self.get_argument("ptempc")) exodata["planet"]["f_unit"] = 'fp/f*' elif self.get_argument("constant_unit") == 'rp^2/r*^2': exodata["planet"]["f_unit"] = 'rp^2/r*^2' elif exodata["planet"]["type"] == "grid": exodata["planet"]["mass"] = float(self.get_argument("pmass")) exodata["planet"]["m_unit"] = str(self.get_argument("m_unit")) exodata["planet"]["temp"] = float(self.get_argument("ptempg")) exodata["planet"]["chem"] = str(self.get_argument("pchem")) exodata["planet"]["cloud"] = self.get_argument("cloud") exodata["observation"]["noise_floor"] = 0.0 exodata["calculation"] = 'scale' if (self.get_argument("instrument")=="STIS"): with open(os.path.join(os.path.dirname(__file__), "reference", "stis_input.json")) as data_file: pandata = json.load(data_file) stismode = self.get_argument("stismode") if (self.get_argument("instrument")=="WFC3"): with open(os.path.join(os.path.dirname(__file__), "reference", "wfc3_input.json")) as data_file: pandata = json.load(data_file) pandata["configuration"]['detector']['subarray'] = self.get_argument("subarray") pandata["configuration"]['detector']['nsamp'] = int(self.get_argument("nsamp")) pandata["configuration"]['detector']['samp_seq'] = self.get_argument("samp_seq") pandata["configuration"]['instrument']['disperser'] = self.get_argument("wfc3mode") try: pandata["strategy"]["norbits"] = int(self.get_argument("norbits")) except: pandata["strategy"]["norbits"] = None exodata["observation"]["noccultations"] = int(self.get_argument("noccultations")) pandata["strategy"]["nchan"] = int(self.get_argument("nchan")) pandata["strategy"]["scanDirection"] = self.get_argument("scanDirection") pandata["strategy"]["useFirstOrbit"] = self.get_argument("useFirstOrbit").lower() == 'true' try: pandata["strategy"]["windowSize"] = float(self.get_argument("windowSize")) except: pandata["strategy"]["windowSize"] = 20. pandata["strategy"]["schedulability"] = self.get_argument("schedulability") try: calc_ramp = self.get_argument("ramp") calc_ramp = True except: calc_ramp = False pandata['strategy']['calculateRamp'] = calc_ramp pandata['strategy']['targetFluence'] = float(self.get_argument("targetFluence")) #import pickle as pk #pandata = a['pandeia_input'] #exodata = a['pandexo_input'] finaldata = {"pandeia_input": pandata , "pandexo_input":exodata} #PandExo stats try: hst_log(finaldata) except: pass task = self.executor.submit(wrapper, finaldata) self._add_task(id, self.get_argument("calcName"), task, form_data) response = self._get_task_response_hst(id) response['info'] = {} response['location'] = '/calculation/statushst/{}'.format(id) self.write(dict(response)) self.redirect("../dashboardhst")
[docs] class CalculationStatusHandler(BaseHandler): """ Handlers returning the status of a particular JWST calculation task. """
[docs] def get(self, id): response = self._get_task_response(id) if self.request.connection.stream.closed(): return self.write(dict(response))
[docs] class CalculationStatusHSTHandler(BaseHandler): """ Handlers returning the status of a particular HST calculation task. """
[docs] def get(self, id): response = self._get_task_response_hst(id) if self.request.connection.stream.closed(): return self.write(dict(response))
[docs] class CalculationDownloadTextHandler(BaseHandler):
[docs] def get(self, id): result = self._get_task_result(id) if self.request.connection.stream.closed(): return self.set_header('Content-Type', 'text/plain; charset=utf-8') self.set_header('Content-Disposition', 'attachment; filename=sim_obs.txt') if "FinalSpectrum" in result: #JWST result output = "#wave spectrum spectrum_w_rand error_w_floor\n" spec = result["FinalSpectrum"] for i in range(len(spec["wave"])): output += "{} {} {} {}\n".format( spec["wave"][i], spec["spectrum"][i], spec["spectrum_w_rand"][i], spec["error_w_floor"][i]) elif "planet_spec" in result: #HST result output = "#wave spectrum error\n" spec = result["planet_spec"] for i in range(len(spec["binwave"])): output += "{} {} {}\n".format(round(spec["binwave"][i], 5), spec["binspec"][i], spec["error"]) else: return None self.write(output) self.finish()
[docs] class CalculationDownloadHandler(BaseHandler): """ Handlers returning the downloaded data of a particular calculation task. Handlers returning the status of a particular calculation task. """
[docs] def get(self, id): result = self._get_task_result(id) if self.request.connection.stream.closed(): return file_name = "ETC-calculation" +id+".p" with open(os.path.join(__TEMP__,file_name), "wb") as f: pickle.dump(result, f) buf_size = 4096 self.set_header('Content-Type', 'application/octet-stream') self.set_header('Content-Disposition', 'attachment; filename=' + file_name) with open(os.path.join(__TEMP__,file_name), "rb") as f: while True: data = f.read(buf_size) if not data: break self.write(data) allfiles = os.listdir(__TEMP__) for i in allfiles: if i.find(id) != -1: os.remove(os.path.join(__TEMP__,i)) self.finish()
[docs] class CalculationDownloadPandInHandler(BaseHandler): """ Handlers returning the downloaded data of a particular calculation task. Handlers returning the status of a particular calculation task. """
[docs] def get(self, id): result = self._get_task_result(id) if self.request.connection.stream.closed(): return file_name = "PandExo-Input-file"+id+".txt" #with open(os.path.join(__TEMP__,file_name), "w") as f: # pickle.dump(result, f) np.savetxt(os.path.join(__TEMP__,file_name), np.transpose([result['w'], result['alpha']])) buf_size = 4096 self.set_header('Content-Type', 'application/octet-stream') self.set_header('Content-Disposition', 'attachment; filename=' + file_name) with open(os.path.join(__TEMP__,file_name), "rb") as f: while True: data = f.read(buf_size) if not data: break self.write(data) allfiles = os.listdir(__TEMP__) for i in allfiles: if i.find(id) != -1: os.remove(os.path.join(__TEMP__,i)) self.finish()
[docs] class CalculationViewHandler(BaseHandler): """ This handler deals with passing the results from Pandeia to the `create_component_jwst` function which generates the Bokeh interative plots. """
[docs] def get(self, id): result = self._get_task_result(id) script, div = create_component_jwst(result) if 'apt_div' in result: div['apt_div'] = result['apt_div'] div['calculation_div'] = result['calculation_div'] else: div['timing_div'] = result['timing_div'] div['input_div'] = result['input_div'] div['warnings_div'] = result['warnings_div'] #delete files allfiles = os.listdir(__TEMP__) for i in allfiles: if i.find(id) != -1: os.remove(os.path.join(__TEMP__,i)) self.render("view.html", script=script, div=div, id=id)
[docs] class CalculationViewHSTHandler(BaseHandler): """ This handler deals with passing the results from Pandeia to the `create_component_hst` function which generates the Bokeh interative plots. """
[docs] def get(self, id): result = self._get_task_result(id) script, div = create_component_hst(result) div['info_div'] = result['info_div'] self.render("viewhst.html", script=script, div=div, id=id)
[docs] async def run_tornado(): tornado.options.parse_command_line() BaseHandler.executor = ProcessPoolExecutor(max_workers=options.workers) app = Application() app.listen(options.port, xheaders=True, reuse_port=True) shutdown_event = asyncio.Event() await shutdown_event.wait()
[docs] def main(): tornado.ioloop.IOLoop.current().run_sync(run_tornado)
if __name__ == "__main__": main()