/* C W I N E Version 2.1 This knowledge base is in preliminary form, it should not be considered 'expert'. Copyright (c) Teknowledge Inc, 1986 December 10, 1986 */ /* This knowledge base contains the same knowledge as WINE, yet uses a cyclic control structure that allows multiple recommendations to be made. After a set of wines is recommended, the system asks the user if those wines are satisfactory. If they are, then the consultation is over. If the user indicates that the wines are NOT satisfactory, the system asks which characteristics of the wine (color, body, or sweetness) the user would like to change. After asking for the value of the new characteristic(s), the system makes new recommendations. This process continues until the user indicates that the wines are indeed satisfactory. This knowledge base introduces three new M.1 concepts: * user-modified syntax * display * presupposition * explanation * mostlikely * listof * ordered User-modified syntax is a mechanism by which the knowledge engineer can improve the readability of the knowledge base. The KE can declare particular words as prefix, infix, or postfix 'operators'. These allow non-atomic expression names to be specified without hyphens. For example, 'the-best-sweetness' can be written as 'the best sweetness' if 'the' and 'best' are declared as prefix operators. The 'display' meta-proposition can be used in the premise of a rule to print output on the screen. For example, the proposition 'display(['Welcome to CWINE.',nl])' in a rule will result in the words 'Welcome to CWINE.' followed by a carriage return being printed to the screen. The 'explanation' meta-fact is linked to the label of the knowledge base entry. You can change M.1's response to 'why' by specifying the exact text of the explanation that the user will see for the knowledge base entry indicated by the label. Presuppositions can be used by the knowledge engineer to specify that a particular expression must be 'true' in order for it to make sense to even seek another expression. For example, it wouldn't make sense for the system to ask what kind of sauce is on the meal unless there actually IS a sauce on the meal. This relationship between 'has sauce' and 'sauce' can be explicity represented by a presupposition of the form presupposition(sauce) = has sauce. and does away with the need for various 'screening clauses' in rules that test for sauce, which would otherwise be necessary. The 'mostlikely' function can be used in the premises of rules to find the value of a particular expression that is believed with the highest certainty. For example, 'mostlikely(wine) = X' will bind X to 'zinfandel' if that is the value of 'wine' that has the highest cf. The expression 'listof' returns a list. 'listof' is used in CWINE to obtain a list of the wines. 'listof' is often used in conjunction with the expression 'ordered' which returns successive values of an expression in descending order of their certainty. In CWINE, ordered is used to present wines in order of their suitability. */ /* The following words are used as operators in CWINE: */ prefix best. prefix begin. prefix cycle. prefix default. infix for. prefix end. infix of. prefix has. infix to. postfix ends. prefix main. infix with. prefix new. prefix preferred. prefix recommended. prefix selection. prefix the. prefix user. /* There is no 'goal' specification in the CWINE knowledge base. The top level goal for the consultation ('end the consultation') is specified as an 'initialdata' expression so that M.1 will not automatically display its value at the end of the consultation. */ initialdata = [begin the consultation, end the consultation ]. /* --------------------- BEGINNING CWINE -------------------------------- */ begin message = [ nl,' ********************************************', nl,' * *', nl,' * CWINE *', nl,' * THE CALIFORNIA WINE ADVISOR *', nl,' * *', nl,' ********************************************', nl,' Copyright (c) 1986, Teknowledge Inc.', nl, nl,' This knowledge system will ask you simple questions about your ', nl,' meal, and use its knowledge about what wines should accompany', nl,' meals to make appropriate selections from a database. ', nl, nl, nl]. nocache(begin message). question(begin signal) = [nl,nl,nl,nl,'Are you ready to begin the consultation (y/n)?']. legalvals(begin signal) = [yes,no]. rule-1: if begin message = M and display(M) and begin signal and display('\f') then begin the consultation. rule-2: if not begin signal and display("Issue the 'go' command when you are ready to begin.") and do(abort) then begin the consultation. /* -------------------- CONSULTING ENDS WITH CYCLE N -------------------- */ /* These rules are used to determine whether or not a particular cycle of recommendations is the last one necessary. A particular cycle will be the last one in a consultation if, for that cycle: * the wines have been determined, the wines have been displayed to the user, and the user is happy with those wines, or * no wines are found to be appropriate for the user */ rule-3: if the wine for cycle N is known and user informed of selection N and user happy with selection N then consulting ends with cycle N. rule-4: if the wine for cycle N is unknown and display(['Sorry, I''m unable to recommend any appropriate wines.', nl]) then consulting ends with cycle N. /* ------------------- END THE CONSULTATION ----------------------------- */ /* This is only rule that concludes about the top level goal of the consultation. */ rule-5: if positiveinteger = I and consulting ends with cycle I and display([nl,nl,'The consultation is over.', nl]) then end the consultation. /* ------------------------ FEATURE -------------------------------------- */ /* 'Feature' is a special characteristic of the wine, that's currently only used to indicate a particularly spicy meal. This attribute can have more than one value with certainty at a time. */ multivalued(feature). rule-6: if sauce = spicy then feature = spiciness. /* ----------------------- HAS SAUCE ------------------------------------- */ question(has sauce) = 'Does the meal have a sauce on it?'. legalvals(has sauce) = [yes, no]. automaticmenu(has sauce). /* ----------------------- HAS TURKEY ------------------------------------- */ /* If the main component of the meal is not poultry, then it's reasonable to assume that it doesn't contain turkey. */ rule-7: if not(the main component = poultry) then has turkey = no. question(has turkey) = 'Does the meal have turkey in it?'. legalvals(has turkey) = [yes, no]. automaticmenu(has turkey). /* ------------------------ HAS VEAL -------------------------------------- */ /* Likewise, if the main component of the meal isn't meat, then it's reasonable to assume that it doesn't contain veal. */ rule-8: if not(the main component = meat) then has veal = no. question(has veal) = 'Does the meal have veal in it?'. legalvals(has veal) = [yes, no]. automaticmenu(has veal). /* -------------------- PREVIOUSCYCLE TO M -------------------------------- */ /* This rule finds the number of the cycle that immediately preceeds the current cycle, so that M.1 can carry over the characteristics of wine from the previous recommendation cycle that the user didn't find objectionable. */ rule-9: if M > 1 and M - 1 = N then previouscycle to M = N. /* ----------------------- SAUCE ------------------------------------------ */ presup-sauce:presupposition(sauce) = has sauce. multivalued(sauce). question(sauce) = 'What kind of sauce does the meal have?'. legalvals(sauce) = [spicy, sweet, cream, tomato]. automaticmenu(sauce). enumeratedanswers(sauce). explanation(presup-sauce) = [ 'I prefer to know whether a meal has a sauce before recommending ', 'a wine to accompany it. However, you may answer with ''unknown'' ', 'if you aren''t sure.']. /* ---------------------- TASTINESS ---------------------------- */ multivalued(tastiness). question(tastiness) = 'Is the flavor of the meal delicate, average or strong?'. legalvals(tastiness) = [delicate, average, strong]. /* -------------------------- THE BEST BODY ------------------------------ */ /* These rules are used to find the best body of the wines to recommend, using the meal's tastiness and sauce (if it has one). */ rule-10: if sauce = spicy then the best body = full. rule-11: if tastiness = delicate then the best body = light cf 80. rule-12: if tastiness = average then the best body = light cf 30 and the best body = medium cf 60 and the best body = full cf 30. rule-13: if tastiness = strong then the best body = medium cf 40 and the best body = full cf 80. rule-14: if sauce = cream then the best body = medium cf 40 and the best body = full cf 60. /* ---------------------- THE BEST COLOR ---------------------------------- */ /* These rules help find the best color of wine to recommend, based on characteristics of the meal itself. Rule-16 has an accompanying 'explanation' meta-fact. */ rule-15: if the main component = meat and has veal = no then the best color = red cf 90. rule-16: if the main component = poultry and has turkey = no then the best color = white cf 90 and the best color = red cf 30. explanation(rule-16) = [nl, 'For most poultry dishes, I recommend white wines. Turkey, ', 'however, goes very well with some red wines.', nl]. rule-17: if the main component = fish then the best color = white. rule-18: if not(the main component = fish) and sauce = tomato then the best color = red. rule-19: if the main component = poultry and has turkey then the best color = red cf 80 and the best color = white cf 50. rule-20: if has sauce = yes and sauce = cream then the best color = white cf 40 and the best color = red cf -90. /* ---------------------- THE BEST SWEETNESS --------------------------- */ /* If the meal has a sweet sauce, then a sweet-to-medium-sweet wine is probably appropriate. */ rule-21: if sauce = sweet then the best sweetness = sweet cf 90 and the best sweetness = medium cf 40. /* ----------------- THE DEFAULT X -------------------------------------- */ /* These default characteristics come into play when CWINE is unable to find either the best CHARACTERISTIC or the user's preferred CHARACTERISTIC. The system should never ask the user about these defaults, hence the 'noautomaticquestion' kb entry. */ noautomaticquestion(the default X). the default body = medium. the default color = red cf 50. the default color = white cf 50. the default sweetness = medium. /* -------------------- THE DESCRIPTION FOR N IS GIVEN ------------------- */ /* This rule uses 'mostlikely' to find the values of the characteristics of the wines that are believed with the most certainty. The values of these characteristics are output to the screen by means of a 'display' clause in the rule's premise. */ rule-22: if mostlikely(the recommended color for cycle N) = C and mostlikely(the recommended body for cycle N) = B and mostlikely(the recommended sweetness for cycle N) = S and display([nl,nl,'For this meal I recommend a wine that is ', S, ', ', B, '-bodied, and ', C, '.', nl]) then the description for N is given. /* ------------------ THE FAULT WITH CYCLE N ----------------------------- */ multivalued(the fault with cycle N). question(the fault with cycle N) = 'Which characteristic of the wine would you like to change?'. legalvals(the fault with cycle N) = [color, body, sweetness]. automaticmenu(the fault with cycle N). enumeratedanswers(the fault with cycle N). /* ---------------------- THE MAIN COMPONENT ----------------------------- */ multivalued(the main component). automaticmenu(the main component). enumeratedanswers(the main component). question(the main component) = 'What is the main component of the meal?'. legalvals(the main component) = [meat, fish, poultry]. /* ----------------- THE NEW VALUE FOR X FOR CYCLE N --------------------- */ /* The following question specification will work for color, body and sweetness. Legalvals are still of course different, so those specifications can't be collapsed. */ question(the new value for X for cycle N) = ['What ', X ,' would you prefer?']. legalvals(the new value for color for cycle N) = [red, white]. legalvals(the new value for body for cycle N) = [light, medium, full]. legalvals(the new value for sweetness for cycle N) = [dry, medium, sweet]. automaticmenu(the new value for ANY for cycle N). enumeratedanswers(the new value for ANY for cycle N). /* -------------------------- THE PREFERRED X ---------------------------- */ enumeratedanswers(the preferred X). automaticmenu(the preferred X). /* ----------------------- THE PREFERRED BODY ---------------------------- */ multivalued(the preferred body). question(the preferred body) = 'Do you generally prefer light, medium or full bodied wines?'. legalvals(the preferred body) = [light, medium, full]. /* ----------------------- THE PREFERRED COLOR --------------------------- */ multivalued(the preferred color). question(the preferred color) = 'Do you generally prefer red or white wines?'. legalvals(the preferred color) = [red, white]. /* -------------------- THE PREFERRED SWEETNESS -------------------------- */ multivalued(the preferred sweetness). question(the preferred sweetness) = 'Do you generally prefer dry, medium or sweet wines?'. legalvals(the preferred sweetness) = [dry, medium, sweet]. /* ----------------- THE RECOMMENDED X FOR CYCLE N --------------------- */ /* These rules establish the recommended characteristics for the wines that CWINE recommends during the first cycle (characteristics of subsequent cycles are either carried over or explicitly stated by the user). If the best characteristic is known, then that's recommended. If the best characteristic is NOT known, then the user's preference is recommended (provided the user states one). If neither the best nor the preferred characteristic is known, CWINE resorts to the default characteristics specified above. */ rule-23: if the best X = V then the recommended X for cycle 1 = V. rule-24: if the best X is unknown and the preferred X = V then the recommended X for cycle 1 = V. rule-25: if the best X is unknown and the preferred X is unknown and the default X = Y then the recommended X for cycle 1 = Y. /* This rule carries over a characteristic from one cycle to the next, provided that the user doesn't want to change it. */ rule-26: if previouscycle to M = N and not(the fault with cycle N = X) and the recommended X for cycle N = V then the recommended X for cycle M = V. /* This rule comes into play when the user indicates that a particular characteristic is objectionable. The user is asked what the new value for the characteristic should be, and that value is used in the next cycle of recommendations. */ rule-27: if previouscycle to M = N and the fault with cycle N = X and the new value for X for cycle M = V then the recommended X for cycle M = V. /* ------------------------- THE WINE FOR CYCLE N ------------------------- */ multivalued(the wine for cycle N). /* This rule uses recommends wines for a particular cycle by finding the characteristics of the wines to recommend and doing a 'lookup' operation in a small table of wines and recommends wines that match the characteristics provided. */ rule-28: if the recommended color for cycle N = C and the recommended body for cycle N = B and the recommended sweetness for cycle N = S and wine(C,B,S) = W then the wine for cycle N = W. /* The following rule mentions 'feature', and hence is inconvenient to replace by a table entry. */ rule-29: if the recommended color for cycle N = white and the recommended body for cycle N = full and feature = spiciness then the wine for cycle N = gewuerztraminer. /* --------------------- THE WINELIST FOR N IS DISPLAYED ------------------ */ /* These rules use 'ordered' and 'listof' to create a list of recommended wines with the most-recommended first and the least-recommended last. Each of these rules prints the recommended wines with a 'display' statement in the premise. */ /* This rule succeeds if exactly one wine is recommended. */ rule-30: if listof(the wine for cycle N) = [ONLYONE] and display([nl,'The best wine for this meal is ' , ONLYONE, '.', nl,nl]) then the winelist for N is displayed. /* This rule succeeds if exactly two wines are recommended. */ rule-31: if listof(X,ordered(the wine for cycle N) = X) = [ONE,TWO] and display([nl,'The best wines for this meal are ', ONE, ' and ' , TWO, '.', nl,nl]) then the winelist for N is displayed. /* This rule succeeds if three or more wines are recommended. It prints only the first three wines if more than three are recommended. */ rule-32: if listof(X,ordered(the wine for cycle N) = X) = [ONE,TWO,THREE|REST] and display([nl,'The best wines for this meal are ', ONE, ', ' , TWO, ' and ', THREE, '.', nl,nl]) then the winelist for N is displayed. /* -------------------- USER HAPPY WITH SELECTION N ----------------------- */ question(user happy with selection N) = ['Are you happy with these?']. legalvals(user happy with selection N) = [yes,no]. /* ------------------- USER INFORMED OF SELECTION N ---------------------- */ rule-33: if the description for N is given and the winelist for N is displayed then user informed of selection N. /* --------------------- WINE(X, Y, Z) --------------------------------- */ /* The following facts comprise a small tables that describe wines in terms of their characteristics. These facts are matched by rule-28 in order to come up with particular recommendations. The 'noautomaticquestion' specification prevents M.1 from generating a question when the table doesn't contain a wine for a particular combination of characteristics. */ multivalued(wine(COLOR, BODY, SWEETNESS)). noautomaticquestion(wine(COLOR, BODY, SWEETNESS)). wine(red,medium,medium) = gamay. wine(red,medium,sweet) = gamay. wine(white,light,dry) = chablis. wine(white,medium,dry) = 'sauvignon blanc'. wine(white,medium,dry) = chardonnay. wine(white,medium,medium) = chardonnay. wine(white,full,dry) = chardonnay. wine(white,full,medium) = chardonnay. wine(white,light,dry) = soave. wine(white,light,medium) = soave. wine(white,light,medium) = riesling. wine(white,light,sweet) = riesling. wine(white,medium,medium) = riesling. wine(white,medium,sweet) = riesling. wine(white,light,medium) = 'chenin blanc'. wine(white,light,sweet) = 'chenin blanc'. wine(red,light,ANY) = valpolicella. wine(red,ANY,dry) = 'cabernet sauvignon'. wine(red,ANY,dry) = zinfandel. wine(red,ANY,medium) = 'cabernet sauvignon'. wine(red,ANY,medium) = zinfandel. wine(red,medium,medium) = 'pinot noir'. wine(red,full,ANY) = burgundy. /* --------------------------------------------------------------------- */