Skip to content

Commit

Permalink
assign activity chains
Browse files Browse the repository at this point in the history
  • Loading branch information
rakow committed Aug 7, 2024
1 parent 516ff99 commit f842282
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 51 deletions.
23 changes: 4 additions & 19 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ input/$V/$N-$V-facilities.xml.gz: input/$V/$N-$V-network.xml.gz input/facilities
input/$V/$N-static-$V-10pct.plans.xml.gz: input/facilities.gpkg
$(sc) prepare kansai-population\
--input $(kyoto)/data/census_kansai_region.csv\
--shp $(kyoto)/data/kansai-region-epsg4612.gpkg --shp-crs $(CRS)\
--facilities $< --facilities-attr resident\
--shp $(kyoto)/data/kansai-region.gpkg\
--postal-shp $(kyoto)/data/postalcodes.gpkg\
--facilities $< --facilities-attr all\
--output $@


Expand All @@ -102,27 +103,11 @@ input/$V/$N-$V-10pct.plans-initial.xml.gz: input/$V/$N-static-$V-10pct.plans.xml
$(sc) prepare create-daily-plans --input $< --output $@\
--persons src/main/python/table-persons.csv\
--activities src/main/python/table-activities.csv\
--shp $(kyoto)/data/kansai-region-epsg4612.gpkg --shp-crs $(CRS)\
--shp $(kyoto)/data/postalcodes.gpkg\
--facilities $(word 2,$^)\
--network $(word 3,$^)\


# Assigns locations to the activities
$p/berlin-initial-$V-10pct.plans.xml.gz: $p/$N-activities-$V-25pct.plans.xml.gz input/$V/$N-$V-facilities.xml.gz input/$V/$N-$V-network.xml.gz
$(sc) prepare init-location-choice\
--input $<\
--output $@\
--facilities $(word 2,$^)\
--network $(word 3,$^)\
--shp $(germany)/vg5000/vg5000_ebenen_0101/VG5000_GEM.shp\
--commuter $(germany)/regionalstatistik/commuter.csv\

# For debugging and visualization
$(sc) prepare downsample-population $@\
--sample-size 0.25\
--samples 0.1 0.03 0.01\


# Aggregated target for input plans to calibration
prepare: input/$V/$N-$V-10pct.plans-initial.xml.gz input/$V/$N-$V-network-with-pt.xml.gz
echo "Done"
10 changes: 9 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
<dependency>
<groupId>com.github.matsim-scenarios</groupId>
<artifactId>matsim-berlin</artifactId>
<version>ce81ea226c</version>
<version>6.3-SNAPSHOT</version>
<!-- <version>d9f763ffbc</version>-->
</dependency>


Expand Down Expand Up @@ -103,6 +104,13 @@
<build>
<plugins>

<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
Expand Down
191 changes: 177 additions & 14 deletions src/main/java/org/matsim/prepare/population/CreateDailyPlans.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
package org.matsim.prepare.population;

import me.tongfei.progressbar.ProgressBar;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.Population;
import org.matsim.application.MATSimAppCommand;
import org.matsim.application.options.ShpOptions;
import org.matsim.core.population.PersonUtils;
import org.matsim.core.population.PopulationUtils;
import org.matsim.core.population.algorithms.ParallelPersonAlgorithmUtils;
import org.matsim.core.population.algorithms.PersonAlgorithm;
import picocli.CommandLine;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.SplittableRandom;
import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;

@CommandLine.Command(
name = "create-daily-plans",
Expand All @@ -21,37 +31,40 @@
public class CreateDailyPlans implements MATSimAppCommand, PersonAlgorithm {

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

private final Map<String, CSVRecord> persons = new HashMap<>();
private final Map<Key, List<String>> groups = new HashMap<>();
@CommandLine.Option(names = "--input", description = "Path to input population.")
private Path input;

@CommandLine.Option(names = "--output", description = "Path to output population", required = true)
private Path output;

@CommandLine.Option(names = "--persons", description = "Path to person table", required = true)
private Path personsPath;

@CommandLine.Option(names = "--activities", description = "Path to activity table", required = true)
private Path activityPath;

@CommandLine.Option(names = "--facilities", description = "Path to facilities file", required = true)
private Path facilityPath;

@CommandLine.Option(names = "--network", description = "Path to network file", required = true)
private Path networkPath;

@CommandLine.Option(names = "--seed", description = "Seed used to sample locations", defaultValue = "1")
private long seed;

@CommandLine.Mixin
private ShpOptions shp;

private ThreadLocal<SplittableRandom> rnd;
private Population population;
private Map<String, List<CSVRecord>> activities;
private ProgressBar pb;

public static void main(String[] args) {
new CreateDailyPlans().execute(args);
}

/**
* Initializes random number generator with person specific seed.
*/
private SplittableRandom initRandomNumberGenerator(Person person) {
BigInteger i = new BigInteger(person.getId().toString().getBytes());
return new SplittableRandom(i.longValue() + seed);
}

@Override
public Integer call() throws Exception {

Expand All @@ -60,9 +73,25 @@ public Integer call() throws Exception {
return 2;
}

Population population = PopulationUtils.readPopulation(input.toString());
// TODO: map zones to shape file

activities = RunActivitySampling.readActivities(activityPath);

// Remove activities with missing leg duration
activities.values().removeIf(
rows -> rows.stream().anyMatch(act -> act.get("leg_duration").isBlank() || act.get("duration").isBlank())
);

log.info("Got {} persons after cleaning", activities.size());

try (CSVParser csv = CSVParser.parse(personsPath, StandardCharsets.UTF_8,
CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).build())) {
readPersons(csv);
}

rnd = ThreadLocal.withInitial(() -> new SplittableRandom(seed));
population = PopulationUtils.readPopulation(input.toString());

pb = new ProgressBar("Creating daily plans", population.getPersons().size());

ParallelPersonAlgorithmUtils.run(population, 8, this);

Expand All @@ -74,7 +103,141 @@ public Integer call() throws Exception {
@Override
public void run(Person person) {

// TODO: map zones to shape file
SplittableRandom rnd = initRandomNumberGenerator(person);

Coord homeCoord = Attributes.getHomeCoord(person);

String personId = matchPerson(rnd, createKey(person, Zone.FULL));
if (personId == null) {
personId = matchPerson(rnd, createKey(person, Zone.CITY));
}
if (personId == null) {
personId = matchPerson(rnd, createKey(person, Zone.NONE));
}

if (personId == null) {
log.warn("No matching person found for {}", createKey(person, Zone.NONE));
return;
}

List<CSVRecord> activities = Objects.requireNonNull(this.activities.get(personId), "No activities found");

RunActivitySampling.createPlan(homeCoord, activities, rnd, population.getFactory());

// TODO: select locations

// TODO: add reference modes and weights for fully matched persons

pb.step();
}

/**
* Create subpopulations for sampling.
*/
private void readPersons(CSVParser csv) {

int i = 0;
int skipped = 0;

for (CSVRecord r : csv) {

String idx = r.get("p_id");
String gender = r.get("gender");
int age = Integer.parseInt(r.get("age"));

if (!activities.containsKey(idx)) {
skipped++;
continue;
}

Stream<Key> keys = createKey(gender, age, r.get("location"), r.get("zone"));
keys.forEach(key -> {
groups.computeIfAbsent(key, (k) -> new ArrayList<>()).add(idx);

// Alternative keys with different zones
groups.computeIfAbsent(new Key(key.gender, key.age, r.get("location")), (k) -> new ArrayList<>()).add(idx);
groups.computeIfAbsent(new Key(key.gender, key.age, null), (k) -> new ArrayList<>()).add(idx);

});
persons.put(idx, r);
i++;
}

log.info("Read {} persons from csv. Skipped {} invalid persons", i, skipped);
}

/**
* Match person attributes with person from survey data.
*
* @return daily activities
*/
private String matchPerson(SplittableRandom rnd, Key key) {
List<String> subgroup = groups.get(key);
if (subgroup == null) {
return null;
}

if (subgroup.size() < 5)
return null;

// TODO: needs to be weighted matching
// weights can be preprocessed after reading in

return subgroup.get(rnd.nextInt(subgroup.size()));
}

private Stream<Key> createKey(String gender, int age, String location, String zone) {

String homeZone;
if (zone.isBlank() || zone.equals(location))
homeZone = location;
else
// The last two digits of the postal code are not known
homeZone = location + "_" + zone.substring(0, zone.length() - 2);

if (age <= 10) {
return IntStream.rangeClosed(0, 10).mapToObj(i -> new Key(null, i, homeZone));
}
if (age < 18) {
return IntStream.rangeClosed(11, 18).mapToObj(i -> new Key(gender, i, homeZone));
}

int min = Math.max(18, age - 6);
int max = Math.min(65, age + 6);

// larger groups for older people
if (age > 65) {
min = Math.max(66, age - 10);
max = Math.min(105, age + 10);
}

return IntStream.rangeClosed(min, max).mapToObj(i -> new Key(gender, i, homeZone));
}

private Key createKey(Person person, Zone zone) {

Integer age = PersonUtils.getAge(person);
String gender = PersonUtils.getSex(person);
if (age <= 10)
gender = null;

String homeZone = switch (zone) {
case FULL -> person.getAttributes().getAttribute("city") + "_" + person.getAttributes().getAttribute("postal");
case CITY -> Objects.toString(person.getAttributes().getAttribute("city"));
case NONE -> null;
};

return new Key(gender, age, homeZone);
}

private enum Zone {
FULL, CITY, NONE
}

/**
* Key used to match persons.
*/
public record Key(String gender, int age, String homeZone) {
}

}
Loading

0 comments on commit f842282

Please sign in to comment.