def sn_slider_update(self): """ Update exposure when SNR slider is changed. """ new_sn = pre_decode(self.snratio) new_expt = pre_decode(self.exptime) #new_sn = self.refs["snr_slider"].value #new_expt = self.refs["exp_slider"].value if self.verbose: print("calling sn_updater with sn = {} and exptime {}".format( new_sn, new_expt)) self.exposure.unknown = "magnitude" self.exposure.exptime = new_expt #pre_decode(self.exptime) self.exposure.snr = new_sn #pre_decode(self.snratio) vmag = self.exposure.recover('magnitude')[4].value distance = pre_decode(self.distance) distmod = 5. * np.log10((distance.value + 1e-5) * 1e6) - 5. lmsource = self.refs["cmd_lim_source"] lmsource.data = { 'mags': [distmod - vmag - 0.4], 'maglabel': ["{:4.1f}".format(vmag)], 'sn': ["{}".format(new_sn.value)], 'x_mag': [3.8], 'x_sn': [3.2] } # the 0.4 is just for display purposes (text alignment)
def load(self): # Get the filename from the bokeh interface calcid = self.refs["load_filename"].value #Load the file code = self.load_file(calcid) if not code: #everything went fine #Update the interface self.refs["update_save"].disabled = False self.current_save = calcid self.refs["exp_slider"].value = pre_decode(self.exptime).value self.refs["mag_slider"].value = pre_decode( self.renorm_magnitude).value self.refs["ap_slider"].value = pre_decode(self.aperture).value self.refs["snr_slider"].value = pre_decode(self.snratio).value temp = self.templates.index(self.spectrum_type) self.refs["template_select"].value = self.template_options[temp] self.controller(None, None, None) errmsg = [ "Calculation ID {} loaded successfully.".format(calcid), "Calculation ID {} does not exist, please try again.".format( calcid), "Load unsuccessful; please contact the administrators.", "There was an error restoring the save state; please contact" " the administrators." ][code] if code == 0 and self.load_mismatch: errmsg += "<br><br><b><i>Load Mismatch Warning:</b></i> "\ "The saved model parameters did not match the " \ "parameter values saved for this tool. Saved " \ "model values were given priority." self.refs["load_message"].text = errmsg
def _print_initcon(self, verbose): if verbose: #These are our initial conditions print('Telescope diffraction limit: {}'.format(pre_decode(self.telescope.diff_limit_wavelength))) print('Telescope aperture: {}'.format(pre_decode(self.telescope.aperture))) print('Telescope temperature: {}'.format(pre_decode(self.telescope.temperature))) print('Pivot waves: {}'.format(nice_print(self.pivotwave))) print('Pixel sizes: {}'.format(nice_print(self.pixel_size))) print('AB mag zero points: {}'.format(nice_print(self.ab_zeropoint))) print('Quantum efficiency: {}'.format(nice_print(self.total_qe))) print('Aperture correction: {}'.format(nice_print(self.ap_corr))) print('Bandpass resolution: {}'.format(nice_print(self.bandpass_r))) print('Derived_bandpass: {}'.format(nice_print(self.derived_bandpass))) print('Detector read noise: {}'.format(nice_print(self.detector_rn))) print('Dark rate: {}'.format(nice_print(self.dark_current)))
def _update_exptime(self): """ Calculate the exposure time to achieve the desired S/N for the given SED. """ self.camera._print_initcon(self.verbose) #We no longer need to check the inputs, since they are now tracked #attributes instead. #Convert JsonUnits to Quantities for calculations (_snr, _nexp) = self.recover('snr', 'n_exp') (_total_qe, _detector_rn, _dark_current) = self.recover('camera.total_qe', 'camera.detector_rn', 'camera.dark_current') snr2 = -(_snr**2) fstar = self._fstar fsky = self.camera._fsky(verbose=self.verbose) Npix = self.camera._sn_box(self.verbose) thermal = pre_decode(self.camera.c_thermal(verbose=self.verbose)) a = (_total_qe * fstar)**2 b = snr2 * (_total_qe * (fstar + fsky) + thermal + _dark_current * Npix) c = snr2 * _detector_rn**2 * Npix * _nexp texp = ((-b + np.sqrt(b**2 - 4 * a * c)) / (2 * a)).to(u.s) #serialize with JsonUnit for transportation self._exptime = pre_encode(texp) return True #completed successfully
def exposure_update(self): """ Update the exposure when sliders are updated. """ self.update_exposure_params() self.exposure.unknown = 'snr' self.exposure.exptime = pre_decode(self.exptime) new_snrs = self._derived_snrs etcsource = self.refs["cmd_etc_source"] mlsource = self.refs["cmd_mag_source"] etcsource.data = { "mags": mlsource.data["mags"], "snr": new_snrs, "snr_label": new_snrs.astype('|S4'), 'x': np.full(5, 3.2).tolist(), 'y': np.arange(-10.4, 10., 5., dtype=float).tolist() } if self.verbose: print("mag_values in exposure_update: {}".format( etcsource.data['y'])) print("new_snrs in exposure_update: {}".format( etcsource.data['snr'])) noise_scale_factor = int( 10000. / new_snrs[1] ) # divide an number by the S/N at AB = 5 absolute - set up to make it come out right self.refs["noise_stream"].event(noise=int(noise_scale_factor))
def sed(self): """ Return a spectrum, redshifted if necessary. We don't just store the redshifted spectrum because pysynphot doesn't save the original, it just returns a new copy of the spectrum with redshifted wavelengths. """ sed = pre_decode(self._sed) z = self.recover('redshift') return pre_encode(sed.redshift(z))
def update_exposure(self): """ Update the exposure's parameters and recalculate everything. """ #We turn off calculation at the beginning so we can update everything #at once without recalculating every time we change something self.exposure.disable() #Update all the parameters self.exposure.n_exp = 3 self.exposure.exptime = pre_decode(self.exptime) self.exposure.snr = pre_decode(self.snratio) self.exposure.sed_id = self.spectrum_type self.telescope.aperture = self.aperture self.exposure.renorm_sed(pre_decode(self.renorm_magnitude)) #Now we turn calculations back on and recalculate self.exposure.enable() #Set the spectrum template self.spectrum_template = pre_decode(self.exposure.sed)
def update_exposure(self): """ Update the exposure's parameters and recalculate everything. """ #We turn off calculation at the beginning so we can update everything #at once without recalculating every time we change something #Update all the parameters self.telescope.aperture = self.aperture self.spectrograph.mode = self.grating self.exposure.exptime = pre_decode(self.exptime) self.exposure.redshift = pre_decode(self.redshift) self.exposure.sed_id = self.spectrum_type self.exposure.renorm_sed(pre_decode(self.renorm_magnitude), bandpass='******') #Now we turn calculations back on and recalculate self.exposure.enable(make_image=self.make_image, photon_count=self.photon_count, gain=self.EM_gain) #Set the spectrum template self.spectrum_template = pre_decode(self.exposure.sed)
def nice_print(arr): """ Utility to make the verbose output more readable. """ arr = pre_decode(arr) #in case it's a JsonUnit serialization if isinstance(arr, u.Quantity): l = ['{:.2f}'.format(i) for i in arr.value] else: l = ['{:.2f}'.format(i) for i in arr] return ', '.join(l)
def recover(self, *args): """ Since we are now using JsonUnits to handle Bokeh Server JSON problems, this is a convenience method to ease the pain of converting attributes back to Quantities so they can be used for calculation. """ out = [] for arg in args: attr = reduce(getattr, [self] + arg.split('.')) #for nested dot access out.append(pre_decode(attr)) if len(out) == 1: return out[0] return out
def c_thermal(self, verbose=True): """ Calculate the thermal emission counts for the telescope. """ #Convert to Quantities for calculation. (bandpass, pivotwave, aperture, ota_emissivity, total_qe, pixel_size) = self.recover('derived_bandpass', 'pivotwave', 'telescope.effective_aperture', 'telescope.ota_emissivity', 'total_qe', 'pixel_size') box = self._sn_box(verbose) bandwidth = bandpass.to(u.cm) h = const.h.to(u.erg * u.s) # Planck's constant erg s c = const.c.to(u.cm / u.s) # speed of light [cm / s] energy_per_photon = h * c / pivotwave.to(u.cm) / u.ph D = aperture.to(u.cm) # telescope diameter in cm Omega = (pixel_size**2 * box * u.pix).to(u.sr) planck = pre_decode(self.planck) qepephot = total_qe * planck / energy_per_photon if verbose: print('Planck spectrum: {}'.format(nice_print(planck))) print('QE * Planck / E_phot: {}'.format(nice_print(qepephot))) print('E_phot: {}'.format(nice_print(energy_per_photon))) print('Omega: {}'.format(nice_print(Omega))) thermal = (ota_emissivity * planck / energy_per_photon * (np.pi / 4. * D**2) * total_qe * Omega * bandwidth ) #serialize with JsonUnit for transportation return pre_encode(thermal)
def _update_magnitude(self): """ Calculate the limiting magnitude given the desired S/N and exposure time. """ self.camera._print_initcon(self.verbose) #We no longer need to check the inputs, since they are now tracked #attributes instead. #Grab values for calculation (_snr, _exptime, _nexp) = self.recover('snr', 'exptime', 'n_exp') (f0, c_ap, D, dlam) = self.recover('camera.ab_zeropoint', 'camera.ap_corr', 'telescope.effective_aperture', 'camera.derived_bandpass') (QE, RN, DC) = self.recover('camera.total_qe', 'camera.detector_rn', 'camera.dark_current') exptime = _exptime.to(u.s) D = D.to(u.cm) fsky = self.camera._fsky(verbose=self.verbose) Npix = self.camera._sn_box(self.verbose) c_t = pre_decode(self.camera.c_thermal(verbose=self.verbose)) snr2 = -(_snr**2) a0 = (QE * exptime)**2 b0 = snr2 * QE * exptime c0 = snr2 * ((QE * fsky + c_t + Npix * DC) * exptime + (RN**2 * Npix * _nexp)) k = (-b0 + np.sqrt(b0**2 - 4. * a0 * c0)) / (2. * a0) flux = (4. * k) / (f0 * c_ap * np.pi * D**2 * dlam) self._magnitude = pre_encode(-2.5 * np.log10(flux.value) * u.mag('AB')) return True #completed successfully
def crowding_limit(self, crowding_apparent_magnitude, distance): """ Calculate the crowding limit. """ aperture = pre_decode(self.aperture) g_solar = 4.487 stars_per_arcsec_limit = 10. * (aperture / 2.4)**2 # (JD1) distmod = 5. * np.log10((distance + 1e-5) * 1e6) - 5. #why this fudge? g = np.array( -self.dataframe.gmag ) # absolute magnitude in the g band - the -1 corrects for the sign flip in load_datasets (for plotting) g.sort( ) # now sorted from brightest to faintest in terms of absolute g magnitude luminosity = 10.**(-0.4 * (g - g_solar)) total_luminosity_in_lsun = np.sum(luminosity) number_of_stars = np.full_like( g, 1.0 ) # initial number of stars in each "bin" - a list of 10,000 stars total_absolute_magnitude = -2.5 * np.log10( total_luminosity_in_lsun) + g_solar apparent_brightness_at_this_distance = total_absolute_magnitude + distmod scale_factor = 10.**(-0.4 * (crowding_apparent_magnitude - apparent_brightness_at_this_distance)) cumulative_number_of_stars = np.cumsum( scale_factor * number_of_stars) # the cumulative run of luminosity in Lsun crowding_limit = np.interp(stars_per_arcsec_limit.value, cumulative_number_of_stars, g) return crowding_limit
def _update_snr(self): """ Calculate the SNR for the given exposure time and SED. """ self.camera._print_initcon(self.verbose) #We no longer need to check the inputs, since they are now tracked #attributes instead. #Convert JsonUnits to Quantities for calculations (_exptime, _nexp, n_bands) = self.recover('_exptime', 'n_exp', 'camera.n_bands') (_total_qe, _detector_rn, _dark_current) = self.recover('camera.total_qe', 'camera.detector_rn', 'camera.dark_current') #calculate everything number_of_exposures = np.full(n_bands, _nexp) desired_exp_time = (np.full(n_bands, _exptime.value) * _exptime.unit).to(u.second) time_per_exposure = desired_exp_time / number_of_exposures fstar = self._fstar signal_counts = _total_qe * fstar * desired_exp_time fsky = self.camera._fsky(verbose=self.verbose) sky_counts = _total_qe * fsky * desired_exp_time shot_noise_in_signal = np.sqrt(signal_counts) shot_noise_in_sky = np.sqrt(sky_counts) sn_box = self.camera._sn_box(self.verbose) read_noise = _detector_rn**2 * sn_box * number_of_exposures dark_noise = sn_box * _dark_current * desired_exp_time thermal = pre_decode(self.camera.c_thermal(verbose=self.verbose)) thermal_counts = desired_exp_time * thermal snr = signal_counts / np.sqrt(signal_counts + sky_counts + read_noise + dark_noise + thermal_counts) if self.verbose: print('# of exposures: {}'.format(_nexp)) print('Time per exposure: {}'.format(time_per_exposure[0])) print('Signal counts: {}'.format(nice_print(signal_counts))) print('Signal shot noise: {}'.format( nice_print(shot_noise_in_signal))) print('Sky counts: {}'.format(nice_print(sky_counts))) print('Sky shot noise: {}'.format(nice_print(shot_noise_in_sky))) print('Total read noise: {}'.format(nice_print(read_noise))) print('Dark current noise: {}'.format(nice_print(dark_noise))) print('Thermal counts: {}'.format(nice_print(thermal_counts))) print() print('SNR: {}'.format(snr)) print('Max SNR: {} in {} band'.format( snr.max(), self.camera.bandnames[snr.argmax()])) #serialize with JsonUnit for transportation self._snr = pre_encode(snr) return True #completed successfully
def renorm_sed(self, new_mag, bandpass='******'): sed = self.recover('_sed') self._sed = renorm_sed(sed, pre_decode(new_mag), bandpass=bandpass) self.calculate(make_image=False)