10 ' CPM-PERT Program from Interface Age, Feb 1981
20 ' Written by Richard Parry
30 ' Adapted to MBASIC by Charles H Strom
40 ' Dressed up & Adapted to Freedom 100 and Decision I 10 Apr 84 [Toad hall]
50 ' Added COUT and LIST location from CP/M and BIOS jump table to alleviate
60 ' problems with reserved locations in Page 0, problems with RST 1 and 3
70 ' (my CP/M didn't have them), having to run any special programs, etc.
80 ' Also changed location in MBASIC of CONOUT routine (different in V5.1).
90 ' David Kirschbaum, Toad Hall
100 ' 7573 Jennings Lane, Fayetteville NC 28303  (919)868-3471/396-6862
110 ' ARPANet ABN.ISCAMS@USC-ISID
120 '
130 '== Locate COUT (conout) and LIST in CP/M BIOS Jump Table ==
140 '
150 ' Location of warm boot in BIOS jump table can be found at bytes 0001
160 ' (Least Significant Byte, LSB), and 0002 (Most Significant Byte, MSB).
170 '
180 COUT1%=PEEK(1) : COUT2%=PEEK(2)	'get LSB and MSB of warm boot jump
190 COUT1%=COUT1%+9	'bump up 9 bytes from warm boot to COUT
200   IF COUT1%<256 THEN 220		'no need to increase MSB
210 COUT2%=COUT2%+256 : COUT1%=COUT1%-256	'inx MSB, dx LSB
220 LST2%=COUT2% : LST1%=COUT1%+3	'bump up 3 bytes from COUT to LIST
230   IF LST1%<256 THEN 280		'no need to increase MSB
240 LST2%=LST2%+256 : LST1%=LST1%-256	'inx MSB, dx LSB
250 '
260 '== Initialize Normal Distribution Constants
270 '
280 RN=15: RS=SQR(3/RN)
290 CONOUT%= &H41B8	'Loc in MBASIC 5.1 of call to CONOUT
300 '			 old value for MBASIC 5.21 was &H41E4
310 CLR$=CHR$(27)+"*"	' Home cursor and clear screen for Freedom 100 terminal
320 '
330 ' == Input Data ==
340 ' 
350 PRINT CLR$;
360 PRINT "CPM or PERT Simulation?  (C/P):  ";
370 Q$=INKEY$:IF LEN(Q$)<1 THEN 370 ELSE PRINT Q$
380   IF Q$<>"C" AND Q$<>"P" THEN PRINT "ERROR!  Try again.":GOTO 360
390 PRINT "Do you want a HARD-COPY record?  (Y/N):  ";
400 HC$=INKEY$:IF LEN(HC$)<1 THEN 400 ELSE PRINT HC$
410   IF HC$<>"Y" AND HC$<>"N" THEN PRINT "ERROR!  Try again.":GOTO 390
420   IF HC$<>"Y" THEN 450
430 PRINT "Note - SETUP.ASM must be loaded before MBASIC"
440 PRINT "or printer will not function!"
450 PRINT:INPUT "Number of Activities:  ",N
460 DIM ML(N), MO(N), MP(N), CP(N), ME(N), SD(N), IC(20)
470 DIM S(N), F(N), D(N), E(N), L(N), F1(N)
480 FOR I=1 TO N
490   PRINT CLR$: PRINT "Activity:  ",I: PRINT
500   GOSUB 2190				'Go to Input Data routine
510 NEXT I
520 PRINT CLR$;
530 PRINT "Would you like to examine or edit the Input Data?  (Y/N):  ";
540 Q1$=INKEY$:IF LEN(Q1$)<1 THEN 540 ELSE PRINT Q1$
550   IF Q1$<>"Y" AND Q1$<>"N" THEN PRINT "ERROR!  Try again.":GOTO 530
560   IF Q1$="N" THEN 860
570 GOSUB 2350		'Sort Input Data
580 '
590 '== Display Input Data ==
600 '
610 IF HC$<>"Y" THEN 640
620 IF HC$<>"Y" THEN 640
630   POKE CONOUT%,LST1%:POKE CONOUT%+1,LST2%
640 PRINT CLR$:IF Q$<>"C" THEN 690
650 PRINT "ACTIVITY #    FROM       TO     DURATION"
660 FOR I = 1 TO N
670   PRINT TAB(5); I; TAB(15); S(I); TAB(25); F(I); TAB(35); D(I)
680 NEXT I:GOTO 740
690 PRINT "ACTIVITY #    FROM       TO        ML       MO        MP"
700 FOR I = 1 TO N
710   PRINT TAB(5); I; TAB(15); S(I); TAB(25); F(I);
720   PRINT TAB(35); ML(I); TAB(45); MO(I); TAB(55); MP(I)
730 NEXT I:PRINT
740 POKE CONOUT%,COUT1%:POKE CONOUT%+1,COUT2%
750 PRINT "Would you like to edit an Activity?  (Y/N):  ";
760 Q1$=INKEY$:IF LEN(Q1$)<1 THEN 760 ELSE PRINT Q1$
770   IF Q1$<>"Y" AND Q1$<>"N" THEN PRINT "ERROR!  Try again.":GOTO 750
780   IF Q1$="N" THEN 860
790 '
800 '== Edit Mode ==
810 '
820 PRINT:INPUT "What activity needs alteration?  (0 to end):  ",I
830   IF I=0 THEN 520
840 GOSUB 2190				'Go to Input Data Routine
850 GOTO 820
860 GOSUB 2350			'Go to Sort Routine
870   IF Q$<>"C" THEN 1210
880 '
890 ' Critical Path Analysis requested.  Perform Critical Path
900 ' Analysis once and display results.
910 '
920 GOSUB 2600
930 C2=0
940 IF HC$<>"Y" THEN 960
950   POKE CONOUT%,LST1%:POKE CONOUT%+1,LST2%
960 PRINT CLR$;"CP Analysis is:"
970 PRINT: PRINT: PRINT "FROM","TO","EST","LFT","FLOAT": PRINT
980 FOR I = 1 TO N:PRINT S(I),F(I),E(S(I)),L(F(I)),F1(I):NEXT I
990 PRINT "The Critical Path Length is: ";PL
1000 PRINT: PRINT "The Critical Path is:":PRINT "FROM","TO": PRINT
1010 FOR I = 1 TO N
1020     IF F1(I) = 0 THEN 1040
1030 NEXT I
1040 PRINT S(I),F(I): C2=C2+1: IF I>N THEN 1080
1050 FOR M= 1 TO N
1060     IF S(M)=F(I) AND F1(M) = 0 THEN I=M: GOTO 1040
1070 NEXT M
1080   IF C1<>C2 THEN PRINT: PRINT "There is more than one Critical Path."
1090 PRINT:POKE CONOUT%,COUT1%:POKE CONOUT%+1,COUT2%
1100 PRINT "Would you like to edit an Activity or stop program?  (E/S):  ";
1110 Q1$=INKEY$:IF LEN(Q1$)<1 THEN 1110 ELSE PRINT Q1$
1120   IF Q1$="E" THEN PRINT:GOTO 640
1130   IF Q1$<>"S" THEN PRINT "ERROR!  Try again.":GOTO 1100
1140 END
1150 '
1160 '== PERT Simulation requested.  Perform Critical Path Analysis the
1170 '   number of times specified.  Store path lengths and increment
1180 '  Activities which appear on Critical Path.  Construct Histogram
1190 '  and display results.
1200 '
1210 FOR I = 1 TO N
1220   ME(I) = (MO(I)+4*ML(I)+MP(I))/6	'Compute Mean of each activity.
1230   SD(I) = (MP(I)-MO(I))/6	'Compute Standard Deviation of each activity.
1240 NEXT I
1250 '== Compute Most Optimistic Path Length ==
1260 DU=0: FOR I=1 TO N: CP(I)=0: E(I)=0: L(I)=0: NEXT I
1270 FOR I = 1 TO N: D(I)=MO(I): NEXT I
1280 GOSUB 2600
1290 BC=PL
1300 '== Compute Most Pessimistic Path Length ==
1310 DU=0: FOR I=1 TO N: CP(I)=0: E(I)=0: L(I)=0: NEXT I
1320 FOR I = 1 TO N: D(I)=MP(I): NEXT I
1330 GOSUB 2600
1340 WC=PL
1350 '== Initialize Key Variables ==
1360 DU=0: FOR I = 1 TO N: CP(I)=0: E(I)=0: L(I)=0: NEXT I
1370 LS=0: HS=0: FOR I=1 TO 20: IC(I)=0: NEXT I
1380 '== Initialize Random Number Generator ==
1390 RANDOMIZE
1400 '== Propose # of Transactions as 20 * # of Activities ==
1410 PRINT "Number of transactions should be >= "; 20*N
1420 INPUT "Number of transactions:  ",NS
1430 PRINT: PRINT "++SIMULATION IN PROGRESS++"
1440 '
1450 '== Construct Histogram ==
1460 '-- Set Appropriate Interval (i.e., Integer >=1) --
1470 LL=INT(BC)
1480   IF WC-BC<=20 THEN IN=1
1490 IN=INT((WC-BC)/20)+1
1500 '
1510 '-- Perform Simulation --
1520 TC=100
1530 FOR K=1 TO NS
1540     IF K=TC THEN PRINT "++ Simulation in Progress ++";TC: TC=TC+100
1550   FOR J=1 TO N
1560     S=0: E(J)=0: L(J)=0
1570       IF ML(J)=0 THEN D(J)=0: GOTO 1600
1580     FOR I=1 TO RN: S=S+2*RND-1: NEXT I
1590     D(J)=ME(J)+SD(J)*S*RS
1600   NEXT J
1610   GOSUB 2600
1620 '-- Find Interval for This Path Length --
1630   I3=(PL-LL)/IN+2
1640     IF I3<1 THEN LS=LS+1: GOTO 1680
1650     IF I3>20 THEN HS=HS+1: GOTO 1680
1660   I3=INT(I3)
1670   IC(I3)=IC(I3)+1
1680 NEXT K
1690 '
1700 '-- Print Frequency Distribution Table --
1710 IF HC$<>"Y" THEN 1730
1720   POKE CONOUT%,LST1%:POKE CONOUT%+1,LST2%
1730 PRINT CLR$;"++FREQUENCY DISTRIBUTION TABLE++": PRINT
1740 PRINT "Most OPTIMISTIC  path length: "; BC
1750 PRINT "Most PESSIMISTIC path length: "; WC
1760 PRINT "Number of transactions LOWER  than histogram range: ";LS
1770 PRINT "Number of transactions HIGHER than histogram range: ";HS: PRINT
1780 PRINT "     INTERVAL      FREQ.      PCT."
1790 I1=LL-IN: I2=LL
1800 FOR M=1 TO 20
1810   PRINT"=>";I1;"<";I2;TAB(20);IC(M);TAB(30);INT(.5+100*IC(M)/NS)
1820   I1=I1+IN: I2=I2+IN
1830 NEXT M
1840 '
1850 '== Print Histogram ==
1860 '-- Compute Histogram Scale Factor --
1870 SC=0: LO=18: J=0: LL=INT(BC)
1880 FOR M=1 TO 20
1890     IF IC(M)>SC THEN SC=IC(M)
1900 NEXT M
1910 SC=50/SC: X$="PATH LENGTH"
1920 PRINT: PRINT: PRINT TAB(24); "++ HISTOGRAM ++": PRINT
1930 PRINT TAB(18);"RELATIVE FREQUENCY OF PATH LENGTHS"
1940 PRINT TAB(LO); "+------------------------------------------------+"
1950 FOR M=1 TO 20
1960   HM=IC(M)*SC
1970   FOR K=1 TO 3
1980     J=J+1: PRINT MID$(X$,J,1);TAB(2);
1990       IF K=2 THEN PRINT ">=";LL-IN;"<";LL;: LL=LL+IN
2000     PRINT TAB(LO);
2010       IF IC(M)=0 THEN PRINT: GOTO 2030
2020     FOR I=1 TO HM: PRINT "*";: NEXT I: PRINT
2030   NEXT K
2040 NEXT M
2050 '
2060 '-- Print Activity Analysis --
2070 PRINT: PRINT
2080 PRINT TAB(10); "+++ CP ACTIVITY ANALYSIS TABLE +++": PRINT
2090 PRINT "ACTIVITY #    FROM      TO     CP FREQ.    PCT."
2100 FOR I=1 TO N
2110   PRINT TAB(5);I;TAB(15);S(I);TAB(25);F(I);
2120   PRINT TAB(35);CP(I);TAB(45);INT(.5+100*CP(I)/NS)
2130 NEXT I
2140 PRINT: PRINT "DUPLICATE critical paths occurred";DU;"times."
2150 GOTO 1090		'Quit??
2160 '
2170 '== Input Data Routine ==
2180 '
2190 INPUT "FROM:  ",S(I)
2200 INPUT "  TO:  ",F(I)
2210   IF F(I)>N THEN PRINT "++End Node # NOT <= # of Activities++":GOTO 2200
2220   IF S(I)>F(I) THEN PRINT "++Start Node MUST be < End Node++":GOTO 2190
2230   IF LEFT$(Q$,1)="C" THEN INPUT "Duration:  ",D(I): GOTO 2310
2240 INPUT "     Most Likely:  ",ML(I)
2250 '-- Check for Dummy Activity --
2260   IF ML(I)=0 THEN MO(I)=0: MP(I)=0: GOTO 2310
2270 INPUT " Most Optimistic:  ", MO(I)
2280   IF MO(I)>ML(I) THEN PRINT "++MO MUST be <= ML++": GOTO 2270
2290 INPUT "Most Pessimistic:  ", MP(I)
2300   IF MP(I)<ML(I) THEN PRINT "++MP MUST be >= ML++": GOTO 2290
2310 RETURN
2320 '
2330 '== Sort Data Using Start Node as Key ==
2340 '
2350 PRINT: PRINT "Sorting in Progress": PRINT
2360 SW=0
2370 FOR I=1 TO N-1
2380   J=I+1
2390     IF S(I)<=S(J) THEN 2470
2400   EX=S(I): S(I)=S(J): S(J)=EX
2410   EX=F(I): F(I)=F(J): F(J)=EX
2420   EX=D(I): D(I)=D(J): D(J)=EX
2430   EX=ML(I): ML(I)=ML(J): ML(J)=EX
2440   EX=MO(I): MO(I)=MO(J): MO(J)=EX
2450   EX=MP(I): MP(I)=MP(J): MP(J)=EX
2460   SW=1
2470 NEXT I
2480   IF SW=1 THEN 2360
2490 RETURN
2500 '
2510 '== The following subroutine is used by both the CPM Analysis
2520 '   and the PERT Simulation Analysis.  While the CPM Analysis
2530 '   calls the routine only once, the Simulation calls the
2540 '   routine the number of times requested by the user.
2550 '   The Earliest, Latest, and Float Times are computed, and
2560 '   from this data the Critical Path Length and Critical Path
2570 '   are calculated.  Duplicate Critical Paths are only counted once.
2580 '
2590 '== Compute Earliest Starting Time ==
2600 C1=0: C2=0: PL=0
2610 FOR I=1 TO N
2620   M1=E(S(I))+D(I)
2630     IF E(F(I))<=M1 THEN E(F(I))=M1
2640 NEXT I
2650 '== Compute Latest Finishing Time ==
2660 L(F(N))=E(F(N))
2670 FOR I=N TO 1 STEP -1
2680   L1=S(I): M2=L(F(I))-D(I)
2690     IF L(L1)>=M2 OR L(L1)=0 THEN L(L1)=M2
2700 NEXT I
2710 '== Compute Float Time ==
2720 FOR I=1 TO N
2730   F1(I)=L(F(I))-E(S(I))-D(I)
2740     IF F1(I)<.0001 THEN F1(I)=0: C1=C1+1
2750 NEXT I
2760 '== Compute Critical Path Length ==
2770 FOR I=1 TO N
2780     IF L(F(I))>PL THEN PL=L(F(I))
2790 NEXT I
2800 '== Compute Critical Path ==
2810 FOR I=1 TO N
2820     IF F1(I)=0 THEN 2840
2830 NEXT I
2840 C2=C2+1: CP(I)=CP(I)+1
2850   IF I>N THEN 2890
2860 FOR M=1 TO N
2870     IF S(M)=F(I) AND F1(M)=0 THEN I=M: GOTO 2840
2880 NEXT M
2890   IF C1<>C2 THEN DU=DU+1
2900 RETURN
