Source code for producer.design.fobosfocalplane

"""
Hard-coded method to construct the FOBOS focal plane design files.

.. include:: ../include/links.rst
"""

import time

from IPython import embed

import numpy

from .focalplane import module_layout
from ..deploy import FOBOSModeBitMask


[docs]def baseline_module_type(): """ Define the type of each module. Module types are: 0. single starbugs only 1. mini-IFU module 2. location of Big IFU 3. imaging bundle module 4. flux-calibration module """ return numpy.array([0, # 1 0, # 2 0, # 3 0, # 4 3, # 5 0, # 6 0, # 7 3, # 8 0, # 9 0, # 10 3, # 11 1, # 12 1, # 13 3, # 14 1, # 15 1, # 16 3, # 17 0, # 18 0, # 19 0, # 20 1, # 21 4, # 22 1, # 23 1, # 24 4, # 25 1, # 26 0, # 27 0, # 28 0, # 29 4, # 30 3, # 31 1, # 32 1, # 33 3, # 34 1, # 35 1, # 36 3, # 37 4, # 38 0, # 39 0, # 40 1, # 41 1, # 42 4, # 43 1, # 44 1, # 45 3, # 46 1, # 47 1, # 48 0, # 49 0, # 50 1, # 51 4, # 52 1, # 53 1, # 54 0, # 55 1, # 56 1, # 57 4, # 58 1, # 59 2, # 60 0, # 61 1, # 62 1, # 63 3, # 64 1, # 65 1, # 66 4, # 67 1, # 68 1, # 69 0, # 70 0, # 71 4, # 72 3, # 73 1, # 74 1, # 75 3, # 76 1, # 77 1, # 78 3, # 79 4, # 80 0, # 81 0, # 82 0, # 83 1, # 84 4, # 85 1, # 86 1, # 87 4, # 88 1, # 89 0, # 90 0, # 91 0, # 92 3, # 93 1, # 94 1, # 95 3, # 96 1, # 97 1, # 98 3, # 99 0, # 100 0, # 101 3, # 102 0, # 103 0, # 104 3, # 105 0, # 106 0, # 107 0, # 108 0]) # 109
[docs]def module_mode(mod_type): """ Provide the default mode and designated sky bits of the starbugs in each module type. There are 5 module types: 0. single starbugs only 1. mini-IFU module 2. monolithic IFU 3. imaging bundle module 4. flux-calibration module The active starbugs depend on the spectrograph mode. The modes are tracked using a bit value, where bits can be either: 0. Single fiber mode. 1. multi-IFU mode. 2. Monolithic IFU mode. So, e.g., a starbug with a mode value of 7 is deployed for all 3 modes. Starbug ID (or array index + 1) per module is expected to be. | 03 | 02 07 | 01 06 12 | 05 11 | 04 10 16 | 09 15 | 08 14 19 | 13 18 | 17 Only IFU modules have designated sky apertures, which are starbug IDs 3, 4, 16, and 17. Args: mod_type (:obj:`int`): The module type. Must be 0, 1, 2, 3, or 4, as listed above. Returns: :obj:`tuple`: Two integer `numpy.ndarray`_ objects providing (1) bits used to determine if the starbug is active during the spectrograph mode and (2) bits used to determine if the starbug houses a designated sky fiber during the spectrograph mode. """ bm = FOBOSModeBitMask() active_bits = numpy.zeros(19, dtype=numpy.int16) sky_bits = numpy.zeros(19, dtype=numpy.int16) if mod_type == 0: # Single-fiber only module: All starbugs are active in MOS mode only. # None of the apertures are explicitly designated for sky observations. return bm.turn_on(active_bits, 'MOS'), sky_bits if mod_type == 1: # IFU module: All but the center starbug are active in MOS mode, none of # which are explicitly designated for sky. active_bits = bm.turn_on(active_bits, 'MOS') active_bits[9] = bm.turn_off(active_bits[9], 'MOS') # In IFU mode, the center starbug is active, and 4 single-fiber # apertures on the outer ring are designated for sky-only observations. active_bits[[2,3,9,15,16]] = bm.turn_on(active_bits[[2,3,9,15,16]], 'IFU') sky_bits[[2,3,15,16]] = bm.turn_on(sky_bits[[2,3,15,16]], 'IFU') return active_bits, sky_bits if mod_type == 2: # Monolithic IFU module: All starbugs are turn off. return active_bits, sky_bits if mod_type in [3, 4]: # Guide and flux-calibration modules: The center starbug is always on, # but the other bugs are only on in MOS mode. None of the apertures are # explicitly designated for sky. active_bits = bm.turn_on(active_bits, 'MOS') active_bits[9] = bm.turn_on(active_bits[9], 'IFU') active_bits[9] = bm.turn_on(active_bits[9], 'MONO') return active_bits, sky_bits raise ValueError(f'Unknown module type: {mod_type}')
[docs]def _special_module_mode_config1(active_bits, sky_bits, module_id): bm = FOBOSModeBitMask() _active_bits = active_bits.copy() _sky_bits = sky_bits.copy() if module_id not in [38, 44, 49, 58, 59, 70, 80]: # No changes needed return _active_bits, _sky_bits if module_id == 44: # Turn off the current MOS mode bits _active_bits = bm.turn_off(_active_bits, 'MOS') # The remaining starbugs with non-zero bits should all be for IFU mode; # include these in MOS mode, as well, for both the active bits and the # designated sky fibers. indx = _active_bits > 0 _active_bits[indx] = bm.turn_on(_active_bits[indx], 'MOS') indx = _sky_bits > 0 _sky_bits[indx] = bm.turn_on(_sky_bits[indx], 'MOS') # The fiber budget allows us to turn on 5 more single fibers for MOS # mode only _active_bits[[0,4,10,11,13]] = bm.turn_on(_active_bits[[0,4,10,11,13]], 'MOS') return _active_bits, _sky_bits if module_id in [49, 59, 70]: # Turn on the outer ring of fibers for use as sky fibers during # the monolithic IFU mode indx = [0,1,2,3,6,7,8,10,11,12,15,16,17,18] _active_bits[indx] = bm.turn_on(_active_bits[indx], 'MONO') _sky_bits[indx] = bm.turn_on(_sky_bits[indx], 'MONO') return _active_bits, _sky_bits if module_id in [38, 58, 80]: # Turn on the outer ring of fibers for use as sky fibers during # the monolithic IFU mode indx = [0,1,2,3,6,7,8,11,12,15,16,17,18] _active_bits[indx] = bm.turn_on(_active_bits[indx], 'MONO') _sky_bits[indx] = bm.turn_on(_sky_bits[indx], 'MONO') return _active_bits, _sky_bits
[docs]def _special_module_mode_config2(active_bits, sky_bits, module_id): bm = FOBOSModeBitMask() _active_bits = active_bits.copy() _sky_bits = sky_bits.copy() if module_id not in [14, 15, 44, 54, 65, 70, 80]: # No changes needed return _active_bits, _sky_bits if module_id == 44: # Turn off the current MOS mode bits _active_bits = bm.turn_off(_active_bits, 'MOS') # The remaining starbugs with non-zero bits should all be for IFU mode; # include these in MOS mode, as well, for both the active bits and the # designated sky fibers. indx = _active_bits > 0 _active_bits[indx] = bm.turn_on(_active_bits[indx], 'MOS') indx = _sky_bits > 0 _sky_bits[indx] = bm.turn_on(_sky_bits[indx], 'MOS') # The fiber budget allows us to turn on 5 more single fibers for MOS # mode only _active_bits[[0,4,10,11,13]] = bm.turn_on(_active_bits[[0,4,10,11,13]], 'MOS') return _active_bits, _sky_bits if module_id in [14, 65, 70]: # Turn on the outer ring of fibers for use as sky fibers during # the monolithic IFU mode indx = [0,1,2,3,6,7,8,10,11,12,15,16,17,18] _active_bits[indx] = bm.turn_on(_active_bits[indx], 'MONO') _sky_bits[indx] = bm.turn_on(_sky_bits[indx], 'MONO') return _active_bits, _sky_bits if module_id in [15, 54, 80]: # Turn on the outer ring of fibers for use as sky fibers during # the monolithic IFU mode indx = [0,1,2,3,6,7,8,11,12,15,16,17,18] _active_bits[indx] = bm.turn_on(_active_bits[indx], 'MONO') _sky_bits[indx] = bm.turn_on(_sky_bits[indx], 'MONO') return _active_bits, _sky_bits
[docs]def special_module_mode(active_bits, sky_bits, module_id, spec_map): """ Apply adjustments to active and sky bits for some modules: - Spectrograph 1 always deploys a single IFU for quick ToO follow-up. This means that module 44 always deploys an IFU and 4 sky fibers, and the other starbugs never do anything... - When using the monolithic IFU, the 12 starbugs in the outer hexagonal ring are activated for 2 modules in each spectrograph for sky-only observations. The modules are 38, 49, 58, 59, 70, and 80 in spectrograph-to-module-mapping design 1, and 14, 15, 54, 65, 70, 80 for design 2. """ if spec_map == 1: return _special_module_mode_config1(active_bits, sky_bits, module_id) if spec_map == 2: return _special_module_mode_config2(active_bits, sky_bits, module_id) raise ValueError(f'Unknown spectrograph configuration {spec_map}.')
[docs]def module_spectrograph(): """ Define two possible module-to-spectrograph allocations. The first distributes the modules (roughly) evenly between the 3 spectrographs across the full field of view. The second divides the focal plane into three regions, one per spectrograph. The returned index gives the spectrograph number. """ return numpy.array([[2,0], # 1 [1,0], # 2 [0,2], # 3 [0,0], # 4 [1,0], # 5 [2,0], # 6 [1,2], # 7 [0,2], # 8 [0,2], # 9 [2,0], # 10 [0,0], # 11 [1,0], # 12 [0,0], # 13 [2,0], # 14 [1,2], # 15 [0,2], # 16 [1,2], # 17 [2,2], # 18 [1,0], # 19 [1,0], # 20 [0,0], # 21 [1,0], # 22 [2,0], # 23 [0,2], # 24 [2,2], # 25 [2,2], # 26 [2,2], # 27 [1,2], # 28 [0,0], # 29 [0,0], # 30 [2,0], # 31 [2,0], # 32 [1,0], # 33 [0,2], # 34 [2,2], # 35 [1,2], # 36 [2,2], # 37 [0,2], # 38 [0,2], # 39 [2,0], # 40 [0,0], # 41 [1,0], # 42 [2,0], # 43 [0,0], # 44 [1,2], # 45 [1,2], # 46 [0,2], # 47 [1,2], # 48 [1,2], # 49 [0,0], # 50 [2,0], # 51 [1,0], # 52 [0,0], # 53 [2,0], # 54 [0,2], # 55 [0,2], # 56 [2,2], # 57 [1,2], # 58 [0,2], # 59 [-1,-1], # 60 [1,0], # 61 [1,0], # 62 [2,0], # 63 [1,0], # 64 [1,1], # 65 [2,1], # 66 [0,2], # 67 [1,2], # 68 [2,2], # 69 [2,2], # 70 [0,0], # 71 [2,1], # 72 [0,0], # 73 [1,1], # 74 [0,1], # 75 [2,1], # 76 [1,1], # 77 [0,1], # 78 [0,2], # 79 [2,1], # 80 [2,2], # 81 [1,1], # 82 [2,1], # 83 [0,1], # 84 [0,1], # 85 [2,1], # 86 [0,1], # 87 [1,1], # 88 [2,1], # 89 [1,1], # 90 [1,2], # 91 [2,1], # 92 [1,1], # 93 [2,1], # 94 [1,1], # 95 [0,1], # 96 [2,1], # 97 [1,1], # 98 [2,1], # 99 [0,1], # 100 [0,1], # 101 [2,1], # 102 [1,1], # 103 [2,1], # 104 [1,1], # 105 [0,1], # 106 [0,1], # 107 [1,1], # 108 [2,1]]).T # 109
[docs]def fobos_module_layout(module_file, starbug_file, version='0.1'): starbug_grid, module_grid = module_layout(0.725, 19., 5, 13, module_pitch=90., max_dist=11.2) if starbug_grid.shape[0] != 19: raise ValueError('FOBOS modules should have 19 Starbugs.') if module_grid.shape[0] != 109: raise ValueError('Number of FOBOS modules changed.') module_dist = numpy.sqrt(module_grid[:,0]**2 + module_grid[:,1]**2) module_dist /= 60. # Write the file with the module coordinates module_type = baseline_module_type() module_spec = module_spectrograph() module_base = module_dist < 10.4 # 0 - single starbugs only # 1 - Location of Big IFU # 2 - mini-IFU module # 3 - imaging bundle module # 4 - flux-calibration module header_text = f'FOBOS Starbug module layout (v{version})\n\n' \ f'Generated {time.strftime("%a %d %b %Y %H:%M:%S", time.localtime())}\n\n' \ 'Columns are:\n' \ ' MID - Module ID number\n' \ ' XI, ETA - Module center coordinates in arcmin\n' \ ' relative to the focal plan center\n' \ ' T - The special payload type at the module\n' \ ' center: (0) single-fiber, (1) 37-fiber IFU, \n' \ ' (2) monolithic IFU, (3) imaging bundle,\n' \ ' (4) flux-calibration bundle.\n' \ ' B - Flag (1=True) that the module is in the\n' \ ' baseline CoDR design.\n\n' # Save the module positions and types numpy.savetxt(module_file, numpy.column_stack((numpy.arange(module_grid.shape[0])+1, module_grid[:,0]/60., module_grid[:,1]/60., module_type, module_base.astype(int))), fmt='%5d %7.3f %7.3f %2d %2d', header=header_text+f"{'MID':>3} {'XI':>7} {'ETA':>7} {'T':>2} {'B':>2}") # Build the coordinates, types, mode, etc, for all Starbugs module_id = numpy.tile(numpy.arange(module_grid.shape[0])+1, (starbug_grid.shape[0],1)).T active_bit_ran = numpy.zeros(module_id.shape, dtype=numpy.int16) active_bit_reg = numpy.zeros(module_id.shape, dtype=numpy.int16) sky_bit_ran = numpy.zeros(module_id.shape, dtype=numpy.int16) sky_bit_reg = numpy.zeros(module_id.shape, dtype=numpy.int16) for i in module_id[:,0]: active_bit_ran[i-1,:], sky_bit_ran[i-1,:] \ = special_module_mode(*module_mode(module_type[i-1]), i, 1) active_bit_reg[i-1,:], sky_bit_reg[i-1,:] \ = special_module_mode(*module_mode(module_type[i-1]), i, 2) starbug_id = numpy.tile(numpy.arange(starbug_grid.shape[0])+1, (module_grid.shape[0],1)) starbug_type = numpy.zeros(module_id.shape, dtype=int) # "Special" starbug always at the module center starbug_type[:,9] = module_type starbug_spec_ran = numpy.tile(module_spec[0], (starbug_grid.shape[0],1)).T+1 starbug_spec_reg = numpy.tile(module_spec[1], (starbug_grid.shape[0],1)).T+1 starbug_base = numpy.tile(module_base, (starbug_grid.shape[0],1)).T starbug_coo = (module_grid[:,None,:] + starbug_grid[None,:,:]) # Remove the module with the monolithic IFU indx = module_type != 2 module_id = module_id[indx].ravel() starbug_id = starbug_id[indx].ravel() starbug_type = starbug_type[indx].ravel() active_bit_ran = active_bit_ran[indx].ravel() active_bit_reg = active_bit_reg[indx].ravel() sky_bit_ran = sky_bit_ran[indx].ravel() sky_bit_reg = sky_bit_reg[indx].ravel() starbug_spec_ran = starbug_spec_ran[indx].ravel() starbug_spec_reg = starbug_spec_reg[indx].ravel() starbug_base = starbug_base[indx].ravel() starbug_coo = starbug_coo[indx].reshape(-1,2) # Write the file with the Starbug coordinates header_text = f'FOBOS Starbug module layout (v{version})\n\n' \ f'Generated {time.strftime("%a %d %b %Y %H:%M:%S", time.localtime())}\n\n' \ 'Provides bits to select which Starbugs are active (ON) and which \n' \ 'are explicitly designated for sky observations (SKY) in each of 3 \n' \ 'modes: (0) single-fiber aperture deployment, (1) 37-fiber IFU \n' \ 'deployment, (2) monolithic IFU deployment. These bits are based on \n' \ 'two designs for the spectrograph-to-module mapping: modules for each \n' \ 'spectrograph are distributed over (1) the full field-of-view or (2) a \n' \ 'designated region covering 1/3 of the field-of-view. For example, to \n' \ 'select active starbugs in mode 0 in design 1, do the bitwise \n' \ 'operation:\n\n' \ ' ON1 & (1 << 0) \n\n' \ 'Columns are:\n' \ ' BID - Starbug ID number\n' \ ' MID - Module ID number\n' \ ' SPC1 - Spectrograph (design 1)\n' \ ' ON1 - Selection bit for active starbugs (design 1)\n' \ ' SKY1 - Selection bit for designated sky apertures (design 1)\n' \ ' SPC2 - Spectrograph (design 2)\n' \ ' ON2 - Selection bit for active starbugs (design 2)\n' \ ' SKY2 - Selection bit for designated sky apertures (design 2)\n' \ ' XI, ETA - Starbug "home" coordinates in arcmin\n' \ ' relative to the focal plane center\n' \ ' T - Payload type: (0) single-fiber, (1) 37-fiber \n' \ ' IFU, (2) monolithic IFU, (3) imaging bundle,\n' \ ' (4) flux-calibration bundle.\n' \ ' B - Flag (1=True) that the module/starbug is in the\n' \ ' baseline CoDR design.\n\n' # Save the starbug positions and types numpy.savetxt(starbug_file, numpy.column_stack((starbug_id, module_id, starbug_spec_ran, active_bit_ran, sky_bit_ran, starbug_spec_reg, active_bit_reg, sky_bit_reg, starbug_coo[:,0]/60., starbug_coo[:,1]/60., starbug_type, starbug_base.astype(int))), fmt='%5d %4d %4d %4d %4d %4d %4d %4d %8.4f %8.4f %2d %2d', header=header_text+f"{'BID':>3} {'MID':>4} {'SPC1':>4} {'ON1':>4} " f"{'SKY1':>4} {'SPC2':>4} {'ON2':>4} {'SKY2':>4} " f"{'XI':>8} {'ETA':>8} {'T':>2} {'B':>2}")