Example #1
0
def define_constraints(minAltitude, earliestObs, latestObs):
    """
    Define various constraints to define 'observability'.

    Format for times: YYYY-MM-DD

    Parameters
    ----------
    minAltitude : int
        minimum local elevation in degree
    earliestObs : str
        Earliest time of observation
    latestObs : str
        Latest time of observation

    Returns
    -------
    constraints : list
        list of constraints
    earliestObs : Time object
        Earliest time of observation
    latestObs : Time object
        Latest time of observation
    """
    constraints = [AtNightConstraint.twilight_astronomical(),
                   AltitudeConstraint(min=minAltitude*u.deg)]
    return constraints, Time(earliestObs), Time(latestObs)
Example #2
0
def run_months(observers, nameList, args):
    targets = [FixedTarget(coord=lookuptarget(name),name=name) for name in nameList]
    targetLabelList, ylabelsize = makeTargetLabels(nameList,args)

    constraints = [
        AltitudeConstraint(min=args.minAlt*u.deg),
        AtNightConstraint.twilight_astronomical(),
    ]
    
    outfn = args.outFileNameBase+"_monthly.pdf"
    with PdfPages(outfn) as pdf:
        for observer in observers:
            observability_months_table = months_observable(constraints,observer,targets,time_grid_resolution=1*u.hour)

            observability_months_grid = numpy.zeros((len(targets),12))
            for i, observable in enumerate(observability_months_table):
                for jMonth in range(1,13):
                    observability_months_grid[i,jMonth-1] = jMonth in observable

            observable_targets = targets
            observable_target_labels = targetLabelList
            ever_observability_months_grid = observability_months_grid
            if args.onlyEverObservable:
                target_is_observable = numpy.zeros(len(targets))
                for iMonth in range(observability_months_grid.shape[1]):
                    target_is_observable += observability_months_grid[:,iMonth]
                target_is_observable = target_is_observable > 0. # change to boolean numpy array
                observable_targets = [x for x, o in zip(targets,target_is_observable) if o]
                observable_target_labels = [x for x, o in zip(targetLabelList,target_is_observable) if o]
                ever_observability_months_grid = observability_months_grid[target_is_observable,:]

            fig, ax = mpl.subplots(
                figsize=(8.5,11),
                gridspec_kw={
                    "top":0.92,
                    "bottom":0.03,
                    "left":0.13,
                    "right":0.98,
                },
                tight_layout=False,constrained_layout=False
            )
            extent = [-0.5, -0.5+12, -0.5, len(observable_targets)-0.5]
            ax.imshow(ever_observability_months_grid, extent=extent, origin="lower", aspect="auto", cmap=mpl.get_cmap("Greens"))
            ax.xaxis.tick_top()
            ax.invert_yaxis()
            ax.set_yticks(range(0,len(observable_targets)))
            ax.set_yticklabels(observable_target_labels, fontsize=ylabelsize)
            ax.set_xticks(range(12))
            ax.set_xticklabels(["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"])
            ax.set_xticks(numpy.arange(extent[0], extent[1]), minor=True)
            ax.set_yticks(numpy.arange(extent[2], extent[3]), minor=True)
            ax.grid(which="minor",color="black",ls="-", linewidth=1)
            ax.tick_params(axis='y', which='minor', left=False, right=False)
            ax.tick_params(axis='x', which='minor', bottom=False, top=False)
        
            fig.suptitle(f"Monthly Observability at {observer.name}")
            fig.text(1.0,0.0,"Constraints: Astronomical Twilight, Altitude $\geq {:.0f}^\circ$".format(args.minAlt),ha="right",va="bottom")
            pdf.savefig(fig)
        print(f"Writing out file: {outfn}")
Example #3
0
 def __init__(self,
              lat,
              lon,
              elevation,
              ra,
              dec,
              discDate,
              airmassConstraint=2):
     self.airmassConstraint = airmassConstraint
     self.altConstraint = math.degrees(math.asin(1 /
                                                 self.airmassConstraint))
     self.location = EarthLocation(lat=lat, lon=lon, height=elevation * u.m)
     self.discDate = discDate
     self.observer = Observer(location=self.location, name="LT")
     self.target = SkyCoord(ra=ra, dec=dec, unit=(u.hourangle, u.deg))
     self.constraints = [
         AirmassConstraint(self.airmassConstraint),
         AtNightConstraint.twilight_astronomical()
     ]
     print("Discovery Date :", discDate.value)
Example #4
0
def observability(self, json_exoplanet_array, longitude, latitude, elevation, start_date, end_date, min_altitude, max_altitude, resolution):
    '''background task to (slowly) find planet observabilities'''
    
    # create Observer instance
    if elevation is None: elevation = 0
    custom_location = Observer(longitude=longitude*u.deg, latitude=latitude*u.deg, elevation=elevation*u.m)

    # time range
    if start_date is not None: start_date = Time(datetime.combine(datetime.fromisoformat(start_date), datetime.min.time()), out_subfmt='date')
    else: start_date = Time(datetime.combine(datetime.now(), datetime.min.time()), out_subfmt='date')
    if end_date is not None: end_date = Time(datetime.combine(datetime.fromisoformat(end_date), datetime.min.time()), out_subfmt='date')
    else: end_date = start_date + 30*u.day
    time_range = Time([start_date, end_date])

    # altitude and night time constraints
    if min_altitude is None: min_altitude = 0
    if max_altitude is None: max_altitude = 90
    constraints = [AltitudeConstraint(min_altitude*u.deg, max_altitude*u.deg), AtNightConstraint.twilight_astronomical()]
    
    # calculate if observable within lookahead time from now
    if resolution is None: resolution = 0.5
    
    # since we had to use a json string as argument, recreate the custom_exoplanet_array from the json
    custom_exoplanet_array = []
    json_unload = json.loads(json_exoplanet_array) # unpack json to make a dictionary
    for dict_ in json_unload:
        planet = Exoplanet()
        # copy properties back over from dictionary
        for prop, val in dict_.items(): functions.rsetattr(planet, prop, val)
        custom_exoplanet_array.append(planet)

    # trying to split array into chunks so we can say the task has been completed to the nearest percent
    num_chunks = round(len(custom_exoplanet_array)/800 * (resolution/0.5)**-1 * ((end_date-start_date).jd/30)**2 * 10)
    if num_chunks == 0: num_chunks = 1
    exoplanet_array_chunks = np.array_split(custom_exoplanet_array, num_chunks) # split exoplanet array into num_chunk parts

    planets_done = 0 # planets calculated for so far
    planets_lost = 0 # planets removed so far

    recombined_chunks = []
    for chunk in exoplanet_array_chunks:
        # make an array of FixedTarget instances
        target_table = []
        for planet in chunk:
            target_table.append((planet.name, planet.host.ra, planet.host.dec))
        targets = [FixedTarget(coord=SkyCoord(ra=ra*u.deg, dec=dec*u.deg), name=name)
                   for name, ra, dec in target_table]

        # calculate
        ever_observable = is_observable(constraints, custom_location, targets, time_range=time_range, time_grid_resolution=resolution*u.hour)

        # note unobservables
        to_pop = []
        for i in range(len(chunk)):
            chunk[i].observable = bool(ever_observable[i])
            if chunk[i].observable == False:
                to_pop.append(i)
        # remove unobservables
        chunk = np.delete(chunk, to_pop)

        # record numbers
        planets_done += len(targets)
        planets_lost += len(targets) - len(chunk)

        # write json
        recombined_chunks += chunk.tolist()
        observable_exoplanet_array = [planet for planet in recombined_chunks if planet.observable]
        json_exoplanet_array = functions.planet_array_to_json_array(observable_exoplanet_array, 'host_')

        # graph of decision metric vs rank
        tooltip_dict = {
            'name'            : [],
            'mass'            : [],
            'radius'          : [],
            'orbital_period'  : [],
            'semi_major_axis' : [],
            'temp_calculated' : [],
            'detection_type'  : [],
            'decision_metric' : [],
            'filter'          : [],
            't_exp'           : [],
        }
        graph = functions.metric_rank_bar_graph(observable_exoplanet_array, tooltip_dict)

        # update status
        self.update_state(state='PROGRESS',
                          meta={'current'         : planets_done,
                                'removed'         : planets_lost,
                                'total'           : len(custom_exoplanet_array),
                                'exoplanet_array' : json_exoplanet_array,
                                'graph'           : graph,
                                'finished'        : 'n'})
    
    return {'current': planets_done, 'removed': planets_lost, 'total': len(custom_exoplanet_array), 'exoplanet_array': json_exoplanet_array, 'graph': graph, 'finished': 'y'}
def get_event_observability(
    eventclass,
    site, ra, dec, name, t_mid_0, period, duration, n_transits=100,
    obs_start_time=Time(dt.datetime.today().isoformat()),
    min_altitude = None,
    oot_duration = 30*u.minute,
    minokmoonsep = 30*u.deg,
    max_airmass = None,
    twilight_limit = 'nautical'):
    """
    note: barycentric corrections not yet implemented. (could do this myself!)
    -> 16 minutes of imprecision is baked into this observability calculator!

    args:

        eventclass: e.g., "OIBE". Function does NOT return longer events.

        site (astroplan.observer.Observer)

        ra, dec (units u.deg), e.g.:
            ra=101.28715533*u.deg, dec=16.71611586*u.deg,
        or can also accept
            ra="17 56 35.51", dec="-29 32 21.5"

        name (str), e.g., "Sirius"

        t_mid_0 (float): in BJD_TDB, preferably (but see note above).

        period (astropy quantity, units time)

        duration (astropy quantity, units time)

        n_transits (int): number of transits forward extrapolated to

        obs_start_time (astropy.Time object): when to start calculation from

        min_altitude (astropy quantity, units deg): 20 degrees is the more
        relevant constraint.

        max_airmass: e.g., 2.5. One of max_airmass or min_altitude is required.

        oot_duration (astropy quantity, units time): with which to brack
        transit observations, to get an OOT baseline.

        twilight_limit: 'astronomical', 'nautical', 'civil' for -18, -12, -6
        deg.
    """
    if eventclass not in [
        'OIBEO', 'OIBE', 'IBEO', 'IBE', 'BEO', 'OIB', 'OI', 'EO'
    ]:
        raise AssertionError

    if (isinstance(ra, u.quantity.Quantity) and
        isinstance(dec, u.quantity.Quantity)
    ):
        target_coord = SkyCoord(ra=ra, dec=dec)
    elif (isinstance(ra, str) and
          isinstance(dec, str)
    ):
        target_coord = SkyCoord(ra=ra, dec=dec, unit=(u.hourangle, u.deg))
    else:
        raise NotImplementedError

    if (
        not isinstance(max_airmass, float)
        or isinstance(min_altitude, u.quantity.Quantity)
    ):
        raise NotImplementedError

    target = FixedTarget(coord=target_coord, name=name)

    primary_eclipse_time = Time(t_mid_0, format='jd')

    system = EclipsingSystem(primary_eclipse_time=primary_eclipse_time,
                             orbital_period=period, duration=duration,
                             name=name)

    midtransit_times = system.next_primary_eclipse_time(
        obs_start_time, n_eclipses=n_transits)

    # for the time being, omit any local time constraints.
    if twilight_limit == 'astronomical':
        twilight_constraint = AtNightConstraint.twilight_astronomical()
    elif twilight_limit == 'nautical':
        twilight_constraint = AtNightConstraint.twilight_nautical()
    else:
        raise NotImplementedError('civil twilight is janky.')

    constraints = [twilight_constraint,
                   AltitudeConstraint(min=min_altitude),
                   AirmassConstraint(max=max_airmass),
                   MoonSeparationConstraint(min=minokmoonsep)]

    # tabulate ingress and egress times.
    ing_egr = system.next_primary_ingress_egress_time(
        obs_start_time, n_eclipses=n_transits
    )

    oibeo_window = np.concatenate(
        (np.array(ing_egr[:,0] - oot_duration)[:,None],
         np.array(ing_egr[:,1] + oot_duration)[:,None]),
        axis=1)
    oibe_window = np.concatenate(
        (np.array(ing_egr[:,0] - oot_duration)[:,None],
         np.array(ing_egr[:,1])[:,None]),
        axis=1)
    ibeo_window = np.concatenate(
        (np.array(ing_egr[:,0])[:,None],
         np.array(ing_egr[:,1] + oot_duration)[:,None]),
        axis=1)
    oib_window = np.concatenate(
        (np.array(ing_egr[:,0] - oot_duration)[:,None],
         np.array(midtransit_times)[:,None]),
        axis=1)
    beo_window = np.concatenate(
        (np.array(midtransit_times)[:,None],
         np.array(ing_egr[:,1] + oot_duration)[:,None]),
        axis=1)
    ibe_window = ing_egr
    oi_window = np.concatenate(
        (np.array(ing_egr[:,0] - oot_duration)[:,None],
        np.array(ing_egr[:,0])[:,None]),
        axis=1)
    eo_window = np.concatenate(
        (np.array(ing_egr[:,1])[:,None],
        np.array(ing_egr[:,1] + oot_duration)[:,None]),
        axis=1)

    keys = ['oibeo','oibe','ibeo','oib','beo','ibe','oi','eo']
    windows = [oibeo_window, oibe_window, ibeo_window,
               oib_window, beo_window, ibe_window, oi_window, eo_window]
    is_obs_dict = {}
    for key, window in zip(keys, windows):
        is_obs_dict[key] = np.array(
            is_event_observable(constraints, site, target,
                                times_ingress_egress=window)
        ).flatten()

    is_obs_df = pd.DataFrame(is_obs_dict)

    is_obs_df['ing'] = ing_egr[:,0]
    is_obs_df['egr'] = ing_egr[:,1]
    is_obs_df['isoing'] = Time(ing_egr[:,0], format='iso')
    is_obs_df['isoegr'] = Time(ing_egr[:,1], format='iso')

    # this function returns the observable events that are LONGEST. e.g.,
    # during an OIBEO transit you COULD observe just OIB, but why would you?

    if eventclass == 'OIBEO':
        event_ind = np.array(is_obs_df[eventclass.lower()])[None,:]
    elif eventclass in ['IBEO', 'OIBE']:
        event_ind = np.array(
            is_obs_df[eventclass.lower()] & ~is_obs_df['oibeo']
        )[None,:]
    elif eventclass in ['IBE', 'OIB', 'BEO']:
        event_ind = np.array(
            is_obs_df[eventclass.lower()]
            & ~is_obs_df['oibeo']
            & ~is_obs_df['oibe']
            & ~is_obs_df['ibeo']
        )[None,:]
    elif eventclass in ['OI', 'EO']:
        event_ind = np.array(
            is_obs_df[eventclass.lower()]
            & ~is_obs_df['oibeo']
            & ~is_obs_df['oibe']
            & ~is_obs_df['ibeo']
            & ~is_obs_df['oib']
            & ~is_obs_df['ibe']
            & ~is_obs_df['beo']
        )[None,:]

    # get moon separation over each transit. take minimum moon sep at
    # ing/tmid/egr as the moon separation.
    moon_tmid = get_moon(midtransit_times, location=site.location)
    moon_separation_tmid = moon_tmid.separation(target_coord)

    moon_ing = get_moon(ing_egr[:,0], location=site.location)
    moon_separation_ing = moon_ing.separation(target_coord)

    moon_egr = get_moon(ing_egr[:,1], location=site.location)
    moon_separation_egr = moon_egr.separation(target_coord)

    moon_separation = np.round(np.array(
        [moon_separation_tmid, moon_separation_ing,
         moon_separation_egr]).min(axis=0),0).astype(int)

    moon_illumination = np.round(
        100*moon.moon_illumination(midtransit_times),0).astype(int)

    # completely observable transits (OOT, ingress, bottom, egress, OOT)
    oibeo = is_event_observable(constraints, site, target,
                                times_ingress_egress=oibeo_window)

    ing_tmid_egr = np.concatenate(
        (np.array(ing_egr[:,0])[:,None],
         np.array(midtransit_times)[:,None],
         np.array(ing_egr[:,1])[:,None]),
        axis=1)

    target_window = np.array(windows)[
        int(np.argwhere(np.array(keys)==eventclass.lower())), :, :
    ]

    return (
        event_ind, oibeo, ing_tmid_egr, target_window,
        moon_separation, moon_illumination
    )
Example #6
0
from django.test import TestCase

from mop.toolbox.obs_details import all_night_moon_sep, calculate_visibility
from mop.toolbox.LCO_obs_locs import choose_loc

OGG = choose_loc('OGG')
v_test_target = ['Sirius', 100.7362500 * u.deg, -16.6459444 * u.deg]
v_date = Time("2019-12-25 00:00:00", scale='utc')
v_coords = SkyCoord(v_test_target[1], v_test_target[2], frame='icrs')
v_obs_begin = OGG.twilight_evening_astronomical(v_date, which='nearest')
v_obs_end = OGG.twilight_morning_astronomical(v_date, which='next')
v_observing_range = [v_obs_begin, v_obs_end]
constraints = [
    AirmassConstraint(2.0),
    AltitudeConstraint(20 * u.deg, 85 * u.deg),
    AtNightConstraint.twilight_astronomical()
]
ever_observable = is_observable(constraints,
                                OGG,
                                v_coords,
                                time_range=v_observing_range)

v_fail_start = Time("2019-12-24 10:00:00", scale='utc')
v_fail_end = Time("2019-12-25 10:00:00", scale='utc')


class TestVisibilityCalc(TestCase):
    def test_timeobj(self):
        self.assertEqual(v_date.scale, 'utc')
        self.assertEqual(v_date.value, '2019-12-25 00:00:00.000')
Example #7
0
for i in range(0, l):
    targs.append(
        FixedTarget(coord=SkyCoord(ra=(t[i]['ra']),
                                   dec=(t[i]['dec']),
                                   unit=(u.hourangle, u.deg)),
                    name=t[i]['alt_name'] + " [" + t[i]['iau_name'] + ", " +
                    t[i]['ra'] + ", " + t[i]['dec'] + "]"))
    # Accounting for the fact that ra is in [hour min sec] and dec is in [deg arcmin arcsec]
    # the "name" attribute is all of the information just smashed together and I know it's unclean, please don't judge me

    # print((targs[len(targs)-1].name)) # This was just to check where errors occurred

time_range = Time(["2018-04-15 00:01", "2018-04-30 23:59"])
# This just takes the whole swath of time, not refining it or anything

cons = [AirmassConstraint(2), AtNightConstraint.twilight_astronomical()]
# don't use civil twilight because astronomers aren't civil

obs_tab = observability_table(cons, kitt, targs, time_range=time_range)
# print(obs_tab)

obs_targs = []
total_save = []
# total_save is to keep all the data, not just some of it

for i in range(0, len(obs_tab)):
    if obs_tab[i]['ever observable']:
        obs_targs.append([
            obs_tab[i]['target name'],
            obs_tab[i]['fraction of time observable']
        ])
Example #8
0
def observability_objects(data):
    """
    Test the observability of a list of objects for a single date

    Parameters
    ----------
    data : POST data format

    data = {
        'observatory' : 'OT',
        'altitude_lower_limit' : '30',
        'altitude_higher_limit' : '90',
        'objects' : [{
                'name' : 'Kelt 8b',
                'RA' : 283.30551667 ,
                'Dec' : 24.12738139,
                'dates' : [
                        ['2020-06-11 00:16:30', '2020-06-11 03:44:26'],
                        ['2020-06-14 06:07:56', '2020-06-14 09:35:53']
                    ]
                },
                {
                    'name' : 'TIC 123456789',
                    'RA' : 13.13055667 ,
                    'Dec' : 24.13912738,
                    'dates' : [
                        ['2020-06-11 23:59:59']
                    ]
                }
            ]
        }

    Returns
    -------
    observability : dict
        Dictionary with the observability and moon distance for all objects

        {'V0879 Cas' : {
                'observability' : 'True', 'moon_separation' : 30.4
            },
        'RU Scl' : {
            'observability' : 'True', 'moon_separation' : 10.8
            }
        }

    """

    import astropy.units as u
    from astroplan import FixedTarget
    from astroplan import (AltitudeConstraint, AtNightConstraint)
    from astroplan import is_observable, is_always_observable

    # Site location
    location = get_location(data['observatory'])

    # dict of observability for each target
    observabilities = {}

    if 'twilight_type' not in data.keys():
        data['twilight_type'] = 'astronomical'

    if data['twilight_type'] == 'civil':
        twilight_constraint = AtNightConstraint.twilight_civil()
    elif data['twilight_type'] == 'nautical':
        twilight_constraint = AtNightConstraint.twilight_nautical()
    else:
        twilight_constraint = AtNightConstraint.twilight_astronomical()

    # Observation constraints
    constraints = [
        AltitudeConstraint(
            float(data['altitude_lower_limit']) * u.deg,
            float(data['altitude_higher_limit']) * u.deg), twilight_constraint
    ]

    for target in data['objects']:

        coords = SkyCoord(ra=target['RA'] * u.deg, dec=target['Dec'] * u.deg)
        fixed_target = [FixedTarget(coord=coords, name=target['name'])]

        observabilities[target['name']] = []

        for date in target['dates']:

            # time range for transits
            # Always observable for time range
            if len(date) > 1:
                # If exoplanet transit, test observability always during transit,
                # if not, test observability *ever* during night
                time_range = Time([date[0], date[1]])

                # Are targets *always* observable in the time range?
                observable = is_always_observable(constraints,
                                                  location,
                                                  fixed_target,
                                                  time_range=time_range)

            # No time range, *ever* observabable during the night
            # Observability is test from sunset to sunrise
            # Default time resolution is 0.5h
            else:
                sunset = location.sun_set_time(Time(date[0]))
                sunrise = location.sun_rise_time(Time(date[0]), 'next')
                time_range = Time([sunset, sunrise])
                observable = is_observable(constraints,
                                           location,
                                           fixed_target,
                                           time_range=time_range)

            # Moon location for the observation date
            moon = location.moon_altaz(Time(date[0]))
            moon_separation = moon.separation(coords)

            observabilities[target['name']].append({
                'observable':
                str(observable[0]),
                'moon_separation':
                moon_separation.degree
            })

    return observabilities
Example #9
0
def observability_dates(data):
    """
    Test the observability of a single objects for several nights

    If the first element of 'dates' contains a single date, then
    the observability is test as *ever* for the night. 
    If a time range is given, observability is test as *always* for the time range

    Parameters
    ----------
    data : POST data format

        # data for transiting planet, time range constrained
        data = {
            'name' : 'Kelt 8b',
            'RA' : 283.30551667 ,
            'Dec' : 24.12738139,
            'observatory' : 'OT',
            'altitude_lower_limit' : '30',
            'altitude_higher_limit' : '90',
            'twilight_type' : 'astronomical',
            'dates' : [
                    ['2020-06-11 00:16:30', '2020-06-11 03:44:26'],
                    ['2020-06-14 06:07:56', '2020-06-14 09:35:53']
                ]
            }
        
        # data for ordinary target, twilight constrained
        # single date list

        data = {
            'name' : 'KIC8012732',
            'RA' : 284.72949583 ,
            'Dec' : 43.86421667,
            'observatory' : 'OT',
            'altitude_lower_limit' : '30',
            'altitude_higher_limit' : '90',
            'dates' : [
                        ['2020-06-11 23:00:00']
                    ]
            }

    Returns
    -------
    observability : dict
        Dictionary with the observability and moon distance for all objects

        {'V0879 Cas' : {
                'observability' : 'True', 'moon_separation' : 30.4
            },
        'RU Scl' : {
                'observability' : 'True', 'moon_separation' : 10.8
            }
        }

    """

    import astropy.units as u
    from astroplan import FixedTarget
    from astroplan import (AltitudeConstraint, AtNightConstraint)
    from astroplan import is_observable, is_always_observable

    # Site location
    location = get_location(data['observatory'])

    coords = SkyCoord(ra=data['RA'] * u.deg, dec=data['Dec'] * u.deg)
    fixed_target = [FixedTarget(coord=coords, name=data['name'])]

    # List of dates of observability
    observabilities = []

    if 'twilight_type' not in data.keys():
        data['twilight_type'] = 'astronomical'

    if data['twilight_type'] == 'civil':
        twilight_constraint = AtNightConstraint.twilight_civil()
    elif data['twilight_type'] == 'nautical':
        twilight_constraint = AtNightConstraint.twilight_nautical()
    else:
        twilight_constraint = AtNightConstraint.twilight_astronomical()

    # Observation constraints
    constraints = [
        AltitudeConstraint(
            float(data['altitude_lower_limit']) * u.deg,
            float(data['altitude_higher_limit']) * u.deg), twilight_constraint
    ]

    for date in data['dates']:

        # time range for transits
        # Always observable for time range
        if len(data['dates'][0]) > 0:

            # If exoplanet transits, check for observability always during transit,
            # if not, check observability *ever* during night
            time_range = Time([date[0], date[1]])

            # Are targets *always* observable in the time range?
            observable = is_always_observable(constraints,
                                              location,
                                              fixed_target,
                                              time_range=time_range)

        # No time range, *ever* observabable during the night
        else:
            observable = is_observable(constraints,
                                       location,
                                       fixed_target,
                                       times=Time(date[0]))

        # Moon location for the observation date
        moon = location.moon_altaz(Time(date[0]))
        moon_separation = moon.separation(coords)

        observabilities.append({
            'observable': str(observable[0]),
            'moon_separation': moon_separation.degree
        })

    return observabilities
Example #10
0
def observability(data):
    """
    Test the observability of a list of objects for a single date

    Parameters
    ----------
    data : POST data format

        Observatory, date, limits and objects
        data = {
            'observatory' : 'OT',
            'date' : '2020-06-11 00:16:30',
            'date_end' : '2020-06-11 03:44:26',
            'altitude_lower_limit' : '30',
            'altitude_higher_limit' : '90',
            'twilight_type' : 'astronomical',
            'objects' : [{
                    'name' : 'Kelt 8b',
                    'RA' : 283.30551667 ,
                    'Dec' : 24.12738139
                    },
                    (more objects...)
                ]
            }

    Returns
    -------
    observability : dict
        Dictionary with the observability and moon distance for all objects
        {
            'V0879 Cas' : {
                'observability' : 'True', 'moon_separation' : 30.4
            },
            'RU Scl' : {
                'observability' : 'True', 'moon_separation' : 10.8
            }
        }

    """

    import astropy.units as u
    from astroplan import FixedTarget
    from astroplan import (AltitudeConstraint, AtNightConstraint)
    from astroplan import is_observable, is_always_observable

    # Site location
    location = get_location(data['observatory'])

    time_range = Time([data['date'], data['date_end']])

    if 'twilight_type' not in data.keys():
        data['twilight_type'] = 'astronomical'

    if data['twilight_type'] == 'civil':
        twilight_constraint = AtNightConstraint.twilight_civil()
    elif data['twilight_type'] == 'nautical':
        twilight_constraint = AtNightConstraint.twilight_nautical()
    else:
        twilight_constraint = AtNightConstraint.twilight_astronomical()

    # Observation constraints
    constraints = [
        AltitudeConstraint(
            int(data['altitude_lower_limit']) * u.deg,
            int(data['altitude_higher_limit']) * u.deg), twilight_constraint
    ]

    # Dictionary with star name and observability (bool str)
    result = {}

    # Moon location for the observation date
    middle_observing_time = time_range[-1] - (time_range[-1] -
                                              time_range[0]) / 2
    moon = location.moon_altaz(middle_observing_time)

    for target in data['objects']:
        # Object coordinates
        coords = SkyCoord(ra=target['RA'] * u.deg, dec=target['Dec'] * u.deg)
        fixed_target = [FixedTarget(coord=coords, name=target['name'])]

        if 'transit' in target.keys():
            time_range = Time(
                [target['transit']['t_early'], target['transit']['t_late']])
            # Are targets *always* observable in the time range?
            observable = is_always_observable(constraints,
                                              location,
                                              fixed_target,
                                              time_range=time_range)
        else:
            time_range = Time([data['date'], data['date_end']])
            # Are targets *ever* observable in the time range?
            observable = is_observable(constraints,
                                       location,
                                       fixed_target,
                                       time_range=time_range)

        moon_separation = moon.separation(coords)

        result[target['name']] = {
            'observable': str(observable[0]),
            'moon_separation': moon_separation.degree
        }

    return result
Example #11
0
    async def _schedule(self):
        """Actually do the scheduling, usually run in a separate process."""

        # only global constraint is the night
        if self._twilight == "astronomical":
            constraints = [AtNightConstraint.twilight_astronomical()]
        elif self._twilight == "nautical":
            constraints = [AtNightConstraint.twilight_nautical()]
        else:
            raise ValueError("Unknown twilight type.")

        # make shallow copies of all blocks and loop them
        copied_blocks = [copy.copy(block) for block in self._blocks]
        for block in copied_blocks:
            # astroplan's PriorityScheduler expects lower priorities to be more important, so calculate
            # 1000 - priority
            block.priority = 1000.0 - block.priority
            if block.priority < 0:
                block.priority = 0

            # it also doesn't match the requested observing windows exactly, so we make them a little smaller.
            for constraint in block.constraints:
                if isinstance(constraint, TimeConstraint):
                    constraint.min += 30 * u.second
                    constraint.max -= 30 * u.second

        # get start time for scheduler
        start = self._schedule_start
        now_plus_safety = Time.now() + self._safety_time * u.second
        if start is None or start < now_plus_safety:
            # if no ETA exists or is in the past, use safety time
            start = now_plus_safety

        # get running scheduled block, if any
        if self._current_task_id is None:
            log.info("No running block found.")
            running_task = None
        else:
            # get running task from archive
            log.info("Trying to find running block in current schedule...")
            now = Time.now()
            tasks = await self._task_archive.get_pending_tasks(
                now, now, include_running=True)
            if self._current_task_id in tasks:
                running_task = tasks[self._current_task_id]
            else:
                log.info("Running block not found in last schedule.")
                running_task = None

        # if start is before end time of currently running block, change that
        if running_task is not None:
            log.info("Found running block that ends at %s.", running_task.end)

            # get block end plus some safety
            block_end = running_task.end + 10.0 * u.second
            if start < block_end:
                start = block_end
                log.info(
                    "Start time would be within currently running block, shifting to %s.",
                    start.isot)

        # calculate end time
        end = start + TimeDelta(self._schedule_range * u.hour)

        # remove currently running block and filter by start time
        blocks = []
        for b in filter(
                lambda x: x.configuration["request"]["id"] != self.
                _current_task_id, copied_blocks):
            time_constraint_found = False
            # loop all constraints
            for c in b.constraints:
                if isinstance(c, TimeConstraint):
                    # we found a time constraint
                    time_constraint_found = True

                    # does the window start before the end of the scheduling range?
                    if c.min < end:
                        # yes, store block and break loop
                        blocks.append(b)
                        break
            else:
                # loop has finished without breaking
                # if no time constraint has been found, we still take the block
                if time_constraint_found is False:
                    blocks.append(b)

        # if need new update, skip here
        if self._need_update:
            log.info("Not running scheduler, since update was requested.")
            return

        # no blocks found?
        if len(blocks) == 0:
            log.info("No blocks left for scheduling.")
            await self._task_archive.update_schedule([], start)
            return

        # log it
        log.info(
            "Calculating schedule for %d schedulable block(s) starting at %s...",
            len(blocks), start)

        # we don't need any transitions
        transitioner = Transitioner()

        # create scheduler
        scheduler = PriorityScheduler(constraints,
                                      self.observer,
                                      transitioner=transitioner)

        # run scheduler
        time_range = Schedule(start, end)
        loop = asyncio.get_running_loop()
        schedule = await loop.run_in_executor(None, scheduler, blocks,
                                              time_range)

        # if need new update, skip here
        if self._need_update:
            log.info(
                "Not using scheduler results, since update was requested.")
            return

        # update
        await self._task_archive.update_schedule(schedule.scheduled_blocks,
                                                 start)
        if len(schedule.scheduled_blocks) > 0:
            log.info("Finished calculating schedule for %d block(s):",
                     len(schedule.scheduled_blocks))
            for i, block in enumerate(schedule.scheduled_blocks, 1):
                log.info(
                    "  #%d: %s to %s (%.1f)",
                    block.configuration["request"]["id"],
                    block.start_time.strftime("%H:%M:%S"),
                    block.end_time.strftime("%H:%M:%S"),
                    block.priority,
                )
        else:
            log.info("Finished calculating schedule for 0 blocks.")
Example #12
0
    async def get_schedulable_blocks(self) -> List[ObservingBlock]:
        """Returns list of schedulable blocks.

        Returns:
            List of schedulable blocks
        """

        # check
        if self._portal_instrument_type is None:
            raise ValueError("No instrument type for portal set.")

        # get data
        schedulable = await self._portal_get(
            urljoin(self._url, "/api/requestgroups/schedulable_requests/"))

        # get proposal priorities
        data = await self._portal_get(urljoin(self._url, "/api/proposals/"))
        tac_priorities = {p["id"]: p["tac_priority"] for p in data["results"]}

        # loop all request groups
        blocks = []
        for group in schedulable:
            # get base priority, which is tac_priority * ipp_value
            proposal = group["proposal"]
            if proposal not in tac_priorities:
                log.error('Could not find proposal "%s".', proposal)
                continue
            base_priority = group["ipp_value"] * tac_priorities[proposal]

            # loop all requests in group
            for req in group["requests"]:
                # still pending?
                if req["state"] != "PENDING":
                    continue

                # duration
                duration = req["duration"] * u.second

                # time constraints
                time_constraints = [
                    TimeConstraint(Time(wnd["start"]), Time(wnd["end"]))
                    for wnd in req["windows"]
                ]

                # loop configs
                for cfg in req["configurations"]:
                    # get instrument and check, whether we schedule it
                    instrument = cfg["instrument_type"]
                    if instrument.lower(
                    ) != self._portal_instrument_type.lower():
                        continue

                    # target
                    t = cfg["target"]
                    target = SkyCoord(t["ra"] * u.deg,
                                      t["dec"] * u.deg,
                                      frame=t["type"].lower())

                    # constraints
                    c = cfg["constraints"]
                    constraints = []
                    if "max_airmass" in c and c["max_airmass"] is not None:
                        constraints.append(
                            AirmassConstraint(max=c["max_airmass"],
                                              boolean_constraint=False))
                    if "min_lunar_distance" in c and c[
                            "min_lunar_distance"] is not None:
                        constraints.append(
                            MoonSeparationConstraint(
                                min=c["min_lunar_distance"] * u.deg))
                    if "max_lunar_phase" in c and c[
                            "max_lunar_phase"] is not None:
                        constraints.append(
                            MoonIlluminationConstraint(
                                max=c["max_lunar_phase"]))
                        # if max lunar phase <= 0.4 (which would be DARK), we also enforce the sun to be <-18 degrees
                        if c["max_lunar_phase"] <= 0.4:
                            constraints.append(
                                AtNightConstraint.twilight_astronomical())

                    # priority is base_priority times duration in minutes
                    # priority = base_priority * duration.value / 60.
                    priority = base_priority

                    # create block
                    block = ObservingBlock(
                        FixedTarget(target, name=req["id"]),
                        duration,
                        priority,
                        constraints=[*constraints, *time_constraints],
                        configuration={"request": req},
                    )
                    blocks.append(block)

        # return blocks
        return blocks
Example #13
0
def find_observability(exoplanet_array, default_lookahead, longitude, latitude,
                       elevation, start_date, end_date, min_altitude,
                       max_altitude, resolution, flash, display):
    '''Function to calculate'''
    '''# find timezone
    timezone_name = tf.timezone_at(lng=longitude, lat=latitude)
    delta_degree = 1
    while timezone_name is None:
        delta_degree += 1
        timezone_name = tf.closest_timezone_at(lng=longitude, lat=latitude, delta_degree=delta_degree)
    if flash: flash.append('Timezone   : ' + timezone_name)
    print(timezone_name)'''

    # create Observer instance
    if elevation is None: elevation = 0
    custom_location = Observer(longitude=longitude * u.deg,
                               latitude=latitude * u.deg,
                               elevation=elevation * u.m)

    # make an array of FixedTarget instances
    target_table = []
    for planet in exoplanet_array:
        target_table.append((planet.name, planet.host.ra, planet.host.dec))
    targets = [
        FixedTarget(coord=SkyCoord(ra=ra * u.deg, dec=dec * u.deg), name=name)
        for name, ra, dec in target_table
    ]

    # time range
    if start_date is not None:
        start_date = Time(datetime.combine(start_date, datetime.min.time()),
                          out_subfmt='date')
    else:
        start_date = Time(datetime.combine(datetime.now(),
                                           datetime.min.time()),
                          out_subfmt='date')
    if end_date is not None:
        end_date = Time(datetime.combine(end_date, datetime.min.time()),
                        out_subfmt='date')
    else:
        end_date = start_date + default_lookahead * u.day
    time_range = Time([start_date, end_date])
    #time_range_int = # no. days as an int
    if display:
        flash.append('Start date : ' + str(start_date.iso))
        flash.append('End date   : ' + str(end_date.iso))

    # altitude and night time constraints
    if min_altitude is None: min_altitude = 0
    if max_altitude is None: max_altitude = 90
    constraints = [
        AltitudeConstraint(min_altitude * u.deg, max_altitude * u.deg),
        AtNightConstraint.twilight_astronomical()
    ]

    # calculate if observable within lookahead time from now
    if resolution is None: resolution = 0.5

    # new route, faster by not bothering to recheck planets already found to be observable
    # UPDATE, ASTROPY IS ALREADY OPTIMISED FOR THIS, THIS TAKES 10 TIMES LONGER, LEAVING IN FOR DEMONSTRATION PURPOSES ONLY (commented out lines in 'old route' are also only for demonstrating the difference)
    # start = time.perf_counter()
    # ever_observable = [False] * len(exoplanet_array)
    # day = 0
    # while start_date + day*u.day != end_date:
    #     print(str(start_date + day*u.day) + ', ' + str(end_date))
    #     for i in range(len(ever_observable)):
    #         if ever_observable[i] == False:
    #             ever_observable[i] = is_observable(constraints, custom_location, [targets[i]], time_range=Time([start_date+day*u.day, start_date+(day+1)*u.day]), time_grid_resolution=resolution*u.hour)[0]
    #             print(str(day) + ', ' + str(i) + ', ' + targets[i].name + ', ' + str(ever_observable[i]))
    #     day += 1
    # end = time.perf_counter()
    # print(str(end - start) + ' seconds fast')

    # new route, faster by not bothering to recheck planets already found to be observable
    # UPDATE, ASTROPY IS ALREADY OPTIMISED FOR THIS, THIS TAKES 10 TIMES LONGER, LEAVING IN FOR DEMONSTRATION PURPOSES ONLY (commented out lines in 'old route' are also only for demonstrating the difference)
    # start = time.perf_counter()
    # ever_observable = [False] * len(exoplanet_array)
    # day = 0
    # for i in range(len(ever_observable)):
    #     for day in range(30):
    #         print(str(start_date + day*u.day) + ', ' + str(end_date))

    #         if ever_observable[i] == False:
    #             ever_observable[i] = is_observable(constraints, custom_location, [targets[i]], time_range=Time([start_date+day*u.day, start_date+(day+1)*u.day]), time_grid_resolution=resolution*u.hour)[0]
    #             print(str(day) + ', ' + str(i) + ', ' + targets[i].name + ', ' + str(ever_observable[i]))
    #         else: break
    # end = time.perf_counter()
    # print(str(end - start) + ' seconds fast')

    # trying to split array into chunks so we can say the task has been completed to the nearest percent
    # tosplit_target_array = copy.deepcopy(targets)
    # start = time.perf_counter()
    # target_chunks = np.array_split(tosplit_target_array, 10) # split targets into 10 parts (not 100, this makes the calculation time too long)
    # for i in range(len(target_chunks)):
    #     is_observable(constraints, custom_location, target_chunks[i].tolist(), time_range=time_range, time_grid_resolution=resolution*u.hour)
    #     #print('%i calculated' %i)
    # end = time.perf_counter()
    # print(str(end - start) + ' seconds fast')

    # old route, slow, commented out lines are for demonstrating that it is better than a manual method by a factor of 10
    start = time.perf_counter()
    ever_observable = is_observable(constraints,
                                    custom_location,
                                    targets,
                                    time_range=time_range,
                                    time_grid_resolution=resolution * u.hour)
    end = time.perf_counter()
    print(str(end - start) + ' seconds slow')

    for i in range(len(exoplanet_array)):
        exoplanet_array[i].observable = ever_observable[i]
    if display: flash.append('Timing resolution = {} hrs'.format(resolution))
Example #14
0
def get_transit_observability(site,
                              ra,
                              dec,
                              name,
                              t_mid_0,
                              period,
                              duration,
                              n_transits=100,
                              obs_start_time=Time(
                                  dt.datetime.today().isoformat()),
                              min_altitude=None,
                              oot_duration=30 * u.minute,
                              minokmoonsep=30 * u.deg,
                              max_airmass=None,
                              twilight_limit='nautical'):
    """
    note: barycentric corrections not yet implemented. (could do this myself!)
    -> 16 minutes of imprecision is baked into this observability calculator!

    args:

        site (astroplan.observer.Observer)

        ra, dec (units u.deg), e.g.:
            ra=101.28715533*u.deg, dec=16.71611586*u.deg,
        or can also accept
            ra="17 56 35.51", dec="-29 32 21.5"

        name (str), e.g., "Sirius"

        t_mid_0 (float): in BJD_TDB, preferably (but see note above).

        period (astropy quantity, units time)

        duration (astropy quantity, units time)

        n_transits (int): number of transits forward extrapolated to

        obs_start_time (astropy.Time object): when to start calculation from

        min_altitude (astropy quantity, units deg): 20 degrees is the more
        relevant constraint.

        max_airmass: e.g., 2.5. One of max_airmass or min_altitude is required.

        oot_duration (astropy quantity, units time): with which to brack
        transit observations, to get an OOT baseline.

        twilight_limit: 'astronomical', 'nautical', 'civil' for -18, -12, -6
        deg.
    """

    if (isinstance(ra, u.quantity.Quantity)
            and isinstance(dec, u.quantity.Quantity)):
        target_coord = SkyCoord(ra=ra, dec=dec)
    elif (isinstance(ra, str) and isinstance(dec, str)):
        target_coord = SkyCoord(ra=ra, dec=dec, unit=(u.hourangle, u.deg))
    else:
        raise NotImplementedError

    if (not isinstance(max_airmass, float)
            or isinstance(min_altitude, u.quantity.Quantity)):
        raise NotImplementedError

    target = FixedTarget(coord=target_coord, name=name)

    primary_eclipse_time = Time(t_mid_0, format='jd')

    system = EclipsingSystem(primary_eclipse_time=primary_eclipse_time,
                             orbital_period=period,
                             duration=duration,
                             name=name)

    midtransit_times = system.next_primary_eclipse_time(obs_start_time,
                                                        n_eclipses=n_transits)

    # for the time being, omit any local time constraints.
    if twilight_limit == 'astronomical':
        twilight_constraint = AtNightConstraint.twilight_astronomical()
    elif twilight_limit == 'nautical':
        twilight_constraint = AtNightConstraint.twilight_nautical()
    else:
        raise NotImplementedError('civil twilight is janky.')

    constraints = [
        twilight_constraint,
        AltitudeConstraint(min=min_altitude),
        AirmassConstraint(max=max_airmass),
        MoonSeparationConstraint(min=minokmoonsep)
    ]

    # observable just at midtime (bottom)
    b = is_event_observable(constraints, site, target, times=midtransit_times)

    # observable full transits (ingress, bottom, egress)
    ing_egr = system.next_primary_ingress_egress_time(obs_start_time,
                                                      n_eclipses=n_transits)

    ibe = is_event_observable(constraints,
                              site,
                              target,
                              times_ingress_egress=ing_egr)

    # get moon separation over each transit. take minimum moon sep at
    # ing/tmid/egr as the moon separation.
    moon_tmid = get_moon(midtransit_times, location=site.location)
    moon_separation_tmid = moon_tmid.separation(target_coord)

    moon_ing = get_moon(ing_egr[:, 0], location=site.location)
    moon_separation_ing = moon_ing.separation(target_coord)

    moon_egr = get_moon(ing_egr[:, 1], location=site.location)
    moon_separation_egr = moon_egr.separation(target_coord)

    moon_separation = np.round(
        np.array(
            [moon_separation_tmid, moon_separation_ing,
             moon_separation_egr]).min(axis=0), 0).astype(int)

    moon_illumination = np.round(
        100 * moon.moon_illumination(midtransit_times), 0).astype(int)

    # completely observable transits (OOT, ingress, bottom, egress, OOT)
    oot_ing_egr = np.concatenate(
        (np.array(ing_egr[:, 0] - oot_duration)[:, None],
         np.array(ing_egr[:, 1] + oot_duration)[:, None]),
        axis=1)

    oibeo = is_event_observable(constraints,
                                site,
                                target,
                                times_ingress_egress=oot_ing_egr)

    ing_tmid_egr = np.concatenate(
        (np.array(ing_egr[:, 0])[:, None], np.array(midtransit_times)[:, None],
         np.array(ing_egr[:, 1])[:, None]),
        axis=1)

    return ibe, oibeo, ing_tmid_egr, moon_separation, moon_illumination
Example #15
0
    def _schedule_thread(self):
        # only constraint is the night
        if self._twilight == 'astronomical':
            constraints = [AtNightConstraint.twilight_astronomical()]
        elif self._twilight == 'nautical':
            constraints = [AtNightConstraint.twilight_nautical()]
        else:
            raise ValueError('Unknown twilight type.')

        # we don't need any transitions
        transitioner = Transitioner()

        # run forever
        while not self.closing.is_set():
            # need update?
            if self._need_update:
                # reset need for update
                self._need_update = False

                # get start time for scheduler
                start = self._schedule_start
                now_plus_safety = Time.now() + self._safety_time * u.second
                if start is None or start < now_plus_safety:
                    # if no ETA exists or is in the past, use safety time
                    start = now_plus_safety
                end = start + TimeDelta(self._schedule_range * u.hour)

                # remove currently running block and filter by start time
                blocks = []
                for b in filter(
                        lambda b: b.configuration['request']['id'] != self.
                        _current_task_id, self._blocks):
                    time_constraint_found = False
                    # loop all constraints
                    for c in b.constraints:
                        if isinstance(c, TimeConstraint):
                            # we found a time constraint
                            time_constraint_found = True

                            # does the window start before the end of the scheduling range?
                            if c.min < end:
                                # yes, store block and break loop
                                blocks.append(b)
                                break
                    else:
                        # loop has finished without breaking
                        # if no time constraint has been found, we still take the block
                        if time_constraint_found is False:
                            blocks.append(b)

                # log it
                log.info(
                    'Calculating schedule for %d schedulable block(s) starting at %s...',
                    len(blocks), start)

                # init scheduler and schedule
                scheduler = SequentialScheduler(constraints,
                                                self.observer,
                                                transitioner=transitioner)
                time_range = Schedule(start, end)
                schedule = scheduler(blocks, time_range)

                # update
                self._task_archive.update_schedule(schedule.scheduled_blocks,
                                                   start)
                if len(schedule.scheduled_blocks) > 0:
                    log.info('Finished calculating schedule for %d block(s):',
                             len(schedule.scheduled_blocks))
                    for i, block in enumerate(schedule.scheduled_blocks, 1):
                        log.info('  #%d: %s to %s (%.1f)',
                                 block.configuration['request']['id'],
                                 block.start_time, block.end_time,
                                 block.priority)
                else:
                    log.info('Finished calculating schedule for 0 blocks.')

            # sleep a little
            self.closing.wait(1)
Example #16
0
def run_nights(observers, nameList, args):
    # Define range of times to observe between
    startDate = datetime.datetime.strptime(args.startDate,"%Y-%m-%d")
    beginTimeFirstNight = datetime.datetime(startDate.year,startDate.month,startDate.day,hour=16)
    endTimeFirstNight = beginTimeFirstNight + datetime.timedelta(hours=16)
    t_datetimes_nights_list = []
    for iDay in range(args.nNights):
        beginTime = beginTimeFirstNight + datetime.timedelta(days=iDay)
        endTime = endTimeFirstNight + datetime.timedelta(days=iDay)
        currTime = beginTime
        t_datetime = []
        while currTime <= endTime:
            t_datetime.append(currTime)
            currTime += datetime.timedelta(hours=1)
        t_datetimes_nights_list.append(t_datetime)

    targets = [FixedTarget(coord=lookuptarget(name),name=name) for name in nameList]
    targetLabelList, ylabelsize = makeTargetLabels(nameList,args)

    constraints = [
        AltitudeConstraint(min=args.minAlt*u.deg),
        AtNightConstraint.twilight_astronomical(),
        MoonSeparationConstraint(min=args.minMoonSep*u.deg),
        MoonIlluminationConstraint(max=args.maxMoonIllum),
    ]

    outfn = args.outFileNameBase+"_nightly.pdf"
    with PdfPages(outfn) as pdf:
        for observer in observers:
            fig, axes = mpl.subplots(
                figsize=(8.5,11),
                ncols=args.nNights,
                sharex="col",
                gridspec_kw={
                    "top":0.92,
                    "bottom":0.03,
                    "left":0.13,
                    "right":0.98,
                    "hspace":0,
                    "wspace":0
                },
                tight_layout=False,constrained_layout=False
            )

            observability_grids = []
            for iNight in range(args.nNights):
                t_datetime = t_datetimes_nights_list[iNight]
                time_grid = [Time(observer.timezone.localize(t)) for t in t_datetime]
                
                observability_grid = numpy.zeros((len(targets),len(time_grid)-1))
                for i in range(len(time_grid)-1):
                    tmp = is_always_observable(constraints, observer, targets, times=[time_grid[i],time_grid[i+1]])
                    observability_grid[:, i] = tmp
                observability_grids.append(observability_grid)

            observable_targets = targets
            observable_target_labels = targetLabelList
            ever_observability_grids = observability_grids
            if args.onlyEverObservable:
                target_is_observable = numpy.zeros(len(targets))
                for observability_grid in observability_grids:
                    for iTime in range(observability_grid.shape[1]):
                        target_is_observable += observability_grid[:,iTime]
                target_is_observable = target_is_observable > 0. # change to boolean numpy array
                observable_targets = [x for x, o in zip(targets,target_is_observable) if o]
                observable_target_labels = [x for x, o in zip(targetLabelList,target_is_observable) if o]
                ever_observability_grids = []
                for observability_grid in observability_grids:
                    ever_observability_grid = observability_grid[target_is_observable,:]
                    ever_observability_grids.append(ever_observability_grid)

            for iNight in range(args.nNights):
                ax = axes[iNight]
                t_datetime = t_datetimes_nights_list[iNight]
                extent = [0, len(t_datetime)-1, -0.5, len(observable_targets)-0.5]
                ax.imshow(ever_observability_grids[iNight], extent=extent, origin="lower", aspect="auto", cmap=mpl.get_cmap("Greens"))
                ax.xaxis.tick_top()
                ax.xaxis.set_label_position("top")
                ax.invert_yaxis()

                if iNight == 0:
                    ax.set_yticks(range(0,len(observable_targets)))
                    ax.set_yticklabels(observable_target_labels, fontsize=ylabelsize)
                else:
                    ax.set_yticks([])

                ax.set_xticks(range(0,len(t_datetime)-1,4))
                ax.set_xticks(range(0,len(t_datetime)),minor=True)
                ax.set_xticklabels([t_datetime[i].strftime("%Hh") for i in range(0,len(t_datetime)-1,4)])

                ax.set_xlabel(t_datetime[0].strftime("%a %b %d"))

                ax.set_yticks(numpy.arange(extent[2], extent[3]), minor=True)

                ax.grid(axis="x",which="minor",color="0.7",ls="-", linewidth=0.5)
                ax.grid(axis="x",which="major",color="0.7",ls="-", linewidth=1)
                ax.grid(axis="y",which="minor",color="0.7",ls="-", linewidth=0.5)

                ax.tick_params(axis='y', which='minor', left=False, right=False)
                ax.tick_params(axis='x', which='minor', bottom=False, top=False)
        
            fig.suptitle(f"Observability at {observer.name} in {startDate.year}")
            fig.text(1.0,0.0,"Constraints: Astronomical Twilight, Altitude $\geq {:.0f}^\circ$, Moon Seperation $\geq {:.0f}^\circ$, Moon Illumination $\leq {:.2f}$".format(args.minAlt,args.minMoonSep,args.maxMoonIllum),ha="right",va="bottom")
            pdf.savefig(fig)
        print(f"Writing out file: {outfn}")