Skip to content

Commit

Permalink
feat: configure fixedOrder globally instead for every spawner (#350)
Browse files Browse the repository at this point in the history
* feat: configure fixedOrder globally instead for every spawner
* Update fed/mosaic-mapping/src/main/java/org/eclipse/mosaic/fed/mapping/ambassador/SpawningFramework.java
* fix: use fixedOrder in default case
* fix: use fixedOrder per default changes test result
* feat: FixedOrder does not include any randomness anymore
* initial selection chooses item with highest weight. for two or more items with equal weights, the first found item is returned.
* feat: use stochastic mapping order by default instead of fixed order
* fix: fixedOrder property does not exist anymore in MatrixMapper
* fix: apply changed default value of fixedOrder property
* fix: fix tests after change of default behavior (fixedOrder = false)
* fix: set fixedOrder for old behavior for tests
  • Loading branch information
kschrab authored Oct 4, 2023
1 parent 8ff2ccd commit 4c3b72d
Show file tree
Hide file tree
Showing 19 changed files with 132 additions and 146 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"config": {
"fixedOrder": true
},
"prototypes": [
{
"name": "Car",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"config": {
"fixedOrder": true
},
"prototypes": [
{
"name": "Car",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

package org.eclipse.mosaic.fed.mapping.ambassador;

import org.eclipse.mosaic.fed.mapping.ambassador.weighting.FixedOrderSelector;
import org.eclipse.mosaic.fed.mapping.ambassador.weighting.StochasticSelector;
import org.eclipse.mosaic.fed.mapping.ambassador.weighting.WeightedSelector;
import org.eclipse.mosaic.fed.mapping.config.CMappingAmbassador;
import org.eclipse.mosaic.fed.mapping.config.CPrototype;
import org.eclipse.mosaic.interactions.mapping.VehicleRegistration;
Expand Down Expand Up @@ -71,7 +73,7 @@ public class MappingAmbassador extends AbstractFederateAmbassador {
/**
* Cache stochastic selectors to avoid unnecessary instantiations.
*/
private final Map<String, StochasticSelector<CPrototype>> typeDistributionSelectors = new HashMap<>();
private final Map<String, WeightedSelector<CPrototype>> typeDistributionSelectors = new HashMap<>();

/**
* Constructor for the {@link MappingAmbassador}.
Expand Down Expand Up @@ -134,9 +136,13 @@ private void handleInteraction(ScenarioVehicleRegistration scenarioVehicle) thro

final List<CPrototype> typeDistribution = framework.getTypeDistributionByName(scenarioVehicle.getVehicleType().getName());
if (!typeDistribution.isEmpty()) {
StochasticSelector<CPrototype> selector = typeDistributionSelectors.get(scenarioVehicle.getVehicleType().getName());
WeightedSelector<CPrototype> selector = typeDistributionSelectors.get(scenarioVehicle.getVehicleType().getName());
if (selector == null) {
selector = new StochasticSelector<>(typeDistribution, randomNumberGenerator);
if (mappingAmbassadorConfiguration.config != null && mappingAmbassadorConfiguration.config.fixedOrder) {
selector = new FixedOrderSelector<>(typeDistribution);
} else {
selector = new StochasticSelector<>(typeDistribution, randomNumberGenerator);
}
typeDistributionSelectors.put(scenarioVehicle.getVehicleType().getName(), selector);
}
final CPrototype selected = selector.nextItem();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
public class OriginDestinationVehicleFlowGenerator {
private final List<COriginDestinationMatrixMapper.COriginDestinationPoint> originDestinationPointConfigurations;
private final List<CPrototype> prototypeConfigurations;
private final Boolean fixedOrder;

/**
* Values for the OD-matrix. Unit should be vehicles/hour.
*/
Expand All @@ -49,7 +47,6 @@ public class OriginDestinationVehicleFlowGenerator {
OriginDestinationVehicleFlowGenerator(COriginDestinationMatrixMapper matrixMapperConfiguration) {
this.originDestinationPointConfigurations = matrixMapperConfiguration.points;
this.prototypeConfigurations = matrixMapperConfiguration.types;
this.fixedOrder = matrixMapperConfiguration.fixedOrder;
this.odValues = matrixMapperConfiguration.odValues;
this.startingTime = matrixMapperConfiguration.startingTime;
this.maxTime = matrixMapperConfiguration.maxTime;
Expand All @@ -64,7 +61,7 @@ public class OriginDestinationVehicleFlowGenerator {
* @param randomNumberGenerator {@link RandomNumberGenerator} used for flow noise
* @param flowNoise flag if the stream should be affected by noise
*/
void generateVehicleStreams(SpawningFramework framework, RandomNumberGenerator randomNumberGenerator, boolean flowNoise) {
void generateVehicleStreams(SpawningFramework framework, RandomNumberGenerator randomNumberGenerator, boolean flowNoise, boolean fixedOrder) {
for (int fromId = 0; fromId < originDestinationPointConfigurations.size(); fromId++) {
for (int toId = 0; toId < originDestinationPointConfigurations.size(); toId++) {

Expand All @@ -78,15 +75,14 @@ void generateVehicleStreams(SpawningFramework framework, RandomNumberGenerator r
vehicleConfiguration.destination = originDestinationPointConfigurations.get(toId).position;
vehicleConfiguration.targetFlow = flow;
vehicleConfiguration.types = prototypeConfigurations;
vehicleConfiguration.fixedOrder = fixedOrder;

vehicleConfiguration.startingTime = startingTime;
vehicleConfiguration.maxTime = maxTime;
vehicleConfiguration.laneSelectionMode = laneSelectionMode;
vehicleConfiguration.departSpeedMode = departureSpeedMode;
// Assuming, if we only have set a flow, that we want to have an unlimited number of vehicles

framework.addVehicleStream(new VehicleFlowGenerator(vehicleConfiguration, randomNumberGenerator, flowNoise));
framework.addVehicleStream(new VehicleFlowGenerator(vehicleConfiguration, randomNumberGenerator, flowNoise, fixedOrder));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,9 @@ public SpawningFramework(CMappingAmbassador mappingConfiguration,
}

boolean flowNoise = mappingConfiguration.config != null && mappingConfiguration.config.randomizeFlows;
boolean fixedOrder = mappingConfiguration.config != null && mappingConfiguration.config.fixedOrder;
for (OriginDestinationVehicleFlowGenerator mapper : matrices) {
mapper.generateVehicleStreams(this, rng, flowNoise);
mapper.generateVehicleStreams(this, rng, flowNoise, fixedOrder);
}

// Use the prototype configurations to complete the spawner-definitions:
Expand Down Expand Up @@ -315,7 +316,12 @@ private boolean generateVehicleFlows(List<CVehicle> vehicles, CMappingConfigurat
}

if (vehicleConfiguration.startingTime >= 0) {
vehicleFlowGenerators.add(new VehicleFlowGenerator(vehicleConfiguration, rng, config != null && config.randomizeFlows));
boolean flowNoise = config != null && config.randomizeFlows;
boolean fixedOrder = config != null && config.fixedOrder;

vehicleFlowGenerators.add(
new VehicleFlowGenerator(vehicleConfiguration, rng, flowNoise, fixedOrder)
);
}
}
return spawnersExist;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public class VehicleFlowGenerator {
*
* @param vehicleConfiguration vehicle spawner configuration
*/
public VehicleFlowGenerator(CVehicle vehicleConfiguration, @Nonnull RandomNumberGenerator randomNumberGenerator, boolean flowNoise) {
public VehicleFlowGenerator(CVehicle vehicleConfiguration, @Nonnull RandomNumberGenerator randomNumberGenerator, boolean flowNoise, boolean fixedOrder) {

// Enforce that types are defined
if (vehicleConfiguration.types == null || vehicleConfiguration.types.isEmpty()) {
Expand All @@ -141,7 +141,7 @@ public VehicleFlowGenerator(CVehicle vehicleConfiguration, @Nonnull RandomNumber
// create SpawningMode using given definitions
this.spawningMode = createSpawningMode(vehicleConfiguration, randomNumberGenerator, flowNoise);
// create the selector deciding which vehicle is spawned next
this.selector = createSelector(vehicleConfiguration, randomNumberGenerator);
this.selector = createSelector(randomNumberGenerator, fixedOrder);
// create lane index list
this.lanes = createLanes(vehicleConfiguration);
// create lane selector deciding which lane the next vehicle is spawned on
Expand Down Expand Up @@ -268,12 +268,11 @@ private SpawningMode createSpawningMode(
return newSpawningMode;
}

private WeightedSelector<VehicleTypeSpawner> createSelector(
CVehicle vehicleConfiguration, RandomNumberGenerator randomNumberGenerator) {
private WeightedSelector<VehicleTypeSpawner> createSelector(RandomNumberGenerator randomNumberGenerator, boolean fixedOrder) {
if (types.size() == 1) {
selector = () -> Iterables.getOnlyElement(types);
} else if (vehicleConfiguration.fixedOrder) {
selector = new FixedOrderSelector<>(types, randomNumberGenerator);
} else if (fixedOrder) {
selector = new FixedOrderSelector<>(types);
} else {
selector = new StochasticSelector<>(types, randomNumberGenerator);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@

package org.eclipse.mosaic.fed.mapping.ambassador.weighting;

import org.eclipse.mosaic.lib.math.RandomNumberGenerator;

import org.apache.commons.lang3.Validate;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;

/**
* Helper class allowing the random and pseudo-random selection of multiple
Expand All @@ -32,8 +29,6 @@
* @param <T> The type of the object to be returned
*/
public class FixedOrderSelector<T extends Weighted> implements WeightedSelector<T> {

private final RandomNumberGenerator randomNumberGenerator;
private List<Item<T>> items;

private static class Item<T extends Weighted> implements Weighted {
Expand Down Expand Up @@ -67,15 +62,13 @@ public double getWeight() {
* Constructor for {@link FixedOrderSelector}.
*
* @param objects list of all types that might be selected from
* @param randGenerator random number generator to select the first item
*/
public FixedOrderSelector(List<T> objects, @Nonnull RandomNumberGenerator randGenerator) {
public FixedOrderSelector(List<T> objects) {
Validate.notNull(objects, "Illegal constructor call for WeightedSelector: objects is null.");
Validate.isTrue(!objects.isEmpty(), "Illegal constructor call for WeightedSelector: objects is empty!");

// init with base values
this.items = new ArrayList<>(objects.size());
this.randomNumberGenerator = randGenerator;

// map all objects to an item so they can be weighted
this.items = objects.stream().map((o) -> {
Expand All @@ -87,23 +80,6 @@ public FixedOrderSelector(List<T> objects, @Nonnull RandomNumberGenerator randGe
}).collect(Collectors.toList());
}

/**
* This method actually initializes the Selector, called
* on first selection (first call of {@link #nextItem()}.
*
* @return the selector deciding which element is selected first
*/
private StochasticSelector<Item<T>> init() {
// initialize weights with base values
double sumWeights = items.stream().mapToDouble((item) -> item.object.getWeight()).sum();
// set normalized weights, if no weights have been set, apply same weight to all items
items.forEach((item) -> item.normalizedWeight = sumWeights > 0 ? (item.object.getWeight() / sumWeights) : (1d / items.size()));

items = items.stream().filter(item -> item.getWeight() > 0).collect(Collectors.toList());

return new StochasticSelector<>(items, randomNumberGenerator);
}

/**
* Request the next item, if no item has been selected yet the weights will be
* initialized and the first item will be randomly selected. Otherwise, the next
Expand All @@ -115,18 +91,11 @@ private StochasticSelector<Item<T>> init() {
public T nextItem() {

Item<T> selectedItem;

if (totalSelections == 0) {
StochasticSelector<Item<T>> firstItemSelector = init();
selectedItem = firstItemSelector.nextItem();
items = normalizeWeights(items);
selectedItem = selectFirstItem();
} else {
Comparator<Item<T>> compareBySelectionToWeightRatio =
Comparator.comparingDouble((item) -> (item.selections / (double) totalSelections) - item.getWeight());
Comparator<Item<T>> compareByPriority = Comparator.comparingInt((item) -> item.priority);
// choose item which has been selected the least according to its weight
selectedItem = items.stream()
.min(compareBySelectionToWeightRatio.thenComparing(compareByPriority))
.orElse(null);
selectedItem = selectNextItem();
}

if (selectedItem == null) {
Expand All @@ -143,4 +112,33 @@ public T nextItem() {
totalSelections++;
return selectedItem.object;
}

/**
* Normalizes the weights of all items to sum 1.
*/
private List<Item<T>> normalizeWeights(List<Item<T>> items) {
// initialize weights with base values
double sumWeights = items.stream().mapToDouble((item) -> item.object.getWeight()).sum();
// set normalized weights, if no weights have been set, apply same weight to all items
items.forEach((item) -> item.normalizedWeight = sumWeights > 0 ? (item.object.getWeight() / sumWeights) : (1d / items.size()));

return items.stream().filter(item -> item.getWeight() > 0).collect(Collectors.toList());
}

private Item<T> selectFirstItem() {
Comparator<Item<T>> compareByWeight =Comparator.comparingDouble(Item::getWeight);
Comparator<Item<T>> compareByPriority = Comparator.comparingInt((item) -> item.priority);
// choose first item according to weight. if two or items have the same weight, the first item in the list will be chosen
return items.stream().max(compareByWeight.thenComparing(compareByPriority)).orElse(null);
}

private Item<T> selectNextItem() {
Comparator<Item<T>> compareBySelectionToWeightRatio =
Comparator.comparingDouble((item) -> (item.selections / (double) totalSelections) - item.getWeight());
Comparator<Item<T>> compareByPriority = Comparator.comparingInt((item) -> item.priority);
// choose item which has been selected the least according to its weight
return items.stream()
.min(compareBySelectionToWeightRatio.thenComparing(compareByPriority))
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ public class CMappingConfiguration {
*/
public double scaleTraffic = 1.0;

/**
* Determines if selection of a vehicles type when spawning follows a fixedOrder or stochastic model.
* When set to true the spawning-process will choose exactly the same types with every execution.
* When set to false the order of types may be different and selected weights will be reached more slowly.
*/
public boolean fixedOrder = false;

/**
* Defines the point in time to start spawning vehicles. If not set (default),
* all vehicles will be spawned according to the vehicles configuration. [s]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,6 @@ public enum SpawningMode {
*/
public String typeDistribution;

/**
* Determines if selection of a vehicles type when spawning follows a fixedOrder or stochastic model.
* When set to true the spawning-process will choose exactly the same types with every execution.
* When set to false the order of types may be different and selected weights will be reached more slowly.
*/
public boolean fixedOrder = true;

/**
* The index of the connection of the route where the vehicle will start on.
*/
Expand Down Expand Up @@ -228,7 +221,6 @@ public boolean equals(Object o) {
.append(startingTime, that.startingTime)
.append(targetFlow, that.targetFlow)
.append(departSpeed, that.departSpeed)
.append(fixedOrder, that.fixedOrder)
.append(departConnectionIndex, that.departConnectionIndex)
.append(pos, that.pos)
.append(maxTime, that.maxTime)
Expand Down Expand Up @@ -260,7 +252,6 @@ public int hashCode() {
.append(departSpeedMode)
.append(types)
.append(typeDistribution)
.append(fixedOrder)
.append(departConnectionIndex)
.append(pos)
.append(route)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@
"minimum": 0,
"default": 1.0
},
"fixedOrder": {
"description": "Determines if selection of a vehicles type when spawning follows a fixedOrder or stochastic model. When set to true the spawning-process will choose exactly the same types with every execution. When set to false (default) the order of types follow a stochastic manner and will be different each time, however, selected weights may be reached more slowly.",
"type": "boolean",
"default": false
},
"adjustStartingTimes": {
"description": "If set to true and if the parameter start is set, the starting times of each spawner is adjusted accordingly, so that we shouldn't wait in case that the simulation starting time and spawner starting time are widely spread out. All spawners before start will be completely ignored then.",
"type": "boolean",
Expand Down Expand Up @@ -333,11 +338,6 @@
],
"default": "CONSTANT"
},
"fixedOrder": {
"description": "Determines if selection of a vehicles type when spawning follows a fixedOrder or stochastic model. When set to true the spawning-process will choose exactly the same types with every execution. When set to false the order of types may be different and selected weights will be reached more slowly.",
"type": "boolean",
"default": true
},
"departConnectionIndex": {
"description": "The index of the connection of the route where the vehicle will start on.",
"type": "number",
Expand Down Expand Up @@ -403,11 +403,6 @@
"type": "array",
"items": { "$ref": "#/definitions/prototype" }
},
"fixedOrder": {
"description": "If fixedOrder is true the spawning-process will be exactly the same with every execution. If left false the order is different and the selected weights will be reached slower than in the fixedOrder mode.",
"type": "boolean",
"default": true
},
"odValues": {
"description": "Values for the OD-matrix. Unit should be vehicles/hour.",
"type": "array",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public void initializeWithMappingFile_assertSpawningInteractions() throws Except
assertEquals(8 * TIME.SECOND, lastTimeAdvanceGrant);

ambassador.advanceTime(lastTimeAdvanceGrant);
assertVehicleRegistration();
assertVehicleRegistration("org.eclipse.mosaic.app.tutorials.barnim.WeatherWarningApp");

assertEquals(11 * TIME.SECOND, lastTimeAdvanceGrant);

Expand Down Expand Up @@ -197,13 +197,13 @@ public void initializeWithMappingFile_timeSpanConfigured() throws Exception {
assertVehicleRegistration("Car2App");

ambassador.advanceTime(17 * TIME.SECOND);
assertVehicleRegistration("Car2App");
assertVehicleRegistration("Car1App");

ambassador.advanceTime(20 * TIME.SECOND);
assertVehicleRegistration("Car1App");
assertVehicleRegistration("Car2App");

ambassador.advanceTime(23 * TIME.SECOND);
assertVehicleRegistration("Car2App");
assertVehicleRegistration("Car1App");

ambassador.advanceTime(26 * TIME.SECOND);
assertNull(lastReceivedInteraction);
Expand Down
Loading

0 comments on commit 4c3b72d

Please sign in to comment.