Skip to content

Commit

Permalink
v0.02.0 (#92)
Browse files Browse the repository at this point in the history
* add PCS tie breaking for smaller MDDs
* Canonize testing benchmark and comparative diverse test functions
* add Config static class
* add debug and info to command line args
* remove I_SolutionCostFunction.addCommonCostsToReport from CBS
* Add support for new instance format with arbitrary graphs
* performance: instant location lookup in GraphBasedGridMap
* return GenericRunManager to default solvers (prioritized planning and CBS)
* change PriorityConstrainedSearch to use PCSCompTieBreakSmallerMDDs by default
* Fix comparative test averages reporting
* add RunParametersBuilder copy function
* consolidate more reporting and metrics in A_Solver
* workaround for LaCAMStar_Solver nodes counting
* fix typos in InstanceReport.StandardFields 
* switch CBS_Solver to using the builder pattern
  • Loading branch information
J-morag authored Oct 25, 2024
1 parent f9dfde6 commit aa3bc80
Show file tree
Hide file tree
Showing 37 changed files with 1,152 additions and 2,517 deletions.
22 changes: 16 additions & 6 deletions src/main/java/BasicMAPF/DataTypesAndStructures/MDDs/MDD.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@
import BasicMAPF.Solvers.ConstraintsAndConflicts.Constraint.I_ConstraintSet;
import BasicMAPF.Solvers.ConstraintsAndConflicts.SwappingConflict;
import BasicMAPF.Solvers.ConstraintsAndConflicts.VertexConflict;
import Environment.Config;
import org.apache.commons.lang3.NotImplementedException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

public class MDD {
private static final int DEBUG = 1;
private MDDNode start;
private MDDNode goal;
private int numNodes;
/**
* Allows random access to MDD levels. The first index is the depth.
* The second index is sorted on the natural ordering of the nodes.
Expand All @@ -35,6 +36,8 @@ public MDD(@NotNull MDDSearchNode goal){
public MDD(@NotNull MDDNode start, @NotNull MDDNode goal){
this.start = start;
this.goal = goal;
initializeUpToLevel(getDepth());
this.numNodes = levels.stream().mapToInt(List::size).sum();
verifyIntegrity(null, null);
}

Expand Down Expand Up @@ -92,6 +95,7 @@ public MDD(@NotNull MDD other, @Nullable I_ConstraintSet constraints){
else {
this.start = newMDDStartNode;
this.goal = goalCopy;
this.numNodes = collectedNodes.size();
}

verifyIntegrity(constraints, null);
Expand All @@ -105,7 +109,7 @@ public MDD deepCopyWithConstraints(@NotNull I_ConstraintSet constraints){
}

/**
* Copy (deep) with constraint.
* Copy with constraint.
*/
public MDD shallowCopyWithConstraint(@NotNull Constraint constraint, boolean isPositiveConstraint) {
if (!isPositiveConstraint){
Expand All @@ -128,7 +132,7 @@ public MDD shallowCopyWithConstraint(@NotNull Constraint constraint, boolean isP
int constraintStartDepth = constraint.prevLocation != null ? constraint.time-1 : constraint.time; // assumes depth := time
MDDNode constraintStartNodeCopy = constraint.prevLocation != null ?
new MDDNode(constraint.prevLocation, constraintStartDepth, constraint.agent) : constraintEndNodeShallowCopy;
if (DEBUG >= 2 && Collections.binarySearch(getLevel(constraintStartDepth), constraintStartNodeCopy) < 0){
if (Config.DEBUG >= 2 && Collections.binarySearch(getLevel(constraintStartDepth), constraintStartNodeCopy) < 0){
throw new IllegalStateException("constraintStartNodeCopy" + constraintStartNodeCopy + " not found in level " + constraintStartDepth + " of " + this);
}

Expand Down Expand Up @@ -209,11 +213,13 @@ private void initialize(MDDSearchNode goal){
}
currentLevel.addAll(previousLevel.values());
}
this.start = currentLevel.poll();

this.start = currentLevel.poll();
if (this.start.getDepth() != 0){
throw new IllegalStateException("MDD start node has depth " + this.start.getDepth() + " instead of 0");
}

this.numNodes = mddNodesToSearchNodes.size();
}

public MDDNode getStart() {
Expand Down Expand Up @@ -418,7 +424,7 @@ else if (compare < 0){
}

public void verifyIntegrity(@Nullable I_ConstraintSet negativeConstraints, @Nullable Constraint positiveConstraint) {
if (DEBUG >= 2 && getDepth() != -1){
if (Config.DEBUG >= 2 && getDepth() != -1){
for (int i = 0; i < getDepth(); i++) {
List<MDDNode> level = getLevel(i);
for (MDDNode node : level) {
Expand All @@ -434,7 +440,7 @@ public void verifyIntegrity(@Nullable I_ConstraintSet negativeConstraints, @Null
if (! plan.getLastMove().currLocation.equals(this.getGoal().getLocation())){
throw new IllegalStateException("MDD goal is " + this.getGoal().getLocation() + " but plan for agent " + getAgent() + " ends at " + plan.getLastMove().currLocation + ": " + this);
}
if (DEBUG >= 3){
if (Config.DEBUG >= 3){
// all neighbors are in the next level
for (MDDNode neighbor : node.getNeighbors()) {
if (Collections.binarySearch(getLevel(i+1), neighbor) < 0){
Expand Down Expand Up @@ -518,4 +524,8 @@ public boolean acceptedBy(I_ConstraintSet constraints) {
}
return ! constraintsRejectStayingAtTargetForever(constraints, goal);
}

public int numNodes() {
return numNodes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ public class RunParametersBuilder {
public Random randomNumberGenerator;
public Agent[] priorityOrder;

public RunParametersBuilder copy(RunParameters rp) {
this.timeout = rp.timeout;
this.softTimeout = rp.softTimeout;
this.constraints = rp.constraints;
this.instanceReport = rp.instanceReport;
this.existingSolution = rp.existingSolution;
this.singleAgentGAndH = rp.singleAgentGAndH;
this.problemStartTime = rp.problemStartTime;
this.randomNumberGenerator = rp.randomNumberGenerator;
this.priorityOrder = rp.priorityOrder;
return this;
}

/**
* @see RunParameters#timeout
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package BasicMAPF.Instances.InstanceBuilders;

import BasicMAPF.Instances.Agent;
import BasicMAPF.Instances.InstanceManager;
import BasicMAPF.Instances.InstanceProperties;
import BasicMAPF.Instances.MAPF_Instance;
import BasicMAPF.Instances.Maps.Coordinates.CoordinateNamed;
import BasicMAPF.Instances.Maps.*;
import Environment.Config;
import Environment.IO_Package.Enum_IO;
import Environment.IO_Package.IO_Manager;
import Environment.IO_Package.Reader;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.regex.Pattern;


public class InstanceBuilder_ArbitraryGraph implements I_InstanceBuilder{

/* =constants= */

public static final String FILE_TYPE_MAP = ".edgelist";
public static final String FILE_TYPE_SCENARIO = ".scene";

/* =Default Values= */
private static final int defaultNumOfAgents = 10;

/* = Fields = */

private final ArrayList<MAPF_Instance> instanceList = new ArrayList<>();

/* = Instance Fields = */

@Override
public void prepareInstances(String mapName, InstanceManager.InstancePath instancePath, InstanceProperties instanceProperties) {
if (!(instancePath instanceof InstanceManager.Moving_AI_Path)) { return; }

InstanceManager.Moving_AI_Path moving_ai_path = (InstanceManager.Moving_AI_Path) instancePath;
if( instanceProperties == null ){ instanceProperties = new InstanceProperties(); }

GraphMap graphMap = getMap(moving_ai_path, instanceProperties);
if( graphMap == null ){ return; }

// create agent properties
int[] numOfAgentsFromProperties = (instanceProperties.numOfAgents == null || instanceProperties.numOfAgents.length == 0
? new int[]{this.defaultNumOfAgents} : instanceProperties.numOfAgents);

populateAgents(mapName, instanceProperties, moving_ai_path, graphMap, numOfAgentsFromProperties);
}

public GraphMap getMap(InstanceManager.InstancePath instancePath, InstanceProperties instanceProperties){
Map<CoordinateNamed, List<CoordinateNamed>> coordinatesAdjacencyLists = new HashMap<>();
Map<CoordinateNamed, List<Integer>> coordinatesEdgeWeights = new HashMap<>();
Map<CoordinateNamed, Enum_MapLocationType> coordinatesLocationType = new HashMap<>();
Map<CoordinateNamed, List<String>> coordinatesLocationSubtypes = new HashMap<>();
Set<CoordinateNamed> canonicalCoordinates = new HashSet<>();

// read the file line by line
Reader reader = new Reader();
Enum_IO enum_io = reader.openFile(instancePath.path);
if( !enum_io.equals(Enum_IO.OPENED) ){
reader.closeFile();
return null; /* couldn't open the file */
}
String nextLine = reader.getNextLine();

boolean isUndirected = nextLine.contains("undirected");
nextLine = reader.getNextLine();

while ( nextLine != null ){
// parse the line
String[] splitLine = nextLine.split(" ");
if (splitLine.length == 2){
// coordinate and its neighbor
CoordinateNamed coordinate1 = new CoordinateNamed(splitLine[0]);
CoordinateNamed coordinate2 = new CoordinateNamed(splitLine[1]);
canonicalCoordinates.add(coordinate1);
canonicalCoordinates.add(coordinate2);
coordinatesLocationType.put(coordinate1, Enum_MapLocationType.EMPTY);
coordinatesLocationType.put(coordinate2, Enum_MapLocationType.EMPTY);
coordinatesLocationSubtypes.put(coordinate1, Collections.emptyList());
coordinatesLocationSubtypes.put(coordinate2, Collections.emptyList());
// add the edge
List<CoordinateNamed> neighbors = coordinatesAdjacencyLists.computeIfAbsent(coordinate1, coor -> new ArrayList<>());
if (Config.DEBUG >= 1 && neighbors.contains(coordinate2)){
throw new IllegalStateException("Duplicate edge: " + coordinate1 + " -> " + coordinate2);
}
neighbors.add(coordinate2);
List<Integer> edgeWeights = coordinatesEdgeWeights.computeIfAbsent(coordinate1, coor -> new ArrayList<>());
if (Config.DEBUG >= 1 && edgeWeights.size() != neighbors.size()-1){
throw new IllegalStateException("Edge weights out of sync with neighbors: " + coordinate1 + " -> " + coordinate2);
}
edgeWeights.add(1);
if (isUndirected){
neighbors = coordinatesAdjacencyLists.computeIfAbsent(coordinate2, coor -> new ArrayList<>());
if (Config.DEBUG >= 1 && neighbors.contains(coordinate1)){
throw new IllegalStateException("Duplicate edge: " + coordinate2 + " -> " + coordinate1);
}
neighbors.add(coordinate1);
edgeWeights = coordinatesEdgeWeights.computeIfAbsent(coordinate2, coor -> new ArrayList<>());
if (Config.DEBUG >= 1 && edgeWeights.size() != neighbors.size()-1){
throw new IllegalStateException("Edge weights out of sync with neighbors: " + coordinate2 + " -> " + coordinate1);
}
edgeWeights.add(1);
}
}
nextLine = reader.getNextLine();
}

return MapFactory.newArbitraryGraphMap(coordinatesAdjacencyLists, coordinatesEdgeWeights,
coordinatesLocationType, true, coordinatesLocationSubtypes);
}

private void populateAgents(@NotNull String instanceName, InstanceProperties instanceProperties,
InstanceManager.Moving_AI_Path moving_ai_path, GraphMap graphMap, int[] numOfAgentsFromProperties) {
MAPF_Instance mapf_instance;

HashMap<String, CoordinateNamed> canonicalCoordinates = new HashMap<>();
for (I_Location location : graphMap.getAllLocations()) {
canonicalCoordinates.put(((CoordinateNamed) location.getCoordinate()).name, (CoordinateNamed) location.getCoordinate());
}

List<String> agentLines = getAgentLines(moving_ai_path, Arrays.stream(numOfAgentsFromProperties).max().getAsInt());
Agent[] allAgents = getAgents(agentLines, Arrays.stream(numOfAgentsFromProperties).max().getAsInt(), canonicalCoordinates, graphMap);
if (allAgents == null) {
return;
}

for (int numOfAgentsFromProperty : numOfAgentsFromProperties) {
if (numOfAgentsFromProperty > allAgents.length) {
numOfAgentsFromProperty = allAgents.length;
}
Agent[] agents = Arrays.copyOfRange(allAgents, 0, numOfAgentsFromProperty);
mapf_instance = makeInstance(instanceName, graphMap, agents, moving_ai_path);
if (instanceProperties.regexPattern.matcher(mapf_instance.extendedName).matches()){
mapf_instance.setObstaclePercentage(instanceProperties.obstacles.getReportPercentage());
this.instanceList.add(mapf_instance);
}
}
}

private Agent[] getAgents(List<String> agentLines, int numOfAgentsFromProperties, HashMap<String, CoordinateNamed> canonicalCoordinates, GraphMap graphMap) {
if( agentLines == null){ return null; }
agentLines.removeIf(Objects::isNull);
Agent[] arrayOfAgents = new Agent[Math.min(numOfAgentsFromProperties, agentLines.size())];

if(agentLines.isEmpty()){ return null; }

// Iterate over all the agents in numOfAgents
for (int id = 0; id < arrayOfAgents.length; id++) {
String[] splitLine = agentLines.get(id).split(" ");
if (Config.DEBUG >= 1){
if (splitLine.length != 3) throw new IllegalStateException("Invalid agent line: " + agentLines.get(id));
if (Integer.parseInt(splitLine[0]) != id) throw new IllegalStateException("Invalid agent id: " + agentLines.get(id));
}
if (Config.DEBUG >= 1 && !canonicalCoordinates.containsKey(splitLine[1])){
throw new IllegalStateException("Unknown coordinate: " + splitLine[1]);
}
if (Config.DEBUG >= 1 && !canonicalCoordinates.containsKey(splitLine[2])){
throw new IllegalStateException("Unknown coordinate: " + splitLine[2]);
}
Agent agent = new Agent(id, canonicalCoordinates.get(splitLine[1]), canonicalCoordinates.get(splitLine[2]));
arrayOfAgents[id] = agent;
}
return arrayOfAgents;
}

private List<String> getAgentLines(InstanceManager.Moving_AI_Path movingAiPath, int numOfNeededAgents) {
// Open scenario file
Reader reader = new Reader();
Enum_IO enum_io = reader.openFile(movingAiPath.scenarioPath);
if( !enum_io.equals(Enum_IO.OPENED) ){
reader.closeFile();
return null; /* couldn't open the file */
}

ArrayList<String> agentsLines = new ArrayList<>(); // Init queue of agents lines

String nextLine = reader.getNextLine();
// Add lines as the num of needed agents
for (int i = 0; nextLine != null && !nextLine.isEmpty() && i < numOfNeededAgents ; i++) {
agentsLines.add(nextLine);
nextLine = reader.getNextLine(); // next line
}

reader.closeFile();
return agentsLines;
}

protected MAPF_Instance makeInstance(String instanceName, I_Map graphMap, Agent[] agents, InstanceManager.Moving_AI_Path instancePath){
String[] splitScenarioPath = instancePath.scenarioPath.split(Pattern.quote(IO_Manager.pathSeparator));
return new MAPF_Instance(instanceName, graphMap, agents, splitScenarioPath[splitScenarioPath.length-1]);
}

@Override
public MAPF_Instance getNextExistingInstance(){
if( ! this.instanceList.isEmpty() ){
return this.instanceList.remove(0);
}
return null;
}

@Override
public MapDimensions.Enum_mapOrientation getMapOrientation() {
return null;
}

@Override
public InstanceManager.InstancePath[] getInstancesPaths(String directoryPath) {
InstanceManager.InstancePath[] pathArray = IO_Manager.getFilesFromDirectory(directoryPath);
if(pathArray == null){ return null; }

ArrayList<InstanceManager.InstancePath> list = new ArrayList<>();

for (InstanceManager.InstancePath instancePath : pathArray ) {
if ( instancePath.path.endsWith(FILE_TYPE_MAP) ){
String[] splitPath = instancePath.path.split(Pattern.quote(IO_Manager.pathSeparator));
String mapPrefix = splitPath[splitPath.length-1].replace(FILE_TYPE_MAP, "");
for (InstanceManager.InstancePath scenarioCandidate : pathArray ){
if(isRelevantScenarioFile(scenarioCandidate, mapPrefix)){
list.add( new InstanceManager.Moving_AI_Path(instancePath.path, scenarioCandidate.path));
}
}
}
}

pathArray = new InstanceManager.InstancePath[list.size()];
for (int i = 0; i < pathArray.length; i++) {
pathArray[i] = list.get(i);
}
return pathArray;
}

private static boolean isRelevantScenarioFile(InstanceManager.InstancePath scenarioCandidate, String mapPrefix) {
String[] splitPath = scenarioCandidate.path.split(Pattern.quote(IO_Manager.pathSeparator));
return splitPath[splitPath.length-1].replace(FILE_TYPE_SCENARIO, "").equals(mapPrefix) && scenarioCandidate.path.endsWith(FILE_TYPE_SCENARIO);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package BasicMAPF.Instances.Maps.Coordinates;

import org.jetbrains.annotations.NotNull;

public class CoordinateNamed implements I_Coordinate {
public final String name;

public CoordinateNamed(@NotNull String name) {
this.name = name;
}

@Override
public float distance(I_Coordinate other) {
return 0;
}

@Override
public int compareTo(@NotNull I_Coordinate o) {
return 0;
}

@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CoordinateNamed that)) return false;

return name.equals(that.name);
}

@Override
public int hashCode() {
return name.hashCode();
}

@Override
public String toString() {
return name;
}
}
Loading

0 comments on commit aa3bc80

Please sign in to comment.