def littrow(): """ Trace rectangular beam into OPG in Littrow. 400 nm groove period 8.4 m groove convergence """ #Set up beam rays = sources.rectArray(50., 50., 1e2) #Trace to Littrow and diffract tran.transform(rays, 0, 0, 0, 0, 52.28 * np.pi / 180, 0) surf.flat(rays) tran.transform(rays, 0, 8400., 0, 0, 0, 0) tran.radgrat(rays, 400. / 8400., 1, 632.8) #Steer out beam tilt tran.steerX(rays) tran.steerY(rays) #Bring rays to common plane surf.flat(rays) #Get wavefront r = anal.wavefront(rays, 200, 200) return r
def trace_gwat(gwat_xous=gwat_xous, gwat_facets=gwat_facets, order=order, wave=wave, N=10**5, diff_focus_zloc=None): # Tracing from the source through the SPO MMs. gwat_rays = gwatf.make_gwat_source(N, wave, gwathw.source_dist, gwathw.clock_angles) spomm_rays = ArcSPO.SPOPetalTrace(gwat_rays, gwat_xous) # I'm now doing this rather than what's embedded in the facet calculation. Is it bad? Time will tell. # Computing the SPO focus characteristics, the focal length, and the effective radius. focused_spo_rays, dz = gwatf.bring_rays_to_disp_focus(spomm_rays) xFocus, yFocus, zFocus = array( [mean(focused_spo_rays.x), mean(focused_spo_rays.y), -dz]) # Now creating a saved version of the SPO MM rays (spomm_rays) and readying a set of rays # referenced to the SPO focus for tracing. system_spo_rays = copy.deepcopy(spomm_rays) prays = system_spo_rays.yield_prays() tran.transform(prays, xFocus, yFocus, -zFocus, 0., 0., 0.) system_spo_rays.set_prays(prays) # Setting the GWAT order in accordance with this function for "order selection". gwatf.set_gwat_order(order, facets=gwat_facets) # And now tracing the rays through the adjusted CAT petal. grat_rays = ArcCAT.CATPetalTrace(system_spo_rays, gwat_facets) ## Focusing the diffracted rays. #meas_rays = copy.deepcopy(grat_rays) #prays = meas_rays.yield_prays() #surf.focusX(prays) #meas_rays.set_prays(prays) # Cleaning up and naming everything appropriately. system_spo_rays = system_spo_rays system_grat_rays = grat_rays system_spo_focused_rays, spo_dz = gwatf.bring_rays_to_disp_focus( system_spo_rays) if diff_focus_zloc is None: system_diff_rays, diff_focus_zloc = gwatf.bring_rays_to_disp_focus( system_grat_rays) prays = system_diff_rays.yield_prays() tran.transform(prays, 0., 0., -diff_focus_zloc, 0., 0., 0) system_diff_rays.set_prays(prays) else: system_diff_rays = copy.deepcopy(system_grat_rays) prays = system_diff_rays.yield_prays() tran.transform(prays, 0., 0., diff_focus_zloc, 0., 0., 0) surf.flat(prays) tran.transform(prays, 0., 0., -diff_focus_zloc, 0., 0., 0) system_diff_rays.set_prays(prays) return system_spo_rays, system_grat_rays, system_spo_focused_rays, system_diff_rays, diff_focus_zloc
def littrow(): """ Trace rectangular beam into OPG in Littrow. 400 nm groove period 8.4 m groove convergence """ #Set up beam rays = sources.rectArray(50., 50., 1e2) #Trace to Littrow and diffract tran.transform(rays, 0, 0, 0, 0, 52.28 * np.pi / 180, 0) surf.flat(rays) tran.transform(rays, 0, 8400., 0, 0, 0, 0) tran.radgrat(rays, 400. / 8400., 1, 632.8) #Steer out beam tilt tran.steerX(rays) tran.steerY(rays) #Bring rays to common plane surf.flat(rays) #Interpolate slopes to regular grid y, dx, dy = anal.interpolateVec(rays, 5, 200, 200) x, dx, dy = anal.interpolateVec(rays, 4, 200, 200) #Prepare arrays for integration #Reconstruct requires nans be replaced by 100 #Fortran functions require arrays to be packed in #fortran contiguous mode x = man.padRect(x) y = man.padRect(y) phase = np.zeros(np.shape(x), order='F') phase[np.isnan(x)] = 100. x[np.isnan(x)] = 100. y[np.isnan(y)] = 100. y = np.array(y, order='F') x = np.array(x, order='F') #Reconstruct and remove border phase = reconstruct.reconstruct(x, y, 1e-12, dx, phase) phase[phase == 100] = np.nan x[x == 100] = np.nan y[y == 100] = np.nan return man.stripnans(phase), man.stripnans(x), man.stripnans(y)
def get_corner_throughput_frac(module_width=150 * u.mm): ''' Returns percentage of incident photons from optic that will not interact with gratings in the OGRE grating array and will instead make it to the central CCD. Parameters ---------- module_width : Quantity Total width of OGRE grating module, centered at x=0. Returns ------- throughput_frac : float Fraction of photons that do not interact with the OGRE grating array. ''' # Define rays. rays = ogre.create_rays() # Pass through optic. rays = ogre.ogre_mirror_module(rays, scatter='bg') # Move to center of OGRE grating module. z_cen = np.mean(np.concatenate((ogre.zg_cen_left, ogre.zg_cen_right))) trans.transform(rays, 0, 0, z_cen, 0, 0, 0) surfaces.flat(rays) # Find starting fraction. start_frac = len(rays[1]) # How many get through. ind = np.where(abs(rays[1]) > module_width.to('mm').value / 2)[0] return len(ind) / float(start_frac)
def traceSingleRay(rtrace,zexit,R0,Z0,zeta,rpos=False): """ Trace single ray through WS shell and return ray at a given exit aperture z position """ #Set up ray ray = sources.pointsource(0.,1) ray[6] = -ray[6] ray[1][0] = rtrace tran.transform(ray,0,0,-Z0,0,0,0) #Trace to shell surf.wsPrimary(ray,R0,Z0,zeta) tran.reflect(ray) surf.wsSecondary(ray,R0,Z0,zeta) tran.reflect(ray) if rpos is True: return ray[1][0],ray[3][0] #Go to exit aperture tran.transform(ray,0,0,zexit,0,0,0) surf.flat(ray) return ray
# Go to center of grating. conv_length = L / np.cos(gammy) trans.transform(rays, 0, 0, conv_length, 0, 0, 0, coords=glob_coords) # Add incidence angle. trans.transform(rays, 0, 0, 0, gammy, 0, 0, coords=glob_coords) # Get +y in groove direction. trans.transform(rays, 0, 0, 0, -np.pi / 2, 0, 0, coords=glob_coords) trans.transform(rays, 0, 0, 0, 0, 0, np.pi, coords=glob_coords) # Add yaw. trans.transform(rays, 0, 0, 0, 0, 0, yaw, coords=glob_coords) # Project rays onto grating surface. surfaces.flat(rays) # Move to coordinate system to hub location (required for radgrat). trans.transform(rays, 0, -L, 0, 0, 0, 0, coords=glob_coords) good_ind = np.where((rays[2] < 3300.) & (rays[2] > 3200.))[0] rays = [r[good_ind] for r in deepcopy(rays)] # Reflect rays. trans.reflect(rays) ys = np.array(rays[2]).copy() zero_point = L + 50 ys -= zero_point ys *= -1e6
def get_grating_throughput_frac(mid_width=5 * u.mm, edge_support_width=5 * u.mm, grat_thickness=ogre.grating_thickness, rib_width=2 * u.mm, rib_num=3, module_width=150 * u.mm): ''' Returns percentage of incident photons from optic that will interact with gratings in the OGRE grating array. Parameters ---------- mid_width : Quantity Width between two grating stacks in grating module. edge_support_width : Quantity Width of left/right outside support in grating module. grat_thickness : Quantity Thickness of grating in OGRE grating stack. rib_width : Quantity Width of individual rib on wedged grating substrate. rib_num : Quantity Number of ribs on wedged grating substrate. module_width : Quantity Total width of OGRE grating module, centered at x=0. Returns ------- throughput_frac : float Fraction of photons that interact with gratings in the OGRE grating array. ''' # Create rays. rays = ogre.create_rays(n=1000000) # Pass them through the optic with Beckmann + Gaussian scatter. rays = ogre.ogre_mirror_module(rays, scatter='bg') # Move them to the mean grating center point. z_mean = (np.mean(ogre.zg_cen_left) + np.mean(ogre.zg_cen_right)) / 2 trans.transform(rays, 0, 0, z_mean.to('mm').value, 0, 0, 0) surfaces.flat(rays) # Define starting number. start_num = len(rays[0]) # Find photons that hit in between grating stacks. center_ind = np.where(np.abs(rays[1]) < mid_width.to('mm').value / 2)[0] # Find photons with x-positions g.t. maximal grating stack x-extent. outside_ind = np.where( np.abs(rays[1]) > (module_width / 2 - edge_support_width).to('mm').value)[0] # Find photons that hit ribs. grating_width = module_width / 2 - edge_support_width - mid_width / 2 # Find rib positions. rib_cen_positions = np.linspace( rib_width.to('mm').value / 2, (grating_width - rib_width / 2).to('mm').value, rib_num) * u.mm + mid_width / 2 rib_ind = np.array([], dtype=int) for rc in rib_cen_positions: ri = np.where((np.abs(rays[1]) > (rc - rib_width / 2).to('mm').value) & (np.abs(rays[1]) < (rc + rib_width / 2).to('mm').value))[0] rib_ind = np.concatenate((rib_ind, ri)) # Account grating thickness. thick_ind = np.array([], dtype=int) for r in ogre.rg_cen.to('mm').value: # Find which rays will hit grating thickness. ind = np.where((rays[2] < r) & (rays[2] > r - grat_thickness.to('mm').value))[0] # Add to array list. thick_ind = np.concatenate((thick_ind, ind)) # Combine all rays that will be occulted. occ_ind = np.concatenate((center_ind, outside_ind, rib_ind, thick_ind)) # Remove occulted rays. rays = [np.delete(r, occ_ind) for r in rays] # What is ending number? end_num = len(rays[1]) # print('Starting #: ' + str(start_num)) # print('Ending #: ' + str(end_num)) # Fraction of photons making it through? frac = float(end_num) / start_num return frac
def ogre_simulation_one_channel(waves, orders, j=j, oa_misalign=oa_misalign, g_misalign=g_misalign, s_misalign=s_misalign, m_misalign=m_misalign, cen_width=5 * u.mm, rib_width=2 * u.mm, rib_num=3, edge_width=5. * u.mm, mod_width=150 * u.mm): ''' Simulate rays going through the OGRE spectrometer. This function will just simulate rays passing through the spectrometer with misalignments, but will not take throughput into consideration. A seperate function is needed to correct this output for throughput. Parameters ---------- waves : Quantity Array of wavelengths to simulate. Each wavelength in the array will be associated with one ray (100 wavelengths = 100 rays). orders : numpy.ndarray Diffraction orders associated with wavelengths passed. j : Quantity FWHM of the jitter throughout the observation. oa_misalign : g_misalign : s_misalign : m_misalign : Returns ------- rays : [opd, x, y, z, l, m, n, nx, ny, nz] Rays on the focal plane of the OGRE spectrometer. waves : Quantity Wavelengths corresponding to each simulated ray. orders : array Diffracted order of each simulated ray. ''' # Define initial rays. n = len(waves) rays = ogre.create_rays(10 * n) # Translate rays to center of optic assembly. rays[3] = np.ones(len(rays[1])) * oa_z_cen surfaces.flat(rays) # Misalign rays relative to optic assembly. trans.transform(rays, 0, 0, 0, oa_pitch, oa_yaw, 0) # Move rays to R_INT position. This is just a global offset. rays[3] = np.ones(len(rays[1])) * 3500. # Add jitter. xj = np.random.normal(0, j.to('rad').value, size=len(rays[4])) yj = np.random.normal(0, j.to('rad').value, size=len(rays[4])) rays[4] += xj rays[5] += yj rays[6] = np.sqrt(1 - rays[4]**2 - rays[5]**2) # Pass rays through optic. rays = ogre.ogre_mirror_module(rays, scatter='bg') # Randomly select indicies to keep based on count rate analysis. n_rays = len(rays[0]) rand_inds = np.random.choice(np.arange(n_rays, dtype=int), len(waves), replace=False) # Keep only those selected indicies. rays = [r[rand_inds] for r in rays] # Add random period error effect. rand_fact = np.random.normal(1, 0.000094, size=len(waves)) # Pass through grating moudle. rays, diff_ind = ogre.ogre_grating_module(rays, waves=waves * orders / rand_fact, g_misalign=g_misalign, s_misalign=s_misalign, m_misalign=m_misalign, return_diff_ind=True) # Keep only wavelength/orders that make it through grating module. waves = waves[diff_ind] orders = orders[diff_ind] # Occult rays that will hit ribs. rays, waves, orders = ogre.grating_rib_occultation(rays, waves=waves, orders=orders) # Transform coordinate system to be at center of rotation. trans.transform(rays, 0, 0, oa_z_cen, 0, 0, 0) # Propagate rays to misaligned plane, simulating center plane of optic assembly. surfaces.flat(rays) # Transform the rays back by the pitch angle to the optical axis coordinate system. trans.itransform(rays, 0, 0, 0, oa_pitch, oa_yaw, 0) # Put the rays back at the intersection plane of the ideal optic. rays[3] = np.ones(len(rays[3])) * oa_z_cen # Misalign in z-hat. trans.transform(rays, 0, 0, oa_z, 0, 0, 0) # Propagate to the focal plane. rays = ogre.ogre_focal_plane(rays, det_misalignments=[0, 0, 0, 0, 0, -det_z]) return rays, waves, orders
def traceXRS(rsec,smax,pmin,fun,nodegap,L=200.,Nshell=1e3,energy=1000.,\ rough=1.,offaxis=0.,rrays=False): """ Using output from defineRx, trace the nodes in a Lynx design. Provide number of sections, and the zeta as a function of radius interpolation function. Calculate node radii iteratively, providing appropriate gaps between nodes in order to limit off-axis vignetting. """ gap = L*3e-3+0.4 #.4 mm glass thickness plus vignetting #Trace through all shells, computing reflectivity and geometric area #for each shell previousrho = [] for i in range(len(smax)): #Variable to store radial position of bottom of previous #secondary mirror previousrho.append(0.) for r in rsec[i]: #Determine zeta for this shell...must be at least .01 psi = np.polyval(fun[i],r) psi = np.max([.01,psi]) #Set up aperture z = np.sqrt(1e4**2-r**2) a0 = wsPrimrad(pmin[i],r,z,psi=psi) a1 = wsPrimrad(pmin[i]+L,r,z,psi=psi) rays = sources.annulus(a0,a1,Nshell) tran.transform(rays,0,0,-z,0,0,0) #Set up weights (cm^2) weights = np.repeat((a1**2-a0**2) * np.pi / 100. / Nshell,Nshell) #Trace to primary surf.wsPrimary(rays,r,z,psi) rays[4] = rays[4]+np.sin(offaxis) rays[6] = -np.sqrt(1.-rays[4]**2) tran.reflect(rays) ang = anal.grazeAngle(rays) weights = weights*\ pol.computeReflectivities(ang,energy,rough,1,cons)[0] #Trace to secondary surf.wsSecondary(rays,r,z,psi) tran.reflect(rays) ang = anal.grazeAngle(rays) weights = weights*\ pol.computeReflectivities(ang,energy,rough,1,cons)[0] #Handle vignetting ind = np.logical_and(rays[3]>smax[i]-L,rays[3]<smax[i]) if sum(ind) == 0: pdb.set_trace() rays = tran.vignette(rays,ind=ind) weights = weights[ind] #Go to exit aperture and confirm rays don't #hit back of previous shell tran.transform(rays,0,0,smax[i]-L,0,0,0) surf.flat(rays) rho = np.sqrt(rays[1]**2+rays[2]**2) ind = rho > previousrho[-1] #if np.sum(ind)==0: #pdb.set_trace() if np.sum(~ind) > 100: print '%i rays hit back of shell' % np.sum(~ind) print r,psi #pdb.set_trace() rays = tran.vignette(rays,ind=ind) weights = weights[ind] previousrho.append(wsSecrad(smax[i]-L,r,z,psi=psi)+.4) #Go to focus if rrays is False: tran.transform(rays,0,0,-smax[i]+L,0,0,0) surf.flat(rays) #Accumulate master rays try: mrays = [np.concatenate([mrays[ti],rays[ti]]) for ti in range(10)] mweights = np.concatenate([mweights,weights]) except: mrays = rays mweights = weights if rrays is True: return mrays,mweights,previousrho #Go to focus try: surf.focusI(rays,weights=weights) except: pdb.set_trace() return anal.hpd(mrays,weights=mweights)/1e4*180/np.pi*60**2,\ anal.rmsCentroid(mrays,weights=mweights)/1e4*180/np.pi*60**2,\ np.sum(mweights)
def tracePerfectXRS(L=200.,nodegap=50.,Nshell=1e3,energy=1000.,\ rough=1.,offaxis=0.,rrays=False,rnodes=False): """ Trace rays through a perfect Lynx design where all the shells are on the spherical principle surface, with zeta equal to unity. """ #Construct node positions rad = np.array([200.]) z = np.sqrt(1e4**2-rad[-1]**2) #Gap is projected radial shell plus thickness plus vignetting gap rout = wsPrimrad(z+L+nodegap/2.,rad[-1],z) gap = L*3e-3 + 0.4 #Establish radius vector rad = np.array([]) for sec in range(3): rout = 0. #First node position rad = np.append(rad,200.+(1300./3)*sec) #rguess = np.linspace( while rout+gap < 200.+(1300./3)*(sec+1)-10.: #Need to adjust this condition #Compute parameters for current node z = np.sqrt(1e4**2-rad[-1]**2) rout = wsPrimrad(z+L+nodegap/2.,rad[-1],z) rad = np.append(rad,rout+gap) rad = rad[:-1] if rnodes is True: return rad #Use radial nodes and trace Lynx, keeping track of effective area previousrho = 0. for r in rad: #Set up aperture z = np.sqrt(1e4**2-r**2) a0 = wsPrimrad(z+nodegap/2.,r,z) a1 = wsPrimrad(z+nodegap/2.+L,r,z) rays = sources.annulus(a0,a1,Nshell) tran.transform(rays,0,0,-z,0,0,0) #Set up weights (cm^2) weights = np.repeat((a1**2-a0**2) * np.pi / 100. / Nshell,Nshell) #Trace to primary surf.wsPrimary(rays,r,z,1.) rays[4] = rays[4]+np.sin(offaxis) rays[6] = -np.sqrt(1.-rays[4]**2) tran.reflect(rays) ang = anal.grazeAngle(rays) weights = weights*\ pol.computeReflectivities(ang,energy,rough,1,cons)[0] #Trace to secondary surf.wsSecondary(rays,r,z,1.) tran.reflect(rays) ang = anal.grazeAngle(rays) weights = weights*\ pol.computeReflectivities(ang,energy,rough,1,cons)[0] #Handle vignetting ind = np.logical_and(rays[3]>z-nodegap/2.-L,rays[3]<z-nodegap/2.) if sum(ind) == 0: pdb.set_trace() rays = tran.vignette(rays,ind=ind) weights = weights[ind] #Go to exit aperture and confirm rays don't - EDIT HERE! #hit back of previous shell tran.transform(rays,0,0,z-nodegap/2-L,0,0,0) surf.flat(rays) rho = np.sqrt(rays[1]**2+rays[2]**2) ind = rho > previousrho #if np.sum(ind)==0: #pdb.set_trace() if np.sum(~ind) > 100: print '%i rays hit back of shell' % np.sum(~ind) print r #pdb.set_trace() rays = tran.vignette(rays,ind=ind) weights = weights[ind] previousrho = wsSecrad(z-nodegap/2-L,r,z)+.4 #Go to focus try: surf.focusI(rays,weights=weights) except: pdb.set_trace() #Accumulate master rays try: mrays = [np.concatenate([mrays[ti],rays[ti]]) for ti in range(10)] mweights = np.concatenate([mweights,weights]) except: mrays = rays mweights = weights if rrays is True: return mrays,mweights return anal.hpd(mrays,weights=mweights)/1e4*180/np.pi*60**2,\ anal.rmsCentroid(mrays,weights=mweights)/1e4*180/np.pi*60**2,\ np.sum(mweights)