def test_sk_shoot(): """Test SecondKick with photon shooting. Particularly the flux of the final image. """ rng = galsim.BaseDeviate(1234) obj = galsim.SecondKick(lam=500, r0=0.2, diam=4, flux=1.e4) im = galsim.Image(500, 500, scale=1) im.setCenter(0, 0) added_flux, photons = obj.drawPhot(im, poisson_flux=False, rng=rng.duplicate()) print('obj.flux = ', obj.flux) print('added_flux = ', added_flux) print('photon fluxes = ', photons.flux.min(), '..', photons.flux.max()) print('image flux = ', im.array.sum()) assert np.isclose(added_flux, obj.flux) assert np.isclose(im.array.sum(), obj.flux) photons2 = obj.makePhot(poisson_flux=False, rng=rng.duplicate()) assert photons2 == photons, "SecondKick makePhot not equivalent to drawPhot" # Can treat the profile as a convolution of a delta function and put it in a photon_ops list. delta = galsim.DeltaFunction(flux=1.e4) psf = galsim.SecondKick(lam=500, r0=0.2, diam=4) photons3 = delta.makePhot(poisson_flux=False, rng=rng.duplicate(), photon_ops=[psf]) np.testing.assert_allclose(photons3.x, photons.x) np.testing.assert_allclose(photons3.y, photons.y) np.testing.assert_allclose(photons3.flux, photons.flux)
def test_sk_scale(): """Test sk scale argument""" kwargs = { 'lam': 500, 'r0': 0.2, 'diam': 4.0, 'flux': 2.2, 'obscuration': 0.3 } sk_arcsec = galsim.SecondKick(scale_unit=galsim.arcsec, **kwargs) sk_arcmin = galsim.SecondKick(scale_unit='arcmin', **kwargs) do_pickle(sk_arcsec) do_pickle(sk_arcmin) np.testing.assert_almost_equal(sk_arcsec.flux, sk_arcmin.flux) np.testing.assert_almost_equal(sk_arcsec.kValue(0.0, 0.0), sk_arcmin.kValue(0.0, 0.0)) np.testing.assert_almost_equal(sk_arcsec.kValue(0.0, 1.0), sk_arcmin.kValue(0.0, 60.0)) np.testing.assert_almost_equal(sk_arcsec.kValue(0.0, 10.0), sk_arcmin.kValue(0.0, 600.0)) np.testing.assert_almost_equal( sk_arcsec._sbs.xValue(galsim.PositionD(0.0, 6.0)._p), sk_arcmin._sbs.xValue(galsim.PositionD(0.0, 0.1)._p) / 60**2, decimal=5) np.testing.assert_almost_equal( sk_arcsec._sbs.xValue(galsim.PositionD(0.0, 0.6)._p), sk_arcmin._sbs.xValue(galsim.PositionD(0.0, 0.01)._p) / 60**2, decimal=5) np.testing.assert_almost_equal( sk_arcsec._sba.xValue(galsim.PositionD(0.0, 6.0)._p), sk_arcmin._sba.xValue(galsim.PositionD(0.0, 0.1)._p) / 60**2, decimal=5) np.testing.assert_almost_equal( sk_arcsec._sba.xValue(galsim.PositionD(0.0, 0.6)._p), sk_arcmin._sba.xValue(galsim.PositionD(0.0, 0.01)._p) / 60**2, decimal=5) if __name__ == '__main__': # These are a little slow, since the xValue is doing a real-space convolution integral. np.testing.assert_almost_equal(sk_arcsec.xValue(0.0, 6.0), sk_arcmin.xValue(0.0, 0.1) / 60**2, decimal=5) np.testing.assert_almost_equal(sk_arcsec.xValue(0.0, 1.2), sk_arcmin.xValue(0.0, 0.02) / 60**2, decimal=5) img1 = sk_arcsec.drawImage(nx=32, ny=32, scale=0.2) img2 = sk_arcmin.drawImage(nx=32, ny=32, scale=0.2 / 60.0) np.testing.assert_almost_equal(img1.array, img2.array) # Also check that default flux works del kwargs['flux'] sk_arcsec = galsim.SecondKick(scale_unit=galsim.arcsec, **kwargs) sk_arcmin = galsim.SecondKick(scale_unit='arcmin', **kwargs) do_pickle(sk_arcsec) do_pickle(sk_arcmin) np.testing.assert_almost_equal(sk_arcmin.flux, 1.0) np.testing.assert_almost_equal(sk_arcsec.flux, 1.0)
def test_init(): """Test generation of SecondKick profiles """ obscuration = 0.5 if __name__ == '__main__': lams = [300.0, 500.0, 1100.0] r0_500s = [0.1, 0.15, 0.3] kcrits = [0.1, 0.2, 0.4] else: lams = [500.0] r0_500s = [0.15] kcrits = [0.2] for lam in lams: for r0_500 in r0_500s: r0 = r0_500 * (lam / 500)**(6. / 5) for kcrit in kcrits: t0 = time.time() kwargs = {'lam': lam, 'r0': r0, 'kcrit': kcrit, 'diam': 4.0} print(kwargs) sk = galsim.SecondKick(flux=2.2, **kwargs) t1 = time.time() print(' stepk, maxk = ', sk.stepk, sk.maxk) np.testing.assert_almost_equal(sk.flux, 2.2) do_pickle(sk) t2 = time.time() gsp = galsim.GSParams(xvalue_accuracy=1.e-8, kvalue_accuracy=1.e-8) sk2 = galsim.SecondKick(flux=2.2, gsparams=gsp, **kwargs) assert sk2 != sk assert sk2 == sk.withGSParams(gsp) assert sk2 == sk.withGSParams(xvalue_accuracy=1.e-8, kvalue_accuracy=1.e-8) # Raw sk objects are hard to draw due to a large maxk/stepk ratio. # Decrease maxk by convolving in a smallish Gaussian. obj = galsim.Convolve(sk, galsim.Gaussian(fwhm=0.2)) print(' obj stepk, maxk = ', obj.stepk, obj.maxk) check_basic(obj, "SecondKick") t3 = time.time() img = galsim.Image(16, 16, scale=0.2) do_shoot(obj, img, "SecondKick") t4 = time.time() do_kvalue(obj, img, "SecondKick") t5 = time.time() print(' times = ', t1 - t0, t2 - t1, t3 - t2, t4 - t3, t5 - t4) # Check negative flux sk2 = galsim.SecondKick(flux=-2.2, **kwargs) sk3 = sk.withFlux(-2.2) assert sk2 == sk3 obj2 = galsim.Convolve(sk2, galsim.Gaussian(fwhm=0.2)) check_basic(obj2, "SecondKick with negative flux")
def test_limiting_cases(): """SecondKick has some two interesting limiting cases. A) When kcrit = 0, SecondKick = Convolve(Airy, VonKarman). B) When kcrit = inf, SecondKick = Airy Test these. """ lam = 500.0 r0 = 0.2 diam = 8.36 obscuration = 0.61 # First kcrit=0 sk = galsim.SecondKick(lam, r0, diam, obscuration, kcrit=0.0) limiting_case = galsim.Convolve( galsim.VonKarman(lam, r0, L0=1.e8), galsim.Airy(lam=lam, diam=diam, obscuration=obscuration) ) print(sk.stepk, sk.maxk) print(limiting_case.stepk, limiting_case.maxk) for k in [0.0, 0.1, 0.3, 1.0, 3.0, 10.0, 20.0]: print(sk.kValue(0, k).real, limiting_case.kValue(0, k).real) np.testing.assert_allclose( sk.kValue(0, k).real, limiting_case.kValue(0, k).real, rtol=1e-3, atol=1e-4) # Normally, one wouldn't use SecondKick.xValue, since it does a real-space convolution, # so it's slow. But we do allow it, so test it here. import time t0 = time.time() xv_2k = sk.xValue(0,0) print("xValue(0,0) = ",xv_2k) t1 = time.time() # The VonKarman * Airy xValue is much slower still, so don't do that. # Instead compare it to the 'sb' image. xv_image = limiting_case.drawImage(nx=1,ny=1,method='sb',scale=0.1)(1,1) print('from image ',xv_image) t2 = time.time() print('t = ',t1-t0, t2-t1) np.testing.assert_almost_equal(xv_2k, xv_image, decimal=3) # kcrit=inf sk = galsim.SecondKick(lam, r0, diam, obscuration, kcrit=np.inf) limiting_case = galsim.Airy(lam=lam, diam=diam, obscuration=obscuration) for k in [0.0, 0.1, 0.3, 1.0, 3.0, 10.0, 20.0]: print(sk.kValue(0, k).real, limiting_case.kValue(0, k).real) np.testing.assert_allclose( sk.kValue(0, k).real, limiting_case.kValue(0, k).real, rtol=1e-3, atol=1e-4)
def test_sk_ne(): gsp = galsim.GSParams(maxk_threshold=1.1e-3, folding_threshold=5.1e-3) objs = [galsim.SecondKick(lam=500.0, r0=0.2, diam=4.0), galsim.SecondKick(lam=550.0, r0=0.2, diam=4.0), galsim.SecondKick(lam=500.0, r0=0.25, diam=4.0), galsim.SecondKick(lam=500.0, r0=0.25, diam=4.2), galsim.SecondKick(lam=500.0, r0=0.25, diam=4.2, obscuration=0.4), galsim.SecondKick(lam=500.0, r0=0.25, diam=4.2, kcrit=1.234), galsim.SecondKick(lam=500.0, r0=0.25, diam=4.2, flux=2.2), galsim.SecondKick(lam=500.0, r0=0.25, diam=4.2, scale_unit='arcmin'), galsim.SecondKick(lam=500.0, r0=0.2, diam=4.0, gsparams=gsp)] all_obj_diff(objs)
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_sk_shoot(): """Test SecondKick with photon shooting. Particularly the flux of the final image. """ rng = galsim.BaseDeviate(1234) obj = galsim.SecondKick(lam=500, r0=0.2, diam=4, flux=1.e4) im = galsim.Image(500, 500, scale=1) im.setCenter(0, 0) added_flux, photons = obj.drawPhot(im, poisson_flux=False, rng=rng) print('obj.flux = ', obj.flux) print('added_flux = ', added_flux) print('photon fluxes = ', photons.flux.min(), '..', photons.flux.max()) print('image flux = ', im.array.sum()) assert np.isclose(added_flux, obj.flux) assert np.isclose(im.array.sum(), obj.flux)
def test_structure_function(): """Test that SecondKick structure function is equivalent to vonKarman structure function when kcrit=0. This is nontrivial since the SecondKick structure function is numerically integrated, while the vK structure function is evaluated analytically. """ lam = 500.0 r0 = 0.2 diam = 8.36 obscuration = 0.61 sk = galsim.SecondKick(lam, r0, diam, obscuration, kcrit=0.0) vk = galsim.VonKarman(lam, r0, L0=1.e10) for rho in [0.01, 0.03, 0.1, 0.3, 1.0, 3.0]: sksf = sk._structure_function(rho / r0) vksf = vk._structure_function(rho) print(sksf, vksf) np.testing.assert_allclose(sksf, vksf, rtol=2e-3, atol=1.e-3)
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)