Source code for rubin_scheduler.site_models.cloud_data

__all__ = ("CloudData", "ConstantCloudData")

import os
import sqlite3
from dataclasses import dataclass

import numpy as np
from astropy.time import Time

from rubin_scheduler.data import get_data_dir


[docs] class CloudData: """Handle the cloud information. This class deals with the cloud information that was previously produced for OpSim version 3. Parameters ---------- start_time : astropy.time.Time The time of the start of the simulation. The cloud database will be assumed to start on Jan 01 of the same year. cloud_db : str, optional The full path name for the cloud database. Default None, which will use the database stored in the module (site_models/clouds_ctio_1975_2022.db). offset_year : float, optional Offset into the cloud database by 'offset_year' years. Default 0. scale : float (1e6) Enforce machine precision for cross-platform repeatability by scaling and rounding date values. """ def __init__(self, start_time, cloud_db=None, offset_year=0, scale=1e6): self.cloud_db = cloud_db if self.cloud_db is None: self.cloud_db = os.path.join(get_data_dir(), "site_models", "clouds_ctio_1975_2022.db") # Cloud database starts in Jan 01 of the year of the # start of the simulation. year_start = start_time.datetime.year + offset_year self.start_time = Time("%d-01-01" % year_start, format="isot", scale="tai") self.cloud_dates = None self.cloud_values = None self.scale = scale self.read_data()
[docs] def __call__(self, time): """Get the cloud for the specified time. Parameters ---------- time : `astropy.time.Time` Time in the simulation for which to find the current cloud coverage. The difference between this time and the start_time, plus the offset, will be used to query the cloud database for the 'current' conditions. Returns ------- cloud_value : `float` The fraction of the sky that is cloudy (measured in steps of 8ths) closest to the specified time. """ delta_time = (time - self.start_time).sec dbdate = delta_time % self.time_range + self.min_time if self.scale is not None: dbdate = np.round(dbdate * self.scale).astype(int) idx = np.searchsorted(self.cloud_dates, dbdate) # searchsorted ensures that left < date < right # but we need to know if date is closer to left or to right left = self.cloud_dates[idx - 1] right = self.cloud_dates[idx] # If we are only doing one time if np.size(left) == 1: if dbdate - left < right - dbdate: idx -= 1 # If we have an array of times else: d1 = dbdate - left d2 = right - dbdate to_sub = np.where(d1 < d2) idx[to_sub] -= 1 return self.cloud_values[idx]
[docs] def read_data(self): """Read the cloud data from disk. The default behavior is to use the module stored database. However, an alternate database file can be provided. The alternate database file needs to have a table called *Cloud* with the following columns: cloudId int : A unique index for each cloud entry. c_date int : The time (units=seconds) since the start of the simulation for the cloud observation. cloud float : The cloud coverage (in steps of 8ths) of the sky. """ with sqlite3.connect(self.cloud_db) as conn: cur = conn.cursor() query = "select c_date, cloud from Cloud order by c_date;" cur.execute(query) results = np.array(cur.fetchall()) self.cloud_dates = np.hsplit(results, 2)[0].flatten() self.cloud_values = np.hsplit(results, 2)[1].flatten() cur.close() # Make sure seeing dates are ordered appropriately (monotonically # increasing). ordidx = self.cloud_dates.argsort() self.cloud_dates = self.cloud_dates[ordidx] # Record this information, in case the cloud database does not # start at t=0. self.min_time = self.cloud_dates[0] self.max_time = self.cloud_dates[-1] self.time_range = self.max_time - self.min_time if self.scale is not None: self.cloud_dates = np.round(self.cloud_dates * self.scale).astype(int) self.cloud_values = self.cloud_values[ordidx]
[docs] @dataclass class ConstantCloudData: """Generate constant cloud information. Parameters ---------- cloud_fraction : `float` The fraction of the sky that is cloudy. """ cloud_fraction: float = 0.0
[docs] def __call__(self, time): """Get the cloud for the specified time. Parameters ---------- time : `astropy.time.Time` In principle, the time in the simulation for which to find the current cloud coverage. In practice, this argument is ignored. Returns ------- cloud_faction : `float` The fraction of the sky that is cloudy. """ return self.cloud_fraction