Skip to content

Commit

Permalink
target can be configured, improve construction heuristic
Browse files Browse the repository at this point in the history
  • Loading branch information
rakow committed Apr 1, 2024
1 parent 18d1f50 commit 9d9fdbe
Show file tree
Hide file tree
Showing 12 changed files with 559 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package org.matsim.application;

import org.matsim.core.config.ReflectiveConfigGroup;

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

/**
* Categorize values into groups.
*/
public final class Category {

private static final Set<String> TRUE = Set.of("true", "yes", "1", "on", "y", "j", "ja");
private static final Set<String> FALSE = Set.of("false", "no", "0", "off", "n", "nein");

/**
* Unique values of the category.
*/
private final Set<String> values;

/**
* Groups of values that have been subsumed under a single category.
* These are values separated by ,
*/
private final Map<String, String> grouped;


/**
* Regular expressions for each category.
*/
private final Map<String, Pattern> regex;

/**
* Range categories.
*/
private final List<Range> ranges;

public Category(Set<String> values) {
this.values = values;
this.grouped = new HashMap<>();
this.regex = new HashMap<>();
for (String v : values) {
if (v.contains(",")) {
String[] grouped = v.split(",");
for (String g : grouped) {
this.grouped.put(g, v);
}
}

if (v.startsWith("/") && v.endsWith("/")) {
this.regex.put(v, Pattern.compile(v.substring(1, v.length() - 1), Pattern.CASE_INSENSITIVE));
}
}

boolean range = this.values.stream().allMatch(v -> v.contains("-") || v.contains("+"));
if (range) {
ranges = new ArrayList<>();
for (String value : this.values) {
if (value.contains("-")) {
String[] parts = value.split("-");
ranges.add(new Range(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]), value));
} else if (value.contains("+")) {
ranges.add(new Range(Double.parseDouble(value.replace("+", "")), Double.POSITIVE_INFINITY, value));
}
}

ranges.sort(Comparator.comparingDouble(r -> r.left));
} else
ranges = null;


// Check if all values are boolean
if (values.stream().allMatch(v -> TRUE.contains(v.toLowerCase()) || FALSE.contains(v.toLowerCase()))) {
for (String value : values) {
Set<String> group = TRUE.contains(value.toLowerCase()) ? TRUE : FALSE;
for (String g : group) {
this.grouped.put(g, value);
}
}
}
}

/**
* Create categories from config parameters.
*/
public static Map<String, Category> fromConfigParams(Collection<? extends ReflectiveConfigGroup> params) {

Map<String, Set<String>> categories = new HashMap<>();

// Collect all values
for (ReflectiveConfigGroup parameter : params) {
for (Map.Entry<String, String> kv : parameter.getParams().entrySet()) {
categories.computeIfAbsent(kv.getKey(), k -> new HashSet<>()).add(kv.getValue());
}
}

return categories.entrySet().stream()
.collect(HashMap::new, (m, e) -> m.put(e.getKey(), new Category(e.getValue())), HashMap::putAll);
}

/**
* Categorize a single value.
*/
public String categorize(Object value) {

if (value == null)
return null;

if (value instanceof Boolean) {
// Booleans and synonyms are in the group map
return categorize(((Boolean) value).toString().toLowerCase());
} else if (value instanceof Number) {
return categorizeNumber((Number) value);
} else {
String v = value.toString();
if (values.contains(v))
return v;
else if (grouped.containsKey(v))
return grouped.get(v);
else {
for (Map.Entry<String, Pattern> kv : regex.entrySet()) {
if (kv.getValue().matcher(v).matches())
return kv.getKey();
}
}

try {
double d = Double.parseDouble(v);
return categorizeNumber(d);
} catch (NumberFormatException e) {
return null;
}
}
}

private String categorizeNumber(Number value) {

if (ranges != null) {
for (Range r : ranges) {
if (value.doubleValue() >= r.left && value.doubleValue() < r.right)
return r.label;
}
}

// Match string representation
String v = value.toString();
if (values.contains(v))
return v;
else if (grouped.containsKey(v))
return grouped.get(v);


// Convert the number to a whole number, which will have a different string representation
if (value instanceof Float || value instanceof Double) {
return categorizeNumber(value.longValue());
}

return null;
}

@Override
public String toString() {
return "Category{" +
"values=" + values +
(grouped != null && !grouped.isEmpty() ? ", grouped=" + grouped : "") +
(regex != null && !regex.isEmpty() ? ", regex=" + regex : "") +
(ranges != null && !ranges.isEmpty() ? ", ranges=" + ranges : "") +
'}';
}

/**
* Number range.
*
* @param left Left bound of the range.
* @param right Right bound of the range. (exclusive)
* @param label Label of this group.
*/
private record Range(double left, double right, String label) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.matsim.modechoice;

import com.google.common.base.Joiner;
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
import jakarta.validation.constraints.Positive;
import org.matsim.application.Category;
import org.matsim.core.config.ReflectiveConfigGroup;
import org.matsim.utils.objectattributes.attributable.Attributes;

import java.util.Map;
import java.util.Objects;

/**
* Target parameters for mode shares.
* This group allows arbitrary attributes to be defined, which are matched against person attributes.
*/
public final class ModeTargetParameters extends ReflectiveConfigGroup {

public static final String GROUP_NAME = "modeTargetParameters";

private static final String SHARES = "shares";

private static Joiner.MapJoiner JOINER = Joiner.on(",").withKeyValueSeparator("=");

@Parameter
@Positive
@Comment("Allowed tolerance for mode share.")
public double tolerance = 0.01;

private Object2DoubleMap<String> shares = new Object2DoubleOpenHashMap<>();

public ModeTargetParameters() {
super(GROUP_NAME, true);
}

public ModeTargetParameters(String subpopulation, Map<String, Double> shares) {
super(GROUP_NAME, true);
this.shares.putAll(shares);
this.addParam("subpopulation", subpopulation);
}

/**
* Name of the config group.
*/
public String getGroupName() {
Map<String, String> params = getParams();
params.remove("shares");
params.remove("tolerance");
return JOINER.join(params);
}

public Object2DoubleMap<String> getShares() {
return shares;
}

@StringSetter(SHARES)
void setShares(String shares) {
this.shares = new Object2DoubleOpenHashMap<>();
String[] parts = shares.split(",");
for (String part : parts) {
String[] kv = part.split("=");
this.shares.put(kv[0], Double.parseDouble(kv[1]));
}
}

@StringGetter(SHARES)
String getSharesString() {
return JOINER.join(shares);
}


/**
* Match attributes from an object with parameters defined in config.
*/
public boolean matchPerson(Attributes attr, Map<String, Category> categories) {

for (Map.Entry<String, String> e : getParams().entrySet()) {
if (e.getKey().equals(SHARES) || e.getKey().equals("tolerance"))
continue;

// might be null if not defined
Object objValue = attr.getAttribute(e.getKey());
String category = categories.get(e.getKey()).categorize(objValue);

// compare as string
if (!Objects.toString(category).equals(e.getValue()))
return false;
}

return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -128,22 +128,4 @@ public void addParameterSet(ConfigGroup set) {
}
}

/**
* Target parameters for mode shares.
* This group allows arbitrary attributes to be defined, which are matched against person attributes.
*/
public static final class ModeTargetParameters extends ReflectiveConfigGroup {

private static final String GROUP_NAME = "modeTargetParameters";

@Parameter
@Comment("Target mode shares. Should only contain relevant modes.")
public Map<String, Double> shares = new HashMap<>();

public ModeTargetParameters() {
super(GROUP_NAME, true);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
import org.matsim.modechoice.replanning.scheduled.AllBestPlansStrategyProvider;
import org.matsim.modechoice.replanning.scheduled.ReRouteSelectedStrategyProvider;
import org.matsim.modechoice.replanning.scheduled.ScheduledStrategyChooser;
import org.matsim.modechoice.replanning.scheduled.TimeMutateSelectedStrategyProvider;

import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.OptionalDouble;
import java.util.Set;

/**
* Module for to enable scheduled mode choice.
Expand All @@ -42,6 +46,9 @@ public class ScheduledModeChoiceModule extends AbstractModule {
ALL_BEST_K_PLAN_MODES_STRATEGY
);
public static String REROUTE_SELECTED = "ReRouteSelected";
public static String TIME_MUTATE_SELECTED = "TimeAllocationMutatorSelected";


private final Builder builder;

private ScheduledModeChoiceModule(Builder builder) {
Expand All @@ -60,9 +67,9 @@ public void install() {

addPlanStrategyBinding(ALL_BEST_K_PLAN_MODES_STRATEGY).toProvider(AllBestPlansStrategyProvider.class);
addPlanStrategyBinding(REROUTE_SELECTED).toProvider(ReRouteSelectedStrategyProvider.class);
addPlanStrategyBinding(TIME_MUTATE_SELECTED).toProvider(TimeMutateSelectedStrategyProvider.class);

bind(new TypeLiteral<StrategyChooser<Plan, Person>>() {
}).to(ScheduledStrategyChooser.class);
bind(new TypeLiteral<StrategyChooser<Plan, Person>>() {}).to(ScheduledStrategyChooser.class);

ScheduledModeChoiceConfigGroup config = ConfigUtils.addOrGetModule(getConfig(), ScheduledModeChoiceConfigGroup.class);

Expand All @@ -76,6 +83,13 @@ public void install() {
.mapToDouble(ReplanningConfigGroup.StrategySettings::getWeight)
.findFirst();

OptionalDouble timeMutate = strategies.stream().filter(s ->
(s.getStrategyName().equals(DefaultPlanStrategiesModule.DefaultStrategy.TimeAllocationMutator) ||
s.getStrategyName().equals(DefaultPlanStrategiesModule.DefaultStrategy.TimeAllocationMutator_ReRoute)) &&
s.getSubpopulation().equals(subpopulation))
.mapToDouble(ReplanningConfigGroup.StrategySettings::getWeight)
.findFirst();

strategies.removeIf(s -> s.getSubpopulation().equals(subpopulation) && EXCLUDED.contains(s.getStrategyName()));

ReplanningConfigGroup.StrategySettings strategy = new ReplanningConfigGroup.StrategySettings();
Expand All @@ -91,6 +105,14 @@ public void install() {
s.setWeight(reroute.getAsDouble());
strategies.add(s);
}

if (timeMutate.isPresent()) {
ReplanningConfigGroup.StrategySettings s = new ReplanningConfigGroup.StrategySettings();
s.setStrategyName(TIME_MUTATE_SELECTED);
s.setSubpopulation(subpopulation);
s.setWeight(timeMutate.getAsDouble());
strategies.add(s);
}
}

strategies.forEach(s -> getConfig().replanning().addStrategySettings(s));
Expand Down
Loading

0 comments on commit 9d9fdbe

Please sign in to comment.