logic.py 48,2 ko
Newer Older
withal's avatar
withal a validé
class HybridWumpusAgent(agents.Agent):
    """An agent for the wumpus world that does logical inference. [Figure 7.20]"""
        super().__init__()
        self.kb = WumpusKB(self.dimrow)
        self.t = 0
        self.plan = list()
        self.current_position = WumpusPosition(1, 1, 'UP')


    def execute(self, percept):
        self.kb.make_percept_sentence(percept, self.t)
        self.kb.add_temporal_sentences(self.t)

        temp = list()

        for i in range(1, self.dimrow+1):
            for j in range(1, self.dimrow+1):
                if self.kb.ask_if_true(location(i, j, self.t)):
                    temp.append(i)
                    temp.append(j)

        if self.kb.ask_if_true(facing_north(self.t)):
            self.current_position = WumpusPosition(temp[0], temp[1], 'UP')
        elif self.kb.ask_if_true(facing_south(self.t)):
            self.current_position = WumpusPosition(temp[0], temp[1], 'DOWN')
        elif self.kb.ask_if_true(facing_west(self.t)):
            self.current_position = WumpusPosition(temp[0], temp[1], 'LEFT')
        elif self.kb.ask_if_true(facing_east(self.t)):
            self.current_position = WumpusPosition(temp[0], temp[1], 'RIGHT')

        safe_points = list()
        for i in range(1, self.dimrow+1):
            for j in range(1, self.dimrow+1):
                if self.kb.ask_if_true(ok_to_move(i, j, self.t)):
                    safe_points.append([i, j])

        if self.kb.ask_if_true(percept_glitter(self.t)):
            goals = list()
            goals.append([1, 1])
            self.plan.append('Grab')
            actions = plan_route(self.current_position,goals,safe_points)
            for action in actions:
                self.plan.append(action)
            self.plan.append('Climb')

        if len(self.plan) == 0:
            unvisited = list()
            for i in range(1, self.dimrow+1):
                for j in range(1, self.dimrow+1):
                    for k in range(self.t):
                        if self.kb.ask_if_true(location(i, j, k)):
                            unvisited.append([i, j])
            unvisited_and_safe = list()
            for u in unvisited:
                for s in safe_points:
                    if u not in unvisited_and_safe and s == u:
                        unvisited_and_safe.append(u)

            temp = plan_route(self.current_position,unvisited_and_safe,safe_points)
            for t in temp:
                self.plan.append(t)

        if len(self.plan) == 0 and self.kb.ask_if_true(have_arrow(self.t)):
            possible_wumpus = list()
            for i in range(1, self.dimrow+1):
                for j in range(1, self.dimrow+1):
                    if not self.kb.ask_if_true(wumpus(i, j)):
                        possible_wumpus.append([i, j])

            temp = plan_shot(self.current_position, possible_wumpus, safe_points)
            for t in temp:
                self.plan.append(t)

        if len(self.plan) == 0:
            not_unsafe = list()
            for i in range(1, self.dimrow+1):
                for j in range(1, self.dimrow+1):
                    if not self.kb.ask_if_true(ok_to_move(i, j, self.t)):
                        not_unsafe.append([i, j])
            temp = plan_route(self.current_position, not_unsafe, safe_points)
            for t in temp:
                self.plan.append(t)

        if len(self.plan) == 0:
            start = list()
            start.append([1, 1])
            temp = plan_route(self.current_position, start, safe_points)
            for t in temp:
                self.plan.append(t)
            self.plan.append('Climb')

        action = self.plan[0]
        self.plan = self.plan[1:]
        self.kb.make_action_sentence(action, self.t)
        self.t += 1

        return action
withal's avatar
withal a validé
def plan_route(current, goals, allowed):
    
def plan_shot(current, goals, allowed):
    raise NotImplementedError


# ______________________________________________________________________________
withal's avatar
withal a validé
def SAT_plan(init, transition, goal, t_max, SAT_solver=dpll_satisfiable):
C.G.Vedant's avatar
C.G.Vedant a validé
    """Converts a planning problem to Satisfaction problem by translating it to a cnf sentence.
    # Functions used by SAT_plan
C.G.Vedant's avatar
C.G.Vedant a validé
    def translate_to_SAT(init, transition, goal, time):
        clauses = []
        states = [state for state in transition]
        # Symbol claiming state s at time t
        state_counter = itertools.count()
C.G.Vedant's avatar
C.G.Vedant a validé
        for s in states:
            for t in range(time+1):
                state_sym[s, t] = Expr("State_{}".format(next(state_counter)))
        # Add initial state axiom
C.G.Vedant's avatar
C.G.Vedant a validé
        clauses.append(state_sym[init, 0])

        # Add goal state axiom
C.G.Vedant's avatar
C.G.Vedant a validé
        clauses.append(state_sym[goal, time])

        # All possible transitions
        transition_counter = itertools.count()
C.G.Vedant's avatar
C.G.Vedant a validé
        for s in states:
            for action in transition[s]:
                s_ = transition[s][action]
                for t in range(time):
                    # Action 'action' taken from state 's' at time 't' to reach 's_'
                    action_sym[s, action, t] = Expr(
                        "Transition_{}".format(next(transition_counter)))
C.G.Vedant's avatar
C.G.Vedant a validé

                    # Change the state from s to s_
Peter Norvig's avatar
Peter Norvig a validé
                    clauses.append(action_sym[s, action, t] |'==>'| state_sym[s, t])
                    clauses.append(action_sym[s, action, t] |'==>'| state_sym[s_, t + 1])
        # Allow only one state at any time
C.G.Vedant's avatar
C.G.Vedant a validé
        for t in range(time+1):
            # must be a state at any time
            clauses.append(associate('|', [state_sym[s, t] for s in states]))
C.G.Vedant's avatar
C.G.Vedant a validé

            for s in states:
                for s_ in states[states.index(s) + 1:]:
                    # for each pair of states s, s_ only one is possible at time t
C.G.Vedant's avatar
C.G.Vedant a validé
                    clauses.append((~state_sym[s, t]) | (~state_sym[s_, t]))

        # Restrict to one transition per timestep
C.G.Vedant's avatar
C.G.Vedant a validé
        for t in range(time):
            # list of possible transitions at time t
C.G.Vedant's avatar
C.G.Vedant a validé
            transitions_t = [tr for tr in action_sym if tr[2] == t]

            # make sure at least one of the transitions happens
            clauses.append(associate('|', [action_sym[tr] for tr in transitions_t]))
C.G.Vedant's avatar
C.G.Vedant a validé

            for tr in transitions_t:
                for tr_ in transitions_t[transitions_t.index(tr) + 1:]:
                    # there cannot be two transitions tr and tr_ at time t
                    clauses.append(~action_sym[tr] | ~action_sym[tr_])
        # Combine the clauses to form the cnf
C.G.Vedant's avatar
C.G.Vedant a validé
        return associate('&', clauses)

    def extract_solution(model):
        true_transitions = [t for t in action_sym if model[action_sym[t]]]
        # Sort transitions based on time, which is the 3rd element of the tuple
        true_transitions.sort(key=lambda x: x[2])
        return [action for s, action, time in true_transitions]
    # Body of SAT_plan algorithm
withal's avatar
withal a validé
    for t in range(t_max):
        # dictionaries to help extract the solution from model
        state_sym = {}
        action_sym = {}

withal's avatar
withal a validé
        cnf = translate_to_SAT(init, transition, goal, t)
        model = SAT_solver(cnf)
        if model is not False:
            return extract_solution(model)
    return None

# ______________________________________________________________________________
def unify(x, y, s={}):
    """Unify expressions x,y with substitution s; return a substitution that
    would make x,y equal, or None if x,y can not unify. x and y can be
    variables (e.g. Expr('x')), constants, lists, or Exprs. [Figure 9.1]"""
    if s is None:
        return None
    elif x == y:
        return s
    elif is_variable(x):
        return unify_var(x, y, s)
    elif is_variable(y):
        return unify_var(y, x, s)
    elif isinstance(x, Expr) and isinstance(y, Expr):
        return unify(x.args, y.args, unify(x.op, y.op, s))
    elif isinstance(x, str) or isinstance(y, str):
        return None
    elif issequence(x) and issequence(y) and len(x) == len(y):
MircoT's avatar
MircoT a validé
        if not x:
            return s
        return unify(x[1:], y[1:], unify(x[0], y[0], s))
    else:
        return None

    """A variable is an Expr with no args and a lowercase symbol as the op."""
Peter Norvig's avatar
Peter Norvig a validé
    return isinstance(x, Expr) and not x.args and x.op[0].islower()
def unify_var(var, x, s):
    if var in s:
        return unify(s[var], x, s)
C.G.Vedant's avatar
C.G.Vedant a validé
    elif x in s:
        return unify(var, s[x], s)
    elif occur_check(var, x, s):
def occur_check(var, x, s):
    """Return true if variable var occurs anywhere in x
    (or in subst(s, x), if s has a binding for x)."""
    elif is_variable(x) and x in s:
        return occur_check(var, s[x], s)
        return (occur_check(var, x.op, s) or
                occur_check(var, x.args, s))
withal's avatar
withal a validé
    elif isinstance(x, (list, tuple)):
norvig's avatar
norvig a validé
        return first(e for e in x if occur_check(var, e, s))
    else:
        return False
    """Copy the substitution s and extend it by setting var to val; return copy."""
def subst(s, x):
    """Substitute the substitution s into the expression x.
    >>> subst({x: 42, y:0}, F(x) + y)
    (F(42) + 0)
    """
withal's avatar
withal a validé
    if isinstance(x, list):
        return [subst(s, xi) for xi in x]
withal's avatar
withal a validé
    elif isinstance(x, tuple):
        return tuple([subst(s, xi) for xi in x])
withal's avatar
withal a validé
    elif not isinstance(x, Expr):
withal's avatar
withal a validé
    elif is_var_symbol(x.op):
withal's avatar
withal a validé
    else:
        return Expr(x.op, *[subst(s, arg) for arg in x.args])
withal's avatar
withal a validé
def standardize_variables(sentence, dic=None):
Peter Norvig's avatar
Peter Norvig a validé
    """Replace all the variables in sentence with new variables."""
MircoT's avatar
MircoT a validé
    if dic is None:
        dic = {}
    if not isinstance(sentence, Expr):
withal's avatar
withal a validé
    elif is_var_symbol(sentence.op):
        if sentence in dic:
            return dic[sentence]
        else:
            v = Expr('v_{}'.format(next(standardize_variables.counter)))
withal's avatar
withal a validé
    else:
        return Expr(sentence.op,
withal's avatar
withal a validé
                    *[standardize_variables(a, dic) for a in sentence.args])
withal's avatar
withal a validé
standardize_variables.counter = itertools.count()
# ______________________________________________________________________________
class FolKB(KB):
    """A knowledge base consisting of first-order definite clauses.
    >>> kb0 = FolKB([expr('Farmer(Mac)'), expr('Rabbit(Pete)'),
    ...              expr('(Rabbit(r) & Farmer(f)) ==> Hates(f, r)')])
    >>> kb0.tell(expr('Rabbit(Flopsie)'))
    >>> kb0.retract(expr('Rabbit(Pete)'))
    >>> kb0.ask(expr('Hates(Mac, x)'))[x]
    Flopsie
    >>> kb0.ask(expr('Wife(Pete, x)'))
    False
    def __init__(self, initial_clauses=None):
MircoT's avatar
MircoT a validé
        self.clauses = []  # inefficient: no indexing
        if initial_clauses:
            for clause in initial_clauses:
                self.tell(clause)

    def tell(self, sentence):
        if is_definite_clause(sentence):
            self.clauses.append(sentence)
        else:
            raise Exception("Not a definite clause: {}".format(sentence))

    def ask_generator(self, query):
withal's avatar
withal a validé
        return fol_bc_ask(self, query)

    def retract(self, sentence):
        self.clauses.remove(sentence)

withal's avatar
withal a validé
    def fetch_rules_for_goal(self, goal):
        return self.clauses

def fol_fc_ask(KB, alpha):
    """A simple forward-chaining algorithm. [Figure 9.3]"""
    # TODO: Improve efficiency
C.G.Vedant's avatar
C.G.Vedant a validé
    kb_consts = list({c for clause in KB.clauses for c in constant_symbols(clause)})
    def enum_subst(p):
        query_vars = list({v for clause in p for v in variables(clause)})
        for assignment_list in itertools.product(kb_consts, repeat=len(query_vars)):
            theta = {x: y for x, y in zip(query_vars, assignment_list)}
            yield theta

    # check if we can answer without new inferences
    for q in KB.clauses:
        phi = unify(q, alpha, {})
        if phi is not None:
            yield phi
    while True:
        new = []
        for rule in KB.clauses:
            p, q = parse_definite_clause(rule)
C.G.Vedant's avatar
C.G.Vedant a validé
            for theta in enum_subst(p):
                if set(subst(theta, p)).issubset(set(KB.clauses)):
                    q_ = subst(theta, q)
                    if all([unify(x, q_, {}) is None for x in KB.clauses + new]):
                        new.append(q_)
                        phi = unify(q_, alpha, {})
                        if phi is not None:
                            yield phi
        if not new:
            break
        for clause in new:
            KB.tell(clause)
    return None
withal's avatar
withal a validé
def fol_bc_ask(KB, query):
    """A simple backward-chaining algorithm for first-order logic. [Figure 9.6]
    KB should be an instance of FolKB, and query an atomic sentence."""
withal's avatar
withal a validé
    return fol_bc_or(KB, query, {})

withal's avatar
withal a validé
def fol_bc_or(KB, goal, theta):
    for rule in KB.fetch_rules_for_goal(goal):
        lhs, rhs = parse_definite_clause(standardize_variables(rule))
        for theta1 in fol_bc_and(KB, lhs, unify(rhs, goal, theta)):
            yield theta1

withal's avatar
withal a validé
def fol_bc_and(KB, goals, theta):
    if theta is None:
        pass
    elif not goals:
        yield theta
withal's avatar
withal a validé
    else:
        first, rest = goals[0], goals[1:]
        for theta1 in fol_bc_or(KB, subst(theta, first), theta):
            for theta2 in fol_bc_and(KB, rest, theta1):
                yield theta2
C.G.Vedant's avatar
C.G.Vedant a validé
# A simple KB that defines the relevant conditions of the Wumpus World as in Fig 7.4.
# See Sec. 7.4.3
wumpus_kb = PropKB()

P11, P12, P21, P22, P31, B11, B21 = expr('P11, P12, P21, P22, P31, B11, B21')
wumpus_kb.tell(~P11)
wumpus_kb.tell(B11 | '<=>' | ((P12 | P21)))
wumpus_kb.tell(B21 | '<=>' | ((P11 | P22 | P31)))
wumpus_kb.tell(~B11)
wumpus_kb.tell(B21)

test_kb = FolKB(
    map(expr, ['Farmer(Mac)',
               'Rabbit(Pete)',
               'Mother(MrsMac, Mac)',
               'Mother(MrsRabbit, Pete)',
               '(Rabbit(r) & Farmer(f)) ==> Hates(f, r)',
               '(Mother(m, c)) ==> Loves(m, c)',
               '(Mother(m, r) & Rabbit(r)) ==> Rabbit(m)',
               '(Farmer(f)) ==> Human(f)',
               # Note that this order of conjuncts
               # would result in infinite recursion:
               # '(Human(h) & Mother(m, h)) ==> Human(m)'
               '(Mother(m, h) & Human(h)) ==> Human(m)'
               ]))

crime_kb = FolKB(
    map(expr, ['(American(x) & Weapon(y) & Sells(x, y, z) & Hostile(z)) ==> Criminal(x)',
               'Owns(Nono, M1)',
               'Missile(M1)',
               '(Missile(x) & Owns(Nono, x)) ==> Sells(West, x, Nono)',
               'Missile(x) ==> Weapon(x)',
               'Enemy(x, America) ==> Hostile(x)',
               'American(West)',
               'Enemy(Nono, America)'
               ]))

# ______________________________________________________________________________

# Example application (not in the book).
# You can use the Expr class to do symbolic differentiation.  This used to be
# a part of AI; now it is considered a separate field, Symbolic Algebra.

def diff(y, x):
    """Return the symbolic derivative, dy/dx, as an Expr.
    However, you probably want to simplify the results with simp.
    >>> diff(x * x, x)
    ((x * 1) + (x * 1))
    """
MircoT's avatar
MircoT a validé
    if y == x:
Peter Norvig's avatar
Peter Norvig a validé
        return 1
MircoT's avatar
MircoT a validé
    elif not y.args:
Peter Norvig's avatar
Peter Norvig a validé
        return 0
    else:
        u, op, v = y.args[0], y.op, y.args[-1]
MircoT's avatar
MircoT a validé
        if op == '+':
            return diff(u, x) + diff(v, x)
        elif op == '-' and len(y.args) == 1:
MircoT's avatar
MircoT a validé
            return -diff(u, x)
        elif op == '-':
            return diff(u, x) - diff(v, x)
        elif op == '*':
            return u * diff(v, x) + v * diff(u, x)
        elif op == '/':
            return (v * diff(u, x) - u * diff(v, x)) / (v * v)
        elif op == '**' and isnumber(x.op):
            return (v * u ** (v - 1) * diff(u, x))
MircoT's avatar
MircoT a validé
        elif op == '**':
            return (v * u ** (v - 1) * diff(u, x) +
                    u ** v * Expr('log')(u) * diff(v, x))
MircoT's avatar
MircoT a validé
        elif op == 'log':
            return diff(u, x) / u
        else:
            raise ValueError("Unknown op: {} in diff({}, {})".format(op, y, x))
    """Simplify the expression x."""
    if isnumber(x) or not x.args:
MircoT's avatar
MircoT a validé
        return x
MircoT's avatar
MircoT a validé
    args = list(map(simp, x.args))
    u, op, v = args[0], x.op, args[-1]
withal's avatar
withal a validé
    if op == '+':
Peter Norvig's avatar
Peter Norvig a validé
        if v == 0:
MircoT's avatar
MircoT a validé
            return u
Peter Norvig's avatar
Peter Norvig a validé
        if u == 0:
MircoT's avatar
MircoT a validé
            return v
        if u == v:
Peter Norvig's avatar
Peter Norvig a validé
            return 2 * u
MircoT's avatar
MircoT a validé
        if u == -v or v == -u:
Peter Norvig's avatar
Peter Norvig a validé
            return 0
withal's avatar
withal a validé
    elif op == '-' and len(args) == 1:
MircoT's avatar
MircoT a validé
        if u.op == '-' and len(u.args) == 1:
            return u.args[0]  # --y ==> y
withal's avatar
withal a validé
    elif op == '-':
Peter Norvig's avatar
Peter Norvig a validé
        if v == 0:
MircoT's avatar
MircoT a validé
            return u
Peter Norvig's avatar
Peter Norvig a validé
        if u == 0:
MircoT's avatar
MircoT a validé
            return -v
        if u == v:
Peter Norvig's avatar
Peter Norvig a validé
            return 0
MircoT's avatar
MircoT a validé
        if u == -v or v == -u:
Peter Norvig's avatar
Peter Norvig a validé
            return 0
withal's avatar
withal a validé
    elif op == '*':
Peter Norvig's avatar
Peter Norvig a validé
        if u == 0 or v == 0:
            return 0
        if u == 1:
MircoT's avatar
MircoT a validé
            return v
Peter Norvig's avatar
Peter Norvig a validé
        if v == 1:
MircoT's avatar
MircoT a validé
            return u
        if u == v:
            return u ** 2
withal's avatar
withal a validé
    elif op == '/':
Peter Norvig's avatar
Peter Norvig a validé
        if u == 0:
            return 0
        if v == 0:
MircoT's avatar
MircoT a validé
            return Expr('Undefined')
        if u == v:
Peter Norvig's avatar
Peter Norvig a validé
            return 1
MircoT's avatar
MircoT a validé
        if u == -v or v == -u:
Peter Norvig's avatar
Peter Norvig a validé
            return 0
withal's avatar
withal a validé
    elif op == '**':
Peter Norvig's avatar
Peter Norvig a validé
        if u == 0:
            return 0
        if v == 0:
            return 1
        if u == 1:
            return 1
        if v == 1:
MircoT's avatar
MircoT a validé
            return u
withal's avatar
withal a validé
    elif op == 'log':
Peter Norvig's avatar
Peter Norvig a validé
        if u == 1:
            return 0
MircoT's avatar
MircoT a validé
    else:
        raise ValueError("Unknown op: " + op)
    # If we fall through to here, we can not simplify further
    """Differentiate and then simplify."""
withal's avatar
withal a validé
    return simp(diff(y, x))