def plotdmagvss(sma,eccen,inc,omega,Omega,ax=None,num=None): #need a mechanism for calculating evenly spaced distribution of nu circ = circ_from_E(sma,eccen) b = np.sqrt(sma**2.*(1.-eccen**2.)) #semi-minor axis area = np.pi*sma*b #area inside the ellipse #Note: We only need to evenly distribute over area bc equal area #in equal time is one of Kepler's laws numPts=200 #number of points to get dA = area/numPts #each dA aiming to achieve rs = np.zeros(numPts)#All the r to use nus = np.zeros(numPts)#All the nu to use xs = np.zeros(numPts) ys = np.zeros(numPts) zs = np.zeros(numPts) rs[0] = r_koe(sma,eccen,0.) #Set initial r nus[0] = 0. #EXAMPLE nus[1] = nus[0] + 2.*dA/(rs[0]*(rs[0]-dr_koe(a,e,nus[0]))) xs[0] = x_koe(rs[0],inc,omega,Omega,nus[0]) ys[0] = y_koe(rs[0],inc,omega,Omega,nus[0]) zs[0] = z_koe(rs[0],inc,omega,Omega,nus[0]) #EXAMPLE nus[1] = nus[0] + 2.*dA/(rs[0]*(rs[0]-dr_koe(a,e,nus[0]))) for i in np.arange(numPts-1)+1: nus[i] = nus[i-1] + 2.*dA/(rs[i-1]*(rs[i-1]-dr_koe(sma,eccen,nus[i-1]))) rs[i] = r_koe(sma,eccen,nus[i]) xs[i] = x_koe(rs[i],inc,omega,Omega,nus[i]) ys[i] = y_koe(rs[i],inc,omega,Omega,nus[i]) zs[i] = z_koe(rs[i],inc,omega,Omega,nus[i]) ss = s_koe(sma,eccen,nus,inc,omega) betas = np.arcsin(zs/rs) #From my paper Phis = phiLambert(betas*u.rad) dmags = deltaMag(p=1.,Rp=1.*u.earthRad,d=rs*u.AU,Phi=Phis) if ax == None: plt.figure() plt.plot(ss,dmags) plt.xlabel('s') plt.ylabel('dmag') plt.show(block=False) return None else: ax.plot(ss,dmags) #ax.scatter(ss,dmags,s=4) #E = np.pi/2.-omega#-omega/2.#-omega #nu = np.arccos((np.cos(E)-eccen)/(1.-eccen*np.cos(E))) + omega/2.*np.cos(inc) nu = np.arccos(-eccen) s = s_koe(sma,eccen,nu,inc,omega) r = r_koe(sma,eccen,nu) z = z_koe(r,inc,omega,Omega,nu) beta = np.arcsin(z/r) #From my paper Phi = phiLambert(beta*u.rad) dmag = deltaMag(p=1.,Rp=1.*u.earthRad,d=r*u.AU,Phi=Phi) ax.scatter(s,dmag) ra = sma*(1.+eccen) #DELETEax.plot([ra*np.sin(inc),ra*np.sin(inc)],[20.,23]) ax.set_xlim([0.,ra]) plt.show(block=False) return ax
def max_dmag_filter(self): """Includes stars if maximum delta mag is in the allowed orbital range Removed from prototype filters. Prototype is already calling the int_cutoff_filter with OS.dMag0 and the completeness_filter with Comp.dMagLim """ PPop = self.PlanetPopulation PPMod = self.PlanetPhysicalModel Comp = self.Completeness # s and beta arrays s = np.tan(self.OpticalSystem.WA0) * self.dist if PPop.scaleOrbits: s /= np.sqrt(self.L) beta = np.array([1.10472881476178] * len(s)) * u.rad # fix out of range values below = np.where(s < np.min(PPop.rrange) * np.sin(beta))[0] above = np.where(s > np.max(PPop.rrange) * np.sin(beta))[0] s[below] = np.sin(beta[below]) * np.min(PPop.rrange) beta[above] = np.arcsin(s[above] / np.max(PPop.rrange)) # calculate delta mag p = np.max(PPop.prange) Rp = np.max(PPop.Rprange) d = s / np.sin(beta) Phi = PPMod.calc_Phi(beta) i = np.where(deltaMag(p, Rp, d, Phi) < Comp.dMagLim)[0] self.revise_lists(i)
def max_dmag_filter(self): """Includes stars if maximum delta mag is in the allowed orbital range Removed from prototype filters. Prototype is already calling the int_cutoff_filter with OS.dMag0 and the completeness_filter with Comp.dMagLim """ PPop = self.PlanetPopulation PPMod = self.PlanetPhysicalModel Comp = self.Completeness # s and beta arrays s = np.tan(self.OpticalSystem.WA0)*self.dist if PPop.scaleOrbits: s /= np.sqrt(self.L) beta = np.array([1.10472881476178]*len(s))*u.rad # fix out of range values below = np.where(s < np.min(PPop.rrange)*np.sin(beta))[0] above = np.where(s > np.max(PPop.rrange)*np.sin(beta))[0] s[below] = np.sin(beta[below])*np.min(PPop.rrange) beta[above] = np.arcsin(s[above]/np.max(PPop.rrange)) # calculate delta mag p = np.max(PPop.prange) Rp = np.max(PPop.Rprange) d = s/np.sin(beta) Phi = PPMod.calc_Phi(beta) i = np.where(deltaMag(p, Rp, d, Phi) < Comp.dMagLim)[0] self.revise_lists(i)
def max_dmag_filter(self): """Includes stars if maximum delta mag is in the allowed orbital range """ PPop = self.PlanetPopulation PPMod = self.PlanetPhysicalModel OS = self.OpticalSystem # s and beta arrays s = np.tan(OS.IWA)*self.dist if PPop.scaleOrbits: s /= np.sqrt(self.L) beta = np.array([1.10472881476178]*len(s))*u.rad # fix out of range values below = np.where(s < np.min(PPop.rrange)*np.sin(beta))[0] above = np.where(s > np.max(PPop.rrange)*np.sin(beta))[0] s[below] = np.sin(beta[below])*np.min(PPop.rrange) beta[above] = np.arcsin(s[above]/np.max(PPop.rrange)) # calculate delta mag p = np.max(PPop.prange) Rp = np.max(PPop.Rprange) d = s/np.sin(beta) Phi = PPMod.calc_Phi(beta) i = np.where(deltaMag(p,Rp,d,Phi) < OS.dMagLim)[0] self.revise_lists(i)
def test2(self): r"""Testing a couple of specific cases.""" p = np.array([0.1, 0.2]) Rp = np.array([1.0, 2.0]) * const.R_earth d = np.array([1.0, 2.0]) * u.au Phi = np.array([0.1, 0.5]) result = deltaMag(p, Rp, d, Phi) expected = np.array([26.85, 24.35]) np.testing.assert_allclose(expected, result, rtol=1e-2, atol=0.)
def init_systems(self): """Finds initial time-dependant parameters. Assigns each planet an initial position, velocity, planet-star distance, apparent separation, phase function, surface brightness of exo-zodiacal light, delta magnitude, and working angle. This method makes use of the systems' physical properties (masses, distances) and their orbital elements (a, e, I, O, w, M0). """ PPMod = self.PlanetPhysicalModel ZL = self.ZodiacalLight TL = self.TargetList a = self.a.to('AU').value # semi-major axis e = self.e # eccentricity I = self.I.to('rad').value # inclinations O = self.O.to('rad').value # right ascension of the ascending node w = self.w.to('rad').value # argument of perigee M0 = self.M0.to('rad').value # initial mean anomany E = eccanom(M0, e) # eccentric anomaly Mp = self.Mp # planet masses #This is the a1 a2 a3 and b1 b2 b3 are the euler angle transformation from the I,J,K refernece frame # to an x,y,z frame see https://en.wikipedia.org/wiki/Orbital_elements#Keplerian_elements a1 = np.cos(O) * np.cos(w) - np.sin(O) * np.cos(I) * np.sin(w) a2 = np.sin(O) * np.cos(w) + np.cos(O) * np.cos(I) * np.sin(w) a3 = np.sin(I) * np.sin(w) A = a * np.vstack((a1, a2, a3)) * u.AU b1 = -np.sqrt(1 - e**2) * (np.cos(O) * np.sin(w) + np.sin(O) * np.cos(I) * np.cos(w)) b2 = np.sqrt(1 - e**2) * (-np.sin(O) * np.sin(w) + np.cos(O) * np.cos(I) * np.cos(w)) b3 = np.sqrt(1 - e**2) * np.sin(I) * np.cos(w) B = a * np.vstack((b1, b2, b3)) * u.AU r1 = np.cos(E) - e r2 = np.sin(E) mu = const.G * (Mp + TL.MsTrue[self.plan2star]) v1 = np.sqrt(mu / self.a**3) / (1 - e * np.cos(E)) v2 = np.cos(E) self.r = (A * r1 + B * r2).T.to('AU') # position self.v = (v1 * (-A * r2 + B * v2)).T.to('AU/day') # velocity self.d = np.linalg.norm(self.r, axis=1) * self.r.unit # planet-star distance self.s = np.linalg.norm(self.r[:, 0:2], axis=1) * self.r.unit # apparent separation self.phi = PPMod.calc_Phi(np.arccos(self.r[:, 2] / self.d)) # planet phase self.fEZ = ZL.fEZ(TL.MV[self.plan2star], self.I, self.d) # exozodi brightness self.dMag = deltaMag(self.p, self.Rp, self.d, self.phi) # delta magnitude self.WA = np.arctan(self.s / TL.dist[self.plan2star]).to( 'arcsec') # working angle
def test1(self): r"""Testing some limiting cases.""" p = np.array([0.0, 1.0, 1.0, 1.0, 1.0]) Rp = np.array([1.0, 0.0, 1.0, 1.0, 1.0]) * u.kilometer d = np.array([1.0, 1.0, 0.0, 1.0, np.inf]) * u.kilometer Phi = np.array([1.0, 1.0, 1.0, 0.0, 1.0]) # suppress division-by-zero warnings with np.errstate(divide='ignore'): result = deltaMag(p, Rp, d, Phi) expected = np.array([np.inf, np.inf, -np.inf, np.inf, np.inf]) np.testing.assert_allclose(expected, result, rtol=1e-1, atol=0)
def dmagErrorFromdmagInt(nus, inds, inc, w, a, e, Rp, p, dmag): Phi = (1. + np.sin(np.tile(inc[inds], (2, 1)).T) * np.sin(nus + np.tile(w[inds], (2, 1)).T) )**2. / 4. #TRYING THIS TO CIRCUMVENT POTENTIAL ARCCOS d = np.tile(a[inds].to('AU'), (2, 1)).T * (1. - np.tile(e[inds], (2, 1)).T**2.) / ( np.tile(e[inds], (2, 1)).T * np.cos(nus) + 1.) dmags = deltaMag( np.tile(p[inds], (2, 1)).T, np.tile(Rp[inds].to('AU'), (2, 1)).T, d, Phi) #calculate dmag of the specified x-value return np.sum(np.abs(dmags - dmag))
def init_systems(self): """Finds initial time-dependant parameters. Assigns each planet an initial position, velocity, planet-star distance, apparent separation, phase function, surface brightness of exo-zodiacal light, delta magnitude, working angle, and initializes the planet current times to zero. This method makes us of the systems' physical properties (masses, distances) and their orbital elements (a, e, I, O, w, M0). """ PPMod = self.PlanetPhysicalModel ZL = self.ZodiacalLight TL = self.TargetList sInds = self.plan2star # indices of target stars sDist = TL.dist[sInds] # distances to target stars Ms = TL.MsTrue[sInds]*const.M_sun # masses of target stars a = self.a.to('AU').value # semi-major axis e = self.e # eccentricity I = self.I.to('rad').value # inclinations O = self.O.to('rad').value # right ascension of the ascending node w = self.w.to('rad').value # argument of perigee M0 = self.M0.to('rad').value # initial mean anomany E = eccanom(M0, e) # eccentric anomaly Mp = self.Mp # planet masses a1 = np.cos(O)*np.cos(w) - np.sin(O)*np.cos(I)*np.sin(w) a2 = np.sin(O)*np.cos(w) + np.cos(O)*np.cos(I)*np.sin(w) a3 = np.sin(I)*np.sin(w) A = a*np.vstack((a1,a2,a3))*u.AU b1 = -np.sqrt(1.-e**2)*(np.cos(O)*np.sin(w) + np.sin(O)*np.cos(I)*np.cos(w)) b2 = np.sqrt(1.-e**2)*(-np.sin(O)*np.sin(w) + np.cos(O)*np.cos(I)*np.cos(w)) b3 = np.sqrt(1.-e**2)*np.sin(I)*np.cos(w) B = a*np.vstack((b1,b2,b3))*u.AU r1 = np.cos(E) - e r2 = np.sin(E) mu = const.G*(Mp + Ms) v1 = np.sqrt(mu/self.a**3)/(1. - e*np.cos(E)) v2 = np.cos(E) self.r = (A*r1 + B*r2).T.to('AU') # position self.v = (v1*(-A*r2 + B*v2)).T.to('AU/day') # velocity self.d = np.sqrt(np.sum(self.r**2, axis=1)) # planet-star distance self.s = np.sqrt(np.sum(self.r[:,0:2]**2, axis=1)) # apparent separation self.phi = PPMod.calc_Phi(np.arcsin(self.s/self.d)) # planet phase self.fEZ = ZL.fEZ(TL, sInds, self.I, self.d) # exozodi brightness self.dMag = deltaMag(self.p, self.Rp, self.d, self.phi) # delta magnitude self.WA = np.arctan(self.s/sDist).to('mas') # working angle # current time (normalized to zero at mission start) of planet positions self.planTime = np.zeros(self.nPlans)*u.day
def init_systems(self): """Finds initial time-dependant parameters. Assigns each planet an initial position, velocity, planet-star distance, apparent separation, phase function, surface brightness of exo-zodiacal light, delta magnitude, and working angle. This method makes use of the systems' physical properties (masses, distances) and their orbital elements (a, e, I, O, w, M0). """ PPMod = self.PlanetPhysicalModel ZL = self.ZodiacalLight TL = self.TargetList a = self.a.to('AU').value # semi-major axis e = self.e # eccentricity I = self.I.to('rad').value # inclinations O = self.O.to('rad').value # right ascension of the ascending node w = self.w.to('rad').value # argument of perigee M0 = self.M0.to('rad').value # initial mean anomany E = eccanom(M0, e) # eccentric anomaly Mp = self.Mp # planet masses a1 = np.cos(O)*np.cos(w) - np.sin(O)*np.cos(I)*np.sin(w) a2 = np.sin(O)*np.cos(w) + np.cos(O)*np.cos(I)*np.sin(w) a3 = np.sin(I)*np.sin(w) A = a*np.vstack((a1, a2, a3))*u.AU b1 = -np.sqrt(1 - e**2)*(np.cos(O)*np.sin(w) + np.sin(O)*np.cos(I)*np.cos(w)) b2 = np.sqrt(1 - e**2)*(-np.sin(O)*np.sin(w) + np.cos(O)*np.cos(I)*np.cos(w)) b3 = np.sqrt(1 - e**2)*np.sin(I)*np.cos(w) B = a*np.vstack((b1, b2, b3))*u.AU r1 = np.cos(E) - e r2 = np.sin(E) mu = const.G*(Mp + TL.MsTrue[self.plan2star]) v1 = np.sqrt(mu/self.a**3)/(1 - e*np.cos(E)) v2 = np.cos(E) self.r = (A*r1 + B*r2).T.to('AU') # position self.v = (v1*(-A*r2 + B*v2)).T.to('AU/day') # velocity self.d = np.linalg.norm(self.r, axis=1)*self.r.unit # planet-star distance self.s = np.linalg.norm(self.r[:,0:2], axis=1)*self.r.unit # apparent separation self.phi = PPMod.calc_Phi(np.arccos(self.r[:,2]/self.d)) # planet phase self.fEZ = ZL.fEZ(TL.MV[self.plan2star], self.I, self.d) # exozodi brightness self.dMag = deltaMag(self.p, self.Rp, self.d, self.phi) # delta magnitude self.WA = np.arctan(self.s/TL.dist[self.plan2star]).to('arcsec')# working angle
def genplans(self, nplan): """Generates planet data needed for Monte Carlo simulation Args: nplan (integer): Number of planets Returns: tuple: s (astropy Quantity array): Planet apparent separations in units of AU dMag (ndarray): Difference in brightness """ PPop = self.PlanetPopulation nplan = int(nplan) # sample uniform distribution of mean anomaly M = np.random.uniform(high=2.0 * np.pi, size=nplan) # sample quantities a, e, p, Rp = PPop.gen_plan_params(nplan) # check if circular orbits if np.sum(PPop.erange) == 0: r = a e = 0.0 E = M else: E = eccanom(M, e) # orbital radius r = a * (1.0 - e * np.cos(E)) beta = np.arccos(1.0 - 2.0 * np.random.uniform(size=nplan)) * u.rad s = r * np.sin(beta) # phase function Phi = self.PlanetPhysicalModel.calc_Phi(beta) # calculate dMag dMag = deltaMag(p, Rp, r, Phi) return s, dMag
def genplans(self, nplan): """Generates planet data needed for Monte Carlo simulation Args: nplan (integer): Number of planets Returns: tuple: s (astropy Quantity array): Planet apparent separations in units of AU dMag (ndarray): Difference in brightness """ PPop = self.PlanetPopulation nplan = int(nplan) # sample uniform distribution of mean anomaly M = np.random.uniform(high=2.0*np.pi,size=nplan) # sample quantities a, e, p, Rp = PPop.gen_plan_params(nplan) # check if circular orbits if np.sum(PPop.erange) == 0: r = a e = 0.0 E = M else: E = eccanom(M,e) # orbital radius r = a*(1.0-e*np.cos(E)) beta = np.arccos(1.0-2.0*np.random.uniform(size=nplan))*u.rad s = r*np.sin(beta) # phase function Phi = self.PlanetPhysicalModel.calc_Phi(beta) # calculate dMag dMag = deltaMag(p,Rp,r,Phi) return s, dMag
def __init__(self, **specs): # call prototype constructor SurveySimulation.__init__(self, **specs) TL = self.TargetList SU = self.SimulatedUniverse # reinitialize working angles and delta magnitudes used for integration self.WAint = np.zeros(TL.nStars)*u.arcsec self.dMagint = np.zeros(TL.nStars) # calculate estimates of shortest WAint and largest dMagint for each target for sInd in range(TL.nStars): pInds = np.where(SU.plan2star == sInd)[0] self.WAint[sInd] = np.arctan(np.min(SU.a[pInds])/TL.dist[sInd]).to('arcsec') phis = np.array([np.pi/2]*pInds.size) dMags = deltaMag(SU.p[pInds], SU.Rp[pInds], SU.a[pInds], phis) self.dMagint[sInd] = np.min(dMags) # populate outspec with arrays self._outspec['WAint'] = self.WAint.value self._outspec['dMagint'] = self.dMagint
def __init__(self, **specs): # call prototype constructor SurveySimulation.__init__(self, **specs) TL = self.TargetList SU = self.SimulatedUniverse # reinitialize working angles and delta magnitudes used for integration self.WAint = np.zeros(TL.nStars)*u.arcsec self.dMagint = np.zeros(TL.nStars) # calculate estimates of shortest WAint and largest dMagint for each target for sInd in range(TL.nStars): pInds = np.where(SU.plan2star == sInd)[0] self.WAint[sInd] = np.arctan(np.min(SU.a[pInds])/TL.dist[sInd]).to('arcsec') phis = np.array([np.pi/2]*pInds.size) dMags = deltaMag(SU.p[pInds], SU.Rp[pInds], SU.a[pInds], phis) self.dMagint[sInd] = np.min(dMags) # populate outspec with arrays self._outspec['WAint'] = self.WAint.value self._outspec['dMagint'] = self.dMagint
def run_sim(self): """Performs the survey simulation This method has access to the following: Obs: Observatory class object TK: TimeKeeping class object SU: SimulatedUniverse class object TL: TargetList class object PPro: PostProcessing class object OS: OpticalSystem class object PPop: PlanetPopulation class object Returns: string (str): String 'Simulation results in .DRM' """ Obs = self.Observatory TK = self.TimeKeeping SU = self.SimulatedUniverse TL = self.TargetList PPro = self.PostProcessing OS = self.OpticalSystem PPop = self.PlanetPopulation PPMod = self.PlanetPhysicalModel Logger.info('run_sim beginning') # initialize values updated later # number of visits to target star visited = np.zeros(TL.nStars,dtype=int) # target revisit list revisit_list = np.array([]) extended_list = np.array([]) # current time (normalized to zero at mission start) of planet positions planPosTime = np.array([TK.currentTimeNorm.to('day').value]*SU.nPlans)*u.day # number of planet observations observed = np.zeros((SU.nPlans,), dtype=int) # set occulter separation if haveOcculter if OS.haveOcculter: Obs.currentSep = Obs.occulterSep # initialize run options # keep track of spectral characterizations, 0 is no characterization spectra = np.zeros(SU.nPlans, dtype=int) # get index of first target star sInd,_ = self.next_target() # loop until mission is finished while not TK.mission_is_over(): Logger.info('current time is %r' % TK.currentTimeNorm) print 'Current mission time: ', TK.currentTimeNorm obsbegin = TK.currentTimeNorm.to('day') # dictionary containing results DRM = {} DRM['target_ind'] = sInd # target star index DRM['arrival_time'] = TK.currentTimeNorm.to('day').value # arrival time if OS.haveOcculter: DRM['scMass'] = Obs.scMass.to('kg').value # spacecraft mass # get target list star index of detections for extended_list if TK.currentTimeNorm > TK.missionLife and extended_list.shape[0] == 0: for i in xrange(len(self.DRM)): if self.DRM[i]['det'] == 1: extended_list = np.hstack((extended_list, self.DRM[i]['target_ind'])) extended_list = np.unique(extended_list) # filter planet indices pInds = np.where(SU.plan2star == sInd)[0] # belonging to target star nPlans = len(pInds) WA = SU.get_current_WA(pInds) pInds = pInds[(WA>OS.IWA) * (WA<OS.OWA)] # inside [IWA-OWA] Phi = PPMod.calc_Phi(np.arcsin(SU.s[pInds]/SU.d[pInds])) dMag = deltaMag(SU.p[pInds],SU.Rp[pInds],SU.d[pInds],Phi) pInds = pInds[dMag < OS.dMagLim] # bright enough Logger.info('Observing %r/%r planets around star #%r/%r.'%(len(pInds),\ nPlans,sInd+1,TL.nStars)) # update visited list for current star visited[sInd] += 1 # find out if observations are possible and get relevant data observationPossible, t_int, DRM = self.observation_detection(pInds, sInd, DRM, planPosTime) t_int += Obs.settlingTime # store detection integration time DRM['det_int_time'] = t_int.to('day').value if not TK.allocate_time(t_int): # time too large: skip it and allocate default value dtAlloc observationPossible = False TK.allocate_time(TK.dtAlloc) if pInds.shape[0] != 0: Logger.info('Imaging possible: %s', observationPossible) # determine detection, missed detection, false alarm booleans FA, DET, MD, NULL = PPro.det_occur(observationPossible) # encode detection status s, DRM, observed = self.det_data(DRM, FA, DET, MD, sInd, pInds, \ observationPossible, observed) # perform characterization if SNchar defined if PPro.SNchar > 0: DRM, FA, spectra = self.observation_characterization(observationPossible, \ pInds, sInd, spectra, DRM, FA, t_int) if pInds.shape[0] != 0: Logger.info('Characterization possible: %s', observationPossible) # schedule a revisit if pInds.shape[0] != 0 and (DET or FA): # if there are planets, revisit based on planet with minimum separation sp = np.min(s) Mp = SU.Mp[pInds[np.argmin(s)]] mu = const.G*(Mp + TL.MsTrue[sInd]*const.M_sun) T = 2.*np.pi*np.sqrt(sp**3/mu) t_rev = TK.currentTimeNorm + T/2. else: # revisit based on average of population semi-major axis and mass sp = SU.s.mean() Mp = SU.Mp.mean() mu = const.G*(Mp + TL.MsTrue[sInd]*const.M_sun) T = 2.*np.pi*np.sqrt(sp**3/mu) t_rev = TK.currentTimeNorm + 0.75*T # populate revisit list (sInd is converted to float) revisit = np.array([sInd, t_rev.to('day').value]) if revisit_list.size == 0: revisit_list = np.array([revisit]) else: revisit_list = np.vstack((revisit_list, revisit)) # update completeness values obsend = TK.currentTimeNorm.to('day') nexttime = TK.currentTimeNorm TL.comp0 = self.Completeness.completeness_update(sInd, TL, obsbegin, \ obsend, nexttime) # append result values to self.DRM self.DRM.append(DRM) # with occulter if spacecraft fuel is depleted, exit loop if OS.haveOcculter and Obs.scMass < Obs.dryMass: print 'Total fuel mass exceeded at %r' % TK.currentTimeNorm break # acquire a new target star index sInd, DRM = self.next_target(sInd, DRM) Logger.info('run_sim finishing OK') print 'Survey simulation: finishing OK' return 'Simulation results in .DRM'
def genplans(self, nplan): """Generates planet data needed for Monte Carlo simulation Args: nplan (integer): Number of planets Returns: s (astropy Quantity array): Planet apparent separations in units of AU dMag (ndarray): Difference in brightness """ nplan = int(nplan) # sample uniform distribution of mean anomaly M = np.random.uniform(high=2.*np.pi,size=nplan) # sample semi-major axis a = self.PlanetPopulation.gen_sma(nplan).to('AU').value # sample other necessary orbital parameters if np.sum(self.PlanetPopulation.erange) == 0: # all circular orbits r = a e = 0. E = M else: # sample eccentricity if self.PlanetPopulation.constrainOrbits: e = self.PlanetPopulation.gen_eccen_from_sma(nplan,a*u.AU) else: e = self.PlanetPopulation.gen_eccen(nplan) # Newton-Raphson to find E E = eccanom(M,e) # orbital radius r = a*(1-e*np.cos(E)) # orbit angle sampling O = self.PlanetPopulation.gen_O(nplan).to('rad').value w = self.PlanetPopulation.gen_w(nplan).to('rad').value I = self.PlanetPopulation.gen_I(nplan).to('rad').value r1 = r*(np.cos(E) - e) r1 = np.hstack((r1.reshape(len(r1),1), r1.reshape(len(r1),1), r1.reshape(len(r1),1))) r2 = r*np.sin(E)*np.sqrt(1. - e**2) r2 = np.hstack((r2.reshape(len(r2),1), r2.reshape(len(r2),1), r2.reshape(len(r2),1))) a1 = np.cos(O)*np.cos(w) - np.sin(O)*np.sin(w)*np.cos(I) a2 = np.sin(O)*np.cos(w) + np.cos(O)*np.sin(w)*np.cos(I) a3 = np.sin(w)*np.sin(I) A = np.hstack((a1.reshape(len(a1),1), a2.reshape(len(a2),1), a3.reshape(len(a3),1))) b1 = -np.cos(O)*np.sin(w) - np.sin(O)*np.cos(w)*np.cos(I) b2 = -np.sin(O)*np.sin(w) + np.cos(O)*np.cos(w)*np.cos(I) b3 = np.cos(w)*np.sin(I) B = np.hstack((b1.reshape(len(b1),1), b2.reshape(len(b2),1), b3.reshape(len(b3),1))) # planet position, planet-star distance, apparent separation r = (A*r1 + B*r2)*u.AU d = np.sqrt(np.sum(r**2, axis=1)) s = np.sqrt(np.sum(r[:,0:2]**2, axis=1)) # sample albedo, planetary radius, phase function p = self.PlanetPopulation.gen_albedo(nplan) Rp = self.PlanetPopulation.gen_radius(nplan) beta = np.arccos(r[:,2]/d) Phi = self.PlanetPhysicalModel.calc_Phi(beta) # calculate dMag dMag = deltaMag(p,Rp,d,Phi) return s, dMag
def gen_update(self, TL): """Generates dynamic completeness values for multiple visits of each star in the target list Args: TL (TargetList): TargetList class object """ OS = TL.OpticalSystem PPop = TL.PlanetPopulation # limiting planet delta magnitude for completeness dMagMax = self.dMagLim # get name for stored dynamic completeness updates array # inner and outer working angles for detection mode mode = list( filter(lambda mode: mode['detectionMode'] == True, OS.observingModes))[0] IWA = mode['IWA'] OWA = mode['OWA'] extstr = self.extstr + 'IWA: ' + str(IWA) + ' OWA: ' + str(OWA) + \ ' dMagMax: ' + str(dMagMax) + ' nStars: ' + str(TL.nStars) ext = hashlib.md5(extstr.encode('utf-8')).hexdigest() self.dfilename += ext self.dfilename += '.dcomp' path = os.path.join(self.cachedir, self.dfilename) # if the 2D completeness update array exists as a .dcomp file load it if os.path.exists(path): self.vprint( 'Loading cached dynamic completeness array from "%s".' % path) try: with open(path, "rb") as ff: self.updates = pickle.load(ff) except UnicodeDecodeError: with open(path, "rb") as ff: self.updates = pickle.load(ff, encoding='latin1') self.vprint('Dynamic completeness array loaded from cache.') else: # run Monte Carlo simulation and pickle the resulting array self.vprint( 'Cached dynamic completeness array not found at "%s".' % path) self.vprint('Beginning dynamic completeness calculations') # dynamic completeness values: rows are stars, columns are number of visits self.updates = np.zeros((TL.nStars, 5)) # number of planets to simulate nplan = int(2e4) # sample quantities which do not change in time a, e, p, Rp = PPop.gen_plan_params(nplan) a = a.to('AU').value # sample angles I, O, w = PPop.gen_angles(nplan) I = I.to('rad').value O = O.to('rad').value w = w.to('rad').value Mp = PPop.gen_mass(nplan) # M_earth rmax = a * (1. + e) # AU # sample quantity which will be updated M = np.random.uniform(high=2. * np.pi, size=nplan) newM = np.zeros((nplan, )) # population values smin = (np.tan(IWA) * TL.dist).to('AU').value if np.isfinite(OWA): smax = (np.tan(OWA) * TL.dist).to('AU').value else: smax = np.array([np.max(PPop.arange.to('AU').value)*\ (1.+np.max(PPop.erange))]*TL.nStars) # fill dynamic completeness values for sInd in xrange(TL.nStars): mu = (const.G * (Mp + TL.MsTrue[sInd])).to('AU3/day2').value n = np.sqrt(mu / a**3) # in 1/day # normalization time equation from Brown 2015 dt = 58.0 * (TL.L[sInd] / 0.83)**(3.0 / 4.0) * ( TL.MsTrue[sInd] / (0.91 * u.M_sun))**(1.0 / 2.0) # days # remove rmax < smin pInds = np.where(rmax > smin[sInd])[0] # calculate for 5 successive observations for num in xrange(5): if num == 0: self.updates[sInd, num] = TL.comp0[sInd] if not pInds.any(): break # find Eccentric anomaly if num == 0: E = eccanom(M[pInds], e[pInds]) newM[pInds] = M[pInds] else: E = eccanom(newM[pInds], e[pInds]) r1 = a[pInds] * (np.cos(E) - e[pInds]) r1 = np.hstack( (r1.reshape(len(r1), 1), r1.reshape(len(r1), 1), r1.reshape(len(r1), 1))) r2 = (a[pInds] * np.sin(E) * np.sqrt(1. - e[pInds]**2)) r2 = np.hstack( (r2.reshape(len(r2), 1), r2.reshape(len(r2), 1), r2.reshape(len(r2), 1))) a1 = np.cos(O[pInds]) * np.cos(w[pInds]) - np.sin( O[pInds]) * np.sin(w[pInds]) * np.cos(I[pInds]) a2 = np.sin(O[pInds]) * np.cos(w[pInds]) + np.cos( O[pInds]) * np.sin(w[pInds]) * np.cos(I[pInds]) a3 = np.sin(w[pInds]) * np.sin(I[pInds]) A = np.hstack( (a1.reshape(len(a1), 1), a2.reshape(len(a2), 1), a3.reshape(len(a3), 1))) b1 = -np.cos(O[pInds]) * np.sin(w[pInds]) - np.sin( O[pInds]) * np.cos(w[pInds]) * np.cos(I[pInds]) b2 = -np.sin(O[pInds]) * np.sin(w[pInds]) + np.cos( O[pInds]) * np.cos(w[pInds]) * np.cos(I[pInds]) b3 = np.cos(w[pInds]) * np.sin(I[pInds]) B = np.hstack( (b1.reshape(len(b1), 1), b2.reshape(len(b2), 1), b3.reshape(len(b3), 1))) # planet position, planet-star distance, apparent separation r = (A * r1 + B * r2) # position vector (AU) d = np.linalg.norm(r, axis=1) # planet-star distance s = np.linalg.norm(r[:, 0:2], axis=1) # apparent separation beta = np.arccos(r[:, 2] / d) # phase angle Phi = self.PlanetPhysicalModel.calc_Phi( beta * u.rad) # phase function dMag = deltaMag(p[pInds], Rp[pInds], d * u.AU, Phi) # difference in magnitude toremoves = np.where((s > smin[sInd]) & (s < smax[sInd]))[0] toremovedmag = np.where(dMag < dMagMax)[0] toremove = np.intersect1d(toremoves, toremovedmag) pInds = np.delete(pInds, toremove) if num == 0: self.updates[sInd, num] = TL.comp0[sInd] else: self.updates[sInd, num] = float(len(toremove)) / nplan # update M newM[pInds] = (newM[pInds] + n[pInds] * dt) / ( 2 * np.pi) % 1 * 2. * np.pi if (sInd + 1) % 50 == 0: self.vprint('stars: %r / %r' % (sInd + 1, TL.nStars)) # ensure that completeness values are between 0 and 1 self.updates = np.clip(self.updates, 0., 1.) # store dynamic completeness array as .dcomp file with open(path, 'wb') as ff: pickle.dump(self.updates, ff) self.vprint('Dynamic completeness calculations finished') self.vprint('Dynamic completeness array stored in %r' % path)
def propag_system(self, sInd, dt): """Propagates planet time-dependant parameters: position, velocity, planet-star distance, apparent separation, phase function, surface brightness of exo-zodiacal light, delta magnitude, and working angle. This method uses the Kepler state transition matrix to propagate a planet's state (position and velocity vectors) forward in time using the Kepler state transition matrix. Args: sInd (integer): Index of the target system of interest dt (astropy Quantity): Time increment in units of day, for planet position propagation """ PPMod = self.PlanetPhysicalModel ZL = self.ZodiacalLight TL = self.TargetList assert np.isscalar(sInd), \ "Can only propagate one system at a time, sInd must be scalar." # check for planets around this target pInds = np.where(self.plan2star == sInd)[0] if len(pInds) == 0: return # check for positive time increment assert dt >= 0, "Time increment (dt) to propagate a planet must be positive." if dt == 0: return # Calculate initial positions in AU and velocities in AU/day r0 = self.r[pInds].to('AU').value v0 = self.v[pInds].to('AU/day').value # stack dimensionless positions and velocities nPlans = pInds.size x0 = np.reshape(np.concatenate((r0, v0), axis=1), nPlans * 6) # Calculate vector of gravitational parameter in AU3/day2 Ms = TL.MsTrue[[sInd]] Mp = self.Mp[pInds] mu = (const.G * (Mp + Ms)).to('AU3/day2').value # use keplerSTM.py to propagate the system prop = planSys(x0, mu, epsmult=10.) try: prop.takeStep(dt.to('day').value) except ValueError: #try again with larger epsmult and two steps to force convergence prop = planSys(x0, mu, epsmult=100.) try: prop.takeStep(dt.to('day').value / 2.) prop.takeStep(dt.to('day').value / 2.) except ValueError: raise ValueError('planSys error') # split off position and velocity vectors x1 = np.array(np.hsplit(prop.x0, 2 * nPlans)) rind = np.array(range(0, len(x1), 2)) # even indices vind = np.array(range(1, len(x1), 2)) # odd indices # update planets' position, velocity, planet-star distance, apparent, separation, # phase function, exozodi surface brightness, delta magnitude and working angle self.r[pInds] = x1[rind] * u.AU self.v[pInds] = x1[vind] * u.AU / u.day # if sys.version_info[0] > 2: # self.d[pInds] = np.linalg.norm(self.r[pInds], axis=1) # self.s[pInds] = np.linalg.norm(self.r[pInds,0:2], axis=1) # else: # self.d[pInds] = np.linalg.norm(self.r[pInds], axis=1)*self.r.unit # self.s[pInds] = np.linalg.norm(self.r[pInds,0:2], axis=1)*self.r.unit try: self.d[pInds] = np.linalg.norm(self.r[pInds], axis=1) self.phi[pInds] = PPMod.calc_Phi( np.arccos(self.r[pInds, 2] / self.d[pInds])) except: self.d[pInds] = np.linalg.norm(self.r[pInds], axis=1) * self.r.unit self.phi[pInds] = PPMod.calc_Phi( np.arccos(self.r[pInds, 2] / self.d[pInds])) # self.fEZ[pInds] = ZL.fEZ(TL.MV[sInd], self.I[pInds], self.d[pInds]) self.dMag[pInds] = deltaMag(self.p[pInds], self.Rp[pInds], self.d[pInds], self.phi[pInds]) try: self.s[pInds] = np.linalg.norm(self.r[pInds, 0:2], axis=1) self.WA[pInds] = np.arctan(self.s[pInds] / TL.dist[sInd]).to('arcsec') except: self.s[pInds] = np.linalg.norm(self.r[pInds, 0:2], axis=1) * self.r.unit self.WA[pInds] = np.arctan(self.s[pInds] / TL.dist[sInd]).to('arcsec')
def set_planet_phase(self, beta = np.pi/2): """Positions all planets at input star-planet-observer phase angle where possible. For systems where the input phase angle is not achieved, planets are positioned at quadrature (phase angle of 90 deg). The position found here is not unique. The desired phase angle will be achieved at two points on the planet's orbit (for non-face on orbits). Args: beta (float): star-planet-observer phase angle in radians. """ PPMod = self.PlanetPhysicalModel ZL = self.ZodiacalLight TL = self.TargetList a = self.a.to('AU').value # semi-major axis e = self.e # eccentricity I = self.I.to('rad').value # inclinations O = self.O.to('rad').value # right ascension of the ascending node w = self.w.to('rad').value # argument of perigee Mp = self.Mp # planet masses # make list of betas betas = beta*np.ones(w.shape) mask = np.cos(betas)/np.sin(I) > 1. num = len(np.where(mask == True)[0]) betas[mask] = np.pi/2. mask = np.cos(betas)/np.sin(I) < -1. num += len(np.where(mask == True)[0]) betas[mask] = np.pi/2. if num > 0: self.vprint('***Warning***') self.vprint('{} planets out of {} could not be set to phase angle {} radians.'.format(num,self.nPlans,beta)) self.vprint('These planets are set to quadrature (phase angle pi/2)') # solve for true anomaly nu = np.arcsin(np.cos(betas)/np.sin(I)) - w # setup for position and velocity a1 = np.cos(O)*np.cos(w) - np.sin(O)*np.cos(I)*np.sin(w) a2 = np.sin(O)*np.cos(w) + np.cos(O)*np.cos(I)*np.sin(w) a3 = np.sin(I)*np.sin(w) A = np.vstack((a1, a2, a3)) b1 = -(np.cos(O)*np.sin(w) + np.sin(O)*np.cos(I)*np.cos(w)) b2 = (-np.sin(O)*np.sin(w) + np.cos(O)*np.cos(I)*np.cos(w)) b3 = np.sin(I)*np.cos(w) B = np.vstack((b1, b2, b3)) r = a*(1.-e**2)/(1.-e*np.cos(nu)) mu = const.G*(Mp + TL.MsTrue[self.plan2star]) v1 = -np.sqrt(mu/(self.a*(1.-self.e**2)))*np.sin(nu) v2 = np.sqrt(mu/(self.a*(1.-self.e**2)))*(self.e + np.cos(nu)) self.r = (A*r*np.cos(nu) + B*r*np.sin(nu)).T*u.AU # position self.v = (A*v1 + B*v2).T.to('AU/day') # velocity self.d = np.linalg.norm(self.r, axis=1)*self.r.unit # planet-star distance self.s = np.linalg.norm(self.r[:,0:2], axis=1)*self.r.unit # apparent separation self.phi = PPMod.calc_Phi(np.arccos(self.r[:,2]/self.d)) # planet phase self.fEZ = ZL.fEZ(TL.MV[self.plan2star], self.I, self.d) # exozodi brightness self.dMag = deltaMag(self.p, self.Rp, self.d, self.phi) # delta magnitude self.WA = np.arctan(self.s/TL.dist[self.plan2star]).to('arcsec')# working angle
def check_visible_end(self, observationPossible, t_int, t_trueint, sInd, pInds, t_char_calc): """Determines if planets are visible at the end of the observation time Args: observationPossible (ndarray): 1D numpy ndarray of booleans indicating if an observation of each planet is possible t_int (Quantity): integration time (units of time) t_trueint (Quantity): 1D numpy ndarray of calculated integration times for planets (units of time) sInd (int): target star index pInds (ndarray): 1D numpy ndarray of planet indices t_char_calc (bool): boolean where True is for characterization calculations Returns: t_int, observationPossible, chargo (Quantity, ndarray, bool): true integration time for planetary system (units of day) (t_char_calc = False) or maximum characterization time for planetary system (t_char_calc = True), updated 1D numpy ndarray of booleans indicating if an observation or characterization of each planet is possible, boolean where True is to encode characterization data (t_char_calc = True only) """ TL = self.TargetList Obs = self.Observatory SU = self.SimulatedUniverse OS = self.OpticalSystem PPop = self.PlanetPopulation PPMod = self.PlanetPhysicalModel # set chargo to False initially chargo = False for i in xrange(len(pInds)): if observationPossible[i]: # is planet visible at the end of the observation time? if Obs.kogood[sInd]: # propagate planet to observational period end, and find dMagend dt = t_int[i] if t_char_calc else t_trueint[i] + Obs.settlingTime j = pInds[[i]] # must be an array of size 1 rend, vend, send, dend = SU.prop_system(SU.r[j],SU.v[j],\ SU.Mp[j],TL.MsTrue[sInd], dt) Phi = PPMod.calc_Phi(np.arcsin(send/dend)) dMagend = deltaMag(SU.p[j],SU.Rp[j],dend,Phi)[0] WAend = np.arctan(send/TL.dist[sInd])[0] if (dMagend <= OS.dMagLim) & (WAend >= OS.IWA): obsRes = 1 # planet visible at the end of observation if not t_char_calc: # update integration time t_int = max(t_trueint[i],t_int) if t_int != TL.maxintTime[sInd] else t_trueint[i] # if kogood, do characterization if t_char_calc: chargo = True if t_char_calc: if np.any(observationPossible): t_int = np.max(t_int[observationPossible]) return t_int, observationPossible, chargo else: return t_int, observationPossible
def propag_system(self, sInd, dt): """Propagates planet time-dependant parameters: position, velocity, planet-star distance, apparent separation, phase function, surface brightness of exo-zodiacal light, delta magnitude, and working angle. This method uses the Kepler state transition matrix to propagate a planet's state (position and velocity vectors) forward in time using the Kepler state transition matrix. Args: sInd (integer): Index of the target system of interest dt (astropy Quantity): Time increment in units of day, for planet position propagation """ PPMod = self.PlanetPhysicalModel ZL = self.ZodiacalLight TL = self.TargetList assert np.isscalar(sInd), \ "Can only propagate one system at a time, sInd must be scalar." # check for planets around this target pInds = np.where(self.plan2star == sInd)[0] if len(pInds) == 0: return # check for positive time increment assert dt >= 0, "Time increment (dt) to propagate a planet must be positive." if dt == 0: return # Calculate initial positions in AU and velocities in AU/day r0 = self.r[pInds].to('AU').value v0 = self.v[pInds].to('AU/day').value # stack dimensionless positions and velocities nPlans = pInds.size x0 = np.reshape(np.concatenate((r0, v0), axis=1), nPlans*6) # Calculate vector of gravitational parameter in AU3/day2 Ms = TL.MsTrue[[sInd]] Mp = self.Mp[pInds] mu = (const.G*(Mp + Ms)).to('AU3/day2').value # use keplerSTM.py to propagate the system prop = planSys(x0, mu, epsmult=10.) try: prop.takeStep(dt.to('day').value) except ValueError: #try again with larger epsmult and two steps to force convergence prop = planSys(x0, mu, epsmult=100.) try: prop.takeStep(dt.to('day').value/2.) prop.takeStep(dt.to('day').value/2.) except ValueError: raise ValueError('planSys error') # split off position and velocity vectors x1 = np.array(np.hsplit(prop.x0, 2*nPlans)) rind = np.array(range(0, len(x1), 2)) # even indices vind = np.array(range(1, len(x1), 2)) # odd indices # update planets' position, velocity, planet-star distance, apparent, separation, # phase function, exozodi surface brightness, delta magnitude and working angle self.r[pInds] = x1[rind]*u.AU self.v[pInds] = x1[vind]*u.AU/u.day self.d[pInds] = np.linalg.norm(self.r[pInds], axis=1)*self.r.unit self.s[pInds] = np.linalg.norm(self.r[pInds,0:2], axis=1)*self.r.unit self.phi[pInds] = PPMod.calc_Phi(np.arccos(self.r[pInds,2]/self.d[pInds])) self.fEZ[pInds] = ZL.fEZ(TL.MV[sInd], self.I[pInds], self.d[pInds]) self.dMag[pInds] = deltaMag(self.p[pInds], self.Rp[pInds], self.d[pInds], self.phi[pInds]) self.WA[pInds] = np.arctan(self.s[pInds]/TL.dist[sInd]).to('arcsec')
def cij(a, e, M0, I, w, O, Rp, p, t, mu, potential_planets, d, IWA, OWA, dMag0): ''' Calculates the dynamic completeness value of the second visit to a star Args: a (ndarray of Astropy quantities): Semi-major axis e (ndarray): Eccentricity M0 (ndarray of Astropy quantities): Mean anomaly I (Astropy quantity): Inclination (rad) w (Astropy quantity): Argument of periastron (rad) O (Astropy quantity): Longitude of the ascending node (rad) Rp (Astropy quantity): Planetary radius p (float): Geometric albedo of planets t (float): Time progressed in seconds mu (Astropy quantity): Gravitational parameter potential_planets (ndarray of booleans): A true value indicates that the planet has not been eliminated from the search around this planet d (astropy quantity): Distance to star IWA (astropy quantity): Telescope's inner working angle OWA (astropy quantity): Telescope's outer working angle dMag0 (float): Telescope's limiting difference in brightness between the star and planet Returns: c2j (ndarray): Dynamic completeness value ''' total_planets = len(a) #total number of planets # Get indices for which planets are to be propagated planet_indices = np.arange( total_planets ) #np.linspace(0, total_planets-1, total_planets).astype(int) potential_planet_indices = planet_indices[potential_planets] # Get the values for the propagated planets a_p = a[potential_planets] e_p = e[potential_planets] M0_p = M0[potential_planets] I_p = I[potential_planets] w_p = w[potential_planets] Rp_p = Rp[potential_planets] p_p = p[potential_planets] # Calculate the mean anomaly for the planet population after the time period M1 = meananom(mu, a_p, t, M0_p) # Calculate the anomalies for each planet after the time period passes E = fun.eccanom(M1.value, e_p) nu = fun.trueanom(E, e_p) theta = nu + w_p.value r = a_p * (1. - e_p**2.) / (1. + e_p * np.cos(nu)) s = (r.value/4.)*np.sqrt(4.*np.cos(2.*I_p.value) + 4.*np.cos(2.*theta)-2.*np.cos(2.*I_p.value-2.*theta) \ - 2.*np.cos(2.*I_p.value+2.*theta) + 12.) #From beta = np.arccos(-np.sin(I_p) * np.sin(theta)) phi = (np.sin(beta.value) + (np.pi - beta.value) * np.cos(beta.value)) / np.pi dMag = deltaMag(p_p, Rp_p.to(u.km), r.to(u.AU), phi) min_separation = IWA.to(u.arcsec).value * dist_to_star.to(u.pc).value max_separation = OWA.to(u.arcsec).value * dist_to_star.to(u.pc).value # Right now visible planets is an array with the size of the number of potential_planets # I need to convert it back to size of the total number of planets with each visible_planets = (s > min_separation) & (s < max_separation) & (dMag < dMag0) # Calculate the completeness cij = np.sum(visible_planets) / float(np.sum(potential_planets)) # Create an array with all of the visible planets with their original indices visible_planet_indices = potential_planet_indices[visible_planets] full_visible_planets = np.zeros(total_planets, dtype=bool) full_visible_planets[visible_planet_indices] = True return [cij, full_visible_planets]
def gen_update(self, TL): """Generates dynamic completeness values for multiple visits of each star in the target list Args: TL (TargetList): TargetList class object """ OS = TL.OpticalSystem PPop = TL.PlanetPopulation # limiting planet delta magnitude for completeness dMagMax = self.dMagLim # get name for stored dynamic completeness updates array # inner and outer working angles for detection mode mode = list(filter(lambda mode: mode['detectionMode'] == True, OS.observingModes))[0] IWA = mode['IWA'] OWA = mode['OWA'] extstr = self.extstr + 'IWA: ' + str(IWA) + ' OWA: ' + str(OWA) + \ ' dMagMax: ' + str(dMagMax) + ' nStars: ' + str(TL.nStars) ext = hashlib.md5(extstr.encode('utf-8')).hexdigest() self.dfilename += ext self.dfilename += '.dcomp' path = os.path.join(self.cachedir, self.dfilename) # if the 2D completeness update array exists as a .dcomp file load it if os.path.exists(path): self.vprint('Loading cached dynamic completeness array from "%s".' % path) try: with open(path, "rb") as ff: self.updates = pickle.load(ff) except UnicodeDecodeError: with open(path, "rb") as ff: self.updates = pickle.load(ff,encoding='latin1') self.vprint('Dynamic completeness array loaded from cache.') else: # run Monte Carlo simulation and pickle the resulting array self.vprint('Cached dynamic completeness array not found at "%s".' % path) self.vprint('Beginning dynamic completeness calculations') # dynamic completeness values: rows are stars, columns are number of visits self.updates = np.zeros((TL.nStars, 5)) # number of planets to simulate nplan = int(2e4) # sample quantities which do not change in time a, e, p, Rp = PPop.gen_plan_params(nplan) a = a.to('AU').value # sample angles I, O, w = PPop.gen_angles(nplan) I = I.to('rad').value O = O.to('rad').value w = w.to('rad').value Mp = PPop.gen_mass(nplan) # M_earth rmax = a*(1.+e) # AU # sample quantity which will be updated M = np.random.uniform(high=2.*np.pi,size=nplan) newM = np.zeros((nplan,)) # population values smin = (np.tan(IWA)*TL.dist).to('AU').value if np.isfinite(OWA): smax = (np.tan(OWA)*TL.dist).to('AU').value else: smax = np.array([np.max(PPop.arange.to('AU').value)*\ (1.+np.max(PPop.erange))]*TL.nStars) # fill dynamic completeness values for sInd in xrange(TL.nStars): mu = (const.G*(Mp + TL.MsTrue[sInd])).to('AU3/day2').value n = np.sqrt(mu/a**3) # in 1/day # normalization time equation from Brown 2015 dt = 58.0*(TL.L[sInd]/0.83)**(3.0/4.0)*(TL.MsTrue[sInd]/(0.91*u.M_sun))**(1.0/2.0) # days # remove rmax < smin pInds = np.where(rmax > smin[sInd])[0] # calculate for 5 successive observations for num in xrange(5): if num == 0: self.updates[sInd, num] = TL.comp0[sInd] if not pInds.any(): break # find Eccentric anomaly if num == 0: E = eccanom(M[pInds],e[pInds]) newM[pInds] = M[pInds] else: E = eccanom(newM[pInds],e[pInds]) r1 = a[pInds]*(np.cos(E) - e[pInds]) r1 = np.hstack((r1.reshape(len(r1),1), r1.reshape(len(r1),1), r1.reshape(len(r1),1))) r2 = (a[pInds]*np.sin(E)*np.sqrt(1. - e[pInds]**2)) r2 = np.hstack((r2.reshape(len(r2),1), r2.reshape(len(r2),1), r2.reshape(len(r2),1))) a1 = np.cos(O[pInds])*np.cos(w[pInds]) - np.sin(O[pInds])*np.sin(w[pInds])*np.cos(I[pInds]) a2 = np.sin(O[pInds])*np.cos(w[pInds]) + np.cos(O[pInds])*np.sin(w[pInds])*np.cos(I[pInds]) a3 = np.sin(w[pInds])*np.sin(I[pInds]) A = np.hstack((a1.reshape(len(a1),1), a2.reshape(len(a2),1), a3.reshape(len(a3),1))) b1 = -np.cos(O[pInds])*np.sin(w[pInds]) - np.sin(O[pInds])*np.cos(w[pInds])*np.cos(I[pInds]) b2 = -np.sin(O[pInds])*np.sin(w[pInds]) + np.cos(O[pInds])*np.cos(w[pInds])*np.cos(I[pInds]) b3 = np.cos(w[pInds])*np.sin(I[pInds]) B = np.hstack((b1.reshape(len(b1),1), b2.reshape(len(b2),1), b3.reshape(len(b3),1))) # planet position, planet-star distance, apparent separation r = (A*r1 + B*r2) # position vector (AU) d = np.linalg.norm(r,axis=1) # planet-star distance s = np.linalg.norm(r[:,0:2],axis=1) # apparent separation beta = np.arccos(r[:,2]/d) # phase angle Phi = self.PlanetPhysicalModel.calc_Phi(beta*u.rad) # phase function dMag = deltaMag(p[pInds],Rp[pInds],d*u.AU,Phi) # difference in magnitude toremoves = np.where((s > smin[sInd]) & (s < smax[sInd]))[0] toremovedmag = np.where(dMag < dMagMax)[0] toremove = np.intersect1d(toremoves, toremovedmag) pInds = np.delete(pInds, toremove) if num == 0: self.updates[sInd, num] = TL.comp0[sInd] else: self.updates[sInd, num] = float(len(toremove))/nplan # update M newM[pInds] = (newM[pInds] + n[pInds]*dt)/(2*np.pi) % 1 * 2.*np.pi if (sInd+1) % 50 == 0: self.vprint('stars: %r / %r' % (sInd+1,TL.nStars)) # ensure that completeness values are between 0 and 1 self.updates = np.clip(self.updates, 0., 1.) # store dynamic completeness array as .dcomp file with open(path, 'wb') as ff: pickle.dump(self.updates, ff) self.vprint('Dynamic completeness calculations finished') self.vprint('Dynamic completeness array stored in %r' % path)
if PPop.scaleOrbits: s /= np.sqrt(TL.L) beta = np.array([1.10472881476178] * len(s)) * u.rad # fix out of range values below = np.where(s < np.min(PPop.rrange) * np.sin(beta))[0] above = np.where(s > np.max(PPop.rrange) * np.sin(beta))[0] s[below] = np.sin(beta[below]) * np.min(PPop.rrange) beta[above] = np.arcsin(s[above] / np.max(PPop.rrange)) # calculate delta mag p = np.max(PPop.prange) Rp = np.max(PPop.Rprange) d = s / np.sin(beta) Phi = PPMod.calc_Phi(beta) i = np.where(deltaMag(p, Rp, d, Phi) < Comp.dMagLim)[0] postmax_dmag_filter = len(i) ################################################# #### int_cutoff_filter ########################## nanVmagInds = np.argwhere(np.isnan(TL.Vmag)) nanBVInds = np.argwhere(np.isnan(TL.BV)) sInds = list(np.arange(len(TL.Vmag))) sInds = np.asarray( [ind for ind in sInds if not ind in nanVmagInds and not ind in nanBVInds]) #DELETE mV = TL.starMag(sInds,565.0*u.nm) #DELETE Cp, Cb, Csp = OS.Cp_Cb_Csp(TL, sInds, ) mode = list( filter(lambda mode: mode['detectionMode'] == True, TL.OpticalSystem.observingModes))[0] fZ = 0. / u.arcsec**2
def propag_system(self, sInd, currentTimeNorm): """Propagates planet time-dependant parameters: position, velocity, planet-star distance, apparent separation, phase function, surface brightness of exo-zodiacal light, delta magnitude, working angle, and the planet current time array. This method uses the Kepler state transition matrix to propagate a planet's state (position and velocity vectors) forward in time using the Kepler state transition matrix. Args: sInd (integer): Index of the target system of interest currentTimeNorm (astropy Quantity): Current mission time normalized to zero at mission start in units of day """ PPMod = self.PlanetPhysicalModel ZL = self.ZodiacalLight TL = self.TargetList assert np.isscalar(sInd), "Can only propagate one system at a time, \ sInd must be scalar." # check for planets around this target pInds = np.where(self.plan2star == sInd)[0] if not np.any(pInds): return # check for positive time increment dt = currentTimeNorm - self.planTime[pInds][0] assert dt >= 0, "Time increment (dt) to propagate a planet must be positive." if dt == 0: return # Initial positions in AU and velocities in AU/day rold = self.r[pInds].to('AU').value vold = self.v[pInds].to('AU/day').value # stack dimensionless positions and velocities x0 = np.array([]) for i in xrange(len(rold)): x0 = np.hstack((x0, rold[i], vold[i])) # calculate system's distance and masses sDist = TL.dist[[sInd]] Ms = TL.MsTrue[[sInd]]*const.M_sun Mp = self.Mp[pInds] # calculate vector of gravitational parameter mu = (const.G*(Mp + Ms)).to('AU3/day2').value # use keplerSTM.py to propagate the system prop = planSys(x0, mu, epsmult=10.) try: prop.takeStep(dt.to('day').value) except ValueError: #try again with larger epsmult and two steps to force convergence prop = planSys(x0, mu, epsmult=100.) try: prop.takeStep(dt.to('day').value/2.) prop.takeStep(dt.to('day').value/2.) except ValueError: raise ValueError('planSys error') # split off position and velocity vectors x1 = np.array(np.hsplit(prop.x0, 2*len(rold))) rind = np.array(range(0,len(x1),2)) # even indices vind = np.array(range(1,len(x1),2)) # odd indices # update planets' position, velocity, planet-star distance, apparent # separation, phase function, exozodi surface brightness, delta magnitude, # working angle, and current time self.r[pInds] = x1[rind]*u.AU self.v[pInds] = x1[vind]*u.AU/u.day self.d[pInds] = np.sqrt(np.sum(self.r[pInds]**2, axis=1)) self.s[pInds] = np.sqrt(np.sum(self.r[pInds,0:2]**2, axis=1)) self.phi[pInds] = PPMod.calc_Phi(np.arcsin(self.s[pInds]/self.d[pInds])) self.fEZ[pInds] = ZL.fEZ(TL, sInd, self.I[pInds],self.d[pInds]) self.dMag[pInds] = deltaMag(self.p[pInds],self.Rp[pInds],self.d[pInds],self.phi[pInds]) self.WA[pInds] = np.arctan(self.s[pInds]/sDist).to('mas') self.planTime[pInds] = currentTimeNorm
def observation_detection(self, pInds, sInd, DRM, planPosTime): """Finds if planet observations are possible and relevant information This method makes use of the following inherited class objects: Args: pInds (ndarray): 1D numpy ndarray of planet indices sInd (int): target star index DRM (dict): dictionary containing simulation results planPosTime (Quantity): 1D numpy ndarray containing times of planet positions (units of time) Returns: observationPossible, t_int, DRM (ndarray, Quantity, dict, Quantity, ndarray, Quantity): 1D numpy ndarray of booleans indicating if an observation of each planet is possible, integration time (units of time), dictionary containing survey simulation results, apparent separation (units of distance), 1D numpy ndarray of delta magnitude, difference in magnitude between planet and star, irradiance (units of :math:`1/(m^2*nm*s)`) """ Obs = self.Observatory TK = self.TimeKeeping SU = self.SimulatedUniverse TL = self.TargetList OS = self.OpticalSystem ZL = self.ZodiacalLight PPop = self.PlanetPopulation PPMod = self.PlanetPhysicalModel # initialize with True if planets are present at the target star observationPossible = np.ones(len(pInds),bool) if len(pInds) else False # propagate the system if a planet position time do not match up with current time for i,pInd in enumerate(pInds): if planPosTime[pInd] != TK.currentTimeNorm: # propagate planet positions and velocities try: dt = TK.currentTimeNorm - planPosTime[pInd] j = np.array([pInd]) SU.r[j],SU.v[j],SU.s[j],SU.d[j] = SU.prop_system(SU.r[j],\ SU.v[j],SU.Mp[j],TL.MsTrue[sInd],dt) # update planet position times planPosTime[pInd] += dt except ValueError: observationPossible[i] = False # set integration time to max integration time as a default t_int = TL.maxintTime[sInd] # determine true integration time and update observationPossible if np.any(observationPossible): sInds = np.array([sInd]*len(pInds)) Phi = PPMod.calc_Phi(np.arcsin(SU.s[pInds]/SU.d[pInds])) dMag = deltaMag(SU.p[pInds],SU.Rp[pInds],SU.d[pInds],Phi) WA = SU.get_current_WA(pInds) fEZ = SU.fEZ[pInds] fZ = ZL.fZ(TL,sInds,OS.Imager['lam'],Obs.r_sc) t_trueint = OS.calc_intTime(TL,sInds,dMag,WA,fEZ,fZ) observationPossible = observationPossible & (t_trueint <= OS.intCutoff) # determine if planets are observable at the end of observation # and update integration time if np.any(observationPossible): try: t_int, observationPossible = self.check_visible_end(observationPossible,\ t_int, t_trueint, sInd, pInds, False) except ValueError: observationPossible = False if OS.haveOcculter: # find disturbance forces on occulter dF_lateral, dF_axial = Obs.distForces(TK, TL, sInd) # store these values DRM['dF_lateral'] = dF_lateral.to('N').value DRM['dF_axial'] = dF_axial.to('N').value # decrement mass for station-keeping intMdot, mass_used, deltaV = Obs.mass_dec(dF_lateral, t_int) # store these values DRM['det_dV'] = deltaV.to('m/s').value DRM['det_mass_used'] = mass_used.to('kg').value Obs.scMass -= mass_used # patch negative t_int if np.any(t_int < 0): Logger.warning('correcting negative t_int to arbitrary value') t_int = (1.0+np.random.rand())*u.day return observationPossible, t_int, DRM
def observation_characterization(self, observationPossible, pInds, sInd, spectra, DRM, FA, t_int): """Finds if characterizations are possible and relevant information Args: observationPossible (ndarray): 1D numpy ndarray of booleans indicating if an observation of each planet is possible pInds (ndarray): 1D numpy ndarray of planet indices sInd (int): target star index spectra (ndarray): numpy ndarray of values indicating if planet spectra has been captured DRM (dict): dictionary containing survey simulation results FA (bool): False Alarm boolean t_int (Quantity): integration time (units of time) Returns: DRM, FA, spectra (dict, bool, ndarray): dictionary containing survey simulation results, False Alarm boolean, numpy ndarray of values indicating if planet spectra has been captured """ Obs = self.Observatory SU = self.SimulatedUniverse TL = self.TargetList TK = self.TimeKeeping OS = self.OpticalSystem ZL = self.ZodiacalLight PPop = self.PlanetPopulation PPMod = self.PlanetPhysicalModel # check if characterization has been done if pInds.shape[0] != 0: if np.any(observationPossible): if np.any(spectra[pInds[observationPossible]] == 0): # perform first characterization # find characterization time sInds = np.array([sInd]*len(pInds)) Phi = PPMod.calc_Phi(np.arcsin(SU.s[pInds]/SU.d[pInds])) dMag = deltaMag(SU.p[pInds],SU.Rp[pInds],SU.d[pInds],Phi) WA = SU.get_current_WA(pInds) fEZ = SU.fEZ[pInds] fZ = ZL.fZ(TL,sInds,OS.Spectro['lam'],Obs.r_sc) t_char = OS.calc_charTime(TL,sInds,dMag,WA,fZ,fEZ) # account for 5 bands and one coronagraph t_char *= 4 # patch negative t_char if np.any(t_char < 0): Logger.warning('correcting negative t_char to arb. value') t_char_value = (4+2*np.random.rand())*u.day t_char[t_char < 0] = t_char_value # determine which planets will be observable at the end of observation charPossible = observationPossible & (t_char <= OS.intCutoff) try: t_char, charPossible, chargo = self.check_visible_end(charPossible, \ t_char, t_char, sInd, pInds, True) except ValueError: chargo = False if chargo: # encode relevant first characterization data if OS.haveOcculter: # decrement sc mass # find disturbance forces on occulter dF_lateral, dF_axial = Obs.distForces(TK, TL, sInd) # decrement mass for station-keeping intMdot, mass_used, deltaV = Obs.mass_dec(dF_lateral, t_int) mass_used_char = t_char*intMdot deltaV_char = dF_lateral/Obs.scMass*t_char Obs.scMass -= mass_used_char # encode information in DRM DRM['char_1_time'] = t_char.to('day').value DRM['char_1_dV'] = deltaV_char.to('m/s').value DRM['char_1_mass_used'] = mass_used_char.to('kg').value else: DRM['char_1_time'] = t_char.to('day').value # if integration time goes beyond observation duration, set quantities if not TK.allocate_time(t_char.max()): charPossible = False # if this was a false alarm, it has been noted, update FA if FA: FA = False # if planet is visible at end of characterization, # spectrum is captured if np.any(charPossible): if OS.haveOcculter: spectra[pInds[charPossible]] = 1 # encode success DRM['char_1_success'] = 1 else: lamEff = np.arctan(SU.s[pInds]/TL.dist[sInd]) / OS.IWA.to('rad') lamEff *= OS.Spectro['lam']/OS.pupilDiam*np.sqrt(OS.pupilArea/OS.shapeFac) charPossible = charPossible & (lamEff >= 800.*u.nm) # encode results if np.any(charPossible): spectra[pInds[charPossible]] = 1 DRM['char_1_success'] = 1 else: DRM['char_1_success'] = lamEff.max().to('nm').value return DRM, FA, spectra
s_circle = np.asarray([s_inner.to('AU').value]) #NEED TO MAKE GOOD HANDLING FOR E=0 ORBITS. SPECIFICALLY FOR MIN AND MAX SOLVING # dmajorp,dminorp,_,_,Op,x,y,Phi,xreal,only2RealInds,yrealAllRealInds,fourIntInds,twoIntOppositeXInds,twoIntSameYInds,nu_minSepPoints,nu_maxSepPoints,\ # nu_lminSepPoints,nu_lmaxSepPoints,nu_fourInt,nu_twoIntSameY,nu_twoIntOppositeX,nu_IntersectionsOnly2, yrealImagInds,\ # t_minSep,t_maxSep,t_lminSep,t_lmaxSep,t_fourInt0,t_fourInt1,t_fourInt2,t_fourInt3,t_twoIntSameY0,\ # t_twoIntSameY1,t_twoIntOppositeX0,t_twoIntOppositeX1,t_IntersectionOnly20,t_IntersectionOnly21,\ # _,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_, periods = calcMasterIntersections(smavenus,e,W,w,inc,s_circle,starMass,False)\ #need beta from true anomaly function betas = betaFunc(inc, nus, w) beta_smax = betaFunc(inc, 0., w) Phis = quasiLambertPhaseFunction(betas) Phi_smax = quasiLambertPhaseFunction(beta_smax) rsvenus = smavenus * (1. - e**2.) / (1. + e * np.cos(nus)) dmags_venus = deltaMag(pvenus, Rpvenus, rsvenus[0], Phis) dmag_venus_smax = deltaMag(pvenus, Rpvenus, rsvenus[0], Phi_smax) seps_venus = planet_star_separation(smavenus, e, nus, w, inc) WA_venus_smax = (smavenus.to('AU').value / (15. * u.pc.to('AU'))) * u.rad.to('arcsec') * u.arcsec #Calculate integration time at WA_venus_smax venus_intTime = OS.calc_intTime(TL, [0], ZL.fZ0 * 100000., ZL.fEZ0 * 100000., dmag_venus_smax, WA_venus_smax, mode) mean_anomalyvenus = venus_intTime.to( 'year').value * 2. * np.pi / periods_venus #total angle moved by planet eccentric_anomalyvenus = mean_anomalyvenus #solve eccentric anomaly from mean anomaly nus_venus = trueAnomalyFromEccentricAnomaly( e, eccentric_anomalyvenus
def det_data(self, DRM, FA, DET, MD, sInd, pInds, observationPossible, observed): """Determines detection status This method encodes detection status (FA, DET, MD) values in the DRM dictionary. Args: DRM (dict): dictionary containing simulation results FA (bool): Boolean signifying False Alarm DET (bool): Boolean signifying DETection MD (bool): Boolean signifying Missed Detection sInd (int): index of star in target list pInds (ndarray): idices of planets belonging to target star observationPossible (ndarray): 1D numpy ndarray of booleans indicating if an observation of each planet is possible observed (ndarray): 1D numpy ndarray indicating number of observations for each planet Returns: s, DRM, observed (Quantity, dict, ndarray): apparent separation (units of distance), delta magnitude, irradiance (units of flux per time), dictionary containing simulation results, 1D numpy ndarray indicating number of observations for each planet """ SU = self.SimulatedUniverse TL = self.TargetList PPop = self.PlanetPopulation PPMod = self.PlanetPhysicalModel # planet indexes DRM['plan_inds'] = pInds # default DRM detection status to null detection DRM['det_status'] = 0 # apparent separation placeholders s = SU.s[pInds] if pInds.size else np.array([1.])*u.AU if FA: # false alarm DRM['det_status'] = -2 ds = np.random.rand()*(PPop.arange.max() - PPop.arange.min()) s += ds*np.sqrt(TL.L[sInd]) if PPop.scaleOrbits else ds elif MD: # missed detection DRM['det_status'] = -1 elif DET: # detection observed[pInds[observationPossible]] += 1 DRM['det_status'] = observationPossible.astype(int).tolist() DRM['det_WA'] = np.arctan(s/TL.dist[sInd]).min().to('mas').value Phi = PPMod.calc_Phi(np.arcsin(SU.s[pInds]/SU.d[pInds])) DRM['det_dMag'] = deltaMag(SU.p[pInds], SU.Rp[pInds], SU.d[pInds],Phi).max() return s, DRM, observed
#### dmag vs nu extrema and intersection Verification plot #ind = indsWith4Int[0] for ind in indsWith4Int: num = ind plt.figure(num=num) plt.rc('axes', linewidth=2) plt.rc('lines', linewidth=2) plt.rcParams['axes.linewidth'] = 2 plt.rc('font', weight='bold') nus = np.linspace(start=0, stop=2. * np.pi, num=300) phis = (1. + np.sin(inc[ind]) * np.sin(nus + w[ind]) )**2. / 4. #TRYING THIS TO CIRCUMVENT POTENTIAL ARCCOS ds = a[ind] * (1. - e[ind]**2.) / (e[ind] * np.cos(nus) + 1.) dmags = deltaMag(p[ind], Rp[ind].to('AU'), ds, phis) #calculate dmag of the specified x-value plt.plot(nus, dmags, color='black', zorder=10) #plt.plot([0.,2.*np.pi],[dmag,dmag],color='blue') plt.scatter(nuMinDmag[ind], mindmag[ind], color='cyan', marker='d', zorder=20) plt.scatter(nuMaxDmag[ind], maxdmag[ind], color='red', marker='d', zorder=20) lind = np.where(ind == indsWith4)[0] if ind in indsWith2Int:
compMethod2 = rawdata['compMethod2'] total_time_visible_error = rawdata['total_time_visible_error'] visbools = rawdata['visbools'] else: start = time.time() for i in np.arange(numPlans):#len(inc)): print('Working on: ' + str(i) + '/' + str(numPlans)) #period = periods[i] #np.linspace(start=0.,stop=periods[i]) M = np.linspace(start=0.,stop=2.*np.pi,num=numPoints) #Even distribution across mean anomaly E = np.asarray([eccanom(M[j],e[i]) for j in np.arange(len(M))]) nus_range = trueAnomalyFromEccentricAnomaly(e[i],E) betas = betaFunc(inc[i],nus_range,w[i]) Phis = quasiLambertPhaseFunction(betas) rs = sma[i]*u.AU*(1.-e[i]**2.)/(1.+e[i]*np.cos(nus_range)) dmags = deltaMag(p[i],Rp[i],rs,Phis) seps = planet_star_separation(sma[i],e[i],nus_range,w[i],inc[i]) visibleBool = (seps > s_inner)*(seps < s_outer)*(dmags < dmag_upper) visbools.append(visibleBool) compMethod2.append(np.sum(visibleBool.astype('int'))/numPoints) total_time_visible_error.append(np.abs(compMethod2[i]-totalCompleteness[i])) stop = time.time() print('Execution Time (s): ' + str(stop-start)) compMethod2 = np.asarray(compMethod2) print(compMethod2) print(totalCompleteness[0:len(compMethod2)]) print(total_time_visible_error) print(np.max(total_time_visible_error))
def gen_update(self, TL): """Generates dynamic completeness values for multiple visits of each star in the target list Args: TL (TargetList module): TargetList class object """ OS = TL.OpticalSystem PPop = self.PlanetPopulation print 'Beginning completeness update calculations' # initialize number of visits self.visits = np.array([0] * TL.nStars) # dynamic completeness values: rows are stars, columns are number of visits self.updates = np.zeros((TL.nStars, 5)) # number of planets to simulate nplan = int(2e4) # normalization time dt = 1e9 * u.day # sample quantities which do not change in time a = PPop.gen_sma(nplan) # AU e = PPop.gen_eccen(nplan) I = PPop.gen_I(nplan) # deg O = PPop.gen_O(nplan) # deg w = PPop.gen_w(nplan) # deg p = PPop.gen_albedo(nplan) Rp = PPop.gen_radius(nplan) # km Mp = PPop.gen_mass(nplan) # kg rmax = a * (1. + e) # sample quantity which will be updated M = np.random.uniform(high=2. * np.pi, size=nplan) newM = np.zeros((nplan, )) # population values smin = (np.tan(OS.IWA) * TL.dist).to('AU') if np.isfinite(OS.OWA): smax = (np.tan(OS.OWA) * TL.dist).to('AU') else: smax = np.array([np.max(PPop.arange.to('AU').value)*\ (1.+np.max(PPop.erange))]*TL.nStars)*u.AU # fill dynamic completeness values for sInd in xrange(TL.nStars): Mstar = TL.MsTrue[sInd] * const.M_sun # remove rmax < smin pInds = np.where(rmax > smin[sInd])[0] # calculate for 5 successive observations for num in xrange(5): if num == 0: self.updates[sInd, num] = TL.comp0[sInd] if not pInds.any(): break # find Eccentric anomaly if num == 0: E = eccanom(M[pInds], e[pInds]) newM[pInds] = M[pInds] else: E = eccanom(newM[pInds], e[pInds]) r1 = a[pInds] * (np.cos(E) - e[pInds]) r1 = np.hstack((r1.reshape(len(r1), 1), r1.reshape(len(r1), 1), r1.reshape(len(r1), 1))) r2 = (a[pInds] * np.sin(E) * np.sqrt(1. - e[pInds]**2)) r2 = np.hstack((r2.reshape(len(r2), 1), r2.reshape(len(r2), 1), r2.reshape(len(r2), 1))) a1 = np.cos(O[pInds]) * np.cos(w[pInds]) - np.sin( O[pInds]) * np.sin(w[pInds]) * np.cos(I[pInds]) a2 = np.sin(O[pInds]) * np.cos(w[pInds]) + np.cos( O[pInds]) * np.sin(w[pInds]) * np.cos(I[pInds]) a3 = np.sin(w[pInds]) * np.sin(I[pInds]) A = np.hstack((a1.reshape(len(a1), 1), a2.reshape(len(a2), 1), a3.reshape(len(a3), 1))) b1 = -np.cos(O[pInds]) * np.sin(w[pInds]) - np.sin( O[pInds]) * np.cos(w[pInds]) * np.cos(I[pInds]) b2 = -np.sin(O[pInds]) * np.sin(w[pInds]) + np.cos( O[pInds]) * np.cos(w[pInds]) * np.cos(I[pInds]) b3 = np.cos(w[pInds]) * np.sin(I[pInds]) B = np.hstack((b1.reshape(len(b1), 1), b2.reshape(len(b2), 1), b3.reshape(len(b3), 1))) # planet position, planet-star distance, apparent separation r = (A * r1 + B * r2) * u.AU # position vector d = np.sqrt(np.sum(r**2, axis=1)) # planet-star distance s = np.sqrt(np.sum(r[:, 0:2]**2, axis=1)) # apparent separation beta = np.arccos(r[:, 2] / d) # phase angle Phi = self.PlanetPhysicalModel.calc_Phi(beta) # phase function dMag = deltaMag(p[pInds], Rp[pInds], d, Phi) # difference in magnitude toremoves = np.where((s > smin[sInd]) & (s < smax[sInd]))[0] toremovedmag = np.where(dMag < OS.dMagLim)[0] toremove = np.intersect1d(toremoves, toremovedmag) pInds = np.delete(pInds, toremove) if num == 0: self.updates[sInd, num] = TL.comp0[sInd] else: self.updates[sInd, num] = float(len(toremove)) / nplan # update M mu = const.G * (Mstar + Mp[pInds]) n = np.sqrt(mu / a[pInds]**3) newM[pInds] = (newM[pInds] + n * dt) / (2 * np.pi) % 1 * 2. * np.pi if (sInd + 1) % 50 == 0: print 'stars: %r / %r' % (sInd + 1, TL.nStars) print 'Completeness update calculations finished'
def set_planet_phase(self, beta=np.pi / 2): """Positions all planets at input star-planet-observer phase angle where possible. For systems where the input phase angle is not achieved, planets are positioned at quadrature (phase angle of 90 deg). The position found here is not unique. The desired phase angle will be achieved at two points on the planet's orbit (for non-face on orbits). Args: beta (float): star-planet-observer phase angle in radians. """ PPMod = self.PlanetPhysicalModel ZL = self.ZodiacalLight TL = self.TargetList a = self.a.to('AU').value # semi-major axis e = self.e # eccentricity I = self.I.to('rad').value # inclinations O = self.O.to('rad').value # right ascension of the ascending node w = self.w.to('rad').value # argument of perigee Mp = self.Mp # planet masses # make list of betas betas = beta * np.ones(w.shape) mask = np.cos(betas) / np.sin(I) > 1. num = len(np.where(mask == True)[0]) betas[mask] = np.pi / 2. mask = np.cos(betas) / np.sin(I) < -1. num += len(np.where(mask == True)[0]) betas[mask] = np.pi / 2. if num > 0: self.vprint('***Warning***') self.vprint( '{} planets out of {} could not be set to phase angle {} radians.' .format(num, self.nPlans, beta)) self.vprint( 'These planets are set to quadrature (phase angle pi/2)') # solve for true anomaly nu = np.arcsin(np.cos(betas) / np.sin(I)) - w # setup for position and velocity a1 = np.cos(O) * np.cos(w) - np.sin(O) * np.cos(I) * np.sin(w) a2 = np.sin(O) * np.cos(w) + np.cos(O) * np.cos(I) * np.sin(w) a3 = np.sin(I) * np.sin(w) A = np.vstack((a1, a2, a3)) b1 = -(np.cos(O) * np.sin(w) + np.sin(O) * np.cos(I) * np.cos(w)) b2 = (-np.sin(O) * np.sin(w) + np.cos(O) * np.cos(I) * np.cos(w)) b3 = np.sin(I) * np.cos(w) B = np.vstack((b1, b2, b3)) r = a * (1. - e**2) / (1. - e * np.cos(nu)) mu = const.G * (Mp + TL.MsTrue[self.plan2star]) v1 = -np.sqrt(mu / (self.a * (1. - self.e**2))) * np.sin(nu) v2 = np.sqrt(mu / (self.a * (1. - self.e**2))) * (self.e + np.cos(nu)) self.r = (A * r * np.cos(nu) + B * r * np.sin(nu)).T * u.AU # position self.v = (A * v1 + B * v2).T.to('AU/day') # velocity # if sys.version_info[0] > 2: # self.d = np.linalg.norm(self.r, axis=1) # planet-star distance # self.s = np.linalg.norm(self.r[:,0:2], axis=1) # apparent separation # else: # self.d = np.linalg.norm(self.r, axis=1)*self.r.unit # planet-star distance # self.s = np.linalg.norm(self.r[:,0:2], axis=1)*self.r.unit # apparent separation try: self.d = np.linalg.norm(self.r, axis=1) # planet-star distance self.phi = PPMod.calc_Phi( np.arccos(self.r[:, 2].to('AU').value / self.d.to('AU').value) * u.rad) # planet phase except: self.d = np.linalg.norm( self.r, axis=1) * self.r.unit # planet-star distance self.phi = PPMod.calc_Phi( np.arccos(self.r[:, 2].to('AU').value / self.d.to('AU').value) * u.rad) # planet phase self.fEZ = ZL.fEZ(TL.MV[self.plan2star], self.I, self.d) # exozodi brightness self.dMag = deltaMag(self.p, self.Rp, self.d, self.phi) # delta magnitude try: self.s = np.linalg.norm(self.r[:, 0:2], axis=1) # apparent separation self.WA = np.arctan(self.s / TL.dist[self.plan2star]).to( 'arcsec') # working angle except: self.s = np.linalg.norm( self.r[:, 0:2], axis=1) * self.r.unit # apparent separation self.WA = np.arctan(self.s / TL.dist[self.plan2star]).to( 'arcsec') # working angle
def genplans(self, nplan): """Generates planet data needed for Monte Carlo simulation Args: nplan (integer): Number of planets Returns: s (astropy Quantity array): Planet apparent separations in units of AU dMag (ndarray): Difference in brightness """ PPop = self.PlanetPopulation nplan = int(nplan) # sample uniform distribution of mean anomaly M = np.random.uniform(high=2. * np.pi, size=nplan) # sample semi-major axis a = PPop.gen_sma(nplan).to('AU').value # sample other necessary orbital parameters if np.sum(PPop.erange) == 0: # all circular orbits r = a e = 0. E = M else: # sample eccentricity if PPop.constrainOrbits: e = PPop.gen_eccen_from_sma(nplan, a * u.AU) else: e = PPop.gen_eccen(nplan) # Newton-Raphson to find E E = eccanom(M, e) # orbital radius r = a * (1 - e * np.cos(E)) # orbit angle sampling O = PPop.gen_O(nplan).to('rad').value w = PPop.gen_w(nplan).to('rad').value I = PPop.gen_I(nplan).to('rad').value r1 = a * (np.cos(E) - e) r1 = np.hstack((r1.reshape(len(r1), 1), r1.reshape(len(r1), 1), r1.reshape(len(r1), 1))) r2 = a * np.sin(E) * np.sqrt(1. - e**2) r2 = np.hstack((r2.reshape(len(r2), 1), r2.reshape(len(r2), 1), r2.reshape(len(r2), 1))) a1 = np.cos(O) * np.cos(w) - np.sin(O) * np.sin(w) * np.cos(I) a2 = np.sin(O) * np.cos(w) + np.cos(O) * np.sin(w) * np.cos(I) a3 = np.sin(w) * np.sin(I) A = np.hstack((a1.reshape(len(a1), 1), a2.reshape(len(a2), 1), a3.reshape(len(a3), 1))) b1 = -np.cos(O) * np.sin(w) - np.sin(O) * np.cos(w) * np.cos(I) b2 = -np.sin(O) * np.sin(w) + np.cos(O) * np.cos(w) * np.cos(I) b3 = np.cos(w) * np.sin(I) B = np.hstack((b1.reshape(len(b1), 1), b2.reshape(len(b2), 1), b3.reshape(len(b3), 1))) # planet position, planet-star distance, apparent separation r = (A * r1 + B * r2) * u.AU d = np.sqrt(np.sum(r**2, axis=1)) s = np.sqrt(np.sum(r[:, 0:2]**2, axis=1)) # sample albedo, planetary radius, phase function p = PPop.gen_albedo(nplan) Rp = PPop.gen_radius(nplan) beta = np.arccos(r[:, 2] / d) Phi = self.PlanetPhysicalModel.calc_Phi(beta) # calculate dMag dMag = deltaMag(p, Rp, d, Phi) return s, dMag
#NEED TO BE ABLE TO PUT BOUNDS INTO BOX WITH 4 SIDES #AND BOX WITH 3 SIDES #### dmag vs nu extrema and intersection Verification plot num = 88833543453218 plt.figure(num=num) plt.rc('axes', linewidth=2) plt.rc('lines', linewidth=2) plt.rcParams['axes.linewidth'] = 2 plt.rc('font', weight='bold') ind = fourIntersectionInd #indsWith4Int[0] nus = np.linspace(start=0, stop=2. * np.pi, num=100) phis = (1. + np.sin(inc[ind]) * np.sin(nus + w[ind]) )**2. / 4. #TRYING THIS TO CIRCUMVENT POTENTIAL ARCCOS ds = sma[ind] * (1. - e[ind]**2.) / (e[ind] * np.cos(nus) + 1.) dmags = deltaMag(p[ind], Rp[ind].to('AU'), ds * u.AU, phis) #calculate dmag of the specified x-value plt.plot(nus, dmags, color='black', zorder=10) #plt.plot([0.,2.*np.pi],[dmag,dmag],color='blue') plt.scatter(nuMinDmag[ind], mindmag[ind], color='teal', marker='D', zorder=20) plt.plot([0., 2. * np.pi], [mindmag[ind], mindmag[ind]], color='teal', zorder=20) plt.scatter(nuMaxDmag[ind], maxdmag[ind], color='red', marker='D', zorder=20) plt.plot([0., 2. * np.pi], [maxdmag[ind], maxdmag[ind]], color='red', zorder=20) lind = np.where(ind == indsWith4)[0] if ind in indsWith2Int: mind = np.where(ind == indsWith2Int)[0][0] plt.scatter(nus2Int[mind],
def gen_update(self, targlist): """Generates dynamic completeness values for multiple visits of each star in the target list Args: targlist (TargetList): TargetList module """ print 'Beginning completeness update calculations' self.visits = np.array([0]*targlist.nStars) self.updates = [] # number of planets to simulate nplan = int(2e4) # normalization time dt = 1e9*u.day # sample quantities which do not change in time a = self.PlanetPopulation.gen_sma(nplan) # AU e = self.PlanetPopulation.gen_eccen(nplan) I = self.PlanetPopulation.gen_I(nplan) # deg O = self.PlanetPopulation.gen_O(nplan) # deg w = self.PlanetPopulation.gen_w(nplan) # deg p = self.PlanetPopulation.gen_albedo(nplan) Rp = self.PlanetPopulation.gen_radius(nplan) # km Mp = self.PlanetPopulation.gen_mass(nplan) # kg rmax = a*(1.+e) rmin = a*(1.-e) # sample quantity which will be updated M = np.random.uniform(high=2.*np.pi,size=nplan) newM = np.zeros((nplan,)) # population values smin = (np.tan(targlist.OpticalSystem.IWA)*targlist.dist).to('AU') if np.isfinite(targlist.OpticalSystem.OWA): smax = (np.tan(targlist.OpticalSystem.OWA)*targlist.dist).to('AU') else: smax = np.array([np.max(self.PlanetPopulation.arange.to('AU').value)*\ (1.+np.max(self.PlanetPopulation.erange))]*targlist.nStars)*u.AU # fill dynamic completeness values for sInd in xrange(targlist.nStars): Mstar = targlist.MsTrue[sInd]*const.M_sun # remove rmax < smin and rmin > smax inside = np.where(rmax > smin[sInd])[0] outside = np.where(rmin < smax[sInd])[0] pInds = np.intersect1d(inside,outside) dynamic = [] # calculate for 5 successive observations for num in xrange(5): if not pInds.any(): dynamic.append(0.) break # find Eccentric anomaly if num == 0: E = eccanom(M[pInds],e[pInds]) newM[pInds] = M[pInds] else: E = eccanom(newM[pInds],e[pInds]) r = a[pInds]*(1.-e[pInds]*np.cos(E)) r1 = r*(np.cos(E) - e[pInds]) r1 = np.hstack((r1.reshape(len(r1),1), r1.reshape(len(r1),1), r1.reshape(len(r1),1))) r2 = (r*np.sin(E)*np.sqrt(1. - e[pInds]**2)) r2 = np.hstack((r2.reshape(len(r2),1), r2.reshape(len(r2),1), r2.reshape(len(r2),1))) a1 = np.cos(O[pInds])*np.cos(w[pInds]) - np.sin(O[pInds])*np.sin(w[pInds])*np.cos(I[pInds]) a2 = np.sin(O[pInds])*np.cos(w[pInds]) + np.cos(O[pInds])*np.sin(w[pInds])*np.cos(I[pInds]) a3 = np.sin(w[pInds])*np.sin(I[pInds]) A = np.hstack((a1.reshape(len(a1),1), a2.reshape(len(a2),1), a3.reshape(len(a3),1))) b1 = -np.cos(O[pInds])*np.sin(w[pInds]) - np.sin(O[pInds])*np.cos(w[pInds])*np.cos(I[pInds]) b2 = -np.sin(O[pInds])*np.sin(w[pInds]) + np.cos(O[pInds])*np.cos(w[pInds])*np.cos(I[pInds]) b3 = np.cos(w[pInds])*np.sin(I[pInds]) B = np.hstack((b1.reshape(len(b1),1), b2.reshape(len(b2),1), b3.reshape(len(b3),1))) # planet position, planet-star distance, apparent separation r = (A*r1 + B*r2)*u.AU # position vector d = np.sqrt(np.sum(r**2, axis=1)) # planet-star distance s = np.sqrt(np.sum(r[:,0:2]**2, axis=1)) # apparent separation beta = np.arccos(r[:,2]/d) # phase angle Phi = self.PlanetPhysicalModel.calc_Phi(beta) # phase function dMag = deltaMag(p[pInds],Rp[pInds],d,Phi) # difference in magnitude toremoves = np.where((s > smin[sInd]) & (s < smax[sInd]))[0] toremovedmag = np.where(dMag < targlist.OpticalSystem.dMagLim)[0] toremove = np.intersect1d(toremoves, toremovedmag) pInds = np.delete(pInds, toremove) if num == 0: dynamic.append(targlist.comp0[sInd]) else: dynamic.append(float(len(toremove))/nplan) # update M mu = const.G*(Mstar+Mp[pInds]) n = np.sqrt(mu/a[pInds]**3) newM[pInds] = (newM[pInds] + n*dt)/(2*np.pi) % 1 * 2.*np.pi self.updates.append(dynamic) if (sInd+1) % 50 == 0: print 'stars: %r / %r' % (sInd+1,targlist.nStars) self.updates = np.array(self.updates) print 'Completeness update calculations finished'