From a49ca81e6bd00ab3001cc550a448f412077d9295 Mon Sep 17 00:00:00 2001 From: Samagra Date: Sun, 8 Jul 2018 18:14:20 +0530 Subject: [PATCH 01/16] Adds generic Search --- .../basic/uninformed/GenericSearch.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java diff --git a/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java b/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java new file mode 100644 index 0000000000..cd45b3136e --- /dev/null +++ b/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java @@ -0,0 +1,90 @@ +package aima.core.search.basic.uninformed; + +import aima.core.search.api.Node; +import aima.core.search.api.NodeFactory; +import aima.core.search.api.Problem; +import aima.core.search.basic.support.BasicNodeFactory; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; + +/** + * Artificial Intelligence A Modern Approach (4th Edition): Figure ??, page ??. + *
+ *
+ * + *
+ * function GENERIC-SEARCH(problem) returns a solution, or failure
+ *  frontier ← a queue initially containing one path, for the problem's initial state
+ *  reached ← a table of {state: the best path that reached state}; initially empty
+ *  solution ← failure
+ *  while frontier is not empty and solution can possibly be improved do
+ *    parent ← some node that we choose to remove from frontier
+ *    for child in successors(parent) do
+ *      s ← child.state
+ *      if s is not in reached or child is a cheaper path than reached[s] then
+ *        reached[s] ← child
+ *        add child to frontier
+ *        if child is a goal and is cheaper than solution then
+ *          solution = child
+ *  return solution
+ * 
+ * + * Figure ?? In the GENERIC-SEARCH algorithm, we keep track of the best + * solution found so far, as well as a set of states that we have already + * reached, and a frontier of paths from which we will choose the next path + * to expand. In any specific search algorithm, we specify (1) the criteria + * for ordering the paths in the frontier, and (2) the procedure for + * determining when it is no longer possible to improve on a solution. + * + * + * @author samagra + */ + +public abstract class GenericSearch { + protected NodeFactory nodeFactory = new BasicNodeFactory<>(); + HashMap> reached = new HashMap<>(); + + /** + * function GENERIC-SEARCH(problem) returns a solution, or failure + * @param problem + * The search problem. + * @return + * The solution to the search problem. + */ + public Node genericSearch(Problem problem) { + //  frontier ← a queue initially containing one path, for the problem's initial state + Queue> frontier = newFrontier(problem.initialState()); + reached.clear(); + Node solution = null; + while (!frontier.isEmpty() && this.canImprove(reached, solution)) { + Node parent = frontier.remove(); + for (A action : + problem.actions(parent.state())) { + Node child = nodeFactory.newChildNode(problem, parent, action); + S s = child.state(); + if (!reached.containsKey(s) || (child.pathCost() < reached.get(s).pathCost())) { + reached.put(s, child); + frontier = this.addToFrontier(child, frontier); + if (problem.isGoalState(child.state()) && (child.pathCost() < solution.pathCost())) { + solution = child; + } + } + } + } + return solution; + } + + abstract Queue> addToFrontier(Node child, + Queue> frontier); + + abstract boolean canImprove(HashMap> reached, + Node solution); + + public Queue> newFrontier(S initialState) { + Queue> frontier = new LinkedList<>(); + frontier.add(nodeFactory.newRootNode(initialState)); + return frontier; + } +} From 3a87a3dba42b18333151483b92b10805d8f5ce5b Mon Sep 17 00:00:00 2001 From: Samagra Date: Sun, 8 Jul 2018 19:38:21 +0530 Subject: [PATCH 02/16] Adds Breadth first Search as a test case --- .../basic/uninformed/GenericSearch.java | 43 ++++++++++++++++--- .../search/uninformed/GenericSearchTest.java | 33 ++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java diff --git a/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java b/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java index cd45b3136e..44f194410a 100644 --- a/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java @@ -43,7 +43,7 @@ */ public abstract class GenericSearch { - protected NodeFactory nodeFactory = new BasicNodeFactory<>(); + protected NodeFactory nodeFactory = new BasicNodeFactory<>(); // to generate new nodes. HashMap> reached = new HashMap<>(); /** @@ -56,32 +56,65 @@ public abstract class GenericSearch { public Node genericSearch(Problem problem) { //  frontier ← a queue initially containing one path, for the problem's initial state Queue> frontier = newFrontier(problem.initialState()); + // reached ← a table of {state: the best path that reached state}; initially empty reached.clear(); + // solution ← failure Node solution = null; + // while frontier is not empty and solution can possibly be improved do while (!frontier.isEmpty() && this.canImprove(reached, solution)) { + // parent ← some node that we choose to remove from frontier Node parent = frontier.remove(); + System.out.println("Parent ="+parent.toString()); + // for child in successors(parent) do for (A action : problem.actions(parent.state())) { Node child = nodeFactory.newChildNode(problem, parent, action); + // s ← child.state S s = child.state(); + // if s is not in reached or child is a cheaper path than reached[s] then if (!reached.containsKey(s) || (child.pathCost() < reached.get(s).pathCost())) { + // reached[s] ← child reached.put(s, child); + // add child to frontier frontier = this.addToFrontier(child, frontier); - if (problem.isGoalState(child.state()) && (child.pathCost() < solution.pathCost())) { + // if child is a goal and is cheaper than solution then + if (problem.isGoalState(child.state()) &&(solution==null || (child.pathCost() < solution.pathCost()))) { + // solution = child solution = child; } } } } + // return solution return solution; } - abstract Queue> addToFrontier(Node child, + /** + * The strategy for adding nodes to the frontier + * @param child + * @param frontier + * @return + */ + public abstract Queue> addToFrontier(Node child, Queue> frontier); - abstract boolean canImprove(HashMap> reached, - Node solution); + /** + * the procedure for determining when it is no longer possible to improve on a solution. + * @param reached + * The reached states. + * @param solution + * The current solution. + * @return + * A boolean stating if we can improve the solution. + */ + public abstract boolean canImprove(HashMap> reached, + Node solution); + /** + * This method initialises a new frontier. + * @param initialState + * @return + */ public Queue> newFrontier(S initialState) { Queue> frontier = new LinkedList<>(); frontier.add(nodeFactory.newRootNode(initialState)); diff --git a/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java new file mode 100644 index 0000000000..af42033805 --- /dev/null +++ b/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java @@ -0,0 +1,33 @@ +package aima.test.unit.search.uninformed; + +import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania; +import aima.core.environment.support.ProblemFactory; +import aima.core.search.api.Node; +import aima.core.search.api.Problem; +import aima.core.search.basic.uninformed.GenericSearch; +import org.junit.Test; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; + +public class GenericSearchTest { + GenericSearch bfs = new GenericSearch() { + @Override + public Queue addToFrontier(Node child, Queue frontier) { + ((LinkedList) frontier).add(child); + return frontier; + } + + @Override + public boolean canImprove(HashMap reached, Node solution) { + return solution==null; + } + }; + + @Test + public void romaniabfsTest(){ + System.out.println(bfs.genericSearch(ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem( + SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.ARAD))); + } +} From a2a6bc38e70088e7c3b19dbcbf8ce7fb22b0b2aa Mon Sep 17 00:00:00 2001 From: Samagra Date: Sun, 8 Jul 2018 20:26:38 +0530 Subject: [PATCH 03/16] Adds depth first search version of generic search --- .../basic/uninformed/GenericSearch.java | 31 +++++++------- .../search/uninformed/GenericSearchTest.java | 40 ++++++++++++++++--- 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java b/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java index 44f194410a..edb4f6bc9c 100644 --- a/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java @@ -30,7 +30,7 @@ *          solution = child *  return solution * - * + *

* Figure ?? In the GENERIC-SEARCH algorithm, we keep track of the best * solution found so far, as well as a set of states that we have already * reached, and a frontier of paths from which we will choose the next path @@ -38,20 +38,18 @@ * for ordering the paths in the frontier, and (2) the procedure for * determining when it is no longer possible to improve on a solution. * - * * @author samagra */ public abstract class GenericSearch { - protected NodeFactory nodeFactory = new BasicNodeFactory<>(); // to generate new nodes. - HashMap> reached = new HashMap<>(); + public NodeFactory nodeFactory = new BasicNodeFactory<>(); // to generate new nodes. + public HashMap> reached = new HashMap<>(); /** * function GENERIC-SEARCH(problem) returns a solution, or failure - * @param problem - * The search problem. - * @return - * The solution to the search problem. + * + * @param problem The search problem. + * @return The solution to the search problem. */ public Node genericSearch(Problem problem) { //  frontier ← a queue initially containing one path, for the problem's initial state @@ -64,7 +62,6 @@ public Node genericSearch(Problem problem) { while (!frontier.isEmpty() && this.canImprove(reached, solution)) { // parent ← some node that we choose to remove from frontier Node parent = frontier.remove(); - System.out.println("Parent ="+parent.toString()); // for child in successors(parent) do for (A action : problem.actions(parent.state())) { @@ -78,7 +75,7 @@ public Node genericSearch(Problem problem) { // add child to frontier frontier = this.addToFrontier(child, frontier); // if child is a goal and is cheaper than solution then - if (problem.isGoalState(child.state()) &&(solution==null || (child.pathCost() < solution.pathCost()))) { + if (problem.isGoalState(child.state()) && (solution == null || (child.pathCost() < solution.pathCost()))) { // solution = child solution = child; } @@ -91,27 +88,27 @@ public Node genericSearch(Problem problem) { /** * The strategy for adding nodes to the frontier + * * @param child * @param frontier * @return */ public abstract Queue> addToFrontier(Node child, - Queue> frontier); + Queue> frontier); /** * the procedure for determining when it is no longer possible to improve on a solution. - * @param reached - * The reached states. - * @param solution - * The current solution. - * @return - * A boolean stating if we can improve the solution. + * + * @param reached The reached states. + * @param solution The current solution. + * @return A boolean stating if we can improve the solution. */ public abstract boolean canImprove(HashMap> reached, Node solution); /** * This method initialises a new frontier. + * * @param initialState * @return */ diff --git a/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java index af42033805..8ceba31a92 100644 --- a/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java +++ b/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java @@ -3,31 +3,59 @@ import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania; import aima.core.environment.support.ProblemFactory; import aima.core.search.api.Node; -import aima.core.search.api.Problem; import aima.core.search.basic.uninformed.GenericSearch; +import org.junit.Assert; import org.junit.Test; import java.util.HashMap; import java.util.LinkedList; import java.util.Queue; +/** + * @author samagra + */ public class GenericSearchTest { + // Generic Search as breadth first search GenericSearch bfs = new GenericSearch() { @Override public Queue addToFrontier(Node child, Queue frontier) { - ((LinkedList) frontier).add(child); + ((LinkedList) frontier).addLast(child); return frontier; } @Override public boolean canImprove(HashMap reached, Node solution) { - return solution==null; + return solution == null; } }; + // GenericSearch as depth first search + GenericSearch dfs = new GenericSearch() { + @Override + public Queue addToFrontier(Node child, Queue frontier) { + ((LinkedList) frontier).addFirst(child); + return frontier; + } + + @Override + public boolean canImprove(HashMap reached, Node solution) { + return solution == null; + } + }; + + @Test + public void romaniabfsTest() { + Node solution = bfs.genericSearch(ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem( + SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.ARAD)); + Assert.assertEquals("In(Arad)", solution.state().toString()); + System.out.println(bfs.reached.toString()); + } + @Test - public void romaniabfsTest(){ - System.out.println(bfs.genericSearch(ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem( - SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.ARAD))); + public void romaniadfsTest() { + Node solution = dfs.genericSearch(ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem( + SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.ARAD)); + Assert.assertEquals("In(Arad)", solution.state().toString()); + System.out.println(dfs.reached.toString()); } } From 5cf985c4fcdac67dd386405edb3af6f1e6eb49fa Mon Sep 17 00:00:00 2001 From: Samagra Date: Sun, 15 Jul 2018 21:28:36 +0530 Subject: [PATCH 04/16] Adds bfs and successor functions --- .../aima/core/search/basic/SearchUtils.java | 26 ++++++++++++ .../uninformedsearch/BreadthFirstSearch.java | 40 +++++++++++++++++++ .../GenericSearch.java | 2 +- .../GenericSearchInterface.java | 8 ++++ .../search/uninformed/GenericSearchTest.java | 2 +- 5 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/aima/core/search/basic/SearchUtils.java create mode 100644 core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java rename core/src/main/java/aima/core/search/basic/{uninformed => uninformedsearch}/GenericSearch.java (99%) create mode 100644 core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java diff --git a/core/src/main/java/aima/core/search/basic/SearchUtils.java b/core/src/main/java/aima/core/search/basic/SearchUtils.java new file mode 100644 index 0000000000..bdac711eb3 --- /dev/null +++ b/core/src/main/java/aima/core/search/basic/SearchUtils.java @@ -0,0 +1,26 @@ +package aima.core.search.basic; + +import aima.core.search.api.Node; +import aima.core.search.api.NodeFactory; +import aima.core.search.api.Problem; +import aima.core.search.basic.support.BasicNodeFactory; + +import java.util.ArrayList; +import java.util.List; + +public class SearchUtils { + public static List> successors(Problem problem, Node parent ){ + S s = parent.state(); + List> nodes = new ArrayList<>(); + + NodeFactory nodeFactory = new BasicNodeFactory<>(); + for (A action : + problem.actions(s)) { + S sPrime = problem.result(s,action); + double cost = parent.pathCost() + problem.stepCost(s,action,sPrime); + Node node = nodeFactory.newChildNode(problem,parent,action); + nodes.add(node); + } + return nodes; + } +} diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java new file mode 100644 index 0000000000..dc0d8f616e --- /dev/null +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java @@ -0,0 +1,40 @@ +package aima.core.search.basic.uninformedsearch; + +import aima.core.search.api.Node; +import aima.core.search.api.NodeFactory; +import aima.core.search.api.Problem; +import aima.core.search.basic.SearchUtils; +import aima.core.search.basic.support.BasicNodeFactory; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; + +public class BreadthFirstSearch implements GenericSearchInterface { + NodeFactory nodeFactory = new BasicNodeFactory(); + + @Override + public Node search(Problem problem) { + if (problem.isGoalState(problem.initialState())){ + return nodeFactory.newRootNode(problem.initialState()); + } + Queue frontier = new LinkedList<>(); + ((LinkedList) frontier).add(nodeFactory.newRootNode(problem.initialState())); + HashSet reached = new HashSet<>(); + Node solution = null; + while (!frontier.isEmpty()){ + Node parent = frontier.remove(); + for (Node child: SearchUtils.successors(problem,parent)){ + S s = child.state(); + if (problem.isGoalState(s)){ + return child; + } + if (!reached.contains(s)){ + reached.add(s); + frontier.add(child); + } + } + } + return solution; + } +} diff --git a/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java similarity index 99% rename from core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java rename to core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java index edb4f6bc9c..106b895fb6 100644 --- a/core/src/main/java/aima/core/search/basic/uninformed/GenericSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java @@ -1,4 +1,4 @@ -package aima.core.search.basic.uninformed; +package aima.core.search.basic.uninformedsearch; import aima.core.search.api.Node; import aima.core.search.api.NodeFactory; diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java new file mode 100644 index 0000000000..2611305801 --- /dev/null +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java @@ -0,0 +1,8 @@ +package aima.core.search.basic.uninformedsearch; + +import aima.core.search.api.Node; +import aima.core.search.api.Problem; + +public interface GenericSearchInterface { + Node search(Problem problem); +} diff --git a/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java index 8ceba31a92..0dcd3262ad 100644 --- a/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java +++ b/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java @@ -3,7 +3,7 @@ import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania; import aima.core.environment.support.ProblemFactory; import aima.core.search.api.Node; -import aima.core.search.basic.uninformed.GenericSearch; +import aima.core.search.basic.uninformedsearch.GenericSearch; import org.junit.Assert; import org.junit.Test; From 1999507bce918ed37eb345e84631b0215eb1c4fc Mon Sep 17 00:00:00 2001 From: Samagra Date: Sun, 15 Jul 2018 21:53:10 +0530 Subject: [PATCH 05/16] Adds uniformCost Search --- .../uninformedsearch/UniformCostSearch.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java new file mode 100644 index 0000000000..8141bd2b83 --- /dev/null +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java @@ -0,0 +1,51 @@ +package aima.core.search.basic.uninformedsearch; + +import aima.core.search.api.Node; +import aima.core.search.api.NodeFactory; +import aima.core.search.api.Problem; +import aima.core.search.basic.SearchUtils; +import aima.core.search.basic.support.BasicNodeFactory; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.PriorityQueue; + +public class UniformCostSearch implements GenericSearchInterface { + + PriorityQueue> frontier = new PriorityQueue<>(new Comparator>() { + @Override + public int compare(Node o1, Node o2) { + return (int)(o1.pathCost()-o2.pathCost()); + } + }); + + NodeFactory nodeFactory = new BasicNodeFactory<>(); + + @Override + public Node search(Problem problem) { + if (problem.isGoalState(problem.initialState())){ + return nodeFactory.newRootNode(problem.initialState()); + } + frontier.clear(); + frontier.add(nodeFactory.newRootNode(problem.initialState())); + HashMap> reached = new HashMap<>(); + Node solution = null; + while (!frontier.isEmpty() && + (solution==null || frontier.peek().pathCost() parent = frontier.poll(); + for (Node child : + SearchUtils.successors(problem, parent)) { + S s = child.state(); + if (!reached.containsKey(s) || + child.pathCost() Date: Mon, 16 Jul 2018 00:07:40 +0530 Subject: [PATCH 06/16] Adds depth limited search and iterative deepening search --- .../aima/core/search/basic/SearchUtils.java | 10 +++++ .../uninformedsearch/DepthLimitedSearch.java | 37 +++++++++++++++++++ .../IterativeDeepeningSearch.java | 18 +++++++++ 3 files changed, 65 insertions(+) create mode 100644 core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java create mode 100644 core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java diff --git a/core/src/main/java/aima/core/search/basic/SearchUtils.java b/core/src/main/java/aima/core/search/basic/SearchUtils.java index bdac711eb3..c1c14196be 100644 --- a/core/src/main/java/aima/core/search/basic/SearchUtils.java +++ b/core/src/main/java/aima/core/search/basic/SearchUtils.java @@ -23,4 +23,14 @@ public static List> successors(Problem problem, Node p } return nodes; } + + public static int depth(Node node){ + Node temp = node; + int count = 0; + while(temp!=null){ + count ++; + temp = temp.parent(); + } + return count; + } } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java new file mode 100644 index 0000000000..d98eff9aab --- /dev/null +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java @@ -0,0 +1,37 @@ +package aima.core.search.basic.uninformedsearch; + +import aima.core.search.api.Node; +import aima.core.search.api.NodeFactory; +import aima.core.search.api.Problem; +import aima.core.search.basic.SearchUtils; +import aima.core.search.basic.support.BasicNodeFactory; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + +public class DepthLimitedSearch { + NodeFactory nodeFactory = new BasicNodeFactory<>(); + + public Node search(Problem problem, int l){ + Stack> frontier = new Stack<>(); + frontier.push(nodeFactory.newRootNode(problem.initialState())); + Node solution = null; + while(!frontier.isEmpty()){ + Node parent = frontier.pop(); + if(SearchUtils.depth(parent)>l){ + solution = null; + } + else { + for (Node child : + SearchUtils.successors(problem, parent)) { + if (problem.isGoalState(child.state())){ + return child; + } + frontier.push(child); + } + } + } + return solution; + } +} diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java new file mode 100644 index 0000000000..2d41f0545b --- /dev/null +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java @@ -0,0 +1,18 @@ +package aima.core.search.basic.uninformedsearch; + +import aima.core.search.api.Node; +import aima.core.search.api.Problem; + +public class IterativeDeepeningSearch implements GenericSearchInterface{ + DepthLimitedSearch depthLimitedSearch = new DepthLimitedSearch<>(); + + @Override + public Node search(Problem problem) { + for(int depth =0;depth>=0;depth++){ + Node result = depthLimitedSearch.search(problem,depth); + if (result!=null) + return result; + } + return null; + } +} From c6e85a4cfdd5d2d751d30a78acfebc3048b818f9 Mon Sep 17 00:00:00 2001 From: Samagra Date: Wed, 18 Jul 2018 08:55:41 +0530 Subject: [PATCH 07/16] Adds comments --- .../uninformedsearch/BreadthFirstSearch.java | 48 +++++++++++++ .../uninformedsearch/DepthLimitedSearch.java | 70 +++++++++++++++---- 2 files changed, 105 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java index dc0d8f616e..07af12bbf9 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java @@ -10,31 +10,79 @@ import java.util.LinkedList; import java.util.Queue; +/** + *

+ *  function BREADTH-FIRST-SEARCH(problem) returns a solution, or failure
+ *  if problem's initial state is a goal then return empty path to initial state
+ *  frontier ← a FIFO queue initially containing one path, for the problem's initial state
+ *  reached ← a set of states; initially empty
+ *  solution ← failure
+ *  while frontier is not empty do
+ *    parent ← the first node in frontier
+ *    for child in successors(parent) do
+ *      s ← child.state
+ *      if s is a goal then
+ *        return child
+ *      if s is not in reached then
+ *        add s to reached
+ *        add child to the end of frontier
+ *  return solution
+ * 
+ * + * Figure 3.9 Breadth-first search algorithm. + * + * @param The generic object representing actions. + * + * @param The generic object representing state. + * + * @author samagra + */ public class BreadthFirstSearch implements GenericSearchInterface { + // Node factory is just a helper class to generate new nodes + // It takes the problem and current state in order to generate new nodes NodeFactory nodeFactory = new BasicNodeFactory(); + /** + * function BREADTH-FIRST-SEARCH(problem) returns a solution, or failure + * @param problem The search problem. + * @return The goal node if any else null. + */ @Override public Node search(Problem problem) { + //  if problem's initial state is a goal then return empty path to initial state if (problem.isGoalState(problem.initialState())){ return nodeFactory.newRootNode(problem.initialState()); } + // frontier ← a FIFO queue initially containing one path, for the problem's initial state Queue frontier = new LinkedList<>(); ((LinkedList) frontier).add(nodeFactory.newRootNode(problem.initialState())); + // reached ← a set of states; initially empty HashSet reached = new HashSet<>(); + // solution ← failure Node solution = null; + // while frontier is not empty do while (!frontier.isEmpty()){ + // parent ← the first node in frontier Node parent = frontier.remove(); + // for child in successors(parent) do for (Node child: SearchUtils.successors(problem,parent)){ + // s ← child.state S s = child.state(); + // if s is a goal then if (problem.isGoalState(s)){ + // return child return child; } + // if s is not in reached then if (!reached.contains(s)){ + // add s to reached reached.add(s); + // add child to the end of frontier frontier.add(child); } } } + // return solution return solution; } } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java index d98eff9aab..4c0b6c4ffc 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java @@ -6,32 +6,76 @@ import aima.core.search.basic.SearchUtils; import aima.core.search.basic.support.BasicNodeFactory; -import java.util.LinkedList; -import java.util.Queue; import java.util.Stack; -public class DepthLimitedSearch { - NodeFactory nodeFactory = new BasicNodeFactory<>(); +/** + *
+ *  function DEPTH-LIMITED-SEARCH(problem, l) returns a solution, or failure, or cutoff
+ *  frontier ← a FIFO queue initially containing one path, for the problem's initial state
+ *  solution ← failure
+ *  while frontier is not empty do
+ *    parent ← pop(frontier)
+ *    if depth(parent) > l then
+ *      solution ← cutoff
+ *    else
+ *        for child in successors(parent) do
+ *          if child is a goal then
+ *            return child
+ *          add child to frontier
+ *  return solution
+ * 
+ * + * Figure 3.14 An implementation of depth-limited tree search. The + * algorithm has two different ways to signal failure to find a solution: + * it returns failure when it has exhausted all paths and proved there is no + * solution at any depth, and returns cutoff to mean there might be a solution + * at a deeper depth than l. Note that this algorithm does not keep track of + * reached states, and thus might visit the same state multiple times on different paths. + * + * @param
The generic object representing actions. + * @param The generic object representing state. + * + * @author samagra + */ +public class DepthLimitedSearch { + NodeFactory nodeFactory = new BasicNodeFactory<>(); - public Node search(Problem problem, int l){ - Stack> frontier = new Stack<>(); + /** + * function DEPTH-LIMITED-SEARCH(problem, l) returns a solution, or failure, or cutoff + * @param problem The search problem. + * @param l The cutoff limit. + * @return The goal state if exists/found else null + */ + public Node search(Problem problem, int l) { + //  frontier ← a LIFO queue initially containing one path, for the problem's initial state + Stack> frontier = new Stack<>(); frontier.push(nodeFactory.newRootNode(problem.initialState())); - Node solution = null; - while(!frontier.isEmpty()){ - Node parent = frontier.pop(); - if(SearchUtils.depth(parent)>l){ + //  solution ← failure + Node solution = null; + //  while frontier is not empty do + while (!frontier.isEmpty()) { + // parent ← pop(frontier) + Node parent = frontier.pop(); + // if depth(parent) > l then + if (SearchUtils.depth(parent) > l) { + // solution ← cutoff solution = null; - } - else { + // else + } else { + // for child in successors(parent) do for (Node child : SearchUtils.successors(problem, parent)) { - if (problem.isGoalState(child.state())){ + // if child is a goal then + if (problem.isGoalState(child.state())) { + // return child return child; } + // add child to frontier frontier.push(child); } } } + // return solution return solution; } } From cf8c2c39210213446fbbeae030eb26236a487dd3 Mon Sep 17 00:00:00 2001 From: Samagra Date: Wed, 18 Jul 2018 18:08:33 +0530 Subject: [PATCH 08/16] Adds comments --- .../GenericSearchInterface.java | 33 ++++++++++++++ .../IterativeDeepeningSearch.java | 33 ++++++++++++++ .../uninformedsearch/UniformCostSearch.java | 44 ++++++++++++++++++- 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java index 2611305801..1691a25236 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java @@ -3,6 +3,39 @@ import aima.core.search.api.Node; import aima.core.search.api.Problem; +/** + * An interface that identifies a generic search algorithm. + * + * Artificial Intelligence A Modern Approach (4th Edition): Figure ??, page ??. + *
+ *
+ * + *
+ * function GENERIC-SEARCH(problem) returns a solution, or failure
+ *  frontier ← a queue initially containing one path, for the problem's initial state
+ *  reached ← a table of {state: the best path that reached state}; initially empty
+ *  solution ← failure
+ *  while frontier is not empty and solution can possibly be improved do
+ *    parent ← some node that we choose to remove from frontier
+ *    for child in successors(parent) do
+ *      s ← child.state
+ *      if s is not in reached or child is a cheaper path than reached[s] then
+ *        reached[s] ← child
+ *        add child to frontier
+ *        if child is a goal and is cheaper than solution then
+ *          solution = child
+ *  return solution
+ * 
+ *

+ * Figure ?? In the GENERIC-SEARCH algorithm, we keep track of the best + * solution found so far, as well as a set of states that we have already + * reached, and a frontier of paths from which we will choose the next path + * to expand. In any specific search algorithm, we specify (1) the criteria + * for ordering the paths in the frontier, and (2) the procedure for + * determining when it is no longer possible to improve on a solution. + * + * @author samagra + */ public interface GenericSearchInterface { Node search(Problem problem); } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java index 2d41f0545b..a2a92879c9 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java @@ -3,13 +3,46 @@ import aima.core.search.api.Node; import aima.core.search.api.Problem; +/** + *

- * + *

* Figure 3.14 An implementation of depth-limited tree search. The * algorithm has two different ways to signal failure to find a solution: * it returns failure when it has exhausted all paths and proved there is no @@ -34,48 +34,38 @@ * * @param The generic object representing actions. * @param The generic object representing state. - * * @author samagra */ public class DepthLimitedSearch { + + // A helper class to generate new nodes. NodeFactory nodeFactory = new BasicNodeFactory<>(); /** * function DEPTH-LIMITED-SEARCH(problem, l) returns a solution, or failure, or cutoff + * * @param problem The search problem. - * @param l The cutoff limit. + * @param l The cutoff limit. * @return The goal state if exists/found else null */ public Node search(Problem problem, int l) { - //  frontier ← a LIFO queue initially containing one path, for the problem's initial state Stack> frontier = new Stack<>(); frontier.push(nodeFactory.newRootNode(problem.initialState())); - //  solution ← failure Node solution = null; - //  while frontier is not empty do while (!frontier.isEmpty()) { - // parent ← pop(frontier) Node parent = frontier.pop(); - // if depth(parent) > l then if (SearchUtils.depth(parent) > l) { - // solution ← cutoff solution = null; - // else } else { - // for child in successors(parent) do for (Node child : SearchUtils.successors(problem, parent)) { - // if child is a goal then if (problem.isGoalState(child.state())) { - // return child return child; } - // add child to frontier frontier.push(child); } } } - // return solution return solution; } } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java index 858a9bee08..b69c998376 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java @@ -40,7 +40,7 @@ * @author samagra */ -public abstract class GenericSearch implements SearchForActionsFunction { +public abstract class GenericSearch implements SearchForActionsFunction { public NodeFactory nodeFactory = new BasicNodeFactory<>(); // to generate new nodes. public HashMap> reached = new HashMap<>(); @@ -55,33 +55,26 @@ public Node genericSearch(Problem problem) { Queue> frontier = newFrontier(problem.initialState()); // reached ← a table of {state: the best path that reached state}; initially empty reached.clear(); - // solution ← failure Node solution = null; // while frontier is not empty and solution can possibly be improved do while (!frontier.isEmpty() && this.canImprove(reached, solution)) { // parent ← some node that we choose to remove from frontier Node parent = frontier.remove(); - // for child in successors(parent) do for (A action : problem.actions(parent.state())) { Node child = nodeFactory.newChildNode(problem, parent, action); - // s ← child.state S s = child.state(); // if s is not in reached or child is a cheaper path than reached[s] then if (!reached.containsKey(s) || (child.pathCost() < reached.get(s).pathCost())) { - // reached[s] ← child reached.put(s, child); - // add child to frontier frontier = this.addToFrontier(child, frontier); // if child is a goal and is cheaper than solution then if (problem.isGoalState(child.state()) && (solution == null || (child.pathCost() < solution.pathCost()))) { - // solution = child solution = child; } } } } - // return solution return solution; } @@ -119,10 +112,10 @@ public Queue> newFrontier(S initialState) { @Override public List apply(Problem problem) { - Node solution = this.genericSearch(problem); - Node parent = solution.parent(); + Node solution = this.genericSearch(problem); + Node parent = solution.parent(); List actions = new ArrayList<>(); - while(parent != null){ + while (parent != null) { actions.add(parent.action()); parent = parent.parent(); } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java index 1691a25236..492676894d 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearchInterface.java @@ -5,7 +5,7 @@ /** * An interface that identifies a generic search algorithm. - * + *

* Artificial Intelligence A Modern Approach (4th Edition): Figure ??, page ??. *
*
@@ -36,6 +36,6 @@ * * @author samagra */ -public interface GenericSearchInterface { - Node search(Problem problem); +public interface GenericSearchInterface { + Node search(Problem problem); } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java index 5a9ec500fc..592e4117a5 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java @@ -5,14 +5,12 @@ import aima.core.search.api.SearchForActionsFunction; import aima.core.search.basic.SearchUtils; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** *

  *
- /**
+ * /**
  * Artificial Intelligence A Modern Approach (4th Edition): Figure ??, page ??.
  * 
*
@@ -23,7 +21,7 @@ * result ← DEPTH-LIMITED-SEARCH(problem, depth) * if result != cutoff then return result *
- * + *

* Figure ?? The iterative deepening search algorithm, which repeatedly applies * depth-limited search with increasing limits. It terminates when a solution is * found or if the depth-limited search returns failure, meaning that no @@ -34,30 +32,34 @@ * @author Ruediger Lunde * @author samagra */ -public class IterativeDeepeningSearch implements GenericSearchInterface, SearchForActionsFunction { - DepthLimitedSearch depthLimitedSearch = new DepthLimitedSearch<>(); +public class IterativeDeepeningSearch implements GenericSearchInterface, SearchForActionsFunction { + DepthLimitedSearch depthLimitedSearch = new DepthLimitedSearch<>(); /** * function ITERATIVE-DEEPENING-SEARCH(problem) returns a solution, or failure + * * @param problem The search problem * @return The solution if exists else continues to run */ @Override public Node search(Problem problem) { - //for depth = 0 to infinity do - for(int depth =0;depth>=0;depth++){ - //result ← DEPTH-LIMITED-SEARCH(problem, depth) - Node result = depthLimitedSearch.search(problem,depth); - // if result != cutoff then return result - if (result!=null) + for (int depth = 0; depth >= 0; depth++) { + Node result = depthLimitedSearch.search(problem, depth); + if (result != null) return result; } return null; } + /** + * Returns a list of actions to be taken to reach the goal state. + * + * @param problem + * @return + */ @Override public List apply(Problem problem) { - Node solution = this.search(problem); + Node solution = this.search(problem); return SearchUtils.generateActions(solution); } } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java index dc53b6af3a..03c972e356 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java @@ -27,72 +27,67 @@ *          solution = child *  return solution * - * + *

* Figure 3.11 Uniform-cost search on a graph. Finds optimal * paths for problems with varying step costs. * * @param The generic class representing action. * @param The generic class representing states. - * * @author samagra */ -public class UniformCostSearch implements GenericSearchInterface, SearchForActionsFunction { +public class UniformCostSearch implements GenericSearchInterface, SearchForActionsFunction { // frontier ← a priority queue ordered by pathCost, with a node for the initial state - PriorityQueue> frontier = new PriorityQueue<>(new Comparator>() { + PriorityQueue> frontier = new PriorityQueue<>(new Comparator>() { @Override public int compare(Node o1, Node o2) { - return (int)(o1.pathCost()-o2.pathCost()); + return (int) (o1.pathCost() - o2.pathCost()); } }); - private NodeFactory nodeFactory = new BasicNodeFactory<>(); + private NodeFactory nodeFactory = new BasicNodeFactory<>(); @Override public Node search(Problem problem) { - // if problem's initial state is a goal then return empty path to initial state - if (problem.isGoalState(problem.initialState())){ + if (problem.isGoalState(problem.initialState())) { return nodeFactory.newRootNode(problem.initialState()); } // frontier ← a priority queue ordered by pathCost, with a node for the initial state frontier.clear(); frontier.add(nodeFactory.newRootNode(problem.initialState())); - // reached ← a table of {state: the best path that reached state}; initially empty HashMap> reached = new HashMap<>(); - // solution ← failure - Node solution = null; + Node solution = null; // while frontier is not empty and top(frontier) is cheaper than solution do while (!frontier.isEmpty() && - (solution==null || frontier.peek().pathCost() parent = frontier.poll(); - // for child in successors(parent) do + (solution == null || frontier.peek().pathCost() < solution.pathCost())) { + Node parent = frontier.poll(); for (Node child : SearchUtils.successors(problem, parent)) { - // s ← child.state S s = child.state(); // if s is not in reached or child is a cheaper path than reached[s] then if (!reached.containsKey(s) || - child.pathCost() apply(Problem problem) { - Node solution = this.search(problem); + Node solution = this.search(problem); if (solution == null) return new ArrayList<>(); return SearchUtils.generateActions(solution); diff --git a/test/src/test/java/aima/test/unit/search/informed/GreedyBestFirstSearchTest.java b/test/src/test/java/aima/test/unit/search/informed/GreedyBestFirstSearchTest.java index 9f11b8a3f6..9d51a437f3 100644 --- a/test/src/test/java/aima/test/unit/search/informed/GreedyBestFirstSearchTest.java +++ b/test/src/test/java/aima/test/unit/search/informed/GreedyBestFirstSearchTest.java @@ -1,72 +1,67 @@ package aima.test.unit.search.informed; -import static aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania.*; -import static org.junit.Assert.assertEquals; - -import aima.core.environment.map2d.GoAction; -import aima.core.environment.map2d.InState; -import aima.core.environment.map2d.Map2D; -import aima.core.environment.map2d.Map2DFunctionFactory; -import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania; +import aima.core.environment.map2d.*; import aima.core.environment.support.ProblemFactory; import aima.core.search.api.Node; import aima.core.search.api.Problem; import aima.core.search.api.SearchForActionsFunction; import aima.core.search.basic.informed.GreedyBestFirstSearch; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.function.ToDoubleFunction; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; + +import static aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania.*; +import static org.junit.Assert.assertEquals; @RunWith(Parameterized.class) public class GreedyBestFirstSearchTest { - private static final String GREEDYBFS = "GreedyBestFirstSearch"; + private static final String GREEDYBFS = "GreedyBestFirstSearch"; + @Parameter + public String searchFunctionName; - @Parameters - public static Collection implementations() { - return Arrays.asList(new Object[][]{{GREEDYBFS}}); - } + @Parameters + public static Collection implementations() { + return Arrays.asList(new Object[][]{{GREEDYBFS}}); + } - @Parameter - public String searchFunctionName; + @Test + public void testSimplifiedRoadMapOfPartOfRomania() { + Map2D map = new SimplifiedRoadMapOfPartOfRomania(); + String initialLocation = ARAD; - @Test - public void testSimplifiedRoadMapOfPartOfRomania() { - Map2D map = new SimplifiedRoadMapOfPartOfRomania(); - String initialLocation = ARAD; + String finalLocation = initialLocation; + Problem problem = ProblemFactory + .getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, finalLocation); + List goal = new ArrayList<>(); + assertEquals(goal, searchForActions(problem, + new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, finalLocation))); - String finalLocation = initialLocation; - Problem problem = ProblemFactory - .getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, finalLocation); - List goal = new ArrayList<>(); - assertEquals(goal, searchForActions(problem, - new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, finalLocation))); + finalLocation = BUCHAREST; + problem = ProblemFactory + .getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, finalLocation); + goal = Arrays.asList(new GoAction(SIBIU), new GoAction(FAGARAS), new GoAction(BUCHAREST)); + assertEquals(goal, searchForActions(problem, + new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, finalLocation))); + } - finalLocation = BUCHAREST; - problem = ProblemFactory - .getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, finalLocation); - goal = Arrays.asList(new GoAction(SIBIU), new GoAction(FAGARAS), new GoAction(BUCHAREST)); - assertEquals(goal, searchForActions(problem, - new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, finalLocation))); - } + private List searchForActions(Problem problem, ToDoubleFunction> h) { + SearchForActionsFunction searchForActionsFunction; - private List searchForActions(Problem problem, ToDoubleFunction> h) { - SearchForActionsFunction searchForActionsFunction; + if (GREEDYBFS.equals(searchFunctionName)) { + searchForActionsFunction = new GreedyBestFirstSearch<>(h); + } else { + throw new UnsupportedOperationException(); + } - if (GREEDYBFS.equals(searchFunctionName)) { - searchForActionsFunction = new GreedyBestFirstSearch<>(h); - } else { - throw new UnsupportedOperationException(); + return searchForActionsFunction.apply(problem); } - - return searchForActionsFunction.apply(problem); - } } diff --git a/test/src/test/java/aima/test/unit/search/informed/InformedSearchTest.java b/test/src/test/java/aima/test/unit/search/informed/InformedSearchTest.java index ae57e11e30..8d72476e72 100644 --- a/test/src/test/java/aima/test/unit/search/informed/InformedSearchTest.java +++ b/test/src/test/java/aima/test/unit/search/informed/InformedSearchTest.java @@ -1,10 +1,6 @@ package aima.test.unit.search.informed; -import aima.core.environment.map2d.GoAction; -import aima.core.environment.map2d.InState; -import aima.core.environment.map2d.Map2D; -import aima.core.environment.map2d.Map2DFunctionFactory; -import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania; +import aima.core.environment.map2d.*; import aima.core.environment.support.ProblemFactory; import aima.core.search.api.Node; import aima.core.search.api.Problem; @@ -28,50 +24,49 @@ @RunWith(Parameterized.class) public class InformedSearchTest { - private static final String A_STAR = "AStarSearch"; - private static final String RBFS = "RecursiveBestFirstSearch"; + private static final String A_STAR = "AStarSearch"; + private static final String RBFS = "RecursiveBestFirstSearch"; + @Parameter + public String searchFunctionName; - @Parameters(name = "{index}: {0}") - public static Collection implementations() { - return Arrays.asList(new Object[][] { { RBFS }, { A_STAR } }); - } + @Parameters(name = "{index}: {0}") + public static Collection implementations() { + return Arrays.asList(new Object[][]{{RBFS}, {A_STAR}}); + } - @Parameter - public String searchFunctionName; + public List searchForActions(Problem problem, ToDoubleFunction> hf) { - public List searchForActions(Problem problem, ToDoubleFunction> hf) { + SearchForActionsFunction searchForActionsFunction; + if (A_STAR.equals(searchFunctionName)) { + searchForActionsFunction = new AStarSearch<>(hf); + } else if (RBFS.equals(searchFunctionName)) { + searchForActionsFunction = new RecursiveBestFirstSearch<>(hf); + } else { + throw new UnsupportedOperationException(); + } + return searchForActionsFunction.apply(problem); + } - SearchForActionsFunction searchForActionsFunction; - if (A_STAR.equals(searchFunctionName)) { - searchForActionsFunction = new AStarSearch<>(hf); - } else if (RBFS.equals(searchFunctionName)) { - searchForActionsFunction = new RecursiveBestFirstSearch<>(hf); - } else { - throw new UnsupportedOperationException(); - } - return searchForActionsFunction.apply(problem); - } + @Test + public void testSimplifiedRoadMapOfPartOfRomania() { + Map2D map = new SimplifiedRoadMapOfPartOfRomania(); + String initialLocation = SimplifiedRoadMapOfPartOfRomania.ARAD; + String goal = initialLocation; - @Test - public void testSimplifiedRoadMapOfPartOfRomania() { - Map2D map = new SimplifiedRoadMapOfPartOfRomania(); - String initialLocation = SimplifiedRoadMapOfPartOfRomania.ARAD; - String goal = initialLocation; + Problem problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, + goal); + if (A_STAR.equals(searchFunctionName)) + assertEquals(new ArrayList<>(), searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, goal))); + else + assertEquals(Arrays.asList((String) null), searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, goal))); - Problem problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, - goal); - if(A_STAR.equals(searchFunctionName)) - assertEquals(new ArrayList<>(), searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, goal))); - else - assertEquals(Arrays.asList((String) null), searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, goal))); - - goal = SimplifiedRoadMapOfPartOfRomania.BUCHAREST; - problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, goal); - assertEquals( - Arrays.asList(new GoAction(SimplifiedRoadMapOfPartOfRomania.SIBIU), - new GoAction(SimplifiedRoadMapOfPartOfRomania.RIMNICU_VILCEA), - new GoAction(SimplifiedRoadMapOfPartOfRomania.PITESTI), - new GoAction(SimplifiedRoadMapOfPartOfRomania.BUCHAREST)), - searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, goal))); - } + goal = SimplifiedRoadMapOfPartOfRomania.BUCHAREST; + problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, goal); + assertEquals( + Arrays.asList(new GoAction(SimplifiedRoadMapOfPartOfRomania.SIBIU), + new GoAction(SimplifiedRoadMapOfPartOfRomania.RIMNICU_VILCEA), + new GoAction(SimplifiedRoadMapOfPartOfRomania.PITESTI), + new GoAction(SimplifiedRoadMapOfPartOfRomania.BUCHAREST)), + searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, goal))); + } } diff --git a/test/src/test/java/aima/test/unit/search/local/HillClimbingSearchTest.java b/test/src/test/java/aima/test/unit/search/local/HillClimbingSearchTest.java index baeb7abcdc..75c8a614b8 100644 --- a/test/src/test/java/aima/test/unit/search/local/HillClimbingSearchTest.java +++ b/test/src/test/java/aima/test/unit/search/local/HillClimbingSearchTest.java @@ -1,9 +1,11 @@ package aima.test.unit.search.local; -import java.util.Arrays; -import java.util.Collection; -import java.util.function.ToDoubleFunction; - +import aima.core.environment.support.ProblemFactory; +import aima.core.search.api.Problem; +import aima.core.search.api.SearchForStateFunction; +import aima.core.search.basic.local.HillClimbingSearch; +import aima.core.util.datastructure.Pair; +import aima.extra.search.local.HillClimbingSearchWithSidewaysMoves; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -11,12 +13,9 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; -import aima.core.environment.support.ProblemFactory; -import aima.core.search.api.Problem; -import aima.core.search.api.SearchForStateFunction; -import aima.core.search.basic.local.HillClimbingSearch; -import aima.core.util.datastructure.Pair; -import aima.extra.search.local.HillClimbingSearchWithSidewaysMoves; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.ToDoubleFunction; /** * @author Ciaran O'Reilly @@ -25,83 +24,81 @@ @RunWith(Parameterized.class) public class HillClimbingSearchTest { - @Parameters(name = "{index}: {0}") - public static Collection implementations() { - return Arrays.asList(new Object[][] { { "HillClimbingSearch" }, { "HillClimbingSearchWithSidewaysMoves" } }); - } + @Parameter + public String searchForStateFunctionName; + // The state value function will be represented by the ascii value of the + // first character in the state name. + ToDoubleFunction asciiChar0StateValueFn = state -> { + return (double) state.charAt(0); + }; + ToDoubleFunction> y_valueFn = x_y -> x_y.getSecond().doubleValue(); + + @Parameters(name = "{index}: {0}") + public static Collection implementations() { + return Arrays.asList(new Object[][]{{"HillClimbingSearch"}, {"HillClimbingSearchWithSidewaysMoves"}}); + } + + public S searchForState(Problem problem, ToDoubleFunction stateValueFn, + boolean isSteepestAscentVersion) { + SearchForStateFunction searchForStateFunction; + + if ("HillClimbingSearch".equals(searchForStateFunctionName)) { + searchForStateFunction = new HillClimbingSearch(stateValueFn, isSteepestAscentVersion); + } else { + searchForStateFunction = new HillClimbingSearchWithSidewaysMoves(stateValueFn, isSteepestAscentVersion); + } + return searchForStateFunction.apply(problem); + } - @Parameter - public String searchForStateFunctionName; + @Test + public void testReachableGlobalMaximum() { + Assert.assertEquals("Z", + searchForState(ProblemFactory.getSimpleBinaryTreeProblem("F", "Z"), asciiChar0StateValueFn, true)); - // The state value function will be represented by the ascii value of the - // first character in the state name. - ToDoubleFunction asciiChar0StateValueFn = state -> { - return (double) state.charAt(0); - }; + Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM, + searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(6, true), y_valueFn, true) + .getSecond().intValue()); - ToDoubleFunction> y_valueFn = x_y -> x_y.getSecond().doubleValue(); + Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM, + searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(12, true), y_valueFn, true) + .getSecond().intValue()); + } - public S searchForState(Problem problem, ToDoubleFunction stateValueFn, - boolean isSteepestAscentVersion) { - SearchForStateFunction searchForStateFunction; + @Test + public void testReachableLocalMaximum() { + Assert.assertEquals("O", + searchForState(ProblemFactory.getSimpleBinaryTreeProblem("A", "Z"), asciiChar0StateValueFn, true)); - if ("HillClimbingSearch".equals(searchForStateFunctionName)) { - searchForStateFunction = new HillClimbingSearch(stateValueFn, isSteepestAscentVersion); - } else { - searchForStateFunction = new HillClimbingSearchWithSidewaysMoves(stateValueFn, isSteepestAscentVersion); - } - return searchForStateFunction.apply(problem); - } + Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_LOCAL_MAXIMUM_VALUE, + searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(14, true), y_valueFn, true) + .getSecond().intValue()); + } - @Test - public void testReachableGlobalMaximum() { - Assert.assertEquals("Z", - searchForState(ProblemFactory.getSimpleBinaryTreeProblem("F", "Z"), asciiChar0StateValueFn, true)); - - Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM, - searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(6, true), y_valueFn, true) - .getSecond().intValue()); - - Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM, - searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(12, true), y_valueFn, true) - .getSecond().intValue()); - } + @Test + public void testNoSuccessors() { + Assert.assertEquals("P", + searchForState(ProblemFactory.getSimpleBinaryTreeProblem("P", "Z"), asciiChar0StateValueFn, true)); + Assert.assertEquals("F", + searchForState(ProblemFactory.getSimpleBinaryTreeProblem("F", "Z"), asciiChar0StateValueFn, false)); - @Test - public void testReachableLocalMaximum() { - Assert.assertEquals("O", - searchForState(ProblemFactory.getSimpleBinaryTreeProblem("A", "Z"), asciiChar0StateValueFn, true)); - - Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_LOCAL_MAXIMUM_VALUE, - searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(14, true), y_valueFn, true) - .getSecond().intValue()); - } + Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_LOCAL_MAXIMUM_VALUE, + searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(16, true), y_valueFn, true) + .getSecond().intValue()); + } - @Test - public void testNoSuccessors() { - Assert.assertEquals("P", - searchForState(ProblemFactory.getSimpleBinaryTreeProblem("P", "Z"), asciiChar0StateValueFn, true)); - Assert.assertEquals("F", - searchForState(ProblemFactory.getSimpleBinaryTreeProblem("F", "Z"), asciiChar0StateValueFn, false)); - - Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_LOCAL_MAXIMUM_VALUE, - searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(16, true), y_valueFn, true) - .getSecond().intValue()); - } - - @Test - public void testSidewaysMoves() { - if ("HillClimbingSearch".equals(searchForStateFunctionName)) { - // Not supported by simple example implementation - Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_SHOULDER_VALUE, - searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(3, true), y_valueFn, true) - .getSecond().intValue()); - } - // Supported by alternative HillClimbing search implementation - else { - Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM, - searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(3, true), y_valueFn, true) - .getSecond().intValue()); - } - } + @Test + public void testSidewaysMoves() { + if ("HillClimbingSearch".equals(searchForStateFunctionName)) { + // Not supported by simple example implementation + Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_SHOULDER_VALUE, + searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(3, true), y_valueFn, true) + .getSecond().intValue()); + } + // Supported by alternative HillClimbing search implementation + else { + Assert.assertEquals(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM, + searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(3, true), y_valueFn, true) + .getSecond().intValue()); + } + } } diff --git a/test/src/test/java/aima/test/unit/search/local/SimulatedAnnealingTest.java b/test/src/test/java/aima/test/unit/search/local/SimulatedAnnealingTest.java index 795bee945e..f4735a5482 100644 --- a/test/src/test/java/aima/test/unit/search/local/SimulatedAnnealingTest.java +++ b/test/src/test/java/aima/test/unit/search/local/SimulatedAnnealingTest.java @@ -1,74 +1,73 @@ package aima.test.unit.search.local; -import java.util.Arrays; -import java.util.Collection; -import java.util.function.ToDoubleFunction; - -import org.junit.Test; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - import aima.core.environment.support.ProblemFactory; import aima.core.search.api.Problem; import aima.core.search.api.SearchForStateFunction; import aima.core.search.basic.local.SimulatedAnnealingSearch; import aima.core.util.datastructure.Pair; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.util.Arrays; +import java.util.Collection; +import java.util.function.ToDoubleFunction; /** - * * @author Ciaran O'Reilly * @author Anurag Rai */ public class SimulatedAnnealingTest { - @Parameters(name = "{index}: {0}") - public static Collection implementations() { - return Arrays.asList(new Object[][] { { "SimulatedAnnealingSearch" } }); - } - - @Parameter - public String searchForStateFunctionName; + @Parameter + public String searchForStateFunctionName; + // The state value function will be represented by the ascii value of the + // first character in the state name. + ToDoubleFunction asciiChar0StateValueFn = state -> { + return (double) state.charAt(0); + }; + ToDoubleFunction> y_valueFn = x_y -> x_y.getSecond().doubleValue(); + + @Parameters(name = "{index}: {0}") + public static Collection implementations() { + return Arrays.asList(new Object[][]{{"SimulatedAnnealingSearch"}}); + } + + public S searchForState(Problem problem, ToDoubleFunction stateValueFn, boolean isGradientAscentVersion) { + SearchForStateFunction searchForStateFunction; + + searchForStateFunction = new SimulatedAnnealingSearch(stateValueFn, isGradientAscentVersion); + + return searchForStateFunction.apply(problem); + } + + // + // NOTE: We use timeouts as simulated-annealing selects a random action so in most cases you cannot predetermine its result. + @Test(timeout = 1000) + public void testReachableGlobalMaximum() { + while (!"Z".equals(searchForState(ProblemFactory.getSimpleBinaryTreeProblem("F", "Z"), asciiChar0StateValueFn, true))) + ; - // The state value function will be represented by the ascii value of the - // first character in the state name. - ToDoubleFunction asciiChar0StateValueFn = state -> { - return (double) state.charAt(0); - }; - - ToDoubleFunction> y_valueFn = x_y -> x_y.getSecond().doubleValue(); + while (ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM != + searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(6, true), y_valueFn, true) + .getSecond().intValue()) ; - public S searchForState(Problem problem, ToDoubleFunction stateValueFn, boolean isGradientAscentVersion) { - SearchForStateFunction searchForStateFunction; - - searchForStateFunction = new SimulatedAnnealingSearch(stateValueFn, isGradientAscentVersion); - - return searchForStateFunction.apply(problem); - } - - // - // NOTE: We use timeouts as simulated-annealing selects a random action so in most cases you cannot predetermine its result. - @Test(timeout=1000) - public void testReachableGlobalMaximum() { - while (!"Z".equals(searchForState(ProblemFactory.getSimpleBinaryTreeProblem("F", "Z"), asciiChar0StateValueFn, true))); - - while(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM != - searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(6, true), y_valueFn, true) - .getSecond().intValue()); - - // Simulated annealing should always find the global maximum in this search space within a reasonable number of attempts. - for (int x = 0; x < ProblemFactory.DEFAULT_DISCRETE_FUNCTION_DEPENDENT_VALUES.length; x++) { - while(ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM != - searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(x, true), y_valueFn, true) - .getSecond().intValue()); - } - } + // Simulated annealing should always find the global maximum in this search space within a reasonable number of attempts. + for (int x = 0; x < ProblemFactory.DEFAULT_DISCRETE_FUNCTION_DEPENDENT_VALUES.length; x++) { + while (ProblemFactory.DEFAULT_DISCRETE_FUNCTION_GLOBAL_MAXIMIM != + searchForState(ProblemFactory.getDefaultSimpleDiscreteFunctionProblem(x, true), y_valueFn, true) + .getSecond().intValue()) ; + } + } - @Test(timeout=1000) - public void testReachableLocalMaximum() { - while(!"O".equals(searchForState(ProblemFactory.getSimpleBinaryTreeProblem("A", "Z"), asciiChar0StateValueFn, true))); - } + @Test(timeout = 1000) + public void testReachableLocalMaximum() { + while (!"O".equals(searchForState(ProblemFactory.getSimpleBinaryTreeProblem("A", "Z"), asciiChar0StateValueFn, true))) + ; + } - @Test(timeout=1000) - public void testNoSuccessors() { - while (!"P".equals(searchForState(ProblemFactory.getSimpleBinaryTreeProblem("P", "Z"), asciiChar0StateValueFn, true))); - } + @Test(timeout = 1000) + public void testNoSuccessors() { + while (!"P".equals(searchForState(ProblemFactory.getSimpleBinaryTreeProblem("P", "Z"), asciiChar0StateValueFn, true))) + ; + } } diff --git a/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java index a0c2cf9140..d02d3ee649 100644 --- a/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java +++ b/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java @@ -23,19 +23,19 @@ public class BreadthFirstSearchTest { BreadthFirstSearch breadthFirstSearch = new BreadthFirstSearch(); @Test - public void testSimplifiedRomania(){ + public void testSimplifiedRomania() { Problem problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem( SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.BUCHAREST); Node result = breadthFirstSearch.search(problem); - Assert.assertEquals("In(Bucharest)",result.state().toString()); + Assert.assertEquals("In(Bucharest)", result.state().toString()); List actions = Arrays.asList(new GoAction(SimplifiedRoadMapOfPartOfRomania.SIBIU), new GoAction(SimplifiedRoadMapOfPartOfRomania.FAGARAS), new GoAction(SimplifiedRoadMapOfPartOfRomania.BUCHAREST)); - Assert.assertEquals(actions,breadthFirstSearch.apply(problem)); + Assert.assertEquals(actions, breadthFirstSearch.apply(problem)); } @Test - public void testVacuumEnvironment(){ + public void testVacuumEnvironment() { Assert.assertEquals(new ArrayList(), breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A", new VELocalState("A", VacuumEnvironment.Status.Clean), @@ -113,7 +113,7 @@ public void testVacuumEnvironment(){ } @Test - public void simpleBinaryTreeTests(){ + public void simpleBinaryTreeTests() { Assert.assertEquals(new ArrayList(), breadthFirstSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "A"))); @@ -129,7 +129,7 @@ public void simpleBinaryTreeTests(){ } @Test - public void unreachableStates(){ + public void unreachableStates() { Assert.assertEquals(Collections.emptyList(), breadthFirstSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("B", "A"))); } diff --git a/test/src/test/java/aima/test/unit/search/uninformedsearch/IterativeDeepeningSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformedsearch/IterativeDeepeningSearchTest.java index a251b026fa..618dd21f75 100644 --- a/test/src/test/java/aima/test/unit/search/uninformedsearch/IterativeDeepeningSearchTest.java +++ b/test/src/test/java/aima/test/unit/search/uninformedsearch/IterativeDeepeningSearchTest.java @@ -7,12 +7,10 @@ import aima.core.environment.vacuum.VacuumEnvironment; import aima.core.search.api.Node; import aima.core.search.api.Problem; -import aima.core.search.basic.uninformedsearch.BreadthFirstSearch; import aima.core.search.basic.uninformedsearch.IterativeDeepeningSearch; import org.junit.Assert; import org.junit.Test; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -20,11 +18,11 @@ public class IterativeDeepeningSearchTest { IterativeDeepeningSearch iterativeDeepeningSearch = new IterativeDeepeningSearch(); @Test - public void testSimplifiedRomania(){ + public void testSimplifiedRomania() { Problem problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem( SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.BUCHAREST); Node result = iterativeDeepeningSearch.search(problem); - Assert.assertEquals("In(Bucharest)",result.state().toString()); + Assert.assertEquals("In(Bucharest)", result.state().toString()); List actions = Arrays.asList(new GoAction(SimplifiedRoadMapOfPartOfRomania.SIBIU), new GoAction(SimplifiedRoadMapOfPartOfRomania.FAGARAS), new GoAction(SimplifiedRoadMapOfPartOfRomania.BUCHAREST)); @@ -32,7 +30,7 @@ public void testSimplifiedRomania(){ } @Test - public void testVacuumEnvironment(){ + public void testVacuumEnvironment() { Assert.assertEquals(Arrays.asList(VacuumEnvironment.ACTION_RIGHT, VacuumEnvironment.ACTION_SUCK), iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A", new VELocalState("A", VacuumEnvironment.Status.Clean), @@ -100,7 +98,7 @@ public void testVacuumEnvironment(){ } @Test - public void simpleBinaryTreeTests(){ + public void simpleBinaryTreeTests() { Assert.assertEquals(Arrays.asList("B"), iterativeDeepeningSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "B"))); Assert.assertEquals(Arrays.asList("C"), iterativeDeepeningSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "C")));

+ *
+ /**
+ * Artificial Intelligence A Modern Approach (4th Edition): Figure ??, page ??.
+ * 
+ *
+ * + *
+ * function ITERATIVE-DEEPENING-SEARCH(problem) returns a solution, or failure
+ *   for depth = 0 to infinity  do
+ *     result ← DEPTH-LIMITED-SEARCH(problem, depth)
+ *     if result != cutoff then return result
+ * 
+ * + * Figure ?? The iterative deepening search algorithm, which repeatedly applies + * depth-limited search with increasing limits. It terminates when a solution is + * found or if the depth-limited search returns failure, meaning that no + * solution exists. + * + * @author Ciaran O'Reilly + * @author Ravi Mohan + * @author Ruediger Lunde + * @author samagra + */ public class IterativeDeepeningSearch implements GenericSearchInterface{ DepthLimitedSearch depthLimitedSearch = new DepthLimitedSearch<>(); + /** + * function ITERATIVE-DEEPENING-SEARCH(problem) returns a solution, or failure + * @param problem The search problem + * @return The solution if exists else continues to run + */ @Override public Node search(Problem problem) { + //for depth = 0 to infinity do for(int depth =0;depth>=0;depth++){ + //result ← DEPTH-LIMITED-SEARCH(problem, depth) Node result = depthLimitedSearch.search(problem,depth); + // if result != cutoff then return result if (result!=null) return result; } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java index 8141bd2b83..2729851a93 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java @@ -10,8 +10,36 @@ import java.util.HashMap; import java.util.PriorityQueue; +/** + *
+ *  function UNIFORM-COST-SEARCH(problem) returns a solution, or failure
+ *  if problem's initial state is a goal then return empty path to initial state
+ *  frontier ← a priority queue ordered by pathCost, with a node for the initial state
+ *  reached ← a table of {state: the best path that reached state}; initially empty
+ *  solution ← failure
+ *  while frontier is not empty and top(frontier) is cheaper than solution do
+ *    parent ← pop(frontier)
+ *    for child in successors(parent) do
+ *      s ← child.state
+ *      if s is not in reached or child is a cheaper path than reached[s] then
+ *        reached[s] ← child
+ *        add child to the frontier
+ *        if child is a goal and is cheaper than solution then
+ *          solution = child
+ *  return solution
+ * 
+ * + * Figure 3.11 Uniform-cost search on a graph. Finds optimal + * paths for problems with varying step costs. + * + * @param
The generic class representing action. + * @param The generic class representing states. + * + * @author samagra + */ public class UniformCostSearch implements GenericSearchInterface { + // frontier ← a priority queue ordered by pathCost, with a node for the initial state PriorityQueue> frontier = new PriorityQueue<>(new Comparator>() { @Override public int compare(Node o1, Node o2) { @@ -19,33 +47,47 @@ public int compare(Node o1, Node o2) { } }); - NodeFactory nodeFactory = new BasicNodeFactory<>(); + private NodeFactory nodeFactory = new BasicNodeFactory<>(); @Override public Node search(Problem problem) { + // if problem's initial state is a goal then return empty path to initial state if (problem.isGoalState(problem.initialState())){ return nodeFactory.newRootNode(problem.initialState()); } + // frontier ← a priority queue ordered by pathCost, with a node for the initial state frontier.clear(); frontier.add(nodeFactory.newRootNode(problem.initialState())); + // reached ← a table of {state: the best path that reached state}; initially empty HashMap> reached = new HashMap<>(); + // solution ← failure Node solution = null; + // while frontier is not empty and top(frontier) is cheaper than solution do while (!frontier.isEmpty() && (solution==null || frontier.peek().pathCost() parent = frontier.poll(); + // for child in successors(parent) do for (Node child : SearchUtils.successors(problem, parent)) { + // s ← child.state S s = child.state(); + // if s is not in reached or child is a cheaper path than reached[s] then if (!reached.containsKey(s) || child.pathCost() Date: Thu, 19 Jul 2018 00:54:44 +0530 Subject: [PATCH 09/16] Adds methods to retrieve actions --- .../uninformedsearch/BreadthFirstSearch.java | 20 +++++++++++++++---- .../basic/uninformedsearch/GenericSearch.java | 20 +++++++++++++++---- .../IterativeDeepeningSearch.java | 20 ++++++++++++++++++- .../uninformedsearch/UniformCostSearch.java | 20 +++++++++++++++---- 4 files changed, 67 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java index 07af12bbf9..9ff21871f9 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java @@ -3,12 +3,11 @@ import aima.core.search.api.Node; import aima.core.search.api.NodeFactory; import aima.core.search.api.Problem; +import aima.core.search.api.SearchForActionsFunction; import aima.core.search.basic.SearchUtils; import aima.core.search.basic.support.BasicNodeFactory; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Queue; +import java.util.*; /** * - * + *

* Figure 3.9 Breadth-first search algorithm. * * @param The generic object representing actions. - * * @param The generic object representing state. - * * @author samagra */ -public class BreadthFirstSearch implements GenericSearchInterface, SearchForActionsFunction { +public class BreadthFirstSearch implements GenericSearchInterface, SearchForActionsFunction { // Node factory is just a helper class to generate new nodes // It takes the problem and current state in order to generate new nodes - NodeFactory nodeFactory = new BasicNodeFactory(); + NodeFactory nodeFactory = new BasicNodeFactory(); public BreadthFirstSearch() { } /** * function BREADTH-FIRST-SEARCH(problem) returns a solution, or failure + * * @param problem The search problem. * @return The goal node if any else null. */ @Override - public Node search(Problem problem) { - //  if problem's initial state is a goal then return empty path to initial state - if (problem.isGoalState(problem.initialState())){ + public Node search(Problem problem) { + if (problem.isGoalState(problem.initialState())) { return nodeFactory.newRootNode(problem.initialState()); } // frontier ← a FIFO queue initially containing one path, for the problem's initial state Queue frontier = new LinkedList<>(); ((LinkedList) frontier).add(nodeFactory.newRootNode(problem.initialState())); - // reached ← a set of states; initially empty HashSet reached = new HashSet<>(); - // solution ← failure - Node solution = null; - // while frontier is not empty do - while (!frontier.isEmpty()){ - // parent ← the first node in frontier - Node parent = frontier.remove(); - // for child in successors(parent) do - for (Node child: SearchUtils.successors(problem,parent)){ - // s ← child.state + Node solution = null; + while (!frontier.isEmpty()) { + Node parent = frontier.remove(); + for (Node child : SearchUtils.successors(problem, parent)) { S s = child.state(); - // if s is a goal then - if (problem.isGoalState(s)){ - // return child + if (problem.isGoalState(s)) { return child; } // if s is not in reached then - if (!reached.contains(s)){ - // add s to reached + if (!reached.contains(s)) { reached.add(s); - // add child to the end of frontier frontier.add(child); } } } - // return solution return solution; } + /** + * Extracts a list of actions from a solution state. + * + * @param problem + * @return + */ @Override public List apply(Problem problem) { - Node solution = this.search(problem); - if(solution == null) + Node solution = this.search(problem); + if (solution == null) return new ArrayList<>(); return SearchUtils.generateActions(solution); } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java index 4c0b6c4ffc..f35c248b7f 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/DepthLimitedSearch.java @@ -24,7 +24,7 @@ *          add child to frontier *  return solution *

@@ -37,7 +36,7 @@
  *
  * @author samagra
  */
-public class BreadthFirstSearch implements GenericSearchInterface {
+public class BreadthFirstSearch implements GenericSearchInterface, SearchForActionsFunction {
     // Node factory is just a helper class to generate new nodes
     // It takes the problem and current state in order to generate new nodes
     NodeFactory nodeFactory = new BasicNodeFactory();
@@ -85,4 +84,17 @@ public Node search(Problem problem) {
         // return solution
         return solution;
     }
+
+    @Override
+    public List apply(Problem problem) {
+        Node solution = this.search(problem);
+        Node parent = solution.parent();
+        List actions = new ArrayList<>();
+        while(parent != null){
+            actions.add(parent.action());
+            parent = parent.parent();
+        }
+        Collections.reverse(actions);
+        return actions;
+    }
 }
diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java
index 106b895fb6..858a9bee08 100644
--- a/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java
+++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/GenericSearch.java
@@ -3,11 +3,10 @@
 import aima.core.search.api.Node;
 import aima.core.search.api.NodeFactory;
 import aima.core.search.api.Problem;
+import aima.core.search.api.SearchForActionsFunction;
 import aima.core.search.basic.support.BasicNodeFactory;
 
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Queue;
+import java.util.*;
 
 /**
  * Artificial Intelligence A Modern Approach (4th Edition): Figure ??, page ??.
@@ -41,7 +40,7 @@
  * @author samagra
  */
 
-public abstract class GenericSearch {
+public abstract class GenericSearch implements SearchForActionsFunction {
     public NodeFactory nodeFactory = new BasicNodeFactory<>(); // to generate new nodes.
     public HashMap> reached = new HashMap<>();
 
@@ -117,4 +116,17 @@ public Queue> newFrontier(S initialState) {
         frontier.add(nodeFactory.newRootNode(initialState));
         return frontier;
     }
+
+    @Override
+    public List apply(Problem problem) {
+        Node solution = this.genericSearch(problem);
+        Node parent = solution.parent();
+        List actions = new ArrayList<>();
+        while(parent != null){
+            actions.add(parent.action());
+            parent = parent.parent();
+        }
+        Collections.reverse(actions);
+        return actions;
+    }
 }
diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java
index a2a92879c9..c9f0342d8b 100644
--- a/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java
+++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java
@@ -2,6 +2,11 @@
 
 import aima.core.search.api.Node;
 import aima.core.search.api.Problem;
+import aima.core.search.api.SearchForActionsFunction;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * 
@@ -28,7 +33,7 @@
  * @author Ruediger Lunde
  * @author samagra
  */
-public class IterativeDeepeningSearch implements GenericSearchInterface{
+public class IterativeDeepeningSearch implements GenericSearchInterface, SearchForActionsFunction {
     DepthLimitedSearch depthLimitedSearch = new DepthLimitedSearch<>();
 
     /**
@@ -48,4 +53,17 @@ public Node search(Problem problem) {
         }
         return null;
     }
+
+    @Override
+    public List apply(Problem problem) {
+        Node solution = this.search(problem);
+        Node parent = solution.parent();
+        List actions = new ArrayList<>();
+        while(parent != null){
+            actions.add(parent.action());
+            parent = parent.parent();
+        }
+        Collections.reverse(actions);
+        return actions;
+    }
 }
diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java
index 2729851a93..7a5d26447b 100644
--- a/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java
+++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java
@@ -3,12 +3,11 @@
 import aima.core.search.api.Node;
 import aima.core.search.api.NodeFactory;
 import aima.core.search.api.Problem;
+import aima.core.search.api.SearchForActionsFunction;
 import aima.core.search.basic.SearchUtils;
 import aima.core.search.basic.support.BasicNodeFactory;
 
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.PriorityQueue;
+import java.util.*;
 
 /**
  * 
@@ -37,7 +36,7 @@
  *
  * @author samagra
  */
-public class UniformCostSearch implements GenericSearchInterface {
+public class UniformCostSearch implements GenericSearchInterface, SearchForActionsFunction {
 
     // frontier ← a priority queue ordered by pathCost, with a node for the initial state
     PriorityQueue> frontier = new PriorityQueue<>(new Comparator>() {
@@ -90,4 +89,17 @@ public Node search(Problem problem) {
         // return solution
         return solution;
     }
+
+    @Override
+    public List apply(Problem problem) {
+        Node solution = this.search(problem);
+        Node parent = solution.parent();
+        List actions = new ArrayList<>();
+        while(parent != null){
+            actions.add(parent.action());
+            parent = parent.parent();
+        }
+        Collections.reverse(actions);
+        return actions;
+    }
 }

From 942b0e7cf7c60c7a5f89bb1e16e9546f0ed62f8a Mon Sep 17 00:00:00 2001
From: Samagra 
Date: Thu, 19 Jul 2018 09:07:01 +0530
Subject: [PATCH 10/16] Adds generator for actions

---
 .../aima/core/search/basic/SearchUtils.java   | 12 +++++
 .../uninformedsearch/BreadthFirstSearch.java  | 12 ++---
 .../search/uninformed/TestSearchTest.java     | 44 +++++++++++++++++++
 .../BreadthFirstSearchTest.java               | 28 ++++++++++++
 4 files changed, 88 insertions(+), 8 deletions(-)
 create mode 100644 test/src/test/java/aima/test/unit/search/uninformed/TestSearchTest.java
 create mode 100644 test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java

diff --git a/core/src/main/java/aima/core/search/basic/SearchUtils.java b/core/src/main/java/aima/core/search/basic/SearchUtils.java
index c1c14196be..ae62265dca 100644
--- a/core/src/main/java/aima/core/search/basic/SearchUtils.java
+++ b/core/src/main/java/aima/core/search/basic/SearchUtils.java
@@ -6,6 +6,7 @@
 import aima.core.search.basic.support.BasicNodeFactory;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 public class SearchUtils {
@@ -33,4 +34,15 @@ public static int depth(Node node){
         }
         return count;
     }
+
+    public static  List generateActions(Node solution){
+        Node parent = solution;
+        List actions = new ArrayList<>();
+        while(parent.parent() != null){
+            actions.add(parent.action());
+            parent = parent.parent();
+        }
+        Collections.reverse(actions);
+        return actions;
+    }
 }
diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java
index 9ff21871f9..afbe959f65 100644
--- a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java
+++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java
@@ -41,6 +41,9 @@ public class BreadthFirstSearch implements GenericSearchInterface, Sea
     // It takes the problem and current state in order to generate new nodes
     NodeFactory nodeFactory = new BasicNodeFactory();
 
+    public BreadthFirstSearch() {
+    }
+
     /**
      * function BREADTH-FIRST-SEARCH(problem) returns a solution, or failure
      * @param problem The search problem.
@@ -88,13 +91,6 @@ public Node search(Problem problem) {
     @Override
     public List apply(Problem problem) {
         Node solution = this.search(problem);
-        Node parent = solution.parent();
-        List actions = new ArrayList<>();
-        while(parent != null){
-            actions.add(parent.action());
-            parent = parent.parent();
-        }
-        Collections.reverse(actions);
-        return actions;
+        return SearchUtils.generateActions(solution);
     }
 }
diff --git a/test/src/test/java/aima/test/unit/search/uninformed/TestSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformed/TestSearchTest.java
new file mode 100644
index 0000000000..c6ddbe5aea
--- /dev/null
+++ b/test/src/test/java/aima/test/unit/search/uninformed/TestSearchTest.java
@@ -0,0 +1,44 @@
+package aima.test.unit.search.uninformed;
+
+import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania;
+import aima.core.environment.support.ProblemFactory;
+import aima.core.search.api.Node;
+import aima.core.search.basic.uninformedsearch.BreadthFirstSearch;
+import aima.core.search.basic.uninformedsearch.GenericSearch;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Queue;
+
+
+public class TestSearchTest {
+    GenericSearch bfs = new GenericSearch() {
+        @Override
+        public Queue addToFrontier(Node child, Queue frontier) {
+            frontier.add(child);
+            return frontier;
+        }
+
+        @Override
+        public boolean canImprove(HashMap reached, Node solution) {
+            return solution==null;
+        }
+
+        @Override
+        public Object apply(Object o) {
+            return new ArrayList<>();
+        }
+    };
+    String s = "sapas";
+    BreadthFirstSearch search = new BreadthFirstSearch();
+    @Test
+    public void testClassTest(){
+        Node result = search.search(ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(
+                SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.ARAD));
+        System.out.println(result.toString());
+    }
+}
diff --git a/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java
new file mode 100644
index 0000000000..968f0c3a54
--- /dev/null
+++ b/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java
@@ -0,0 +1,28 @@
+package aima.test.unit.search.uninformedsearch;
+
+import aima.core.environment.map2d.GoAction;
+import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania;
+import aima.core.environment.support.ProblemFactory;
+import aima.core.search.api.Node;
+import aima.core.search.api.Problem;
+import aima.core.search.basic.uninformedsearch.BreadthFirstSearch;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class BreadthFirstSearchTest {
+    BreadthFirstSearch breadthFirstSearch = new BreadthFirstSearch();
+
+    @Test
+    public void testSimplifiedRomania(){
+        Problem problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(
+                SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.BUCHAREST);
+        Node result = breadthFirstSearch.search(problem);
+        List actions = Arrays.asList(new GoAction(SimplifiedRoadMapOfPartOfRomania.SIBIU),
+                new GoAction(SimplifiedRoadMapOfPartOfRomania.FAGARAS),
+                new GoAction(SimplifiedRoadMapOfPartOfRomania.BUCHAREST));
+        Assert.assertEquals(actions,breadthFirstSearch.apply(problem));
+    }
+}

From 0bdf4d29dc89130bbd5c2cd631db11367c153451 Mon Sep 17 00:00:00 2001
From: Samagra 
Date: Thu, 19 Jul 2018 11:29:01 +0530
Subject: [PATCH 11/16] Adds bfs tests

---
 .../uninformedsearch/BreadthFirstSearch.java  |   2 +
 .../BreadthFirstSearchTest.java               | 108 ++++++++++++++++++
 2 files changed, 110 insertions(+)

diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java
index afbe959f65..d47cec30f8 100644
--- a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java
+++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java
@@ -91,6 +91,8 @@ public Node search(Problem problem) {
     @Override
     public List apply(Problem problem) {
         Node solution = this.search(problem);
+        if(solution == null)
+            return new ArrayList<>();
         return SearchUtils.generateActions(solution);
     }
 }
diff --git a/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java
index 968f0c3a54..a0c2cf9140 100644
--- a/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java
+++ b/test/src/test/java/aima/test/unit/search/uninformedsearch/BreadthFirstSearchTest.java
@@ -3,15 +3,22 @@
 import aima.core.environment.map2d.GoAction;
 import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania;
 import aima.core.environment.support.ProblemFactory;
+import aima.core.environment.vacuum.VELocalState;
+import aima.core.environment.vacuum.VacuumEnvironment;
 import aima.core.search.api.Node;
 import aima.core.search.api.Problem;
 import aima.core.search.basic.uninformedsearch.BreadthFirstSearch;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
+/**
+ * @author samagra
+ */
 public class BreadthFirstSearchTest {
     BreadthFirstSearch breadthFirstSearch = new BreadthFirstSearch();
 
@@ -20,9 +27,110 @@ public void testSimplifiedRomania(){
         Problem problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(
                 SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.BUCHAREST);
         Node result = breadthFirstSearch.search(problem);
+        Assert.assertEquals("In(Bucharest)",result.state().toString());
         List actions = Arrays.asList(new GoAction(SimplifiedRoadMapOfPartOfRomania.SIBIU),
                 new GoAction(SimplifiedRoadMapOfPartOfRomania.FAGARAS),
                 new GoAction(SimplifiedRoadMapOfPartOfRomania.BUCHAREST));
         Assert.assertEquals(actions,breadthFirstSearch.apply(problem));
     }
+
+    @Test
+    public void testVacuumEnvironment(){
+        Assert.assertEquals(new ArrayList(),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A",
+                        new VELocalState("A", VacuumEnvironment.Status.Clean),
+                        new VELocalState("B", VacuumEnvironment.Status.Clean))));
+
+        Assert.assertEquals(Arrays.asList(VacuumEnvironment.ACTION_RIGHT, VacuumEnvironment.ACTION_SUCK),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A",
+                        new VELocalState("A", VacuumEnvironment.Status.Clean),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(Arrays.asList(VacuumEnvironment.ACTION_SUCK),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Clean))));
+
+        Assert.assertEquals(
+                Arrays.asList(VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_RIGHT,
+                        VacuumEnvironment.ACTION_SUCK),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(new ArrayList(),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("B",
+                        new VELocalState("A", VacuumEnvironment.Status.Clean),
+                        new VELocalState("B", VacuumEnvironment.Status.Clean))));
+
+        Assert.assertEquals(Arrays.asList(VacuumEnvironment.ACTION_SUCK),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("B",
+                        new VELocalState("A", VacuumEnvironment.Status.Clean),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(Arrays.asList(VacuumEnvironment.ACTION_LEFT, VacuumEnvironment.ACTION_SUCK),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("B",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Clean))));
+
+        Assert.assertEquals(
+                Arrays.asList(VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_LEFT,
+                        VacuumEnvironment.ACTION_SUCK),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("B",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty))));
+
+        // A slightly larger world
+        Assert.assertEquals(
+                Arrays.asList(VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_RIGHT,
+                        VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_RIGHT, VacuumEnvironment.ACTION_SUCK),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("C", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(
+                Arrays.asList(VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_LEFT,
+                        VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_LEFT, VacuumEnvironment.ACTION_SUCK),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("C",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("C", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(
+                // NOTE: This sequence of actions is only correct because we
+                // always order our possible actions [Left, Suck, Right] in
+                // ProblemFactory.getSimpleVacuumWorldProblem(), if the order
+                // was different we would likely get a different answer.
+                Arrays.asList(VacuumEnvironment.ACTION_LEFT, VacuumEnvironment.ACTION_SUCK,
+                        VacuumEnvironment.ACTION_RIGHT, VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_RIGHT,
+                        VacuumEnvironment.ACTION_SUCK),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("B",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("C", VacuumEnvironment.Status.Dirty))));
+
+    }
+
+    @Test
+    public void simpleBinaryTreeTests(){
+        Assert.assertEquals(new ArrayList(),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "A")));
+
+        Assert.assertEquals(Arrays.asList("B"), breadthFirstSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "B")));
+
+        Assert.assertEquals(Arrays.asList("C"), breadthFirstSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "C")));
+
+        Assert.assertEquals(Arrays.asList("B", "E"),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "E")));
+
+        Assert.assertEquals(Arrays.asList("C", "F", "L"),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "L")));
+    }
+
+    @Test
+    public void unreachableStates(){
+        Assert.assertEquals(Collections.emptyList(),
+                breadthFirstSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("B", "A")));
+    }
 }

From 8f4a72ff7fe16df3fb18318b36f6f237d87db3af Mon Sep 17 00:00:00 2001
From: Samagra 
Date: Thu, 19 Jul 2018 11:48:58 +0530
Subject: [PATCH 12/16] Add tests for IDS and UCS

---
 .../IterativeDeepeningSearch.java             |  10 +-
 .../uninformedsearch/UniformCostSearch.java   |  11 +-
 .../search/uninformed/TestSearchTest.java     |  44 -------
 .../GenericSearchTest.java                    |   2 +-
 .../IterativeDeepeningSearchTest.java         | 114 ++++++++++++++++++
 .../UniformCostSearchTest.java                |  25 ++++
 6 files changed, 145 insertions(+), 61 deletions(-)
 delete mode 100644 test/src/test/java/aima/test/unit/search/uninformed/TestSearchTest.java
 rename test/src/test/java/aima/test/unit/search/{uninformed => uninformedsearch}/GenericSearchTest.java (97%)
 create mode 100644 test/src/test/java/aima/test/unit/search/uninformedsearch/IterativeDeepeningSearchTest.java
 create mode 100644 test/src/test/java/aima/test/unit/search/uninformedsearch/UniformCostSearchTest.java

diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java
index c9f0342d8b..5a9ec500fc 100644
--- a/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java
+++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/IterativeDeepeningSearch.java
@@ -3,6 +3,7 @@
 import aima.core.search.api.Node;
 import aima.core.search.api.Problem;
 import aima.core.search.api.SearchForActionsFunction;
+import aima.core.search.basic.SearchUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -57,13 +58,6 @@ public Node search(Problem problem) {
     @Override
     public List apply(Problem problem) {
         Node solution = this.search(problem);
-        Node parent = solution.parent();
-        List actions = new ArrayList<>();
-        while(parent != null){
-            actions.add(parent.action());
-            parent = parent.parent();
-        }
-        Collections.reverse(actions);
-        return actions;
+        return SearchUtils.generateActions(solution);
     }
 }
diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java
index 7a5d26447b..dc53b6af3a 100644
--- a/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java
+++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/UniformCostSearch.java
@@ -93,13 +93,8 @@ public Node search(Problem problem) {
     @Override
     public List apply(Problem problem) {
         Node solution = this.search(problem);
-        Node parent = solution.parent();
-        List actions = new ArrayList<>();
-        while(parent != null){
-            actions.add(parent.action());
-            parent = parent.parent();
-        }
-        Collections.reverse(actions);
-        return actions;
+        if (solution == null)
+            return new ArrayList<>();
+        return SearchUtils.generateActions(solution);
     }
 }
diff --git a/test/src/test/java/aima/test/unit/search/uninformed/TestSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformed/TestSearchTest.java
deleted file mode 100644
index c6ddbe5aea..0000000000
--- a/test/src/test/java/aima/test/unit/search/uninformed/TestSearchTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package aima.test.unit.search.uninformed;
-
-import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania;
-import aima.core.environment.support.ProblemFactory;
-import aima.core.search.api.Node;
-import aima.core.search.basic.uninformedsearch.BreadthFirstSearch;
-import aima.core.search.basic.uninformedsearch.GenericSearch;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Queue;
-
-
-public class TestSearchTest {
-    GenericSearch bfs = new GenericSearch() {
-        @Override
-        public Queue addToFrontier(Node child, Queue frontier) {
-            frontier.add(child);
-            return frontier;
-        }
-
-        @Override
-        public boolean canImprove(HashMap reached, Node solution) {
-            return solution==null;
-        }
-
-        @Override
-        public Object apply(Object o) {
-            return new ArrayList<>();
-        }
-    };
-    String s = "sapas";
-    BreadthFirstSearch search = new BreadthFirstSearch();
-    @Test
-    public void testClassTest(){
-        Node result = search.search(ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(
-                SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.ARAD));
-        System.out.println(result.toString());
-    }
-}
diff --git a/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformedsearch/GenericSearchTest.java
similarity index 97%
rename from test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java
rename to test/src/test/java/aima/test/unit/search/uninformedsearch/GenericSearchTest.java
index 0dcd3262ad..db7e52f137 100644
--- a/test/src/test/java/aima/test/unit/search/uninformed/GenericSearchTest.java
+++ b/test/src/test/java/aima/test/unit/search/uninformedsearch/GenericSearchTest.java
@@ -1,4 +1,4 @@
-package aima.test.unit.search.uninformed;
+package aima.test.unit.search.uninformedsearch;
 
 import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania;
 import aima.core.environment.support.ProblemFactory;
diff --git a/test/src/test/java/aima/test/unit/search/uninformedsearch/IterativeDeepeningSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformedsearch/IterativeDeepeningSearchTest.java
new file mode 100644
index 0000000000..a251b026fa
--- /dev/null
+++ b/test/src/test/java/aima/test/unit/search/uninformedsearch/IterativeDeepeningSearchTest.java
@@ -0,0 +1,114 @@
+package aima.test.unit.search.uninformedsearch;
+
+import aima.core.environment.map2d.GoAction;
+import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania;
+import aima.core.environment.support.ProblemFactory;
+import aima.core.environment.vacuum.VELocalState;
+import aima.core.environment.vacuum.VacuumEnvironment;
+import aima.core.search.api.Node;
+import aima.core.search.api.Problem;
+import aima.core.search.basic.uninformedsearch.BreadthFirstSearch;
+import aima.core.search.basic.uninformedsearch.IterativeDeepeningSearch;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class IterativeDeepeningSearchTest {
+    IterativeDeepeningSearch iterativeDeepeningSearch = new IterativeDeepeningSearch();
+
+    @Test
+    public void testSimplifiedRomania(){
+        Problem problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(
+                SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.BUCHAREST);
+        Node result = iterativeDeepeningSearch.search(problem);
+        Assert.assertEquals("In(Bucharest)",result.state().toString());
+        List actions = Arrays.asList(new GoAction(SimplifiedRoadMapOfPartOfRomania.SIBIU),
+                new GoAction(SimplifiedRoadMapOfPartOfRomania.FAGARAS),
+                new GoAction(SimplifiedRoadMapOfPartOfRomania.BUCHAREST));
+        Assert.assertEquals(actions, iterativeDeepeningSearch.apply(problem));
+    }
+
+    @Test
+    public void testVacuumEnvironment(){
+        Assert.assertEquals(Arrays.asList(VacuumEnvironment.ACTION_RIGHT, VacuumEnvironment.ACTION_SUCK),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A",
+                        new VELocalState("A", VacuumEnvironment.Status.Clean),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(Arrays.asList(VacuumEnvironment.ACTION_SUCK),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Clean))));
+
+        Assert.assertEquals(
+                Arrays.asList(VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_RIGHT,
+                        VacuumEnvironment.ACTION_SUCK),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(Arrays.asList(VacuumEnvironment.ACTION_SUCK),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("B",
+                        new VELocalState("A", VacuumEnvironment.Status.Clean),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(Arrays.asList(VacuumEnvironment.ACTION_LEFT, VacuumEnvironment.ACTION_SUCK),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("B",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Clean))));
+
+        Assert.assertEquals(
+                Arrays.asList(VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_LEFT,
+                        VacuumEnvironment.ACTION_SUCK),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("B",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty))));
+
+        // A slightly larger world
+        Assert.assertEquals(
+                Arrays.asList(VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_RIGHT,
+                        VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_RIGHT, VacuumEnvironment.ACTION_SUCK),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("A",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("C", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(
+                Arrays.asList(VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_LEFT,
+                        VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_LEFT, VacuumEnvironment.ACTION_SUCK),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("C",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("C", VacuumEnvironment.Status.Dirty))));
+
+        Assert.assertEquals(
+                // NOTE: This sequence of actions is only correct because we
+                // always order our possible actions [Left, Suck, Right] in
+                // ProblemFactory.getSimpleVacuumWorldProblem(), if the order
+                // was different we would likely get a different answer.
+                Arrays.asList(VacuumEnvironment.ACTION_RIGHT, VacuumEnvironment.ACTION_SUCK,
+                        VacuumEnvironment.ACTION_LEFT, VacuumEnvironment.ACTION_SUCK, VacuumEnvironment.ACTION_LEFT,
+                        VacuumEnvironment.ACTION_SUCK),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleVacuumWorldProblem("B",
+                        new VELocalState("A", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("B", VacuumEnvironment.Status.Dirty),
+                        new VELocalState("C", VacuumEnvironment.Status.Dirty))));
+
+    }
+
+    @Test
+    public void simpleBinaryTreeTests(){
+        Assert.assertEquals(Arrays.asList("B"), iterativeDeepeningSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "B")));
+
+        Assert.assertEquals(Arrays.asList("C"), iterativeDeepeningSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "C")));
+
+        Assert.assertEquals(Arrays.asList("B", "E"),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "E")));
+
+        Assert.assertEquals(Arrays.asList("C", "F", "L"),
+                iterativeDeepeningSearch.apply(ProblemFactory.getSimpleBinaryTreeProblem("A", "L")));
+    }
+}
diff --git a/test/src/test/java/aima/test/unit/search/uninformedsearch/UniformCostSearchTest.java b/test/src/test/java/aima/test/unit/search/uninformedsearch/UniformCostSearchTest.java
new file mode 100644
index 0000000000..81e858253e
--- /dev/null
+++ b/test/src/test/java/aima/test/unit/search/uninformedsearch/UniformCostSearchTest.java
@@ -0,0 +1,25 @@
+package aima.test.unit.search.uninformedsearch;
+
+import aima.core.environment.map2d.GoAction;
+import aima.core.environment.map2d.SimplifiedRoadMapOfPartOfRomania;
+import aima.core.environment.support.ProblemFactory;
+import aima.core.search.basic.uninformedsearch.UniformCostSearch;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class UniformCostSearchTest {
+    UniformCostSearch uniformCostSearch = new UniformCostSearch();
+
+    @Test
+    public void testUniformCostSearch() {
+        Assert.assertEquals(
+                Arrays.asList(new GoAction(SimplifiedRoadMapOfPartOfRomania.SIBIU),
+                        new GoAction(SimplifiedRoadMapOfPartOfRomania.RIMNICU_VILCEA),
+                        new GoAction(SimplifiedRoadMapOfPartOfRomania.PITESTI),
+                        new GoAction(SimplifiedRoadMapOfPartOfRomania.BUCHAREST)),
+                uniformCostSearch.apply(ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(
+                        SimplifiedRoadMapOfPartOfRomania.ARAD, SimplifiedRoadMapOfPartOfRomania.BUCHAREST)));
+    }
+}

From 9ce6881596c12bd043af70287613e82acecabd24 Mon Sep 17 00:00:00 2001
From: Samagra 
Date: Thu, 19 Jul 2018 14:21:10 +0530
Subject: [PATCH 13/16] Adds A* search and greedy best first search

---
 .../search/basic/informed/AStarSearch.java    | 173 ++++++++----------
 .../basic/informed/GreedyBestFirstSearch.java | 162 +++++++---------
 .../informed/GreedyBestFirstSearchTest.java   |   4 +-
 3 files changed, 144 insertions(+), 195 deletions(-)

diff --git a/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java b/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java
index 64c0829b66..40d9f67e06 100644
--- a/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java
+++ b/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java
@@ -1,20 +1,15 @@
 package aima.core.search.basic.informed;
 
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.PriorityQueue;
-import java.util.Queue;
-import java.util.Set;
-import java.util.function.ToDoubleFunction;
-
 import aima.core.search.api.Node;
 import aima.core.search.api.NodeFactory;
 import aima.core.search.api.Problem;
-import aima.core.search.api.SearchController;
 import aima.core.search.api.SearchForActionsFunction;
+import aima.core.search.basic.SearchUtils;
 import aima.core.search.basic.support.BasicNodeFactory;
-import aima.core.search.basic.support.BasicSearchController;
+import aima.core.search.basic.uninformedsearch.GenericSearchInterface;
+
+import java.util.*;
+import java.util.function.ToDoubleFunction;
 
 /**
  * 
@@ -34,104 +29,80 @@
  *          else if child.STATE is in frontier with higher COST then
  *             replace that frontier node with child
  * 
- * + * * @author Ciaran O'Reilly */ -public class AStarSearch implements SearchForActionsFunction { - // function A*-SEARCH((problem) returns a solution, or failure - @Override - public List
apply(Problem problem) { - // node <- a node with STATE = problem.INITIAL-STATE, PATH-COST=0 - Node node = newRootNode(problem.initialState(), 0); - // frontier <- a priority queue ordered by PATH-COST + h(NODE), with - // node as the - // only element - Queue> frontier = newPriorityQueueOrderedByPathCostPlusH(node); - // explored <- an empty set - Set explored = newExploredSet(); - // loop do - while (true) { - // if EMPTY?(frontier) then return failure - if (frontier.isEmpty()) { - return failure(); - } - // node <- POP(frontier) // chooses the lowest-cost node in frontier - node = frontier.remove(); - // if problem.GOAL-TEST(node.STATE) then return SOLUTION(node) - if (isGoalState(node, problem)) { - return solution(node); - } - // add node.STATE to explored - explored.add(node.state()); - // for each action in problem.ACTIONS(node.STATE) do - for (A action : problem.actions(node.state())) { - // child <- CHILD-NODE(problem, node, action) - Node child = newChildNode(problem, node, action); - // if child.STATE is not in explored or frontier then - if (!(explored.contains(child.state()) || containsState(frontier, child.state()))) { - // frontier <- INSERT(child, frontier) - frontier.add(child); - } // else if child.STATE is in frontier with higher COST then - else if (removedNodeFromFrontierWithSameStateAndHigherCost(child, frontier)) { - // replace that frontier node with child - frontier.add(child); - } - } - } - } - - // - // Supporting Code - protected ToDoubleFunction> h; - protected NodeFactory nodeFactory = new BasicNodeFactory<>(); - protected SearchController searchController = new BasicSearchController(); - - public AStarSearch(ToDoubleFunction> h) { - this.h = h; - } - - public ToDoubleFunction> getHeuristicFunctionH() { - return h; - } - - public Node newRootNode(S initialState, double pathCost) { - return nodeFactory.newRootNode(initialState, pathCost); - } +public class AStarSearch implements GenericSearchInterface, SearchForActionsFunction { - public Node newChildNode(Problem problem, Node node, A action) { - return nodeFactory.newChildNode(problem, node, action); - } + // The heuristic function + protected ToDoubleFunction> h; + // A helper class to generate new nodes. + protected NodeFactory nodeFactory = new BasicNodeFactory<>(); - public Queue> newPriorityQueueOrderedByPathCostPlusH(Node initialNode) { - Queue> frontier = new PriorityQueue<>( - Comparator.comparingDouble(n -> n.pathCost() + h.applyAsDouble(n))); - frontier.add(initialNode); - return frontier; - } + // frontier ← a priority queue ordered by f(n) = h(n)+g(n), with a node for the initial state + PriorityQueue> frontier = new PriorityQueue<>(new Comparator>() { + @Override + public int compare(Node o1, Node o2) { + return (int) (h.applyAsDouble(o1) - h.applyAsDouble(o2)); + } + }); - public Set newExploredSet() { - return new HashSet<>(); - } - public List failure() { - return searchController.failure(); - } + public AStarSearch(ToDoubleFunction> h) { + this.h = h; + } - public List solution(Node node) { - return searchController.solution(node); - } + @Override + public Node search(Problem problem) { + if (problem.isGoalState(problem.initialState())) { + return nodeFactory.newRootNode(problem.initialState()); + } + frontier.clear(); + frontier.add(nodeFactory.newRootNode(problem.initialState())); + // reached ← a table of {state: the best path that reached state}; initially empty + HashMap> reached = new HashMap<>(); + // solution ← failure + Node solution = null; + // while frontier is not empty and top(frontier) is cheaper than solution do + while (!frontier.isEmpty() && + (solution == null || getCostValue(frontier.peek()) < getCostValue(solution))) { + // parent ← pop(frontier) + Node parent = frontier.poll(); + // for child in successors(parent) do + for (Node child : + SearchUtils.successors(problem, parent)) { + // s ← child.state + S s = child.state(); + // if s is not in reached or child is a cheaper path than reached[s] then + if (!reached.containsKey(s) || + getCostValue(child) < getCostValue(reached.get(s))) { + // reached[s] ← child + reached.put(s, child); + // add child to the frontier + frontier.add(child); + // if child is a goal and is cheaper than solution then + if (problem.isGoalState(s) && + (solution == null || getCostValue(child) < getCostValue(solution))) { + // solution = child + solution = child; + } + } + } + } + // return solution + return solution; + } - public boolean isGoalState(Node node, Problem problem) { - return searchController.isGoalState(node, problem); - } - - public boolean containsState(Queue> frontier, S state) { - // NOTE: Not very efficient (i.e. linear in the size of the frontier) - return frontier.stream().anyMatch(frontierNode -> frontierNode.state().equals(state)); - } + @Override + public List apply(Problem problem) { + Node solution = this.search(problem); + if (solution == null) + return new ArrayList<>(); + else + return SearchUtils.generateActions(solution); + } - public boolean removedNodeFromFrontierWithSameStateAndHigherCost(Node child, Queue> frontier) { - // NOTE: Not very efficient (i.e. linear in the size of the frontier) - return frontier.removeIf(n -> n.state().equals(child.state()) && n.pathCost() > child.pathCost()); - } + private double getCostValue(Node node) { + return node.pathCost() + h.applyAsDouble(node); + } } diff --git a/core/src/main/java/aima/core/search/basic/informed/GreedyBestFirstSearch.java b/core/src/main/java/aima/core/search/basic/informed/GreedyBestFirstSearch.java index 259cf07e17..842987240c 100644 --- a/core/src/main/java/aima/core/search/basic/informed/GreedyBestFirstSearch.java +++ b/core/src/main/java/aima/core/search/basic/informed/GreedyBestFirstSearch.java @@ -3,16 +3,12 @@ import aima.core.search.api.Node; import aima.core.search.api.NodeFactory; import aima.core.search.api.Problem; -import aima.core.search.api.SearchController; import aima.core.search.api.SearchForActionsFunction; +import aima.core.search.basic.SearchUtils; import aima.core.search.basic.support.BasicNodeFactory; -import aima.core.search.basic.support.BasicSearchController; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.PriorityQueue; -import java.util.Queue; -import java.util.Set; +import aima.core.search.basic.uninformedsearch.GenericSearchInterface; + +import java.util.*; import java.util.function.ToDoubleFunction; /** @@ -34,96 +30,76 @@ * replace that frontier node with child *
*/ -public class GreedyBestFirstSearch implements SearchForActionsFunction { - // function GREEDY-BEST-FIRST-SEARCH(problem) returns a solution, or failure - @Override - public List apply(Problem problem) { - // node <- a node with STATE = problem.INITIAL-STATE, PATH-COST=0 - Node node = newRootNode(problem.initialState(), 0); - // frontier <- a priority queue ordered by h(NODE), with node as the only element - Queue> frontier = newPriorityQueueOrderedByH(node); - // explored <- an empty set - Set explored = newExploredSet(); - // loop do - while (true) { - // if EMPTY?(frontier) then return failure - if (frontier.isEmpty()) { - return failure(); - } - // node <- POP(frontier) // chooses the lowest-cost node in frontier - node = frontier.remove(); - // if problem.GOAL-TEST(node.STATE) then return SOLUTION(node) - if (problem.isGoalState(node.state())) { - return solution(node); - } - // add node.STATE to explored - explored.add(node.state()); - // for each action in problem.ACTIONS(node.STATE) do - for (A action : problem.actions(node.state())) { - // child <- CHILD-NODE(problem, node, action) - Node child = newChildNode(problem, node, action); - // if child.STATE is not in explored or frontier then - if (!(explored.contains(child.state())) || containsState(frontier, child.state())) { - // frontier <- INSERT(child, frontier) - frontier.add(child); - } // else if child.STATE is in frontier with higher COST then - else if (removedNodeFromFrontierWithSameStateAndHigherCost(child, frontier)) { - // replace that frontier node with child - frontier.add(child); - } - } - } - } - - /* Supporting Code */ - protected ToDoubleFunction> h; - protected NodeFactory nodeFactory = new BasicNodeFactory<>(); - protected SearchController searchController = new BasicSearchController<>(); - - public GreedyBestFirstSearch(ToDoubleFunction> h) { - this.h = h; - } - - public Node newRootNode(S initialState, double pathCost) { - return nodeFactory.newRootNode(initialState, pathCost); - } - - public Queue> newPriorityQueueOrderedByH(Node initialNode) { - Queue> frontier = new PriorityQueue<>( - Comparator.comparingDouble(value -> h.applyAsDouble(value))); - frontier.add(initialNode); - return frontier; - } +public class GreedyBestFirstSearch implements GenericSearchInterface, SearchForActionsFunction { - public Set newExploredSet() { - return new HashSet<>(); - } + // The heuristic function + protected ToDoubleFunction> h; + // The helper class to generate nodes. + protected NodeFactory nodeFactory = new BasicNodeFactory<>(); + // frontier ← a priority queue ordered by h(n), with a node for the initial state + PriorityQueue> frontier = new PriorityQueue<>(new Comparator>() { + @Override + public int compare(Node o1, Node o2) { + return (int) (h.applyAsDouble(o1) - h.applyAsDouble(o2)); + } + }); - public List failure() { - return searchController.failure(); - } + // Constructor for the class which takes in a heuristic function. + public GreedyBestFirstSearch(ToDoubleFunction> h) { + this.h = h; + } - public boolean isGoalState(Node node, Problem problem) { - return searchController.isGoalState(node, problem); - } + @Override + public Node search(Problem problem) { + if (problem.isGoalState(problem.initialState())) { + return nodeFactory.newRootNode(problem.initialState()); + } - public List solution(Node node) { - return searchController.solution(node); - } + frontier.clear(); + frontier.add(nodeFactory.newRootNode(problem.initialState())); + // reached ← a table of {state: the best path that reached state}; initially empty + HashMap> reached = new HashMap<>(); + // solution ← failure + Node solution = null; + // while frontier is not empty and top(frontier) is cheaper than solution do + while (!frontier.isEmpty() && + (solution == null || h.applyAsDouble(frontier.peek()) < h.applyAsDouble(solution))) { + // parent ← pop(frontier) + Node parent = frontier.poll(); + // for child in successors(parent) do + for (Node child : + SearchUtils.successors(problem, parent)) { + // s ← child.state + S s = child.state(); + // if s is not in reached or child is a cheaper path than reached[s] then + if (!reached.containsKey(s) || + h.applyAsDouble(child) < h.applyAsDouble(reached.get(s))) { + // reached[s] ← child + reached.put(s, child); + // add child to the frontier + frontier.add(child); + // if child is a goal and is cheaper than solution then + if (problem.isGoalState(s) && + (solution == null || h.applyAsDouble(child) < h.applyAsDouble(solution))) { + // solution = child + solution = child; + } + } + } + } + // return solution + return solution; + } - public Node newChildNode(Problem problem, Node node, A action) { - return nodeFactory.newChildNode(problem, node, action); - } - public boolean containsState(Queue> frontier, S state) { - // NOTE: Not very efficient (i.e. linear in the size of the frontier) - return frontier.stream().anyMatch(frontierNode -> frontierNode.state().equals(state)); - } + // function GREEDY-BEST-FIRST-SEARCH(problem) returns a solution, or failure + @Override + public List apply(Problem problem) { + Node solution = this.search(problem); + if (solution == null) + return new ArrayList<>(); + else + return SearchUtils.generateActions(solution); + } - private boolean removedNodeFromFrontierWithSameStateAndHigherCost(Node child, - Queue> frontier) { - // NOTE: Not very efficient (i.e. linear in the size of the frontier) - return frontier.removeIf( - n -> n.state().equals(child.state()) && h.applyAsDouble(n) > h.applyAsDouble(child)); - } } diff --git a/test/src/test/java/aima/test/unit/search/informed/GreedyBestFirstSearchTest.java b/test/src/test/java/aima/test/unit/search/informed/GreedyBestFirstSearchTest.java index 372bdc97e9..9f11b8a3f6 100644 --- a/test/src/test/java/aima/test/unit/search/informed/GreedyBestFirstSearchTest.java +++ b/test/src/test/java/aima/test/unit/search/informed/GreedyBestFirstSearchTest.java @@ -13,6 +13,8 @@ import aima.core.search.api.Problem; import aima.core.search.api.SearchForActionsFunction; import aima.core.search.basic.informed.GreedyBestFirstSearch; + +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -44,7 +46,7 @@ public void testSimplifiedRoadMapOfPartOfRomania() { String finalLocation = initialLocation; Problem problem = ProblemFactory .getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, finalLocation); - List goal = Arrays.asList((GoAction) null); + List goal = new ArrayList<>(); assertEquals(goal, searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, finalLocation))); From 8f9fbfea229a4767ce77a214c6a2d7841b42ce4e Mon Sep 17 00:00:00 2001 From: Samagra Date: Thu, 19 Jul 2018 14:48:12 +0530 Subject: [PATCH 14/16] Adds simulated Annealing and Hill climbing search --- .../basic/local/HillClimbingSearch.java | 51 +++++-------------- .../basic/local/SimulatedAnnealingSearch.java | 49 ++++-------------- 2 files changed, 25 insertions(+), 75 deletions(-) diff --git a/core/src/main/java/aima/core/search/basic/local/HillClimbingSearch.java b/core/src/main/java/aima/core/search/basic/local/HillClimbingSearch.java index accb03371d..58fb68b04f 100644 --- a/core/src/main/java/aima/core/search/basic/local/HillClimbingSearch.java +++ b/core/src/main/java/aima/core/search/basic/local/HillClimbingSearch.java @@ -31,20 +31,22 @@ * @author Ravi Mohan * @author Paul Anton * @author Mike Stampone + * @author samagra */ -public class HillClimbingSearch implements SearchForStateFunction { +public class HillClimbingSearch implements SearchForStateFunction { + // function HILL-CLIMBING(problem) returns a state that is a local maximum @Override public S apply(Problem problem) { // current <- MAKE-NODE(problem.INITIAL-STATE) - Node current = makeNode(problem.initialState()); + S current = problem.initialState(); // loop do while (true) { // neighbor <- a highest-valued successor of current - Node neighbor = highestValuedSuccessor(current, problem); + S neighbor = highestValuedSuccessor(current, problem); // if neighbor.VALUE <= current.VALUE then return current.STATE - if (neighbor.value <= current.value) { - return current.state; + if (stateValueFn.applyAsDouble(neighbor) <= stateValueFn.applyAsDouble(current)) { + return current; } // current <- neighbor current = neighbor; @@ -54,30 +56,6 @@ public S apply(Problem problem) { // // Supporting Code - /** - * The algorithm does not maintain a search tree, so the data structure for - * the current node need only record the state and value of the - * objective/cost function. - * - * @author oreilly - * - * @param - * the type of the state space - */ - public static class Node { - S state; - double value; - - Node(S state, double value) { - this.state = state; - this.value = value; - } - - @Override - public String toString() { - return "N(" + state + ", " + value + ")"; - } - } /* * Represents an objective (higher better) or cost/heuristic (lower better) @@ -99,15 +77,14 @@ public HillClimbingSearch(ToDoubleFunction stateValueFn, boolean isSteepestAs } } - public Node makeNode(S state) { - return new Node<>(state, stateValueFn.applyAsDouble(state)); - } - public Node highestValuedSuccessor(Node current, Problem problem) { - Node highestValueSuccessor = null; - for (A action : problem.actions(current.state)) { - Node successor = makeNode(problem.result(current.state, action)); - if (highestValueSuccessor == null || successor.value > highestValueSuccessor.value) { + + public S highestValuedSuccessor(S current, Problem problem) { + S highestValueSuccessor = null; + for (A action : problem.actions(current)) { + S successor = problem.result(current, action); + if (highestValueSuccessor == null || stateValueFn.applyAsDouble(successor) + > stateValueFn.applyAsDouble(highestValueSuccessor)) { highestValueSuccessor = successor; } } diff --git a/core/src/main/java/aima/core/search/basic/local/SimulatedAnnealingSearch.java b/core/src/main/java/aima/core/search/basic/local/SimulatedAnnealingSearch.java index a38d63aa4f..b2747de31c 100644 --- a/core/src/main/java/aima/core/search/basic/local/SimulatedAnnealingSearch.java +++ b/core/src/main/java/aima/core/search/basic/local/SimulatedAnnealingSearch.java @@ -4,6 +4,7 @@ import java.util.Random; import java.util.function.ToDoubleFunction; +import aima.core.search.api.Node; import aima.core.search.api.Problem; import aima.core.search.api.SearchForStateFunction; import aima.core.search.basic.support.BasicSchedule; @@ -44,19 +45,20 @@ public class SimulatedAnnealingSearch implements SearchForStateFunction problem, ToDoubleFunction schedule) { // current <- MAKE-NODE(problem.INITIAL-STATE) - Node current = makeNode(problem.initialState()); + S current = problem.initialState(); // for t = 1 to INFINITY do for (int t = 1; true; t++) { // T <- schedule(t) double T = schedule(t); // if T = 0 then return current if (T == 0) { - return current.state; + return current; } // next <- a randomly selected successor of current - Node next = randomlySelectSuccessor(current, problem); + S next = randomlySelectSuccessor(current, problem); // ΔE <- next.VALUE - current.VALUE - double DeltaE = next.value - current.value; + double DeltaE = stateValueFn.applyAsDouble(next) - + stateValueFn.applyAsDouble(current); // if ΔE > 0 then current <- next if (DeltaE > 0) { current = next; @@ -68,38 +70,11 @@ else if (Math.exp(DeltaE / T) > random.nextDouble()) { } } - // - // Supporting Code @Override public S apply(Problem problem) { return simulatedAnnealing(problem, schedule); } - /** - * The algorithm does not maintain a search tree, so the data structure for - * the current node need only record the state and value of the - * objective/cost function. - * - * @author oreilly - * - * @param - * the type of the state space - */ - public static class Node { - S state; - double value; - - Node(S state, double value) { - this.state = state; - this.value = value; - } - - @Override - public String toString() { - return "N(" + state + ", " + value + ")"; - } - } - /* * Represents an objective (higher better) or cost/heuristic (lower better) * function. If a cost/heuristic function is passed in the @@ -108,6 +83,7 @@ public String toString() { */ protected ToDoubleFunction stateValueFn; protected ToDoubleFunction schedule; + // Pseudo-random number generator for handling probabilities protected Random random = new Random(); public SimulatedAnnealingSearch(ToDoubleFunction stateValueFn) { @@ -128,9 +104,6 @@ public SimulatedAnnealingSearch(ToDoubleFunction stateValueFn, boolean isGrad this.schedule = scheduler; } - public Node makeNode(S state) { - return new Node<>(state, stateValueFn.applyAsDouble(state)); - } public double schedule(int t) { double T = schedule.applyAsDouble(t); @@ -141,15 +114,15 @@ public double schedule(int t) { return T; } - public Node randomlySelectSuccessor(Node current, Problem problem) { + public S randomlySelectSuccessor(S current, Problem problem) { // Default successor to current, so that in the case we reach a dead-end // state i.e. one without reversible actions we will return something. // This will not break the code above as the loop will exit when the // temperature winds down to 0. - Node successor = current; - List actions = problem.actions(current.state); + S successor = current; + List actions = problem.actions(current); if (actions.size() > 0) { - successor = makeNode(problem.result(current.state, actions.get(random.nextInt(actions.size())))); + successor = problem.result(current, actions.get(random.nextInt(actions.size()))); } return successor; } From 8b0c16dd9aae553a02a5df9b7e3103fa96ae4fb3 Mon Sep 17 00:00:00 2001 From: Samagra Date: Thu, 19 Jul 2018 15:47:37 +0530 Subject: [PATCH 15/16] Improvements as per test cases --- .../java/aima/core/search/basic/informed/AStarSearch.java | 2 +- .../aima/test/unit/search/informed/InformedSearchTest.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java b/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java index 40d9f67e06..3dc8a9cb3e 100644 --- a/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java +++ b/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java @@ -43,7 +43,7 @@ public class AStarSearch implements GenericSearchInterface, SearchFo PriorityQueue> frontier = new PriorityQueue<>(new Comparator>() { @Override public int compare(Node o1, Node o2) { - return (int) (h.applyAsDouble(o1) - h.applyAsDouble(o2)); + return (int) (getCostValue(o1) - getCostValue(o2)); } }); diff --git a/test/src/test/java/aima/test/unit/search/informed/InformedSearchTest.java b/test/src/test/java/aima/test/unit/search/informed/InformedSearchTest.java index b8fba21470..ae57e11e30 100644 --- a/test/src/test/java/aima/test/unit/search/informed/InformedSearchTest.java +++ b/test/src/test/java/aima/test/unit/search/informed/InformedSearchTest.java @@ -17,6 +17,7 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -59,7 +60,10 @@ public void testSimplifiedRoadMapOfPartOfRomania() { Problem problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, goal); - assertEquals(Arrays.asList((String) null), searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, goal))); + if(A_STAR.equals(searchFunctionName)) + assertEquals(new ArrayList<>(), searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, goal))); + else + assertEquals(Arrays.asList((String) null), searchForActions(problem, new Map2DFunctionFactory.StraightLineDistanceHeuristic(map, goal))); goal = SimplifiedRoadMapOfPartOfRomania.BUCHAREST; problem = ProblemFactory.getSimplifiedRoadMapOfPartOfRomaniaProblem(initialLocation, goal); From 2c7803e7290449c828ee6aa0b8447713b7e98366 Mon Sep 17 00:00:00 2001 From: Samagra Date: Thu, 19 Jul 2018 19:09:26 +0530 Subject: [PATCH 16/16] Removes unwanted comments --- .../aima/core/search/basic/SearchUtils.java | 51 +++-- .../search/basic/informed/AStarSearch.java | 56 +++--- .../basic/informed/GreedyBestFirstSearch.java | 50 +++-- .../basic/local/HillClimbingSearch.java | 131 ++++++------ .../basic/local/SimulatedAnnealingSearch.java | 186 +++++++++--------- .../uninformedsearch/BreadthFirstSearch.java | 47 ++--- .../uninformedsearch/DepthLimitedSearch.java | 20 +- .../basic/uninformedsearch/GenericSearch.java | 15 +- .../GenericSearchInterface.java | 6 +- .../IterativeDeepeningSearch.java | 28 +-- .../uninformedsearch/UniformCostSearch.java | 43 ++-- .../informed/GreedyBestFirstSearchTest.java | 87 ++++---- .../search/informed/InformedSearchTest.java | 85 ++++---- .../search/local/HillClimbingSearchTest.java | 159 ++++++++------- .../search/local/SimulatedAnnealingTest.java | 111 ++++++----- .../BreadthFirstSearchTest.java | 12 +- .../IterativeDeepeningSearchTest.java | 10 +- 17 files changed, 537 insertions(+), 560 deletions(-) diff --git a/core/src/main/java/aima/core/search/basic/SearchUtils.java b/core/src/main/java/aima/core/search/basic/SearchUtils.java index ae62265dca..a2d3af6482 100644 --- a/core/src/main/java/aima/core/search/basic/SearchUtils.java +++ b/core/src/main/java/aima/core/search/basic/SearchUtils.java @@ -9,36 +9,63 @@ import java.util.Collections; import java.util.List; +/** + * Some utility functions for the search module + */ public class SearchUtils { - public static List> successors(Problem problem, Node parent ){ + + /** + * Calculates the successors of a given node for a given problem. + * + * @param problem + * @param parent + * @param + * @param + * @return + */ + public static List> successors(Problem problem, Node parent) { S s = parent.state(); - List> nodes = new ArrayList<>(); + List> nodes = new ArrayList<>(); - NodeFactory nodeFactory = new BasicNodeFactory<>(); + NodeFactory nodeFactory = new BasicNodeFactory<>(); for (A action : problem.actions(s)) { - S sPrime = problem.result(s,action); - double cost = parent.pathCost() + problem.stepCost(s,action,sPrime); - Node node = nodeFactory.newChildNode(problem,parent,action); + S sPrime = problem.result(s, action); + double cost = parent.pathCost() + problem.stepCost(s, action, sPrime); + Node node = nodeFactory.newChildNode(problem, parent, action); nodes.add(node); } return nodes; } - public static int depth(Node node){ + /** + * Calculates the depth of a node in a particular tree. + * + * @param node + * @return + */ + public static int depth(Node node) { Node temp = node; int count = 0; - while(temp!=null){ - count ++; + while (temp != null) { + count++; temp = temp.parent(); } return count; } - public static List generateActions(Node solution){ - Node parent = solution; + /** + * Extracts the list of actions from a solution state. + * + * @param solution + * @param + * @param + * @return + */ + public static List generateActions(Node solution) { + Node parent = solution; List actions = new ArrayList<>(); - while(parent.parent() != null){ + while (parent.parent() != null) { actions.add(parent.action()); parent = parent.parent(); } diff --git a/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java b/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java index 3dc8a9cb3e..df52288f8e 100644 --- a/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java +++ b/core/src/main/java/aima/core/search/basic/informed/AStarSearch.java @@ -14,23 +14,24 @@ /** *
  * function A*-SEARCH(problem) returns a solution, or failure
- *   node ← a node with STATE = problem.INITIAL-STATE, PATH-COST=0
- *   frontier ← a priority queue ordered by PATH-COST + h(NODE), with node as the only element
- *   explored ← an empty set
- *   loop do
- *      if EMPTY?(frontier) then return failure
- *      node <- POP(frontier) // chooses the lowest-cost node in frontier
- *      if problem.GOAL-TEST(node.STATE) then return SOLUTION(node)
- *      add node.STATE to explored
- *      for each action in problem.ACTIONS(node.STATE) do
- *          child ← CHILD-NODE(problem, node, action)
- *          if child.STATE is not in explored or frontier then
- *             frontier ← INSERT(child, frontier)
- *          else if child.STATE is in frontier with higher COST then
- *             replace that frontier node with child
+ *  if problem's initial state is a goal then return empty path to initial state
+ *  frontier ← a priority queue ordered by f(n) = h(n) + g(n), with a node for the initial state
+ *  reached ← a table of {state: the best path that reached state}; initially empty
+ *  solution ← failure
+ *  while frontier is not empty and top(frontier) is cheaper than solution do
+ *    parent ← pop(frontier)
+ *    for child in successors(parent) do
+ *      s ← child.state
+ *      if s is not in reached or child is a cheaper path than reached[s] then
+ *        reached[s] ← child
+ *        add child to the frontier
+ *        if child is a goal and is cheaper than solution then
+ *          solution = child
+ *  return solution
  * 
* * @author Ciaran O'Reilly + * @author samagra */ public class AStarSearch implements GenericSearchInterface, SearchForActionsFunction { @@ -48,6 +49,11 @@ public int compare(Node o1, Node o2) { }); + /** + * The constructor that takes in the heuristics function. + * + * @param h + */ public AStarSearch(ToDoubleFunction> h) { this.h = h; } @@ -61,38 +67,36 @@ public Node search(Problem problem) { frontier.add(nodeFactory.newRootNode(problem.initialState())); // reached ← a table of {state: the best path that reached state}; initially empty HashMap> reached = new HashMap<>(); - // solution ← failure Node solution = null; // while frontier is not empty and top(frontier) is cheaper than solution do while (!frontier.isEmpty() && (solution == null || getCostValue(frontier.peek()) < getCostValue(solution))) { - // parent ← pop(frontier) Node parent = frontier.poll(); - // for child in successors(parent) do for (Node child : SearchUtils.successors(problem, parent)) { - // s ← child.state S s = child.state(); // if s is not in reached or child is a cheaper path than reached[s] then if (!reached.containsKey(s) || getCostValue(child) < getCostValue(reached.get(s))) { - // reached[s] ← child reached.put(s, child); - // add child to the frontier frontier.add(child); - // if child is a goal and is cheaper than solution then + // if child is a goal and is cheaper than solution if (problem.isGoalState(s) && (solution == null || getCostValue(child) < getCostValue(solution))) { - // solution = child solution = child; } } } } - // return solution return solution; } + /** + * Returns the list of actions that need to be taken in order to achieve the goal. + * + * @param problem The search problem + * @return the list of actions + */ @Override public List
apply(Problem problem) { Node solution = this.search(problem); @@ -102,6 +106,12 @@ public List apply(Problem problem) { return SearchUtils.generateActions(solution); } + /** + * Finds the value of f(n) = g(n)+h(n) for a node n. + * + * @param node The node n + * @return f(n) + */ private double getCostValue(Node node) { return node.pathCost() + h.applyAsDouble(node); } diff --git a/core/src/main/java/aima/core/search/basic/informed/GreedyBestFirstSearch.java b/core/src/main/java/aima/core/search/basic/informed/GreedyBestFirstSearch.java index 842987240c..3bfe6e1b61 100644 --- a/core/src/main/java/aima/core/search/basic/informed/GreedyBestFirstSearch.java +++ b/core/src/main/java/aima/core/search/basic/informed/GreedyBestFirstSearch.java @@ -14,21 +14,23 @@ /** *
  * function GREEDY-BEST-FIRST-SEARCH(problem) returns a solution, or failure
- *   node ← a node with STATE = problem.INITIAL-STATE, PATH-COST=0
- *   frontier ← a priority queue ordered by h(NODE), with node as the only element
- *   explored ← an empty set
- *   loop do
- *      if EMPTY?(frontier) then return failure
- *      node ← POP(frontier) // chooses the lowest-cost node in frontier
- *      if problem.GOAL-TEST(node.STATE) then return SOLUTION(node)
- *      add node.STATE to explored
- *      for each action in problem.ACTIONS(node.STATE) do
- *         child ← CHILD-NODE(problem, node, action)
- *         if child.STATE is not in explored or frontier then
- *            frontier ← INSERT(child, frontier)
- *         else if child.STATE is in frontier with higher COST then
- *            replace that frontier node with child
+ *  if problem's initial state is a goal then return empty path to initial state
+ *  frontier ← a priority queue ordered by h(n), with a node for the initial state
+ *  reached ← a table of {state: the best path that reached state}; initially empty
+ *  solution ← failure
+ *  while frontier is not empty and top(frontier) is cheaper than solution do
+ *    parent ← pop(frontier)
+ *    for child in successors(parent) do
+ *      s ← child.state
+ *      if s is not in reached or child is a cheaper path than reached[s] then
+ *        reached[s] ← child
+ *        add child to the frontier
+ *        if child is a goal and is cheaper than solution then
+ *          solution = child
+ *  return solution
  * 
+ * + * @author samagra */ public class GreedyBestFirstSearch implements GenericSearchInterface, SearchForActionsFunction { @@ -59,40 +61,36 @@ public Node search(Problem problem) { frontier.add(nodeFactory.newRootNode(problem.initialState())); // reached ← a table of {state: the best path that reached state}; initially empty HashMap> reached = new HashMap<>(); - // solution ← failure Node solution = null; - // while frontier is not empty and top(frontier) is cheaper than solution do while (!frontier.isEmpty() && - (solution == null || h.applyAsDouble(frontier.peek()) < h.applyAsDouble(solution))) { - // parent ← pop(frontier) + (solution == null || h.applyAsDouble(frontier.peek()) + < h.applyAsDouble(solution))) { Node parent = frontier.poll(); - // for child in successors(parent) do for (Node child : SearchUtils.successors(problem, parent)) { - // s ← child.state S s = child.state(); // if s is not in reached or child is a cheaper path than reached[s] then if (!reached.containsKey(s) || h.applyAsDouble(child) < h.applyAsDouble(reached.get(s))) { - // reached[s] ← child reached.put(s, child); - // add child to the frontier frontier.add(child); - // if child is a goal and is cheaper than solution then if (problem.isGoalState(s) && (solution == null || h.applyAsDouble(child) < h.applyAsDouble(solution))) { - // solution = child solution = child; } } } } - // return solution return solution; } - // function GREEDY-BEST-FIRST-SEARCH(problem) returns a solution, or failure + /** + * Returns the list of actions that need to be taken in order to achieve the goal. + * + * @param problem The search problem. + * @return The list of actions. + */ @Override public List
apply(Problem problem) { Node solution = this.search(problem); diff --git a/core/src/main/java/aima/core/search/basic/local/HillClimbingSearch.java b/core/src/main/java/aima/core/search/basic/local/HillClimbingSearch.java index 58fb68b04f..2cd107cf32 100644 --- a/core/src/main/java/aima/core/search/basic/local/HillClimbingSearch.java +++ b/core/src/main/java/aima/core/search/basic/local/HillClimbingSearch.java @@ -1,24 +1,22 @@ package aima.core.search.basic.local; -import java.util.function.ToDoubleFunction; - import aima.core.search.api.Problem; import aima.core.search.api.SearchForStateFunction; +import java.util.function.ToDoubleFunction; + /** * Artificial Intelligence A Modern Approach (4th Edition): Figure ??, page ??. *
*

- * + * *

  * function HILL-CLIMBING(problem) returns a state that is a local maximum
- *
- *   current ← MAKE-NODE(problem.INITIAL-STATE)
- *   loop do
- *     neighbor ← a highest-valued successor of current
- *     if neighbor.VALUE ≤ current.VALUE then return current.STATE
- *     current ← neighbor
- * 
+ *  current ← problem.INITIAL-STATE + *  loop do + *    neighbor ← a highest-valued successor of current + *    if VALUE(neighbour) ≤ VALUE(current) then return current + *    current ← neighbor
*

* Figure ?? The hill-climbing search algorithm, which is the most basic local * search technique. At each step the current node is replaced by the best @@ -33,67 +31,64 @@ * @author Mike Stampone * @author samagra */ -public class HillClimbingSearch implements SearchForStateFunction { - - // function HILL-CLIMBING(problem) returns a state that is a local maximum - @Override - public S apply(Problem problem) { - // current <- MAKE-NODE(problem.INITIAL-STATE) - S current = problem.initialState(); - // loop do - while (true) { - // neighbor <- a highest-valued successor of current - S neighbor = highestValuedSuccessor(current, problem); - // if neighbor.VALUE <= current.VALUE then return current.STATE - if (stateValueFn.applyAsDouble(neighbor) <= stateValueFn.applyAsDouble(current)) { - return current; - } - // current <- neighbor - current = neighbor; - } - } - - // - // Supporting Code - - - /* - * Represents an objective (higher better) or cost/heuristic (lower better) - * function. If a cost/heuristic function is passed in the - * 'isSteepestAscentVersion' should be set to false for the algorithm to - * search for minimums. - */ - protected ToDoubleFunction stateValueFn; +public class HillClimbingSearch implements SearchForStateFunction { - public HillClimbingSearch(ToDoubleFunction stateValueFn) { - this(stateValueFn, true); - } + /* + * Represents an objective (higher better) or cost/heuristic (lower better) + * function. If a cost/heuristic function is passed in the + * 'isSteepestAscentVersion' should be set to false for the algorithm to + * search for minimums. + */ + protected ToDoubleFunction stateValueFn; - public HillClimbingSearch(ToDoubleFunction stateValueFn, boolean isSteepestAscentVersion) { - this.stateValueFn = stateValueFn; - if (!isSteepestAscentVersion) { - // Convert from one to the other by switching the sign - this.stateValueFn = (state) -> stateValueFn.applyAsDouble(state) * -1; - } - } + // Constructors. + public HillClimbingSearch(ToDoubleFunction stateValueFn) { + this(stateValueFn, true); + } + public HillClimbingSearch(ToDoubleFunction stateValueFn, boolean isSteepestAscentVersion) { + this.stateValueFn = stateValueFn; + if (!isSteepestAscentVersion) { + // Convert from one to the other by switching the sign + this.stateValueFn = (state) -> stateValueFn.applyAsDouble(state) * -1; + } + } + // function HILL-CLIMBING(problem) returns a state that is a local maximum + @Override + public S apply(Problem problem) { + S current = problem.initialState(); + while (true) { + S neighbor = highestValuedSuccessor(current, problem); + if (stateValueFn.applyAsDouble(neighbor) <= stateValueFn.applyAsDouble(current)) { + return current; + } + current = neighbor; + } + } - public S highestValuedSuccessor(S current, Problem problem) { - S highestValueSuccessor = null; - for (A action : problem.actions(current)) { - S successor = problem.result(current, action); - if (highestValueSuccessor == null || stateValueFn.applyAsDouble(successor) - > stateValueFn.applyAsDouble(highestValueSuccessor)) { - highestValueSuccessor = successor; - } - } - // If no successor then just be our own neighbor - // as the calling code will just return 'current' as - // the <= test will be true - if (highestValueSuccessor == null) { - highestValueSuccessor = current; - } - return highestValueSuccessor; - } + /** + * Calculates the successor with the highest value. + * + * @param current + * @param problem + * @return + */ + public S highestValuedSuccessor(S current, Problem problem) { + S highestValueSuccessor = null; + for (A action : problem.actions(current)) { + S successor = problem.result(current, action); + if (highestValueSuccessor == null || stateValueFn.applyAsDouble(successor) + > stateValueFn.applyAsDouble(highestValueSuccessor)) { + highestValueSuccessor = successor; + } + } + // If no successor then just be our own neighbor + // as the calling code will just return 'current' as + // the <= test will be true + if (highestValueSuccessor == null) { + highestValueSuccessor = current; + } + return highestValueSuccessor; + } } \ No newline at end of file diff --git a/core/src/main/java/aima/core/search/basic/local/SimulatedAnnealingSearch.java b/core/src/main/java/aima/core/search/basic/local/SimulatedAnnealingSearch.java index b2747de31c..c34cd50117 100644 --- a/core/src/main/java/aima/core/search/basic/local/SimulatedAnnealingSearch.java +++ b/core/src/main/java/aima/core/search/basic/local/SimulatedAnnealingSearch.java @@ -1,129 +1,119 @@ package aima.core.search.basic.local; -import java.util.List; -import java.util.Random; -import java.util.function.ToDoubleFunction; - -import aima.core.search.api.Node; import aima.core.search.api.Problem; import aima.core.search.api.SearchForStateFunction; import aima.core.search.basic.support.BasicSchedule; +import java.util.List; +import java.util.Random; +import java.util.function.ToDoubleFunction; + /** * Artificial Intelligence A Modern Approach (4th Edition): Figure ??, page ??. *
*
- * + * *

- * function SIMULATED-ANNEALING(problem, schedule) returns a solution state
- *   inputs: problem, a problem
- *           schedule, a mapping from time to "temperature"
+ * function SIMULATED-ANNEALING(problem,schedule) returns a solution state
  *
- *   current ← MAKE-NODE(problem.INITIAL-STATE)
- *   for t = 1 to ∞ do
- *     T ← schedule(t)
- *     if T = 0 then return current
- *     next ← a randomly selected successor of current
- *     ΔE ← next.VALUE - current.value
- *     if ΔE > 0 then current ← next
- *     else current ← next only with probability eΔE/T
+ *  current ← problem.INITIAL-STATE
+ *  for t = 1 to ∞ do
+ *    T ← schedule(t)
+ *    if T = 0 then return current
+ *    next ← a randomly selected successor of current
+ *    ΔE ← VALUE(next) - VALUE(current)
+ *    if ΔE > 0 then current ← next
+ *    else current ← next only with probability eΔE/T
  * 
- * + *

* Figure ?? The simulated annealing search algorithm, a version of stochastic * hill climbing where some downhill moves are allowed. Downhill moves are * accepted readily early in the annealing schedule and then less often as time * goes on. The schedule input determines the value of the temperature T as a * function of time. - * + * * @author Ciaran O'Reilly * @author Anurag Rai * @author Ravi Mohan * @author Mike Stampone * @author Ruediger Lunde + * @author samagra */ public class SimulatedAnnealingSearch implements SearchForStateFunction { - // function SIMULATED-ANNEALING(problem, schedule) returns a solution state - public S simulatedAnnealing(Problem problem, ToDoubleFunction schedule) { - // current <- MAKE-NODE(problem.INITIAL-STATE) - S current = problem.initialState(); - // for t = 1 to INFINITY do - for (int t = 1; true; t++) { - // T <- schedule(t) - double T = schedule(t); - // if T = 0 then return current - if (T == 0) { - return current; - } - // next <- a randomly selected successor of current - S next = randomlySelectSuccessor(current, problem); - // ΔE <- next.VALUE - current.VALUE - double DeltaE = stateValueFn.applyAsDouble(next) - - stateValueFn.applyAsDouble(current); - // if ΔE > 0 then current <- next - if (DeltaE > 0) { - current = next; - } - // else current <- next only with probability e^ΔE/T - else if (Math.exp(DeltaE / T) > random.nextDouble()) { - current = next; - } - } - } - - @Override - public S apply(Problem problem) { - return simulatedAnnealing(problem, schedule); - } - - /* - * Represents an objective (higher better) or cost/heuristic (lower better) - * function. If a cost/heuristic function is passed in the - * 'isGradientAscentVersion' should be set to false for the algorithm to - * search for minimums. - */ - protected ToDoubleFunction stateValueFn; - protected ToDoubleFunction schedule; - // Pseudo-random number generator for handling probabilities - protected Random random = new Random(); - - public SimulatedAnnealingSearch(ToDoubleFunction stateValueFn) { - this(stateValueFn, true); - } + /* + * Represents an objective (higher better) or cost/heuristic (lower better) + * function. If a cost/heuristic function is passed in the + * 'isGradientAscentVersion' should be set to false for the algorithm to + * search for minimums. + */ + protected ToDoubleFunction stateValueFn; + // The schedule input determines the value of the “temperature” T as a function of time; + protected ToDoubleFunction schedule; + // Pseudo-random number generator for handling probabilities + protected Random random = new Random(); + // Constructors + public SimulatedAnnealingSearch(ToDoubleFunction stateValueFn) { + this(stateValueFn, true); + } + public SimulatedAnnealingSearch(ToDoubleFunction stateValueFn, boolean isGradientAscentVersion) { + this(stateValueFn, isGradientAscentVersion, new BasicSchedule()); + } - public SimulatedAnnealingSearch(ToDoubleFunction stateValueFn, boolean isGradientAscentVersion) { - this(stateValueFn, isGradientAscentVersion, new BasicSchedule()); - } + public SimulatedAnnealingSearch(ToDoubleFunction stateValueFn, boolean isGradientAscentVersion, + ToDoubleFunction scheduler) { + this.stateValueFn = stateValueFn; + if (!isGradientAscentVersion) { + // Convert from one to the other by switching the sign + this.stateValueFn = (state) -> stateValueFn.applyAsDouble(state) * -1; + } + this.schedule = scheduler; + } - public SimulatedAnnealingSearch(ToDoubleFunction stateValueFn, boolean isGradientAscentVersion, - ToDoubleFunction scheduler) { - this.stateValueFn = stateValueFn; - if (!isGradientAscentVersion) { - // Convert from one to the other by switching the sign - this.stateValueFn = (state) -> stateValueFn.applyAsDouble(state) * -1; - } - this.schedule = scheduler; - } + // function SIMULATED-ANNEALING(problem, schedule) returns a solution state + public S simulatedAnnealing(Problem problem, ToDoubleFunction schedule) { + S current = problem.initialState(); + for (int t = 1; true; t++) { + double T = schedule(t); + if (T == 0) { + return current; + } + S next = randomlySelectSuccessor(current, problem); + double DeltaE = stateValueFn.applyAsDouble(next) - + stateValueFn.applyAsDouble(current); + if (DeltaE > 0) { + current = next; + } + // else current <- next only with probability e^ΔE/T + else if (Math.exp(DeltaE / T) > random.nextDouble()) { + current = next; + } + } + } + @Override + public S apply(Problem problem) { + return simulatedAnnealing(problem, schedule); + } - public double schedule(int t) { - double T = schedule.applyAsDouble(t); - if (T < 0) { - throw new IllegalArgumentException( - "Configured schedule returns negative temperatures: t=" + t + ", T=" + T); - } - return T; - } + public double schedule(int t) { + double T = schedule.applyAsDouble(t); + if (T < 0) { + throw new IllegalArgumentException( + "Configured schedule returns negative temperatures: t=" + t + ", T=" + T); + } + return T; + } - public S randomlySelectSuccessor(S current, Problem problem) { - // Default successor to current, so that in the case we reach a dead-end - // state i.e. one without reversible actions we will return something. - // This will not break the code above as the loop will exit when the - // temperature winds down to 0. - S successor = current; - List actions = problem.actions(current); - if (actions.size() > 0) { - successor = problem.result(current, actions.get(random.nextInt(actions.size()))); - } - return successor; - } + public S randomlySelectSuccessor(S current, Problem problem) { + // Default successor to current, so that in the case we reach a dead-end + // state i.e. one without reversible actions we will return something. + // This will not break the code above as the loop will exit when the + // temperature winds down to 0. + S successor = current; + List actions = problem.actions(current); + if (actions.size() > 0) { + successor = problem.result(current, actions.get(random.nextInt(actions.size()))); + } + return successor; + } } diff --git a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java index d47cec30f8..85f3cd0112 100644 --- a/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java +++ b/core/src/main/java/aima/core/search/basic/uninformedsearch/BreadthFirstSearch.java @@ -27,71 +27,64 @@ *        add child to the end of frontier *  return solution *