home *** CD-ROM | disk | FTP | other *** search
/ Programmer's ROM - The Computer Language Library / programmersrom.iso / ada / ai / expert.ada < prev    next >
Encoding:
Text File  |  1988-05-03  |  35.3 KB  |  1,049 lines

  1.  
  2.  
  3. -------- SIMTEL20 Ada Software Repository Prologue ------------
  4. --                                                           -*
  5. -- Unit name    : EXPERT
  6. -- Version      : 1.0
  7. -- Author       : Alan McDonley
  8. --              : Texas Instruments
  9. --              : 
  10. --              : 
  11. -- DDN Address  : N/A
  12. -- Copyright    : (c)
  13. -- Date created :  11 Sep 85
  14. -- Release date :  3 Dec 85
  15. -- Last update  :  3 Dec 85
  16. -- Machine/System Compiled/Run on : VAX 11/785, VMS 4.1, DEC Ada
  17. --                                                           -*
  18. ---------------------------------------------------------------
  19. --                                                           -*
  20. -- Keywords     :  Expert System, Artificial Intelligence
  21. ----------------:
  22. --
  23. -- Abstract     :  
  24. ----------------:  
  25. -- EXPERT is a backward chaining or goal driven expert system.  It is
  26. --based on two articles, first Sept 1981 BYTE (Duda and Gaschnig) 
  27. --published the expert system in BASIC 
  28. --skirting the use of recursion, second Jan/Feb 85 issue of 
  29. --JOURNAL OF PASCAL,ADA, & MODULA-2 (Darrell Morgeson) 
  30. --published in Modula-2 with recursion 
  31. --implemented.  The listing had one logic error which caused pointer 
  32. --explosion on the last hypothesis in the GETRULE routine.  This 
  33. --implementation follows the MODULA-2 design completely and
  34. --was not designed from the ground up in Ada.  Many improvements would 
  35. --be possible if more time permitted my working on this.
  36. --                                                           -*
  37. ------------------ Revision history ---------------------------
  38. --                                                           -*
  39. -- DATE         VERSION    AUTHOR                  HISTORY
  40. -- 12/3/85      1.0     Alan McDonley           Initial Release
  41. --                                                           -*
  42. ------------------ Distribution and Copyright -----------------
  43. --                                                           -*
  44. -- This prologue must be included in all copies of this software.
  45. --
  46. -- This software is released to the Ada community.
  47. -- This software is released to the Public Domain (note:
  48. --   software released to the Public Domain is not subject
  49. --   to copyright protection).
  50. -- Restrictions on use or distribution:  NONE
  51. --                                                           -*
  52. ------------------ Disclaimer ---------------------------------
  53. --                                                           -*
  54. -- This software and its documentation are provided "AS IS" and
  55. -- without any expressed or implied warranties whatsoever.
  56. -- No warranties as to performance, merchantability, or fitness
  57. -- for a particular purpose exist.
  58. --
  59. -- Because of the diversity of conditions and hardware under
  60. -- which this software may be used, no warranty of fitness for
  61. -- a particular purpose is offered.  The user is advised to
  62. -- test the software thoroughly before relying on it.  The user
  63. -- must assume the entire risk and liability of using this
  64. -- software.
  65. --
  66. -- In no event shall any person or organization of people be
  67. -- held responsible for any direct, indirect, consequential
  68. -- or inconsequential damages or lost profits.
  69. --                                                           -*
  70. -------------------END-PROLOGUE--------------------------------
  71.  
  72. with text_io; use text_io;
  73. package DYN is
  74.  
  75. ------------------------------------------------------------------------------
  76. --  This is a package of several string manipulation functions based on     --
  77. -- a built-in dynamic string type DYN_STRING.  It is an adaptation and      --
  78. -- extension of the package proposed by Sylvan Rubin of Ford Aerospace and  --
  79. -- Communications Corporation in the Nov/Dec 1984 issue of the Journal of   --
  80. -- Pascal, Ada and Modula-2.  Some new functions have been added, the       --
  81. -- SUBSTRING function has been modified to permit it to return the right    --
  82. -- part of a string if the third parameter is permitted to default, and     --
  83. -- much of the body code has been rewritten.                                --
  84. ------------------------------------------------------------------------------
  85. -- R.G. Cleaveland 07 December 1984:                                        --
  86. --  Implementation initially with the Telesoft Ada version                  --
  87. -- This required definition of the DYN_STRING type without use of a         --
  88. -- discriminant; an arbitrary maximum string length was chosen.  This       --
  89. -- should be changed when an improved compiler is available.                --
  90. ------------------------------------------------------------------------------
  91. -- Richard Powers 03 January 1985:                                          --
  92. -- changed to be used with a real compiler.                                 --
  93. -- Some of the routines removed by my whim.                                 --
  94. ------------------------------------------------------------------------------
  95. -- Richard Powers 26 January 1985:
  96. -- Added UPPER_CASE function
  97. ------------------------------------------------------------------------------
  98. -- Alan McDonley 28 AUG 1985;
  99. -- Added overload for GET_LINE, PUT, OPEN
  100. ------------------------------------------------------------------------------
  101.  
  102. type DYN_STRING is private;
  103.  
  104. STRING_TOO_SHORT: exception;
  105.  
  106. function D_STRING(CHAR: character)  return DYN_STRING;
  107.         -- Creates a one-byte dynamic string of contents CHAR.
  108.  
  109. function D_STRING(STR : string   )  return DYN_STRING;
  110.         -- Creates a dynamic string of contents STR.
  111.  
  112. function D_STRING(INT : in INTEGER ) return DYN_STRING;
  113.         -- Creates a dynamic string of contents INT.
  114.  
  115. -- The following four functions convert from dynamic strings to the
  116. -- desired representation:
  117. function CHAR(DSTR: DYN_STRING) return character;
  118. function STR (DSTR: DYN_STRING) return string;
  119. function INT (DSTR: DYN_STRING) return integer;
  120. function FLT (DSTR: DYN_STRING) return float;
  121.  
  122. function LENGTH(DSTR: DYN_STRING) return natural;
  123. function "<" (DS1, DS2: DYN_STRING) return boolean;
  124. function "&" (DS1, DS2: DYN_STRING) return DYN_STRING;
  125.  
  126. function SUBSTRING (DSTR: DYN_STRING;      -- Returns a subpart of this string
  127.                     START  : natural;      -- starting at this position
  128.                     LENGTH : natural := 0) -- and of this length.
  129.                 return DYN_STRING;
  130.                 -- if LENGTH is zero or not specified, the remainder of the
  131.                 -- string is returned (eg the "RIGHT" function).
  132.  
  133. function INDEX (SOURCE_STRING,              --If this string contains
  134.                 PATTERN_STRING: DYN_STRING; --this string starting at or AFTER
  135.                 START_POS: integer)         --this position, the position of
  136.                 return integer;             --such start is returned.
  137.                 -- If the string lengths prohibit the search -1 is returned.
  138.                 -- If no match was found, 0 is returned.
  139.                 -- (This is like the INSTR function of BASIC).
  140.  
  141. function RINDEX (SOURCE_STRING,             --If this string contains
  142.                 PATTERN_STRING: DYN_STRING; --this string starting at or BEFORE
  143.                 START_POS: integer)         --this position, the position of
  144.                 return integer;             --such start is returned.
  145.                 -- If the string lengths prohibit the search -1 is returned.
  146.                 -- If no match was found, 0 is returned.
  147.  
  148. function UPPER_CASE(STRG : in DYN.DYN_STRING) return STRING;
  149.                 -- Return the input string in upper case
  150.  
  151.  
  152. Procedure GET_LINE (filename: in text_io.file_type;
  153.                     item    : out dyn.dyn_string;
  154.                     last    : out natural) ;
  155.  
  156. Procedure GET_LINE (item    : out dyn.dyn_string;
  157.                     last    : out natural) ;
  158.  
  159. Procedure PUT (filename: in text_io.file_type;
  160.                item    : in dyn.dyn_string);
  161.  
  162. Procedure PUT (item    : in dyn.dyn_string);
  163.  
  164. Procedure OPEN (filename : in out text_io.file_type;
  165.                 mode : in  text_io.file_mode;
  166.                 filenm   : in  dyn_string);
  167.  
  168. private
  169.  
  170.         type STRING_CONTENTS(SIZE : natural := 0) is
  171.            record
  172.                DATA: string(1..SIZE);
  173.            end record;
  174.  
  175.         type DYN_STRING is access STRING_CONTENTS;
  176.  
  177. end DYN;
  178.  
  179. ----------------------------------------------------------------------------
  180.  
  181. package body DYN is
  182.  
  183. package MY_INTEGER_IO is new INTEGER_IO(INTEGER);
  184.  
  185. package MY_FLOAT_IO is new FLOAT_IO(FLOAT);
  186.  
  187. function "&" (DS1, DS2: DYN_STRING) return DYN_STRING is
  188.         DS3 : DYN_STRING;
  189.     begin
  190.         DS3 := new STRING_CONTENTS(DS1.SIZE+DS2.SIZE);
  191.         DS3.DATA(1..DS3.SIZE):=   DS1.DATA(1..DS1.SIZE)
  192.                                 & DS2.DATA(1..DS2.SIZE);
  193.         return DS3;
  194.     end "&";
  195.  
  196. function D_STRING(CHAR: character)  return DYN_STRING is
  197.         DS : DYN_STRING;
  198.     begin
  199.         DS := new STRING_CONTENTS(SIZE=>1);
  200.         DS.DATA(1) := CHAR;
  201.         return DS;
  202.     end D_STRING;
  203.  
  204. function D_STRING(STR : string   )  return DYN_STRING is
  205.         DS : DYN_STRING;
  206.     begin
  207.         DS := new STRING_CONTENTS(SIZE => STR'length);
  208.         DS.DATA(1..DS.SIZE)  := STR;
  209.         return DS;
  210.     end D_STRING;
  211.  
  212. function D_STRING( INT : in INTEGER ) return DYN_STRING is
  213.         DS : DYN_STRING;
  214.     begin
  215.         DS:=D_STRING(integer'image(int));
  216.         return DS;
  217.     end D_STRING;
  218.                  
  219. function CHAR(DSTR: DYN_STRING) return character is
  220.     begin
  221.         return DSTR.DATA(1);
  222.     end CHAR;
  223.  
  224. function STR (DSTR: DYN_STRING) return string is
  225.     begin
  226.         return DSTR.DATA(1..DSTR.SIZE);
  227.     end STR;
  228.  
  229. function INT (DSTR: DYN_STRING) return integer is
  230.         V: integer;
  231.         L: positive;
  232.     begin
  233.         MY_INTEGER_IO.get(STR(DSTR),V,L);
  234.         return V;
  235.     end INT;
  236.  
  237. function FLT (DSTR: DYN_STRING) return float is
  238.         V: float;
  239.         L: positive;
  240.     begin
  241.         MY_FLOAT_IO.get(STR(DSTR),V,L);
  242.         return V;
  243.     end FLT;
  244.  
  245. function LENGTH(DSTR: DYN_STRING) return natural is
  246.     begin
  247.         return DSTR.SIZE;
  248.     end LENGTH;
  249.  
  250. function "<" (DS1, DS2: DYN_STRING) return boolean is
  251.     begin
  252.         if STR(DS1) < STR(DS2)
  253.         then return (TRUE);
  254.         else return (FALSE);
  255.         end if;
  256.     end "<";
  257.  
  258. function SUBSTRING (DSTR: DYN_STRING;
  259.                     START  : natural;
  260.                     LENGTH : natural := 0)
  261.                                            return DYN_STRING is
  262.         DS: DYN_STRING;
  263.         L : natural := LENGTH;
  264.     begin
  265.         if (START < 1) or (START > DSTR.SIZE)
  266.         then raise CONSTRAINT_ERROR;
  267.         else if L = 0
  268.              then L := DSTR.SIZE-START+1;
  269.              end if;
  270.              if DSTR.SIZE < START + L - 1
  271.              then  raise STRING_TOO_SHORT;
  272.              else
  273.                    DS := new STRING_CONTENTS(L);
  274.                    DS.DATA(1..L) := DSTR.DATA(START..START+L-1);
  275.                    return DS;
  276.              end if;
  277.          end if;
  278.     end SUBSTRING;
  279.  
  280. function INDEX(SOURCE_STRING, PATTERN_STRING: DYN_STRING;
  281.                         START_POS: integer) return integer is
  282.         NO_MATCH        : integer := 0;
  283.         NO_FIT          : integer := -1;
  284.     begin
  285.         if SOURCE_STRING.SIZE < PATTERN_STRING.SIZE + START_POS - 1
  286.         or START_POS < 1
  287.         then return NO_FIT;
  288.         end if;
  289.         for I in START_POS..SOURCE_STRING.SIZE-PATTERN_STRING.SIZE+1 loop
  290.             if SOURCE_STRING.DATA(I..I+PATTERN_STRING.SIZE-1)
  291.                = PATTERN_STRING.DATA(1..PATTERN_STRING.SIZE)
  292.             then return I;
  293.             end if;
  294.         end loop;
  295.         return NO_MATCH;
  296.     end INDEX;
  297.  
  298. function RINDEX(SOURCE_STRING, PATTERN_STRING: DYN_STRING;
  299.                         START_POS: integer) return integer is
  300.         NO_MATCH        : integer := 0;
  301.         NO_FIT          : integer := -1;
  302.     begin
  303.         if SOURCE_STRING.SIZE < PATTERN_STRING.SIZE + START_POS - 1
  304.         or START_POS < 1
  305.         then return NO_FIT;
  306.         end if;
  307.         for I in reverse 1..START_POS loop
  308.             if SOURCE_STRING.DATA(I..I+PATTERN_STRING.SIZE-1)
  309.                = PATTERN_STRING.DATA(1..PATTERN_STRING.SIZE)
  310.             then return I;
  311.             end if;
  312.         end loop;
  313.         return NO_MATCH;
  314.     end RINDEX;
  315.  
  316.     function UPPER_CASE(STRG : in DYN.DYN_STRING) return STRING is
  317.         ANSWER : STRING(1..LENGTH(STRG));
  318.     begin
  319.         ANSWER := STR(STRG);
  320.         for I in 1..LENGTH(STRG) loop
  321.             if (ANSWER(I) >= 'a') and (ANSWER(I) <= 'z') then
  322.                 ANSWER(I) := CHARACTER'VAL(CHARACTER'POS(ANSWER(I)) -
  323.                     CHARACTER'POS('a') + CHARACTER'POS('A'));
  324.             end if;
  325.         end loop;
  326.         return ANSWER;
  327. end UPPER_CASE;
  328.  
  329. Procedure GET_LINE (filename: in text_io.file_type;
  330.                     item    : out dyn.dyn_string;
  331.                     last    : out natural) is
  332.  
  333. static_string_var: string(1..255);
  334. lastchar:natural;
  335.  
  336. BEGIN
  337.   Text_io.get_line(filename,static_string_var,lastchar);
  338.   item:=dyn.substring(dyn.d_string(static_string_var),1,lastchar);
  339.   last:=lastchar;
  340. END;
  341.  
  342.  
  343. Procedure GET_LINE (item    : out dyn.dyn_string;
  344.                     last    : out natural) is
  345.  
  346. static_string_var: string(1..255);
  347. lastchar:natural;
  348.  
  349. BEGIN
  350.   Text_io.get_line(static_string_var,lastchar);
  351.   item:=dyn.substring(dyn.d_string(static_string_var),1,lastchar);
  352.   last:=lastchar;
  353. END;
  354.  
  355. Procedure PUT (filename : in text_io.file_type;
  356.                item     : in dyn.dyn_string) is
  357.  
  358. BEGIN
  359.    put(filename,STR(item));
  360. END;
  361.  
  362. Procedure PUT (item  : in dyn.dyn_string) is
  363.  
  364. BEGIN
  365.    put(STR(item));
  366. END;
  367.  
  368. Procedure OPEN (filename : in out text_io.file_type;
  369.                 mode     : in  text_io.file_mode;
  370.                 filenm   : in  dyn_string) is
  371. BEGIN
  372.   text_io.open(filename,mode,STR(filenm));
  373. end;
  374.  
  375. end DYN;
  376.  
  377.  
  378.  
  379.  
  380. --
  381. -- EXPERT SYSTEM
  382. --
  383.  
  384. with TEXT_IO,DYN;
  385.  
  386. procedure  EXPERT IS
  387.  
  388. ------------------------------------------------------------------------------ 
  389. --GENERAL INFORMATION:
  390. --
  391. -- EXPERT is a backward chaining or goal driven expert system.  It is based on
  392. --two articles, first Sept 1981 BYTE (Duda and Gaschnig) 
  393. --published the expert system in BASIC 
  394. --skirting the use of recursion, second Jan/Feb 85 issue of 
  395. --JOURNAL OF PASCAL,ADA, & MODULA-2 (Darrell Morgeson) 
  396. --published in Modula-2 with recursion 
  397. --implemented.  The listing had one logic error which caused pointer 
  398. --explosion on the last hypothesis in the GETRULE routine.  This 
  399. --implementation follows the MODULA-2 design completely and
  400. --was not designed from the ground up in Ada.  Many improvements would 
  401. --be possible if more time permitted my working on this.
  402.  
  403. --AUTHOR: (sort of, translator) Alan McDonley (303) 593-7528 (home phone)
  404. --DATE: 11 SEPT 85
  405. --
  406. ------------------------------------------------------------------------------ 
  407.  
  408. --INPUTS:
  409. --
  410. -- Expert requires an expert rulebase.  The rule base consists of the following
  411. -- items in the particular format:
  412. -- 
  413. --DATABASE NAME (thrown away by program)
  414. --HYPOTHESIS 1  (Must fit on one line, any characters including spaces legal)
  415. --.
  416. --.
  417. --HYPOTHESIS n
  418. --               (blank line to signify end of all hypothesis)
  419. --IF,ANTECEDENT 1[,ANTECEDENT n][,CONCLUSION n],THEN,CONCLUSION 1,
  420. --IF,.....,CONCLUSION n, (must be a comma after last conclusion)
  421. --
  422. --  If you put spaces after comma be careful to note that the space becomes 
  423. --  part of symbol and that the symbols "IF" and "THEN" may not have a space
  424. --  preceding them as they are defined by EXPERT without the spaces.
  425. -- 
  426. --  Each Hypothesis must appear as a conclusion at least once, program will 
  427. --  check for this and halt cleanly if not true.
  428. --
  429. --  Rules may cross line boundry at any comma (start of a symbol). See Animals
  430. --  rulebase for example.
  431. --
  432. --  Blank lines may be inserted after any comma (useful to separate long rules)
  433. ------------------------------------------------------------------------------ 
  434.  
  435. --EXAMPLE RULE BASES: (remove -- from each line to use)
  436.  
  437. --THIS IS AN ANIMAL RULE SET
  438. --IS ALBATROSS
  439. --IS PENGUIN
  440. --IS OSTRICH
  441. --IS ZEBRA
  442. --IS GIRAFFE
  443. --IS TIGER
  444. --IS CHEETAH
  445.  
  446. --IF,HAS HAIR,THEN,IS MAMMAL,
  447. --IF,GIVES MILK,THEN,IS MAMMAL,
  448. --IF,HAS FEATHERS,THEN,IS BIRD,
  449. --IF,FLIES,LAYS EGGS,THEN,IS BIRD,
  450. --IF,EATS MEAT,THEN,IS CARNIVORE,
  451. --IF,HAS POINTED TEETH,HAS CLAWS,HAS FORWARD EYES,THEN,IS CARNIVORE,
  452. --IF,IS MAMMAL,HAS HOOFS,THEN,IS UNGULATE,
  453. --IF,IS MAMMAL,CHEWS CUD,THEN,IS UNGULATE,
  454. --IF,IS MAMMAL,IS CARNIVORE,HAS TAWNY COLOR,HAS BLACK STRIPES,THEN,IS TIGER,
  455. --IF,IS MAMMAL,IS CARNIVORE,HAS TAWNY COLOR,HAS DARK SPOTS,
  456. --THEN,IS CHEETAH,
  457. --IF,IS UNGULATE,HAS LONG NECK,HAS LONG LEGS,HAS DARK SPOTS,
  458. --THEN,IS GIRAFFE,
  459. --IF,IS UNGULATE,HAS BLACK STRIPES,THEN,IS ZEBRA,
  460. --IF,IS BIRD,DOES NOT FLY,HAS LONG NECK,IS BLACK AND WHITE,
  461. --THEN,IS OSTRICH,
  462. --IF,IS BIRD,DOES NOT FLY,SWIMS,IS BLACK AND WHITE,THEN,IS PENGUIN,
  463. --IF,IS BIRD,FLIES WELL,THEN,IS ALBATROSS,
  464.  
  465. ------------------------------------------------------------------------------ 
  466.  
  467. --EXAMPLE RULE BASE: (remove -- from each line to use)
  468.  
  469. --FUSION DATABASE FROM "THE BUTTON" BY DANIEL FORD
  470. --DEFINITE SS-18 LAUNCH
  471. --DEFINITE SOFT TARGET ATTACK
  472.  
  473. --IF,DSP EAST REPORTS,SOUTH CENTRAL SIBERIA LAUNCH,THEN,PROBABLE SS-18 LAUNCH,
  474. --IF,DSP EAST REPORTS,MONGOLIAN BORDER LAUNCH,THEN,PROBABLE SS-11 LAUNCH,
  475. --IF,PROBABLE SS-11 LAUNCH,THEN,PROBABLE SOFT TARGET ATTACK,
  476. --IF,DEFINITE SS-11 LAUNCH,THEN,DEFINITE SOFT TARGET ATTACK,
  477. --IF,PROBABLE SS-18 LAUNCH,ALASKAN RADAR SHOWS INCOMING ICBM,THEN,
  478. --DEFINITE SS-18 LAUNCH,
  479. --IF,PROBABLE SS-11 LAUNCH,FYLINGDALES MOOR-ENGLAND RADAR SHOWS INCOMING ICBM,
  480. --THEN,DEFINITE SS-11 LAUNCH,
  481.  
  482. ------------------------------------------------------------------------------ 
  483. --OPERATION:
  484. --
  485. --To use compile MYDYN.ADA, then EXPERT.ADA, then link EXPERT.
  486. --RUN EXPERT
  487. --enter name of rulebase 
  488. --answer questions
  489.  
  490. ------------------------------------------------------------------------------ 
  491. --THEORY OF OPERATION:
  492. --
  493. --EXPERT loads the rulebase, creating pointers to each symbol, rule and
  494. --hypothesis.  EXPERT attempts to prove the last hypothesis first.  To prove
  495. --an hypothesis, the rules are searched for one having the hypothesis as a 
  496. --conclusion, when found, VERIFIED attempts to verify that rule by calling
  497. --VERIFIED recursively to verify each antecedent of that rule.  When all 
  498. --antecedents of a rule are verified true, EXPERT deduces the rule is true,
  499. --If the conclusion of the rule is an hypothesis then execution stops.
  500. --If any antecedent of a rule is false, EXPERT searches for another rule 
  501. --to prove the conclusion.
  502. --EXAMPLE:
  503. --for rulebase:
  504. --TEST RULEBASE
  505. --HYPOTHESIS 1
  506. --
  507. --IF,ANTECEDENT 1,THEN,CONCLUSION 1,
  508. --IF,ANTECEDENT 2,CONCLUSION 1,THEN,HYPOTHESIS 1,
  509. --
  510. --EXPERT begins by looking for a rule with HYPOTHESIS 1 as its conclusion and 
  511. --finds rule 2.  To verify rule 2 it attempts to verify the first antecedent
  512. --which is ANTECEDENT 2.  ANTECEDENT 2 does not appear as a conclusion in any
  513. --other rule and so the user is asked to verify (Is this true? ANTECEDENT 2) 
  514. --the antecedent.  The user may respond Y or y for YES, N or n for NO, or
  515. --E or e for EXPLAIN.  Requesting an explanation at this point will print:
  516. -- 
  517. --TRYING TO PROVE RULE 2:
  518. --IF ANTECEDENT 2
  519. --   CONCLUSION 1
  520. --THEN
  521. --   HYPOTHESIS 1
  522. --
  523. --Is this true? ANTECEDENT 2?
  524. -- 
  525. --If the user responds yes, then the symbol value is set to true and the 
  526. --next antecedent of rule 2 ( CONCLUSION 1) is verified.  To verify
  527. --CONCLUSION 1 the rule set is searched for a rule with CONCLUSION 1 as the
  528. --conclusion.  Rule 1 is found.  So the first antecedent of rule 1 is verified
  529. --by searching the ruleset for a rule with ANTECEDENT 1 as the conclusion.  None
  530. --is found, so the user is asked to verify it.  If the user answers yes (true)
  531. -- then EXPERT deduces rule 1 (CONCLUSION 1) to be true, and subsequently
  532. -- deduces rule 2 (HYPOTHESIS 1) to be true since both antecedents (ANTECEDENT 1
  533. -- and CONCLUSION 1) are true.  Execution stops after displaying the deductions.
  534. -- Backward chaining or goal driven chaining is the process of starting with
  535. -- a hypothesis and finding ways to prove it true, where as forward chaining
  536. -- would start with a known antecedent, then another antecedent (possibly 
  537. -- unrelated to the hypothesis that the first antecedent is part of) 
  538. -- and prove one or more hypothesis.
  539. --
  540. --        BACKWARD                       FORWARD
  541. --
  542. --        goal                    false antecedent  
  543. --         |                          |          
  544. --      false antecedent              |              antecedent
  545. --         |                          |                  |     \
  546. --      new goal                     no goal         antecedent  \
  547. --         |                                             |        other goals
  548. --      antecedent                                   proven goal
  549. --         |
  550. --      antecedent
  551. --         |
  552. --      proven goal
  553. --
  554. -- Each has a particular purpose and sometimes mixing the two is the best.
  555. -- Forward chaining would work well on parallel processors, and where random
  556. -- data is coming in.
  557. -- Backward Chaining works well were requested data or queued systems are 
  558. -- involved.
  559. -- 
  560. -- Symbols are stored in a binary tree with no duplicate symbols.  Rules are
  561. -- stored as a list of pointers to the symbols belonging to the rule.
  562. -- Symbol values begin as U or Unknown and gradually are proven (T or true) or
  563. -- disproved (F or False).
  564. --
  565. -- Current limits of the array of symbol pointers and the array of rule pointers
  566. -- are set to 100 rules containing no more than 250 symbols.  These of course 
  567. -- are easily modified.
  568. --
  569. ------------------------------------------------------------------------------ 
  570. ------------------------------------------------------------------------------ 
  571.  
  572. use TEXT_IO,DYN;
  573.  
  574. Package INTEGER_IO is new TEXT_IO.INTEGER_IO(INTEGER);
  575.  
  576. MAXSTRGSRULES : constant := 250;
  577. MAXRULES : constant := 100;
  578.  
  579. subtype RULRANGE is integer range 0..MAXRULES;
  580. subtype PTRRANGE is integer range 1..MAXSTRGSRULES;
  581.  
  582. type HYPTTYPE;
  583. type HYPTPTR  is access HYPTTYPE;
  584.  
  585. type HYPTTYPE is record    
  586.         SYMBOL: DYN_STRING; 
  587.         NEXT  : HYPTPTR;
  588. end record;
  589.  
  590. type SYMBOLTYPE;
  591. type SYMBOLPTR is access SYMBOLTYPE;
  592. type SYMBOLTYPE is record
  593.         SYMBOL : DYN_STRING ;
  594.         VALUE  : CHARACTER;
  595.         LEFT, RIGHT : SYMBOLPTR;
  596. end record;
  597.  
  598. HYPTHDR, HYPTTAIL : HYPTPTR;
  599. RULES : array (PTRRANGE'first..PTRRANGE'last) of SYMBOLPTR;
  600. NUMRULES : RULRANGE;
  601. RULEPTR : array (RULRANGE'first..RULRANGE'last) of PTRRANGE;
  602.  
  603. INFILE : TEXT_IO.FILE_TYPE;
  604. IN_FILE_NAME : DYN_STRING;
  605. RESPONSE : DYN_STRING;
  606. RESPONSE_LEN : natural;
  607.  
  608. Function CompareStr(str1,str2: in DYN_STRING) return integer is
  609. -- If two dynamic strings are of same length and contents return 0,
  610. -- else order the strings
  611. begin
  612.   if STR(STR1) = STR( STR2) then return (0);
  613.   elsif STR(STR1) < STR( STR2) then return (-1);
  614.   else return( 1 );
  615.   end if;
  616. end CompareStr;
  617.  
  618. Procedure GETRULES is
  619.  
  620. --GET HYPOTHESIS and RULES from rulebase
  621.  
  622. NUMSTRGSRULES : PTRRANGE;  --current number of symbols in rules
  623. ROOT : SYMBOLPTR; -- root of symbol binary tree
  624. STRG : DYN_STRING; 
  625. SSTRG: DYN_STRING;
  626.  
  627.    Procedure MAKEHYPTLIST (SYMBOL : DYN_STRING) is
  628.    --create a circular list of hypothesis strings
  629.  
  630.    HYPT : HYPTPTR;
  631.    begin
  632.      HYPT :=new HYPTTYPE;
  633.      If HYPTHDR = Null then
  634.        HYPTHDR := HYPT;
  635.        HYPTTAIL:= HYPT;
  636.      else
  637.        HYPTTAIL.NEXT := hypt;
  638.        HYPTTAIL := HYPT;
  639.      end if;
  640.      HYPT.SYMBOL := SYMBOL;
  641.      HYPT.NEXT := HYPTHDR;
  642.    end MAKEHYPTLIST;
  643.  
  644.    Procedure SEARCH( CURPTR, 
  645.                      PREVPTR : in out SYMBOLPTR; 
  646.                      SYMBOL  : DYN_STRING;
  647.                      FOUND   : in out boolean ) is
  648.    -- searches the binary tree containing the rule symbols to see if the 
  649.    -- symbol already exists.  If not then prevptr points to the node at
  650.    -- which the next symbol will be inserted
  651.    
  652.    begin
  653.     FOUND := false;
  654.     While not( FOUND) and (CURPTR /= NULL) loop
  655.       PREVPTR := CURPTR;
  656.       If CompareStr(SYMBOL,CURPTR.SYMBOL) = 0 then FOUND := true;
  657.       elsif CompareStr(SYMBOL,CURPTR.SYMBOL) < 0 then 
  658.         CURPTR :=CURPTR.LEFT ;
  659.       else CURPTR := CURPTR.RIGHT;
  660.       end if;
  661.     end loop;
  662.    end SEARCH;
  663.  
  664.   Procedure MAKESYMBOL( ptr  : in out SYMBOLPTR;
  665.                         strg : DYN_STRING) is
  666.   --creates a symbol node, fills the node with the string (antecedent or 
  667.   --conclusion), then sets symbol value to unknown, and links the symbol into
  668.   -- the binary symbol tree.
  669.  
  670.   NEWPTR : SYMBOLPTR;
  671.   
  672.   begin
  673.     NEWPTR := new SYMBOLTYPE;
  674.     NEWPTR.SYMBOL := STRG;
  675.     NEWPTR.VALUE := 'U' ;
  676.     NEWPTR.LEFT := NULL;
  677.     NEWPTR.RIGHT:= NULL;
  678.     PTR := NEWPTR;
  679.   end MAKESYMBOL;
  680.  
  681.   Procedure LOADRULES( STRG : DYN_STRING ) is
  682.  
  683.   -- parses a rule extracting symbols and puting them in rule tree
  684.  
  685.   LOCAL_STRG : DYN_STRING := STRG;
  686.   CURPTR,PREVPTR : SYMBOLPTR;
  687.   SYMBOL : DYN_STRING;
  688.   FOUND : BOOLEAN := false;
  689.   ENDSYMBOL : natural;
  690.  
  691.   begin
  692.     while LENGTH(LOCAL_STRG)>0 loop    
  693.       ENDSYMBOL := dyn.index(LOCAL_STRG,D_STRING(','),1); -- find  comma
  694.       if ENDSYMBOL > 0  then 
  695.         SYMBOL := dyn.SUBSTRING( LOCAL_STRG,1,ENDSYMBOL-1); --get 
  696.                                                         --symbol,leave comma 
  697.         if dyn.LENGTH(LOCAL_STRG)> ENDSYMBOL then --if more to string
  698.           LOCAL_STRG := dyn.SUBSTRING(LOCAL_STRG,ENDSYMBOL+1,0);--remove symbol
  699.         else LOCAL_STRG :=dyn.D_STRING(""); --end of string
  700.         end if;
  701.  
  702.         if root = null then
  703.           -- begins the binary tree
  704.           MAKESYMBOL( ROOT, SYMBOL);
  705.       CURPTR := ROOT;
  706.     else
  707.       CURPTR := ROOT;
  708.       SEARCH (CURPTR,PREVPTR,SYMBOL,FOUND);
  709.       If not found then
  710.         MAKESYMBOL (CURPTR, SYMBOL);
  711.         If CompareStr(PREVPTR.SYMBOL,SYMBOL) > 0 then
  712.           PREVPTR.LEFT := CURPTR;
  713.         else
  714.           PREVPTR.RIGHT := CURPTR;
  715.         end if;
  716.       end if;
  717.     end if;
  718.     if CompareStr(CURPTR.SYMBOL, D_STRING("IF")) = 0 then
  719.       RULEPTR (NUMRULES) := NUMSTRGSRULES; -- put first antecedents symbol 
  720.                                                -- number as start of rule
  721.       NUMRULES := NUMRULES + 1;
  722.     end if;
  723.     RULES (NUMSTRGSRULES) := CURPTR;
  724.     NUMSTRGSRULES := NUMSTRGSRULES + 1;
  725.       end if;
  726.     end loop;
  727.   end LOADRULES;
  728.  
  729. begin --GETRULES
  730.   HYPTHDR := null;
  731.   HYPTTAIL := null;
  732.  
  733.   TEXT_IO.PUT("ENTER NAME OF RULE BASE: ");
  734.   DYN.GET_LINE(IN_FILE_NAME, RESPONSE_LEN );
  735.   DYN.OPEN(INFILE,TEXT_IO.IN_FILE,IN_FILE_NAME);
  736.   DYN.GET_LINE(INFILE,STRG,RESPONSE_LEN); -- dispose of header line
  737.  
  738.   loop
  739.     DYN.GET_LINE( INFILE, SSTRG, RESPONSE_LEN); --read first hypothesis
  740.     If RESPONSE_LEN = 0 then exit ; end if;
  741.     MAKEHYPTLIST(SSTRG);
  742.   end loop;
  743.  
  744.   NUMRULES := 1;
  745.   NUMSTRGSRULES:=1;
  746.   ROOT:= NULL;
  747.   loop
  748.     DYN.GET_LINE( INFILE, STRG, RESPONSE_LEN); -- read a rule line
  749.     If RESPONSE_LEN >0 then  -- skip blank lines between rules if any
  750.       LOADRULES (STRG);
  751.     end if;
  752.     If TEXT_IO.END_OF_FILE(INFILE) then exit; end if;  -- if no more rules exit
  753.   end loop;
  754.  
  755.   RULEPTR ( NUMRULES ) := NUMSTRGSRULES; --set end of list mark
  756.   NUMRULES:= NUMRULES - 1;
  757.   TEXT_IO.CLOSE(INFILE);
  758.  
  759. end GETRULES;
  760.  
  761. Function VERIFIED( TGTSTRG : DYN_STRING;
  762.                     NEWSTRPTR : PTRRANGE;
  763.                     STARTRULE,
  764.                     CURRULNUM : RULRANGE) return boolean is
  765. -- Attempts to verify a particular symbol (antecedent) as true or false
  766. -- recurses if antecedent is a conclusion in another rule attempting to 
  767. -- prove each antecedent for the conclusion.
  768.  
  769. NEWSTR    : DYN_STRING;
  770. NOMORESTRGS,     
  771. FOUND : boolean;
  772. -- need local copies for sending to procedures with out parms
  773. NEW_STRPTR : PTRRANGE := NEWSTRPTR;  --symbol number
  774. TGTSTRING : DYN_STRING :=TGTSTRG;    --symbol string
  775. STARTRUL : RULRANGE := STARTRULE;    --first rule to look in for the symbol
  776. CURRULENUM: RULRANGE := CURRULNUM;   --current rule that working on
  777.  
  778.   Function USERVERIFIES( TGTSTRG  : DYN_STRING;
  779.                          CURRULNUM : RULRANGE;
  780.                          NEWSTRPTR : PTRRANGE) RETURN BOOLEAN IS
  781.  
  782.   --ASKS USER ABOUT THE TGTSTRG AND RECORDS HIS ANSWER when antecedent does
  783.   --not appear as a conclusion in any other rule.
  784.  
  785.   CH:character;
  786.   Validchars : dyn_string:= D_STRING("YyNnEe");
  787.   I,J:natural;
  788.  
  789.   BEGIN
  790.     Loop
  791.       loop 
  792.         Text_io.PUT("Is this true? ");
  793.     Dyn.PUT(TGTSTRG);
  794.         Text_io.PUT(" ?? ");
  795.     Text_io.Get(CH );
  796.  
  797.       exit when (DYN.INDEX(Validchars,d_string(CH),1 ) ) > 0;
  798.       end loop;
  799.       case CH is
  800.        when 'Y' | 'y' =>
  801.             RULES(NEWSTRPTR).value:='T';
  802.           return true;
  803.        when 'N' | 'n' =>
  804.       RULES (NEWSTRPTR).value:= 'F';
  805.       return false;
  806.        when 'E' | 'e' =>
  807.         Text_io.new_line(2);
  808.       Text_io.PUT( "TRYING TO USE RULE");
  809.       Integer_io.PUT(currulnum);
  810.           Text_io.new_line;
  811.           i:= NEWSTRPTR;
  812.           loop
  813.             i:= i-1;
  814.             exit when CompareStr (rules (i).symbol,D_string("IF")) = 0;
  815.           end loop;
  816.         i:=i+1;
  817.       if i /= NEWSTRPTR then
  818.         Text_io.new_line(2);
  819.         Text_io.put("I already know that :");
  820.         Text_io.new_line;
  821.         J:= i;
  822.             loop
  823.           Text_io.put("   ");
  824.           Dyn.put(rules(j).symbol);
  825.           Text_io.new_line;
  826.            exit when J = (newstrptr - 1);
  827.               j:=j+1;
  828.         end loop;
  829.       end if;
  830.           Text_io.new_line(2);
  831.       Text_io.put("IF");
  832.       Text_io.new_line;
  833.       i:= NEWSTRPTR;
  834.       loop
  835.           Text_io.put("   ");
  836.           Dyn.put(rules(i).symbol);
  837.           Text_io.new_line;
  838.           i:=i+1;
  839.         exit when CompareStr (RULES (i).SYMBOL, D_string("THEN"))=0;
  840.       end loop;
  841.       Text_io.put("THEN");
  842.       Text_io.new_line;
  843.           Text_io.put("   ");
  844.           Dyn.put(rules(i+1).symbol);
  845.       Text_io.new_line(2);
  846.       
  847.        when others => NULL;
  848.       end case;
  849.     end loop;
  850.   end USERVERIFIES;
  851.  
  852.   Procedure GETRULE (STARTRULE : in out RULRANGE;
  853.                      TGTSTRG   : DYN_STRING;
  854.                      FOUND     : out Boolean) is
  855.  
  856.   -- Finds the rule in which the tgtstrg appears as a consequent;
  857.   -- begins search at the value of startrule; makes no change to 
  858.   -- startrule unless a new rule is found
  859.  
  860.   RUL    : RULRANGE;
  861.   STRPTR: PTRRANGE;
  862.   STRG    : DYN_STRING;
  863.  
  864.   begin --Gets relevant rules for tgtstrg if any; Puts result 
  865.         -- in currulnum
  866.       RUL := STARTRULE + 1;
  867.   
  868. loop
  869.     IF RUL > NUMRULES then    --This line and three following put at top
  870.       FOUND:= false;          --of loop to prevent overrunning structure
  871.    exit;                      --when startrule is passed in as last rule
  872.     end if;                   --of rulebase.  Alan McDonley
  873.  
  874.     STRPTR := RULEPTR(RUL + 1) - 1;
  875.     STRG := RULES (STRPTR ).SYMBOL;
  876.     IF COMPARESTR(STRG,TGTSTRG) = 0 then --if found
  877.       FOUND:=True;
  878.       STARTRULE := RUL;
  879.    exit;
  880.     end if;
  881.     RUL := RUL + 1;
  882.                 --Location of test in journal listing
  883.   end loop;
  884.   end GETRULE;
  885.  
  886.   Procedure GETSTRING( NEWSTR    : in out DYN_STRING;
  887.                  NEWSTRPTR: PTRRANGE;
  888.                        NOMORESTRGS : out boolean;
  889.                  CURRULNUM : RULRANGE) is
  890.  
  891.   -- Works in the current rule to find the next antecedent;
  892.   -- if no more antecedents, sets rule consequent to 'T'rue
  893.   -- and nomrestrgs to true;
  894.  
  895.   begin
  896.     If CompareStr(RULES (NEWSTRPTR).SYMBOL, D_STRING("THEN")) = 0 then
  897.       -- no more antecedents, confirm consequent as true
  898.       RULES (NEWSTRPTR + 1).VALUE := 'T';
  899.       NOMORESTRGS := true;
  900.       Text_io.new_line(2);
  901.       Text_io.put("Rule");
  902.       Integer_io.Put(currulnum);
  903.       Text_io.put(" deduces ");
  904.       DYN.put(RULES (NEWSTRPTR + 1).SYMBOL);
  905.       Text_io.new_line(2);
  906.     else  -- Set newstr to the next antecedent
  907.       NEWSTR := RULES (NEWSTRPTR).SYMBOL;
  908.       NOMORESTRGS := false;
  909.     end if;
  910.   end GETSTRING;
  911.  
  912. begin --VERIFIED
  913.  
  914.   -- Check to see if value is T or F
  915.   
  916.   if RULES ( NEW_STRPTR).VALUE = 'T' then
  917.     Return true ;
  918.   elsif RULES ( NEW_STRPTR).VALUE = 'F' Then
  919.     return false;
  920.   end if;
  921.  
  922.   -- Find a rule with tgtstring as consequent; if there are none,
  923.   -- must ask the user about the veracity of the antecedent;
  924.   GETRULE (STARTRUL, TGTSTRING, FOUND);
  925.   If not found then
  926.     if USERVERIFIES (TGTSTRING, CURRULENUM, NEW_STRPTR) then
  927.       return true;
  928.     else
  929.       return false;
  930.     end if;
  931.   else
  932.     CURRULENUM := STARTRUL;
  933.     loop --1
  934.       NEW_STRPTR := RULEPTR (CURRULENUM) + 1; -- first antecedent
  935.       GETSTRING (NEWSTR, NEW_STRPTR, NOMORESTRGS, CURRULENUM);
  936.       loop --2
  937.         -- Stay with this rule until all antecedents have been
  938.     -- confirmed as true; exit the loop if one is false
  939.     
  940.     if VERIFIED (NEWSTR, NEW_STRPTR, 0, CURRULENUM) then
  941.       NEW_STRPTR := NEW_STRPTR + 1;
  942.       GETSTRING ( NEWSTR, NEW_STRPTR, NOMORESTRGS, CURRULENUM);
  943.       if NOMORESTRGS then
  944.         return true;
  945.       end if;
  946.     else
  947.       exit;
  948.     end if;
  949.       end loop;  --2 
  950.       GETRULE( CURRULENUM, TGTSTRING, FOUND);
  951.       if not FOUND then
  952.     -- Last call to VERIFIED is FLSE; RECORD TGTSTRING
  953.     -- as 'F'alse and return from verified
  954.     RULES (NEW_STRPTR).VALUE := 'F';
  955.     return false;
  956.       end if;
  957.     end loop; --1
  958.   end if;
  959. end VERIFIED;
  960.  
  961. Procedure DIAGNOSE is
  962. -- Called to make a reccommendation
  963. -- first checks to be sure rulebase is consistant, then procedes to verify
  964. -- the last hypothesis in the rulebase.
  965. -- If not proven moves to next hypothesis from last,etc. till first is 
  966. -- disproved or an hypothesis is proven.
  967.  
  968.   hypothesis : DYN_STRING;
  969.   HYPT : HYPTPTR;
  970.  
  971.   Procedure CKHYPT (HYPOTHESIS : DYN_STRING) is
  972.     RULE : RULRANGE;
  973.     STRPTR: PTRRANGE;
  974.     STRG  : DYN_STRING;
  975.     BAD_RULEBASE : EXCEPTION;
  976.  
  977.     -- Checks to insure all hypothesis have applicable rules
  978.     -- (some rule contains hypothesis as conclusion)
  979.  
  980.     begin
  981.       RULE := 1;
  982.       loop
  983.         STRPTR := RULEPTR (RULE + 1) - 1; --Consequent of rule;
  984.     STRG := RULES ( STRPTR).SYMBOL;
  985.     if CompareStr ( STRG, HYPOTHESIS) = 0 then 
  986.        exit;
  987.     end if;
  988.     RULE := RULE + 1;
  989.  
  990.     if RULE > NUMRULES then
  991.       Text_io.new_line;
  992.       Dyn.put(HYPOTHESIS);
  993.       Text_io.put(" not in rule set ");
  994.           raise BAD_RULEBASE;
  995.           -- hypothesis cannot be confirmed with rule base so halt program  
  996.     end if;
  997.       end loop;
  998.     end CKHYPT;
  999.   
  1000.   --Go thru the hypothesis one at a time until one is confirmed
  1001.   -- as true or all are false
  1002.  
  1003. begin -- DIAGNOSE
  1004.   Text_io.new_line;
  1005.   Text_io.put("I will use my ");
  1006.   Integer_io.put(NUMRULES);
  1007.   Text_io.put(" rules to prove one of the following:");
  1008.   Text_io.new_line(2);
  1009.   HYPT:= HYPTHDR;
  1010.   loop
  1011.     CKHYPT( HYPT.SYMBOL);
  1012.     DYN.put( HYPT.SYMBOL);
  1013.     TEXT_IO.NEW_LINE;
  1014.     HYPT := HYPT.NEXT;
  1015.    EXIT when HYPT = HYPTHDR;
  1016.   end loop;
  1017.   Text_io.new_line(2);
  1018.   Text_io.put("Please answer with (Y)es, (N)o, or (E)xplain ");
  1019.   Text_io.new_line(2);
  1020.  
  1021.   HYPT := HYPTHDR;
  1022.   loop
  1023.     HYPOTHESIS := HYPT.SYMBOL;
  1024.     if VERIFIED( HYPOTHESIS,1,0,1) then 
  1025.       text_io.put("RECOMMENDATION: ");
  1026.       dyn.put(HYPOTHESIS);
  1027.       text_io.new_line;
  1028.    exit;
  1029.     end if;
  1030.     HYPT := HYPT.NEXT;
  1031.     if HYPT = HYPTHDR then 
  1032.       Text_io.put( "NO RECOMMENDATION CAN BE CONFIRMED");
  1033.    exit;
  1034.     end if;
  1035.   end loop;
  1036. end DIAGNOSE;
  1037.  
  1038. begin -- EXPERT  main block
  1039.       
  1040.   for i in 1.. 10 loop
  1041.     TEXT_IO.NEW_LINE;
  1042.   end loop;
  1043.   TEXT_IO.PUT_LINE("Ada EXPERT SYSTEM");
  1044.   GETRULES;
  1045.   DIAGNOSE;
  1046.  
  1047. END EXPERT;
  1048.  
  1049.