def test_optic(): if __name__ == '__main__': nside = 128 else: nside = 32 rays = batoid.rayGrid(20, 12.0, 0.005, 0.005, -1.0, nside, 500e-9, 1.0, batoid.ConstMedium(1.0)) nrays = len(rays) print("Tracing {} rays.".format(nrays)) t_fast = 0.0 t_slow = 0.0 telescope = batoid.Optic.fromYaml("HSC.yaml") do_pickle(telescope) t0 = time.time() rays_fast = telescope.trace(rays.copy()) t1 = time.time() rays_slow = batoid.RayVector([telescope.trace(r.copy()) for r in rays]) t2 = time.time() assert rays_fast == rays_slow t_fast = t1 - t0 t_slow = t2 - t1 print("Fast trace: {:5.3f} s".format(t_fast)) print(" {} rays per second".format(int(nrays / t_fast))) print("Slow trace: {:5.3f} s".format(t_slow)) print(" {} rays per second".format(int(nrays / t_slow)))
def test_traceReverse(): telescope = batoid.Optic.fromYaml("HSC.yaml") init_rays = batoid.RayVector.asGrid( # backDist=20, lx=8.3, nx=128, backDist=25, lx=8.3, nx=6, theta_x=0.005, theta_y=0.005, wavelength=500e-9, medium=batoid.ConstMedium(1.0)) forward_rays = telescope.trace(init_rays.copy()) # Now, turn the result rays around and trace backwards forward_rays.propagate(40.0) reverse_rays = forward_rays.copy() reverse_rays.vx[:] *= -1 reverse_rays.vy[:] *= -1 reverse_rays.vz[:] *= -1 reverse_rays.t[:] *= -1 final_rays = telescope.trace(reverse_rays.copy(), reverse=True) # propagate all the way to t=0 final_rays.propagate(0.0) final_rays.toCoordSys(batoid.globalCoordSys) w = ~final_rays.vignetted np.testing.assert_allclose(init_rays.x[w], final_rays.x[w]) np.testing.assert_allclose(init_rays.y[w], final_rays.y[w]) np.testing.assert_allclose(init_rays.z[w], final_rays.z[w]) np.testing.assert_allclose(init_rays.vx[w], -final_rays.vx[w]) np.testing.assert_allclose(init_rays.vy[w], -final_rays.vy[w]) np.testing.assert_allclose(init_rays.vz[w], -final_rays.vz[w]) np.testing.assert_allclose(final_rays.t[w], 0)
def test_optic(): if __name__ == '__main__': nside = 128 else: nside = 32 rays = batoid.rayGrid(20, 12.0, 0.005, 0.005, -1.0, nside, 500e-9, 1.0, batoid.ConstMedium(1.0)) nrays = len(rays) print("Tracing {} rays.".format(nrays)) t_fast = 0.0 t_slow = 0.0 fn = os.path.join(batoid.datadir, "HSC", "HSC.yaml") config = yaml.load(open(fn)) telescope = batoid.parse.parse_optic(config['opticalSystem']) do_pickle(telescope) t0 = time.time() rays_fast, _ = telescope.trace(rays) t1 = time.time() rays_slow = batoid.RayVector([telescope.trace(r)[0] for r in rays]) t2 = time.time() assert rays_fast == rays_slow t_fast = t1 - t0 t_slow = t2 - t1 print("Fast trace: {:5.3f} s".format(t_fast)) print(" {} rays per second".format(int(nrays / t_fast))) print("Slow trace: {:5.3f} s".format(t_slow)) print(" {} rays per second".format(int(nrays / t_slow)))
def test_traceReverse(): if __name__ == '__main__': nside = 128 else: nside = 32 telescope = batoid.Optic.fromYaml("HSC.yaml") init_rays = batoid.rayGrid(20, 12.0, 0.005, 0.005, -1.0, nside, 500e-9, 1.0, batoid.ConstMedium(1.0)) forward_rays = telescope.trace(init_rays.copy()) # Now, turn the result rays around and trace backwards forward_rays.propagate(40.0) reverse_rays = batoid.RayVector( [batoid.Ray(r.r, -r.v, -r.t, r.wavelength) for r in forward_rays]) final_rays = telescope.trace(reverse_rays.copy(), reverse=True) # propagate all the way to t=0 final_rays = final_rays.propagate(0.0) final_rays.toCoordSys(batoid.globalCoordSys) w = np.where(np.logical_not(final_rays.vignetted))[0] for idx in w: np.testing.assert_allclose(init_rays[idx].x, final_rays[idx].x) np.testing.assert_allclose(init_rays[idx].y, final_rays[idx].y) np.testing.assert_allclose(init_rays[idx].z, final_rays[idx].z) np.testing.assert_allclose(init_rays[idx].vx, -final_rays[idx].vx) np.testing.assert_allclose(init_rays[idx].vy, -final_rays[idx].vy) np.testing.assert_allclose(init_rays[idx].vz, -final_rays[idx].vz) np.testing.assert_allclose(final_rays[idx].t, 0)
def test_circularGrid(): dist = 10.0 outer = 4.1 inner = 0.5 xcos = 0.1 ycos = 0.2 zcos = -np.sqrt(1.0 - xcos**2 - ycos**2) nradii = 5 naz = 50 wavelength = 500e-9 flux = 1.2 medium = batoid.ConstMedium(1.2) rays = batoid.circularGrid(dist, outer, inner, xcos, ycos, zcos, nradii, naz, wavelength, flux, medium) assert rays.monochromatic == True # Check that all rays are perpendicular to v ray0 = rays[0] for ray in rays: dr = ray.r - ray0.r dp = np.dot(dr, ray0.v) np.testing.assert_allclose(dp, 0.0, atol=1e-14, rtol=0.0) np.testing.assert_allclose(ray.wavelength, wavelength) np.testing.assert_allclose(ray.flux, flux) np.testing.assert_allclose(np.linalg.norm(ray.v), 1./1.2) np.testing.assert_allclose(ray.v[0]*1.2, xcos) np.testing.assert_allclose(ray.v[1]*1.2, ycos)
def test_traceReverse(): if __name__ == '__main__': nside = 128 else: nside = 32 fn = os.path.join(batoid.datadir, "HSC", "HSC.yaml") config = yaml.load(open(fn)) telescope = batoid.parse.parse_optic(config['opticalSystem']) init_rays = batoid.rayGrid(20, 12.0, 0.005, 0.005, -1.0, nside, 500e-9, 1.0, batoid.ConstMedium(1.0)) forward_rays, _ = telescope.trace(init_rays, outCoordSys=batoid.CoordSys()) # Now, turn the result rays around and trace backwards forward_rays = forward_rays.propagatedToTime(40.0) reverse_rays = batoid.RayVector( [batoid.Ray(r.r, -r.v, -r.t, r.wavelength) for r in forward_rays]) final_rays, _ = telescope.traceReverse(reverse_rays, outCoordSys=batoid.CoordSys()) # propagate all the way to t=0 final_rays = final_rays.propagatedToTime(0.0) w = np.where(np.logical_not(final_rays.vignetted))[0] for idx in w: np.testing.assert_allclose(init_rays[idx].x, final_rays[idx].x) np.testing.assert_allclose(init_rays[idx].y, final_rays[idx].y) np.testing.assert_allclose(init_rays[idx].z, final_rays[idx].z) np.testing.assert_allclose(init_rays[idx].vx, -final_rays[idx].vx) np.testing.assert_allclose(init_rays[idx].vy, -final_rays[idx].vy) np.testing.assert_allclose(init_rays[idx].vz, -final_rays[idx].vz) np.testing.assert_allclose(final_rays[idx].t, 0)
def test_rSplit(): rng = np.random.default_rng(5) for _ in range(100): R = rng.normal(0.7, 0.8) conic = rng.uniform(-2.0, 1.0) ncoef = rng.integers(0, 5) coefs = [rng.normal(0, 1e-10) for i in range(ncoef)] asphere = batoid.Asphere(R, conic, coefs) theta_x = rng.normal(0.0, 1e-8) theta_y = rng.normal(0.0, 1e-8) rays = batoid.RayVector.asPolar( backDist=10.0, medium=batoid.Air(), wavelength=500e-9, outer=0.25*R, theta_x=theta_x, theta_y=theta_y, nrad=10, naz=10 ) coating = batoid.SimpleCoating(0.9, 0.1) reflectedRays = asphere.reflect(rays.copy(), coating=coating) m1 = batoid.Air() m2 = batoid.ConstMedium(1.1) refractedRays = asphere.refract(rays.copy(), m1, m2, coating=coating) refractedRays2, reflectedRays2 = asphere.rSplit(rays, m1, m2, coating) rays_allclose(reflectedRays, reflectedRays2) rays_allclose(refractedRays, refractedRays2)
def parse_medium(value): from numbers import Real if isinstance(value, batoid.Medium): return value elif isinstance(value, Real): return batoid.ConstMedium(value) elif isinstance(value, str): if value == 'air': return batoid.Air() elif value == 'silica': return batoid.SellmeierMedium(0.6961663, 0.4079426, 0.8974794, 0.0684043**2, 0.1162414**2, 9.896161**2) elif value == 'hsc_air': return batoid.ConstMedium(1.0) w = [0.4, 0.6, 0.75, 0.9, 1.1] w = [w_ * 1e-6 for w_ in w] if value == 'hsc_silica': return batoid.TableMedium( batoid.Table(w, [ 1.47009272, 1.45801158, 1.45421013, 1.45172729, 1.44917721 ], batoid.Table.Interpolant.linear)) elif value == 'hsc_bsl7y': return batoid.TableMedium( batoid.Table(w, [ 1.53123287, 1.51671428, 1.51225242, 1.50939738, 1.50653251 ], batoid.Table.Interpolant.linear)) elif value == 'hsc_pbl1y': return batoid.TableMedium( batoid.Table(w, [ 1.57046066, 1.54784671, 1.54157395, 1.53789058, 1.53457169 ], batoid.Table.Interpolant.linear)) elif value == 'NLAK10': return batoid.SellmeierMedium(1.72878017, 0.169257825, 1.19386956, 0.00886014635, 0.0363416509, 82.9009069) elif value == 'PFK85': return batoid.SumitaMedium(2.1858326, -0.0050155632, 0.0075107775, 0.00017770562, -1.2164148e-05, 6.1341005e-07) elif value == 'BK7': return batoid.SellmeierMedium(1.03961212, 0.231792344, 1.01046945, 0.00600069867, 0.0200179144, 103.560653) else: raise RuntimeError("Unknown medium {}".format(value))
def test_rayGrid(): dist = 10.0 length = 10.0 xcos = 0.1 ycos = 0.2 zcos = -np.sqrt(1.0 - xcos**2 - ycos**2) nside = 10 wavelength = 500e-9 flux = 1.2 medium = batoid.ConstMedium(1.2) rays = batoid.rayGrid( dist, length, xcos, ycos, zcos, nside, wavelength, flux, medium, lattice=True ) assert rays.monochromatic == True # Check that all rays are perpendicular to v ray0 = rays[0] for ray in rays: dr = ray.r - ray0.r dp = np.dot(dr, ray0.v) np.testing.assert_allclose(dp, 0.0, atol=1e-14, rtol=0.0) np.testing.assert_allclose(ray.wavelength, wavelength) np.testing.assert_allclose(ray.flux, flux) np.testing.assert_allclose(np.linalg.norm(ray.v), 1./1.2) np.testing.assert_allclose(ray.v[0]*1.2, xcos) np.testing.assert_allclose(ray.v[1]*1.2, ycos) # Check that ray that intersects at origin is initially dist away. # Need the ray that is in the middle in both dimensions... idx = np.ravel_multi_index((nside//2, nside//2), (nside, nside)) rays.propagateInPlace(dist*1.2) np.testing.assert_equal(rays[idx].r, [0,0,0]) # but mean position won't be the origin, since lattice implies off-center assert np.linalg.norm(np.mean(rays.r, axis=0)) > 0.5 # Now try again with lattice flag set to False rays = batoid.rayGrid( dist, length, xcos, ycos, zcos, nside, wavelength, flux, medium, lattice=False ) # "Central" ray will not intersect origin in this case, but average of # all rays should be the origin idx = np.ravel_multi_index((nside//2, nside//2), (nside, nside)) rays.propagateInPlace(dist*1.2) assert np.linalg.norm(rays[idx].r) > 0.1 np.testing.assert_allclose(np.linalg.norm(np.mean(rays.r, axis=0)), 0.0, rtol=0, atol=1e-14) # If we use an odd nside, then both the central point and the mean will be the origin. nside = 11 rays = batoid.rayGrid( dist, length, xcos, ycos, zcos, nside, wavelength, flux, medium, lattice=False ) idx = np.ravel_multi_index((nside//2, nside//2), (nside, nside)) rays.propagateInPlace(dist*1.2) np.testing.assert_allclose(rays[idx].r, [0,0,0], rtol=0, atol=1e-14) np.testing.assert_allclose(np.linalg.norm(np.mean(rays.r, axis=0)), 0.0, rtol=0, atol=1e-14)
def test_refract(): rng = np.random.default_rng(5772156) size = 10_000 for i in range(10): jmax = rng.integers(4, 100) coef = rng.normal(size=jmax+1)*1e-3 R_outer = rng.uniform(0.5, 5.0) R_inner = rng.uniform(0.0, 0.65*R_outer) zernike = batoid.Zernike(coef, R_outer=R_outer, R_inner=R_inner) lim = 0.7*R_outer m0 = batoid.ConstMedium(rng.normal(1.2, 0.01)) m1 = batoid.ConstMedium(rng.normal(1.3, 0.01)) x = rng.uniform(-lim, lim, size=size) y = rng.uniform(-lim, lim, size=size) z = np.full_like(x, -10.0) vx = rng.uniform(-1e-5, 1e-5, size=size) vy = rng.uniform(-1e-5, 1e-5, size=size) vz = np.sqrt(1-vx*vx-vy*vy)/m0.n rv = batoid.RayVector(x, y, z, vx, vy, vz, t=0) rvr = batoid.refract(zernike, rv.copy(), m0, m1) rvr2 = zernike.refract(rv.copy(), m0, m1) rays_allclose(rvr, rvr2, atol=1e-13) # print(f"{np.sum(rvr.failed)/len(rvr)*100:.2f}% failed") normal = zernike.normal(rvr.x, rvr.y) # Test Snell's law s0 = np.sum(np.cross(normal, rv.v*m0.n)[~rvr.failed], axis=-1) s1 = np.sum(np.cross(normal, rvr.v*m1.n)[~rvr.failed], axis=-1) np.testing.assert_allclose( m0.n*s0, m1.n*s1, rtol=0, atol=1e-9 ) # Test that rv.v, rvr.v and normal are all in the same plane np.testing.assert_allclose( np.einsum( "ad,ad->a", np.cross(normal, rv.v), rv.v )[~rvr.failed], 0.0, rtol=0, atol=1e-12 )
def test_refract(): rng = np.random.default_rng(5772) for i in range(1000): R = rng.uniform(1.0, 2.0) sphere = batoid.Sphere(R) reflectivity = rng.uniform(0, 1) transmissivity = rng.uniform(0, 1) coating = batoid.SimpleCoating(reflectivity, transmissivity) m1 = batoid.ConstMedium(rng.uniform(1.1, 1.2)) m2 = batoid.ConstMedium(rng.uniform(1.2, 1.3)) rv = batoid.RayVector(0, 0, 10, 0, 0, -1, 0, 500e-9, 1.0) sphere.refract(rv, m1, m2, coating=coating) assert (rv.flux[0] == transmissivity)
def test_refract(): rng = np.random.default_rng(577215) size = 10_000 for i in range(100): zmax = np.inf while zmax > 3.0: R = 0.0 while abs(R) < 15.0: # Don't allow too small radius of curvature R = 1. / rng.normal(0.0, 0.3) # negative allowed conic = rng.uniform(-2.0, 1.0) ncoef = rng.choice(5) coefs = [rng.normal(0, 1e-8) for i in range(ncoef)] asphere = batoid.Asphere(R, conic, coefs) lim = min(0.7 * abs(R) / np.sqrt(1 + conic) if conic > -1 else 5, 5) zmax = abs(asphere.sag(lim, lim)) m0 = batoid.ConstMedium(rng.normal(1.2, 0.01)) m1 = batoid.ConstMedium(rng.normal(1.3, 0.01)) x = rng.uniform(-lim, lim, size=size) y = rng.uniform(-lim, lim, size=size) z = np.full_like(x, -10.0) vx = rng.uniform(-1e-5, 1e-5, size=size) vy = rng.uniform(-1e-5, 1e-5, size=size) vz = np.sqrt(1 - vx * vx - vy * vy) / m0.n rv = batoid.RayVector(x, y, z, vx, vy, vz, t=0) rvr = batoid.refract(asphere, rv.copy(), m0, m1) rvr2 = asphere.refract(rv.copy(), m0, m1) rays_allclose(rvr, rvr2) # print(f"{np.sum(rvr.failed)/len(rvr)*100:.2f}% failed") normal = asphere.normal(rvr.x, rvr.y) # Test Snell's law s0 = np.sum(np.cross(normal, rv.v * m0.n)[~rvr.failed], axis=-1) s1 = np.sum(np.cross(normal, rvr.v * m1.n)[~rvr.failed], axis=-1) np.testing.assert_allclose(m0.n * s0, m1.n * s1, rtol=0, atol=1e-9) # Test that rv.v, rvr.v and normal are all in the same plane np.testing.assert_allclose(np.einsum("ad,ad->a", np.cross(normal, rv.v), rv.v)[~rvr.failed], 0.0, rtol=0, atol=1e-12)
def test_const_medium(): import random random.seed(5) n = 1.4 const_medium = batoid.ConstMedium(n) for i in range(100): w = random.uniform(0.5, 1.0) assert const_medium.getN(w) == n do_pickle(const_medium)
def test_hsc_psf(): # Just testing that doesn't crash for the moment fn = os.path.join(batoid.datadir, "HSC", "HSC.yaml") config = yaml.load(open(fn)) telescope = batoid.parse.parse_optic(config['opticalSystem']) stampSize = 0.75 # arcsec nx = 64 focalLength = 15.0 # guess if __name__ == '__main__': thetas = [0.0, 1350.0, 2700.0] # arcsec else: thetas = [2700.0] for theta in thetas: print(theta / 3600.0) dirCos = batoid.utils.gnomicToDirCos(0.0, theta / 206265) rays = batoid.circularGrid(20.0, 4.1, 0.9, dirCos[0], dirCos[1], -dirCos[2], 10, 100, 620e-9, 1.0, batoid.ConstMedium(1.0)) telescope.traceInPlace(rays) rays.trimVignettedInPlace() xs = rays.x - np.mean(rays.x) ys = rays.y - np.mean(rays.y) xs *= 206265 / focalLength # meters to arcsec ys *= 206265 / focalLength # Need to add half-pixel offset xs += stampSize / nx / 2 ys += stampSize / nx / 2 dx = stampSize / nx * focalLength / 206265 # meters psf = batoid.huygensPSF(telescope, 0.0, theta / 206265, 620e-9, nx=nx, dx=dx, dy=dx) if __name__ == '__main__': import matplotlib.pyplot as plt fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111) ax.imshow(psf.array, extent=np.r_[-1, 1, -1, 1] * stampSize / 2) ax.scatter(xs, ys, s=5, c='r', alpha=0.5) ax.set_title("HSC PSF field={:5.2f}".format(theta / 3600.0)) ax.set_xlabel("arcsec") ax.set_ylabel("arcsec") fig.tight_layout() plt.show()
def test_ConstMedium(): rng = np.random.default_rng(5) for i in range(100): n = rng.uniform(1.0, 2.0) const_medium = batoid.ConstMedium(n) wavelengths = rng.uniform(300e-9, 1100e-9, size=100) for w in wavelengths: assert const_medium.getN(w) == n np.testing.assert_array_equal(const_medium.getN(wavelengths), np.full_like(wavelengths, n)) do_pickle(const_medium)
def test_ne(): filename = os.path.join(batoid.datadir, "media", "silica_dispersion.txt") wave, n = np.genfromtxt(filename).T wave *= 1e-6 # microns -> meters table = batoid.Table(wave, n, batoid.Table.Interpolant.linear) table2 = batoid.Table(wave * 1.01, n, batoid.Table.Interpolant.linear) objs = [ batoid.ConstMedium(1.0), batoid.ConstMedium(1.1), batoid.TableMedium(table), batoid.TableMedium(table2), batoid.SellmeierMedium(0.6961663, 0.4079426, 0.8974794, 0.0684043**2, 0.1162414**2, 9.896161**2), batoid.SellmeierMedium(0.4079426, 0.6961663, 0.8974794, 0.0684043**2, 0.1162414**2, 9.896161**2), batoid.Air(), batoid.Air(pressure=100) ] all_obj_diff(objs)
def test_refract(): rng = np.random.default_rng(577215) size = 10_000 for i in range(100): R = 1./rng.normal(0.0, 0.3) conic = rng.uniform(-2.0, 1.0) quad = batoid.Quadric(R, conic) m0 = batoid.ConstMedium(rng.normal(1.2, 0.01)) m1 = batoid.ConstMedium(rng.normal(1.3, 0.01)) lim = min(0.7*abs(R)/np.sqrt(1+conic) if conic > -1 else 10, 10) x = rng.uniform(-lim, lim, size=size) y = rng.uniform(-lim, lim, size=size) z = np.full_like(x, -100.0) vx = rng.uniform(-1e-5, 1e-5, size=size) vy = rng.uniform(-1e-5, 1e-5, size=size) vz = np.sqrt(1-vx*vx-vy*vy)/m0.n rv = batoid.RayVector(x, y, z, vx, vy, vz) rvr = batoid.refract(quad, rv.copy(), m0, m1) rvr2 = quad.refract(rv.copy(), m0, m1) rays_allclose(rvr, rvr2) # print(f"{np.sum(rvr.failed)/len(rvr)*100:.2f}% failed") normal = quad.normal(rvr.x, rvr.y) # Test Snell's law s0 = np.sum(np.cross(normal, rv.v*m0.n)[~rvr.failed], axis=-1) s1 = np.sum(np.cross(normal, rvr.v*m1.n)[~rvr.failed], axis=-1) np.testing.assert_allclose( m0.n*s0, m1.n*s1, rtol=0, atol=1e-9 ) # Test that rv.v, rvr.v and normal are all in the same plane np.testing.assert_allclose( np.einsum( "ad,ad->a", np.cross(normal, rv.v), rv.v )[~rvr.failed], 0.0, rtol=0, atol=1e-12 )
def test_paraboloid_refraction_plane(): import random random.seed(577) wavelength = 500e-9 # arbitrary para = batoid.Paraboloid(-20.0) m1 = batoid.ConstMedium(1.11) m2 = batoid.ConstMedium(1.32) for i in range(1000): x = random.gauss(0, 1) y = random.gauss(0, 1) vx = random.gauss(0, 1e-1) vy = random.gauss(0, 1e-1) v = normalized(np.array([vx, vy, 1])) / m1.getN(wavelength) ray = batoid.Ray(x, y, -10, v[0], v[1], v[2], 0) rray = para.refract(ray, m1, m2) np.testing.assert_allclose(np.linalg.norm(rray.v), 1. / m2.getN(wavelength), rtol=1e-15) # also check refractInPlace rray2 = batoid.Ray(ray) para.refractInPlace(rray2, m1, m2) assert rray == rray2 # ray.v, surfaceNormal, and rray.v should all be in the same plane, and # hence (ray.v x surfaceNormal) . rray.v should have zero magnitude. # magnitude zero. normal = para.normal(rray.r[0], rray.r[1]) np.testing.assert_allclose(np.dot(np.cross(ray.v, normal), rray.v), 0.0, rtol=0, atol=1e-15) # Test Snell's law np.testing.assert_allclose( m1.getN(wavelength) * np.linalg.norm(np.cross(normalized(ray.v), normal)), m2.getN(wavelength) * np.linalg.norm(np.cross(normalized(rray.v), normal)), rtol=0, atol=1e-15)
def test_asphere_refraction_reversal(): import random random.seed(577215) wavelength = 500e-9 # arbitrary asphere = batoid.Asphere(23.0, -0.97, [1e-5, 1e-6]) m1 = batoid.ConstMedium(1.7) m2 = batoid.ConstMedium(1.9) for i in range(1000): x = random.gauss(0, 1) y = random.gauss(0, 1) vx = random.gauss(0, 1e-1) vy = random.gauss(0, 1e-1) ray = batoid.Ray([x, y, -0.1], normalized(np.array([vx, vy, 1])) / m1.getN(wavelength), 0) rray = asphere.refract(ray, m1, m2) np.testing.assert_allclose(np.linalg.norm(rray.v), 1. / m2.getN(wavelength), rtol=1e-15) # Invert the refracted ray, and see that it ends back at the starting # point # Keep going a bit before turning around though turn_around = rray.positionAtTime(rray.t + 0.1) return_ray = batoid.Ray(turn_around, -rray.v, -(rray.t + 0.1)) riray = asphere.intersect(return_ray) # First check that we intersected at the same point np.testing.assert_allclose(rray.r[0], riray.r[0], rtol=0, atol=1e-10) np.testing.assert_allclose(rray.r[1], riray.r[1], rtol=0, atol=1e-10) np.testing.assert_allclose(rray.r[2], riray.r[2], rtol=0, atol=1e-10) # Refract and propagate back to t=0. cray = asphere.refract(return_ray, m2, m1) np.testing.assert_allclose(np.linalg.norm(cray.v), 1. / m1.getN(wavelength), rtol=1e-15) cpoint = cray.positionAtTime(0) np.testing.assert_allclose(cpoint[0], x, rtol=0, atol=1e-10) np.testing.assert_allclose(cpoint[1], y, rtol=0, atol=1e-10) np.testing.assert_allclose(cpoint[2], -0.1, rtol=0, atol=1e-10)
def test_asphere_refraction_plane(): import random random.seed(57721) wavelength = 500e-9 # arbitrary asphere = batoid.Asphere(25.0, -0.97, [1e-3, 1e-5]) m1 = batoid.ConstMedium(1.7) m2 = batoid.ConstMedium(1.2) for i in range(1000): x = random.gauss(0, 1) y = random.gauss(0, 1) vx = random.gauss(0, 1e-1) vy = random.gauss(0, 1e-1) v = normalized(np.array([vx, vy, 1])) / m1.getN(wavelength) ray = batoid.Ray((x, y, -0.1), (v[0], v[1], v[2]), 0) rray = asphere.refract(ray, m1, m2) np.testing.assert_allclose(np.linalg.norm(rray.v), 1. / m2.getN(wavelength), rtol=1e-14) # also check refractInPlace rray2 = ray.copy() asphere.refractInPlace(rray2, m1, m2) assert rray == rray2 # ray.v, surfaceNormal, and rray.v should all be in the same plane, and # hence (ray.v x surfaceNormal) . rray.v should have zero magnitude. # magnitude zero. normal = asphere.normal(rray.r[0], rray.r[1]) np.testing.assert_allclose(np.dot(np.cross(ray.v, normal), rray.v), 0.0, rtol=0, atol=1e-14) # Test Snell's law np.testing.assert_allclose( m1.getN(wavelength) * np.linalg.norm(np.cross(normalized(ray.v), normal)), m2.getN(wavelength) * np.linalg.norm(np.cross(normalized(rray.v), normal)), rtol=0, atol=1e-15)
def test_inplace(): rays = batoid.rayGrid(10.0, 10.0, 0.1, 0.1, 1.0, 1024, 500e-9, 1.0, batoid.ConstMedium(1.2)) print("propagating {} rays.".format(len(rays))) rays2 = batoid.RayVector(rays) t0 = time.time() outrays = rays.propagatedToTime(11.2) t1 = time.time() rays2.propagateInPlace(11.2) t2 = time.time() print("immutable propagation took {:6.3f} seconds.".format(t1-t0)) print("in-place propagation took {:6.3f} seconds.".format(t2-t1)) assert outrays == rays2
def parse_medium(config): from numbers import Real if config is None: return None if isinstance(config, batoid.Medium): return config if isinstance(config, Real): return batoid.ConstMedium(config) # This dict may be referenced again in an ancestor config, so copy it # before parsing config = dict(**config) typ = config.pop('type') # TableMedium, Sellmeier, ConstMedium, SumitaMedium, Air end up here... evalstr = "batoid.{}(**config)".format(typ) return eval(evalstr)
def test_pointSourceCircularGrid(): source = [0, 1, 10] outer = 0.1 inner = 0.0 nradii = 2 naz = 6 wavelength = 500e-9 flux = 1.0 medium = batoid.ConstMedium(1) rays = batoid.pointSourceCircularGrid(source, outer, inner, nradii, naz, wavelength, flux, medium) # Verify that the central ray is pointed at the origin # (last ray is central if inner==0.0) centerRay = rays[len(rays) - 1] centerRay.propagateInPlace(np.sqrt(101)) np.testing.assert_allclose(centerRay.r, [0, 0, 0], rtol=0, atol=1e-10)
def test_traceFull(): if __name__ == '__main__': nside = 128 else: nside = 32 rays = batoid.rayGrid(20, 12.0, 0.005, 0.005, -1.0, nside, 500e-9, 1.0, batoid.ConstMedium(1.0)) nrays = len(rays) print("Tracing {} rays.".format(nrays)) telescope = batoid.Optic.fromYaml("HSC.yaml") tf = telescope.traceFull(rays) rays = telescope.trace(rays) assert rays == tf['D']['out']
def test_rSplit(): for i in range(100): R = np.random.normal(0.7, 0.8) conic = np.random.uniform(-2.0, 1.0) ncoef = np.random.randint(0, 4) coefs = [np.random.normal(0, 1e-10) for i in range(ncoef)] asphere = batoid.Asphere(R, conic, coefs) rays = batoid.rayGrid(10, 2*R, 0.0, 0.0, -1.0, 16, 500e-9, 1.0, batoid.Air()) coating = batoid.SimpleCoating(0.9, 0.1) reflectedRays = asphere.reflect(rays, coating) m1 = batoid.Air() m2 = batoid.ConstMedium(1.1) refractedRays = asphere.refract(rays, m1, m2, coating) reflectedRays2, refractedRays2 = asphere.rSplit(rays, m1, m2, coating) assert reflectedRays == reflectedRays2 assert refractedRays == refractedRays2
def test_traceFull(): if __name__ == '__main__': nside = 128 else: nside = 32 rays = batoid.rayGrid(20, 12.0, 0.005, 0.005, -1.0, nside, 500e-9, 1.0, batoid.ConstMedium(1.0)) nrays = len(rays) print("Tracing {} rays.".format(nrays)) fn = os.path.join(batoid.datadir, "HSC", "HSC.yaml") config = yaml.load(open(fn)) telescope = batoid.parse.parse_optic(config['opticalSystem']) tf = telescope.traceFull(rays) rays, _ = telescope.trace(rays) assert rays == tf[-1]['out']
def test_optic(): rays = batoid.RayVector.asGrid(backDist=20, lx=12, nx=128, theta_x=0.005, theta_y=0.005, wavelength=500e-9, medium=batoid.ConstMedium(1.0)) nrays = len(rays) print("Tracing {} rays.".format(nrays)) # Do one with full pathname import os filename = os.path.join(batoid.datadir, "HSC", "HSC.yaml") with np.testing.assert_raises(FileNotFoundError): telescope = batoid.Optic.fromYaml(filename + ".gobbledegook") telescope = batoid.Optic.fromYaml(filename) # ... and one without telescope = batoid.Optic.fromYaml("HSC.yaml") do_pickle(telescope) rays1 = telescope.trace(rays.copy()) # Try tracing but skipping the baffles for k, v in telescope.itemDict.items(): if isinstance(v, batoid.Baffle): v.skip = True rays2 = telescope.trace(rays.copy()) # rays2 will have fewer rays vignetted rays assert np.sum(rays2.vignetted) < np.sum(rays1.vignetted) # every place where rays2 is vignetted, is also vignetted in rays1 w = rays2.vignetted assert np.all(rays1.vignetted[w]) # and every place rays1 is not vignetted has some coords as in rays2 w = ~rays1.vignetted np.testing.assert_allclose(rays1.r[w], rays2.r[w]) np.testing.assert_allclose(rays1.v[w], rays2.v[w]) np.testing.assert_allclose(rays1.t[w], rays2.t[w])
def test_uniformCircularGrid(): dist = 10.0 outer = 4.1 inner = 0.5 xcos = 0 ycos = 0 zcos = -1 nray = 100000 wavelength = 500e-9 flux = 1. medium = batoid.ConstMedium(1.) seed = 0 rays = batoid.uniformCircularGrid(dist, outer, inner, xcos, ycos, zcos, nray, wavelength, flux, medium, seed=seed) radius = np.hypot(rays.x, rays.y) angle = np.arctan2(rays.y, rays.x) np.testing.assert_almost_equal(radius.max(), outer, decimal=4) np.testing.assert_almost_equal(radius.min(), inner, decimal=4) np.testing.assert_almost_equal(rays.x.mean(), 0, decimal=2) np.testing.assert_almost_equal(rays.y.mean(), 0, decimal=2) np.testing.assert_almost_equal(angle.mean(), 0, decimal=2) # test radial distribution for cutoff in np.linspace(inner, outer, 5): frac = np.sum(radius < cutoff) / nray expected = (cutoff ** 2 - inner ** 2) / (outer ** 2 - inner ** 2) np.testing.assert_almost_equal(frac, expected, decimal=1) # test seed, reproducibility rays2 = batoid.uniformCircularGrid(dist, outer, inner, xcos, ycos, zcos, nray, wavelength, flux, medium, seed=seed) newseed = 666 rays3 = batoid.uniformCircularGrid(dist, outer, inner, xcos, ycos, zcos, nray, wavelength, flux, medium, seed=newseed) assert np.all(rays.r == rays2.r) assert not np.all(rays2.r == rays3.r)
def parallel_trace_timing(nside=1024, nthread=None, minChunk=None): if nthread is not None: print("setting to nthread to {}".format(nthread)) batoid._batoid.setNThread(nthread) print("Using {} threads".format(batoid._batoid.getNThread())) if minChunk is not None: print("setting to minChunk to {}".format(minChunk)) batoid._batoid.setMinChunk(minChunk) print("Using minChunk of {}".format(batoid._batoid.getMinChunk())) # 0.3, 0.3 should be in bounds for current wide-field telescopes theta_x = np.deg2rad(0.3) theta_y = np.deg2rad(0.3) dirCos = np.array([theta_x, theta_y, -1.0]) dirCos = batoid.utils.normalized(dirCos) rays = batoid.circularGrid( 20, 4.2, 0.5, dirCos[0], dirCos[1], dirCos[2], nside, nside, 700e-9, 1.0, batoid.ConstMedium(1.0) ) nrays = len(rays) print("Tracing {} rays.".format(nrays)) print() if args.lsst: fn = os.path.join(batoid.datadir, "LSST", "LSST_r.yaml") pm = 'LSST.M1' else: fn = os.path.join(batoid.datadir, "HSC", "HSC.yaml") pm = 'SubaruHSC.PM' config = yaml.load(open(fn)) telescope = batoid.parse.parse_optic(config['opticalSystem']) # Optionally perturb the primary mirror using Zernike polynomial if args.perturbZ != 0: orig = telescope.itemDict[pm].surface coefs = np.random.normal(size=args.perturbZ+1)*1e-6 # micron perturbations perturbation = batoid.Zernike(coefs, R_outer=telescope.pupilSize) telescope.itemDict[pm].surface = batoid.Sum([orig, perturbation]) # Optionally perturb primary mirror using bicubic spline if args.perturbBC != 0: orig = telescope.itemDict[pm].surface xs = np.linspace(-5, 5, 100) ys = np.linspace(-5, 5, 100) def f(x, y): return args.perturbBC*(np.cos(x) + np.sin(y)) zs = f(*np.meshgrid(xs, ys)) bc = batoid.Bicubic(xs, ys, zs) telescope.itemDict[pm].surface = batoid.Sum([orig, bc]) print("Immutable trace") t0 = time.time() rays_out, _ = telescope.trace(rays) t1 = time.time() print("{} rays per second".format(int(nrays/(t1-t0)))) print() print("Trace in place") t0 = time.time() telescope.traceInPlace(rays) t1 = time.time() print("{} rays per second".format(int(nrays/(t1-t0)))) assert rays == rays_out if args.plot: import matplotlib.pyplot as plt rays.trimVignettedInPlace() x = rays.x y = rays.y x -= np.mean(x) y -= np.mean(y) x *= 1e6 y *= 1e6 plt.scatter(x, y, s=1, alpha=0.01) plt.xlim(np.std(x)*np.r_[-3,3]) plt.ylim(np.std(y)*np.r_[-3,3]) plt.xlabel("x (microns)") plt.ylabel("y (microns)") plt.show()
def parse_optic(config, coordSys=batoid.CoordSys(), inMedium=batoid.ConstMedium(1.0), outMedium=None): """ @param config configuration dictionary @param coordSys sys to which transformations in config are added @param inMedium default in Medium, often set by optic parent @param outMedium default out Medium, often set by optic parent """ if 'obscuration' in config: obscuration = parse_obscuration(config.pop('obscuration')) else: obscuration = None name = config.pop('name', "") if 'coordSys' in config: coordSys = parse_coordSys(config.pop('coordSys'), coordSys) inMedium = parse_medium(config.pop('inMedium', inMedium)) outMedium = parse_medium(config.pop('outMedium', outMedium)) if outMedium is None: outMedium = inMedium typ = config.pop('type') if typ == 'Mirror': surface = parse_surface(config.pop('surface')) return batoid.optic.Mirror(surface, name=name, coordSys=coordSys, obscuration=obscuration, inMedium=inMedium, outMedium=outMedium) elif typ == 'RefractiveInterface': surface = parse_surface(config.pop('surface')) return batoid.optic.RefractiveInterface(surface, name=name, coordSys=coordSys, obscuration=obscuration, inMedium=inMedium, outMedium=outMedium) elif typ == 'OPDScreen': surface = parse_surface(config.pop('surface')) screen = parse_surface(config.pop('screen')) return batoid.optic.OPDScreen(surface, screen, name=name, coordSys=coordSys, obscuration=obscuration, inMedium=inMedium, outMedium=outMedium) elif typ == 'Baffle': surface = parse_surface(config.pop('surface')) return batoid.optic.Baffle(surface, name=name, coordSys=coordSys, obscuration=obscuration, inMedium=inMedium, outMedium=outMedium) elif typ == 'Detector': surface = parse_surface(config.pop('surface')) return batoid.optic.Detector(surface, name=name, coordSys=coordSys, obscuration=obscuration, inMedium=inMedium, outMedium=outMedium) elif typ == 'Lens': medium = parse_medium(config.pop('medium')) itemsConfig = config.pop('items') items = [ parse_optic(itemsConfig[0], coordSys=coordSys, inMedium=inMedium, outMedium=medium), parse_optic(itemsConfig[1], coordSys=coordSys, inMedium=medium, outMedium=outMedium) ] return batoid.optic.Lens(items, name=name, coordSys=coordSys, inMedium=inMedium, outMedium=outMedium) elif typ == 'CompoundOptic': itemsConfig = config.pop('items') items = [ parse_optic(iC, coordSys=coordSys, inMedium=inMedium, outMedium=outMedium) for iC in itemsConfig ] # Look for a few more possible attributes kwargs = {} for k in ['backDist', 'sphereRadius', 'pupilSize', 'pupilObscuration']: if k in config: kwargs[k] = config[k] if 'stopSurface' in config: kwargs['stopSurface'] = parse_optic(config['stopSurface']) return batoid.optic.CompoundOptic(items, inMedium=inMedium, outMedium=outMedium, name=name, coordSys=coordSys, **kwargs) elif typ == 'Interface': surface = parse_surface(config.pop('surface')) return batoid.optic.Interface(surface, name=name, coordSys=coordSys) else: raise ValueError(f"Unknown optic type: {typ}")