diff --git a/scheduler/core/components/optimizer/greedymax.py b/scheduler/core/components/optimizer/greedymax.py index a094dfe6..579971b4 100644 --- a/scheduler/core/components/optimizer/greedymax.py +++ b/scheduler/core/components/optimizer/greedymax.py @@ -12,7 +12,8 @@ import numpy as np import numpy.typing as npt from lucupy.minimodel import (NIR_INSTRUMENTS, Group, NightIndex, Observation, ObservationClass, ObservationID, - ObservationStatus, Program, QAState, Sequence, Site, UniqueGroupID, Wavelengths) + ObservationStatus, Program, QAState, Sequence, Site, UniqueGroupID, Wavelengths, + ObservationMode) from lucupy.minimodel.resource import Resource from lucupy.types import Interval, ZeroTime @@ -26,15 +27,6 @@ logger = logger_factory.create_logger(__name__) -@final -class Mode(str, Enum): - """ - TODO: Get rid of this later as per GSCHED-413. - """ - SPECTROSCOPY = 'spectroscopy' - IMAGING = 'imaging' - - @final @dataclass(frozen=True) class ObsPlanData: @@ -134,14 +126,14 @@ def first_nonzero_time(inlist: List[timedelta]) -> int: @staticmethod def num_nir_standards(exec_sci: timedelta, wavelengths: Wavelengths = frozenset(), - mode: Mode = Mode.SPECTROSCOPY) -> int: + mode: ObservationMode = ObservationMode.LONGSLIT) -> int: """ Calculated the number of NIR standards from the length of the NIR science and the mode """ n_std = 0 # TODO: need mode or other info to distinguish imaging from spectroscopy - if mode == Mode.IMAGING: + if mode == ObservationMode.IMAGING: time_per_standard = timedelta(hours=2.0) else: if all(wave <= 2.5 for wave in wavelengths): @@ -222,7 +214,7 @@ def _exec_time_remaining(self, # How many standards are needed? # TODO: need mode or other info to distinguish imaging from spectroscopy if exec_sci_nir > ZeroTime and len(part_times) > 0: - n_std = self.num_nir_standards(exec_sci_nir, wavelengths=group.wavelengths(), mode=Mode.SPECTROSCOPY) + n_std = self.num_nir_standards(exec_sci_nir, wavelengths=group.wavelengths(), mode=group.obs_mode()) # if only partner standards, set n_std to the number of standards in group (e.g. specphots) if nprt > 0 and nsci == 0: diff --git a/scheduler/core/programprovider/ocs/__init__.py b/scheduler/core/programprovider/ocs/__init__.py index c4c759d3..4884cf57 100644 --- a/scheduler/core/programprovider/ocs/__init__.py +++ b/scheduler/core/programprovider/ocs/__init__.py @@ -483,7 +483,10 @@ def find_filter(filter_input: str, filter_dict: Mapping[str, float]) -> Optional fpu = instrument else: if instrument in OcsProgramProvider.FPU_FOR_INSTRUMENT: - if OcsProgramProvider.FPU_FOR_INSTRUMENT[instrument] in data: + if OcsProgramProvider._FPUKeys.CUSTOM in data: + # This will assign the MDF name to the FPU + fpu = data[OcsProgramProvider._FPUKeys.CUSTOM] + elif OcsProgramProvider.FPU_FOR_INSTRUMENT[instrument] in data: fpu = data[OcsProgramProvider.FPU_FOR_INSTRUMENT[instrument]] else: # TODO: Might need to raise an exception here. Check code with science. @@ -549,15 +552,18 @@ def search_list(val, alist): return any(val in elem for elem in alist) def determine_mode(inst: str) -> ObservationMode: + + # print(f'inst: {inst} dispersers: {dispersers}') + # print(f'\t fpus: {fpus}') obs_mode = ObservationMode.UNKNOWN - if search_list('GMOS', inst): - if 'MIRROR' in dispersers: + if 'GMOS' in inst: + if 'Mirror' in dispersers or 'MIRROR' in dispersers: obs_mode = ObservationMode.IMAGING elif search_list('arcsec', fpus): obs_mode = ObservationMode.LONGSLIT elif search_list('IFU', fpus): obs_mode = ObservationMode.IFU - elif 'CUSTOM_MASK' in fpus: + elif search_list('G', fpus): obs_mode = ObservationMode.MOS elif inst in ["GSAOI", "'Alopeke", "Zorro"]: obs_mode = ObservationMode.IMAGING @@ -568,7 +574,7 @@ def determine_mode(inst: str) -> ObservationMode: elif inst == 'Flamingos2': if search_list('LONGSLIT', fpus): obs_mode = ObservationMode.LONGSLIT - if search_list('FPU_NONE', fpu) and search_list('IMAGING', dispersers): + elif search_list('IMAGING', dispersers): obs_mode = ObservationMode.IMAGING elif inst == 'NIRI': if search_list('NONE', dispersers) and search_list('MASK_IMAGING', fpus): @@ -658,7 +664,7 @@ def autocorr_lag(x): instrument_resources = frozenset([self._sources.origin.resource.lookup_resource(instrument)]) if 'GMOS' in instrument: # Convert FPUs and dispersers to barcodes. - fpu_resources = frozenset([self._sources.origin.resource.fpu_to_barcode(site, fpu) for fpu in fpus]) + fpu_resources = frozenset([self._sources.origin.resource.fpu_to_barcode(site, fpu, instrument) for fpu in fpus]) disperser_resources = frozenset([self._sources.origin.resource.lookup_resource(disperser.split('_')[0]) for disperser in dispersers]) resources = frozenset([r for r in fpu_resources | disperser_resources | instrument_resources]) @@ -771,7 +777,8 @@ def autocorr_lag(x): qa_state=QAState.NONE, guide_state=False, resources=resources, - wavelengths=frozenset(wavelengths))) + wavelengths=frozenset(wavelengths), + obs_mode=mode)) if (step[OcsProgramProvider._AtomKeys.OBSERVE_TYPE].upper() not in OcsProgramProvider._OBSERVE_TYPES and n_pattern == 0): @@ -870,7 +877,7 @@ def parse_observation(self, atoms = self.parse_atoms(site, data[OcsProgramProvider._ObsKeys.SEQUENCE], qa_states, split=split) # exec_time = sum([atom.exec_time for atom in atoms], ZeroTime) + acq_overhead # for atom in atoms: - # print(f'\t\t\t {atom.id} {atom.exec_time}') + # print(f'\t\t\t {atom.id} {atom.exec_time} {atom.obs_mode}') # TODO: Should this be a list of all targets for the observation? targets = [] diff --git a/scheduler/services/resource/base.py b/scheduler/services/resource/base.py index 2ba04a5f..315a40da 100644 --- a/scheduler/services/resource/base.py +++ b/scheduler/services/resource/base.py @@ -8,7 +8,7 @@ from typing import Dict, List, Set, Tuple, Union, Final from io import BytesIO, StringIO -from lucupy.minimodel import Site, ALL_SITES +from lucupy.minimodel import Site, ALL_SITES, Resource from lucupy.helpers import str_to_bool import gelidum import requests @@ -102,6 +102,17 @@ def __init__(self, sites: FrozenSet[Site] = ALL_SITES): # The final output from this class: the configuration per night. self._night_configurations: Dict[Site, Dict[date, NightConfiguration]] = {site: {} for site in self._sites} + @staticmethod + def _mdf_to_barcode(mdfname: str, inst: str) -> Resource: + """Legacy MOS mask barcode convention""" + barcode = None + instd = {'GMOS': '1', 'GMOS-N': '1', 'GMOS-S': '1', 'Flamingos2': '3'} + semd = {'A': '0', 'B': '1'} + progd = {'Q': '0', 'C': '1', 'L': '2', 'F': '3', 'S': '8', 'D': '9'} + if inst in instd.keys(): + barcode = instd[inst] + semd[mdfname[6]] + progd[mdfname[7]] + mdfname[-6:-3] + mdfname[-2:] + return Resource(id=barcode) + def _itcd_fpu_to_barcode_parser(self, r: List[str], site: Site) -> Set[str]: return {self._itcd_fpu_to_barcode[site][r[0].strip()].id} | {i.strip() for i in r[1:]} @@ -171,12 +182,19 @@ def get_resources(self, site: Site, night_date: date) -> FrozenSet[Resource]: return frozenset(self._resources[site][night_date]) - def fpu_to_barcode(self, site: Site, fpu_name: str) -> Optional[Resource]: + def fpu_to_barcode(self, site: Site, fpu_name: str, instrument: str) -> Optional[Resource]: """ Convert a long FPU name into the barcode, if it exists. """ + barcode = None itcd_fpu_name = self._convert_fpu_to_itcd_name(site, fpu_name) - return self._itcd_fpu_to_barcode[site].get(itcd_fpu_name) + # print(f'fpu_to_barcode {fpu_name} {instrument} {itcd_fpu_name}') + if itcd_fpu_name: + barcode = self._itcd_fpu_to_barcode[site].get(itcd_fpu_name) + elif fpu_name.startswith('G'): + barcode = self._mdf_to_barcode(fpu_name, inst=instrument) + # print(f'\t barcode {barcode}') + return barcode class FileBasedResourceService(ResourceService):