Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
    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
Esempio n. 5
0
                                 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))