Ticket #86: TSP_mainloop.py

File TSP_mainloop.py, 60.6 KB (added by rodj59, 3 years ago)
Line 
1#
2# Definitions for TSP/VRP with constraints.
3#
4# Copyright(c) Rod Millar 2008
5# email: rodj59@yahoo.com.au , rodj59@y7.com.au
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19#
20
21import pg
22
23class plpy_class(object):
24    "Emulate the plpy module functionality from PostgreSQL"
25    def __init__(self,Hostname="rod1"):
26        # connect to the PostgreSQL DB
27        self.db = pg.DB("transdb",Hostname,5432,None,None,'transport','lost')
28
29    def execute(self,qry):
30        "Emulate the PostgreSQL plpy.execute() function"
31        # ie. return a list of dictionaries for each row retrieved
32        rows = self.db.query(qry)
33        try:
34            drows = rows.dictresult()
35            return drows
36        except:
37            #print "*** execute(%s) returns = " % qry,rows
38            return rows
39
40    def __del__(self):
41        # close the DB connection
42        self.db.close()
43
44plpy = plpy_class() # the DB interface
45
46
47# Main loop
48#       Python function to find a path ordering which satisfies all constraints.
49#
50# max_dist = maximum distance deemed to be a consistent solution
51# debug =  > 0 to produce debugging output in debug_table
52# search_dist = 0|1  if want to search for best distance above max_dist also
53# search_lim = max no of cycles to search
54# lahead = max number of options for look-ahead to consider
55# ordered = 0 if you want to force search evaluation to be priority ordered
56#def mainloop(tab varchar,link_tab varchar,job_tab varchar,max_dist float,debug int,search_dist int,search_lim int,lahead int,ordered int)
57def mainloop(tab ,link_tab ,job_tab ,max_dist ,debug=1 ,search_dist=1 ,search_lim=35 ,lahead=10 ,ordered=0 ):
58    import time
59    global dbg_line,apply_changes_time,mainloop_time,non_gridlock_time,gridlock_time,FIRST_SEQ
60    MS = plpy.execute("select 'now'::timestamp as time")[0]['time']
61    gridlock_time  = plpy.execute("select '00:00:00'::interval as time")[0]['time']
62    non_gridlock_time = plpy.execute("select '00:00:00'::interval as time")[0]['time']
63    apply_changes_time  = plpy.execute("select '00:00:00'::interval as time")[0]['time']
64    search_limit = search_lim
65    best_dist = 2**32 # eg. start with inifinite distance as best
66   
67    # seq key to first destination in the path set
68    FIRST_SEQ = plpy.execute("select seq from %s where job_type in ('s','S') order by time_bound asc limit 1" % tab)[0]['seq']
69
70
71
72    def inconsistency_class(tab,link_tab,seq,max_dist):
73        rows = plpy.execute("select inconsistency_class('%s','%s',%s,%f)" % \
74                    (tab,link_tab,seq,max_dist))
75        rows1 = plpy.execute("select * from %s where seq = %s" % (tab,seq))
76        return rows[0]['inconsistency_class'],rows1[0]
77
78    def priority_delta(cost,priority):
79        "Find the prioritised value of a solution, lower values being better"
80        # might need to make this return just cost when positive ??? XXX HERE
81        return cost - priority
82
83    def min_priority_link(links):
84        "Select & return the path_link from the list which has the lowest priority"
85        ANS = links[0]
86        for link in links[1:]:
87            if link['priority'] < ANS['priority']:
88                ANS = link
89        return ANS
90
91    def movement_exclusion_pre(prev,next,dest,pre=None,dest2=None):
92        """Return non-zero if the proposed movement of d to between p and n is not worthwhile.
93        pre = the destination currently the predecessor of dest.
94        This is called before the proposed movement has been applied."""
95        # XXX HERE could return > 1 to indicate the inner loop can break ???
96        if pre is None:
97            prows = plpy.execute("select prev_dest from %s where next_dest = %d and active" % \
98                          (link_tab,dest['seq']))
99            if len(prows) <= 0:
100                return 1 # could be the first node of the path ... but redundant from above !!
101
102            pre = prows[0]['prev_dest'] # existing active predecessor to dest
103
104
105        if prev['job_type'] in ('s','S') and dest['pu'] == 'f':
106            return 1 # dont link a drop straight after a start-of-day destination
107        if next['job_type'] in ('e','E') and ((dest2 is None and dest['pu'] == 't') or (dest2 is not None and dest2['pu'] == 't')):
108            return 1 # dont link a PU immediately before an end-of-day destination
109        if pre == prev['seq']:
110            return 1 # dont link back in same position !!!
111
112        if prev['job'] == next['job'] and next['job_type'] not in ('p','P','s','S','e','E'):
113            return 1 # we dont split non point-2-point jobs !!!
114   
115        if prev['job_type'] in ('e','E') and next['job_type'] in ('s','S') :
116            return 1 # we dont insert between end-of-day & start-of-next-day markers
117
118        if prev == dest:
119            return 1 # we cannot link destination as a successor of itself !!!
120
121        # Add test to avoid overloading HERE XXXX !!!!!!!!!! ... or in the post method
122
123        if  dest2 is None  or dest2 is dest:
124                if dest['pu'] == 'f':
125                    # dest is a Drop, find its PU
126                    dPU = plpy.execute("select * from %s where job = %s and pu" % (tab,dest['job']))[0]
127                    if dPU['time'] > prev['time']:
128                        return 10 # we dont move a Drop to an earlier position than its PickUp, move PU first
129                    if dest['time_bound'] <= prev['time_bound'] and prev['pu'] == 't':
130                        # ie. latest Drop is earlier than earliest PU for predecessor
131                        return 1 # constraints unsatisfiable !!! XXX HERE
132
133
134                elif dest['pu'] == 't':
135                    dD = plpy.execute("select * from %s where job = %s and not pu" % (tab,dest['job']))[0]
136                    if dD['time'] <= prev['time']:
137                        return 11 # we dont move a PU to later position than its Drop, move the Drop first
138                    if dest['time_bound'] >= next['time_bound'] and next['pu'] == 'f':
139                        # ie. earliest PU time is later than latest Drop time for next dest
140                        return 1 # constraints unsatisfiable !!! XXX HERE
141                    # also consider loads XXX HERE ... but need to allow sequences of more than one
142                    #  destinations to be moved at a time for this otherwise incomplete !!!
143       
144        else: # dest2 is not None
145            # need to know the set of destinations which will be moved
146            intermediates = plpy.execute("select * from following_destinations_inclusive('%s','%s',%s) where time <= '%s'::timestamp" % \
147                                    ('tab','link_tab',dest['seq'],dest2['time']))
148            for inter in intermediates:
149                if inter['seq'] in (prev['seq'],next['seq']):
150                    return 1 # cant link segment to within itself
151                if inter['pu'] == 'f':
152                    # inter is a Drop, find its PU
153                    dPU = plpy.execute("select * from %s where job = %s and pu" % (tab,inter['job']))[0]
154                    if dPU['time'] > prev['time']:
155                        return 1 # we dont move a Drop to an earlier position than its PickUp, move PU first
156                    if inter['time_bound'] <= prev['time_bound'] and prev['pu'] == 't':
157                        # ie. latest Drop is earlier than earliest PU for predecessor
158                        return 1 # constraints unsatisfiable !!! XXX HERE
159                elif inter['pu'] == 't':
160                    dD = plpy.execute("select * from %s where job = %s and not pu" % (tab,inter['job']))[0]
161                    if dD['time'] <= prev['time']:
162                        return 1 # we dont move a PU to later position than its Drop, move the Drop first
163                    if inter['time_bound'] >= next['time_bound'] and next['pu'] == 'f':
164                        # ie. earliest PU time is later than latest Drop time for next dest
165                        return 1 # constraints unsatisfiable !!! XXX HERE
166
167        return 0
168
169    def movement_exclusion_post(p,n,d,d2=None):
170        """Return non-zero if the movement of d to between p and n is not worthwhile.
171        This is called only after the movement has been applied: ie. sequence p->d->n
172        is in the current (non-committed) path"""
173       
174        if d['pu'] == 'f':
175                # the following are tentative
176                # dont move a drop to earlier than its earliest acceptable pickup ( excluded automatically???)
177                dPU = plpy.execute("select * from %s where job = %s and pu" % (tab,d['job']))[0]
178                if dPU['time_bound'] > d['time']:
179                        return 1
180                # dont move a Drop to a position where its time is later than its timebound
181                #  XXX HERE strengthen this XXX !!! ???
182                #if d['load_pcnt'] > 1.01:
183                 #   #return 1  # don't overload
184                   # pass
185                if d['time'] > d['time_bound'] :
186                        # superficially, the drop is too late
187                        # ... check what the direct time would be
188                        if d['job_type'] in ('p','P'):
189                            # p2p , take time from path_link
190#                           if d['time_bound'] < d['time']:
191#                               return 1
192                            nts = plpy.execute("select '%s'::timestamp + time_int as time from %s where prev_dest = %s and next_dest = %s" % \
193                                                (dPU['time'],link_tab,dPU['seq'],d['seq']))[0]['time']
194                            if nts > d['time_bound']:
195                                return 1
196                        else:
197                            assert d['job_type'] in ('h','H','f','F'),"*** Assertion error: unexpected job_type"
198                            return 1 # hourly job destinations are moved in pairs
199                        # dont move a Drop destination to after a Pickup destination when the latest drop timebound is
200                        # earlier than the earliest timebound for the destination Pickup
201                        oPUs = plpy.execute("select * from following_destinations('%s','%s',%s) where pu and time <= '%s'::timestamp order by time_bound desc limit 1" % \
202                                            (tab,link_tab,d['seq'],p['time']))
203                        if len(oPUs):
204                                oPU = oPUs[0]
205                                if oPU['time_bound'] > d['time_bound']:
206                                    return 1
207
208        elif d['pu'] == 't':
209                # the following are tentative
210                # dont move a Pickup to later than its latest acceptable drop - min travel time (consider load)
211                dD = plpy.execute("select * from %s where job = %s and not pu" % (tab,d['job']))[0]
212                if d['time'] > dD['time_bound']:
213                        return 1
214                  #  pass
215                #if d['load_pcnt'] > 1.01:
216                 #   return 1  # dont permit moves which overload (premised on multi-dest moves)
217                    #pass
218
219                # dont move a Pickup to a position where its Drop time is later than latest timebound
220                # HERE ... below could be strengthened to preclude all overdue moves
221                if dD['time'] > dD['time_bound'] and d['time'] < dD['time_bound']: # only applies if previously consistent ???
222                    # ie. superficially, this drop is too late
223                    # ... but must check what the direct time from PU would be before excluding it
224                    if dD['job_type'] in ('p','P'): # other case should be filtered out elsewhere !!! XXX
225                        # p2p take time from path_link
226                        nts = plpy.execute("select '%s'::timestamp + time_int as time from %s where prev_dest = %s and next_dest = %s" % \
227                                                (d['time'],link_tab,d['seq'],dD['seq']))[0]['time']
228                        if nts > dD['time_bound'] :
229                                return 1 # impossible for job to be on-time
230                    else:
231                        assert dD['job_type'] in ('h','H','f','F'),"*** Assertion error: unexpected job_type"
232                        # NB. hourly job destinations are moved in pairs !
233                        if dD['time'] > dD['time_bound']:
234                                return 1
235
236                # dont move a Pickup to before a drop destination when the earliest pickup timebound
237                # is later than the latest timebound for the destination drop
238                oDs = plpy.execute("select * from preceding_destinations('%s','%s',%s) where (not pu) and time <= '%s'::timestamp order by time_bound asc limit 1" % \
239                                        (tab,link_tab,d['seq'],n['time']))
240                if len(oDs):
241                    oD = oDs[0]
242                    if oD['time_bound'] < d['time_bound']:
243                            return 1
244        return 0
245
246    def apply_changes(prev,next,dest,priority=None,dest2=None):
247        "Move dest to between prev & next (seq nos). Return old predecessor,successor of dest"
248        global apply_changes_time,dbg_line
249        TS = plpy.execute("select 'now'::timestamp as time")[0]['time']
250        if dest2 == dest :
251            assert dest2 is dest,"***Assertion failed, dest1 is not dest2"
252            dest2 = None
253
254        if debug > 3:
255            dbg_line = dbg_line + 1
256            plpy.execute("insert into debug_table values ('%s',%d)" % \
257                    ("Apply_changes(%d,%d,%d, time TS=%s) " % (prev,next,dest,TS ),dbg_line))
258
259        # if dest2 is not None, means the path segment between dest & dest2(inclusive) is to be moved
260        # to between prev & next
261        if dest2 is not None:
262            # moving a segment bounded by dest & dest2
263            old_links = plpy.execute("select * from path_link_deactivation_triple('%s','%s',%d,%d,%d,%d)" % (tab,link_tab,prev,next,dest,dest2))
264                       
265            new_links = plpy.execute("select * from path_link_activation_triple('%s','%s',%d,%d,%d,%d)"  % \
266                        (tab,link_tab,prev,next,dest,dest2))
267
268        else:  # only moving a single destination
269            old_links = plpy.execute("select * from path_link_deactivation_triple('%s','%s',%d,%d,%d)" % (tab,link_tab,prev,next,dest))
270                       
271            new_links = plpy.execute("select * from path_link_activation_triple('%s','%s',%d,%d,%d)"  % \
272                        (tab,link_tab,prev,next,dest))
273
274        assert   len(old_links) == 3 , "Error: number of old_links = %d, prev=%s,next=%s,dest=%s,dest2=%s" % (len(old_links),prev,next,dest,dest2)
275        assert   len(new_links) == 3 , "Error: number of new_links = %d, prev=%s,next=%s,dest=%s,dest2=%s" % (len(new_links),prev,next,dest,dest2)
276        # deactivate the old links
277        min_old_pr = None
278        for oidr in old_links:
279            if priority is not None:
280#               plpy.execute("update %s set active = false,priority = %f where prev_dest = %s and next_dest = %s" % \
281#                       (link_tab,priority,oidr['prev_dest'],oidr['next_dest']))
282                plpy.execute("update %s set active = false where prev_dest = %s and next_dest = %s" % (link_tab,oidr['prev_dest'],oidr['next_dest']))
283                if min_old_pr is None or min_old_pr['priority'] > oidr['priority']:
284                    min_old_pr = oidr
285
286            else:
287                plpy.execute("update %s set active = false where prev_dest = %s and next_dest = %s" % (link_tab,oidr['prev_dest'],oidr['next_dest']))
288            if debug > 3:
289                dbg_line = dbg_line + 1
290                plpy.execute("insert into debug_table values ('%s',%d)" % ("deactivate prev=%s , next=%s, " % (oidr['prev_dest'],oidr['next_dest']),dbg_line))
291
292            # collect the original prev & next destination sequence nos
293            if oidr['next_dest'] == dest :
294                old_prev = oidr['prev_dest']
295            elif oidr['prev_dest'] == dest :
296                old_next = oidr['next_dest']
297        # increment the priority of the lowest priority de-activated link
298        if min_old_pr is not None:
299            plpy.execute("update %s set priority = %s where prev_dest = %s and next_dest = %s" % \
300                            (link_tab,priority,min_old_pr['prev_dest'],min_old_pr['next_dest']))
301        # activate the new links
302        for nidr in new_links:
303            # activate the new links
304            if debug > 3:
305                dbg_line = dbg_line + 1
306                plpy.execute("insert into debug_table values ('%s',%d)" % \
307                        ("activating path_link , priority= %s,prev= %s , next = %s, # of new = %d" % (priority,nidr['prev_dest'],nidr['next_dest'],len(new_links))  ,dbg_line))
308            if 0 and priority is not None:
309                plpy.execute("update %s set active = true,priority = %f where prev_dest = %s and next_dest = %s" % \
310                        (link_tab,priority-1,nidr['prev_dest'],nidr['next_dest']))
311            else:
312                assert nidr['prev_dest'] is not None,"Exception, nidr is None"
313                plpy.execute("update %s set active = true where prev_dest = %s and next_dest = %s" % \
314                        (link_tab,nidr['prev_dest'],nidr['next_dest']))
315       
316        # now propagate constraints through the newly active path
317
318        # XXX HERE ... would be better if we knew the earliest destination which was changed !!!
319        # ... but note, propagation always stops when no change found ...
320        for seq_row in old_links : # + [{'oid':dest}]:
321               
322            changes = plpy.execute("select * from dest_changes('%s','%s','%s',(select prev_dest from %s where prev_dest = %s and next_dest = %s))" % \
323                                (tab,link_tab,job_tab,link_tab,seq_row['prev_dest'],seq_row['next_dest']))
324            if debug > 3:
325                dbg_line = dbg_line +1
326                plpy.execute("insert into debug_table values ('%s,prev=%d,next=%d, changes = %s',%d)" % ("activating changes  from prev_dest ",seq_row['prev_dest'],seq_row['next_dest'],changes[0]['dest_changes'],dbg_line))
327        # update cumulative execution time
328        apply_changes_time = plpy.execute("select (('now'::timestamp - '%s'::timestamp ) + '%s'::interval) as time" % (TS,apply_changes_time))[0]['time']
329        return old_prev,old_next,old_links,new_links
330       
331    dbg_line = 1
332    plpy.execute("insert into debug_table values ('%s',%d)" % \
333                    ("***At start apply_changes_time=%s) " % (apply_changes_time, ),dbg_line))
334       
335    best_dist_path = None
336    consistent_count = 0
337    if ordered ==  0 :
338        incons = plpy.execute("select * from inconsistent_dests('%s','%s',%f) order by priority asc" % (tab,link_tab,max_dist))
339    else:
340        incons = plpy.execute("select * from %s" % tab)  # in this case just grab all regardless of inconsistency
341    num_dests = len(incons) # number of destinations in the path
342    if debug :
343        plpy.execute("delete from debug_table") # debugging
344    while  (ordered <> 0 and search_limit > 0 and consistent_count < num_dests) or ( ordered == 0 and len(incons) and search_limit > 0) :  # NB. not really necessary to identify inconsistency set in advance HERE XXX
345
346        # keep processing until no inconsistencies remain or search limit is exceeded
347        search_limit = search_limit -1
348        if search_limit < 0:
349            # abort with failure, return the empty destinations list
350            return []
351
352        # ... NB. to escape an inconsistency, priority must exceed weakest justification for it
353        # , which means the priority of the active link
354
355
356        # option_costs = [ [prev,next,dest,dest2,priority,cost,detect] ... ] for each candidate
357        option_costs = []
358        gridlocked = 1
359        consistent_count = 0
360
361        for option1 in incons:
362            # XXX HERE ... if options are not guaranteed inconsistent in advance, need to apply the test here
363            if ordered <> 0:
364                flag,option = inconsistency_class(tab,link_tab,option1['seq'],max_dist)
365            else:
366                option = option1
367                flag = option['flag']
368
369            # take the alternate (ie. presently inactive) path_links & compute the cost to activate each
370            # ... which is the priority of the weakest active path_link precluding the activation
371       
372            # but first decide what causes the inconsistency so the minimal option set is considered
373            if debug > 3:
374                dbg_line = dbg_line+1
375                dbg_line = dbg_line + 1
376
377                plpy.execute("insert into debug_table values ('%s',%d)" % \
378                        (" *** for option in incons seq=%d,suburb=%s,time=%s,dist=%f,load=%f,flag=%s,pu=%s" % \
379                                (option['seq'],option['suburb'],option['time'],option['dist'],option['load_pcnt'],option['flag'],option['pu']),dbg_line))
380
381
382            if flag[5] == '1': # flag & 0000010 :
383                # missing predecessor
384                # ... this case should no longer apply since changes are applied in sets so the path remains complete
385                raise "Error: missing predecessor flag detected"
386
387            elif flag[3] == '1': #flag & 0001000 :
388                # drop before pickup
389                # ... either move the drop after the pickup or the pickup before the drop
390                # NB. the one processed first will necessarily have no lesser priority than the other
391                # destination of the same job !
392                if debug > 1:
393                    dbg_line = dbg_line + 1
394                    plpy.execute("insert into debug_table values ( '*** Drop before PU, seq # = %s, job # = %s, suburb = %s , flag = %s, pu=%s',%d)" % (option['seq'],option['job'],option['suburb'], flag,option['pu'],dbg_line))
395                   
396
397                # see if we have a drop or a pickup
398                if option['pu'] == 'f' :   #  XXX pg.DB returns 't' or 'f', plpy return 0 or 1 !!!
399                    Drop = option
400                    prows = plpy.execute("select * from %s where job = %s and pu" % (tab,option['job']))
401                    PU = prows[0]
402                else :
403                    drows = plpy.execute("select * from %s where job = %s and not pu" % (tab,option['job']))
404                    PU = option
405                    Drop = drows[0]
406
407                # find whichever has highest priority
408                if Drop['priority'] > PU['priority']:
409                    Detect = Drop
410                else:
411                    Detect = PU
412               
413                # now PU/Drop are pickup & drop destinations corresponding  to options job
414
415                # try moving the Drop to a location after pickup
416
417                # find the set of destinations which follow the Pickup
418                follows =  plpy.execute("select * from following_destinations_inclusive('%s','%s',%d)" % \
419                                    (tab,link_tab,PU['seq']))
420                   
421                # the Drop must be linked as the predecessor of one of these
422                option_priority = None
423                temp_options = []
424                temp_options_nongridlock = []
425                for i in range(1,len(follows)) :
426                        prev = follows[i-1]
427                        next = follows[i]
428                        if prev['job'] == next['job'] and prev['job_type'] not in ('p','P','s','S'):
429                            continue
430                       
431                        if movement_exclusion_pre(prev,next,Drop): continue
432                       
433                        rows = plpy.execute("select * from path_link_reloc_priority('%s','%s',%d,%d,%d)" % \
434                                                (tab,link_tab,prev['seq'],next['seq'],Drop['seq']))
435                        # priority of moving the Drop to this location
436                        if rows[0]['path_link_reloc_priority'] < Detect['priority']:
437                            temp_options_nongridlock.append([prev,next,Drop,Drop,Detect['priority'],rows[0]['path_link_reloc_priority'],Detect['seq']])
438                        elif option_priority is None : # or option_priority > rows[0]['path_link_reloc_priority']:
439                            Dest = Drop
440                            option_priority  = rows[0]['path_link_reloc_priority']
441                           
442                            temp_options = [[prev,next,Dest,Dest,Detect['priority'],option_priority,Detect['seq']]]
443                        #elif option_priority == rows[0]['path_link_reloc_priority']:
444                        else:
445                            temp_options.append([prev,next,Dest,Dest,Detect['priority'],option_priority,Detect['seq']])
446
447                # here indx is candidate index to cheapest option  in follows
448
449                # try moving the Pickup to a location before the Drop
450               
451                precedes = plpy.execute("select * from preceding_destinations_inclusive('%s','%s',%d)" % \
452                                        (tab,link_tab,Drop['seq'])) 
453
454                # the Pickup must be linked as the predecessor of one of these
455                for i in range(1,len(precedes)):
456                        prev = precedes[i]
457                        next = precedes[i-1]
458
459                        if movement_exclusion_pre(prev,next,PU): continue
460
461                        rows = plpy.execute("select * from path_link_reloc_priority('%s','%s',%d,%d,%d)" % \
462                                            (tab,link_tab,prev['seq'],next['seq'],PU['seq']))
463
464                        if rows[0]['path_link_reloc_priority'] < Detect['priority']:
465                            temp_options_nongridlock.append([prev,next,PU,PU,Detect['priority'],rows[0]['path_link_reloc_priority'],Detect['seq']])
466                        elif option_priority is None : # or option_priority > rows[0]['path_link_reloc_priority']:
467                            Dest = PU
468                            option_priority = rows[0]['path_link_reloc_priority']
469
470                            temp_options = [[prev,next,Dest,Dest,Detect['priority'],option_priority,Detect['seq']]]
471                        #elif option_priority == rows[0]['path_link_reloc_priority'] :
472                        else:
473                            temp_options.append([prev,next,Dest,Dest,Detect['priority'],option_priority,Detect['seq']])
474                           
475                if debug :
476                    try:
477                        dbg_line = dbg_line+1
478                        plpy.execute("insert into debug_table values ('%s',%d)" % \
479                            ("Drop b4 PU, prev=%d,next=%d,dest=%d,priority=%f,detect=%s,cost=%s" % \
480                                (prev['seq'],next['seq'],Dest['seq'],Detect['priority'],Detect['seq'],option_priority),dbg_line))
481                    except:
482                        pass  # means only non-gridlock options found
483
484                option_costs = option_costs+ temp_options + temp_options_nongridlock
485
486            elif flag[4] == '1': # flag & 0000100 :
487                # pickup & drop on different days
488                # ... either move the drop backward or move the pickup forward to same day
489
490                if flag[3] == '1':
491                    continue # if Drop before PU inconsistency also exists, we fix that first !
492
493                # locate the PU & Drop
494                if option['pu']  == 't':
495                    PU = option
496                    Drops = plpy.execute("select * from %s where not pu and job = %s limit 1" % \
497                                    (tab,option['job']))
498                    Drop = Drops[0]
499                else:
500                    Drop = option
501                    PUs = plpy.execute("select * from %s where pu and job = %s limit 1" % \
502                                    (tab,option['job']))
503                    PU = PUs[0]
504
505                # find whichever has highest priority
506                if PU['priority'] < Drop['priority']:
507                    Detect = Drop
508                else:
509                    Detect = PU
510
511
512                # find the am destination on same day as the Drop
513                rows = plpy.execute("select * from preceding_destinations('%s','%s',%d) where job_type in ('s','S') limit 1" % \
514                                    (tab,link_tab,Drop['seq']))
515                assert len(rows),"*** PU/Drop differing days but no intermediate Home destinations !\n"
516                BefDrop = rows[0] # Home destination in morning before Drop
517
518                # find the pm destination on same day as PU
519                rows = plpy.execute("select * from following_destinations('%s','%s',%d) where job_type in ('e','E') limit 1" % \
520                                    (tab,link_tab,PU['seq']))
521                assert len(rows),"*** PU/Drop differing days but no intermediate Home destinations !\n"
522                AftPU = rows[0] # Home destination in afternoon after PU
523
524                # method is to move PU to same day as drop, or drop to same day as pickup
525               
526
527                # try moving PU to Drop day first
528                # ...
529                rows1 = plpy.execute("select * from preceding_destinations_inclusive('%s','%s',%d)" % \
530                                        (tab,link_tab,Drop['seq']))
531                option_priority = None
532                temp_options = []
533                temp_options_nongridlock = []
534                for i in range(1,len(rows1)):
535                    prev = rows1[i]
536                    next = rows1[i-1]
537
538                    if movement_exclusion_pre(prev,next,PU): continue
539
540                    # now check the priority to move PU to each position on the same day as the Drop
541                    rows = plpy.execute("select * from path_link_reloc_priority('%s','%s',%d,%d,%d)" % \
542                                    (tab,link_tab,prev['seq'],next['seq'],PU['seq']))
543
544                    if  rows[0]['path_link_reloc_priority'] < Detect['priority']:
545                        temp_options_nongridlock.append([prev,next,PU,PU,Detect['priority'],rows[0]['path_link_reloc_priority'],Detect['seq']])
546                    elif option_priority is None : # or option_priority > rows[0]['path_link_reloc_priority']:
547                        option_priority = rows[0]['path_link_reloc_priority']
548                   
549                        temp_options = [[prev,next,PU,PU,Detect['priority'],option_priority,Detect['seq']]]
550                        if debug > 2:
551                            dbg_line = dbg_line + 1
552                            plpy.execute("insert into debug_table values ('%s',%d) " % \
553                                ( " ###  PU/Drop Different Days (move PU) : added option prev=%s,next=%s,dest=%s,cost=%s,priority=%s,detect=%s" % (prev['seq'],next['seq'],PU['seq'],option_priority,Detect['priority'],Detect['seq']),dbg_line))
554
555                       
556                    #elif option_priority == rows[0]['path_link_reloc_priority']:
557                    else:
558                        temp_options.append([prev,next,PU,PU,Detect['priority'],option_priority,Detect['seq']])
559                        if debug > 2:
560                            dbg_line = dbg_line + 1
561                            plpy.execute("insert into debug_table values ('%s',%d) " % \
562                                ( " ###  PU/Drop Different Days (move PU) : added option prev=%s,next=%s,dest=%s,cost=%s,priority=%s,detect=%s" % (prev['seq'],next['seq'],PU['seq'],option_priority,Detect['priority'],Detect['seq']),dbg_line))
563
564                       
565                    if prev['seq'] == BefDrop['seq']:
566                        break  # we have reached the am Home destination
567
568                # indx is candidate index to the cheapest option in rows
569               
570                       
571                # now try moving Drop to PU day
572                rows2 = plpy.execute("select * from following_destinations_inclusive('%s','%s',%d)" % \
573                                        (tab,link_tab,PU['seq']))
574                for i in range(1,len(rows2)):
575                    prev = rows2[i-1]
576                    next = rows2[i]
577
578                    if movement_exclusion_pre(prev,next,Drop): continue
579
580                    # now check the priority to move PU to each position on the same day as the Drop
581                    rows = plpy.execute("select * from path_link_reloc_priority('%s','%s',%d,%d,%d)" % \
582                                    (tab,link_tab,prev['seq'],next['seq'],Drop['seq']))
583
584
585                    if  rows[0]['path_link_reloc_priority'] < Detect['priority']:
586                        temp_options_nongridlock.append([prev,next,Drop,Drop,Detect['priority'],rows[0]['path_link_reloc_priority'],Detect['seq']])
587                    elif option_priority is None : # or option_priority > rows[0]['path_link_reloc_priority']:
588                        option_priority = rows[0]['path_link_reloc_priority']
589
590                        temp_options = [[prev,next,Drop,Drop,Detect['priority'],option_priority,Detect['seq']]]
591                        if debug > 2:
592                            dbg_line = dbg_line + 1
593                            plpy.execute("insert into debug_table values ('%s',%d) " % \
594                                ( " ###  PU/Drop Different Days(move Drop) : added option prev=%s,next=%s,dest=%s,cost=%s,priority=%s,detect=%s" % (prev['seq'],next['seq'],Drop['seq'],option_priority,Detect['priority'],Detect['seq']),dbg_line))
595
596                    #elif option_priority == rows[0]['path_link_reloc_priority']:
597                    else:
598                        temp_options.append([prev,next,Drop,Drop,Detect['priority'],option_priority,Detect['seq']])
599                        if debug > 2:
600                            dbg_line = dbg_line + 1
601                            plpy.execute("insert into debug_table values ('%s',%d) " % \
602                                ( " ###  PU/Drop Different Days(move Drop) : added option prev=%s,next=%s,dest=%s,cost=%s,priority=%s,detect=%s" % (prev['seq'],next['seq'],Drop['seq'],option_priority,Detect['priority'],Detect['seq']),dbg_line))
603
604                    if next['seq'] == AftPU['seq']:
605                        break  # we have reached the pm Home destination
606
607                # indx is candidate index to the cheapest option in rows
608           
609                # select whichever is the cheapest
610                option_costs = option_costs + temp_options+ temp_options_nongridlock
611               
612
613                if debug :
614                    dbg_line = dbg_line+1
615                    plpy.execute("insert into debug_table values ('%s',%d)" % \
616                        ("PU & Drop on different days, prev=%s,next=%s,dest=%s,dest2=%s,priority=%s , cost=%s, detect=%s" % \
617                                (option_costs[-1][0]['seq'],option_costs[-1][1]['seq'],option_costs[-1][2]['seq'],option_costs[-1][3]['seq'],option_costs[-1][4],option_costs[-1][5],option_costs[-1][6]),dbg_line))
618
619            elif flag[1] == '1' and flag[0] != '1': # flag & 0100000 :
620                # drop is too late
621                # ... move the drop earlier or delete a destination before  drop ( .. or rearrange the preceding path ... thats the dilemma )
622
623                PUs = plpy.execute("select * from %s where pu and job = %d" % \
624                                (tab,option['job']))
625                PU = PUs[0] # PU is the corresponding Pickup
626               
627                # now find the destinations between the two ... [PU +1, ... ,Drop-1]
628#               intermediates =  plpy.execute("select * from following_destinations('%s','%s',%d) where seq in (select seq from preceding_destinations('%s','%s',%d))" % \
629#                           (tab,link_tab,PU['seq'],tab,link_tab,option['seq']))
630
631                following = plpy.execute("select * from following_destinations_inclusive('%s','%s',%d)" % \
632                                            (tab,link_tab,option['seq']))
633
634                # destinations before DROP   NB. XXX HERE can optimise this by limiting to destinations whose time is after the scheduled earliest PU time !!!
635                preceding = plpy.execute("select * from preceding_destinations('%s','%s',%d)" % \
636                                (tab,link_tab,option['seq']))
637                option_priority = None
638               
639                if debug > 1:
640                    dbg_line = dbg_line + 1
641                    plpy.execute("insert into debug_table values ('%s',%d)" % \
642                                    ("--- Drop too late, PU = %s, Drop = %s,  # of following = %d, # of intermediates = %d " % (PU['seq'],option['seq'],len(following),len(intermediates))  ,dbg_line))
643
644                temp_options = [] 
645                temp_options_nongridlock = []
646                Drop = option
647                # first consider costs to move the destination to an earlier position in the path
648                for i in  range(1,len(preceding)):
649                    prev = preceding[i]
650                    next = preceding[i-1]
651                    if prev['job_type'] in ('s','S') and next['job_type'] in ('e','E'):
652                        continue # not between end-of-day/start-of-day
653                    if next['seq'] == option['seq']:
654                        # dont move Drop before PU  XXX HERE
655                        Drop = PU # ... switch to moving PU further backwards instead XXX ???
656
657                    if movement_exclusion_pre(prev,next,Drop):
658                        continue
659
660                    rows = plpy.execute("select * from path_link_reloc_priority('%s','%s',%d,%d,%d)" % \
661                                        (tab,link_tab,prev['seq'],next['seq'],Drop['seq']))
662                    if debug > 1:
663                        dbg_line = dbg_line + 1
664                        plpy.execute("insert into debug_table values ('%s',%d)" % \
665                                    ("--- Drop too late, to move Drop %s  to  between %s and %s, cost = %s " % (Drop['seq'],prev['seq'],next['seq'],rows[0]['path_link_reloc_priority'])  ,dbg_line))
666
667                    if rows[0]['path_link_reloc_priority'] < option['priority']:
668                        temp_options_nongridlock.append([prev,next,Drop,Drop,option['priority'],option_priority,option['seq']])
669                    elif option_priority is None : # or option_priority > rows[0]['path_link_reloc_priority'] :
670                        option_priority = rows[0]['path_link_reloc_priority']
671                        # questionable but necessary to enable the look-ahead to function on equal priorities
672                        temp_options = [[prev,next,option,option,option['priority'],option_priority,option['seq']]]
673                    #elif option_priority == rows[0]['path_link_reloc_priority'] :
674                    else:
675                        temp_options.append([prev,next,option,option,option['priority'],option_priority,option['seq']])
676                # XXX HERE .. actually, the following should consider moving inter to all positions, as for dist-too-long case !!! since a sequence change might change the travel times
677                # next consider costs to move a destination in the preceding path to after the drop
678                for inter in preceding:
679                    for j in range(1,len(following)):
680                        jprev1 = following[j-1]
681                        jnext1 = following[j]
682
683
684                        if inter['seq'] == jprev1['seq']: continue
685                        if jprev1['job_type'] in ('e','E') and jnext1['job_type'] in ('s','S'):
686                            continue
687                        if jprev1['job_type'] not in ('p','P'):
688                            continue
689                        if jprev1['job'] == inter['job'] and inter['pu'] == 't':
690                            continue
691
692                        if movement_exclusion_pre(jprev1,jnext1,inter):
693                            continue
694
695                        # priority to move inter(mediate) dest to after Drop
696                        rows = plpy.execute("select * from path_link_reloc_priority('%s','%s',%s,%s,%s)" % \
697                                        (tab,link_tab,jprev1['seq'],jnext1['seq'],inter['seq']))
698
699                        if debug > 2:
700                            dbg_line = dbg_line + 1
701                            plpy.execute("insert into debug_table values ('%s',%d)" % \
702                                    ("--- Drop too late, to move Intermed %s  to  between %s  %s, cost = %s " % (inter['seq'],jprev1['seq'],jnext1['seq'],rows[0]['path_link_reloc_priority'])  ,dbg_line))
703
704                        if rows[0]['path_link_reloc_priority'] < option['priority']:
705                            temp_options_nongridlock.append([jprev1,jnext1,inter,inter,option['priority'],rows[0]['path_link_reloc_priority'],option['seq']])
706                        elif option_priority is None : # or option_priority > rows[0]['path_link_reloc_priority']:
707                            option_priority = rows[0]['path_link_reloc_priority']
708                            temp_options = [[jprev1,jnext1,inter,inter,option['priority'],option_priority,option['seq']]]
709                        #elif option_priority == rows[0]['path_link_reloc_priority']:
710                        else:
711                            temp_options.append([jprev1,jnext1,inter,inter,option['priority'],option_priority,option['seq']])
712
713                        if debug :
714                            dbg_line = dbg_line + 1
715                            plpy.execute("insert into debug_table values ('%s',%d)" % \
716                                    ("--- Drop too late, to move Intermed %s  to  between %s  %s, cost = %s " % (inter['seq'],jprev1['seq'],jnext1['seq'],rows[0]['path_link_reloc_priority'])  ,dbg_line))
717       
718                if debug > 2:
719                    for item in temp_options2:
720                        dbg_line = dbg_line + 1
721                        plpy.execute("insert into debug_table values ('%s',%d)" % \
722                            ("--> temp_option2 ,p=%s, n=%s, d=%s,%s , pr=%s, c=%s, det=%s " % (item[0],item[1],item[2],item[3],item[4],item[5],item[6]), dbg_line))
723       
724                if len(temp_options) +  len(temp_options_nongridlock) < 1:
725#                   continue # skip this
726                    raise "*** Exception : Wot!!!  Excluded all options !!!"  # this option not viable !!! XXX but why ???
727
728                option_costs = option_costs + temp_options + temp_options_nongridlock
729
730                if debug > 1 :
731                    dbg_line = dbg_line+1
732                    plpy.execute("insert into debug_table values ('%s',%d)" % \
733                        ("Drop too late %s" % len(option_costs),dbg_line))
734                    for item in option_costs:
735                        dbg_line = dbg_line + 1
736                        plpy.execute("insert into debug_table values ('%s',%d)" % \
737                            ("-->  Drop too late, added option ,prev=%s, next=%s, dest=%s,dest2=%s , priority=%s, cost=%s, detection seq=%s " % (item[0],item[1],item[2],item[3],item[4],item[5,item[6]]), dbg_line))
738
739            elif flag[2] == '1' and option['pu'] == 't': # flag & 0010000 :
740                # overloaded ..
741                # first make sure we are working with the PU of the job
742                if debug > 1:
743                    dbg_line = dbg_line + 1
744                    plpy.execute("insert into debug_table values ('%s',%d) " % \
745                        ( " ### in Overloaded : option[pu] = %s, option[seq] = %s" % (option['pu'],option['seq']),dbg_line))
746
747                if  option['pu'] != 't':
748                    raise "Exception Error: executing overloaded with a non-PU destination option" # NB. this will be dealt with by anther jobs PU
749                    # NB XXX HERE ... could alternately seek first dest in which current overload occurs ???
750                else:
751                    PU = option
752                    prows = plpy.execute("select * from path_dest where not pu and job = %s" % option['job'])
753                    Drop = prows[0]
754               
755                # find the PUs which precede the PU where the corresponding Drop follows the PU
756                rows = plpy.execute("select * from pu_b4_drop_after('%s','%s',%d) order by time asc" % \
757                                    (tab,link_tab,PU['seq']))
758               
759               
760                # NB. we assume the vehicle can carry all loads one-at-a-time
761                assert len(rows),"Vehicle cannot carry the load, this should not happen !!!"
762
763
764                # ... either move a preceding PU to after Drop or move a following Drop to before PU
765               
766                # find the set of destinations following PU (inclusive)
767                follows =  plpy.execute("select * from following_destinations_inclusive('%s','%s',%d)" % \
768                                            (tab,link_tab,PU['seq']))
769
770               
771                # find the set of destinations preceding PU (inclusive)
772                precedes = plpy.execute("select * from preceding_destinations_inclusive('%s','%s',%d)" % \
773                                            (tab,link_tab,PU['seq']))
774               
775
776                # try moving a preceding PU to after PU (NB. can restrict to before the PUs corresponding Drop XXX HERE)
777                option_priority = None
778                temp_options = []
779                temp_options_nongridlock = []
780                for pre_PU in rows:
781                    if pre_PU['pu'] == 'f':
782                        continue # skip drops
783
784                    for j in range(1,len(follows)):
785                        prev = follows[j-1]
786                        next = follows[j]
787
788                        if prev['job_type'] in ('e','E') and next['jobt_type'] in ('s','S'):
789                            continue # not between end-of-day/start-of-day
790#                       if pre_PU['job'] == prev['job'] and pre_PU is not prev :
791#                           #break
792#                           pre_PU = prev  # XXX HERE ... a succinct switch here ... does it work ???
793#                           #continue # dont move PU to after its corresponding Drop (XXX HERE ... this may ruin completeness !!!)
794                                  # ... may need to consider instead moving the Drop to after Drop, or both together
795
796                        if movement_exclusion_pre(prev,next,pre_PU):
797                            continue
798
799                        rows1 = plpy.execute("select * from path_link_reloc_priority('%s','%s',%d,%d,%d)" % \
800                                    (tab,link_tab,prev['seq'],next['seq'],pre_PU['seq']))
801                        priority1 = rows1[0]['path_link_reloc_priority']
802
803                        if priority1 < PU['priority']:
804                            temp_options_nongridlock.append([prev,next,pre_PU,pre_PU,PU['priority'],option_priority,PU['seq']])
805                        elif option_priority is None : # or option_priority > priority1:
806                            option_priority = priority1
807                            temp_options = [[prev,next,pre_PU,pre_PU,PU['priority'],priority1,PU['seq']]]
808                        #elif option_priority == priority1:
809                        else:
810                            temp_options.append([prev,next,pre_PU,pre_PU,PU['priority'],priority1,PU['seq']])
811
812                # try moving a following Drop to before PU (NB. can restrict to after the following Drops corresponding PU XXX HERE)
813                for post_Drop in rows:
814                    if  post_Drop['pu'] == 't':
815                        continue # skip Pickups
816
817                    for j in range(1,len(precedes)):
818                        prev = precedes[j]
819                        next = precedes[j-1]
820                        if prev['job_type'] in ('s','S'):
821                                continue
822#                       if next['job'] == post_Drop['job']:
823#                               #break
824#                               post_Drop = next # ... see above ... XXX will this work ??? ie. switch to the PU .. to make room for moving Drop in the next action ...
825#                               #continue # don't go beyond the Drops PU !!! XXX
826
827                        if movement_exclusion_pre(prev,next,post_Drop):
828                            continue
829
830                        rows2 = plpy.execute("select * from path_link_reloc_priority('%s','%s',%d,%d,%d)" % \
831                                    (tab,link_tab,prev['seq'],next['seq'],post_Drop['seq']))
832                        priority2 = rows2[0]['path_link_reloc_priority']
833                       
834                        if debug > 2:
835                            dbg_line = dbg_line + 1
836                            plpy.execute("insert into debug_table values ('%s',%d) " % \
837                                ( " ### in Overloaded code block: second loop, prev=%s, next=%s,option_priority=%s, priority2=%s" % (prev['seq'],next['seq'],option_priority,priority2 ),dbg_line))
838
839                        if priority2 < PU['priority']:
840                            temp_options_nongridlock.append([prev,next,post_Drop,post_Drop,PU['priority'],option_priority,PU['seq']])
841                        elif option_priority is None : # or option_priority > priority2:
842                            option_priority = priority2
843                            temp_options = [[prev,next,post_Drop,post_Drop,PU['priority'],option_priority,PU['seq']]]
844                        #elif option_priority == priority2:
845                        else:
846                            temp_options.append([prev,next,post_Drop,post_Drop,PU['priority'],priority2,PU['seq']])
847               
848                # try moving the PU to after a following Drop
849                for post_Drop in rows:   # ie. PU before Drop after set
850                    if post_Drop['pu'] == 't':
851                        continue # skip Pickups
852                    if post_Drop['job_type'] in ('e','E'):
853                        continue # not between end-of-day/start-of-day
854
855                    # find the seq # of destination which follows post_Drop
856                    next = plpy.execute("select * from following_destinations('%s','%s',%s) limit 1" % \
857                            (tab,link_tab,post_Drop['seq']))[0]
858
859                    if debug  > 2:
860                        dbg_line + 1
861                        plpy.execute("insert into debug_table values ('%s',%d) " % \
862                        ( " ### in Overloaded : third loop: prev = %s, next = %s, dest = %s" % (PU['seq'],post_Drop['seq'],next['next_dest']),dbg_line))
863                    # HERE XXX add movement_exclusion_pre( ) call HERE !!!!!!!!!!
864                    if movement_exclusion_pre(PU,post_Drop,next):
865                        continue
866                    rows3 = plpy.execute("select * from path_link_reloc_priority('%s','%s',%s,%s,%s) " % \
867                                (tab,link_tab,post_Drop['seq'],next['seq'],PU['seq']))
868                   
869                    priority3 = rows3[0]['path_link_reloc_priority']
870                    if priority3 < PU['priority']:
871                        temp_options_nongridlock.append([post_Drop,next,PU,PU,PU['priority'],priority3,PU['seq']])
872
873                    elif option_priority is None :#or option_priority > priority3:
874                        option_priority = priority3
875                        temp_options = [[post_Drop,next,PU,PU,PU['priority'],option_priority,PU['seq']]]
876                    #elif option_priority == priority3:
877                    else:
878                        temp_options.append([post_Drop,next,PU,PU,PU['priority'],priority3,PU['seq']])
879                # select the best option
880               
881                option_costs = option_costs+ temp_options + temp_options_nongridlock
882                if len(temp_options) + len(temp_options_nongridlock) <= 0:
883                    continue
884                if debug  :
885                    dbg_line = dbg_line+1
886                    plpy.execute("insert into debug_table values ('%s',%d)" % \
887                        ("Vehicle overloaded, prev=%s,next=%s,dest=%s,dest2=%s,cost=%s, detect = %s" % \
888                                (option_costs[-1][0]['seq'],option_costs[-1][1]['seq'],option_costs[-1][2]['seq'],option_costs[-1][3]['seq'],option_costs[-1][5],option_costs[-1][6]),dbg_line))
889             
890
891
892            elif flag[0] == '1' : # flag & 1000000 :
893                # distance too long
894                # ... select an active link preceding the inconsistency detection point and deactivate it
895                # or, put another way, move one of the destinations in the path prior to detection
896                # .. the alternate position should preferably minimise the travel distance ... but this
897                # may take many further steps to evaluate ...
898
899                # it is only the destinations which precede this destination which can affect distance ...
900                i_precedes = plpy.execute("select * from preceding_destinations('%s','%s',%d) where inconsistency_class('%s','%s',seq,%f) & B'1000000' = B'1000000' limit 1 " % \
901                                            (tab,link_tab,option['seq'],tab,link_tab,max_dist ))
902
903                # further it is only necessary to process the first destination at which distance
904                # is too great !
905                if len(i_precedes):
906                    continue # we skip this since processing will happen at an earlier inconsistent destination
907                             # NB. distance is not as high a priority as the other constraints
908               
909                precedes = plpy.execute("select * from preceding_destinations_inclusive('%s','%s',%d)" % \
910                                            (tab,link_tab,option['seq']))
911               
912                # one of the destinations up to & including option must have its position changed
913                # ... it can be moved to anywhere  but it is preferable that the distances to its
914                # new predecessor/successor be less than original
915                Dest = None
916                # NB. must know which destination is the first in the sequence !!! XXX HERE
917                others = plpy.execute("select * from following_destinations_inclusive('%s','%s',%s)"  % \
918                                        (tab,link_tab,FIRST_SEQ))
919                option_priority = None
920                temp_options = []
921                temp_options_nonblocking = []
922                for dest in precedes:
923                    # find the priority to move dest somewhere else
924
925                    if debug > 2:
926                            dbg_line = dbg_line + 1
927                            plpy.execute("insert into debug_table values ('%s',%d)" % \
928                                    ("--- Dist too long, dest = %s, suburb = %s, priority = %s " % (dest['seq'],dest['suburb'],dest['priority'])  ,dbg_line))
929
930                    if dest['job_type'] in ('s','S','e','E') :
931                        continue  # this is not a movable destination eg. start/end of day marker
932
933                    prows = plpy.execute("select prev_dest from %s where next_dest = %d and active" % \
934                                            (link_tab,dest['seq']))
935                    if len(prows) <= 0:
936                        continue # could be the first node of the path ... but redundant from above !!
937
938                    pre = prows[0]['prev_dest'] # existing active predecessor to dest
939                   
940                    for i in range(1,len(others)):
941                        prev = others[i-1]
942                        next = others[i]
943                       
944                        flg = movement_exclusion_pre(prev,next,dest,pre)
945                        if flg:
946                            if flg == 11:
947                                break # skip rest of inner loop if dest is after PU
948                            continue # skip any known exclusions
949                       
950                        rows = plpy.execute("select * from path_link_reloc_priority('%s','%s',%d,%d,%d)" % \
951                                            (tab,link_tab,prev['seq'],next['seq'],dest['seq']))
952                        mpriority = rows[0]['path_link_reloc_priority'] # priority to move dest to between prev & next
953                        if debug > 2:
954                            dbg_line = dbg_line + 1
955                            plpy.execute("insert into debug_table values ('%s',%d)" % \
956                                    ("--- reloc priority prev=%s nest=%s dest=%s , priority=%f " % (prev['seq'],next['seq'],dest['seq'] ,mpriority)  ,dbg_line))
957                        if mpriority > 8e99:
958                            continue  # ie. infinite priority
959
960
961                        if mpriority < option['priority']:
962                            # collect all which cost less than priority
963                            temp_options_nonblocking.append([prev,next,dest,dest,option['priority'],mpriority,option['seq']])
964
965                        elif option_priority is None : # or option_priority > mpriority :
966                            option_priority = mpriority
967                            temp_options = [[prev,next,dest,dest,option['priority'],option_priority,option['seq']]]
968
969                        #elif option_priority == mpriority  : # NB. collect all that cost less than priority
970                        else:
971                            # where priorities are equal, we can favour moves which appear to
972                            # reduce distance traveled .. eg. if the distance to it from new predecessor
973                            # is less than from the old predecessor
974
975#                           # the proposed new predecessor link
976
977                            # this distance is shorter & priority is the same,  select this instead ...
978                            temp_options.append([prev,next,dest,dest,option['priority'],option_priority,option['seq']])
979               
980                # append the best option
981                option_costs = option_costs+ temp_options + temp_options_nonblocking
982                if debug  :
983                    dbg_line = dbg_line+1
984                    plpy.execute("insert into debug_table values ('%s',%d)" % \
985                            ("^^^ Dist too long: number of option_costs = %d" % (len(option_costs),),dbg_line))
986                    dbg_line = dbg_line + 1
987                if len(temp_options) + len(temp_options_nonblocking) <= 0:
988                    continue
989
990            elif flag[6] == '1' : # flag & 0000001 :
991                # missing successor
992                # ... tis case should no longer apply since changes are applied in sets so the path remains complete
993                raise "Error, missing successor flag detected"
994
995            else:
996                if flag == '0000000': consistent_count = consistent_count + 1  # reset consistent destination count
997                continue  # ie. this node is not inconsistent !!!
998           
999        # at this point we have added an option(s) to the end of option_costs
1000        # ... we now see whether sufficient priority exists to rectify the inconsistency via
1001        # this option
1002        #p,n,d,d2,priority,cost,detect = option_costs[-1]
1003        option_costs.sort(key=lambda x: x[5] - x[4]) # sort on ascending cost - priority x[5] - x[4]
1004       
1005        NS = plpy.execute("select 'now'::timestamp as time")[0]['time']
1006        best = None
1007 
1008        # record inconsistencies before any changes are made
1009        row00 = plpy.execute("select * from totals('%s','%s',%f)" % (tab,link_tab,max_dist))[0]
1010        non_dist_inconsistencies_orig  = row00['non_dist_inconsistencies']
1011 
1012        for ix in range(min(len(option_costs),lahead)): # consider at most lahead options (arbitrary limit)
1013                choice = option_costs[ix]
1014                cst = choice[5]
1015                if cst > 8e99  :
1016                        #continue
1017                        break  # cost is infinite , exit loop
1018                p = choice[0]
1019                n = choice[1]
1020                d = choice[2]
1021                d2 = choice[3]
1022                prty = choice[4]
1023                   
1024                if cst >= prty:
1025                        continue # cost is greater than priority needed to make the change
1026                detect = choice[6]
1027         
1028                # apply the changes
1029                old_prev,old_next,old_links,new_links = apply_changes(p['seq'],n['seq'],d['seq'],None,d2['seq'])
1030                # need to update fields from the p,n,d,d2 nodes
1031                choice[0] = p = plpy.execute("select * from %s where seq = %s" % (tab,p['seq']))[0]
1032                choice[1] = n = plpy.execute("select * from %s where seq = %s" % (tab,n['seq']))[0]
1033                choice[2] = d = plpy.execute("select * from %s where seq = %s" % (tab,d['seq']))[0]
1034                choice[3] = d2 = plpy.execute("select * from %s where seq = %s" % (tab,d2['seq']))[0]
1035         
1036               
1037         
1038                # calculate inconsistencies after change
1039                row0 = plpy.execute("select * from totals('%s','%s',%f)" % (tab,link_tab,max_dist))[0]
1040                td = row0['total_dist']
1041                tt = row0['total_time']
1042                ip = row0['inconsistent_priority']
1043                cp = row0['consistent_priority']
1044                i = row0['inconsistencies']
1045                c = row0['consistencies']
1046                non_dist_inconsistencies = row0['non_dist_inconsistencies']
1047         
1048                if debug > 1:
1049                        dbg_line = dbg_line + 1
1050                        plpy.execute("insert into debug_table values ('%s',%d)" % \
1051                            ("--- In Move wout Gridlock, Considering option prev=%s,next=%s,dest=%s,dest2=%s,priority=%s,cost=%s,non_dist inconsistencies=%s, inconsistencies=%s,total_dist=%s,inconsistent_priority=%s" % (p['seq'],n['seq'],d['seq'],d2['seq'],prty,cst,non_dist_inconsistencies,i,td,ip),dbg_line))
1052               
1053                # test if applied move violates any known exclusions
1054                #  ... multi-dest moves permit excluding all options which introduce non-distance inconsistencies
1055                #escape =  non_dist_inconsistencies > non_dist_inconsistencies_orig or movement_exclusion_post(p,n,d,d2)
1056                escape =  movement_exclusion_post(p,n,d,d2)
1057         
1058                if search_dist > 0 and non_dist_inconsistencies == 0:
1059                        # ... record best result with no inconsistencies other than distance ...
1060                        if td < best_dist:
1061                            best_dist = td
1062                            best_dist_path = plpy.execute("select * from %s where active" % (link_tab,))
1063         
1064                apply_changes(old_prev,old_next,d['seq'],None,d2['seq']) # return to initial position before the next test
1065             
1066                if escape:
1067                    continue # skip excluded options
1068         
1069                if best is None :
1070                    best = (td,tt,ip,cp,i,c,choice,ix)
1071                else:
1072                    # compare inconsistencies after change to previous best
1073                    if i < best[4]:
1074                        # fewer inconsistencies
1075                        best = (td,tt,ip,cp,i,c,choice,ix)
1076                        continue
1077                    elif i == best[4] :
1078                        # same inconsistencies, consider total distance
1079                        if td < best[0]:
1080                            best = (td,tt,ip,cp,i,c,choice,ix)
1081                            continue
1082                        elif td < (best[0] + 0.1):
1083                            # same total distance, consider total inconsistent priority
1084                            if ip < best[2]:
1085                                best = (td,tt,ip,cp,i,c,choice,ix)
1086                                continue
1087                if  i < 0.01 :
1088                    break   # solution found
1089        if best is not None:
1090                chosen = option_costs[best[-1]]
1091                priority = chosen[4]
1092                cost = chosen[5]
1093                p = chosen[0]
1094                n = chosen[1]
1095                d = chosen[2]
1096                d2 = chosen[3]
1097                if priority > cost :
1098                    gridlocked = 0
1099                    # the priority is sufficient to outweigh the cost
1100                    # the change is activated
1101                    # ...
1102                    apply_changes(p['seq'],n['seq'],d['seq'],priority,d2['seq']) # HERE
1103         
1104                    # deactivate the existing links and adjust priority
1105                   
1106                    if debug :
1107                        dbg_line = dbg_line + 1
1108         
1109                        # calculate inconsistencies after change
1110                        row0 = plpy.execute("select * from totals('%s','%s',%f)" % (tab,link_tab,max_dist))[0]
1111                        td = row0['total_dist']
1112                        tt = row0['total_time']
1113                        ip = row0['inconsistent_priority']
1114                        cp = row0['consistent_priority']
1115                        i = row0['inconsistencies']
1116                        c = row0['consistencies']
1117                        non_dist_inconsistencies = row0['non_dist_inconsistencies']
1118                        plpy.execute("insert into debug_table values ('%s',%d)" % \
1119                            ("--- In Move wout Gridlock, Chosen IS option prev=%s,next=%s,dest=%s,dest2=%s,priority=%s,cost=%s,non_dist inconsistencies=%s, inconsistencies=%s,total_dist=%s,inconsistent_priority=%s" % (p['seq'],n['seq'],d['seq'],d2['seq'],priority,cost,non_dist_inconsistencies,i,td,ip),dbg_line))
1120         
1121                    if ordered <> 0:
1122                        # need to dispose of old options after a change is made
1123                        option_costs = []
1124                        best = None
1125                    else:
1126                        continue  # exit the loop after the first successful change .. maybe not ??? XXX HERE
1127        non_gridlock_time = plpy.execute("select ('now'::timestamp - '%s'::timestamp) + '%s'::interval as time" % \
1128                        (NS,non_gridlock_time))[0]['time']
1129       
1130       
1131        GS = plpy.execute("select 'now'::timestamp as time")[0]['time']
1132        if gridlocked and ( (ordered == 0) or consistent_count < num_dests):
1133            # means insufficient priority exists to remedy any of the existing inconsistencies
1134            # ... must choose an inconsistent destination & increase its priority
1135
1136            # basically need to select one of the inconsistent destinations & increase its priority
1137            # to exceed the cost of remedy ..
1138            # ... which one to pick is arbitrary, but will select the one whose remedy cost is least
1139
1140            # remedy cost is last field in the option_costs list for each option
1141            # ... can also do analysis of which introduces the least additional inconsistencies ...
1142            option_costs.sort(key=lambda x: x[-2])
1143            choice = option_costs[0]  # cheapest cost option
1144            cost = choice[-2]
1145             
1146            if cost > 8e99 :
1147                return [] # failed to find a solution satisfying all constraints
1148            # once again, this is arbitrary ...
1149            # .. we implement a look-ahead and select the one of these which results in the
1150            # least number of inconsistencies after application
1151            best = None
1152
1153            # record inconsistencies before any changes are made
1154            row00 = plpy.execute("select * from totals('%s','%s',%f)" % (tab,link_tab,max_dist))[0]
1155            non_dist_inconsistencies_orig  = row00['non_dist_inconsistencies']
1156
1157            end_it = 1   # set to 0 to apply strong exclusions unless gridlock occurs
1158            while  end_it < 2:
1159              if end_it <> 0: end_it = 2
1160              for ix in range(0,min(lahead,len(option_costs))): # consider at most 20 options (arbitrary limit)
1161                  choice = option_costs[ix]
1162                  cst = choice[-2]
1163                  if cst > 8e99  or cst >  (cost + 0.01) :
1164                        break  # cost is greater than minimum, or infinite , exit loop
1165                  p = choice[0]
1166                  n = choice[1]
1167                  d = choice[2]
1168                  d2 = choice[3]
1169                  prty = choice[4]
1170                 
1171                  detect = choice[6]
1172
1173                  # apply the changes
1174                  old_prev,old_next,old_links,new_links = apply_changes(p['seq'],n['seq'],d['seq'],None,d2['seq'])
1175                  # need to update fields from the p,n,d,d2 nodes
1176                  choice[0] = p = plpy.execute("select * from %s where seq = %s" % (tab,p['seq']))[0]
1177                  choice[1] = n = plpy.execute("select * from %s where seq = %s" % (tab,n['seq']))[0]
1178                  choice[2] = d = plpy.execute("select * from %s where seq = %s" % (tab,d['seq']))[0]
1179                  choice[3] = d2 = plpy.execute("select * from %s where seq = %s" % (tab,d2['seq']))[0]
1180
1181               
1182
1183                  # calculate inconsistencies after change
1184                  row0 = plpy.execute("select * from totals('%s','%s',%f)" % (tab,link_tab,max_dist))[0]
1185                  td = row0['total_dist']
1186                  tt = row0['total_time']
1187                  ip = row0['inconsistent_priority']
1188                  cp = row0['consistent_priority']
1189                  i = row0['inconsistencies']
1190                  c = row0['consistencies']
1191                  non_dist_inconsistencies = row0['non_dist_inconsistencies']
1192               
1193                  # test if applied move violates any known exclusions
1194                  # ... multi-dest moves permit excluding all options which introduce non-distance inconsistencies
1195                  if end_it < 1: # look for a move which doesn't add non-distance inconsistencies first
1196                      escape = non_dist_inconsistencies > non_dist_inconsistencies_orig or movement_exclusion_post(p,n,d,d2)
1197                  else:
1198                      escape = movement_exclusion_post(p,n,d,d2)
1199
1200
1201                  if search_dist > 0 and non_dist_inconsistencies == 0:
1202                        # ... record best result with no inconsistencies other than distance ...
1203                        if td < best_dist:
1204                            best_dist = td
1205                            best_dist_path = plpy.execute("select * from %s where active" % (link_tab,))
1206
1207                  apply_changes(old_prev,old_next,d['seq'],None,d2['seq']) # return to initial position before the next test
1208               
1209                  if escape:
1210                      continue # skip excluded options
1211                   
1212                  if  debug > 1:
1213                    dbg_line = dbg_line + 1
1214                    plpy.execute("insert into debug_table values ('%s',%s)" % \
1215                            ("# of options = %s ,td=%s, tt=%s, ip = %s, cp = %s, i=%s, c= %s , choice = %s, old_prev = %s, old_next = %s" % (len(option_costs),td,tt,ip,cp,i,c,choice,old_prev,old_next),dbg_line))
1216                   
1217                  if best is None :
1218                    best = (td,tt,ip,cp,i,c,choice,ix)
1219                  else:
1220                    # compare inconsistencies after change to previous best
1221                    if i < best[4]:
1222                        # fewer inconsistencies
1223                        best = (td,tt,ip,cp,i,c,choice,ix)
1224                    elif i == best[4] :
1225                        # same inconsistencies, consider total distance
1226                        if td < best[0]:
1227                            best = (td,tt,ip,cp,i,c,choice,ix)
1228                        elif td < (best[0] + 0.1):
1229                            # same total distance, consider total inconsistent priority
1230                            if ip < best[2]:
1231                                best = (td,tt,ip,cp,i,c,choice,ix)
1232                  if  i < 0.01 :
1233                        break   # solution found
1234
1235            # re-apply the best option
1236            if len(option_costs) <= 0 or best is None:
1237                dbg_line = dbg_line + 1
1238                mainloop_time = plpy.execute("select 'now'::timestamp - '%s'::timestamp as time" % (MS))[0]['time']
1239                plpy.execute("insert into debug_table values ('%s',%d) " % \
1240                        ( " ### Exit Program: Permanent Gridlock,cum exec times: mainloop=%s apply_changes=%s ,gridlock_time=%s,non_gridlock_time=%s" % (mainloop_time,apply_changes_time,gridlock_time,non_gridlock_time ),dbg_line))
1241                end_it = 1 # gridlocked, so must accept some non-distance inconsistencies
1242               
1243 
1244            if debug:
1245                dbg_line = dbg_line + 1
1246                plpy.execute("insert into debug_table values ('%s',%d) " % \
1247                        ( """ *** Re-apply best gridlock escape found!!! : best dist = %s,time=%s,inconcist=%s,consist=%s,inconsist pr=%s,consist pr=%s, best option = %s,%s  """ % (best[0],best[1],best[4],best[5],best[2],best[3],option_costs[best[-1]][2]['seq'],option_costs[best[-1]][3]['seq'] ),dbg_line))
1248           
1249            # re-apply the best option
1250            apply_changes(option_costs[best[-1]][0]['seq'],option_costs[best[-1]][1]['seq'],option_costs[best[-1]][2]['seq'],None,option_costs[best[-1]][3]['seq'])
1251
1252            # fix priorities of the activated/deactivated links
1253            # ... the deactivated path_links are given priority of path_dest node
1254            #       the activated path_links are given priority of path_dest - 1
1255            #           the path_dest is given priority of the cost + 1
1256            # ... in this way the selected link can be overriden again once by the path_dest
1257            # without further priority increase thus enabling all options to be considered
1258           
1259            # increase the priority of the lowest priority deactivated link
1260            cheapest_deactivated_link = min_priority_link(old_links)
1261            plpy.execute("update %s set priority = %s where prev_dest = %s and next_dest = %s" % \
1262                                (link_tab,option_costs[best[-1]][5] + 1,cheapest_deactivated_link['prev_dest'],cheapest_deactivated_link['next_dest']))
1263
1264            if debug:
1265                dbg_line = dbg_line + 1
1266                plpy.execute("insert into debug_table values ('%s',%d)" % \
1267                        ("Gridlock: update %s set priority = %f where prev/dest in (%s/%s,%s/%s,%s/%s)" % \
1268                        (link_tab,option_costs[best[-1]][5]+1,new_links[0]['prev_dest'],new_links[0]['next_dest'],new_links[1]['prev_dest'],new_links[1]['next_dest'],new_links[2]['prev_dest'],new_links[2]['next_dest']) \
1269                                ,dbg_line))
1270                dbg_line = dbg_line + 1
1271                plpy.execute("insert into debug_table values ('%s',%d)" % \
1272                    ("update %s set priority = %f where ( prev_dest = %s and next_dest = %s) or (prev_dest = %s and next_dest = %s) or (prev_dest = %s and next_dest = %s)" % \
1273
1274                        (link_tab,option_costs[best[-1]][5],old_links[0]['prev_dest'],old_links[0]['next_dest'],old_links[1]['prev_dest'],old_links[1]['next_dest'],old_links[2]['prev_dest'],old_links[2]['next_dest']) \
1275                                ,dbg_line))
1276
1277#           # now increase its priority
1278            plpy.execute("update %s set priority = %f where seq = %s" % \
1279                            (tab,option_costs[best[-1]][4] + 1,option_costs[best[-1]][6]))
1280
1281           
1282            if debug:
1283                dbg_line = dbg_line + 1
1284                plpy.execute("insert into debug_table values ('%s',%d)" % \
1285                        ("** Gridlock: Increased priority of seq=%d to %f" % (option_costs[best[-1]][6],option_costs[best[-1]][5]),dbg_line))
1286
1287        gridlock_time = plpy.execute("select ('now'::timestamp - '%s'::timestamp) + '%s'::interval as time" % ( GS,gridlock_time))[0]['time']
1288       
1289        if ordered == 0:
1290            incons = plpy.execute("select * from inconsistent_dests('%s','%s',%f)" % (tab,link_tab,max_dist))
1291   
1292#    if len(incons) > 0 and search_dist > 0:
1293    if (ordered == 0 and len(incons) > 0 and search_dist > 0) or (ordered <> 0 and consistent_count < num_dests and search_dist > 0):
1294        # we have not been able to satisfy all constraints
1295        # ... see if we have been able to satisfy all constraints except distance
1296        if best_dist_path is not None:
1297            # re-instate this solution before returning
1298            plpy.execute("update %s set active = false" % link_tab)
1299            for row in best_dist_path:
1300                plpy.execute("update %s set active = true where prev_dest = %s and next_dest = %s" % \
1301                        (link_tab,row['prev_dest'],row['next_dest']) )
1302            seq_rows = plpy.execute("select seq from %s" % tab)
1303            # do better HERE XXX
1304            for seq in seq_rows:
1305                plpy.execute("select * from dest_changes('%s','%s','%s',%s)" % \
1306                        (tab,link_tab,job_tab,seq['seq']))
1307    dbg_line = dbg_line + 1
1308    mainloop_time = plpy.execute("select 'now'::timestamp - '%s'::timestamp as time" % (MS))[0]['time']
1309    now=plpy.execute("select 'now'::timestamp as time")[0]['time']
1310    plpy.execute("insert into debug_table values ('%s',%d) " % \
1311            ( " ### END of prog, cum exec times: mainloop=%s apply_changes=%s ,gridlock_time=%s,non_gridlock_time=%s" % (mainloop_time,apply_changes_time,gridlock_time,non_gridlock_time ),dbg_line))
1312
1313    # successful, return the ordered destinations list
1314    return plpy.execute("select * from %s order by time asc" % tab)
1315