diff --git a/doctests.py b/doctests.py new file mode 100644 index 000000000..152136ea6 --- /dev/null +++ b/doctests.py @@ -0,0 +1,25 @@ +"""Run all doctests from modules on the command line. Use -v for verbose. + +Example usages: + + python doctests.py *.py + python doctests.py -v *.py + +You can add more module-level tests with + __doc__ += "..." +You can add stochastic tests with + __doc__ += random_tests("...") +""" + +from aima.utils import ignore + +if __name__ == "__main__": + import sys, glob, doctest + args = [arg for arg in sys.argv[1:] if arg != '-v'] + if not args: args = ['*.py'] + modules = [__import__(name.replace('.py','')) + for arg in args for name in glob.glob(arg)] + for module in modules: + doctest.testmod(module, report=1, optionflags=doctest.REPORT_UDIFF) + summary = doctest.master.summarize() if modules else (0, 0) + print '%d failed out of %d' % summary diff --git a/nqueens_problem.py b/nqueens_problem.py new file mode 100644 index 000000000..dd87e07d3 --- /dev/null +++ b/nqueens_problem.py @@ -0,0 +1,143 @@ +""" +N-Queens Problem as a Graph Search +""" +import numpy as np +import heapq +from itertools import chain + + +class PriorityQueue: + """ A priority queue based on heapq: push returns the lowest value (highest priority) + + >>> values = list(zip(range(7,0,-1), sorted('abcdefg', reverse=True))) + >>> values + [(7, 'g'), (6, 'f'), (5, 'e'), (4, 'd'), (3, 'c'), (2, 'b'), (1, 'a')] + >>> q = PriorityQueue(values) + >>> q + [(1, 'a'), (3, 'c'), (2, 'b'), (4, 'd'), (6, 'f'), (7, 'g'), (5, 'e')] + >>> q.pop() + (1, 'a') + >>> q + [(2, 'b'), (3, 'c'), (5, 'e'), (4, 'd'), (6, 'f'), (7, 'g')] + >>> q.pop() + (2, 'b') + >>> print(repr(q)) + [(3, 'c'), (4, 'd'), (5, 'e'), (7, 'g'), (6, 'f')] + >>> q.push((0, '')) + >>> q + [(0, ''), (4, 'd'), (3, 'c'), (7, 'g'), (6, 'f'), (5, 'e')] + >>> q.peek() + (0, '') + >>> q[0] + (0, '') + >>> q[0] + (0, '') + """ + + def __init__(self, values=()): + self.heap = list(values) + self.heapify(values) + for s in dir(self.heap): + if s.startswith('__') and not getattr(self, s, None): + setattr(self, s, getattr(self.heap, s)) + + def heapify(self, values=()): + self.heap = list(values) + return heapq.heapify(self.heap) + + def merge(self, *iterables): + self.heap = heapq.merge(chain([self.heap], *iterables)) + + def peek(self): + return self[0] + + def pop(self): + return heapq.heappop(self.heap) + + def push(self, value): + return heapq.heappush(self.heap, value) + + def pushpop(self, value): + return heapq.heappushpop(self.heap, value) + + def replace(self, value): + return heapq.heapreplace(self.heap, value) + + def __repr__(self): + return repr(self.heap) + + def __str__(self): + return str(self.heap) + + +class NQueens: + """ An NxN chessboard with N queens that are each "safe" from attack from all others """ + + def __init__(self, n=9): + self.frontier = list() + self.n = n + # 3 redundant representations + self.state = np.array([np.nan] * n) + self.sorted_state = np.array(sorted(self.state)) + self.grid = np.zeros((self.n, self.n)) + self.frontier = PriorityQueue() + + def isgoal(self, state): + state = state or self.state + return self.isvalid() and None not in self.state + + def isvalid(self, state=None): + if state is None: + self.update_state(state) + if np.isnan(self.state.sum()): + return False + if any(self.sorted_state != np.array(list(range(self.n)))): + return False + for i in range(int(self.n / 2)): + if np.diag(self.grid, i).sum() > 1: + return False + if np.diag(self.grid, -i).sum() > 1: + return False + return True + + def heuristic(self): + ones = np.ones(self.n) + s = (self.grid.sum(axis=0) - ones).abs().sum() + s += (self.grid.sum(axis=1) - ones).abs().sum() + for i in range(int(self.n / 2)): + s += (np.diag(self.grid, i) - 1).abs().sum() + s += (np.diag(self.grid, -i) - 1).abs().sum() + return s + + def place_queen(self, i, j): + self.state[i] = j + self.update_state() + + def remove_queen(self, i, j): + self.state[i] = np.nan + self.update_state() + + def sorted_grid(self, state): + sorted_state = np.array(sorted(self.state)) + grid = np.zeros((self.n, self.n)) + for i, j in enumerate(self.state): + grid[i, j] = 1 + return sorted_state, grid + + def update_state(self, state=None): + self.sorted_state, self.grid = self.sorted_grid(state or self.state) + + def expand_frontier(self): + empty_cols = np.arange(self.n)[self.grid.sum(axis=0)] + empty_rows = np.arange(self.n)[self.grid.sum(axis=1)] + for i in empty_cols: + for j in empty_rows: + state = self.state + state[i] = j + self.frontier.push((self.heuristic(state), state)) + + def __repr__(self): + return repr(self.heap) + + def __str__(self): + return str(self.heap) diff --git a/probability.doctest b/probability.doctest new file mode 100644 index 000000000..bd0f9436d --- /dev/null +++ b/probability.doctest @@ -0,0 +1,72 @@ + +>>> cpt = burglary.variable_node('Alarm').cpt +>>> parents = ['Burglary', 'Earthquake'] +>>> event = {'Burglary': True, 'Earthquake': True} +>>> print '%4.2f' % cpt.p(True, parents, event) +0.95 +>>> event = {'Burglary': False, 'Earthquake': True} +>>> print '%4.2f' % cpt.p(False, parents, event) +0.71 +>>> BoolCPT({T: 0.2, F: 0.625}).p(False, ['Burglary'], event) +0.375 +>>> BoolCPT(0.75).p(False, [], {}) +0.25 + +(fixme: The following test p_values which has been folded into p().) +>>> cpt = BoolCPT(0.25) +>>> cpt.p_values(F, ()) +0.75 +>>> cpt = BoolCPT({T: 0.25, F: 0.625}) +>>> cpt.p_values(T, (T,)) +0.25 +>>> cpt.p_values(F, (F,)) +0.375 +>>> cpt = BoolCPT({(T, T): 0.2, (T, F): 0.31, +... (F, T): 0.5, (F, F): 0.62}) +>>> cpt.p_values(T, (T, F)) +0.31 +>>> cpt.p_values(F, (F, F)) +0.38 + + +>>> cpt = BoolCPT({True: 0.2, False: 0.7}) +>>> cpt.rand(['A'], {'A': True}) in [True, False] +True +>>> cpt = BoolCPT({(True, True): 0.1, (True, False): 0.3, +... (False, True): 0.5, (False, False): 0.7}) +>>> cpt.rand(['A', 'B'], {'A': True, 'B': False}) in [True, False] +True + + +>>> enumeration_ask('Earthquake', {}, burglary).show_approx() +'False: 0.998, True: 0.002' + + +>>> s = prior_sample(burglary) +>>> s['Burglary'] in [True, False] +True +>>> s['Alarm'] in [True, False] +True +>>> s['JohnCalls'] in [True, False] +True +>>> len(s) +5 + + +>>> s = {'A': True, 'B': False, 'C': True, 'D': False} +>>> consistent_with(s, {}) +True +>>> consistent_with(s, s) +True +>>> consistent_with(s, {'A': False}) +False +>>> consistent_with(s, {'D': True}) +False + +>>> seed(21); p = rejection_sampling('Earthquake', {}, burglary, 1000) +>>> [p[True], p[False]] +[0.001, 0.999] + +>>> seed(71); p = likelihood_weighting('Earthquake', {}, burglary, 1000) +>>> [p[True], p[False]] +[0.002, 0.998] diff --git a/skeleton.py b/skeleton.py new file mode 100644 index 000000000..eeabf89ff --- /dev/null +++ b/skeleton.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This is a skeleton file that can serve as a starting point for a Python +console script. To run this script uncomment the following lines in the +[options.entry_points] section in setup.cfg: + + console_scripts = + fibonacci = aima.skeleton:run + +Then run `python setup.py install` which will install the command `fibonacci` +inside your current environment. +Besides console scripts, the header (i.e. until _logger...) of this file can +also be used as template for Python modules. + +Note: This skeleton file can be safely removed if not needed! +""" + +import argparse +import sys +import logging + +from aima import __version__ + +__author__ = "Hobson Lane" +__copyright__ = "Hobson Lane" +__license__ = "mit" + +_logger = logging.getLogger(__name__) + + +def fib(n): + """Fibonacci example function + + Args: + n (int): integer + + Returns: + int: n-th Fibonacci number + """ + assert n > 0 + a, b = 1, 1 + for i in range(n-1): + a, b = b, a+b + return a + + +def parse_args(args): + """Parse command line parameters + + Args: + args ([str]): command line parameters as list of strings + + Returns: + :obj:`argparse.Namespace`: command line parameters namespace + """ + parser = argparse.ArgumentParser( + description="Just a Fibonnaci demonstration") + parser.add_argument( + '--version', + action='version', + version='aima {ver}'.format(ver=__version__)) + parser.add_argument( + dest="n", + help="n-th Fibonacci number", + type=int, + metavar="INT") + parser.add_argument( + '-v', + '--verbose', + dest="loglevel", + help="set loglevel to INFO", + action='store_const', + const=logging.INFO) + parser.add_argument( + '-vv', + '--very-verbose', + dest="loglevel", + help="set loglevel to DEBUG", + action='store_const', + const=logging.DEBUG) + return parser.parse_args(args) + + +def setup_logging(loglevel): + """Setup basic logging + + Args: + loglevel (int): minimum loglevel for emitting messages + """ + logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s" + logging.basicConfig(level=loglevel, stream=sys.stdout, + format=logformat, datefmt="%Y-%m-%d %H:%M:%S") + + +def main(args): + """Main entry point allowing external calls + + Args: + args ([str]): command line parameter list + """ + args = parse_args(args) + setup_logging(args.loglevel) + _logger.debug("Starting crazy calculations...") + print("The {}-th Fibonacci number is {}".format(args.n, fib(args.n))) + _logger.info("Script ends here") + + +def run(): + """Entry point for console_scripts + """ + main(sys.argv[1:]) + + +if __name__ == "__main__": + run()