| ?-
The question mark indicates that whatever you type at this prompt will be interpreted as a query. Although you can delete the question mark, it makes no difference – whatever you type at the Console will be interpreted in the same way. When you enter a query, what is being queried is a database of things Flex knows about, i.e. its knowledge base. Even before you have supplied any knowledge, the knowledge base is not empty; it contains information about the current status of Flex itself, for example.
Try typing the following at the prompt (not forgetting the full stop):
statistics(X, Y) .
Notice that the case (i.e. capitalization) of your input is important, but the spaces are optional. The full stop is important as it marks the end of the query. Now press the RETURN key and you will receive a message similar to this:
X = runtime ,
Y = {10175706, 10175706} ;
Your query can be regarded as a relation in Flex, and the system has tried to find a pair of values for X and Y. However, this isn't the only solution. To see the others, keep hitting the space bar. If, after you have seen one or more such solutions, you hit the RETURN key, the system will stop looking for alternative solutions.
You may find it useful to maximize the screen space available by clicking the mouse on the middle box in the top right-hand corner of the main application window. Click on the middle box again to return to using only part of the screen. Similarly, you can increase the size of the Console window by clicking on the middle box towards the top right-hand corner of the window. The Console, and any other windows that you open, will now fill the screen. Click on the same box to return to normal size windows.
In order to define a Flex program, you need to create and save a file. Go to the File menu and select New. This will create a new window called 'Untitled', where you can type your Flex program. Before using a Flex program you must save it with a .KSL extension, for example MYPROG.KSL. This is important because when you come to compile your program, Flex will need to decide whether to interpret the code as Flex or Prolog commands. If the file name ends in .KSL the file is interpreted as Flex code, otherwise it is interpreted as Prolog code.
When you are ready to test your program, select Compile from the Run menu. Note that your program should be in the active window. A window can be activated by clicking on it, or by selecting it in the Window menu, where the active window is marked with a tick. (Compile All will compile every program that appears in an open Flex window.) A report on the compilation (whether successful or not) will appear in the Console. If you wish to see the compilation report in a separate window called 'Messages', choose Preferences… from the Options menu. In the field titled Report Stream, click on the radio button for Message Log; then leave the Preferences window by clicking on OK. If you try to compile Flex code in an Untitled window without saving, it will be interpreted as Prolog code, and the compilation will fail (because it isn't Prolog code ).
ruleset boiler
contains all rules ;
select rule using first come first served ;
update ruleset by removing each selected rule;
initiate by doing restart .
The main advantages of explicitly coding the desired behaviour for yourself are:
action start;
do invoke ruleset boiler
and write('Finished ').
Once you have compiled your program, you can then type
start.
in the Console window to start the program.
If at any time you are having difficulty in making Flex behave as you expect, you may find it helpful to:
rule r1
if water_level is low
then write('** open the control valve **')
score 10.
The problem here is that the full stop (or period) is not recognized as such. Instead it is interpreted as a decimal point associated with the number 10. The correct way to write this rule would be:
rule r1a
if water_level is low
then write('** open the control valve **')
score 10 .
The extra space before the dot makes all the difference!
action write_value(Variable)
do write($Variable) % write name of variable
and write(' is ')
and write(Variable) % write value of variable
and nl .
Here, a semicolon can be included after the name of the action and its parameters, but is not required. (You are likely to find write_value a useful action to include in your own programs. To do this, you will need to include its definition in your programs.) Other definitions, such as rulesets, use semicolons (in place of the word 'and') to separate their sub-parts. In these cases you must not put a semicolon after the name of the ruleset:
ruleset boiler
contains all rules ;
select rule using first come first served ;
update ruleset by removing each selected rule ;
initiate by doing restart .
action setpressure
do pressure becomes 10
and write_value($pressure) .
If, after compiling the program, you were to type
setpressure.
in the Console window, the response would be:
pressure is 10.
With the dollar character inserted in the definition of write_value, the variable's name (i.e. pressure) is used. Without the dollar, the variable's value (i.e. 10) is used. You can check the value of a global variable, such as pressure, by typing a query like this in the Console window:
write_value(pressure) .
rule version2
rule version3
rule version1
if systems is knowledgebased
then choice becomes flex
and write_value($choice) .
if the systems are knowledgebased
then a choice becomes flex
and write_value($choice) .
if systems = knowledgebased
then choice := flex
and write_value($choice) .
initialise.
in the Console window and press the RETURN key. Note that initialise is spelt with an 's' rather than a 'z', and that as ever it is important to include the full stop. This command causes all Flex definitions to be forgotten. Any definitions that you want to be remembered will have to be recompiled.
After you have finished working on one problem, and before working on the next, it is wise to issue the initialise command.
% this is also a comment
/* this is a comment
split over several lines */
In the first case, everything between the /* and */ is treated as a comment. In the second case, everything after the % on that line is treated as a comment.
Sometimes your program may contain valid Flex, but doesn't behave as you expect. Unfortunately, the supplied debugging tools generally work at the level of the underlying Prolog and are of little help when developing Flex programs. If a Flex program compiles, but doesn't work as expected, you can introduce 'spies' as explained in the Flex Reference Manual (see the Flex predicates entry for spyfact). You may also find it helpful to make your program explain what it is doing, for example:
rule r6
if write('inspecting rule r6') and nl
/* Announce that this rule is being examined */
and steam is escaping
then steam_outlet := blocked
and write('firing rule r6')
/* Announce that this rule is firing */
and nl . % print a 'new line' character
A good place to start is with the boiler example from Chapter 2. In order for you to run the rules in Flex, you need some way of telling the system the current state of the boiler. That is why there are some questions defined in FORWARDCHAINBOILER1.KSL. To run the demonstration, you will need to compile the Flex program by choosing Compile from the Run menu. Then go to the Console window, where you should find the following prompt:
| ?-
Commands for immediate execution are typed after this prompt. Such commands are sometimes called queries, owing to way in which they are treated by the Prolog language that underlies Flex. After the prompt, type start. remembering to include the full stop or period. Flex will execute the start action defined in the program, which in turn causes it to forward-chain through the rules, asking you some questions about the state of the boiler as it goes.
For example, you will be asked 'What is the temperature?'. Try clicking 'Explain' for a reason, then select 'high'. Then set the release valve to 'closed', the transducer output to 'low' and the flow rate to 'low'. What advice do you get?
You should have found that the instruction to open the control valve was offered twice and the instruction to shut down the boiler tubes was offered once. The 'open the control valve!' instruction was repeated because both rules r2_1 and r2_2 have fired. There should also be messages resulting from the firing of other rules. Finally you will see the word 'yes' (sometimes you will see 'no' instead). This indicates whether or not Prolog's final effort at matching a pattern was successful; as a Flex user, you can usually disregard it.
After you have tried running the ruleset a few times, it will become tiresome to answer questions about the state of the boiler. In anticipation of this, I have prepared a console interface. Close the FORWARDCHAINBOILER1.KSL window and type initialise. in the Console window, so that the previous rules are forgotten. Then open FORWARDCHAINBOILER2.KSL, compile it, and type controlpanel. in the Console window. A rudimentary boiler control panel will appear on your screen after you press RETURN. You can now make whatever changes you wish to the boiler via the control panel (the activated setting is marked with a black radio button; to change settings, click on the button). These values will be picked up by the Flex rules when you type start. in the Console window. The four little windows that comprise the control panel can be moved around like any other windows, and can be activated using the Flex menu entitled Windows. You will probably find it easiest to resize the Console window so that it no longer overlaps the control panel, as I have shown below.
Flex offers two types of conflict resolution: first come, first served (which it regards as conflict resolution "switched off") and priority values (conflict resolution "switched on"). The first come, first served mechanism is summarized in Figure 2.7 of the book. Conflict resolution in general is shown in Figure 2.2 of the book, which illustrates forward chaining. Where priority values are being used, the box marked "select a rule from the conflict set..." can be imagined instead as a box marked "select a rule with the highest priority value...". In Flex, the priority values for conflict resolution are called scores.
Now try altering rules r2_7 and r2_8 in FORWARDCHAINBOILER2.KSL so that they have scores of 1 and 10 respectively. A few hints:
Flex allows the scores to be allocated dynamically. This means that they don't have to be stated explicitly in advance, but can be calculated as an 'action' when a rule is fired. Take a look at WATER.KSL among the classic example programs to see some examples of dynamic scoring.
rule r1
if release_valve is stuck
then steam_outlet becomes blocked.
Whenever you write a rule like this, Flex assumes that you want to apply forward chaining. Further preferences for how the forward chaining should be applied can be given, as in the definition of the ruleset boiler that appears in FORWARDCHAINBOILER2.KSL. Backward-chaining rules in flex are called relations rather than rules. Rules are simply conditional relationships, so the Flex terminology can be justified. Re-writing rule r1 as a backward-chaining relation, I get:
relation blocked(steam_outlet)
if stuck(release_valve).
The full stop at the end is an important part of both rule r1 and the corresponding relation. Notice that although the rule and the relation are doing a similar job, they are not directly interchangeable. When the condition part of rule r1 is satisfied and the rule is chosen for firing, a value (blocked) is assigned to a variable (steam_outlet). In contrast, when Flex backward-chains it only uses those relations that it needs in its attempt to satisfy a goal, which is usually entered as a query. No explicit assignments of values to global variables are made. For example, assume that the above relation was part of a compiled program, and the following query was entered at the Console:
blocked(X).
When Flex encounters a variable that begins with an upper-case letter or an underscore, it recognizes it as a local variable and looks for values that can be matched with it. In this case, Flex would recognize that this query could be satisfied if the local variable X were matched to steam_outlet, and if the truth of another relation, stuck(release_valve), could be established. The latter relation becomes the new goal and, if it is satisfied, Flex will return:
X = steam_outlet
Notice, by the way, that you can also write relations that are unconditional:
relation blocked(steam_outlet).
Instead of a rule, this relation now represents a fact. In this example, the relation name is blocked, and steam_outlet is the value of its single argument. This relation could be replaced with an equivalent relation that uses the local variable Y:
relation blocked(Y)
if Y is steam_outlet.
The file MIXEDCHAINBOILER.KSL shows how forward-chaining rules and backward-chaining relations can be used together. The relations are used for getting values from the control panel, so the only rules are the familiar ones r2_1 - r2_10 (with conflict resolution used to ensure the correct ordering of rules r7 and r8 - see Conflict resolution). If you want to try it out: initialise; open and compile MIXEDCHAINBOILER.KSL; if necessary, recreate a control panel by typing controlpanel. in the Console; make your selections in the control panel, and type "start." .
In BACKCHAINBOILER.KSL, I have replaced rules r2_1 - r2_10 with backward-chaining relations. We no longer need any of the action or ruleset definitions. To test whether the relations are working, type a query at the console, e.g.:
?- needs_opening(X).
Flex will return all matches (if any) to X, which ought to be the names of all valves that need opening. The matches are displayed one at a time. To see more matches, press the space bar. (In this case there may only be one match.)
bayes_boiler( 0, 1, 1, 0.1, 0.02, P ).
You should receive the following response:
P = 0.6918 ...
The figures in brackets in your query represent the starting conditions given in the example in the book. The first three are explicitly-stated starting conditions. The fourth and fifth are not explicitly given in the book, so the prior probabilities are used:
uncertainty_trace.
or
uncertainty_notrace.
You can also switch the tracing of uncertainty on or off within your flex programs by including the following instructions:
trace propagation .
or
notrace propagation .
Turn on the trace facility, and repeat the query that you set previously. You will see that the trace is expressed in terms of probability values. To see the same calculations as odds values, alter the penultimate line of the program so that it reads:
and propagate bayes_boiler_rules odds rules
Recompile the program and set the query again. You should now see an equivalent trace, this time with the calculations expressed as odds. Both sets of outputs should match the worked example in the book.
cert_boiler( -1, 1, 1, _, _, C ).
You should receive the following response:
C = 0.56
You can turn the trace facility on or off as described above. Compare the response that you have obtained with the worked example in the book. They should be the same. The first three figures in brackets in your query represent the starting conditions given in Section 3.3.4 of the book:
and the certainty_factor that the water_level is not low = CF1
Recompile the file and type the following query at the Console:
?- cert_boiler( 1, 1, 1, _, _, C ).
You should receive the same response as before because 'not low' with certainty 1 is the same as 'low' with certainty 1.
As well as displaying fuzzy sets, you can use the editor to change a wide variety of the parameters of a fuzzy variable or even define new fuzzy variables. If you select OK when you have finished, the editor will automatically generate Flint code that defines the fuzzy variables and fuzzy sets you created or changed using the editor. In the current example, your changes will appear in the window containing FUZZYBOILER.KSL. Any changes made using the fuzzy variable editor will include a comment showing the time and date of the edit. For now, discard any changes that you have made by closing the FUZZY1.KSL window without saving your changes, then opening the file again.
Compile FUZZYBOILER.KSL and type the following in the Console window:
fuzzy_boiler(180, Pressure).
This will set Flint the goal of determining the pressure when the temperature is 180°C, which is the same example that is used in the book. You should receive the following response (as certainty tracing is explicitly switched on within the program, the response will be preceded by some trace information):
Pressure = 0.625
Units of MNm-2 (or MPa) are assumed. The book shows that this is the expected response when defuzzification uses Larsen's product operation rule and the mirror rule. FLINT incorporates the following defuzzification options, with which you may wish to experiment:
fuzzy_boiler(180, Pressure).
This time you should receive the response shown in the book for the case where a bounded range was used (except that only three places of decimals are given in the book):
Pressure = 0.5272 ...
I suggest you close FUZZYBOILER.KSL without saving your changes.
Return to Adrian Hopgood's home page.
|
|
Owner: Modified: |
Adrian Hopgood December 2012 |