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

DESCRIPTION
    Inspired in AI::ExpertSystem::Simple but with additional features:

    *   Uses backward, forward and mixed algorithms.

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

    *   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.

    *   Uses certainty factors.

Attributes
    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 it has at 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_dict
        For making easier your job, AI::ExpertSystem::Advanced asks you only
        the id of the "initial_facts". Once you provide them then a
        dictinary is created.

        This "initial_facts_dict" dictionary basically provides a standard
        interface to get the sign of the facts.

    goals_to_check
        When doing the "backward()" algorithm it's needed to have at least
        one goal (aka hypothesis).

        This could be pretty similar to "initial_facts", with the difference
        that the initial facts are used more with the causes of the rules,
        and this one with the goals (usually one in a well defined knowledge
        database).

        From our example of symptoms and diseases lets imagine we have the
        hypothesis that a patient has flu, we don't know the symptoms it
        has, we want the expert system to keep asking us for them to make
        sure that our hypothesis is correct.

    goals_to_check_dict
        Very similar to "goals_to_check" (and indeed of
        "initial_facts_dict"). We want to make the job easier at the moment
        of assigning goals and based on this only the list of goals is
        needed, then a dictionary will be created with the data of
        "goals_to_check".

    inference_facts
        Inference facts are basically the core of an expert system. These
        are facts that are found and copied when the a set of facts
        (initial, inference or asked) match with the causes of a goal.

        "inference_facts" is a AI::ExpertSystem::Advanced::Dictionary, it
        will store the name of the fact, the rule that caused these facts to
        be copied to this dictionary, the sign and the algorithm that
        triggered the copy.

    knowledge_db
        The object reference of the knowledge database
        AI::ExpertSystem::Advanced is using.

    asked_facts
        During the "backward()" algorithm there will be cases when there's
        no clarity if a fact exists. In these cases the "backward()" will be
        asking the user (via automation or real questions) if a fact exists.

        Going back to the "initial_facts" example of symptoms and diseases.
        Imagine the algorithm is checking a rule, some of the facts of the
        rule make a match with the ones of "initial_facts" or
        "inference_facts" but some wont, for these *unsure* facts the
        "backward()" will ask the user if a symptom (a fact) exists. All
        these asked facts will be stored here.

    visited_rules
        Keeps a record of all the rules the algorithms have visited.

    verbose
        By default this is turned off. If you want to know what happens
        behind the scenes turn this on.

        Everything that needs to be debugged will be passed to the "debug()"
        method of your "viewer".

    viewer
        Is the object AI::ExpertSystem::Advanced will be using for printing
        what is happening and for interacting with the user (such as asking
        the "asked_facts").

        This is practical if you want to use a viewer object that is not
        provided by AI::ExpertSystem::Advanced::Viewer::Factory.

    viewer_class
        Is the the class name of the "viewer".

        You can decide to use the viewers
        AI::ExpertSystem::Advanced::Viewer::Factory offers, in this case you
        can pass the object or only the name of your favorite viewer.

    found_factor
        In your knowledge database you can give different *weights* to the
        facts of each rule (eg to define what facts have more *priority*).
        During the "mixed()" algorithm it will be checking what causes are
        found in the "inference_facts" and in the "asked_facts"
        dictionaries, then the total number of matches (or total number of
        certainity factors of each fact) will be compared versus the value
        of this factor, if it's higher or equal then the rule will be
        triggered.

        You can read the documentation of the "mixed()" algorithm to know
        the two ways this factor can be used.

    shot_rules
        All the rules that are shot are stored here. This is a hash, the key
        of each item is the rule id while its value is the precision time
        when the rule was shot.

        The precision time is useful for knowing when a rule was shot and
        based on that you can know what steps it followed so you can compare
        (or reproduce) them.

Constants
    * FACT_SIGN_NEGATIVE
        Used when a fact is negative, aka, a fact doesn't happen.

    * FACT_SIGN_POSITIVE
        Used for those facts that happen.

    * FACT_SIGN_UNSURE
        Used when there's no straight answer of a fact, eg, we don't know if
        an answer will change the result.

Methods
  shoot($rule, $algorithm)
    Shoots the given rule. It will do the following verifications:

    *   Each of the facts (causes) will be compared against the
        "initial_facts_dict" and "asked_facts" (in this order).

    *   If an initial or asked fact matches with a cause but it's negative
        then all of its goals (usually only one by rule) will be copied to
        the "initial_facts_dict" and "inference_facts" with a negative sign,
        otherwise a positive sign will be used.

    *   Will add the rule to the "shot_rules" hash.

  is_rule_shot($rule)
    Verifies if the given $rule has been shot.

  get_goals_by_rule($rule)
    Will ask the "knowledge_db" for the goals of the given $rule. A
    AI::ExpertSystem::Advanced::Dictionary will be returned.

  get_causes_by_rule($rule)
    Will ask the "knowledge_db" for the causes of the given $rule. A
    AI::ExpertSystem::Advanced::Dictionary will be returned.

  is_fact_negative($dict_name, $fact)
    Will check if the given $fact of the given dictionary ($dict_name) is
    negative.

  copy_to_inference_facts($facts, $sign, $algorithm, $rule)
    Copies the given $facts (a dictionary, usually goal(s) of a rule) to the
    "inference_facts" dictionary. All the given goals will be copied with
    the given $sign.

    Additionally it will add the given $algorithm and $rule to the inference
    facts. So later we can know how we got to a certain inference fact.

  compare_causes_with_facts($rule)
    Compares the causes of the given $rule with:

    *   Initial facts

    *   Inference facts

    *   Asked facts

    It will be couting the matches of all of the above dictionaries, so for
    example if we have four causes, two make match with initial facts, other
    with inference and the remaining one with the asked facts, then it will
    evaluate to true since we have a match of the four causes.

  get_causes_match_factor($rule)
    Similar to "compare_causes_with_facts()" but with the difference that it
    will count the *match factor* of each matched cause and return the total
    of this weight.

    The match factor is used by the "mixed()" algorithm and is useful to
    know if a certain rule should be shoot or not.

    The *match factor* is calculated in two ways:

    *   Will do a sum of the weight for each matched cause. Please note that
        if only one cause of a rule has a specified weight then the
        remaining causes will default to the total weight minus 1 and then
        divided with the total number of causes (matched or not) that don't
        have a weight.

    *   If no weight is found with all the causes of the given rule, then
        the total number of matches will be divided by the total number of
        causes.

  is_goal_in_our_facts($goal)
    Checks if the given $goal is in:

    *   The asked facts

    *   The inference facts

  remove_last_visited_rule()
    Removes the last visited rule and return its number.

  visit_rule($rule)
    Adds the given $rule to the end of the "visited_rules".

  copy_to_goals_to_check($facts)
    Copies a list of facts (usually a list of causes of a rule) to
    <goals_to_check_dict>.

  ask_about($fact)
    Uses "viewer" to ask the user for the existence of the given "fact".

    The valid answers are:

    + or "FACT_SIGN_POSITIVE"
        In case user knows of it.

    - or "FACT_SIGN_NEGATIVE"
        In case user doesn't knows of it.

    ~ or "FACT_SIGN_UNSURE"
        In case user doesn't have any clue about the given fact.

  get_rule_by_goal($goal)
    Looks in the "knowledge_db" for the rule that has the given goal. If a
    rule is found its number is returned, otherwise undef.

  forward()
    The forward chaining algorithm is one of the main methods used in Expert
    Systems. It starts with a set of variables (known as initial facts) and
    reads the available rules.

    It will be reading rule by rule and for each one it will compare its
    causes with the initial facts and with the inference facts. If all of
    these causes are in our facts then the rule will be shoot and all of its
    goals will be copied/converted to inference facts and will restart
    reading from the first rule.

  backward()
    The backward algorithm starts with a set of *assumed* goals (facts). It
    will start reading goal by goal. For each goal it will check if it
    exists in the "asked_facts" and "inference_facts".

    *   If the goal exist then it will be removed from the dictionary, it
        will also verify if there are more visited rules to shoot.

        If there are still more visited rules to shoot then it will take the
        last one and remove it. Then this visited rule will be shoot. Once
        the rule is shoot it verifies if there are still still more goals to
        check, if this is the case then it starts reading from the first
        goal (at this time the "goals_to_check_dict" is reduced by 1.
        However if there are no more goals to check then it will finish,
        making the end of the algorithm.

        In case there are no more visited rules to shoot then it will finish
        making the end of the algorithm.

    *   If the goal doesn't exist in the "asked_facts" or "inference_facts"
        then the goal will be searched in the list of goals of all the
        rules.

        In case it finds the rule that has the goal, this rule will be
        marked (added) to the list of visited rules ("visited_rules"). Also
        all of the causes of this rule will be added to the top of the
        "goals_to_check_dict". Once this is done it will start reading again
        all of the goals to check.

        If there's the case where the goal doesn't exist as a goal in our
        rules then it will ask the user (via "ask_about") for the existence
        of it. If user is not sure about it then the algorithm ends.

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

    The first thing it does is to run the "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 *run* of "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 was found then it will start reading
    the rules and creating a list of intuitive facts.

    For each rule it will get a *certainty factor* of its causes versus the
    "initial_facts_dict", "inference_facts" and "asked_facts". In case the
    certainity factor is greater or equal than "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 for any intuitive fact, if
    no facts are found then it ends with the intuition, otherwise it will
    run the "backward()" algorithm for each one of these facts (eg, each
    fact will be converted to a goal). After each *run* of the "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 "forward()" algorithm and restarts (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 and diseases of a
    patient. Then the algorithm will start looking for any additional
    disease you could be missing. Then once it ends looking for diseases it
    will check if we know what the disease is (by looking for the positive
    fact). If there's still no clue then it starts looking in *viceversa*,
    now knowing a list of possible diseases and also a list of symptoms. It
    repeats all the process until a positive *inference* fact is found.

  summary($return)
    The main purpose of any expert system is the ability to explain: what is
    happening, how it got to a result, what assumption it required to make,
    which facts it excluded and which used.

    This method will use the "viewer" (or return the result) in YAML format
    of all the rules that were shot. It will explain how it got to each one
    of the causes so a better explanation can be done by the "viewer".

    If $return is defined (eg, it got any parameter) then the result wont be
    passed to the "viewer", instead it will be returned as a string.

SEE ALSO
    Take a look AI::ExpertSystem::Simple too.

AUTHOR
    Pablo Fischer (pablo@pablo.com.mx).

COPYRIGHT
    Copyright (C) 2010 by Pablo Fischer.

    This library is free software; you can redistribute it and/or modify it
    under the same terms as Perl itself.

