pgRouting

Ticket #86: TSP_rjm.sql

File TSP_rjm.sql, 46.4 kB (added by rodj59, 8 months 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 create type total_type as (
21     total_dist float(4),
22     total_time  interval,
23     inconsistent_priority double precision,
24     consistent_priority   double precision,
25     inconsistencies     float(4),
26     consistencies       int,
27     non_dist_inconsistencies    int
28 );
29
30 create type time_dist_type as (
31     time interval,
32     dist float(4)
33 );
34 create type path_dest_type as(
35     job         int,            -- job number
36     pu          boolean,        -- t if is PU destination, f if is Drop
37     suburb      varchar,       
38     city        varchar,
39     time        timestamp,      -- scheduled reaching of the destination
40     time_bound  timestamp,      -- earliest PU or latest Drop time limit
41     dist        float(4),       -- cumulative distance along path
42     priority    double precision,
43     seq         int ,-- the sequence number in original path
44     load_pcnt   float(4),       -- cumulative load as percentage of capacity
45     job_type    varchar(1)      -- P/H/F/S/E =  point2point/Hourly/Furn/StartofDay/EndofDay
46 );
47 create type inconsistent_path_dest_type as(
48     job         int,            -- job number
49     pu          boolean,        -- t if is PU destination, f if is Drop
50     suburb      varchar,       
51     city        varchar,
52     time        timestamp,      -- scheduled reaching of the destination
53     time_bound  timestamp,      -- earliest PU or latest Drop time limit
54     dist        float(4),       -- cumulative distance along path
55     priority    double precision,
56     seq         int ,-- the sequence number in original path
57     load_pcnt   float(4),       -- cumulative load as percentage of capacity
58     job_type    varchar(1),     -- P/H/F/S/E =  point2point/Hourly/Furn/StartofDay/EndofDay
59     flag        bit(7)          -- bit pattern to indicate type(s) of inconsistency
60 );
61
62 create type path_links_type as(
63     prev_dest   int,            -- preceding path_dest in current path
64     next_dest   int,            -- next path_dest in current path
65     dist        float(4),       -- distance travel between prev and next for job
66     time_int    interval,       -- time to traverse the distance prev to next for the job
67     priority    double precision, -- dynamic priority
68     active      boolean         -- t if link is part of the current path
69 );
70
71 create type path_links_oid  as (-- NB. OID methods are not presently used !!! XXX HERE
72     prev_dest   int,            -- preceding path_dest in current path
73     next_dest   int,            -- next path_dest in current path
74     dist        float(4),       -- distance travel between prev and next for job
75     time_int    interval,       -- time to traverse the distance prev to next for the job
76     priority    double precision, -- dynamic priority
77     active      boolean,                -- t if link is part of the current path
78     oid         int             -- the oid of the DB row (NB. system bug in plpythonu necessitates this)
79 );
80
81
82 create table debug_table (
83     msg         varchar,        -- debug message
84     number      int8            -- sequence number
85 );
86
87 create table path_job (
88     job int primary key,        -- the job number
89     load_pcnt   float(4),       -- the percentage of vehicle capacity onboard
90     direct_dist float(4),       -- direct distance of the job
91     time_int    interval,       -- total/loading time for the job
92     job_type    varchar(1),     -- P/H/F/S/E  .. S = start of day, E = endofday
93     priority    double precision -- priority of the job in search
94 );
95
96 --
97 -- leave out the pre_link/next_link if feeding into Anton's Gaul based TSP
98 --
99 create table path_dest (
100     job         int,            -- job number
101     pu          boolean,        -- t if is PU destination, f if is Drop
102     suburb      varchar,       
103     city        varchar,
104     time        timestamp,      -- scheduled reaching of the destination
105     time_bound  timestamp,      -- earliest PU or latest Drop time limit
106     dist        float(4),       -- cumulative distance along path
107     priority    double precision,
108     seq         int primary key,-- the sequence number in original path
109     load_pcnt   float(4),       -- cumulative load as percentage of capacity
110     job_type    varchar(1)      -- P/H/F/S/E =  point2point/Hourly/Furn/StartofDay/EndofDay
111 );
112 create index path_jindx on path_dest (job);
113
114 --
115 --
116 --
117 create table path_links (
118     prev_dest   int,            -- preceding path_dest in current path
119     next_dest   int,            -- next path_dest in current path
120     dist        float(4),       -- distance travel between prev and next for job
121     time_int    interval,       -- time to traverse the distance prev to next for the job
122     active      boolean,        -- t if link is part of the current path
123     priority    double precision, -- dynamic priority
124     primary key (prev_dest,next_dest)
125 );
126
127
128 -- priority scaled by number of distinct types of inconsistency detected
129 create or replace function priority_value(tab varchar,link_tab varchar,seq int,max_dist float(4),dist float(4))
130     returns float(8) as
131 $$
132     scale=plpy.execute("select inconsistency_scale('%s','%s',%s,%s,%f) * priority as icon from %s where seq = %s" % \
133                 (tab,link_tab,seq,max_dist,dist,tab,seq))[0]
134     return scale * r['priority']
135 $$ language 'plpythonu' volatile strict;
136
137 -- number of distinct types of inconsistency detected on a destination
138 create or replace function inconsistency_scale(tab varchar,link_tab varchar,seq int,max_dist float,dist float)
139     returns float(8) as
140 $$
141     r=plpy.execute("select inconsistency_class('%s','%s',%s,%f) as icon " % \
142                 (tab,link_tab,seq,max_dist))[0]
143     count = r['icon'].count('1')
144     if r['icon'][0] == '1':
145         count = count -1 + (dist / (max_dist * 2))
146     return count
147 $$ language 'plpythonu' volatile strict;
148
149 --
150 -- Find the total distance travelled,total time, total inconsistencies for a particular path
151 -- eg. invoke as "select * from totals('path_links')"
152 create or replace function totals(tab varchar,link_tab varchar,max_dist float)
153     returns total_type as
154 $$
155 declare
156     record1 record;
157     record2 record;
158     record3 record;
159     dist_inconsistencies_rec record;
160     res total_type;
161     tot int;
162 begin
163     -- NB. benign bugfix required HERE XXXX
164     -- ... the distance & time will be wrong for hourly jobs ... but doesn't matter w.r.t.
165     --     strategic choices since static !!!  ... so is it worth fixing ???
166     -- select only non-ceiling ie. positive priorities
167     execute 'select sum(dist) as distance,sum(time_int) as time from '||
168             quote_ident(link_tab)||' where active '
169         into record1;
170     execute 'select count(dist) as cnt ,sum(priority)' ||
171                 ' as priority from '||quote_ident(tab)||
172             ' where consistent('||quote_literal(tab)||
173             ','||quote_literal(link_tab)||',seq,'||max_dist||')'
174         into record2;
175     res.total_dist := record1.distance;
176     res.total_time := record1.time;
177     res.consistencies := record2.cnt ;
178     execute 'select count(*) as tot,sum(priority*inconsistency_scale('||
179             quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||
180             ','||res.total_dist||')) as priority , '
181             ||' sum(inconsistency_scale('||
182             quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||
183             ','||res.total_dist||')) as ics from '||quote_ident(tab)
184         into record3;
185     -- count individual inconsistency types
186     res.inconsistencies := record3.ics;
187     res.consistent_priority := record2.priority;
188     res.inconsistent_priority := record3.priority ;
189
190     execute 'select count(seq) as cnt,sum(inconsistency_scale('||
191                 quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||
192                 ','||res.total_dist||')) as tot from '||quote_ident(tab)||
193                 ' where inconsistency_class('||quote_literal(tab)||
194                     ','||quote_literal(link_tab)||',seq,'||max_dist||
195                     ') = B''1000000'''
196         into dist_inconsistencies_rec;
197
198     execute 'select count(seq) from '||quote_ident(tab)
199         into tot;
200     -- non_dist_inconsistencies excludes inconsistencies which are distance_only
201     res.non_dist_inconsistencies := tot - res.consistencies - dist_inconsistencies_rec.cnt;
202
203     return res;
204 end;
205 $$
206 language 'plpgsql' volatile strict;
207
208
209 --
210 -- classify the type of inconsistency
211 -- binary flag in the following field position ->
212 --      0 = distance
213 --      1 = time  (ie. delivery too late, NB. PU too early never happens !)
214 --      2 = load
215 --      3 = drop before pickup
216 --      4 = PU & Drop on different days
217 --      5 = missing predecessor link
218 --      6 = missing successor link
219 --
220 -- eg 0010000 = load inconsistency only
221 create or replace function inconsistency_class(tab varchar,link_tab varchar,seq int,max_dist float)
222     returns bit(7) as
223 $$
224 declare
225     row1 path_dest;
226     row2 path_dest;
227     flag boolean;
228     count int;
229     ans bit(7);
230 begin
231     ans := B'0000000';
232     execute 'select * from '||quote_ident(tab)||' where seq = '||seq
233         into row1;
234     if row1.dist > max_dist
235     then
236         -- distance inconsistency
237         ans := B'1000000';
238     end if;
239     if row1.job_type in ('e','E')
240     then
241         -- the end-of-day destination
242         if row1.time <> row1.time_bound
243         then
244             ans := ans | B'0100000';
245         end if;
246     end if;
247     if row1.job_type in ('p','P','h','H','f','F')
248     then
249         -- eg. a PU or Drop of a normal job
250         if row1.load_pcnt > 1.0
251         then
252             -- load inconsistency
253             ans := ans | B'0010000';
254         end if;
255         -- find the other destination of the job
256         execute 'select * from '||quote_ident(tab)||' where seq != '||seq||
257                 ' and job = '||row1.job
258             into row2;
259         if row1.pu = 't'
260         then
261             -- a pickup
262             -- .. check for drop after pickup
263             execute 'select time < '''||row1.time||'''::timestamp from '||quote_ident(tab)||
264                         ' where job = '||row1.job||' and pu is false'
265                 into flag;
266             if flag
267             then
268                 -- drop before pickup inconsistency
269                 ans := ans | B'0001000';
270             end if;
271             -- see if there is any Home destination between the two destinations
272             execute 'select count(seq) from '||quote_ident(tab)||' where job_type in (''S'',''s'',''E'',''e'') and time > '''||
273                         row1.time||'''::timestamp and time < '''||row2.time||'''::timestamp'
274                 into count;
275             if count > 0
276                 then
277                     -- PU & drop on different days inconsistency
278                     ans := ans | B'0000100';
279             end if;
280         else
281             -- a drop
282             execute 'select time > '''||row1.time||'''::timestamp from '||quote_ident(tab)||
283                         ' where job = '||row1.job||' and pu is true'
284                 into flag;
285             if flag
286             then
287                 -- drop before pickup inconsistency
288                 ans := ans | B'0001000';
289             end if;
290             execute 'select '''||row1.time||'''::timestamp > '''||row1.time_bound||'''::timestamp'
291                 into flag;
292             if flag
293             then
294                 -- delivery too late inconsistency
295                 ans := ans | B'0100000';
296             end if;
297             -- see if there is any Home destination between the two destinations
298             execute 'select count(seq) from '||quote_ident(tab)||' where job_type in (''S'',''s'',''E'',''e'') and time > '''||
299                         row2.time||'''::timestamp and time < '''||row1.time||'''::timestamp'
300                 into count;
301             if count > 0
302                 then
303                     -- PU & drop on different days inconsistency
304                     ans := ans | B'0000100';
305             end if;
306         end if;
307         -- see if both prev & next destinations are linked to this destination
308         execute 'select count(*) from '||quote_ident(link_tab)||' where ( prev_dest = '||
309                     row1.seq||' ) and active'
310             into count;
311         if count  != 1
312             then
313                 ans := ans | B'0000001';
314         end if;
315         execute 'select count(*) from '||quote_ident(link_tab)||' where ( '||
316                     '  next_dest = '||row1.seq||') and active'
317             into count;
318         if count != 1
319             then
320                 ans := ans | B'0000010';
321         end if;
322     end if;
323     -- no inconsistencies detected
324     return ans;
325 end;
326 $$
327 language 'plpgsql' volatile strict;
328 --
329 -- Test if a destination is consistent.
330 -- eg.  select * from consistent('path_dest',seq_no,max_dist)
331 --
332 create or replace function consistent(tab varchar , link_tab varchar, seq int,max_dist float)
333     returns boolean as
334 $$
335 declare
336     flag int;
337 begin
338     execute 'select * from inconsistency_class('||quote_literal(tab)||','||quote_literal(link_tab)||','||seq||','||max_dist||')'
339         into flag;
340     return flag = 0;
341 end;
342 $$
343 language 'plpgsql' volatile strict;
344
345
346 --
347 -- Find the set of path_links records representing potential predecessor destination
348 -- links for the given destination.
349 -- eg. select * from prev_links('path_links',seq_no,active_flag  't'|'f')
350 --
351 create or replace function prev_links(tab varchar,seqno int,active varchar)
352     returns setof path_links as
353 $$
354 declare
355     row path_links;
356 begin
357     for row in execute 'select * from '||quote_ident(tab)||' where '||
358                 '  active = '||quote_literal(active)||'::bool and next_dest = '|| seqno
359     loop
360         return next row;
361     end loop;
362     return;
363 end;
364 $$
365 language 'plpgsql' volatile strict;
366 create or replace function prev_links_oid(tab varchar,oid int,active varchar)
367     returns setof path_links as
368 $$
369 declare
370     row path_links;
371 begin
372     for row in execute 'select * from '||quote_ident(tab)||' where '||
373                 '  active = '||quote_literal(active)||'::bool and oid = '|| oid
374     loop
375         return next row;
376     end loop;
377     return;
378 end;
379 $$
380 language 'plpgsql' volatile strict;
381 create or replace function prev_links_oid(tab varchar,oid int)
382     returns setof path_links as
383 $$
384 declare
385     row path_links;
386 begin
387     for row in execute 'select * from '||quote_ident(tab)||' where '||
388                 ' oid = '|| oid
389     loop
390         return next row;
391     end loop;
392     return;
393 end;
394 $$
395 language 'plpgsql' volatile strict;
396
397
398
399 -- ditto for successor destinations
400
401 create or replace function next_links(tab varchar,seqno int,active varchar)
402     returns setof path_links as
403 $$
404 declare
405     row path_links;
406 begin
407     for row in execute 'select * from '||quote_ident(tab)||
408                 ' where active = '||quote_literal(active)||'::bool and prev_dest = '|| seqno
409     loop
410         return next row;
411     end loop;
412     return;
413 end;
414 $$
415 language 'plpgsql' volatile strict;
416
417 --
418
419 --
420 -- Find the actual travel/wait time interval for a destination from its predecessor
421 -- given the current active path_links records
422 -- ... takes the time for the previous destination, adds the path_links time & then takes max of
423 -- that & the earliest allowable pickup time ( in the case of a pickup ) or earliest home
424 -- time in the case of the pm_home destination
425 --
426 create or replace function dest_time(tab varchar,link_tab varchar,job_tab varchar,seq int)
427     returns time_dist_type as
428 $$
429 declare
430     rec1 record;
431     row1 record;
432     td   time_dist_type;
433 begin
434     -- compute the timeinterval based on previous active def + path_links interval
435     execute 'select * from '||quote_ident(tab)||' where seq = '||seq
436         into row1;
437
438     if row1.job_type in ('s','S')
439         then
440         -- this is a start-of-day destination, we require a 12-hour minimum gap from previous days close XXX WARNING HERE XXX
441         execute 'select  greatest(''12:00:00''::interval + b.time,'''||row1.time_bound||'''::timestamp) - b.time as time,a.dist as dist from '||
442                 quote_ident(link_tab)||' a,'||quote_ident(tab)||' b where a.active and a.prev_dest = b.seq and a.next_dest = '||
443                 row1.seq
444             into rec1;
445     else
446         if row1.pu or row1.job_type in ('e','E')
447         then
448         -- means we must not return a time less than time-bound
449         execute 'select  greatest(a.time_int + b.time,'''||row1.time_bound||'''::timestamp) - b.time as time,a.dist as dist from '||
450                 quote_ident(link_tab)||' a,'||quote_ident(tab)||' b where a.active and a.prev_dest = b.seq and a.next_dest = '||
451                 row1.seq
452             into rec1;
453         elsif row1.job_type not in ('p','P','s','S') and not row1.pu
454             then
455                 -- means this is the Drop destination of an hourly job (H/F) whose duration & distance is fixed
456                 --    ... and whose direct predecessor is necessarily same jobs PU destination ...
457                 execute 'select direct_dist as dist,time_int as time from '||quote_ident(job_tab)||'   where job = '||row1.job
458                     into rec1;
459         else
460             -- means we return whatever time we get
461             execute 'select  a.time_int  as time,a.dist as dist from '||
462                 quote_ident(link_tab)||' a,'||quote_ident(tab)||' b where a.active and  a.next_dest = '||
463                 row1.seq
464             into rec1;
465         end if;
466     end if;
467
468     td.time := rec1.time;
469     td.dist := rec1.dist;
470    return td;
471 end;
472 $$
473 language 'plpgsql' volatile strict;
474
475
476 --
477 -- Find the actual travel/wait timestamp for each destination
478 -- given the current active path_links records
479 -- ... walks from given seq # forward until no changes are required or
480 -- until the final destination is reached & applies changes as needed.
481 -- NB. seqno should be that of the predecessor to the changed link
482 -- Returns # of changes made.
483 create or replace function dest_changes(tab varchar,link_tab varchar,job_tab varchar,seq_no int)
484     returns int as
485 $$
486 declare
487     cur_dest_rec record;
488     next_links_rec record;
489     next_dest_ts_rec record;
490     next_dest_rec record;
491     next_dest_job_rec record;
492     cur_dest_job_rec record;
493     change_cnt int;
494     final_seqno int;
495     td  time_dist_type;
496     dist float(4);
497     load float(4);
498     seq  int;
499
500 begin
501     -- compute the timeinterval based on previous active def + path_links interval
502
503     -- find the final destination in the path (ie. an 'e' node)
504     execute 'select seq from '||quote_ident(tab)||' where job_type in (''s'',''S'',''e'',''E'')  and seq <> 1 order by time_bound desc limit 1'
505         into final_seqno;
506
507     -- start at the specified seq no
508     seq := seq_no;
509     change_cnt := 0;
510
511     execute 'select time,dist,load_pcnt,job,pu from '||quote_ident(tab)||' where seq = '||seq
512         into cur_dest_rec;
513
514     --  job for current dest
515     execute 'select * from '||quote_ident(job_tab)||' where job = '||cur_dest_rec.job
516         into cur_dest_job_rec;
517
518     dist := cur_dest_rec.dist;
519     load := cur_dest_rec.load_pcnt;
520     while seq <> final_seqno
521         loop
522             -- find active link to next destination
523             execute 'select * from next_links('||quote_literal(link_tab)||','||seq||',''true'')'
524                 into next_links_rec;
525
526             -- fetch travel time/distance to next destination
527             execute 'select * from dest_time('||quote_literal(tab)||','||quote_literal(link_tab)||
528                     ','||quote_literal(job_tab)
529                     ||','||next_links_rec.next_dest||')'
530                 into td;
531            
532             -- calculate cumulative distance/time
533             dist := dist + td.dist;
534
535             -- compute timestamp to reach next destination
536             execute 'select '''||cur_dest_rec.time||'''::timestamp + '''||td.time||'''::interval as time'
537                 into next_dest_ts_rec;
538        
539             -- path_dest row for next destination
540             execute 'select * from '||quote_ident(tab)||' where seq = '||next_links_rec.next_dest
541                 into next_dest_rec;
542
543             -- compute load at next dest
544
545             --  .. job for next dest
546             execute 'select * from '||quote_ident(job_tab)||' where job = '||next_dest_rec.job
547                 into next_dest_job_rec; 
548
549             if not cur_dest_rec.pu
550                 then
551                     -- if current dest is Drop, subtract load_pcnt
552                     load := load - cur_dest_job_rec.load_pcnt;
553             end if;
554             if next_dest_rec.pu
555                 then
556                     -- if next dest is PU, add load_pcnt
557                     load := load + next_dest_job_rec.load_pcnt;
558             end if;
559                    
560
561             if  (-0.01 > (dist - next_dest_rec.dist) or ( dist - next_dest_rec.dist) > 0.01 ) or (next_dest_ts_rec.time != next_dest_rec.time) or ( -0.01 > (next_dest_rec.load_pcnt - load) or (next_dest_rec.load_pcnt - load) > 0.01 )
562                 then
563                     -- we need to propagate down the path
564                     change_cnt := change_cnt + 1; -- increment # of changes tally
565                     -- apply the changes
566                     execute 'update '||quote_ident(tab)||' set time = '''||next_dest_ts_rec.time||
567                             '''::timestamp,dist = '||dist||',load_pcnt = '||load||' where seq = '||next_links_rec.next_dest ;
568
569                     seq := next_links_rec.next_dest;
570                     execute 'select time,dist,pu,job from '||quote_ident(tab)||' where seq = '||seq
571                         into cur_dest_rec;
572                     --  job for next current dest
573                     execute 'select * from '||quote_ident(job_tab)||' where job = '||cur_dest_rec.job
574                         into cur_dest_job_rec;
575             else
576                 -- no change, so halt the propagation
577                 return change_cnt;
578             end if;
579         end loop;
580    return change_cnt;
581 end;
582 $$
583 language 'plpgsql' volatile strict;
584
585
586 --
587 -- Find the set of destinations which precede another in the current path
588 --
589 create or replace function preceding_destinations(tab varchar,link_tab varchar, seq int)
590     returns setof path_dest as
591 $$
592 declare
593     row1 record;
594     row2 path_dest;
595     sno int;
596     row_cnt int;
597 begin
598     sno := seq;
599     if seq = 1
600         then
601             return;
602     end if;
603     loop
604         execute 'select * from prev_links('||quote_literal(link_tab)||','||sno||',''t'')'
605             into row1;
606         get diagnostics row_cnt = ROW_COUNT;
607         exit when row_cnt <= 0; -- this path segment must be detached !!
608         -- follow the active links backwards until first node is reached
609         execute 'select * from '||quote_ident(tab)||' where seq = ' ||row1.prev_dest
610             into row2;
611         return next row2;
612         exit when row2.seq = 1;
613         sno := row1.prev_dest;
614     end loop;
615     return;
616 end;
617 $$
618 language 'plpgsql' volatile strict;
619 create or replace function preceding_destinations_inclusive(tab varchar,link_tab varchar,seq int)
620     returns setof path_dest as
621 $$
622 declare
623     row path_dest;
624 begin
625     -- return the seq destination first
626     execute 'select * from '||quote_ident(tab)||' where seq = '||seq
627         into row;
628     return next row;
629     for row in execute 'select * from preceding_destinations('||quote_literal(tab)||','||
630                     quote_literal(link_tab)||','||seq||')'
631     loop
632         return next row;
633     end loop;
634     return;
635
636 end;
637 $$
638 language 'plpgsql' volatile strict;
639
640
641
642 -- Find the set of destinations which follow another in the current path(inclusive)
643 --
644 create or replace function following_destinations(tab varchar,link_tab varchar, seq int)
645     returns setof path_dest as
646 $$
647 declare
648     row1 record;
649     row2 path_dest;
650     sno int;
651     max_seq int;
652     row_cnt int;
653 begin
654     -- find highest seq no in destinations set
655     execute 'select max(seq) from '||quote_ident(tab)||' where job = 0'
656         into max_seq;
657     sno := seq;
658     if seq = max_seq
659         then
660             return;
661     end if;
662     loop
663         execute 'select * from next_links('||quote_literal(link_tab)||','||sno||',''t'')'
664             into row1;
665         get diagnostics row_cnt = ROW_COUNT;
666         exit when row_cnt <= 0;  -- this part of the path must be detached !
667         execute 'select * from '||quote_ident(tab)||' where seq = '||row1.next_dest
668             into row2;
669         return next row2;
670         exit when row2.seq = max_seq ;
671         sno := row1.next_dest;
672     end loop;
673     return;
674 end;
675 $$
676 language 'plpgsql' volatile strict;
677
678 create or replace function following_destinations_inclusive(tab varchar,link_tab varchar,seq int)
679     returns setof path_dest as
680 $$
681 declare
682     row path_dest;
683 begin
684     -- return the seq destination first
685     execute 'select * from '||quote_ident(tab)||' where seq = '||seq
686         into row;
687     return next row;
688     for row in execute 'select * from following_destinations('||quote_literal(tab)||','||
689                     quote_literal(link_tab)||','||seq||')'
690     loop
691         return next row;
692     end loop;
693     return;
694
695 end;
696 $$
697 language 'plpgsql' volatile strict;
698
699
700
701 -- Find the set of destinations without predecessor in the current path (excluding first & last !)
702 --
703 create or replace function dests_without_pred(tab varchar,link_tab varchar)
704     returns setof path_dest as
705 $$
706 declare
707     seq int;
708     row path_dest;
709 begin
710     for seq in execute 'select next_dest from '||quote_ident(link_tab)||' where not active except select next_dest from '||
711             quote_ident(link_tab)||' where active'
712
713         loop
714             execute 'select * from '||quote_ident(tab)||' where seq = '||seq
715                 into row;
716             return next row;
717     end loop;
718     return;
719 end;
720 $$
721 language 'plpgsql' volatile strict;
722
723 -- Find the set of destinations without successor in the current path
724 --
725 create or replace function dests_without_succ(tab varchar,link_tab varchar)
726     returns setof path_dest as
727 $$
728 declare
729     seq int;
730     row path_dest;
731 begin
732     for seq in execute 'select prev_dest from '||quote_ident(link_tab)||' where not active except select prev_dest from '||
733             quote_ident(link_tab)||' where active'
734
735         loop
736             execute 'select * from '||quote_ident(tab)||' where seq = '||seq
737                 into row;
738             return next row;
739     end loop;
740     return;
741
742 end;
743 $$
744 language 'plpgsql' volatile strict;
745
746
747
748 --
749 --
750 -- Given an inconsistent load destination node, find the set of destinations where the
751 -- PU destinations precede it & the Drop destinations follow it.
752 -- ... returns the set of path_dest nodes
753 --
754 create or replace function pu_b4_drop_after(tab varchar,link_tab varchar,seq int)
755     returns setof path_dest as
756 $$
757 declare
758     row1 path_dest;
759     dest path_dest;
760 begin
761     for row1 in execute 'select * from preceding_destinations('||quote_literal(tab)||','||quote_literal(link_tab)||
762                 ','||seq||')  where pu'
763         loop
764             for dest in execute 'select * from following_destinations('||quote_literal(tab)||','||quote_literal(link_tab)||
765                 ','||seq||') where not pu and job = '||row1.job
766                 loop
767                     return next row1;
768                     return next dest;
769                     -- exit loop;  -- should only be one !!! .. unless multi-drops permitted
770             end loop;
771     end loop;
772     return;
773 end;
774 $$
775 language 'plpgsql' volatile strict;
776
777 --
778 -- Find the set of inconsistent destinations
779 --
780 create or replace function inconsistent_dests(tab varchar,link_tab varchar,max_dist float)
781     returns setof inconsistent_path_dest_type as
782 $$
783 declare
784     row1 record;
785     row2 inconsistent_path_dest_type;
786 begin
787     for row1 in execute 'select *,inconsistency_class('||quote_literal(tab)||','||
788                 quote_literal(link_tab)||',seq,'||max_dist||') as flag from '||quote_ident(tab)||
789                 '  where not consistent('||
790                 quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||') order by priority desc'
791     loop
792         row2.job := row1.job;
793         row2.pu := row1.pu;
794         row2.suburb := row1.suburb;
795         row2.city := row1.city;
796         row2.time := row1.time;
797         row2.time_bound := row1.time_bound;
798         row2.dist := row1.dist;
799         row2.priority := row1.priority;
800         row2.seq := row1.seq;
801         row2.load_pcnt := row1.load_pcnt;
802         row2.job_type := row1.job_type;
803         row2.flag := row1.flag;
804         return next row2;
805     end loop;
806     return;
807 end;
808 $$
809 language 'plpgsql' volatile strict;
810
811 --
812 -- Find the set of consistent destinations
813 --
814 create or replace function consistent_dests(tab varchar,link_tab varchar,max_dist float)
815     returns setof path_dest as
816 $$
817 declare
818     row1 path_dest;
819 begin
820     for row1 in execute 'select * from '||quote_ident(tab)||' where consistent('||
821                 quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||') order by priority asc'
822     loop
823         return next row1;
824     end loop;
825     return;
826 end;
827 $$
828 language 'plpgsql' volatile strict;
829
830
831 --  DON'T USE THESE FUNCTIONS ANYMORE .... PRIORITY IS DIRECTLY RECORDED ON THE path_link RECORD
832 --      Priority of a path_link is the priority of the path_dest it is presently the predecessor of
833 -- if it is active.
834 --      If the link is inactive, its priority is negative of path_dest.
835 -- To activate an inactive link, the sum of its priority & the path_dest seeking it must exceed 0 !
836 --
837 create or replace function path_link_priority(tab varchar,link_tab varchar,prev_dest int,next_dest int)
838     returns double precision as
839 $$
840 declare
841     link path_links;
842     dest path_dest;
843 begin
844     execute 'select * from '||quote_ident(link_tab)||' where prev_dest = '||prev_dest||
845                 ' and next_dest = '||next_dest
846         into link;
847     execute 'select * from '||quote_ident(tab)||' where seq = '||next_dest
848             into dest;
849 --    if dest.job == 0 or dest.pu is None:  # eg. this is an end-of-day job
850 --      return - dest.priority  # eg. end-of-day has no resistance priority
851     if link.active != true
852         then
853             return - dest.priority; -- eg. negative of the destinations existing priority
854     else
855         return  dest.priority;
856     end if;
857 end;
858 $$
859 language 'plpgsql' volatile strict;
860
861 create or replace function path_link_priority(tab varchar,next_dest int,active boolean)
862     returns double precision as
863 $$
864 declare
865     dest path_dest;
866 begin
867     execute 'select * from '||quote_ident(tab)||' where seq = '||next_dest
868             into dest;
869 --    if dest.job == 0 or dest.pu is None:  # eg. this is an end-of-day job
870 --      return - dest.priority  # eg. end-of-day has no resistance priority
871     if active != true
872         then
873             return - dest.priority; -- eg. negative of the destinations existing priority
874     else
875         return  dest.priority;
876     end if;
877 end;
878 $$
879 language 'plpgsql' volatile strict;
880
881 --
882 -- This variant calculates the priority of moving a destination to a different position
883 -- in the active path.
884 -- .. prev_dest,next_dest = seq #'s of destinations between which to insert
885 --    dest = seq # of destination to be inserted
886 create or replace function path_link_reloc_priority(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest int)
887     returns double precision as
888 $$
889 declare
890     rec1 record;
891     rec2 record;
892     priority float(8);
893     priority2 float(8);
894     cnt int;
895 begin
896     execute 'select count(*) from '||quote_ident(tab)||' a, '||quote_ident(tab)||
897                 ' b where a.job = b.job and a.job_type not in (''p'',''P'',''e'',''E'')'
898                 ||' and a.seq = '||prev_dest||' and b.seq = '||next_dest
899             into cnt;
900
901     if cnt > 0
902         then
903             -- NB. can omit this exit test iff no alternate path_links are created  for such jobs !!!
904             return 9e99; -- ie. cannot splice anything between PU/Drop of hourly job !!!
905     end if;
906
907     -- find the priority of the three active links which need to be deactivated (if active)
908     execute 'select max(priority)  as priority,count(*) as count from path_link_deactivation_triple('||
909                 quote_literal(tab)||','||quote_literal(link_tab)||','||prev_dest|| ','||next_dest||','||dest||')'
910         into rec1;
911     execute 'select max(priority) as priority,count(*) as count from path_link_activation_triple('||
912                 quote_literal(tab)||','||quote_literal(link_tab)||','||prev_dest||','||next_dest||','||dest||')'
913         into rec2;
914    
915     if rec1.count < 3
916         then
917         return 9e99;  -- ie. infinite priority
918     else
919         priority = rec1.priority;
920     end if;
921     if rec2.count < 3
922         then
923         return 9e99;  -- ie. infinite priority
924     else
925         priority2 = rec2.priority;
926     end if;
927 --    return rec1.priority + rec2.priority;
928     if priority2 > priority
929     then
930         -- ie. the priority to activate an inactive link is the greater obstacle
931         return priority2;
932     else
933         return priority;
934     end if;
935 end;
936 $$
937 language 'plpgsql' volatile strict;
938 create or replace function path_link_reloc_priority(tab varchar,link_tab varchar,next_dest int,dest int)
939     returns double precision as
940 $$
941 declare
942     priority float(8);
943     prev int;
944 begin
945     -- find the priority of the three active links which need to be deactivated (if active)
946     execute 'select prev_dest from path_links where active and next_dest = '||next_dest||' limit 1'
947         into prev;
948
949     execute 'select * from path_link_reloc_priority('||quote_literal(tab)||','
950         || quote_literal(link_tab)||','||prev|| ','||next_dest||','||dest||')'
951         into priority;
952     return priority;
953 end;
954 $$
955 language 'plpgsql' volatile strict;
956
957 -- This variant is for moving complete path segments bounded by two destinations to a new location
958 -- NB. this could replace the single destination version by simply feeding it the same destination twice !!! XXX
959 create or replace function path_link_reloc_priority_2(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest int,dest2 int)
960     returns double precision as
961 $$
962 declare
963     rec1 record;
964     rec2 record;
965     priority float(8);
966     priority2 float(8);
967     cnt int;
968 begin
969     execute 'select count(*) from '||quote_ident(tab)||' a, '||quote_ident(tab)||
970                 ' b where a.job = b.job and a.job_type not in (''p'',''P'',''e'',''E'')'
971                 ||' and a.seq = '||prev_dest||' and b.seq = '||next_dest
972             into cnt;
973
974     if cnt > 0
975         then
976             -- NB. can omit this exit test iff no alternate path_links are created  for such jobs !!!
977             return 9e99; -- ie. cannot splice anything between PU/Drop of hourly job !!!
978     end if;
979
980     -- find the priority of the three active links which need to be deactivated (if active)
981     execute 'select max(priority)  as priority,count(*) as count from path_link_deactivation_triple('||
982                 quote_literal(tab)||','||quote_literal(link_tab)||','||prev_dest|| ','||next_dest||','||dest||
983                 ','||dest2||')'
984         into rec1;
985     execute 'select max(priority) as priority,count(*) as count from path_link_activation_triple('||
986                 quote_literal(tab)||','||quote_literal(link_tab)||','||prev_dest||','||next_dest||','||dest||
987                 ','||dest2||')'
988         into rec2;
989    
990     if rec1.count < 3
991         then
992         return 9e99;  -- ie. infinite priority
993     else
994         priority = rec1.priority;
995     end if;
996     if rec2.count < 3
997         then
998         return 9e99;  -- ie. infinite priority
999     else
1000         priority2 = rec2.priority;
1001     end if;
1002 --    return rec1.priority + rec2.priority;
1003     if priority2 > priority
1004     then
1005         -- ie. the priority to activate an inactive link is the greater obstacle
1006         return priority2;
1007     else
1008         return priority;
1009     end if;
1010 end;
1011 $$
1012 language 'plpgsql' volatile strict;
1013 create or replace function path_link_reloc_priority_2(tab varchar,link_tab varchar,next_dest int,dest int,dest2 int)
1014     returns double precision as
1015 $$
1016 declare
1017     priority float(8);
1018     prev int;
1019 begin
1020     -- find the priority of the three active links which need to be deactivated (if active)
1021     execute 'select prev_dest from path_links where active and next_dest = '||next_dest||' limit 1'
1022         into prev;
1023
1024     execute 'select * from path_link_reloc_priority_2('|| quote_literal(tab)||','||
1025         quote_literal(link_tab)||','||prev|| ','||next_dest||','||dest||
1026         ','||dest2||')'
1027         into priority;
1028     return priority;
1029 end;
1030 $$
1031 language 'plpgsql' volatile strict;
1032
1033
1034
1035 --
1036 -- Find the pair of active path_links records which need to be deactivated to allow the specified
1037 -- path_links record to be activated.
1038 -- NB. this set may contain only one or even be empty !
1039 --
1040 create or replace function path_link_deactivation_pair(link_tab varchar,prev_dest int, next_dest int)
1041     returns setof path_links as
1042 $$
1043 declare
1044     link path_links;
1045 begin
1046     for link in execute 'select * from '||quote_ident(link_tab)||' where  active and  ( prev_dest = '||prev_dest||
1047             ' or next_dest = '||next_dest||')'
1048     loop
1049         return next link;
1050     end loop;
1051     return;
1052 end;
1053 $$
1054 language 'plpgsql' volatile strict;
1055
1056 --
1057 -- Find the three links which need to be deactivated
1058 -- to remove a destination from its current location & splice it in at a new
1059 -- position in the active path indicated by the specified path_link.
1060 --
1061 -- dest = seq no of destination to be moved
1062 -- prev_dest,next_dest = seq no's of destinations in-between which to insert dest
1063 create or replace function path_link_deactivation_triple(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest int)
1064     returns setof path_links as
1065 $$
1066 declare
1067     link path_links;
1068     rec1 record;
1069     rec2 record;
1070     row_count1 int;
1071     row_count2 int;
1072 begin
1073     -- see if the job is hourly or p2p
1074     execute 'select * from '||quote_ident(tab)||' where seq = '||dest
1075         into rec1;
1076     get diagnostics row_count1 = ROW_COUNT;
1077     if row_count1 < 1
1078         then
1079             return;
1080     end if;
1081     if rec1.job_type = 'p' or rec1.job_type  = 'P'
1082     then
1083    
1084         for link in execute 'select * from '||quote_ident(link_tab)||' where active and ( prev_dest = '||
1085                 dest||' or next_dest = '||dest||' or ( prev_dest = '||prev_dest||' and next_dest = '||
1086                     next_dest||' ) )'
1087         loop
1088             return next link;
1089         end loop;
1090     else
1091         if rec1.job_type != 'f' and rec1.job_type != 'F' and rec1.job_type != 'h' and rec1.job_type != 'H'
1092             then
1093                 -- should not be called with any other job types
1094                 return ;
1095         end if;
1096         -- the job is a hourly, so must keep link to PU/D
1097         -- .. fetch the other destination of the same job
1098         execute 'select * from '||quote_ident(tab)||' where job = '||rec1.job||' and not (seq = '||
1099                     dest||')'
1100             into rec2;
1101         for link in execute 'select * from '||quote_ident(link_tab)||' where active and (prev_dest = '||
1102                         prev_dest||' and next_dest = '||next_dest||')'
1103         loop
1104        
1105             return next link;
1106         end loop;
1107         if rec2.pu = true
1108         then
1109             -- dest is the drop, so need to get predecessor of rec2 & successor of rec1
1110             for link in execute 'select * from '||quote_ident(link_tab)||' where active and  (next_dest = '||
1111                         rec2.seq||')'
1112             loop
1113                 return next link;
1114             end loop;
1115             for link in execute 'select * from '||quote_ident(link_tab)||' where active and (prev_dest = '||
1116                         rec1.seq||')'
1117             loop
1118                 return next link;
1119             end loop;
1120         else
1121             -- dest is the PU, so need to get predecessor or rec1 & successor rec2
1122             -- dest is the drop, so need to get predecessor of rec2 & successor of rec1
1123             for link in execute 'select * from '||quote_ident(link_tab)||' where active and  (next_dest = '||
1124                         rec1.seq||')'
1125             loop
1126                 return next link;
1127             end loop;
1128             for link in execute 'select * from '||quote_ident(link_tab)||' where active and (prev_dest = '||
1129                         rec2.seq||')'
1130             loop
1131                 return next link;