-
Notifications
You must be signed in to change notification settings - Fork 453
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
660 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package org.matsim.core.replanning.conflicts; | ||
|
||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Random; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.matsim.api.core.v01.Id; | ||
import org.matsim.api.core.v01.IdSet; | ||
import org.matsim.api.core.v01.population.Person; | ||
import org.matsim.api.core.v01.population.Plan; | ||
import org.matsim.api.core.v01.population.Population; | ||
import org.matsim.core.replanning.ReplanningUtils; | ||
|
||
import com.google.common.base.Preconditions; | ||
|
||
/** | ||
* This class handles conflicts during replanning. ConflictResolvers are used to | ||
* identify agents whose plans are conflicting with others and need to be | ||
* "rejected" in order to resolve these conflicts. "Rejecting" means to switch | ||
* those agents back to a plan in their memory that does not cause any | ||
* conflicts. Those are plans that are not "potentially conflicting", i.e., | ||
* could interfere in any way with another agent. Those are usually plans that | ||
* don't contain a certain restricted/limited/capacitated mode or resource. The | ||
* logic of conflicts is defined using the ConflictResolver interface. | ||
*/ | ||
public class ConflictManager { | ||
private final static Logger logger = LogManager.getLogger(ConflictManager.class); | ||
|
||
private final Set<ConflictResolver> resolvers; | ||
private final ConflictWriter writer; | ||
private final Random random; | ||
|
||
public ConflictManager(Set<ConflictResolver> resolvers, ConflictWriter writer, Random random) { | ||
this.resolvers = resolvers; | ||
this.random = random; | ||
this.writer = writer; | ||
} | ||
|
||
public void initializeReplanning(Population population) { | ||
if (resolvers.size() > 0) { // only require if active | ||
population.getPersons().values().forEach(ReplanningUtils::setInitialPlan); | ||
} | ||
} | ||
|
||
public void run(Population population, int iteration) { | ||
if (resolvers.size() == 0) { | ||
return; | ||
} | ||
|
||
logger.info("Resolving conflicts ..."); | ||
|
||
Map<String, Integer> conflictCounts = new HashMap<>(); | ||
IdSet<Person> conflictingIds = new IdSet<>(Person.class); | ||
|
||
for (ConflictResolver resolver : resolvers) { | ||
IdSet<Person> resolverConflictingIds = resolver.resolve(population, iteration); | ||
conflictCounts.put(resolver.getName(), resolverConflictingIds.size()); | ||
conflictingIds.addAll(resolverConflictingIds); | ||
} | ||
|
||
logger.info(" Conflicts: " + conflictCounts.entrySet().stream() | ||
.map(entry -> String.format("%s=%d", entry.getKey(), entry.getValue())) | ||
.collect(Collectors.joining(", "))); | ||
|
||
int switchedToInitialCount = 0; | ||
int switchedToRandomCount = 0; | ||
|
||
for (Id<Person> personId : conflictingIds) { | ||
Person person = population.getPersons().get(personId); | ||
|
||
// If the initial plan is non-conflicting, switch back to it | ||
Plan initialPlan = ReplanningUtils.getInitialPlan(person); | ||
|
||
if (initialPlan != null && !isPotentiallyConflicting(initialPlan)) { | ||
person.setSelectedPlan(initialPlan); | ||
switchedToInitialCount++; | ||
} else { | ||
// Select a random non-conflicting plan | ||
List<Plan> candidates = person.getPlans().stream().filter(p -> !isPotentiallyConflicting(p)) | ||
.collect(Collectors.toList()); | ||
Preconditions.checkState(candidates.size() > 0, | ||
String.format("Agent %s has no non-conflicting plan", personId)); | ||
|
||
// Shuffle, and select the first | ||
Collections.shuffle(candidates, random); | ||
person.setSelectedPlan(candidates.get(0)); | ||
|
||
switchedToRandomCount++; | ||
} | ||
} | ||
|
||
logger.info(String.format(" %d (%.2f%%) switched to initial", switchedToInitialCount, | ||
(double) switchedToInitialCount / population.getPersons().size())); | ||
logger.info(String.format(" %d (%.2f%%) switched to random", switchedToRandomCount, | ||
(double) switchedToRandomCount / population.getPersons().size())); | ||
|
||
writer.write(iteration, switchedToInitialCount, switchedToRandomCount, conflictCounts); | ||
|
||
logger.info(" Done resolving conflicts!"); | ||
} | ||
|
||
public boolean isPotentiallyConflicting(Plan plan) { | ||
for (ConflictResolver resolver : resolvers) { | ||
if (resolver.isPotentiallyConflicting(plan)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package org.matsim.core.replanning.conflicts; | ||
|
||
import java.io.File; | ||
import java.util.Random; | ||
import java.util.Set; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.matsim.core.controler.AbstractModule; | ||
import org.matsim.core.controler.OutputDirectoryHierarchy; | ||
import org.matsim.core.gbl.MatsimRandom; | ||
|
||
import com.google.inject.Binder; | ||
import com.google.inject.Provides; | ||
import com.google.inject.Singleton; | ||
import com.google.inject.binder.LinkedBindingBuilder; | ||
import com.google.inject.multibindings.Multibinder; | ||
|
||
/** | ||
* Prepares injection of the conflict resolution logic during replanning | ||
* | ||
* @author Sebastian Hörl (sebhoerl), IRT SystemX | ||
*/ | ||
public class ConflictModule extends AbstractModule { | ||
private final static Logger logger = LogManager.getLogger(ConflictModule.class); | ||
|
||
private final static String OUTPUT_FILE = "conflicts.csv"; | ||
|
||
@Override | ||
public void install() { | ||
// initialize the builder | ||
getMultibinder(binder()); | ||
} | ||
|
||
@Provides | ||
@Singleton | ||
ConflictWriter provideConflictWriter(OutputDirectoryHierarchy outputDirectoryHierarchy) { | ||
File outputPath = new File(outputDirectoryHierarchy.getOutputFilename(OUTPUT_FILE)); | ||
return new ConflictWriter(outputPath); | ||
} | ||
|
||
@Provides | ||
@Singleton | ||
ConflictManager provideConflictManager(Set<ConflictResolver> resolvers, ConflictWriter writer) { | ||
if (!getConfig().replanning().getPlanSelectorForRemoval() | ||
.equals(WorstPlanForRemovalSelectorWithConflicts.SELECTOR_NAME)) { | ||
logger.warn("The replanning.planSelectorForRemoval is not set to " | ||
+ WorstPlanForRemovalSelectorWithConflicts.SELECTOR_NAME | ||
+ ". This will likely cause problems with the conflict logic if you are not sure what you are doing."); | ||
} | ||
|
||
Random random = MatsimRandom.getRandom(); // no need for local instance, not parallel! | ||
return new ConflictManager(resolvers, writer, random); | ||
} | ||
|
||
static Multibinder<ConflictResolver> getMultibinder(Binder binder) { | ||
return Multibinder.newSetBinder(binder, ConflictResolver.class); | ||
} | ||
|
||
/** | ||
* Allows to bind a conflict resolver in an AbstractModule, for instance: | ||
* | ||
* <code> | ||
* ConflictModule.bindResolver(binder()).toInstance(new ConflictResolver() { | ||
* // ... | ||
* }); | ||
* </code> | ||
*/ | ||
static public LinkedBindingBuilder<ConflictResolver> bindResolver(Binder binder) { | ||
return getMultibinder(binder).addBinding(); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package org.matsim.core.replanning.conflicts; | ||
|
||
import org.matsim.api.core.v01.IdSet; | ||
import org.matsim.api.core.v01.population.Person; | ||
import org.matsim.api.core.v01.population.Plan; | ||
import org.matsim.api.core.v01.population.Population; | ||
|
||
/** | ||
* This interface is called after standard replanning. Its purpose is to check | ||
* the population and detect any conflicts between agents. The interface must | ||
* then return a list of agents that should be reset to a non-conflicting plan | ||
* in order to resolve all conflicts. Plans that are not conflicting are | ||
* identified as such using the isPotentiallyConflicting method. | ||
* | ||
* @author Sebastian Hörl (sebhoerl), IRT SystemX | ||
*/ | ||
public interface ConflictResolver { | ||
IdSet<Person> resolve(Population population, int iteration); | ||
|
||
boolean isPotentiallyConflicting(Plan plan); | ||
|
||
String getName(); | ||
} |
61 changes: 61 additions & 0 deletions
61
matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictWriter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package org.matsim.core.replanning.conflicts; | ||
|
||
import java.io.BufferedWriter; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.matsim.core.utils.io.IOUtils; | ||
|
||
/** | ||
* Writes high-level statistics on the conflict resolution process per iteration | ||
* | ||
* @author Sebastian Hörl (sebhoerl), IRT SystemX | ||
*/ | ||
public class ConflictWriter { | ||
private final File outputPath; | ||
|
||
public ConflictWriter(File outputPath) { | ||
this.outputPath = outputPath; | ||
} | ||
|
||
public void write(int iteration, int rejectedToInitial, int rejectedToRandom, Map<String, Integer> conflictCounts) { | ||
boolean writeHeader = !outputPath.exists(); | ||
|
||
List<String> resolvers = new ArrayList<>(conflictCounts.keySet()); | ||
Collections.sort(resolvers); | ||
|
||
try { | ||
BufferedWriter writer = IOUtils.getAppendingBufferedWriter(outputPath.getPath()); | ||
|
||
if (writeHeader) { | ||
List<String> header = new ArrayList<>( | ||
Arrays.asList("iteration", "rejected_total", "switched_to_initial", "switched_to_random")); | ||
resolvers.stream().map(r -> "resolver:" + r).forEach(header::add); | ||
|
||
writer.write(String.join(";", header) + "\n"); | ||
} | ||
|
||
List<String> row = new ArrayList<>(Arrays.asList(String.valueOf(iteration), // | ||
String.valueOf(rejectedToInitial + rejectedToRandom), // | ||
String.valueOf(rejectedToInitial), // | ||
String.valueOf(rejectedToRandom) // | ||
)); | ||
|
||
for (String resolver : resolvers) { | ||
row.add(String.valueOf(conflictCounts.get(resolver))); | ||
} | ||
|
||
writer.write(String.join(";", row) + "\n"); | ||
|
||
writer.close(); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
} |
Oops, something went wrong.