PyScore

001: # -*- python -*-
002: #### championship.py ## python package PyScore.tabulate module championship ####
003: 
004: # PyScore
005: # a race scoring programme
006: # written by Matt Draisey
007: # 2004 April 6
008: 
009: reloadables=[]
010: 
011: #### championship.py ## python package PyScore.tabulate module championship ####
012: 
013: import math
014: from relational import base
015: from tabulate import racesheet,scoresheet
016: from utility.verbose import vv,vvv,vvvv,debug
017: 
018: ######## ######## some actual working code ######## ########
019: 
020: def startcompleted(start):
021:     try: start.abandoned
022:     except AttributeError:
023:         try: start.race.abandoned
024:         except AttributeError: pass
025:         else: raise AttributeError
026:     else: raise AttributeError
027:     return start
028: 
029: def tabulate_championship(
030:     racesheetvalues,startsheetvalues,handicapsheet,
031:     seriestab,seriesdivisiontab,seriesscoretab,
032:     championship,
033:     champdivisionnew,champdivisiontab,
034:     champscorenew,champscoretab,
035: ):
036:     print>>vv,"TABULATING CHAMPIONSHIP RESULTS"
037: 
038:     infer_registration(seriesscoretab,champscorenew())
039: 
040:     infer_fleet_registration(handicapsheet,champscoretab)
041: 
042:     collect_by_start(
043:         seriesdivisiontab.values(),seriesscoretab,
044:         champdivisionnew(),champscoretab
045:     )
046: 
047:     championship.races=sum(
048:         [s.races for s in seriestab.values().adjoin_sort([])],[] # could be heap
049:     )
050: 
051: def infer_registration(seriesscoretab,scorenew):
052:     """
053:     Sometimes we may have boats registered for different divisions
054:     in different series that constitute the overall championship.
055:     In this case a boat may compete in more than one division.  The
056:     series is always the atom by which we determine results to be used
057:     in calculating championship results.
058:     """
059: 
060:     print>>vvv,"  INFER REGISTRATION"
061: 
062:     for (divisionname,boatentries) in seriesscoretab.adjoin_hierarchy(
063:         [("seriesdivision",lambda e,s: s.divisionname),"boat"]
064:     ):
065:         for (boat,scoreentries) in boatentries:
066:             champscore=scorenew(boat,divisionname)
067: 
068:     #for (boat,divisionentries) in seriesscoretab.adjoin_hierarchy(
069:     #   ["boat",("seriesdivision",lambda e,s: s.divisionname)]
070:     #):
071:     #   for (divisionname,scoreentries) in divisionentries:
072:     #           championshipscore=scorenew(boat,divisionname)
073: 
074: def infer_fleet_registration(handicapsheet,scoretab):
075:     """
076:     We determine fleet subdivisions of a division, which are scored
077:     together per race, yet are ranked separately for the championship.
078:     """
079: 
080:     print>>vvv,"  INFER FLEET"
081: 
082:     for(
083:         boat,[champscoreentries,handicapentries]
084:     ) in base.adjoin_polyhierarchy(
085:         scoretab,
086:         base.adjoin_filter(
087:             handicapsheet.obverse_filter(["seriespoints"]),
088:             ["fleet"]
089:         ),
090:         ["boat"]
091:     ):
092:         try:
093:             boatscore=champscoreentries.next()
094:         except StopIteration:
095:             continue
096: 
097:         print>>vvvv,"    BOAT:",boatscore.boat
098: 
099:         maxstarts=[0]
100:         registered=[]
101:         for (
102:             fleet,entries
103:         ) in base.hierarchy_filter(["fleet"],handicapentries):
104:             startentries=[e.start.race for e in entries]
105:             startentries.reverse()
106:             starts=[len(startentries)]+startentries
107:             if starts>maxstarts:
108:                 maxstarts=starts
109:                 registered=[fleet]
110:             elif starts==maxstarts:
111:                 registered+=[fleet]
112:         assert len(registered)>0
113:         registered.sort()
114:         boatscore.fleet=registered[0]
115:         if len(registered)>1:
116:             boatscore.fleetduplicates=registered
117: 
118:     # boats can only belong to one fleet within a division, so when we
119:     # can't otherwise decide, arbitrarily choose the first in fleet order.
120: 
121: def collect_by_start(seriesdivisiontab,seriesscoretab,divisionnew,scoretab):
122:     """
123:     Walk through each division, collecting races which have starts in
124:     that division and determine number of exclusions.
125: 
126:     Work on each start in turn, working on only registered boats.
127:     """
128: 
129:     print>>vvv,"  COLLECT BY START"
130: 
131:     for (
132:         divisionname,
133:         [seriesdivisionentries,scoreentries,seriesscoreentries]
134:     ) in base.polyhierarchy_filter(
135:         ["divisionname"],
136:         seriesdivisiontab.adjoin_sort(["divisionname"]), # could be heap
137:         base.adjoin_sort( # could be heap
138:             scoretab.adjoin_filter(["boat"]),
139:             [("championshipdivision",lambda e,s:s.divisionname)]
140:         ),
141:         base.adjoin_sort( # could be heap
142:             seriesscoretab.adjoin_filter(["boat"]),
143:             [("seriesdivision",lambda e,s: s.divisionname)]
144:         ),
145:     ):
146:         print>>vvvv,"    DIVISION",divisionname
147: 
148:         seriesdivisions=scoresheet.ScoreList(seriesdivisionentries)
149: 
150:         starts=sum([d.starts for d in seriesdivisions],[])
151: 
152:         champslice={}
153:         i=j=0
154:         for d in seriesdivisions:
155:             j=i+d.completed
156:             champslice[d]=(i,j)
157:             i=j
158:         completed=i
159: 
160:         nonecompleted=sum(
161:             [[d.dnc]*d.completed for d in seriesdivisions],[]
162:         )
163: 
164:         champdivision=divisionnew(divisionname)
165:         champdivision.starts=starts
166: 
167:         assert completed==len(starts)
168:         excluded=champdivision.championship.\
169:             championshipname.exclusions[completed]
170:         included=completed-excluded
171:         champdivision.completed=completed
172:         champdivision.excluded=excluded
173:         champdivision.included=included
174: 
175:         if not starts:
176:             continue
177: 
178:         scorelist=scoresheet.ScoreList(scoreentries)
179: 
180:         champdivision.numberofentrants=len(scorelist)
181:         champdivision.dnc=champdivision.numberofentrants+1
182: 
183:         registeredentries=[
184:             jointentry
185:             for (boat,jointentries) in base.polyhierarchy_filter(
186:                 ["boat"],scorelist,seriesscoreentries
187:             )
188:             for jointentry in base.outer_product(jointentries)
189:         ]
190:         
191:         for (
192:             boatscore,entries
193:         ) in base.hierarchy_filter(["score"],registeredentries):
194:             entrylist=scoresheet.ScoreList(entries)
195: 
196:             boatscore.handicaps=[None]*completed
197:             boatscore.points=nonecompleted[:]
198: 
199:             for (seriesdivision,entry) in base.adjoin_filter(
200:                 entrylist,["seriesdivision"]
201:             ):
202:                 (i,j)=champslice[seriesdivision]
203:                 boatscore.handicaps[i:j]=entry.handicaps
204:                 boatscore.points[i:j]=entry.points
205: 
206:         total_scores(champdivision,scorelist)
207: 
208: def total_scores(champdivision,scoretab):
209:     """
210:     Sum all included races and provide sufficient information
211:     to rank boats according to rule A8.
212:     """
213: 
214:     for score in scoretab:
215:         sorted=[(p,-i) for (i,p) in enumerate(score.points)]
216:         sorted.sort()
217: 
218:         score.excluded=[False]*champdivision.completed
219:         for (p,i) in sorted[champdivision.included:]:
220:             score.excluded[-i]=True
221: 
222:         del sorted[champdivision.included:]
223:         sorted=[p for (p,i) in sorted]
224:         score.totalpoints=sum(sorted)
225: 
226:         reversed=score.points[:]
227:         reversed.reverse()
228: 
229:         score.total=(score.totalpoints,sorted,reversed)
230: 
231:     rank=1
232:     for (total,scoreentries) in scoretab.adjoin_hierarchy(["total"]):
233:         sharedrank=rank
234:         sharedtotals=[e for e in scoreentries]
235:         for seriesscore in sharedtotals:
236:             seriesscore.ranking=sharedrank
237:             rank+=1
238: 
239:     for (fleet,totals) in scoretab.adjoin_hierarchy(["fleet","total"]):
240:         rank=1
241:         for (total,scoreentries) in totals:
242:             sharedrank=rank
243:             sharedtotals=[e for e in scoreentries]
244:             for seriesscore in sharedtotals:
245:                 seriesscore.fleetranking=sharedrank
246:                 rank+=1
247: 
248: #### championship.py ## python package PyScore.tabulate module championship ####