cases.h

/* This is the header file for the Cases module.  It is included by all
   twelve modules. */

/* maxima */

#define Max_Identifier_Length 16
#define Max_Attribute_Options 4

/* the number of characters first allocated for a string, and the number of
   extra characters allocated if that string has to be extended */

#define String_Increment 256

/* a "pseudo-infinite" weight for the calculation of weighted correlation
   coefficients */

#define Very_Heavy_Indeed 1000000.0

/* the threshold of likelihood, below which a given number of YES/YES pairs
   is considered unusually high or unusually low */

#define Threshold 0.05

/* arithmetic is precise to (log Precision) decimal places */

#define Precision 100.0

/* distance and weight comparisons are precise to (log Distance_Precision)
   decimal places (this is the threshold within which two cases are
   considered equidistant, or two weights are considered equal) */

#define Distance_Precision 100.0

/* the format for the display of floating point numbers (the number of
   decimal places should be (log Distance_Precision)) */

#define Floating_Point_Format "%.2f"

/* string constants for output to LaTeX files */

#define Raise_Height "0.6\\ht\\strutbox"
#define Column_Separation "\\tabcolsep"
#define Matrix_Column_Separation "0.4em"
#define Heading "\\subsection*"
#define Subheading "\\subsubsection*"
#define Skip "\\medskip\\noindent"
#define Identifier_Font "\\sf"
#define Null_String "{[{\\it null string\\/}]}"

/* special LaTeX symbols */

#define Yes_Symbol "$\\bullet$"
#define No_Symbol "$\\times$"
#define Unknown_Symbol ""
#define Functional_Dependence_Symbol "\\rule[0.25ex]{0.35em}{0.35em}"
#define Stochastic_Dependence_Symbol "$\\bullet$"
#define Specified_Direction_Symbol "$\\Rightarrow$"
#define Ideal_Point_Direction_Symbol\
   "$\\stackrel{\\scriptscriptstyle I~}{\\Rightarrow}$"
#define Centroid_Direction_Symbol\
   "$\\stackrel{\\scriptscriptstyle\\mu~}{\\Rightarrow}$"
#define All_Directions_Symbol\
   "$\\stackrel{\\scriptscriptstyle\\star~}{\\Rightarrow}$"
#define External_Area_Symbol "$\\Leftrightarrow$"
#define External_Result_Symbol "$\\Leftarrow$"
#define Disjunction_Symbol "$\\vee$"

/* file extensions */

#define LaTeX_File_Extension ".tex"
#define Log_File_Extension ".log"
#define Specification_File_Extension ".cls"

/* character constants */

#define Attribute_Vector_Begin_Character '('
#define Attribute_Vector_End_Character ')'
#define Little_A_Character 'a'
#define Little_Z_Character 'z'
#define Big_A_Character 'A'
#define Big_Z_Character 'Z'
#define Zero_Character '0'
#define Nine_Character '9'
#define Yes_Character 'Y'
#define No_Character 'N'
#define Unknown_Character 'U'
#define Help_Character 'H'
#define Quit_Character 'Q'

/* other constants */

#define Year_Digits 4
#define Yes_Value 1.0
#define No_Value 0.0

/* enumerated types */

typedef enum {
   NO,
   YES,
   UNKNOWN
} attribute_value_type;

typedef enum {
   NEARER,
   EQUIDISTANT,
   FURTHER
} relative_distance_type;

/* structure types */

typedef struct {
   boolean infinite;
   floating_point finite;
} weight_type;

typedef struct {
   cardinal infinite;
   floating_point finite;
} distance_subtype;

typedef struct {
   distance_subtype known;
   distance_subtype unknown;
} distance_type;

typedef struct {
   boolean meaningless;
   floating_point unweighted;
   floating_point weighted;
} correlation_type;

typedef struct {
   distance_type distance;
   cardinal number_of_known_differences,
      number_of_known_pairs;
   floating_point weighted_association_coefficient;
   correlation_type correlation_coefficient;
} metrics_type;

typedef struct vector_element {
   attribute_value_type attribute_value;
   struct vector_element *next;
} vector_element;

typedef struct matrix_element {
   attribute_value_type attribute_value;
   struct matrix_element *case_next,
     *attribute_next;
} matrix_element;

typedef struct centroid_element {
   boolean unknown;
   floating_point value;
   struct centroid_element *next;
} centroid_element;

typedef struct probability_element {
   boolean unknown,
      functional_dependence;
   floating_point probability_that_or_fewer,
      probability_that_or_more;
   struct probability_element *next;
} probability_element;

typedef struct identifier_list_element {
   string identifier;
   struct identifier_list_element *next;
} identifier_list_element;

typedef struct weight_list_element {
   weight_type weight;
   struct weight_list_element *next;
} weight_list_element;

typedef struct hypothetical_list_element {
   vector_element *hypothetical_head;
   distance_type nearest_neighbour_distance;
   struct hypothetical_list_element *next;
} hypothetical_list_element;

/* the structure type kase is so-named because the more obvious case is a
   reserved word */

typedef struct kase {
   cardinal number;
   string name;
   string short_name;
   string citation;
   cardinal year;
   string court_string;
   cardinal court_rank;
   matrix_element *matrix_head;
   string summary;
   boolean summarized;
   metrics_type metrics;
   struct kase *equidistant_known_next,
     *equidistant_unknown_next,
     *next;
} kase;

typedef struct result {
   string identifier,
      string;
   kase *case_head,
     *nearest_known_case,
     *nearest_unknown_case;
   relative_distance_type nearest_known_compared_with_unknown;
   distance_subtype specified_direction;
   struct result *equidistant_specified_direction_next;
   vector_element *ideal_point_head;
   metrics_type ideal_point_metrics;
   struct result *equidistant_ideal_point_next;
   distance_subtype ideal_point_direction;
   struct result *equidistant_ideal_point_direction_next;
   centroid_element *centroid_head;
   metrics_type centroid_metrics;
   struct result *equidistant_centroid_next;
   distance_subtype centroid_direction;
   struct result *equidistant_centroid_direction_next;
   hypothetical_list_element *hypothetical_list_head;
   struct result *equidistant_next,
     *next;
} result;

typedef struct direction_list_element {
   result *result;
   struct direction_list_element *next;
} direction_list_element;

typedef struct local_attribute_type {
   string question,
      help;
} local_attribute_type;

typedef struct external_attribute_type {
   string area_identifier;
   identifier_list_element *yes_identifier_head,
     *no_identifier_head,
     *unknown_identifier_head;
} external_attribute_type;

typedef struct attribute {
   cardinal number;
   boolean external_attribute;
   union {
      local_attribute_type local;
      external_attribute_type external;
   }  details;
   string yes;
   direction_list_element *yes_direction_head;
   string no;
   direction_list_element *no_direction_head;
   string unknown;
   direction_list_element *unknown_direction_head;
   matrix_element *matrix_head;
   floating_point mean;
   weight_type weight;
   weight_list_element *weights_head;
   probability_element *probability_head;
   struct attribute *next;
} attribute;

typedef struct area {
   string identifier,
      opening,
      closing;
   result *result_head;
   cardinal number_of_results;
   attribute *attribute_head;
   cardinal number_of_attributes;
   boolean infinite_weight;
   boolean correlation_coefficients;
   result *nearest_result;
   result *nearest_ideal_point;
   result *nearest_centroid;
   result *strongest_specified_direction;
   result *strongest_ideal_point_direction;
   result *strongest_centroid_direction;
   struct area *next;
} area;

typedef struct court {
   string identifier,
      string;
   cardinal rank;
   struct court *next;
} court;

typedef struct {
   court *court_head;
   area *area_head;
} case_law_specification;

/* external functions */

extern boolean
Is_Zero(
      floating_point x);

extern boolean
Is_Equal(
      floating_point x,
      floating_point y,
      floating_point precision);

extern boolean
Is_Less(
      floating_point x,
      floating_point y,
      floating_point precision);

extern boolean
Is_Zero_Subdistance(
      distance_subtype x);

extern boolean
Is_Zero_Distance(
      distance_type x);

extern boolean
Attribute_Value(
      attribute_value_type attribute_value,
      floating_point *value);

extern attribute_value_type
Nearest_Attribute_Value(
      floating_point value);

extern void
Write_Floating_Point(
      file stream,
      floating_point number,
      string warning_string);

extern case_law_specification
Initialize_Cases(
      file log_stream,
      boolean inputable_latex,
      boolean verbose,
      string specification_filename,
      string dump_filename,
      string probabilities_filename,
      string weights_filename);

extern string
Case_Law(
      file log_stream,
      case_law_specification case_law,
      string area_identifier,
      boolean adjust,
      boolean echo,
      boolean inputable_latex,
      boolean verbose,
      cardinal hypothetical_reports,
      cardinal hypothetical_changes,
      cardinal level,
      string distances_filename,
      string weights_filename,
      string report_filename);

cases.c

/* This is the implementation file for the Cases module. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "shyster.h"
#include "cases.h"
#include "tokenizer.h"
#include "parser.h"
#include "dumper.h"
#include "checker.h"
#include "scales.h"
#include "adjuster.h"
#include "consultant.h"
#include "odometer.h"
#include "reporter.h"

static void
error_exit(
      file stream,
      const string message)
{
   Write_Error_Message_And_Exit(stream, "Cases", message);
}

static void
warning(
      file stream,
      const string message,
      cardinal level)
{
   Write_Warning_Message(stream, "Cases", message, level);
}

extern boolean
Is_Zero(
      floating_point x)

/* Returns TRUE, iff x = 0 precise to (log Precision) decimal places. */

{
   return floor((double) x * Precision + 0.5) == 0.0;
}

extern boolean
Is_Equal(
      floating_point x,
      floating_point y,
      floating_point precision)

/* Returns TRUE, iff x = y precise to (log precision) decimal places. */

{
   return floor((double) x * precision + 0.5) == floor((double) y * precision + 0.5);
}

extern boolean
Is_Less(
      floating_point x,
      floating_point y,
      floating_point precision)

/* Returns TRUE, iff x < y precise to (log precision) decimal places. */

{
   return floor((double) x * precision + 0.5) < floor((double) y * precision + 0.5);
}

extern boolean
Is_Zero_Subdistance(
      distance_subtype x)

/* Returns TRUE, iff both of x's infinite and finite components are zero. */

{
   return (x.infinite == 0) && Is_Zero(x.finite);
}

extern boolean
Is_Zero_Distance(
      distance_type x)

/* Returns TRUE, iff both of x's known and unknown components are zero. */

{
   return Is_Zero_Subdistance(x.known) && Is_Zero_Subdistance(x.unknown);
}

extern boolean
Attribute_Value(
      attribute_value_type attribute_value,
      floating_point *value)

/* Sets *value to 1, if attribute_value is YES; to 0, if attribute_value is
   NO.  Returns TRUE, iff attribute_value is known. */

{
   switch (attribute_value) {
      case YES:
         *value = Yes_Value;
         return TRUE;
      case NO:
         *value = No_Value;
         return TRUE;
      default:
         return FALSE;
   }
}

extern attribute_value_type
Nearest_Attribute_Value(
      floating_point value)

/* Returns the nearest attribute value to value
   (0 <= value < 0.5: NO; 0.5 <= value <= 1: YES) */

{
   if (value < 0.5)
      return NO;
   else
      return YES;
}

extern void
Write_Floating_Point(
      file stream,
      floating_point number,
      string warning_string)

/* Writes number, precise to (log Precision) decimal places.  If
   warning_string is not empty, it is written after number. */

{
   if (Is_Less(number, 0.0, Precision))
      fprintf(stream, "$-$");

   fprintf(stream, Floating_Point_Format,
         floor(fabs((double) number) * Precision + 0.5) / Precision);

   if (strcmp(warning_string, Empty_String))
      fprintf(stream, "\\rlap{\\makebox[\\tabcolsep]{%s}}", warning_string);
}

extern case_law_specification
Initialize_Cases(
      file log_stream,
      boolean inputable_latex,
      boolean verbose,
      string specification_filename,
      string dump_filename,
      string probabilities_filename,
      string weights_filename)

/* Calls the Tokenizer and Parser to read the case law specification in
   specification_filename and build an internal representation of that
   specification.  Invokes the Dumper to dump that internal representation to
   dump_filename.  Uses the Checker to check for attribute dependence, and to
   write the probabilities to probabilities_filename.  Calls the Scales
   module to assign weights to all the attributes, and to write those weights
   to weights_filename.  Returns a pointer to SHYSTER's internal
   representation of the specification with all attributes weighted. */

{
   file specification_stream = NULL,
      dump_stream = NULL,
      probabilities_stream = NULL,
      weights_stream = NULL;
   char filename[Max_Filename_Length];
   char message[Max_Error_Message_Length];
   case_law_specification case_law;
   area *area_pointer;
   result *result_pointer;

   if (specification_filename == NULL)

      /* there is no specification filename */

      error_exit(log_stream, "no case law specification file specified");

   /* open the case law specification file */

   sprintf(filename, "%s%s", specification_filename, Specification_File_Extension);
   if ((specification_stream = fopen(filename, "r")) == NULL) {
      sprintf(message, "can't open case law specification file \"%s\"", filename);
      error_exit(log_stream, message);
   }
   fprintf(log_stream,
         "Reading case law specification from \"%s\" ...\n\n", filename);

   case_law = Parse_Specification(specification_stream, log_stream);

   if (case_law.area_head == NULL)

      /* no areas were specified in the specification file */

      error_exit(log_stream, "no case law specified");

   /* close the case law specification file */

   if (fclose(specification_stream) == EOF) {
      sprintf(message, "can't close case law specification file \"%s\"", filename);
      error_exit(log_stream, message);
   }
   fprintf(log_stream, "Case law specification is valid.\n\n");

   /* check that every result in every area has either a case or an ideal
      point */

   for (area_pointer = case_law.area_head; area_pointer != NULL;
         area_pointer = area_pointer->next)
      for (result_pointer = area_pointer->result_head; result_pointer != NULL;
            result_pointer = result_pointer->next)
         if (result_pointer->case_head == NULL)
            if (result_pointer->ideal_point_head == NULL) {
               sprintf(message, "%s result in %s area has neither cases"
                     " nor an ideal point", result_pointer->identifier,
                     area_pointer->identifier);
               warning(log_stream, message, Top_Level);
            } else {
               sprintf(message, "%s result in %s area has no cases",
                     result_pointer->identifier, area_pointer->identifier);
               warning(log_stream, message, Top_Level);
            }

   if (dump_filename != NULL) {

      /* a dump filename was specified, so open the dump file */

      sprintf(filename, "%s%s", dump_filename, LaTeX_File_Extension);
      if ((dump_stream = fopen(filename, "w")) == NULL) {
         sprintf(message, "can't open dump file \"%s\"", filename);
         error_exit(log_stream, message);
      }
      fprintf(log_stream, "Writing dump to \"%s\".\n\n", filename);

      Dump_Specification(dump_stream, log_stream, case_law, inputable_latex, verbose);

      /* close the dump file */

      if (fclose(dump_stream) == EOF) {
         sprintf(message, "can't close dump file \"%s\"", filename);
         error_exit(log_stream, message);
      }
   }
   if (probabilities_filename != NULL) {

      /* a probabilities filename was specified, so open the probabilities
         file */

      sprintf(filename, "%s%s", probabilities_filename, LaTeX_File_Extension);
      if ((probabilities_stream = fopen(filename, "w")) == NULL) {
         sprintf(message, "can't open probabilities file \"%s\"", filename);
         error_exit(log_stream, message);
      }
      fprintf(log_stream, "Writing probabilities to \"%s\".\n\n",
            filename);
   }
   Check_for_Attribute_Dependence(probabilities_stream, log_stream, case_law, inputable_latex);

   if (probabilities_filename != NULL)

      /* a probabilities filename was specified, so close the probabilities
         file */

      if (fclose(probabilities_stream) == EOF) {
         sprintf(message, "can't close probabilities file \"%s\"", filename);
         error_exit(log_stream, message);
      }
   if (weights_filename != NULL) {

      /* a weights filename was specified, so open the weights file */

      sprintf(filename, "%s%s", weights_filename, LaTeX_File_Extension);
      if ((weights_stream = fopen(filename, "w")) == NULL) {
         sprintf(message, "can't open weights file \"%s\"", filename);
         error_exit(log_stream, message);
      }
      fprintf(log_stream, "Writing weights to \"%s\".\n\n", filename);
   }
   Weight_Attributes(weights_stream, log_stream, case_law, inputable_latex);

   if (weights_filename != NULL)

      /* a weights filename was specified, so close the weights file */

      if (fclose(weights_stream) == EOF) {
         sprintf(message, "can't close weights file \"%s\"", filename);
         error_exit(log_stream, message);
      }
   return case_law;
}

static void
write_facts(
      file log_stream,
      vector_element *vector_pointer)

/* Writes the fact vector pointed to by vector_pointer. Uses Y, N and U
   characters to represent YES, NO and UNKNOWN, respectively. */

{
   fprintf(log_stream, "(");
   while (vector_pointer != NULL) {
      switch (vector_pointer->attribute_value) {
         case YES:
            fprintf(log_stream, "Y");
            break;
         case NO:
            fprintf(log_stream, "N");
            break;
         case UNKNOWN:
            fprintf(log_stream, "U");
            break;
      }

      vector_pointer = vector_pointer->next;
   }
   fprintf(log_stream, ")");
}

static vector_element *
copy_facts(
      file log_stream,
      vector_element *vector_pointer)

/* Returns a pointer to a copy of the fact vector pointed to by
   vector_pointer. */

{
   vector_element *temp_pointer;

   /* allocate memory for this vector element */

   if ((temp_pointer =
               (vector_element *) malloc(sizeof(vector_element))) == NULL)
      error_exit(log_stream, "malloc failed during fact copying");

   temp_pointer->attribute_value = vector_pointer->attribute_value;
   if (vector_pointer->next == NULL)
      temp_pointer->next = NULL;
   else
      temp_pointer->next = copy_facts(log_stream, vector_pointer->next);

   return temp_pointer;
}

static void
remove_facts(vector_element *vector_pointer)

/* Frees the memory taken up by the fact vector pointed to by vector_pointer. */

{
   if (vector_pointer != NULL) {
      remove_facts(vector_pointer->next);
      free(vector_pointer);
   }
}

static void
mark_differences(
      file log_stream,
      vector_element *vector_pointer_X,
      vector_element *vector_pointer_Y)

/* Marks, with carets, the differences between the two fact vectors pointed
   to by vector_pointer_X and vector_pointer_Y (one of which has already been
   written on the previous line of log_stream). */

{
   if (vector_pointer_X != NULL) {
      if (vector_pointer_X->attribute_value != vector_pointer_Y->attribute_value)
         fprintf(log_stream, "^");
      else
         fprintf(log_stream, " ");
      mark_differences(log_stream, vector_pointer_X->next, vector_pointer_Y->next);
   }
}

static void
instantiate(
      file log_stream,
      file distances_stream,
      file report_stream,
      case_law_specification case_law,
      area *area_pointer,
      vector_element *instantiated_head,
      vector_element *facts_head,
      result *nearest_result,
      boolean verbose,
      cardinal *instantiation_number,
      cardinal *different_results,
      cardinal level)

/* Instantiates the unknown attribute values in the fact vector pointed to by
   instantiated_head to create instantiations of the instant case in which
   all the attribute values are known.  Treats each instantiation as if it
   were a new instant case, and invokes the Odometer and the Reporter to
   recalculate the distances and argue with the instantiation. */

{
   vector_element *vector_pointer;
   static char message[Max_Error_Message_Length];
   boolean all_known = TRUE;
   cardinal temp_cardinal = *instantiation_number / 10;

   /* make a copy of the fact vector pointed to by instantiated_head */

   instantiated_head = copy_facts(log_stream, instantiated_head);

   for (vector_pointer = instantiated_head; (vector_pointer != NULL) &&
         (all_known = vector_pointer->attribute_value != UNKNOWN);
         vector_pointer = vector_pointer->next);

   if (all_known && (*instantiation_number != 0)) {

      /* all of the attribute values in the instantiation are known, and the
         instantiation is not the instant case itself, so treat it as if it
         were a new instant case */

      Indent(log_stream, level);
      fprintf(log_stream, "Instantiation %u is ", *instantiation_number);
      write_facts(log_stream, instantiated_head);
      fprintf(log_stream, ".\n");

      Indent(log_stream, level + 5);

      while (temp_cardinal != 0) {
         fprintf(log_stream, " ");
         temp_cardinal = temp_cardinal / 10;
      }

      mark_differences(log_stream, instantiated_head, facts_head);
      fprintf(log_stream, "\n");

      Calculate_Distances(distances_stream, log_stream, area_pointer, case_law,
            instantiated_head, FALSE, *instantiation_number, level + 1);

      if (area_pointer->nearest_result == nearest_result) {

         /* the same result was reached as was reached in the instant case,
            so write a report to a NULL report file (nothing will be added to
            the report, although information will still be added to the log
            file) */

         Write_Report(NULL, log_stream, area_pointer, instantiated_head, facts_head,
               verbose, FALSE, FALSE, *instantiation_number, level + 1);

      } else {

         /* a different result was reached, so write a full report on the
            instantiation */

         Write_Report(report_stream, log_stream, area_pointer, instantiated_head, facts_head,
               verbose, FALSE, FALSE, *instantiation_number, level + 1);

         sprintf(message, "Instantiation %u in %s area has a different result "
               "to that of the uninstantiated instant case",
               *instantiation_number, area_pointer->identifier);
         warning(log_stream, message, level);

         (*different_results)++;
      }

      (*instantiation_number)++;
   }
   if (*instantiation_number == 0)
      *instantiation_number = 1;

   if (!all_known) {

      /* vector_pointer points to the first UNKNOWN attribute value in the
         instantiation, so set it to YES and instantiate the whole fact
         vector then set it to NO and instantiate the whole fact vector again */

      vector_pointer->attribute_value = YES;
      instantiate(log_stream, distances_stream, report_stream, case_law, area_pointer,
            instantiated_head, facts_head, nearest_result, verbose, instantiation_number,
            different_results, level);

      vector_pointer->attribute_value = NO;
      instantiate(log_stream, distances_stream, report_stream, case_law, area_pointer,
            instantiated_head, facts_head, nearest_result, verbose, instantiation_number,
            different_results, level);
   }

   /* free the memory taken up by the instantiation */

   remove_facts(instantiated_head);
}

static void
add_to_hypothetical_list(
      file log_stream,
      vector_element *new_head,
      distance_type new_distance,
      hypothetical_list_element **hypothetical_list_pointer,
      cardinal count,
      cardinal hypothetical_reports)

/* Inserts the hypothetical pointed to by new_head into the list of
   hypotheticals pointed to by *hypothetical_list_pointer.  The list is kept
   sorted: the nearer a hypothetical is to the instant case, the closer it is
   to the head of the list (the new hypothetical is new_distance from the
   instant case).  *hypothetical_list_pointer points to hypothetical number
   count in the list.  The total number of hypotheticals in the list is not
   allowed to exceed hypothetical_reports. Removes the last hypothetical in
   the list if inserting the new hypothetical makes the list too long. */

{
   hypothetical_list_element *temp_pointer;

   if (count > hypothetical_reports)

      /* *hypothetical_list_pointer points past the end of the list, so
         return without inserting anything into the list */

      return;

   if ((*hypothetical_list_pointer == NULL) || (Relative_Distance(new_distance,
                     (*hypothetical_list_pointer)->nearest_neighbour_distance) ==
               NEARER)) {

      /* the new hypothetical belongs at the head of the list, so allocate
         memory for it, make a copy of it, and put that copy at the head of
         the list */

      if ((temp_pointer =
                  (hypothetical_list_element *) malloc(sizeof(hypothetical_list_element))) ==
            NULL)
         error_exit(log_stream, "malloc failed during hypothetical handling");

      temp_pointer->hypothetical_head = copy_facts(log_stream, new_head);
      temp_pointer->nearest_neighbour_distance = new_distance;
      temp_pointer->next = *hypothetical_list_pointer;
      *hypothetical_list_pointer = temp_pointer;

      /* skip through to the end of the list */

      while ((temp_pointer->next != NULL) && (count < hypothetical_reports)) {
         temp_pointer = temp_pointer->next;
         count++;
      }

      if (temp_pointer->next != NULL) {

         /* there's now one more hypothetical in the list than will be
            required when reports are written, so remove the last
            hypothetical from the list and free the memory it takes up */

         remove_facts(temp_pointer->next->hypothetical_head);
         free(temp_pointer->next);
         temp_pointer->next = NULL;
      }
   } else

      /* the hypothetical does not go at the head of the list, so check
         whether it belongs somewhere in the rest of the list */

      add_to_hypothetical_list(log_stream, new_head, new_distance,
            &(*hypothetical_list_pointer)->next, count + 1, hypothetical_reports);
}

static void
hypothesize(
      file log_stream,
      case_law_specification case_law,
      area *area_pointer,
      attribute *attribute_pointer,
      vector_element *hypothetical_head,
      vector_element *facts_head,
      result *instant_result,
      distance_type instant_distance,
      cardinal *hypothetical_number,
      cardinal hypothetical_reports,
      cardinal hypothetical_changes,
      cardinal level)

/* Makes hypothetical variations to the fact vector pointed to by
   hypothetical_head, allowing no more than hypothetical_changes differences
   (in the known attribute values) between the hypothetical and the instant
   case (the fact vector of which is pointed to by facts_head).  Treats each
   hypothetical as if it were a new instant case, and invokes the Odometer to
   recalculate the distances.

   Builds (for each result) a list of those hypotheticals which are eligible to
   be reported on. A hypothetical is considered eligible to be reported on if
   its nearest result is different to that of the instant case (pointed to by
   instant_result), or if it has the same nearest result but its nearest
   neighbour is nearer the instant case than is that of the instant case
   (which is instant_distance from the instant case). Lists only the nearest
   hypothetical_reports hypotheticals for each result. */

{
   vector_element *vector_pointer = facts_head,
     *hypothetical_pointer,
     *new_head;
   cardinal count;

   if (attribute_pointer == NULL)
      return;

   /* count the known differences between the hypothetical and the instant
      case */

   count = 0;
   for (hypothetical_pointer = hypothetical_head; hypothetical_pointer != NULL;
         hypothetical_pointer = hypothetical_pointer->next) {
      if ((vector_pointer->attribute_value != UNKNOWN) &&
            (hypothetical_pointer->attribute_value != vector_pointer->attribute_value))
         count++;
      vector_pointer = vector_pointer->next;
   }

   if ((hypothetical_changes != 0) && (count == hypothetical_changes))

      /* the maximum number of changes has already been made */

      return;

   new_head = copy_facts(log_stream, hypothetical_head);

   /* find the attribute to change in the new hypothetical */

   count = 1;
   for (hypothetical_pointer = new_head; count < attribute_pointer->number;
         hypothetical_pointer = hypothetical_pointer->next)
      count++;

   if (hypothetical_pointer->attribute_value == UNKNOWN) {

      /* the value of the attribute to change in the new hypothetical is
         UNKNOWN, so ignore it - UNKNOWN values have already been
         instantiated by instantiate() - and continue hypothesizing using the
         next attribute */

      hypothesize(log_stream, case_law, area_pointer, attribute_pointer->next,
            new_head, facts_head, instant_result, instant_distance,
            hypothetical_number, hypothetical_reports, hypothetical_changes, level);

   } else {

      (*hypothetical_number)++;

      /* change the (known) value of the attribute in the new hypothetical */

      if (hypothetical_pointer->attribute_value == YES)
         hypothetical_pointer->attribute_value = NO;
      else
         hypothetical_pointer->attribute_value = YES;

      /* calculate distances without writing anything to the log file or the
         distances file */

      Calculate_Distances(NULL, NULL, area_pointer, case_law, new_head, TRUE, 0, level + 1);

      if (area_pointer->nearest_result == instant_result) {

         /* the hypothetical has the same result as that of the instant case,
            so only add it to the hypothetical list if its nearest neighbour
            is nearer than was that of the instant case */

         if (instant_result->nearest_known_compared_with_unknown != FURTHER) {

            /* the nearest known neighbour is the nearest neighbour (although
               there may be an equidistant case with an unknown distance) */

            if (Relative_Distance(instant_result->nearest_known_case->metrics.distance,
                        instant_distance) == NEARER)
               add_to_hypothetical_list(log_stream, new_head,
                     instant_result->nearest_known_case->metrics.distance,
                     &instant_result->hypothetical_list_head, 1, hypothetical_reports);

         } else {

            /* the nearest unknown neighbour is the nearest neighbour */

            if (Relative_Distance(instant_result->nearest_unknown_case->metrics.distance,
                        instant_distance) == NEARER)
               add_to_hypothetical_list(log_stream, new_head,
                     instant_result->nearest_unknown_case->metrics.distance,
                     &instant_result->hypothetical_list_head, 1, hypothetical_reports);
         }

      } else {

         /* the hypothetical has a different result to that of the instant
            case */

         if (area_pointer->nearest_result->nearest_known_compared_with_unknown !=
               FURTHER)

            /* the nearest known neighbour is the nearest neighbour (although
               there may be an equidistant case with an unknown distance) */

            add_to_hypothetical_list(log_stream, new_head,
                  area_pointer->nearest_result->nearest_known_case->metrics.distance,
                  &area_pointer->nearest_result->hypothetical_list_head, 1,
                  hypothetical_reports);

         else

            /* the nearest unknown neighbour is the nearest neighbour */

            add_to_hypothetical_list(log_stream, new_head,
                  area_pointer->nearest_result->nearest_unknown_case->metrics.distance,
                  &area_pointer->nearest_result->hypothetical_list_head, 1,
                  hypothetical_reports);
      }

      /* hypothesize, using the next attribute, with the unchanged
         hypothetical */

      hypothesize(log_stream, case_law, area_pointer, attribute_pointer->next,
            hypothetical_head, facts_head, instant_result, instant_distance,
            hypothetical_number, hypothetical_reports, hypothetical_changes, level);

      /* ... and with the new hypothetical */

      hypothesize(log_stream, case_law, area_pointer, attribute_pointer->next,
            new_head, facts_head, instant_result, instant_distance,
            hypothetical_number, hypothetical_reports, hypothetical_changes, level);
   }

   /* free the memory taken up by the hypothetical */

   remove_facts(new_head);
}

static void
write_hypotheticals(
      file log_stream,
      file distances_stream,
      file report_stream,
      case_law_specification case_law,
      area *area_pointer,
      vector_element *facts_head,
      boolean verbose,
      boolean same_result,
      hypothetical_list_element *hypothetical_list_pointer,
      cardinal *hypothetical_number,
      cardinal level)

/* Writes reports on the hypotheticals in the list, the head of which is
   pointed to by hypothetical_list_pointer. */

{
   cardinal temp_cardinal;

   /* while there are still hypotheticals in the list ... */

   while (hypothetical_list_pointer != NULL) {

      /* treat the hypothetical as if it were the instant case */

      (*hypothetical_number)++;
      Indent(log_stream, level);
      fprintf(log_stream, "Hypothetical %u is ", *hypothetical_number);
      write_facts(log_stream, hypothetical_list_pointer->hypothetical_head);
      fprintf(log_stream, ".\n");

      Indent(log_stream, level + 4);
      fprintf(log_stream, "   ");
      temp_cardinal = *hypothetical_number / 10;
      while (temp_cardinal != 0) {
         fprintf(log_stream, " ");
         temp_cardinal = temp_cardinal / 10;
      }

      mark_differences(log_stream, hypothetical_list_pointer->hypothetical_head, facts_head);
      fprintf(log_stream, "\n");

      /* calculate the distances again (when they were calculated in
         hypothesize(), nothing was written to the log file or the distances
         file) and write a report on this hypothetical (which also writes to
         the log file) */

      Calculate_Distances(distances_stream, log_stream, area_pointer, case_law,
            hypothetical_list_pointer->hypothetical_head, TRUE, *hypothetical_number,
            level + 1);

      Write_Report(report_stream, log_stream, area_pointer,
            hypothetical_list_pointer->hypothetical_head, facts_head, verbose, TRUE,
            same_result, *hypothetical_number, level + 1);

      hypothetical_list_pointer = hypothetical_list_pointer->next;
   }
}

extern string
Case_Law(
      file log_stream,
      case_law_specification case_law,
      string area_identifier,
      boolean adjust,
      boolean echo,
      boolean inputable_latex,
      boolean verbose,
      cardinal hypothetical_reports,
      cardinal hypothetical_changes,
      cardinal level,
      string distances_filename,
      string weights_filename,
      string report_filename)

/* Determines the "likely result" of the instant case in the area_identifier
   area of the case_law specification, and constructs an argument supporting
   that conclusion.

   Calls the Adjuster, if adjust is TRUE.  Invokes the Consultant to
   interrogate the user as to the attribute values in the instant case.  (The
   Consultant recursively invokes Case_Law(), if required, to resolve open
   textured - external - attributes.)  Calls the Odometer to calculate the
   distances between the instant case and the leading cases, and to determine
   the nearest neighbours and results.  Invokes the Reporter to write a report
   about the instant case.

   Returns the identifier of the "likely result." */

{
   file distances_stream = NULL,
      report_stream = NULL;
   static char filename[Max_Filename_Length];
   static char message[Max_Error_Message_Length];
   vector_element *facts_head;
   area *area_pointer;
   result *result_pointer,
     *instant_result = NULL;
   kase *case_pointer;
   distance_type instant_distance;
   cardinal instantiation_number = 0,
      hypothetical_number = 0,
      different_results = 0,
      hypothetical_count = 0;

   /* find the area with an identifier matching area_identifier */

   for (area_pointer = case_law.area_head;
         (area_pointer != NULL) && strcmp(area_pointer->identifier, area_identifier);
         area_pointer = area_pointer->next);

   if (area_pointer == NULL) {

      /* area_identifier does not match the identifier of any area */

      sprintf(message, "%s area not found", area_identifier);
      error_exit(log_stream, message);
   }
   Indent(log_stream, level);
   fprintf(log_stream, "Area is %s.\n\n", area_identifier);

   if (distances_filename != NULL) {

      /* a distances filename was specified, so open a distances file for
         this area */

      sprintf(filename, "%s-%s%s", distances_filename,
            area_identifier, LaTeX_File_Extension);
      if ((distances_stream = fopen(filename, "w")) == NULL) {
         sprintf(message, "can't open distances file \"%s\"", filename);
         error_exit(log_stream, message);
      }
      Indent(log_stream, level);
      fprintf(log_stream, "Writing distances to \"%s\".\n\n", filename);
   }
   if (report_filename != NULL) {

      /* a report filename was specified, so open a report file for this area */

      sprintf(filename, "%s-%s%s", report_filename,
            area_identifier, LaTeX_File_Extension);
      if ((report_stream = fopen(filename, "w")) == NULL) {
         sprintf(message, "can't open report file \"%s\"", filename);
         error_exit(log_stream, message);
      }
      Indent(log_stream, level);
      fprintf(log_stream, "Writing report to \"%s\".\n\n", filename);
   }
   if (adjust)
      Adjust_Attributes(log_stream, area_pointer, weights_filename, level, inputable_latex);

   /* interrogate the user as to the facts in the instant case */

   if ((facts_head = Get_Facts(log_stream, case_law, area_pointer, adjust, echo, inputable_latex,
                     verbose, hypothetical_reports, hypothetical_changes, level,
                     distances_filename, weights_filename, report_filename)) != NULL) {

      /* the user has entered some facts */

      if (distances_stream != NULL) {

         /* a distances file is open for this area, so write its header */

         fprintf(distances_stream, "%% Distances file\n\n");
         Write_LaTeX_Header(distances_stream, inputable_latex);
      }
      if (report_stream != NULL) {

         /* a report file is open for this area, so write its header */

         fprintf(report_stream, "%% Report file\n\n");
         Write_LaTeX_Header(report_stream, inputable_latex);
      }
      Indent(log_stream, level);
      fprintf(log_stream, "Fact vector is ");
      write_facts(log_stream, facts_head);
      fprintf(log_stream, ".\n\n");

      Calculate_Distances(distances_stream, log_stream, area_pointer,
            case_law, facts_head, FALSE, 0, level + 1);

      /* mark all cases as unsummarized */

      for (result_pointer = area_pointer->result_head; result_pointer != NULL;
            result_pointer = result_pointer->next)
         for (case_pointer = result_pointer->case_head; case_pointer != NULL;
               case_pointer = case_pointer->next)
            case_pointer->summarized = FALSE;

      Write_Report(report_stream, log_stream, area_pointer, facts_head,
            NULL, verbose, FALSE, FALSE, 0, level + 1);

      instant_result = area_pointer->nearest_result;

      if (instant_result->nearest_known_compared_with_unknown != FURTHER)

         /* the nearest known neighbour is the nearest neighbour (although
            there may be an equidistant case with an unknown distance) */

         instant_distance = instant_result->nearest_known_case->metrics.distance;

      else

         /* the nearest unknown neighbour is the nearest neighbour */

         instant_distance = instant_result->nearest_unknown_case->metrics.distance;

      /* instantiate the unknown attribute values in the instant case */

      instantiate(log_stream, distances_stream, report_stream, case_law, area_pointer,
            facts_head, facts_head, instant_result, verbose, &instantiation_number,
            &different_results, level);

      instantiation_number--;

      if (hypothetical_reports != 0) {

         /* the user requested hypothesizing */

         hypothesize(log_stream, case_law, area_pointer, area_pointer->attribute_head,
               facts_head, facts_head, instant_result, instant_distance,
               &hypothetical_number, hypothetical_reports, hypothetical_changes, level);

         /* write the hypotheticals for this result */

         write_hypotheticals(log_stream, distances_stream, report_stream,
               case_law, area_pointer, facts_head, verbose, TRUE,
               instant_result->hypothetical_list_head, &hypothetical_count, level);

         /* write the hypotheticals for all other results */

         for (result_pointer = area_pointer->result_head; result_pointer != NULL;
               result_pointer = result_pointer->next)
            if (result_pointer != instant_result)
               write_hypotheticals(log_stream, distances_stream, report_stream,
                     case_law, area_pointer, facts_head, verbose, FALSE,
                     result_pointer->hypothetical_list_head, &hypothetical_count, level);
      }

      /* write details of the instantiations to the log file */

      if (instantiation_number == 0)
         sprintf(message, "No instantiations");
      else if (different_results == 0) {
         if (instantiation_number == 2)
            sprintf(message, "Both");
         else
            sprintf(message, "All %u", instantiation_number);
         sprintf(message, "%s instantiations have the same nearest result "
               "as does the instant case", message);
      } else
         sprintf(message, "%u of the %u instantiations %s a nearest result "
               "different to that of the instant case", different_results,
               instantiation_number, different_results == 1 ? "has" : "have");
      Write(log_stream, message, ".\n", level, Hang);

      /* write details of the hypothesizing to the log file */

      Indent(log_stream, level);
      if (hypothetical_reports == 0)
         fprintf(log_stream, "No hypotheticals.\n\n");
      else {
         fprintf(log_stream, "Reported on %u hypothetical%s of %u",
               hypothetical_count, hypothetical_count == 1 ? "" : "s", hypothetical_number);
         if (hypothetical_changes != 0)
            fprintf(log_stream, " (limit of %u change%s)", hypothetical_changes,
                  hypothetical_changes == 1 ? "" : "s");
         fprintf(log_stream, ".\n\n");
      }

      if (distances_stream != NULL)

         /* a distances file is open for this area, so write its trailer */

         Write_LaTeX_Trailer(distances_stream, inputable_latex);

      if (report_stream != NULL)

         /* a report file is open for this area, so write its trailer */

         Write_LaTeX_Trailer(report_stream, inputable_latex);
   }
   if (distances_filename != NULL) {

      /* a distances filename was specified, so close the distances file */

      sprintf(filename, "%s-%s%s", distances_filename,
            area_identifier, LaTeX_File_Extension);
      if (fclose(distances_stream) == EOF) {
         sprintf(message, "can't close distances file \"%s\"", filename);
         error_exit(log_stream, message);
      }
   }
   if (report_filename != NULL) {

      /* a report filename was specified, so close the report file */

      sprintf(filename, "%s-%s%s", report_filename,
            area_identifier, LaTeX_File_Extension);
      if (fclose(report_stream) == EOF) {
         sprintf(message, "can't close report file \"%s\"", filename);
         error_exit(log_stream, message);
      }
   }

   /* return the identifier of the "likely result" */

   if (instant_result == NULL)
      return NULL;
   else
      return instant_result->identifier;
}

Other SHYSTER modules: Shyster, Statutes, Tokenizer, Parser, Dumper, Checker, Scales, Adjuster, Consultant, Odometer and Reporter.
Copyright noticeValid HTML 4.0
Home page:  <http://www.popple.net/james/>
E-mail:  <james@popple.net>
Last modified:  30 April 1995