Commit e157b605 authored by Tobias Meuser's avatar Tobias Meuser
Browse files

Merged tm/vehicular-services into master-integration

parents 8b80fdfa 0fbcbc65
/*
* Copyright (c) 2005-2010 KOM � Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.relevance.vehicular.impl;
import java.util.List;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.information.PointInformation;
public class SimpleQoIBasedImpactFunction<T extends PointInformation> extends AbstractQoIBasedImpactFunction<T> {
public SimpleQoIBasedImpactFunction(List<T> pInformation, double pAccuracy, int pCacheSize) {
super(pInformation, pAccuracy, pCacheSize);
}
@Override
public double calculateImpact(T pInformation) {
return accuracy * super.calculateImpact(pInformation);
}
@Override
protected double determineB(Object pUnused, Object pPossibleValue, double pChangeRate, double pRate,
double pErrorProbability, double pNumberOfMessages, double pCostWrongKeep, double pCostWrongChange) {
return determineB(pPossibleValue, pChangeRate, pRate, pErrorProbability, pNumberOfMessages, pCostWrongKeep,
pCostWrongChange, 1);
}
protected double determineB(Object pPossibleValue, double pChangeRate, double pRate, double pErrorProbability,
double pNumberOfMessages, double pCostWrongKeep, double pCostWrongChange, int reversed) {
if (pErrorProbability == 0 || pErrorProbability == 1 || pErrorProbability == 0.5) {
return Double.NaN;
}
double b;
int optimalAmount = getOptimalMessageAmountForSwitch(pChangeRate, pErrorProbability, pCostWrongKeep,
pCostWrongChange);
if (optimalAmount == 1) {
return Double.NEGATIVE_INFINITY;
}
boolean first = true;
double leftSide;
double rightSide;
double step = 5;
if (pErrorProbability < 0.5) {
b = -2 * step * reversed;
} else {
b = 2 * step * reversed;
}
int similar = 0;
double lastDifference = -1;
do {
leftSide = calculateWeightingForOldState(optimalAmount, pRate, pErrorProbability, pNumberOfMessages, b);
rightSide = calculateWeightingForNewState(optimalAmount, pRate, pErrorProbability, pNumberOfMessages, b);
if (Math.abs(Math.round((rightSide - leftSide) * ACCURACY_FACTOR)) == lastDifference) {
similar++;
} else {
lastDifference = Math.abs(Math.round((rightSide - leftSide) * ACCURACY_FACTOR));
similar = 0;
}
if (Double.isNaN(leftSide) || Double.isNaN(rightSide) || similar > 100) {
if (reversed != -1) {
double determineB = determineB(pPossibleValue, pChangeRate, pRate, pErrorProbability,
pNumberOfMessages, pCostWrongKeep, pCostWrongChange, -1);
if (!Double.isNaN(determineB)) {
return determineB;
} else {
return b;
}
} else {
return Double.NaN;
}
}
leftSide = Math.round(leftSide * ACCURACY_FACTOR);
rightSide = Math.round(rightSide * ACCURACY_FACTOR);
if (leftSide > rightSide) {
if (b < 0) {
b -= step;
if (!first) {
step *= 0.5;
}
} else {
b -= step;
step *= 0.5;
first = false;
}
} else if (leftSide < rightSide) {
if (b > 0) {
b += step;
if (!first) {
step *= 0.5;
}
} else {
b += step;
step *= 0.5;
first = false;
}
} else {
break;
}
} while (true);
return b;
}
public double calculateWeightingForOldState(int optimalMessageAmount, double rate, double errorProbability,
double pNumberOfMessages, double b) {
double impact = 0;
for (int a = optimalMessageAmount; a < pNumberOfMessages; a++) {
impact += calculateImpact(errorProbability, pNumberOfMessages, pNumberOfMessages - a, b);
}
return impact;
}
public double calculateWeightingForNewState(int optimalMessageAmount, double rate, double errorProbability,
double numberOfMessages, double b) {
double impact = 0;
for (int a = 0; a < optimalMessageAmount; a++) {
impact += calculateImpact(errorProbability, numberOfMessages, numberOfMessages - a, b);
}
return impact;
}
}
/*
* Copyright (c) 2005-2010 KOM � Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.relevance.vehicular.impl;
import java.util.List;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.TemporalDependencyMatrix;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.measurement.MeasurementDistributionTypeContainer;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.vectoral.VectoralProperty;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.information.PointInformation;
public class VectoralQoIBasedImpactFunction<T extends PointInformation> extends AbstractQoIBasedImpactFunction<T> {
private static final double MIN_STEP = 0.0001;
public VectoralQoIBasedImpactFunction(List<T> pInformation, double pAccuracy, int pMaxCacheSize,
Object pLastDecision) {
super(pInformation, pAccuracy, pMaxCacheSize, pLastDecision);
}
@Override
protected double determineB(Object pTemplate, Object pPossibleValue, double pChangeRate, double pRate,
double pErrorProbability, double pNumberOfMessages, double pCostWrongKeep, double pCostWrongChange) {
if (!(pTemplate instanceof VectoralProperty)) {
throw new AssertionError("Vectoral property required as input for pTemplate, but got "
+ pTemplate.getClass().getSimpleName());
}
return determineBWithCast((VectoralProperty) pTemplate, (int) pPossibleValue, pChangeRate, pRate,
pErrorProbability, pNumberOfMessages, pCostWrongKeep, pCostWrongChange);
}
public double determineBWithCast(VectoralProperty pTemplate, int pPossibleValue, double change, double rate,
double errorProbability, double pNumberOfMessages, double costSlow, double costFast) {
if (errorProbability == 0 || errorProbability == 1 || errorProbability == 0.5) {
return Double.NaN;
}
if (_lastDecision != null) {
if (pPossibleValue == ((VectoralProperty) _lastDecision).getMostProbableIndex()) {
return Double.NaN;
}
} else {
if (pPossibleValue == pTemplate.getDefaultIndex()) {
return Double.NaN;
}
}
double b;
double p_c = change;
int optimalAmount = getOptimalMessageAmountForSwitch(p_c, errorProbability, costSlow, costFast);
if (maxCacheSize <= optimalAmount) {
return Double.POSITIVE_INFINITY;
}
if ((int) pNumberOfMessages <= optimalAmount) {
return Double.POSITIVE_INFINITY;
}
if (optimalAmount == 1) {
return Double.NEGATIVE_INFINITY;
}
boolean first = true;
double step = 10;
if (errorProbability < 0.5) {
b = -1 * step;
} else {
b = 1 * step;
}
do {
VectoralProperty valueAfterN = calculateMostProbable(pTemplate, pPossibleValue, optimalAmount, rate,
errorProbability, pNumberOfMessages, b);
double probableValueAfterN = Double.NaN;
if (valueAfterN != null) {
probableValueAfterN = valueAfterN.getMostProbableIndex();
}
VectoralProperty valueBeforeN = calculateMostProbable(pTemplate, pPossibleValue, optimalAmount - 1, rate,
errorProbability, pNumberOfMessages, b);
double probableValueBeforeN = Double.NaN;
if (valueBeforeN != null) {
probableValueBeforeN = valueBeforeN.getMostProbableIndex();
}
if (probableValueAfterN == pPossibleValue && probableValueAfterN != probableValueBeforeN) {
return b;
}
if (!Double.isNaN(probableValueBeforeN) && !Double.isNaN(probableValueBeforeN) && step >= MIN_STEP) {
if (probableValueAfterN != pPossibleValue) {
if (b < 0) {
b -= step;
if (!first) {
step *= 0.5;
}
} else {
b -= step;
step *= 0.5;
first = false;
}
} else if (probableValueAfterN == pPossibleValue && probableValueAfterN == probableValueBeforeN) {
if (b > 0) {
b += step;
if (!first) {
step *= 0.5;
}
} else {
b += step;
step *= 0.5;
first = false;
}
} else {
return Double.NaN;
}
} else {
return Double.NaN;
}
} while (true);
}
public VectoralProperty calculateMostProbable(VectoralProperty pTemplate, int pNewValue, int optimalMessageAmount,
double rate, double errorProbability, double pNumberOfMessages, double b) {
VectoralProperty currentProperty = null;
for (int a = optimalMessageAmount + 1; a <= Math.min(pNumberOfMessages, maxCacheSize); a++) {
VectoralProperty jamProperty = pTemplate.clone();
if (_lastDecision != null) {
jamProperty.set(((VectoralProperty) _lastDecision).getMostProbableIndex(), accuracy,
MeasurementDistributionTypeContainer.getDistribution(pTemplate.getClass()));
} else {
jamProperty.set(pTemplate.getDefaultIndex(), accuracy,
MeasurementDistributionTypeContainer.getDistribution(pTemplate.getClass()));
}
TemporalDependencyMatrix temporalDependencyMatrix = jamProperty.getDependencyMatrix();
temporalDependencyMatrix = temporalDependencyMatrix.age((long) (a * rate));
double impact = calculateImpact(errorProbability, pNumberOfMessages, pNumberOfMessages - a, b);
if (Double.isNaN(impact)) {
return null;
}
temporalDependencyMatrix = modifyDependencyMatrix(temporalDependencyMatrix, impact);
jamProperty = (VectoralProperty) jamProperty.age(1, temporalDependencyMatrix);
if (currentProperty != null) {
currentProperty = (VectoralProperty) currentProperty.combine(jamProperty);
} else {
currentProperty = jamProperty;
}
}
for (int a = 0; a <= optimalMessageAmount; a++) {
VectoralProperty jamProperty = pTemplate.clone();
jamProperty.setGaussianWithAccuracy(pNewValue, accuracy);
TemporalDependencyMatrix temporalDependencyMatrix = jamProperty.getDependencyMatrix();
temporalDependencyMatrix = temporalDependencyMatrix.age((long) (a * rate));
double impact = calculateImpact(errorProbability, pNumberOfMessages, pNumberOfMessages - a, b);
if (Double.isNaN(impact)) {
return null;
}
temporalDependencyMatrix = modifyDependencyMatrix(temporalDependencyMatrix, impact);
jamProperty = (VectoralProperty) jamProperty.age(1, temporalDependencyMatrix);
if (currentProperty != null) {
currentProperty = (VectoralProperty) currentProperty.combine(jamProperty);
} else {
currentProperty = jamProperty;
}
}
return currentProperty;
}
private TemporalDependencyMatrix modifyDependencyMatrix(TemporalDependencyMatrix pDependencyMatrix,
double pImpact) {
TemporalDependencyMatrix result = pDependencyMatrix.clone();
double[][] dependencies = result.getDependencies();
for (int i = 0; i < dependencies.length; i++) {
double finalPercentages = 1.0 / dependencies[i].length;
for (int j = 0; j < dependencies[i].length; j++) {
dependencies[i][j] = finalPercentages
+ (pDependencyMatrix.getDependencies()[i][j] - finalPercentages) * pImpact;
}
}
return result;
}
}
......@@ -25,4 +25,6 @@ import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.En
public interface EnvironmentSensor extends SensorComponent {
Environment getEnvironment();
Environment getEnvironment(Class... pAllowedClasses);
}
......@@ -22,11 +22,12 @@
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.tudarmstadt.maki.simonstrator.api.Host;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.Environment;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.EnvironmentProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.EnvironmentProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.plugin.EnvironmentSensorPlugin;
/**
......@@ -57,13 +58,38 @@ public class ExtendableEnvironmentSensor implements EnvironmentSensor {
public void shutdown() {
}
public List<EnvironmentSensorPlugin> getPlugins() {
return Collections.unmodifiableList(_plugins);
}
@Override
public Environment getEnvironment() {
List<EnvironmentProperty> properties = new ArrayList<>();
for (EnvironmentSensorPlugin plugin : _plugins) {
properties.addAll(plugin.getEnvironmentProperties());
return getEnvironment(new Class[0]);
}
@Override
public Environment getEnvironment(Class... pAllowedClasses) {
if (pAllowedClasses.length == 0) {
List<EnvironmentProperty> properties = new ArrayList<>();
for (EnvironmentSensorPlugin plugin : _plugins) {
properties.addAll(plugin.getEnvironmentProperties());
}
return new Environment(properties);
} else {
List<EnvironmentProperty> properties = new ArrayList<>();
for (EnvironmentSensorPlugin plugin : _plugins) {
boolean allowed = false;
for (int i = 0; i < pAllowedClasses.length; i++) {
if (pAllowedClasses[i].isAssignableFrom(plugin.getSupportedClass())) {
allowed = true;
}
}
if (allowed) {
properties.addAll(plugin.getEnvironmentProperties());
}
}
return new Environment(properties);
}
return new Environment(properties);
}
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.benefit;
import de.tudarmstadt.maki.simonstrator.api.component.HostComponent;
import de.tudarmstadt.maki.simonstrator.api.component.pubsub.Notification;
public interface BenefitEstimator extends HostComponent {
double calculateBenefit(NodeProperties pProperties, Notification pNotification);
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.benefit;
public enum BenefitEstimatorType {
DECISION_BASED, INTERSECTION, VALUE_BASED, VALUE_ONLY
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.benefit;
import de.tudarmstadt.maki.simonstrator.api.common.graph.INodeID;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
public class NodeProperties {
private final INodeID _id;
private final Location _location;
public NodeProperties(INodeID pId) {
this(pId, null);
}
public NodeProperties(INodeID pId, Location pLocation) {
_id = pId;
_location = pLocation;
}
public INodeID getId() {
return _id;
}
public Location getLocation() {
return _location;
}
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.costs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.tudarmstadt.maki.simonstrator.api.component.pubsub.Notification;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.LocationBasedEnvironmentProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.RoadProperty;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.information.EnvironmentInformation;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.information.RoadInformation;
import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;
public class ConfigurableVehicularPropertyImpactEstimator implements VehicularPropertyImpactEstimator {
private Map<Class<?>, VehicularPropertyImpact> _properties = new HashMap<>();
public ConfigurableVehicularPropertyImpactEstimator() {
PropertyImpactEstimatorFactory.registerPropertyBenefitEstimator(this);
}
public void setPropertyImpact(VehicularPropertyImpact pImpact) {
try {
_properties.put(Class.forName(pImpact.getProperty()), pImpact);
} catch (ClassNotFoundException e) {
throw new AssertionError("Unknown class: ", e);
}
}
@Override
public double calculateImpactIfKnown(Class<? extends RoadProperty> pProperty) {
if (_properties.containsKey(pProperty)) {
return _properties.get(pProperty).getImpactKnown();
}
return 0;
}
@Override
public List<Double> getImpactLevels() {
List<Double> levels = new ArrayList<>();
for (VehicularPropertyImpact property : _properties.values()) {
if (!levels.contains(property.getImpactKnown())) {
levels.add(property.getImpactKnown());
}
}
Collections.sort(levels);
return levels;
}
@Override
public double calculateImpactIfUnknown(Class<? extends RoadProperty> pProperty) {
if (_properties.containsKey(pProperty)) {
return _properties.get(pProperty).getImpactUnknown();
}
return 0;
}
@Override
public double calculateImpactIfKnown(Notification pNotification) {
EnvironmentInformation<? extends LocationBasedEnvironmentProperty> environmentInformation = EnvironmentInformation
.createFromNotification(pNotification);
if (environmentInformation instanceof RoadInformation) {
return calculateImpactIfKnown(((RoadInformation) environmentInformation).getValue());
}
throw new AssertionError("Cost calculation only valid for road information!");
}
@Override
public double calculateImpactIfUnknown(Notification pNotification) {
EnvironmentInformation<? extends LocationBasedEnvironmentProperty> environmentInformation = EnvironmentInformation
.createFromNotification(pNotification);
if (environmentInformation instanceof RoadInformation) {
return calculateImpactIfUnknown(((RoadInformation) environmentInformation).getValue());
}
throw new AssertionError("Cost calculation only valid for road information!");
}
public static class VehicularPropertyImpact {
private String _property;
private double _impactKnown;
private double _impactUnknown;
@XMLConfigurableConstructor({ "property", "known", "unknown" })
public VehicularPropertyImpact(String pProperty, double pImpactKnown, double pImpactUnknown) {
_property = pProperty;
_impactKnown = pImpactKnown;
_impactUnknown = pImpactUnknown;
}
@XMLConfigurableConstructor({ "property", "known" })
public VehicularPropertyImpact(String pProperty, double pImpactKnown) {
_property = pProperty;
_impactKnown = pImpactKnown;
_impactUnknown = pImpactKnown;
}
public String getProperty() {
return _property;
}
public double getImpactKnown() {
return _impactKnown;
}
public double getImpactUnknown() {
return _impactUnknown;
}
}
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.costs;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
public class DecisionPoint {
private Location _location;
private double _benefit;
public DecisionPoint(Location pLocation, double pBenefit) {
_location = pLocation;
_benefit = pBenefit;
}
public Location getLocation() {
return _location;
}
public void setLocation(Location pLocation) {
_location = pLocation;
}
public double getBenefit() {
return _benefit;
}
public void setBenefit(double pBenefit) {
_benefit = pBenefit;
}
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.costs;
import java.util.ArrayList;
import java.util.List;
import de.tudarmstadt.maki.simonstrator.api.component.pubsub.Notification;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.BumpProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.FogProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.HazardProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.JamProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.LocationBasedEnvironmentProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.RainProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.RoadProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.TrafficSignProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.VehicleProperty;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.information.EnvironmentInformation;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.information.RoadInformation;
public class DefaultVehicularPropertyImpactEstimator implements VehicularPropertyImpactEstimator {
@Override
public double calculateImpactIfKnown(Class<? extends RoadProperty> pProperty) {
if (pProperty.equals(JamProperty.class)) {
return Math.pow(10, 2);
} else if (pProperty.equals(RainProperty.class)) {
return 1;
} else if (pProperty.equals(FogProperty.class)) {
return 1;
} else if (pProperty.equals(HazardProperty.class)) {
return Math.pow(10, 3);
} else if (pProperty.equals(TrafficSignProperty.class)) {
return 10;
} else if (pProperty.equals(VehicleProperty.class)) {
return 0;
}
return 10;
}
@Override
public List<Double> getImpactLevels() {
List<Double> levels = new ArrayList<>();
levels.add(1d);
levels.add(10d);
levels.add(100d);
levels.add(1000d);
return levels;
}
@Override
public double calculateImpactIfKnown(Notification pNotification) {
EnvironmentInformation<? extends LocationBasedEnvironmentProperty> environmentInformation = EnvironmentInformation
.createFromNotification(pNotification);
if (environmentInformation instanceof RoadInformation) {
return calculateImpactIfKnown(((RoadInformation) environmentInformation).getValue());
}
throw new AssertionError("Cost calculation only valid for road information!");
}
@Override
public double calculateImpactIfUnknown(Notification pNotification) {
EnvironmentInformation<? extends LocationBasedEnvironmentProperty> environmentInformation = EnvironmentInformation
.createFromNotification(pNotification);
if (environmentInformation instanceof RoadInformation) {
return calculateImpactIfUnknown(((RoadInformation) environmentInformation).getValue());
}
throw new AssertionError("Cost calculation only valid for road information!");
}
@Override
public double calculateImpactIfUnknown(Class<? extends RoadProperty> pProperty) {
if (pProperty.equals(JamProperty.class)) {
return Math.pow(10, 4);
} else if (pProperty.equals(BumpProperty.class)) {
return Math.pow(10, -2);
} else if (pProperty.equals(HazardProperty.class)) {
return Math.pow(10, 6);
}
return 1;
}
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.costs;
import de.tudarmstadt.maki.simonstrator.api.component.pubsub.Notification;
public interface PropertyImpactEstimator {
double calculateImpactIfKnown(Notification pNotification);
double calculateImpactIfUnknown(Notification pNotification);
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.costs;
public class PropertyImpactEstimatorFactory {
private static PropertyImpactEstimator _costEstimator = null;
public static void registerPropertyBenefitEstimator(PropertyImpactEstimator pBenefitEstimator) {
synchronized (PropertyImpactEstimatorFactory.class) {
_costEstimator = pBenefitEstimator;
}
}
public static PropertyImpactEstimator getPropertyBenefitEstimator() {
synchronized (PropertyImpactEstimatorFactory.class) {
if (_costEstimator == null) {
System.err.println("No property impact estimator set! Falling back to "
+ DefaultVehicularPropertyImpactEstimator.class.getSimpleName());
_costEstimator = new DefaultVehicularPropertyImpactEstimator();
}
}
return _costEstimator;
}
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.jam;
/**
* @author Tobias Meuser (tobias.meuser@kom.tu-darmstadt.de)
* @version 1.0 at 24.04.2018
*
*/
public class JamInformationContainer {
public static long EVENT_DURATION = 0;
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.costs;
public class VehicleCostPreference {
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.costs;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.RoadNetworkEdge;
public class VehicularDecisionPoint extends DecisionPoint {
private RoadNetworkEdge _eventLocation;
public VehicularDecisionPoint(RoadNetworkEdge pEventLocation, Location pLocation, double pBenefit) {
super(pLocation, pBenefit);
_eventLocation = pEventLocation;
}
public RoadNetworkEdge getEventEdge() {
return _eventLocation;
}
public RoadNetworkEdge getDecisionPoint() {
return getEventEdge();
}
public void setEdge(RoadNetworkEdge pEdge) {
_eventLocation = pEdge;
}
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.costs;
import java.util.ArrayList;
import java.util.List;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.RoadNetwork;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.RoadNetworkEdge;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.RoadNetworkRoute;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.paths.VehiclePathTracker;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.paths.VehiclePathTrackerFactory;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.routing.DijkstraAlgorithm;
import de.tudarmstadt.maki.simonstrator.api.component.vehicular.roadnetwork.routing.RoutingAlgorithm;
public class VehicularDetouringDecisionPoint extends VehicularDecisionPoint {
private RoutingAlgorithm routing = new DijkstraAlgorithm();
private static final boolean USE_STATIC_BENEFIT_FOR_DETOUR = true;
private RoadNetworkEdge _detourLocation;
public VehicularDetouringDecisionPoint(RoadNetworkEdge pDetourLocation, RoadNetworkEdge pEventLocation,
Location pLocation, double pBenefit) {
super(pEventLocation, pLocation, pBenefit);
_detourLocation = pDetourLocation;
}
@Override
public RoadNetworkEdge getDecisionPoint() {
return _detourLocation;
}
public double calculateBenefit(RoadNetworkRoute pPath) {
VehiclePathTracker tracker = VehiclePathTrackerFactory.getVehiclePathTracker();
RoadNetworkEdge start = _detourLocation;
RoadNetworkEdge destination = pPath.getDestination();
RoadNetworkEdge edgeToAvoid = getEventEdge();
List<RoadNetworkRoute> knownRoutes = new ArrayList<>();
knownRoutes.add(pPath);
List<RoadNetworkEdge> edgesToAvoid = new ArrayList<>();
edgesToAvoid.add(edgeToAvoid);
RoadNetworkRoute newRoute = routing.findRoute(RoadNetwork.CURRENT_ROAD_NETWORK, start, destination, knownRoutes,
edgesToAvoid);
if (newRoute == null) {
return 0;
}
List<RoadNetworkEdge> oldSubEdges = new ArrayList<>();
boolean add = false;
for (RoadNetworkEdge roadNetworkEdge : pPath.getRoute()) {
if (roadNetworkEdge.equals(start)) {
add = true;
}
if (add) {
oldSubEdges.add(roadNetworkEdge);
}
}
RoadNetworkRoute oldRoute = new RoadNetworkRoute(oldSubEdges);
double newRouteTravelTime = newRoute.getTravelTime();
double routeTravelTimeWithEvent = oldRoute.getTravelTime();
double routeTravelTimeWithoutEvent = oldRoute.getTravelTime();
boolean found = false;
for (int i = 0; i < oldRoute.getRoute().size() - 1; i++) {
if (oldRoute.getRoute().get(i).equals(edgeToAvoid)) {
routeTravelTimeWithEvent -= tracker.getTravelTime(oldRoute.getRoute().get(i),
oldRoute.getRoute().get(i + 1));
routeTravelTimeWithoutEvent -= tracker.getTravelTime(oldRoute.getRoute().get(i),
oldRoute.getRoute().get(i + 1));
found = true;
}
}
if (found) {
routeTravelTimeWithEvent += (edgeToAvoid.getLength() / edgeToAvoid.getMaxSpeed());
routeTravelTimeWithoutEvent += (edgeToAvoid.getLength() / edgeToAvoid.getOriginalMaxSpeed());
}
if (USE_STATIC_BENEFIT_FOR_DETOUR) {
return super.getBenefit() + (routeTravelTimeWithoutEvent - newRouteTravelTime) * 10;
} else {
return (routeTravelTimeWithEvent - newRouteTravelTime) * 10;
}
}
public double calculateOverhead(RoadNetworkRoute pPath) {
VehiclePathTracker tracker = VehiclePathTrackerFactory.getVehiclePathTracker();
RoadNetworkEdge start = _detourLocation;
RoadNetworkEdge destination = pPath.getDestination();
RoadNetworkEdge edgeToAvoid = getEventEdge();
List<RoadNetworkRoute> knownRoutes = new ArrayList<>();
knownRoutes.add(pPath);
List<RoadNetworkEdge> edgesToAvoid = new ArrayList<>();
edgesToAvoid.add(edgeToAvoid);
RoadNetworkRoute newRoute = routing.findRoute(RoadNetwork.CURRENT_ROAD_NETWORK, start, destination, knownRoutes,
edgesToAvoid);
if (newRoute == null) {
return 0;
}
List<RoadNetworkEdge> oldSubEdges = new ArrayList<>();
boolean add = false;
for (RoadNetworkEdge roadNetworkEdge : pPath.getRoute()) {
if (roadNetworkEdge.equals(start)) {
add = true;
}
if (add) {
oldSubEdges.add(roadNetworkEdge);
}
}
RoadNetworkRoute oldRoute = new RoadNetworkRoute(oldSubEdges);
double newRouteTravelTime = newRoute.getTravelTime();
double routeTravelTimeWithoutEvent = oldRoute.getTravelTime();
boolean found = false;
for (int i = 0; i < oldRoute.getRoute().size() - 1; i++) {
if (oldRoute.getRoute().get(i).equals(edgeToAvoid)) {
routeTravelTimeWithoutEvent -= tracker.getTravelTime(oldRoute.getRoute().get(i),
oldRoute.getRoute().get(i + 1));
found = true;
}
}
if (found) {
routeTravelTimeWithoutEvent += (edgeToAvoid.getLength() / edgeToAvoid.getOriginalMaxSpeed());
}
return (routeTravelTimeWithoutEvent - newRouteTravelTime) * 10;
}
@Override
public double getBenefit() {
throw new UnsupportedOperationException();
}
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.costs;
import java.util.List;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.RoadProperty;
public interface VehicularPropertyImpactEstimator extends PropertyImpactEstimator {
default double calculateImpactIfKnown(RoadProperty pProperty) {
return calculateImpactIfKnown(pProperty.getClass());
}
double calculateImpactIfKnown(Class<? extends RoadProperty> pProperty);
default double calculateImpactIfUnknown(RoadProperty pProperty) {
return calculateImpactIfUnknown(pProperty.getClass());
}
double calculateImpactIfUnknown(Class<? extends RoadProperty> pProperty);
List<Double> getImpactLevels();
}
......@@ -25,6 +25,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.EnvironmentProperty;
/**
* @author Tobias Meuser (tobias.meuser@kom.tu-darmstadt.de)
* @version 1.0 at 27.02.2018
......
......@@ -25,6 +25,8 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import Jama.Matrix;
/**
* @author Tobias Meuser (tobias.meuser@kom.tu-darmstadt.de)
* @version 1.0 at 12.04.2018
......@@ -82,6 +84,24 @@ public class TemporalDependencyMatrix implements Cloneable {
return _agedMatrices.get(pAge);
}
public TemporalDependencyMatrix calculateInverse() {
Matrix dependencyMatrix = new Matrix(_dependencies);
Matrix unitMatrix = new Matrix(age(0)._dependencies);
Matrix inverse = dependencyMatrix.solve(unitMatrix);
double[][] inverseArray = inverse.getArray();
return new TemporalDependencyMatrix(inverseArray);
}
public TemporalDependencyMatrix combine(TemporalDependencyMatrix pOther) {
Matrix ownMatrix = new Matrix(_dependencies);
Matrix otherMatrix = new Matrix(pOther._dependencies);
Matrix combined = ownMatrix.times(otherMatrix);
double[][] combinedArray = combined.getArray();
return new TemporalDependencyMatrix(combinedArray);
}
public double[][] cloneArray(double[][] pDependencies) {
double[][] current = new double[pDependencies.length][];
......@@ -124,4 +144,30 @@ public class TemporalDependencyMatrix implements Cloneable {
public double getChangeProbability(int pI) {
return 1 - _dependencies[pI][pI];
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < _dependencies.length; i++) {
if (i != 0) {
buffer.append("\n");
}
buffer.append(Arrays.toString(_dependencies[i]));
}
return buffer.toString();
}
public TemporalDependencyMatrix average(TemporalDependencyMatrix pOneStepDependencyMatrix2, double weight) {
TemporalDependencyMatrix result = clone();
for (int i = 0; i < result._dependencies.length; i++) {
for (int j = 0; j < result._dependencies[i].length; j++) {
result._dependencies[i][j] = (weight * result._dependencies[i][j]
+ pOneStepDependencyMatrix2._dependencies[i][j])
/ (weight + 1);
}
}
return result;
}
}
/*
* Copyright (c) 2005-2010 KOM � Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.aggregation;
public class AggregationInformation {
private int numberOfIndiviualMeasurements;
private long _minTimestamp;
public AggregationInformation(int pNumberOfIndiviualMeasurements, long pMinTimestamp) {
numberOfIndiviualMeasurements = pNumberOfIndiviualMeasurements;
_minTimestamp = pMinTimestamp;
}
public int getNumberOfIndiviualMeasurements() {
return numberOfIndiviualMeasurements;
}
public void setNumberOfIndiviualMeasurements(int pNumberOfIndiviualMeasurements) {
numberOfIndiviualMeasurements = pNumberOfIndiviualMeasurements;
}
public long getMinTimestamp() {
return _minTimestamp;
}
public void setMinTimestamp(long pMinTimestamp) {
_minTimestamp = pMinTimestamp;
}
}
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of Simonstrator.KOM.
*
* Simonstrator.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.jam;
import java.util.HashMap;
import java.util.Map;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.data.properties.RoadProperty;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.environment.generator.lifetime.LifetimeDistribution;
/**
* @author Tobias Meuser (tobias.meuser@kom.tu-darmstadt.de)
* @version 1.0 at 24.04.2018
*
*/
public class EventInformationContainer {
private static Map<Class<? extends RoadProperty>, Long> durations = new HashMap<>();
private static Map<Class<? extends RoadProperty>, LifetimeDistribution> lifetimeDistribution = new HashMap<>();
private static Map<Class<? extends RoadProperty>, Double> disseminationRange = new HashMap<>();
public static void registerEventDuration(Class<? extends RoadProperty> pClass, long pDuration) {
if (!durations.containsKey(pClass)) {
durations.put(pClass, pDuration);
} else {
throw new AssertionError(pClass + " has already been registered!");
}
}
public static void registerEventRange(Class<? extends RoadProperty> pClass, double pRange) {
if (!disseminationRange.containsKey(pClass)) {
disseminationRange.put(pClass, pRange);
} else {
throw new AssertionError(pClass + " has already been registered!");
}
}
public static void registerEventLifetimeDistribution(Class<? extends RoadProperty> pClass,
LifetimeDistribution pLifetimeDistribution) {
if (!lifetimeDistribution.containsKey(pClass)) {
lifetimeDistribution.put(pClass, pLifetimeDistribution);
} else {
throw new AssertionError(pClass + " has already been registered!");
}
}
public static long getEventDuration(Class<? extends RoadProperty> pClass) {
if (durations.containsKey(pClass)) {
return durations.get(pClass);
}
throw new AssertionError(pClass + " has not been registered!");
}
public static double getEventRange(Class<? extends RoadProperty> pClass) {
if (disseminationRange.containsKey(pClass)) {
return disseminationRange.get(pClass);
}
return Double.POSITIVE_INFINITY;
}
public static LifetimeDistribution getEventLifetimeDistribution(Class<? extends RoadProperty> pClass) {
if (lifetimeDistribution.containsKey(pClass)) {
return lifetimeDistribution.get(pClass);
}
return null;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment