Minimal correct readme

Signed-off-by: Jordan Wilberding <diginux@gmail.com>
This commit is contained in:
Eric Merritt 2012-02-20 13:45:15 -06:00 committed by Jordan Wilberding
parent 8353035a1e
commit 6f2b3efc0c

547
README.md
View file

@ -1,534 +1,21 @@
ERESYE - ERlang Expert SYstem Engine Erlware Commons
======================================== ===============
Introduction Introduction
------------ ------------
This article describe how to write a simple Artificial Intelligence Erlware commons can best be described as an extension to the stdlib
application with Erlang, by using a *rule production system*. application that is distributed with Erlang. These are things that we
at Erlware have found useful for production applications but are not
To reach this objective, we will exploit the **ERESYE** tool, an included with the distribution. We hope that as things in this library
Erlang Inference Engine that allows rule-based systems to be written prove themselves useful, they will make their way into the main Erlang
directly in Erlang. distribution. However, whether they do or not, we hope that this
application will prove generally useful.
As it is widely known, a rule-based system is composed by a
**knowledge base**, which stores a set of *facts* representing the Goals for the project
'universe of discourse' of a given application, and a set of ---------------------
**production rules**, which are used to infer knowledge and/or reason
about the knowledge. A rule is activated when one or more facts match * Generally Useful Code
the template(s) given in the rule declaration: in such a case, the * High Quality
body of the rule contains a code that is thus executed * Well Documented
* Well Tested
In ERESYE, *facts* are expressed by means of Erlang tuples or records,
while rules are written using standard Erlang function clauses, whose
declaration reports, in the clause head, the facts or fact templates
that have to be matched for the rule to be activated and executed.
For more information about ERESYE please refer to the paper docs directory.
For more information about rule-based inference engines and expert
systems, you can refer to the book: *S. Russell and
P. Norvig. **Artificial Intelligence: A Modern Approach/2E.** Prentice
Hall, 2003.*
To write an AI application with ERESYE the following steps have to be
performed:
1. Indentify your universe of discourse and determine the facts that
have to be used to represent such a world;
2. Indentify the rules that you need and write them by using, e.g.
first-order-logic predicates or even natural language;
3. Implement the system by writing your rules as Erlang function
clauses, according to the modality required by ERESYE.
The Application: the Domain of Relatives
----------------------------------------
We will design a system able to derive new knowledge using some
inference rules and starting from a small set; as a sample
application, we chose the domain of relatives: we will start from some
base concepts, such as *parent*, *male* and *female*, and then, by
means of a proper set of rules, we will derive the concepts of
*mother*, *father*, *sister*, *brother*, *grandmother* and
*grandfather*.
According to the list above, we will first derive the facts that will be
used to represent our concepts. Given the set of relationships above, they
will be represented by means of the following facts:
<table border="1" align="center">
<thead>
<tr>
<td>#</td>
<td>Concept</td>
<td>Fact / Erlang tuple</td>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>X is male</td>
<td><tt>{male, X}</tt></td>
</tr>
<tr>
<td>2</td>
<td>X is female</td>
<td><tt>{female, X}</tt></td>
</tr>
<tr>
<td>3</td>
<td>X is Y's parent</td>
<td><tt>{parent, X, Y}</tt></td>
</tr>
<tr>
<td>4</td>
<td>X is Y's mother</td>
<td><tt>{mother, X, Y}</tt></td>
</tr>
<tr>
<td>5</td>
<td>X is Y's father</td>
<td><tt>{father, X, Y}</tt></td>
</tr>
<tr>
<td>6</td>
<td>X is Y's sister</td>
<td><tt>{sister, X, Y}</tt></td>
</tr>
<tr>
<td>7</td>
<td>X is Y's brother</td>
<td><tt>{brother, X, Y}</tt></td>
</tr>
<tr>
<td>8</td>
<td>X is Y's grandmother</td>
<td><tt>{grandmother, X, Y}</tt></td>
</tr>
<tr>
<td>9</td>
<td>X is Y's grandfather</td>
<td><tt>{grandfather, X, Y}</tt></td>
</tr>
</tbody>
</table>
Concepts 1, 2 and 3 will be used as a base to derive the other ones.
Deriving new concepts by means of rules
---------------------------------------
#### Concept: mother
The rule to derive the concept of mother is quite
straightforward:
if X is female and X is Y's parent then X is Y's mother.
From the point of view of ERESYE, since knowledge is stored in the
*knowledge base* of the engine, the rule above is translated into the
following one: *if the facts {female, X} and {parent, X, Y} are
*asserted* in the knowledge base, then we assert the fact {mother, X,
Y}.
The rule *mother* can be thus written as follows:
%%
%% if (X is female) and (X is Y's parent) then (X is Y's mother)
%%
mother (Engine, {female, X}, {parent, X, Y}) ->
eresye:assert (Engine, {mother, X, Y}).
#### Concept: father
This concept can be easily derived by means of the following rule:
%%
%% if (X is male) and (X is Y's parent) then (X is Y's father)
%%
father (Engine, {male, X}, {parent, X, Y}) ->
eresye:assert (Engine, {father, X, Y}).
#### Concept: sister
This concept can be expressed by the following rule:
if Y and Z have the same parent and Z is female, then Z
is the Y's sister.
The ERESYE rule used to map this concept is:
%%
%% if (Y and Z have the same parent X) and (Z is female)
%% then (Z is Y's sister)
%%
sister (Engine, {parent, X, Y}, {parent, X, Z}, {female, Z}) when Y =/= Z ->
eresye:assert (Engine, {sister, Z, Y}).
Please note the guard, which is needed to ensure that when Y and Z are
bound to the same value, the rule is not activated (indeed this is
possible since the same fact can match both the first and second
'parent' pattern).
#### Concept: brother
Given the previous one, this concept is now quite simple to
implement:
%%
%% if (Y and Z have the same parent X) and (Z is male)
%% then (Z is Y's brother)
%%
brother (Engine, {parent, X, Y}, {parent, X, Z}, {male, Z}) when Y =/= Z ->
eresye:assert (Engine, {brother, Z, Y}).
#### Concepts: grandmother and grandfather
The former concept can be expressed by means of the rule:
if X is Y's mother and Y is Z's parent, then X is Z's
grandmother.</u>* The latter concept is now obvious.
Both can be implemented using the following ERESYE rules:
%%
%% if (X is Y's mother) and (Y is Z's parent)
%% then (X is Z's grandmother)
%%
grandmother (Engine, {mother, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandmother, X, Z}).
%%
%% if (X is Y's father) and (Y is Z's parent)
%% then (X is Z's grandfather)
%%
grandfather (Engine, {father, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandfather, X, Z}).
Instantiating the Engine and Populating the Knowledge Base
----------------------------------------------------------
After writing the rules, we need to:
- instantiate the engine;
- add the rules above to the engine;
- populate the knowledge base with a set of initial facts.
We do this in the function *start* below:
start () ->
eresye:start (relatives),
lists:foreach (fun (X) ->
eresye:add_rule (relatives, {?MODULE, X})
end,
[mother, father, brother, sister,
grandfather, grandmother]),
eresye:assert (relatives,
[{male, bob}, {male, corrado}, {male, mark}, {male, caesar},
{female, alice}, {female, sara}, {female, jane}, {female, anna},
{parent, jane, bob}, {parent, corrado, bob},
{parent, jane, mark}, {parent, corrado, mark},
{parent, jane, alice}, {parent, corrado, alice},
{parent, bob, caesar}, {parent, bob, anna},
{parent, sara, casear}, {parent, sara, anna}]),
ok.
As the listing reports, creating a new ERESYE engine implies to call
the function *eresye:start/1*, giving the name of the engine to be
created
Then, we have to add the rules to the engine by using the function
*eresye:add_rule/2*: it takes two arguments, the name of the engine
and a tuple representing the function in the form *{Module,
FuncName}*; obviously the function *Module:FuncName* must be
exported. Function *add_rule* has to be called for each rule that has
to be added; for this reason, the code above has an iteration over the
list of rules written before.
Finally, we populate the inference engine with a set of sample facts
by giving them, in a list, to the function *eresye:assert/2*. To test
our rules, we considered the relationships in the Figure below and
assert only the facts for *male*, *female* and *parent*.
Testing the application
-----------------------
The final complete code of our AI application is thus the following:
%%%
%%% relatives.erl
%%%
-module (relatives).
-compile ([export_all]).
%%
%% if (X is female) and (X is Y's parent) then (X is Y's mother)
%%
mother (Engine, {female, X}, {parent, X, Y}) ->
eresye:assert (Engine, {mother, X, Y}).
%%
%% if (X is male) and (X is Y's parent) then (X is Y's father)
%%
father (Engine, {male, X}, {parent, X, Y}) ->
eresye:assert (Engine, {father, X, Y}).
%%
%% if (Y and Z have the same parent X) and (Z is female)
%% then (Z is Y's sister)
%%
sister (Engine, {parent, X, Y}, {parent, X, Z}, {female, Z}) when Y =/= Z ->
eresye:assert (Engine, {sister, Z, Y}).
%%
%% if (Y and Z have the same parent X) and (Z is male)
%% then (Z is Y's brother)
%%
brother (Engine, {parent, X, Y}, {parent, X, Z}, {male, Z}) when Y =/= Z ->
eresye:assert (Engine, {brother, Z, Y}).
%%
%% if (X is Y's father) and (Y is Z's parent)
%% then (X is Z's grandfather)
%%
grandfather (Engine, {father, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandfather, X, Z}).
%%
%% if (X is Y's mother) and (Y is Z's parent)
%% then (X is Z's grandmother)
%%
grandmother (Engine, {mother, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandmother, X, Z}).
start () ->
eresye:start (relatives),
lists:foreach (fun (X) ->
eresye:add_rule (relatives, {?MODULE, X})
end,
[mother, father,
brother, sister,
grandfather, grandmother]),
eresye:assert (relatives,
[{male, bob},
{male, corrado},
{male, mark},
{male, caesar},
{female, alice},
{female, sara},
{female, jane},
{female, anna},
{parent, jane, bob},
{parent, corrado, bob},
{parent, jane, mark},
{parent, corrado, mark},
{parent, jane, alice},
{parent, corrado, alice},
{parent, bob, caesar},
{parent, bob, anna},
{parent, sara, casear},
{parent, sara, anna}]),
ok.
Now it's time to test our application:
Erlang (BEAM) emulator version 5.5 [source] [async-threads:0] [hipe]
Eshell V5.5 (abort with ^G)
1> c(relatives).
{ok,relatives}
2> relatives:start().
ok
3>
Following the call to function *relatives:start/0*, the engine is
created and populated; if no errors occurred, the rules should have
been processed and the new facts derived. To check this, we can use
the function *eresye:get_kb/1*, which returns the list of facts
asserted into the knowledge base of a given engine:
4> eresye:get_kb(relatives).
[{brother,bob,mark},
{sister,alice,bob},
{sister,alice,mark},
{brother,bob,alice},
{brother,mark,alice},
{grandmother,jane,caesar},
{grandfather,corrado,caesar},
{grandmother,jane,anna},
{grandfather,corrado,anna},
{sister,anna,caesar},
{brother,caesar,anna},
{sister,anna,casear},
{mother,sara,anna},
{mother,sara,casear},
{parent,sara,anna},
{father,bob,anna},
{parent,sara,casear},
{father,bob,caesar},
{parent,bob,anna},
{father,corrado,alice},
{parent,bob,caesar},
{mother,jane,alice},
{parent,corrado,alice},
{father,corrado,mark},
{parent,jane,alice},
{mother,jane,mark},
{parent,corrado|...},
{brother|...},
{...}|...]
5>
The presence of facts representing concepts like *father*, *sister*,
etc., proves that the rules seems to be working as expected.
We can however query the knowledge base using specific fact templates.
For example, if we want to know who are Alice's brothers, we can use
the function *eresye:query_kb/2* as follows:
6> eresye:query_kb(relatives, {brother, '_', alice}).
[{brother,bob,alice},{brother,mark,alice}]
7>
The facts returned conform to the relationships depicted in the figure
above, thus proving that the rules written are really working.
As the example shows, function *eresye:query_kb/2* takes the engine
name as the first argument, while, for the second parameter, a tuple
has to be specified, representing the fact template to be matched; in
such a tuple, the atom *'_'* plays the role of a wildcard. However, to
specify a more complex matching, a *fun* can be used as a tuple
element; this *fun* has to return a boolean value which indicates if
the element matches the template. For example, to select both Alice's
and Anna's brothers, we can use the following function call:
7> eresye:query_kb(relatives, {brother, '_', fun (X) -> (X == alice) or (X == anna) end}).
[{brother,bob,alice},{brother,mark,alice},{brother,caesar,anna}]
8>
Deriving new concepts by means of rules
---------------------------------------
#### Concept: 'mother'
The rule to derive the concept of mother is quite straightforward:
if X is female and X is Y's parent then X is Y's mother.
From the point of view of ERESYE, since knowledge is stored in the
*knowledge base* of the engine, the rule above is translated into the
following one: *<u>if the facts *{female, X}* and *{parent, X, Y}* are
**asserted** in the knowledge base, then we assert the fact *{mother,
X, Y}*.</u>*
The rule *mother* can be thus written as follows:
%%
%% if (X is female) and (X is Y's parent) then (X is Y's mother)
%%
mother (Engine, {female, X}, {parent, X, Y}) ->
eresye:assert (Engine, {mother, X, Y}).
#### Concept: 'father'
This concept can be easily derived by means of the following rule:
%%
%% if (X is male) and (X is Y's parent) then (X is Y's father)
%%
father (Engine, {male, X}, {parent, X, Y}) ->
eresye:assert (Engine, {father, X, Y}).
#### Concept: 'sister'
This concept can be expressed by the following rule:
if Y and Z have the same parent and Z is female, then Z is the Y's
sister.
The ERESYE rule used to map this concept is:
%%
%% if (Y and Z have the same parent X) and (Z is female)
%% then (Z is Y's sister)
%%
sister (Engine, {parent, X, Y}, {parent, X, Z}, {female, Z}) when Y =/= Z ->
eresye:assert (Engine, {sister, Z, Y}).
Please note the guard, which is needed to ensure that when Y and Z are
bound to the same value, the rule is not activated (indeed this is possible
since the same fact can match both the first and second
'parent' pattern).
#### Concept: 'brother'
Given the previous one, this concept is now quite simple to
implement:
%%
%% if (Y and Z have the same parent X) and (Z is male)
%% then (Z is Y's brother)
%%
brother (Engine, {parent, X, Y}, {parent, X, Z}, {male, Z}) when Y =/= Z ->
eresye:assert (Engine, {brother, Z, Y}).
#### Concepts: 'grandmother' and 'grandfather'
The former concept can be expressed by means of the rule:
if X is Y's mother and Y is Z's parent, then X is Z's grandmother.
The latter concept is now obvious. Both can be implemented using the
following ERESYE rules:
%%
%% if (X is Y's mother) and (Y is Z's parent)
%% then (X is Z's grandmother)
%%
grandmother (Engine, {mother, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandmother, X, Z}).
%%
%% if (X is Y's father) and (Y is Z's parent)
%% then (X is Z's grandfather)
%%
grandfather (Engine, {father, X, Y}, {parent, Y, Z}) ->
eresye:assert (Engine, {grandfather, X, Z}).
Conclusions
-----------
This HowTo not only shows how to use the ERESYE engine to write an AI
application, but also highlights the versatility of the Erlang language:
the characteristics of functional and symbolic programming, together with
the possibility of performing *introspection* of function declaration,
can be successfully exploited for application domains which are completely
new for Erlang but can surely be very interesting.