Example #1
0
    def setUp(self):

        self.lst = LstPressureWeather()

        # get the one proposal and it's one session
        proposal = Proposal.objects.all()[0]
        s = proposal.session_set.all()[0]

        # give it some values so it will show up in plot
        s.grade = SessionGrade.objects.get(grade = 'A')
        s.weather_type = WeatherType.objects.get(type = 'Poor')
        s.target.min_lst = 0.0
        s.target.max_lst = hr2rad(12.5)
        s.target.save()
        time = 6.5 # hrs
        s.allotment.allocated_time = time # hrs
        s.allotment.save()
        s.save()
        self.session = s

        ps = [0.0]*6
        ps.extend([1.0]*12)
        ps.extend([0.0]*6)
        self.sessPressure = numpy.array(ps)

        self.wPoor = WeatherType.objects.get(type = 'Poor')
        self.wGood = WeatherType.objects.get(type = 'Good')
        self.wExcellent = WeatherType.objects.get(type = 'Excellent')
Example #2
0
    def __init__(self, semester):


        self.sun = Sun()
        self.weather = LstPressureWeather(semester = semester)

        try:
            self.semester = DSSSemester.objects.get(semester = semester)
        except DSSSemester.DoesNotExist:
            self.semester = DSSSemester.objects.create(semester =  semester)

        self.timeRange = (self.semester.start()
                        , self.semester.end())
        self.published = None

        # Galactic Center goes from 15 to 20 hours [,)
        self.gcHrs = (15, 21) 

        # for making sure we don't count a given session > once
        self.preAssignedSessions = []

        # initialize all the buckets we'll be calculating
        self.totalAvailableHrs = SemesterTimes() 
        self.maintHrs = SemesterTimes()
        self.shutdownHrs = SemesterTimes()
        self.testHrs = SemesterTimes()
        self.astronomyAvailableHrs = SemesterTimes()
        self.carryOver = {}
        #self.newAstronomyGradeAHrs = SemesterTimes()
        #self.newAstronomyAllGradeHrs = SemesterTimes()

        self.lowFreqPercent = 0.50
        self.hiFreq1Percent = 0.25
        self.hiFreq2Percent = 0.25

        # how to map from session freq type to time category?
        self.freq2key = {'LF' : 'lowFreq'
                       , 'HF1' : 'hiFreq1'
                       , 'HF2' : 'hiFreq2'
                        }

        self.grades = ['A', 'B', 'C']                
Example #3
0
class SemesterTimeAccounting(object):

    """
    This class is responsible for determining many complicated
    time accounting quantities.
    For each quantity, there are two types: total, and those covered
    by the Galactic Center.
    Here are the rules:
    totalAv = total available time for the semester; simply the days in the semester * 24 hours.
    preAssigned = pre assigned time for the semester; sum of the hours for Maintenance, Shutdown, and testing
    astroAv = totalAv - preAssigned
    carryover = hours from carryover - that is proposals from previous semesters *not* included in preAssigned. carryover = coA + coB + coC
    coA, coB, coC = the carryover broken down by grade; 
    newAstroAv = available for NEW astronomy in the semester.  newAstroAv = totalAv - preAssigned - carryover
    newAstroAvCoA = available for NEW astronomy in the semester only taking grade A carryover into account.  newAstroAvCoA = totalAv - preAssigned - coA

    Here's a summary of the rules:
    astroAv = totalAv - preAssigned
    newAstroAv = totalAv - preAssigned - carryover
    """

    def __init__(self, semester):


        self.sun = Sun()
        self.weather = LstPressureWeather(semester = semester)

        try:
            self.semester = DSSSemester.objects.get(semester = semester)
        except DSSSemester.DoesNotExist:
            self.semester = DSSSemester.objects.create(semester =  semester)

        self.timeRange = (self.semester.start()
                        , self.semester.end())
        self.published = None

        # Galactic Center goes from 15 to 20 hours [,)
        self.gcHrs = (15, 21) 

        # for making sure we don't count a given session > once
        self.preAssignedSessions = []

        # initialize all the buckets we'll be calculating
        self.totalAvailableHrs = SemesterTimes() 
        self.maintHrs = SemesterTimes()
        self.shutdownHrs = SemesterTimes()
        self.testHrs = SemesterTimes()
        self.astronomyAvailableHrs = SemesterTimes()
        self.carryOver = {}
        #self.newAstronomyGradeAHrs = SemesterTimes()
        #self.newAstronomyAllGradeHrs = SemesterTimes()

        self.lowFreqPercent = 0.50
        self.hiFreq1Percent = 0.25
        self.hiFreq2Percent = 0.25

        # how to map from session freq type to time category?
        self.freq2key = {'LF' : 'lowFreq'
                       , 'HF1' : 'hiFreq1'
                       , 'HF2' : 'hiFreq2'
                        }

        self.grades = ['A', 'B', 'C']                

    def checkTimes(self):
        "Basic check that everything adds up"

        self.totalAvailableHrs.check() 
        self.maintHrs.check() 
        self.shutdownHrs.check() 
        self.testHrs.check() 
        self.astronomyAvailableHrs.check() 
        
        preAssigned = self.maintHrs + self.shutdownHrs + self.testHrs
        avail = preAssigned  + self.astronomyAvailableHrs


        assert avail.total == self.totalAvailableHrs.total

        gradeACarryOver = self.carryOver['A']['times'].total.total + self.carryOver['A']['fixed'].total.total
        assert self.newAstroAvailGradeAHrs.total.total == avail.total.total - preAssigned.total.total - gradeACarryOver   

        totalCarryOver = sum([(self.carryOver[g]['times'].total.total + self.carryOver[g]['fixed'].total.total) for g in self.grades])
        assert self.newAstroAvailAllGradeHrs.total.total == avail.total.total - preAssigned.total.total - totalCarryOver   

    def calcTotalAvailableHours(self):

        # how many hours in this semester?
        days = self.getSemesterDays(self.semester)
        totalHrs = days * 24
        totalGCHrs = self.getGCHrs(totalHrs)
        total = Times(type = 'Total'
                    , total = totalHrs
                    , lowFreq = totalHrs*self.lowFreqPercent
                    , hiFreq1 = totalHrs*self.hiFreq1Percent
                    , hiFreq2 = totalHrs*self.hiFreq2Percent
                     )
        gc = Times(type = 'GC'
                 , total = self.getGCHrs(totalHrs)
                 , lowFreq = self.getGCHrs(total.lowFreq)
                 , hiFreq1 = self.getGCHrs(total.hiFreq1)
                 , hiFreq2 = self.getGCHrs(total.hiFreq2)
                     )         
        return  SemesterTimes(total = total, gc = gc)  

    def calculateTimeAccounting(self):

        self.published = datetime.now()

        self.totalAvailableHrs =  self.calcTotalAvailableHours()  

        # how much has been pre-assigned for this semester?
        # first, find the periods
        self.maintPeriods = self.getMaintenancePeriods()
        self.shutdownPeriods = self.getShutdownPeriods()
        self.testSessions = self.getTestSessions()

        # now calculate their hours
        self.maintHrs = self.getHours(self.maintPeriods)
        self.shutdownHrs = self.getHours(self.shutdownPeriods)
        self.testHrs = self.getSessionsHours(self.testSessions)

        # so, how much does that leave left for real astronomy?
        self.astronomyAvailableHrs = None
        # add up maintHrs, shutdownHrs, testHrs, total and GC, 
        # then subtract those from the totalAvailableHrs
        preAssigned = self.maintHrs + self.shutdownHrs + self.testHrs
        self.astronomyAvailableHrs = self.totalAvailableHrs - preAssigned             
        # now, what's the carry over?
        self.carryOverSessions = self.getCarryOverSessions()
        self.carryOver = self.getCarryOver(self.carryOverSessions)

        # and how does THAT affect time available?
        self.newAstroAvailGradeAHrs = \
            self.getNewAstroAvailableTime(self.carryOver
                                         , self.astronomyAvailableHrs
                                         , ['A']
                                          )

        self.newAstroAvailAllGradeHrs = \
            self.getNewAstroAvailableTime(self.carryOver
                                         , self.astronomyAvailableHrs
                                         , self.grades
                                          )

    def getNewAstroAvailableTime(self, carryover, available, grades):

        carryOverTotals = SemesterTimes()
        for g in grades:
            times = carryover[g]['times']
            fixed = carryover[g]['fixed']
            carryOverTotals += times + fixed

        return available - carryOverTotals

    def report(self):
        "Prints out simple version of the results."

        print "Summary of Semester %s" % self.semester.semester
        print "As of %s" % datetime.now()
        print ""
        print "Time Analysis for Semester %s" % self.semester.semester
        print "%s to %s" % (self.semester.start(), self.semester.end())
        print ""

        print "Hours available in the Semester: "
        print "%s" % self.totalAvailableHrs.total
        print "%s" % self.totalAvailableHrs.gc

        print ""
        print "Maint: %s" % self.maintHrs.total
        print "       %s" % self.maintHrs.gc
        print "Shutd: %s" % self.shutdownHrs.total
        print "       %s" % self.shutdownHrs.gc
        print "Tests: %s" % self.testHrs.total
        print "       %s" % self.testHrs.gc
        print ""

        print "Available for Astronomy = %5.2f, GC[%5.2f]" % \
            (self.astronomyAvailableHrs.total.total
           , self.astronomyAvailableHrs.gc.total)

        print ""
        print "Available for ALL Astronomy during %s" % \
            self.semester.semester    
        print "%s" % self.astronomyAvailableHrs.total
        print "%s" % self.astronomyAvailableHrs.gc

        print ""
        print "Backlog committments for %s" % self.semester.semester
        for g in self.grades:
            tt = self.carryOver[g]['fixed'].total.total + \
                 self.carryOver[g]['times'].total.total
            gt = self.carryOver[g]['fixed'].gc.total + \
                 self.carryOver[g]['times'].gc.total
            print "Group %s Time:" % g
            print "    Fixed: %s" % self.carryOver[g]['fixed'].total
            print "           %s" % self.carryOver[g]['fixed'].gc
            print "    Rest:  %s" % self.carryOver[g]['times'].total
            print "           %s" % self.carryOver[g]['times'].gc
            print "Group %s total Hours = %5.2f GC[%5.2f]" % (g, tt, gt)
            print ""
            
        print "Available for NEW Astronomy during %s" % self.semester.semester
        print "Includes only Group A carry over time:"
        print "    %s" % self.newAstroAvailGradeAHrs.total
        print "    %s" % self.newAstroAvailGradeAHrs.gc
        print ""
        print "Includes Groups A, B and C carry over time:"
        print "    %s" % self.newAstroAvailAllGradeHrs.total
        print "    %s" % self.newAstroAvailAllGradeHrs.gc
        print ""

        #self.debugReport()

    def debugReport(self):
        "For debugging"

        print "DETAILS: "
        print "Maint. Periods: "
        total = 0
        for p in self.maintPeriods:
            print p 
            total += p.duration
        print "Total maint periods: ", total    

        print "Shutdown Periods: "
        total = 0
        for p in self.shutdownPeriods:
            print p 
            total += p.duration
        print "Total shutdown periods: ", total    

        print "Test Sessions: "
        for s in self.testSessions:
            print s

        print "Carryover Sessions:"
        total = 0
        totals = {'A' : 0.0, 'B' : 0.0, 'C' : 0.0, 'None' : 0.0}
        for s in self.carryOverSessions:
            print s, s.session_type, s.grade, s.next_semester.time, self.getTimeType(s) 
            if s.grade is not None:
                totals[s.grade.grade] += s.next_semester.time
            else:
                totals['None'] += s.next_semester.time
            total = s.next_semester.time
        print "Total Carryover Time: ", total, totals    

    def getSemesterDays(self, semester = None):    
        "How many days in the given semester?"
        if semester is None:
            s = self.semester
        else:    
            s = DSSSemester.objects.get(semester = semester)
        return s.numDays() #(s.end() - s.start()).days

    def getGCHrs(self, hrs):
        """
        Given a uniformly distributed number of hours, how many would
        fall within the Galactic Center range?
        """
        gcHrs = (self.gcHrs[1] - self.gcHrs[0]) 
        return hrs * (gcHrs/24.)

    def getMaintenancePeriods(self):
        "What maintenance periods have been scheduled for this semester?"
        allPs = self.getProjectPeriods('Maintenance')
        # this list of periods includes all periods from elective
        # sessions; in this case, we only want one period per elective
        ps = []
        electives = []
        for p in allPs:
            if p.session.session_type.type == 'elective':
                if p.elective is not None and p.elective not in electives:
                    # use this period
                    ps.append(p)
                    # don't use periods from other electives
                    electives.append(p.elective)
            else:
                ps.append(p)
        return ps

    def getShutdownPeriods(self):
        "What shutdown periods have been scheduled for this semester?"
        return self.getProjectPeriods('Shutdown')

    def getProjectPeriods(self, pcode):    
        ss = DSSSession.objects.filter(project__pcode = pcode)
        self.preAssignedSessions.extend(ss)
        
        ps = DSSPeriod.objects.filter( \
            session__project__pcode = pcode
          , start__gt = self.semester.start()
          , start__lt = self.semester.end()).exclude( \
              state__name = 'Deleted').order_by('start')    
        return ps 

    def getTestSessions(self):
        "What are the testing sessions for this semester?"
        sem = self.semester.semester
        testing = Observing_Type.objects.get(type='testing')
        commissioning = Observing_Type.objects.get(type='commissioning')
        calibration = Observing_Type.objects.get(type = 'calibration')
        ss = Session.objects.filter(Q(semester__semester = sem) 
                                    , Q(observing_type = testing) \
                                    | Q(observing_type = commissioning)\
                                    | Q(observing_type = calibration))
        self.preAssignedSessions.extend(ss)
        return ss

    def getHours(self, periods):

        allHrs = SemesterTimes()            
        for p in periods:
            hrs = self.getPeriodHours(p)
            # update the totals
            allHrs += hrs

        return allHrs        
            
    def getPeriodHours(self, period):
        """
        Determines how much of ther various types of time these periods 
        add up.  These types include:
           * how much lies in the galactic center?
           * how much gets billed to each frequency bin?
        Uses their duration, since we'll be looking at mostly pending
        periods, who won't have any time billed yet.
        """

        dur = period.duration
        start = period.start
        end = period.end()

        # Turns out that we FOR NOW, we don't care about day/night
        #dayHrs, nightHrs = self.getHrsInDayTime(start, end)
        gcHrs, nonGCHrs  = self.getHrsInGC(start, end)
        gcFrac = gcHrs / dur

        lowFreqHrs = self.lowFreqPercent * dur
        hiFreq1Hrs = self.hiFreq1Percent * dur
        hiFreq2Hrs = self.hiFreq2Percent * dur

        total= Times(type = 'total'
                  , total = dur
                  , lowFreq= lowFreqHrs
                  , hiFreq1= hiFreq1Hrs
                  , hiFreq2= hiFreq2Hrs
                  )       

        gc = total.factor(gcFrac)
        gc.type = 'gc'
        
        return SemesterTimes(total = total, gc = gc)

    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)

    def fltEqual(self, flt1, flt2):
        eps = 1e-3
        return abs(flt1 - flt2) < eps

    def getHrsInGC(self, start, end):
        "Split up given time range by Galactic Center overlap."
        dur = TimeAgent.dtDiffHrs(start, end)
        # convert local time range to LST range
        lstStart = sla.Absolute2RelativeLST(start)
        lstEnd = sla.Absolute2RelativeLST(end)
        # be simplistic about the overalp
        if lstEnd < lstStart:
            lstEnd += 24.0
        
        gcHrs, nonGcHrs = self.findOverlap((lstStart, lstEnd), self.gcHrs, dur)
        return (gcHrs, nonGcHrs)

    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)

    def getSessionsHours(self, ss):

        all = SemesterTimes()
        for s in ss:
            if s.allotment is not None and \
              s.allotment.allocated_time is not None:
                t = self.getSessionHours(s)
                all += t
        return all

    def getTimeType(self, session):
        "Which of the three types for this session?"
        # if there is no freq. category, use low freq.
        freqCat = session.determineFreqCategory()
        return self.freq2key[freqCat] if freqCat is not None else 'lowFreq'

    def getSessionHours(self, s):
        "PHT Sessions simply bill to the freq they are at."

        t = Times()          

        # do this right with the LstPressureWeather class; 
        # but we don't care about the LST distruibution
        ps = [s.allotment.allocated_time]
        ps.extend([0.0]*23)
        ps = numpy.array(ps)
        wps = self.weather.binSession(s, ps)
        t.fromWeather(wps)

        gcHrs, nonGCHrs = self.getGCHoursFromSession(s)
        if gcHrs + nonGCHrs != 0.0:
            gcFrac = gcHrs / (gcHrs + nonGCHrs) 
        else:
            gcFrac = 0.0
        gc       = t.factor(gcFrac)

        return SemesterTimes(total = t, gc = gc)

            
            
    def getGCHoursFromSession(self, session):
        if session.allotment is None \
          or session.allotment.allocated_time is None \
          or session.target is None \
          or session.target.min_lst is None \
          or session.target.max_lst is None:
            return 0.0, 0.0
        # use max/min LST as compared to the Galctic Center
        min_lst = rad2hr(session.target.min_lst)
        max_lst = rad2hr(session.target.max_lst)
        # check for overlap
        if min_lst > max_lst:
            ss = [(0.0, max_lst), (min_lst, 24.0)]
        else:
            ss = [(min_lst, max_lst)]
        dur = session.allotment.allocated_time
        # now find overlaps for each range w/ the Gal. Center
        gcHrs = 0.0
        nonGCHrs = 0.0
        for s in ss:
            gc, nonGC = self.findOverlap(s, self.gcHrs, dur)
            gcHrs += gc
            nonGCHrs += nonGC
        return gcHrs, nonGCHrs                                 

    def getCarryOverSessions(self):
        """
        Returns any sessions that have a corresponding
        DSS Session and won't be complete next semester.
        In other words, they contribute to carry over.
        """

        preAssigned = [s.name for s in self.preAssignedSessions]

        # TBF: how to do the not None query?
        ss = Session.objects.filter(next_semester__complete = False).order_by('name')
        ss2 = [s for s in ss if s.dss_session is not None and s.name not in preAssigned]
        return ss2
            
    def getCarryOver(self, sessions):
        """
        How much do these sessions contribute to the 
        carryover, divided by Grade and Frequency,
        among other things.
        """

        carryOver = {}
        for g in ['A', 'B', 'C']:
            grade = SessionGrade.objects.get(grade = g)
            ss = [s for s in sessions if s.grade == grade]
            carryOver[g] = self.getCarryOverForGrade(ss)
        return carryOver    

    def getCarryOverForGrade(self, sessions):
        "Carry over is divided between fixed and everything else"

        fixed = SessionType.objects.get(type = 'Fixed')
        fixedSess = [s for s in sessions if s.session_type == fixed]
        otherSess = [s for s in sessions if s.session_type != fixed]

        fixed = self.getCarryOverTimes(fixedSess) 
        st = self.getCarryOverTimes(otherSess)

        return dict(fixed = fixed
                  , times = st)

    def getCarryOverTimes(self, ss):

       sts = SemesterTimes()
       for s in ss:
          st = self.getCarryOverForSession(s)
          sts += st 
       return sts    

    def getCarryOverForSession(self, s):
        """
        How much will the given session contribute to this
        semester's carryover?
        """

        t = Times(type = 'Total')          

        # add next semester's time to this time type 
        time = s.next_semester.time if s.next_semester.time is not None else 0.0
        ps = [time]      
        ps.extend([0.0]*23) 
        ps = numpy.array(ps)
        wps = self.weather.binSession(s, ps)  
        t.fromWeather(wps)  

        # how much of this time is Galactic Center time?
        gcHrs, nonGcHrs = self.getGCHoursFromSession(s)
        if (gcHrs + nonGcHrs) > 0.0:
            gcFrac = gcHrs / (gcHrs + nonGcHrs)
        else:
            gcFrac = 0.0
        gc = t.factor(gcFrac)
        gc.type = 'GC'

        return SemesterTimes(total = t, gc = gc) 
Example #4
0
class TestLstPressureWeather(TestCase):

    fixtures = ['proposal_GBT12A-002.json', 'scheduler.json']

    def setUp(self):

        self.lst = LstPressureWeather()

        # get the one proposal and it's one session
        proposal = Proposal.objects.all()[0]
        s = proposal.session_set.all()[0]

        # give it some values so it will show up in plot
        s.grade = SessionGrade.objects.get(grade = 'A')
        s.weather_type = WeatherType.objects.get(type = 'Poor')
        s.target.min_lst = 0.0
        s.target.max_lst = hr2rad(12.5)
        s.target.save()
        time = 6.5 # hrs
        s.allotment.allocated_time = time # hrs
        s.allotment.save()
        s.save()
        self.session = s

        ps = [0.0]*6
        ps.extend([1.0]*12)
        ps.extend([0.0]*6)
        self.sessPressure = numpy.array(ps)

        self.wPoor = WeatherType.objects.get(type = 'Poor')
        self.wGood = WeatherType.objects.get(type = 'Good')
        self.wExcellent = WeatherType.objects.get(type = 'Excellent')
   
    def createSession(self):
        "Create a new session for the tests"
        p = Proposal.objects.all()[0]
        return createSession(p)
    
    def test_binOpenSession(self):

        # make sure it shows up in poor
        ps = self.lst.binOpenSession(self.session, self.sessPressure)
        exp = Pressures(poor = self.sessPressure)
        self.assertEqual(exp, ps)

        # now make sure it shows up in good
        self.session.weather_type = self.wGood 
        ps = self.lst.binOpenSession(self.session, self.sessPressure)
        exp = Pressures(good = self.sessPressure)
        self.assertEqual(exp, ps)

    def test_binFixedSession(self):

        # make sure it shows up spread around 
        ps = self.lst.binFixedSession(self.session, self.sessPressure)
        exp = Pressures(poor = self.sessPressure*0.5
                      , good = self.sessPressure*0.25
                      , excellent = self.sessPressure*0.25
                        )
        self.assertEqual(exp, ps)

        # distribution is different for good weather
        self.session.weather_type = self.wGood
        ps = self.lst.binFixedSession(self.session, self.sessPressure)
        exp = Pressures(excellent = self.sessPressure*0.5
                      , good = self.sessPressure*0.5)
        self.assertEqual(exp, ps)

        # and pretty simple for excellent weather 
        self.session.weather_type = self.wExcellent
        ps = self.lst.binFixedSession(self.session, self.sessPressure)
        exp = Pressures(excellent = self.sessPressure)
        self.assertEqual(exp, ps)

    def test_binWindowedSession(self):

        self.session.monitoring.window_size = 2
        ps = self.lst.binWindowedSession(self.session, self.sessPressure)
        exp = Pressures(poor = self.sessPressure*0.5
                      , good = self.sessPressure*0.25
                      , excellent = self.sessPressure*0.25
                        )
        self.assertEqual(exp, ps)

        # now change the window size
        self.session.monitoring.window_size = 20
        ps = self.lst.binWindowedSession(self.session, self.sessPressure)
        self.assertEqual(ps.good.tolist(), ps.excellent.tolist())
        exp = Pressures(poor = self.sessPressure*0.75
                      , good = self.sessPressure*0.125
                      , excellent = self.sessPressure*0.125
                        )
        self.assertEqual(exp, ps)

    def test_binSession(self):

        # make sure it shows up in poor
        ps = self.lst.binSession(self.session, self.sessPressure)
        exp = Pressures(poor = self.sessPressure)
        self.assertEqual(exp, ps)