diff --git a/Scheduler/feature_scheduler/maintel/ddf_sv_generator.dat b/Scheduler/feature_scheduler/maintel/ddf_sv_generator.dat new file mode 100644 index 00000000..8eb75775 --- /dev/null +++ b/Scheduler/feature_scheduler/maintel/ddf_sv_generator.dat @@ -0,0 +1,32 @@ +ddf_name,season_length,season,u,g,r,i,z,y,n_sequences,flush_length,even_odd_None,g_depth_limit + +# COSMOS Shallow season +#COSMOS, 225, 0, 6, 0, 0, 0, 0, 0, 20, 2, none, 23.5 +#COSMOS, 225, 0, 0, 0, 0, 0, 0, 4, 30, 2, none, 23.5 +#COSMOS, 225, 0, 0, 6, 0, 6, 0, 0, 40, 1.0, even, 22.8 +#COSMOS, 225, 0, 0, 0, 6, 0, 6, 0, 40, 1.0, odd, 22.8 + +# XMM_LSS deep season +#XMM_LSS, 225, 0, 8, 0, 0, 0, 0, 0, 20, 2, none, 23.5 +#XMM_LSS, 225, 0, 0, 0, 0, 0, 0, 20, 30, 2, none, 23.5 +XMM_LSS, 225, 0, 0, 6, 0, 6, 0, 0, 40, 0.5, even, 22.8 +XMM_LSS, 225, 0, 0, 0, 6, 0, 6, 0, 40, 0.5, odd, 22.8 +XMM_LSS, 180, 0, 0, 10, 20, 35, 35, 0, 28, 2, none, 22.8 + +# ELAISS1 shallow seasons +#ELAISS1, 225, 0, 6, 0, 0, 0, 0, 0, 20, 2, none, 23.5 +#ELAISS1, 225, 0, 0, 0, 0, 0, 0, 4, 30, 2, none, 23.5 +ELAISS1, 225, 0, 0, 5, 0, 8, 0, 0, 50, 1.0, even, 22.8 +ELAISS1, 225, 0, 0, 0, 8, 0, 6, 0, 50, 1.0, odd, 22.8 + +# ECDFS shallow seasons +#ECDFS, 225, 0, 6, 0, 0, 0, 0, 0, 20, 2, none, 23.5 +#ECDFS, 225, 0, 0, 0, 0, 0, 0, 4, 30, 2, none, 23.5 +ECDFS, 225, 0, 0, 6, 0, 6, 0, 0, 40, 1, even, 22.8 +ECDFS, 225, 0, 0, 0, 6, 0, 6, 0, 40, 1, odd, 22.8 + +# EDFS_a shallow seasons +#EDFS_a, 225, 0, 6, 0, 0, 0, 0, 0, 20, 2, none, 23.5 +#EDFS_a, 225, 0, 0, 0, 0, 0, 0, 4, 30, 2, none, 23.5 +EDFS_a, 225, 0, 0, 6, 0, 6, 0, 0, 40, 1.0, even, 22.8 +EDFS_a, 225, 0, 0, 0, 6, 0, 6, 0, 40, 1.0, odd, 22.8 diff --git a/Scheduler/feature_scheduler/maintel/fbs_config_minimal_fieldsurvey.py b/Scheduler/feature_scheduler/maintel/fbs_config_minimal_fieldsurvey.py index 7747b9be..25c2580f 100644 --- a/Scheduler/feature_scheduler/maintel/fbs_config_minimal_fieldsurvey.py +++ b/Scheduler/feature_scheduler/maintel/fbs_config_minimal_fieldsurvey.py @@ -117,6 +117,8 @@ def get_scheduler(): "nexps": nexps, } + nnights = 500 + # Carina # Custom landscape dither radius = np.sqrt(2.4) @@ -125,7 +127,7 @@ def get_scheduler(): delta_ra = linear * np.cos(orientation) delta_dec = linear * np.sin(orientation) config_detailers = [ - detailers.DitherDetailer(max_dither=0.7, per_night=False), + detailers.DitherDetailer(max_dither=0.7, per_night=False, nnights=nnights), detailers.DeltaCoordDitherDetailer(delta_ra=delta_ra, delta_dec=delta_dec), # Note: 5 centers * 4.5 deg per visit * 4 visits = 90 deg detailers.CameraSmallRotPerObservationListDetailer( @@ -157,7 +159,7 @@ def get_scheduler(): delta_ra = linear * np.cos(orientation) delta_dec = linear * np.sin(orientation) config_detailers = [ - detailers.DitherDetailer(max_dither=0.7, per_night=False), + detailers.DitherDetailer(max_dither=0.7, per_night=False, nnights=nnights), detailers.DeltaCoordDitherDetailer(delta_ra=delta_ra, delta_dec=delta_dec), # Note: 5 centers * 4.5 deg per visit * 4 visits = 90 deg detailers.CameraSmallRotPerObservationListDetailer( @@ -189,7 +191,7 @@ def get_scheduler(): delta_ra = linear * np.cos(orientation) delta_dec = linear * np.sin(orientation) config_detailers = [ - detailers.DitherDetailer(max_dither=0.4, per_night=False), + detailers.DitherDetailer(max_dither=0.4, per_night=False, nnights=nnights), detailers.DeltaCoordDitherDetailer(delta_ra=delta_ra, delta_dec=delta_dec), # Note: 5 centers * 4.5 deg per visit * 4 visits = 90 deg detailers.CameraSmallRotPerObservationListDetailer( @@ -221,7 +223,7 @@ def get_scheduler(): delta_ra = linear * np.cos(orientation) delta_dec = linear * np.sin(orientation) config_detailers = [ - detailers.DitherDetailer(max_dither=0.7, per_night=False), + detailers.DitherDetailer(max_dither=0.7, per_night=False, nnights=nnights), detailers.DeltaCoordDitherDetailer(delta_ra=delta_ra, delta_dec=delta_dec), # Note: 5 centers * 4.5 deg per visit * 4 visits = 90 deg detailers.CameraSmallRotPerObservationListDetailer( @@ -253,7 +255,7 @@ def get_scheduler(): delta_ra = linear * np.cos(orientation) delta_dec = linear * np.sin(orientation) config_detailers = [ - detailers.DitherDetailer(max_dither=0.7, per_night=False), + detailers.DitherDetailer(max_dither=0.7, per_night=False, nnights=nnights), detailers.DeltaCoordDitherDetailer(delta_ra=delta_ra, delta_dec=delta_dec), # Note: 5 centers * 4.5 deg per visit * 4 visits = 90 deg detailers.CameraSmallRotPerObservationListDetailer( @@ -297,7 +299,7 @@ def get_scheduler(): delta_ra = linear * np.cos(orientation) delta_dec = linear * np.sin(orientation) config_detailers = [ - detailers.DitherDetailer(max_dither=0.7, per_night=False), + detailers.DitherDetailer(max_dither=0.7, per_night=False, nnights=nnights), detailers.DeltaCoordDitherDetailer(delta_ra=delta_ra, delta_dec=delta_dec), # Note: 3 centers * 1 deg per visit * 4 visits = 90 deg detailers.CameraSmallRotPerObservationListDetailer( @@ -356,7 +358,7 @@ def get_scheduler(): # Default for LSST DDFs # detailers.DitherDetailer(max_dither=0.2, per_night=False), # Experimental larger dither pattern - detailers.DitherDetailer(max_dither=1.4, per_night=False), + detailers.DitherDetailer(max_dither=1.4, per_night=False, nnights=nnights), # Note: 1 center * 3 deg per visit * 30 visits = 90 deg detailers.CameraSmallRotPerObservationListDetailer( max_rot=45.0, @@ -400,7 +402,7 @@ def get_scheduler(): delta_ra = linear * np.cos(orientation) delta_dec = linear * np.sin(orientation) config_detailers = [ - detailers.DitherDetailer(max_dither=1.4, per_night=False), + detailers.DitherDetailer(max_dither=1.4, per_night=False, nnights=nnights), detailers.DeltaCoordDitherDetailer(delta_ra=delta_ra, delta_dec=delta_dec), # Note: 7 centers * 1 deg per visit * 5 visits = 35 deg detailers.CameraSmallRotPerObservationListDetailer( @@ -433,7 +435,7 @@ def get_scheduler(): # Default for other fields # 2x raft scale dithers config_detailers = [ - detailers.DitherDetailer(max_dither=1.4, per_night=False), + detailers.DitherDetailer(max_dither=1.4, per_night=False, nnights=nnights), # Note: 1 center * 3 deg per visit * 30 visits = 90 deg detailers.CameraSmallRotPerObservationListDetailer( max_rot=45.0, diff --git a/Scheduler/feature_scheduler/maintel/fbs_config_sv_survey.py b/Scheduler/feature_scheduler/maintel/fbs_config_sv_survey.py index 51812fd9..047a37fe 100644 --- a/Scheduler/feature_scheduler/maintel/fbs_config_sv_survey.py +++ b/Scheduler/feature_scheduler/maintel/fbs_config_sv_survey.py @@ -99,7 +99,9 @@ def get_scheduler() -> tuple[int, CoreScheduler]: # Set up the DDF surveys to dither single_ddf_dither = detailers.DitherDetailer( - per_night=per_night, max_dither=max_dither + per_night=per_night, + max_dither=max_dither, + nnights=1000, ) # per_night true requires an update of FBS for Euclid # dither_detailer = detailers.SplitDetailer( diff --git a/Scheduler/feature_scheduler/maintel/fbs_config_sv_survey_generator_only.py b/Scheduler/feature_scheduler/maintel/fbs_config_sv_survey_generator_only.py new file mode 100644 index 00000000..70a2e72b --- /dev/null +++ b/Scheduler/feature_scheduler/maintel/fbs_config_sv_survey_generator_only.py @@ -0,0 +1,268 @@ +from pathlib import Path + +import lsst.ts.fbs.utils.maintel.sv_config as svc +import lsst.ts.fbs.utils.maintel.sv_surveys as svs +import numpy as np +import rubin_scheduler.scheduler.detailers as detailers +from astropy.time import Time +from rubin_scheduler.scheduler.basis_functions import AltAzShadowMaskBasisFunction +from rubin_scheduler.scheduler.schedulers import CoreScheduler +from rubin_scheduler.scheduler.surveys import LongGapSurvey + +__all__ = ("get_scheduler",) + + +def get_scheduler() -> tuple[int, CoreScheduler]: + """Construct the SV survey scheduler. + + Returns + ------- + nside : `int` + Healpix map resolution. + scheduler : `rubin_scheduler.scheduler.scheduler.CoreScheduler` + Feature based scheduler. + """ + + nside = 32 + science_program = "BLOCK-365" + band_to_filter = { + "u": "u_24", + "g": "g_6", + "r": "r_57", + "i": "i_39", + "z": "z_20", + "y": "y_10", + } + exptime = 30 + nexp = 1 + u_exptime = 38 + u_nexp = 1 + + footprint_weight = 10.0 # standard is 1.5 for blob surveys + + # survey_start is used to "start the clock" for several basis functions + survey_start_mjd = Time("2025-06-20T12:00:00", format="isot", scale="utc").mjd + # survey_length controls distribution of DDF sequences + survey_length = 100 # days + + camera_rot_limits = (-80.0, 80.0) + pair_time = 33.0 + # Adjust these as the expected timing vary. + # They set the expected time and number of pointings in a 'blob'. + blob_survey_params = { + "slew_approx": 11, + "band_change_approx": 140.0, + "read_approx": 2.4, + "flush_time": 30.0, + "smoothing_kernel": None, + "nside": nside, + "seed": 42, + "dither": "night", + "twilight_scale": True, + } + + # DDF dither dithers + camera_ddf_rot_limit = 75 # Rotator limit for DDF (degrees) + camera_ddf_rot_per_visit = 2.0 # small rotation per visit (degrees) + max_dither = 0.5 # Max radial dither for DDF (degrees) + per_night = False # Dither DDF per night (True) or per visit (False) + # Get path for ddf sequence configuration file + config_dir = Path(__file__).parent + ddf_config_file = Path.joinpath(config_dir, "ddf_sv_generator.dat") + if not Path.exists(ddf_config_file): + raise ValueError( + f"Expected target yaml file does not exist at {ddf_config_file}" + ) + + # Template acquisition parameters + template_ha_range = 2.5 + template_area = 50.0 + template_time = 33.0 + + # Long gaps frequency + nights_off = 3 # For long gaps + gaps_night_pattern = [True] + [False] * nights_off + + # Survey footprints + survey_info = svc.survey_footprint(survey_start_mjd=survey_start_mjd, nside=nside) + # footprints for primary Surveys + footprints = survey_info["Footprints"] + + long_gaps = svs.gen_long_gaps_survey( + nside=nside, + footprints=footprints, + pair_time=pair_time, + camera_rot_limits=camera_rot_limits, + exptime=exptime, + nexp=nexp, + u_exptime=u_exptime, + u_nexp=u_nexp, + night_pattern=gaps_night_pattern, + blob_survey_params=blob_survey_params, + footprint_weight=footprint_weight, + ) + + # Set up the DDF surveys to dither + single_ddf_dither = detailers.DitherDetailer( + per_night=per_night, + max_dither=max_dither, + nnights=1000, + ) + # per_night true requires an update of FBS for Euclid + # dither_detailer = detailers.SplitDetailer( + # single_ddf_dither, detailers.EuclidDitherDetailer(per_night=True) + # ) + detailer_list = [ + detailers.CameraSmallRotPerObservationListDetailer( + min_rot=-camera_ddf_rot_limit, + max_rot=camera_ddf_rot_limit, + per_visit_rot=camera_ddf_rot_per_visit, + ), + single_ddf_dither, + detailers.BandSortDetailer(), + ] + + ddfs = svs.gen_ddf_surveys( + detailer_list=detailer_list, + nside=nside, + expt={ + "u": u_exptime, + "g": exptime, + "r": exptime, + "i": exptime, + "z": exptime, + "y": exptime, + }, + nexp={"u": u_nexp, "g": nexp, "r": nexp, "i": nexp, "z": nexp, "y": nexp}, + survey_start=survey_start_mjd, + survey_length=survey_length / 365.25, + ddf_config_file=ddf_config_file, + ) + + greedy = svs.gen_greedy_surveys( + nside=nside, + footprints=footprints, + camera_rot_limits=camera_rot_limits, + exptime=exptime, + nexp=nexp, + u_exptime=u_exptime, + u_nexp=u_nexp, + ) + + blobs = svs.generate_blobs( + nside=nside, + footprints=footprints, + pair_time=pair_time, + camera_rot_limits=camera_rot_limits, + exptime=exptime, + nexp=nexp, + u_exptime=u_exptime, + u_nexp=u_nexp, + survey_start=survey_start_mjd, + blob_survey_params=blob_survey_params, + footprint_weight=footprint_weight, + ) + + twi_blobs = svs.generate_twi_blobs( + nside=nside, + footprints=footprints, + pair_time=15.0, + camera_rot_limits=camera_rot_limits, + exptime=exptime, + nexp=nexp, + night_pattern=[True, True], + blob_survey_params=blob_survey_params, + footprint_weight=footprint_weight, + ) + + templ_surveys = svs.gen_template_surveys( + nside=nside, + footprints=footprints, + pair_time=template_time, + camera_rot_limits=camera_rot_limits, + exptime=exptime, + nexp=nexp, + u_exptime=u_exptime, + u_nexp=u_nexp, + area_required=template_area, + HA_min=template_ha_range, + HA_max=24 - template_ha_range, + science_program=science_program, + blob_survey_params=blob_survey_params, + footprint_weight=footprint_weight, + ) + + lvk_templates = svs.gen_lvk_templates( + nside=nside, + bands=("g", "i"), + survey_start=survey_start_mjd, + footprints_hp=survey_info["extra_templates_array"], + camera_rot_limits=camera_rot_limits, + exptime=exptime, + nexp=nexp, + science_program=science_program, + ) + + # No near-sun twilight survey yet + # No Roman survey yet + surveys = [ddfs, long_gaps, templ_surveys, blobs, twi_blobs, greedy, lvk_templates] + + # Label regions for all surveys + for tier in surveys: + for survey in tier: + if isinstance(survey, LongGapSurvey): + survey.blob_survey.detailers.append(detailers.LabelRegionsAndDDFs()) + survey.scripted_survey.detailers.append(detailers.LabelRegionsAndDDFs()) + else: + survey.detailers.append(detailers.LabelRegionsAndDDFs()) + + # Add AltAz basis function to mask off all of the sky outside of the + # morning azimuth range, for the entire night + altaz_mask = AltAzShadowMaskBasisFunction( + nside=nside, + min_alt=20, + max_alt=86.5, + min_az=120, + max_az=290, + shadow_minutes=40, + ) + altaz_mask2 = AltAzShadowMaskBasisFunction( + nside=nside, + min_alt=20, + max_alt=86.5, + min_az=120, + max_az=290, + shadow_minutes=60, + ) + for tier in surveys: + for survey in tier: + if isinstance(survey, LongGapSurvey): + survey.blob_survey.basis_functions.append(altaz_mask) + survey.blob_survey.basis_functions.append(altaz_mask2) + survey.blob_survey.basis_weights = np.concatenate( + [survey.blob_survey.basis_weights, np.array([0, 0])] + ) + survey.scripted_survey.basis_functions.append(altaz_mask) + survey.scripted_survey.basis_functions.append(altaz_mask2) + survey.scripted_survey.basis_weights = np.concatenate( + [survey.scripted_survey.basis_weights, np.array([0, 0])] + ) + else: + survey.basis_functions.append(altaz_mask) + survey.basis_functions.append(altaz_mask2) + survey.basis_weights = np.concatenate( + [survey.basis_weights, np.array([0, 0])] + ) + + scheduler = CoreScheduler( + surveys, + nside=nside, + band_to_filter=band_to_filter, + survey_start_mjd=survey_start_mjd, + ) + + return (nside, scheduler) + + +if __name__ == "config": + (nside, scheduler) = get_scheduler() diff --git a/Scheduler/v8/fbs_config_sv_survey_generator_only.yaml b/Scheduler/v8/fbs_config_sv_survey_generator_only.yaml new file mode 100644 index 00000000..4d6cfaac --- /dev/null +++ b/Scheduler/v8/fbs_config_sv_survey_generator_only.yaml @@ -0,0 +1,51 @@ +maintel: + driver_type: feature_scheduler + mode: ADVANCE + startup_type: COLD + startup_database: /home/saluser/rubin_sim_data/sv_fbs_observation_database.sql + models: + observatory_model: + camera: + filter_max_changes_burst_num: 1000000 + filter_max_changes_avg_num: 30000 + park: + filter_position: r + telescope: + azimuth_maxspeed: 2.0 + azimuth_accel: 2.0 + altitude_maxspeed: 1.0 + altitude_accel: 1.0 + settle_time: 3.0 + driver_configuration: + parameters: + night_boundary: -10.0 + stop_tracking_observing_script_name: maintel/stop_tracking.py + feature_scheduler_driver_configuration: + observation_database_name: /home/saluser/rubin_sim_data/sv_fbs_observation_database.sql + scheduler_config: /net/obs-env/auto_base_packages/ts_config_ocs/Scheduler/feature_scheduler/maintel/fbs_config_sv_survey_generator_only.py + telemetry: + lfa_client: + source: DREAM + delta_time: 300.0 + db_name: efd + streams: + - name: seeing + efd_table: lsst.sal.DIMM.logevent_dimmMeasurement + efd_columns: + - fwhm + efd_delta_time: 300.0 + fill_value: 1.0 + - name: wind_speed + efd_table: lsst.sal.ESS.airFlow + efd_columns: + - speed + efd_delta_time: 300.0 + fill_value: 0.0 + csc_index: 301 + - name: wind_direction + efd_table: lsst.sal.ESS.airFlow + efd_columns: + - direction + efd_delta_time: 300.0 + fill_value: 0.0 + csc_index: 301