class TestSemesterTimeAccounting(TestCase): fixtures = ['proposal_GBT12A-002.json', 'scheduler.json'] def setUp(self): """ Setup periods to look like this: UTC: |12345678901234567890123|12345678901234567890123| GC: | <---------> | <----------> | Day: | <------------|--> <------------| Maint: | xxxx | xxxxxxxx | """ self.semester = '12A' self.ta = SemesterTimeAccounting(self.semester) # make some maintenance periods self.maintProj = create_maintenance_project() # daytime period: dt1 = datetime(2012, 3, 20, 13) # EST mp1 = create_maintenance_period(dt1, 4.0) # TBF: bug? mp1.session.project = self.maintProj mp1.session.save() # day & night period dt2 = datetime(2012, 3, 21, 5) # EST mp2 = create_maintenance_period(dt2, 8.0) # TBF: bug? mp2.session.project = self.maintProj mp2.session.save() self.maintPeriods = [mp1, mp2] # BUG: this observing type missing in scheduler.json fixture o = Observing_Type(type = 'commissioning') o.save() def tearDown(self): # clean up for p in self.maintPeriods: s = p.session p.remove() s.delete() self.maintProj.delete() def createTestingSessions(self): """ Create a PHT Session that will be one of the 'testing' types, for this semester, and that does not overlap with the Galactic Center. """ # I'm lazy; let's just change the session we've already got p = Proposal.objects.all()[0] s = p.session_set.all()[0] s.observing_type = Observing_Type.objects.get(type = 'testing') semester = Semester.objects.get(semester = self.semester) s.semester = semester s.target.min_lst = 0.0 s.target.max_lst = hr2rad(5.0) s.target.save() s.allotment.allocated_time = 2.5 s.allotment.save() s.save() return [s] def createCarryOverSession(self): """ Creates a PHT Session that will contribute to the carryover: * intersects with Galactic Center * L band - Low Freq group * Grade A * Next Semester time to complete """ sem = Semester.objects.get(semester = '12A') p = Proposal.objects.all()[0] data = { 'name' : 'nextSemesterSession' , 'pcode' : p.pcode , 'grade' : 'A' , 'semester' : sem , 'requested_time' : 3.5 , 'allocated_time' : 3.5 , 'session_type' : 'Open - Low Freq' , 'observing_type' : 'continuum' , 'weather_type' : 'Poor' , 'repeats' : 2 , 'min_lst' : '10:00:00.0' , 'max_lst' : '20:00:00.0' , 'elevation_min' : '00:00:00.0' , 'next_sem_complete' : False , 'next_sem_time' : 1.0 , 'receivers' : 'L' } adapter = SessionHttpAdapter() adapter.initFromPost(data) # just so that is HAS a DSS session. adapter.session.dss_session = self.maintProj.sesshun_set.all()[0] adapter.session.save() return adapter.session def test_getSemesterDays(self): self.assertEqual(182, self.ta.getSemesterDays()) self.assertEqual(184, self.ta.getSemesterDays(semester = '12B')) def test_getGCHrs(self): self.assertEqual(6.0, self.ta.getGCHrs(24.0)) def test_getMaintenancePeriods(self): ps = self.ta.getMaintenancePeriods() self.assertEqual(2, len(ps)) self.assertEqual(self.maintPeriods[0].start , ps[0].start) def test_getTestSessions(self): ss = self.ta.getTestSessions() self.assertEqual(0, len(ss)) self.createTestingSessions() ss = self.ta.getTestSessions() self.assertEqual(1, len(ss)) def test_getCarryOverSessions(self): ss = self.ta.getCarryOverSessions() self.assertEqual(0, len(ss)) s = self.createCarryOverSession() ss = self.ta.getCarryOverSessions() self.assertEqual(1, len(ss)) def test_getSessionHours(self): s = self.createTestingSessions()[0] ts = self.ta.getSessionHours(s) exp = Times(total = 2.5 , lowFreq = 2.5) self.assertEqual(exp, ts.total) self.assertEqual(Times(), ts.gc) # now change it's lst range to intersect # with the GC s.target.min_lst = hr2rad(10.0) s.target.max_lst = hr2rad(20.0) s.target.save() ts = self.ta.getSessionHours(s) self.assertEqual(exp, ts.total) exp = Times(total = 1.25 , lowFreq = 1.25) self.assertEqual(exp, ts.gc) def test_getCarryOver(self): # create the input s = self.createCarryOverSession() # analyze this input carryOver = self.ta.getCarryOver([s]) # check result exp = {} grades = ['A', 'B', 'C'] for g in grades: exp[g] = {'fixed' : SemesterTimes() , 'times' : SemesterTimes()} exp['A']['times'].total.total = 1.0 exp['A']['times'].total.lowFreq = 1.0 exp['A']['times'].gc.total = 0.5 exp['A']['times'].gc.lowFreq = 0.5 self.assertEqual(exp, carryOver) def test_getNewAstroAvailableTime(self): # setup up blank inputs grades = ['A', 'B', 'C'] c = {} for g in grades: c[g] = {'fixed' : SemesterTimes() , 'times' : SemesterTimes()} a = SemesterTimes() # blank inputs should produce blank outputs newAv = self.ta.getNewAstroAvailableTime(c, a, ['A']) self.assertEquals(SemesterTimes(), newAv) newAv = self.ta.getNewAstroAvailableTime(c, a, grades) self.assertEquals(SemesterTimes(), newAv) # now make it some real numbers to crunch on total = 10.0 gc = 5.0 totalFixed = 100.0 for g in ['A', 'B']: c[g]['times'].total = Times(total = total , lowFreq = total) #c[g]['times'].total.total = total #c[g]['times'].total.lowFreq = total c[g]['times'].gc = Times(total = gc , lowFreq = gc) #c[g]['times'].gc.total = gc #c[g]['times'].gc.lowFreq = gc c[g]['fixed'].total.total = totalFixed c[g]['fixed'].total.lowFreq = .50 * totalFixed c[g]['fixed'].total.hiFreq1 = .25 * totalFixed c[g]['fixed'].total.hiFreq2 = .25 * totalFixed a = self.ta.calcTotalAvailableHours() # just grade A newAv = self.ta.getNewAstroAvailableTime(c, a, ['A']) # total should go down by total + totalFixed. t = Times(total = a.total.total - total - totalFixed , lowFreq = a.total.lowFreq - total - (.5*totalFixed) , hiFreq1 = a.total.hiFreq1 - (.25*totalFixed) , hiFreq2 = a.total.hiFreq2 - (.25*totalFixed) ) self.assertEqual(t, newAv.total) g = Times(total = a.gc.total - gc , lowFreq = a.gc.lowFreq - gc , hiFreq1 = a.gc.hiFreq1 , hiFreq2 = a.gc.hiFreq2 ) self.assertEqual(g, newAv.gc) # now all grades newAv = self.ta.getNewAstroAvailableTime(c, a, grades) # times should got down twice as much t = Times(total = a.total.total - (2 * (total + totalFixed)) , lowFreq = a.total.lowFreq - (2 * (total + (.5*totalFixed))) , hiFreq1 = a.total.hiFreq1 - (2.*(.25*totalFixed)) , hiFreq2 = a.total.hiFreq2 - (2.*(.25*totalFixed)) ) self.assertEqual(t, newAv.total) g = Times(total = a.gc.total - (2*gc) , lowFreq = a.gc.lowFreq - (2*gc) , hiFreq1 = a.gc.hiFreq1 , hiFreq2 = a.gc.hiFreq2 ) self.assertEqual(g, newAv.gc) def test_getPeriodHours(self): # day time only period mp1 = self.maintPeriods[0] hrs = self.ta.getPeriodHours(mp1) exp = Times(total = 4.0 , lowFreq = 2.0 , hiFreq1 = 1.0 , hiFreq2 = 1.0 ) self.assertEqual(exp, hrs.total) # a little night AND day period mp2 = self.maintPeriods[1] hrs = self.ta.getPeriodHours(mp2) self.assertEqual(8.0, hrs.total.total) self.assertEqual(4.0, hrs.total.lowFreq) self.assertAlmostEqual(2.0, hrs.total.hiFreq1, 3) self.assertAlmostEqual(2.0, hrs.total.hiFreq2, 3) self.assertAlmostEqual(5.3558844564835955, hrs.gc.total, 3) def test_getHrsInGC(self): # LST of 17.5 - 18.5 is entirely in GC dur = 1.0 dt1 = datetime(2012, 3, 19, 11) dt2 = dt1 + timedelta(hours = dur) self.assertEqual((dur, 0.0), self.ta.getHrsInGC(dt1, dt2)) # LST of 12.5 - 13.5 is entirely NOT in GC dur = 1.0 dt1 = datetime(2012, 3, 19, 6) dt2 = dt1 + timedelta(hours = dur) self.assertEqual((0.0, dur), self.ta.getHrsInGC(dt1, dt2)) # LST of 12.5 - 17.5 overlaps with GC by about 2.5 hrs dur = 5.0 dt1 = datetime(2012, 3, 19, 6) dt2 = dt1 + timedelta(hours = dur) gc, nonGc = self.ta.getHrsInGC(dt1, dt2) self.assertAlmostEquals(gc + nonGc, dur, 3) self.assertAlmostEquals(2.50350778689, nonGc, 3) self.assertAlmostEquals(2.49649221311, gc, 3) # LST of 12.5 - 23.5 encompasses all of GC - 6 hours dur = 11.0 dt1 = datetime(2012, 3, 19, 6) dt2 = dt1 + timedelta(hours = dur) gc, nonGc = self.ta.getHrsInGC(dt1, dt2) self.assertAlmostEquals(gc + nonGc, dur, 3) self.assertAlmostEquals(5.9836173979, gc, 3) self.assertAlmostEquals(5.0163826021, nonGc, 3) # make sure LST wrap around is OK: 18.5 to 2.5 dur = 12 dt1 = datetime(2012, 3, 19, 12) dt2 = dt1 + timedelta(hours = dur) gc, nonGc = self.ta.getHrsInGC(dt1, dt2) self.assertAlmostEquals(gc + nonGc, dur, 3) self.assertAlmostEquals(2.48694527307, gc, 3) self.assertAlmostEquals(9.51305472693, nonGc, 3) def test_getHrsInDayTime(self): # all night time dur = 1.0 dt1 = datetime(2012, 3, 19, 1) dt2 = dt1 + timedelta(hours = dur) day, night = self.ta.getHrsInDayTime(dt1, dt2) self.assertEquals(day, 0.0) self.assertEquals(night, dur) # all day time dur = 1.0 dt1 = datetime(2012, 3, 19, 13) dt2 = dt1 + timedelta(hours = dur) day, night = self.ta.getHrsInDayTime(dt1, dt2) self.assertEquals(day, dur) self.assertEquals(night, 0.0) # small overlap dur = 6.0 dt1 = datetime(2012, 3, 19, 9) dt2 = dt1 + timedelta(hours = dur) day, night = self.ta.getHrsInDayTime(dt1, dt2) self.assertEquals(dur, day + night) self.assertEquals(day, 3.61) self.assertEquals(night, 2.39) # multi-day time range - like a long shutdown dur = (24*2.0) + 11.0 dt1 = datetime(2012, 3, 19, 12) dt2 = dt1 + timedelta(hours = dur) day, night = self.ta.getHrsInDayTime(dt1, dt2) self.assertEquals(dur, day + night) self.assertAlmostEquals(41.348888888888887, day, 3) self.assertAlmostEquals(17.6511111111, night, 3) def test_calculateTimeAccounting(self): """ Here's where we tie it all together: """ # calculate everything self.ta.calculateTimeAccounting() # check the buckets numDays = 182 #self.ta.numDays totalAv = self.ta.totalAvailableHrs self.assertEqual(numDays*24, totalAv.total.total) self.assertEqual((numDays*24)*(6/24.), totalAv.gc.total) # TBF: other totalAv.total fields? # there are maint. periods to account for hrs = self.ta.maintHrs expMntT = Times(total = 12.0 , lowFreq = 6.0 , hiFreq1 = 3.0 , hiFreq2 = 3.0 ) self.assertEqual(expMntT, hrs.total) expMntGC = Times(total = 5.355884) # TBF other fields? self.assertAlmostEqual(expMntGC.total, hrs.gc.total, 3) # no shutdown or testing hrs = self.ta.shutdownHrs self.assertEqual(Times(), hrs.total) hrs = self.ta.testHrs self.assertEqual(Times(), hrs.total) # and no carry over expCO = {} for g in self.ta.grades: expCO[g] = {'fixed' : SemesterTimes() , 'times' : SemesterTimes() } self.assertEqual(expCO, self.ta.carryOver) # do our internal check self.ta.checkTimes() # check the hours available to astronomy expAvT = Times(total = 4356. , lowFreq = 2178.0 , hiFreq1 = 1089.0 , hiFreq2 = 1089.0 ) self.assertEqual(expAvT, self.ta.astronomyAvailableHrs.total) expAvGC = Times(total = 1086.6441) self.assertAlmostEqual(expAvGC.total , self.ta.astronomyAvailableHrs.gc.total, 3) # Testing, part two! # now introduce some 12A testing time ss = self.createTestingSessions() # and some carry over s = self.createCarryOverSession() # and recalculate everything self.ta.calculateTimeAccounting() # these should not have changed totalAv = self.ta.totalAvailableHrs self.assertEqual(numDays*24, totalAv.total.total) self.assertEqual((numDays*24)*(6/24.), totalAv.gc.total) # but now we have testing exp = Times(total = 2.5 , lowFreq = 2.5) self.assertEqual(exp, self.ta.testHrs.total) # which lowers our overall available time expAvT = expAvT - exp self.assertEqual(expAvT , self.ta.astronomyAvailableHrs.total) # and we've got carry over too! expCO['A']['times'].total = Times(total = 1.0 , lowFreq = 1.0) expCO['A']['times'].gc = Times(total = 0.5 , lowFreq = 0.5) self.assertEqual(expCO['B'], self.ta.carryOver['B']) self.assertEqual(expCO['C'], self.ta.carryOver['C']) # which affects the time we have for NEW astronomy: # no Grade B & C carry over means these should be equal self.assertEqual(self.ta.newAstroAvailGradeAHrs , self.ta.newAstroAvailAllGradeHrs) t = expAvT - Times(total = 1.0, lowFreq = 1.0) newAstro = SemesterTimes(total = t, gc = g) self.assertEqual(t, self.ta.newAstroAvailGradeAHrs.total)