From:	SCOVCB::GALVIN_FX    31-JAN-1989 09:03
To:	ARISIA::EVERHART
Subj:	

package body STATISTICS is

   use UTILITY.REAL_IO, UTILITY.INT_IO; 

   REPORT_OPT : UTILITY.OPTION := UTILITY.ON;
   TRACE_OPT  : UTILITY.OPTION := UTILITY.OFF;


   function COUNT_RELATION (X,Y : COUNT_STATISTIC) return boolean is
      begin
         return false;
      end COUNT_RELATION;

   function TALLY_RELATION (X,Y : TALLY_STATISTIC) return boolean is
      begin
         return false;
      end TALLY_RELATION;

   function ACCUM_RELATION (X,Y : ACCUMULATE_STATISTIC) return boolean is
      begin
         return false;
      end ACCUM_RELATION;

   function HIST_RELATION (X,Y : HISTOGRAM_STATISTIC) return boolean is
      begin
         return false;
      end HIST_RELATION;

   function PLOT_RELATION (X,Y : PLOT_STATISTIC) return boolean is
      begin
         return false;
      end PLOT_RELATION;


   package COUNT_Q is new queues ("COUNT_Q ", COUNT_STATISTIC, COUNT_RELATION);

   package CQ is
      COUNT_STAT_Q : COUNT_Q.QUEUE;
   end CQ;
   use CQ;

   package TALLY_Q is new queues ("TALLY_Q ", TALLY_STATISTIC, TALLY_RELATION);

   package TQ is
      TALLY_STAT_Q : TALLY_Q.QUEUE;
   end TQ;
   use TQ;

   package ACCUM_Q is new queues ("ACCUM_Q ", ACCUMULATE_STATISTIC, 
                                  ACCUM_RELATION);

   package AQ is
      ACCUM_STAT_Q : ACCUM_Q.QUEUE;
   end AQ;
   use AQ;

   package HIST_Q is new queues ("HIST_Q  ", HISTOGRAM_STATISTIC, 
                                 HIST_RELATION);

   package HQ is
      HIST_STAT_Q : HIST_Q.QUEUE;
   end HQ;
   use HQ;

   package PLOT_Q is new queues ("PLOT_Q  ", PLOT_STATISTIC, PLOT_RELATION);

   package PQ is
      PLOT_STAT_Q : PLOT_Q.QUEUE;
   end PQ;
   use PQ;


   procedure SPACE (NO_OF_SPACES : in integer) is
      -- Output a specified no. of spaces on the current line.
      BLANKS : string (1 .. 100) :=
               "                                                  "
             & "                                                  ";
   begin
      case NO_OF_SPACES is
         when 0        => null;
         when 1..100   => put (BLANKS (1 .. NO_OF_SPACES));
         when 101..132 => put (BLANKS);
                          put (BLANKS (1 .. NO_OF_SPACES - 100));
         when others   => raise CONSTRAINT_ERROR;
      end case;
   end SPACE;

   procedure LN_FEED (NO_OF_NEW_LINES : integer) is
      -- Output a specified number of carriage returns and linefeeds.
   begin
      case NO_OF_NEW_LINES is
         when 0      => null;
         when 1..60  => for I in 1 .. NO_OF_NEW_LINES loop
                           new_line;
                        end loop;
         when others => raise CONSTRAINT_ERROR;
      end case;
   end LN_FEED;


   procedure REPORT_STATISTICS is
         COUNT_REF    : COUNT_STATISTIC;
         TALLY_REF    : TALLY_STATISTIC;
         ACCUM_REF    : ACCUMULATE_STATISTIC;
         HIST_REF     : HISTOGRAM_STATISTIC;
         PLOT_REF     : PLOT_STATISTIC;
         LENGTH       : integer;
   begin
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                         SIMULATION.CURRENT_PROCESS_NAME,
                         "REPORT_STATISTICS", UTILITY.LF);
      end if;

      LENGTH := COUNT_Q.LENGTH_OF_QUEUE (COUNT_STAT_Q);
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE ("Count Statistic Queue Length: ", 
                               UTILITY.NO_LF); 
         put (LENGTH); new_line;
      end if;
      if LENGTH > 0 then
         COUNT_Q.FIRST_IN_QUEUE (COUNT_STAT_Q, COUNT_REF);
         REPORT_COUNT (COUNT_REF);
         for i in 2..LENGTH loop
            COUNT_Q.NEXT_IN_QUEUE (COUNT_STAT_Q, COUNT_REF);
            REPORT_COUNT (COUNT_REF);
         end loop;
      end if;

      LENGTH := TALLY_Q.LENGTH_OF_QUEUE (TALLY_STAT_Q);
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE ("Tally Statistic Queue Length: ", 
                               UTILITY.NO_LF); 
         put (LENGTH); new_line;
      end if;
      if LENGTH > 0 then
         TALLY_Q.FIRST_IN_QUEUE (TALLY_STAT_Q, TALLY_REF);
         REPORT_TALLY (TALLY_REF);
         for i in 2..LENGTH loop
            TALLY_Q.NEXT_IN_QUEUE (TALLY_STAT_Q, TALLY_REF);
            REPORT_TALLY (TALLY_REF);
         end loop;
      end if;

      LENGTH := ACCUM_Q.LENGTH_OF_QUEUE (ACCUM_STAT_Q);
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE ("Accumulate Statistic Queue Length: ", 
                               UTILITY.NO_LF); 
         put (LENGTH); new_line;
      end if;
      if LENGTH > 0 then
         ACCUM_Q.FIRST_IN_QUEUE (ACCUM_STAT_Q, ACCUM_REF);
         REPORT_ACCUMULATE (ACCUM_REF);
         for i in 2..LENGTH loop
            ACCUM_Q.NEXT_IN_QUEUE (ACCUM_STAT_Q, ACCUM_REF);
            REPORT_ACCUMULATE (ACCUM_REF);
         end loop;
      end if;

      LENGTH := HIST_Q.LENGTH_OF_QUEUE (HIST_STAT_Q);
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE ("Histogram Statistic Queue Length: ", 
                               UTILITY.NO_LF); 
         put (LENGTH); new_line;
      end if;
      if LENGTH > 0 then
         HIST_Q.FIRST_IN_QUEUE (HIST_STAT_Q, HIST_REF);
         REPORT_HISTOGRAM (HIST_REF);
         for i in 2..LENGTH loop
            HIST_Q.NEXT_IN_QUEUE (HIST_STAT_Q, HIST_REF);
            REPORT_HISTOGRAM (HIST_REF);
         end loop;
      end if;

      LENGTH := PLOT_Q.LENGTH_OF_QUEUE (PLOT_STAT_Q);
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE ("Plot Statistic Queue Length: ", 
                               UTILITY.NO_LF); 
         put (LENGTH); new_line;
      end if;
      if LENGTH > 0 then
         PLOT_Q.FIRST_IN_QUEUE (PLOT_STAT_Q, PLOT_REF);
         REPORT_PLOT (PLOT_REF);
         for i in 2..LENGTH loop
            PLOT_Q.NEXT_IN_QUEUE (PLOT_STAT_Q, PLOT_REF);
            REPORT_PLOT (PLOT_REF);
         end loop;
      end if;

   end REPORT_STATISTICS;


-- Count Operators :
-- -----------------

   procedure CREATE_COUNT (COUNT_REF : in out COUNT_STATISTIC;
                           COUNT_TITLE : in string) is
   begin
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                         SIMULATION.CURRENT_PROCESS_NAME,
                         "CREATE_COUNT" & COUNT_TITLE, UTILITY.LF);
      end if;
      if COUNT_REF /= null then
         raise COUNT_REDECLARATION_ERROR;
      else
         COUNT_REF := new COUNT_REC;
         COUNT_REF.TITLE := UTILITY.STRING_MATCH (COUNT_TITLE, 60);
         COUNT_REF.COUNT := 0;
         COUNT_Q.INSERT_QUEUE (COUNT_STAT_Q, COUNT_REF);
      end if;
   end CREATE_COUNT;


   procedure COLLECT_COUNT (COUNT_REF : in out COUNT_STATISTIC) is
   begin
      if COUNT_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      else
         COUNT_REF.COUNT := COUNT_REF.COUNT + 1;
         if TRACE_OPT = UTILITY.ON then
            UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                            SIMULATION.CURRENT_PROCESS_NAME,
                            "COLLECT_COUNT " & COUNT_REF.TITLE, UTILITY.LF);
            UTILITY.OUTPUT_TRACE ("Current Count: ", UTILITY.NO_LF); 
            put (COUNT_REF.COUNT);
            new_line;
         end if;
      end if;
   end COLLECT_COUNT;


   procedure REPORT_COUNT (COUNT_REF : in COUNT_STATISTIC) is
   begin
      if COUNT_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      end if;

      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                               "REPORT_COUNT " & COUNT_REF.TITLE, UTILITY.LF);
      end if;

      if REPORT_OPT = UTILITY.ON then
         UTILITY.CONTROL_OUTPUT;
         new_line;
         new_line;
         put ("COUNT STATISTIC                             ");
         put ("    simulation time = ");
         put (SIMULATION.CURRENT_SIMULATION_TIME); new_line;
         SPACE (10); put (COUNT_REF.TITLE); new_line;
         SPACE (5); put ("Total Count = "); SPACE (10);
         put (COUNT_REF.COUNT); new_line; 
         new_line;
         UTILITY.RELEASE_OUTPUT_CONTROL;
      end if;
   end REPORT_COUNT;


-- Tally Operators :
-- -----------------

   procedure CREATE_TALLY (TALLY_REF : in out TALLY_STATISTIC;
                           TALLY_TITLE : in string) is
   begin
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                               "CREATE_TALLY " & TALLY_TITLE, UTILITY.LF);
      end if;
      if TALLY_REF /= null then
         raise TALLY_REDECLARATION_ERROR;
      else
         TALLY_REF := new TALLY_REC;
         TALLY_REF.TITLE := UTILITY.STRING_MATCH (TALLY_TITLE, 60);
         TALLY_REF.SUM_TALLIED := 0.0;
         TALLY_REF.SUM_OF_SQUARES := 0.0;
         TALLY_REF.MIN_TALLIED := float'last;
         TALLY_REF.MAX_TALLIED := float'first;
         TALLY_REF.NUMBER_TALLIED := 0;
         TALLY_Q.INSERT_QUEUE (TALLY_STAT_Q, TALLY_REF);
      end if;
   end CREATE_TALLY;


   procedure COLLECT_TALLY (TALLY_REF : in out TALLY_STATISTIC;
                            TALLY_VALUE : in float) is
   begin
      if TALLY_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      end if;

      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                         "COLLECT_TALLY " & TALLY_REF.TITLE, UTILITY.LF);
         UTILITY.OUTPUT_TRACE ("Current Tally Value: ", UTILITY.NO_LF); 
         put (TALLY_VALUE);
         new_line;
      end if;

      TALLY_REF.SUM_TALLIED := TALLY_REF.SUM_TALLIED + TALLY_VALUE;
      TALLY_REF.SUM_OF_SQUARES := TALLY_REF.SUM_OF_SQUARES + TALLY_VALUE ** 2;
      TALLY_REF.NUMBER_TALLIED := TALLY_REF.NUMBER_TALLIED + 1;

      TALLY_REF.MIN_TALLIED := MATH.MIN (TALLY_REF.MIN_TALLIED, TALLY_VALUE);
      TALLY_REF.MAX_TALLIED := MATH.MAX (TALLY_REF.MAX_TALLIED, TALLY_VALUE);
   end COLLECT_TALLY;


   procedure COLLECT_TALLY (TALLY_REF : in out TALLY_STATISTIC;
                            TALLY_VALUE : in integer) is
   begin
      COLLECT_TALLY (TALLY_REF, float (TALLY_VALUE));
   end COLLECT_TALLY;


   procedure REPORT_TALLY (TALLY_REF : in TALLY_STATISTIC) is
      AVG, STD, MIN, MAX : float := 0.0;
   begin
      if TALLY_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      end if;

      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                         "REPORT_TALLY " & TALLY_REF.TITLE, UTILITY.LF);
         UTILITY.OUTPUT_TRACE ("Sum of Tallies: ", UTILITY.NO_LF); 
         put (TALLY_REF.SUM_TALLIED); 
         SPACE (5); put ("Sum of Tallies Squared "); 
         put (TALLY_REF.SUM_OF_SQUARES);
         new_line;
      end if;

      if REPORT_OPT = UTILITY.ON then
         if TALLY_REF.NUMBER_TALLIED > 0 then
            MIN := TALLY_REF.MIN_TALLIED;
            MAX := TALLY_REF.MAX_TALLIED;
            AVG := TALLY_REF.SUM_TALLIED / float(TALLY_REF.NUMBER_TALLIED);
            if TALLY_REF.NUMBER_TALLIED = 1 or else MIN = MAX then
               STD := 0.0;
            elsif TALLY_REF.SUM_OF_SQUARES <=
               TALLY_REF.SUM_TALLIED * TALLY_REF.SUM_TALLIED /
               float (TALLY_REF.NUMBER_TALLIED) then
               STD := 0.0;
            else
-- Note: if all statistics are equal, computer round off can cause neg. sqrt
               STD := MATH.SQRT ((TALLY_REF.SUM_OF_SQUARES - 
                                  TALLY_REF.SUM_TALLIED * TALLY_REF.SUM_TALLIED 
                                / float(TALLY_REF.NUMBER_TALLIED))  
                                / float(TALLY_REF.NUMBER_TALLIED - 1));
            end if;
         end if;

         UTILITY.CONTROL_OUTPUT;
         new_line;
         new_line;
         put ("TALLY STATISTIC                             ");
         put ("    simulation time = ");
         put (SIMULATION.CURRENT_SIMULATION_TIME); new_line;
         SPACE (10); put (TALLY_REF.TITLE); new_line;
         put ("     Average Tallied        "); put (AVG); new_line;
         put ("     Standard Deviation     "); put (STD); new_line;
         put ("     Minimum Tallied        "); put (MIN); new_line;
         put ("     Maximum Tallied        "); put (MAX); new_line;
         put ("     Number of Tallies      "); put (TALLY_REF.NUMBER_TALLIED);
         new_line; new_line;
         UTILITY.RELEASE_OUTPUT_CONTROL;
      end if;
   end REPORT_TALLY;


-- Accumulate Operators :
-- ----------------------

   procedure CREATE_ACCUMULATE (ACCUMULATE_REF : in out ACCUMULATE_STATISTIC;
                                ACCUMULATE_TITLE : in string) is
   begin
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                         "CREATE_ACCUMULATE " & ACCUMULATE_TITLE, UTILITY.LF);
      end if;

      if ACCUMULATE_REF /= null then
         raise ACCUMULATE_REDECLARATION_ERROR;
      end if;

      ACCUMULATE_REF := new ACCUMULATE_REC;
      ACCUMULATE_REF.TITLE := UTILITY.STRING_MATCH (ACCUMULATE_TITLE,60);
      ACCUMULATE_REF.WEIGHTED_SUM := 0.0;
      ACCUMULATE_REF.WEIGHTED_SUM_OF_SQUARES := 0.0;
      ACCUMULATE_REF.MIN_ACCUMULATED := float'last;
      ACCUMULATE_REF.MAX_ACCUMULATED := float'first;
      ACCUMULATE_REF.NUMBER_ACCUMULATED := 0;
      ACCUMULATE_REF.COLLECT_TIME := float'first;
      ACCUMULATE_REF.VALUE := 0.0;
      ACCUM_Q.INSERT_QUEUE (ACCUM_STAT_Q, ACCUMULATE_REF);
   end CREATE_ACCUMULATE;


   procedure COLLECT_ACCUMULATE (ACCUMULATE_REF : in out ACCUMULATE_STATISTIC;
                                 ACCUMULATE_VALUE : in float) is
      WEIGHTED_VALUE : float;
      CURRENT_TIME : float;
   begin
      if ACCUMULATE_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      end if;

      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                         SIMULATION.CURRENT_PROCESS_NAME,
                         "COLLECT_ACCUMULATE " & ACCUMULATE_REF.TITLE, 
                         UTILITY.LF);
         UTILITY.OUTPUT_TRACE ("Current Accumulate Value: ", UTILITY.NO_LF); 
         put (ACCUMULATE_VALUE);
         new_line;
      end if;

      CURRENT_TIME := SIMULATION.CURRENT_SIMULATION_TIME;

      if ACCUMULATE_REF.COLLECT_TIME >= 0.0 then
         WEIGHTED_VALUE
            := (CURRENT_TIME - ACCUMULATE_REF.COLLECT_TIME) * 
               ACCUMULATE_REF.VALUE;
         ACCUMULATE_REF.WEIGHTED_SUM
            := ACCUMULATE_REF.WEIGHTED_SUM + WEIGHTED_VALUE;
         ACCUMULATE_REF.WEIGHTED_SUM_OF_SQUARES
            := ACCUMULATE_REF.WEIGHTED_SUM_OF_SQUARES
               + WEIGHTED_VALUE * WEIGHTED_VALUE;
         ACCUMULATE_REF.NUMBER_ACCUMULATED 
            := ACCUMULATE_REF.NUMBER_ACCUMULATED + 1;

         ACCUMULATE_REF.MIN_ACCUMULATED
            := MATH.MIN (ACCUMULATE_REF.MIN_ACCUMULATED, WEIGHTED_VALUE);
         ACCUMULATE_REF.MAX_ACCUMULATED
            := MATH.MAX (ACCUMULATE_REF.MAX_ACCUMULATED, WEIGHTED_VALUE);
      end if;
      ACCUMULATE_REF.COLLECT_TIME := CURRENT_TIME;
      ACCUMULATE_REF.VALUE := ACCUMULATE_VALUE;
   end COLLECT_ACCUMULATE;


   procedure COLLECT_ACCUMULATE (ACCUMULATE_REF : in out ACCUMULATE_STATISTIC;
                                 ACCUMULATE_VALUE : in integer) is
   begin
      if ACCUMULATE_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      else
         COLLECT_ACCUMULATE (ACCUMULATE_REF, float (ACCUMULATE_VALUE));
      end if;
   end COLLECT_ACCUMULATE;


   procedure REPORT_ACCUMULATE (ACCUMULATE_REF : in ACCUMULATE_STATISTIC) is
      WEIGHTED_VALUE, STD2 : float;
      AVG, STD, MIN, MAX : float := 0.0;
   begin
      if ACCUMULATE_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      end if;

      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                               "REPORT_ACCUMULATE " & ACCUMULATE_REF.TITLE, 
                               UTILITY.LF);
         UTILITY.OUTPUT_TRACE ("Weighted Sum = ", UTILITY.NO_LF); 
         put (ACCUMULATE_REF.WEIGHTED_SUM); 
         SPACE (5); put ("Weighted Sum of Squares = ");
         put (ACCUMULATE_REF.WEIGHTED_SUM_OF_SQUARES); 
         new_line;
      end if;

      if REPORT_OPT = UTILITY.ON then
         WEIGHTED_VALUE
            := ACCUMULATE_REF.VALUE * (SIMULATION.CURRENT_SIMULATION_TIME - 
               ACCUMULATE_REF.COLLECT_TIME);
         ACCUMULATE_REF.WEIGHTED_SUM := ACCUMULATE_REF.WEIGHTED_SUM + 
               WEIGHTED_VALUE;
         ACCUMULATE_REF.WEIGHTED_SUM_OF_SQUARES
            := ACCUMULATE_REF.WEIGHTED_SUM_OF_SQUARES
               + WEIGHTED_VALUE * WEIGHTED_VALUE;
         ACCUMULATE_REF.NUMBER_ACCUMULATED
            := ACCUMULATE_REF.NUMBER_ACCUMULATED + 1;
         ACCUMULATE_REF.MIN_ACCUMULATED
            := MATH.MIN (ACCUMULATE_REF.MIN_ACCUMULATED, WEIGHTED_VALUE);
         ACCUMULATE_REF.MAX_ACCUMULATED
            := MATH.MAX (ACCUMULATE_REF.MAX_ACCUMULATED, WEIGHTED_VALUE);

         if SIMULATION.CURRENT_SIMULATION_TIME > 0.0 then
            MIN := ACCUMULATE_REF.MIN_ACCUMULATED;
            MAX := ACCUMULATE_REF.MAX_ACCUMULATED; 
            AVG := ACCUMULATE_REF.WEIGHTED_SUM / 
                   SIMULATION.CURRENT_SIMULATION_TIME;
            STD2 := ACCUMULATE_REF.WEIGHTED_SUM_OF_SQUARES / 
                    SIMULATION.CURRENT_SIMULATION_TIME - AVG * AVG;
-- Note: if all statistics are equal, computer round off can cause neg. sqrt
            if STD2 > 0.0 then
               STD := MATH.SQRT (STD2);            -- else default STD = 0.0
            end if;
         end if;

         UTILITY.CONTROL_OUTPUT;
         new_line;
         new_line;
         put ("ACCUMULATE STATISTIC                        ");
         put ("    simulation time = ");
         put (SIMULATION.CURRENT_SIMULATION_TIME); new_line;
         SPACE (10); put (ACCUMULATE_REF.TITLE); new_line;
         put ("     Average Accumulated            "); put (AVG); new_line;
         put ("     Standard Deviation             "); put (STD); new_line;
         put ("     Minimum Accumulated            "); put (MIN); new_line;
         put ("     Maximum Accumulated            "); put (MAX); new_line;
         put ("     Number of Weighted Accumulates ");
         put (ACCUMULATE_REF.NUMBER_ACCUMULATED); new_line;
         new_line;
         UTILITY.RELEASE_OUTPUT_CONTROL;
      end if;
   end REPORT_ACCUMULATE;


-- Histogram Operators :
-- ---------------------

   procedure CREATE_HISTOGRAM (HISTOGRAM_REF : in out HISTOGRAM_STATISTIC;
                               HISTOGRAM_TITLE : in string;
                               NUMBER_OF_CELLS : in integer;
                               MIN_VALUE, MAX_VALUE : in float) is
      CELL_INDEX : positive;
   begin
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                               "CREATE_HISTOGRAM " & HISTOGRAM_TITLE, 
                               UTILITY.LF);
         UTILITY.OUTPUT_TRACE ("NUMBER_OF_CELLS: ", UTILITY.NO_LF); 
         put (NUMBER_OF_CELLS); 
         space (5); put ("MIN_VALUE: "); put (MIN_VALUE); 
         space (5); put ("MAX_VALUE: "); put (MAX_VALUE); 
         new_line;
      end if;

      if HISTOGRAM_REF /= null then
         raise HISTOGRAM_REDECLARATION_ERROR;
      else
         HISTOGRAM_REF := new HISTOGRAM_REC (NUMBER_OF_CELLS);
         HISTOGRAM_REF.TITLE := UTILITY.STRING_MATCH (HISTOGRAM_TITLE,60);
         HISTOGRAM_REF.MIN_VALUE := MIN_VALUE;
         HISTOGRAM_REF.MAX_VALUE := MAX_VALUE;
         HISTOGRAM_REF.CELL_WIDTH :=
            (MAX_VALUE - MIN_VALUE) / float (NUMBER_OF_CELLS);
         for CELL_INDEX in 1 .. NUMBER_OF_CELLS loop
            HISTOGRAM_REF.CELL (CELL_INDEX) := 0;
         end loop;
         HIST_Q.INSERT_QUEUE (HIST_STAT_Q, HISTOGRAM_REF);
      end if;
   end CREATE_HISTOGRAM;


   procedure CREATE_HISTOGRAM (HISTOGRAM_REF : in out HISTOGRAM_STATISTIC;
                               HISTOGRAM_TITLE : in string;
                               NUMBER_OF_CELLS : in integer;
                               MIN_VALUE, MAX_VALUE : in integer) is
   begin
      if HISTOGRAM_REF /= null then
         raise HISTOGRAM_REDECLARATION_ERROR;
      else
         CREATE_HISTOGRAM (HISTOGRAM_REF, HISTOGRAM_TITLE, NUMBER_OF_CELLS,
                          float (MIN_VALUE), float (MAX_VALUE));
      end if;
   end CREATE_HISTOGRAM;


   procedure COLLECT_HISTOGRAM (HISTOGRAM_REF : in out HISTOGRAM_STATISTIC;
                                HISTOGRAM_VALUE : in float) is
      CELL_INDEX : positive;
   begin
      if HISTOGRAM_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      end if;

      CELL_INDEX := integer ((HISTOGRAM_VALUE - HISTOGRAM_REF.MIN_VALUE)
                           / HISTOGRAM_REF.CELL_WIDTH + 0.5);
      CELL_INDEX := MIN (CELL_INDEX, HISTOGRAM_REF.NUMBER_OF_CELLS);
      CELL_INDEX := MAX (CELL_INDEX, 1);
      HISTOGRAM_REF.CELL (CELL_INDEX) := 
         HISTOGRAM_REF.CELL (CELL_INDEX) + 1;

      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                               "COLLECT_HISTOGRAM " & HISTOGRAM_REF.TITLE, 
                               UTILITY.LF);
         UTILITY.OUTPUT_TRACE ("Current Histogram Value: ", UTILITY.NO_LF); 
         put (HISTOGRAM_VALUE);
         space (5); put ("CELL_INDEX: "); put (CELL_INDEX);
         new_line;
      end if;
   end COLLECT_HISTOGRAM;
      

   procedure COLLECT_HISTOGRAM (HISTOGRAM_REF : in out HISTOGRAM_STATISTIC;
                                HISTOGRAM_VALUE : in integer) is
   begin
      if HISTOGRAM_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      else
         COLLECT_HISTOGRAM (HISTOGRAM_REF, float (HISTOGRAM_VALUE));
      end if;
   end COLLECT_HISTOGRAM;


   procedure REPORT_HISTOGRAM (HISTOGRAM_REF : in HISTOGRAM_STATISTIC) is
      HEIGHT, CUML_HEIGHT, NO_OF_ENTRIES, ACCUMULATION : integer := 0;
      CUML_FREQ, UPPER_CELL_LIMIT : float := 0.0;
   begin
      if HISTOGRAM_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      end if;

      for i in 1 .. HISTOGRAM_REF.NUMBER_OF_CELLS loop
         NO_OF_ENTRIES := NO_OF_ENTRIES + HISTOGRAM_REF.CELL (i);
      end loop;

      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                               "REPORT_HISTOGRAM " & HISTOGRAM_REF.TITLE, 
                               UTILITY.LF);
         UTILITY.OUTPUT_TRACE ("Number of Entries : ", UTILITY.NO_LF); 
         put (NO_OF_ENTRIES); new_line;
         UTILITY.OUTPUT_TRACE ("    Cell No.    Cell Total", UTILITY.LF);
         for i in 1 .. HISTOGRAM_REF.NUMBER_OF_CELLS loop
            UTILITY.OUTPUT_TRACE (" ", UTILITY.NO_LF);
            put (i); put (HISTOGRAM_REF.CELL (i)); new_line;
         end loop;
         new_line;
      end if;

      if REPORT_OPT = UTILITY.ON then
         -- Print Title :
         UTILITY.CONTROL_OUTPUT;
         new_page;
         put ("HISTOGRAM STATISTIC                         ");
         put ("    simulation time = ");
         put (SIMULATION.CURRENT_SIMULATION_TIME); new_line;
         SPACE (10); put (HISTOGRAM_REF.TITLE); new_line;
         LN_FEED (1);

         space(50); put("PERCENT"); new_line;

         put("OBSV"); space(5); put("CUML"); space(5); put("UPPER"); new_line;

         put("FREQ"); space(5); put("FREQ"); space(3); put("CELL LIMIT");
         put("  0        20        40        60        80       100");
         new_line;

         SPACE (24);
         for i in 1..11 loop
            put("    +");
         end loop;
         new_line;
      
         -- Draw histogram :
         for i in 1..HISTOGRAM_REF.NUMBER_OF_CELLS loop
            put (HISTOGRAM_REF.CELL(i), 4);
            SPACE (4);
            ACCUMULATION := ACCUMULATION + HISTOGRAM_REF.CELL(i);
            CUML_FREQ := float (ACCUMULATION) / float (NO_OF_ENTRIES);
            put (CUML_FREQ, 1, 4, 0);
            SPACE (2);
            if i = HISTOGRAM_REF.NUMBER_OF_CELLS then
               put (" INFINITY ");
            else
               UPPER_CELL_LIMIT := HISTOGRAM_REF.CELL_WIDTH * float(i);
               put (UPPER_CELL_LIMIT, 1, 4, 3);
            end if;
            SPACE (2);
            put ("+");
            HEIGHT := HISTOGRAM_REF.CELL(i) * 50 / NO_OF_ENTRIES;
            CUML_HEIGHT := integer(CUML_FREQ * 50.0 + 0.5) - 1;
            for j in 1..HEIGHT loop
               put ("*");
            end loop;
            if CUML_HEIGHT > HEIGHT then
               SPACE (CUML_HEIGHT - HEIGHT - 1);
               put ("C");
            end if;
            if CUML_HEIGHT < 50 then
               SPACE (49 - CUML_HEIGHT);
               put ("+");
            end if;
            new_line;
         end loop;

         -- Print bottom of histogram :
         put ("----"); SPACE (20);
         for i in 1..11 loop
            put ("    +");
         end loop;
         new_line;

         put (NO_OF_ENTRIES, 4); SPACE (24);
         put ("0        20        40        60        80       100");
         new_line;

         LN_FEED (2);
         put ("  Lower Bound  "); put (HISTOGRAM_REF.MIN_VALUE); new_line;
         put ("  Upper Bound  "); put (HISTOGRAM_REF.MAX_VALUE); new_line;
         put ("  No. of cells "); put (HISTOGRAM_REF.NUMBER_OF_CELLS); new_line;
         put ("  Cell width   "); put (HISTOGRAM_REF.CELL_WIDTH); new_line;
         LN_FEED (2);
         UTILITY.RELEASE_OUTPUT_CONTROL;
      end if;
   end REPORT_HISTOGRAM;


-- PLOT Operators :
-- ----------------

   procedure CREATE_PLOT (PLOT_REF : in out PLOT_STATISTIC;
                          PLOT_TITLE : in string;
                          X_TITLE, Y_TITLE : in string;
                          X_DELTA : in float;
                          MIN_X_VALUE : in float := float'first;
                          MAX_X_VALUE : in float := float'last;
                          MIN_Y_VALUE : in float := float'first;
                          MAX_Y_VALUE : in float := float'last) is
   begin
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                               "CREATE_PLOT " & PLOT_TITLE, UTILITY.LF);
         UTILITY.OUTPUT_TRACE ("X_TITLE: " & X_TITLE, UTILITY.NO_LF); 
         SPACE (5);  put ("Y_TITLE: " & Y_TITLE); 
         SPACE (5);  put ("DELTA_X: "); put (X_DELTA); new_line;
         UTILITY.OUTPUT_TRACE ("MIN_X_VALUE: ", UTILITY.NO_LF); 
         put (MIN_X_VALUE); SPACE (5); 
         put ("MAX_X_VALUE: "); put (MAX_X_VALUE); new_line;
         UTILITY.OUTPUT_TRACE ("MIN_Y_VALUE: ", UTILITY.NO_LF); 
         put (MIN_Y_VALUE); SPACE (5);
         put ("MAX_Y_VALUE: "); put (MAX_Y_VALUE); new_line;
      end if;

      if PLOT_REF /= null then
         raise PLOT_REDECLARATION_ERROR;
      else
         PLOT_REF := new PLOT_REC;
         PLOT_REF.PLOT_TITLE := UTILITY.STRING_MATCH (PLOT_TITLE, 60);
         PLOT_REF.X_TITLE := UTILITY.STRING_MATCH (X_TITLE, 8);
         PLOT_REF.Y_TITLE := UTILITY.STRING_MATCH (Y_TITLE, 8);
         PLOT_REF.DELTA_X := X_DELTA;
         PLOT_REF.USER_MIN_X := MIN_X_VALUE;
         PLOT_REF.USER_MAX_X := MAX_X_VALUE;
         PLOT_REF.USER_MIN_Y := MIN_Y_VALUE;
         PLOT_REF.USER_MAX_Y := MAX_Y_VALUE;
         PLOT_REF.NUMBER_OF_POINTS := 0;
         PLOT_REF.COORDINATES_LIST := null;
         PLOT_Q.INSERT_QUEUE (PLOT_STAT_Q, PLOT_REF);
      end if;
   end CREATE_PLOT;

   procedure CREATE_PLOT (PLOT_REF : in out PLOT_STATISTIC;
                          PLOT_TITLE : in string;
                          X_TITLE, Y_TITLE : in string;
                          X_DELTA : in integer;
                          MIN_X_VALUE : in integer := integer'first;
                          MAX_X_VALUE : in integer := integer'last;
                          MIN_Y_VALUE : in integer := integer'first;
                          MAX_Y_VALUE : in integer := integer'last) is

      MINX, MAXX, MINY, MAXY : float;
   begin
      if PLOT_REF /= null then
         raise PLOT_REDECLARATION_ERROR;
      else
         if MIN_X_VALUE = integer'first then
            MINX := float'first;
         else
            MINX := float (MIN_X_VALUE);
         end if;
         if MAX_X_VALUE = integer'last then
            MAXX := float'last;
         else
            MAXX := float (MAX_X_VALUE);
         end if;
         if MIN_Y_VALUE = integer'first then
            MINY := float'first;
         else
            MINY := float (MIN_Y_VALUE);
         end if;
         if MAX_Y_VALUE = integer'last then
            MAXY := float'last;
         else
            MAXY := float (MAX_Y_VALUE);
         end if;

         CREATE_PLOT (PLOT_REF, PLOT_TITLE, X_TITLE, Y_TITLE,
                      float (X_DELTA), MINX, MAXX, MINY, MAXY);
      end if;
   end CREATE_PLOT;


   procedure COLLECT_PLOT (PLOT_REF : in out PLOT_STATISTIC;
                           X_VALUE, Y_VALUE : in float) is

      procedure INSERT_POINT (X, Y : in float) is
         -- Perform orderly insertion (low X and Y values first) :
         PT, T : COORDINATES := PLOT_REF.COORDINATES_LIST;
      begin
         -- Find end of coordinates list.
         while (T /= null) loop
            PT := T;
            T := T.NEXT;
         end loop;

         if PT = T then
            -- First point, insert at top of list :
            PT := new COORDINATES_REC;
            PT.X_COORDINATE := X;
            PT.Y_COORDINATE := Y;
            PT.NEXT := PLOT_REF.COORDINATES_LIST;
            PLOT_REF.COORDINATES_LIST := PT;
         else
            -- Insert new point after current point :
            -- Make sure that the independant coordinate is growing.
            if PT.X_COORDINATE >= X then
               raise PLOT_VALUE_INVALID;
            end if;
            PT.NEXT := new COORDINATES_REC;
            PT := PT.NEXT;
            PT.X_COORDINATE := X;
            PT.Y_COORDINATE := Y;
            PT.NEXT := T;
         end if;

      end INSERT_POINT;

   begin  -- COLLECT_PLOT
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                               "COLLECT_PLOT " & PLOT_REF.PLOT_TITLE,
                               UTILITY.LF);
         UTILITY.OUTPUT_TRACE ("X_VALUE: ", UTILITY.NO_LF); put (X_VALUE); 
         space (5); put ("Y_VALUE: "); put (Y_VALUE); new_line;
      end if;

      if PLOT_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      else
         PLOT_REF.MIN_X := MATH.MIN (PLOT_REF.MIN_X, X_VALUE);
         PLOT_REF.MAX_X := MATH.MAX (PLOT_REF.MAX_X, X_VALUE);
         PLOT_REF.MIN_Y := MATH.MIN (PLOT_REF.MIN_Y, Y_VALUE);
         PLOT_REF.MAX_Y := MATH.MAX (PLOT_REF.MAX_Y, Y_VALUE);
         INSERT_POINT (X_VALUE, Y_VALUE);
         PLOT_REF.NUMBER_OF_POINTS := PLOT_REF.NUMBER_OF_POINTS + 1;
      end if;
   end COLLECT_PLOT;


   procedure COLLECT_PLOT (PLOT_REF : in out PLOT_STATISTIC;
                           X_VALUE, Y_VALUE : in integer) is
   begin
      if PLOT_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      else
         COLLECT_PLOT (PLOT_REF, float (X_VALUE), float (Y_VALUE));
      end if;
   end COLLECT_PLOT;


   procedure REPORT_PLOT (PLOT_REF : in PLOT_STATISTIC) is
      -- Draw a plot of the points collected as (x,y) pairs.
      PARTITION : float;
      Y_RANGE : float;
      QTR_OF_Y_RANGE : float;
      MINX, MAXX, MINY, MAXY : float;
      Y_STRING : string (1..65);
      Y_STRING_BACKGROUND : constant string (1..65) :=
      "+               +               +               +               +";
      PERCENT_BACKGROUND  : constant string (1..66) :=
      "0%             25%             50%             75%            100%";

      procedure PLOT_POINTS (PLOT_LIST : in COORDINATES) is
         -- This procedure plots the points on any general
         -- output device, specifically any line printer.
         PT : COORDINATES := PLOT_LIST;
         LO_X, HI_X : float;
         ABSOLUTE_Y : integer;
         PT_CHAR : string (1 .. 1) := "*";

      begin  -- PLOT_POINTS
         HI_X := MINX;
         while PT /= null and HI_X <= MAXX loop
            LO_X := HI_X;
            HI_X := LO_X + PLOT_REF.DELTA_X;
            put (LO_X, 1, 5, 3); SPACE (2);
            Y_STRING := Y_STRING_BACKGROUND;
            while PT /= null and then
               (LO_X <= PT.X_COORDINATE and then HI_X >  PT.X_COORDINATE)  loop
               if Y_RANGE /= 0.0 then
                  ABSOLUTE_Y := 
                  integer ((PT.Y_COORDINATE - MINY) * 64.0 / Y_RANGE) + 1;
               else
                  ABSOLUTE_Y := 1;
               end if;

               -- out of bounds points go on boundries
               if ABSOLUTE_Y < 1 then
                  ABSOLUTE_Y := 1;
               end if;
               if ABSOLUTE_Y > 65 then
                  ABSOLUTE_Y := 65;
               end if;

               Y_STRING (ABSOLUTE_Y..ABSOLUTE_Y) := PT_CHAR;
               PT := PT.NEXT;
            end loop;
            put (Y_STRING);
            new_line;
         end loop;
      end PLOT_POINTS;

   begin  -- REPORT_PLOT
      if PLOT_REF = null then
         raise STATISTIC_DOES_NOT_EXIST;
      end if;

      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                               SIMULATION.CURRENT_PROCESS_NAME,
                             "REPORT_PLOT " & PLOT_REF.PLOT_TITLE, UTILITY.LF);
      end if;

      if REPORT_OPT = UTILITY.ON then
         -- Determine proper limits
         if PLOT_REF.USER_MIN_Y = float'first then
            MINY := PLOT_REF.MIN_Y;
         else
            MINY := PLOT_REF.USER_MIN_Y;
         end if;

         if PLOT_REF.USER_MAX_Y = float'last then
            MAXY := PLOT_REF.MAX_Y;
         else
            MAXY := PLOT_REF.USER_MAX_Y;
         end if;

         if PLOT_REF.USER_MIN_X = float'first then
            MINX := PLOT_REF.MIN_X;
         else
            MINX := PLOT_REF.USER_MIN_X;
         end if;

         if PLOT_REF.USER_MAX_X = float'last then
            MAXX := PLOT_REF.MAX_X;
         else
            MAXX := PLOT_REF.USER_MAX_X;
         end if;

         PARTITION := MINY;
         Y_RANGE := MAXY - MINY;
         QTR_OF_Y_RANGE := Y_RANGE / 4.0;

         -- Print titles :
         UTILITY.CONTROL_OUTPUT;
         new_page;
         new_line;
         put ("PLOT STATISTIC                              ");
         put ("    simulation time = ");
         put (SIMULATION.CURRENT_SIMULATION_TIME); new_line;
         SPACE (10); put (PLOT_REF.PLOT_TITLE); new_line;
         new_line;
         put_line ("* = " & PLOT_REF.Y_TITLE); 
         put ("SCALE  ");
         for i in 1 .. 5 loop
            put (PARTITION, 1, 2, 3); SPACE (8);
            PARTITION := PARTITION + QTR_OF_Y_RANGE;
         end loop;
         new_line;
         new_line;

         -- print plot:
         if PLOT_REF.NUMBER_OF_POINTS > 0 then
            space (13); put_line (PERCENT_BACKGROUND);
            put (PLOT_REF.X_TITLE); space (5); 
            put (Y_STRING_BACKGROUND); new_line;
            PLOT_POINTS (PLOT_REF.COORDINATES_LIST);
            space (13); put_line (Y_STRING_BACKGROUND);
            space (13); put_line (PERCENT_BACKGROUND);
            LN_FEED (2);
         else
            LN_FEED (2);
            put ("Number of points is ZERO.");
            LN_FEED (2);
         end if;
         UTILITY.RELEASE_OUTPUT_CONTROL;
      end if;
   end REPORT_PLOT;


-- Report/Trace Output Operators :
-- ------------------------------------

   procedure SET_STATISTICS_REPORT_OPTION 
      (REPORT_OPTION : in UTILITY.OPTION := UTILITY.ON) is
   begin
      REPORT_OPT := REPORT_OPTION;
   end SET_STATISTICS_REPORT_OPTION;

   procedure SET_STATISTICS_TRACE_OPTION 
      (TRACE_OPTION : in UTILITY.OPTION := UTILITY.OFF) is
   begin
      TRACE_OPT := TRACE_OPTION;
      if TRACE_OPT = UTILITY.ON then
         UTILITY.OUTPUT_TRACE (SIMULATION.CURRENT_SIMULATION_TIME,
                         SIMULATION.CURRENT_PROCESS_NAME,
                         "SET STATISTICS TRACE OPTION ON", UTILITY.LF);
      end if;
--      COUNT_Q.SET_QUEUE_TRACE_OPTION (TRACE_OPTION);
--      TALLY_Q.SET_QUEUE_TRACE_OPTION (TRACE_OPTION);
--      ACCUM_Q.SET_QUEUE_TRACE_OPTION (TRACE_OPTION);
--      HIST_Q.SET_QUEUE_TRACE_OPTION (TRACE_OPTION);
--      PLOT_Q.SET_QUEUE_TRACE_OPTION (TRACE_OPTION);
   end SET_STATISTICS_TRACE_OPTION;


-- Statistics Package Initialization
-- ---------------------------------
begin
   COUNT_Q.CREATE_QUEUE (COUNT_STAT_Q, "COUNT_Q");
   TALLY_Q.CREATE_QUEUE (TALLY_STAT_Q, "TALLY_Q");
   ACCUM_Q.CREATE_QUEUE (ACCUM_STAT_Q, "ACCUM_Q");
   HIST_Q.CREATE_QUEUE  (HIST_STAT_Q,  "HIST_Q");
   PLOT_Q.CREATE_QUEUE  (PLOT_STAT_Q,  "PLOT_Q");

end STATISTICS;
