From a054240c4f9099cb5c5b7c78ee45fd35266589e9 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Wed, 13 Mar 2024 17:10:19 +0000 Subject: [PATCH 01/39] add linear scaling factor class --- .../com/arup/cml/abm/kpi/LinearScale.java | 64 ++++++++++++++++++ .../com/arup/cml/abm/kpi/ScalingFactor.java | 5 ++ .../com/arup/cml/abm/kpi/TestLinearScale.java | 66 +++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 src/main/java/com/arup/cml/abm/kpi/LinearScale.java create mode 100644 src/main/java/com/arup/cml/abm/kpi/ScalingFactor.java create mode 100644 src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java diff --git a/src/main/java/com/arup/cml/abm/kpi/LinearScale.java b/src/main/java/com/arup/cml/abm/kpi/LinearScale.java new file mode 100644 index 0000000..d389ec3 --- /dev/null +++ b/src/main/java/com/arup/cml/abm/kpi/LinearScale.java @@ -0,0 +1,64 @@ +package com.arup.cml.abm.kpi; + +public class LinearScale implements ScalingFactor { + private double leftScaleBound; + private double rightScaleBound; + private double leftValueBound; + private double rightValueBound; + + public LinearScale(double leftScaleBound, double rightScaleBound, double leftValueBound, double rightValueBound) { + // leftValueBound is mapped to leftScaleBound + // rightValueBound is mapped to rightScaleBound + // any value between value bounds is mapped, linearly between values [leftScaleBound, rightScaleBound] + // leftScaleBound < rightScaleBound necessarily + // leftValueBound can be larger than rightValueBound a value will be scaled reversely. + + if (leftScaleBound > rightScaleBound) { + throw new RuntimeException("leftScaleBound cannot be larger than rightValueBound"); + } + if ((leftValueBound == rightValueBound) && (leftScaleBound != rightScaleBound)) { + throw new RuntimeException("The bounds given for linear scale are invalid. " + + "Left and right bounds cannot both be the same value."); + } + + this.leftScaleBound = leftScaleBound; + this.rightScaleBound = rightScaleBound; + this.leftValueBound = leftValueBound; + this.rightValueBound = rightValueBound; + } + + public LinearScale(double leftValueBound, double rightValueBound) { + this(0, 10, leftValueBound, rightValueBound); + } + + private boolean isReversedLinearScale() { + return leftValueBound > rightValueBound; + } + + private boolean isWithinBounds(double value) { + if (this.isReversedLinearScale() && ((value <= leftValueBound) && (value >= rightValueBound))) { + return true; + } else { + return (value >= leftValueBound) && (value <= rightValueBound); + } + } + + @Override + public double scale(double value) { + if (this.isWithinBounds(value)) { + return ((value - leftValueBound) / (rightValueBound - leftValueBound)) * (rightScaleBound - leftScaleBound); + } else if (this.isReversedLinearScale()) { + if (value < rightValueBound) { + return rightScaleBound; + } else { + return leftScaleBound; + } + } else { + if (value > rightValueBound) { + return rightScaleBound; + } else { + return leftScaleBound; + } + } + } +} diff --git a/src/main/java/com/arup/cml/abm/kpi/ScalingFactor.java b/src/main/java/com/arup/cml/abm/kpi/ScalingFactor.java new file mode 100644 index 0000000..5260b3b --- /dev/null +++ b/src/main/java/com/arup/cml/abm/kpi/ScalingFactor.java @@ -0,0 +1,5 @@ +package com.arup.cml.abm.kpi; + +public interface ScalingFactor { + double scale(double value); +} diff --git a/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java b/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java new file mode 100644 index 0000000..b852a5c --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java @@ -0,0 +1,66 @@ +package com.arup.cml.abm.kpi; + +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TestLinearScale { + double lowerScaleBound = 0.0; + double upperScaleBound = 10.0; + + double lowerValueBound = 10.0; + double upperValueBound = 30.0; + + LinearScale basicScaleFactor = new LinearScale(lowerScaleBound, upperScaleBound, lowerValueBound, upperValueBound); + LinearScale reverseScaleFactor = new LinearScale(lowerScaleBound, upperScaleBound, upperValueBound, lowerValueBound); + + @Test + public void mapsTheMidpoint() { + assertThat(basicScaleFactor.scale(20)).isEqualTo(5.0); + } + + @Test + public void mapsCloserToLeftBound() { + assertThat(basicScaleFactor.scale(11.0)).isEqualTo(0.5); + } + + @Test + public void mapsCloserToRightBound() { + assertThat(basicScaleFactor.scale(29.0)).isEqualTo(9.5); + } + + @Test + public void outsideLeftBoundMapsToLowerScaleBound() { + assertThat(basicScaleFactor.scale(5.0)).isEqualTo(lowerScaleBound); + } + + @Test + public void outsideRightBoundMapsToUpperScaleBound() { + assertThat(basicScaleFactor.scale(35.0)).isEqualTo(upperScaleBound); + } + + @Test + public void reversedFactorStillMapsTheMidpoint() { + assertThat(reverseScaleFactor.scale(20)).isEqualTo(5.0); + } + + @Test + public void reversedFactorMapsCloserToRightBound() { + assertThat(reverseScaleFactor.scale(11.0)).isEqualTo(9.5); + } + + @Test + public void reversedFactorMapsCloserToLeftBound() { + assertThat(reverseScaleFactor.scale(29.0)).isEqualTo(0.5); + } + + @Test + public void reversedFactorOutsideLeftBoundMapsToLowerScaleBound() { + assertThat(reverseScaleFactor.scale(35.0)).isEqualTo(lowerScaleBound); + } + + @Test + public void reversedFactorOutsideRightBoundMapsToUpperScaleBound() { + assertThat(reverseScaleFactor.scale(5.0)).isEqualTo(upperScaleBound); + } +} From 28eb3276a5e65b241b1e25f89d40de96458b5685 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Wed, 13 Mar 2024 17:21:04 +0000 Subject: [PATCH 02/39] simplify if statements for reversed scaling factor by multiplying by -1 --- .../com/arup/cml/abm/kpi/LinearScale.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/LinearScale.java b/src/main/java/com/arup/cml/abm/kpi/LinearScale.java index d389ec3..ffef6f6 100644 --- a/src/main/java/com/arup/cml/abm/kpi/LinearScale.java +++ b/src/main/java/com/arup/cml/abm/kpi/LinearScale.java @@ -5,6 +5,7 @@ public class LinearScale implements ScalingFactor { private double rightScaleBound; private double leftValueBound; private double rightValueBound; + private boolean isReversedLinearScale = false; public LinearScale(double leftScaleBound, double rightScaleBound, double leftValueBound, double rightValueBound) { // leftValueBound is mapped to leftScaleBound @@ -20,6 +21,11 @@ public LinearScale(double leftScaleBound, double rightScaleBound, double leftVal throw new RuntimeException("The bounds given for linear scale are invalid. " + "Left and right bounds cannot both be the same value."); } + if (leftValueBound > rightValueBound) { + this.isReversedLinearScale = true; + leftValueBound = -leftValueBound; + rightValueBound = -rightValueBound; + } this.leftScaleBound = leftScaleBound; this.rightScaleBound = rightScaleBound; @@ -32,27 +38,20 @@ public LinearScale(double leftValueBound, double rightValueBound) { } private boolean isReversedLinearScale() { - return leftValueBound > rightValueBound; + return this.isReversedLinearScale; } private boolean isWithinBounds(double value) { - if (this.isReversedLinearScale() && ((value <= leftValueBound) && (value >= rightValueBound))) { - return true; - } else { - return (value >= leftValueBound) && (value <= rightValueBound); - } + return (value >= leftValueBound) && (value <= rightValueBound); } @Override public double scale(double value) { + if (this.isReversedLinearScale()) { + value = -value; + } if (this.isWithinBounds(value)) { return ((value - leftValueBound) / (rightValueBound - leftValueBound)) * (rightScaleBound - leftScaleBound); - } else if (this.isReversedLinearScale()) { - if (value < rightValueBound) { - return rightScaleBound; - } else { - return leftScaleBound; - } } else { if (value > rightValueBound) { return rightScaleBound; From 1ff7a666ef3af44ba1c19c45ee3e7430aaae665d Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Thu, 14 Mar 2024 14:56:52 +0000 Subject: [PATCH 03/39] add a couple more test for linear scale factor --- .../com/arup/cml/abm/kpi/LinearScale.java | 8 ++++++++ .../com/arup/cml/abm/kpi/TestLinearScale.java | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/main/java/com/arup/cml/abm/kpi/LinearScale.java b/src/main/java/com/arup/cml/abm/kpi/LinearScale.java index ffef6f6..7ab9567 100644 --- a/src/main/java/com/arup/cml/abm/kpi/LinearScale.java +++ b/src/main/java/com/arup/cml/abm/kpi/LinearScale.java @@ -41,6 +41,14 @@ private boolean isReversedLinearScale() { return this.isReversedLinearScale; } + public double getLowerScaleBound() { + return leftScaleBound; + } + + public double getUpperScaleBound() { + return rightScaleBound; + } + private boolean isWithinBounds(double value) { return (value >= leftValueBound) && (value <= rightValueBound); } diff --git a/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java b/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java index b852a5c..f44752e 100644 --- a/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java +++ b/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java @@ -1,5 +1,7 @@ package com.arup.cml.abm.kpi; +import com.arup.cml.abm.kpi.data.LinkLog; +import com.arup.cml.abm.kpi.data.exceptions.LinkLogPassengerConsistencyException; import org.junit.Test; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -63,4 +65,22 @@ public void reversedFactorOutsideLeftBoundMapsToLowerScaleBound() { public void reversedFactorOutsideRightBoundMapsToUpperScaleBound() { assertThat(reverseScaleFactor.scale(5.0)).isEqualTo(upperScaleBound); } + + @Test + public void defaultsToZeroToTenScaleBounds() { + LinearScale defaultedLinearScale = new LinearScale(45, 50); + assertThat(defaultedLinearScale.getLowerScaleBound()).isEqualTo(0.0); + assertThat(defaultedLinearScale.getUpperScaleBound()).isEqualTo(10.0); + } + + @Test(expected = RuntimeException.class) + public void throwsExceptionWhenRightScaleBoundIsLargerThanLeftScaleBound() { + new LinearScale(10, 0, 45, 50); + } + + @Test(expected = RuntimeException.class) + public void throwsExceptionWhenValueBoundsEqualButScaleBoundsAreNot() { + new LinearScale(0, 1, 45, 45); + } + } From bbd765b5a8b540145215eeebd0be4dc8db5bd187 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 11:32:29 +0000 Subject: [PATCH 04/39] add tests for affordability KPI --- .../kpi/tablesaw/TablesawKpiCalculator.java | 1 + .../kpi/builders/KpiCalculatorBuilder.java | 7 +- .../cml/abm/kpi/builders/LegsBuilder.java | 73 +++++++++-- .../cml/abm/kpi/builders/PersonsBuilder.java | 36 ++++-- .../kpi/builders/ScoringConfigBuilder.java | 52 ++++++++ .../TestTablesawAffordabilityKpi.java | 122 ++++++++++++++++++ 6 files changed, 266 insertions(+), 25 deletions(-) create mode 100644 src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java create mode 100644 src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index f20ac83..ab32922 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -1,6 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; import com.arup.cml.abm.kpi.KpiCalculator; +import com.arup.cml.abm.kpi.ScalingFactor; import com.arup.cml.abm.kpi.data.MoneyLog; import com.arup.cml.abm.kpi.domain.NetworkLinkLog; import org.apache.logging.log4j.LogManager; diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java index a99b8b3..8f384e4 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java @@ -25,7 +25,7 @@ public class KpiCalculatorBuilder { MoneyLog moneyLog = new MoneyLog(); String persons; ActivityFacilities facilities = new FacilitiesBuilder().build(); - ScoringConfigGroup scoring = new ScoringConfigGroup(); + ScoringConfigGroup scoring = new ScoringConfigBuilder().build(); public KpiCalculatorBuilder(TemporaryFolder tmpDir) { this.tmpDir = tmpDir; @@ -74,6 +74,11 @@ public KpiCalculatorBuilder withFacilities(ActivityFacilities facilities) { return this; } + public KpiCalculatorBuilder withScoring(ScoringConfigGroup scoring) { + this.scoring = scoring; + return this; + } + public TablesawKpiCalculator build() { return new TablesawKpiCalculator(network, schedule, vehicles, linkLog, getInputStream(persons), moneyLog, scoring, facilities, getInputStream(legs), getInputStream(trips), diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java index 9374bd0..a7bf5b8 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java @@ -4,14 +4,32 @@ import tech.tablesaw.api.IntColumn; import tech.tablesaw.api.StringColumn; import tech.tablesaw.api.Table; -import tech.tablesaw.columns.Column; import tech.tablesaw.io.csv.CsvWriteOptions; import java.nio.file.Path; -import java.util.Set; public class LegsBuilder { TemporaryFolder tmpDir; + + String defaultPerson = "Bobby"; + String defaultTripId = "Bobby_0"; + String defaultDepTime = "07:38:40"; + String defaultTravTime = "00:09:12"; + String defaultWaitTime = "00:00:00"; + Integer defaultDistance = 5500; + String defaultMode = "car"; + String defaultStartLink = "1-3"; + String defaultStartX = "-500"; + String defaultStartY = "-200"; + String defaultEndLink = "4-5"; + String defaultEndX = "4600"; + String defaultEndY = "800"; + String defaultAccessStopId = ""; + String defaultEgressStopId = ""; + String defaultTransitLine = ""; + String defaultTransitRoute = ""; + String defaultVehicleId = "Bobby"; + Table legs = Table.create("legs").addColumns( StringColumn.create("person"), StringColumn.create("trip_id"), @@ -37,22 +55,53 @@ public LegsBuilder(TemporaryFolder tmpDir) { this.tmpDir = tmpDir; } - private void fillWithDudValues() { - Set intCols = Set.of("distance"); - for (Column col : legs.columns()) { - if (intCols.contains(col.name())) { - col.append(0); - } else { - col.append("dud"); - } - } + public LegsBuilder withLeg( + String person, String trip_id, String dep_time, String trav_time, String wait_time, Integer distance, + String mode, String start_link, String start_x, String start_y, String end_link, String end_x, String end_y, + String access_stop_id, String egress_stop_id, String transit_line, String transit_route, String vehicle_id + ) { + legs.stringColumn("person").append(person); + legs.stringColumn("trip_id").append(trip_id); + legs.stringColumn("dep_time").append(dep_time); + legs.stringColumn("trav_time").append(trav_time); + legs.stringColumn("wait_time").append(wait_time); + legs.intColumn("distance").append(distance); + legs.stringColumn("mode").append(mode); + legs.stringColumn("start_link").append(start_link); + legs.stringColumn("start_x").append(start_x); + legs.stringColumn("start_y").append(start_y); + legs.stringColumn("end_link").append(end_link); + legs.stringColumn("end_x").append(end_x); + legs.stringColumn("end_y").append(end_y); + legs.stringColumn("access_stop_id").append(access_stop_id); + legs.stringColumn("egress_stop_id").append(egress_stop_id); + legs.stringColumn("transit_line").append(transit_line); + legs.stringColumn("transit_route").append(transit_route); + legs.stringColumn("vehicle_id").append(vehicle_id); + return this; + } + + public LegsBuilder withDefaultLeg() { + return this.withLeg( + defaultPerson, defaultTripId, defaultDepTime, defaultTravTime, defaultWaitTime, defaultDistance, defaultMode, + defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, defaultAccessStopId, + defaultEgressStopId, defaultTransitLine, defaultTransitRoute, defaultVehicleId + ); + } + + public LegsBuilder withLegWithDistanceAndMode(String person, String tripId, Integer distance, String mode) { + return this.withLeg( + person, tripId, defaultDepTime, defaultTravTime, defaultWaitTime, distance, mode, + defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, defaultAccessStopId, + defaultEgressStopId, defaultTransitLine, defaultTransitRoute, defaultVehicleId + ); } public String build() { if (legs.isEmpty()) { // empty table gets into trouble reading all the columns, if the table is empty, it is assumed it's not // being used, so filling it with dud vales, just for the shape is ok - fillWithDudValues(); + this.withDefaultLeg(); } String legsPath = String.valueOf(Path.of(tmpDir.getRoot().getAbsolutePath(), "output_legs.csv")); CsvWriteOptions options = CsvWriteOptions.builder(legsPath).separator(';').build(); diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java index a110cd1..ece0d4c 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java @@ -4,15 +4,16 @@ import tech.tablesaw.api.DoubleColumn; import tech.tablesaw.api.StringColumn; import tech.tablesaw.api.Table; -import tech.tablesaw.columns.Column; import tech.tablesaw.io.csv.CsvWriteOptions; import java.nio.file.Path; -import java.util.Set; public class PersonsBuilder { TemporaryFolder tmpDir; + String defaultPerson = "Bobby"; + double defaultIncome = 10000.0; + String defaultSubpopulation = "default"; Table persons = Table.create("persons").addColumns( StringColumn.create("person"), DoubleColumn.create("income"), @@ -23,22 +24,33 @@ public PersonsBuilder(TemporaryFolder tmpDir) { this.tmpDir = tmpDir; } - private void fillWithDudValues() { - Set numberCols = Set.of("income", "monetaryDistanceRate", "dailyMonetaryConstant"); - for (Column col : persons.columns()) { - if (numberCols.contains(col.name())) { - col.append(1.0); - } else { - col.append("dud"); - } - } + public PersonsBuilder withPerson( + String person, double income, String subpopulation + ) { + persons.stringColumn("person").append(person); + persons.doubleColumn("income").append(income); + persons.stringColumn("subpopulation").append(subpopulation); + return this; + } + + public PersonsBuilder withDefaultPerson() { + return this.withPerson( + defaultPerson, defaultIncome, defaultSubpopulation + ); + } + + public PersonsBuilder withPersonWithMissingIncome(String person, String subpopulation) { + persons.stringColumn("person").append(person); + persons.doubleColumn("income").appendMissing(); + persons.stringColumn("subpopulation").append(subpopulation); + return this; } public String build() { if (persons.isEmpty()) { // empty table gets into trouble reading all the columns, if the table is empty, it is assumed it's not // being used, so filling it with dud vales, just for the shape is ok - fillWithDudValues(); + this.withDefaultPerson(); } String path = String.valueOf(Path.of(tmpDir.getRoot().getAbsolutePath(), "output_persons.csv")); CsvWriteOptions options = CsvWriteOptions.builder(path).separator(';').build(); diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java new file mode 100644 index 0000000..593e2fd --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java @@ -0,0 +1,52 @@ +package com.arup.cml.abm.kpi.builders; + +import org.matsim.core.config.groups.ScoringConfigGroup; + +public class ScoringConfigBuilder { + ScoringConfigGroup scoring; + String defaultSubpopulation = "default"; + String defaultMode = "car"; + double defaultDailyMonetaryConstant = 0.0; + double defaultMonetaryDistanceRate = 0.0; + double defaultConstant = 0.0; + double defaultMarginalUtilityOfDistance = 0.0; + double defaultDailyUtilityConstant = 0.0; + double defaultMarginalUtilityOfTraveling = 0.0; + + public ScoringConfigBuilder() { + scoring = new ScoringConfigGroup(); + } + + public ScoringConfigBuilder withScoringParams( + String subpopulation, String mode, double dailyMonetaryConstant, double monetaryDistanceRate, + double constant, double marginalUtilityOfDistance, double dailyUtilityConstant, + double marginalUtilityOfTraveling) { + ScoringConfigGroup.ScoringParameterSet paramSet = scoring.getOrCreateScoringParameters(subpopulation); + ScoringConfigGroup.ModeParams modeParams = new ScoringConfigGroup.ModeParams(mode); + modeParams.setDailyMonetaryConstant(dailyMonetaryConstant); + modeParams.setMonetaryDistanceRate(monetaryDistanceRate); + modeParams.setConstant(constant); + modeParams.setMarginalUtilityOfDistance(marginalUtilityOfDistance); + modeParams.setDailyUtilityConstant(dailyUtilityConstant); + modeParams.setMarginalUtilityOfTraveling(marginalUtilityOfTraveling); + paramSet.addModeParams(modeParams); + return this; + } + + public ScoringConfigBuilder withMonetaryCostsForSubpopulationAndMode( + String subpopulation, String mode, double dailyMonetaryConstant, double monetaryDistanceRate) { + return this.withScoringParams(subpopulation, mode, dailyMonetaryConstant, monetaryDistanceRate, + defaultConstant, defaultMarginalUtilityOfDistance, defaultDailyUtilityConstant, + defaultMarginalUtilityOfTraveling); + } + + public ScoringConfigBuilder withDefaultScoringParams() { + return this.withScoringParams(defaultSubpopulation, defaultMode, defaultDailyMonetaryConstant, + defaultMonetaryDistanceRate, defaultConstant, defaultMarginalUtilityOfDistance, + defaultDailyUtilityConstant, defaultMarginalUtilityOfTraveling); + } + + public ScoringConfigGroup build() { + return this.scoring; + } +} diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java new file mode 100644 index 0000000..09145cf --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java @@ -0,0 +1,122 @@ +package com.arup.cml.abm.kpi.tablesaw; + +import com.arup.cml.abm.kpi.builders.*; +import org.assertj.core.data.Offset; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TestTablesawAffordabilityKpi { + + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + + @Test + public void testSingleAgentGivesRatioOfOne() { + String bobby = "Bobby"; + String bobbySubpop = "default"; + Integer bobbyTripLength = 10; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsBuilder(tmpDir) + .withLegWithDistanceAndMode(bobby, "bobby_1", bobbyTripLength, "car") + .build()) + .withPersons(new PersonsBuilder(tmpDir) + .withPerson(bobby, 10000, bobbySubpop) + .build()) + .withScoring(new ScoringConfigBuilder() + .withMonetaryCostsForSubpopulationAndMode(bobbySubpop, "car", 1.0, 1.0) + .build()) + .build(); + double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); + + assertThat(outputKpi).isEqualTo(1) + .as("There is only one agent so the ratio of all travel to the low income group " + + "is expected to be 1"); + } + + @Test + public void lowerIncomeAgentSpendsTwiceAsMuch() { + String subpop = "default"; + String mode = "car"; + Integer tripLength = 10; + double dailyConstant = 1; + double distanceCost = 1; + double singleTripCost = dailyConstant + tripLength * distanceCost; + + String poorBobby = "PoorBobby"; + double poorBobbyIncome = 10000; + + String richBobby = "MintedBobby"; + double richBobbyIncome = 1000000; // it's all about those zeros :) + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsBuilder(tmpDir) + // poor Bobby has to make two trips and spends twice as much on travel + .withLegWithDistanceAndMode(poorBobby, "poor_bobby_1", tripLength, mode) + .withLegWithDistanceAndMode(poorBobby, "poor_bobby_2", tripLength, mode) + .withLegWithDistanceAndMode(richBobby, "rich_bobby_1", tripLength, mode) + .build()) + .withPersons(new PersonsBuilder(tmpDir) + .withPerson(poorBobby, poorBobbyIncome, subpop) + .withPerson(richBobby, richBobbyIncome, subpop) + .build()) + .withScoring(new ScoringConfigBuilder() + .withMonetaryCostsForSubpopulationAndMode(subpop, mode, dailyConstant, distanceCost) + .build()) + .build(); + double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); + + double poorBobbyDayAverage = 2 * singleTripCost; + double overallDayAverage = 3 * singleTripCost / 2; + assertThat(outputKpi).isCloseTo(poorBobbyDayAverage / overallDayAverage, Offset.offset(0.009)) + .as("There is only one agent so the ratio of all travel to the low income group " + + "is expected to be 1"); + } + + @Test + public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { + String mode = "car"; + Integer tripLength = 10; + double dailyConstant = 1; + double distanceCost = 1; + double singleTripCost = dailyConstant + tripLength * distanceCost; + + String poorBobby = "PoorBobby"; + String poorBobbySubpop = "low income"; + + String richBobby = "MintedBobby"; + String richBobbySubpop = "high income"; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsBuilder(tmpDir) + // poor Bobby has to make two trips and spends twice as much on travel + .withLegWithDistanceAndMode(poorBobby, "poor_bobby_1", tripLength, mode) + .withLegWithDistanceAndMode(poorBobby, "poor_bobby_2", tripLength, mode) + .withLegWithDistanceAndMode(richBobby, "rich_bobby_1", tripLength, mode) + .build()) + .withPersons(new PersonsBuilder(tmpDir) + .withPersonWithMissingIncome(poorBobby, poorBobbySubpop) + .withPersonWithMissingIncome(richBobby, richBobbySubpop) + .build()) + .withScoring(new ScoringConfigBuilder() + .withMonetaryCostsForSubpopulationAndMode(poorBobbySubpop, mode, dailyConstant, distanceCost) + .withMonetaryCostsForSubpopulationAndMode(richBobbySubpop, mode, dailyConstant, distanceCost) + .build()) + .build(); + double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); + + double poorBobbyDayAverage = 2 * singleTripCost; + double overallDayAverage = 3 * singleTripCost / 2; + assertThat(outputKpi).isCloseTo(poorBobbyDayAverage / overallDayAverage, Offset.offset(0.009)) + .as("There is only one agent so the ratio of all travel to the low income group " + + "is expected to be 1"); + } + + +} + From 66b84edd17cc45227ba2e7aa6b148dd28476671c Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 15:17:52 +0000 Subject: [PATCH 05/39] add scaling for affordability KPI --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 2 +- .../com/arup/cml/abm/kpi/LinearScale.java | 4 +- .../kpi/matsim/run/MatsimKpiGenerator.java | 5 +- .../kpi/tablesaw/TablesawKpiCalculator.java | 6 +- .../TestTablesawAffordabilityKpi.java | 52 +++++++++++------- .../expected-kpi-affordability.csv.gz | Bin 24 -> 23 bytes .../expected-kpi-affordability.csv.gz | Bin 24 -> 24 bytes 7 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index e01214a..8a3e0fe 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -5,7 +5,7 @@ import java.nio.file.Path; public interface KpiCalculator { - double writeAffordabilityKpi(Path outputDirectory); + double writeAffordabilityKpi(Path outputDirectory, ScalingFactor scalingFactor); void writePtWaitTimeKpi(Path outputDirectory); diff --git a/src/main/java/com/arup/cml/abm/kpi/LinearScale.java b/src/main/java/com/arup/cml/abm/kpi/LinearScale.java index 7ab9567..4ba69bf 100644 --- a/src/main/java/com/arup/cml/abm/kpi/LinearScale.java +++ b/src/main/java/com/arup/cml/abm/kpi/LinearScale.java @@ -17,7 +17,7 @@ public LinearScale(double leftScaleBound, double rightScaleBound, double leftVal if (leftScaleBound > rightScaleBound) { throw new RuntimeException("leftScaleBound cannot be larger than rightValueBound"); } - if ((leftValueBound == rightValueBound) && (leftScaleBound != rightScaleBound)) { + if (leftValueBound == rightValueBound && leftScaleBound != rightScaleBound) { throw new RuntimeException("The bounds given for linear scale are invalid. " + "Left and right bounds cannot both be the same value."); } @@ -50,7 +50,7 @@ public double getUpperScaleBound() { } private boolean isWithinBounds(double value) { - return (value >= leftValueBound) && (value <= rightValueBound); + return value >= leftValueBound && value <= rightValueBound; } @Override diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index 800d7ec..52117b6 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -1,6 +1,8 @@ package com.arup.cml.abm.kpi.matsim.run; import com.arup.cml.abm.kpi.KpiCalculator; +import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.ScalingFactor; import com.arup.cml.abm.kpi.data.MoneyLog; import com.arup.cml.abm.kpi.domain.NetworkLinkLog; import com.arup.cml.abm.kpi.matsim.MatsimUtils; @@ -92,7 +94,8 @@ public void run() { CompressionType.gzip ); - kpiCalculator.writeAffordabilityKpi(outputDir); + ScalingFactor scalingFactor = new LinearScale(0, 10, 1.25, 1); + kpiCalculator.writeAffordabilityKpi(outputDir, scalingFactor); kpiCalculator.writePtWaitTimeKpi(outputDir); kpiCalculator.writeModalSplitKpi(outputDir); kpiCalculator.writeOccupancyRateKpi(outputDir); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index ab32922..703caaf 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -167,7 +167,7 @@ public void accept(Row row) { } @Override - public double writeAffordabilityKpi(Path outputDirectory) { + public double writeAffordabilityKpi(Path outputDirectory, ScalingFactor scalingFactor) { LOGGER.info("Writing Affordability KPI to {}", outputDirectory); // join personal income / subpop info @@ -250,7 +250,9 @@ public void accept(Double income) { .where(intermediate.stringColumn(incomeColumnName).isEqualTo(lowIncomeName)) .doubleColumn("mean_daily_monetary_cost") .get(0); - double kpi = round(lowIncomeAverageCost / overallAverageCost, 2); + double kpi = round( + scalingFactor.scale(lowIncomeAverageCost / overallAverageCost), + 2); writeContentToFile(String.format("%s/kpi-affordability.csv", outputDirectory), String.valueOf(kpi), this.compressionType); return kpi; } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java index 09145cf..e0e5ab8 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java @@ -1,5 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; +import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.ScalingFactor; import com.arup.cml.abm.kpi.builders.*; import org.assertj.core.data.Offset; import org.junit.Rule; @@ -11,6 +13,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class TestTablesawAffordabilityKpi { + // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative + // `equivalentScalingFactor` to multiply the expected Affordability KPI Ratio by + ScalingFactor scalingFactor = new LinearScale(0, 1, 0, 10); + double equivalentScalingFactor = 1.0 / 10.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -32,11 +38,12 @@ public void testSingleAgentGivesRatioOfOne() { .withMonetaryCostsForSubpopulationAndMode(bobbySubpop, "car", 1.0, 1.0) .build()) .build(); - double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); + double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath()), scalingFactor); - assertThat(outputKpi).isEqualTo(1) + double expectedRatio = 1; + assertThat(outputKpi).isEqualTo(expectedRatio * equivalentScalingFactor) .as("There is only one agent so the ratio of all travel to the low income group " + - "is expected to be 1"); + "is expected to be 1, and 0.1 after scaling"); } @Test @@ -46,7 +53,6 @@ public void lowerIncomeAgentSpendsTwiceAsMuch() { Integer tripLength = 10; double dailyConstant = 1; double distanceCost = 1; - double singleTripCost = dailyConstant + tripLength * distanceCost; String poorBobby = "PoorBobby"; double poorBobbyIncome = 10000; @@ -69,13 +75,19 @@ public void lowerIncomeAgentSpendsTwiceAsMuch() { .withMonetaryCostsForSubpopulationAndMode(subpop, mode, dailyConstant, distanceCost) .build()) .build(); - double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); - - double poorBobbyDayAverage = 2 * singleTripCost; - double overallDayAverage = 3 * singleTripCost / 2; - assertThat(outputKpi).isCloseTo(poorBobbyDayAverage / overallDayAverage, Offset.offset(0.009)) - .as("There is only one agent so the ratio of all travel to the low income group " + - "is expected to be 1"); + double outputKpi = kpiCalculator.writeAffordabilityKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + scalingFactor + ); + + // why 1.33.. ? + // 2x / (3x/2) = 4x/3x = 1 + 1/3 + // 2x <- poor Bobby cost + // (3x/2) <- overall average + double expectedRatio = 1 + 1.0/3; + assertThat(outputKpi).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) + .as("There are two agents, the poorer agent spends twice as much on travel. The ratio of " + + "all travel to the low income group is expected to be 1.33, and 0.133 after scaling."); } @Test @@ -84,7 +96,6 @@ public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { Integer tripLength = 10; double dailyConstant = 1; double distanceCost = 1; - double singleTripCost = dailyConstant + tripLength * distanceCost; String poorBobby = "PoorBobby"; String poorBobbySubpop = "low income"; @@ -108,13 +119,16 @@ public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { .withMonetaryCostsForSubpopulationAndMode(richBobbySubpop, mode, dailyConstant, distanceCost) .build()) .build(); - double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); - - double poorBobbyDayAverage = 2 * singleTripCost; - double overallDayAverage = 3 * singleTripCost / 2; - assertThat(outputKpi).isCloseTo(poorBobbyDayAverage / overallDayAverage, Offset.offset(0.009)) - .as("There is only one agent so the ratio of all travel to the low income group " + - "is expected to be 1"); + double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath()), scalingFactor); + + // why 1.33.. ? + // 2x / (3x/2) = 4x/3x = 1 + 1/3 + // 2x <- poor Bobby cost + // (3x/2) <- overall average + double expectedRatio = 1 + 1.0/3; + assertThat(outputKpi).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) + .as("There are two agents, the poorer agent spends twice as much on travel. The ratio of " + + "all travel to the low income group is expected to be 1.33, and 0.133 after scaling."); } diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-affordability.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-affordability.csv.gz index ea525526f373131201cc66b5aceaf6b4ccbfad53..afe448d26287c639626b0561dce23f38cb2c8777 100644 GIT binary patch literal 23 Zcmb2|=3oGW|Hc=L85o#YzkFi`@&Puv1l#}s literal 24 bcmb2|=3oGW|HfA~Z((2%lWVJG0SW*BL-Pe& diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-affordability.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-affordability.csv.gz index 4e19f26d8ab903698de459d363a035968e444cd0..5d00bfd06c318d5ba113f5aeaee758be7af76b5d 100644 GIT binary patch literal 24 bcmb2|=3oGW|HdX4j2ReC%t=dN0SW*BKUD=v literal 24 bcmb2|=3oGW|Hc=L%@`Rj-Ay)Q0SW*BK|=*= From f092004743e334a8fa242f71b99449c8ba906b76 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 15:52:21 +0000 Subject: [PATCH 06/39] refactor to make it more obvious we're testing with linear scaling factor --- ...fordabilityKpiWithLinearScalingFactor.java | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java new file mode 100644 index 0000000..e422854 --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java @@ -0,0 +1,142 @@ +package com.arup.cml.abm.kpi.tablesaw; + +import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.builders.*; +import org.assertj.core.data.Offset; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TestTablesawAffordabilityKpiWithLinearScalingFactor { + // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative + // `equivalentScalingFactor` to multiply the expected Affordability KPI Ratio by + ScalingFactor linearScalingFactor = new LinearScale(0, 1, 0, 10); + double equivalentScalingFactor = 1.0 / 10.0; + + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + + @Test + public void testSingleAgentGivesRatioOfOne() { + String bobby = "Bobby"; + String bobbySubpop = "default"; + Integer bobbyTripLength = 10; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsBuilder(tmpDir) + .withLegWithDistanceAndMode(bobby, "bobby_1", bobbyTripLength, "car") + .build()) + .withPersons(new PersonsBuilder(tmpDir) + .withPerson(bobby, 10000, bobbySubpop) + .build()) + .withScoring(new ScoringConfigBuilder() + .withMonetaryCostsForSubpopulationAndMode(bobbySubpop, "car", 1.0, 1.0) + .build()) + .build(); + double outputKpi = kpiCalculator.writeAffordabilityKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor + ); + + double expectedRatio = 1; + assertThat(outputKpi).isEqualTo(expectedRatio * equivalentScalingFactor) + .as("There is only one agent so the ratio of all travel to the low income group " + + "is expected to be 1, and 0.1 after scaling"); + } + + @Test + public void lowerIncomeAgentSpendsTwiceAsMuch() { + String subpop = "default"; + String mode = "car"; + Integer tripLength = 10; + double dailyConstant = 1; + double distanceCost = 1; + + String poorBobby = "PoorBobby"; + double poorBobbyIncome = 10000; + + String richBobby = "MintedBobby"; + double richBobbyIncome = 1000000; // it's all about those zeros :) + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsBuilder(tmpDir) + // poor Bobby has to make two trips and spends twice as much on travel + .withLegWithDistanceAndMode(poorBobby, "poor_bobby_1", tripLength, mode) + .withLegWithDistanceAndMode(poorBobby, "poor_bobby_2", tripLength, mode) + .withLegWithDistanceAndMode(richBobby, "rich_bobby_1", tripLength, mode) + .build()) + .withPersons(new PersonsBuilder(tmpDir) + .withPerson(poorBobby, poorBobbyIncome, subpop) + .withPerson(richBobby, richBobbyIncome, subpop) + .build()) + .withScoring(new ScoringConfigBuilder() + .withMonetaryCostsForSubpopulationAndMode(subpop, mode, dailyConstant, distanceCost) + .build()) + .build(); + double outputKpi = kpiCalculator.writeAffordabilityKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor + ); + + // why 1.33.. ? + // 2x / (3x/2) = 4x/3x = 1 + 1/3 + // 2x <- poor Bobby cost + // (3x/2) <- overall average + double expectedRatio = 1 + 1.0/3; + assertThat(outputKpi).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) + .as("There are two agents, the poorer agent spends twice as much on travel. The ratio of " + + "all travel to the low income group is expected to be 1.33, and 0.133 after scaling."); + } + + @Test + public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { + String mode = "car"; + Integer tripLength = 10; + double dailyConstant = 1; + double distanceCost = 1; + + String poorBobby = "PoorBobby"; + String poorBobbySubpop = "low income"; + + String richBobby = "MintedBobby"; + String richBobbySubpop = "high income"; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsBuilder(tmpDir) + // poor Bobby has to make two trips and spends twice as much on travel + .withLegWithDistanceAndMode(poorBobby, "poor_bobby_1", tripLength, mode) + .withLegWithDistanceAndMode(poorBobby, "poor_bobby_2", tripLength, mode) + .withLegWithDistanceAndMode(richBobby, "rich_bobby_1", tripLength, mode) + .build()) + .withPersons(new PersonsBuilder(tmpDir) + .withPersonWithMissingIncome(poorBobby, poorBobbySubpop) + .withPersonWithMissingIncome(richBobby, richBobbySubpop) + .build()) + .withScoring(new ScoringConfigBuilder() + .withMonetaryCostsForSubpopulationAndMode(poorBobbySubpop, mode, dailyConstant, distanceCost) + .withMonetaryCostsForSubpopulationAndMode(richBobbySubpop, mode, dailyConstant, distanceCost) + .build()) + .build(); + double outputKpi = kpiCalculator.writeAffordabilityKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor + ); + + // why 1.33.. ? + // 2x / (3x/2) = 4x/3x = 1 + 1/3 + // 2x <- poor Bobby cost + // (3x/2) <- overall average + double expectedRatio = 1 + 1.0/3; + assertThat(outputKpi).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) + .as("There are two agents, the poorer agent spends twice as much on travel. The ratio of " + + "all travel to the low income group is expected to be 1.33, and 0.133 after scaling."); + } + + +} + From f65eaaa488904d60776180bfc90e1ed5fcb459be Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 16:03:18 +0000 Subject: [PATCH 07/39] no test in test names --- .../TestTablesawAffordabilityKpiWithLinearScalingFactor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java index e422854..d3a726e 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java @@ -22,7 +22,7 @@ public class TestTablesawAffordabilityKpiWithLinearScalingFactor { public TemporaryFolder tmpDir = new TemporaryFolder(); @Test - public void testSingleAgentGivesRatioOfOne() { + public void singleAgentGivesRatioOfOne() { String bobby = "Bobby"; String bobbySubpop = "default"; Integer bobbyTripLength = 10; From d6bdf976cc6c2da04465146f5bdbb37edd7b89b3 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 16:31:44 +0000 Subject: [PATCH 08/39] add tests for pt wait time --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 2 +- .../kpi/tablesaw/TablesawKpiCalculator.java | 3 +- .../kpi/builders/KpiCalculatorBuilder.java | 2 +- .../cml/abm/kpi/builders/LegsBuilder.java | 10 ++ .../cml/abm/kpi/builders/PersonsBuilder.java | 12 +- .../kpi/builders/ScoringConfigBuilder.java | 13 +- .../TestTablesawAffordabilityKpi.java | 136 ------------------ .../tablesaw/TestTablesawPtWaitTimeKpi.java | 86 +++++++++++ 8 files changed, 119 insertions(+), 145 deletions(-) delete mode 100644 src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java create mode 100644 src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index 8a3e0fe..a116b18 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -7,7 +7,7 @@ public interface KpiCalculator { double writeAffordabilityKpi(Path outputDirectory, ScalingFactor scalingFactor); - void writePtWaitTimeKpi(Path outputDirectory); + double writePtWaitTimeKpi(Path outputDirectory); void writeModalSplitKpi(Path outputDirectory); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index 703caaf..f39f18d 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -271,7 +271,7 @@ private static String findStringWithSubstring(StringColumn col, String substring } @Override - public void writePtWaitTimeKpi(Path outputDirectory) { + public double writePtWaitTimeKpi(Path outputDirectory) { LOGGER.info("Writing PT Wait Time KPI to {}", outputDirectory); // pull out legs with PT stops information @@ -314,6 +314,7 @@ public void writePtWaitTimeKpi(Path outputDirectory) { kpi = round(kpi, 2); LOGGER.info("PT Wait Time KPI {}", kpi); writeContentToFile(String.format("%s/kpi-pt-wait-time.csv", outputDirectory), String.valueOf(kpi), this.compressionType); + return kpi; } @Override diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java index 8f384e4..271b212 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java @@ -25,7 +25,7 @@ public class KpiCalculatorBuilder { MoneyLog moneyLog = new MoneyLog(); String persons; ActivityFacilities facilities = new FacilitiesBuilder().build(); - ScoringConfigGroup scoring = new ScoringConfigBuilder().build(); + ScoringConfigGroup scoring = new ScoringConfigBuilder().withDefaultScoringParams().build(); public KpiCalculatorBuilder(TemporaryFolder tmpDir) { this.tmpDir = tmpDir; diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java index a7bf5b8..cb973ff 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java @@ -97,6 +97,16 @@ public LegsBuilder withLegWithDistanceAndMode(String person, String tripId, Inte ); } + public LegsBuilder withDefaultPtLegWithTiming( + String person, String tripId, String depTime, String travTime, String waitTime) { + return this.withLeg( + person, tripId, depTime, travTime, waitTime, defaultDistance, "bus", + defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, + "Stop A", "Stop B", "SomeTransitLine", + "SomeTransitRoute", "SomeBus" + ); + } + public String build() { if (legs.isEmpty()) { // empty table gets into trouble reading all the columns, if the table is empty, it is assumed it's not diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java index ece0d4c..2bdfeff 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java @@ -11,7 +11,8 @@ public class PersonsBuilder { TemporaryFolder tmpDir; - String defaultPerson = "Bobby"; + String defaultPerson1 = "Bobby"; + String defaultPerson2 = "Bobbina"; double defaultIncome = 10000.0; String defaultSubpopulation = "default"; Table persons = Table.create("persons").addColumns( @@ -33,9 +34,12 @@ public PersonsBuilder withPerson( return this; } - public PersonsBuilder withDefaultPerson() { + public PersonsBuilder withDefaultPersons() { + this.withPerson( + defaultPerson1, defaultIncome, defaultSubpopulation + ); return this.withPerson( - defaultPerson, defaultIncome, defaultSubpopulation + defaultPerson2, defaultIncome, defaultSubpopulation ); } @@ -50,7 +54,7 @@ public String build() { if (persons.isEmpty()) { // empty table gets into trouble reading all the columns, if the table is empty, it is assumed it's not // being used, so filling it with dud vales, just for the shape is ok - this.withDefaultPerson(); + this.withDefaultPersons(); } String path = String.valueOf(Path.of(tmpDir.getRoot().getAbsolutePath(), "output_persons.csv")); CsvWriteOptions options = CsvWriteOptions.builder(path).separator(';').build(); diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java index 593e2fd..adc7964 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java @@ -5,7 +5,6 @@ public class ScoringConfigBuilder { ScoringConfigGroup scoring; String defaultSubpopulation = "default"; - String defaultMode = "car"; double defaultDailyMonetaryConstant = 0.0; double defaultMonetaryDistanceRate = 0.0; double defaultConstant = 0.0; @@ -41,9 +40,19 @@ public ScoringConfigBuilder withMonetaryCostsForSubpopulationAndMode( } public ScoringConfigBuilder withDefaultScoringParams() { - return this.withScoringParams(defaultSubpopulation, defaultMode, defaultDailyMonetaryConstant, + this.withScoringParams(defaultSubpopulation, "car", defaultDailyMonetaryConstant, defaultMonetaryDistanceRate, defaultConstant, defaultMarginalUtilityOfDistance, defaultDailyUtilityConstant, defaultMarginalUtilityOfTraveling); + this.withScoringParams(defaultSubpopulation, "drt", defaultDailyMonetaryConstant, + defaultMonetaryDistanceRate, defaultConstant, defaultMarginalUtilityOfDistance, + defaultDailyUtilityConstant, defaultMarginalUtilityOfTraveling); + this.withScoringParams(defaultSubpopulation, "bus", defaultDailyMonetaryConstant, + defaultMonetaryDistanceRate, defaultConstant, defaultMarginalUtilityOfDistance, + defaultDailyUtilityConstant, defaultMarginalUtilityOfTraveling); + this.withScoringParams(defaultSubpopulation, "rail", defaultDailyMonetaryConstant, + defaultMonetaryDistanceRate, defaultConstant, defaultMarginalUtilityOfDistance, + defaultDailyUtilityConstant, defaultMarginalUtilityOfTraveling); + return this; } public ScoringConfigGroup build() { diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java deleted file mode 100644 index e0e5ab8..0000000 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpi.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.arup.cml.abm.kpi.tablesaw; - -import com.arup.cml.abm.kpi.LinearScale; -import com.arup.cml.abm.kpi.ScalingFactor; -import com.arup.cml.abm.kpi.builders.*; -import org.assertj.core.data.Offset; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.nio.file.Path; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -public class TestTablesawAffordabilityKpi { - // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative - // `equivalentScalingFactor` to multiply the expected Affordability KPI Ratio by - ScalingFactor scalingFactor = new LinearScale(0, 1, 0, 10); - double equivalentScalingFactor = 1.0 / 10.0; - - @Rule - public TemporaryFolder tmpDir = new TemporaryFolder(); - - @Test - public void testSingleAgentGivesRatioOfOne() { - String bobby = "Bobby"; - String bobbySubpop = "default"; - Integer bobbyTripLength = 10; - - TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withLegs(new LegsBuilder(tmpDir) - .withLegWithDistanceAndMode(bobby, "bobby_1", bobbyTripLength, "car") - .build()) - .withPersons(new PersonsBuilder(tmpDir) - .withPerson(bobby, 10000, bobbySubpop) - .build()) - .withScoring(new ScoringConfigBuilder() - .withMonetaryCostsForSubpopulationAndMode(bobbySubpop, "car", 1.0, 1.0) - .build()) - .build(); - double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath()), scalingFactor); - - double expectedRatio = 1; - assertThat(outputKpi).isEqualTo(expectedRatio * equivalentScalingFactor) - .as("There is only one agent so the ratio of all travel to the low income group " + - "is expected to be 1, and 0.1 after scaling"); - } - - @Test - public void lowerIncomeAgentSpendsTwiceAsMuch() { - String subpop = "default"; - String mode = "car"; - Integer tripLength = 10; - double dailyConstant = 1; - double distanceCost = 1; - - String poorBobby = "PoorBobby"; - double poorBobbyIncome = 10000; - - String richBobby = "MintedBobby"; - double richBobbyIncome = 1000000; // it's all about those zeros :) - - TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withLegs(new LegsBuilder(tmpDir) - // poor Bobby has to make two trips and spends twice as much on travel - .withLegWithDistanceAndMode(poorBobby, "poor_bobby_1", tripLength, mode) - .withLegWithDistanceAndMode(poorBobby, "poor_bobby_2", tripLength, mode) - .withLegWithDistanceAndMode(richBobby, "rich_bobby_1", tripLength, mode) - .build()) - .withPersons(new PersonsBuilder(tmpDir) - .withPerson(poorBobby, poorBobbyIncome, subpop) - .withPerson(richBobby, richBobbyIncome, subpop) - .build()) - .withScoring(new ScoringConfigBuilder() - .withMonetaryCostsForSubpopulationAndMode(subpop, mode, dailyConstant, distanceCost) - .build()) - .build(); - double outputKpi = kpiCalculator.writeAffordabilityKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()), - scalingFactor - ); - - // why 1.33.. ? - // 2x / (3x/2) = 4x/3x = 1 + 1/3 - // 2x <- poor Bobby cost - // (3x/2) <- overall average - double expectedRatio = 1 + 1.0/3; - assertThat(outputKpi).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) - .as("There are two agents, the poorer agent spends twice as much on travel. The ratio of " + - "all travel to the low income group is expected to be 1.33, and 0.133 after scaling."); - } - - @Test - public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { - String mode = "car"; - Integer tripLength = 10; - double dailyConstant = 1; - double distanceCost = 1; - - String poorBobby = "PoorBobby"; - String poorBobbySubpop = "low income"; - - String richBobby = "MintedBobby"; - String richBobbySubpop = "high income"; - - TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withLegs(new LegsBuilder(tmpDir) - // poor Bobby has to make two trips and spends twice as much on travel - .withLegWithDistanceAndMode(poorBobby, "poor_bobby_1", tripLength, mode) - .withLegWithDistanceAndMode(poorBobby, "poor_bobby_2", tripLength, mode) - .withLegWithDistanceAndMode(richBobby, "rich_bobby_1", tripLength, mode) - .build()) - .withPersons(new PersonsBuilder(tmpDir) - .withPersonWithMissingIncome(poorBobby, poorBobbySubpop) - .withPersonWithMissingIncome(richBobby, richBobbySubpop) - .build()) - .withScoring(new ScoringConfigBuilder() - .withMonetaryCostsForSubpopulationAndMode(poorBobbySubpop, mode, dailyConstant, distanceCost) - .withMonetaryCostsForSubpopulationAndMode(richBobbySubpop, mode, dailyConstant, distanceCost) - .build()) - .build(); - double outputKpi = kpiCalculator.writeAffordabilityKpi(Path.of(tmpDir.getRoot().getAbsolutePath()), scalingFactor); - - // why 1.33.. ? - // 2x / (3x/2) = 4x/3x = 1 + 1/3 - // 2x <- poor Bobby cost - // (3x/2) <- overall average - double expectedRatio = 1 + 1.0/3; - assertThat(outputKpi).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) - .as("There are two agents, the poorer agent spends twice as much on travel. The ratio of " + - "all travel to the low income group is expected to be 1.33, and 0.133 after scaling."); - } - - -} - diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java new file mode 100644 index 0000000..d3d3732 --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java @@ -0,0 +1,86 @@ +package com.arup.cml.abm.kpi.tablesaw; + +import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; +import com.arup.cml.abm.kpi.builders.LegsBuilder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TestTablesawPtWaitTimeKpi { + + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + + @Test + public void ptWaitTimeReturnsTheSinglePtWaitTimeWithSingleAgent() { + String bobby = "Bobby"; + String bobbyPtWaitTime = "00:06:00"; + Double bobbyPtWaitTimeSeconds = 6.0 * 60.0; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsBuilder(tmpDir) + .withDefaultPtLegWithTiming(bobby, "bobby_1", "09:00:00", "00:30:00", bobbyPtWaitTime) + .build()) + .build(); + double outputKpi = kpiCalculator.writePtWaitTimeKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// scalingFactor + ); + + assertThat(outputKpi).isEqualTo(bobbyPtWaitTimeSeconds) + .as("PT Wait Time KPI should return the only PT wait time recorded with one agent"); + } + + @Test + public void ptWaitTimeReturnsZeroWhenPtLegIsOutsidePeakTime() { + String bobby = "Bobby"; + String bobbyPtWaitTime = "00:06:00"; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsBuilder(tmpDir) + .withDefaultPtLegWithTiming(bobby, "bobby_1", "23:00:00", "00:30:00", bobbyPtWaitTime) + .build()) + .build(); + double outputKpi = kpiCalculator.writePtWaitTimeKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// scalingFactor + ); + + assertThat(outputKpi).isEqualTo(0.0) + .as("PT Wait Time KPI should return 0, as PT Leg is outside peak time"); + } + + @Test + public void givesAverageOfTwoPeakPtLegs() { + String bobby = "Bobby"; + String bobbyPtWaitTime = "00:06:00"; + Double bobbyPtWaitTimeSeconds = 6.0 * 60.0; + + String bobbina = "Bobbina"; + String bobbinaPtWaitTime = "00:12:00"; + Double bobbinaPtWaitTimeSeconds = 12.0 * 60.0; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsBuilder(tmpDir) + .withDefaultPtLegWithTiming(bobby, "bobby_1", "09:00:00", "00:30:00", bobbyPtWaitTime) + .withDefaultPtLegWithTiming(bobbina, "bobbina_1", "09:00:00", "00:30:00", bobbinaPtWaitTime) + .build()) + .build(); + double outputKpi = kpiCalculator.writePtWaitTimeKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// scalingFactor + ); + + assertThat(outputKpi).isEqualTo((bobbyPtWaitTimeSeconds + bobbinaPtWaitTimeSeconds) / 2) + .as("PT Wait Time KPI should return average of 6 and 12 minutes for Bobby " + + "and his friend Bobbina"); + } + +} + From 2ecd79099634e6600a1ee9ec5d575968edba9c81 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 16:51:04 +0000 Subject: [PATCH 09/39] add scaling for pt wait time kpi --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 2 +- .../kpi/matsim/run/MatsimKpiGenerator.java | 6 ++--- .../kpi/tablesaw/TablesawKpiCalculator.java | 4 ++-- .../tablesaw/TestTablesawPtWaitTimeKpi.java | 21 ++++++++++-------- .../expected-kpi-pt-wait-time.csv.gz | Bin 55 -> 24 bytes .../expected-kpi-pt-wait-time.csv.gz | Bin 25 -> 24 bytes 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index a116b18..0670c72 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -7,7 +7,7 @@ public interface KpiCalculator { double writeAffordabilityKpi(Path outputDirectory, ScalingFactor scalingFactor); - double writePtWaitTimeKpi(Path outputDirectory); + double writePtWaitTimeKpi(Path outputDirectory, ScalingFactor scalingFactor); void writeModalSplitKpi(Path outputDirectory); diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index 52117b6..8320e98 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -2,7 +2,6 @@ import com.arup.cml.abm.kpi.KpiCalculator; import com.arup.cml.abm.kpi.LinearScale; -import com.arup.cml.abm.kpi.ScalingFactor; import com.arup.cml.abm.kpi.data.MoneyLog; import com.arup.cml.abm.kpi.domain.NetworkLinkLog; import com.arup.cml.abm.kpi.matsim.MatsimUtils; @@ -94,9 +93,8 @@ public void run() { CompressionType.gzip ); - ScalingFactor scalingFactor = new LinearScale(0, 10, 1.25, 1); - kpiCalculator.writeAffordabilityKpi(outputDir, scalingFactor); - kpiCalculator.writePtWaitTimeKpi(outputDir); + kpiCalculator.writeAffordabilityKpi(outputDir, new LinearScale(0, 10, 1.25, 1)); + kpiCalculator.writePtWaitTimeKpi(outputDir, new LinearScale(0, 10, 15 * 60.0, 5 * 60.0)); kpiCalculator.writeModalSplitKpi(outputDir); kpiCalculator.writeOccupancyRateKpi(outputDir); kpiCalculator.writeVehicleKMKpi(outputDir); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index f39f18d..5f4198c 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -271,7 +271,7 @@ private static String findStringWithSubstring(StringColumn col, String substring } @Override - public double writePtWaitTimeKpi(Path outputDirectory) { + public double writePtWaitTimeKpi(Path outputDirectory, ScalingFactor scalingFactor) { LOGGER.info("Writing PT Wait Time KPI to {}", outputDirectory); // pull out legs with PT stops information @@ -311,7 +311,7 @@ public double writePtWaitTimeKpi(Path outputDirectory) { .and(table.intColumn("hour").isLessThan(10))) .intColumn("wait_time_seconds") .mean(); - kpi = round(kpi, 2); + kpi = round(scalingFactor.scale(kpi), 2); LOGGER.info("PT Wait Time KPI {}", kpi); writeContentToFile(String.format("%s/kpi-pt-wait-time.csv", outputDirectory), String.valueOf(kpi), this.compressionType); return kpi; diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java index d3d3732..4887fa1 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java @@ -13,7 +13,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class TestTablesawPtWaitTimeKpi { - + // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative + // `equivalentScalingFactor` to multiply the expected KPI output by + ScalingFactor linearScalingFactor = new LinearScale(0, 1, 0, 15 * 60); + double equivalentScalingFactor = 1.0 / (15.0 * 60); @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -29,11 +32,11 @@ public void ptWaitTimeReturnsTheSinglePtWaitTimeWithSingleAgent() { .build()) .build(); double outputKpi = kpiCalculator.writePtWaitTimeKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// scalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); - assertThat(outputKpi).isEqualTo(bobbyPtWaitTimeSeconds) + assertThat(outputKpi).isEqualTo(bobbyPtWaitTimeSeconds * equivalentScalingFactor) .as("PT Wait Time KPI should return the only PT wait time recorded with one agent"); } @@ -48,8 +51,8 @@ public void ptWaitTimeReturnsZeroWhenPtLegIsOutsidePeakTime() { .build()) .build(); double outputKpi = kpiCalculator.writePtWaitTimeKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// scalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi).isEqualTo(0.0) @@ -73,11 +76,11 @@ public void givesAverageOfTwoPeakPtLegs() { .build()) .build(); double outputKpi = kpiCalculator.writePtWaitTimeKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// scalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); - assertThat(outputKpi).isEqualTo((bobbyPtWaitTimeSeconds + bobbinaPtWaitTimeSeconds) / 2) + assertThat(outputKpi).isEqualTo((bobbyPtWaitTimeSeconds + bobbinaPtWaitTimeSeconds) / 2 * equivalentScalingFactor) .as("PT Wait Time KPI should return average of 6 and 12 minutes for Bobby " + "and his friend Bobbina"); } diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-pt-wait-time.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-pt-wait-time.csv.gz index c8b46ca0b748f893d943acfa2ad15e80f00bcc9e..289a03daf5d58622a7b76e0b10c9f2987723bf35 100644 GIT binary patch literal 24 bcmb2|=3oGW|Hc=M4Okg2Xlg%Y0SW*BK2-%P literal 55 zcmb2|=HOWR Date: Fri, 15 Mar 2024 17:01:05 +0000 Subject: [PATCH 10/39] rename pt wait test --- ...va => TestTablesawPtWaitTimeKpiWithLinearScalingFactor.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawPtWaitTimeKpi.java => TestTablesawPtWaitTimeKpiWithLinearScalingFactor.java} (98%) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearScalingFactor.java similarity index 98% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearScalingFactor.java index 4887fa1..0bf92b0 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearScalingFactor.java @@ -12,7 +12,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class TestTablesawPtWaitTimeKpi { +public class TestTablesawPtWaitTimeKpiWithLinearScalingFactor { // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by ScalingFactor linearScalingFactor = new LinearScale(0, 1, 0, 15 * 60); From d7cb6404f985200eb4741136ad5522c2a0b60c06 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 17:29:33 +0000 Subject: [PATCH 11/39] add test for occupancy rate kpi --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 2 +- .../kpi/tablesaw/TablesawKpiCalculator.java | 3 +- .../cml/abm/kpi/builders/LinkLogBuilder.java | 5 +++ .../cml/abm/kpi/builders/VehiclesBuilder.java | 5 +++ .../tablesaw/TestTablesawOccupancyeKpi.java | 43 +++++++++++++++++++ 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpi.java diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index 0670c72..2f4f897 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -11,7 +11,7 @@ public interface KpiCalculator { void writeModalSplitKpi(Path outputDirectory); - void writeOccupancyRateKpi(Path outputDirectory); + double writeOccupancyRateKpi(Path outputDirectory); double writeVehicleKMKpi(Path outputDirectory); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index 5f4198c..d2c04d3 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -332,7 +332,7 @@ public void writeModalSplitKpi(Path outputDirectory) { } @Override - public void writeOccupancyRateKpi(Path outputDirectory) { + public double writeOccupancyRateKpi(Path outputDirectory) { LOGGER.info("Writing Occupancy Rate KPI to {}", outputDirectory); // add capacity of the vehicle @@ -369,6 +369,7 @@ public void writeOccupancyRateKpi(Path outputDirectory) { LOGGER.info("Occupancy Rate KPI {}", kpi); writeContentToFile(String.format("%s/kpi-occupancy-rate.csv", outputDirectory), String.valueOf(kpi), this.compressionType); + return kpi; } @Override diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/LinkLogBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/LinkLogBuilder.java index 3e330b6..8a22c10 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/LinkLogBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/LinkLogBuilder.java @@ -11,6 +11,11 @@ public LinkLogBuilder withEntry(String vehicleID, String linkID, double startTim return this; } + public LinkLogBuilder withOccupant(String vehicleID, String personID) { + linkLog.personBoardsVehicle(vehicleID, personID); + return this; + } + public LinkLog build() { return this.linkLog; } diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java index 9daee69..8276377 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java @@ -26,10 +26,15 @@ public VehiclesBuilder withVehicleType(String vehicleType) { } public VehiclesBuilder withVehicleType(String vehicleType, String mode) { + return this.withVehicleTypeOfCapacity(vehicleType, mode, 2); + } + + public VehiclesBuilder withVehicleTypeOfCapacity(String vehicleType, String mode, Integer seats) { Id vehicleTypeId = Id.create(vehicleType, VehicleType.class); if (!vehicles.getVehicleTypes().containsKey(vehicleTypeId)) { VehicleType matsimVehicleType = VehicleUtils.createVehicleType(vehicleTypeId); matsimVehicleType.setNetworkMode(mode); + matsimVehicleType.getCapacity().setSeats(seats); vehicles.addVehicleType(matsimVehicleType); } return this; diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpi.java new file mode 100644 index 0000000..c837799 --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpi.java @@ -0,0 +1,43 @@ +package com.arup.cml.abm.kpi.tablesaw; + +import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; +import com.arup.cml.abm.kpi.builders.LinkLogBuilder; +import com.arup.cml.abm.kpi.builders.VehiclesBuilder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TestTablesawOccupancyeKpi { + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + + @Test + public void twoPeopleInAFourSeaterGiveHalfOccupancy() { + String someCar = "someCar"; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLinkLog(new LinkLogBuilder() + .withOccupant(someCar, "Bobby") + .withOccupant(someCar, "Bobbina") + .withEntry(someCar, "someLink", (9 * 60 * 60), (9 * 60 * 60) + 25) + .build()) + .withVehicles(new VehiclesBuilder() + .withVehicleTypeOfCapacity("car", "car", 4) + .withVehicle(someCar, "car", "car") + .build()) + .build(); + double outputKpi = kpiCalculator.writeOccupancyRateKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi).isEqualTo(0.5) + .as("Occupancy Rate should be at half with two people in a car that fits four."); + } + +} + From c5383742261598b21e3399a51ad13a4a6858d0f1 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 17:44:58 +0000 Subject: [PATCH 12/39] add scaling for occupancy rate kpi --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 2 +- .../abm/kpi/matsim/run/MatsimKpiGenerator.java | 2 +- .../kpi/tablesaw/TablesawKpiCalculator.java | 4 ++-- ...wOccupancyeKpiWithLinearScalingFactor.java} | 17 ++++++++++++----- .../expected-kpi-occupancy-rate.csv.gz | Bin 56 -> 23 bytes .../expected-kpi-occupancy-rate.csv.gz | Bin 24 -> 23 bytes 6 files changed, 16 insertions(+), 9 deletions(-) rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawOccupancyeKpi.java => TestTablesawOccupancyeKpiWithLinearScalingFactor.java} (65%) diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index 2f4f897..ce5f1aa 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -11,7 +11,7 @@ public interface KpiCalculator { void writeModalSplitKpi(Path outputDirectory); - double writeOccupancyRateKpi(Path outputDirectory); + double writeOccupancyRateKpi(Path outputDirectory, ScalingFactor scalingFactor); double writeVehicleKMKpi(Path outputDirectory); diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index 8320e98..bb7ef4b 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -96,7 +96,7 @@ public void run() { kpiCalculator.writeAffordabilityKpi(outputDir, new LinearScale(0, 10, 1.25, 1)); kpiCalculator.writePtWaitTimeKpi(outputDir, new LinearScale(0, 10, 15 * 60.0, 5 * 60.0)); kpiCalculator.writeModalSplitKpi(outputDir); - kpiCalculator.writeOccupancyRateKpi(outputDir); + kpiCalculator.writeOccupancyRateKpi(outputDir, new LinearScale(0, 10, 0.2, 0.6)); kpiCalculator.writeVehicleKMKpi(outputDir); kpiCalculator.writePassengerKMKpi(outputDir); kpiCalculator.writeSpeedKpi(outputDir); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index d2c04d3..13fb284 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -332,7 +332,7 @@ public void writeModalSplitKpi(Path outputDirectory) { } @Override - public double writeOccupancyRateKpi(Path outputDirectory) { + public double writeOccupancyRateKpi(Path outputDirectory, ScalingFactor scalingFactor) { LOGGER.info("Writing Occupancy Rate KPI to {}", outputDirectory); // add capacity of the vehicle @@ -365,7 +365,7 @@ public double writeOccupancyRateKpi(Path outputDirectory) { double kpi = averageOccupancyPerVehicle.doubleColumn("Mean [numberOfPeople] / Mean [capacity]").sum(); kpi = kpi / numberOfVehicles; - kpi = round(kpi, 2); + kpi = round(scalingFactor.scale(kpi), 2); LOGGER.info("Occupancy Rate KPI {}", kpi); writeContentToFile(String.format("%s/kpi-occupancy-rate.csv", outputDirectory), String.valueOf(kpi), this.compressionType); diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpiWithLinearScalingFactor.java similarity index 65% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpi.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpiWithLinearScalingFactor.java index c837799..7c12475 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpiWithLinearScalingFactor.java @@ -1,5 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; +import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.ScalingFactor; import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; import com.arup.cml.abm.kpi.builders.LinkLogBuilder; import com.arup.cml.abm.kpi.builders.VehiclesBuilder; @@ -11,7 +13,11 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class TestTablesawOccupancyeKpi { +public class TestTablesawOccupancyeKpiWithLinearScalingFactor { + // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative + // `equivalentScalingFactor` to multiply the expected KPI output by + ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 1); + double equivalentScalingFactor = 10.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -31,12 +37,13 @@ public void twoPeopleInAFourSeaterGiveHalfOccupancy() { .build()) .build(); double outputKpi = kpiCalculator.writeOccupancyRateKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); - assertThat(outputKpi).isEqualTo(0.5) - .as("Occupancy Rate should be at half with two people in a car that fits four."); + assertThat(outputKpi).isEqualTo(0.5 * equivalentScalingFactor) + .as("Occupancy Rate should be at half with two people in a car that fits four," + + "and 5 after scaling."); } } diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-occupancy-rate.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-occupancy-rate.csv.gz index e7dbf89d3a8b1d40feed551f8890adb3a5ccb744..afe448d26287c639626b0561dce23f38cb2c8777 100644 GIT binary patch literal 23 Zcmb2|=3oGW|Hc=L85o#YzkFi`@&Puv1l#}s literal 56 zcmb2|=HL*LyPV3voLW(knp~2aqMKcishgjiTw0Krmt3h^lvt9gmt0)NV0^)N0}I2N LPydry7#J7;5WN!| diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-occupancy-rate.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-occupancy-rate.csv.gz index 8f1ebf0fa35984bf5e07e87520e59772ccfe19a1..afe448d26287c639626b0561dce23f38cb2c8777 100644 GIT binary patch literal 23 Zcmb2|=3oGW|Hc=L85o#YzkFi`@&Puv1l#}s literal 24 bcmb2|=3oGW|Hc=LP1qS;y^ER40u%rMM0o}H From c1e432a8094a0935f7f8e45f9aa99600f6e265f7 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 17:46:39 +0000 Subject: [PATCH 13/39] fix typo in occupancy test file --- ...ava => TestTablesawOccupancyKpiWithLinearScalingFactor.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawOccupancyeKpiWithLinearScalingFactor.java => TestTablesawOccupancyKpiWithLinearScalingFactor.java} (96%) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpiWithLinearScalingFactor.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java similarity index 96% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpiWithLinearScalingFactor.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java index 7c12475..eedbf73 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyeKpiWithLinearScalingFactor.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java @@ -13,7 +13,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class TestTablesawOccupancyeKpiWithLinearScalingFactor { +public class TestTablesawOccupancyKpiWithLinearScalingFactor { // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 1); From f4af05ffaf7135b979af0e6fd66de0b77c7907bc Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 18:15:49 +0000 Subject: [PATCH 14/39] improve vehicles builder --- .../cml/abm/kpi/builders/VehiclesBuilder.java | 42 +++++++++---------- .../tablesaw/TestTablesawCongestionKpi.java | 2 +- ...awOccupancyKpiWithLinearScalingFactor.java | 3 +- .../tablesaw/TestTablesawVehicleKmKpi.java | 2 +- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java index 8276377..8fedce8 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java @@ -9,34 +9,32 @@ public class VehiclesBuilder { Vehicles vehicles = VehicleUtils.createVehiclesContainer(); - public VehiclesBuilder withVehicle(String id, String vehicleType) { - return withVehicle(id, vehicleType, "car"); + public VehiclesBuilder withVehicle(String vehicleId, String vehicleType) { + return withVehicleOfMode(vehicleId, vehicleType, "car"); } - public VehiclesBuilder withVehicle(String id, String vehicleType, String mode) { - withVehicleType(vehicleType, mode); - VehicleType matsimVehicleType = vehicles.getVehicleTypes().get(Id.create(vehicleType, VehicleType.class)); - Vehicle vehicle = VehicleUtils.createVehicle(Id.createVehicleId(id), matsimVehicleType); - vehicles.addVehicle(vehicle); - return this; - } - - public VehiclesBuilder withVehicleType(String vehicleType) { - return withVehicleType(vehicleType, "car"); + public VehiclesBuilder withVehicleOfMode(String vehicleId, String vehicleType, String mode) { + return this.withVehicleOfCapacity(vehicleId, vehicleType, mode, 2); } - public VehiclesBuilder withVehicleType(String vehicleType, String mode) { - return this.withVehicleTypeOfCapacity(vehicleType, mode, 2); + public VehiclesBuilder withVehicleOfCapacity(String vehicleId, String vehicleTypeID, String mode, Integer seats) { + Id vehTypeId = Id.create(vehicleTypeID, VehicleType.class); + VehicleType matsimVehicleType = vehicles.getVehicleTypes().getOrDefault( + vehTypeId, VehicleUtils.createVehicleType(vehTypeId)); + matsimVehicleType.setNetworkMode(mode); + matsimVehicleType.getCapacity().setSeats(seats); + vehicles.addVehicleType(matsimVehicleType); + vehicles.addVehicle(VehicleUtils.createVehicle(Id.createVehicleId(vehicleId), matsimVehicleType)); + return this; } - public VehiclesBuilder withVehicleTypeOfCapacity(String vehicleType, String mode, Integer seats) { - Id vehicleTypeId = Id.create(vehicleType, VehicleType.class); - if (!vehicles.getVehicleTypes().containsKey(vehicleTypeId)) { - VehicleType matsimVehicleType = VehicleUtils.createVehicleType(vehicleTypeId); - matsimVehicleType.setNetworkMode(mode); - matsimVehicleType.getCapacity().setSeats(seats); - vehicles.addVehicleType(matsimVehicleType); - } + public VehiclesBuilder withVehicleWithEmissionsFactor(String vehicleId, String vehicleTypeId, double emissionsFactor) { + Id vehTypeId = Id.create(vehicleTypeId, VehicleType.class); + VehicleType matsimVehicleType = vehicles.getVehicleTypes().getOrDefault( + vehTypeId, VehicleUtils.createVehicleType(vehTypeId)); + matsimVehicleType.getEngineInformation().getAttributes().putAttribute("emissionsFactor", emissionsFactor); + vehicles.addVehicleType(matsimVehicleType); + vehicles.addVehicle(VehicleUtils.createVehicle(Id.createVehicleId(vehicleId), matsimVehicleType)); return this; } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java index a96eaf6..2c81354 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java @@ -29,7 +29,7 @@ public void linkWithInfiniteSpeedDoesNotBreakCongestionKpi() { .withNetworkLink("otherLink", "B", "A") .build()) .withVehicles(new VehiclesBuilder() - .withVehicle("someCar", "car", "car") + .withVehicle("someCar", "car") .build()) .withLinkLog(new LinkLogBuilder() .withEntry("someCar", "infLink", (9 * 60 * 60), (9 * 60 * 60) + 25) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java index eedbf73..72f59e2 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java @@ -32,8 +32,7 @@ public void twoPeopleInAFourSeaterGiveHalfOccupancy() { .withEntry(someCar, "someLink", (9 * 60 * 60), (9 * 60 * 60) + 25) .build()) .withVehicles(new VehiclesBuilder() - .withVehicleTypeOfCapacity("car", "car", 4) - .withVehicle(someCar, "car", "car") + .withVehicleOfCapacity(someCar, "car", "car", 4) .build()) .build(); double outputKpi = kpiCalculator.writeOccupancyRateKpi( diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawVehicleKmKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawVehicleKmKpi.java index 3ff566e..c958857 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawVehicleKmKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawVehicleKmKpi.java @@ -27,7 +27,7 @@ public void vehicleKmKpiOutputIsOfTheRightUnit() { .withNetworkLinkWithLength("someLink", "A", "B", metreLength) .build()) .withVehicles(new VehiclesBuilder() - .withVehicle("someCar", "car", "car") + .withVehicle("someCar", "car") .build()) .withLinkLog(new LinkLogBuilder() .withEntry("someCar", "someLink", (9 * 60 * 60), (9 * 60 * 60) + 25) From 32e160a9a487ed8d5eb3ac9a56d294117781ccae Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Fri, 15 Mar 2024 18:33:53 +0000 Subject: [PATCH 15/39] add test for ghg, find and fix bug when calculating per capita --- .../kpi/tablesaw/TablesawKpiCalculator.java | 2 +- .../cml/abm/kpi/builders/PersonsBuilder.java | 6 ++ .../abm/kpi/tablesaw/TestTablesawGHGKpi.java | 54 ++++++++++++++++++ .../expected-kpi-ghg-emissions.csv.gz | Bin 23 -> 24 bytes .../expected-kpi-ghg-emissions.csv.gz | Bin 24 -> 24 bytes 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index 13fb284..cc456e3 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -498,7 +498,7 @@ public double writeGHGKpi(Path outputDirectory) { .setName("emissions")); double emissionsTotal = round(table.numberColumn("emissions").sum(), 2); - double emissionsPerCapita = round(emissionsTotal / personModeScores.column("person").size(), 2); + double emissionsPerCapita = round(emissionsTotal / personModeScores.column("person").countUnique(), 2); writeContentToFile( String.format("%s/intermediate-ghg-emissions.csv", outputDirectory), String.format("emissions_total,emissions_per_capita\n%f,%f", emissionsTotal, emissionsPerCapita), diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java index 2bdfeff..971cd5c 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java @@ -34,6 +34,12 @@ public PersonsBuilder withPerson( return this; } + public PersonsBuilder withPerson( + String person + ) { + return this.withPerson(person, defaultIncome, defaultSubpopulation); + } + public PersonsBuilder withDefaultPersons() { this.withPerson( defaultPerson1, defaultIncome, defaultSubpopulation diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java new file mode 100644 index 0000000..a28629a --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java @@ -0,0 +1,54 @@ +package com.arup.cml.abm.kpi.tablesaw; + +import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.builders.*; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TestTablesawGHGKpi { + // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative + // `equivalentScalingFactor` to multiply the expected KPI output by + ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 1); + double equivalentScalingFactor = 10.0; + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + + @Test + public void oneKilometerJourneyWithEmissionsFactorTwoAndSinglePersonGivesKpiOutputTwo() { + String someCar = "someCar"; + String someLink = "someLink"; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withNetwork(new NetworkBuilder() + .withNetworkNode("A", 1, 1) + .withNetworkNode("B", 2, 2) + .withNetworkLinkWithLength(someLink, "A", "B", 1000.0) + .build()) + .withLinkLog(new LinkLogBuilder() + .withEntry(someCar, someLink, (9 * 60 * 60), (9 * 60 * 60) + 25) + .build()) + .withVehicles(new VehiclesBuilder() + .withVehicleWithEmissionsFactor(someCar, "car", 2.0) + .build()) + .withPersons(new PersonsBuilder(tmpDir) + .withPerson("Bobby") + .build()) + .build(); + double outputKpi = kpiCalculator.writeGHGKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi).isEqualTo(2.0) + .as("A vehicle with emissions factor 2 drives a kilometer, " + + "so the output of the GHG KPI is expected to be 2."); + } + +} + diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz index c5c2c1b85e32d50636544a28991c7bffa3a23222..85cf6566b8d841b2189954845ee8607491e0a647 100644 GIT binary patch literal 24 acmb2|=3oGW|HfuljoBHvnGXc900jUyumny3 literal 23 Zcmb2|=3oGW|HfC1Ss3cSTeLF+`2af*1t|ak diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz index 371da57b23ef49889df81121a12aa9b83b231ffc..9e7321d4c4e37a90694a5cf991ab478ae27d8b8b 100644 GIT binary patch literal 24 bcmb2|=3oGW|C_HE8?!SkHy2&c0u%rMLpcRZ literal 24 bcmb2|=3oGW|HfC0jae9KcO7bG0SW*BL6QZd From 43f663be51719cf2cb408de223230414b1f5a4a0 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Mon, 18 Mar 2024 09:44:03 +0000 Subject: [PATCH 16/39] update changelog with ghg bug fix #73 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d411dc6..d92ecc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] - + +### Added + +- + +### Fixed + +- GHG Emissions KPI results where under-reported, by exaggerating number of people in per capita calculations ([#73](https://github.com/arup-group/gelato/issues/73)) + +### Changed + +- ## [0.0.3-alpha] - 2024-03-07 From 6d5541b35d4e3c1b9ae2e119e26f121c7527eab1 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Tue, 19 Mar 2024 12:28:22 +0000 Subject: [PATCH 17/39] add scaling for GHG KPI --- .../java/com/arup/cml/abm/kpi/KpiCalculator.java | 2 +- .../abm/kpi/matsim/run/MatsimKpiGenerator.java | 2 +- .../abm/kpi/tablesaw/TablesawKpiCalculator.java | 5 ++--- .../cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java | 12 ++++++------ .../expected-kpi-ghg-emissions.csv.gz | Bin 24 -> 23 bytes .../expected-kpi-ghg-emissions.csv.gz | Bin 24 -> 23 bytes 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index ce5f1aa..86c472e 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -19,7 +19,7 @@ public interface KpiCalculator { void writeSpeedKpi(Path outputDirectory); - double writeGHGKpi(Path outputDirectory); + double writeGHGKpi(Path outputDirectory, ScalingFactor scalingFactor); double writeTravelTimeKpi(Path outputDirectory); diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index bb7ef4b..532bccb 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -100,7 +100,7 @@ public void run() { kpiCalculator.writeVehicleKMKpi(outputDir); kpiCalculator.writePassengerKMKpi(outputDir); kpiCalculator.writeSpeedKpi(outputDir); - kpiCalculator.writeGHGKpi(outputDir); + kpiCalculator.writeGHGKpi(outputDir, new LinearScale(0, 10, 8.87, 0.0)); kpiCalculator.writeAccessToMobilityServicesKpi(outputDir); kpiCalculator.writeCongestionKpi(outputDir); kpiCalculator.writeTravelTimeKpi(outputDir); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index cc456e3..5f9aac7 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -477,7 +477,7 @@ public void writeSpeedKpi(Path outputDirectory) { } @Override - public double writeGHGKpi(Path outputDirectory) { + public double writeGHGKpi(Path outputDirectory, ScalingFactor scalingFactor) { LOGGER.info("Writing GHG KPIs to {}", outputDirectory); // add link length to the link log table @@ -504,8 +504,7 @@ public double writeGHGKpi(Path outputDirectory) { String.format("emissions_total,emissions_per_capita\n%f,%f", emissionsTotal, emissionsPerCapita), this.compressionType); - // TODO Add Scaling - double kpi = emissionsPerCapita; + double kpi = scalingFactor.scale(emissionsPerCapita); writeContentToFile(String.format("%s/kpi-ghg-emissions.csv", outputDirectory), String.valueOf(kpi), this.compressionType); return kpi; } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java index a28629a..16cf75a 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java @@ -14,8 +14,8 @@ public class TestTablesawGHGKpi { // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by - ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 1); - double equivalentScalingFactor = 10.0; + ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 20); + double equivalentScalingFactor = 1.0 / 2.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -41,13 +41,13 @@ public void oneKilometerJourneyWithEmissionsFactorTwoAndSinglePersonGivesKpiOutp .build()) .build(); double outputKpi = kpiCalculator.writeGHGKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); - assertThat(outputKpi).isEqualTo(2.0) + assertThat(outputKpi).isEqualTo(2.0 * equivalentScalingFactor) .as("A vehicle with emissions factor 2 drives a kilometer, " + - "so the output of the GHG KPI is expected to be 2."); + "so the output of the GHG KPI is expected to be 2, and 1 after scaling"); } } diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz index 85cf6566b8d841b2189954845ee8607491e0a647..afe448d26287c639626b0561dce23f38cb2c8777 100644 GIT binary patch literal 23 Zcmb2|=3oGW|Hc=L85o#YzkFi`@&Puv1l#}s literal 24 acmb2|=3oGW|HfuljoBHvnGXc900jUyumny3 diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz index 9e7321d4c4e37a90694a5cf991ab478ae27d8b8b..afe448d26287c639626b0561dce23f38cb2c8777 100644 GIT binary patch literal 23 Zcmb2|=3oGW|Hc=L85o#YzkFi`@&Puv1l#}s literal 24 bcmb2|=3oGW|C_HE8?!SkHy2&c0u%rMLpcRZ From 70e494d0dd221870bab3502d2fe39b8221c1e76e Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Mon, 25 Mar 2024 11:05:10 +0000 Subject: [PATCH 18/39] add tests for Travel Time KPI --- .../cml/abm/kpi/builders/LegsBuilder.java | 27 ++--- .../cml/abm/kpi/builders/TripsBuilder.java | 106 +++++++++++++++--- .../tablesaw/TestTablesawTravelTimeKpi.java | 71 ++++++++++++ 3 files changed, 176 insertions(+), 28 deletions(-) create mode 100644 src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java index cb973ff..6370b3a 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java @@ -1,6 +1,7 @@ package com.arup.cml.abm.kpi.builders; import org.junit.rules.TemporaryFolder; +import tech.tablesaw.api.DoubleColumn; import tech.tablesaw.api.IntColumn; import tech.tablesaw.api.StringColumn; import tech.tablesaw.api.Table; @@ -19,11 +20,11 @@ public class LegsBuilder { Integer defaultDistance = 5500; String defaultMode = "car"; String defaultStartLink = "1-3"; - String defaultStartX = "-500"; - String defaultStartY = "-200"; + double defaultStartX = -500.0; + double defaultStartY = -200.0; String defaultEndLink = "4-5"; - String defaultEndX = "4600"; - String defaultEndY = "800"; + double defaultEndX = 4600.0; + double defaultEndY = 800.0; String defaultAccessStopId = ""; String defaultEgressStopId = ""; String defaultTransitLine = ""; @@ -39,11 +40,11 @@ public class LegsBuilder { IntColumn.create("distance"), StringColumn.create("mode"), StringColumn.create("start_link"), - StringColumn.create("start_x"), - StringColumn.create("start_y"), + DoubleColumn.create("start_x"), + DoubleColumn.create("start_y"), StringColumn.create("end_link"), - StringColumn.create("end_x"), - StringColumn.create("end_y"), + DoubleColumn.create("end_x"), + DoubleColumn.create("end_y"), StringColumn.create("access_stop_id"), StringColumn.create("egress_stop_id"), StringColumn.create("transit_line"), @@ -57,7 +58,7 @@ public LegsBuilder(TemporaryFolder tmpDir) { public LegsBuilder withLeg( String person, String trip_id, String dep_time, String trav_time, String wait_time, Integer distance, - String mode, String start_link, String start_x, String start_y, String end_link, String end_x, String end_y, + String mode, String start_link, double start_x, double start_y, String end_link, double end_x, double end_y, String access_stop_id, String egress_stop_id, String transit_line, String transit_route, String vehicle_id ) { legs.stringColumn("person").append(person); @@ -68,11 +69,11 @@ public LegsBuilder withLeg( legs.intColumn("distance").append(distance); legs.stringColumn("mode").append(mode); legs.stringColumn("start_link").append(start_link); - legs.stringColumn("start_x").append(start_x); - legs.stringColumn("start_y").append(start_y); + legs.doubleColumn("start_x").append(start_x); + legs.doubleColumn("start_y").append(start_y); legs.stringColumn("end_link").append(end_link); - legs.stringColumn("end_x").append(end_x); - legs.stringColumn("end_y").append(end_y); + legs.doubleColumn("end_x").append(end_x); + legs.doubleColumn("end_y").append(end_y); legs.stringColumn("access_stop_id").append(access_stop_id); legs.stringColumn("egress_stop_id").append(egress_stop_id); legs.stringColumn("transit_line").append(transit_line); diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java index 56e68c7..a9a95f1 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java @@ -2,6 +2,7 @@ import org.junit.rules.TemporaryFolder; import tech.tablesaw.api.DoubleColumn; +import tech.tablesaw.api.IntColumn; import tech.tablesaw.api.StringColumn; import tech.tablesaw.api.Table; import tech.tablesaw.columns.Column; @@ -12,6 +13,30 @@ public class TripsBuilder { TemporaryFolder tmpDir; + LegsBuilder legsBuilder; + String defaultPerson = "Bobby"; + String defaultTripNumber = "0"; + String defaultTripId = "Bobby_0"; + String defaultDepTime = "07:38:40"; + String defaultTravTime = "00:09:12"; + String defaultWaitTime = "00:00:00"; + Integer defaultTravelledDistance = 5500; + Integer defaultEuclideanDistance = 4000; + String defaultMainMode = "car"; + String defaultLongestDistanceMode = "car"; + String defaultModes = "car"; + String defaultStartActivityType = "home"; + String defaultStartFacilityId = "home_Bobby"; + String defaultEndActivityType = "work"; + String defaultEndFacilityId = "work_Bobby"; + String defaultStartLink = "1-3"; + double defaultStartX = -500.0; + double defaultStartY = -200.0; + String defaultEndLink = "4-5"; + double defaultEndX = 4600.0; + double defaultEndY = 800.0; + String defaultFirstPtBoardingStop = ""; + String defaultLastPtEgressStop = ""; Table trips = Table.create("trips").addColumns( StringColumn.create("person"), StringColumn.create("trip_number"), @@ -19,8 +44,8 @@ public class TripsBuilder { StringColumn.create("dep_time"), StringColumn.create("trav_time"), StringColumn.create("wait_time"), - StringColumn.create("traveled_distance"), - StringColumn.create("euclidean_distance"), + IntColumn.create("traveled_distance"), + IntColumn.create("euclidean_distance"), StringColumn.create("main_mode"), StringColumn.create("longest_distance_mode"), StringColumn.create("modes"), @@ -40,27 +65,78 @@ public class TripsBuilder { public TripsBuilder(TemporaryFolder tmpDir) { this.tmpDir = tmpDir; + this.legsBuilder = new LegsBuilder(tmpDir); } - private void fillWithDudValues() { - Set timeCols = Set.of("dep_time", "trav_time", "wait_time"); - Set numberCols = Set.of("start_x", "start_y", "end_x", "end_y"); - for (Column col : trips.columns()) { - if (timeCols.contains(col.name())) { - col.append("00:00:00"); - } else if (numberCols.contains(col.name())) { - col.append(1.0); - } else { - col.append("dud"); - } - } + public TripsBuilder withTrip( + String person, String trip_number, String trip_id, String dep_time, String trav_time, String wait_time, + Integer traveled_distance, Integer euclidean_distance, String main_mode, String longest_distance_mode, + String modes, String start_activity_type, String start_facility_id, String end_activity_type, String end_facility_id, + String start_link, double start_x, double start_y, String end_link, double end_x, double end_y, + String first_pt_boarding_stop, String last_pt_egress_stop + ) { + trips.stringColumn("person").append(person); + trips.stringColumn("trip_number").append(trip_number); + trips.stringColumn("trip_id").append(trip_id); + trips.stringColumn("dep_time").append(dep_time); + trips.stringColumn("trav_time").append(trav_time); + trips.stringColumn("wait_time").append(wait_time); + trips.intColumn("traveled_distance").append(traveled_distance); + trips.intColumn("euclidean_distance").append(euclidean_distance); + trips.stringColumn("main_mode").append(main_mode); + trips.stringColumn("longest_distance_mode").append(longest_distance_mode); + trips.stringColumn("modes").append(modes); + trips.stringColumn("start_activity_type").append(start_activity_type); + trips.stringColumn("start_facility_id").append(start_facility_id); + trips.stringColumn("end_activity_type").append(end_activity_type); + trips.stringColumn("end_facility_id").append(end_facility_id); + trips.stringColumn("start_link").append(start_link); + trips.doubleColumn("start_x").append(start_x); + trips.doubleColumn("start_y").append(start_y); + trips.stringColumn("end_link").append(end_link); + trips.doubleColumn("end_x").append(end_x); + trips.doubleColumn("end_y").append(end_y); + trips.stringColumn("first_pt_boarding_stop").append(first_pt_boarding_stop); + trips.stringColumn("last_pt_egress_stop").append(last_pt_egress_stop); + + this.legsBuilder.withLeg( + person, trip_id, dep_time, trav_time, wait_time, traveled_distance, main_mode, + start_link, start_x, start_y, end_link, end_x, end_y, first_pt_boarding_stop, + last_pt_egress_stop, this.legsBuilder.defaultTransitLine, this.legsBuilder.defaultTransitRoute, + this.legsBuilder.defaultVehicleId + ); + return this; + } + + public TripsBuilder withTripWithTravelTime(String person, String trip_number, String trav_time) { + return this.withTrip( + person, trip_number, String.format("{}_{}", person, trip_number), defaultDepTime, trav_time, defaultWaitTime, + defaultTravelledDistance, defaultEuclideanDistance, defaultMainMode, defaultLongestDistanceMode, defaultModes, + defaultStartActivityType, defaultStartFacilityId, defaultEndActivityType, defaultEndFacilityId, + defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, + defaultFirstPtBoardingStop, defaultLastPtEgressStop + ); + } + + public TripsBuilder withDefaultTrip() { + return this.withTrip( + defaultPerson, defaultTripNumber, defaultTripId, defaultDepTime, defaultTravTime, defaultWaitTime, + defaultTravelledDistance, defaultEuclideanDistance, defaultMainMode, defaultLongestDistanceMode, defaultModes, + defaultStartActivityType, defaultStartFacilityId, defaultEndActivityType, defaultEndFacilityId, + defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, + defaultFirstPtBoardingStop, defaultLastPtEgressStop + ); + } + + public LegsBuilder getLegsBuilder() { + return legsBuilder; } public String build() { if (trips.isEmpty()) { // empty table gets into trouble reading all the columns, if the table is empty, it is assumed it's not // being used, so filling it with dud vales, just for the shape is ok - fillWithDudValues(); + this.withDefaultTrip(); } String tripsPath = String.valueOf(Path.of(tmpDir.getRoot().getAbsolutePath(), "output_trips.csv")); CsvWriteOptions options = CsvWriteOptions.builder(tripsPath).separator(';').build(); diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java new file mode 100644 index 0000000..2ad6900 --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java @@ -0,0 +1,71 @@ +package com.arup.cml.abm.kpi.tablesaw; + +import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.builders.*; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TestTablesawTravelTimeKpi { + // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative + // `equivalentScalingFactor` to multiply the expected KPI output by +// ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 20); + double equivalentScalingFactor = 1.0; + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + + @Test + public void singleTripGivesTrivialAverage() { + String trav_time = "00:20:00"; + Double trav_time_minutes = 20.0; + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withTripWithTravelTime("Bobby", "1", trav_time) + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .build(); + double outputKpi = kpiCalculator.writeTravelTimeKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi).isEqualTo(trav_time_minutes * equivalentScalingFactor) + .as("KPI output is the average of travel times. " + + "Average of one travel time is that travel time"); + } + + @Test + public void twoTripsProduceAverageOfTravelTimes() { + String bobby_trav_time = "00:20:00"; + Double bobby_trav_time_minutes = 20.0; + String bobbina_trav_time = "01:04:00"; + Double bobbina_trav_time_minutes = 64.0; + + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withTripWithTravelTime("Bobby", "1", bobby_trav_time) + .withTripWithTravelTime("Bobbina", "1", bobbina_trav_time) + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .build(); + double outputKpi = kpiCalculator.writeTravelTimeKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi).isEqualTo( + ((bobby_trav_time_minutes + bobbina_trav_time_minutes) / 2) * equivalentScalingFactor) + .as("Should be the average of two travel times"); + } + +} + From 7ca4cb533b7d058e83ec2b2ef9fbb405dc01294e Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Mon, 25 Mar 2024 11:44:32 +0000 Subject: [PATCH 19/39] add scaling to travel time --- .../java/com/arup/cml/abm/kpi/KpiCalculator.java | 2 +- .../abm/kpi/matsim/run/MatsimKpiGenerator.java | 2 +- .../abm/kpi/tablesaw/TablesawKpiCalculator.java | 7 +++++-- .../kpi/tablesaw/TestTablesawTravelTimeKpi.java | 12 ++++++------ .../expected-kpis/expected-kpi-travel-time.csv.gz | Bin 25 -> 24 bytes .../expected-kpis/expected-kpi-travel-time.csv.gz | Bin 48 -> 24 bytes 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index 86c472e..3be290a 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -21,7 +21,7 @@ public interface KpiCalculator { double writeGHGKpi(Path outputDirectory, ScalingFactor scalingFactor); - double writeTravelTimeKpi(Path outputDirectory); + double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFactor); Table writeAccessToMobilityServicesKpi(Path outputDirectory); diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index 532bccb..6c662dd 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -103,7 +103,7 @@ public void run() { kpiCalculator.writeGHGKpi(outputDir, new LinearScale(0, 10, 8.87, 0.0)); kpiCalculator.writeAccessToMobilityServicesKpi(outputDir); kpiCalculator.writeCongestionKpi(outputDir); - kpiCalculator.writeTravelTimeKpi(outputDir); + kpiCalculator.writeTravelTimeKpi(outputDir, new LinearScale(0, 10, 90.0, 10.0)); kpiCalculator.writeMobilitySpaceUsageKpi(outputDir); MemoryObserver.stop(); } diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index 5f9aac7..b146cbd 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -510,7 +510,7 @@ public double writeGHGKpi(Path outputDirectory, ScalingFactor scalingFactor) { } @Override - public double writeTravelTimeKpi(Path outputDirectory) { + public double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFactor) { LOGGER.info("Writing Travel Time KPI to {}", outputDirectory); // convert H:M:S format to seconds @@ -527,7 +527,10 @@ public double writeTravelTimeKpi(Path outputDirectory) { .setName("Travel Time by trip purpose"); this.writeTableCompressed(intermediate, String.format("%s/intermediate-travel-time.csv", outputDirectory), this.compressionType); - double kpi = trips.intColumn("trav_time_minutes").mean(); + double kpi = round( + scalingFactor.scale( trips.intColumn("trav_time_minutes").mean()), + 2 + ); writeContentToFile(String.format("%s/kpi-travel-time.csv", outputDirectory), String.valueOf(kpi), this.compressionType); return kpi; } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java index 2ad6900..9715fe2 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java @@ -14,8 +14,8 @@ public class TestTablesawTravelTimeKpi { // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by -// ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 20); - double equivalentScalingFactor = 1.0; + ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 50); + double equivalentScalingFactor = 1 / 5.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -32,8 +32,8 @@ public void singleTripGivesTrivialAverage() { .withLegs(tripsBuilder.getLegsBuilder().build()) .build(); double outputKpi = kpiCalculator.writeTravelTimeKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi).isEqualTo(trav_time_minutes * equivalentScalingFactor) @@ -58,8 +58,8 @@ public void twoTripsProduceAverageOfTravelTimes() { .withLegs(tripsBuilder.getLegsBuilder().build()) .build(); double outputKpi = kpiCalculator.writeTravelTimeKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi).isEqualTo( diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-travel-time.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-travel-time.csv.gz index e8eea0484a43d20167b7e1df7ab19206c83ff5ba..a6503d7fed6c725a020163dc37cf5fedecd6b025 100644 GIT binary patch literal 24 bcmb2|=3oGW|HfC14Otjg_)oKA0SW*BJ(C3S literal 25 ccmb2|=3oGW|Hg)wjm=pZZaXg7%nB3%07fqb&;S4c diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-travel-time.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-travel-time.csv.gz index 122d930404dc7e124f4011886707cabb574a07b3..901ca643ac17c3a718fabd7fefd06467fba786a7 100644 GIT binary patch literal 24 bcmb2|=3oGW|HjviH?S~>p3?Zo0u%rMLlXt8 literal 48 zcmb2|=HTc)aW$2JIlCZJx1=bsEHy{BBr`WvFS)pk!PxAYvDp>}2W5s0j5eNv3=9ka Dm#7Zr From 18140e366c7c255d9a490871fbd51da0080baaf0 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Mon, 25 Mar 2024 15:16:18 +0000 Subject: [PATCH 20/39] add test for mobility access KPI --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 3 +- .../kpi/tablesaw/TablesawKpiCalculator.java | 19 +- .../kpi/builders/TransitScheduleBuilder.java | 57 +++- .../cml/abm/kpi/builders/TripsBuilder.java | 24 +- .../TestTablesawAccessToMobility.java | 270 ++++++++++++++++++ 5 files changed, 358 insertions(+), 15 deletions(-) create mode 100644 src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index 3be290a..c7934da 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -3,6 +3,7 @@ import tech.tablesaw.api.Table; import java.nio.file.Path; +import java.util.Map; public interface KpiCalculator { double writeAffordabilityKpi(Path outputDirectory, ScalingFactor scalingFactor); @@ -23,7 +24,7 @@ public interface KpiCalculator { double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFactor); - Table writeAccessToMobilityServicesKpi(Path outputDirectory); + Map writeAccessToMobilityServicesKpi(Path outputDirectory); Table writeCongestionKpi(Path directory); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index b146cbd..6ef3c6d 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -536,7 +536,7 @@ public double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFact } @Override - public Table writeAccessToMobilityServicesKpi(Path outputDirectory) { + public Map writeAccessToMobilityServicesKpi(Path outputDirectory) { LOGGER.info("Writing Access To Mobility Services KPI to {}", outputDirectory); LOGGER.info("Filtering trips table with {} rows to find trips that started from 'home'", @@ -549,7 +549,7 @@ public Table writeAccessToMobilityServicesKpi(Path outputDirectory) { table.column("start_x").setName("x"); table.column("start_y").setName("y"); BooleanColumn usedPtColumn = BooleanColumn.create("used_pt"); - LOGGER.info(String.format("Iterating over the 'home' trips to record use of PT", table.rowCount())); + LOGGER.info("Iterating over {} 'home' trips to record use of PT", table.rowCount()); table.stringColumn("first_pt_boarding_stop").forEach(new Consumer() { @Override public void accept(String aString) { @@ -563,8 +563,7 @@ public void accept(String aString) { table.addColumns(usedPtColumn); table.removeColumns(table.column("first_pt_boarding_stop")); table = table.dropDuplicateRows(); - LOGGER.info(String.format("Added a new column recording use of PT")); - + LOGGER.info("Added a new column recording use of PT"); LOGGER.info("Checking access to bus stops"); table = addPTAccessColumnWithinDistance( @@ -590,7 +589,7 @@ public void accept(String aString) { String.format("%s/intermediate-access-to-mobility-services.csv", outputDirectory), this.compressionType); - LOGGER.info(String.format("Calculating bus access to mobility KPI")); + LOGGER.info("Calculating bus access to mobility KPI"); double bus_kpi = ((double) table.booleanColumn("bus_access_400m").countTrue() / table.booleanColumn("bus_access_400m").size()) * 100; @@ -598,7 +597,7 @@ public void accept(String aString) { writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-bus.csv", outputDirectory), String.valueOf(bus_kpi), this.compressionType); - LOGGER.info(String.format("Calculating rail access to mobility KPI")); + LOGGER.info("Calculating rail access to mobility KPI"); double rail_kpi = ((double) table.booleanColumn("rail_access_800m").countTrue() / table.booleanColumn("rail_access_800m").size()) * 100; @@ -616,8 +615,12 @@ public void accept(String aString) { writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-pt-and-pt-used.csv", outputDirectory), String.valueOf(used_pt_kpi), this.compressionType); - LOGGER.info(String.format("Finished calculating access to mobility KPIs")); - return table; + LOGGER.info("Finished calculating access to mobility KPIs"); + return Map.of( + "bus_kpi", bus_kpi, + "rail_kpi", rail_kpi, + "used_pt_kpi", used_pt_kpi + ); } public Table addPTAccessColumnWithinDistance(Table table, Table stops, double distance, String columnName) { diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/TransitScheduleBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/TransitScheduleBuilder.java index da9f86f..2976600 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/TransitScheduleBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/TransitScheduleBuilder.java @@ -1,15 +1,64 @@ package com.arup.cml.abm.kpi.builders; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.population.routes.NetworkRoute; +import org.matsim.core.population.routes.RouteUtils; import org.matsim.pt.transitSchedule.TransitScheduleFactoryImpl; -import org.matsim.pt.transitSchedule.api.TransitSchedule; -import org.matsim.pt.transitSchedule.api.TransitScheduleFactory; +import org.matsim.pt.transitSchedule.api.*; + +import java.util.List; public class TransitScheduleBuilder { + TransitScheduleFactoryImpl transitScheduleFactory; TransitSchedule schedule; + String defaultMode = "bus"; + String defaultTransitStopId = "StopA"; + double defaultStopCoordX = 0.0; + double defaultStopCoordY = 0.0; + public TransitScheduleBuilder() { - TransitScheduleFactory builder = new TransitScheduleFactoryImpl(); - schedule = builder.createTransitSchedule(); + transitScheduleFactory = new TransitScheduleFactoryImpl(); + schedule = transitScheduleFactory.createTransitSchedule(); + } + + public TransitScheduleBuilder withTransitLine(String transitLineId, String mode) { + return this.withTransitLineWithStop(transitLineId, defaultTransitStopId, defaultStopCoordX, defaultStopCoordY, mode); + }; + + public TransitScheduleBuilder withTransitLineWithStop( + String transitLineId, String transitStopId, double transitStopCoordX, double transitStopCoordY, String mode) { + TransitStopFacility transitStop = transitScheduleFactory.createTransitStopFacility( + Id.create(transitStopId, TransitStopFacility.class), + new Coord(transitStopCoordX, transitStopCoordY), + false + ); + schedule.addStopFacility(transitStop); + TransitRouteStop routeStop = transitScheduleFactory.createTransitRouteStop(transitStop, 0,0); + transitStop.setLinkId(Id.create("accessLink", Link.class)); + + TransitLine tL = transitScheduleFactory.createTransitLine(Id.create(transitLineId, TransitLine.class)); + tL.addRoute(transitScheduleFactory.createTransitRoute( + Id.create(String.format("%s_1", transitLineId), TransitRoute.class), + RouteUtils.createNetworkRoute(List.of(Id.create("accessLink", Link.class))), + List.of(routeStop), + mode + )); + schedule.addTransitLine(tL); + return this; + } + + public TransitScheduleBuilder withTransitStopWithMode( + String transitStopID, double coordX, double coordY, String mode) { + String transitLineId = String.format("transitLine_%d", schedule.getTransitLines().size()); + return this.withTransitLineWithStop(transitLineId, transitStopID, coordX, coordY, mode); + } + + public TransitScheduleBuilder withTransitStop( + String transitStopID, double coordX, double coordY) { + return this.withTransitStopWithMode(transitStopID, coordX, coordY, defaultMode); } public TransitSchedule build() { diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java index a9a95f1..481203e 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java @@ -108,9 +108,29 @@ public TripsBuilder withTrip( return this; } - public TripsBuilder withTripWithTravelTime(String person, String trip_number, String trav_time) { + public TripsBuilder withTripWithStartLocationAndType( + String person, String trip_number, String startActivityType, double startX, double startY) { + return this.withPtTripWithStartLocationAndType( + person, trip_number, startActivityType, startX, startY, + defaultMainMode, defaultFirstPtBoardingStop, defaultLastPtEgressStop + ); + } + + public TripsBuilder withPtTripWithStartLocationAndType( + String person, String trip_number, String startActivityType, double startX, double startY, + String mode, String ptBoardingStop, String ptEgressStop) { + return this.withTrip( + person, trip_number, String.format("{}_{}", person, trip_number), defaultDepTime, defaultTravTime, defaultWaitTime, + defaultTravelledDistance, defaultEuclideanDistance, mode, mode, mode, + startActivityType, defaultStartFacilityId, defaultEndActivityType, defaultEndFacilityId, + defaultStartLink, startX, startY, defaultEndLink, defaultEndX, defaultEndY, + ptBoardingStop, ptEgressStop + ); + } + + public TripsBuilder withTripWithTravelTime(String person, String tripNumber, String travTime) { return this.withTrip( - person, trip_number, String.format("{}_{}", person, trip_number), defaultDepTime, trav_time, defaultWaitTime, + person, tripNumber, String.format("{}_{}", person, tripNumber), defaultDepTime, travTime, defaultWaitTime, defaultTravelledDistance, defaultEuclideanDistance, defaultMainMode, defaultLongestDistanceMode, defaultModes, defaultStartActivityType, defaultStartFacilityId, defaultEndActivityType, defaultEndFacilityId, defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java new file mode 100644 index 0000000..f141b32 --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java @@ -0,0 +1,270 @@ +package com.arup.cml.abm.kpi.tablesaw; + +import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; +import com.arup.cml.abm.kpi.builders.TransitScheduleBuilder; +import com.arup.cml.abm.kpi.builders.TripsBuilder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.nio.file.Path; +import java.util.Map; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TestTablesawAccessToMobility { + // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative + // `equivalentScalingFactor` to multiply the expected KPI output by +// ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 50); + double equivalentScalingFactor = 1.0; + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + + @Test + public void singleAgentHasAccessToBus() { + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withTripWithStartLocationAndType("Bobby", "1", "home", 0.0, 0.0) + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .withTransitSchedule(new TransitScheduleBuilder() + .withTransitStopWithMode("BusStop", 400.0, 0.0, "bus") + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + .as("Bus KPI output is expected to be 100%, " + + "because there is only one agent and they have access to a bus stop."); + assertThat(outputKpi.get("rail_kpi")).isEqualTo(0.0 * equivalentScalingFactor) + .as("Rail KPI output is expected to be 0%, " + + "because there is only one agent and they don't have access to rail."); + assertThat(outputKpi.get("used_pt_kpi")).isEqualTo(0.0 * equivalentScalingFactor) + .as("Used PT KPI output is expected to be 0%, " + + "because there is only one agent and they didn't use PT"); + } + + @Test + public void nonHomeTripsAreFilteredOutAndDontContributeToKpiOutput() { + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withTripWithStartLocationAndType("Bobby", "1", "home", 0.0, 0.0) + .withTripWithStartLocationAndType("Bobby", "2", "work", 0.0, 0.0) + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .withTransitSchedule(new TransitScheduleBuilder() + .withTransitStopWithMode("BusStop", 400.0, 0.0, "bus") + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + .as("Bus KPI output is expected to be 100%, " + + "because there is only one agent and they have access to a bus stop."); + } + + @Test + public void singleAgentHasAccessToRail() { + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withTripWithStartLocationAndType("Bobby", "1", "home", 0.0, 0.0) + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .withTransitSchedule(new TransitScheduleBuilder() + .withTransitStopWithMode("RailStop", 800.0, 0.0, "rail") + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi.get("bus_kpi")).isEqualTo(0.0 * equivalentScalingFactor) + .as("Bus KPI output is expected to be 0%, " + + "because there is only one agent and they don't have access to a bus stop."); + assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + .as("Rail KPI output is expected to be 100%, " + + "because there is only one agent and they have access to rail."); + assertThat(outputKpi.get("used_pt_kpi")).isEqualTo(0.0 * equivalentScalingFactor) + .as("Used PT KPI output is expected to be 0%, " + + "because there is only one agent and they didn't use PT"); + } + + @Test + public void singleAgentHasAccessToRailAndUsesPT() { + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withPtTripWithStartLocationAndType("Bobby", "1", "home", + 0.0, 0.0, "rail", "A", "B") + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .withTransitSchedule(new TransitScheduleBuilder() + .withTransitStopWithMode("RailStop", 800.0, 0.0, "rail") + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + .as("Rail KPI output is expected to be 100%, " + + "because there is only one agent and they have access to rail."); + assertThat(outputKpi.get("used_pt_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + .as("Used PT KPI output is expected to be 100%, " + + "because there is only one agent and they used PT"); + } + + @Test + public void twoAgentsWithBusAccess() { + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withTripWithStartLocationAndType("Bobby", "1", "home", + 0.0, 0.0) + .withTripWithStartLocationAndType("Bobbina", "1", "home", + 399.0, 0.0) + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .withTransitSchedule(new TransitScheduleBuilder() + .withTransitStopWithMode("BusStop", 0.0, 0.0, "bus") + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + .as("Bus KPI output is expected to be 100%, " + + "because both agents have access to bus."); + } + + @Test + public void twoAgentsWithDifferentBusAccess() { + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withTripWithStartLocationAndType("Bobby", "1", "home", + 0.0, 0.0) + .withTripWithStartLocationAndType("Bobbina", "1", "home", + 401.0, 0.0) + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .withTransitSchedule(new TransitScheduleBuilder() + .withTransitStopWithMode("BusStop", 0.0, 0.0, "bus") + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi.get("bus_kpi")).isEqualTo(50.0 * equivalentScalingFactor) + .as("Bus KPI output is expected to be 50%, " + + "because one agent has access to bus and the other doesn't."); + } + + @Test + public void twoAgentsWithRailAccess() { + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withTripWithStartLocationAndType("Bobby", "1", "home", + 0.0, 0.0) + .withTripWithStartLocationAndType("Bobbina", "1", "home", + 799.0, 0.0) + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .withTransitSchedule(new TransitScheduleBuilder() + .withTransitStopWithMode("RailStop", 0.0, 0.0, "rail") + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + .as("Bus KPI output is expected to be 100%, " + + "because both agents have access to rail."); + } + + @Test + public void twoAgentsWithDifferentRailAccess() { + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withTripWithStartLocationAndType("Bobby", "1", "home", + 0.0, 0.0) + .withTripWithStartLocationAndType("Bobbina", "1", "home", + 801.0, 0.0) + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .withTransitSchedule(new TransitScheduleBuilder() + .withTransitStopWithMode("RailStop", 0.0, 0.0, "rail") + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi.get("rail_kpi")).isEqualTo(50.0 * equivalentScalingFactor) + .as("Rail KPI output is expected to be 50%, " + + "because one agent has access to rail and the other doesn't."); + } + + @Test + public void twoAgentsWithDifferentPTAccessButBothUsePT() { + TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withTrips(tripsBuilder + .withPtTripWithStartLocationAndType("Bobby", "1", "home", + 0.0, 0.0, "rail", "A", "B") + .withPtTripWithStartLocationAndType("Bobbina", "1", "home", + 801.0, 0.0, "bus", "A", "B") + .build()) + .withLegs(tripsBuilder.getLegsBuilder().build()) + .withTransitSchedule(new TransitScheduleBuilder() + .withTransitStopWithMode("RailStop", 0.0, 0.0, "rail") + .withTransitStopWithMode("BusStop", 800.0, 0.0, "bus") + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()) +// linearScalingFactor + ); + + assertThat(outputKpi.get("bus_kpi")).isEqualTo(50.0 * equivalentScalingFactor) + .as("Bus KPI output is expected to be 50%, " + + "because one agent has access to bus and the other doesn't."); + assertThat(outputKpi.get("rail_kpi")).isEqualTo(50.0 * equivalentScalingFactor) + .as("Rail KPI output is expected to be 50%, " + + "because one agent has access to rail and the other doesn't."); + assertThat(outputKpi.get("used_pt_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + .as("Used KPI output is expected to be 100%, " + + "because both agents use PT."); + } + +} + From d78d6a6f96d1ec7bcaa1a00f6681b82e669d57a2 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Mon, 25 Mar 2024 15:43:37 +0000 Subject: [PATCH 21/39] add scaling for mobility access KPI output --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 2 +- .../kpi/matsim/run/MatsimKpiGenerator.java | 2 +- .../kpi/tablesaw/TablesawKpiCalculator.java | 8 ++-- .../TestTablesawAccessToMobility.java | 44 +++++++++--------- ...-to-mobility-services-access-to-bus.csv.gz | Bin 25 -> 24 bytes ...y-services-access-to-pt-and-pt-used.csv.gz | Bin 25 -> 24 bytes 6 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index c7934da..15ed2b6 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -24,7 +24,7 @@ public interface KpiCalculator { double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFactor); - Map writeAccessToMobilityServicesKpi(Path outputDirectory); + Map writeAccessToMobilityServicesKpi(Path outputDirectory, ScalingFactor scalingFactor); Table writeCongestionKpi(Path directory); diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index 6c662dd..1b01041 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -101,7 +101,7 @@ public void run() { kpiCalculator.writePassengerKMKpi(outputDir); kpiCalculator.writeSpeedKpi(outputDir); kpiCalculator.writeGHGKpi(outputDir, new LinearScale(0, 10, 8.87, 0.0)); - kpiCalculator.writeAccessToMobilityServicesKpi(outputDir); + kpiCalculator.writeAccessToMobilityServicesKpi(outputDir, new LinearScale(0, 10, 0.0, 100.0)); kpiCalculator.writeCongestionKpi(outputDir); kpiCalculator.writeTravelTimeKpi(outputDir, new LinearScale(0, 10, 90.0, 10.0)); kpiCalculator.writeMobilitySpaceUsageKpi(outputDir); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index 6ef3c6d..da7f00d 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -536,7 +536,7 @@ public double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFact } @Override - public Map writeAccessToMobilityServicesKpi(Path outputDirectory) { + public Map writeAccessToMobilityServicesKpi(Path outputDirectory, ScalingFactor scalingFactor) { LOGGER.info("Writing Access To Mobility Services KPI to {}", outputDirectory); LOGGER.info("Filtering trips table with {} rows to find trips that started from 'home'", @@ -593,7 +593,7 @@ public void accept(String aString) { double bus_kpi = ((double) table.booleanColumn("bus_access_400m").countTrue() / table.booleanColumn("bus_access_400m").size()) * 100; - bus_kpi = round(bus_kpi, 2); + bus_kpi = round(scalingFactor.scale(bus_kpi), 2); writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-bus.csv", outputDirectory), String.valueOf(bus_kpi), this.compressionType); @@ -601,7 +601,7 @@ public void accept(String aString) { double rail_kpi = ((double) table.booleanColumn("rail_access_800m").countTrue() / table.booleanColumn("rail_access_800m").size()) * 100; - rail_kpi = round(rail_kpi, 2); + rail_kpi = round(scalingFactor.scale(rail_kpi), 2); writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-rail.csv", outputDirectory), String.valueOf(rail_kpi), this.compressionType); @@ -611,7 +611,7 @@ public void accept(String aString) { double used_pt_kpi = ((double) table.where(ptAccess.and(table.booleanColumn("used_pt").isTrue()) ).rowCount() / table.rowCount()) * 100; - used_pt_kpi = round(used_pt_kpi, 2); + used_pt_kpi = round(scalingFactor.scale(used_pt_kpi), 2); writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-pt-and-pt-used.csv", outputDirectory), String.valueOf(used_pt_kpi), this.compressionType); diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java index f141b32..7c08dd5 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java @@ -1,5 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; +import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.ScalingFactor; import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; import com.arup.cml.abm.kpi.builders.TransitScheduleBuilder; import com.arup.cml.abm.kpi.builders.TripsBuilder; @@ -13,10 +15,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class TestTablesawAccessToMobility { - // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative + // In this case, this scale is the proposed KPI scale. We have a natural multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by -// ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 50); - double equivalentScalingFactor = 1.0; + ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 100); + double equivalentScalingFactor = 1 / 10.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -34,8 +36,8 @@ public void singleAgentHasAccessToBus() { .build()) .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -64,8 +66,8 @@ public void nonHomeTripsAreFilteredOutAndDontContributeToKpiOutput() { .build()) .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -87,8 +89,8 @@ public void singleAgentHasAccessToRail() { .build()) .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(0.0 * equivalentScalingFactor) @@ -117,8 +119,8 @@ public void singleAgentHasAccessToRailAndUsesPT() { .build()) .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -146,8 +148,8 @@ public void twoAgentsWithBusAccess() { .build()) .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -172,8 +174,8 @@ public void twoAgentsWithDifferentBusAccess() { .build()) .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(50.0 * equivalentScalingFactor) @@ -198,8 +200,8 @@ public void twoAgentsWithRailAccess() { .build()) .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -224,8 +226,8 @@ public void twoAgentsWithDifferentRailAccess() { .build()) .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi.get("rail_kpi")).isEqualTo(50.0 * equivalentScalingFactor) @@ -251,8 +253,8 @@ public void twoAgentsWithDifferentPTAccessButBothUsePT() { .build()) .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( - Path.of(tmpDir.getRoot().getAbsolutePath()) -// linearScalingFactor + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(50.0 * equivalentScalingFactor) diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-access-to-mobility-services-access-to-bus.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-access-to-mobility-services-access-to-bus.csv.gz index 15ce3dd1b41fc252f8b5335c4b47ee7d336013f2..b4a11e6bdce7ca938d3e4a26275dfdde25c6845d 100644 GIT binary patch literal 24 bcmb2|=3oGW|HfC0O;{Lq=`^2V0SW*BKb8eu literal 25 ccmb2|=3oGW|HdX)j7?Y=dZ#Yg&I%L(07feX+5i9m diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-access-to-mobility-services-access-to-pt-and-pt-used.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-access-to-mobility-services-access-to-pt-and-pt-used.csv.gz index 15ce3dd1b41fc252f8b5335c4b47ee7d336013f2..b4a11e6bdce7ca938d3e4a26275dfdde25c6845d 100644 GIT binary patch literal 24 bcmb2|=3oGW|HfC0O;{Lq=`^2V0SW*BKb8eu literal 25 ccmb2|=3oGW|HdX)j7?Y=dZ#Yg&I%L(07feX+5i9m From 40f8663617eb4ab3e5d962ecf596630fb69682fd Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Mon, 25 Mar 2024 16:10:54 +0000 Subject: [PATCH 22/39] add more tests for congestion --- .../tablesaw/TestTablesawCongestionKpi.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java index 2c81354..8770ebb 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java @@ -19,6 +19,72 @@ public class TestTablesawCongestionKpi { @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); + @Test + public void reportsAverageDelayRatio() { + double firstDelayRatio = 5.0; + double secondDelayRatio = 10.0; + double averageDelayRatio = (firstDelayRatio + secondDelayRatio) / 2; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withNetwork(new NetworkBuilder() + .withNetworkNode("A", 1, 1) + .withNetworkNode("B", 2, 2) + .withNetworkLink("otherLink", "B", "A") + .build()) + .withVehicles(new VehiclesBuilder() + .withVehicle("someCar", "car") + .build()) + .withLinkLog(new LinkLogBuilder() + .withEntry("someCar", "otherLink", (9 * 60 * 60) + 25, (9 * 60 * 60) + 25 + firstDelayRatio) + .withEntry("someCar", "otherLink", (9 * 60 * 60) + 30, (9 * 60 * 60) + 30 + secondDelayRatio) + .build()) + .build(); + Table outputKpi = kpiCalculator.writeCongestionKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); + + assertThat(outputKpi.rowCount()).isEqualTo(1).as("Congestion KPI table should include only one row/mode"); + Row metrics = outputKpi.row(0); + assertThat(metrics.getString("mode")).isEqualTo("car").as("Mode should be car"); + assertThat(metrics.getDouble("Mean [delayRatio]")).isEqualTo(averageDelayRatio) + .as("Mean delay should be the average of 5 and 10"); + } + + @Test + public void reportsDelayRatiosPerMode() { + double carDelayRatio = 15.0; + double rocketDelayRatio = 1.0; + double horseDelayRatio = 30.0; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withNetwork(new NetworkBuilder() + .withNetworkNode("A", 1, 1) + .withNetworkNode("B", 2, 2) + .withNetworkLink("otherLink", "B", "A") + .build()) + .withVehicles(new VehiclesBuilder() + .withVehicle("someCar", "car") + .withVehicleOfMode("someRocket", "rocket", "rocket") + .withVehicleOfMode("someHorse", "horse", "horse") + .build()) + .withLinkLog(new LinkLogBuilder() + .withEntry("someCar", "otherLink", (9 * 60 * 60) + 25, (9 * 60 * 60) + 25 + carDelayRatio) + .withEntry("someRocket", "otherLink", (9 * 60 * 60) + 25, (9 * 60 * 60) + 25 + rocketDelayRatio) + .withEntry("someHorse", "otherLink", (9 * 60 * 60) + 25, (9 * 60 * 60) + 25 + horseDelayRatio) + .build()) + .build(); + Table outputKpi = kpiCalculator.writeCongestionKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); + + assertThat(outputKpi.rowCount()).isEqualTo(3).as("Congestion KPI table should include three rows/modes"); + Row carMetric = outputKpi.row(0); + assertThat(carMetric.getString("mode")).isEqualTo("car").as("Mode should be car"); + assertThat(carMetric.getDouble("Mean [delayRatio]")).isEqualTo(carDelayRatio).as("Mean delay should be 15"); + Row rocketMetric = outputKpi.row(1); + assertThat(rocketMetric.getString("mode")).isEqualTo("rocket").as("Mode should be rocket"); + assertThat(rocketMetric.getDouble("Mean [delayRatio]")).isEqualTo(rocketDelayRatio).as("Mean delay should be 1"); + Row horseMetric = outputKpi.row(2); + assertThat(horseMetric.getString("mode")).isEqualTo("horse").as("Mode should be horse"); + assertThat(horseMetric.getDouble("Mean [delayRatio]")).isEqualTo(horseDelayRatio).as("Mean delay should be 30"); + } + @Test public void linkWithInfiniteSpeedDoesNotBreakCongestionKpi() { TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) From 4de1e0380e190f12a1ece49a74deb0a72ffe8795 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Mon, 25 Mar 2024 16:56:53 +0000 Subject: [PATCH 23/39] add scaling for congestion KPi output --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 2 +- .../kpi/matsim/run/MatsimKpiGenerator.java | 2 +- .../kpi/tablesaw/TablesawKpiCalculator.java | 12 ++++- .../tablesaw/TestTablesawCongestionKpi.java | 45 ++++++++++++++---- .../expected-kpi-congestion.csv.gz | Bin 89 -> 84 bytes .../expected-kpi-congestion.csv.gz | Bin 61 -> 84 bytes 6 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index 15ed2b6..f4c0c69 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -26,7 +26,7 @@ public interface KpiCalculator { Map writeAccessToMobilityServicesKpi(Path outputDirectory, ScalingFactor scalingFactor); - Table writeCongestionKpi(Path directory); + Table writeCongestionKpi(Path directory, ScalingFactor scalingFactor); double writeMobilitySpaceUsageKpi(Path outputDirectory); } diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index 1b01041..b03cc47 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -102,7 +102,7 @@ public void run() { kpiCalculator.writeSpeedKpi(outputDir); kpiCalculator.writeGHGKpi(outputDir, new LinearScale(0, 10, 8.87, 0.0)); kpiCalculator.writeAccessToMobilityServicesKpi(outputDir, new LinearScale(0, 10, 0.0, 100.0)); - kpiCalculator.writeCongestionKpi(outputDir); + kpiCalculator.writeCongestionKpi(outputDir, new LinearScale(0, 10, 3.0, 1.25)); kpiCalculator.writeTravelTimeKpi(outputDir, new LinearScale(0, 10, 90.0, 10.0)); kpiCalculator.writeMobilitySpaceUsageKpi(outputDir); MemoryObserver.stop(); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index da7f00d..15fd4bb 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -659,7 +659,7 @@ public Table addPTAccessColumnWithinDistance(Table table, Table stops, double di } @Override - public Table writeCongestionKpi(Path outputDirectory) { + public Table writeCongestionKpi(Path outputDirectory, ScalingFactor scalingFactor) { LOGGER.info("Writing Congestion KPIs to {}", outputDirectory); // compute travel time on links @@ -715,7 +715,17 @@ public Table writeCongestionKpi(Path outputDirectory) { .summarize("delayRatio", mean) .by("mode") .setName("Congestion KPI"); + + // add scaled output column + DoubleColumn normalisedDelayedRatio = DoubleColumn.create("Normalised [Mean [delayRatio]]"); + kpi.doubleColumn("Mean [delayRatio]") + .forEach(meanDelayRatio -> normalisedDelayedRatio.append( + scalingFactor.scale(meanDelayRatio) + )); + kpi.addColumns(normalisedDelayedRatio); + kpi.replaceColumn(round(kpi.doubleColumn("Mean [delayRatio]"), 2)); + kpi.replaceColumn(round(kpi.doubleColumn("Normalised [Mean [delayRatio]]"), 2)); this.writeTableCompressed(kpi, String.format("%s/kpi-congestion.csv", outputDirectory), compressionType); return kpi; } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java index 8770ebb..5b17b0c 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java @@ -1,5 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; +import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.ScalingFactor; import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; import com.arup.cml.abm.kpi.builders.LinkLogBuilder; import com.arup.cml.abm.kpi.builders.NetworkBuilder; @@ -15,6 +17,10 @@ import java.nio.file.Path; public class TestTablesawCongestionKpi { + // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative + // `equivalentScalingFactor` to multiply the expected KPI output by + ScalingFactor linearScalingFactor = new LinearScale(0, 1, 0, 50); + double equivalentScalingFactor = 1.0 / 50.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -39,13 +45,18 @@ public void reportsAverageDelayRatio() { .withEntry("someCar", "otherLink", (9 * 60 * 60) + 30, (9 * 60 * 60) + 30 + secondDelayRatio) .build()) .build(); - Table outputKpi = kpiCalculator.writeCongestionKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); + Table outputKpi = kpiCalculator.writeCongestionKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor + ); assertThat(outputKpi.rowCount()).isEqualTo(1).as("Congestion KPI table should include only one row/mode"); Row metrics = outputKpi.row(0); assertThat(metrics.getString("mode")).isEqualTo("car").as("Mode should be car"); assertThat(metrics.getDouble("Mean [delayRatio]")).isEqualTo(averageDelayRatio) - .as("Mean delay should be the average of 5 and 10"); + .as("Mean delay should be the average of 5 and 10."); + assertThat(metrics.getDouble("Normalised [Mean [delayRatio]]")).isEqualTo(averageDelayRatio * equivalentScalingFactor) + .as("Mean delay should be the average of 5 and 10, scaled by `equivalentScalingFactor`"); } @Test @@ -71,18 +82,30 @@ public void reportsDelayRatiosPerMode() { .withEntry("someHorse", "otherLink", (9 * 60 * 60) + 25, (9 * 60 * 60) + 25 + horseDelayRatio) .build()) .build(); - Table outputKpi = kpiCalculator.writeCongestionKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); + Table outputKpi = kpiCalculator.writeCongestionKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor + ); assertThat(outputKpi.rowCount()).isEqualTo(3).as("Congestion KPI table should include three rows/modes"); Row carMetric = outputKpi.row(0); assertThat(carMetric.getString("mode")).isEqualTo("car").as("Mode should be car"); - assertThat(carMetric.getDouble("Mean [delayRatio]")).isEqualTo(carDelayRatio).as("Mean delay should be 15"); + assertThat(carMetric.getDouble("Mean [delayRatio]")).isEqualTo(carDelayRatio) + .as("Mean delay should be 15"); + assertThat(carMetric.getDouble("Normalised [Mean [delayRatio]]")).isEqualTo(carDelayRatio * equivalentScalingFactor) + .as("Mean delay should be 15, scaled by `equivalentScalingFactor`"); Row rocketMetric = outputKpi.row(1); assertThat(rocketMetric.getString("mode")).isEqualTo("rocket").as("Mode should be rocket"); - assertThat(rocketMetric.getDouble("Mean [delayRatio]")).isEqualTo(rocketDelayRatio).as("Mean delay should be 1"); + assertThat(rocketMetric.getDouble("Mean [delayRatio]")).isEqualTo(rocketDelayRatio) + .as("Mean delay should be 1"); + assertThat(rocketMetric.getDouble("Normalised [Mean [delayRatio]]")).isEqualTo(rocketDelayRatio * equivalentScalingFactor) + .as("Mean delay should be 1, scaled by `equivalentScalingFactor`"); Row horseMetric = outputKpi.row(2); assertThat(horseMetric.getString("mode")).isEqualTo("horse").as("Mode should be horse"); - assertThat(horseMetric.getDouble("Mean [delayRatio]")).isEqualTo(horseDelayRatio).as("Mean delay should be 30"); + assertThat(horseMetric.getDouble("Mean [delayRatio]")).isEqualTo(horseDelayRatio) + .as("Mean delay should be 30"); + assertThat(horseMetric.getDouble("Normalised [Mean [delayRatio]]")).isEqualTo(horseDelayRatio * equivalentScalingFactor) + .as("Mean delay should be 30, scaled by `equivalentScalingFactor`"); } @Test @@ -102,12 +125,18 @@ public void linkWithInfiniteSpeedDoesNotBreakCongestionKpi() { .withEntry("someCar", "otherLink", (9 * 60 * 60) + 25, (9 * 60 * 60) + 30) .build()) .build(); - Table outputKpi = kpiCalculator.writeCongestionKpi(Path.of(tmpDir.getRoot().getAbsolutePath())); + Table outputKpi = kpiCalculator.writeCongestionKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearScalingFactor + ); assertThat(outputKpi.rowCount()).isEqualTo(1).as("Congestion KPI table should include only one row/mode"); Row metrics = outputKpi.row(0); assertThat(metrics.getString("mode")).isEqualTo("car").as("Mode should be car"); - assertThat(metrics.getDouble("Mean [delayRatio]")).isEqualTo(5).as("Mean delay should be 5"); + assertThat(metrics.getDouble("Mean [delayRatio]")).isEqualTo(5) + .as("Mean delay should be 5"); + assertThat(metrics.getDouble("Normalised [Mean [delayRatio]]")).isEqualTo(5 * equivalentScalingFactor) + .as("Mean delay should be 5, scaled by `equivalentScalingFactor`"); } } diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-congestion.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-congestion.csv.gz index 9c32d2611f7346daadabc09ec0ee0963601d9974..239ef0c374bf0679ab7ba3bac8456f4b500a03df 100644 GIT binary patch literal 84 zcmb2|=3oGW|EJITdtUwM>w6}+!_V`q=NcX_os(z!uYNqOf6DjFNgY4mz>Z@RdY^h} m>0UFuwAtXA;f2j6PrWp?E*f4oHe&q5vxDKU(ixFJpkV+T-Xxj; literal 89 zcmV-f0H*&RiwFoI_SIzo17&z{Wn*+@WG!oOX)R-KZf9k4bZKvHE@N|c0L#r!N!9U9 vP0Uk>PD#y4tPDyl$;^-CN-8bZG19Xz=1M6l(J|9AGT{OMJg}q=DF6TfN@OFE diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-congestion.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-congestion.csv.gz index 4e2492ec81acb97287d204e1182609fc315af021..d07167cd6aec6b383c38a4096d23211c58bca967 100644 GIT binary patch delta 57 zcmV-90LK44RFEJv)$z+O%1zA4EKW^Ph{lkO Date: Mon, 25 Mar 2024 17:34:17 +0000 Subject: [PATCH 24/39] rename scaling to normalisation --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 14 +-- ...LinearScale.java => LinearNormaliser.java} | 28 +++--- .../java/com/arup/cml/abm/kpi/Normaliser.java | 5 ++ .../com/arup/cml/abm/kpi/ScalingFactor.java | 5 -- .../kpi/matsim/run/MatsimKpiGenerator.java | 16 ++-- .../kpi/tablesaw/TablesawKpiCalculator.java | 34 ++++---- .../cml/abm/kpi/TestLinearNormaliser.java | 84 ++++++++++++++++++ .../com/arup/cml/abm/kpi/TestLinearScale.java | 86 ------------------- ...AccessToMobilityWithLinearNormaliser.java} | 28 +++--- ...AffordabilityKpiWithLinearNormaliser.java} | 16 ++-- ...sawCongestionKpiWithLinearNormaliser.java} | 16 ++-- ...stTablesawGHGKpiWithLinearNormaliser.java} | 12 +-- ...esawOccupancyKpiWithLinearNormaliser.java} | 12 +-- ...sawPtWaitTimeKpiWithLinearNormaliser.java} | 16 ++-- ...sawTravelTimeKpiWithLinearNormaliser.java} | 14 +-- 15 files changed, 192 insertions(+), 194 deletions(-) rename src/main/java/com/arup/cml/abm/kpi/{LinearScale.java => LinearNormaliser.java} (65%) create mode 100644 src/main/java/com/arup/cml/abm/kpi/Normaliser.java delete mode 100644 src/main/java/com/arup/cml/abm/kpi/ScalingFactor.java create mode 100644 src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java delete mode 100644 src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawAccessToMobility.java => TestTablesawAccessToMobilityWithLinearNormaliser.java} (95%) rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawAffordabilityKpiWithLinearScalingFactor.java => TestTablesawAffordabilityKpiWithLinearNormaliser.java} (93%) rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawCongestionKpi.java => TestTablesawCongestionKpiWithLinearNormaliser.java} (94%) rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawGHGKpi.java => TestTablesawGHGKpiWithLinearNormaliser.java} (83%) rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawOccupancyKpiWithLinearScalingFactor.java => TestTablesawOccupancyKpiWithLinearNormaliser.java} (81%) rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawPtWaitTimeKpiWithLinearScalingFactor.java => TestTablesawPtWaitTimeKpiWithLinearNormaliser.java} (87%) rename src/test/java/com/arup/cml/abm/kpi/tablesaw/{TestTablesawTravelTimeKpi.java => TestTablesawTravelTimeKpiWithLinearNormaliser.java} (85%) diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index f4c0c69..300ebe0 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -6,13 +6,13 @@ import java.util.Map; public interface KpiCalculator { - double writeAffordabilityKpi(Path outputDirectory, ScalingFactor scalingFactor); + double writeAffordabilityKpi(Path outputDirectory, Normaliser normaliser); - double writePtWaitTimeKpi(Path outputDirectory, ScalingFactor scalingFactor); + double writePtWaitTimeKpi(Path outputDirectory, Normaliser normaliser); void writeModalSplitKpi(Path outputDirectory); - double writeOccupancyRateKpi(Path outputDirectory, ScalingFactor scalingFactor); + double writeOccupancyRateKpi(Path outputDirectory, Normaliser normaliser); double writeVehicleKMKpi(Path outputDirectory); @@ -20,13 +20,13 @@ public interface KpiCalculator { void writeSpeedKpi(Path outputDirectory); - double writeGHGKpi(Path outputDirectory, ScalingFactor scalingFactor); + double writeGHGKpi(Path outputDirectory, Normaliser normaliser); - double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFactor); + double writeTravelTimeKpi(Path outputDirectory, Normaliser normaliser); - Map writeAccessToMobilityServicesKpi(Path outputDirectory, ScalingFactor scalingFactor); + Map writeAccessToMobilityServicesKpi(Path outputDirectory, Normaliser normaliser); - Table writeCongestionKpi(Path directory, ScalingFactor scalingFactor); + Table writeCongestionKpi(Path directory, Normaliser normaliser); double writeMobilitySpaceUsageKpi(Path outputDirectory); } diff --git a/src/main/java/com/arup/cml/abm/kpi/LinearScale.java b/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java similarity index 65% rename from src/main/java/com/arup/cml/abm/kpi/LinearScale.java rename to src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java index 4ba69bf..8a9dcf9 100644 --- a/src/main/java/com/arup/cml/abm/kpi/LinearScale.java +++ b/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java @@ -1,24 +1,24 @@ package com.arup.cml.abm.kpi; -public class LinearScale implements ScalingFactor { +public class LinearNormaliser implements Normaliser { private double leftScaleBound; private double rightScaleBound; private double leftValueBound; private double rightValueBound; private boolean isReversedLinearScale = false; - public LinearScale(double leftScaleBound, double rightScaleBound, double leftValueBound, double rightValueBound) { - // leftValueBound is mapped to leftScaleBound - // rightValueBound is mapped to rightScaleBound - // any value between value bounds is mapped, linearly between values [leftScaleBound, rightScaleBound] - // leftScaleBound < rightScaleBound necessarily + public LinearNormaliser(double leftIntervalBound, double rightIntervalBound, double leftValueBound, double rightValueBound) { + // leftValueBound is mapped to leftIntervalBound + // rightValueBound is mapped to rightIntervalBound + // any value between value bounds is mapped, linearly between values [leftIntervalBound, rightIntervalBound] + // leftIntervalBound < rightIntervalBound necessarily // leftValueBound can be larger than rightValueBound a value will be scaled reversely. - if (leftScaleBound > rightScaleBound) { - throw new RuntimeException("leftScaleBound cannot be larger than rightValueBound"); + if (leftIntervalBound > rightIntervalBound) { + throw new RuntimeException("leftIntervalBound cannot be larger than rightValueBound"); } - if (leftValueBound == rightValueBound && leftScaleBound != rightScaleBound) { - throw new RuntimeException("The bounds given for linear scale are invalid. " + + if (leftValueBound == rightValueBound && leftIntervalBound != rightIntervalBound) { + throw new RuntimeException("The bounds given for linear interval are invalid. " + "Left and right bounds cannot both be the same value."); } if (leftValueBound > rightValueBound) { @@ -27,13 +27,13 @@ public LinearScale(double leftScaleBound, double rightScaleBound, double leftVal rightValueBound = -rightValueBound; } - this.leftScaleBound = leftScaleBound; - this.rightScaleBound = rightScaleBound; + this.leftScaleBound = leftIntervalBound; + this.rightScaleBound = rightIntervalBound; this.leftValueBound = leftValueBound; this.rightValueBound = rightValueBound; } - public LinearScale(double leftValueBound, double rightValueBound) { + public LinearNormaliser(double leftValueBound, double rightValueBound) { this(0, 10, leftValueBound, rightValueBound); } @@ -54,7 +54,7 @@ private boolean isWithinBounds(double value) { } @Override - public double scale(double value) { + public double normalise(double value) { if (this.isReversedLinearScale()) { value = -value; } diff --git a/src/main/java/com/arup/cml/abm/kpi/Normaliser.java b/src/main/java/com/arup/cml/abm/kpi/Normaliser.java new file mode 100644 index 0000000..9ae5fa2 --- /dev/null +++ b/src/main/java/com/arup/cml/abm/kpi/Normaliser.java @@ -0,0 +1,5 @@ +package com.arup.cml.abm.kpi; + +public interface Normaliser { + double normalise(double value); +} diff --git a/src/main/java/com/arup/cml/abm/kpi/ScalingFactor.java b/src/main/java/com/arup/cml/abm/kpi/ScalingFactor.java deleted file mode 100644 index 5260b3b..0000000 --- a/src/main/java/com/arup/cml/abm/kpi/ScalingFactor.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.arup.cml.abm.kpi; - -public interface ScalingFactor { - double scale(double value); -} diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index b03cc47..4e5312f 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -1,7 +1,7 @@ package com.arup.cml.abm.kpi.matsim.run; import com.arup.cml.abm.kpi.KpiCalculator; -import com.arup.cml.abm.kpi.LinearScale; +import com.arup.cml.abm.kpi.LinearNormaliser; import com.arup.cml.abm.kpi.data.MoneyLog; import com.arup.cml.abm.kpi.domain.NetworkLinkLog; import com.arup.cml.abm.kpi.matsim.MatsimUtils; @@ -93,17 +93,17 @@ public void run() { CompressionType.gzip ); - kpiCalculator.writeAffordabilityKpi(outputDir, new LinearScale(0, 10, 1.25, 1)); - kpiCalculator.writePtWaitTimeKpi(outputDir, new LinearScale(0, 10, 15 * 60.0, 5 * 60.0)); + kpiCalculator.writeAffordabilityKpi(outputDir, new LinearNormaliser(0, 10, 1.25, 1)); + kpiCalculator.writePtWaitTimeKpi(outputDir, new LinearNormaliser(0, 10, 15 * 60.0, 5 * 60.0)); kpiCalculator.writeModalSplitKpi(outputDir); - kpiCalculator.writeOccupancyRateKpi(outputDir, new LinearScale(0, 10, 0.2, 0.6)); + kpiCalculator.writeOccupancyRateKpi(outputDir, new LinearNormaliser(0, 10, 0.2, 0.6)); kpiCalculator.writeVehicleKMKpi(outputDir); kpiCalculator.writePassengerKMKpi(outputDir); kpiCalculator.writeSpeedKpi(outputDir); - kpiCalculator.writeGHGKpi(outputDir, new LinearScale(0, 10, 8.87, 0.0)); - kpiCalculator.writeAccessToMobilityServicesKpi(outputDir, new LinearScale(0, 10, 0.0, 100.0)); - kpiCalculator.writeCongestionKpi(outputDir, new LinearScale(0, 10, 3.0, 1.25)); - kpiCalculator.writeTravelTimeKpi(outputDir, new LinearScale(0, 10, 90.0, 10.0)); + kpiCalculator.writeGHGKpi(outputDir, new LinearNormaliser(0, 10, 8.87, 0.0)); + kpiCalculator.writeAccessToMobilityServicesKpi(outputDir, new LinearNormaliser(0, 10, 0.0, 100.0)); + kpiCalculator.writeCongestionKpi(outputDir, new LinearNormaliser(0, 10, 3.0, 1.25)); + kpiCalculator.writeTravelTimeKpi(outputDir, new LinearNormaliser(0, 10, 90.0, 10.0)); kpiCalculator.writeMobilitySpaceUsageKpi(outputDir); MemoryObserver.stop(); } diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index 15fd4bb..102e30e 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -1,7 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; import com.arup.cml.abm.kpi.KpiCalculator; -import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.Normaliser; import com.arup.cml.abm.kpi.data.MoneyLog; import com.arup.cml.abm.kpi.domain.NetworkLinkLog; import org.apache.logging.log4j.LogManager; @@ -167,7 +167,7 @@ public void accept(Row row) { } @Override - public double writeAffordabilityKpi(Path outputDirectory, ScalingFactor scalingFactor) { + public double writeAffordabilityKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing Affordability KPI to {}", outputDirectory); // join personal income / subpop info @@ -251,7 +251,7 @@ public void accept(Double income) { .doubleColumn("mean_daily_monetary_cost") .get(0); double kpi = round( - scalingFactor.scale(lowIncomeAverageCost / overallAverageCost), + normaliser.normalise(lowIncomeAverageCost / overallAverageCost), 2); writeContentToFile(String.format("%s/kpi-affordability.csv", outputDirectory), String.valueOf(kpi), this.compressionType); return kpi; @@ -271,7 +271,7 @@ private static String findStringWithSubstring(StringColumn col, String substring } @Override - public double writePtWaitTimeKpi(Path outputDirectory, ScalingFactor scalingFactor) { + public double writePtWaitTimeKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing PT Wait Time KPI to {}", outputDirectory); // pull out legs with PT stops information @@ -311,7 +311,7 @@ public double writePtWaitTimeKpi(Path outputDirectory, ScalingFactor scalingFact .and(table.intColumn("hour").isLessThan(10))) .intColumn("wait_time_seconds") .mean(); - kpi = round(scalingFactor.scale(kpi), 2); + kpi = round(normaliser.normalise(kpi), 2); LOGGER.info("PT Wait Time KPI {}", kpi); writeContentToFile(String.format("%s/kpi-pt-wait-time.csv", outputDirectory), String.valueOf(kpi), this.compressionType); return kpi; @@ -332,7 +332,7 @@ public void writeModalSplitKpi(Path outputDirectory) { } @Override - public double writeOccupancyRateKpi(Path outputDirectory, ScalingFactor scalingFactor) { + public double writeOccupancyRateKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing Occupancy Rate KPI to {}", outputDirectory); // add capacity of the vehicle @@ -365,7 +365,7 @@ public double writeOccupancyRateKpi(Path outputDirectory, ScalingFactor scalingF double kpi = averageOccupancyPerVehicle.doubleColumn("Mean [numberOfPeople] / Mean [capacity]").sum(); kpi = kpi / numberOfVehicles; - kpi = round(scalingFactor.scale(kpi), 2); + kpi = round(normaliser.normalise(kpi), 2); LOGGER.info("Occupancy Rate KPI {}", kpi); writeContentToFile(String.format("%s/kpi-occupancy-rate.csv", outputDirectory), String.valueOf(kpi), this.compressionType); @@ -477,7 +477,7 @@ public void writeSpeedKpi(Path outputDirectory) { } @Override - public double writeGHGKpi(Path outputDirectory, ScalingFactor scalingFactor) { + public double writeGHGKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing GHG KPIs to {}", outputDirectory); // add link length to the link log table @@ -504,13 +504,13 @@ public double writeGHGKpi(Path outputDirectory, ScalingFactor scalingFactor) { String.format("emissions_total,emissions_per_capita\n%f,%f", emissionsTotal, emissionsPerCapita), this.compressionType); - double kpi = scalingFactor.scale(emissionsPerCapita); + double kpi = normaliser.normalise(emissionsPerCapita); writeContentToFile(String.format("%s/kpi-ghg-emissions.csv", outputDirectory), String.valueOf(kpi), this.compressionType); return kpi; } @Override - public double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFactor) { + public double writeTravelTimeKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing Travel Time KPI to {}", outputDirectory); // convert H:M:S format to seconds @@ -528,7 +528,7 @@ public double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFact this.writeTableCompressed(intermediate, String.format("%s/intermediate-travel-time.csv", outputDirectory), this.compressionType); double kpi = round( - scalingFactor.scale( trips.intColumn("trav_time_minutes").mean()), + normaliser.normalise( trips.intColumn("trav_time_minutes").mean()), 2 ); writeContentToFile(String.format("%s/kpi-travel-time.csv", outputDirectory), String.valueOf(kpi), this.compressionType); @@ -536,7 +536,7 @@ public double writeTravelTimeKpi(Path outputDirectory, ScalingFactor scalingFact } @Override - public Map writeAccessToMobilityServicesKpi(Path outputDirectory, ScalingFactor scalingFactor) { + public Map writeAccessToMobilityServicesKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing Access To Mobility Services KPI to {}", outputDirectory); LOGGER.info("Filtering trips table with {} rows to find trips that started from 'home'", @@ -593,7 +593,7 @@ public void accept(String aString) { double bus_kpi = ((double) table.booleanColumn("bus_access_400m").countTrue() / table.booleanColumn("bus_access_400m").size()) * 100; - bus_kpi = round(scalingFactor.scale(bus_kpi), 2); + bus_kpi = round(normaliser.normalise(bus_kpi), 2); writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-bus.csv", outputDirectory), String.valueOf(bus_kpi), this.compressionType); @@ -601,7 +601,7 @@ public void accept(String aString) { double rail_kpi = ((double) table.booleanColumn("rail_access_800m").countTrue() / table.booleanColumn("rail_access_800m").size()) * 100; - rail_kpi = round(scalingFactor.scale(rail_kpi), 2); + rail_kpi = round(normaliser.normalise(rail_kpi), 2); writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-rail.csv", outputDirectory), String.valueOf(rail_kpi), this.compressionType); @@ -611,7 +611,7 @@ public void accept(String aString) { double used_pt_kpi = ((double) table.where(ptAccess.and(table.booleanColumn("used_pt").isTrue()) ).rowCount() / table.rowCount()) * 100; - used_pt_kpi = round(scalingFactor.scale(used_pt_kpi), 2); + used_pt_kpi = round(normaliser.normalise(used_pt_kpi), 2); writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-pt-and-pt-used.csv", outputDirectory), String.valueOf(used_pt_kpi), this.compressionType); @@ -659,7 +659,7 @@ public Table addPTAccessColumnWithinDistance(Table table, Table stops, double di } @Override - public Table writeCongestionKpi(Path outputDirectory, ScalingFactor scalingFactor) { + public Table writeCongestionKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing Congestion KPIs to {}", outputDirectory); // compute travel time on links @@ -720,7 +720,7 @@ public Table writeCongestionKpi(Path outputDirectory, ScalingFactor scalingFacto DoubleColumn normalisedDelayedRatio = DoubleColumn.create("Normalised [Mean [delayRatio]]"); kpi.doubleColumn("Mean [delayRatio]") .forEach(meanDelayRatio -> normalisedDelayedRatio.append( - scalingFactor.scale(meanDelayRatio) + normaliser.normalise(meanDelayRatio) )); kpi.addColumns(normalisedDelayedRatio); diff --git a/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java new file mode 100644 index 0000000..66047c9 --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java @@ -0,0 +1,84 @@ +package com.arup.cml.abm.kpi; + +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class TestLinearNormaliser { + double lowerIntervalBound = 0.0; + double upperIntervalBound = 10.0; + + double lowerValueBound = 10.0; + double upperValueBound = 30.0; + + LinearNormaliser basicScaleFactor = new LinearNormaliser(lowerIntervalBound, upperIntervalBound, lowerValueBound, upperValueBound); + LinearNormaliser reverseScaleFactor = new LinearNormaliser(lowerIntervalBound, upperIntervalBound, upperValueBound, lowerValueBound); + + @Test + public void mapsTheMidpoint() { + assertThat(basicScaleFactor.normalise(20)).isEqualTo(5.0); + } + + @Test + public void mapsCloserToLeftBound() { + assertThat(basicScaleFactor.normalise(11.0)).isEqualTo(0.5); + } + + @Test + public void mapsCloserToRightBound() { + assertThat(basicScaleFactor.normalise(29.0)).isEqualTo(9.5); + } + + @Test + public void outsideLeftBoundMapsToLowerScaleBound() { + assertThat(basicScaleFactor.normalise(5.0)).isEqualTo(lowerIntervalBound); + } + + @Test + public void outsideRightBoundMapsToUpperScaleBound() { + assertThat(basicScaleFactor.normalise(35.0)).isEqualTo(upperIntervalBound); + } + + @Test + public void reversedFactorStillMapsTheMidpoint() { + assertThat(reverseScaleFactor.normalise(20)).isEqualTo(5.0); + } + + @Test + public void reversedFactorMapsCloserToRightBound() { + assertThat(reverseScaleFactor.normalise(11.0)).isEqualTo(9.5); + } + + @Test + public void reversedFactorMapsCloserToLeftBound() { + assertThat(reverseScaleFactor.normalise(29.0)).isEqualTo(0.5); + } + + @Test + public void reversedFactorOutsideLeftBoundMapsToLowerScaleBound() { + assertThat(reverseScaleFactor.normalise(35.0)).isEqualTo(lowerIntervalBound); + } + + @Test + public void reversedFactorOutsideRightBoundMapsToUpperScaleBound() { + assertThat(reverseScaleFactor.normalise(5.0)).isEqualTo(upperIntervalBound); + } + + @Test + public void defaultsToZeroToTenScaleBounds() { + LinearNormaliser defaultedLinearNormaliser = new LinearNormaliser(45, 50); + assertThat(defaultedLinearNormaliser.getLowerScaleBound()).isEqualTo(0.0); + assertThat(defaultedLinearNormaliser.getUpperScaleBound()).isEqualTo(10.0); + } + + @Test(expected = RuntimeException.class) + public void throwsExceptionWhenRightScaleBoundIsLargerThanLeftScaleBound() { + new LinearNormaliser(10, 0, 45, 50); + } + + @Test(expected = RuntimeException.class) + public void throwsExceptionWhenValueBoundsEqualButScaleBoundsAreNot() { + new LinearNormaliser(0, 1, 45, 45); + } + +} diff --git a/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java b/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java deleted file mode 100644 index f44752e..0000000 --- a/src/test/java/com/arup/cml/abm/kpi/TestLinearScale.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.arup.cml.abm.kpi; - -import com.arup.cml.abm.kpi.data.LinkLog; -import com.arup.cml.abm.kpi.data.exceptions.LinkLogPassengerConsistencyException; -import org.junit.Test; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -public class TestLinearScale { - double lowerScaleBound = 0.0; - double upperScaleBound = 10.0; - - double lowerValueBound = 10.0; - double upperValueBound = 30.0; - - LinearScale basicScaleFactor = new LinearScale(lowerScaleBound, upperScaleBound, lowerValueBound, upperValueBound); - LinearScale reverseScaleFactor = new LinearScale(lowerScaleBound, upperScaleBound, upperValueBound, lowerValueBound); - - @Test - public void mapsTheMidpoint() { - assertThat(basicScaleFactor.scale(20)).isEqualTo(5.0); - } - - @Test - public void mapsCloserToLeftBound() { - assertThat(basicScaleFactor.scale(11.0)).isEqualTo(0.5); - } - - @Test - public void mapsCloserToRightBound() { - assertThat(basicScaleFactor.scale(29.0)).isEqualTo(9.5); - } - - @Test - public void outsideLeftBoundMapsToLowerScaleBound() { - assertThat(basicScaleFactor.scale(5.0)).isEqualTo(lowerScaleBound); - } - - @Test - public void outsideRightBoundMapsToUpperScaleBound() { - assertThat(basicScaleFactor.scale(35.0)).isEqualTo(upperScaleBound); - } - - @Test - public void reversedFactorStillMapsTheMidpoint() { - assertThat(reverseScaleFactor.scale(20)).isEqualTo(5.0); - } - - @Test - public void reversedFactorMapsCloserToRightBound() { - assertThat(reverseScaleFactor.scale(11.0)).isEqualTo(9.5); - } - - @Test - public void reversedFactorMapsCloserToLeftBound() { - assertThat(reverseScaleFactor.scale(29.0)).isEqualTo(0.5); - } - - @Test - public void reversedFactorOutsideLeftBoundMapsToLowerScaleBound() { - assertThat(reverseScaleFactor.scale(35.0)).isEqualTo(lowerScaleBound); - } - - @Test - public void reversedFactorOutsideRightBoundMapsToUpperScaleBound() { - assertThat(reverseScaleFactor.scale(5.0)).isEqualTo(upperScaleBound); - } - - @Test - public void defaultsToZeroToTenScaleBounds() { - LinearScale defaultedLinearScale = new LinearScale(45, 50); - assertThat(defaultedLinearScale.getLowerScaleBound()).isEqualTo(0.0); - assertThat(defaultedLinearScale.getUpperScaleBound()).isEqualTo(10.0); - } - - @Test(expected = RuntimeException.class) - public void throwsExceptionWhenRightScaleBoundIsLargerThanLeftScaleBound() { - new LinearScale(10, 0, 45, 50); - } - - @Test(expected = RuntimeException.class) - public void throwsExceptionWhenValueBoundsEqualButScaleBoundsAreNot() { - new LinearScale(0, 1, 45, 45); - } - -} diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java similarity index 95% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java index 7c08dd5..8ab5697 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobility.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java @@ -1,7 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; -import com.arup.cml.abm.kpi.LinearScale; -import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.LinearNormaliser; +import com.arup.cml.abm.kpi.Normaliser; import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; import com.arup.cml.abm.kpi.builders.TransitScheduleBuilder; import com.arup.cml.abm.kpi.builders.TripsBuilder; @@ -14,10 +14,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class TestTablesawAccessToMobility { - // In this case, this scale is the proposed KPI scale. We have a natural multiplicative +public class TestTablesawAccessToMobilityWithLinearNormaliser { + // In this case, the scale interval is the one proposed for this KPI. We have a natural multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by - ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 100); + Normaliser linearNormaliser = new LinearNormaliser(0, 10, 0, 100); double equivalentScalingFactor = 1 / 10.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -37,7 +37,7 @@ public void singleAgentHasAccessToBus() { .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -67,7 +67,7 @@ public void nonHomeTripsAreFilteredOutAndDontContributeToKpiOutput() { .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -90,7 +90,7 @@ public void singleAgentHasAccessToRail() { .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(0.0 * equivalentScalingFactor) @@ -120,7 +120,7 @@ public void singleAgentHasAccessToRailAndUsesPT() { .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -149,7 +149,7 @@ public void twoAgentsWithBusAccess() { .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -175,7 +175,7 @@ public void twoAgentsWithDifferentBusAccess() { .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(50.0 * equivalentScalingFactor) @@ -201,7 +201,7 @@ public void twoAgentsWithRailAccess() { .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) @@ -227,7 +227,7 @@ public void twoAgentsWithDifferentRailAccess() { .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.get("rail_kpi")).isEqualTo(50.0 * equivalentScalingFactor) @@ -254,7 +254,7 @@ public void twoAgentsWithDifferentPTAccessButBothUsePT() { .build(); Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.get("bus_kpi")).isEqualTo(50.0 * equivalentScalingFactor) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java similarity index 93% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java index d3a726e..e1f876e 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearScalingFactor.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java @@ -1,7 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; -import com.arup.cml.abm.kpi.LinearScale; -import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.LinearNormaliser; +import com.arup.cml.abm.kpi.Normaliser; import com.arup.cml.abm.kpi.builders.*; import org.assertj.core.data.Offset; import org.junit.Rule; @@ -12,10 +12,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class TestTablesawAffordabilityKpiWithLinearScalingFactor { - // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative +public class TestTablesawAffordabilityKpiWithLinearNormaliser { + // the scale interval is not the one proposed for this KPI. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected Affordability KPI Ratio by - ScalingFactor linearScalingFactor = new LinearScale(0, 1, 0, 10); + Normaliser linearNormaliser = new LinearNormaliser(0, 1, 0, 10); double equivalentScalingFactor = 1.0 / 10.0; @Rule @@ -40,7 +40,7 @@ public void singleAgentGivesRatioOfOne() { .build(); double outputKpi = kpiCalculator.writeAffordabilityKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); double expectedRatio = 1; @@ -80,7 +80,7 @@ public void lowerIncomeAgentSpendsTwiceAsMuch() { .build(); double outputKpi = kpiCalculator.writeAffordabilityKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); // why 1.33.. ? @@ -124,7 +124,7 @@ public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { .build(); double outputKpi = kpiCalculator.writeAffordabilityKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); // why 1.33.. ? diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpiWithLinearNormaliser.java similarity index 94% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpiWithLinearNormaliser.java index 5b17b0c..b5fd6e7 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpiWithLinearNormaliser.java @@ -1,7 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; -import com.arup.cml.abm.kpi.LinearScale; -import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.LinearNormaliser; +import com.arup.cml.abm.kpi.Normaliser; import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; import com.arup.cml.abm.kpi.builders.LinkLogBuilder; import com.arup.cml.abm.kpi.builders.NetworkBuilder; @@ -16,10 +16,10 @@ import java.nio.file.Path; -public class TestTablesawCongestionKpi { - // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative +public class TestTablesawCongestionKpiWithLinearNormaliser { + // the scale interval is not the one proposed for this KPI. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by - ScalingFactor linearScalingFactor = new LinearScale(0, 1, 0, 50); + Normaliser linearNormaliser = new LinearNormaliser(0, 1, 0, 50); double equivalentScalingFactor = 1.0 / 50.0; @Rule @@ -47,7 +47,7 @@ public void reportsAverageDelayRatio() { .build(); Table outputKpi = kpiCalculator.writeCongestionKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.rowCount()).isEqualTo(1).as("Congestion KPI table should include only one row/mode"); @@ -84,7 +84,7 @@ public void reportsDelayRatiosPerMode() { .build(); Table outputKpi = kpiCalculator.writeCongestionKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.rowCount()).isEqualTo(3).as("Congestion KPI table should include three rows/modes"); @@ -127,7 +127,7 @@ public void linkWithInfiniteSpeedDoesNotBreakCongestionKpi() { .build(); Table outputKpi = kpiCalculator.writeCongestionKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi.rowCount()).isEqualTo(1).as("Congestion KPI table should include only one row/mode"); diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpiWithLinearNormaliser.java similarity index 83% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpiWithLinearNormaliser.java index 16cf75a..d85d151 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpiWithLinearNormaliser.java @@ -1,7 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; -import com.arup.cml.abm.kpi.LinearScale; -import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.LinearNormaliser; +import com.arup.cml.abm.kpi.Normaliser; import com.arup.cml.abm.kpi.builders.*; import org.junit.Rule; import org.junit.Test; @@ -11,10 +11,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class TestTablesawGHGKpi { - // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative +public class TestTablesawGHGKpiWithLinearNormaliser { + // the scale interval is not the one proposed for this KPI. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by - ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 20); + Normaliser linearNormaliser = new LinearNormaliser(0, 10, 0, 20); double equivalentScalingFactor = 1.0 / 2.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -42,7 +42,7 @@ public void oneKilometerJourneyWithEmissionsFactorTwoAndSinglePersonGivesKpiOutp .build(); double outputKpi = kpiCalculator.writeGHGKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi).isEqualTo(2.0 * equivalentScalingFactor) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearNormaliser.java similarity index 81% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearNormaliser.java index 72f59e2..1147d2a 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearScalingFactor.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearNormaliser.java @@ -1,7 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; -import com.arup.cml.abm.kpi.LinearScale; -import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.LinearNormaliser; +import com.arup.cml.abm.kpi.Normaliser; import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; import com.arup.cml.abm.kpi.builders.LinkLogBuilder; import com.arup.cml.abm.kpi.builders.VehiclesBuilder; @@ -13,10 +13,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class TestTablesawOccupancyKpiWithLinearScalingFactor { - // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative +public class TestTablesawOccupancyKpiWithLinearNormaliser { + // the scale interval is not the one proposed for this KPI. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by - ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 1); + Normaliser linearNormaliser = new LinearNormaliser(0, 10, 0, 1); double equivalentScalingFactor = 10.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -37,7 +37,7 @@ public void twoPeopleInAFourSeaterGiveHalfOccupancy() { .build(); double outputKpi = kpiCalculator.writeOccupancyRateKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi).isEqualTo(0.5 * equivalentScalingFactor) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearScalingFactor.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java similarity index 87% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearScalingFactor.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java index 0bf92b0..a3861bf 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearScalingFactor.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java @@ -1,7 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; -import com.arup.cml.abm.kpi.LinearScale; -import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.LinearNormaliser; +import com.arup.cml.abm.kpi.Normaliser; import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; import com.arup.cml.abm.kpi.builders.LegsBuilder; import org.junit.Rule; @@ -12,10 +12,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class TestTablesawPtWaitTimeKpiWithLinearScalingFactor { - // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative +public class TestTablesawPtWaitTimeKpiWithLinearNormaliser { + // the scale interval is not the one proposed for this KPI. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by - ScalingFactor linearScalingFactor = new LinearScale(0, 1, 0, 15 * 60); + Normaliser linearNormaliser = new LinearNormaliser(0, 1, 0, 15 * 60); double equivalentScalingFactor = 1.0 / (15.0 * 60); @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -33,7 +33,7 @@ public void ptWaitTimeReturnsTheSinglePtWaitTimeWithSingleAgent() { .build(); double outputKpi = kpiCalculator.writePtWaitTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi).isEqualTo(bobbyPtWaitTimeSeconds * equivalentScalingFactor) @@ -52,7 +52,7 @@ public void ptWaitTimeReturnsZeroWhenPtLegIsOutsidePeakTime() { .build(); double outputKpi = kpiCalculator.writePtWaitTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi).isEqualTo(0.0) @@ -77,7 +77,7 @@ public void givesAverageOfTwoPeakPtLegs() { .build(); double outputKpi = kpiCalculator.writePtWaitTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi).isEqualTo((bobbyPtWaitTimeSeconds + bobbinaPtWaitTimeSeconds) / 2 * equivalentScalingFactor) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java similarity index 85% rename from src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java rename to src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java index 9715fe2..6ad2b5d 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpi.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java @@ -1,7 +1,7 @@ package com.arup.cml.abm.kpi.tablesaw; -import com.arup.cml.abm.kpi.LinearScale; -import com.arup.cml.abm.kpi.ScalingFactor; +import com.arup.cml.abm.kpi.LinearNormaliser; +import com.arup.cml.abm.kpi.Normaliser; import com.arup.cml.abm.kpi.builders.*; import org.junit.Rule; import org.junit.Test; @@ -11,10 +11,10 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class TestTablesawTravelTimeKpi { - // this scale is not the proposed KPI scale. The Value bounds where chosen so that we have a multiplicative +public class TestTablesawTravelTimeKpiWithLinearNormaliser { + // the scale interval is not the one proposed for this KPI. The Value bounds where chosen so that we have a multiplicative // `equivalentScalingFactor` to multiply the expected KPI output by - ScalingFactor linearScalingFactor = new LinearScale(0, 10, 0, 50); + Normaliser linearNormaliser = new LinearNormaliser(0, 10, 0, 50); double equivalentScalingFactor = 1 / 5.0; @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); @@ -33,7 +33,7 @@ public void singleTripGivesTrivialAverage() { .build(); double outputKpi = kpiCalculator.writeTravelTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi).isEqualTo(trav_time_minutes * equivalentScalingFactor) @@ -59,7 +59,7 @@ public void twoTripsProduceAverageOfTravelTimes() { .build(); double outputKpi = kpiCalculator.writeTravelTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), - linearScalingFactor + linearNormaliser ); assertThat(outputKpi).isEqualTo( From db691b2866478fcec280e5cbe27097cad80c66f6 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Tue, 26 Mar 2024 11:59:28 +0000 Subject: [PATCH 25/39] output actual and normalised KPI outputs --- .../com/arup/cml/abm/kpi/KpiCalculator.java | 12 +- .../kpi/tablesaw/TablesawKpiCalculator.java | 126 ++++++++++++------ ...wAccessToMobilityWithLinearNormaliser.java | 68 ++++++---- ...wAffordabilityKpiWithLinearNormaliser.java | 17 ++- ...esawCongestionKpiWithLinearNormaliser.java | 6 +- ...estTablesawGHGKpiWithLinearNormaliser.java | 6 +- ...lesawOccupancyKpiWithLinearNormaliser.java | 6 +- ...esawPtWaitTimeKpiWithLinearNormaliser.java | 16 ++- ...esawTravelTimeKpiWithLinearNormaliser.java | 12 +- ...-to-mobility-services-access-to-bus.csv.gz | Bin 23 -> 43 bytes ...y-services-access-to-pt-and-pt-used.csv.gz | Bin 23 -> 43 bytes ...to-mobility-services-access-to-rail.csv.gz | Bin 23 -> 43 bytes .../expected-kpi-affordability.csv.gz | Bin 23 -> 46 bytes .../expected-kpi-ghg-emissions.csv.gz | Bin 23 -> 46 bytes .../expected-kpi-occupancy-rate.csv.gz | Bin 23 -> 44 bytes .../expected-kpi-pt-wait-time.csv.gz | Bin 24 -> 48 bytes .../expected-kpi-travel-time.csv.gz | Bin 24 -> 48 bytes ...-to-mobility-services-access-to-bus.csv.gz | Bin 24 -> 45 bytes ...y-services-access-to-pt-and-pt-used.csv.gz | Bin 24 -> 45 bytes ...to-mobility-services-access-to-rail.csv.gz | Bin 23 -> 43 bytes .../expected-kpi-affordability.csv.gz | Bin 24 -> 47 bytes .../expected-kpi-ghg-emissions.csv.gz | Bin 23 -> 46 bytes .../expected-kpi-occupancy-rate.csv.gz | Bin 23 -> 46 bytes .../expected-kpi-pt-wait-time.csv.gz | Bin 24 -> 48 bytes .../expected-kpi-travel-time.csv.gz | Bin 24 -> 48 bytes 25 files changed, 171 insertions(+), 98 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java index 300ebe0..45ac928 100644 --- a/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/KpiCalculator.java @@ -6,13 +6,13 @@ import java.util.Map; public interface KpiCalculator { - double writeAffordabilityKpi(Path outputDirectory, Normaliser normaliser); + Map writeAffordabilityKpi(Path outputDirectory, Normaliser normaliser); - double writePtWaitTimeKpi(Path outputDirectory, Normaliser normaliser); + Map writePtWaitTimeKpi(Path outputDirectory, Normaliser normaliser); void writeModalSplitKpi(Path outputDirectory); - double writeOccupancyRateKpi(Path outputDirectory, Normaliser normaliser); + Map writeOccupancyRateKpi(Path outputDirectory, Normaliser normaliser); double writeVehicleKMKpi(Path outputDirectory); @@ -20,11 +20,11 @@ public interface KpiCalculator { void writeSpeedKpi(Path outputDirectory); - double writeGHGKpi(Path outputDirectory, Normaliser normaliser); + Map writeGHGKpi(Path outputDirectory, Normaliser normaliser); - double writeTravelTimeKpi(Path outputDirectory, Normaliser normaliser); + Map writeTravelTimeKpi(Path outputDirectory, Normaliser normaliser); - Map writeAccessToMobilityServicesKpi(Path outputDirectory, Normaliser normaliser); + Map> writeAccessToMobilityServicesKpi(Path outputDirectory, Normaliser normaliser); Table writeCongestionKpi(Path directory, Normaliser normaliser); diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index 102e30e..af084d9 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -167,7 +167,7 @@ public void accept(Row row) { } @Override - public double writeAffordabilityKpi(Path outputDirectory, Normaliser normaliser) { + public Map writeAffordabilityKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing Affordability KPI to {}", outputDirectory); // join personal income / subpop info @@ -250,14 +250,19 @@ public void accept(Double income) { .where(intermediate.stringColumn(incomeColumnName).isEqualTo(lowIncomeName)) .doubleColumn("mean_daily_monetary_cost") .get(0); - double kpi = round( - normaliser.normalise(lowIncomeAverageCost / overallAverageCost), - 2); - writeContentToFile(String.format("%s/kpi-affordability.csv", outputDirectory), String.valueOf(kpi), this.compressionType); - return kpi; + double kpi = lowIncomeAverageCost / overallAverageCost; + Map kpiOutput = Map.of( + "actual", round(kpi, 2), + "normalised", round(normaliser.normalise(kpi), 2) + ); + writeContentToFile(String.format("%s/kpi-affordability.csv", outputDirectory), kpiOutput, this.compressionType); + return kpiOutput; } LOGGER.warn("We could not give you a KPI, check logs and intermediate output."); - return -1.0; + return Map.of( + "actual", -1.0, + "normalised", -1.0 + ); } private static String findStringWithSubstring(StringColumn col, String substring) { @@ -271,7 +276,7 @@ private static String findStringWithSubstring(StringColumn col, String substring } @Override - public double writePtWaitTimeKpi(Path outputDirectory, Normaliser normaliser) { + public Map writePtWaitTimeKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing PT Wait Time KPI to {}", outputDirectory); // pull out legs with PT stops information @@ -311,10 +316,13 @@ public double writePtWaitTimeKpi(Path outputDirectory, Normaliser normaliser) { .and(table.intColumn("hour").isLessThan(10))) .intColumn("wait_time_seconds") .mean(); - kpi = round(normaliser.normalise(kpi), 2); - LOGGER.info("PT Wait Time KPI {}", kpi); - writeContentToFile(String.format("%s/kpi-pt-wait-time.csv", outputDirectory), String.valueOf(kpi), this.compressionType); - return kpi; + + Map kpiOutput = Map.of( + "actual", round(kpi, 2), + "normalised", round(normaliser.normalise(kpi), 2) + ); + writeContentToFile(String.format("%s/kpi-pt-wait-time.csv", outputDirectory), kpiOutput, this.compressionType); + return kpiOutput; } @Override @@ -332,7 +340,7 @@ public void writeModalSplitKpi(Path outputDirectory) { } @Override - public double writeOccupancyRateKpi(Path outputDirectory, Normaliser normaliser) { + public Map writeOccupancyRateKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing Occupancy Rate KPI to {}", outputDirectory); // add capacity of the vehicle @@ -365,11 +373,13 @@ public double writeOccupancyRateKpi(Path outputDirectory, Normaliser normaliser) double kpi = averageOccupancyPerVehicle.doubleColumn("Mean [numberOfPeople] / Mean [capacity]").sum(); kpi = kpi / numberOfVehicles; - kpi = round(normaliser.normalise(kpi), 2); - LOGGER.info("Occupancy Rate KPI {}", kpi); - writeContentToFile(String.format("%s/kpi-occupancy-rate.csv", outputDirectory), String.valueOf(kpi), this.compressionType); - return kpi; + Map kpiOutput = Map.of( + "actual", round(kpi, 2), + "normalised", round(normaliser.normalise(kpi), 2) + ); + writeContentToFile(String.format("%s/kpi-occupancy-rate.csv", outputDirectory), kpiOutput, this.compressionType); + return kpiOutput; } @Override @@ -477,7 +487,7 @@ public void writeSpeedKpi(Path outputDirectory) { } @Override - public double writeGHGKpi(Path outputDirectory, Normaliser normaliser) { + public Map writeGHGKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing GHG KPIs to {}", outputDirectory); // add link length to the link log table @@ -504,13 +514,16 @@ public double writeGHGKpi(Path outputDirectory, Normaliser normaliser) { String.format("emissions_total,emissions_per_capita\n%f,%f", emissionsTotal, emissionsPerCapita), this.compressionType); - double kpi = normaliser.normalise(emissionsPerCapita); - writeContentToFile(String.format("%s/kpi-ghg-emissions.csv", outputDirectory), String.valueOf(kpi), this.compressionType); - return kpi; + Map kpiOutput = Map.of( + "actual", round(emissionsPerCapita, 2), + "normalised", round(normaliser.normalise(emissionsPerCapita), 2) + ); + writeContentToFile(String.format("%s/kpi-ghg-emissions.csv", outputDirectory), kpiOutput, this.compressionType); + return kpiOutput; } @Override - public double writeTravelTimeKpi(Path outputDirectory, Normaliser normaliser) { + public Map writeTravelTimeKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing Travel Time KPI to {}", outputDirectory); // convert H:M:S format to seconds @@ -527,16 +540,18 @@ public double writeTravelTimeKpi(Path outputDirectory, Normaliser normaliser) { .setName("Travel Time by trip purpose"); this.writeTableCompressed(intermediate, String.format("%s/intermediate-travel-time.csv", outputDirectory), this.compressionType); - double kpi = round( - normaliser.normalise( trips.intColumn("trav_time_minutes").mean()), - 2 + double kpi = trips.intColumn("trav_time_minutes").mean(); + + Map kpiOutput = Map.of( + "actual", round(kpi, 2), + "normalised", round(normaliser.normalise(kpi), 2) ); - writeContentToFile(String.format("%s/kpi-travel-time.csv", outputDirectory), String.valueOf(kpi), this.compressionType); - return kpi; + writeContentToFile(String.format("%s/kpi-travel-time.csv", outputDirectory), kpiOutput, this.compressionType); + return kpiOutput; } @Override - public Map writeAccessToMobilityServicesKpi(Path outputDirectory, Normaliser normaliser) { + public Map> writeAccessToMobilityServicesKpi(Path outputDirectory, Normaliser normaliser) { LOGGER.info("Writing Access To Mobility Services KPI to {}", outputDirectory); LOGGER.info("Filtering trips table with {} rows to find trips that started from 'home'", @@ -590,36 +605,45 @@ public void accept(String aString) { this.compressionType); LOGGER.info("Calculating bus access to mobility KPI"); - double bus_kpi = ((double) table.booleanColumn("bus_access_400m").countTrue() / + double busKpi = ((double) table.booleanColumn("bus_access_400m").countTrue() / table.booleanColumn("bus_access_400m").size()) * 100; - bus_kpi = round(normaliser.normalise(bus_kpi), 2); + Map busKpiOutput = Map.of( + "actual", round(busKpi, 2), + "normalised", round(normaliser.normalise(busKpi), 2) + ); writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-bus.csv", outputDirectory), - String.valueOf(bus_kpi), this.compressionType); + busKpiOutput, this.compressionType); LOGGER.info("Calculating rail access to mobility KPI"); - double rail_kpi = ((double) table.booleanColumn("rail_access_800m").countTrue() / + double railKpi = ((double) table.booleanColumn("rail_access_800m").countTrue() / table.booleanColumn("rail_access_800m").size()) * 100; - rail_kpi = round(normaliser.normalise(rail_kpi), 2); + Map railKpiOutput = Map.of( + "actual", round(railKpi, 2), + "normalised", round(normaliser.normalise(railKpi), 2) + ); writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-rail.csv", outputDirectory), - String.valueOf(rail_kpi), this.compressionType); + railKpiOutput, this.compressionType); LOGGER.info("Computing utilised PT KPI"); Selection ptAccess = table.booleanColumn("bus_access_400m").isTrue() .or(table.booleanColumn("rail_access_800m").isTrue()); - double used_pt_kpi = ((double) table.where(ptAccess.and(table.booleanColumn("used_pt").isTrue()) + double usedPtKpi = ((double) table.where(ptAccess.and(table.booleanColumn("used_pt").isTrue()) ).rowCount() / table.rowCount()) * 100; - used_pt_kpi = round(normaliser.normalise(used_pt_kpi), 2); + Map usedPtKpiOutput = Map.of( + "actual", round(usedPtKpi, 2), + "normalised", round(normaliser.normalise(usedPtKpi), 2) + ); writeContentToFile(String.format("%s/kpi-access-to-mobility-services-access-to-pt-and-pt-used.csv", outputDirectory), - String.valueOf(used_pt_kpi), this.compressionType); + usedPtKpiOutput, this.compressionType); LOGGER.info("Finished calculating access to mobility KPIs"); return Map.of( - "bus_kpi", bus_kpi, - "rail_kpi", rail_kpi, - "used_pt_kpi", used_pt_kpi + "bus_kpi", busKpiOutput, + "rail_kpi", railKpiOutput, + "used_pt_kpi", usedPtKpiOutput ); } @@ -795,7 +819,6 @@ public double writeMobilitySpaceUsageKpi(Path outputDirectory) { double finalKpi = kpi.numberColumn("parking_space_demand").sum() / personModeScores.column("person").size(); LOGGER.info("Finished calculating the final KPI"); - // TODO Add Scaling finalKpi = round(finalKpi, 2); writeContentToFile(String.format("%s/kpi-mobility-space-usage.csv", outputDirectory), String.valueOf(finalKpi), @@ -826,7 +849,7 @@ private Table sanitiseInfiniteColumnValuesInTable(Table table, DoubleColumn colu Table infiniteValuesTable = table.where(column.eval(Double::isInfinite)); if (!infiniteValuesTable.isEmpty()) { LOGGER.warn("Table: '{}' has {} row(s) affected by infinite values in column: '{}'. " + - "These rows will be dropped for this calculation.", + "These rows will be dropped for this calculation.", table.name(), infiniteValuesTable.rowCount(), column.name()); return table.dropWhere(column.eval(Double::isInfinite)); } else { @@ -929,7 +952,7 @@ private Table fixFacilitiesInTripsTable(Table activityFacilities, Table trips) { private Table addCostToLegs(Table legs, Table personModeScores, MoneyLog moneyLog) { LOGGER.info("Adding costs to legs table. Legs table has {} rows, personModeScores table " + - "has {} rows, moneyLog has {} entries", + "has {} rows, moneyLog has {} entries", legs.rowCount(), personModeScores.rowCount(), moneyLog.getMoneyLogData().size()); @@ -1441,6 +1464,23 @@ private void writeContentToFile(String path, String content, CompressionType com LOGGER.info("Finished writing file {}", path); } + private void writeContentToFile(String path, Map content, CompressionType compressionType) { + // keys form columns + StringBuilder csvColumns = new StringBuilder(); + StringBuilder csvValues = new StringBuilder(); + + for (String key : content.keySet().stream().sorted().toList()) { + csvColumns.append(key).append(","); + csvValues.append(content.get(key).toString()).append(","); + } + csvColumns.deleteCharAt(csvColumns.length() - 1); + csvValues.deleteCharAt(csvValues.length() - 1); + writeContentToFile( + path, + String.format(csvColumns + "\n" + csvValues), + compressionType); + } + private void writeSupportingData(Path outputDir) { LOGGER.info("Writing supporting data files"); try { diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java index 8ab5697..5229660 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java @@ -23,7 +23,7 @@ public class TestTablesawAccessToMobilityWithLinearNormaliser { public TemporaryFolder tmpDir = new TemporaryFolder(); @Test - public void singleAgentHasAccessToBus() { + public void singleAgentHasAccessOnlyToBus() { TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) @@ -35,18 +35,21 @@ public void singleAgentHasAccessToBus() { .withTransitStopWithMode("BusStop", 400.0, 0.0, "bus") .build()) .build(); - Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 100%, " + "because there is only one agent and they have access to a bus stop."); - assertThat(outputKpi.get("rail_kpi")).isEqualTo(0.0 * equivalentScalingFactor) + assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(0.0); + assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 0%, " + "because there is only one agent and they don't have access to rail."); - assertThat(outputKpi.get("used_pt_kpi")).isEqualTo(0.0 * equivalentScalingFactor) + assertThat(outputKpi.get("used_pt_kpi").get("actual")).isEqualTo(0.0); + assertThat(outputKpi.get("used_pt_kpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Used PT KPI output is expected to be 0%, " + "because there is only one agent and they didn't use PT"); } @@ -65,12 +68,13 @@ public void nonHomeTripsAreFilteredOutAndDontContributeToKpiOutput() { .withTransitStopWithMode("BusStop", 400.0, 0.0, "bus") .build()) .build(); - Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 100%, " + "because there is only one agent and they have access to a bus stop."); } @@ -88,18 +92,21 @@ public void singleAgentHasAccessToRail() { .withTransitStopWithMode("RailStop", 800.0, 0.0, "rail") .build()) .build(); - Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi.get("bus_kpi")).isEqualTo(0.0 * equivalentScalingFactor) + assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(0.0); + assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 0%, " + "because there is only one agent and they don't have access to a bus stop."); - assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 100%, " + "because there is only one agent and they have access to rail."); - assertThat(outputKpi.get("used_pt_kpi")).isEqualTo(0.0 * equivalentScalingFactor) + assertThat(outputKpi.get("used_pt_kpi").get("actual")).isEqualTo(0.0); + assertThat(outputKpi.get("used_pt_kpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Used PT KPI output is expected to be 0%, " + "because there is only one agent and they didn't use PT"); } @@ -118,15 +125,17 @@ public void singleAgentHasAccessToRailAndUsesPT() { .withTransitStopWithMode("RailStop", 800.0, 0.0, "rail") .build()) .build(); - Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 100%, " + "because there is only one agent and they have access to rail."); - assertThat(outputKpi.get("used_pt_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("used_pt_kpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("used_pt_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Used PT KPI output is expected to be 100%, " + "because there is only one agent and they used PT"); } @@ -147,12 +156,13 @@ public void twoAgentsWithBusAccess() { .withTransitStopWithMode("BusStop", 0.0, 0.0, "bus") .build()) .build(); - Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi.get("bus_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 100%, " + "because both agents have access to bus."); } @@ -173,12 +183,13 @@ public void twoAgentsWithDifferentBusAccess() { .withTransitStopWithMode("BusStop", 0.0, 0.0, "bus") .build()) .build(); - Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi.get("bus_kpi")).isEqualTo(50.0 * equivalentScalingFactor) + assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(50.0); + assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 50%, " + "because one agent has access to bus and the other doesn't."); } @@ -199,12 +210,13 @@ public void twoAgentsWithRailAccess() { .withTransitStopWithMode("RailStop", 0.0, 0.0, "rail") .build()) .build(); - Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi.get("rail_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 100%, " + "because both agents have access to rail."); } @@ -225,12 +237,13 @@ public void twoAgentsWithDifferentRailAccess() { .withTransitStopWithMode("RailStop", 0.0, 0.0, "rail") .build()) .build(); - Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi.get("rail_kpi")).isEqualTo(50.0 * equivalentScalingFactor) + assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(50.0); + assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 50%, " + "because one agent has access to rail and the other doesn't."); } @@ -252,18 +265,21 @@ public void twoAgentsWithDifferentPTAccessButBothUsePT() { .withTransitStopWithMode("BusStop", 800.0, 0.0, "bus") .build()) .build(); - Map outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi.get("bus_kpi")).isEqualTo(50.0 * equivalentScalingFactor) + assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(50.0); + assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 50%, " + "because one agent has access to bus and the other doesn't."); - assertThat(outputKpi.get("rail_kpi")).isEqualTo(50.0 * equivalentScalingFactor) + assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(50.0); + assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 50%, " + "because one agent has access to rail and the other doesn't."); - assertThat(outputKpi.get("used_pt_kpi")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("used_pt_kpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("used_pt_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Used KPI output is expected to be 100%, " + "because both agents use PT."); } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java index e1f876e..78bb9f7 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java @@ -9,6 +9,7 @@ import org.junit.rules.TemporaryFolder; import java.nio.file.Path; +import java.util.Map; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -38,13 +39,14 @@ public void singleAgentGivesRatioOfOne() { .withMonetaryCostsForSubpopulationAndMode(bobbySubpop, "car", 1.0, 1.0) .build()) .build(); - double outputKpi = kpiCalculator.writeAffordabilityKpi( + Map outputKpi = kpiCalculator.writeAffordabilityKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); double expectedRatio = 1; - assertThat(outputKpi).isEqualTo(expectedRatio * equivalentScalingFactor) + assertThat(outputKpi.get("actual")).isEqualTo(expectedRatio); + assertThat(outputKpi.get("normalised")).isEqualTo(expectedRatio * equivalentScalingFactor) .as("There is only one agent so the ratio of all travel to the low income group " + "is expected to be 1, and 0.1 after scaling"); } @@ -78,7 +80,7 @@ public void lowerIncomeAgentSpendsTwiceAsMuch() { .withMonetaryCostsForSubpopulationAndMode(subpop, mode, dailyConstant, distanceCost) .build()) .build(); - double outputKpi = kpiCalculator.writeAffordabilityKpi( + Map outputKpi = kpiCalculator.writeAffordabilityKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); @@ -88,7 +90,9 @@ public void lowerIncomeAgentSpendsTwiceAsMuch() { // 2x <- poor Bobby cost // (3x/2) <- overall average double expectedRatio = 1 + 1.0/3; - assertThat(outputKpi).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) + + assertThat(outputKpi.get("actual")).isCloseTo(expectedRatio, Offset.offset(0.009)); + assertThat(outputKpi.get("normalised")).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) .as("There are two agents, the poorer agent spends twice as much on travel. The ratio of " + "all travel to the low income group is expected to be 1.33, and 0.133 after scaling."); } @@ -122,7 +126,7 @@ public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { .withMonetaryCostsForSubpopulationAndMode(richBobbySubpop, mode, dailyConstant, distanceCost) .build()) .build(); - double outputKpi = kpiCalculator.writeAffordabilityKpi( + Map outputKpi = kpiCalculator.writeAffordabilityKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); @@ -132,7 +136,8 @@ public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { // 2x <- poor Bobby cost // (3x/2) <- overall average double expectedRatio = 1 + 1.0/3; - assertThat(outputKpi).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) + assertThat(outputKpi.get("actual")).isCloseTo(expectedRatio, Offset.offset(0.009)); + assertThat(outputKpi.get("normalised")).isCloseTo(expectedRatio * equivalentScalingFactor, Offset.offset(0.009)) .as("There are two agents, the poorer agent spends twice as much on travel. The ratio of " + "all travel to the low income group is expected to be 1.33, and 0.133 after scaling."); } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpiWithLinearNormaliser.java index b5fd6e7..ff34f24 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawCongestionKpiWithLinearNormaliser.java @@ -45,7 +45,7 @@ public void reportsAverageDelayRatio() { .withEntry("someCar", "otherLink", (9 * 60 * 60) + 30, (9 * 60 * 60) + 30 + secondDelayRatio) .build()) .build(); - Table outputKpi = kpiCalculator.writeCongestionKpi( + Table outputKpi = (Table) kpiCalculator.writeCongestionKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); @@ -82,7 +82,7 @@ public void reportsDelayRatiosPerMode() { .withEntry("someHorse", "otherLink", (9 * 60 * 60) + 25, (9 * 60 * 60) + 25 + horseDelayRatio) .build()) .build(); - Table outputKpi = kpiCalculator.writeCongestionKpi( + Table outputKpi = (Table) kpiCalculator.writeCongestionKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); @@ -125,7 +125,7 @@ public void linkWithInfiniteSpeedDoesNotBreakCongestionKpi() { .withEntry("someCar", "otherLink", (9 * 60 * 60) + 25, (9 * 60 * 60) + 30) .build()) .build(); - Table outputKpi = kpiCalculator.writeCongestionKpi( + Table outputKpi = (Table) kpiCalculator.writeCongestionKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpiWithLinearNormaliser.java index d85d151..7e0536a 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawGHGKpiWithLinearNormaliser.java @@ -8,6 +8,7 @@ import org.junit.rules.TemporaryFolder; import java.nio.file.Path; +import java.util.Map; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -40,12 +41,13 @@ public void oneKilometerJourneyWithEmissionsFactorTwoAndSinglePersonGivesKpiOutp .withPerson("Bobby") .build()) .build(); - double outputKpi = kpiCalculator.writeGHGKpi( + Map outputKpi = kpiCalculator.writeGHGKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi).isEqualTo(2.0 * equivalentScalingFactor) + assertThat(outputKpi.get("actual")).isEqualTo(2.0); + assertThat(outputKpi.get("normalised")).isEqualTo(2.0 * equivalentScalingFactor) .as("A vehicle with emissions factor 2 drives a kilometer, " + "so the output of the GHG KPI is expected to be 2, and 1 after scaling"); } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearNormaliser.java index 1147d2a..dc3d526 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawOccupancyKpiWithLinearNormaliser.java @@ -10,6 +10,7 @@ import org.junit.rules.TemporaryFolder; import java.nio.file.Path; +import java.util.Map; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -35,12 +36,13 @@ public void twoPeopleInAFourSeaterGiveHalfOccupancy() { .withVehicleOfCapacity(someCar, "car", "car", 4) .build()) .build(); - double outputKpi = kpiCalculator.writeOccupancyRateKpi( + Map outputKpi = kpiCalculator.writeOccupancyRateKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi).isEqualTo(0.5 * equivalentScalingFactor) + assertThat(outputKpi.get("actual")).isEqualTo(0.5); + assertThat(outputKpi.get("normalised")).isEqualTo(0.5 * equivalentScalingFactor) .as("Occupancy Rate should be at half with two people in a car that fits four," + "and 5 after scaling."); } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java index a3861bf..73244a8 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java @@ -9,6 +9,7 @@ import org.junit.rules.TemporaryFolder; import java.nio.file.Path; +import java.util.Map; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -31,12 +32,13 @@ public void ptWaitTimeReturnsTheSinglePtWaitTimeWithSingleAgent() { .withDefaultPtLegWithTiming(bobby, "bobby_1", "09:00:00", "00:30:00", bobbyPtWaitTime) .build()) .build(); - double outputKpi = kpiCalculator.writePtWaitTimeKpi( + Map outputKpi = kpiCalculator.writePtWaitTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi).isEqualTo(bobbyPtWaitTimeSeconds * equivalentScalingFactor) + assertThat(outputKpi.get("actual")).isEqualTo(bobbyPtWaitTimeSeconds); + assertThat(outputKpi.get("normalised")).isEqualTo(bobbyPtWaitTimeSeconds * equivalentScalingFactor) .as("PT Wait Time KPI should return the only PT wait time recorded with one agent"); } @@ -50,12 +52,13 @@ public void ptWaitTimeReturnsZeroWhenPtLegIsOutsidePeakTime() { .withDefaultPtLegWithTiming(bobby, "bobby_1", "23:00:00", "00:30:00", bobbyPtWaitTime) .build()) .build(); - double outputKpi = kpiCalculator.writePtWaitTimeKpi( + Map outputKpi = kpiCalculator.writePtWaitTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi).isEqualTo(0.0) + assertThat(outputKpi.get("actual")).isEqualTo(0.0); + assertThat(outputKpi.get("normalised")).isEqualTo(0.0) .as("PT Wait Time KPI should return 0, as PT Leg is outside peak time"); } @@ -75,12 +78,13 @@ public void givesAverageOfTwoPeakPtLegs() { .withDefaultPtLegWithTiming(bobbina, "bobbina_1", "09:00:00", "00:30:00", bobbinaPtWaitTime) .build()) .build(); - double outputKpi = kpiCalculator.writePtWaitTimeKpi( + Map outputKpi = kpiCalculator.writePtWaitTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi).isEqualTo((bobbyPtWaitTimeSeconds + bobbinaPtWaitTimeSeconds) / 2 * equivalentScalingFactor) + assertThat(outputKpi.get("actual")).isEqualTo((bobbyPtWaitTimeSeconds + bobbinaPtWaitTimeSeconds) / 2); + assertThat(outputKpi.get("normalised")).isEqualTo((bobbyPtWaitTimeSeconds + bobbinaPtWaitTimeSeconds) / 2 * equivalentScalingFactor) .as("PT Wait Time KPI should return average of 6 and 12 minutes for Bobby " + "and his friend Bobbina"); } diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java index 6ad2b5d..dd773c5 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java @@ -8,6 +8,7 @@ import org.junit.rules.TemporaryFolder; import java.nio.file.Path; +import java.util.Map; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -31,12 +32,13 @@ public void singleTripGivesTrivialAverage() { .build()) .withLegs(tripsBuilder.getLegsBuilder().build()) .build(); - double outputKpi = kpiCalculator.writeTravelTimeKpi( + Map outputKpi = kpiCalculator.writeTravelTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi).isEqualTo(trav_time_minutes * equivalentScalingFactor) + assertThat(outputKpi.get("actual")).isEqualTo(trav_time_minutes); + assertThat(outputKpi.get("normalised")).isEqualTo(trav_time_minutes * equivalentScalingFactor) .as("KPI output is the average of travel times. " + "Average of one travel time is that travel time"); } @@ -57,12 +59,14 @@ public void twoTripsProduceAverageOfTravelTimes() { .build()) .withLegs(tripsBuilder.getLegsBuilder().build()) .build(); - double outputKpi = kpiCalculator.writeTravelTimeKpi( + Map outputKpi = kpiCalculator.writeTravelTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser ); - assertThat(outputKpi).isEqualTo( + assertThat(outputKpi.get("actual")).isEqualTo( + (bobby_trav_time_minutes + bobbina_trav_time_minutes) / 2); + assertThat(outputKpi.get("normalised")).isEqualTo( ((bobby_trav_time_minutes + bobbina_trav_time_minutes) / 2) * equivalentScalingFactor) .as("Should be the average of two travel times"); } diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-access-to-mobility-services-access-to-bus.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-access-to-mobility-services-access-to-bus.csv.gz index afe448d26287c639626b0561dce23f38cb2c8777..80ff68515a238cf4fd55340042da70b2b5edb3f0 100644 GIT binary patch literal 43 vcmb2|=3oGW|K2`&n!aZ)o;^G literal 23 Zcmb2|=3oGW|Hc=L85o#YzkFi`@&Puv1l#}s diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-occupancy-rate.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-occupancy-rate.csv.gz index afe448d26287c639626b0561dce23f38cb2c8777..e29f0da94f4798fa86f8504b3a4e66c22169822e 100644 GIT binary patch literal 44 wcmb2|=3oGW|K2`&n!aZ)o;H8x~nShx1-Oc|g;0GC)1 AX#fBK literal 24 bcmb2|=3oGW|HfC14Otjg_)oKA0SW*BJ(C3S diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-access-to-mobility-services-access-to-bus.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-access-to-mobility-services-access-to-bus.csv.gz index b4a11e6bdce7ca938d3e4a26275dfdde25c6845d..b745d639a5b1a889967e1997dfda2427a81706d4 100644 GIT binary patch literal 45 xcmb2|=3oGW|K2`&n!aZ)o; literal 23 Zcmb2|=3oGW|Hc=L85o#YzkFi`@&Puv1l#}s diff --git a/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-occupancy-rate.csv.gz b/src/test/resources/integration-test-data/smol-matsim-outputs/expected-kpis/expected-kpi-occupancy-rate.csv.gz index afe448d26287c639626b0561dce23f38cb2c8777..c0adfac976ae919b82b272e8573c89bdb75f5473 100644 GIT binary patch literal 46 ycmb2|=3oGW|K2`&n!aZ)o;Vb8fIb7g=E0i!q) Aod5s; literal 24 bcmb2|=3oGW|HcUH{QU)(3g}YAp=wh0F^5c AmjD0& literal 24 bcmb2|=3oGW|HjviH?S~>p3?Zo0u%rMLlXt8 From 2d608fe81415c72d0b79f89d5593803accb159f2 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Tue, 26 Mar 2024 12:01:19 +0000 Subject: [PATCH 26/39] update changelog with scaling/normalisation --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d92ecc3..e3e4b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- +- Scaling/Normalisation to values between `0` and `10` for metrics ([#69](https://github.com/arup-group/gelato/issues/69)): +`Affordability`, `PT Wait Time`, `Occupancy`, `GHG`, `Travel Time`, `Access to Mobility services`, `Congestion` ### Fixed From cef5bd991c8a13f24f91dc14a6615f76b6fc2b0c Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Tue, 26 Mar 2024 13:40:14 +0000 Subject: [PATCH 27/39] remove more mentions of scaling --- .../arup/cml/abm/kpi/LinearNormaliser.java | 32 +++++++------- .../cml/abm/kpi/TestLinearNormaliser.java | 42 +++++++++---------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java b/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java index 8a9dcf9..50f3aa5 100644 --- a/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java +++ b/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java @@ -1,11 +1,11 @@ package com.arup.cml.abm.kpi; public class LinearNormaliser implements Normaliser { - private double leftScaleBound; - private double rightScaleBound; + private double leftIntervalBound; + private double rightIntervalBound; private double leftValueBound; private double rightValueBound; - private boolean isReversedLinearScale = false; + private boolean isReversed = false; public LinearNormaliser(double leftIntervalBound, double rightIntervalBound, double leftValueBound, double rightValueBound) { // leftValueBound is mapped to leftIntervalBound @@ -22,13 +22,13 @@ public LinearNormaliser(double leftIntervalBound, double rightIntervalBound, dou "Left and right bounds cannot both be the same value."); } if (leftValueBound > rightValueBound) { - this.isReversedLinearScale = true; + this.isReversed = true; leftValueBound = -leftValueBound; rightValueBound = -rightValueBound; } - this.leftScaleBound = leftIntervalBound; - this.rightScaleBound = rightIntervalBound; + this.leftIntervalBound = leftIntervalBound; + this.rightIntervalBound = rightIntervalBound; this.leftValueBound = leftValueBound; this.rightValueBound = rightValueBound; } @@ -37,16 +37,16 @@ public LinearNormaliser(double leftValueBound, double rightValueBound) { this(0, 10, leftValueBound, rightValueBound); } - private boolean isReversedLinearScale() { - return this.isReversedLinearScale; + private boolean isReversed() { + return this.isReversed; } - public double getLowerScaleBound() { - return leftScaleBound; + public double getLowerIntervalBound() { + return leftIntervalBound; } - public double getUpperScaleBound() { - return rightScaleBound; + public double getUpperIntervalBound() { + return rightIntervalBound; } private boolean isWithinBounds(double value) { @@ -55,16 +55,16 @@ private boolean isWithinBounds(double value) { @Override public double normalise(double value) { - if (this.isReversedLinearScale()) { + if (this.isReversed()) { value = -value; } if (this.isWithinBounds(value)) { - return ((value - leftValueBound) / (rightValueBound - leftValueBound)) * (rightScaleBound - leftScaleBound); + return ((value - leftValueBound) / (rightValueBound - leftValueBound)) * (rightIntervalBound - leftIntervalBound); } else { if (value > rightValueBound) { - return rightScaleBound; + return rightIntervalBound; } else { - return leftScaleBound; + return leftIntervalBound; } } } diff --git a/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java index 66047c9..d4f19b4 100644 --- a/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java @@ -11,73 +11,73 @@ public class TestLinearNormaliser { double lowerValueBound = 10.0; double upperValueBound = 30.0; - LinearNormaliser basicScaleFactor = new LinearNormaliser(lowerIntervalBound, upperIntervalBound, lowerValueBound, upperValueBound); - LinearNormaliser reverseScaleFactor = new LinearNormaliser(lowerIntervalBound, upperIntervalBound, upperValueBound, lowerValueBound); + LinearNormaliser basicLinearNormaliser = new LinearNormaliser(lowerIntervalBound, upperIntervalBound, lowerValueBound, upperValueBound); + LinearNormaliser reverseLinearNormaliser = new LinearNormaliser(lowerIntervalBound, upperIntervalBound, upperValueBound, lowerValueBound); @Test public void mapsTheMidpoint() { - assertThat(basicScaleFactor.normalise(20)).isEqualTo(5.0); + assertThat(basicLinearNormaliser.normalise(20)).isEqualTo(5.0); } @Test public void mapsCloserToLeftBound() { - assertThat(basicScaleFactor.normalise(11.0)).isEqualTo(0.5); + assertThat(basicLinearNormaliser.normalise(11.0)).isEqualTo(0.5); } @Test public void mapsCloserToRightBound() { - assertThat(basicScaleFactor.normalise(29.0)).isEqualTo(9.5); + assertThat(basicLinearNormaliser.normalise(29.0)).isEqualTo(9.5); } @Test - public void outsideLeftBoundMapsToLowerScaleBound() { - assertThat(basicScaleFactor.normalise(5.0)).isEqualTo(lowerIntervalBound); + public void outsideLeftBoundMapsToLowerIntervalBound() { + assertThat(basicLinearNormaliser.normalise(5.0)).isEqualTo(lowerIntervalBound); } @Test - public void outsideRightBoundMapsToUpperScaleBound() { - assertThat(basicScaleFactor.normalise(35.0)).isEqualTo(upperIntervalBound); + public void outsideRightBoundMapsToUpperIntervalBound() { + assertThat(basicLinearNormaliser.normalise(35.0)).isEqualTo(upperIntervalBound); } @Test public void reversedFactorStillMapsTheMidpoint() { - assertThat(reverseScaleFactor.normalise(20)).isEqualTo(5.0); + assertThat(reverseLinearNormaliser.normalise(20)).isEqualTo(5.0); } @Test public void reversedFactorMapsCloserToRightBound() { - assertThat(reverseScaleFactor.normalise(11.0)).isEqualTo(9.5); + assertThat(reverseLinearNormaliser.normalise(11.0)).isEqualTo(9.5); } @Test public void reversedFactorMapsCloserToLeftBound() { - assertThat(reverseScaleFactor.normalise(29.0)).isEqualTo(0.5); + assertThat(reverseLinearNormaliser.normalise(29.0)).isEqualTo(0.5); } @Test - public void reversedFactorOutsideLeftBoundMapsToLowerScaleBound() { - assertThat(reverseScaleFactor.normalise(35.0)).isEqualTo(lowerIntervalBound); + public void reversedFactorOutsideLeftBoundMapsToLowerIntervalBound() { + assertThat(reverseLinearNormaliser.normalise(35.0)).isEqualTo(lowerIntervalBound); } @Test - public void reversedFactorOutsideRightBoundMapsToUpperScaleBound() { - assertThat(reverseScaleFactor.normalise(5.0)).isEqualTo(upperIntervalBound); + public void reversedFactorOutsideRightBoundMapsToUpperIntervalBound() { + assertThat(reverseLinearNormaliser.normalise(5.0)).isEqualTo(upperIntervalBound); } @Test - public void defaultsToZeroToTenScaleBounds() { + public void defaultsToZeroToTenIntervalBounds() { LinearNormaliser defaultedLinearNormaliser = new LinearNormaliser(45, 50); - assertThat(defaultedLinearNormaliser.getLowerScaleBound()).isEqualTo(0.0); - assertThat(defaultedLinearNormaliser.getUpperScaleBound()).isEqualTo(10.0); + assertThat(defaultedLinearNormaliser.getLowerIntervalBound()).isEqualTo(0.0); + assertThat(defaultedLinearNormaliser.getUpperIntervalBound()).isEqualTo(10.0); } @Test(expected = RuntimeException.class) - public void throwsExceptionWhenRightScaleBoundIsLargerThanLeftScaleBound() { + public void throwsExceptionWhenRightIntervalBoundIsLargerThanLeftIntervalBound() { new LinearNormaliser(10, 0, 45, 50); } @Test(expected = RuntimeException.class) - public void throwsExceptionWhenValueBoundsEqualButScaleBoundsAreNot() { + public void throwsExceptionWhenValueBoundsEqualButIntervalBoundsAreNot() { new LinearNormaliser(0, 1, 45, 45); } From 81a712b1d7d088e0c1ac920917ea8e4c1fb05716 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Tue, 26 Mar 2024 13:50:38 +0000 Subject: [PATCH 28/39] use camel case --- .../kpi/tablesaw/TablesawKpiCalculator.java | 8 +-- ...wAccessToMobilityWithLinearNormaliser.java | 64 +++++++++---------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java index af084d9..1501f7f 100644 --- a/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java +++ b/src/main/java/com/arup/cml/abm/kpi/tablesaw/TablesawKpiCalculator.java @@ -641,9 +641,9 @@ public void accept(String aString) { LOGGER.info("Finished calculating access to mobility KPIs"); return Map.of( - "bus_kpi", busKpiOutput, - "rail_kpi", railKpiOutput, - "used_pt_kpi", usedPtKpiOutput + "busKpi", busKpiOutput, + "railKpi", railKpiOutput, + "usedPtKpi", usedPtKpiOutput ); } @@ -1465,7 +1465,7 @@ private void writeContentToFile(String path, String content, CompressionType com } private void writeContentToFile(String path, Map content, CompressionType compressionType) { - // keys form columns + // keys of content map form columns in the output csv file StringBuilder csvColumns = new StringBuilder(); StringBuilder csvValues = new StringBuilder(); diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java index 5229660..cc2f460 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java @@ -40,16 +40,16 @@ public void singleAgentHasAccessOnlyToBus() { linearNormaliser ); - assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(100.0); - assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("busKpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("busKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 100%, " + "because there is only one agent and they have access to a bus stop."); - assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(0.0); - assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) + assertThat(outputKpi.get("railKpi").get("actual")).isEqualTo(0.0); + assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 0%, " + "because there is only one agent and they don't have access to rail."); - assertThat(outputKpi.get("used_pt_kpi").get("actual")).isEqualTo(0.0); - assertThat(outputKpi.get("used_pt_kpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) + assertThat(outputKpi.get("usedPtKpi").get("actual")).isEqualTo(0.0); + assertThat(outputKpi.get("usedPtKpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Used PT KPI output is expected to be 0%, " + "because there is only one agent and they didn't use PT"); } @@ -73,8 +73,8 @@ public void nonHomeTripsAreFilteredOutAndDontContributeToKpiOutput() { linearNormaliser ); - assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(100.0); - assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("busKpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("busKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 100%, " + "because there is only one agent and they have access to a bus stop."); } @@ -97,16 +97,16 @@ public void singleAgentHasAccessToRail() { linearNormaliser ); - assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(0.0); - assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) + assertThat(outputKpi.get("busKpi").get("actual")).isEqualTo(0.0); + assertThat(outputKpi.get("busKpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 0%, " + "because there is only one agent and they don't have access to a bus stop."); - assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(100.0); - assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("railKpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 100%, " + "because there is only one agent and they have access to rail."); - assertThat(outputKpi.get("used_pt_kpi").get("actual")).isEqualTo(0.0); - assertThat(outputKpi.get("used_pt_kpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) + assertThat(outputKpi.get("usedPtKpi").get("actual")).isEqualTo(0.0); + assertThat(outputKpi.get("usedPtKpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Used PT KPI output is expected to be 0%, " + "because there is only one agent and they didn't use PT"); } @@ -130,12 +130,12 @@ public void singleAgentHasAccessToRailAndUsesPT() { linearNormaliser ); - assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(100.0); - assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("railKpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 100%, " + "because there is only one agent and they have access to rail."); - assertThat(outputKpi.get("used_pt_kpi").get("actual")).isEqualTo(100.0); - assertThat(outputKpi.get("used_pt_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("usedPtKpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("usedPtKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Used PT KPI output is expected to be 100%, " + "because there is only one agent and they used PT"); } @@ -161,8 +161,8 @@ public void twoAgentsWithBusAccess() { linearNormaliser ); - assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(100.0); - assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("busKpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("busKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 100%, " + "because both agents have access to bus."); } @@ -188,8 +188,8 @@ public void twoAgentsWithDifferentBusAccess() { linearNormaliser ); - assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(50.0); - assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) + assertThat(outputKpi.get("busKpi").get("actual")).isEqualTo(50.0); + assertThat(outputKpi.get("busKpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 50%, " + "because one agent has access to bus and the other doesn't."); } @@ -215,8 +215,8 @@ public void twoAgentsWithRailAccess() { linearNormaliser ); - assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(100.0); - assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("railKpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 100%, " + "because both agents have access to rail."); } @@ -242,8 +242,8 @@ public void twoAgentsWithDifferentRailAccess() { linearNormaliser ); - assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(50.0); - assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) + assertThat(outputKpi.get("railKpi").get("actual")).isEqualTo(50.0); + assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 50%, " + "because one agent has access to rail and the other doesn't."); } @@ -270,16 +270,16 @@ public void twoAgentsWithDifferentPTAccessButBothUsePT() { linearNormaliser ); - assertThat(outputKpi.get("bus_kpi").get("actual")).isEqualTo(50.0); - assertThat(outputKpi.get("bus_kpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) + assertThat(outputKpi.get("busKpi").get("actual")).isEqualTo(50.0); + assertThat(outputKpi.get("busKpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 50%, " + "because one agent has access to bus and the other doesn't."); - assertThat(outputKpi.get("rail_kpi").get("actual")).isEqualTo(50.0); - assertThat(outputKpi.get("rail_kpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) + assertThat(outputKpi.get("railKpi").get("actual")).isEqualTo(50.0); + assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 50%, " + "because one agent has access to rail and the other doesn't."); - assertThat(outputKpi.get("used_pt_kpi").get("actual")).isEqualTo(100.0); - assertThat(outputKpi.get("used_pt_kpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) + assertThat(outputKpi.get("usedPtKpi").get("actual")).isEqualTo(100.0); + assertThat(outputKpi.get("usedPtKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Used KPI output is expected to be 100%, " + "because both agents use PT."); } From ab5646d21f409187e24f7611d815724be27aa134 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Thu, 28 Mar 2024 17:47:22 +0000 Subject: [PATCH 29/39] apply PR comment changes: docs tidy --- CHANGELOG.md | 4 +-- .../arup/cml/abm/kpi/LinearNormaliser.java | 36 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3e4b58..feaf9f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Scaling/Normalisation to values between `0` and `10` for metrics ([#69](https://github.com/arup-group/gelato/issues/69)): +- Normalisation to values between `0` and `10` for metrics ([#69](https://github.com/arup-group/gelato/issues/69)): `Affordability`, `PT Wait Time`, `Occupancy`, `GHG`, `Travel Time`, `Access to Mobility services`, `Congestion` ### Fixed -- GHG Emissions KPI results where under-reported, by exaggerating number of people in per capita calculations ([#73](https://github.com/arup-group/gelato/issues/73)) +- GHG Emissions KPI under-reporting bug ([#73](https://github.com/arup-group/gelato/issues/73)) ### Changed diff --git a/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java b/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java index 50f3aa5..c54bb68 100644 --- a/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java +++ b/src/main/java/com/arup/cml/abm/kpi/LinearNormaliser.java @@ -7,18 +7,27 @@ public class LinearNormaliser implements Normaliser { private double rightValueBound; private boolean isReversed = false; + /** + * Provides 1D affine transformation: M * value + C + * mapping values: [leftValueBound, rightValueBound] -> [leftIntervalBound, rightIntervalBound] + * leftValueBound can be larger than rightValueBound, a given value will be scaled reversely. + * Values outside the boundaries, are mapped to their closest bound. + * E.g. + * If [0, 1] -> [0, 10] then 0.4 -> 4, -5 -> 0, 2 -> 10 + * If [1, 0] -> [0, 10] then 0.4 -> 6, -5 -> 10, 2 -> 0 + * @param leftIntervalBound left bound of the destination interval, + * leftIntervalBound < rightIntervalBound, necessarily. + * @param rightIntervalBound right bound of the destination interval, + * rightIntervalBound > leftIntervalBound, necessarily. + * @param leftValueBound left bound of the origin interval, it is mapped to leftIntervalBound. + * @param rightValueBound right bound of the origin interval, it is mapped to rightIntervalBound. + */ public LinearNormaliser(double leftIntervalBound, double rightIntervalBound, double leftValueBound, double rightValueBound) { - // leftValueBound is mapped to leftIntervalBound - // rightValueBound is mapped to rightIntervalBound - // any value between value bounds is mapped, linearly between values [leftIntervalBound, rightIntervalBound] - // leftIntervalBound < rightIntervalBound necessarily - // leftValueBound can be larger than rightValueBound a value will be scaled reversely. - if (leftIntervalBound > rightIntervalBound) { - throw new RuntimeException("leftIntervalBound cannot be larger than rightValueBound"); + throw new IllegalArgumentException("leftIntervalBound cannot be larger than rightValueBound"); } if (leftValueBound == rightValueBound && leftIntervalBound != rightIntervalBound) { - throw new RuntimeException("The bounds given for linear interval are invalid. " + + throw new IllegalArgumentException("The bounds given for linear interval are invalid. " + "Left and right bounds cannot both be the same value."); } if (leftValueBound > rightValueBound) { @@ -33,6 +42,17 @@ public LinearNormaliser(double leftIntervalBound, double rightIntervalBound, dou this.rightValueBound = rightValueBound; } + /** + * Provides 1D affine transformation: M * value + C + * mapping values: [leftValueBound, rightValueBound] -> [0, 10] + * leftValueBound can be larger than rightValueBound, a given value will be scaled reversely. + * Values outside the boundaries, are mapped to their closest bound. + * E.g. + * If [0, 1] -> [0, 10] then 0.4 -> 4, -5 -> 0, 2 -> 10 + * If [1, 0] -> [0, 10] then 0.4 -> 6, -5 -> 10, 2 -> 0 + * @param leftValueBound left bound of the origin interval, it is mapped to 0. + * @param rightValueBound right bound of the origin interval, it is mapped to 10. + */ public LinearNormaliser(double leftValueBound, double rightValueBound) { this(0, 10, leftValueBound, rightValueBound); } From 616ac28c76e8dfa99707e8bf282424c65d621a36 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Thu, 28 Mar 2024 17:47:43 +0000 Subject: [PATCH 30/39] apply PR comment changes: fewer magic numbers --- .../abm/kpi/matsim/run/MatsimKpiGenerator.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index 4e5312f..cc95cd7 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -93,17 +93,20 @@ public void run() { CompressionType.gzip ); - kpiCalculator.writeAffordabilityKpi(outputDir, new LinearNormaliser(0, 10, 1.25, 1)); - kpiCalculator.writePtWaitTimeKpi(outputDir, new LinearNormaliser(0, 10, 15 * 60.0, 5 * 60.0)); + double leftIntervalBound = 0.0; + double rightIntervalBound = 10.0; + double secondsInAMinute = 60.0; + kpiCalculator.writeAffordabilityKpi(outputDir, new LinearNormaliser(leftIntervalBound, rightIntervalBound, 1.25, 1)); + kpiCalculator.writePtWaitTimeKpi(outputDir, new LinearNormaliser(leftIntervalBound, rightIntervalBound, 15 * secondsInAMinute, 5 * secondsInAMinute)); kpiCalculator.writeModalSplitKpi(outputDir); - kpiCalculator.writeOccupancyRateKpi(outputDir, new LinearNormaliser(0, 10, 0.2, 0.6)); + kpiCalculator.writeOccupancyRateKpi(outputDir, new LinearNormaliser(leftIntervalBound, rightIntervalBound, 0.2, 0.6)); kpiCalculator.writeVehicleKMKpi(outputDir); kpiCalculator.writePassengerKMKpi(outputDir); kpiCalculator.writeSpeedKpi(outputDir); - kpiCalculator.writeGHGKpi(outputDir, new LinearNormaliser(0, 10, 8.87, 0.0)); - kpiCalculator.writeAccessToMobilityServicesKpi(outputDir, new LinearNormaliser(0, 10, 0.0, 100.0)); - kpiCalculator.writeCongestionKpi(outputDir, new LinearNormaliser(0, 10, 3.0, 1.25)); - kpiCalculator.writeTravelTimeKpi(outputDir, new LinearNormaliser(0, 10, 90.0, 10.0)); + kpiCalculator.writeGHGKpi(outputDir, new LinearNormaliser(leftIntervalBound, rightIntervalBound, 8.87, 0.0)); + kpiCalculator.writeAccessToMobilityServicesKpi(outputDir, new LinearNormaliser(leftIntervalBound, rightIntervalBound, 0.0, 100.0)); + kpiCalculator.writeCongestionKpi(outputDir, new LinearNormaliser(leftIntervalBound, rightIntervalBound, 3.0, 1.25)); + kpiCalculator.writeTravelTimeKpi(outputDir, new LinearNormaliser(leftIntervalBound, rightIntervalBound, 90.0, 10.0)); kpiCalculator.writeMobilitySpaceUsageKpi(outputDir); MemoryObserver.stop(); } From bed3a50e3ff9bc8fba3325973896bd244c6c77bf Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Thu, 28 Mar 2024 18:11:23 +0000 Subject: [PATCH 31/39] apply PR comment changes: refactor builders --- .../cml/abm/kpi/TestLinearNormaliser.java | 2 +- .../kpi/builders/KpiCalculatorBuilder.java | 6 +- .../arup/cml/abm/kpi/builders/LegBuilder.java | 74 ++++++++ .../cml/abm/kpi/builders/LegsBuilder.java | 122 ------------- .../abm/kpi/builders/LegsTableBuilder.java | 96 ++++++++++ .../cml/abm/kpi/builders/PersonsBuilder.java | 8 +- .../kpi/builders/ScoringConfigBuilder.java | 55 ++---- .../cml/abm/kpi/builders/TripBuilder.java | 78 ++++++++ .../cml/abm/kpi/builders/TripsBuilder.java | 166 ------------------ .../abm/kpi/builders/TripsTableBuilder.java | 144 +++++++++++++++ .../cml/abm/kpi/builders/VehiclesBuilder.java | 1 - ...wAccessToMobilityWithLinearNormaliser.java | 143 +++++++++------ ...wAffordabilityKpiWithLinearNormaliser.java | 40 +++-- ...esawPtWaitTimeKpiWithLinearNormaliser.java | 25 ++- ...esawTravelTimeKpiWithLinearNormaliser.java | 34 +++- 15 files changed, 571 insertions(+), 423 deletions(-) create mode 100644 src/test/java/com/arup/cml/abm/kpi/builders/LegBuilder.java delete mode 100644 src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java create mode 100644 src/test/java/com/arup/cml/abm/kpi/builders/LegsTableBuilder.java create mode 100644 src/test/java/com/arup/cml/abm/kpi/builders/TripBuilder.java delete mode 100644 src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java create mode 100644 src/test/java/com/arup/cml/abm/kpi/builders/TripsTableBuilder.java diff --git a/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java index d4f19b4..4fd1ad2 100644 --- a/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/TestLinearNormaliser.java @@ -40,7 +40,7 @@ public void outsideRightBoundMapsToUpperIntervalBound() { } @Test - public void reversedFactorStillMapsTheMidpoint() { + public void reversedFactorMapsTheMidpoint() { assertThat(reverseLinearNormaliser.normalise(20)).isEqualTo(5.0); } diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java index 271b212..bf65cd6 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/KpiCalculatorBuilder.java @@ -25,12 +25,12 @@ public class KpiCalculatorBuilder { MoneyLog moneyLog = new MoneyLog(); String persons; ActivityFacilities facilities = new FacilitiesBuilder().build(); - ScoringConfigGroup scoring = new ScoringConfigBuilder().withDefaultScoringParams().build(); + ScoringConfigGroup scoring = new ScoringConfigBuilder().build(); public KpiCalculatorBuilder(TemporaryFolder tmpDir) { this.tmpDir = tmpDir; - this.legs = new LegsBuilder(tmpDir).build(); - this.trips = new TripsBuilder(tmpDir).build(); + this.legs = new LegsTableBuilder(tmpDir).build(); + this.trips = new TripsTableBuilder(tmpDir).build(); this.persons = new PersonsBuilder(tmpDir).build(); } diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/LegBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/LegBuilder.java new file mode 100644 index 0000000..45c01f9 --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/builders/LegBuilder.java @@ -0,0 +1,74 @@ +package com.arup.cml.abm.kpi.builders; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.core.population.PopulationUtils; +import org.matsim.core.population.routes.RouteUtils; +import org.matsim.core.scoring.EventsToLegs; +import org.matsim.pt.routes.DefaultTransitPassengerRoute; +import org.matsim.pt.routes.TransitPassengerRoute; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +public class LegBuilder { + Leg leg = PopulationUtils.createLeg("car"); + + public LegBuilder() { + leg.setRoute(RouteUtils.createGenericRouteImpl(Id.create("1-1", Link.class), Id.create("2-2", Link.class))); + leg.getAttributes().putAttribute(EventsToLegs.VEHICLE_ID_ATTRIBUTE_NAME, "specialVehicle"); + leg.setDepartureTime(0.0); + leg.setTravelTime(0.0); + } + + public LegBuilder withDistance(Integer distance) { + return this; + } + + public LegBuilder withMode(String mode) { + return this; + } + + public LegBuilder withDepTime(String depTime) { + leg.setDepartureTime(getTimeInSeconds(depTime)); + return this; + } + + private double getTimeInSeconds(String time) { + String[] timeComponents = time.split(":"); + double hours = Double.parseDouble(timeComponents[0]); + double minutes = Double.parseDouble(timeComponents[1]); + double seconds = Double.parseDouble(timeComponents[2]); + return (hours * 60.0 * 60.0) + (minutes * 60.0) + seconds; + } + + public LegBuilder withTravTime(String travTime) { + leg.setTravelTime(getTimeInSeconds(travTime)); + return this; + } + + public LegBuilder withWaitTime(String waitTime) { + // this is not the official way to ser wait time in a matsim leg but the official way is too complicated for + // our purpose + leg.getAttributes().putAttribute("waitTime", getTimeInSeconds(waitTime)); + return this; + } + + public LegBuilder ofSomePtType() { + TransitPassengerRoute route = new DefaultTransitPassengerRoute( + Id.create("accessLinkId", Link.class), + Id.create("egressLinkId", Link.class), + Id.create("accessTransitStopFacilityId", TransitStopFacility.class), + Id.create("egressTransitStopFacilityId", TransitStopFacility.class), + Id.create("transitLineId", TransitLine.class), + Id.create("transitRouteId", TransitRoute.class) + ); + leg.setRoute(route); + return this.withWaitTime("00:00:00"); + } + + public Leg build() { + return leg; + } +} diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java deleted file mode 100644 index 6370b3a..0000000 --- a/src/test/java/com/arup/cml/abm/kpi/builders/LegsBuilder.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.arup.cml.abm.kpi.builders; - -import org.junit.rules.TemporaryFolder; -import tech.tablesaw.api.DoubleColumn; -import tech.tablesaw.api.IntColumn; -import tech.tablesaw.api.StringColumn; -import tech.tablesaw.api.Table; -import tech.tablesaw.io.csv.CsvWriteOptions; - -import java.nio.file.Path; - -public class LegsBuilder { - TemporaryFolder tmpDir; - - String defaultPerson = "Bobby"; - String defaultTripId = "Bobby_0"; - String defaultDepTime = "07:38:40"; - String defaultTravTime = "00:09:12"; - String defaultWaitTime = "00:00:00"; - Integer defaultDistance = 5500; - String defaultMode = "car"; - String defaultStartLink = "1-3"; - double defaultStartX = -500.0; - double defaultStartY = -200.0; - String defaultEndLink = "4-5"; - double defaultEndX = 4600.0; - double defaultEndY = 800.0; - String defaultAccessStopId = ""; - String defaultEgressStopId = ""; - String defaultTransitLine = ""; - String defaultTransitRoute = ""; - String defaultVehicleId = "Bobby"; - - Table legs = Table.create("legs").addColumns( - StringColumn.create("person"), - StringColumn.create("trip_id"), - StringColumn.create("dep_time"), - StringColumn.create("trav_time"), - StringColumn.create("wait_time"), - IntColumn.create("distance"), - StringColumn.create("mode"), - StringColumn.create("start_link"), - DoubleColumn.create("start_x"), - DoubleColumn.create("start_y"), - StringColumn.create("end_link"), - DoubleColumn.create("end_x"), - DoubleColumn.create("end_y"), - StringColumn.create("access_stop_id"), - StringColumn.create("egress_stop_id"), - StringColumn.create("transit_line"), - StringColumn.create("transit_route"), - StringColumn.create("vehicle_id") - ); - - public LegsBuilder(TemporaryFolder tmpDir) { - this.tmpDir = tmpDir; - } - - public LegsBuilder withLeg( - String person, String trip_id, String dep_time, String trav_time, String wait_time, Integer distance, - String mode, String start_link, double start_x, double start_y, String end_link, double end_x, double end_y, - String access_stop_id, String egress_stop_id, String transit_line, String transit_route, String vehicle_id - ) { - legs.stringColumn("person").append(person); - legs.stringColumn("trip_id").append(trip_id); - legs.stringColumn("dep_time").append(dep_time); - legs.stringColumn("trav_time").append(trav_time); - legs.stringColumn("wait_time").append(wait_time); - legs.intColumn("distance").append(distance); - legs.stringColumn("mode").append(mode); - legs.stringColumn("start_link").append(start_link); - legs.doubleColumn("start_x").append(start_x); - legs.doubleColumn("start_y").append(start_y); - legs.stringColumn("end_link").append(end_link); - legs.doubleColumn("end_x").append(end_x); - legs.doubleColumn("end_y").append(end_y); - legs.stringColumn("access_stop_id").append(access_stop_id); - legs.stringColumn("egress_stop_id").append(egress_stop_id); - legs.stringColumn("transit_line").append(transit_line); - legs.stringColumn("transit_route").append(transit_route); - legs.stringColumn("vehicle_id").append(vehicle_id); - return this; - } - - public LegsBuilder withDefaultLeg() { - return this.withLeg( - defaultPerson, defaultTripId, defaultDepTime, defaultTravTime, defaultWaitTime, defaultDistance, defaultMode, - defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, defaultAccessStopId, - defaultEgressStopId, defaultTransitLine, defaultTransitRoute, defaultVehicleId - ); - } - - public LegsBuilder withLegWithDistanceAndMode(String person, String tripId, Integer distance, String mode) { - return this.withLeg( - person, tripId, defaultDepTime, defaultTravTime, defaultWaitTime, distance, mode, - defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, defaultAccessStopId, - defaultEgressStopId, defaultTransitLine, defaultTransitRoute, defaultVehicleId - ); - } - - public LegsBuilder withDefaultPtLegWithTiming( - String person, String tripId, String depTime, String travTime, String waitTime) { - return this.withLeg( - person, tripId, depTime, travTime, waitTime, defaultDistance, "bus", - defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, - "Stop A", "Stop B", "SomeTransitLine", - "SomeTransitRoute", "SomeBus" - ); - } - - public String build() { - if (legs.isEmpty()) { - // empty table gets into trouble reading all the columns, if the table is empty, it is assumed it's not - // being used, so filling it with dud vales, just for the shape is ok - this.withDefaultLeg(); - } - String legsPath = String.valueOf(Path.of(tmpDir.getRoot().getAbsolutePath(), "output_legs.csv")); - CsvWriteOptions options = CsvWriteOptions.builder(legsPath).separator(';').build(); - legs.write().usingOptions(options); - return legsPath; - } -} diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/LegsTableBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/LegsTableBuilder.java new file mode 100644 index 0000000..6a2989a --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/builders/LegsTableBuilder.java @@ -0,0 +1,96 @@ +package com.arup.cml.abm.kpi.builders; + +import org.junit.rules.TemporaryFolder; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.core.scoring.EventsToLegs; +import org.matsim.pt.routes.TransitPassengerRoute; +import tech.tablesaw.api.DoubleColumn; +import tech.tablesaw.api.IntColumn; +import tech.tablesaw.api.StringColumn; +import tech.tablesaw.api.Table; +import tech.tablesaw.io.csv.CsvWriteOptions; + +import java.nio.file.Path; + +public class LegsTableBuilder { + TemporaryFolder tmpDir; + Table legs; + + public LegsTableBuilder(TemporaryFolder tmpDir) { + this.tmpDir = tmpDir; + init().withLeg("Bobby", "1", new LegBuilder().build()); + } + + private LegsTableBuilder init() { + legs = Table.create("legs").addColumns( + StringColumn.create("person"), + StringColumn.create("trip_id"), + StringColumn.create("dep_time"), + StringColumn.create("trav_time"), + StringColumn.create("wait_time"), + IntColumn.create("distance"), + StringColumn.create("mode"), + StringColumn.create("start_link"), + DoubleColumn.create("start_x"), + DoubleColumn.create("start_y"), + StringColumn.create("end_link"), + DoubleColumn.create("end_x"), + DoubleColumn.create("end_y"), + StringColumn.create("access_stop_id"), + StringColumn.create("egress_stop_id"), + StringColumn.create("transit_line"), + StringColumn.create("transit_route"), + StringColumn.create("vehicle_id") + ); + return this; + } + + public LegsTableBuilder reset() { + return this.init(); + } + + public LegsTableBuilder withLeg(String person, String trip_id, Leg leg) { + legs.stringColumn("person").append(person); + legs.stringColumn("trip_id").append(trip_id); + legs.stringColumn("dep_time").append(secondsToString(leg.getDepartureTime().seconds())); + legs.stringColumn("trav_time").append(secondsToString(leg.getTravelTime().seconds())); + legs.stringColumn("wait_time").append(secondsToString((double) leg.getAttributes().getAsMap().getOrDefault("waitTime", 0.0))); + legs.intColumn("distance").append((int) Math.round(leg.getRoute().getDistance())); + legs.stringColumn("mode").append(leg.getMode()); + legs.stringColumn("start_link").append(leg.getRoute().getStartLinkId().toString()); + legs.doubleColumn("start_x").append(0.0); + legs.doubleColumn("start_y").append(0.0); + legs.stringColumn("end_link").append(leg.getRoute().getEndLinkId().toString()); + legs.doubleColumn("end_x").append(0.0); + legs.doubleColumn("end_y").append(0.0); + if (leg.getRoute() instanceof TransitPassengerRoute) { + TransitPassengerRoute route = (TransitPassengerRoute) leg.getRoute(); + legs.stringColumn("access_stop_id").append(route.getAccessStopId().toString()); + legs.stringColumn("egress_stop_id").append(route.getEgressStopId().toString()); + legs.stringColumn("transit_line").append(route.getLineId().toString()); + legs.stringColumn("transit_route").append(route.getRouteId().toString()); + } else { + legs.stringColumn("access_stop_id").appendMissing(); + legs.stringColumn("egress_stop_id").appendMissing(); + legs.stringColumn("transit_line").appendMissing(); + legs.stringColumn("transit_route").appendMissing(); + } + legs.stringColumn("vehicle_id").append( + leg.getAttributes().getAttribute(EventsToLegs.VEHICLE_ID_ATTRIBUTE_NAME).toString()); + return this; + } + + private String secondsToString(double seconds) { + int hours = (int) (seconds / 3600); + int minutes = (int) ((seconds % 3600) / 60); + int secondsRemainder = (int) seconds % 60; + return String.format("%02d:%02d:%02d", hours, minutes, secondsRemainder); + } + + public String build() { + String legsPath = String.valueOf(Path.of(tmpDir.getRoot().getAbsolutePath(), "output_legs.csv")); + CsvWriteOptions options = CsvWriteOptions.builder(legsPath).separator(';').build(); + legs.write().usingOptions(options); + return legsPath; + } +} diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java index 971cd5c..a6608b1 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/PersonsBuilder.java @@ -25,18 +25,14 @@ public PersonsBuilder(TemporaryFolder tmpDir) { this.tmpDir = tmpDir; } - public PersonsBuilder withPerson( - String person, double income, String subpopulation - ) { + public PersonsBuilder withPerson(String person, double income, String subpopulation) { persons.stringColumn("person").append(person); persons.doubleColumn("income").append(income); persons.stringColumn("subpopulation").append(subpopulation); return this; } - public PersonsBuilder withPerson( - String person - ) { + public PersonsBuilder withPerson(String person) { return this.withPerson(person, defaultIncome, defaultSubpopulation); } diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java index adc7964..d51a7b3 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/ScoringConfigBuilder.java @@ -4,54 +4,35 @@ public class ScoringConfigBuilder { ScoringConfigGroup scoring; - String defaultSubpopulation = "default"; - double defaultDailyMonetaryConstant = 0.0; - double defaultMonetaryDistanceRate = 0.0; - double defaultConstant = 0.0; - double defaultMarginalUtilityOfDistance = 0.0; - double defaultDailyUtilityConstant = 0.0; - double defaultMarginalUtilityOfTraveling = 0.0; public ScoringConfigBuilder() { scoring = new ScoringConfigGroup(); + this.withMode("car"); + this.withMode("drt"); + this.withMode("bus"); + this.withMode("rail"); } - public ScoringConfigBuilder withScoringParams( - String subpopulation, String mode, double dailyMonetaryConstant, double monetaryDistanceRate, - double constant, double marginalUtilityOfDistance, double dailyUtilityConstant, - double marginalUtilityOfTraveling) { - ScoringConfigGroup.ScoringParameterSet paramSet = scoring.getOrCreateScoringParameters(subpopulation); + public ScoringConfigBuilder withMode(String mode) { + ScoringConfigGroup.ScoringParameterSet paramSet = scoring.getOrCreateScoringParameters("default"); ScoringConfigGroup.ModeParams modeParams = new ScoringConfigGroup.ModeParams(mode); - modeParams.setDailyMonetaryConstant(dailyMonetaryConstant); - modeParams.setMonetaryDistanceRate(monetaryDistanceRate); - modeParams.setConstant(constant); - modeParams.setMarginalUtilityOfDistance(marginalUtilityOfDistance); - modeParams.setDailyUtilityConstant(dailyUtilityConstant); - modeParams.setMarginalUtilityOfTraveling(marginalUtilityOfTraveling); + modeParams.setDailyMonetaryConstant(0.0); + modeParams.setMonetaryDistanceRate(0.0); + modeParams.setConstant(0.0); + modeParams.setMarginalUtilityOfDistance(0.0); + modeParams.setDailyUtilityConstant(0.0); + modeParams.setMarginalUtilityOfTraveling(0.0); paramSet.addModeParams(modeParams); return this; } - public ScoringConfigBuilder withMonetaryCostsForSubpopulationAndMode( + public ScoringConfigBuilder withMonetaryCosts( String subpopulation, String mode, double dailyMonetaryConstant, double monetaryDistanceRate) { - return this.withScoringParams(subpopulation, mode, dailyMonetaryConstant, monetaryDistanceRate, - defaultConstant, defaultMarginalUtilityOfDistance, defaultDailyUtilityConstant, - defaultMarginalUtilityOfTraveling); - } - - public ScoringConfigBuilder withDefaultScoringParams() { - this.withScoringParams(defaultSubpopulation, "car", defaultDailyMonetaryConstant, - defaultMonetaryDistanceRate, defaultConstant, defaultMarginalUtilityOfDistance, - defaultDailyUtilityConstant, defaultMarginalUtilityOfTraveling); - this.withScoringParams(defaultSubpopulation, "drt", defaultDailyMonetaryConstant, - defaultMonetaryDistanceRate, defaultConstant, defaultMarginalUtilityOfDistance, - defaultDailyUtilityConstant, defaultMarginalUtilityOfTraveling); - this.withScoringParams(defaultSubpopulation, "bus", defaultDailyMonetaryConstant, - defaultMonetaryDistanceRate, defaultConstant, defaultMarginalUtilityOfDistance, - defaultDailyUtilityConstant, defaultMarginalUtilityOfTraveling); - this.withScoringParams(defaultSubpopulation, "rail", defaultDailyMonetaryConstant, - defaultMonetaryDistanceRate, defaultConstant, defaultMarginalUtilityOfDistance, - defaultDailyUtilityConstant, defaultMarginalUtilityOfTraveling); + ScoringConfigGroup.ScoringParameterSet paramSet = scoring.getOrCreateScoringParameters(subpopulation); + ScoringConfigGroup.ModeParams modeParams = new ScoringConfigGroup.ModeParams(mode); + modeParams.setDailyMonetaryConstant(dailyMonetaryConstant); + modeParams.setMonetaryDistanceRate(monetaryDistanceRate); + paramSet.addModeParams(modeParams); return this; } diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/TripBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/TripBuilder.java new file mode 100644 index 0000000..b5d1f05 --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/builders/TripBuilder.java @@ -0,0 +1,78 @@ +package com.arup.cml.abm.kpi.builders; + +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Activity; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.PopulationFactory; +import org.matsim.core.population.PopulationUtils; +import org.matsim.facilities.ActivityFacility; + +import java.util.Arrays; +import java.util.List; + + +public class TripBuilder { + PopulationFactory popfactory = PopulationUtils.getFactory(); + Trip trip = new Trip(); + public class Trip { + // so it seems making a matsim Trip is kind of convoluted, so here is a simple version + public Activity startActivity; + public Activity endActivity; + public List legs; + public Trip(){ + this.setStartActivity("home", "0-0", new Coord(0, 0)); + this.setEndActivity("work", "1-1", new Coord(1, 1)); + this.legs = Arrays.asList(new LegBuilder().build()); + } + + public void setStartActivity(String type, String linkId, Coord coord) { + this.startActivity = popfactory.createActivityFromCoord(type, coord); + this.startActivity.setFacilityId(Id.create(String.format("%s_%s", type, linkId), ActivityFacility.class)); + this.startActivity.setLinkId(Id.create(linkId, Link.class)); + } + + public void setEndActivity(String type, String linkId, Coord coord) { + this.endActivity = popfactory.createActivityFromCoord(type, coord); + this.endActivity.setFacilityId(Id.create(String.format("%s_%s", type, linkId), ActivityFacility.class)); + this.endActivity.setLinkId(Id.create(linkId, Link.class)); + } + } + + public TripBuilder() {} + + public TripBuilder withStartLocation(double startX, double startY) { + trip.setStartActivity(trip.startActivity.getType(), trip.startActivity.getLinkId().toString(), new Coord(startX, startY)); + return this; + } + + public TripBuilder withStartActivityType(String startActivityType) { + trip.setStartActivity(startActivityType, trip.startActivity.getLinkId().toString(), trip.startActivity.getCoord()); + return this; + } + + public TripBuilder withLegs(List legs) { + trip.legs = legs; + return this; + } + + private void setFacilityIds() { + trip.startActivity.setFacilityId(Id.create( + String.format("%s_%f_%f", + trip.startActivity.getType(), + trip.startActivity.getCoord().getX(), + trip.startActivity.getCoord().getY()), + ActivityFacility.class)); + trip.endActivity.setFacilityId(Id.create( + String.format("%s_%f_%f", + trip.endActivity.getType(), + trip.endActivity.getCoord().getX(), + trip.endActivity.getCoord().getY()), + ActivityFacility.class)); + } + + public Trip build() { + return trip; + } +} diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java deleted file mode 100644 index 481203e..0000000 --- a/src/test/java/com/arup/cml/abm/kpi/builders/TripsBuilder.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.arup.cml.abm.kpi.builders; - -import org.junit.rules.TemporaryFolder; -import tech.tablesaw.api.DoubleColumn; -import tech.tablesaw.api.IntColumn; -import tech.tablesaw.api.StringColumn; -import tech.tablesaw.api.Table; -import tech.tablesaw.columns.Column; -import tech.tablesaw.io.csv.CsvWriteOptions; - -import java.nio.file.Path; -import java.util.Set; - -public class TripsBuilder { - TemporaryFolder tmpDir; - LegsBuilder legsBuilder; - String defaultPerson = "Bobby"; - String defaultTripNumber = "0"; - String defaultTripId = "Bobby_0"; - String defaultDepTime = "07:38:40"; - String defaultTravTime = "00:09:12"; - String defaultWaitTime = "00:00:00"; - Integer defaultTravelledDistance = 5500; - Integer defaultEuclideanDistance = 4000; - String defaultMainMode = "car"; - String defaultLongestDistanceMode = "car"; - String defaultModes = "car"; - String defaultStartActivityType = "home"; - String defaultStartFacilityId = "home_Bobby"; - String defaultEndActivityType = "work"; - String defaultEndFacilityId = "work_Bobby"; - String defaultStartLink = "1-3"; - double defaultStartX = -500.0; - double defaultStartY = -200.0; - String defaultEndLink = "4-5"; - double defaultEndX = 4600.0; - double defaultEndY = 800.0; - String defaultFirstPtBoardingStop = ""; - String defaultLastPtEgressStop = ""; - Table trips = Table.create("trips").addColumns( - StringColumn.create("person"), - StringColumn.create("trip_number"), - StringColumn.create("trip_id"), - StringColumn.create("dep_time"), - StringColumn.create("trav_time"), - StringColumn.create("wait_time"), - IntColumn.create("traveled_distance"), - IntColumn.create("euclidean_distance"), - StringColumn.create("main_mode"), - StringColumn.create("longest_distance_mode"), - StringColumn.create("modes"), - StringColumn.create("start_activity_type"), - StringColumn.create("start_facility_id"), - StringColumn.create("end_activity_type"), - StringColumn.create("end_facility_id"), - StringColumn.create("start_link"), - DoubleColumn.create("start_x"), - DoubleColumn.create("start_y"), - StringColumn.create("end_link"), - DoubleColumn.create("end_x"), - DoubleColumn.create("end_y"), - StringColumn.create("first_pt_boarding_stop"), - StringColumn.create("last_pt_egress_stop") - ); - - public TripsBuilder(TemporaryFolder tmpDir) { - this.tmpDir = tmpDir; - this.legsBuilder = new LegsBuilder(tmpDir); - } - - public TripsBuilder withTrip( - String person, String trip_number, String trip_id, String dep_time, String trav_time, String wait_time, - Integer traveled_distance, Integer euclidean_distance, String main_mode, String longest_distance_mode, - String modes, String start_activity_type, String start_facility_id, String end_activity_type, String end_facility_id, - String start_link, double start_x, double start_y, String end_link, double end_x, double end_y, - String first_pt_boarding_stop, String last_pt_egress_stop - ) { - trips.stringColumn("person").append(person); - trips.stringColumn("trip_number").append(trip_number); - trips.stringColumn("trip_id").append(trip_id); - trips.stringColumn("dep_time").append(dep_time); - trips.stringColumn("trav_time").append(trav_time); - trips.stringColumn("wait_time").append(wait_time); - trips.intColumn("traveled_distance").append(traveled_distance); - trips.intColumn("euclidean_distance").append(euclidean_distance); - trips.stringColumn("main_mode").append(main_mode); - trips.stringColumn("longest_distance_mode").append(longest_distance_mode); - trips.stringColumn("modes").append(modes); - trips.stringColumn("start_activity_type").append(start_activity_type); - trips.stringColumn("start_facility_id").append(start_facility_id); - trips.stringColumn("end_activity_type").append(end_activity_type); - trips.stringColumn("end_facility_id").append(end_facility_id); - trips.stringColumn("start_link").append(start_link); - trips.doubleColumn("start_x").append(start_x); - trips.doubleColumn("start_y").append(start_y); - trips.stringColumn("end_link").append(end_link); - trips.doubleColumn("end_x").append(end_x); - trips.doubleColumn("end_y").append(end_y); - trips.stringColumn("first_pt_boarding_stop").append(first_pt_boarding_stop); - trips.stringColumn("last_pt_egress_stop").append(last_pt_egress_stop); - - this.legsBuilder.withLeg( - person, trip_id, dep_time, trav_time, wait_time, traveled_distance, main_mode, - start_link, start_x, start_y, end_link, end_x, end_y, first_pt_boarding_stop, - last_pt_egress_stop, this.legsBuilder.defaultTransitLine, this.legsBuilder.defaultTransitRoute, - this.legsBuilder.defaultVehicleId - ); - return this; - } - - public TripsBuilder withTripWithStartLocationAndType( - String person, String trip_number, String startActivityType, double startX, double startY) { - return this.withPtTripWithStartLocationAndType( - person, trip_number, startActivityType, startX, startY, - defaultMainMode, defaultFirstPtBoardingStop, defaultLastPtEgressStop - ); - } - - public TripsBuilder withPtTripWithStartLocationAndType( - String person, String trip_number, String startActivityType, double startX, double startY, - String mode, String ptBoardingStop, String ptEgressStop) { - return this.withTrip( - person, trip_number, String.format("{}_{}", person, trip_number), defaultDepTime, defaultTravTime, defaultWaitTime, - defaultTravelledDistance, defaultEuclideanDistance, mode, mode, mode, - startActivityType, defaultStartFacilityId, defaultEndActivityType, defaultEndFacilityId, - defaultStartLink, startX, startY, defaultEndLink, defaultEndX, defaultEndY, - ptBoardingStop, ptEgressStop - ); - } - - public TripsBuilder withTripWithTravelTime(String person, String tripNumber, String travTime) { - return this.withTrip( - person, tripNumber, String.format("{}_{}", person, tripNumber), defaultDepTime, travTime, defaultWaitTime, - defaultTravelledDistance, defaultEuclideanDistance, defaultMainMode, defaultLongestDistanceMode, defaultModes, - defaultStartActivityType, defaultStartFacilityId, defaultEndActivityType, defaultEndFacilityId, - defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, - defaultFirstPtBoardingStop, defaultLastPtEgressStop - ); - } - - public TripsBuilder withDefaultTrip() { - return this.withTrip( - defaultPerson, defaultTripNumber, defaultTripId, defaultDepTime, defaultTravTime, defaultWaitTime, - defaultTravelledDistance, defaultEuclideanDistance, defaultMainMode, defaultLongestDistanceMode, defaultModes, - defaultStartActivityType, defaultStartFacilityId, defaultEndActivityType, defaultEndFacilityId, - defaultStartLink, defaultStartX, defaultStartY, defaultEndLink, defaultEndX, defaultEndY, - defaultFirstPtBoardingStop, defaultLastPtEgressStop - ); - } - - public LegsBuilder getLegsBuilder() { - return legsBuilder; - } - - public String build() { - if (trips.isEmpty()) { - // empty table gets into trouble reading all the columns, if the table is empty, it is assumed it's not - // being used, so filling it with dud vales, just for the shape is ok - this.withDefaultTrip(); - } - String tripsPath = String.valueOf(Path.of(tmpDir.getRoot().getAbsolutePath(), "output_trips.csv")); - CsvWriteOptions options = CsvWriteOptions.builder(tripsPath).separator(';').build(); - trips.write().usingOptions(options); - return tripsPath; - } -} diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/TripsTableBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/TripsTableBuilder.java new file mode 100644 index 0000000..83222f2 --- /dev/null +++ b/src/test/java/com/arup/cml/abm/kpi/builders/TripsTableBuilder.java @@ -0,0 +1,144 @@ +package com.arup.cml.abm.kpi.builders; + +import org.junit.rules.TemporaryFolder; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.pt.routes.TransitPassengerRoute; +import tech.tablesaw.api.DoubleColumn; +import tech.tablesaw.api.IntColumn; +import tech.tablesaw.api.StringColumn; +import tech.tablesaw.api.Table; +import tech.tablesaw.io.csv.CsvWriteOptions; + +import java.nio.file.Path; + +public class TripsTableBuilder { + TemporaryFolder tmpDir; + LegsTableBuilder legsTableBuilder; + Table trips; + + public TripsTableBuilder(TemporaryFolder tmpDir) { + this.tmpDir = tmpDir; + this.legsTableBuilder = new LegsTableBuilder(tmpDir); + init().withTrip("Bobby", "0", new TripBuilder().build()); + } + + private TripsTableBuilder init() { + trips = Table.create("trips").addColumns( + StringColumn.create("person"), + StringColumn.create("trip_number"), + StringColumn.create("trip_id"), + StringColumn.create("dep_time"), + StringColumn.create("trav_time"), + StringColumn.create("wait_time"), + IntColumn.create("traveled_distance"), + IntColumn.create("euclidean_distance"), + StringColumn.create("main_mode"), + StringColumn.create("longest_distance_mode"), + StringColumn.create("modes"), + StringColumn.create("start_activity_type"), + StringColumn.create("start_facility_id"), + StringColumn.create("end_activity_type"), + StringColumn.create("end_facility_id"), + StringColumn.create("start_link"), + DoubleColumn.create("start_x"), + DoubleColumn.create("start_y"), + StringColumn.create("end_link"), + DoubleColumn.create("end_x"), + DoubleColumn.create("end_y"), + StringColumn.create("first_pt_boarding_stop"), + StringColumn.create("last_pt_egress_stop") + ); + return this; + } + + public TripsTableBuilder reset() { + legsTableBuilder.reset(); + return this.init(); + } + + public TripsTableBuilder withTrip(String person, String trip_number, TripBuilder.Trip trip) { + String trip_id = String.format("%s_%s", person, trip_number); + String first_pt_boarding_stop = ""; + String last_pt_egress_stop = ""; + StringBuilder modes = new StringBuilder(); + String longest_distance_mode = ""; + int longest_distance = 0; + boolean isPtTrip = false; + double totalTravTime = 0.0; + double totalWaitTime = 0.0; + int totalDistance = 0; + for (Leg leg : trip.legs) { + // check for PT info + if (leg.getRoute() instanceof TransitPassengerRoute) { + isPtTrip = true; + TransitPassengerRoute route = (TransitPassengerRoute) leg.getRoute(); + if (first_pt_boarding_stop.isBlank()) { + first_pt_boarding_stop = route.getAccessStopId().toString(); + } + last_pt_egress_stop = route.getEgressStopId().toString(); + } + // get times + totalTravTime += leg.getTravelTime().seconds(); + totalWaitTime += (double) leg.getAttributes().getAsMap().getOrDefault("waitTime", 0.0); + // distance and modes + int dist = (int) Math.round(leg.getRoute().getDistance()); + totalDistance += dist; + if (longest_distance < dist){ + longest_distance_mode = leg.getMode(); + longest_distance = dist; + } + modes.append(leg.getMode()).append(","); + // update legs builder + this.legsTableBuilder.withLeg(person, trip_id, leg); + } + modes.deleteCharAt(modes.length() - 1); + + trips.stringColumn("person").append(person); + trips.stringColumn("trip_number").append(trip_number); + trips.stringColumn("trip_id").append(trip_id); + trips.stringColumn("dep_time").append(secondsToString(trip.legs.get(0).getDepartureTime().seconds())); + trips.stringColumn("trav_time").append(secondsToString(totalTravTime)); + trips.stringColumn("wait_time").append(secondsToString(totalWaitTime)); + trips.intColumn("traveled_distance").append(totalDistance); + trips.intColumn("euclidean_distance").append(totalDistance); + trips.stringColumn("main_mode").append(longest_distance_mode); + trips.stringColumn("longest_distance_mode").append(longest_distance_mode); + trips.stringColumn("modes").append(modes.toString()); + trips.stringColumn("start_activity_type").append(trip.startActivity.getType()); + trips.stringColumn("start_facility_id").append(trip.startActivity.getFacilityId().toString()); + trips.stringColumn("end_activity_type").append(trip.endActivity.getType()); + trips.stringColumn("end_facility_id").append(trip.endActivity.getFacilityId().toString()); + trips.stringColumn("start_link").append(trip.startActivity.getLinkId().toString()); + trips.doubleColumn("start_x").append(trip.startActivity.getCoord().getX()); + trips.doubleColumn("start_y").append(trip.startActivity.getCoord().getY()); + trips.stringColumn("end_link").append(trip.endActivity.getLinkId().toString()); + trips.doubleColumn("end_x").append(trip.endActivity.getCoord().getX()); + trips.doubleColumn("end_y").append(trip.endActivity.getCoord().getY()); + if (isPtTrip) { + trips.stringColumn("first_pt_boarding_stop").append(first_pt_boarding_stop); + trips.stringColumn("last_pt_egress_stop").append(last_pt_egress_stop); + } else { + trips.stringColumn("first_pt_boarding_stop").appendMissing(); + trips.stringColumn("last_pt_egress_stop").appendMissing(); + } + return this; + } + + private String secondsToString(double seconds) { + int hours = (int) (seconds / 3600); + int minutes = (int) ((seconds % 3600) / 60); + int secondsRemainder = (int) seconds % 60; + return String.format("%02d:%02d:%02d", hours, minutes, secondsRemainder); + } + + public LegsTableBuilder getLegsBuilder() { + return legsTableBuilder; + } + + public String build() { + String tripsPath = String.valueOf(Path.of(tmpDir.getRoot().getAbsolutePath(), "output_trips.csv")); + CsvWriteOptions options = CsvWriteOptions.builder(tripsPath).separator(';').build(); + trips.write().usingOptions(options); + return tripsPath; + } +} diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java index 8fedce8..2dfd849 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/VehiclesBuilder.java @@ -1,7 +1,6 @@ package com.arup.cml.abm.kpi.builders; import org.matsim.api.core.v01.Id; -import org.matsim.vehicles.Vehicle; import org.matsim.vehicles.VehicleType; import org.matsim.vehicles.VehicleUtils; import org.matsim.vehicles.Vehicles; diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java index cc2f460..87c1289 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java @@ -2,14 +2,13 @@ import com.arup.cml.abm.kpi.LinearNormaliser; import com.arup.cml.abm.kpi.Normaliser; -import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; -import com.arup.cml.abm.kpi.builders.TransitScheduleBuilder; -import com.arup.cml.abm.kpi.builders.TripsBuilder; +import com.arup.cml.abm.kpi.builders.*; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.nio.file.Path; +import java.util.Arrays; import java.util.Map; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -24,13 +23,15 @@ public class TestTablesawAccessToMobilityWithLinearNormaliser { @Test public void singleAgentHasAccessOnlyToBus() { - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withTripWithStartLocationAndType("Bobby", "1", "home", 0.0, 0.0) + .withTrips(tripsTableBuilder + .withTrip("Bobby", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(0.0, 0.0).build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .withTransitSchedule(new TransitScheduleBuilder() .withTransitStopWithMode("BusStop", 400.0, 0.0, "bus") .build()) @@ -56,14 +57,18 @@ public void singleAgentHasAccessOnlyToBus() { @Test public void nonHomeTripsAreFilteredOutAndDontContributeToKpiOutput() { - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withTripWithStartLocationAndType("Bobby", "1", "home", 0.0, 0.0) - .withTripWithStartLocationAndType("Bobby", "2", "work", 0.0, 0.0) + .withTrips(tripsTableBuilder.reset() + .withTrip("Bobby", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(0.0, 0.0).build()) + .withTrip("Bobby", + "2", + new TripBuilder().withStartActivityType("work").withStartLocation(0.0, 0.0).build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .withTransitSchedule(new TransitScheduleBuilder() .withTransitStopWithMode("BusStop", 400.0, 0.0, "bus") .build()) @@ -81,13 +86,15 @@ public void nonHomeTripsAreFilteredOutAndDontContributeToKpiOutput() { @Test public void singleAgentHasAccessToRail() { - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withTripWithStartLocationAndType("Bobby", "1", "home", 0.0, 0.0) + .withTrips(tripsTableBuilder + .withTrip("Bobby", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(0.0, 0.0).build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .withTransitSchedule(new TransitScheduleBuilder() .withTransitStopWithMode("RailStop", 800.0, 0.0, "rail") .build()) @@ -113,14 +120,19 @@ public void singleAgentHasAccessToRail() { @Test public void singleAgentHasAccessToRailAndUsesPT() { - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withPtTripWithStartLocationAndType("Bobby", "1", "home", - 0.0, 0.0, "rail", "A", "B") + .withTrips(tripsTableBuilder.reset() + .withTrip("Bobby", + "1", + new TripBuilder() + .withStartActivityType("home") + .withStartLocation(0.0, 0.0) + .withLegs(Arrays.asList(new LegBuilder().ofSomePtType().withMode("rail").build())) + .build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .withTransitSchedule(new TransitScheduleBuilder() .withTransitStopWithMode("RailStop", 800.0, 0.0, "rail") .build()) @@ -142,16 +154,18 @@ public void singleAgentHasAccessToRailAndUsesPT() { @Test public void twoAgentsWithBusAccess() { - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withTripWithStartLocationAndType("Bobby", "1", "home", - 0.0, 0.0) - .withTripWithStartLocationAndType("Bobbina", "1", "home", - 399.0, 0.0) + .withTrips(tripsTableBuilder.reset() + .withTrip("Bobby", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(0.0, 0.0).build()) + .withTrip("Bobbina", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(399.0, 0.0).build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .withTransitSchedule(new TransitScheduleBuilder() .withTransitStopWithMode("BusStop", 0.0, 0.0, "bus") .build()) @@ -169,16 +183,19 @@ public void twoAgentsWithBusAccess() { @Test public void twoAgentsWithDifferentBusAccess() { - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withTripWithStartLocationAndType("Bobby", "1", "home", - 0.0, 0.0) - .withTripWithStartLocationAndType("Bobbina", "1", "home", - 401.0, 0.0) + .withTrips(tripsTableBuilder + .reset() + .withTrip("Bobby", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(0.0, 0.0).build()) + .withTrip("Bobbina", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(401.0, 0.0).build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .withTransitSchedule(new TransitScheduleBuilder() .withTransitStopWithMode("BusStop", 0.0, 0.0, "bus") .build()) @@ -196,16 +213,18 @@ public void twoAgentsWithDifferentBusAccess() { @Test public void twoAgentsWithRailAccess() { - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withTripWithStartLocationAndType("Bobby", "1", "home", - 0.0, 0.0) - .withTripWithStartLocationAndType("Bobbina", "1", "home", - 799.0, 0.0) + .withTrips(tripsTableBuilder.reset() + .withTrip("Bobby", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(0.0, 0.0).build()) + .withTrip("Bobbina", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(799.0, 0.0).build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .withTransitSchedule(new TransitScheduleBuilder() .withTransitStopWithMode("RailStop", 0.0, 0.0, "rail") .build()) @@ -223,16 +242,18 @@ public void twoAgentsWithRailAccess() { @Test public void twoAgentsWithDifferentRailAccess() { - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withTripWithStartLocationAndType("Bobby", "1", "home", - 0.0, 0.0) - .withTripWithStartLocationAndType("Bobbina", "1", "home", - 801.0, 0.0) + .withTrips(tripsTableBuilder.reset() + .withTrip("Bobby", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(0.0, 0.0).build()) + .withTrip("Bobbina", + "1", + new TripBuilder().withStartActivityType("home").withStartLocation(801.0, 0.0).build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .withTransitSchedule(new TransitScheduleBuilder() .withTransitStopWithMode("RailStop", 0.0, 0.0, "rail") .build()) @@ -250,16 +271,26 @@ public void twoAgentsWithDifferentRailAccess() { @Test public void twoAgentsWithDifferentPTAccessButBothUsePT() { - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withPtTripWithStartLocationAndType("Bobby", "1", "home", - 0.0, 0.0, "rail", "A", "B") - .withPtTripWithStartLocationAndType("Bobbina", "1", "home", - 801.0, 0.0, "bus", "A", "B") + .withTrips(tripsTableBuilder.reset() + .withTrip("Bobby", + "1", + new TripBuilder() + .withStartActivityType("home") + .withStartLocation(0.0, 0.0) + .withLegs(Arrays.asList(new LegBuilder().ofSomePtType().withMode("rail").build())) + .build()) + .withTrip("Bobbina", + "1", + new TripBuilder() + .withStartActivityType("home") + .withStartLocation(801.0, 0.0) + .withLegs(Arrays.asList(new LegBuilder().ofSomePtType().withMode("bus").build())) + .build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .withTransitSchedule(new TransitScheduleBuilder() .withTransitStopWithMode("RailStop", 0.0, 0.0, "rail") .withTransitStopWithMode("BusStop", 800.0, 0.0, "bus") diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java index 78bb9f7..d82e7d4 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java @@ -29,14 +29,14 @@ public void singleAgentGivesRatioOfOne() { Integer bobbyTripLength = 10; TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withLegs(new LegsBuilder(tmpDir) - .withLegWithDistanceAndMode(bobby, "bobby_1", bobbyTripLength, "car") + .withLegs(new LegsTableBuilder(tmpDir).reset() + .withLeg(bobby, "bobby_1", new LegBuilder().withDistance(bobbyTripLength).withMode("car").build()) .build()) .withPersons(new PersonsBuilder(tmpDir) .withPerson(bobby, 10000, bobbySubpop) .build()) .withScoring(new ScoringConfigBuilder() - .withMonetaryCostsForSubpopulationAndMode(bobbySubpop, "car", 1.0, 1.0) + .withMonetaryCosts(bobbySubpop, "car", 1.0, 1.0) .build()) .build(); Map outputKpi = kpiCalculator.writeAffordabilityKpi( @@ -66,18 +66,24 @@ public void lowerIncomeAgentSpendsTwiceAsMuch() { double richBobbyIncome = 1000000; // it's all about those zeros :) TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withLegs(new LegsBuilder(tmpDir) + .withLegs(new LegsTableBuilder(tmpDir).reset() // poor Bobby has to make two trips and spends twice as much on travel - .withLegWithDistanceAndMode(poorBobby, "poor_bobby_1", tripLength, mode) - .withLegWithDistanceAndMode(poorBobby, "poor_bobby_2", tripLength, mode) - .withLegWithDistanceAndMode(richBobby, "rich_bobby_1", tripLength, mode) + .withLeg(poorBobby, + "poor_bobby_1", + new LegBuilder().withDistance(tripLength).withMode(mode).build()) + .withLeg(poorBobby, + "poor_bobby_2", + new LegBuilder().withDistance(tripLength).withMode(mode).build()) + .withLeg(richBobby, + "rich_bobby_1", + new LegBuilder().withDistance(tripLength).withMode(mode).build()) .build()) .withPersons(new PersonsBuilder(tmpDir) .withPerson(poorBobby, poorBobbyIncome, subpop) .withPerson(richBobby, richBobbyIncome, subpop) .build()) .withScoring(new ScoringConfigBuilder() - .withMonetaryCostsForSubpopulationAndMode(subpop, mode, dailyConstant, distanceCost) + .withMonetaryCosts(subpop, mode, dailyConstant, distanceCost) .build()) .build(); Map outputKpi = kpiCalculator.writeAffordabilityKpi( @@ -111,19 +117,25 @@ public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { String richBobbySubpop = "high income"; TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withLegs(new LegsBuilder(tmpDir) + .withLegs(new LegsTableBuilder(tmpDir).reset() // poor Bobby has to make two trips and spends twice as much on travel - .withLegWithDistanceAndMode(poorBobby, "poor_bobby_1", tripLength, mode) - .withLegWithDistanceAndMode(poorBobby, "poor_bobby_2", tripLength, mode) - .withLegWithDistanceAndMode(richBobby, "rich_bobby_1", tripLength, mode) + .withLeg(poorBobby, + "poor_bobby_1", + new LegBuilder().withDistance(tripLength).withMode(mode).build()) + .withLeg(poorBobby, + "poor_bobby_2", + new LegBuilder().withDistance(tripLength).withMode(mode).build()) + .withLeg(richBobby, + "rich_bobby_1", + new LegBuilder().withDistance(tripLength).withMode(mode).build()) .build()) .withPersons(new PersonsBuilder(tmpDir) .withPersonWithMissingIncome(poorBobby, poorBobbySubpop) .withPersonWithMissingIncome(richBobby, richBobbySubpop) .build()) .withScoring(new ScoringConfigBuilder() - .withMonetaryCostsForSubpopulationAndMode(poorBobbySubpop, mode, dailyConstant, distanceCost) - .withMonetaryCostsForSubpopulationAndMode(richBobbySubpop, mode, dailyConstant, distanceCost) + .withMonetaryCosts(poorBobbySubpop, mode, dailyConstant, distanceCost) + .withMonetaryCosts(richBobbySubpop, mode, dailyConstant, distanceCost) .build()) .build(); Map outputKpi = kpiCalculator.writeAffordabilityKpi( diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java index 73244a8..337dd47 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawPtWaitTimeKpiWithLinearNormaliser.java @@ -3,7 +3,8 @@ import com.arup.cml.abm.kpi.LinearNormaliser; import com.arup.cml.abm.kpi.Normaliser; import com.arup.cml.abm.kpi.builders.KpiCalculatorBuilder; -import com.arup.cml.abm.kpi.builders.LegsBuilder; +import com.arup.cml.abm.kpi.builders.LegBuilder; +import com.arup.cml.abm.kpi.builders.LegsTableBuilder; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -28,8 +29,10 @@ public void ptWaitTimeReturnsTheSinglePtWaitTimeWithSingleAgent() { Double bobbyPtWaitTimeSeconds = 6.0 * 60.0; TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withLegs(new LegsBuilder(tmpDir) - .withDefaultPtLegWithTiming(bobby, "bobby_1", "09:00:00", "00:30:00", bobbyPtWaitTime) + .withLegs(new LegsTableBuilder(tmpDir) + .withLeg(bobby, + "bobby_1", + new LegBuilder().withDepTime("09:00:00").withTravTime("00:30:00").ofSomePtType().withWaitTime(bobbyPtWaitTime).build()) .build()) .build(); Map outputKpi = kpiCalculator.writePtWaitTimeKpi( @@ -48,8 +51,10 @@ public void ptWaitTimeReturnsZeroWhenPtLegIsOutsidePeakTime() { String bobbyPtWaitTime = "00:06:00"; TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withLegs(new LegsBuilder(tmpDir) - .withDefaultPtLegWithTiming(bobby, "bobby_1", "23:00:00", "00:30:00", bobbyPtWaitTime) + .withLegs(new LegsTableBuilder(tmpDir) + .withLeg(bobby, + "bobby_1", + new LegBuilder().withDepTime("23:00:00").withTravTime("00:30:00").ofSomePtType().withWaitTime(bobbyPtWaitTime).build()) .build()) .build(); Map outputKpi = kpiCalculator.writePtWaitTimeKpi( @@ -73,9 +78,13 @@ public void givesAverageOfTwoPeakPtLegs() { Double bobbinaPtWaitTimeSeconds = 12.0 * 60.0; TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withLegs(new LegsBuilder(tmpDir) - .withDefaultPtLegWithTiming(bobby, "bobby_1", "09:00:00", "00:30:00", bobbyPtWaitTime) - .withDefaultPtLegWithTiming(bobbina, "bobbina_1", "09:00:00", "00:30:00", bobbinaPtWaitTime) + .withLegs(new LegsTableBuilder(tmpDir) + .withLeg(bobby, + "bobby_1", + new LegBuilder().withDepTime("09:00:00").withTravTime("00:30:00").ofSomePtType().withWaitTime(bobbyPtWaitTime).build()) + .withLeg(bobbina, + "bobbina_1", + new LegBuilder().withDepTime("09:00:00").withTravTime("00:30:00").ofSomePtType().withWaitTime(bobbinaPtWaitTime).build()) .build()) .build(); Map outputKpi = kpiCalculator.writePtWaitTimeKpi( diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java index dd773c5..4dfc31c 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawTravelTimeKpiWithLinearNormaliser.java @@ -8,6 +8,7 @@ import org.junit.rules.TemporaryFolder; import java.nio.file.Path; +import java.util.Arrays; import java.util.Map; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -24,13 +25,18 @@ public class TestTablesawTravelTimeKpiWithLinearNormaliser { public void singleTripGivesTrivialAverage() { String trav_time = "00:20:00"; Double trav_time_minutes = 20.0; - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withTripWithTravelTime("Bobby", "1", trav_time) + .withTrips(tripsTableBuilder.reset() + .withTrip( + "Bobby", + "1", + new TripBuilder() + .withLegs(Arrays.asList(new LegBuilder().withTravTime(trav_time).build())) + .build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .build(); Map outputKpi = kpiCalculator.writeTravelTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), @@ -50,14 +56,24 @@ public void twoTripsProduceAverageOfTravelTimes() { String bobbina_trav_time = "01:04:00"; Double bobbina_trav_time_minutes = 64.0; - TripsBuilder tripsBuilder = new TripsBuilder(tmpDir); + TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) - .withTrips(tripsBuilder - .withTripWithTravelTime("Bobby", "1", bobby_trav_time) - .withTripWithTravelTime("Bobbina", "1", bobbina_trav_time) + .withTrips(tripsTableBuilder.reset() + .withTrip( + "Bobby", + "1", + new TripBuilder() + .withLegs(Arrays.asList(new LegBuilder().withTravTime(bobby_trav_time).build())) + .build()) + .withTrip( + "Bobbina", + "1", + new TripBuilder() + .withLegs(Arrays.asList(new LegBuilder().withTravTime(bobbina_trav_time).build())) + .build()) .build()) - .withLegs(tripsBuilder.getLegsBuilder().build()) + .withLegs(tripsTableBuilder.getLegsBuilder().build()) .build(); Map outputKpi = kpiCalculator.writeTravelTimeKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), From 69d3c17f15ab18d1cb94a6a6218f03cd024fc464 Mon Sep 17 00:00:00 2001 From: Divya Sharma Date: Tue, 2 Apr 2024 11:28:49 +0100 Subject: [PATCH 32/39] add caveat and fix typos --- KPI_Data_Requirements_and_Expectations.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/KPI_Data_Requirements_and_Expectations.md b/KPI_Data_Requirements_and_Expectations.md index b76edfa..263eb8b 100644 --- a/KPI_Data_Requirements_and_Expectations.md +++ b/KPI_Data_Requirements_and_Expectations.md @@ -10,11 +10,13 @@ In general the following outputs from a MATSim model are expected on top of the - `output_households.xml.gz` (can be empty) - `output_facilities.xml.gz` (can be empty) - `output_vehicles.xml.gz` -- `soutput_persons.csv.gz` -- `soutput_legs.xml.gz` -- `soutput_trips.xml.gz` +- `output_persons.csv.gz` +- `output_legs.xml.gz` +- `output_trips.xml.gz` - `drt_vehicles.xml.gz` (for simulations with DRT mode) +Please note, if you are using a simulation configuration that modifies the inherent structure of the above MATsim outputs, that will impact your KPI calculation. For example, if you have used the Eqasim Cutter extension ([link](https://github.com/eqasim-org/eqasim-java/blob/develop/docs/cutting.md)), this will increase the number of trips in your `output_trips.xml.gz` file and relabel some to an `outside` mode or activity. This will impact how the KPI is structured and calculated, and you will need to validate the outputs are as you intend. + ## Affordability ### Cost of Travel From a93c313360091962357ec81d86e788812c980b53 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Mon, 8 Apr 2024 11:22:37 +0100 Subject: [PATCH 33/39] apply PR comment changes: extra test and remove useless constructor --- .../cml/abm/kpi/builders/TripBuilder.java | 2 -- ...wAffordabilityKpiWithLinearNormaliser.java | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/arup/cml/abm/kpi/builders/TripBuilder.java b/src/test/java/com/arup/cml/abm/kpi/builders/TripBuilder.java index b5d1f05..5bdf91b 100644 --- a/src/test/java/com/arup/cml/abm/kpi/builders/TripBuilder.java +++ b/src/test/java/com/arup/cml/abm/kpi/builders/TripBuilder.java @@ -40,8 +40,6 @@ public void setEndActivity(String type, String linkId, Coord coord) { } } - public TripBuilder() {} - public TripBuilder withStartLocation(double startX, double startY) { trip.setStartActivity(trip.startActivity.getType(), trip.startActivity.getLinkId().toString(), new Coord(startX, startY)); return this; diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java index d82e7d4..44b092e 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAffordabilityKpiWithLinearNormaliser.java @@ -154,6 +154,32 @@ public void lowerIncomeAgentSpendsTwiceAsMuchRelyingOnSubpopulations() { "all travel to the low income group is expected to be 1.33, and 0.133 after scaling."); } + @Test + public void noIncomeOrNonIncomeSubpopGivesNoResult() { + String bobby = "Bobby"; + String bobbySubpop = "default"; + Integer bobbyTripLength = 10; + + TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) + .withLegs(new LegsTableBuilder(tmpDir).reset() + .withLeg(bobby, "bobby_1", new LegBuilder().withDistance(bobbyTripLength).withMode("car").build()) + .build()) + .withPersons(new PersonsBuilder(tmpDir) + .withPersonWithMissingIncome(bobby, bobbySubpop) + .build()) + .withScoring(new ScoringConfigBuilder() + .withMonetaryCosts(bobbySubpop, "car", 1.0, 1.0) + .build()) + .build(); + Map outputKpi = kpiCalculator.writeAffordabilityKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearNormaliser + ); + assertThat(outputKpi.get("actual")).isEqualTo(-1); + assertThat(outputKpi.get("normalised")).isEqualTo(-1) + .as("No income data and no income aware subpopulations are not able to produce " + + "a meaningful result"); + } } From 4e2cc855441c77851a894ba9311f1b05121e0dd4 Mon Sep 17 00:00:00 2001 From: "kasia.kozlowska" Date: Mon, 8 Apr 2024 11:47:46 +0100 Subject: [PATCH 34/39] apply PR comment changes: refactor access to mobility tests to assert fewer things at any one time --- ...wAccessToMobilityWithLinearNormaliser.java | 116 ++++++++++++++++-- 1 file changed, 105 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java index 87c1289..84a47b5 100644 --- a/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java +++ b/src/test/java/com/arup/cml/abm/kpi/tablesaw/TestTablesawAccessToMobilityWithLinearNormaliser.java @@ -21,10 +21,8 @@ public class TestTablesawAccessToMobilityWithLinearNormaliser { @Rule public TemporaryFolder tmpDir = new TemporaryFolder(); - @Test - public void singleAgentHasAccessOnlyToBus() { + private TablesawKpiCalculator singleAgentHasAccessOnlyToBusAndDoesntUseIt() { TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); - TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) .withTrips(tripsTableBuilder .withTrip("Bobby", @@ -36,6 +34,13 @@ public void singleAgentHasAccessOnlyToBus() { .withTransitStopWithMode("BusStop", 400.0, 0.0, "bus") .build()) .build(); + return kpiCalculator; + } + + @Test + public void singleAgentWithBusAccessIs100percBusAccessible() { + TablesawKpiCalculator kpiCalculator = singleAgentHasAccessOnlyToBusAndDoesntUseIt(); + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser @@ -45,10 +50,32 @@ public void singleAgentHasAccessOnlyToBus() { assertThat(outputKpi.get("busKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 100%, " + "because there is only one agent and they have access to a bus stop."); + } + + @Test + public void singleAgentWithBusAccessIsNotRailAccessible() { + TablesawKpiCalculator kpiCalculator = singleAgentHasAccessOnlyToBusAndDoesntUseIt(); + + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearNormaliser + ); + assertThat(outputKpi.get("railKpi").get("actual")).isEqualTo(0.0); assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 0%, " + "because there is only one agent and they don't have access to rail."); + } + + @Test + public void singleAgentWithBusAccessDidNotUsePT() { + TablesawKpiCalculator kpiCalculator = singleAgentHasAccessOnlyToBusAndDoesntUseIt(); + + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearNormaliser + ); + assertThat(outputKpi.get("usedPtKpi").get("actual")).isEqualTo(0.0); assertThat(outputKpi.get("usedPtKpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Used PT KPI output is expected to be 0%, " + @@ -84,10 +111,8 @@ public void nonHomeTripsAreFilteredOutAndDontContributeToKpiOutput() { "because there is only one agent and they have access to a bus stop."); } - @Test - public void singleAgentHasAccessToRail() { + private TablesawKpiCalculator singleAgentHasAccessToRailAndDoesntUseIt() { TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); - TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) .withTrips(tripsTableBuilder .withTrip("Bobby", @@ -99,6 +124,13 @@ public void singleAgentHasAccessToRail() { .withTransitStopWithMode("RailStop", 800.0, 0.0, "rail") .build()) .build(); + return kpiCalculator; + } + + @Test + public void singleAgentWithRailAccessIsNotBusAccessible() { + TablesawKpiCalculator kpiCalculator = singleAgentHasAccessToRailAndDoesntUseIt(); + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser @@ -108,20 +140,38 @@ public void singleAgentHasAccessToRail() { assertThat(outputKpi.get("busKpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 0%, " + "because there is only one agent and they don't have access to a bus stop."); + } + + @Test + public void singleAgentWithRailAccessIs100percRailAccessible() { + TablesawKpiCalculator kpiCalculator = singleAgentHasAccessToRailAndDoesntUseIt(); + + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearNormaliser + ); assertThat(outputKpi.get("railKpi").get("actual")).isEqualTo(100.0); assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 100%, " + "because there is only one agent and they have access to rail."); + } + + @Test + public void singleAgentWithRailAccessDidNotUsePT() { + TablesawKpiCalculator kpiCalculator = singleAgentHasAccessToRailAndDoesntUseIt(); + + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearNormaliser + ); assertThat(outputKpi.get("usedPtKpi").get("actual")).isEqualTo(0.0); assertThat(outputKpi.get("usedPtKpi").get("normalised")).isEqualTo(0.0 * equivalentScalingFactor) .as("Used PT KPI output is expected to be 0%, " + "because there is only one agent and they didn't use PT"); } - @Test - public void singleAgentHasAccessToRailAndUsesPT() { + private TablesawKpiCalculator singleAgentHasAccessToRailAndUsesIt() { TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); - TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) .withTrips(tripsTableBuilder.reset() .withTrip("Bobby", @@ -137,6 +187,13 @@ public void singleAgentHasAccessToRailAndUsesPT() { .withTransitStopWithMode("RailStop", 800.0, 0.0, "rail") .build()) .build(); + return kpiCalculator; + } + + @Test + public void singleAgentWhoUsesRailIs100percRailAccessible() { + TablesawKpiCalculator kpiCalculator = singleAgentHasAccessToRailAndUsesIt(); + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser @@ -146,6 +203,17 @@ public void singleAgentHasAccessToRailAndUsesPT() { assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 100%, " + "because there is only one agent and they have access to rail."); + } + + @Test + public void singleAgentWhoUsesRailResultsIn100percUtilisedPT() { + TablesawKpiCalculator kpiCalculator = singleAgentHasAccessToRailAndUsesIt(); + + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearNormaliser + ); + assertThat(outputKpi.get("usedPtKpi").get("actual")).isEqualTo(100.0); assertThat(outputKpi.get("usedPtKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Used PT KPI output is expected to be 100%, " + @@ -269,8 +337,7 @@ public void twoAgentsWithDifferentRailAccess() { "because one agent has access to rail and the other doesn't."); } - @Test - public void twoAgentsWithDifferentPTAccessButBothUsePT() { + private TablesawKpiCalculator twoAgentsWithDifferentPTAccessButBothUsePT() { TripsTableBuilder tripsTableBuilder = new TripsTableBuilder(tmpDir); TablesawKpiCalculator kpiCalculator = new KpiCalculatorBuilder(tmpDir) @@ -296,6 +363,13 @@ public void twoAgentsWithDifferentPTAccessButBothUsePT() { .withTransitStopWithMode("BusStop", 800.0, 0.0, "bus") .build()) .build(); + return kpiCalculator; + } + + @Test + public void twoAgentsWithPTAccessAndUseItAre50percBusAccessible() { + TablesawKpiCalculator kpiCalculator = twoAgentsWithDifferentPTAccessButBothUsePT(); + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( Path.of(tmpDir.getRoot().getAbsolutePath()), linearNormaliser @@ -305,10 +379,30 @@ public void twoAgentsWithDifferentPTAccessButBothUsePT() { assertThat(outputKpi.get("busKpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Bus KPI output is expected to be 50%, " + "because one agent has access to bus and the other doesn't."); + } + + @Test + public void twoAgentsWithPTAccessAndUseItAre50percRailAccessible() { + TablesawKpiCalculator kpiCalculator = twoAgentsWithDifferentPTAccessButBothUsePT(); + + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearNormaliser + ); assertThat(outputKpi.get("railKpi").get("actual")).isEqualTo(50.0); assertThat(outputKpi.get("railKpi").get("normalised")).isEqualTo(50.0 * equivalentScalingFactor) .as("Rail KPI output is expected to be 50%, " + "because one agent has access to rail and the other doesn't."); + } + + @Test + public void twoAgentsWithPTAccessAndUseItResultIn100percUtilisedPT() { + TablesawKpiCalculator kpiCalculator = twoAgentsWithDifferentPTAccessButBothUsePT(); + + Map> outputKpi = kpiCalculator.writeAccessToMobilityServicesKpi( + Path.of(tmpDir.getRoot().getAbsolutePath()), + linearNormaliser + ); assertThat(outputKpi.get("usedPtKpi").get("actual")).isEqualTo(100.0); assertThat(outputKpi.get("usedPtKpi").get("normalised")).isEqualTo(100.0 * equivalentScalingFactor) .as("Used KPI output is expected to be 100%, " + From f8e624c3e7ce2026b783876a5530cd302ae59daf Mon Sep 17 00:00:00 2001 From: Divya Date: Wed, 10 Apr 2024 10:20:43 +0000 Subject: [PATCH 35/39] adding drt ev --- src/main/java/com/arup/cml/abm/kpi/matsim/MatsimUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/MatsimUtils.java b/src/main/java/com/arup/cml/abm/kpi/matsim/MatsimUtils.java index 512c83a..50dbb6e 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/MatsimUtils.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/MatsimUtils.java @@ -30,6 +30,8 @@ public class MatsimUtils { public static final double DEFAULT_CAR_EMISSIONS_FACTOR = 0.222; public static final String DEFAULT_BUS_FUEL_TYPE = "cng"; public static final double DEFAULT_BUS_EMISSIONS_FACTOR = 1.372; + public static final String DEFAULT_DRT_FUEL_TYPE = "ev"; + public static final double DEFAULT_DRT_EMISSIONS_FACTOR = 0.076; private Path matsimOutputDir; private Config matsimConfig; private Scenario matsimScenario; @@ -252,6 +254,7 @@ private Vehicles collectVehicles(Scenario scenario) { switch (vehicleType.getNetworkMode()) { case "car" -> setDefaultsForEngineInformationIfNotAvailable(vehicleType, DEFAULT_CAR_FUEL_TYPE, DEFAULT_CAR_EMISSIONS_FACTOR); case "bus" -> setDefaultsForEngineInformationIfNotAvailable(vehicleType, DEFAULT_BUS_FUEL_TYPE, DEFAULT_BUS_EMISSIONS_FACTOR); + case "drt" -> setDefaultsForEngineInformationIfNotAvailable(vehicleType, DEFAULT_DRT_FUEL_TYPE, DEFAULT_DRT_EMISSIONS_FACTOR); } }); return vehicles; From 83383a030c5149bef3a51500ceadf7f015b35ee5 Mon Sep 17 00:00:00 2001 From: Divya Date: Wed, 10 Apr 2024 11:07:06 +0000 Subject: [PATCH 36/39] modified expected kpi test file --- .../expected-kpi-ghg-emissions.csv.gz | Bin 46 -> 47 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz b/src/test/resources/integration-test-data/drt-matsim-outputs/expected-kpis/expected-kpi-ghg-emissions.csv.gz index f0b0ec07e63a36eabd8cb182838955dd14bebe97..632316ef84146db195c153cc901482c10ce01be5 100644 GIT binary patch delta 21 ccmdPXpCB)=+2o?(1!D#V9)~UV(hLj?07Z-iV*mgE delta 20 bcmdPbn;_3`eBJPZF#`jq#bJ3V1_lNIINJo5 From e79eba2358fae6bfcbe9e915050f0cf766f02533 Mon Sep 17 00:00:00 2001 From: Divya Date: Wed, 10 Apr 2024 15:58:43 +0000 Subject: [PATCH 37/39] update documentation --- KPI_Data_Requirements_and_Expectations.md | 10 +++++++--- README.md | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/KPI_Data_Requirements_and_Expectations.md b/KPI_Data_Requirements_and_Expectations.md index 263eb8b..256451e 100644 --- a/KPI_Data_Requirements_and_Expectations.md +++ b/KPI_Data_Requirements_and_Expectations.md @@ -72,16 +72,20 @@ way: ``` -Otherwise, the factors will default to `"fuelType"="petrol"` and `"emissionsFactor"=0.222` for personal agent's cars and +Otherwise, the factors will default to `"fuelType"="petrol"` and `"emissionsFactor"=0.222` for personal agent's cars, `"fuelType"="ev"` and `"emissionsFactor"=0.076` for drt vehicles, and `"fuelType"="cng"` and `"emissionsFactor"=1.372` for buses. Other PT vehicles are not defaulted and do not contribute to emission calculations. -We recommend setting fuel types and emission factors for LGVs and HGVs, otherwise they may will regarded as personal cars. -The defaults we use are: +We recommend setting fuel types and emission factors for LGVs and HGVs, otherwise they will not have an emissions estimate. +The defaults we suggest are: - LGV: `"fuelType"="petrol"`, `"emissionsFactor"=0.317` - HGV: `"fuelType"="diesel"`, `"emissionsFactor"=0.761` +The above values are all based on the European Environmental Agency guidebook. +Car, LGV, HGV, and Buses emissions factors are derived from Tables 3-12 and 3-15 from their [guidebook](https://www.eea.europa.eu/publications/emep-eea-guidebook-2023/part-b-sectoral-guidance-chapters/1-energy/1-a-combustion/1-a-3-b-i/view) +EV emissions factors for cars are from page 32 of their [report](https://www.eea.europa.eu/publications/electric-vehicles-from-life-cycle) estimating life cycle emissions. + # Access to Mobility Services The $(x, y)$ spatial coordinates are assumed to be in metre-based, distance-preserving projection. diff --git a/README.md b/README.md index 7de4132..c5edc33 100644 --- a/README.md +++ b/README.md @@ -364,7 +364,7 @@ We will add more KPIs as we go, but for now, these are the KPIs generated by Gel | Quality of Life | Access to Mobility Services | `kpi-access-to-mobility-services-access-to-{bus,rail,pt-and-pt-used}.csv.gz` | Percentage of agents whose homes are within 400m of bus stops, 800m of rail stops, and whether they used PT for travel | From trip logs, we obtain agents' 'home' activity locations. We find whether there is a stop that serves transit services of modes 'bus' and 'rail', within distances of 400 and 800 metres of person's home location, respectively. | `intermediate-access-to-mobility-services.csv.gz` | |
  • Quality of Life
  • Mobility System Performance
| Affordability | `kpi-affordability.csv.gz` | Ratio of average spend on transport of low income agents : overall average spend on transport of all agents | Using leg logs and monetary scoring values per mode and person's subpopulation (per distance unit and constant), and/or Person Money Events, compute monetary cost for each leg. We compute total and average daily spend on transport by income brackets (numeric values for income are required) or subpopulation, if not available, (if `low income` subpopulation is available, otherwise only intermediate output is generated). Read more: [KPI Data Requirements and Expectations: Affordability](KPI_Data_Requirements_and_Expectations.md#affordability). | `intermediate-affordability.csv.gz` | |
  • Global Environment
  • Mobility System Performance
| Congestion | `kpi-congestion.csv.gz` | Delays in road traffic and in public transport during peak hours compared to free flow travel | Capture free-flow time at the link level, subtract congested time from this value. Congested time is the difference between link entry and exit time. | `intermediate-congestion.csv.gz` | -| Global Environment | GHG Emissions | `kpi-ghg-emissions.csv.gz` | Total Emissions emitted by vehicles | Total kilometres travelled by vehicles multiplied by default emission factors of 0.222 (car) and 1.372 (bus), respectively; or as set by you - read more in [KPI Data Requirements and Expectations: GHG Emissions](KPI_Data_Requirements_and_Expectations.md#ghg-emissions). | `intermediate-ghg-emissions.csv.gz` | +| Global Environment | GHG Emissions | `kpi-ghg-emissions.csv.gz` | Total Emissions emitted by vehicles | Total kilometres travelled by vehicles multiplied by default emission factors of 0.222 kg C02/km (car) and 1.372 kg CO2/km (bus), respectively; or as set by you - read more in [KPI Data Requirements and Expectations: GHG Emissions](KPI_Data_Requirements_and_Expectations.md#ghg-emissions). | `intermediate-ghg-emissions.csv.gz` | |
  • Global Environment
  • Mobility System Performance
| Mobility Space Usage | `kpi-mobility-space-usage{-per-activity-type}.csv.gz` | Estimated parking space demand per capita | Number of persons at facilities, per activity type, who arrived there by car. We use a parking space factor of 11.5 ([link](https://www.interparking-france.com/en/what-are-the-dimensions-of-a-parking-space/)) to calculate total parking demand and weigh it by number of trips. | `intermediate-mobility-space-usage.csv.gz` | |
  • Social
  • Mobility System Performance
| PT Wait Time | `kpi-pt-wait-time.csv.gz` | Average time waiting for a PT boarding | Average trip wait times by transport mode. | `intermediate-pt-wait-time.csv.gz` | |
  • Quality of Life
  • Economic Success
| Travel Time | `kpi-travel-time.csv.gz` | Average travel time across all trips, in minutes | Using trip logs, convert travel time to minutes, average across the trips. | `intermediate-travel-time.csv.gz` | From f8e10af4dbd02a68f100d53bb605b1743af93566 Mon Sep 17 00:00:00 2001 From: Divya Date: Thu, 11 Apr 2024 08:38:26 +0000 Subject: [PATCH 38/39] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index feaf9f8..d00ef08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Normalisation to values between `0` and `10` for metrics ([#69](https://github.com/arup-group/gelato/issues/69)): `Affordability`, `PT Wait Time`, `Occupancy`, `GHG`, `Travel Time`, `Access to Mobility services`, `Congestion` +- EV emissions factors for DRT [#81](https://github.com/arup-group/gelato/issues/81) ### Fixed - GHG Emissions KPI under-reporting bug ([#73](https://github.com/arup-group/gelato/issues/73)) +- Added default values for DRT (previously assumed as zero) [#81](https://github.com/arup-group/gelato/issues/81) ### Changed From f4431b44716b15bb539fc8d34c6f844a346d8518 Mon Sep 17 00:00:00 2001 From: Michael Fitzmaurice Date: Tue, 16 Apr 2024 09:22:38 +0100 Subject: [PATCH 39/39] Prepare v0.0.4-alpha release --- CHANGELOG.md | 6 ++---- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- .../com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d00ef08..24fb96f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - +## [0.0.4-alpha] - 2024-04-16 ### Added @@ -17,9 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - GHG Emissions KPI under-reporting bug ([#73](https://github.com/arup-group/gelato/issues/73)) - Added default values for DRT (previously assumed as zero) [#81](https://github.com/arup-group/gelato/issues/81) -### Changed - -- ## [0.0.3-alpha] - 2024-03-07 @@ -50,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - vehicles get fuel types and emission factors ([#60](https://github.com/arup-group/gelato/issues/60)) - trips and legs have an additional column reporting monetary cost of the trip or leg ([#59](https://github.com/arup-group/gelato/issues/59)) + ## [0.0.2-alpha] - 2024-02-21 ### Added diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 19acbd7..1a7bc72 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.arup.cml gelato Gelato - 0.0.3-alpha + 0.0.4-alpha A command-line post-processing tool to turn MATSim ABM outputs into KPI metrics 2023 diff --git a/pom.xml b/pom.xml index 66553ee..c9ec951 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.arup.cml gelato jar - 0.0.3-alpha + 0.0.4-alpha Gelato A command-line post-processing tool to turn MATSim ABM outputs into KPI metrics diff --git a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java index cc95cd7..0c5ece8 100644 --- a/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java +++ b/src/main/java/com/arup/cml/abm/kpi/matsim/run/MatsimKpiGenerator.java @@ -26,7 +26,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -@Command(name = "MatsimKpiGenerator", version = "0.0.3-alpha", mixinStandardHelpOptions = true) +@Command(name = "MatsimKpiGenerator", version = "0.0.4-alpha", mixinStandardHelpOptions = true) public class MatsimKpiGenerator implements Runnable { private static final Logger LOGGER = LogManager.getLogger(MatsimKpiGenerator.class); public static final String EOL = "\n";