def sim_focalplane(rundate=None, fakepos=False): runtime = None if rundate is None: runtime = datetime.utcnow() else: runtime = datetime.strptime(rundate, "%Y-%m-%dT%H:%M:%S") # First get the starting focalplane from desimodel fp, exclude, state, tmstr = dmio.load_focalplane(runtime) # Make a copy since desimodel is caching the original fp = fp.copy() state = state.copy() npos = len(fp) # Are we replacing the arms and theta lengths with the nominal values? # This is useful in the field rotation test. if fakepos: phys_t = 380.0 phys_p = 200.0 t_min = -0.5 * phys_t t_max = 0.5 * phys_t p_min = 185.0 - phys_p p_max = 185.0 for row in range(npos): petalrot_deg = (float(7 + fp["PETAL"][row]) * 36.0) % 360.0 fp["OFFSET_T"][row] = -170.0 + petalrot_deg fp["OFFSET_P"][row] = -5.0 fp["MIN_T"][row] = t_min fp["MAX_T"][row] = t_max fp["MIN_P"][row] = p_min fp["MAX_P"][row] = p_max fp["LENGTH_R1"][row] = 3.0 fp["LENGTH_R2"][row] = 3.0 state["STATE"][row] = 0 else: # Now set some fibers to stuck / broken mods = { 95: FIBER_STATE_BROKEN, 62: FIBER_STATE_BROKEN, 102: FIBER_STATE_STUCK, 82: FIBER_STATE_STUCK, 131: FIBER_STATE_STUCK } for row, loc in enumerate(state["LOCATION"]): if loc in mods: state["STATE"][row] = mods[loc] return fp, exclude, state
def load_hardware(focalplane=None, rundate=None): """Create a hardware class representing properties of the telescope. Args: focalplane (tuple): Override the focalplane model. If not None, this should be a tuple of the same data types returned by desimodel.io.load_focalplane() rundate (str): ISO 8601 format time stamp as a string in the format YYYY-MM-DDTHH:MM:SS. If None, uses current time. Returns: (Hardware): The hardware object. """ log = Logger.get() # The timestamp for this run. runtime = None if rundate is None: runtime = datetime.utcnow() else: runtime = datetime.strptime(rundate, "%Y-%m-%dT%H:%M:%S") # Get the focalplane information fp = None exclude = None state = None tmstr = "UNKNOWN" if focalplane is None: fp, exclude, state, tmstr = dmio.load_focalplane(runtime) else: fp, exclude, state = focalplane # Get the plate scale platescale = dmio.load_platescale() # We are going to do a quadratic interpolation to the platescale on a fine grid, # and then use that for *linear* interpolation inside the compiled code. The # default platescale data is on a one mm grid spacing. We also do the same # interpolation of the arclength S(R). fine_radius = np.linspace(platescale["radius"][0], platescale["radius"][-1], num=10000, dtype=np.float64) fn = interp1d(platescale["radius"], platescale["theta"], kind="quadratic") fine_theta = fn(fine_radius).astype(np.float64) fn = interp1d(platescale["radius"], platescale["arclength"], kind="quadratic") fine_arc = fn(fine_radius).astype(np.float64) # We are only going to keep rows for LOCATIONs that are assigned to a # science or sky monitor positioner. log.info("Loaded focalplane for time stamp {}".format(runtime)) pos_rows = np.where(fp["DEVICE_TYPE"].astype(str) == "POS")[0] etc_rows = np.where(fp["DEVICE_TYPE"].astype(str) == "ETC")[0] keep_rows = np.unique(np.concatenate((pos_rows, etc_rows))) nloc = len(keep_rows) log.debug( " focalplane table keeping {} rows for POS and ETC devices".format( nloc)) device_type = np.full(nloc, "OOPSBUG", dtype="a8") device_type[:] = fp["DEVICE_TYPE"][keep_rows] locations = np.copy(fp["LOCATION"][keep_rows]) # Map location to row in the table loc_to_fp = dict() for rw, loc in enumerate(fp["LOCATION"]): loc_to_fp[loc] = rw # FIXME: Here we assume that the 32bit STATE column has the same bit # definitions as what is used by fiberassign (defined in hardware.h): # If this is not true, then re-map those values here inside the "state" # table loaded above. # Map location to row of the state table loc_to_state = dict() for rw, loc in enumerate(state["LOCATION"]): loc_to_state[loc] = rw # Convert the exclusion polygons into shapes. excl = dict() for nm, shp in exclude.items(): excl[nm] = dict() for obj in shp.keys(): cr = list() for crc in shp[obj]["circles"]: cr.append(Circle(crc[0], crc[1])) sg = list() for sgm in shp[obj]["segments"]: sg.append(Segments(sgm)) fshp = Shape((0.0, 0.0), cr, sg) excl[nm][obj] = fshp # For each positioner, select the exclusion polynomials. positioners = dict() for loc in locations: exclname = state["EXCLUSION"][loc_to_state[loc]] positioners[loc] = dict() positioners[loc]["theta"] = Shape(excl[exclname]["theta"]) positioners[loc]["phi"] = Shape(excl[exclname]["phi"]) if "gfa" in excl[exclname]: positioners[loc]["gfa"] = Shape(excl[exclname]["gfa"]) else: positioners[loc]["gfa"] = Shape() if "petal" in excl[exclname]: positioners[loc]["petal"] = Shape(excl[exclname]["petal"]) else: positioners[loc]["petal"] = Shape() hw = Hardware( tmstr, locations, fp["PETAL"][keep_rows], fp["DEVICE"][keep_rows], fp["SLITBLOCK"][keep_rows], fp["BLOCKFIBER"][keep_rows], fp["FIBER"][keep_rows], device_type, fp["OFFSET_X"][keep_rows], fp["OFFSET_Y"][keep_rows], np.array([state["STATE"][loc_to_state[x]] for x in locations]), np.array([fp["OFFSET_T"][loc_to_fp[x]] for x in locations]), np.array([fp["MIN_T"][loc_to_fp[x]] for x in locations]), np.array([fp["MAX_T"][loc_to_fp[x]] for x in locations]), np.array([fp["LENGTH_R1"][loc_to_fp[x]] for x in locations]), np.array([fp["OFFSET_P"][loc_to_fp[x]] for x in locations]), np.array([fp["MIN_P"][loc_to_fp[x]] for x in locations]), np.array([fp["MAX_P"][loc_to_fp[x]] for x in locations]), np.array([fp["LENGTH_R2"][loc_to_fp[x]] for x in locations]), fine_radius, fine_theta, fine_arc, [positioners[x]["theta"] for x in locations], [positioners[x]["phi"] for x in locations], [positioners[x]["gfa"] for x in locations], [positioners[x]["petal"] for x in locations]) return hw
def load_hardware(focalplane=None, rundate=None): """Create a hardware class representing properties of the telescope. Args: focalplane (tuple): Override the focalplane model. If not None, this should be a tuple of the same data types returned by desimodel.io.load_focalplane() rundate (str): ISO 8601 format time stamp as a string in the format YYYY-MM-DDTHH:MM:SS+-zz:zz. If None, uses current time. Returns: (Hardware): The hardware object. """ log = Logger.get() # The timestamp for this run. runtime = None if rundate is None: runtime = datetime.now(tz=timezone.utc) else: try: runtime = datetime.strptime(rundate, "%Y-%m-%dT%H:%M:%S%z") except ValueError: runtime = datetime.strptime(rundate, "%Y-%m-%dT%H:%M:%S") msg = "Requested run date '{}' is not timezone-aware. Assuming UTC.".format( runtime) log.warning(msg) runtime = runtime.replace(tzinfo=timezone.utc) runtimestr = None try: runtimestr = runtime.isoformat(timespec="seconds") except TypeError: runtimestr = runtime.isoformat() # Get the focalplane information fp = None exclude = None state = None create_time = "UNKNOWN" if focalplane is None: fp, exclude, state, create_time = dmio.load_focalplane(runtime) else: fp, exclude, state = focalplane # Get the plate scale platescale = dmio.load_platescale() # We are going to do a quadratic interpolation to the platescale on a fine grid, # and then use that for *linear* interpolation inside the compiled code. The # default platescale data is on a one mm grid spacing. We also do the same # interpolation of the arclength S(R). fine_radius = np.linspace(platescale["radius"][0], platescale["radius"][-1], num=10000, dtype=np.float64) fn = interp1d(platescale["radius"], platescale["theta"], kind="quadratic") fine_theta = fn(fine_radius).astype(np.float64) fn = interp1d(platescale["radius"], platescale["arclength"], kind="quadratic") fine_arc = fn(fine_radius).astype(np.float64) # We are only going to keep rows for LOCATIONs that are assigned to a # science or sky monitor positioner. log.info("Loaded focalplane for time stamp {}".format(runtime)) pos_rows = np.where(fp["DEVICE_TYPE"].astype(str) == "POS")[0] etc_rows = np.where(fp["DEVICE_TYPE"].astype(str) == "ETC")[0] keep_rows = np.unique(np.concatenate((pos_rows, etc_rows))) nloc = len(keep_rows) log.debug( " focalplane table keeping {} rows for POS and ETC devices".format( nloc)) device_type = np.full(nloc, "OOPSBUG", dtype="a8") device_type[:] = fp["DEVICE_TYPE"][keep_rows] locations = np.copy(fp["LOCATION"][keep_rows]) # Map location to row in the table loc_to_fp = dict() for rw, loc in enumerate(fp["LOCATION"]): loc_to_fp[loc] = rw # FIXME: Here we assume that the 32bit STATE column has the same bit # definitions as what is used by fiberassign (defined in hardware.h): # If this is not true, then re-map those values here inside the "state" # table loaded above. # Map location to row of the state table loc_to_state = dict() for rw, loc in enumerate(state["LOCATION"]): loc_to_state[loc] = rw # Convert the exclusion polygons into shapes. excl = dict() for nm, shp in exclude.items(): excl[nm] = dict() for obj in shp.keys(): cr = list() for crc in shp[obj]["circles"]: cr.append(Circle(crc[0], crc[1])) sg = list() for sgm in shp[obj]["segments"]: sg.append(Segments(sgm)) fshp = Shape((0.0, 0.0), cr, sg) excl[nm][obj] = fshp # For each positioner, select the exclusion polynomials. positioners = dict() for loc in locations: exclname = state["EXCLUSION"][loc_to_state[loc]] positioners[loc] = dict() positioners[loc]["theta"] = Shape(excl[exclname]["theta"]) positioners[loc]["phi"] = Shape(excl[exclname]["phi"]) if "gfa" in excl[exclname]: positioners[loc]["gfa"] = Shape(excl[exclname]["gfa"]) else: positioners[loc]["gfa"] = Shape() if "petal" in excl[exclname]: positioners[loc]["petal"] = Shape(excl[exclname]["petal"]) else: positioners[loc]["petal"] = Shape() hw = None if "MIN_P" in state.colnames: # This is a new-format focalplane model (after desimodel PR #143) hw = Hardware( runtimestr, locations, fp["PETAL"][keep_rows], fp["DEVICE"][keep_rows], fp["SLITBLOCK"][keep_rows], fp["BLOCKFIBER"][keep_rows], fp["FIBER"][keep_rows], device_type, fp["OFFSET_X"][keep_rows], fp["OFFSET_Y"][keep_rows], np.array([state["STATE"][loc_to_state[x]] for x in locations]), np.array([fp["OFFSET_T"][loc_to_fp[x]] for x in locations]), np.array([state["MIN_T"][loc_to_state[x]] for x in locations]), np.array([state["MAX_T"][loc_to_state[x]] for x in locations]), np.array([state["POS_T"][loc_to_state[x]] for x in locations]), np.array([fp["LENGTH_R1"][loc_to_fp[x]] for x in locations]), np.array([fp["OFFSET_P"][loc_to_fp[x]] for x in locations]), np.array([state["MIN_P"][loc_to_state[x]] for x in locations]), np.array([state["MAX_P"][loc_to_state[x]] for x in locations]), np.array([state["POS_P"][loc_to_state[x]] for x in locations]), np.array([fp["LENGTH_R2"][loc_to_fp[x]] for x in locations]), fine_radius, fine_theta, fine_arc, [positioners[x]["theta"] for x in locations], [positioners[x]["phi"] for x in locations], [positioners[x]["gfa"] for x in locations], [positioners[x]["petal"] for x in locations], ) else: # This is an old-format focalplane model (prior to desimodel PR #143). For # stuck positioners, we want to specify a default POS_T / POS_P to use. # These old models did not include any information about that, so we use the # minimum Theta value and either the maximum Phi value or PI, whichever is # smaller fake_pos_p = np.zeros(len(locations), dtype=np.float64) fake_pos_t = np.zeros(len(locations), dtype=np.float64) for ilid, lid in enumerate(locations): pt = fp["MIN_T"][loc_to_fp[lid]] + fp["OFFSET_T"][loc_to_fp[lid]] pp = fp["MAX_P"][loc_to_fp[lid]] + fp["OFFSET_P"][loc_to_fp[lid]] if pp > 180.0: pp = 180.0 fake_pos_p[ilid] = pp fake_pos_t[ilid] = pt hw = Hardware( runtimestr, locations, fp["PETAL"][keep_rows], fp["DEVICE"][keep_rows], fp["SLITBLOCK"][keep_rows], fp["BLOCKFIBER"][keep_rows], fp["FIBER"][keep_rows], device_type, fp["OFFSET_X"][keep_rows], fp["OFFSET_Y"][keep_rows], np.array([state["STATE"][loc_to_state[x]] for x in locations]), np.array([fp["OFFSET_T"][loc_to_fp[x]] for x in locations]), np.array([fp["MIN_T"][loc_to_fp[x]] for x in locations]), np.array([fp["MAX_T"][loc_to_fp[x]] for x in locations]), fake_pos_t, np.array([fp["LENGTH_R1"][loc_to_fp[x]] for x in locations]), np.array([fp["OFFSET_P"][loc_to_fp[x]] for x in locations]), np.array([fp["MIN_P"][loc_to_fp[x]] for x in locations]), np.array([fp["MAX_P"][loc_to_fp[x]] for x in locations]), fake_pos_p, np.array([fp["LENGTH_R2"][loc_to_fp[x]] for x in locations]), fine_radius, fine_theta, fine_arc, [positioners[x]["theta"] for x in locations], [positioners[x]["phi"] for x in locations], [positioners[x]["gfa"] for x in locations], [positioners[x]["petal"] for x in locations], ) return hw
def test_thetaphi_range(self): # Function to test that all positioners can reach a circle of # targets at fixed distance from their center. def check_reachable(hrdw, radius, increments, log_fail=True): centers = hw.loc_pos_curved_mm theta_arms = hw.loc_theta_arm phi_arms = hw.loc_phi_arm theta_mins = hw.loc_theta_min theta_maxs = hw.loc_theta_max theta_offsets = hw.loc_theta_offset phi_mins = hw.loc_phi_min phi_maxs = hw.loc_phi_max phi_offsets = hw.loc_phi_offset n_failed = 0 for loc in hw.locations: center = centers[loc] theta_arm = theta_arms[loc] phi_arm = phi_arms[loc] theta_min = theta_mins[loc] theta_max = theta_maxs[loc] theta_offset = theta_offsets[loc] phi_min = phi_mins[loc] phi_max = phi_maxs[loc] phi_offset = phi_offsets[loc] ang = np.arange(increments) / (2 * np.pi) test_x = radius * np.cos(ang) + center[0] test_y = radius * np.sin(ang) + center[1] for xy in zip(test_x, test_y): result = hrdw.xy_to_thetaphi(center, xy, theta_arm, phi_arm, theta_offset, phi_offset, theta_min, phi_min, theta_max, phi_max) if result[0] is None or result[1] is None: if log_fail: print("loc {} at ({}, {}) cannot reach ({}, {})". format(loc, center[0], center[1], xy[0], xy[1]), flush=True) n_failed += 1 break else: if not log_fail: # log success instead print( "loc {} at ({}, {}) to ({}, {}) with ({}, {})". format(loc, center[0], center[1], xy[0], xy[1], result[0] * 180.0 / np.pi, result[1] * 180.0 / np.pi), flush=True) return n_failed # Test nominal focalplane hw = load_hardware(rundate=test_assign_date) failed = check_reachable(hw, 3.0, 100) if (failed > 0): print("{} positioners failed to reach ring at 3mm from center". format(failed), flush=True) self.assertTrue(False) # Now we are going to artificially restrict the phi angle range and test that # we cannot access the outer areas of the patrol radius. runtime = datetime.strptime(test_assign_date, "%Y-%m-%dT%H:%M:%S") fp, exclude, state, tmstr = dmio.load_focalplane(runtime) # make a copy so that we aren't modifying the desimodel cache fp = fp.copy() limit_radius = 2.0 open_limit = 2.0 * np.arcsin(0.5 * limit_radius / 3.0) phi_limit_min = (np.pi - open_limit) * 180.0 / np.pi new_min = phi_limit_min - np.array(fp["OFFSET_P"]) fp["MIN_P"][:] = new_min hw = load_hardware(focalplane=(fp, exclude, state)) failed = check_reachable(hw, 3.0, 100, log_fail=False) if (failed != len(hw.locations)): print( "{} positioners reached 3mm from center, despite restricted phi" .format(len(hw.locations) - failed), flush=True) self.assertTrue(False) return
TARGET_TYPE_SUPPSKY, TARGET_TYPE_STANDARD, TARGET_TYPE_SAFE, Targets, TargetsAvailable, TargetTree, LocationsAvailable, load_target_file) from fiberassign.assign import (Assignment, write_assignment_fits, write_assignment_ascii, merge_results, read_assignment_fits_tile) import desimodel.io as dmio e2edir = '/global/homes/m/mjwilson/desi/survey-validation/svdc-spring2020b-onepercent/' randir = minisvdir + 'random/' runtime = datetime.utcnow() # First get the starting focalplane from desimodel fp, exclude, state, tmstr = dmio.load_focalplane(runtime) def getfatiles(targetf, tilef, dirout=randir): ''' will write out fiberassignment files for each tile with the FASSIGN, FTARGETS, FAVAIL HDUS these are what are required to determine the geometry of what fiberassign thinks could have been observed and also match to actual observations (though FASSIGN is not really necessary) targetf is file with all targets to be run through tilef lists the tiles to "assign" dirout is the directory where this all gets written out !make sure this is unique for every different target! ''' tgs = Targets() load_target_file(tgs, targetf) print('loaded target file ' + targetf) tree = TargetTree(tgs, 0.01) hw = load_hardware(focalplane=(fp, exclude, state))