In [29]:
import os
import warnings
import copy
import logging
import numpy as np
import pandas as pd
import sqlite3
import healpy as hp
import matplotlib.pyplot as plt
import colorcet as cc
import skyproj
from IPython.display import display, HTML
from astropy.time import Time, TimeDelta
from astropy.coordinates import SkyCoord
import astropy.units as u
from rubin_scheduler.site_models import Almanac
from rubin_scheduler.scheduler.features import Conditions
from rubin_scheduler.utils import ddf_locations, angular_separation, approx_ra_dec2_alt_az, Site, SURVEY_START_MJD
import rubin_sim.maf as maf
from rubin_nights import connections
import rubin_nights.lfa_data as rn_lfa
import rubin_nights.dayobs_utils as rn_dayobs
import rubin_nights.plot_utils as rn_plots
import rubin_nights.augment_visits as augment_visits
import rubin_nights.rubin_scheduler_addons as rn_sch
import rubin_nights.rubin_sim_addons as rn_sim
import importlib
from lsst_survey_sim import lsst_support, simulate_lsst, plot
band_colors = rn_plots.PlotStyles.band_colors
logging.getLogger('lsst_survey_sim').setLevel(logging.INFO)
#%load_ext memory_profiler
In [2]:
day_obs = 20251020
survey_start = SURVEY_START_MJD
CONFIG_SCRIPT_PATH = "/Users/lynnej/lsst_repos/ts_config_scheduler/Scheduler/feature_scheduler/maintel/fbs_config_lsst_survey.py"
tokenfile = "/Users/lynnej/.lsst/usdf_rsp"
site = "usdf"
sunset, sunrise = rn_dayobs.day_obs_sunset_sunrise(day_obs, sun_alt=-12)
print(sunset.iso, sunrise.iso)
2025-10-20 23:51:29.829 2025-10-21 09:04:37.825
In [3]:
# Retrieve previous visits to start scheduler
refresh_visits = True
if refresh_visits:
initial_opsim = simulate_lsst.fetch_previous_visits(day_obs, tokenfile=tokenfile, site=site)
if initial_opsim is not None:
initial_opsim.to_hdf("opsim.h5", key='visits')
else:
initial_opsim = pd.read_hdf("opsim.h5")
if initial_opsim is None:
print("No visits found")
else:
print(f"Found {len(initial_opsim)} visits")
No visits found
In [4]:
# Configure scheduler and add initial visits
starting_scheduler, initial_opsim, nside = simulate_lsst.setup_scheduler(config_script_path=CONFIG_SCRIPT_PATH, day_obs=day_obs, initial_opsim=initial_opsim, tokenfile=tokenfile, site=site)
/Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 56 ELAISS1 sequences, but only 55 nights. Decreasing sequences. warnings.warn( /Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 200 COSMOS sequences, but only 123 nights. Decreasing sequences. warnings.warn( /Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 200 COSMOS sequences, but only 122 nights. Decreasing sequences. warnings.warn( /Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 200 COSMOS sequences, but only 124 nights. Decreasing sequences. warnings.warn( /Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 200 XMM_LSS sequences, but only 131 nights. Decreasing sequences. warnings.warn( /Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 200 XMM_LSS sequences, but only 135 nights. Decreasing sequences. warnings.warn( /Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 200 ELAISS1 sequences, but only 151 nights. Decreasing sequences. warnings.warn( /Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 200 ELAISS1 sequences, but only 149 nights. Decreasing sequences. warnings.warn( /Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 200 ECDFS sequences, but only 154 nights. Decreasing sequences. warnings.warn( /Users/lynnej/lsst_repos/ts_fbs_utils/python/lsst/ts/fbs/utils/maintel/lsst_ddf_presched.py:441: UserWarning: Asked for 200 EDFS_a sequences, but only 165 nights. Decreasing sequences. warnings.warn(
In [5]:
# Configure band scheduler
band_scheduler = simulate_lsst.setup_band_scheduler()
# Check what bands it expects for day_obs?
conditions = Conditions(mjd=sunset.mjd)
band_scheduler(conditions)
Out[5]:
['g', 'r']
In [6]:
starting_observatory, survey_info = simulate_lsst.setup_observatory(day_obs=day_obs, nside=nside, add_downtime=True, add_clouds=True, seeing=None, real_downtime=False, initial_opsim=initial_opsim)
In [7]:
mask = np.where(survey_info['dayobsmjd'] < rn_dayobs.day_obs_to_time(day_obs).mjd + 365)
print(f"Total nighttime {survey_info["hours_in_night"][mask].sum()}, ")
print(f"total downtime {survey_info["downtime_per_night"][mask].sum()}, ")
print(f"available time {survey_info["hours_in_night"][mask].sum() - survey_info["downtime_per_night"][mask].sum()}")
print(f"Average availability mask - {(survey_info["hours_in_night"][mask].sum() - survey_info["downtime_per_night"][mask].sum()) / survey_info["hours_in_night"][mask].sum()}")
print(f"Average availability over survey {survey_info['system_availability']}")
Total nighttime 3671.403386067599, total downtime 799.6621342669241, available time 2871.741251800675 Average availability mask - 0.7821916988741916 Average availability over survey 0.9289383954009189
In [8]:
for start, end in survey_info['downtimes']:
x = np.floor(start - 0.5)
plt.plot((x, x), (start - x, end - x) , color='k')
x = np.floor(survey_info['sunsets12'] - 0.5)
y = survey_info['sunsets12'] - x
plt.fill_between(x, 0.8, y)
x = np.floor(survey_info['sunrises12'] - 0.5)
y = survey_info['sunrises12'] - x
#plt.axvline(rn_dayobs.day_obs_to_time(day_obs).mjd, color='r', linestyle=':', linewidth=1.8)
#plt.axvline(np.floor(Time("2025-09-06T12:00:00").mjd - 0.5))
plt.fill_between(x, y, 1.6)
plt.ylim(0.9, 1.5)
plt.xlim(survey_info['survey_start'].mjd, rn_dayobs.day_obs_to_time(day_obs).mjd + 400)
plt.xlabel("MJD", fontsize='large')
_ = plt.ylabel("Fraction of MJD", fontsize='large')
#plt.savefig("onsky_downtime.png", bbox_inches='tight')
In [9]:
check_ddfs = True
if check_ddfs:
ddfs = ddf_locations(skycoords=True)
lsst_site = Site('LSST')
times = np.arange(survey_info['survey_start'].mjd, survey_info['survey_end'].mjd, 1/24)
times = np.arange(np.floor(Time.now().mjd - 0.5),survey_info['survey_end'].mjd, 0.25/24)
times = np.arange(sunset.mjd - 0.1, sunrise.mjd + 0.1, 0.05/24)
sunmoon = survey_info['almanac'].get_sun_moon_positions(times)
moon_ra = sunmoon['moon_RA']
moon_dec = sunmoon['moon_dec']
sun_alt = np.degrees(sunmoon['sun_alt'])
ddf_moon_dist = {}
ddf_alt = {}
for ddf in ddfs:
ddf_moon_dist[ddf] = angular_separation(ddfs[ddf].ra.deg, ddfs[ddf].dec.deg, np.degrees(moon_ra), np.degrees(moon_dec))
alt, az = approx_ra_dec2_alt_az(ddfs[ddf].ra.deg, ddfs[ddf].dec.deg, lsst_site.latitude, lsst_site.longitude, times, lmst=None)
ddf_alt[ddf] = alt
plt.figure()
for ddf in ddfs:
mask = np.where((sun_alt <= -12) & (ddf_alt[ddf] > 40))
plt.plot(Time(times[mask], format='mjd').to_datetime(), ddf_moon_dist[ddf][mask], marker='.', linestyle='', label=ddf)
plt.legend(loc=(1.01, 0.5))
plt.axvline(sunset.to_datetime(), color='k', linestyle=':')
plt.axvline(sunrise.to_datetime(), color='k', linestyle=':')
plt.axhline(30)
plt.xticks(rotation=90)
plt.xlabel("MJD")
plt.ylabel("Distance to moon (deg)")
plt.figure()
for ddf in ddfs:
mask = np.where((sun_alt <= -12) & (ddf_alt[ddf] > 40))
plt.plot(Time(times[mask], format='mjd').to_datetime(), ddf_alt[ddf][mask], marker='.', linestyle='', label=ddf)
plt.legend(loc=(1.01, 0.5))
plt.axvline(sunset.to_datetime(), color='k', linestyle=':')
plt.axvline(sunrise.to_datetime(), color='k', linestyle=':')
plt.axhline(30)
plt.xticks(rotation=90)
plt.xlabel("MJD")
plt.ylabel("Altitude (deg)")
In [10]:
reset = True
if reset:
observatory = copy.deepcopy(starting_observatory)
scheduler = copy.deepcopy(starting_scheduler)
In [11]:
# Run the simulation for some nights
sim_nights = 30
observations, scheduler, observatory, rewards, obs_rewards, survey_info = simulate_lsst.run_sim(scheduler=scheduler,
band_scheduler=band_scheduler,
observatory=observatory,
survey_info=survey_info,
day_obs=day_obs,
sim_nights=sim_nights,
keep_rewards=False)
progress = 0.18%
/Users/lynnej/lsst_repos/rubin_scheduler/rubin_scheduler/skybrightness_pre/sky_model_pre.py:345: UserWarning: Sun high, using bright sky approx
warnings.warn("Sun high, using bright sky approx")
progress = 0.91%
/Users/lynnej/lsst_repos/rubin_scheduler/rubin_scheduler/utils/healpy_utils.py:342: UserWarning: Can not connect to any more pixels.
warnings.warn("Can not connect to any more pixels.")
progress = 3.58%
/Users/lynnej/lsst_repos/rubin_scheduler/rubin_scheduler/scheduler/features/features.py:509: UserWarning: Time must flow forwards to track the feature in NObservationsCurrentSeason.Not updating season_map. warnings.warn(
progress = 102.54%Skipped 0 observations Flushed 59 observations from queue for being stale Completed 8503 observations ran in 4 min = 0.1 hours
In [22]:
filename = "test.db"
obsdf = simulate_lsst.consolidate_and_save(observatory, observations, initial_opsim=None, filename=filename)
def add_dayobs(x):
mjdfloor = Time(np.floor(x.observationStartMJD - 0.5) + 0.5, format="mjd", scale="tai")
return rn_dayobs.day_obs_str_to_int(mjdfloor.isot.split("T")[0])
obsdf["day_obs"] = obsdf.apply(add_dayobs, axis=1)
In [13]:
# Need to start at a specific time inside the night?
# sunset, sunrise = rn_dayobs.day_obs_sunset_sunrise(day_obs, sun_alt=-12)
# start = sunset.mjd - 0.1 /24
# end = sunrise.mjd
# with warnings.catch_warnings():
# warnings.simplefilter("ignore", RuntimeWarning)
# vals = sim_runner(
# observatory,
# scheduler,
# band_scheduler=fs,
# sim_start_mjd=start,
# sim_duration=end-start,
# record_rewards=False,
# verbose=True,
# )
# observatory = vals[0]
# scheduler = vals[1]
# observations = vals[2]
# if len(vals) == 5:
# rewards = vals[3]
# obs_rewards = vals[4]
In [14]:
if sim_nights == 1:
targets = pd.DataFrame(observations)
from zoneinfo import ZoneInfo
tz = ZoneInfo("Chile/Continental")
tz_utc = ZoneInfo("UTC")
telescope = "Simonyi"
site = Site('LSST')
almanac = Almanac()
night_events = almanac.get_sunset_info(evening_date=rn_dayobs.day_obs_int_to_str(day_obs), longitude=site.longitude_rad)
def mjd_to_datetime(mjd, scale='utc', timezone=tz):
return Time(mjd, format='mjd', scale=scale).utc.to_datetime(timezone=timezone)
eps = 1
fig, ax = plt.subplots(figsize=(13, 8))
ax_utc = ax.twiny()
ax.set_title(f"{telescope} DAYOBS {day_obs}", pad=20)
# Shade astronomical events
ax.fill_between([mjd_to_datetime(night_events['sun_n12_setting']),
mjd_to_datetime(night_events['sun_n18_setting'])],
2.5, 0.0, color='lightgray', alpha=0.3)
ax.fill_between([mjd_to_datetime(night_events['sunset']),
mjd_to_datetime(night_events['sun_n12_setting'])],
2.5, 0.0, color='gray', alpha=0.3)
ax.fill_between([mjd_to_datetime(night_events['sun_n18_rising']),
mjd_to_datetime(night_events['sun_n12_rising'])],
2.5, 0.0, color='lightgray', alpha=0.3)
ax.fill_between([mjd_to_datetime(night_events['sun_n12_rising']),
mjd_to_datetime(night_events['sunrise'])],
2.5, 0.0, color='gray', alpha=0.3)
ax.fill_between([mjd_to_datetime(night_events['sunrise'] - 2/24),
mjd_to_datetime(night_events['sun_n18_rising'])],
2.5, 0.0, color='pink', alpha=0.1)
if not np.isnan(night_events['moonrise']):
ax.axvline(mjd_to_datetime(night_events['moonrise']), linestyle='-', color='blue', alpha=0.3)
if not np.isnan(night_events['moonset']):
ax.axvline(mjd_to_datetime(night_events['moonset']), linestyle='-', color='red', alpha=0.3)
colors = cc.glasbey_category10
# Assign distinct target sets with different colors
marker_colors = {}
labels = {}
count = 0
if len(targets) > 0:
for sp in targets.observation_reason.unique():
marker_colors[sp] = colors[count]
labels[sp] = sp
count += 1
if len(targets) > 0:
visit_alpha = 0.7
for sp in targets.observation_reason.unique():
qq = targets.query("observation_reason == @sp")
label = sp
ax.plot(mjd_to_datetime(qq.mjd, 'tai'), qq.airmass,
marker='o', linestyle='',
color=marker_colors[sp], label=label,
alpha=visit_alpha, markerfacecolor='none', zorder=3)
ax.legend(loc=(1.01, 0.0), ncol=2)
x0 = night_events['sunset']+30/60/24
ax.set_xlim(mjd_to_datetime(night_events['sunset']+30/60/24),
mjd_to_datetime(night_events['sunrise']-30/60/24))
ax_utc.set_xlim(mjd_to_datetime(night_events['sunset']+30/60/24, 'utc', timezone=tz_utc),
mjd_to_datetime(night_events['sunrise']-30/60/24, 'utc', timezone=tz_utc))
ax.set_xlim(mjd_to_datetime(night_events['sunset']+30/60/24),
mjd_to_datetime(night_events['sunrise']-30/60/24))
ax_utc.set_xlim(mjd_to_datetime(night_events['sunset']+30/60/24, 'utc', timezone=tz_utc),
mjd_to_datetime(night_events['sunrise']-30/60/24, 'utc', timezone=tz_utc))
# Set ticks relevant sides
ax.tick_params(axis="x", bottom=True, top=False, labelbottom=True, labeltop=False)
ax_utc.tick_params(axis="x", bottom=False, top=True, labelbottom=False, labeltop=True)
# Rotate and align bottom ticklabels
plt.setp([tick.label1 for tick in ax.xaxis.get_major_ticks()], rotation=45,
ha="right", va="center", rotation_mode="anchor")
# Rotate and align top ticklabels
plt.setp([tick.label2 for tick in ax_utc.xaxis.get_major_ticks()], rotation=45,
ha="left", va="center",rotation_mode="anchor")
plt.grid(True, alpha=0.2)
plt.ylim(2.5, 0.9)
ax.set_ylabel("Airmass", fontsize="large")
ax.set_xlabel(f"Time ({tz})", fontsize="large")
ax_utc.set_xlabel("Time (UTC)", fontsize='large')
_ = plt.ylabel("Airmass", fontsize="large")
In [18]:
print(obsdf.target_name.unique())
print(obsdf.science_program.unique())
print(obsdf.observation_reason.unique())
['lowdust' 'ddf_edfs_a, lowdust' 'LMC_SMC' 'lowdust, LMC_SMC' 'euclid_overlap, LMC_SMC' 'euclid_overlap, scp' 'euclid_overlap' 'scp' 'scp, LMC_SMC' 'lowdust, ddf_edfs_b' 'scp, lowdust' 'euclid_overlap, lowdust' 'lowdust, nes' 'nes' 'lowdust, ddf_elaiss1' 'ddf_xmm_lss, lowdust' 'euclid_overlap, scp, LMC_SMC' 'scp, lowdust, LMC_SMC' 'lowdust, ddf_ecdfs' 'bulgy, lowdust' 'ddf_edfs_a, lowdust, ddf_edfs_b' 'dusty_plane, lowdust' 'ddf_ecdfs, lowdust' 'euclid_overlap, lowdust, LMC_SMC' 'ddf_elaiss1, lowdust' 'dusty_plane'] ['BLOCK-407'] ['singles_r' 'template_blob_r_33.0' 'template_blob_g_33.0' 'pairs_gr_33.0' 'pairs_iz_15.0' 'template_blob_u_33.0' 'template_blob_i_33.0' 'template_blob_z_33.0' 'pairs_yy_15.0' 'template_blob_y_33.0' 'pairs_yy_33.0' 'ddf_ecdfs' 'ddf_elaiss1' 'pairs_iz_33.0' 'ddf_edfs_a' 'ddf_xmm_lss' 'singles_i' 'pairs_ri_15.0' 'pairs_ri_33.0']
In [25]:
ddf_visits = obsdf.query("observation_reason.str.contains('DD') or observation_reason.str.contains('ddf')").copy()
print("n visits per band")
ddf_visits.loc[:, 'observation_reason'] = ddf_visits.observation_reason.str.lower()
ss = ddf_visits.groupby(["observation_reason", "band"]).agg({'observationStartMJD': 'count'})
ss = ss.reset_index('band').pivot(columns=["band"]).droplevel(0, axis=1)
ss['all'] = ss.sum(axis=1)
display(ss.query("observation_reason.str.contains('dd')")[['u', 'g', 'r', 'i', 'z', 'y', 'all']])
print("n days per band")
ss = ddf_visits.groupby(["observation_reason", "band"]).agg({'day_obs': 'nunique'})
ss = ss.reset_index('band').pivot(columns=["band"]).droplevel(0, axis=1)
ss['all'] = ddf_visits.groupby("observation_reason").agg({'day_obs': 'nunique'})
display(ss.query("observation_reason.str.contains('dd')")[['u', 'g', 'r', 'i', 'z', 'y', 'all']])
n visits per band
| band | u | g | r | i | z | y | all |
|---|---|---|---|---|---|---|---|
| observation_reason | |||||||
| ddf_ecdfs | 15 | 10 | 12 | 10 | 12 | 6 | 65 |
| ddf_edfs_a | 16 | 10 | 12 | 10 | 12 | 6 | 66 |
| ddf_elaiss1 | 18 | 14 | 16 | 14 | 16 | 10 | 88 |
| ddf_xmm_lss | 15 | 12 | 14 | 12 | 14 | 8 | 75 |
n days per band
| band | u | g | r | i | z | y | all |
|---|---|---|---|---|---|---|---|
| observation_reason | |||||||
| ddf_ecdfs | 5 | 5 | 6 | 5 | 6 | 3 | 13 |
| ddf_edfs_a | 4 | 5 | 6 | 5 | 6 | 3 | 13 |
| ddf_elaiss1 | 6 | 7 | 8 | 7 | 8 | 4 | 15 |
| ddf_xmm_lss | 5 | 6 | 7 | 6 | 7 | 4 | 13 |
In [32]:
run_calc = True
if run_calc:
nvisits = {}
coadd = {}
m_nvis = maf.CountMetric(col='observationStartMJD', metric_name = "Nvisits")
m_coadd = maf.Coaddm5Metric(m5_col='fiveSigmaDepth')
s = maf.HealpixSlicer(nside=64, lon_col='fieldRA', lat_col='fieldDec', rot_sky_pos_col_name = 'rotSkyPos')
for b in ['u', 'g', 'r', 'i', 'z', 'y', 'all']:
constraint = f"{b}"
if b == 'all':
opsvis = obsdf.to_records()
else:
opsvis = obsdf.query("band == @b").to_records()
nvisits[b] = maf.MetricBundle(m_nvis, s, constraint)
coadd[b] = maf.MetricBundle(m_coadd, s, constraint)
g = maf.MetricBundleGroup({f'nvisits {b}': nvisits[b], f'coadd {b}': coadd[b]}, None)
g.run_current(constraint, opsvis)
Healpix slicer using NSIDE=64, approximate resolution 54.967783 arcminutes
In [33]:
background = plot.get_background(nside=64)
fig, ax = plt.subplots(nrows=2, ncols=3, figsize=(16, 10),)
axdict = {"u": ax[0][0], "g": ax[0][1], "r": ax[0][2],
"i": ax[1][0], "z": ax[1][1], "y": ax[1][2], "all": None}
for b in ["u", "g", "r", "i", "z", "y"]:
if len(nvisits[b].metric_values.compressed()) > 1:
vmax = np.percentile(nvisits[b].metric_values.compressed(), 95)
else:
vmax = None
label_dec = False
if b == 'u' or b == 'i':
label_dec = True
fig = plot.make_plot(nvisits[b], background=background, proj='McBryde', vmax=vmax, ax=axdict[b], title=f"LSSTCam band {b}", label_dec=label_dec)
fig.tight_layout()
#fig.savefig(os.path.join(out_dir, f"lsstcam_nvisits_band.png"), bbox_inches='tight')
vmax = np.percentile(nvisits['all'].metric_values.compressed(), 95)
fig = plot.make_plot(nvisits['all'], background=background, proj='mcbryde', vmin=None, vmax=vmax, ax=None, title=f"LSSTCam visits")
#fig.savefig(os.path.join(out_dir, f"lsstcam_nvisits.png"), bbox_inches='tight')
In [ ]: