def test_intersect(): import random random.seed(577) for i in range(100): R = random.gauss(25.0, 0.2) conic = random.uniform(-2.0, 1.0) quad = batoid.Quadric(R, conic) for j in range(100): x = random.gauss(0.0, 1.0) y = random.gauss(0.0, 1.0) # If we shoot rays straight up, then it's easy to predict the # intersection points. r0 = batoid.Ray((x, y, -10), (0, 0, 1), 0) r = quad.intersect(r0) np.testing.assert_allclose(r.r[0], x) np.testing.assert_allclose(r.r[1], y) np.testing.assert_allclose(r.r[2], quad.sag(x, y), rtol=0, atol=1e-9) # Check normal for R=0 paraboloid (a plane) quad = batoid.Quadric(0.0, 0.0) np.testing.assert_array_equal(quad.normal(0.1, 0.1), [0, 0, 1])
def test_ne(): objs = [ batoid.Quadric(10.0, 1.0), batoid.Quadric(11.0, 1.0), batoid.Quadric(10.0, 1.1), batoid.Sphere(10.0) ] all_obj_diff(objs)
def test_properties(): np.random.seed(5) for _ in range(100): s1 = batoid.Sphere(np.random.uniform(1, 3)) s2 = batoid.Paraboloid(np.random.uniform(1, 3)) sum = batoid.Sum([s1, s2]) do_pickle(sum) # check commutativity assert sum == batoid.Sum([s2, s1]) # order of sum.surfaces is not guaranteed assert s1 in sum.surfaces assert s2 in sum.surfaces s3 = batoid.Quadric(np.random.uniform(3, 5), np.random.uniform(-0.1, 0.1)) sum2 = batoid.Sum([s1, s2, s3]) do_pickle(sum2) # check commutativity assert sum2 == batoid.Sum([s2, s3, s1]) assert sum2 == batoid.Sum([s3, s1, s2]) assert sum2 == batoid.Sum([s3, s2, s1]) assert sum2 == batoid.Sum([s2, s1, s3]) assert sum2 == batoid.Sum([s1, s3, s2]) assert s1 in sum2.surfaces assert s2 in sum2.surfaces assert s3 in sum2.surfaces do_pickle(sum)
def test_sag(): np.random.seed(57) for _ in range(100): s1 = batoid.Sphere(np.random.uniform(1, 3)) s2 = batoid.Paraboloid(np.random.uniform(1, 3)) sum = batoid.Sum([s1, s2]) x = np.random.normal(size=5000) y = np.random.normal(size=5000) np.testing.assert_allclose( sum.sag(x, y), s1.sag(x, y) + s2.sag(x, y), rtol=1e-12, atol=1e-12 ) s3 = batoid.Quadric(np.random.uniform(3, 5), np.random.uniform(-0.1, 0.1)) sum2 = batoid.Sum([s1, s2, s3]) np.testing.assert_allclose( sum2.sag(x, y), s1.sag(x, y) + s2.sag(x, y) + s3.sag(x, y), rtol=1e-12, atol=1e-12 )
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_reflect(): rng = np.random.default_rng(57721) 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) 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.full_like(x, 1) rv = batoid.RayVector(x, y, z, vx, vy, vz) rvr = batoid.reflect(quad, rv.copy()) rvr2 = quad.reflect(rv.copy()) rays_allclose(rvr, rvr2) # print(f"{np.sum(rvr.failed)/len(rvr)*100:.2f}% failed") normal = quad.normal(rvr.x, rvr.y) # Test law of reflection a0 = np.einsum("ad,ad->a", normal, rv.v)[~rvr.failed] a1 = np.einsum("ad,ad->a", normal, -rvr.v)[~rvr.failed] np.testing.assert_allclose(a0, a1, rtol=0, atol=1e-12) # 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_intersect(): rng = np.random.default_rng(5772) 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) quadCoordSys = batoid.CoordSys(origin=[0, 0, -1]) 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) # If we shoot rays straight up, then it's easy to predict the intersection vx = np.zeros_like(x) vy = np.zeros_like(x) vz = np.ones_like(x) rv = batoid.RayVector(x, y, z, vx, vy, vz) np.testing.assert_allclose(rv.z, -100.0) rv2 = batoid.intersect(quad, rv.copy(), quadCoordSys) assert rv2.coordSys == quadCoordSys rv2 = rv2.toCoordSys(batoid.CoordSys()) np.testing.assert_allclose(rv2.x, x) np.testing.assert_allclose(rv2.y, y) np.testing.assert_allclose(rv2.z, quad.sag(x, y)-1, rtol=0, atol=1e-9) # Check default intersect coordTransform rv2 = rv.copy().toCoordSys(quadCoordSys) batoid.intersect(quad, rv2) assert rv2.coordSys == quadCoordSys rv2 = rv2.toCoordSys(batoid.CoordSys()) np.testing.assert_allclose(rv2.x, x) np.testing.assert_allclose(rv2.y, y) np.testing.assert_allclose(rv2.z, quad.sag(x, y)-1, rtol=0, atol=1e-9)
def test_normal(): rng = np.random.default_rng(577) 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) for j in range(10): lim = 0.7*abs(R)/np.sqrt(1+conic) if conic > -1 else 1 x = rng.uniform(-lim, lim) y = rng.uniform(-lim, lim) result = quad.normal(x, y) np.testing.assert_allclose( result, quadric_normal(R, conic)(x, y)[0,0] ) # Check 0,0 np.testing.assert_equal(quad.normal(0, 0), np.array([0, 0, 1])) # Check vectorization x = rng.uniform(-lim, lim, size=(10, 10)) y = rng.uniform(-lim, lim, size=(10, 10)) np.testing.assert_allclose( quad.normal(x, y), quadric_normal(R, conic)(x, y) ) # Make sure non-unit stride arrays also work np.testing.assert_allclose( quad.normal(x[::5,::2], y[::5,::2]), quadric_normal(R, conic)(x, y)[::5, ::2] )
def test_fail(): quad = batoid.Quadric(1.0, 1.0) ray = batoid.Ray([0, 0, -1], [0, 0, -1]) ray = quad.intersect(ray) assert ray.failed ray = batoid.Ray([0, 0, -1], [0, 0, -1]) quad.intersectInPlace(ray) assert ray.failed
def test_properties(): rng = np.random.default_rng(5) for i in range(100): R = rng.normal(0.0, 0.3) # negative allowed conic = rng.uniform(-2.0, 1.0) quad = batoid.Quadric(R, conic) assert quad.R == R assert quad.conic == conic do_pickle(quad)
def test_fail(): quad = batoid.Quadric(1.0, 0.0) rv = batoid.RayVector(0, 10, 0, 0, 0, -1) # Too far to side rv2 = batoid.intersect(quad, rv.copy()) np.testing.assert_equal(rv2.failed, np.array([True])) # This one passes rv = batoid.RayVector(0, 0, -1, 0, 0, -1) rv2 = batoid.intersect(quad, rv.copy()) np.testing.assert_equal(rv2.failed, np.array([False]))
def test_ne(): objs = [ batoid.Zernike([0,0,0,0,1]), batoid.Zernike([0,0,0,1]), batoid.Zernike([0,0,0,0,1], R_outer=1.1), batoid.Zernike([0,0,0,0,1], R_inner=0.8), batoid.Zernike([0,0,0,0,1], R_outer=1.1, R_inner=0.8), batoid.Quadric(10.0, 1.0) ] all_obj_diff(objs)
def test_properties(): import random random.seed(5) for i in range(100): R = random.gauss(0.7, 0.8) conic = random.uniform(-2.0, 1.0) quad = batoid.Quadric(R, conic) assert quad.R == R assert quad.conic == conic do_pickle(quad)
def test_ne(): objs = [ batoid.Asphere(10.0, 1.0, []), batoid.Asphere(10.0, 1.0, [0]), batoid.Asphere(10.0, 1.0, [0,1]), batoid.Asphere(10.0, 1.0, [1,0]), batoid.Asphere(10.0, 1.1, []), batoid.Asphere(10.1, 1.0, []), batoid.Quadric(10.0, 1.0) ] all_obj_diff(objs)
def test_quad_plus_poly(): import random random.seed(5772) for i in range(100): R = random.gauss(25.0, 0.2) conic = random.uniform(-2.0, 1.0) ncoefs = random.randint(0, 4) coefs = [random.gauss(0, 1e-10) for i in range(ncoefs)] asphere = batoid.Asphere(R, conic, coefs) quad = batoid.Quadric(R, conic) poly = py_poly(coefs) for j in range(100): x = random.gauss(0.0, 1.0) y = random.gauss(0.0, 1.0) np.testing.assert_allclose(asphere.sag(x, y), quad.sag(x, y)+poly(x, y))
def test_paraboloid(): rng = np.random.default_rng(57721566) size = 1000 for i in range(100): R = 1/rng.normal(0.0, 0.3) conic = -1.0 quad = batoid.Quadric(R, conic) para = batoid.Paraboloid(R) x = rng.uniform(-0.7*R, 0.7*R, size=size) y = rng.uniform(-0.7*R, 0.7*R, size=size) np.testing.assert_allclose( quad.sag(x,y), para.sag(x, y), rtol=0, atol=1e-11 ) np.testing.assert_allclose( quad.normal(x,y), para.normal(x, y), rtol=0, atol=1e-11 )
def test_properties(): rng = np.random.default_rng(5) for i in range(100): s1 = batoid.Sphere(rng.uniform(1, 3)) s2 = batoid.Paraboloid(rng.uniform(1, 3)) sum = batoid.Sum([s1, s2]) do_pickle(sum) assert s1 is sum.surfaces[0] assert s2 is sum.surfaces[1] s3 = batoid.Quadric(rng.uniform(3, 5), rng.uniform(-0.1, 0.1)) sum2 = batoid.Sum([s1, s2, s3]) do_pickle(sum2) assert s1 is sum2.surfaces[0] assert s2 is sum2.surfaces[1] assert s3 is sum2.surfaces[2]
def test_sphere(): rng = np.random.default_rng(5772156) size = 1000 for i in range(100): R = 1/rng.normal(0.0, 0.3) conic = 0.0 quad = batoid.Quadric(R, conic) sphere = batoid.Sphere(R) x = rng.uniform(-0.7*R, 0.7*R, size=size) y = rng.uniform(-0.7*R, 0.7*R, size=size) np.testing.assert_allclose( quad.sag(x,y), sphere.sag(x, y), rtol=0, atol=1e-11 ) np.testing.assert_allclose( quad.normal(x,y), sphere.normal(x, y), rtol=0, atol=1e-11 )
def test_properties(): np.random.seed(5) for _ in range(100): s1 = batoid.Sphere(np.random.uniform(1, 3)) s2 = batoid.Paraboloid(np.random.uniform(1, 3)) sum = batoid.Sum([s1, s2]) do_pickle(sum) assert s1 is sum.surfaces[0] assert s2 is sum.surfaces[1] s3 = batoid.Quadric(np.random.uniform(3, 5), np.random.uniform(-0.1, 0.1)) sum2 = batoid.Sum([s1, s2, s3]) do_pickle(sum2) assert s1 is sum2.surfaces[0] assert s2 is sum2.surfaces[1] assert s3 is sum2.surfaces[2] do_pickle(sum)
def test_sag(): rng = np.random.default_rng(57) 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) for j in range(100): lim = 0.7 * abs(R) / np.sqrt(1 + conic) if conic > -1 else 1 x = rng.uniform(-lim, lim) y = rng.uniform(-lim, lim) result = quad.sag(x, y) np.testing.assert_allclose(result, quadric_sag(R, conic)(x, y)) # Check that it returned a scalar float and not an array assert isinstance(result, float) # Check vectorization x = rng.uniform(-lim, lim, size=(10, 10)) y = rng.uniform(-lim, lim, size=(10, 10)) np.testing.assert_allclose(quad.sag(x, y), quadric_sag(R, conic)(x, y)) # Make sure non-unit stride arrays also work np.testing.assert_allclose(quad.sag(x[::5, ::2], y[::5, ::2]), quadric_sag(R, conic)(x, y)[::5, ::2])
def test_sag(): import random random.seed(57) for i in range(100): R = random.gauss(25.0, 0.2) conic = random.uniform(-2.0, 1.0) quad = batoid.Quadric(R, conic) for j in range(100): x = random.gauss(0.0, 1.0) y = random.gauss(0.0, 1.0) result = quad.sag(x, y) np.testing.assert_allclose(result, quadric(R, conic)(x, y)) # Check that it returned a scalar float and not an array assert isinstance(result, float) # Check vectorization x = np.random.normal(0.0, 1.0, size=(10, 10)) y = np.random.normal(0.0, 1.0, size=(10, 10)) np.testing.assert_allclose(quad.sag(x, y), quadric(R, conic)(x, y)) # Make sure non-unit stride arrays also work np.testing.assert_allclose(quad.sag(x[::5, ::2], y[::5, ::2]), quadric(R, conic)(x, y)[::5, ::2])
def test_intersect_vectorized(): import random random.seed(5772) r0s = [ batoid.Ray([ random.gauss(0.0, 0.1), random.gauss(0.0, 0.1), random.gauss(10.0, 0.1) ], [ random.gauss(0.0, 0.1), random.gauss(0.0, 0.1), random.gauss(-1.0, 0.1) ], random.gauss(0.0, 0.1)) for i in range(1000) ] r0s = batoid.RayVector(r0s) for i in range(100): R = random.gauss(25.0, 0.2) conic = random.uniform(-2.0, 1.0) quad = batoid.Quadric(R, conic) r1s = quad.intersect(r0s) r2s = batoid.RayVector([quad.intersect(r0) for r0 in r0s]) assert r1s == r2s
def test_normal(): rng = np.random.default_rng(577) for _ in range(100): s1 = batoid.Sphere(rng.uniform(1, 3)) s2 = batoid.Paraboloid(rng.uniform(1, 3)) sum = batoid.Sum([s1, s2]) x = rng.normal(size=5000) y = rng.normal(size=5000) n1 = s1.normal(x, y) n2 = s2.normal(x, y) nx = n1[:, 0] / n1[:, 2] + n2[:, 0] / n2[:, 2] ny = n1[:, 1] / n1[:, 2] + n2[:, 1] / n2[:, 2] nz = 1. / np.sqrt(nx * nx + ny * ny + 1) nx *= nz ny *= nz normal = np.array([nx, ny, nz]).T np.testing.assert_allclose(sum.normal(x, y), normal, rtol=0, atol=1e-12) s3 = batoid.Quadric(rng.uniform(3, 5), rng.uniform(-0.1, 0.1)) sum2 = batoid.Sum([s1, s2, s3]) n3 = s3.normal(x, y) nx = n1[:, 0] / n1[:, 2] + n2[:, 0] / n2[:, 2] + n3[:, 0] / n3[:, 2] ny = n1[:, 1] / n1[:, 2] + n2[:, 1] / n2[:, 2] + n3[:, 1] / n3[:, 2] nz = 1. / np.sqrt(nx * nx + ny * ny + 1) nx *= nz ny *= nz normal = np.array([nx, ny, nz]).T np.testing.assert_allclose(sum2.normal(x, y), normal, rtol=0, atol=1e-12)
def test_zeroscreen(): """Add a zero phase OPDScreen in front of LSST entrance pupil. Should have _no_ effect. """ lsst = batoid.Optic.fromYaml("LSST_r.yaml") screens = [ batoid.optic.OPDScreen( batoid.Plane(), batoid.Plane(), name='PS', coordSys=lsst.stopSurface.coordSys ), batoid.optic.OPDScreen( batoid.Paraboloid(100.0), batoid.Plane(), name='PS', coordSys=lsst.stopSurface.coordSys ), batoid.optic.OPDScreen( batoid.Quadric(11.0, -0.5), batoid.Plane(), name='PS', coordSys=lsst.stopSurface.coordSys ), batoid.optic.OPDScreen( batoid.Zernike([0, 0, 0, 0, 300e-9, 0, 0, 400e-9, -600e-9]), batoid.Zernike([0]*22), name='PS', coordSys=lsst.stopSurface.coordSys ) ] for screen in screens: tel = batoid.CompoundOptic( (screen, *lsst.items), name='PS0', backDist=lsst.backDist, pupilSize=lsst.pupilSize, inMedium=lsst.inMedium, stopSurface=lsst.stopSurface, sphereRadius=lsst.sphereRadius, pupilObscuration=lsst.pupilObscuration ) do_pickle(tel) rng = np.random.default_rng(57) thx = np.deg2rad(rng.uniform(-1, 1)) thy = np.deg2rad(rng.uniform(-1, 1)) rays = batoid.RayVector.asPolar( optic=tel, wavelength=620e-9, theta_x=thx, theta_y=thy, nrad=5, naz=60 ) tf1 = tel.traceFull(rays) tf2 = lsst.traceFull(rays) np.testing.assert_allclose( tf1['PS']['in'].v, tf1['PS']['out'].v, rtol=0, atol=1e-14 ) for key in tf2: rays_allclose( tf1[key]['out'], tf2[key]['out'], atol=1e-13 )