예제 #1
0
파일: Project.py 프로젝트: mmccarty/nell
    def get_sanctioned_users_blackout_times(self, start, end):
        """
        A project is 'blacked out' when all of its sanctioned observers
        are unavailable or it is using Project Blackouts.
        User blackouts are much more complicated, thus this method.
        Returns a list of tuples describing the time ranges
        where the project is 'blacked out' by user blackouts in UTC.
        """

        if not self.has_sanctioned_observers():
            return []

        blackouts = [o.user.blackout_set.all() \
                     for o in self.get_sanctioned_observers()]

        # Change all to UTC.
        utcBlackouts = []
        for set in blackouts:
            utc = []
            for b in set:
                utc.extend(b.generateDates(start, end))
            utcBlackouts.append(utc)

        if len(utcBlackouts) == 1: # One observer runs the show.
            return sorted(utcBlackouts[0])

        return AnalogSet.unions(AnalogSet.intersects(utcBlackouts))
예제 #2
0
 def findOverlap(self, a, b, dur):
     """Utility for returning the fraction of given duration that
     is spent in the overlap of the two given tuples.
     """
     a1, a2 = a
     b1, b2 = b
     # what's the overlap between them (like the Galactice Center)?
     if AnalogSet.overlaps(a, b):
         ints = AnalogSet.intersect(a, b)
         # if our range is entirely in overlap, avoid calculations
         if self.fltEqual(ints[0], a1) \
             and self.fltEqual(ints[1], a2):
             overlap = dur
             nonOverlap = 0
         else:    
            # otherwise we need to convert back to duration - 
            # one way to do this is via fractions
            frac = (ints[1] - ints[0]) / (a2 - a1)
            overlap = dur * frac
            nonOverlap = (1.0 - frac) * dur
     else:
         overlap = 0
         nonOverlap = dur
         
     return (overlap, nonOverlap)
예제 #3
0
파일: Project.py 프로젝트: mmccarty/nell
    def get_unsanctioned_users_blackout_times(self, start, end):
        universe = (start, end)
        all_blackout_ranges = [[(start, end)]]
        # for all un-sanctioned observers
        one_day = timedelta(days=1)
        for o in self.investigator_set \
                     .exclude(user__sanctioned=True) \
                     .exclude(observer=False):
            # Get reservation times
            reservation_sets =  \
                Reservation.objects.filter(user=o) \
                                   .exclude(start_date__gte=end) \
                                   .exclude(end_date__lte=start)
            # add a day to end_date (check-out day) because we
            # assume they are available all that day just like
            # they are available all day on check-in day
            onsite_ranges = [(rs.start_date, rs.end_date + one_day)
                             for rs in reservation_sets]

            # Get black-out ranges
            blackout_ranges = []
            for b in o.user.blackout_set.all():
                blackout_ranges.extend(b.generateDates(start, end))

            # Get available ranges
            available_ranges = AnalogSet.diffs(onsite_ranges, blackout_ranges)

            # Get this observer's blackout times
            all_blackout_ranges.append(
                AnalogSet.diffs([universe], available_ranges))
        return AnalogSet.intersects(all_blackout_ranges)
예제 #4
0
파일: Period.py 프로젝트: mmccarty/nell
    def get_prescheduled_times(start, end, project = None):
        """
        Returns a list of binary tuples of the form (start, end) that
        describe when this project cannot observe because other 
        projects already have scheduled telescope periods during
        the time range specified by the start and end arguments.
        NOTE: this is functionally identical to 
        Project.get_prescheduled_times, but uses a DB query to
        improve performance; Project cannot do this becuase the query
        causes circular references.
        """

        def truncatePeriodRange(p, start, end):
            "we don't care about periods outside of range"
            s = max(p.start, start)
            e = min(p.end(), end)
            return (s, e) 

        # first query DB simply by the time range
        minutes = timedelta2minutes(end - start)
        ps1 = Period.get_periods(start, minutes)

        # now filter out other stuff
        pId = None
        if project is not None:
            pId = project.id
        scheduled = Period_State.get_state('S')
        times = [truncatePeriodRange(p, start, end) for p in ps1 \
            if p.session.project.id != pId \
            and p.state == scheduled \
            and AnalogSet.overlaps((p.start, p.end()), (start, end))]
        return sorted(AnalogSet.unions(times))
예제 #5
0
 def electivesResolvable(self, p1, p2):
 
     # must be electives
     if not p1.session.isElective() or not p2.session.isElective():
         self.annotate( "Not electives: %s %s" % (p1, p2))
         return False
 
     # one of their elective groups must have another period we can choose
     p1total = len(p1.elective.periods.all())
     p2total = len(p2.elective.periods.all())
     if p1total + p2total < 3:
         # no options!
         self.annotate( "Not enough periods in elective groups: %s %s" % (p1, p2))
         return False
 
     # Okay, so, one of the elective groups must choose it's first period,
     # and the other elective group must choose it's second period.
     # But make sure you choose periods that actually exist
     p1choosen, p2choosen = (0, 1) if p2total > 1 else (1, 0)
     ep1 = p1.elective.periods.all().order_by('start')[p1choosen]
     ep2 = p2.elective.periods.all().order_by('start')[p2choosen]
 
     # if these don't overlap, we're good
     return AnalogSet.overlaps((ep1.start, ep1.end())
                             , (ep2.start, ep2.end())
                              )
예제 #6
0
 def getHrsInDayTime(self, start, end):
     "Split up given time range by PTCS day and night time hours."
     dur = TimeAgent.dtDiffHrs(start, end)
     startDate = date(start.year, start.month, start.day)
     #rise, set = self.sun.getRiseSet(date1)
     # cast a wide net: compute the rise and set times for any days
     # that might be covered by the given time range
     days = (end - start).days + 2
     dayTimes = []
     for day in range(days):
         dt = startDate + timedelta(days = day)
         dayTimes.append(self.sun.getPTCSRiseSet(dt))
     # where does our given time range intersect with day time?    
     ints = AnalogSet.intersects([dayTimes, [(start, end)]])
     if len(ints) > 0:
         # some day time
         day = 0.0
         for intersection in ints:
             td = intersection[1] - intersection[0]
             day += TimeAgent.timedelta2frachours(td)
         # the rest must be night time    
         night = abs(dur - day) 
     else:
         # our range is all night time.
         day = 0.0
         night = dur 
     return (day, night)
예제 #7
0
파일: Project.py 프로젝트: mmccarty/nell
 def get_users_blackout_times(self, start, end):
     """
     Returns the intersection of all users blackout times.
     """
     sanctioned    = self.get_sanctioned_users_blackout_times(start, end)
     unsanctioned  = self.get_unsanctioned_users_blackout_times(start, end)
     return AnalogSet.intersects([sanctioned, unsanctioned])
예제 #8
0
파일: Window.py 프로젝트: mmccarty/nell
 def isInWindow(self, period):
     "Does the given period overlap at all in window (endpoints)"
     if len(self.ranges()) > 0:
         return AnalogSet.overlaps((self.start_datetime(), self.end_datetime())
                                 , (period.start, period.end()))
     else:
         # how can the period be in the window, when the window isn't
         # properly defined?
         return False
예제 #9
0
파일: Window.py 프로젝트: mmccarty/nell
 def isInRanges(self, period):
     """
     Does the given period overlap with any of the window ranges?
     This is more rigourous then isInWindow.
     """
     for wr in self.windowrange_set.all():
         if AnalogSet.overlaps((wr.start_datetime(), wr.end_datetime())
                             , (period.start, period.end())):
             return True
     return False # no overlaps at all!        
예제 #10
0
def report_overlaps(periods, now = None):
    """
    We don't want to report on *any* overlaps in the schedule.  Apply
    some pretty complex rules to keep this more meaningful.
    """
    if now is None:
        now = datetime.utcnow()
    values  = []
    overlap = []
    not_deleted_periods = [p for p in periods if p.state.abbreviation != "D"]
    for p1 in not_deleted_periods:
        start1, end1 = p1.start, p1.end()
        for p2 in not_deleted_periods:
            start2, end2 = p2.start, p2.end()
            if p1 != p2 and p1 not in overlap and p2 not in overlap:
                # what kind of overlap?
                type = ""
                if AnalogSet.overlaps((start1, end1), (start2, end2)):
                    # in the past?
                    if p1.start < now or p2.start < now:
                        type = "Overlap in past"
                    # any scheduled?    
                    if p1.isScheduled() or p2.isScheduled():
                        type = "Overlap with scheduled period"
                    # if it's not one of the above types, see if a 3rd period
                    # is involved:
                    if type == "":
                        # check just the nearest neighbors
                        oneWeekBefore = p1.start - timedelta(days = 7)
                        oneWeekAfter  = p1.start + timedelta(days = 7)
                        neighbors = Period.objects.exclude(state__abbreviation = "D").filter(start__gt = oneWeekBefore
                                       , start__lt = oneWeekAfter)
                        for p3 in neighbors:
                            if p1 != p3 and p2 != p3 and p3 not in overlap:
                                start3, end3 = p3.start, p3.end()
                                if AnalogSet.overlaps((start1, end1), (start3, end3)) \
                                  or AnalogSet.overlaps((start2, end2), (start3, end3)):
                                    type = "Overlap of 3 periods"  
                    if type != "": 
                        values.append("%s: %s and %s" % (type, str(p1), str(p2)))
                        overlap.extend([p1, p2])
    return values
예제 #11
0
파일: Project.py 프로젝트: mmccarty/nell
 def get_blackout_times(self, start, end):
     """
     A project is 'blacked out' when all of its sanctioned and
     unsanctioned, on-site observers are unavailable, or it is
     using Project Blackouts.
     Returns a list of tuples describing the time ranges
     where the project is 'blacked out' in UTC.
     """
     blackouts = self.get_project_blackout_times(start, end)
     blackouts.extend(self.get_users_blackout_times(start, end))
     return AnalogSet.unions(blackouts)
예제 #12
0
 def findOverlaps(self, periods):
     values = []
     overlap = []
     for p1 in periods:
         start1, end1 = p1.start, p1.end()
         for p2 in periods:
             start2, end2 = p2.start, p2.end()
             if p1 != p2 and p1 not in overlap and p2 not in overlap:
                 if AnalogSet.overlaps((start1, end1), (start2, end2)):
                     values.append((p1, p2))
                     overlap.extend([p1, p2])
     return (overlap, values)
예제 #13
0
파일: Project.py 프로젝트: mmccarty/nell
    def get_prescheduled_times(self, start, end):
        """
        Returns a list of binary tuples of the form (start, end) that
        describe when this project cannot observe because other 
        projects already have scheduled telescope periods during
        the time range specified by the start and end arguments.
        """

        def truncatePeriodRange(p, start, end):
            s = max(p.start, start)
            e = min(p.end(), end)
            return (s, e)

        times = [truncatePeriodRange(d, start, end) \
                 for p in Project.objects.all() \
                 # TBF: that's why this takes so long!  limit the
                 # periods by the time range a little!!!!
                 for d in p.getPeriods() \
                 if p != self and \
                    d.state.abbreviation == 'S' and \
                    AnalogSet.overlaps((d.start, d.end()), (start, end))]
        return sorted(AnalogSet.unions(times))
예제 #14
0
    def calculate(self):
        "Calculate all the quantities needed for this report."

        self.currentSemester   = Semester.getCurrentSemester(self.start)
        self.previousSemesters = Semester.getPreviousSemesters(self.start)
        # TBF: watch for bug!
        self.previousSemesters = [s for s in self.previousSemesters if s != self.currentSemester]
        self.futureSemesters   = Semester.getFutureSemesters(self.start)

        # just those scheduled periods in the current week
        self.observed_periods = \
            sorted(set([p for p in self.periods \
                          if AnalogSet.overlaps((p.start, p.end()), (self.start, self.end))]))
        # just those scheduled periods in the upcoming week                  
        self.upcoming_periods = \
            sorted(set([p for p in self.periods \
                          if AnalogSet.overlaps((p.start, p.end())
                                    , (self.next_start, self.next_end))]))

        self.last_month   = self.start - timedelta(days = 31)
        # scheduled periods from last month
        self.last_periods = [p for p in self.periods \
                          if p.start.month == self.last_month.month and \
                             p.start.year  == self.last_month.year]
        # scheduled periods from this month                     
        self.this_periods = [p for p in self.periods \
                          if p.start.month == self.start.month and \
                             p.start.year  == self.start.year]  

        # how does the lost time break down for all the scheduled periods in the current week?
        self.lost_hours["total_time"] = sum([p.accounting.lost_time() for p in self.observed_periods])
        self.lost_hours["weather" ] = sum([p.accounting.lost_time_weather for p in self.observed_periods])
        self.lost_hours["RFI" ] = sum([p.accounting.lost_time_rfi for p in self.observed_periods])
        self.lost_hours["other" ] = sum([p.accounting.lost_time_other for p in self.observed_periods])
        self.lost_hours["billed_to_project" ] = sum([p.accounting.lost_time_bill_project for p in self.observed_periods])

        # how do the scheduled periods break down by type and this
        # and the previous month?
        self.scheduled_hours["astronomy"] = self.get_obs_time_tuple('p.session.project.project_type.type == "science"')
        self.scheduled_hours["maintenance"] = self.get_obs_time_tuple('p.session.project.pcode == "Maintenance"')
        # NOTE: is this the most robust way to determine if a project is a test or commisioning project?
        self.scheduled_hours["test_comm"] = self.get_obs_time_tuple('p.session.project.pcode[0] == "T"')
        self.scheduled_hours["shutdown"] = self.get_obs_time_tuple('p.session.project.pcode == "Shutdown"')

        # The distinction between 'backlog' and 'discharge' is that
        # 'backlog' is from only previous semesters, while discharge
        # inclues *all* semesters.
        # But they both only care about incomplete Astronomy projects
        self.backlog    = [p for p in Project.objects.all() \
            if p.semester in self.previousSemesters \
            and not p.complete \
            and p.get_category() == "Astronomy"] 
                       
        self.backlog_hours["total_time"] = sum([self.ta.getTimeLeft(p) for p in self.backlog])
        self.backlog_hours["years"] = {}
        for y in sorted(list(set([s.start().year for s in self.previousSemesters]))):
            projs = [p for p in self.backlog if p.semester.start().year == y]
            self.backlog_hours["years"]["%d" % y] = (sum([self.ta.getTimeLeft(p) for p in projs]), len(projs))
        self.backlog_hours["monitoring"] = sum([self.ta.getTimeLeft(p) for p in self.backlog \
                           if any([s.session_type.type == "windowed" \
                                   for s in p.sesshun_set.all()])])
        self.backlog_hours["vlbi"] = sum([self.ta.getTimeLeft(p) for p in self.backlog \
                           if any([s.observing_type.type == "vlbi" \
                                   for s in p.sesshun_set.all()])])                           

        self.discharge    = [p for p in Project.objects.all() \
            if not p.complete \
            and p.get_category() == "Astronomy"] 
          

        total_time = sum([self.ta.getTimeLeft(p) for p in self.discharge]) 
        monitoring = sum([self.ta.getTimeLeft(p) for p in self.discharge \
                           if any([s.session_type.type == "windowed" \
                                   for s in p.sesshun_set.all()]) and \
                              self.ta.getProjectTotalTime(p) <= 200.])
        vlbi       = sum([self.ta.getTimeLeft(p) for p in self.discharge \
                           if any([s.observing_type.type == "vlbi" \
                                   for s in p.sesshun_set.all()])])
        large      = sum([self.ta.getTimeLeft(p) for p in self.discharge \
                          if self.ta.getProjectTotalTime(p) > 200.])
        self.discharge_hours['total_time'] = total_time                  
        self.discharge_hours['monitoring'] = monitoring                  
        self.discharge_hours['vlbi']       = vlbi                  
        self.discharge_hours['large']      = large                  
        self.discharge_hours['rest']       = total_time - monitoring - vlbi - large


        self.nextWeekReservations = Reservation.objects.filter(
                                          end_date__gte = self.next_start
                                                       ).filter(
                                          start_date__lte = self.next_end)