Commit e0e72dd6 authored by Stefan Schuhbaeck's avatar Stefan Schuhbaeck

refactor TargetChanger to strategy pattern.

Add a TargetChangerAlgorithmType Enum to
AttributeTargetChanger to select the needed behaviour.
During TargetChangerController init instantiate the
TargetChangerAlgorithm and redirect update to the algorithm.

Default algorithm SELECT_LIST: nextTarget any number of
targets from  [1 ... N], probabilityToChangeTarget exactly
one element between (including) [0.0, 1.0]. If change takes
place pedestrians targetList is replaced with nextTarget
parent bf8cca5d
Pipeline #281670 failed with stages
in 88 minutes and 48 seconds
......@@ -8,7 +8,7 @@
"type" : "RECTANGLE"
},
"reachDistance" : 0.0,
"nextTargetIsPedestrian" : false,
"changeAlgorithmType" : "SELECT_ELEMENT",
"nextTarget" : [-1],
"probabilityToChangeTarget" : [0.0]
"probabilityToChangeTarget" : [0.3]
}
\ No newline at end of file
......@@ -95,10 +95,12 @@ public class VadereCommandHandlerTest extends CommandHandlerTest {
protected void mockIt() {
String scenario = "scenario002";
Topography topo = mock(Topography.class, Mockito.RETURNS_DEEP_STUBS);
Random rnd = mock(Random.class, Mockito.RETURNS_DEEP_STUBS);
when(rnd.nextInt()).thenReturn(42);
when(topo.getContextId()).thenReturn(scenario);
when(simState.getTopography()).thenReturn(topo);
VadereContext ctx = VadereContext.get(simState.getTopography());
ctx.put("random", mock(Random.class, Mockito.RETURNS_DEEP_STUBS));
ctx.put("random", rnd);
VadereContext.add(scenario, ctx);
}
};
......
package org.vadere.simulator.control.scenarioelements;
import org.apache.commons.math3.distribution.BinomialDistribution;
import org.apache.commons.math3.random.JDKRandomGenerator;
import org.vadere.state.attributes.Attributes;
import org.vadere.state.scenario.*;
import org.vadere.simulator.control.scenarioelements.targetchanger.TargetChangerAlgorithm;
import org.vadere.state.scenario.Agent;
import org.vadere.state.scenario.DynamicElement;
import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.scenario.TargetChanger;
import org.vadere.state.scenario.TargetChangerListener;
import org.vadere.state.scenario.Topography;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VShape;
import org.vadere.util.logging.Logger;
import java.awt.geom.Rectangle2D;
import java.util.*;
import java.util.stream.Collectors;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* Change target id of an agent which enters the corresponding {@link TargetChanger} area.
......@@ -39,50 +46,21 @@ public class TargetChangerController {
private Topography topography;
private Map<Integer, Agent> processedAgents;
private LinkedList<Double> probabilitiesToChangeTarget;
private int seed;
private Random random;
private LinkedList<BinomialDistribution> binomialDistributions;
private TargetChangerAlgorithm changerAlgorithm;
// Constructors
public TargetChangerController(Topography topography, TargetChanger targetChanger, Random random) {
throwExceptionOnInvalidInput(targetChanger);
this.changerAlgorithm = TargetChangerAlgorithm.create(targetChanger, topography);
this.changerAlgorithm.throwExceptionOnInvalidInput(targetChanger);
this.changerAlgorithm.init(random);
this.targetChanger = targetChanger;
this.topography = topography;
this.processedAgents = new HashMap<>();
this.random = random;
this.probabilitiesToChangeTarget = targetChanger.getAttributes().getProbabilitiesToChangeTarget();
this.seed = random.nextInt();
this.binomialDistributions = getBinomialDistributions();
}
private void throwExceptionOnInvalidInput(TargetChanger targetChanger) {
int totalTargets = targetChanger.getAttributes().getNextTarget().size();
int totalProbabilities = targetChanger.getAttributes().getProbabilitiesToChangeTarget().size();
boolean inputIsValid = (totalProbabilities == 1) || (totalProbabilities == totalTargets);
if ( inputIsValid == false) {
throw new IllegalArgumentException("The size of \"probabilitiesToChangeTarget\" must be 1 or equal to nextTarget.");
}
}
private LinkedList<BinomialDistribution> getBinomialDistributions() {
LinkedList<BinomialDistribution> binomialDistributions = new LinkedList<>();
JDKRandomGenerator randomGenerator = new JDKRandomGenerator();
randomGenerator.setSeed(seed);
for (Double probability : probabilitiesToChangeTarget) {
binomialDistributions.add(new BinomialDistribution(randomGenerator, BINOMIAL_DISTRIBUTION_SUCCESS_VALUE, probability));
}
return binomialDistributions;
}
// Getters
public Map<Integer, Agent> getProcessedAgents() {
return processedAgents;
......@@ -103,7 +81,7 @@ public class TargetChangerController {
if (hasAgentReachedTargetChangerArea(agent) && processedAgents.containsKey(agent.getId()) == false) {
logEnteringTimeOfAgent(agent, simTimeInSec);
setAgentTargetList(agent);
changerAlgorithm.setAgentTargetList(agent);
notifyListenersTargetChangerAreaReached(agent);
processedAgents.put(agent.getId(), agent);
}
......@@ -143,90 +121,17 @@ public class TargetChangerController {
}
}
private void setAgentTargetList(Agent agent) {
LinkedList<Integer> nextTargets = getNextTargets();
if (nextTargets.size() > 0) {
if (targetChanger.getAttributes().isNextTargetIsPedestrian()) {
useDynamicTargetForAgentOrUseStaticAsFallback(agent, nextTargets);
} else {
useStaticTargetForAgent(agent,nextTargets);
}
}
}
private LinkedList<Integer> getNextTargets() {
LinkedList<Integer> nextTargets = new LinkedList<>();
for (int i = 0; i < binomialDistributions.size(); i++) {
BinomialDistribution binomialDistribution = binomialDistributions.get(i);
int binomialDistributionSample = binomialDistribution.sample();
if (binomialDistributionSample == BINOMIAL_DISTRIBUTION_SUCCESS_VALUE) {
// If only one probability is given, just take the target list which was defined by the user.
// Otherwise, create a list of new targets (if we saw a "success" value while sampling.
if (targetChanger.getAttributes().getProbabilitiesToChangeTarget().size() == 1) {
nextTargets = targetChanger.getAttributes().getNextTarget();
break;
} else {
nextTargets.add(targetChanger.getAttributes().getNextTarget().get(i));
}
}
}
return nextTargets;
}
private void useDynamicTargetForAgentOrUseStaticAsFallback(Agent agent, LinkedList<Integer> keepTargets) {
int nextTarget = (targetChanger.getAttributes().getNextTarget().size() > 0)
? targetChanger.getAttributes().getNextTarget().get(0)
: Attributes.ID_NOT_SET;
Collection<Pedestrian> allPedestrians = topography.getElements(Pedestrian.class);
List<Pedestrian> pedsWithCorrectTargetId = allPedestrians.stream()
.filter(pedestrian -> pedestrian.getTargets().contains(nextTarget))
.collect(Collectors.toList());
if (pedsWithCorrectTargetId.size() > 0) {
// Try to use a pedestrian which has already some followers
// to avoid calculating multiple dynamic floor fields.
List<Pedestrian> pedsWithFollowers = pedsWithCorrectTargetId.stream()
.filter(pedestrian -> pedestrian.getFollowers().isEmpty() == false)
.collect(Collectors.toList());
Pedestrian pedToFollow = (pedsWithFollowers.isEmpty()) ? pedsWithCorrectTargetId.get(0) : pedsWithFollowers.get(0);
agentFollowsOtherPedestrian(agent, pedToFollow);
} else {
useStaticTargetForAgent(agent, keepTargets);
}
}
private void agentFollowsOtherPedestrian(Agent agent, Pedestrian pedToFollow) {
// Create the necessary TargetPedestrian wrapper object.
// The simulation loop creates the corresponding controller objects
// in the next simulation loop based on the exisiting targets in the topography.
TargetPedestrian targetPedestrian = new TargetPedestrian(pedToFollow);
topography.addTarget(targetPedestrian);
// Make "agent" a follower of "pedToFollow".
agent.setSingleTarget(targetPedestrian.getId(), true);
pedToFollow.getFollowers().add(agent);
}
private void useStaticTargetForAgent(Agent agent, LinkedList<Integer> nextTargets) {
agent.setTargets(nextTargets);
agent.setNextTargetListIndex(0);
agent.setIsCurrentTargetAnAgent(false);
}
private void notifyListenersTargetChangerAreaReached(final Agent agent) {
for (TargetChangerListener listener : targetChanger.getTargetChangerListeners()) {
listener.reachedTargetChanger(targetChanger, agent);
}
}
public TargetChangerAlgorithm getChangerAlgorithm() {
return changerAlgorithm;
}
public void setChangerAlgorithm(TargetChangerAlgorithm changerAlgorithm) {
this.changerAlgorithm = changerAlgorithm;
}
}
package org.vadere.simulator.control.scenarioelements.targetchanger;
import org.vadere.state.scenario.TargetChanger;
import org.vadere.state.scenario.Topography;
/**
* Abstract TargetChangerAlgorithm holding the corresponding {@link TargetChanger}
* and {@link Topography} to access during simulation update. This class is triggered
* by a {@link org.vadere.simulator.control.scenarioelements.TargetChangerController}.
*/
public abstract class BaseTargetChangerAlgorithm implements TargetChangerAlgorithm {
protected TargetChanger targetChanger;
protected Topography topography;
public BaseTargetChangerAlgorithm(TargetChanger targetChanger, Topography topography) {
this.targetChanger = targetChanger;
this.topography = topography;
}
protected void checkProbabilityIsNormalized(){
for (Double probabilityToChangeTarget : targetChanger.getAttributes().getProbabilitiesToChangeTarget()){
if (probabilityToChangeTarget < 0.0 || probabilityToChangeTarget > 1.0) {
throw new IllegalArgumentException("Probability must be in range 0.0 to 1.0!");
}
}
}
@Override
public TargetChanger getTargetChanger() {
return targetChanger;
}
}
package org.vadere.simulator.control.scenarioelements.targetchanger;
import org.apache.commons.math3.distribution.BinomialDistribution;
import org.apache.commons.math3.random.JDKRandomGenerator;
import org.vadere.state.attributes.Attributes;
import org.vadere.state.scenario.Agent;
import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.scenario.TargetChanger;
import org.vadere.state.scenario.TargetChangerAlgorithmType;
import org.vadere.state.scenario.TargetPedestrian;
import org.vadere.state.scenario.Topography;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
public class FollowPedestrianTargetChanger extends BaseTargetChangerAlgorithm {
private BinomialDistribution binomialDistribution;
public FollowPedestrianTargetChanger(TargetChanger targetChanger, Topography topography) {
super(targetChanger, topography);
}
@Override
public void throwExceptionOnInvalidInput(TargetChanger targetChanger) {
int totalTargets = targetChanger.getAttributes().getNextTarget().size();
int totalProbabilities = targetChanger.getAttributes().getProbabilitiesToChangeTarget().size();
checkProbabilityIsNormalized();
boolean inputIsValid = (totalProbabilities == 1 && totalTargets == 1);
if (!inputIsValid) {
throw new IllegalArgumentException(String.format(
"The size of \"probabilitiesToChangeTarget\" must be 1 for. %s", TargetChangerAlgorithmType.FOLLOW_PERSON) );
}
}
@Override
public void init(Random rnd){
binomialDistribution = createBinomialDistribution(rnd);
}
@Override
public boolean setAgentTargetList(Agent agent) {
// check probability
if (binomialDistribution.sample() != BINOMIAL_DISTRIBUTION_SUCCESS_VALUE)
return false;
int nextTarget = (targetChanger.getAttributes().getNextTarget().size() > 0)
? targetChanger.getAttributes().getNextTarget().get(0)
: Attributes.ID_NOT_SET;
Collection<Pedestrian> allPedestrians = topography.getElements(Pedestrian.class);
List<Pedestrian> pedsWithCorrectTargetId = allPedestrians.stream()
.filter(pedestrian -> pedestrian.getTargets().contains(nextTarget))
.collect(Collectors.toList());
if (pedsWithCorrectTargetId.size() > 0) {
// Try to use a pedestrian which has already some followers
// to avoid calculating multiple dynamic floor fields.
List<Pedestrian> pedsWithFollowers = pedsWithCorrectTargetId.stream()
.filter(pedestrian -> pedestrian.getFollowers().isEmpty() == false)
.collect(Collectors.toList());
Pedestrian pedToFollow = (pedsWithFollowers.isEmpty()) ? pedsWithCorrectTargetId.get(0) : pedsWithFollowers.get(0);
agentFollowsOtherPedestrian(agent, pedToFollow);
} else {
useStaticTargetForAgent(agent, nextTarget);
}
return true;
}
private void agentFollowsOtherPedestrian(Agent agent, Pedestrian pedToFollow) {
// Create the necessary TargetPedestrian wrapper object.
// The simulation loop creates the corresponding controller objects
// in the next simulation loop based on the exisiting targets in the topography.
TargetPedestrian targetPedestrian = new TargetPedestrian(pedToFollow);
topography.addTarget(targetPedestrian);
// Make "agent" a follower of "pedToFollow".
agent.setSingleTarget(targetPedestrian.getId(), true);
pedToFollow.getFollowers().add(agent);
}
private void useStaticTargetForAgent(Agent agent, Integer nextTargets) {
agent.setSingleTarget(nextTargets, false);
agent.setNextTargetListIndex(0);
agent.setIsCurrentTargetAnAgent(false);
}
}
package org.vadere.simulator.control.scenarioelements.targetchanger;
import org.apache.commons.math3.distribution.EnumeratedIntegerDistribution;
import org.apache.commons.math3.random.JDKRandomGenerator;
import org.vadere.state.scenario.Agent;
import org.vadere.state.scenario.TargetChanger;
import org.vadere.state.scenario.TargetChangerAlgorithmType;
import org.vadere.state.scenario.Topography;
import java.util.Arrays;
import java.util.Random;
import java.util.stream.IntStream;
public class SelectElementTargetChanger extends BaseTargetChangerAlgorithm {
private EnumeratedIntegerDistribution dist;
public SelectElementTargetChanger(TargetChanger targetChanger, Topography topography) {
super(targetChanger, topography);
}
@Override
public void throwExceptionOnInvalidInput(TargetChanger targetChanger) {
int totalTargets = targetChanger.getAttributes().getNextTarget().size();
int totalProbabilities = targetChanger.getAttributes().getProbabilitiesToChangeTarget().size();
boolean inputIsValid = (totalProbabilities == 0 && totalTargets > 0) ||
(totalProbabilities == totalTargets ) ||
(totalProbabilities == totalTargets+1 );
if (!inputIsValid) {
throw new IllegalArgumentException(String.format(
"The size of \"probabilitiesToChangeTarget\" must be 0 or the same length as nextTarget. %s", TargetChangerAlgorithmType.SELECT_LIST) );
}
}
@Override
public void init(Random rnd) {
int nextTargetSize = targetChanger.getAttributes().getNextTarget().size();
int probabilitySize = targetChanger.getAttributes().getProbabilitiesToChangeTarget().size();
int arrSize;
if (probabilitySize == 0){
arrSize = nextTargetSize;
} else {
arrSize = Math.max(nextTargetSize, probabilitySize);
}
int[] entity = IntStream.range(0, arrSize).toArray();
double[] probability = new double[arrSize]; //
if (probabilitySize == 0){
// no probability given thus uniform distribution
Arrays.fill(probability, 1.0 /nextTargetSize);
} else {
// probability same size as nextTargetSize (or one bigger for no change!)
double sum = getTargetChanger().getAttributes().getProbabilitiesToChangeTarget().stream().reduce(0.0, Double::sum);
double norm = 1.0;
if (sum > 1.0)
norm = sum;
for(int i=0; i < probability.length; i++){
double val = targetChanger.getAttributes().getProbabilitiesToChangeTarget().get(i);
probability[i] = val/norm;
}
}
JDKRandomGenerator randomGenerator = new JDKRandomGenerator();
randomGenerator.setSeed(rnd.nextInt());
dist = new EnumeratedIntegerDistribution(randomGenerator, entity, probability);
}
@Override
public boolean setAgentTargetList(Agent agent) {
int index = dist.sample();
if (index == targetChanger.getAttributes().getNextTarget().size())
return false; // do not change target.
agent.setSingleTarget(targetChanger.getAttributes().getNextTarget().get(index), false);
agent.setNextTargetListIndex(0);
agent.setIsCurrentTargetAnAgent(false);
return true;
}
}
package org.vadere.simulator.control.scenarioelements.targetchanger;
import org.apache.commons.math3.distribution.BinomialDistribution;
import org.vadere.state.scenario.Agent;
import org.vadere.state.scenario.TargetChanger;
import org.vadere.state.scenario.TargetChangerAlgorithmType;
import org.vadere.state.scenario.Topography;
import java.util.Random;
public class SelectListTargetChanger extends BaseTargetChangerAlgorithm {
private BinomialDistribution binomialDistribution;
public SelectListTargetChanger(TargetChanger targetChanger, Topography topography) {
super(targetChanger, topography);
}
@Override
public void throwExceptionOnInvalidInput(TargetChanger targetChanger) {
int totalTargets = targetChanger.getAttributes().getNextTarget().size();
int totalProbabilities = targetChanger.getAttributes().getProbabilitiesToChangeTarget().size();
checkProbabilityIsNormalized();
boolean inputIsValid = (totalProbabilities == 1 && totalTargets > 0);
if (!inputIsValid) {
throw new IllegalArgumentException(String.format(
"The size of \"probabilitiesToChangeTarget\" must be 1 and totalTargets must be set. %s", TargetChangerAlgorithmType.SELECT_LIST) );
}
}
@Override
public void init(Random rnd) {
binomialDistribution = createBinomialDistribution(rnd);
}
@Override
public boolean setAgentTargetList(Agent agent) {
if (binomialDistribution.sample() != BINOMIAL_DISTRIBUTION_SUCCESS_VALUE)
return false;
agent.setTargets(targetChanger.getAttributes().getNextTarget());
agent.setNextTargetListIndex(0);
agent.setIsCurrentTargetAnAgent(false);
return true;
}
}
package org.vadere.simulator.control.scenarioelements.targetchanger;
import org.apache.commons.math3.distribution.BinomialDistribution;
import org.apache.commons.math3.random.JDKRandomGenerator;
import org.vadere.state.scenario.Agent;
import org.vadere.state.scenario.TargetChanger;
import org.vadere.state.scenario.TargetChangerAlgorithmType;
import org.vadere.state.scenario.Topography;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;
public class SortedSubListTargetChanger extends BaseTargetChangerAlgorithm {
private ArrayList<BinomialDistribution> binomialDistributions;
public SortedSubListTargetChanger(TargetChanger targetChanger, Topography topography) {
super(targetChanger, topography);
binomialDistributions = new ArrayList<>();
}
@Override
public void throwExceptionOnInvalidInput(TargetChanger targetChanger) {
int totalTargets = targetChanger.getAttributes().getNextTarget().size();
int totalProbabilities = targetChanger.getAttributes().getProbabilitiesToChangeTarget().size();
checkProbabilityIsNormalized();
boolean inputIsValid = (totalProbabilities > 1) && (totalProbabilities == totalTargets);
if (!inputIsValid) {
throw new IllegalArgumentException(String.format(
"The size of \"probabilitiesToChangeTarget\" and \"nextTarget\" must be equal for %s algorithm", TargetChangerAlgorithmType.SORTED_SUB_LIST) );
}
}
@Override
public void init(Random rnd) {
for (Double probability : targetChanger.getAttributes().getProbabilitiesToChangeTarget()) {
JDKRandomGenerator randomGenerator = new JDKRandomGenerator();
randomGenerator.setSeed(rnd.nextInt());
binomialDistributions.add(new BinomialDistribution(randomGenerator, BINOMIAL_DISTRIBUTION_SUCCESS_VALUE, probability));
}
}
@Override
public boolean setAgentTargetList(Agent agent) {
LinkedList<Integer> nextTargets = getNextTargets();
boolean ret = false;
if (nextTargets.size() > 0){
agent.setTargets(nextTargets);
agent.setNextTargetListIndex(0);
agent.setIsCurrentTargetAnAgent(false);
ret = true;
}
return ret;
}
private LinkedList<Integer> getNextTargets() {
LinkedList<Integer> nextTargets = new LinkedList<>();
for (int i = 0; i < binomialDistributions.size(); i++) {