def test_r0_weights(): """Check that r0_weights functions as expected.""" r0_500 = 0.2 # Check that reassembled net r0_500 matches input atm = galsim.Atmosphere(screen_size=10.0, altitude=[0, 1, 2, 3], r0_500=r0_500) r0s = [screen.r0_500 for screen in atm] np.testing.assert_almost_equal( np.sum([r0**(-5. / 3) for r0 in r0s])**(-3. / 5), r0_500) np.testing.assert_almost_equal(atm.r0_500_effective, r0_500) # Check that old manual calculation matches automatic calculation inside Atmosphere() weights = [1, 2, 3, 4] normalized_weights = np.array(weights, dtype=float) / np.sum(weights) r0s_ref = [r0_500 * w**(-3. / 5) for w in normalized_weights] atm = galsim.Atmosphere(screen_size=10.0, altitude=[0, 1, 2, 3], r0_500=r0_500, r0_weights=weights) r0s_test = [screen.r0_500 for screen in atm] np.testing.assert_almost_equal(r0s_test, r0s_ref) np.testing.assert_almost_equal( np.sum([r0**(-5. / 3) for r0 in r0s_test])**(-3. / 5), r0_500) np.testing.assert_almost_equal(atm.r0_500_effective, r0_500)
def test_phase_psf_reset(): """Test that phase screen reset() method correctly resets the screen to t=0.""" rng = galsim.BaseDeviate(1234) # Test frozen AtmosphericScreen first atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng) aper = galsim.Aperture(diam=1.0, lam=500.0) wf1 = atm.wavefront(aper) atm.advance() wf2 = atm.wavefront(aper) # Verify that atmosphere did advance assert not np.all(wf1 == wf2) # Now verify that reset brings back original atmosphere atm.reset() wf3 = atm.wavefront(aper) np.testing.assert_array_equal(wf1, wf3, "Phase screen didn't reset") # Now check with boiling, but no wind. atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, alpha=0.997, rng=rng) wf1 = atm.wavefront(aper) atm.advance() wf2 = atm.wavefront(aper) # Verify that atmosphere did advance assert not np.all(wf1 == wf2) # Now verify that reset brings back original atmosphere atm.reset() wf3 = atm.wavefront(aper) np.testing.assert_array_equal(wf1, wf3, "Phase screen didn't reset")
def test_instantiation_check(): """Check that after instantiating, drawing with the other method will emit a warning. """ atm1 = galsim.Atmosphere(screen_size=10.0, altitude=10, r0_500=0.2) psf1 = atm1.makePSF(lam=500.0, diam=1.0) psf1.drawImage() with assert_warns(galsim.GalSimWarning): psf1.drawImage(method='phot', n_photons=10) atm2 = galsim.Atmosphere(screen_size=10.0, altitude=10, r0_500=0.2) psf2 = atm2.makePSF(lam=500.0, diam=1.0) # exptime = 0, so reasonable to draw w/ FFT psf2.drawImage(method='phot', n_photons=10) with assert_warns(galsim.GalSimWarning): psf2.drawImage()
def test_phase_psf_batch(): """Test that PSFs generated serially match those generated in batch.""" import time NPSFs = 10 exptime = 0.06 rng = galsim.BaseDeviate(1234) atm = galsim.Atmosphere(screen_size=10.0, altitude=10.0, alpha=0.997, rng=rng) theta = [(i*galsim.arcsec, i*galsim.arcsec) for i in range(NPSFs)] kwargs = dict(lam=1000.0, exptime=exptime, diam=1.0) t1 = time.time() psfs = atm.makePSF(theta=theta, **kwargs) print('time for {0} PSFs in batch: {1:.2f} s'.format(NPSFs, time.time() - t1)) t2 = time.time() more_psfs = [] for th in theta: atm.reset() more_psfs.append(atm.makePSF(theta=th, **kwargs)) print('time for {0} PSFs in serial: {1:.2f} s'.format(NPSFs, time.time() - t2)) for psf1, psf2 in zip(psfs, more_psfs): np.testing.assert_array_equal( psf1.img, psf2.img, "Individually generated AtmosphericPSF differs from AtmosphericPSF generated in batch")
def test_phase_psf_batch(): """Test that PSFs generated and drawn serially match those generated and drawn in batch.""" import time NPSFs = 10 exptime = 0.3 rng = galsim.BaseDeviate(1234) atm = galsim.Atmosphere(screen_size=10.0, altitude=10.0, alpha=0.997, time_step=0.01, rng=rng) theta = [(i*galsim.arcsec, i*galsim.arcsec) for i in range(NPSFs)] kwargs = dict(lam=1000.0, exptime=exptime, diam=1.0) t1 = time.time() psfs = [atm.makePSF(theta=th, **kwargs) for th in theta] imgs = [psf.drawImage() for psf in psfs] print('time for {0} PSFs in batch: {1:.2f} s'.format(NPSFs, time.time() - t1)) t2 = time.time() more_imgs = [] for th in theta: psf = atm.makePSF(theta=th, **kwargs) more_imgs.append(psf.drawImage()) print('time for {0} PSFs in serial: {1:.2f} s'.format(NPSFs, time.time() - t2)) for img1, img2 in zip(imgs, more_imgs): np.testing.assert_array_equal( img1, img2, "Individually generated AtmosphericPSF differs from AtmosphericPSF generated in batch")
def test_scale_unit(): """Test that `scale_unit` keyword correctly sets the units for PhaseScreenPSF.""" aper = galsim.Aperture(diam=1.0) rng = galsim.BaseDeviate(1234) # Test frozen AtmosphericScreen first atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng) psf = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec) im1 = psf.drawImage(nx=32, ny=32, scale=0.1, method='no_pixel') psf2 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit='arcmin') im2 = psf2.drawImage(nx=32, ny=32, scale=0.1/60.0, method='no_pixel') np.testing.assert_almost_equal( im1.array, im2.array, 8, 'PhaseScreenPSF inconsistent use of scale_unit') opt_psf1 = galsim.OpticalPSF(lam=500.0, diam=1.0, scale_unit=galsim.arcsec) opt_psf2 = galsim.OpticalPSF(lam=500.0, diam=1.0, scale_unit='arcsec') assert opt_psf1 == opt_psf2, "scale unit did not parse as string" assert_raises(ValueError, galsim.OpticalPSF, lam=500.0, diam=1.0, scale_unit='invalid') assert_raises(ValueError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, scale_unit='invalid') # Check a few other construction errors now too. assert_raises(ValueError, galsim.PhaseScreenPSF, atm, 500.0, scale_unit='arcmin') assert_raises(TypeError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, theta=34.*galsim.degrees) assert_raises(TypeError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, theta=(34, 5)) assert_raises(ValueError, galsim.PhaseScreenPSF, atm, 500.0, aper=aper, exptime=-1)
def test_scale_unit(): """Test that `scale_unit` keyword correctly sets the units for PhaseScreenPSF.""" aper = galsim.Aperture(diam=1.0) rng = galsim.BaseDeviate(1234) # Test frozen AtmosphericScreen first atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng) psf = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec) im1 = psf.drawImage(nx=32, ny=32, scale=0.1, method='no_pixel') psf2 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcmin) im2 = psf2.drawImage(nx=32, ny=32, scale=0.1 / 60.0, method='no_pixel') np.testing.assert_almost_equal( im1.array, im2.array, 8, 'PhaseScreenPSF inconsistent use of scale_unit') opt_psf1 = galsim.OpticalPSF(lam=500.0, diam=1.0, scale_unit=galsim.arcsec) opt_psf2 = galsim.OpticalPSF(lam=500.0, diam=1.0, scale_unit='arcsec') assert opt_psf1 == opt_psf2, "scale unit did not parse as string"
def test_stepk_maxk(): """Test options to specify (or not) stepk and maxk. """ aper = galsim.Aperture(diam=1.0) rng = galsim.BaseDeviate(123456) # Test frozen AtmosphericScreen first atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng) psf = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec) stepk = psf.stepK() maxk = psf.maxK() psf2 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec, _force_stepk=stepk/1.5, _force_maxk=maxk*2.0) np.testing.assert_almost_equal( psf2.stepK(), stepk/1.5, decimal=7, err_msg="PhaseScreenPSF did not adopt forced value for stepK") np.testing.assert_almost_equal( psf2.maxK(), maxk*2.0, decimal=7, err_msg="PhaseScreenPSF did not adopt forced value for maxK") do_pickle(psf) do_pickle(psf2) # Try out non-geometric-shooting psf3 = atm.makePSF(lam=500.0, aper=aper, geometric_shooting=False) img = galsim.Image(32, 32, scale=0.2) do_shoot(psf3, img, "PhaseScreenPSF") # Also make sure a few other methods at least run psf3.centroid() psf3.maxSB()
def _build_atm(self): """Build an atmosphere following Jee and Tyson, galsim and my gut.""" max_wind_speed = 20 # m/s max_screen_size = (self.exposure_time * max_wind_speed) # m altitude = [0, 2.58, 5.16, 7.73, 12.89, 15.46] # km max_screen_size = np.maximum( max_screen_size * 1.5, # factor of 1.5 for periodic FFTs (max_screen_size + 2 * np.array(altitude) * 1e3 * self.field_of_view / 180 * np.pi ) # 2 fields of view ) # draw a new effective r0 w/ 1% deviation around th fiducial value effr0 = self.rng.lognormal(np.log(self.effective_r0_500), 0.01) # draw weights with similar variations fid_weights = np.array([0.652, 0.172, 0.055, 0.025, 0.074, 0.022]) weights = np.exp( self.rng.normal(size=len(fid_weights)) * 0.01 + np.log(fid_weights)) weights /= np.sum(weights) # get effective screen r0's and the pixel scales screen_r0_500 = effr0 * np.power(weights, -3.0 / 5.0) screen_scales = np.clip(0.25 * screen_r0_500, 0.1, np.inf) # compute a good FFT size, clipping at max of 8192 nominal_npix = np.clip( np.ceil(max_screen_size / screen_scales).astype(int), 0, 8192) npix = get_good_fft_sizes(nominal_npix) # for screens that need too many pixels, # we can either go back and make the overall size smaller # or make the pixels bigger # here I am going to make the size smaller since we want the proper # power but can tolerate some wrapping of the FFTs on average screen_sizes = screen_scales * npix LOGGER.debug('screen # of pixels: %s', npix) LOGGER.debug('screen pixel sizes: %s', screen_scales) LOGGER.debug('pixel size / r0_500: %s', screen_scales / screen_r0_500) LOGGER.debug('fraction of ideal screen size: %s', screen_sizes / max_screen_size) speed = self.rng.uniform(0, max_wind_speed, size=6) # m/s direction = [ self.rng.uniform(0, 360) * galsim.degrees for i in range(6) ] self._screens = galsim.Atmosphere(r0_500=screen_r0_500, screen_size=screen_sizes, altitude=altitude, L0=25.0, speed=speed, direction=direction, screen_scale=screen_scales, rng=self.base_deviate)
def test_phase_psf(): atm = galsim.Atmosphere(screen_size=10.0, altitude=0, r0_500=0.15, suppress_warning=True) psf = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0) check_dep(galsim.PhaseScreenPSF.__getattribute__, psf, "img") check_dep(galsim.PhaseScreenPSF.__getattribute__, psf, "finalized")
def test_speedup(): """Make sure that photon-shooting a PhaseScreenPSF with geometric approximation yields significant speedup. """ import time atm = galsim.Atmosphere(screen_size=10.0, altitude=[0,1,2,3], r0_500=0.2) # Should be ~seconds if _prepareDraw() gets executed, ~0.01s otherwise. psf = atm.makePSF(lam=500.0, diam=1.0, exptime=15.0, time_step=0.025) t0 = time.time() psf.drawImage(method='phot', n_photons=1e3) t1 = time.time() assert (t1-t0) < 0.1, "Photon-shooting took too long ({0} s).".format(t1-t0)
def test_sk_phase_psf(): """Test that analytic second kick profile matches what can be obtained from PhaseScreenPSF with an appropriate truncated power spectrum. """ # import matplotlib.pyplot as plt lam = 500.0 r0 = 0.2 diam = 4.0 obscuration = 0.5 rng = galsim.UniformDeviate(1234567890) weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] speed = [rng() * 20 for _ in range(6)] direction = [rng() * 360 * galsim.degrees for _ in range(6)] aper = galsim.Aperture(4.0, obscuration=obscuration, pad_factor=0.5) kcrits = [1, 3, 10] if __name__ == '__main__' else [1] for kcrit in kcrits: # Technically, we should probably use a smaller screen_scale here, but that runs really # slowly. The below seems to work well enough for the tested kcrits. atm = galsim.Atmosphere(r0_500=r0, r0_weights=weights, rng=rng, speed=speed, direction=direction, screen_size=102.4, screen_scale=0.05, suppress_warning=True) atm.instantiate(kmin=kcrit) print('instantiated atm') psf = galsim.PhaseScreenPSF(atm, lam=lam, exptime=10, aper=aper, time_step=0.1) print('made psf') phaseImg = psf.drawImage(nx=64, ny=64, scale=0.02) sk = galsim.SecondKick(lam=lam, r0=r0, diam=diam, obscuration=obscuration, kcrit=kcrit) print('made sk') skImg = sk.drawImage(nx=64, ny=64, scale=0.02) print('made skimg') phaseMom = galsim.hsm.FindAdaptiveMom(phaseImg) skMom = galsim.hsm.FindAdaptiveMom(skImg) print('moments: ', phaseMom.moments_sigma, skMom.moments_sigma) np.testing.assert_allclose(phaseMom.moments_sigma, skMom.moments_sigma, rtol=2e-2)
def test_gc(): """Make sure that pending psfs don't leak memory. """ import gc atm = galsim.Atmosphere(screen_size=10.0, altitude=0, r0_500=0.15, suppress_warning=True) # First check that no PhaseScreenPSFs are known to the garbage collector assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()]) # Make a PhaseScreenPSF and check that it's known to the garbage collector psf = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0) assert any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()]) # If we delete it, it disappears everywhere del psf gc.collect() assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()]) # If we draw one using photon-shooting, it still exists in _pending psf = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0) psf.drawImage(nx=10, ny=10, scale=0.2, method='phot', n_photons=100) assert psf in [p[1]() for p in atm._pending] # If we draw even one of many using fft, _pending gets completely emptied psf2 = atm.makePSF(exptime=0.02, time_step=0.01, diam=1.1, lam=1000.0) psf.drawImage(nx=10, ny=10, scale=0.2) assert atm._pending == [] # And if then deleted, they again don't exist anywhere del psf, psf2 gc.collect() assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()]) # A corner case revealed in coverage tests: # Make sure that everything still works if some, but not all static pending PSFs are deleted. screen = galsim.OpticalScreen(diam=1.1) phaseScreenList = galsim.PhaseScreenList(screen) psf1 = phaseScreenList.makePSF(lam=1000.0, diam=1.1) psf2 = phaseScreenList.makePSF(lam=1000.0, diam=1.1) psf3 = phaseScreenList.makePSF(lam=1000.0, diam=1.1) del psf2 psf1.drawImage(nx=10, ny=10, scale=0.2) del psf1, psf3 assert phaseScreenList._pending == [] gc.collect() assert not any([isinstance(it, galsim.phase_psf.PhaseScreenPSF) for it in gc.get_objects()])
def test_stepk_maxk(): """Test options to specify (or not) stepk and maxk. """ aper = galsim.Aperture(diam=1.0) rng = galsim.BaseDeviate(1234) # Test frozen AtmosphericScreen first atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng) psf = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec) stepk = psf.stepK() maxk = psf.maxK() psf2 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec, _force_stepk=stepk/1.5, _force_maxk=maxk*2.0) np.testing.assert_almost_equal( psf2.stepK(), stepk/1.5, decimal=7, err_msg="PhaseScreenPSF did not adopt forced value for stepK") np.testing.assert_almost_equal( psf2.maxK(), maxk*2.0, decimal=7, err_msg="PhaseScreenPSF did not adopt forced value for maxK") do_pickle(psf) do_pickle(psf2)
def make_movie(args): """Actually make the movie of the atmosphere given command line arguments stored in `args`. """ # Initiate some GalSim random number generators. rng = galsim.BaseDeviate(args.seed) u = galsim.UniformDeviate(rng) # The GalSim atmospheric simulation code describes turbulence in the 3D atmosphere as a series # of 2D turbulent screens. The galsim.Atmosphere() helper function is useful for constructing # this screen list. # First, we estimate a weight for each screen, so that the turbulence is dominated by the lower # layers consistent with direct measurements. The specific values we use are from SCIDAR # measurements on Cerro Pachon as part of the 1998 Gemini site selection process # (Ellerbroek 2002, JOSA Vol 19 No 9). Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46] # km Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts, Ellerbroek_weights, interpolant='linear') # Use given number of uniformly spaced altitudes alts = np.max(Ellerbroek_alts) * np.arange( args.nlayers) / (args.nlayers - 1) weights = Ellerbroek_interp(alts) # interpolate the weights weights /= sum(weights) # and renormalize # Each layer can have its own turbulence strength (roughly inversely proportional to the Fried # parameter r0), wind speed, wind direction, altitude, and even size and scale (though note that # the size of each screen is actually made infinite by "wrapping" the edges of the screen.) The # galsim.Atmosphere helper function is useful for constructing this list, and requires lists of # parameters for the different layers. max_speed = 20 # Pick (an arbitrary) maximum wind speed in m/s. spd = [] # Wind speed in m/s dirn = [] # Wind direction in radians r0_500 = [] # Fried parameter in m at a wavelength of 500 nm. for i in range(args.nlayers): spd.append(u() * max_speed) # Use a random speed between 0 and max_speed dirn.append( u() * 360 * galsim.degrees) # And an isotropically distributed wind direction. # The turbulence strength of each layer is specified by through its Fried parameter r0_500, # which can be thought of as the diameter of a telescope for which atmospheric turbulence # and unaberrated diffraction contribute equally to image resolution (at a wavelength of # 500nm). The weights above are for the refractive index structure function (similar to a # variance or covariance), however, so we need to use an appropriate scaling relation to # distribute the input "net" Fried parameter into a Fried parameter for each layer. For # Kolmogorov turbulence, this is r0_500 ~ (structure function)**(-3/5): r0_500.append(args.r0_500 * weights[i]**(-3. / 5)) print( "Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, " "and r0_500 {:5.3f} m.".format(alts[i], spd[i] * dirn[i].cos(), spd[i] * dirn[i].sin(), r0_500[i])) # Additionally, we set the screen temporal evolution `time_step`, and the screen size and scale. atm = galsim.Atmosphere(r0_500=r0_500, speed=spd, direction=dirn, altitude=alts, rng=rng, time_step=args.time_step, screen_size=args.screen_size, screen_scale=args.screen_scale) # `atm` is now an instance of a galsim.PhaseScreenList object. # Place to store the cumulative PSF image if args.accumulate is set. psf_img_sum = galsim.ImageD(args.psf_nx, args.psf_nx, scale=args.psf_scale) # Field angle (angle on the sky wrt the telescope boresight) at which to compute the PSF. theta = (args.x * galsim.arcmin, args.y * galsim.arcmin) # Construct an Aperture object for computing the PSF. The Aperture object describes the # illumination pattern of the telescope pupil, and chooses good sampling size and resolution # for representing this pattern as an array. aper = galsim.Aperture(diam=args.diam, lam=args.lam, obscuration=args.obscuration, nstruts=args.nstruts, strut_thick=args.strut_thick, strut_angle=args.strut_angle * galsim.degrees, screen_list=atm, pad_factor=args.pad_factor, oversampling=args.oversampling) # Code to setup the Matplotlib animation. metadata = dict(title='Wavefront Movie', artist='Matplotlib') writer = anim.FFMpegWriter(fps=15, bitrate=5000, metadata=metadata) # For the animation code, we essentially draw a single figure first, and then use various # `set_XYZ` methods to update each successive frame. fig = plt.figure(facecolor='k', figsize=(11, 6)) # Axis for the PSF image on the left. psf_ax = fig.add_axes([0.08, 0.15, 0.35, 0.7]) psf_ax.set_xlabel("Arcsec") psf_ax.set_ylabel("Arcsec") psf_im = psf_ax.imshow(np.ones((128, 128), dtype=np.float64), animated=True, vmin=0.0, vmax=args.psf_vmax, cmap='hot', extent=np.r_[-1, 1, -1, 1] * 0.5 * args.psf_nx * args.psf_scale) # Axis for the wavefront image on the right. wf_ax = fig.add_axes([0.51, 0.15, 0.35, 0.7]) wf_ax.set_xlabel("Meters") wf_ax.set_ylabel("Meters") wf_im = wf_ax.imshow(np.ones((128, 128), dtype=np.float64), animated=True, vmin=-args.wf_vmax, vmax=args.wf_vmax, cmap='YlGnBu', extent=np.r_[-1, 1, -1, 1] * 0.5 * aper.pupil_plane_size) cbar_ax = fig.add_axes([0.88, 0.175, 0.03, 0.65]) cbar_ax.set_ylabel("Radians") plt.colorbar(wf_im, cax=cbar_ax) # Overlay an alpha-mask on the wavefront image showing which parts are actually illuminated. ilum = np.ma.masked_greater(aper.illuminated, 0.5) wf_ax.imshow(ilum, alpha=0.4, extent=np.r_[-1, 1, -1, 1] * 0.5 * aper.pupil_plane_size) # Color items white to show up on black background for ax in [psf_ax, wf_ax, cbar_ax]: for _, spine in ax.spines.items(): spine.set_color('w') ax.title.set_color('w') ax.xaxis.label.set_color('w') ax.yaxis.label.set_color('w') ax.tick_params(axis='both', colors='w') etext = psf_ax.text(0.05, 0.92, '', transform=psf_ax.transAxes) etext.set_color('w') nstep = int(args.exptime / args.time_step) # Use astropy ProgressBar to keep track of progress and show an estimate for time to completion. with ProgressBar(nstep) as bar: with writer.saving(fig, args.outfile, 100): for i in range(nstep): # The wavefront() method with `compact=False` returns a 2D array over the pupil # plane described by `aper`. Here, we just get the wavefront for visualization; # this step is normally handled automatically by the PhasePSF code behind the # scenes. wf = atm.wavefront( aper, theta, compact=False) * 2 * np.pi / args.lam # radians # To make an actual PSF GSObject, we use the makePSF() method, including arguments # for the wavelength `lam`, the field angle `theta`, the aperture `aper`, and the # exposure time `exptime`. Here, since we're making a movie, we set the exptime # equal to just a single timestep, though normally we'd want to set this to the # full exposure time. psf = atm.makePSF(lam=args.lam, theta=theta, aper=aper, exptime=args.time_step) # `psf` is now just like an any other GSObject, ready to be convolved, drawn, or # transformed. Here, we just draw it into an image to add to our movie. psf_img0 = psf.drawImage(nx=args.psf_nx, ny=args.psf_nx, scale=args.psf_scale) if args.accumulate: psf_img_sum += psf_img0 psf_img = psf_img_sum / (i + 1) else: psf_img = psf_img0 # Calculate simple estimate of ellipticity e = ellip(simple_moments(psf_img)) # Matplotlib code updating plot elements wf_im.set_array(wf) wf_ax.set_title("t={:5.2f} s".format(i * args.time_step)) psf_im.set_array(psf_img.array) etext.set_text( "$e_1$={:6.3f}, $e_2$={:6.3f}, $r^2$={:6.3f}".format( e['e1'], e['e2'], e['rsqr'])) with warnings.catch_warnings(): warnings.simplefilter("ignore") writer.grab_frame(facecolor=fig.get_facecolor()) bar.update()
def simulate_speckle_psf(args): ''' Returns an atmospheric simulation (Van Karman) to replicate Zorro data. Inputs ====== - color: wavelength of the filter desired - rnd_seed: random seed for the number generators - scale: screen scale for the turbulence layer. TBD what the default should be for robust results - total_time: total exposure time (default: 60s) - params_save_path: path to save the parameters, except if None - exp_time: time for each exposure. Default is .06s Returns ======= an array of simulated short exposure (60ms) PSFs ''' # pixel scale depends on color! if args.color == 562: pixel_scale = .00992 else: pixel_scale = .01095 # GS quantities diameter = 8.1 obscuration = 1.024 / 8.1 nx, ny = 256, 256 # draw random values of r0, r0_weights, altitude, speed, and direction atm_args = draw_atmosphere_params(args.rnd_seed, args.save_params_path) rng = galsim.BaseDeviate(args.rnd_seed) # fix screen size by the highest speed of the layers and the telescope diameter # except if speed > 40, which leads to memory errors screen_size = min(40, max(atm_args['speed'])) * args.total_time + diameter # number of exposures to fit in the total time N = int(args.total_time / args.exp_time) dead_time = 4. / 1000 # make the atmosphere object: atm = galsim.Atmosphere(screen_size=screen_size, rng=rng, **atm_args) if args.scale != 'default': for layer in atm: layer.screen_scale = args.scale * layer.screen_scale # for each exposure in the series, draw a PSF of exposure length exp_time psf_series = np.zeros((N, nx, ny)) for n in range(N): psf = atm.makePSF(lam=args.color, t0=n * (args.exp_time + dead_time), exptime=args.exp_time, diam=diameter, obscuration=obscuration) psf_series[n] = psf.drawImage(nx=nx, ny=ny, scale=pixel_scale).array if args.save_psfs_path is not None: hdu = fits.PrimaryHDU(psf_series) hdu.writeto(args.save_psfs_path) return psf_series
def test_phase_gradient_shoot(): """Test that photon-shooting PSFs match Fourier optics PSFs when using the same phase screens, and also match the expected size from an analytic VonKarman-convolved-with-Airy PSF. """ # Make the atmosphere seed = 12345 r0_500 = 0.15 # m L0 = 20.0 # m nlayers = 6 screen_size = 102.4 # m # Ideally, we'd use as small a screen scale as possible here. The runtime for generating # phase screens scales like `screen_scale`^-2 though, which is pretty steep, so we use a larger- # than-desireable scale for the __name__ != '__main__' branch. This is known to lead to a bias # in PSF size, which we attempt to account for below when actually comparing FFT PSF moments to # photon-shooting PSF moments. Note that we don't need to apply such a correction when # comparing the photon-shooting PSF to the analytic VonKarman PSF since these both avoid the # screen_scale problem to begin with. (Even though we do generate screens for the # photon-shooting PSF, because we truncate the power spectrum above kcrit, we don't require as # high of resolution). if __name__ == '__main__': screen_scale = 0.025 # m else: screen_scale = 0.1 # m max_speed = 20 # m/s rng = galsim.BaseDeviate(seed) u = galsim.UniformDeviate(rng) # Use atmospheric weights from 1998 Gemini site selection process as something reasonably # realistic. (Ellerbroek 2002, JOSA Vol 19 No 9). Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46] # km Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] Ellerbroek_interp = galsim.LookupTable( Ellerbroek_alts, Ellerbroek_weights, interpolant='linear') alts = np.max(Ellerbroek_alts)*np.arange(nlayers)/(nlayers-1) weights = Ellerbroek_interp(alts) weights /= sum(weights) spd = [] # Wind speed in m/s dirn = [] # Wind direction in radians r0_500s = [] # Fried parameter in m at a wavelength of 500 nm. for i in range(nlayers): spd.append(u()*max_speed) dirn.append(u()*360*galsim.degrees) r0_500s.append(r0_500*weights[i]**(-3./5)) rng2 = rng.duplicate() atm = galsim.Atmosphere(r0_500=r0_500, L0=L0, speed=spd, direction=dirn, altitude=alts, rng=rng, screen_size=screen_size, screen_scale=screen_scale) # Make a second atmosphere to use for geometric photon-shooting atm2 = galsim.Atmosphere(r0_500=r0_500, L0=L0, speed=spd, direction=dirn, altitude=alts, rng=rng2, screen_size=screen_size, screen_scale=screen_scale) # These should be equal at the moment, before we've actually instantiated any screens by drawing # with them. assert atm == atm2 lam = 500.0 diam = 4.0 pad_factor = 0.5 oversampling = 0.5 aper = galsim.Aperture(diam=diam, lam=lam, screen_list=atm, pad_factor=pad_factor, oversampling=oversampling) xs = np.empty((10,), dtype=float) ys = np.empty((10,), dtype=float) u.generate(xs) u.generate(ys) thetas = [(x*galsim.degrees, y*galsim.degrees) for x, y in zip(xs, ys)] if __name__ == '__main__': exptime = 15.0 time_step = 0.05 centroid_tolerance = 0.06 size_tolerance = 0.06 # absolute size_bias = 0.02 # as a fraction shape_tolerance = 0.01 else: exptime = 1.0 time_step = 0.1 centroid_tolerance = 0.3 size_tolerance = 0.3 size_bias = 0.15 shape_tolerance = 0.04 psfs = [atm.makePSF(lam, diam=diam, theta=th, exptime=exptime, aper=aper) for th in thetas] psfs2 = [atm2.makePSF(lam, diam=diam, theta=th, exptime=exptime, aper=aper, time_step=time_step) for th in thetas] shoot_moments = [] fft_moments = [] vk = galsim.VonKarman(lam=lam, r0=r0_500*(lam/500)**1.2, L0=L0) airy = galsim.Airy(lam=lam, diam=diam) obj = galsim.Convolve(vk, airy) vkImg = obj.drawImage(nx=48, ny=48, scale=0.05) vkMom = galsim.hsm.FindAdaptiveMom(vkImg) for psf, psf2 in zip(psfs, psfs2): im_shoot = psf.drawImage(nx=48, ny=48, scale=0.05, method='phot', n_photons=100000, rng=rng) im_fft = psf2.drawImage(nx=48, ny=48, scale=0.05) # at this point, the atms should be different. assert atm != atm2 shoot_moment = galsim.hsm.FindAdaptiveMom(im_shoot) fft_moment = galsim.hsm.FindAdaptiveMom(im_fft) print() print() print() print(shoot_moment.observed_shape.g1) print(fft_moment.observed_shape.g1) # import matplotlib.pyplot as plt # fig, axes = plt.subplots(ncols=2) # axes[0].imshow(im_shoot.array) # axes[1].imshow(im_fft.array) # plt.show() np.testing.assert_allclose( shoot_moment.moments_centroid.x, fft_moment.moments_centroid.x, rtol=0, atol=centroid_tolerance, err_msg='Phase gradient centroid x not close to fft centroid') np.testing.assert_allclose( shoot_moment.moments_centroid.y, fft_moment.moments_centroid.y, rtol=0, atol=centroid_tolerance, err_msg='Phase gradient centroid y not close to fft centroid') np.testing.assert_allclose( shoot_moment.moments_sigma, fft_moment.moments_sigma*(1+size_bias), rtol=0, atol=size_tolerance, err_msg='Phase gradient sigma not close to fft sigma') np.testing.assert_allclose( shoot_moment.moments_sigma, vkMom.moments_sigma, rtol=0.1, atol=0, err_msg='Phase gradient sigma not close to infinite exposure analytic sigma' ) np.testing.assert_allclose( shoot_moment.observed_shape.g1, fft_moment.observed_shape.g1, rtol=0, atol=shape_tolerance, err_msg='Phase gradient shape g1 not close to fft shape') np.testing.assert_allclose( shoot_moment.observed_shape.g2, fft_moment.observed_shape.g2, rtol=0, atol=shape_tolerance, err_msg='Phase gradient shape g2 not close to fft shape') shoot_moments.append(shoot_moment) fft_moments.append(fft_moment) # I cheated. Here's code to evaluate how small I could potentially set the tolerances above. # I think they're all fine, but this is admittedly a tad bit backwards. best_size_bias = np.mean([s1.moments_sigma/s2.moments_sigma for s1, s2 in zip(shoot_moments, fft_moments)]) print("best_size_bias = ", best_size_bias) print("xcentroid") print(max(np.abs([s1.moments_centroid.x - s2.moments_centroid.x for s1, s2 in zip(shoot_moments, fft_moments)]))) print("ycentroid") print(max(np.abs([s1.moments_centroid.y - s2.moments_centroid.y for s1, s2 in zip(shoot_moments, fft_moments)]))) print("size") print(max(np.abs([s1.moments_sigma - s2.moments_sigma*(1+size_bias) for s1, s2 in zip(shoot_moments, fft_moments)]))) print("bestsize") print(max(np.abs([s1.moments_sigma - s2.moments_sigma*(best_size_bias) for s1, s2 in zip(shoot_moments, fft_moments)]))) print("g1") print(max(np.abs([s1.observed_shape.g1 - s2.observed_shape.g1 for s1, s2 in zip(shoot_moments, fft_moments)]))) print("g2") print(max(np.abs([s1.observed_shape.g2 - s2.observed_shape.g2 for s1, s2 in zip(shoot_moments, fft_moments)]))) # import matplotlib.pyplot as plt # fig, ax = plt.subplots(nrows=1, ncols=1) # ax.scatter( # [s.observed_shape.g1 for s in shoot_moments], # [s.observed_shape.g1 for s in fft_moments] # ) # xlim = ax.get_xlim() # ylim = ax.get_ylim() # lim = (min(xlim[0], ylim[0]), max(xlim[1], ylim[1])) # ax.set_xlim(lim) # ax.set_ylim(lim) # ax.plot([-100, 100], [-100, 100]) # plt.show() # Verify that shoot with rng=None runs psf.shoot(100, rng=None) # Check that second_kick=False and second_kick=GSObject also run, and that we can shoot # photons with these settings. for second_kick in [False, galsim.Gaussian(fwhm=1)]: psf = atm.makePSF(lam=500.0, exptime=10, aper=aper, second_kick=second_kick) assert psf.second_kick == second_kick img = psf.drawImage(nx=64, ny=64, scale=0.1, method='phot', n_photons=100) # Verify that we can phase_gradient_shoot with 0 or 1 photons. psf.shoot(0) psf.shoot(1)
def test_ne(): """Test Apertures, PhaseScreens, PhaseScreenLists, and PhaseScreenPSFs for not-equals.""" pupil_plane_im = galsim.fits.read(os.path.join(imgdir, pp_file)) # Test galsim.Aperture __ne__ objs = [galsim.Aperture(diam=1.0), galsim.Aperture(diam=1.1), galsim.Aperture(diam=1.0, oversampling=1.5), galsim.Aperture(diam=1.0, pad_factor=1.5), galsim.Aperture(diam=1.0, circular_pupil=False), galsim.Aperture(diam=1.0, obscuration=0.3), galsim.Aperture(diam=1.0, nstruts=3), galsim.Aperture(diam=1.0, nstruts=3, strut_thick=0.2), galsim.Aperture(diam=1.0, nstruts=3, strut_angle=15*galsim.degrees), galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im), galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im, pupil_angle=10.0*galsim.degrees)] all_obj_diff(objs) # Test AtmosphericScreen __ne__ rng = galsim.BaseDeviate(1) objs = [galsim.AtmosphericScreen(10.0, rng=rng), galsim.AtmosphericScreen(1.0, rng=rng), galsim.AtmosphericScreen(10.0, rng=rng, vx=1.0), galsim.AtmosphericScreen(10.0, rng=rng, vy=1.0), galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.999, time_step=0.01), galsim.AtmosphericScreen(10.0, rng=rng, altitude=1.0), galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.999, time_step=0.02), galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.998, time_step=0.02), galsim.AtmosphericScreen(10.0, rng=rng, r0_500=0.1), galsim.AtmosphericScreen(10.0, rng=rng, L0=10.0), galsim.AtmosphericScreen(10.0, rng=rng, vx=10.0), ] all_obj_diff(objs) objs.append(galsim.AtmosphericScreen(10.0, rng=rng)) objs[-1].instantiate() # Should still all be __ne__, but first and last will have the same hash this time. assert hash(objs[0]) == hash(objs[-1]) all_obj_diff(objs, check_hash=False) # Test OpticalScreen __ne__ objs = [galsim.OpticalScreen(diam=1.0), galsim.OpticalScreen(diam=1.0, tip=1.0), galsim.OpticalScreen(diam=1.0, tilt=1.0), galsim.OpticalScreen(diam=1.0, defocus=1.0), galsim.OpticalScreen(diam=1.0, astig1=1.0), galsim.OpticalScreen(diam=1.0, astig2=1.0), galsim.OpticalScreen(diam=1.0, coma1=1.0), galsim.OpticalScreen(diam=1.0, coma2=1.0), galsim.OpticalScreen(diam=1.0, trefoil1=1.0), galsim.OpticalScreen(diam=1.0, trefoil2=1.0), galsim.OpticalScreen(diam=1.0, spher=1.0), galsim.OpticalScreen(diam=1.0, spher=1.0, lam_0=100.0), galsim.OpticalScreen(diam=1.0, aberrations=[0,0,1.1]), # tip=1.1 ] all_obj_diff(objs) # Test PhaseScreenList __ne__ atm = galsim.Atmosphere(10.0, vx=1.0) objs = [galsim.PhaseScreenList(atm), galsim.PhaseScreenList(objs), # Reuse list of OpticalScreens above galsim.PhaseScreenList(objs[0:2])] all_obj_diff(objs) # Test PhaseScreenPSF __ne__ psl = galsim.PhaseScreenList(atm) objs = [galsim.PhaseScreenPSF(psl, 500.0, exptime=0.03, diam=1.0)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.1)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, flux=1.1)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, interpolant='linear')] stepk = objs[0].stepk maxk = objs[0].maxk objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_stepk=stepk/1.5)] objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_maxk=maxk*2.0)] all_obj_diff(objs)
def test_stepk_maxk(): """Test options to specify (or not) stepk and maxk. """ import time aper = galsim.Aperture(diam=1.0) rng = galsim.BaseDeviate(123456) # Test frozen AtmosphericScreen first atm = galsim.Atmosphere(screen_size=30.0, altitude=10.0, speed=0.1, alpha=1.0, rng=rng) psf = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec) t0 = time.time() stepk1 = psf.stepk maxk1 = psf.maxk t1 = time.time() print('stepk1 = ',stepk1) print('maxk1 = ',maxk1) print('t1 = ',t1-t0) psf._prepareDraw() stepk2 = psf.stepk maxk2 = psf.maxk t2 = time.time() print('stepk2 = ',stepk2) print('maxk2 = ',maxk2) print('goodImageSize = ',psf.getGoodImageSize(0.2)) print('t2 = ',t2-t1) np.testing.assert_allclose(stepk1, stepk2, rtol=0.05) np.testing.assert_allclose(maxk1, maxk2, rtol=0.05) # Also make sure that prepareDraw wasn't called to calculate the first one. # Should be very quick to do the first stepk, maxk, but slow to do the second. assert t1-t0 < t2-t1 # Check that stepk changes when gsparams.folding_threshold become more extreme. # (Note: maxk is independent of maxk_threshold because of the hard edge of the aperture.) psf1 = galsim.PhaseScreenPSF(atm, 500.0, diam=1.0, scale_unit=galsim.arcsec, gsparams=galsim.GSParams(folding_threshold=1.e-3, maxk_threshold=1.e-4)) stepk3 = psf1.stepk maxk3 = psf1.maxk print('stepk3 = ',stepk3) print('maxk3 = ',maxk3) print('goodImageSize = ',psf1.getGoodImageSize(0.2)) assert stepk3 < stepk1 assert maxk3 == maxk1 # Check that it respects the force_stepk and force_maxk parameters psf2 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec, _force_stepk=stepk2/1.5, _force_maxk=maxk2*2.0) np.testing.assert_almost_equal( psf2.stepk, stepk2/1.5, decimal=7, err_msg="PhaseScreenPSF did not adopt forced value for stepk") np.testing.assert_almost_equal( psf2.maxk, maxk2*2.0, decimal=7, err_msg="PhaseScreenPSF did not adopt forced value for maxk") do_pickle(psf) do_pickle(psf2) # Try out non-geometric-shooting psf3 = atm.makePSF(lam=500.0, aper=aper, geometric_shooting=False) img = galsim.Image(32, 32, scale=0.2) do_shoot(psf3, img, "PhaseScreenPSF") # Also make sure a few other methods at least run psf3.centroid psf3.max_sb # If we force stepk very low, it will trigger a warning when we try to draw it. psf4 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec, _force_stepk=stepk2/3.5) with assert_warns(galsim.GalSimWarning): psf4._prepareDraw() # Can suppress this warning if desired. psf5 = galsim.PhaseScreenPSF(atm, 500.0, aper=aper, scale_unit=galsim.arcsec, _force_stepk=stepk2/3.5, suppress_warning=True) with assert_raises(AssertionError): with assert_warns(galsim.GalSimWarning): psf5._prepareDraw()
def make_plot(args): # Initiate some GalSim random number generators. rng = galsim.BaseDeviate(args.seed) u = galsim.UniformDeviate(rng) # The GalSim atmospheric simulation code describes turbulence in the 3D atmosphere as a series # of 2D turbulent screens. The galsim.Atmosphere() helper function is useful for constructing # this screen list. # First, we estimate a weight for each screen, so that the turbulence is dominated by the lower # layers consistent with direct measurements. The specific values we use are from SCIDAR # measurements on Cerro Pachon as part of the 1998 Gemini site selection process # (Ellerbroek 2002, JOSA Vol 19 No 9). Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46] # km Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts, Ellerbroek_weights, interpolant='linear') # Use given number of uniformly spaced altitudes alts = np.max(Ellerbroek_alts) * np.arange( args.nlayers) / (args.nlayers - 1) weights = Ellerbroek_interp(alts) # interpolate the weights weights /= sum(weights) # and renormalize # Each layer can have its own turbulence strength (roughly inversely proportional to the Fried # parameter r0), wind speed, wind direction, altitude, and even size and scale (though note that # the size of each screen is actually made infinite by "wrapping" the edges of the screen.) The # galsim.Atmosphere helper function is useful for constructing this list, and requires lists of # parameters for the different layers. spd = [] # Wind speed in m/s dirn = [] # Wind direction in radians r0_500 = [] # Fried parameter in m at a wavelength of 500 nm. for i in range(args.nlayers): spd.append(u() * 20) # Use a random speed between 0 and max_speed dirn.append( u() * 360 * galsim.degrees) # And an isotropically distributed wind direction. # The turbulence strength of each layer is specified by through its Fried parameter r0_500, # which can be thought of as the diameter of a telescope for which atmospheric turbulence # and unaberrated diffraction contribute equally to image resolution (at a wavelength of # 500nm). The weights above are for the refractive index structure function (similar to a # variance or covariance), however, so we need to use an appropriate scaling relation to # distribute the input "net" Fried parameter into a Fried parameter for each layer. For # Kolmogorov turbulence, this is r0_500 ~ (structure function)**(-3/5): r0_500.append(args.r0_500 * weights[i]**(-3. / 5)) print( "Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, " "and r0_500 {:5.3f} m.".format(alts[i], spd[i] * dirn[i].cos(), spd[i] * dirn[i].sin(), r0_500[i])) # Make sure to use a consistent seed for the atmosphere when varying kcrit # Additionally, we set the screen size and scale. atmRng = galsim.BaseDeviate(args.seed + 1) print("Inflating atmosphere") atm = galsim.Atmosphere(r0_500=r0_500, L0=args.L0, speed=spd, direction=dirn, altitude=alts, rng=atmRng, screen_size=args.screen_size, screen_scale=args.screen_scale) with ProgressBar(args.nlayers) as bar: atm.instantiate(_bar=bar) print(atm[0].screen_scale, atm[0].screen_size) print(atm[0]._tab2d.f.shape) # `atm` is now an instance of a galsim.PhaseScreenList object. x = np.linspace(-0.5 * args.nx * args.scale, 0.5 * args.nx * args.scale, args.nx) x, y = np.meshgrid(x, x) img = atm.wavefront(x, y, 0) save_plot(img, args.outprefix + "full.png") del atm kcrits = np.logspace(np.log10(args.kmin), np.log10(args.kmax), 4) r0 = args.r0_500 * (args.lam / 500.0)**(6. / 5) for icol, kcrit in enumerate(kcrits): atmRng = galsim.BaseDeviate(args.seed + 1) atmLowK = galsim.Atmosphere(r0_500=r0_500, L0=args.L0, speed=spd, direction=dirn, altitude=alts, rng=atmRng, screen_size=args.screen_size, screen_scale=args.screen_scale) with ProgressBar(args.nlayers) as bar: atmLowK.instantiate(kmax=kcrit / r0, _bar=bar) img = atmLowK.wavefront(x, y, 0) save_plot(img, "{}{}_{}".format(args.outprefix, icol, "low.png")) del atmLowK atmRng = galsim.BaseDeviate(args.seed + 1) atmHighK = galsim.Atmosphere(r0_500=r0_500, L0=args.L0, speed=spd, direction=dirn, altitude=alts, rng=atmRng, screen_size=args.screen_size, screen_scale=args.screen_scale) with ProgressBar(args.nlayers) as bar: atmHighK.instantiate(kmin=kcrit / r0, _bar=bar) img = atmHighK.wavefront(x, y, 0) save_plot(img, "{}{}_{}".format(args.outprefix, icol, "high.png")) del atmHighK
def test_phase_screen_list(): """Test list-like behaviors of PhaseScreenList.""" rng = galsim.BaseDeviate(1234) rng2 = galsim.BaseDeviate(123) aper = galsim.Aperture(diam=1.0) ar1 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=None, rng=rng) do_pickle(ar1) do_pickle(ar1, func=lambda x: x.tab2d(12.3, 45.6)) do_pickle(ar1, func=lambda x: x.wavefront(aper).sum()) # Check that L0=np.inf and L0=None yield the same thing here too. ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=np.inf, rng=rng) assert ar1 == ar2 # Create a couple new screens with different types/parameters ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.995, rng=rng2) assert ar1 != ar2 ar3 = galsim.OpticalScreen(aberrations=[0, 0, 0, 0, 0, 0, 0, 0, 0.1]) do_pickle(ar3) do_pickle(ar3, func=lambda x:x.wavefront(aper).sum()) atm = galsim.Atmosphere(screen_size=30.0, altitude=[0.0, 1.0], speed=[1.0, 2.0], direction=[0.0*galsim.degrees, 120*galsim.degrees], r0_500=0.15, rng=rng) atm.append(ar3) do_pickle(atm) do_pickle(atm, func=lambda x:x.wavefront(aper).sum()) # testing append, extend, __getitem__, __setitem__, __delitem__, __eq__, __ne__ atm2 = galsim.PhaseScreenList(atm[:-1]) # Refers to first n-1 screens assert atm != atm2 # Append a different screen to the end of atm2 atm2.append(ar2) assert atm != atm2 # Swap the last screen in atm2 for the one that should match atm. del atm2[-1] atm2.append(atm[-1]) assert atm == atm2 # Test building from empty PhaseScreenList atm3 = galsim.PhaseScreenList() atm3.extend(atm2) assert atm == atm2 # Test constructing from existing PhaseScreenList atm4 = galsim.PhaseScreenList(atm3) del atm4[-1] assert atm != atm4 atm4.append(atm[-1]) assert atm == atm4 # Test swap atm4[0], atm4[1] = atm4[1], atm4[0] assert atm != atm4 atm4[0], atm4[1] = atm4[1], atm4[0] assert atm == atm4 wf = atm.wavefront(aper) wf2 = atm2.wavefront(aper) wf3 = atm3.wavefront(aper) wf4 = atm4.wavefront(aper) np.testing.assert_array_equal(wf, wf2, "PhaseScreenLists are inconsistent") np.testing.assert_array_equal(wf, wf3, "PhaseScreenLists are inconsistent") np.testing.assert_array_equal(wf, wf4, "PhaseScreenLists are inconsistent") # Check copy import copy # Shallow copy copies by reference. atm5 = copy.copy(atm) assert atm[0] == atm5[0] assert atm[0] is atm5[0] atm.advance() assert atm[0] == atm5[0] assert atm[0] is atm5[0] # Deepcopy actually makes an indepedent object in memory. atm5 = copy.deepcopy(atm) assert atm[0] == atm5[0] assert atm[0] is not atm5[0] atm.advance() assert atm[0] != atm5[0] # Constructor should accept both list and indiv layers as arguments. atm6 = galsim.PhaseScreenList(atm[0]) atm7 = galsim.PhaseScreenList([atm[0]]) assert atm6 == atm7 atm6 = galsim.PhaseScreenList(atm[0], atm[1]) atm7 = galsim.PhaseScreenList([atm[0], atm[1]]) atm8 = galsim.PhaseScreenList(atm[0:2]) # Slice returns PhaseScreenList, so this works too. assert atm6 == atm7 assert atm6 == atm8 # Check some actual derived PSFs too, not just phase screens. Use a small pupil_plane_size and # relatively large pupil_plane_scale to speed up the unit test. atm.advance_by(1.0) do_pickle(atm) atm.reset() kwargs = dict(exptime=0.06, diam=1.0, lam=1000.0) psf = atm.makePSF(**kwargs) do_pickle(psf) do_pickle(psf, func=lambda x:x.drawImage(nx=20, ny=20, scale=0.1)) # Need to reset atm2 since both atm and atm2 reference the same layer objects (not copies). # Not sure if this is a feature or a bug, but it's also how regular python lists work. atm2.reset() psf2 = atm2.makePSF(**kwargs) atm3.reset() psf3 = atm3.makePSF(**kwargs) atm4.reset() psf4 = atm4.makePSF(**kwargs) np.testing.assert_array_equal(psf, psf2, "PhaseScreenPSFs are inconsistent") np.testing.assert_array_equal(psf, psf3, "PhaseScreenPSFs are inconsistent") np.testing.assert_array_equal(psf, psf4, "PhaseScreenPSFs are inconsistent")
def test_phase_screen_list(): """Test list-like behaviors of PhaseScreenList.""" rng = galsim.BaseDeviate(1234) rng2 = galsim.BaseDeviate(123) aper = galsim.Aperture(diam=1.0) ar1 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=None, time_step=0.01, rng=rng) assert ar1._time == 0.0, "AtmosphericScreen initialized with non-zero time." do_pickle(ar1) do_pickle(ar1, func=lambda x: x.wavefront(aper.u, aper.v, 0.0).sum()) do_pickle(ar1, func=lambda x: np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0))) t = np.empty_like(aper.u) ud = galsim.UniformDeviate(rng.duplicate()) ud.generate(t.ravel()) t *= 0.1 # Only do a few boiling steps do_pickle(ar1, func=lambda x: x.wavefront(aper.u, aper.v, t).sum()) do_pickle(ar1, func=lambda x: np.sum(x.wavefront_gradient(aper.u, aper.v, t))) # Try seeking backwards assert ar1._time > 0.0 ar1._seek(0.0) # But not before t=0.0 with assert_raises(ValueError): ar1._seek(-1.0) # Check that L0=np.inf and L0=None yield the same thing here too. ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.997, L0=np.inf, time_step=0.01, rng=rng) # Before ar2 is instantiated, it's unequal to ar1, even though they were initialized with the # same arguments (the hashes are the same though). After both have been instantiated with the # same range of k (ar1 through use of .wavefront() and ar2 explicitly), then they are equal ( # and the hashes are still the same). assert hash(ar1) == hash(ar2) assert ar1 != ar2 ar2.instantiate() assert ar1 == ar2 assert hash(ar1) == hash(ar2) # Create a couple new screens with different types/parameters ar2 = galsim.AtmosphericScreen(10, 1, alpha=0.995, time_step=0.015, rng=rng2) ar2.instantiate() assert ar1 != ar2 ar3 = galsim.OpticalScreen(diam=1.0, aberrations=[0, 0, 0, 0, 0, 0, 0, 0, 0.1], obscuration=0.3, annular_zernike=True) do_pickle(ar3) do_pickle(ar3, func=lambda x:x.wavefront(aper.u, aper.v).sum()) do_pickle(ar3, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v))) atm = galsim.Atmosphere(screen_size=30.0, altitude=[0.0, 1.0], speed=[1.0, 2.0], direction=[0.0*galsim.degrees, 120*galsim.degrees], r0_500=0.15, rng=rng) atm.append(ar3) do_pickle(atm) do_pickle(atm, func=lambda x:x.wavefront(aper.u, aper.v, 0.0, theta0).sum()) do_pickle(atm, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0))) # testing append, extend, __getitem__, __setitem__, __delitem__, __eq__, __ne__ atm2 = atm[:-1] # Refers to first n-1 screens assert atm != atm2 # Append a different screen to the end of atm2 atm2.append(ar2) assert atm != atm2 # Swap the last screen in atm2 for the one that should match atm. del atm2[-1] atm2.append(atm[-1]) assert atm == atm2 with assert_raises(TypeError): atm['invalid'] with assert_raises(IndexError): atm[3] # Test building from empty PhaseScreenList atm3 = galsim.PhaseScreenList() atm3.extend(atm2) assert atm == atm3 # Test constructing from existing PhaseScreenList atm4 = galsim.PhaseScreenList(atm3) del atm4[-1] assert atm != atm4 atm4.append(atm[-1]) assert atm == atm4 # Test swap atm4[0], atm4[1] = atm4[1], atm4[0] assert atm != atm4 atm4[0], atm4[1] = atm4[1], atm4[0] assert atm == atm4 wf = atm.wavefront(aper.u, aper.v, None, theta0) wf2 = atm2.wavefront(aper.u, aper.v, None, theta0) wf3 = atm3.wavefront(aper.u, aper.v, None, theta0) wf4 = atm4.wavefront(aper.u, aper.v, None, theta0) np.testing.assert_array_equal(wf, wf2, "PhaseScreenLists are inconsistent") np.testing.assert_array_equal(wf, wf3, "PhaseScreenLists are inconsistent") np.testing.assert_array_equal(wf, wf4, "PhaseScreenLists are inconsistent") # Check copy import copy # Shallow copy copies by reference. atm5 = copy.copy(atm) assert atm[0] == atm5[0] assert atm[0] is atm5[0] atm._seek(1.0) assert atm[0]._time == 1.0, "Wrong time for AtmosphericScreen" assert atm[0] == atm5[0] assert atm[0] is atm5[0] # Deepcopy actually makes an indepedent object in memory. atm5 = copy.deepcopy(atm) assert atm[0] == atm5[0] assert atm[0] is not atm5[0] atm._seek(2.0) assert atm[0]._time == 2.0, "Wrong time for AtmosphericScreen" # But we still get equality, since this doesn't depend on mutable internal state: assert atm[0] == atm5[0] # Constructor should accept both list and indiv layers as arguments. atm6 = galsim.PhaseScreenList(atm[0]) atm7 = galsim.PhaseScreenList([atm[0]]) assert atm6 == atm7 do_pickle(atm6, func=lambda x:x.wavefront(aper.u, aper.v, None, theta0).sum()) do_pickle(atm6, func=lambda x:np.sum(x.wavefront_gradient(aper.u, aper.v, 0.0))) atm6 = galsim.PhaseScreenList(atm[0], atm[1]) atm7 = galsim.PhaseScreenList([atm[0], atm[1]]) atm8 = galsim.PhaseScreenList(atm[0:2]) # Slice returns PhaseScreenList, so this works too. assert atm6 == atm7 assert atm6 == atm8 # Check some actual derived PSFs too, not just phase screens. Use a small pupil_plane_size and # relatively large pupil_plane_scale to speed up the unit test. atm._reset() assert atm[0]._time == 0.0, "Wrong time for AtmosphericScreen" kwargs = dict(exptime=0.05, time_step=0.01, diam=1.1, lam=1000.0) psf = atm.makePSF(**kwargs) do_pickle(psf) do_pickle(psf, func=lambda x:x.drawImage(nx=20, ny=20, scale=0.1)) psf2 = atm2.makePSF(**kwargs) psf3 = atm3.makePSF(**kwargs) psf4 = atm4.makePSF(**kwargs) np.testing.assert_array_equal(psf, psf2, "PhaseScreenPSFs are inconsistent") np.testing.assert_array_equal(psf, psf3, "PhaseScreenPSFs are inconsistent") np.testing.assert_array_equal(psf, psf4, "PhaseScreenPSFs are inconsistent") # Check errors in u,v,t shapes. assert_raises(ValueError, ar1.wavefront, aper.u, aper.v[:-1,:-1]) assert_raises(ValueError, ar1.wavefront, aper.u[:-1,:-1], aper.v) assert_raises(ValueError, ar1.wavefront, aper.u, aper.v, 0.1 * aper.u[:-1,:-1]) assert_raises(ValueError, ar1.wavefront_gradient, aper.u, aper.v[:-1,:-1]) assert_raises(ValueError, ar1.wavefront_gradient, aper.u[:-1,:-1], aper.v) assert_raises(ValueError, ar1.wavefront_gradient, aper.u, aper.v, 0.1 * aper.u[:-1,:-1]) assert_raises(ValueError, ar3.wavefront, aper.u, aper.v[:-1,:-1]) assert_raises(ValueError, ar3.wavefront, aper.u[:-1,:-1], aper.v) assert_raises(ValueError, ar3.wavefront_gradient, aper.u, aper.v[:-1,:-1]) assert_raises(ValueError, ar3.wavefront_gradient, aper.u[:-1,:-1], aper.v)
def make_movie(args): rng = galsim.BaseDeviate(args.seed) u = galsim.UniformDeviate(rng) # Generate 1D Gaussian random fields for each aberration. t = np.arange(-args.n/2, args.n/2) corr = np.exp(-0.5*t**2/args.ell**2) pk = np.fft.fft(np.fft.fftshift(corr)) ak = np.sqrt(2*pk) phi = np.random.uniform(size=(args.n, args.jmax)) zk = ak[:, None]*np.exp(2j*np.pi*phi) aberrations = args.n/2*np.fft.ifft(zk, axis=0).real measured_std = np.mean(np.std(aberrations, axis=0)) aberrations *= args.sigma/measured_std aberrations -= np.mean(aberrations, axis=0) # For the atmosphere screens, we first estimates weights, so that the turbulence is dominated by # the lower layers consistent with direct measurements. The specific values we use are from # SCIDAR measurements on Cerro Pachon as part of the 1998 Gemini site selection process # (Ellerbroek 2002, JOSA Vol 19 No 9). Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46] # km Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts, Ellerbroek_weights, interpolant='linear') alts = np.max(Ellerbroek_alts)*np.arange(args.nlayers)/(args.nlayers-1) weights = Ellerbroek_interp(alts) # interpolate the weights weights /= sum(weights) # and renormalize spd = [] # Wind speed in m/s dirn = [] # Wind direction in radians r0_500 = [] # Fried parameter in m at a wavelength of 500 nm. for i in range(args.nlayers): spd.append(u()*args.max_speed) # Use a random speed between 0 and args.max_speed dirn.append(u()*360*galsim.degrees) # And an isotropically distributed wind direction. r0_500.append(args.r0_500*weights[i]**(-3./5)) print("Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, " "and r0_500 {:5.3f} m." .format(alts[i], spd[i]*dirn[i].cos(), spd[i]*dirn[i].sin(), r0_500[i])) if args.nlayers > 0: # Make two identical Atmospheres. They will diverge when one gets drawn using Fourier # optics and the other gets drawn with geometric optics. fft_atm = galsim.Atmosphere(r0_500=r0_500, speed=spd, direction=dirn, altitude=alts, rng=rng.duplicate(), screen_size=args.screen_size, screen_scale=args.screen_scale) geom_atm = galsim.Atmosphere(r0_500=r0_500, speed=spd, direction=dirn, altitude=alts, rng=rng.duplicate(), screen_size=args.screen_size, screen_scale=args.screen_scale) else: fft_atm = galsim.PhaseScreenList() geom_atm = galsim.PhaseScreenList() # Before either of this has been instantiated, they are identical assert fft_atm == geom_atm # If any AtmosphericScreens are included, we manually instantiate here so we can have a # uniformly updating ProgressBar both here and below when actually drawing PSFs. Normally, it's # okay to let the atms automatically instantiate, which happens when the first PSF is drawn, or # the first wavefront is queried. if args.nlayers > 0: print("Instantiating screens") with ProgressBar(2*args.nlayers) as bar: fft_atm.instantiate(_bar=bar) r0 = args.r0_500*(args.lam/500)**1.2 geom_atm.instantiate(kmax=0.2/r0, _bar=bar) # After instantiation, they're only equal if there's no atmosphere. assert fft_atm != geom_atm # Setup Fourier and geometric apertures fft_aper = galsim.Aperture(args.diam, args.lam, obscuration=args.obscuration, pad_factor=args.pad_factor, oversampling=args.oversampling, nstruts=args.nstruts, strut_thick=args.strut_thick, strut_angle=args.strut_angle*galsim.degrees) geom_aper = galsim.Aperture(args.diam, args.lam, obscuration=args.obscuration, pad_factor=args.geom_oversampling, oversampling=0.5, nstruts=args.nstruts, strut_thick=args.strut_thick, strut_angle=args.strut_angle*galsim.degrees) scale = args.size/args.nx extent = np.r_[-1,1,-1,1]*args.size/2 fft_img_sum = galsim.ImageD(args.nx, args.nx, scale=scale) geom_img_sum = galsim.ImageD(args.nx, args.nx, scale=scale) # Code to setup the Matplotlib animation. metadata = dict(title="FFT vs geom movie", artist='Matplotlib') writer = anim.FFMpegWriter(fps=15, bitrate=10000, metadata=metadata) fig = Figure(facecolor='k', figsize=(16, 9)) FigureCanvasAgg(fig) fft_ax = fig.add_axes([0.07, 0.08, 0.36, 0.9]) fft_ax.set_xlabel("Arcsec") fft_ax.set_ylabel("Arcsec") fft_ax.set_title("Fourier Optics") fft_im = fft_ax.imshow(np.ones((args.nx, args.nx), dtype=float), animated=True, extent=extent, vmin=0.0, vmax=args.vmax) # Axis for the wavefront image on the right. geom_ax = fig.add_axes([0.50, 0.08, 0.36, 0.9]) geom_ax.set_xlabel("Arcsec") geom_ax.set_ylabel("Arcsec") geom_ax.set_title("Geometric Optics") geom_im = geom_ax.imshow(np.ones((args.nx, args.nx), dtype=float), animated=True, extent=extent, vmin=0.0, vmax=args.vmax) # Color items white to show up on black background for ax in [fft_ax, geom_ax]: for _, spine in ax.spines.items(): spine.set_color('w') ax.title.set_color('w') ax.xaxis.label.set_color('w') ax.yaxis.label.set_color('w') ax.tick_params(axis='both', colors='w') ztext = [] for i in range(2, args.jmax+1): x = 0.88 y = 0.1 + (args.jmax-i)/args.jmax*0.8 ztext.append(fig.text(x, y, "Z{:d} = {:5.3f}".format(i, 0.0))) ztext[-1].set_color('w') M_fft = fft_ax.text(0.02, 0.955, '', transform=fft_ax.transAxes) M_fft.set_color('w') M_geom = geom_ax.text(0.02, 0.955, '', transform=geom_ax.transAxes) M_geom.set_color('w') etext_fft = fft_ax.text(0.02, 0.91, '', transform=fft_ax.transAxes) etext_fft.set_color('w') etext_geom = geom_ax.text(0.02, 0.91, '', transform=geom_ax.transAxes) etext_geom.set_color('w') fft_mom = np.empty((args.n, 8), dtype=float) geom_mom = np.empty((args.n, 8), dtype=float) fullpath = args.out+"movie.mp4" subdir, filename = os.path.split(fullpath) if subdir and not os.path.isdir(subdir): os.makedirs(subdir) print("Drawing PSFs") with ProgressBar(args.n) as bar: with writer.saving(fig, fullpath, 100): t0 = 0.0 for i, aberration in enumerate(aberrations): optics = galsim.OpticalScreen(args.diam, obscuration=args.obscuration, aberrations=[0]+aberration.tolist()) fft_psl = galsim.PhaseScreenList(fft_atm._layers+[optics]) geom_psl = galsim.PhaseScreenList(geom_atm._layers+[optics]) fft_psf = fft_psl.makePSF( lam=args.lam, aper=fft_aper, t0=t0, exptime=args.time_step) geom_psf = geom_psl.makePSF( lam=args.lam, aper=geom_aper, t0=t0, exptime=args.time_step) fft_img0 = fft_psf.drawImage(nx=args.nx, ny=args.nx, scale=scale) geom_img0 = geom_psf.drawImage(nx=args.nx, ny=args.nx, scale=scale, method='phot', n_photons=100000) t0 += args.time_step if args.accumulate: fft_img_sum += fft_img0 geom_img_sum += geom_img0 fft_img = fft_img_sum/(i+1) geom_img = geom_img_sum/(i+1) else: fft_img = fft_img0 geom_img = geom_img0 fft_im.set_array(fft_img.array) geom_im.set_array(geom_img.array) for j, ab in enumerate(aberration): if j == 0: continue ztext[j-1].set_text("Z{:d} = {:5.3f}".format(j+1, ab)) # Calculate simple estimate of ellipticity mom_fft = galsim.utilities.unweighted_moments(fft_img, origin=fft_img.true_center) mom_geom = galsim.utilities.unweighted_moments(geom_img, origin=geom_img.true_center) e_fft = galsim.utilities.unweighted_shape(mom_fft) e_geom = galsim.utilities.unweighted_shape(mom_geom) Is = ("$M_x$={: 6.4f}, $M_y$={: 6.4f}, $M_{{xx}}$={:6.4f}," " $M_{{yy}}$={:6.4f}, $M_{{xy}}$={: 6.4f}") M_fft.set_text(Is.format(mom_fft['Mx']*fft_img.scale, mom_fft['My']*fft_img.scale, mom_fft['Mxx']*fft_img.scale**2, mom_fft['Myy']*fft_img.scale**2, mom_fft['Mxy']*fft_img.scale**2)) M_geom.set_text(Is.format(mom_geom['Mx']*geom_img.scale, mom_geom['My']*geom_img.scale, mom_geom['Mxx']*geom_img.scale**2, mom_geom['Myy']*geom_img.scale**2, mom_geom['Mxy']*geom_img.scale**2)) etext_fft.set_text("$e_1$={: 6.4f}, $e_2$={: 6.4f}, $r^2$={:6.4f}".format( e_fft['e1'], e_fft['e2'], e_fft['rsqr']*fft_img.scale**2)) etext_geom.set_text("$e_1$={: 6.4f}, $e_2$={: 6.4f}, $r^2$={:6.4f}".format( e_geom['e1'], e_geom['e2'], e_geom['rsqr']*geom_img.scale**2)) fft_mom[i] = (mom_fft['Mx']*fft_img.scale, mom_fft['My']*fft_img.scale, mom_fft['Mxx']*fft_img.scale**2, mom_fft['Myy']*fft_img.scale**2, mom_fft['Mxy']*fft_img.scale**2, e_fft['e1'], e_fft['e2'], e_fft['rsqr']*fft_img.scale**2) geom_mom[i] = (mom_geom['Mx']*geom_img.scale, mom_geom['My']*geom_img.scale, mom_geom['Mxx']*geom_img.scale**2, mom_geom['Myy']*geom_img.scale**2, mom_geom['Mxy']*geom_img.scale**2, e_geom['e1'], e_geom['e2'], e_geom['rsqr']*geom_img.scale**2) writer.grab_frame(facecolor=fig.get_facecolor()) bar.update() def symmetrize_axis(ax): xlim = ax.get_xlim() ylim = ax.get_ylim() lim = min(xlim[0], ylim[0]), max(xlim[1], ylim[1]) ax.set_xlim(lim) ax.set_ylim(lim) ax.plot(lim, lim) # Centroid plot fig = Figure(figsize=(10, 6)) FigureCanvasAgg(fig) axes = [] axes.append(fig.add_subplot(1, 2, 1)) axes.append(fig.add_subplot(1, 2, 2)) axes[0].scatter(fft_mom[:, 0], geom_mom[:, 0]) axes[1].scatter(fft_mom[:, 1], geom_mom[:, 1]) axes[0].set_title("Mx") axes[1].set_title("My") for ax in axes: ax.set_xlabel("Fourier Optics") ax.set_ylabel("Geometric Optics") symmetrize_axis(ax) fig.tight_layout() fig.savefig(args.out+"centroid.png", dpi=300) # Second moment plot fig = Figure(figsize=(16, 6)) FigureCanvasAgg(fig) axes = [] axes.append(fig.add_subplot(1, 3, 1)) axes.append(fig.add_subplot(1, 3, 2)) axes.append(fig.add_subplot(1, 3, 3)) axes[0].scatter(fft_mom[:, 2], geom_mom[:, 2]) axes[1].scatter(fft_mom[:, 3], geom_mom[:, 3]) axes[2].scatter(fft_mom[:, 4], geom_mom[:, 4]) axes[0].set_title("Mxx") axes[1].set_title("Myy") axes[2].set_title("Mxy") for ax in axes: ax.set_xlabel("Fourier Optics") ax.set_ylabel("Geometric Optics") symmetrize_axis(ax) fig.tight_layout() fig.savefig(args.out+"2ndMoment.png", dpi=300) # Ellipticity plot fig = Figure(figsize=(16, 6)) FigureCanvasAgg(fig) axes = [] axes.append(fig.add_subplot(1, 3, 1)) axes.append(fig.add_subplot(1, 3, 2)) axes.append(fig.add_subplot(1, 3, 3)) axes[0].scatter(fft_mom[:, 5], geom_mom[:, 5]) axes[1].scatter(fft_mom[:, 6], geom_mom[:, 6]) axes[2].scatter(fft_mom[:, 7], geom_mom[:, 7]) axes[0].set_title("e1") axes[1].set_title("e2") axes[2].set_title("rsqr") for ax in axes: ax.set_xlabel("Fourier Optics") ax.set_ylabel("Geometric Optics") symmetrize_axis(ax) fig.tight_layout() fig.savefig(args.out+"ellipticity.png", dpi=300)
def __init__(self, airmass, rawSeeing, band, rng, t0=0.0, exptime=30.0, kcrit=0.2, gaussianFWHM=0.3, screen_size=819.2, screen_scale=0.1, doOpt=True, logger=None, nproc=None): self.airmass = airmass self.rawSeeing = rawSeeing self.wlen_eff = dict(u=365.49, g=480.03, r=622.20, i=754.06, z=868.21, y=991.66)[band] # wlen_eff is from Table 2 of LSE-40 (y=y2) self.targetFWHM = rawSeeing * airmass**0.6 * (self.wlen_eff / 500)**(-0.3) self.rng = rng self.t0 = t0 self.exptime = exptime self.screen_size = screen_size self.screen_scale = screen_scale self.logger = logger ctx = multiprocessing.get_context('fork') self.atm = galsim.Atmosphere(mp_context=ctx, **self._getAtmKwargs()) self.aper = galsim.Aperture(diam=8.36, obscuration=0.61, lam=self.wlen_eff, screen_list=self.atm) # Instantiate screens now instead of delaying until after multiprocessing # has started. r0_500 = self.atm.r0_500_effective r0 = r0_500 * (self.wlen_eff / 500.0)**(6. / 5) kmax = kcrit / r0 if logger: logger.warning("Building atmosphere") if nproc is None: nproc = len(self.atm) if nproc == 1: self.atm.instantiate(kmax=kmax, check='phot') else: if logger: logger.warning( f"Using {nproc} processes to build the phase screens") with ctx.Pool( nproc, initializer=galsim.phase_screens.initWorker, initargs=galsim.phase_screens.initWorkerArgs()) as pool: self.atm.instantiate(pool=pool, kmax=kmax, check='phot') if logger: logger.info("Finished building atmosphere") if doOpt: self.atm.append(OptWF(rng, self.wlen_eff)) if logger and gaussianFWHM > 0: logger.debug( "Convolving in Gaussian with FWHM = {}".format(gaussianFWHM)) self.gaussianFWHM = gaussianFWHM
def make_plot(args): # Initiate some GalSim random number generators. rng = galsim.BaseDeviate(args.seed) u = galsim.UniformDeviate(rng) # The GalSim atmospheric simulation code describes turbulence in the 3D atmosphere as a series # of 2D turbulent screens. The galsim.Atmosphere() helper function is useful for constructing # this screen list. # First, we estimate a weight for each screen, so that the turbulence is dominated by the lower # layers consistent with direct measurements. The specific values we use are from SCIDAR # measurements on Cerro Pachon as part of the 1998 Gemini site selection process # (Ellerbroek 2002, JOSA Vol 19 No 9). Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46] # km Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts, Ellerbroek_weights, interpolant='linear') # Use given number of uniformly spaced altitudes alts = np.max(Ellerbroek_alts)*np.arange(args.nlayers)/(args.nlayers-1) weights = Ellerbroek_interp(alts) # interpolate the weights weights /= sum(weights) # and renormalize # Each layer can have its own turbulence strength (roughly inversely proportional to the Fried # parameter r0), wind speed, wind direction, altitude, and even size and scale (though note that # the size of each screen is actually made infinite by "wrapping" the edges of the screen.) The # galsim.Atmosphere helper function is useful for constructing this list, and requires lists of # parameters for the different layers. spd = [] # Wind speed in m/s dirn = [] # Wind direction in radians r0_500 = [] # Fried parameter in m at a wavelength of 500 nm. for i in range(args.nlayers): spd.append(u()*args.max_speed) # Use a random speed between 0 and max_speed dirn.append(u()*360*galsim.degrees) # And an isotropically distributed wind direction. # The turbulence strength of each layer is specified by through its Fried parameter r0_500, # which can be thought of as the diameter of a telescope for which atmospheric turbulence # and unaberrated diffraction contribute equally to image resolution (at a wavelength of # 500nm). The weights above are for the refractive index structure function (similar to a # variance or covariance), however, so we need to use an appropriate scaling relation to # distribute the input "net" Fried parameter into a Fried parameter for each layer. For # Kolmogorov turbulence, this is r0_500 ~ (structure function)**(-3/5): r0_500.append(args.r0_500*weights[i]**(-3./5)) print("Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, " "and r0_500 {:5.3f} m." .format(alts[i], spd[i]*dirn[i].cos(), spd[i]*dirn[i].sin(), r0_500[i])) # Apply fudge factor r0_500 = [r*args.turb_factor**(-3./5) for r in r0_500] # Make sure to use a consistent seed for the atmosphere when varying kcrit # Additionally, we set the screen size and scale. atmRng = galsim.BaseDeviate(args.seed+1) print("Inflating atmosphere") fftAtm = galsim.Atmosphere(r0_500=r0_500, L0=args.L0, speed=spd, direction=dirn, altitude=alts, rng=atmRng, screen_size=args.screen_size, screen_scale=args.screen_scale) with ProgressBar(args.nlayers) as bar: fftAtm.instantiate(_bar=bar) print(fftAtm[0].screen_scale, fftAtm[0].screen_size) print(fftAtm[0]._tab2d.f.shape) # `atm` is now an instance of a galsim.PhaseScreenList object. # Construct an Aperture object for computing the PSF. The Aperture object describes the # illumination pattern of the telescope pupil, and chooses good sampling size and resolution # for representing this pattern as an array. aper = galsim.Aperture(diam=args.diam, lam=args.lam, obscuration=args.obscuration, screen_list=fftAtm, pad_factor=args.pad_factor, oversampling=args.oversampling) print("Drawing with Fourier optics") with ProgressBar(args.exptime/args.time_step) as bar: fftPSF = fftAtm.makePSF(lam=args.lam, aper=aper, exptime=args.exptime, time_step=args.time_step, _bar=bar) fftImg = fftPSF.drawImage(nx=args.nx, ny=args.nx, scale=args.scale) fftMom = galsim.hsm.FindAdaptiveMom(fftImg) vk = galsim.Convolve( galsim.VonKarman(lam=args.lam, r0=args.r0_500*(args.lam/500.0)**(6./5), L0=args.L0), galsim.Airy(lam=args.lam, diam=args.diam, obscuration=args.obscuration) ) vkImg = vk.drawImage(nx=args.nx, ny=args.nx, scale=args.scale) vkMom = galsim.hsm.FindAdaptiveMom(vkImg) # Start output at this point fig, axes = plt.subplots(nrows=5, ncols=4, figsize=(8, 8)) FigureCanvasAgg(fig) for ax in axes.ravel(): ax.set_xticks([]) ax.set_yticks([]) kcrits = np.logspace(np.log10(args.kmin), np.log10(args.kmax), 4) r0 = args.r0_500*(args.lam/500.0)**(6./5) for icol, kcrit in enumerate(kcrits): # reset atmRng atmRng = galsim.BaseDeviate(args.seed+1) print("Inflating atmosphere with kcrit={}".format(kcrit)) atm = galsim.Atmosphere(r0_500=r0_500, L0=args.L0, speed=spd, direction=dirn, altitude=alts, rng=atmRng, screen_size=args.screen_size, screen_scale=args.screen_scale) with ProgressBar(args.nlayers) as bar: atm.instantiate(kmax=kcrit/r0, _bar=bar) kick1 = atm.makePSF(lam=args.lam, aper=aper, exptime=args.exptime, time_step=args.time_step, second_kick=False) r0 = args.r0_500*(args.lam/500)**(6./5) kick2 = galsim.SecondKick(lam=args.lam, r0=r0, diam=args.diam, obscuration=args.obscuration, kcrit=kcrit) img1 = kick1.drawImage(nx=args.nx, ny=args.nx, scale=args.scale, method='phot', n_photons=args.nphot) try: mom1 = galsim.hsm.FindAdaptiveMom(img1) except RuntimeError: mom1 = None img2 = kick2.drawImage(nx=args.nx, ny=args.nx, scale=args.scale, method='phot', n_photons=args.nphot) try: mom2 = galsim.hsm.FindAdaptiveMom(img2) except RuntimeError: mom2 = None geom = galsim.Convolve(kick1, kick2) geomImg = geom.drawImage(nx=args.nx, ny=args.nx, scale=args.scale, method='phot', n_photons=args.nphot) try: geomMom = galsim.hsm.FindAdaptiveMom(geomImg) except RuntimeError: geomMom = None axes[0,icol].imshow(fftImg.array) axes[0,icol].text(0.5, 0.9, "{:6.3f}".format(fftMom.moments_sigma), transform=axes[0,icol].transAxes, color='w') axes[1,icol].imshow(img1.array) if mom1: axes[1,icol].text(0.5, 0.9, "{:6.3f}".format(mom1.moments_sigma), transform=axes[1,icol].transAxes, color='w') axes[2,icol].imshow(img2.array) if mom2: axes[2,icol].text(0.5, 0.9, "{:6.3f}".format(mom2.moments_sigma), transform=axes[2,icol].transAxes, color='w') axes[3,icol].imshow(geomImg.array) if geomMom: axes[3,icol].text(0.5, 0.9, "{:6.3f}".format(geomMom.moments_sigma), transform=axes[3,icol].transAxes, color='w') axes[4,icol].imshow(vkImg.array) axes[4,icol].text(0.5, 0.9, "{:6.3f}".format(vkMom.moments_sigma), transform=axes[4,icol].transAxes, color='w') axes[0,icol].set_title("{:6.3f}".format(kcrit)) axes[0, 0].set_ylabel("DFT") axes[1, 0].set_ylabel("1st kick") axes[2, 0].set_ylabel("2nd kick") axes[3, 0].set_ylabel("Geom") axes[4, 0].set_ylabel("Von Karman") fig.tight_layout() dirname, filename = os.path.split(args.outfile) if not os.path.exists(dirname): os.mkdir(dirname) fig.savefig(args.outfile)
def make_plot(args): # Initiate some GalSim random number generators. rng = galsim.BaseDeviate(args.seed) u = galsim.UniformDeviate(rng) # The GalSim atmospheric simulation code describes turbulence in the 3D atmosphere as a series # of 2D turbulent screens. The galsim.Atmosphere() helper function is useful for constructing # this screen list. # First, we estimate a weight for each screen, so that the turbulence is dominated by the lower # layers consistent with direct measurements. The specific values we use are from SCIDAR # measurements on Cerro Pachon as part of the 1998 Gemini site selection process # (Ellerbroek 2002, JOSA Vol 19 No 9). Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46] # km Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts, Ellerbroek_weights, interpolant='linear') # Use given number of uniformly spaced altitudes alts = np.max(Ellerbroek_alts) * np.arange( args.nlayers) / (args.nlayers - 1) weights = Ellerbroek_interp(alts) # interpolate the weights weights /= sum(weights) # and renormalize # Each layer can have its own turbulence strength (roughly inversely proportional to the Fried # parameter r0), wind speed, wind direction, altitude, and even size and scale (though note that # the size of each screen is actually made infinite by "wrapping" the edges of the screen.) The # galsim.Atmosphere helper function is useful for constructing this list, and requires lists of # parameters for the different layers. spd = [] # Wind speed in m/s dirn = [] # Wind direction in radians r0_500 = [] # Fried parameter in m at a wavelength of 500 nm. for i in range(args.nlayers): spd.append( u() * args.max_speed) # Use a random speed between 0 and max_speed dirn.append( u() * 360 * galsim.degrees) # And an isotropically distributed wind direction. # The turbulence strength of each layer is specified by through its Fried parameter r0_500, # which can be thought of as the diameter of a telescope for which atmospheric turbulence # and unaberrated diffraction contribute equally to image resolution (at a wavelength of # 500nm). The weights above are for the refractive index structure function (similar to a # variance or covariance), however, so we need to use an appropriate scaling relation to # distribute the input "net" Fried parameter into a Fried parameter for each layer. For # Kolmogorov turbulence, this is r0_500 ~ (structure function)**(-3/5): r0_500.append(args.r0_500 * weights[i]**(-3. / 5)) print( "Adding layer at altitude {:5.2f} km with velocity ({:5.2f}, {:5.2f}) m/s, " "and r0_500 {:5.3f} m.".format(alts[i], spd[i] * dirn[i].cos(), spd[i] * dirn[i].sin(), r0_500[i])) # Generate atmosphere, set the initial screen size and scale. atmRng = galsim.BaseDeviate(args.seed + 1) fineAtm = galsim.Atmosphere(r0_500=r0_500, L0=args.L0, speed=spd, direction=dirn, altitude=alts, rng=atmRng, screen_size=args.screen_size, screen_scale=args.screen_scale) with ProgressBar(args.nlayers) as bar: fineAtm.instantiate(_bar=bar) # `fineAtm` is now an instance of a galsim.PhaseScreenList object. # Construct an Aperture object for computing the PSF. The Aperture object describes the # illumination pattern of the telescope pupil, and chooses good sampling size and resolution # for representing this pattern as an array. aper = galsim.Aperture(diam=args.diam, lam=args.lam, obscuration=args.obscuration, screen_list=fineAtm, pad_factor=args.pad_factor, oversampling=args.oversampling) print(repr(aper)) # Start output fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 7)) FigureCanvasAgg(fig) for ax in axes.ravel(): ax.set_xticks([]) ax.set_yticks([]) # Coarse print("Drawing with Fourier optics") with ProgressBar(args.exptime / args.time_step) as bar: psf = fineAtm.makePSF(lam=args.lam, aper=aper, exptime=args.exptime, time_step=args.time_step, _bar=bar) img = psf.drawImage(nx=args.nx, ny=args.nx, scale=args.scale) try: mom = galsim.hsm.FindAdaptiveMom(img) except RuntimeError: mom = None axes[0, 0].imshow(img.array) axes[0, 0].set_title("{}".format(fineAtm[0].screen_scale)) if mom is not None: axes[0, 0].text(0.5, 0.9, "{:6.3f}".format(mom.moments_sigma), transform=axes[0, 0].transAxes, color='w') # Factor of 2 shrunkenAtm = shrink_atm(fineAtm, 2) print("Drawing with shrink scale 2") with ProgressBar(args.exptime / args.time_step) as bar: psf = shrunkenAtm.makePSF(lam=args.lam, aper=aper, exptime=args.exptime, time_step=args.time_step, _bar=bar) img = psf.drawImage(nx=args.nx, ny=args.nx, scale=args.scale) try: mom = galsim.hsm.FindAdaptiveMom(img) except RuntimeError: mom = None axes[0, 1].imshow(img.array) axes[0, 1].set_title("{}".format(shrunkenAtm[0].screen_scale)) if mom is not None: axes[0, 1].text(0.5, 0.9, "{:6.3f}".format(mom.moments_sigma), transform=axes[0, 1].transAxes, color='w') # Factor of 4 shrunkenAtm = shrink_atm(fineAtm, 4) print("Drawing with shrink scale 4") with ProgressBar(args.exptime / args.time_step) as bar: psf = shrunkenAtm.makePSF(lam=args.lam, aper=aper, exptime=args.exptime, time_step=args.time_step, _bar=bar) img = psf.drawImage(nx=args.nx, ny=args.nx, scale=args.scale) try: mom = galsim.hsm.FindAdaptiveMom(img) except RuntimeError: mom = None axes[1, 0].imshow(img.array) axes[1, 0].set_title("{}".format(shrunkenAtm[0].screen_scale)) if mom is not None: axes[1, 0].text(0.5, 0.9, "{:6.3f}".format(mom.moments_sigma), transform=axes[1, 0].transAxes, color='w') # Factor of 8 shrunkenAtm = shrink_atm(fineAtm, 8) print("Drawing with shrink scale 8") with ProgressBar(args.exptime / args.time_step) as bar: psf = shrunkenAtm.makePSF(lam=args.lam, aper=aper, exptime=args.exptime, time_step=args.time_step, _bar=bar) img = psf.drawImage(nx=args.nx, ny=args.nx, scale=args.scale) try: mom = galsim.hsm.FindAdaptiveMom(img) except RuntimeError: mom = None axes[1, 1].imshow(img.array) axes[1, 1].set_title("{}".format(shrunkenAtm[0].screen_scale)) if mom is not None: axes[1, 1].text(0.5, 0.9, "{:6.3f}".format(mom.moments_sigma), transform=axes[1, 1].transAxes, color='w') fig.tight_layout() dirname, filename = os.path.split(args.outfile) if not os.path.exists(dirname): os.mkdir(dirname) fig.savefig(args.outfile)
def test_phase_gradient_shoot(): # Make the atmosphere seed = 12345 r0_500 = 0.2 # m nlayers = 6 screen_size = 102.4 # m screen_scale = 0.1 # m max_speed = 20 # m/s rng = galsim.BaseDeviate(seed) u = galsim.UniformDeviate(rng) # Use atmospheric weights from 1998 Gemini site selection process as something reasonably # realistic. (Ellerbroek 2002, JOSA Vol 19 No 9). Ellerbroek_alts = [0.0, 2.58, 5.16, 7.73, 12.89, 15.46] # km Ellerbroek_weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] Ellerbroek_interp = galsim.LookupTable(Ellerbroek_alts, Ellerbroek_weights, interpolant='linear') alts = np.max(Ellerbroek_alts) * np.arange(nlayers) / (nlayers - 1) weights = Ellerbroek_interp(alts) weights /= sum(weights) spd = [] # Wind speed in m/s dirn = [] # Wind direction in radians r0_500s = [] # Fried parameter in m at a wavelength of 500 nm. for i in range(nlayers): spd.append(u() * max_speed) dirn.append(u() * 360 * galsim.degrees) r0_500s.append(r0_500 * weights[i]**(-3. / 5)) atm = galsim.Atmosphere(r0_500=r0_500, speed=spd, direction=dirn, altitude=alts, rng=rng, screen_size=screen_size, screen_scale=screen_scale) lam = 500.0 diam = 4.0 pad_factor = 1.0 oversampling = 1.0 aper = galsim.Aperture(diam=diam, lam=lam, screen_list=atm, pad_factor=pad_factor, oversampling=oversampling) xs = np.empty((10, ), dtype=float) ys = np.empty((10, ), dtype=float) u.generate(xs) u.generate(ys) thetas = [(x * galsim.degrees, y * galsim.degrees) for x, y in zip(xs, ys)] if __name__ == '__main__': exptime = 15.0 centroid_tolerance = 0.05 second_moment_tolerance = 0.5 else: exptime = 0.2 centroid_tolerance = 0.2 second_moment_tolerance = 1.5 psfs = [ atm.makePSF(lam, diam=diam, theta=th, exptime=exptime, aper=aper) for th in thetas ] shoot_moments = [] fft_moments = [] # At the moment, Ixx and Iyy (but not Ixy) are systematically smaller in phase gradient shooting # mode than in FFT mode. For now, I'm willing to accept this, but we should revisit it once we # get the "second kick" approximation implemented. offset = 0.5 for psf in psfs: im_shoot = psf.drawImage(nx=48, ny=48, scale=0.05, method='phot', n_photons=100000, rng=rng) im_fft = psf.drawImage(nx=48, ny=48, scale=0.05) shoot_moment = galsim.utilities.unweighted_moments(im_shoot) fft_moment = galsim.utilities.unweighted_moments(im_fft) for key in ['Mx', 'My']: np.testing.assert_allclose( shoot_moment[key], fft_moment[key], rtol=0, atol=centroid_tolerance, err_msg='Phase gradient centroid {0} not close to fft centroid' .format(key)) for key in ['Mxx', 'Myy']: np.testing.assert_allclose( shoot_moment[key] + offset, fft_moment[key], rtol=0, atol=second_moment_tolerance, err_msg='Phase gradient second moment {} not close to fft moment' .format(key)) np.testing.assert_allclose( shoot_moment['Mxy'], fft_moment['Mxy'], rtol=0, atol=second_moment_tolerance, err_msg='Phase gradient second moment Mxy not close to fft moment') shoot_moments.append(shoot_moment) fft_moments.append(fft_moment) # Verify that shoot with rng=None runs psf.shoot(100, rng=None) # Constraints on the ensemble should be tighter than for individual PSFs. mean_shoot_moment = {} mean_fft_moment = {} for k in shoot_moments[0]: mean_shoot_moment[k] = np.mean([sm[k] for sm in shoot_moments]) mean_fft_moment[k] = np.mean([fm[k] for fm in fft_moments]) for key in ['Mx', 'My']: np.testing.assert_allclose( mean_shoot_moment[key], mean_fft_moment[key], rtol=0, atol=centroid_tolerance, err_msg= 'Mean phase gradient centroid {0} not close to mean fft centroid'. format(key)) for key in ['Mxx', 'Myy']: np.testing.assert_allclose( mean_shoot_moment[key] + offset, mean_fft_moment[key], rtol=0, atol=second_moment_tolerance, err_msg= 'Mean phase gradient second moment {} not close to mean fft moment' .format(key)) np.testing.assert_allclose( mean_shoot_moment['Mxy'], mean_fft_moment['Mxy'], rtol=0, atol=second_moment_tolerance, err_msg= 'Mean phase gradient second moment Mxy not close to mean fft moment')
def test_ne(): """Test Apertures, PhaseScreens, PhaseScreenLists, and PhaseScreenPSFs for not-equals.""" import copy pupil_plane_im = galsim.fits.read(os.path.join(imgdir, pp_file)) # Test galsim.Aperture __ne__ objs = [galsim.Aperture(diam=1.0), galsim.Aperture(diam=1.1), galsim.Aperture(diam=1.0, oversampling=1.5), galsim.Aperture(diam=1.0, pad_factor=1.5), galsim.Aperture(diam=1.0, circular_pupil=False), galsim.Aperture(diam=1.0, obscuration=0.3), galsim.Aperture(diam=1.0, nstruts=3), galsim.Aperture(diam=1.0, nstruts=3, strut_thick=0.2), galsim.Aperture(diam=1.0, nstruts=3, strut_angle=15*galsim.degrees), galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im), galsim.Aperture(diam=1.0, pupil_plane_im=pupil_plane_im, pupil_angle=10.0*galsim.degrees)] all_obj_diff(objs) # Test AtmosphericScreen __ne__ rng = galsim.BaseDeviate(1) objs = [galsim.AtmosphericScreen(10.0, rng=rng), galsim.AtmosphericScreen(10.0, rng=rng, vx=1.0), galsim.AtmosphericScreen(10.0, rng=rng, vx=1.0), # advance this one below galsim.AtmosphericScreen(10.0, rng=rng, vy=1.0), galsim.AtmosphericScreen(10.0, rng=rng, alpha=0.999), galsim.AtmosphericScreen(10.0, rng=rng, altitude=1.0), galsim.AtmosphericScreen(10.0, rng=rng, time_step=0.1), galsim.AtmosphericScreen(10.0, rng=rng, r0_500=0.1), galsim.AtmosphericScreen(10.0, rng=rng, L0=10.0), galsim.AtmosphericScreen(10.0, rng=rng, vx=10.0), ] objs[2].advance() all_obj_diff(objs) # Test OpticalScreen __ne__ objs = [galsim.OpticalScreen(), galsim.OpticalScreen(tip=1.0), galsim.OpticalScreen(tilt=1.0), galsim.OpticalScreen(defocus=1.0), galsim.OpticalScreen(astig1=1.0), galsim.OpticalScreen(astig2=1.0), galsim.OpticalScreen(coma1=1.0), galsim.OpticalScreen(coma2=1.0), galsim.OpticalScreen(trefoil1=1.0), galsim.OpticalScreen(trefoil2=1.0), galsim.OpticalScreen(spher=1.0), galsim.OpticalScreen(spher=1.0, lam_0=100.0), galsim.OpticalScreen(aberrations=[0,0,1.1]), # tip=1.1 ] all_obj_diff(objs) # Test PhaseScreenList __ne__ atm = galsim.Atmosphere(10.0, vx=1.0) objs = [galsim.PhaseScreenList(atm), galsim.PhaseScreenList(copy.deepcopy(atm)), # advance down below galsim.PhaseScreenList(objs), # Reuse list of OpticalScreens above galsim.PhaseScreenList(objs[0:2])] objs[1].advance() all_obj_diff(objs) # Test PhaseScreenPSF __ne__ objs[0].reset() psl = galsim.PhaseScreenList(atm) objs = [galsim.PhaseScreenPSF(psl, 500.0, exptime=0.03, diam=1.0), galsim.PhaseScreenPSF(psl, 500.0, exptime=0.03, diam=1.0)] # advanced so differs psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0)] psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.1)] psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, flux=1.1)] psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, interpolant='linear')] stepk = objs[0].stepK() maxk = objs[0].maxK() psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_stepk=stepk/1.5)] psl.reset() objs += [galsim.PhaseScreenPSF(psl, 700.0, exptime=0.03, diam=1.0, _force_maxk=maxk*2.0)] all_obj_diff(objs)