def test_center_and_dimensions(self): e = Ellipse(UnitVector3d.X(), UnitVector3d.Y(), Angle(2 * math.pi / 3)) self.assertAlmostEqual(e.getF1().dot(UnitVector3d.X()), 1.0) self.assertAlmostEqual(e.getF2().dot(UnitVector3d.Y()), 1.0) self.assertAlmostEqual(e.getAlpha(), Angle(2 * math.pi / 3)) f = Ellipse(UnitVector3d.X(), Angle(math.pi / 3), Angle(math.pi / 6), Angle(0)) self.assertEqual(f.getCenter(), UnitVector3d.X())
def test_relationships(self): e = Ellipse(UnitVector3d.X(), Angle(math.pi / 3), Angle(math.pi / 6), Angle(0)) self.assertTrue(e.contains(UnitVector3d.X())) self.assertTrue(UnitVector3d.X() in e) c = Circle(UnitVector3d.X(), Angle(math.pi / 2)) self.assertEqual(c.relate(e), CONTAINS) self.assertEqual(e.relate(c), WITHIN)
def testComparisonOperators(self): a1 = Angle(1) a2 = Angle(2) self.assertNotEqual(a1, a2) self.assertLess(a1, a2) self.assertLessEqual(a1, a2) self.assertGreater(a2, a1) self.assertGreaterEqual(a2, a1)
def testArithmeticOperators(self): a = Angle(1) b = -a self.assertEqual(a + b, Angle(0)) self.assertEqual(a - b, 2.0 * a) self.assertEqual(a - b, a * 2.0) self.assertEqual(a / 1.0, a) self.assertEqual(a / a, 1.0) a += a a *= 2 a -= b a /= 5 self.assertEqual(a.asRadians(), 1)
def test_construction(self): self.assertTrue(Circle.empty().isEmpty()) self.assertTrue(Circle().isEmpty()) self.assertTrue(Circle.full().isFull()) c = Circle(UnitVector3d.X()) self.assertEqual(c.getOpeningAngle(), Angle(0)) self.assertEqual(c.getSquaredChordLength(), 0) c = Circle(UnitVector3d.Z(), 2.0) self.assertTrue(c.contains(UnitVector3d.Z())) c = Circle(UnitVector3d.Z(), Angle(math.pi)) self.assertTrue(c.isFull()) d = c.clone() self.assertEqual(c, d) self.assertNotEqual(id(c), id(d)) e = Circle(d) self.assertEqual(d, e)
def test_construction(self): self.assertTrue(Ellipse.empty().isEmpty()) self.assertTrue(Ellipse().isEmpty()) self.assertTrue(Ellipse.full().isFull()) e = Ellipse(Circle(UnitVector3d.X(), Angle(math.pi / 2))) f = Ellipse(UnitVector3d.X(), Angle(math.pi / 2)) self.assertEqual(e, f) self.assertEqual(e.getAlpha(), e.getBeta()) self.assertTrue(e.isCircle()) self.assertTrue(e.isGreatCircle()) g = Ellipse(e) h = e.clone() self.assertEqual(e, g) self.assertEqual(g, h) self.assertNotEqual(id(e), id(g)) self.assertNotEqual(id(g), id(h))
def test_dilation_and_erosion(self): a = Angle(math.pi / 2) c = Circle(UnitVector3d.X()) d = c.dilatedBy(a).erodedBy(a) c.dilateBy(a).erodeBy(a) self.assertEqual(c, d) self.assertEqual(c, Circle(UnitVector3d.X()))
def _makePixelRanges(): """Generate pixel ID ranges for some envelope region""" pointing_v = UnitVector3d(1., 1., -1.) fov = 0.05 # radians region = Circle(pointing_v, Angle(fov / 2)) pixelator = HtmPixelization(HTM_LEVEL) indices = pixelator.envelope(region, 128) return indices.ranges()
def testConstruction(self): a1 = Angle(1.0) a2 = Angle.fromRadians(1.0) a3 = Angle.fromDegrees(57.29577951308232) self.assertEqual(a1, a2) self.assertEqual(a1.asRadians(), 1.0) self.assertEqual(a1, a3) self.assertEqual(a1.asDegrees(), 57.29577951308232)
def testArithmeticOperators(self): a = NormalizedAngle(1) b = -a self.assertEqual(a + b, Angle(0)) self.assertEqual(a - b, 2.0 * a) self.assertEqual(a - b, a * 2.0) self.assertEqual(a / 1.0, a) self.assertEqual(a / a, 1.0)
def test_string(self): c = Circle(UnitVector3d.Z(), Angle(1.0)) self.assertEqual(str(c), 'Circle([0.0, 0.0, 1.0], 1.0)') self.assertEqual(repr(c), 'Circle(UnitVector3d(0.0, 0.0, 1.0), Angle(1.0))') self.assertEqual( c, eval(repr(c), dict(Angle=Angle, Circle=Circle, UnitVector3d=UnitVector3d)))
def test_construction(self): b = Box(Box.allLongitudes(), Box.allLatitudes()) self.assertTrue(b.isFull()) b = Box.fromDegrees(-90, -45, 90, 45) self.assertEqual(b, Box(b.getLon(), b.getLat())) a = Box.fromRadians(-0.5 * math.pi, -0.25 * math.pi, 0.5 * math.pi, 0.25 * math.pi) b = Box(LonLat.fromRadians(-0.5 * math.pi, -0.25 * math.pi), LonLat.fromRadians(0.5 * math.pi, 0.25 * math.pi)) c = Box(LonLat.fromRadians(0, 0), Angle(0.5 * math.pi), Angle(0.25 * math.pi)) d = c.clone() self.assertEqual(a, b) self.assertEqual(b, c) self.assertEqual(c, d) self.assertNotEqual(id(c), id(d)) b = Box() self.assertTrue(b.isEmpty()) self.assertTrue(Box.empty().isEmpty()) self.assertTrue(Box.full().isFull())
def test_string(self): c = Ellipse(UnitVector3d.Z(), Angle(1.0)) self.assertEqual(str(c), 'Ellipse([0.0, 0.0, 1.0], [0.0, 0.0, 1.0], 1.0)') self.assertEqual( repr(c), 'Ellipse(UnitVector3d(0.0, 0.0, 1.0), ' 'UnitVector3d(0.0, 0.0, 1.0), Angle(1.0))') self.assertEqual( c, eval(repr(c), dict(Angle=Angle, Ellipse=Ellipse, UnitVector3d=UnitVector3d)))
def test_relationships(self): c = Circle(UnitVector3d.X(), Angle.fromDegrees(0.1)) d = Circle(UnitVector3d(1, 1, 1), Angle(math.pi / 2)) e = Circle(-UnitVector3d.X()) self.assertTrue(c.contains(UnitVector3d.X())) self.assertTrue(UnitVector3d.X() in c) self.assertTrue(d.contains(c)) self.assertTrue(c.isWithin(d)) self.assertTrue(c.intersects(d)) self.assertTrue(c.intersects(UnitVector3d.X())) self.assertTrue(e.isDisjointFrom(d)) self.assertEqual(d.relate(c), CONTAINS) self.assertEqual(e.relate(d), DISJOINT)
def run(self) -> Optional[int]: """Run whole shebang. """ # load configurations if self.args.app_config: self.config.load(self.args.app_config) if self.args.backend == "sql": self.dbconfig = ApdbSqlConfig() if self.args.config: self.dbconfig.load(self.args.config) elif self.args.backend == "cassandra": self.dbconfig = ApdbCassandraConfig() if self.args.config: self.dbconfig.load(self.args.config) if self.args.dump_config: self.config.saveToStream(sys.stdout) self.dbconfig.saveToStream(sys.stdout) return 0 # instantiate db interface db: Apdb if self.args.backend == "sql": db = ApdbSql(config=self.dbconfig) elif self.args.backend == "cassandra": db = ApdbCassandra(config=self.dbconfig) visitInfoStore = VisitInfoStore(self.args.visits_file) num_tiles = 1 if self.config.divide != 1: tiles = geom.make_tiles(self.config.FOV_rad, self.config.divide) num_tiles = len(tiles) # check that we have reasonable MPI setup if self.config.mp_mode == "mpi": comm = MPI.COMM_WORLD num_proc = comm.Get_size() rank = comm.Get_rank() node = MPI.Get_processor_name() _LOG.info( COLOR_YELLOW + "MPI job rank=%d size=%d, node %s" + COLOR_RESET, rank, num_proc, node) if num_proc != num_tiles: raise ValueError( f"Number of MPI processes ({num_proc}) " f"does not match number of tiles ({num_tiles})") if rank != 0: # run simple loop for all non-master processes self.run_mpi_tile_loop(db, comm) return None # Initialize starting values from database visits table last_visit = visitInfoStore.lastVisit() if last_visit is not None: start_visit_id = last_visit.visitId + 1 nsec = last_visit.visitTime.nsecs( DateTime.TAI) + self.config.interval * 1_000_000_000 start_time = DateTime(nsec, DateTime.TAI) else: start_visit_id = self.config.start_visit_id start_time = self.config.start_time_dt if self.config.divide > 1: _LOG.info("Will divide FOV into %d regions", num_tiles) src_read_period = self.config.src_read_period src_read_visits = round(self.config.src_read_period * self.config.src_read_duty_cycle) _LOG.info("Will read sources for %d visits out of %d", src_read_visits, src_read_period) # read sources file _LOG.info("Start loading variable sources from %r", self.config.sources_file) var_sources = numpy.load(self.config.sources_file) _LOG.info("Finished loading variable sources, count = %s", len(var_sources)) # diaObjectId for last new DIA object, for variable sources we use their # index as objectId, for transients we want to use ID outside that range if last_visit is not None and last_visit.lastObjectId is not None: self.lastObjectId = max(self.lastObjectId, last_visit.lastObjectId) if self.lastObjectId < len(var_sources): _LOG.error('next object id is too low: %s', self.lastObjectId) return 1 _LOG.debug("lastObjectId: %s", self.lastObjectId) # diaSourceId for last DIA source stored in database if last_visit is not None and last_visit.lastSourceId is not None: self.lastSourceId = max(self.lastSourceId, last_visit.lastSourceId) _LOG.info("lastSourceId: %s", self.lastSourceId) # loop over visits visitTimes = _visitTimes(start_time, self.config.interval, self.args.num_visits) for visit_id, dt in enumerate(visitTimes, start_visit_id): if visit_id % 1000 == 0: _LOG.info(COLOR_YELLOW + "+++ Start daily activities" + COLOR_RESET) db.dailyJob() _LOG.info(COLOR_YELLOW + "+++ Done with daily activities" + COLOR_RESET) _LOG.info( COLOR_GREEN + "+++ Start processing visit %s at %s" + COLOR_RESET, visit_id, dt) loop_timer = timer.Timer().start() with timer.Timer("DIA"): # point telescope in random southern direction pointing_xyz = generators.rand_sphere_xyz(1, -1)[0] pointing_v = UnitVector3d(pointing_xyz[0], pointing_xyz[1], pointing_xyz[2]) ra = LonLat.longitudeOf(pointing_v).asDegrees() decl = LonLat.latitudeOf(pointing_v).asDegrees() # sphgeom.Circle opening angle is actually a half of opening angle region = Circle(pointing_v, Angle(self.config.FOV_rad / 2)) _LOG.info("Pointing ra, decl = %s, %s; xyz = %s", ra, decl, pointing_xyz) # Simulating difference image analysis dia = DIA.DIA( pointing_xyz, self.config.FOV_rad, var_sources, self.config.false_per_visit + self.config.transient_per_visit) sources, indices = dia.makeSources() _LOG.info("DIA generated %s sources", len(sources)) # assign IDs to transients for i in range(len(sources)): if indices[i] < 0: self.lastObjectId += 1 indices[i] = self.lastObjectId # print current database row counts, this takes long time # so only do it once in a while modu = 200 if visit_id <= 10000 else 1000 if visit_id % modu == 0: if hasattr(db, "tableRowCount"): counts = db.tableRowCount() # type: ignore for tbl, count in sorted(counts.items()): _LOG.info('%s row count: %s', tbl, count) # numpy seems to do some multi-threaded stuff which "leaks" CPU cycles to the code below # and it gets counted as resource usage in timers, add a short delay here so that threads # finish and don't influence our timers below. time.sleep(0.1) if self.config.divide == 1: # do it in-process with timer.Timer("VisitProcessing"): self.visit(db, visit_id, dt, region, sources, indices) else: if self.config.mp_mode == "fork": tiles = geom.make_tiles(self.config.FOV_rad, self.config.divide, pointing_v) with timer.Timer("VisitProcessing"): # spawn subprocesses to handle individual tiles children = [] for ix, iy, region in tiles: # make sure lastSourceId is unique in in each process self.lastSourceId += len(sources) tile = (ix, iy) pid = os.fork() if pid == 0: # child self.visit(db, visit_id, dt, region, sources, indices, tile) # stop here sys.exit(0) else: _LOG.debug("Forked process %d for tile %s", pid, tile) children.append(pid) # wait until all children finish for pid in children: try: pid, status = os.waitpid(pid, 0) if status != 0: _LOG.warning( COLOR_RED + "Child process PID=%s failed: %s" + COLOR_RESET, pid, status) except OSError as exc: _LOG.warning( COLOR_RED + "wait failed for PID=%s: %s" + COLOR_RESET, pid, exc) elif self.config.mp_mode == "mpi": tiles = geom.make_tiles(self.config.FOV_rad, self.config.divide, pointing_v) _LOG.info("Split FOV into %d tiles for MPI", len(tiles)) # spawn subprocesses to handle individual tiles, special # care needed for self.lastSourceId because it's # propagated back from (0, 0) lastSourceId = self.lastSourceId tile_data = [] for ix, iy, region in tiles: lastSourceId += len(sources) tile = (ix, iy) tile_data += [(visit_id, dt, region, sources, indices, tile, lastSourceId)] # make sure lastSourceId is unique in in each process with timer.Timer("VisitProcessing"): _LOG.info("Scatter sources to %d tile processes", len(tile_data)) self.run_mpi_tile(db, MPI.COMM_WORLD, tile_data) self.lastSourceId = lastSourceId if not self.args.no_update: # store last visit info visitInfoStore.saveVisit(visit_id, dt, self.lastObjectId, self.lastSourceId) _LOG.info( COLOR_BLUE + "--- Finished processing visit %s, time: %s" + COLOR_RESET, visit_id, loop_timer) # stop MPI slaves if num_tiles > 1 and self.config.mp_mode == "mpi": _LOG.info("Stopping MPI tile processes") tile_data_stop = [None] * self.config.divide**2 self.run_mpi_tile(db, MPI.COMM_WORLD, tile_data_stop) return 0
def test_comparison_operators(self): e = Ellipse(UnitVector3d.X(), UnitVector3d.Y(), Angle(2 * math.pi / 3)) f = Ellipse(UnitVector3d.X(), Angle(math.pi / 3), Angle(math.pi / 6), Angle(0)) self.assertEqual(e, e) self.assertNotEqual(e, f)
def test_dilation_and_erosion(self): a = Box.fromRadians(0.5, -0.5, 1.5, 0.5) b = a.dilatedBy(Angle(0.5), Angle(0.5)).erodedBy(Angle(1), Angle(1)) a.dilateBy(Angle(0.5), Angle(0.5)).erodeBy(Angle(1), Angle(1)) self.assertEqual(a, b) self.assertEqual(a, LonLat.fromRadians(1, 0))
def test_yaml(self): a = Ellipse(UnitVector3d.X(), UnitVector3d.Y(), Angle(2 * math.pi / 3)) b = yaml.safe_load(yaml.dump(a)) self.assertEqual(a, b)
def test_pickle(self): a = Ellipse(UnitVector3d.X(), UnitVector3d.Y(), Angle(2 * math.pi / 3)) b = pickle.loads(pickle.dumps(a, pickle.HIGHEST_PROTOCOL)) self.assertEqual(a, b)
def testPickle(self): a = Angle(1.5) b = pickle.loads(pickle.dumps(a)) self.assertEqual(a, b)
def test_complement(self): e = Ellipse(UnitVector3d.X(), Angle(math.pi / 3), Angle(math.pi / 6), Angle(0)) f = e.complemented().complement() self.assertEqual(e, f)
def test_codec(self): e = Ellipse(UnitVector3d.X(), UnitVector3d.Y(), Angle(2 * math.pi / 3)) s = e.encode() self.assertEqual(Ellipse.decode(s), e) self.assertEqual(Region.decode(s), e)
def testString(self): self.assertEqual(str(Angle(1)), '1.0') self.assertEqual(repr(Angle(1)), 'Angle(1.0)') a = Angle(2.5) self.assertEqual(a, eval(repr(a), dict(Angle=Angle)))
def testRotation(self): v = UnitVector3d.Y().rotatedAround(UnitVector3d.X(), Angle(0.5 * math.pi)) self.assertAlmostEqual(v.x(), 0.0, places=15) self.assertAlmostEqual(v.y(), 0.0, places=15) self.assertAlmostEqual(v.z(), 1.0, places=15)
def testSkyMapDimensions(self): """Test involving only skymap dimensions, no joins to instrument""" registry = self.registry # need a bunch of dimensions and datasets for test, we want # "abstract_filter" in the test so also have to add physical_filter # dimensions registry.addDimensionEntry("instrument", dict(instrument="DummyCam")) registry.addDimensionEntry("physical_filter", dict(instrument="DummyCam", physical_filter="dummy_r", abstract_filter="r")) registry.addDimensionEntry("physical_filter", dict(instrument="DummyCam", physical_filter="dummy_i", abstract_filter="i")) registry.addDimensionEntry("skymap", dict(skymap="DummyMap", hash="sha!".encode("utf8"))) for tract in range(10): registry.addDimensionEntry("tract", dict(skymap="DummyMap", tract=tract)) for patch in range(10): registry.addDimensionEntry("patch", dict(skymap="DummyMap", tract=tract, patch=patch, cell_x=0, cell_y=0, region=Box(LonLat(NormalizedAngle(0.), Angle(0.))))) # dataset types collection = "test" run = registry.makeRun(collection=collection) storageClass = StorageClass("testDataset") registry.storageClasses.registerStorageClass(storageClass) calexpType = DatasetType(name="deepCoadd_calexp", dimensions=registry.dimensions.extract(("skymap", "tract", "patch", "abstract_filter")), storageClass=storageClass) registry.registerDatasetType(calexpType) mergeType = DatasetType(name="deepCoadd_mergeDet", dimensions=registry.dimensions.extract(("skymap", "tract", "patch")), storageClass=storageClass) registry.registerDatasetType(mergeType) measType = DatasetType(name="deepCoadd_meas", dimensions=registry.dimensions.extract(("skymap", "tract", "patch", "abstract_filter")), storageClass=storageClass) registry.registerDatasetType(measType) dimensions = registry.dimensions.empty.union(calexpType.dimensions, mergeType.dimensions, measType.dimensions, implied=True) # add pre-existing datasets for tract in (1, 3, 5): for patch in (2, 4, 6, 7): dataId = dict(skymap="DummyMap", tract=tract, patch=patch) registry.addDataset(mergeType, dataId=dataId, run=run) for aFilter in ("i", "r"): dataId = dict(skymap="DummyMap", tract=tract, patch=patch, abstract_filter=aFilter) registry.addDataset(calexpType, dataId=dataId, run=run) # with empty expression builder = DataIdQueryBuilder.fromDimensions(registry, dimensions) builder.requireDataset(calexpType, collections=[collection]) builder.requireDataset(mergeType, collections=[collection]) rows = list(builder.execute()) self.assertEqual(len(rows), 3*4*2) # 4 tracts x 4 patches x 2 filters for dataId in rows: self.assertCountEqual(dataId.keys(), ("skymap", "tract", "patch", "abstract_filter")) self.assertCountEqual(set(dataId["tract"] for dataId in rows), (1, 3, 5)) self.assertCountEqual(set(dataId["patch"] for dataId in rows), (2, 4, 6, 7)) self.assertCountEqual(set(dataId["abstract_filter"] for dataId in rows), ("i", "r")) # limit to 2 tracts and 2 patches builder = DataIdQueryBuilder.fromDimensions(registry, dimensions) builder.requireDataset(calexpType, collections=[collection]) builder.requireDataset(mergeType, collections=[collection]) builder.whereParsedExpression("tract IN (1, 5) AND patch.patch IN (2, 7)") rows = list(builder.execute()) self.assertEqual(len(rows), 2*2*2) # 4 tracts x 4 patches x 2 filters self.assertCountEqual(set(dataId["tract"] for dataId in rows), (1, 5)) self.assertCountEqual(set(dataId["patch"] for dataId in rows), (2, 7)) self.assertCountEqual(set(dataId["abstract_filter"] for dataId in rows), ("i", "r")) # limit to single filter builder = DataIdQueryBuilder.fromDimensions(registry, dimensions) builder.requireDataset(calexpType, collections=[collection]) builder.requireDataset(mergeType, collections=[collection]) builder.whereParsedExpression("abstract_filter = 'i'") rows = list(builder.execute()) self.assertEqual(len(rows), 3*4*1) # 4 tracts x 4 patches x 2 filters self.assertCountEqual(set(dataId["tract"] for dataId in rows), (1, 3, 5)) self.assertCountEqual(set(dataId["patch"] for dataId in rows), (2, 4, 6, 7)) self.assertCountEqual(set(dataId["abstract_filter"] for dataId in rows), ("i",)) # expression excludes everything, specifying non-existing skymap is # not a fatal error, it's operator error builder = DataIdQueryBuilder.fromDimensions(registry, dimensions) builder.requireDataset(calexpType, collections=[collection]) builder.requireDataset(mergeType, collections=[collection]) builder.whereParsedExpression("skymap = 'Mars'") rows = list(builder.execute()) self.assertEqual(len(rows), 0)