AI-ExpertSystem-Advanced

 view release on metacpan or  search on metacpan

lib/AI/ExpertSystem/Advanced.pm  view on Meta::CPAN

#
# AI::ExpertSystem::Advanced
#
# Author(s): Pablo Fischer (pfischer@cpan.org)
# Created: 11/29/2009 18:28:30 CST 18:28:30
package AI::ExpertSystem::Advanced;

=head1 NAME

AI::ExpertSystem::Advanced - Expert System with backward, forward and mixed algorithms

=head1 DESCRIPTION

Inspired in L<AI::ExpertSystem::Simple> but with additional features:

=over 4

=item *

Uses backward, forward and mixed algorithms.

=item *

Offers different views, so user can interact with the expert system via a
terminal or with a friendly user interface.

=item *

The knowledge database can be stored in any format such as YAML, XML or
databases. You just need to choose what driver to use and you are done.

=item *

Uses certainty factors.

=back

=head1 SYNOPSIS

An example of the mixed algorithm:

    use AI::ExpertSystem::Advanced;
    use AI::ExpertSystem::Advanced::KnowledgeDB::Factory;

    my $yaml_kdb = AI::ExpertSystem::Advanced::KnowledgeDB::Factory->new('yaml',
        {
            filename => 'examples/knowledge_db_one.yaml'
        });

    my $ai = AI::ExpertSystem::Advanced->new(
            viewer_class => 'terminal',
            knowledge_db => $yaml_kdb,
            initial_facts => ['I'],
            verbose => 1);
    $ai->mixed();
    $ai->summary();

=cut
use Moose;
use AI::ExpertSystem::Advanced::KnowledgeDB::Base;
use AI::ExpertSystem::Advanced::Viewer::Base;
use AI::ExpertSystem::Advanced::Viewer::Factory;
use AI::ExpertSystem::Advanced::Dictionary;
use Time::HiRes qw(gettimeofday);
use YAML::Syck qw(Dump);

our $VERSION = '0.03';

=head1 Attributes

=over 4

=item B<initial_facts>

A list/set of initial facts the algorithms start using.

During the forward algorithm the task is to find a list of goals caused
by these initial facts (the only data we have in that moment).

Lets imagine your knowledge database is about symptoms and diseases. You need
to find what diseases are caused by the symptoms of a patient, these first
symptons are the initial facts.

Initial facts as also asked and inference facts can be negative or positive. By
default the initial facts are positive.

Keep in mind that the data contained in this array can be the IDs or the name
of the fact.

This array will be converted to L<initial_facts_dict>. And all the data (ids or
or names) will be made of only IDs.

    my $ai = AI::ExpertSystem::Advanced->new(
            viewer_class => 'terminal',

lib/AI/ExpertSystem/Advanced.pm  view on Meta::CPAN

                my $rule_of_goal =  $self->get_rule_by_goal($goal);
                if (defined $rule_of_goal) {
                    $self->{'viewer'}->debug("Found a rule with $goal as a goal")
                        if $self->{'debug'};
                    # Causes of this rule
                    my $rule_causes = $self->get_causes_by_rule($rule_of_goal);
                    # Copy the causes of this rule to our goals to check
                    $self->copy_to_goals_to_check($rule_of_goal, $rule_causes);
                    # We just *visited* this rule, lets check it
                    $self->visit_rule($rule_of_goal, $rule_causes->size());
                    # and yes.. we have more goals to check!
                    $self->{'goals_to_check_dict'}->populate_iterable_array();
                    $more_goals = 1;
                    next WAIT_FOR_MORE_GOALS;
                } else {
                    # Ooops, lets ask about this
                    # We usually get to this case when any of the copied causes
                    # does not exists as a goal in any of the rules
                    my $answer = $self->ask_about($goal);
                    if (
                            $answer eq FACT_SIGN_POSITIVE or
                            $answer eq FACT_SIGN_NEGATIVE) {
                        $self->{'asked_facts'}->append($goal,
                                {
                                    name => $goal,
                                    sign => $answer,
                                    algorithm => 'backward'
                                });
                    } else {
                        $self->{'viewer'}->debug(
                                "Don't know of $goal, nothing else to check"
                                );
                        return 0;
                    }
                    $self->{'goals_to_check_dict'}->populate_iterable_array();
                    $more_goals = 1;
                    next WAIT_FOR_MORE_GOALS;
                }
            }
        }
    }
    return 1;
}

=head2 B<mixed()>

As its name says, it's a mix of L<forward()> and L<backward()> algorithms, it
requires to have at least one initial fact.

The first thing it does is to run the L<forward()> algorithm (hence the need of
at least one initial fact). If the algorithm fails then the mixed algorithm
also ends unsuccessfully.

Once the first I<run> of L<forward()> algorithm happens it starts looking for
any positive inference fact, if only one is found then this ends the algorithm
with the assumption it knows what's happening.

In case no positive inference fact is found then it will start reading the
rules and creating a list of intuitive facts.

For each rule it will get a I<certainty factor> of its causes versus the
initial, inference and asked facts. In case the certainity factor is greater or
equal than L<found_factor> then all of its goals will be copied to the
intuitive facts (eg, read it as: it assumes the goals have something to do with
our first initial facts).

Once all the rules are read then it verifies if there are intuitive facts, if
no facts are found then it ends with the intuition, otherwise it will run the
L<backward()> algorithm for each one of these facts (eg, each fact will be
converted to a goal). After each I<run> of the L<backward()> algorithm it will
verify for any positive inference fact, if just one is found then the algorithm
ends.

At the end (if there are still no positive inference facts) it will run the
L<forward()> algorithm and restart (by looking again for any positive inference
fact).

A good example to understand how this algorithm is useful is: imagine you are
a doctor and know some of the symptoms of a patient. Probably with the first
symptoms you have you can get to a positive conclusion (eg that a patient has
I<X> disease). However in case there's still no clue, then a set of questions
(done by the call of L<backward()>) of symptons related to the initial symptoms
will be asked to the user. For example, we know that that the patient has a
headache but that doesn't give us any positive answer, what if the patient has
flu or another disease? Then a set of these I<related> symptons will be asked
to the user.

=cut
sub mixed {
    my ($self) = @_;

    if (!$self->forward()) {
        $self->{'viewer'}->print_error("The first execution of forward failed");
        return 0;
    }

    use Data::Dumper;

    while(1) {
        # We are satisfied if only one inference fact is positive (eg, means we
        # got to our result)
        while(my $fact = $self->{'inference_facts'}->iterate) {
            my $sign = $self->{'inference_facts'}->get_value($fact, 'sign');
            if ($sign eq FACT_SIGN_POSITIVE) {
                $self->{'viewer'}->debug(
                        "We are done, a positive fact was found"
                        );
                return 1;
            }
        }

        my $intuitive_facts = AI::ExpertSystem::Advanced::Dictionary->new(
                stack => []);

        my ($more_rules, $current_rule) = (1, undef);
        while($more_rules) {
            $current_rule = $self->{'knowledge_db'}->get_next_rule($current_rule);

            # No more rules?
            if (!defined $current_rule) {
                $self->{'viewer'}->debug("We are done with all the rules, bye")



( run in 1.049 second using v1.01-cache-2.11-cpan-39bf76dae61 )