Skip to content

Commit

Permalink
implemented pseudo random error as trip scoring, instead of leg
Browse files Browse the repository at this point in the history
  • Loading branch information
rakow committed Dec 7, 2024
1 parent df66c8f commit 03ca6fe
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.inject.Inject;
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.scoring.ScoringFunction;
import org.matsim.core.scoring.ScoringFunctionFactory;
import org.matsim.core.scoring.SumScoringFunction;
Expand All @@ -13,23 +14,32 @@
*/
public class AdvancedScoringFunctionFactory implements ScoringFunctionFactory {

@Inject
private Config config;

@Inject
private ScoringParametersForPerson params;
private final Config config;
private final AdvancedScoringConfigGroup scoring;
private final ScoringParametersForPerson params;
private final PseudoRandomScorer pseudoRNG;

@Inject
private PseudoRandomScorer pseudoRNG;
public AdvancedScoringFunctionFactory(Config config, ScoringParametersForPerson params, PseudoRandomScorer pseudoRNG) {
this.config = config;
this.scoring = ConfigUtils.addOrGetModule(config, AdvancedScoringConfigGroup.class);
this.params = params;
this.pseudoRNG = pseudoRNG;
}

@Override
public ScoringFunction createNewScoringFunction(Person person) {
final ScoringParameters parameters = params.getScoringParameters(person);

SumScoringFunction sumScoringFunction = new SumScoringFunction();
sumScoringFunction.addScoringFunction(new CharyparNagelActivityScoring(parameters));

if (scoring.pseudoRamdomScale > 0) {
sumScoringFunction.addScoringFunction(new PseudoRandomTripScoring(person.getId(), pseudoRNG));
}

// replaced original leg scoring
sumScoringFunction.addScoringFunction(new PiecewiseLinearlLegScoring(parameters, person.getId(), config.transit().getTransitModes(), pseudoRNG));
sumScoringFunction.addScoringFunction(new PiecewiseLinearlLegScoring(parameters, config.transit().getTransitModes()));
sumScoringFunction.addScoringFunction(new CharyparNagelMoneyScoring(parameters));
sumScoringFunction.addScoringFunction(new CharyparNagelAgentStuckScoring(parameters));
sumScoringFunction.addScoringFunction(new ScoreEventScoring());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@

import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.router.TripStructureUtils;

/**
* Computes a random seed based on person id, previous activity and routing mode.
*/
public final class DefaultPseudoRandomTripError implements PseudoRandomTripError {

@Override
public long getSeed(Id<Person> personId, String routingMode, String prevActivityType) {
public long getSeed(Id<Person> personId, String routingMode, TripStructureUtils.Trip trip) {

int personHash = personId.toString().hashCode();

int modeHash = routingMode.hashCode();
int modeAndActHash = 31 * modeHash + prevActivityType.hashCode();
int modeAndActHash = 31 * modeHash + trip.getOriginActivity().getType().hashCode();

// Combine two integers to long
return (long) personHash << 32 | modeAndActHash & 0xFFFFFFFFL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,19 @@

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.TransportMode;
import org.matsim.api.core.v01.events.ActivityEndEvent;
import org.matsim.api.core.v01.events.Event;
import org.matsim.api.core.v01.events.PersonDepartureEvent;
import org.matsim.api.core.v01.events.PersonEntersVehicleEvent;
import org.matsim.api.core.v01.population.Leg;
import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.Route;
import org.matsim.core.gbl.Gbl;
import org.matsim.core.router.TripStructureUtils;
import org.matsim.core.scoring.ScoringFunction;
import org.matsim.core.scoring.functions.ModeUtilityParameters;
import org.matsim.core.scoring.functions.ScoringParameters;
import org.matsim.pt.PtConstants;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
Expand All @@ -57,31 +51,20 @@ public final class PiecewiseLinearlLegScoring implements org.matsim.core.scoring
* The parameters used for scoring.
*/
private final ScoringParameters params;
private final Id<Person> personId;
private final Set<String> ptModes;
private final PseudoRandomScorer pseudoRNG;
private final double marginalUtilityOfMoney;
private final Set<String> modesAlreadyConsideredForDailyConstants;
private double score;
private boolean nextEnterVehicleIsFirstOfTrip = true;
private boolean nextStartPtLegIsFirstOfTrip = true;
private boolean currentLegIsPtLeg = false;
private double lastActivityEndTime = Double.NaN;
private String lastActivityType = null;
/**
* The number of legs since the last activity.
*/
private int legIndex = 0;
private final List<Score> legScores;

public PiecewiseLinearlLegScoring(final ScoringParameters params, Id<Person> personId, Set<String> ptModes, PseudoRandomScorer pseudoRNG) {
public PiecewiseLinearlLegScoring(final ScoringParameters params, Set<String> ptModes) {
this.params = params;
this.personId = personId;
this.ptModes = ptModes;
this.pseudoRNG = pseudoRNG;
this.modesAlreadyConsideredForDailyConstants = new HashSet<>();
this.marginalUtilityOfMoney = this.params.marginalUtilityOfMoney;
this.legScores = new LinkedList<>();
}

@Override
Expand All @@ -94,36 +77,15 @@ public double getScore() {
return this.score;
}

@Override
public void explainScore(StringBuilder out) {
out.append("legs_util=").append(score);

// Store for each leg
if (!legScores.isEmpty()) {
for (int i = 0; i < legScores.size(); i++) {
out.append(ScoringFunction.SCORE_DELIMITER).append("leg_").append(i).append("_total=").append(legScores.get(i).total);
out.append(ScoringFunction.SCORE_DELIMITER).append("leg_").append(i).append("_randomComponent=").append(legScores.get(i).randomComponent);
out.append(ScoringFunction.SCORE_DELIMITER).append("leg_").append(i).append("_constant=").append(legScores.get(i).constant);
}
}
}

/**
* Calculate the score for a leg.
*/
private Score calcLegScore(final double departureTime, final double arrivalTime, final Leg leg) {
private double calcLegScore(final double departureTime, final double arrivalTime, final Leg leg) {
double tmpScore = 0.0;
// travel time in seconds
double travelTime = arrivalTime - departureTime;
ModeUtilityParameters modeParams = this.params.modeParams.get(leg.getMode());

// The first leg of a trip incurs trip specific random utility
double randomComponent = 0.0;
if (legIndex == 0) {
randomComponent = pseudoRNG.scoreTrip(personId, leg.getRoutingMode(), lastActivityType);
tmpScore += randomComponent;
}

if (modeParams == null) {
if (leg.getMode().equals(TransportMode.transit_walk) || leg.getMode().equals(TransportMode.non_network_walk)) {
modeParams = this.params.modeParams.get(TransportMode.walk);
Expand Down Expand Up @@ -201,24 +163,18 @@ private Score calcLegScore(final double departureTime, final double arrivalTime,
// yyyy the above will cause problems if we ever decide to differentiate pt mode into bus, tram, train, ...
// Might have to move the MainModeIdentifier then. kai, sep'18

return new Score(tmpScore, randomComponent, modeParams.constant);
return tmpScore;
}

@Override
public void handleEvent(Event event) {
if (event instanceof ActivityEndEvent a) {
if (event instanceof ActivityEndEvent) {
// When there is a "real" activity, flags are reset:
if (!PtConstants.TRANSIT_ACTIVITY_TYPE.equals(a.getActType())) {
if (!PtConstants.TRANSIT_ACTIVITY_TYPE.equals(((ActivityEndEvent) event).getActType())) {
this.nextEnterVehicleIsFirstOfTrip = true;
this.nextStartPtLegIsFirstOfTrip = true;
}
this.lastActivityEndTime = event.getTime();

// Trip occurs when non stating activity ends
if (!TripStructureUtils.isStageActivityType(a.getActType())) {
this.legIndex = 0;
this.lastActivityType = ((ActivityEndEvent) event).getActType();
}
}

if (event instanceof PersonEntersVehicleEvent && currentLegIsPtLeg) {
Expand Down Expand Up @@ -251,24 +207,14 @@ public void handleLeg(Leg leg) {
Gbl.assertIf(leg.getDepartureTime().isDefined());
Gbl.assertIf(leg.getTravelTime().isDefined());

Score legScore = calcLegScore(
double legScore = calcLegScore(
leg.getDepartureTime().seconds(), leg.getDepartureTime().seconds() + leg.getTravelTime()
.seconds(), leg);

// Increase leg index after scoring
legIndex++;

if (Double.isNaN(legScore.total)) {
if (Double.isNaN(legScore)) {
log.error("dpTime=" + leg.getDepartureTime().seconds()
+ "; ttime=" + leg.getTravelTime().seconds() + "; leg=" + leg);
throw new RuntimeException("score is NaN");
}

this.score += legScore.total;
this.legScores.add(legScore);
}

private record Score(double total, double randomComponent, double constant) {
this.score += legScore;
}

}
7 changes: 4 additions & 3 deletions src/main/java/org/matsim/run/scoring/PseudoRandomScorer.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.router.TripStructureUtils;

import java.util.SplittableRandom;

Expand Down Expand Up @@ -38,20 +39,20 @@ public PseudoRandomScorer(PseudoRandomTripError tripScore, Config config) {
for (int i = 0; i < WARMUP_ITERATIONS; i++) {
rnd.nextDouble();
}

// Create a random seed from the global one
this.seed = rnd.nextLong();
}

/**
* Calculates the pseudo random score of a trip.
*/
public double scoreTrip(Id<Person> personId, String routingMode, String prevActivityType) {
public double scoreTrip(Id<Person> personId, String routingMode, TripStructureUtils.Trip trip) {

if (tripScore == null || scale == 0)
return 0;

long tripSeed = tripScore.getSeed(personId, routingMode, prevActivityType);
long tripSeed = tripScore.getSeed(personId, routingMode, trip);

// Need to create a new instance because reusing them will also create a lot of intermediate arrays
XoRoShiRo128PlusPlus rng = new XoRoShiRo128PlusPlus(seed, tripSeed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.router.TripStructureUtils;

/**
* Interface to provide pseudo-random errors for a trip.
Expand All @@ -11,7 +12,7 @@ public interface PseudoRandomTripError {
/**
* Return a seed for a trip. The seed must be designed such that it is constant for the same choice situations.
*/
long getSeed(Id<Person> personId, String routingMode, String prevActivityType);
long getSeed(Id<Person> personId, String routingMode, TripStructureUtils.Trip trip);


}
62 changes: 62 additions & 0 deletions src/main/java/org/matsim/run/scoring/PseudoRandomTripScoring.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.matsim.run.scoring;

import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.population.Leg;
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.router.TripStructureUtils;
import org.matsim.core.scoring.ScoringFunction;
import org.matsim.core.scoring.SumScoringFunction;

import java.util.List;

public class PseudoRandomTripScoring implements SumScoringFunction.TripScoring {

private static final Logger log = LogManager.getLogger(PseudoRandomTripScoring.class);

private final Id<Person> id;
private final PseudoRandomScorer rng;

private final DoubleList scores = new DoubleArrayList();
private double score;

public PseudoRandomTripScoring(Id<Person> id, PseudoRandomScorer rng) {
this.id = id;
this.rng = rng;
}

@Override
public void finish() {
}

@Override
public double getScore() {
return score;
}

@Override
public void handleTrip(TripStructureUtils.Trip trip) {

List<Leg> legs = trip.getLegsOnly();
if (legs.isEmpty()) {
log.warn("Trip {} for person {} has not legs and can not be scored", trip, id);
return;
}

double tripScore = rng.scoreTrip(id, legs.getFirst().getRoutingMode(), trip);
scores.add(tripScore);
score += tripScore;
}

@Override
public void explainScore(StringBuilder out) {
out.append("trips_util=").append(score);
for (int i = 0; i < scores.size(); i++) {
double s = scores.getDouble(i);
out.append(ScoringFunction.SCORE_DELIMITER).append("trip_").append(i).append("=").append(s);
}
}
}

0 comments on commit 03ca6fe

Please sign in to comment.