def run(opt, channel, frame_time, total_observing_time, exposure_time): exosim_msg('Create noise timelines ... ') st = time.time() yaw_jitter = pitch_jitter = frame_osf = None jitter_file = opt.aocs.PointingModel().replace("__path__", opt.__path__) if hasattr(opt.aocs, 'pointing_rms'): jit_rms = opt.aocs.pointing_rms() else: jit_rms = None yaw_jitter, pitch_jitter, frame_osf = exolib.pointing_jitter( jitter_file, total_observing_time, frame_time, rms=jit_rms) if opt.aocs.pointing_scan_throw() > 0: pitch_jitter = exolib.pointing_add_scan( pitch_jitter, scan_throw_arcsec=opt.aocs.pointing_scan_throw(), frame_time=frame_time, frame_osf=frame_osf, exposure_time=exposure_time) for key in channel.keys(): key, noise, outputPointingTl = channel_noise_estimator( channel[key], key, yaw_jitter, pitch_jitter, frame_osf, frame_time, opt, total_observing_time, exposure_time) channel[key].noise = noise channel[key].outputPointingTl = outputPointingTl exosim_msg(' - execution time: {:.0f} msec.\n'.format( (time.time() - st) * 1000.0))
def run(opt, channel, frame_time, total_observing_time, exposure_time): exosim_msg('Create noise timelines ... ') st = time.time() yaw_jitter = pitch_jitter = frame_osf = None jitter_file = opt.aocs.PointingModel().replace("__path__", opt.__path__) if hasattr(opt.aocs, 'pointing_rms'): jit_rms = opt.aocs.pointing_rms() else: jit_rms = None yaw_jitter, pitch_jitter, frame_osf = exolib.pointing_jitter(jitter_file, total_observing_time, frame_time, rms = jit_rms) if opt.aocs.pointing_scan_throw()>0: pitch_jitter = exolib.pointing_add_scan(pitch_jitter, scan_throw_arcsec=opt.aocs.pointing_scan_throw(), frame_time = frame_time, frame_osf = frame_osf, exposure_time = exposure_time) for key in channel.keys(): key, noise, outputPointingTl = channel_noise_estimator(channel[key], key, yaw_jitter, pitch_jitter, frame_osf, frame_time, opt) channel[key].noise = noise channel[key].outputPointingTl = outputPointingTl exosim_msg(' - execution time: {:.0f} msec.\n'.format( (time.time()-st)*1000.0))
def __init__(self, wl, level=1.0): exosim_msg('Instantiate Zodi ... ') st = time.time() spectrum = level*(3.5e-14*exolib.planck(wl, 5500*pq.K) + exolib.planck(wl, 270*pq.K) * 3.58e-8) self.sed = sed.Sed(wl, spectrum ) self.transmission = sed.Sed(wl, np.ones(wl.size)) self.units = 'W m**-2 sr**-1 micron**-1' exosim_msg(' - execution time: {:.0f} msec.\n'.format((time.time()-st)*1000.0))
def __init__(self, wl, level=1.0): exosim_msg('Instantiate Zodi ... ') st = time.time() spectrum = level * (3.5e-14 * exolib.planck(wl, 5500 * pq.K) + exolib.planck(wl, 270 * pq.K) * 3.58e-8) self.sed = sed.Sed(wl, spectrum) self.transmission = sed.Sed(wl, np.ones(wl.size)) self.units = 'W m**-2 sr**-1 micron**-1' exosim_msg(' - execution time: {:.0f} msec.\n'.format( (time.time() - st) * 1000.0))
def run_exosim(opt=None): star, planet = exosim.modules.astroscene.run(opt) exosim_msg(' Stellar SED: {:s}\n'.format(os.path.basename(star.ph_filename))) exosim_msg(' Star luminosity {:s}\n'.format(star.luminosity)) #Instanciate Zodi zodi = exosim.classes.zodiacal_light(opt.common.common_wl, level=1.0) exosim.exolib.sed_propagation(star.sed, zodi.transmission) #Run Instrument Model channel = exosim.modules.instrument.run(opt, star, planet, zodi) #Create Signal timelines frame_time, total_observing_time, exposure_time = exosim.modules.timeline_generator.run(opt, channel, planet) #Generate noise timelines exosim.modules.noise.run(opt, channel, frame_time, total_observing_time, exposure_time) #Save exosim.modules.output.run(opt, channel, planet) return star, planet, zodi, channel
def run_exosim(opt=None): star, planet = exosim.modules.astroscene.run(opt) exosim_msg(' Stellar SED: {:s}\n'.format(os.path.basename( star.ph_filename))) exosim_msg(' Star luminosity {:f}\n'.format(star.luminosity)) #Instanciate Zodi zodi = exosim.classes.zodiacal_light(opt.common.common_wl, level=1.0) exosim.exolib.sed_propagation(star.sed, zodi.transmission) #Run Instrument Model channel = exosim.modules.instrument.run(opt, star, planet, zodi) #Create Signal timelines frame_time, total_observing_time, exposure_time = exosim.modules.timeline_generator.run( opt, channel, planet) #Generate noise timelines exosim.modules.noise.run(opt, channel, frame_time, total_observing_time, exposure_time) #Save exosim.modules.output.run(opt, channel, planet) return star, planet, zodi, channel
zodi = exosim.classes.zodiacal_light(opt.common.common_wl, level=1.0) exosim.exolib.sed_propagation(star.sed, zodi.transmission) #Run Instrument Model channel = exosim.modules.instrument.run(opt, star, planet, zodi) #Create Signal timelines frame_time, total_observing_time, exposure_time = exosim.modules.timeline_generator.run( opt, channel, planet) #Generate noise timelines exosim.modules.noise.run(opt, channel, frame_time, total_observing_time, exposure_time) #Save exosim.modules.output.run(opt, channel, planet) return star, planet, zodi, channel if __name__ == "__main__": xmlFileNameDefault = 'exosim_defaults.xml' xmlFileName = sys.argv[1] if len(sys.argv) > 1 else xmlFileNameDefault exosim_msg('Reading options from file ... \n') opt = exosim.Options( filename=xmlFileName).opt #, default_path = exosim.__path__[0]).opt # modify_opt(opt) star, planet, zodi, channel = run_exosim(opt)
def run(opt, channel, planet): exosim_msg('Create signal-only timelines ... ') st = time.time() # Estimate simulation length. Needs to be in units of hours. T14 = planet.get_t14(planet.planet.i.rescale(pq.rad), planet.planet.a.rescale(pq.m), planet.planet.P.rescale(pq.s), planet.planet.R.rescale(pq.m), planet.planet.star.R.rescale(pq.m)).rescale(pq.hour) total_observing_time = T14*(1.0+opt.timeline.before_transit()+opt.timeline.after_transit()) time_at_transit = T14*(0.5+opt.timeline.before_transit()) frame_time = 1.0/opt.timeline.frame_rate() # Frame exposure, CLK for key in channel.keys(): # Having exposure_time here will allow to have different integration times # for different focal planes. exposure_time = opt.timeline.exposure_time() # Exposure time # Estimate NDR rates multiaccum = opt.timeline.multiaccum() # Number of NDRs per exposure allocated_time = (opt.timeline.nGND()+ opt.timeline.nNDR0()+ opt.timeline.nRST()) * frame_time NDR_time = (exposure_time-allocated_time)/(multiaccum-1) nNDR = np.ceil(NDR_time/frame_time).astype(np.int).take(0) # Estimate the base block of CLK cycles base = [opt.timeline.nGND().take(0), opt.timeline.nNDR0().take(0)] for x in xrange(multiaccum-1): base.append(nNDR) base.append(opt.timeline.nRST().take(0)) # Recalculate exposure time and estimates how many exposures are needed exposure_time = sum(base)*frame_time number_of_exposures = np.ceil( (total_observing_time/exposure_time).simplified.take(0)).astype(np.int) total_observing_time = exposure_time*number_of_exposures frame_sequence=np.tile(base, number_of_exposures) # This is Nij time_sequence = frame_time * frame_sequence.cumsum() # This is Tij # Physical time of each NDR ndr_time = np.dstack([time_sequence[1+i::len(base)] \ for i in xrange(multiaccum)]).flatten()*time_sequence.units # Number of frames contributing to each NDR ndr_sequence = np.dstack([frame_sequence[1+i::len(base)] \ for i in xrange(multiaccum)]).flatten() # CLK counter of each NDR ndr_cumulative_sequence = (ndr_time/frame_time).astype(np.int).magnitude # Create the noise-less timeline channel[key].set_timeline(exposure_time, frame_time, ndr_time, ndr_sequence, ndr_cumulative_sequence) # Apply lightcurve model cr = channel[key].planet.sed[::channel[key].osf] cr_wl = channel[key].planet.wl[::channel[key].osf] isPrimaryTransit = True if opt.astroscene.transit_is_primary()=='True' else False channel[key].lc, z, i0, i1 = planet.get_light_curve(cr, cr_wl, channel[key].ndr_time, time_at_transit, isPrimaryTransit) channel[key].set_z(z) exosim_msg(' - execution time: {:.0f} msec.\n'.format( (time.time()-st)*1000.0)) return frame_time, total_observing_time, exposure_time
def run(opt, channel, planet): exosim_msg('Save to file ... ') st = time.time() out_path = os.path.expanduser(opt.common.ExoSimOutputPath().replace( '__path__', opt.__path__)) if not os.path.exists(out_path): os.mkdir(out_path) existing_sims = glob.glob(os.path.join(out_path, 'sim_*')) if not existing_sims: # Empty list sim_number = 0 else: sim_number = sorted([np.int(l.split('_')[-1]) for l in existing_sims])[-1] sim_number += 1 out_path = os.path.join(out_path, 'sim_{:04d}'.format(sim_number)) os.mkdir(out_path) for key in channel.keys(): n_ndr = channel[key].tl_shape[-1] multiaccum = np.int(opt.timeline.multiaccum()) n_exp = n_ndr // multiaccum hdu = fits.PrimaryHDU() hdu.header['NEXP'] = (n_exp, 'Number of exposures') hdu.header['MACCUM'] = (multiaccum, 'Multiaccum') hdu.header['TEXP'] = (channel[key].exposure_time.item(), 'Exp Time [s]') hdu.header['PLANET'] = (planet.planet.name, 'Planet name') hdu.header['STAR'] = (planet.planet.star.name, 'Star name') #### Detector Simulation Values hdu.header['POINTRMS'] = (opt.aocs.pointing_rms.val.item(), 'Model Pointing RMS') tempVal = list( filter(lambda x: x.name == key, opt.channel))[0].detector_pixel.Idc.val.magnitude.item() hdu.header['DET_I_DC'] = (tempVal, 'Detector dark current') tempVal = list(filter( lambda x: x.name == key, opt.channel))[0].detector_pixel.sigma_ro.val.magnitude.item() hdu.header['DETROERR'] = (tempVal, 'Detector readout noise in e-rms') tempVal = list(filter(lambda x: x.name == key, opt.channel))[0].plate_scale.val.rescale( 'degree').magnitude.item() hdu.header['CDELT1'] = (tempVal, 'Degrees/pixel') hdu.header['CDELT2'] = (tempVal, 'Degrees/pixel') hdulist = fits.HDUList(hdu) for i in range(channel[key].tl_shape[-1]): hdu = fits.ImageHDU(channel[key].noise[..., i].astype(np.float32), name='NOISE') hdu.header['EXPNUM'] = (i // multiaccum, 'Exposure Number') hdu.header['ENDRNUM'] = (i % multiaccum, 'NDR Number') hdu.header['EXTNAME'] = 'NOISE' hdulist.append(hdu) # Create column data col1 = fits.Column( name='Wavelength {:f}'.format(channel[key].wl_solution.units), format='E', array=channel[key].wl_solution[channel[key].offs::channel[key]. osf]) col2 = fits.Column( name='Input Contrast Ratio', format='E', array=channel[key].planet.sed[channel[key].offs::channel[key].osf]) col3 = fits.Column( name='Stellar SED', format='E', array=channel[key].star.sed[channel[key].offs::channel[key].osf]) cols = fits.ColDefs([col1, col2, col3]) tbhdu = fits.BinTableHDU.from_columns(cols) tbhdu.name = 'INPUTS' hdulist.append(tbhdu) ######## hdu = fits.ImageHDU(channel[key].outputPointingTl, name='SIM_POINTING') hdulist.append(hdu) ############## hdu = fits.ImageHDU(channel[key].ldc, name='LD_COEFF') hdulist.append(hdu) ############ col1 = fits.Column(name='Time {:f}'.format( channel[key].ndr_time.units), format='E', array=channel[key].ndr_time) col2 = fits.Column(name='z', format='E', array=channel[key].z) cols = fits.ColDefs([col1, col2]) tbhdu = fits.BinTableHDU.from_columns(cols) tbhdu.name = 'TIME' hdulist.append(tbhdu) ######### hdu = fits.ImageHDU(channel[key].lc, name='LIGHT_CURVES') hdulist.append(hdu) #print hdulist hdulist.writeto(os.path.join(out_path, '{:s}_signal.fits'.format(key))) exosim_msg(' - execution time: {:.0f} msec.\n'.format( (time.time() - st) * 1000.0))
def run(opt, star, planet, zodi): exosim_msg('Run instrument model ... ') st = time.time() instrument_emission = Sed(star.sed.wl, np.zeros(star.sed.wl.size, dtype=np.float64)* \ pq.W/pq.m**2/pq.um/pq.sr) instrument_transmission = Sed(star.sed.wl, np.ones(star.sed.wl.size, dtype=np.float64)) for op in opt.common_optics.optical_surface: dtmp_tr = np.loadtxt(op.transmission.replace('__path__', opt.__path__), delimiter=',') dtmp_em = np.loadtxt(op.emissivity.replace('__path__', opt.__path__), delimiter=',') tr = Sed(dtmp_tr[:, 0] * pq.um, dtmp_tr[:, 1] * pq.dimensionless) tr.rebin(opt.common.common_wl) em = Sed(dtmp_em[:, 0] * pq.um, dtmp_em[:, 1] * pq.dimensionless) em.rebin(opt.common.common_wl) exolib.sed_propagation(star.sed, tr) exolib.sed_propagation(zodi.sed, tr) exolib.sed_propagation(instrument_emission, tr, emissivity=em, temperature=op()) instrument_transmission.sed = instrument_transmission.sed * tr.sed channel = {} for ch in opt.channel: #if ch.name != 'NIR Spec': continue channel[ch.name] = Channel(star.sed, planet.cr, zodi.sed, instrument_emission, instrument_transmission, options=ch) ch_optical_surface = ch.optical_surface if isinstance(ch.optical_surface, list) else \ [ch.optical_surface] for op in ch.optical_surface: dtmp = np.loadtxt(op.transmission.replace('__path__', opt.__path__), delimiter=',') tr = Sed(dtmp[:,0]*pq.um, \ dtmp[:,1]*pq.dimensionless) tr.rebin(opt.common.common_wl) em = Sed(dtmp[:,0]*pq.um, \ dtmp[:,2]*pq.dimensionless) em.rebin(opt.common.common_wl) exolib.sed_propagation(channel[ch.name].star, tr) exolib.sed_propagation(channel[ch.name].zodi, tr) exolib.sed_propagation(channel[ch.name].emission, \ tr, emissivity=em,temperature=op()) channel[ch.name].transmission.sed *= tr.sed # BUG workaround. There is a bug in the binning function. If transmission is zero, # it is rebiined to a finite, very small value. This needs to be fixed! # For now, I set to zero all transmission smaller than an arbitrary value #idx = np.where(channel[ch.name].transmission.sed < 1.0e-5) #channel[ch.name].star.sed[idx] = 0.0*channel[ch.name].star.sed.units #channel[ch.name].zodi.sed[idx] = 0.0*channel[ch.name].zodi.sed.units #channel[ch.name].emission.sed[idx] = 0.0*channel[ch.name].emission.sed.units #channel[ch.name].transmission.sed[idx] = 0.0*channel[ch.name].transmission.sed.units # Convert spectral signals dtmp = np.loadtxt(ch.qe().replace('__path__', opt.__path__), delimiter=',') qe = Sed(dtmp[:,0]*pq.um, \ dtmp[:,1]*pq.dimensionless) Responsivity = qe.sed * qe.wl.rescale( pq.m) / (spc.c * spc.h * pq.m * pq.J) * pq.UnitQuantity( 'electron', symbol='e-') Re = scipy.interpolate.interp1d(qe.wl, Responsivity) Aeff = 0.25 * np.pi * opt.common_optics.TelescopeEffectiveDiameter()**2 Omega_pix = 2.0 * np.pi * (1.0 - np.cos(np.arctan(0.5 / ch.wfno()))) * pq.sr Apix = ch.detector_pixel.pixel_size()**2 channel[ch.name].star.sed *= Aeff * \ Re(channel[ch.name].star.wl)*pq.UnitQuantity('electron', 1*pq.counts, symbol='e-')/pq.J channel[ch.name].zodi.sed *= Apix * Omega_pix * \ Re(channel[ch.name].zodi.wl)*pq.UnitQuantity('electron', 1*pq.counts, symbol='e-')/pq.J channel[ch.name].emission.sed *= Apix * Omega_pix * \ Re(channel[ch.name].emission.wl)*pq.UnitQuantity('electron', 1*pq.counts, symbol='e-')/pq.J ### create focal plane #1# allocate focal plane with pixel oversampling such that Nyquist sampling is done correctly fpn = ch.array_geometry() fp = np.zeros((fpn * ch.osf()).astype(np.int)) #2# This is the current sampling interval in the focal plane. fp_delta = ch.detector_pixel.pixel_size() / ch.osf() #3# Load dispersion law if ch.type == 'spectrometer': if hasattr(ch, "dispersion"): dtmp = np.loadtxt(ch.dispersion.path.replace( '__path__', opt.__path__), delimiter=',') ld = scipy.interpolate.interp1d(dtmp[..., 2] * pq.um + ch.dispersion().rescale(pq.um), dtmp[..., 0], bounds_error=False, kind='slinear', fill_value=0.0) elif hasattr(ch, "ld"): # wl = ld[0] + ld[1](x - ld[2]) = ld[1]*x + ld[0]-ldp[1]*ld[2] ld = np.poly1d( (ch.ld()[1], ch.ld()[0] - ch.ld()[1] * ch.ld()[2])) else: exolib.exosim_error("Dispersion law not defined.") #4a# Estimate pixel and wavelength coordinates x_pix_osr = np.arange(fp.shape[1]) * fp_delta x_wav_osr = ld(x_pix_osr.rescale( pq.um).magnitude) * pq.um # walength on each x pixel channel[ch.name].wl_solution = x_wav_osr elif ch.type == 'photometer': #4b# Estimate pixel and wavelength coordinates idx = np.where(channel[ch.name].transmission.sed > channel[ch.name].transmission.sed.max() / np.e) x_wav_osr = np.linspace( channel[ch.name].transmission.wl[idx].min().item(), channel[ch.name].transmission.wl[idx].max().item(), 8 * ch.osf()) * channel[ch.name].transmission.wl.units x_wav_center = (channel[ch.name].transmission.wl[idx]*channel[ch.name].transmission.sed[idx]).sum() / \ channel[ch.name].transmission.sed[idx].sum() channel[ch.name].wl_solution = np.repeat(x_wav_center, fp.shape[1]) else: exolib.exosim_error( "Channel should be either photometer or spectrometer.") d_x_wav_osr = np.zeros_like(x_wav_osr) idx = np.where(x_wav_osr > 0.0) d_x_wav_osr[idx] = np.gradient(x_wav_osr[idx]) if np.any(d_x_wav_osr < 0): d_x_wav_osr *= -1.0 #5# Generate PSFs, one in each detector pixel along spectral axis psf = exolib.Psf(x_wav_osr, ch.wfno(), \ fp_delta, shape='airy') #6# Save results in Channel class channel[ch.name].fp_delta = fp_delta channel[ch.name].psf = psf channel[ch.name].fp = fp channel[ch.name].osf = np.int(ch.osf()) channel[ch.name].offs = np.int(ch.pix_offs()) channel[ch.name].planet.sed *= channel[ch.name].star.sed channel[ch.name].star.rebin(x_wav_osr) channel[ch.name].planet.rebin(x_wav_osr) channel[ch.name].zodi.rebin(x_wav_osr) channel[ch.name].emission.rebin(x_wav_osr) channel[ch.name].transmission.rebin(x_wav_osr) channel[ch.name].star.sed *= d_x_wav_osr channel[ch.name].planet.sed *= d_x_wav_osr channel[ch.name].zodi.sed *= d_x_wav_osr channel[ch.name].emission.sed *= d_x_wav_osr #7# Populate focal plane with monochromatic PSFs if ch.type == 'spectrometer': j0 = np.round(np.arange(fp.shape[1]) - psf.shape[1] // 2).astype( np.int) elif ch.type == 'photometer': j0 = np.repeat(fp.shape[1] // 2, x_wav_osr.size) else: exolib.exosim_error( "Channel should be either photometer or spectrometer.") j1 = j0 + psf.shape[1] idx = np.where((j0 >= 0) & (j1 < fp.shape[1]))[0] i0 = fp.shape[0] // 2 - psf.shape[0] // 2 + channel[ch.name].offs i1 = i0 + psf.shape[0] for k in idx: channel[ch.name].fp[i0:i1, j0[k]:j1[k]] += psf[...,k] * \ channel[ch.name].star.sed[k] #9# Now deal with the planet planet_response = np.zeros(fp.shape[1]) i0p = np.unravel_index(np.argmax(channel[ch.name].psf.sum(axis=2)), channel[ch.name].psf[..., 0].shape)[0] for k in idx: planet_response[j0[k]:j1[k]] += psf[i0p, :, k] * channel[ ch.name].planet.sed[k] #9# Allocate pixel response function kernel, kernel_delta = exolib.PixelResponseFunction( channel[ch.name].psf.shape[0:2], 7 * ch.osf(), # NEED TO CHANGE FACTOR OF 7 ch.detector_pixel.pixel_size(), lx=ch.detector_pixel.pixel_diffusion_length()) channel[ch.name].fp = exolib.fast_convolution( channel[ch.name].fp, channel[ch.name].fp_delta, kernel, kernel_delta) ## TODO CHANGE THIS: need to convolve planet with pixel response function channel[ch.name].planet = Sed( channel[ch.name].wl_solution, planet_response / (1e-30 + fp[(i0 + i1) // 2, ...])) ## Fix units channel[ ch.name].fp = channel[ch.name].fp * channel[ch.name].star.sed.units channel[ch.name].planet.sed = channel[ ch.name].planet.sed * pq.dimensionless ## Deal with diffuse radiation if ch.type == 'spectrometer': channel[ch.name].zodi.sed = scipy.convolve( channel[ch.name].zodi.sed, np.ones(np.int(ch.slit_width() * channel[ch.name].opt.osf())), 'same') * channel[ch.name].zodi.sed.units channel[ch.name].emission.sed = scipy.convolve( channel[ch.name].emission.sed, np.ones(np.int(ch.slit_width() * channel[ch.name].opt.osf())), 'same') * channel[ch.name].emission.sed.units elif ch.type == 'photometer': channel[ch.name].zodi.sed = np.repeat( channel[ch.name].zodi.sed.sum(), channel[ch.name].wl_solution.size) channel[ch.name].zodi.wl = channel[ch.name].wl_solution channel[ch.name].emission.sed = np.repeat( channel[ch.name].emission.sed.sum(), channel[ch.name].wl_solution.size) channel[ch.name].emission.wl = channel[ch.name].wl_solution else: exolib.exosim_error( "Channel should be either photometer or spectrometer.") exosim_msg(' - execution time: {:.0f} msec.\n'.format( (time.time() - st) * 1000.0)) return channel pass
def create_jitter_noise(channel, x_jit, y_jit, frame_osf, frame_time, key, opt, visualize=False): outputPointingTL = create_output_pointing_timeline( x_jit, y_jit, frame_osf, ndrCumSeq=channel.ndr_cumulative_sequence) jitter_x = channel.osf * (x_jit / channel.opt.plate_scale()).simplified jitter_y = channel.osf * (y_jit / channel.opt.plate_scale()).simplified fp_units = channel.fp.units fp = channel.fp.magnitude osf = np.int32(channel.osf) offs = np.int32(channel.offs) magnification_factor = np.ceil( max(3.0 / jitter_x.std(), 3.0 / jitter_y.std())) if (magnification_factor > 1): try: mag = np.int(magnification_factor.item()) | 1 except: mag = np.int(magnification_factor) | 1 fp = exolib.oversample(fp, mag) #### See ExoSim Issue 42, for following. # fp = np.where(fp >= 0.0, fp, 1e-10) osf *= mag offs = mag * offs + mag // 2 jitter_x *= mag jitter_y *= mag if opt.noise.EnableSpatialJitter() != 'True': jitter_y *= 0.0 if opt.noise.EnableSpectralJitter() != 'True': jitter_x *= 0.0 jitter_x = np.round(jitter_x) jitter_y = np.round(jitter_y) noise = np.zeros((int(fp.shape[0] // osf), int(fp.shape[1] // osf), 0)).astype(np.float32) # multiple orders reshaping if (hasattr(channel.opt, "dispersion") and (isinstance(channel.opt.dispersion, list))): exosim_msg( "Using Dask for parellized processing of timeline datacube...") cores = opt.common.num_cores lim = cores // len(channel.opt.dispersion) indxRanges = np.arange(0, lim + 1) * channel.tl_shape[2] // lim # need to separate the "fake" focal planes so that they don't bleed into one another # when jitter is applied. client = Client(n_workers=cores, memory_limit=f'{opt.common.gb_per_core}GB') temp_shape = (noise.shape[0], noise.shape[1] // len(channel.opt.dispersion), 0) fp = np.array(np.split(fp, len(channel.opt.dispersion), 1)) results = [] for d, _ in enumerate(channel.opt.dispersion): jitters = [] for i in range(len(indxRanges) - 1): startIdx = int(indxRanges[i]) endIdx = int(indxRanges[i + 1]) tfp = fp[d].astype(np.float32) tosf = osf.astype(np.int32) tndr = channel.ndr_sequence[startIdx:endIdx].astype(np.int32) tndrs = channel.ndr_cumulative_sequence[ startIdx:endIdx].astype(np.int32) tfosf = frame_osf.astype(np.int32) tjitx = jitter_x.magnitude.astype(np.int32) tjity = jitter_y.magnitude.astype(np.int32) txoff = offs.astype(np.int32) tyoff = offs.astype(np.int32) jitter = delayed(c_create_jitter_noise)(tfp, tosf, tndr, tndrs, tfosf, tjitx, tjity, x_offs=txoff, y_offs=tyoff) jitters.append(jitter) result = da.concatenate([ da.from_delayed( jit, dtype='float32', shape=(temp_shape[0], temp_shape[1], endIdx - startIdx)) for jit in jitters ], axis=2) results.append(result) noise = da.stack(results, axis=0) noise = da.concatenate((noise[da.arange(noise.shape[0])]), axis=1) if visualize: noise.visualize(filename='exosim-dask-noise.png') noise = noise.compute() client.close() else: indxRanges = np.arange(0, 7) * channel.tl_shape[2] // 6 for i in range(len(indxRanges) - 1): startIdx = int(indxRanges[i]) endIdx = int(indxRanges[i + 1]) noise = np.append( noise, c_create_jitter_noise( fp.astype(np.float32), osf.astype(np.int32), channel.ndr_sequence[startIdx:endIdx].astype(np.int32), channel.ndr_cumulative_sequence[startIdx:endIdx].astype( np.int32), frame_osf.astype(np.int32), jitter_x.magnitude.astype(np.int32), jitter_y.magnitude.astype(np.int32), x_offs=offs.astype(np.int32), y_offs=offs.astype(np.int32)).astype(np.float32), axis=2) ## Multiply units to noise in 2 steps, to avoid ## Quantities memmory inefficiency qq = channel.ndr_sequence * fp_units * frame_time noise = noise * qq return noise, outputPointingTL
exosim_msg(' Stellar SED: {:s}\n'.format(os.path.basename(star.ph_filename))) exosim_msg(' Star luminosity {:s}\n'.format(star.luminosity)) #Instanciate Zodi zodi = exosim.classes.zodiacal_light(opt.common.common_wl, level=1.0) exosim.exolib.sed_propagation(star.sed, zodi.transmission) #Run Instrument Model channel = exosim.modules.instrument.run(opt, star, planet, zodi) #Create Signal timelines frame_time, total_observing_time, exposure_time = exosim.modules.timeline_generator.run(opt, channel, planet) #Generate noise timelines exosim.modules.noise.run(opt, channel, frame_time, total_observing_time, exposure_time) #Save exosim.modules.output.run(opt, channel, planet) return star, planet, zodi, channel if __name__ == "__main__": xmlFileNameDefault = 'exosim_twinkle_ep.xml' xmlFileName = sys.argv[1] if len(sys.argv)>1 else xmlFileNameDefault exosim_msg('Reading options from file ... \n') opt = exosim.Options(filename=xmlFileName).opt #, default_path = exosim.__path__[0]).opt # modify_opt(opt) star, planet, zodi, channel = run_exosim(opt) #pass
def run(opt, channel, planet): exosim_msg('Create signal-only timelines ... ') st = time.time() # Estimate simulation length. Needs to be in units of hours. T14 = planet.get_t14(planet.planet.i.rescale(pq.rad), planet.planet.a.rescale(pq.m), planet.planet.P.rescale(pq.s), planet.planet.R.rescale(pq.m), planet.planet.star.R.rescale(pq.m)).rescale(pq.hour) total_observing_time = T14*(1.0+opt.timeline.before_transit()+opt.timeline.after_transit()) time_at_transit = T14*(0.5+opt.timeline.before_transit()) frame_time = 1.0/opt.timeline.frame_rate() # Frame exposure, CLK peak1Max = [] peak2Max = [] ##### FOR TESTING - TO BE DELETED ADD_STELAR_VARIABILITY = False if ADD_STELAR_VARIABILITY: workDir = '/home/alp/Work/Ariel/StarVarModels/' modelFile = 'M2V_SED_Timeseries.fits' fileName = workDir + modelFile wlArr, tArr, lc = loadExternalLightCurve(fileName) print('qqqqqqqqqqqqqqqqqqqqqqqq===================',lc.shape) from scipy import interpolate f = interpolate.interp2d(wlArr, tArr, lc, kind='cubic') #qqqq ##### TESTING END - TO BE DELETED for key in channel.keys(): # Having exposure_time here will allow to have different integration times # for different focal planes. exposure_time = opt.timeline.exposure_time() # Exposure time # Estimate NDR rates multiaccum = opt.timeline.multiaccum() # Number of NDRs per exposure allocated_time = (opt.timeline.nGND()+ opt.timeline.nNDR0()+ opt.timeline.nRST()) * frame_time NDR_time = (exposure_time-allocated_time)/(multiaccum-1) nNDR = np.ceil(NDR_time/frame_time).astype(np.int).take(0) # Estimate the base block of CLK cycles base = [opt.timeline.nGND().take(0), opt.timeline.nNDR0().take(0)] for x in range(int(multiaccum)-1): base.append(nNDR) base.append(opt.timeline.nRST().take(0)) # Recalculate exposure time and estimates how many exposures are needed exposure_time = sum(base)*frame_time number_of_exposures = np.ceil( (total_observing_time/exposure_time).simplified.take(0)).astype(np.int) total_observing_time = exposure_time*number_of_exposures frame_sequence=np.tile(base, number_of_exposures) # This is Nij time_sequence = frame_time * frame_sequence.cumsum() # This is Tij # Physical time of each NDR ndr_time = np.dstack([time_sequence[1+i::len(base)] \ for i in range(int(multiaccum))]).flatten()*time_sequence.units # Number of frames contributing to each NDR ndr_sequence = np.dstack([frame_sequence[1+i::len(base)] \ for i in range(int(multiaccum))]).flatten() # CLK counter of each NDR ndr_cumulative_sequence = (ndr_time/frame_time).astype(np.int).magnitude # Create the noise-less timeline channel[key].set_timeline(exposure_time, frame_time, ndr_time, ndr_sequence, ndr_cumulative_sequence) # Apply lightcurve model cr = channel[key].planet.sed[::channel[key].osf] cr_wl = channel[key].planet.wl[::channel[key].osf] isPrimaryTransit = True if opt.astroscene.transit_is_primary()=='True' else False apply_phase_curve = True if opt.astroscene.apply_phase_curve()=='True' else False channel[key].lc, z, i0, i1 = planet.get_light_curve(cr, cr_wl, channel[key].ndr_time, time_at_transit, isPrimaryTransit, apply_phase_curve) channel[key].ldc = planet.u ## ADD StelarVariability Model here ## Temporary TEST block if ADD_STELAR_VARIABILITY: tInterp = channel[key].ndr_time - channel[key].ndr_time[0] wlInterp = cr_wl lcCorr = f(wlInterp[::-1], tInterp)[::-1, :] print() print('\nCR_WL MIN/MAX', np.min(cr_wl), np.max(cr_wl)) print('=================================', f(0.93345514, 0)) print('=================================', f(3, 0)) print('##############################') print('##############################') print('## APPLYING STELLAR VAR NOISE') print('##############################') print('##############################') import matplotlib.pyplot as plt plt.plot(lcCorr) plt.show() channel[key].lc *=lcCorr.T ## ADD SpotSim LC correction here as one line. ## Temporary TEST block if False: print('QQQQQQQQQQQQQQQ MODDED LD CoEFFS') channel[key].ldc = np.zeros_like(channel[key].ldc) print('QQQQQQQQQQQQQQQ MODDED LD CoEFFS') spotsList2 = [ {'r':3, 'x': 27.65, 'y':23.63}, {'r':6, 'x':-27.41, 'y':-0.19}, {'r':6, 'x':-30.41, 'y':23.19}, {'r':7, 'x': -76.41, 'y':-0.19}, ] spotParams = { 'CONTspot':800, 'CONTfac':100, 'Q':1.6 } Tstar = float(planet.planet.star.T) a = float(planet.planet.a.rescale('m')) b = np.cos(np.radians(float(planet.planet.i)))*\ float(planet.planet.a.rescale('m'))/float(planet.planet.star.R.rescale('m')) exosim_msg('\n') SM = SpotSim2(spotsList2, spotParams, Tstar, a, b, pix=200) lc_spotted = np.ones_like(channel[key].lc) for i in range(len(cr_wl)): exosim_msg('SpotSim - %s - %d/%d\r'%(key, i+1, len(cr_wl))) c1 = channel[key].ldc[i, 0] c2 = channel[key].ldc[i, 1] if float(cr[i])>0: qqqlc, qqqlc_i = SM.calcLC(float(cr_wl[i]), float(cr[i]), c1, c2, z) lc_spotted[i, :] = qqqlc import matplotlib.pyplot as plt tPlot = channel[key].ndr_time for i in range(len(cr_wl)): if i%(2*np.round((len(cr)/32))**1)==0 and np.mean(lc_spotted[i, :])<.999999: yVals = lc_spotted[i, :]# - channel[key].lc[i, :] offset = float(np.mean(yVals[(tPlot>3000) & ((tPlot<3300))])) peak1Max.append([cr_wl[i], float(np.max((yVals-offset)[(tPlot>2200) & ((tPlot<2700))]))]) peak2Max.append([cr_wl[i], float(np.max((yVals-offset)[(tPlot>3400) & ((tPlot<3800))]))]) plt.figure(1) plt.plot(tPlot, yVals, label = '%.2f um'%cr_wl[i]) #plt.plot(tPlot, lc_spotted[i, :], label = '%.2f um'%cr_wl[i]) if key.startswith('FGS'): break channel[key].set_z(z) exosim_msg(' - execution time: {:.0f} msec.\n'.format( (time.time()-st)*1000.0)) return frame_time, total_observing_time, exposure_time
def run(opt, star, planet, zodi): exosim_msg('Run instrument model ... ') st = time.time() instrument_emission = Sed(star.sed.wl, np.zeros(star.sed.wl.size, dtype=np.float64)* \ pq.W/pq.m**2/pq.um/pq.sr) instrument_transmission = Sed(star.sed.wl, np.ones(star.sed.wl.size, dtype=np.float64)) for op in opt.common_optics.optical_surface: dtmp=np.loadtxt(op.transmission.replace('__path__', opt.__path__), delimiter=',') tr = Sed(dtmp[:,0]*pq.um,dtmp[:,1]*pq.dimensionless) tr.rebin(opt.common.common_wl) em = Sed(dtmp[:,0]*pq.um,dtmp[:,2]*pq.dimensionless) em.rebin(opt.common.common_wl) exolib.sed_propagation(star.sed, tr) exolib.sed_propagation(zodi.sed, tr) exolib.sed_propagation(instrument_emission, tr, emissivity=em, temperature=op()) instrument_transmission.sed = instrument_transmission.sed*tr.sed channel = {} for ch in opt.channel: #if ch.name != 'NIR Spec': continue channel[ch.name] = Channel(star.sed, planet.cr, zodi.sed, instrument_emission, instrument_transmission, options=ch) ch_optical_surface = ch.optical_surface if isinstance(ch.optical_surface, list) else \ [ch.optical_surface] for op in ch.optical_surface: dtmp=np.loadtxt(op.transmission.replace( '__path__', opt.__path__), delimiter=',') tr = Sed(dtmp[:,0]*pq.um, \ dtmp[:,1]*pq.dimensionless) tr.rebin(opt.common.common_wl) em = Sed(dtmp[:,0]*pq.um, \ dtmp[:,2]*pq.dimensionless) em.rebin(opt.common.common_wl) exolib.sed_propagation(channel[ch.name].star, tr) exolib.sed_propagation(channel[ch.name].zodi, tr) exolib.sed_propagation(channel[ch.name].emission, \ tr, emissivity=em,temperature=op()) channel[ch.name].transmission.sed *= tr.sed # BUG workaround. There is a bug in the binning function. If transmission is zero, # it is rebiined to a finite, very small value. This needs to be fixed! # For now, I set to zero all transmission smaller than an arbitrary value #idx = np.where(channel[ch.name].transmission.sed < 1.0e-5) #channel[ch.name].star.sed[idx] = 0.0*channel[ch.name].star.sed.units #channel[ch.name].zodi.sed[idx] = 0.0*channel[ch.name].zodi.sed.units #channel[ch.name].emission.sed[idx] = 0.0*channel[ch.name].emission.sed.units #channel[ch.name].transmission.sed[idx] = 0.0*channel[ch.name].transmission.sed.units # Convert spectral signals dtmp=np.loadtxt(ch.qe().replace( '__path__', opt.__path__), delimiter=',') qe = Sed(dtmp[:,0]*pq.um, \ dtmp[:,1]*pq.dimensionless) Responsivity = qe.sed * qe.wl.rescale(pq.m)/(spc.c * spc.h * pq.m * pq.J)*pq.UnitQuantity('electron', symbol='e-') Re = scipy.interpolate.interp1d(qe.wl, Responsivity) Aeff = 0.25*np.pi*opt.common_optics.TelescopeEffectiveDiameter()**2 Omega_pix = 2.0*np.pi*(1.0-np.cos(np.arctan(0.5/ch.wfno())))*pq.sr Apix = ch.detector_pixel.pixel_size()**2 channel[ch.name].star.sed *= Aeff * \ Re(channel[ch.name].star.wl)*pq.UnitQuantity('electron', 1*pq.counts, symbol='e-')/pq.J channel[ch.name].zodi.sed *= Apix * Omega_pix * \ Re(channel[ch.name].zodi.wl)*pq.UnitQuantity('electron', 1*pq.counts, symbol='e-')/pq.J channel[ch.name].emission.sed *= Apix * Omega_pix * \ Re(channel[ch.name].emission.wl)*pq.UnitQuantity('electron', 1*pq.counts, symbol='e-')/pq.J ### create focal plane #1# allocate focal plane with pixel oversampling such that Nyquist sampling is done correctly fpn = ch.array_geometry() fp = np.zeros( (fpn*ch.osf()).astype(np.int) ) #2# This is the current sampling interval in the focal plane. fp_delta = ch.detector_pixel.pixel_size() / ch.osf() #3# Load dispersion law if ch.type == 'spectrometer': if hasattr(ch, "dispersion"): dtmp=np.loadtxt(ch.dispersion.path.replace( '__path__', opt.__path__), delimiter=',') ld = scipy.interpolate.interp1d(dtmp[...,2]*pq.um + ch.dispersion().rescale(pq.um), dtmp[...,0], bounds_error=False, fill_value=0.0) elif hasattr(ch, "ld"): # wl = ld[0] + ld[1](x - ld[2]) = ld[1]*x + ld[0]-ldp[1]*ld[2] ld = np.poly1d( (ch.ld()[1], ch.ld()[0]-ch.ld()[1]*ch.ld()[2]) ) else: exolib.exosim_error("Dispersion law not defined.") #4a# Estimate pixel and wavelength coordinates x_pix_osr = np.arange(fp.shape[1]) * fp_delta x_wav_osr = ld(x_pix_osr.rescale(pq.um))*pq.um # walength on each x pixel channel[ch.name].wl_solution = x_wav_osr elif ch.type == 'photometer': #4b# Estimate pixel and wavelength coordinates idx = np.where(channel[ch.name].transmission.sed > channel[ch.name].transmission.sed.max()/np.e) x_wav_osr = np.linspace(channel[ch.name].transmission.wl[idx].min().item(), channel[ch.name].transmission.wl[idx].max().item(), 8 * ch.osf()) * channel[ch.name].transmission.wl.units x_wav_center = (channel[ch.name].transmission.wl[idx]*channel[ch.name].transmission.sed[idx]).sum() / \ channel[ch.name].transmission.sed[idx].sum() channel[ch.name].wl_solution = np.repeat(x_wav_center, fp.shape[1]) else: exolib.exosim_error("Channel should be either photometer or spectrometer.") d_x_wav_osr = np.zeros_like (x_wav_osr) idx = np.where(x_wav_osr > 0.0) d_x_wav_osr[idx] = np.gradient(x_wav_osr[idx]) if np.any(d_x_wav_osr < 0): d_x_wav_osr *= -1.0 #5# Generate PSFs, one in each detector pixel along spectral axis psf = exolib.Psf(x_wav_osr, ch.wfno(), \ fp_delta, shape='airy') #6# Save results in Channel class channel[ch.name].fp_delta = fp_delta channel[ch.name].psf = psf channel[ch.name].fp = fp channel[ch.name].osf = np.int(ch.osf()) channel[ch.name].offs = np.int(ch.pix_offs()) channel[ch.name].planet.sed *= channel[ch.name].star.sed channel[ch.name].star.rebin(x_wav_osr) channel[ch.name].planet.rebin(x_wav_osr) channel[ch.name].zodi.rebin(x_wav_osr) channel[ch.name].emission.rebin(x_wav_osr) channel[ch.name].transmission.rebin(x_wav_osr) channel[ch.name].star.sed *= d_x_wav_osr channel[ch.name].planet.sed *= d_x_wav_osr channel[ch.name].zodi.sed *= d_x_wav_osr channel[ch.name].emission.sed *= d_x_wav_osr #7# Populate focal plane with monochromatic PSFs if ch.type == 'spectrometer': j0 = np.round(np.arange(fp.shape[1]) - psf.shape[1]/2).astype(np.int) elif ch.type == 'photometer': j0 = np.repeat(fp.shape[1]//2, x_wav_osr.size) else: exolib.exosim_error("Channel should be either photometer or spectrometer.") j1 = j0 + psf.shape[1] idx = np.where((j0>=0) & (j1 < fp.shape[1]))[0] i0 = fp.shape[0]/2 - psf.shape[0]/2 + channel[ch.name].offs i1 = i0 + psf.shape[0] for k in idx: channel[ch.name].fp[i0:i1, j0[k]:j1[k]] += psf[...,k] * \ channel[ch.name].star.sed[k] #9# Now deal with the planet planet_response = np.zeros(fp.shape[1]) i0p = np.unravel_index(np.argmax(channel[ch.name].psf.sum(axis=2)), channel[ch.name].psf[...,0].shape)[0] for k in idx: planet_response[j0[k]:j1[k]] += psf[i0p,:,k] * channel[ch.name].planet.sed[k] #9# Allocate pixel response function kernel, kernel_delta = exolib.PixelResponseFunction( channel[ch.name].psf.shape[0:2], 7*ch.osf(), # NEED TO CHANGE FACTOR OF 7 ch.detector_pixel.pixel_size(), lx = ch.detector_pixel.pixel_diffusion_length()) channel[ch.name].fp = exolib.fast_convolution( channel[ch.name].fp, channel[ch.name].fp_delta, kernel, kernel_delta) ## TODO CHANGE THIS: need to convolve planet with pixel response function channel[ch.name].planet = Sed(channel[ch.name].wl_solution, planet_response/(1e-30+fp[(i0+i1)//2, ...])) ## Fix units channel[ch.name].fp = channel[ch.name].fp*channel[ch.name].star.sed.units channel[ch.name].planet.sed = channel[ch.name].planet.sed*pq.dimensionless ## Deal with diffuse radiation if ch.type == 'spectrometer': channel[ch.name].zodi.sed = scipy.convolve(channel[ch.name].zodi.sed, np.ones(np.int(ch.slit_width()*channel[ch.name].opt.osf())), 'same') * channel[ch.name].zodi.sed.units channel[ch.name].emission.sed = scipy.convolve(channel[ch.name].emission.sed, np.ones(np.int(ch.slit_width()*channel[ch.name].opt.osf())), 'same') * channel[ch.name].emission.sed.units elif ch.type == 'photometer': channel[ch.name].zodi.sed = np.repeat(channel[ch.name].zodi.sed.sum(), channel[ch.name].wl_solution.size) channel[ch.name].zodi.wl = channel[ch.name].wl_solution channel[ch.name].emission.sed = np.repeat(channel[ch.name].emission.sed.sum(), channel[ch.name].wl_solution.size) channel[ch.name].emission.wl = channel[ch.name].wl_solution else: exolib.exosim_error("Channel should be either photometer or spectrometer.") channel[ch.name].fp += channel[ch.name].zodi.sed channel[ch.name].fp += channel[ch.name].emission.sed exosim_msg(' - execution time: {:.0f} msec.\n'.format((time.time()-st)*1000.0)) return channel pass
def run(opt, star, planet, zodi): test_fp = False test_multord = False exosim_msg('Run instrument model ... ') st = time.time() instrument_emission = Sed(star.sed.wl, np.zeros(star.sed.wl.size, dtype=np.float64)* \ pq.W/pq.m**2/pq.um/pq.sr) instrument_transmission = Sed(star.sed.wl, np.ones(star.sed.wl.size, dtype=np.float64)) for op in opt.common_optics.optical_surface: dtmp_tr = np.loadtxt(op.transmission.replace('__path__', opt.__path__), delimiter=',') dtmp_em = np.loadtxt(op.emissivity.replace('__path__', opt.__path__), delimiter=',') tr = Sed(dtmp_tr[:, 0] * pq.um, dtmp_tr[:, 1] * pq.dimensionless) tr.rebin(opt.common.common_wl) em = Sed(dtmp_em[:, 0] * pq.um, dtmp_em[:, 1] * pq.dimensionless) em.rebin(opt.common.common_wl) exolib.sed_propagation(star.sed, tr) exolib.sed_propagation(zodi.sed, tr) exolib.sed_propagation(instrument_emission, tr, emissivity=em, temperature=op()) instrument_transmission.sed = instrument_transmission.sed * tr.sed channel = {} for ch in opt.channel: channel[ch.name] = Channel(star.sed, planet.cr, zodi.sed, instrument_emission, instrument_transmission, options=ch) ch_optical_surface = ch.optical_surface if isinstance(ch.optical_surface, list) else \ [ch.optical_surface] for op in ch_optical_surface: dtmp = np.loadtxt(op.transmission.replace('__path__', opt.__path__), delimiter=',') tr = Sed(dtmp[:,0]*pq.um, \ dtmp[:,1]*pq.dimensionless) tr.rebin(opt.common.common_wl) em = Sed(dtmp[:,0]*pq.um, \ dtmp[:,2]*pq.dimensionless) em.rebin(opt.common.common_wl) exolib.sed_propagation(channel[ch.name].star, tr) exolib.sed_propagation(channel[ch.name].zodi, tr) exolib.sed_propagation(channel[ch.name].emission, \ tr, emissivity=em,temperature=op()) channel[ch.name].transmission.sed *= tr.sed # Convert spectral signals dtmp = np.loadtxt(ch.qe().replace('__path__', opt.__path__), delimiter=',') qe = Sed(dtmp[:,0]*pq.um, \ dtmp[:,1]*pq.dimensionless) Responsivity = qe.sed * qe.wl.rescale( pq.m) / (spc.c * spc.h * pq.m * pq.J) * pq.UnitQuantity( 'electron', symbol='e-') Re = scipy.interpolate.interp1d(qe.wl, Responsivity) Aeff = 0.25 * np.pi * opt.common_optics.TelescopeEffectiveDiameter()**2 Omega_pix = 2.0 * np.pi * (1.0 - np.cos(np.arctan(0.5 / ch.wfno()))) * pq.sr Apix = ch.detector_pixel.pixel_size()**2 channel[ch.name].star.sed *= Aeff * \ Re(channel[ch.name].star.wl)*pq.UnitQuantity('electron', 1*pq.counts, symbol='e-')/pq.J channel[ch.name].zodi.sed *= Apix * Omega_pix * \ Re(channel[ch.name].zodi.wl)*pq.UnitQuantity('electron', 1*pq.counts, symbol='e-')/pq.J channel[ch.name].emission.sed *= Apix * Omega_pix * \ Re(channel[ch.name].emission.wl)*pq.UnitQuantity('electron', 1*pq.counts, symbol='e-')/pq.J ### create focal plane #1# allocate focal plane with pixel oversampling such that Nyquist sampling is done correctly fpn = ch.array_geometry() fp = np.zeros((fpn * ch.osf()).astype(np.int)) #2# This is the current sampling interval in the focal plane. fp_delta = ch.detector_pixel.pixel_size() / ch.osf() #### NEW: for multiple orders. General idea here is to create a new focal #### plane for each order. I'll accomplish this by stacking them horizontally #### and then breaking them apart and adding them back together in the noise module #### after the light curve model is created and applied. Added by David Wright 09/20 # check if we have multiple dispersion files if ch.type == 'spectrometer': if hasattr(ch, "dispersion"): ch_dispersion = ch.dispersion if isinstance(ch.dispersion, list) else \ [ch.dispersion] # allocate some arrays fp_orig = fp.copy() fp = np.zeros( ((fpn[0] * ch.osf()).astype(np.int), (fpn[1] * ch.osf() * len(ch_dispersion)).astype(np.int))) channel[ch.name].wl_solution = np.zeros(fp.shape[1]) channel[ch.name].y_trace = np.zeros( fp.shape[1]) # same size as above m_ord_thru = np.zeros( fp.shape[1]) # throughput for different orders #mult_ord = (hasattr(ch, "dispersion") and (isinstance(ch.dispersion, list))) #if mult_ord: for i, disp in enumerate(ch_dispersion): #for i,disp in enumerate(ch_dispersion[1:]): dtmp = np.loadtxt(disp.path.replace( '__path__', opt.__path__), delimiter=',') wav = dtmp[..., 0] # translate one "focal plane" over, we'll restack later #x_shift = i * fp_delta * fp_orig.shape[1] #pathx = dtmp[...,2]*pq.um + disp.val[0].rescale(pq.um) + x_shift pathx = dtmp[..., 2] * pq.um + disp.val[0].rescale(pq.um) pathy = dtmp[..., 3] * pq.um + disp.val[1].rescale(pq.um) pathint = scipy.interpolate.interp1d(pathx, pathy, bounds_error=False, fill_value=np.nan, kind='linear') ld = scipy.interpolate.interp1d(pathx, wav, bounds_error=False, fill_value=0, kind='linear') # translate as before #x_pix_osr = np.arange(fp_orig.shape[1]) * fp_delta + x_shift x_pix_osr = np.arange(fp_orig.shape[1]) * fp_delta y_pix_osr = pathint(x_pix_osr.rescale(pq.um)) * pq.um x_wav_osr = ld(x_pix_osr.rescale(pq.um)) * pq.um start = i * x_pix_osr.size stop = min(start + x_pix_osr.size, channel[ch.name].wl_solution.size) channel[ch.name].wl_solution[start:stop] = x_wav_osr.copy() channel[ch.name].y_trace[start:stop] = y_pix_osr.copy() if (hasattr(ch.dispersion, "trpath")): dtmp = np.loadtxt(disp.trpath.replace( '__path__', opt.__path__), delimiter=',') tr = Sed(dtmp[:,0]*pq.um, \ dtmp[:,1]*pq.dimensionless) tr.rebin(x_wav_osr) m_ord_thru[start:stop] = tr.sed else: m_ord_thru[start:stop] = np.ones_like( m_ord_thru[start:stop]) # adding these so I don't have to rewrite much of the below code # Interesting behavior: can't *= with quantities channel[ch.name].y_trace = channel[ch.name].y_trace * pq.um channel[ ch.name].wl_solution = channel[ch.name].wl_solution * pq.um y_pix_osr = channel[ch.name].y_trace x_wav_osr = channel[ch.name].wl_solution elif hasattr(ch, "ld"): ld = np.poly1d( (ch.ld()[1], ch.ld()[0] - ch.ld()[1] * ch.ld()[2])) x_pix_osr = np.arange(fp.shape[1]) * fp_delta # Need to be in center of detector. ExoSim no longer assumes this # after my changes. y_pix_osr = (np.zeros_like(x_pix_osr.magnitude) + fp.shape[0] // 2) * fp_delta x_wav_osr = ld(x_pix_osr.rescale(pq.um)) * pq.um channel[ch.name].wl_solution = x_wav_osr m_ord_thru = np.ones( fp.shape[1]) # throughput for different orders else: exolib.exosim_error("Dispersion law not defined.") elif ch.type == 'photometer': #4b# Estimate pixel and wavelength coordinates idx = np.where(channel[ch.name].transmission.sed > channel[ch.name].transmission.sed.max() / np.e) x_wav_osr = np.linspace( channel[ch.name].transmission.wl[idx].min().item(), channel[ch.name].transmission.wl[idx].max().item(), 8 * ch.osf()) * channel[ch.name].transmission.wl.units x_wav_center = (channel[ch.name].transmission.wl[idx]*channel[ch.name].transmission.sed[idx]).sum() / \ channel[ch.name].transmission.sed[idx].sum() channel[ch.name].wl_solution = np.repeat(x_wav_center, fp.shape[1]) # Need to be in center of detector. ExoSim no longer assumes this # after my changes. y_pix_osr = (np.zeros_like(x_pix_osr.magnitude) + fp.shape[0] // 2) * fp_delta m_ord_thru = np.ones( fp.shape[1]) # throughput for different orders else: exolib.exosim_error( "Channel should be either photometer or spectrometer.") d_x_wav_osr = np.zeros_like(x_wav_osr) idx = np.where(x_wav_osr > 0.0) # make sure we don't take the gradient between two orders # the original code would treat all nonzeros as if they were # next to each other, creating bright spots at the point where # the "fake" focal plane is joined if ch.type == 'spectrometer': if hasattr(ch, "dispersion"): idx = np.array(idx[0]) # fix weirdness with [0] num_disp = len(ch_dispersion) for i in np.arange(num_disp): # get a split of idx that covers one "focal plane" idx_split = idx[ np.where((idx < (i + 1) * x_wav_osr.size / num_disp) & (idx >= i * x_wav_osr.size / num_disp))] d_x_wav_osr[idx_split] = np.gradient(x_wav_osr[idx_split]) else: d_x_wav_osr[idx] = np.gradient(x_wav_osr[idx]) else: d_x_wav_osr[idx] = np.gradient(x_wav_osr[idx]) if np.any(d_x_wav_osr < 0): d_x_wav_osr *= -1.0 if ((hasattr(channel[ch.name].opt, "webb_psf")) and (channel[ch.name].opt.webb_psf.use_webbpsf() == "True")): webb_psf_opt = channel[ch.name].opt.webb_psf print("\n Working in instrument.py \n") #print(ch.plate_scale()) #scale = np.rad2deg(ch.plate_scale().item()) ypix, xpix = exolib.get_webb_psf_dims(webb_psf_opt.psf_file(), ch.osf().item()) psf = np.zeros((ypix, xpix, x_wav_osr.size)) for i, _ in enumerate(ch_dispersion): start = i * x_pix_osr.size stop = min(start + x_pix_osr.size, channel[ch.name].wl_solution.size) print(start, stop) psf[...,start:stop] = exolib.webb_Psf_Interp(webb_psf_opt.psf_file(),ch.osf().item(),x_wav_osr[start:stop],\ (y_pix_osr[start:stop].rescale(pq.um)/ch.detector_pixel.pixel_size().rescale(pq.um) * ch.osf().item())) #import matplotlib.pyplot as plt #middle = psf.shape[-1] // 2 #plt.imshow(psf[...,middle]) #plt.show() #sys.exit() #5# Generate PSFs, one in each detector pixel along spectral axis elif ((ch.type == 'spectrometer') and (hasattr(ch, "dispersion"))): psf = exolib.Psf(x_wav_osr, (y_pix_osr.rescale(pq.um)/ch.detector_pixel.pixel_size().rescale(pq.um) * ch.osf().item())\ , ch.wfno(), fp_delta, shape='airy') else: psf = exolib.Psf_orig(x_wav_osr, ch.wfno(), \ fp_delta, shape='airy') # where psf is nans, replace with zeros nanidx = np.isnan(psf) psf[nanidx] = 0.0 # Want to only fill up one oversampled pixel for testing # so that we don't spill into another pixel. Makes summing at a specific # wavelength less of a headache if (test_fp or test_multord): zidx = np.where(psf == 0.0)[-1] psf = np.ones((1, 1, psf.shape[-1])) psf[..., zidx] = 0.0 #6# Save results in Channel class channel[ch.name].fp_delta = fp_delta channel[ch.name].psf = psf channel[ch.name].fp = fp channel[ch.name].osf = np.int(ch.osf()) channel[ch.name].offs = np.int(ch.pix_offs()) channel[ch.name].planet.sed *= channel[ch.name].star.sed channel[ch.name].star.rebin(x_wav_osr) channel[ch.name].planet.rebin(x_wav_osr) channel[ch.name].zodi.rebin(x_wav_osr) channel[ch.name].emission.rebin(x_wav_osr) channel[ch.name].transmission.rebin(x_wav_osr) channel[ch.name].star.sed *= d_x_wav_osr channel[ch.name].planet.sed *= d_x_wav_osr channel[ch.name].zodi.sed *= d_x_wav_osr channel[ch.name].emission.sed *= d_x_wav_osr # this block takes care of throughputs for each spectral order # m_ord_thru is all ones if single-ordered or photometric channel[ch.name].star.sed *= m_ord_thru channel[ch.name].planet.sed *= m_ord_thru channel[ch.name].zodi.sed *= m_ord_thru channel[ch.name].transmission.sed *= m_ord_thru channel[ch.name].emission.sed *= m_ord_thru if ch.type == 'spectrometer': j0 = np.round(np.arange(fp.shape[1]) - psf.shape[1] // 2).astype( np.int) elif ch.type == 'photometer': j0 = np.repeat(fp.shape[1] // 2, x_wav_osr.size) else: exolib.exosim_error( "Channel should be either photometer or spectrometer.") j1 = j0 + psf.shape[1] # loop over fake focal planes here, making a separate idx for each one if ((ch.type == 'spectrometer') and hasattr(ch, "dispersion")): for i, _ in enumerate(ch_dispersion): start = i * x_pix_osr.size stop = (i + 1) * x_pix_osr.size idx = np.where((j0 >= start) & (j1 <= stop) & (np.isfinite(y_pix_osr)))[0] for k in idx: i0 = np.int((y_pix_osr[k].rescale(pq.um)/ch.detector_pixel.pixel_size().rescale(pq.um)\ * ch.osf())//1 - psf.shape[0]//2 + channel[ch.name].offs) i1 = np.int(i0 + psf.shape[0]) #cut = min(j1[k],stop) psfcut = min(psf.shape[1], j1[k] - j0[k]) #if (j0[k] > cut): print('uh oh') channel[ch.name].fp[i0:i1, j0[k]:j1[k]] += psf[:,:psfcut,k] * \ channel[ch.name].star.sed[k] #9# Now deal with the planet planet_response = np.zeros(fp.shape[1]) i0p = np.unravel_index( np.argmax(channel[ch.name].psf.sum(axis=2)), channel[ch.name].psf[..., 0].shape)[0] for k in idx: psfcut = min(psf.shape[1], j1[k] - j0[k]) planet_response[j0[k]:j1[k]] += psf[ i0p, :psfcut, k] * channel[ch.name].planet.sed[k] else: idx = np.where((j0 >= 0) & (j1 < fp.shape[1]))[0] i0 = fp.shape[0] // 2 - psf.shape[0] // 2 + channel[ch.name].offs i1 = i0 + psf.shape[0] for k in idx: channel[ch.name].fp[i0:i1, j0[k]:j1[k]] += psf[...,k] * \ channel[ch.name].star.sed[k] #9# Now deal with the planet planet_response = np.zeros(fp.shape[1]) i0p = np.unravel_index(np.argmax(channel[ch.name].psf.sum(axis=2)), channel[ch.name].psf[..., 0].shape)[0] for k in idx: planet_response[j0[k]:j1[k]] += psf[i0p, :, k] * channel[ ch.name].planet.sed[k] # For testing multiple orders. if (test_multord): import sys fdir = "tests/reference/" fname = "mord_test_order1.npy" fname_xwav = "xwav_order1.npy" np.save(fdir + fname, channel[ch.name].fp) np.save(fdir + fname_xwav, x_wav_osr) sys.exit("Testing multiple orders. Exiting early.") # For Testing. Save out the focal plane at this stage. # Meant to test with 55 Cancri black body out of transit # 0.5m telescope # R=254 # fnum = 18.5 if (test_fp): import sys fdir = "tests/reference/" fname_fp = "sim_image_mult-ord.npy" fname_xwav = "xwav_mult-ord.npy" fname_exact = "exact_image_mult-ord.npy" np.save(fdir + fname_xwav, x_wav_osr) star_temp = 5172 * pq.K #star_radius = (0.943 * 6.957e5 * pq.km).rescale(pq.m) star_radius = 659628500.0 * pq.m print(star_radius) #star_distance = (12.5901 * pq.parsec).rescale(pq.m) star_distance = 3.8849022915525e+17 * pq.m print(star_distance) omega_star = np.pi * (star_radius / star_distance)**2 * pq.sr print("omega_star_instrument.py", omega_star) print(omega_star) area_tel = np.pi * 0.25 * (0.5 * pq.m)**2 print(area_tel) channel[ch.name].transmission.rebin(x_wav_osr) qe.rebin(x_wav_osr) #step_avg = (x_wav_osr.max() - x_wav_osr.min()) / len(x_wav_osr) step = np.diff(x_wav_osr) # need 1 more step step = np.append(step, step[-3:].mean()) * pq.um exact = omega_star * exolib.planck(x_wav_osr,star_temp) * \ area_tel * channel[ch.name].transmission.sed * \ qe.sed * step * x_wav_osr / (pq.c * spc.h * pq.J * pq.s) exact = exact.simplified print(qe.sed) print(channel[ch.name].transmission.sed) print(exolib.planck(x_wav_osr, star_temp)) print(exact) sim = channel[ch.name].fp.sum(axis=0) print(sim) print(x_wav_osr) np.save(fdir + fname_exact, exact) np.save(fdir + fname_fp, sim) sys.exit("Focal plane testing run, ending early.") #9# Allocate pixel response function kernel, kernel_delta = exolib.PixelResponseFunction( channel[ch.name].psf.shape[0:2], 7 * ch.osf(), # NEED TO CHANGE FACTOR OF 7 ch.detector_pixel.pixel_size(), lx=ch.detector_pixel.pixel_diffusion_length()) if ((ch.type == 'spectrometer') and hasattr(ch, "dispersion")): for i, _ in enumerate(ch_dispersion): start = i * x_pix_osr.size stop = (i + 1) * x_pix_osr.size channel[ch.name].fp[:, start:stop] = exolib.fast_convolution( channel[ch.name].fp[:, start:stop], channel[ch.name].fp_delta, kernel, kernel_delta) else: channel[ch.name].fp = exolib.fast_convolution( channel[ch.name].fp, channel[ch.name].fp_delta, kernel, kernel_delta) ## TODO CHANGE THIS: need to convolve planet with pixel response function if ((ch.type == 'spectrometer') and hasattr(ch, "dispersion")): response_temp = np.zeros(fp.shape[1]) for i, k in enumerate(idx): i0 = np.int((y_pix_osr[k].rescale(pq.um)/ch.detector_pixel.pixel_size().rescale(pq.um) \ * ch.osf().item())//1 - psf.shape[0]//2 + channel[ch.name].offs) i1 = np.int(i0 + psf.shape[0]) response_temp[k] = planet_response[k] / (1e-30 + fp[(i0 + i1) // 2, k]) channel[ch.name].planet = Sed(channel[ch.name].wl_solution, response_temp) else: channel[ch.name].planet = Sed( channel[ch.name].wl_solution, planet_response / (1e-30 + fp[(i0 + i1) // 2, ...])) ## Fix units channel[ ch.name].fp = channel[ch.name].fp * channel[ch.name].star.sed.units channel[ch.name].planet.sed = channel[ ch.name].planet.sed * pq.dimensionless ## Deal with diffuse radiation if ch.type == 'spectrometer': channel[ch.name].zodi.sed = scipy.convolve( channel[ch.name].zodi.sed, np.ones(np.int(ch.slit_width() * channel[ch.name].opt.osf())), 'same') * channel[ch.name].zodi.sed.units channel[ch.name].emission.sed = scipy.convolve( channel[ch.name].emission.sed, np.ones(np.int(ch.slit_width() * channel[ch.name].opt.osf())), 'same') * channel[ch.name].emission.sed.units elif ch.type == 'photometer': channel[ch.name].zodi.sed = np.repeat( channel[ch.name].zodi.sed.sum(), channel[ch.name].wl_solution.size) channel[ch.name].zodi.wl = channel[ch.name].wl_solution channel[ch.name].emission.sed = np.repeat( channel[ch.name].emission.sed.sum(), channel[ch.name].wl_solution.size) channel[ch.name].emission.wl = channel[ch.name].wl_solution else: exolib.exosim_error( "Channel should be either photometer or spectrometer.") exosim_msg(' - execution time: {:.0f} msec.\n'.format( (time.time() - st) * 1000.0)) return channel pass
def run(opt, channel, planet): exosim_msg('Save to file ... ') st = time.time() out_path = os.path.expanduser( opt.common.ExoSimOutputPath().replace('__path__', opt.__path__)) if not os.path.exists(out_path): os.mkdir(out_path) existing_sims = glob.glob(os.path.join(out_path, 'sim_*')) if not existing_sims: # Empty list sim_number = 0 else: sim_number = sorted([np.int(l.split('_')[-1]) for l in existing_sims])[-1] sim_number += 1 out_path = os.path.join(out_path, 'sim_{:04d}'.format(sim_number)) os.mkdir(out_path) for key in channel.keys(): n_ndr = channel[key].tl_shape[-1] multiaccum = np.int(opt.timeline.multiaccum()) n_exp = n_ndr // multiaccum hdu = fits.PrimaryHDU() hdu.header['NEXP'] = (n_exp, 'Number of exposures') hdu.header['MACCUM'] = (multiaccum, 'Multiaccum') hdu.header['TEXP'] = (channel[key].exposure_time.item(), 'Exp Time [s]') hdu.header['PLANET'] = (planet.planet.name, 'Planet name') hdu.header['STAR'] = (planet.planet.star.name, 'Star name') #### Detector Simulation Values hdu.header['POINTRMS'] = (opt.aocs.pointing_rms.val.item(), 'Model Pointing RMS') tempVal = filter(lambda x:x.name==key, opt.channel)[0].detector_pixel.Idc.val.magnitude.item() hdu.header['DET_I_DC'] = (tempVal, 'Detector dark current') tempVal = filter(lambda x:x.name==key, opt.channel)[0].detector_pixel.sigma_ro.val.magnitude.item() hdu.header['DETROERR'] = (tempVal, 'Detector readout noise in e-rms') tempVal = filter(lambda x:x.name==key, opt.channel)[0].plate_scale.val.rescale('degree').magnitude.item() hdu.header['CDELT1'] = (tempVal, 'Degrees/pixel') hdu.header['CDELT2'] = (tempVal, 'Degrees/pixel') hdulist = fits.HDUList(hdu) for i in xrange(channel[key].tl_shape[-1]): hdu = fits.ImageHDU(channel[key].noise[..., i].astype(np.float32), name = 'NOISE') hdu.header['EXPNUM'] = (i//multiaccum, 'Exposure Number') hdu.header['ENDRNUM'] = (i%multiaccum, 'NDR Number') hdu.header['EXTNAME'] = 'NOISE' hdulist.append(hdu) # Create column data col1 = fits.Column(name='Wavelength {:s}'.format(channel[key].wl_solution.units), format='E', array=channel[key].wl_solution[channel[key].offs::channel[key].osf]) col2 = fits.Column(name='Input Contrast Ratio', format='E', array=channel[key].planet.sed[channel[key].offs::channel[key].osf]) col3 = fits.Column(name='Stellar SED', format='E', array=channel[key].star.sed[channel[key].offs::channel[key].osf]) cols = fits.ColDefs([col1, col2, col3]) tbhdu = fits.BinTableHDU.from_columns(cols) tbhdu.name = 'INPUTS' hdulist.append(tbhdu) ######## hdu = fits.ImageHDU(channel[key].outputPointingTl, name = 'SIM_POINTING') hdulist.append(hdu) ############ col1 = fits.Column(name='Time {:s}'.format(channel[key].ndr_time.units), format='E', array=channel[key].ndr_time) col2 = fits.Column(name='z', format='E', array=channel[key].z) cols = fits.ColDefs([col1, col2]) tbhdu = fits.BinTableHDU.from_columns(cols) tbhdu.name = 'TIME' hdulist.append(tbhdu) ######### hdu = fits.ImageHDU(channel[key].lc, name = 'LIGHT_CURVES') hdulist.append(hdu) #print hdulist hdulist.writeto(os.path.join(out_path, '{:s}_signal.fits'.format(key))) exosim_msg(' - execution time: {:.0f} msec.\n'.format((time.time()-st)*1000.0))