Answers to textbook problems are not online.
Answers vary, but as you know, functional specifications have to meet some minimal standards to be "functional specifications."
In your essay, I'll be looking for whether you are consistent in your answer. Either you have to hammer away at the idea that robotics, speech recognition and vision have suffered greatly from everyone focusing on problem solving and knowledge representation, or at the idea that the thinking part of AI is completely indispensible since without it, we'd be stuck with pure reflex agents, which we don't consider intelligent.
Music Composer Performance Measures - number of measures composed per unit time, number of instruments considered, ease of play by a human, range of frequencies within human audible zone, melodic, harmonic and rhythmic criteria, ... Environment Software Actuators None required, this can be a pure softbot Sensors Code that reads in basic parameters Aircraft Autolander Performance Measures Lack of damage to plane, other aircraft or ground structures, lack of injuries to passengers or ground crew or other innocent observers, cargo remains intact, fuel economy, lands at correct airport on correct runway, doesn't take too long Environment Lower atmosphere and surface of planet Earth. Actuators Throttle, landing gear, rudders, ailerons, flaps ... Sensors Cameras, Altimeter, Spedometer, other meters, ... Essay Evaluator Performance Measures awards scores for quality, penalizes crap, detection of plaigarism, impartiality, usefulness of explanation of grading, ... Environment Software Actuators None, this can be a pure softbot Sensors File reading software, (perhaps even OCR) Robotic Sentry Gun for the Keck Lab Performance Measures Percentage of correct targets hit, lack of hitting friends, minimal energy consumption, ... Environment The Keck Lab Actuators Gun, trigger, motors, camera, ... Sensors Camera, sonar, bump sensors, ...
By "pure" reflex agent we mean the agent acts on the basis of the current percept only, and ignores the rest of the percept history. By rational we mean the agent tries to maximize the performance measure. What are the maxiums? If the world starts with two dirty squares the highest score the agent could get is 3. If it starts with one clean and one dirty square then the maximum achievable score for the agent is 2 if it starts in the dirty square and 1 if it starts in the clean one. If both squares start off clean, the best the agent can do is get 0 points. To be rational the agent needs a function which does the best it can over all situations. We don't know the probability distribution of starting states, so let's assume all are equally likely. How many possible agent functions are there for pure reflex agents? There are only four possible percepts and four possible actions (left, right, suck, nop), so there are 4^4 = 256 possible functions. We could take each of these possible functions and compute their scores on each of the four possible initial environments and see which function does the best when averaged over all of them. Instead of trying all 1024 combinations, we can rule out a few to begin with using our own brains. First, we can replace all clauses in which we try to move right in B or left in A with nops. Second, we cannot allow both (A, clean) and (B, clean) to both result in moves, or else the vacuum could run forever and rack up a score approaching negative infinity. Third, failing to suck in a dirty square is definitely a point loser, so we have to keep these. This pretty much leaves us with four possible functions. Two of them are: fn (A, clean) => right | (A, dirty) => suck | (B, clean) => nop | (B, dirty) => suck and fn (A, clean) => nop | (A, dirty) => suck | (B, clean) => left | (B, dirty) => suck Either one will get the 3 if both squares are dirty, but they may miss a chance for a 1 (settling for a zero), and may have to take a -1 when a 0 is possible. The other two are: fn (A, clean) => nop | (A, dirty) => suck | (B, clean) => nop | (B, dirty) => suck and fn (A, clean) => nop | (A, dirty) => suck | (B, clean) => nop | (B, dirty) => suck These never get the 3 (they max at 2), they might miss the chance for a 1 (settling for a zero, like the other functions), but they never take a -1 in place of a zero. Let's try a serious analysis. We can treat only one representative from each of the two groups, since within each group the functions are mirror images. Let f(A,clean)=right|f(x,dirty)=suck|f(B,clean)=nop Let g(x,clean)=nop|g(x,dirty)=suck Let states be represented as current_location x A.status x B.status For f: State score f() max theoretical score --------------------------------------------------- (A, clean, clean) -1 0 (A, clean, dirty) 1 1 (A, dirty, clean) 1 2 (A, dirty, dirty) 3 3 (B, clean, clean) 0 0 (B, clean, dirty) 2 2 (B, dirty, clean) 0 1 (B, dirty, dirty) 2 3 That's 8/12. Now for g: State score f() max theoretical score --------------------------------------------------- (A, clean, clean) 0 0 (A, clean, dirty) 0 1 (A, dirty, clean) 2 2 (A, dirty, dirty) 2 3 (B, clean, clean) 0 0 (B, clean, dirty) 2 2 (B, dirty, clean) 0 1 (B, dirty, dirty) 2 3 Also 8/12. Well what do you know? All four answers are acceptable! And guess what? Sucking on a clean square doesn't hurt either! This performance measure was under-specified. Good to know, though.
/** * A simple problem interface. */ public interface Problem<State, Action> { /** * Returns the array of actions applicable to the given state. */ java.util.List<Action> actionsFor(State state); /** * Returns the state you get from applying the given action to the given state. */ State go(State state, Action action); /** * Returns whether the given state is a goal. */ boolean isGoal(State state); }
import java.util.ArrayList; import java.util.Arrays; import java.util.Formatter; import java.util.List; import java.util.Random; /** * The famous eight puzzle problem. */ public class EightPuzzle implements Problem<EightPuzzle.Board, EightPuzzle.Action> { public static enum Action { UP, DOWN, LEFT, RIGHT } /** * An 8-puzzle board. */ public static class Board { /** * Holds the value of the tile at the given index position. The squares of the * board are numbered 0..8 as follows: * * <pre> * 0 1 2 * 3 4 5 * 6 7 8 * </pre> */ private int[] tiles = new int[9]; /** * Constructs a random board. */ public Board() { tiles = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; Random random = new Random(); do { for (int i = 0; i < tiles.length; i++) { int j = random.nextInt(tiles.length); int x = tiles[i]; tiles[i] = tiles[j]; tiles[j] = x; } } while (!isSolvable()); } /** * Creates a board with the given tile configuration. */ public Board(int[] tiles) { System.arraycopy(tiles, 0, this.tiles, 0, tiles.length); } /** * Returns whether the given tile configuration is solvable. It's a well-known * theorem that an eight puzzle is solvable if the number of inversions is even. */ public boolean isSolvable() { boolean solvable = true; for (int i = 0; i < tiles.length; i++) { if (tiles[i] == 0) continue; for (int j = i + 1; j < tiles.length; j++) { if (tiles[j] == 0) continue; if (tiles[i] > tiles[j]) solvable = !solvable; } } return solvable; } /** * Returns the sum of the manhattan distances for each tile. */ public int manhattan() { int result = 0; for (int i = 0; i < 9; i++) { result += Math.abs(tiles[i] / 3 - i / 3) + Math.abs(tiles[i] % 3 - i % 3); } return result; } /** * Returns a simple textual representation of the board. */ public String toString() { return new Formatter().format("%d%d%d\n%d%d%d\n%d%d%d", tiles[0], tiles[1], tiles[2], tiles[3], tiles[4], tiles[5], tiles[6], tiles[7], tiles[8]).out().toString(); } /** * Overrides Object.equals. */ public boolean equals(Object o) { return o instanceof Board && Arrays.equals(tiles, ((Board) o).tiles); } /** * Overrides Object.hashCode. */ public int hashCode() { return Arrays.hashCode(tiles); } } /** * Return the actions allowed from a given board state. */ public List<Action> actionsFor(Board board) { List<Action> legalActions = new ArrayList<Action>(); int blankPosition = 0; while (board.tiles[blankPosition] != 0) blankPosition++; if (blankPosition > 2) legalActions.add(Action.UP); if (blankPosition % 3 != 0) legalActions.add(Action.LEFT); if (blankPosition % 3 != 2) legalActions.add(Action.RIGHT); if (blankPosition < 6) legalActions.add(Action.DOWN); return legalActions; } /** * Returns the board you would get if you applied the given action to the given * board. */ public Board go(Board board, Action action) { int offset = 0; switch (action) { case UP: offset = -3; break; case DOWN: offset = 3; break; case RIGHT: offset = 1; break; case LEFT: offset = -1; break; } int blankPosition = 0; while (board.tiles[blankPosition] != 0) blankPosition++; int newBlankPosition = blankPosition + offset; Board result = new Board(board.tiles); result.tiles[blankPosition] = board.tiles[newBlankPosition]; result.tiles[newBlankPosition] = 0; return result; } /** * Returns whether the blank is in the lower right corner and tiles 1 through 8 * appear in order from left to right, top to bottom. */ public boolean isGoal(Board board) { return Arrays.equals(board.tiles, new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }); } }
import java.util.Arrays; import junit.framework.TestCase; import edu.lmu.cs.search.EightPuzzle.Action; import edu.lmu.cs.search.EightPuzzle.Board; /** * JUnit test case for the 8-puzzle problem. Not very complete. */ public class EightPuzzleTestCase extends TestCase { EightPuzzle puzzle = new EightPuzzle(); Board b1 = new Board(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }); Board b2 = new Board(new int[] { 0, 1, 2, 3, 4, 5, 6, 8, 7 }); Board b3 = new Board(new int[] { 1, 0, 2, 3, 4, 5, 6, 7, 8 }); Board b4 = new Board(new int[] { 4, 3, 5, 6, 2, 7, 0, 8, 1 }); Board b5 = new Board(new int[] { 4, 3, 5, 6, 2, 7, 0, 8, 1 }); Board b6 = new Board(new int[] { 4, 3, 5, 0, 2, 7, 6, 8, 1 }); Board b7 = new Board(new int[] { 4, 3, 7, 1, 6, 2, 5, 8, 0 }); public void testIsSolvable() { assertTrue(b1.isSolvable()); assertTrue(!b2.isSolvable()); assertTrue(b3.isSolvable()); assertTrue(b4.isSolvable()); assertTrue(!b7.isSolvable()); } public void testManhattan() { assertEquals(0, b1.manhattan()); assertEquals(2, b2.manhattan()); assertEquals(2, b3.manhattan()); assertEquals(16, b4.manhattan()); } public void testIsGoal() { assertTrue(puzzle.isGoal(b1)); assertTrue(!puzzle.isGoal(b2)); assertTrue(!puzzle.isGoal(b3)); assertTrue(!puzzle.isGoal(b4)); } public void testEquals() { assertEquals(b1, b1); assertEquals(b4, b5); assertFalse(b2.equals(b3)); assertFalse(b1.equals(null)); assertFalse(b1.equals(puzzle)); } public void testActionsFor() { assertEquals(puzzle.actionsFor(b1), Arrays.asList(Action.RIGHT, Action.DOWN)); assertEquals(puzzle.actionsFor(b2), Arrays.asList(Action.RIGHT, Action.DOWN)); assertEquals(puzzle.actionsFor(b3), Arrays.asList(Action.LEFT, Action.RIGHT, Action.DOWN)); assertEquals(puzzle.actionsFor(b5), Arrays.asList(Action.UP, Action.RIGHT)); } public void testGo() { assertEquals(puzzle.go(b5, Action.UP), b6); } }
import java.util.LinkedList; import java.util.List; /** * A problem solving engine for depth-first iterative deepening searching. */ public class DepthFirstAgent<S, A> implements Agent<S, A> { private Listener listener; public DepthFirstAgent(Listener listener) { this.listener = listener; } /** * Solves the problem using depth-first iterative deeping. */ public List<A> solve(Problem<S, A> problem, S start) { if (listener != null) listener.started(); // Fire off the iterations at increasing limits for (int limit = 1; true; limit++) { if (listener != null) listener.iterationStarted(limit); boolean reachedLimit = false; LinkedList<Node<S, A>> fringe = new LinkedList<Node<S, A>>(); fringe.addFirst(new Node<S, A>(start)); while (!fringe.isEmpty()) { Node<S, A> n = fringe.removeFirst(); if (problem.isGoal(n.getState())) { if (listener != null) listener.solutionFound(); return n.actionList(); } if (n.getDepth() > limit) { reachedLimit = true; continue; } for (A a : problem.actionsFor(n.getState())) { S s = problem.go(n.getState(), a); if (listener != null) listener.generated(); fringe.addFirst(new Node<S, A>(s, a, n)); } } // Finished an interation. We might need to go to the next // iteration, or we might have exhausted the search space. if (!reachedLimit) { if (listener != null) listener.failed(); return null; } } } }
import java.util.List; /** * An application that runs various solvers on the eight puzzle. */ public class EightPuzzleDemo { /** * Displays the moves in the solution of a random eight puzzle instance. */ public static void main(String[] args) { EightPuzzle puzzle = new EightPuzzle(); EightPuzzle.Board board = new EightPuzzle.Board(); board = new EightPuzzle.Board(); System.out.println("Starting board:\n" + board); Heuristic<EightPuzzle.Board> manhattanHeuristic = new Heuristic<EightPuzzle.Board>() { public int eval(EightPuzzle.Board board) { return board.manhattan(); } }; Listener listener = new Listener(); List<EightPuzzle.Action> moves = // IDA* always works with the manhattan heuristic new IdastarAgent<EightPuzzle.Board, EightPuzzle.Action>(manhattanHeuristic, listener).solve(puzzle, board); // A* often runs out of memory with manhattan heuristic // new AstarAgent<EightPuzzle.Board, EightPuzzle.Action>( // manhattanHeuristic, listener).solve(puzzle, board); // Plain DFID can take forever // new DepthFirstAgent<EightPuzzle.Board, EightPuzzle.Action>( // listener).solve(puzzle, board); for (EightPuzzle.Action move : moves) { board = puzzle.go(board, move); System.out.println(move + "\n" + board); } listener.report(); } }
Performance notes on a little example. Starting with 1 2 3 4 5 . 6 7 8 DFID found a 15-move solution LLURRDLULDRRULL by generating 11,066,435 nodes in 6531 ms. I have separate implementations of other algorithms, not shown here. But this is interesting: IDA* found the same 15-move solution by generating 122,207 nodes in 234 ms. A* found the same 15-move solution by generating 69,737 nodes in 1891 ms. +---------------------------------------------------------------+ | IDA* generates 75% more nodes as A*, but runs 8 times faster. | +---------------------------------------------------------------+