From 8c31af15a0d10e5e8757057ac176ccde49a2568a Mon Sep 17 00:00:00 2001
From: rschoene <rene.schoene@tu-dresden.de>
Date: Thu, 10 Jun 2021 19:27:23 +0200
Subject: [PATCH] using and testing BFS algorithm to compute operations

---
 ros3rag.placeB/src/main/jastadd/BFS/BFS.jrag  |   1 -
 .../src/main/jastadd/WorldModelB.jadd         |  72 +++-----
 .../tudresden/inf/st/placeB/SimpleMainB.java  |   3 -
 .../inf/st/placeB/TestComputeOperations.java  | 157 ++++++++++++++++++
 .../de/tudresden/inf/st/placeB/TestUtils.java |  58 +++++++
 5 files changed, 235 insertions(+), 56 deletions(-)
 create mode 100644 ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestComputeOperations.java
 create mode 100644 ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestUtils.java

diff --git a/ros3rag.placeB/src/main/jastadd/BFS/BFS.jrag b/ros3rag.placeB/src/main/jastadd/BFS/BFS.jrag
index e7e1990..13efe23 100644
--- a/ros3rag.placeB/src/main/jastadd/BFS/BFS.jrag
+++ b/ros3rag.placeB/src/main/jastadd/BFS/BFS.jrag
@@ -38,7 +38,6 @@ aspect BFS {
 
   private List<Edge> Vertex.reconstructFrom(Vertex goal, Map<Vertex, Edge> pred) {
     List<Edge> result = new ArrayList<>();
-    System.out.println(pred);
     Vertex current = goal;
     while (!current.equals(this)) {
       Edge e = pred.get(current);
diff --git a/ros3rag.placeB/src/main/jastadd/WorldModelB.jadd b/ros3rag.placeB/src/main/jastadd/WorldModelB.jadd
index e6fc758..14ada75 100644
--- a/ros3rag.placeB/src/main/jastadd/WorldModelB.jadd
+++ b/ros3rag.placeB/src/main/jastadd/WorldModelB.jadd
@@ -62,59 +62,22 @@ aspect Computation {
   //--- computeOperations ---
   syn List<Operation> Difference.computeOperations();
   eq DifferenceObjectAtWrongPlace.computeOperations() {
-    // first, find robots that can reach the locations
-    // there does not necessarily has to be one robot that can reach both
-    Set<Robot> robotsFitForPreviousLocation = new HashSet<>();
-    Set<Robot> robotsFitForNewLocation = new HashSet<>();
-    for (var robot : worldModelB().getRobotList()) {
-      // check if robot can reach both, location (if any) and the object
-      if ((!hasPreviousLocation() || robot.canReach(getPreviousLocation().getName())) &&
-           robot.canReach(getObject().getName())) {
-        robotsFitForPreviousLocation.add(robot);
-      }
-      if (robot.canReach(getNewLocation().getName())) {
-        robotsFitForNewLocation.add(robot);
-      }
-    }
-    if (robotsFitForPreviousLocation.isEmpty()) {
-      StringBuilder sb = new StringBuilder("No robot can reach object ");
-      sb.append(getObject().getName());
-      if (hasPreviousLocation()) {
-        sb.append(" and ").append(getPreviousLocation().getName());
-      }
-      sb.append("!");
-      return Collections.singletonList(new ErrorOperation(sb.toString()));
-    }
-    if (robotsFitForNewLocation.isEmpty()) {
-      return Collections.singletonList(new ErrorOperation("No robot can reach new location " + getNewLocation().getName()));
-    }
-    var result = new ArrayList<Operation>();
-    var robotFitForBothLocations = robotsFitForPreviousLocation.stream()
-        .filter(robotsFitForNewLocation::contains)
-        .findFirst();
-    robotFitForBothLocations.ifPresentOrElse(robot -> {
-      // there is at least one robot that can reach both locations
-      result.add(createPickAndPlace(robot, this.getObject(), this.getNewLocation()));
-    },
-    () -> {
-      // no single robot can reach both locations
-      // find a location, that can be used as exchange point reached by two robots
-      for (var robotForPrevious : robotsFitForPreviousLocation) {
-        for (var robotForNew : robotsFitForNewLocation) {
-          for (var canReachOfRobotForPrevious : robotForPrevious.getCanReachObjectOfInterestWrapper().getCanReachObjectOfInterestList()) {
-            String objectName = canReachOfRobotForPrevious.getObjectName();
-            if (resolveLogicalObjectOfInterest(objectName).isLogicalDropOffLocation() && robotForNew.canReach(objectName)) {
-              // add two operations.
-              // obj -> transitLoc by robotForPrevious
-              // obj -> targetLoc by robotForNew
-              result.add(createPickAndPlace(robotForPrevious, this.getObject(), resolveLogicalObjectOfInterest(objectName).asLogicalDropOffLocation()));
-              result.add(createPickAndPlace(robotForNew, this.getObject(), this.getNewLocation()));
-          }
+    return getPreviousLocation().correspondingVertex().map(previousVertex -> {
+      return getNewLocation().correspondingVertex().map(newVertex -> {
+        List<Edge> shortestPath = previousVertex.BFS(newVertex);
+        if (shortestPath == null || shortestPath.isEmpty()) {
+          return error("No sequence of operations to move " + getObject().getName() + (hasPreviousLocation() ? " from " + getPreviousLocation().getName() : "") + " to " + getNewLocation().getName());
         }
-      }
-    }
-    });
-    return result;
+        List<Operation> result = new ArrayList<>();
+        Vertex transit = previousVertex;
+        for (Edge edge : shortestPath) {
+          Vertex target = edge.getFrom().equals(transit) ? edge.getTo() : edge.getFrom();
+          result.add(createPickAndPlace(edge.getRobot(), getObject(), target.getLocation()));
+          transit = target;
+        }
+        return result;
+      }).orElseGet(() -> error("Could not resolve graph vertex of previous location " + getNewLocation().getName()));
+    }).orElseGet(() -> error("Could not resolve graph vertex of new location " + getPreviousLocation().getName()));
   }
   eq DifferenceNewObject.computeOperations() {
     // FIXME. stub, may be implemented later
@@ -125,6 +88,11 @@ aspect Computation {
     return Collections.emptyList();
   }
 
+  //--- error ---
+  protected static List<Operation> Difference.error(String message) {
+    return Collections.singletonList(new ErrorOperation(message));
+  }
+
   //--- createPickAndPlace ---
   private static Operation DifferenceObjectAtWrongPlace.createPickAndPlace(Robot robot, LogicalMovableObject obj, LogicalDropOffLocation target) {
     var result = new PickAndPlace();
diff --git a/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/SimpleMainB.java b/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/SimpleMainB.java
index d0449b3..aef3930 100644
--- a/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/SimpleMainB.java
+++ b/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/SimpleMainB.java
@@ -1,6 +1,5 @@
 package de.tudresden.inf.st.placeB;
 
-import com.google.protobuf.util.JsonFormat;
 import de.tudresden.inf.st.ceti.Reachability;
 import de.tudresden.inf.st.placeB.ast.*;
 import de.tudresden.inf.st.ros3rag.common.Configuration;
@@ -9,8 +8,6 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
diff --git a/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestComputeOperations.java b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestComputeOperations.java
new file mode 100644
index 0000000..da21587
--- /dev/null
+++ b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestComputeOperations.java
@@ -0,0 +1,157 @@
+package de.tudresden.inf.st.placeB;
+
+import de.tudresden.inf.st.placeB.ast.*;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Testing computing operations from differences.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TestComputeOperations {
+  @Test
+  public void testNoMovePossible() {
+    WorldModelB model = TestUtils.newModel();
+    TestUtils.addObjects(model, "x");
+    TestUtils.addLocations(model, "red", "blue", "green");
+    TestUtils.addRobots(model, "arm1", "arm2");
+    TestUtils.addReachability(model, "arm1", "red", "blue");
+    TestUtils.addReachability(model, "arm2", "blue");
+
+    LogicalScene logicalScene = model.getMyScene().getLogicalScene();
+    LogicalMovableObject x = logicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject();
+    LogicalDropOffLocation red = logicalScene.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation();
+    LogicalDropOffLocation green = logicalScene.resolveLogicalObjectOfInterest("green").asLogicalDropOffLocation();
+    DifferenceObjectAtWrongPlace diff = new DifferenceObjectAtWrongPlace(x, red, green);
+
+    List<Operation> operations = diff.computeOperations();
+    assertThat(operations).isNotEmpty();
+    assertThat(operations).hasSize(1);
+    Operation op = operations.get(0);
+    assertTrue(op.isErrorOperation());
+    assertEquals("+Error: No sequence of operations to move x from red to green+", op.prettyPrint());
+  }
+
+  @Test
+  public void testDirectMove() {
+    WorldModelB model = TestUtils.newModel();
+    TestUtils.addObjects(model, "x");
+    TestUtils.addLocations(model, "red", "blue", "green");
+    TestUtils.addRobots(model, "arm1", "arm2");
+    TestUtils.addReachability(model, "arm1", "red", "blue");
+    TestUtils.addReachability(model, "arm2", "blue");
+
+    LogicalScene logicalScene = model.getMyScene().getLogicalScene();
+    LogicalMovableObject x = logicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject();
+    LogicalDropOffLocation red = logicalScene.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation();
+    LogicalDropOffLocation blue = logicalScene.resolveLogicalObjectOfInterest("blue").asLogicalDropOffLocation();
+    Robot arm1 = model.findRobot("arm1").orElseThrow();
+    DifferenceObjectAtWrongPlace diff = new DifferenceObjectAtWrongPlace(x, red, blue);
+
+    List<Operation> operations = diff.computeOperations();
+    assertThat(operations).isNotEmpty();
+    assertThat(operations).hasSize(1);
+    Operation op = operations.get(0);
+    assertThat(op).isInstanceOf(PickAndPlace.class);
+    PickAndPlace pickAndPlace = (PickAndPlace) op;
+    assertEquals(arm1, pickAndPlace.getRobotToExecute());
+    assertEquals(x, pickAndPlace.getObjectToPick());
+    assertEquals(blue, pickAndPlace.getTargetLocation());
+  }
+
+  @Test
+  public void testIndirectMoveWith2Robots() {
+    WorldModelB model = TestUtils.newModel();
+    TestUtils.addObjects(model, "x");
+    TestUtils.addLocations(model, "red", "blue", "green");
+    TestUtils.addRobots(model, "arm1", "arm2");
+    TestUtils.addReachability(model, "arm1", "red", "blue");
+    TestUtils.addReachability(model, "arm2", "blue", "green");
+
+    LogicalScene logicalScene = model.getMyScene().getLogicalScene();
+    LogicalMovableObject x = logicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject();
+    LogicalDropOffLocation red = logicalScene.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation();
+    LogicalDropOffLocation blue = logicalScene.resolveLogicalObjectOfInterest("blue").asLogicalDropOffLocation();
+    LogicalDropOffLocation green = logicalScene.resolveLogicalObjectOfInterest("green").asLogicalDropOffLocation();
+    Robot arm1 = model.findRobot("arm1").orElseThrow();
+    Robot arm2 = model.findRobot("arm2").orElseThrow();
+    DifferenceObjectAtWrongPlace diff = new DifferenceObjectAtWrongPlace(x, red, green);
+
+    List<Operation> operations = diff.computeOperations();
+    assertThat(operations).isNotEmpty();
+    assertThat(operations).hasSize(2);
+    Operation op1 = operations.get(0);
+    assertThat(op1).isInstanceOf(PickAndPlace.class);
+    PickAndPlace pickAndPlace1 = (PickAndPlace) op1;
+    assertEquals(arm1, pickAndPlace1.getRobotToExecute());
+    assertEquals(x, pickAndPlace1.getObjectToPick());
+    assertEquals(blue, pickAndPlace1.getTargetLocation());
+
+    Operation op2 = operations.get(1);
+    assertThat(op2).isInstanceOf(PickAndPlace.class);
+    PickAndPlace pickAndPlace2 = (PickAndPlace) op2;
+    assertEquals(arm2, pickAndPlace2.getRobotToExecute());
+    assertEquals(x, pickAndPlace2.getObjectToPick());
+    assertEquals(green, pickAndPlace2.getTargetLocation());
+  }
+
+  @Test
+  public void testIndirectMoveWith3Robots() {
+    /*
+     * red -(arm1)-> blue --(arm3)-,
+     *    `-(arm2)-> green <-------'
+     *                 `--(arm4)-> yellow -(arm5)-> purple
+     */
+    WorldModelB model = TestUtils.newModel();
+    TestUtils.addObjects(model, "x");
+    TestUtils.addLocations(model, "red", "blue", "green", "yellow", "purple");
+    TestUtils.addRobots(model, "arm1", "arm2", "arm3", "arm4", "arm5");
+    TestUtils.addReachability(model, "arm1", "red", "blue");
+    TestUtils.addReachability(model, "arm2", "red", "green");
+    TestUtils.addReachability(model, "arm3", "blue", "green");
+    TestUtils.addReachability(model, "arm4", "green", "yellow");
+    TestUtils.addReachability(model, "arm5", "yellow", "purple");
+
+    LogicalScene logicalScene = model.getMyScene().getLogicalScene();
+    LogicalMovableObject x = logicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject();
+    LogicalDropOffLocation red = logicalScene.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation();
+    LogicalDropOffLocation green = logicalScene.resolveLogicalObjectOfInterest("green").asLogicalDropOffLocation();
+    LogicalDropOffLocation yellow = logicalScene.resolveLogicalObjectOfInterest("yellow").asLogicalDropOffLocation();
+    LogicalDropOffLocation purple = logicalScene.resolveLogicalObjectOfInterest("purple").asLogicalDropOffLocation();
+    Robot arm2 = model.findRobot("arm2").orElseThrow();
+    Robot arm4 = model.findRobot("arm4").orElseThrow();
+    Robot arm5 = model.findRobot("arm5").orElseThrow();
+    DifferenceObjectAtWrongPlace diff = new DifferenceObjectAtWrongPlace(x, red, purple);
+
+    List<Operation> operations = diff.computeOperations();
+    assertThat(operations).isNotEmpty();
+    assertThat(operations).hasSize(3);
+
+    Operation op1 = operations.get(0);
+    assertThat(op1).isInstanceOf(PickAndPlace.class);
+    PickAndPlace pickAndPlace1 = (PickAndPlace) op1;
+    assertEquals(arm2, pickAndPlace1.getRobotToExecute());
+    assertEquals(x, pickAndPlace1.getObjectToPick());
+    assertEquals(green, pickAndPlace1.getTargetLocation());
+
+    Operation op2 = operations.get(1);
+    assertThat(op2).isInstanceOf(PickAndPlace.class);
+    PickAndPlace pickAndPlace2 = (PickAndPlace) op2;
+    assertEquals(arm4, pickAndPlace2.getRobotToExecute());
+    assertEquals(x, pickAndPlace2.getObjectToPick());
+    assertEquals(yellow, pickAndPlace2.getTargetLocation());
+
+    Operation op3 = operations.get(2);
+    assertThat(op3).isInstanceOf(PickAndPlace.class);
+    PickAndPlace pickAndPlace3 = (PickAndPlace) op3;
+    assertEquals(arm5, pickAndPlace3.getRobotToExecute());
+    assertEquals(x, pickAndPlace3.getObjectToPick());
+    assertEquals(purple, pickAndPlace3.getTargetLocation());
+  }
+}
diff --git a/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestUtils.java b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestUtils.java
new file mode 100644
index 0000000..befdb39
--- /dev/null
+++ b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestUtils.java
@@ -0,0 +1,58 @@
+package de.tudresden.inf.st.placeB;
+
+import de.tudresden.inf.st.placeB.ast.*;
+
+/**
+ * Utility methods for testing.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TestUtils {
+  public static WorldModelB newModel() {
+    WorldModelB model = new WorldModelB();
+    Scene scene = new Scene();
+    model.setMyScene(scene);
+    return model;
+  }
+
+  public static void addObjects(WorldModelB model, String... names) {
+    for (String name : names) {
+      int index = model.getMyScene().getNumMovableObject();
+      MovableObject obj = new MovableObject()
+          .setName(name)
+          .setPosition(new Position(-index, -index, -index))
+          .setOrientation(new Orientation())
+          .setSize(new Size(1, 1, 1));
+      model.getMyScene().addMovableObject(obj);
+    }
+  }
+
+  public static void addLocations(WorldModelB model, String... names) {
+    for (String name : names) {
+      int index = model.getMyScene().getNumDropOffLocation();
+      DropOffLocation obj = new DropOffLocation()
+          .setName(name)
+          .setPosition(new Position(+index, +index, +index))
+          .setOrientation(new Orientation())
+          .setSize(new Size(1, 1, 1));
+      model.getMyScene().addDropOffLocation(obj);
+    }
+  }
+
+  public static void addRobots(WorldModelB model, String... names) {
+    for (String name : names) {
+      Robot obj = new Robot().setName(name);
+      model.addRobot(obj);
+    }
+  }
+
+  public static void addReachability(WorldModelB model, String robotName, String... reachableObjects) {
+    Robot robot = model.findRobot(robotName).orElseThrow();
+    CanReachObjectOfInterestWrapper wrapper = new CanReachObjectOfInterestWrapper();
+    for (String reachableObjectName : reachableObjects) {
+      wrapper.addCanReachObjectOfInterest(new CanReachObjectOfInterest().setObjectName(reachableObjectName));
+    }
+    robot.setCanReachObjectOfInterestWrapper(wrapper);
+  }
+
+}
-- 
GitLab