diff --git a/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVModule.java b/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVModule.java index 84e59275d7b..cf03625932d 100644 --- a/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVModule.java +++ b/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVModule.java @@ -20,6 +20,8 @@ package playground.vsp.ev; +import com.google.inject.Inject; +import com.google.inject.Singleton; import org.matsim.contrib.ev.EvModule; import org.matsim.contrib.ev.charging.ChargingModule; import org.matsim.contrib.ev.discharging.DischargingModule; @@ -31,9 +33,6 @@ import org.matsim.core.config.groups.ScoringConfigGroup; import org.matsim.core.controler.AbstractModule; import org.matsim.core.mobsim.qsim.AbstractQSimModule; - -import com.google.inject.Inject; -import com.google.inject.Singleton; import org.matsim.core.mobsim.qsim.components.QSimComponentsConfigGroup; public class UrbanEVModule extends AbstractModule { @@ -47,13 +46,6 @@ public class UrbanEVModule extends AbstractModule { QSimComponentsConfigGroup qsimComponentsConfig = ConfigUtils.addOrGetModule( config, QSimComponentsConfigGroup.class ); qsimComponentsConfig.addActiveComponent( EvModule.EV_COMPONENT ); -// UrbanEVConfigGroup urbanEVConfig = ConfigUtils.addOrGetModule( config, UrbanEVConfigGroup.class ); - -// if (urbanEVConfig == null) -// throw new IllegalArgumentException( -// "no config group of type " + UrbanEVConfigGroup.GROUP_NAME + " was specified in the config"); - // was this meaningful? I.e. do we want the code to fail if there is no such config group? kai, apr'23 - //standard EV stuff install(new ChargingInfrastructureModule()); install(new ChargingModule()); @@ -62,7 +54,6 @@ public class UrbanEVModule extends AbstractModule { install(new ElectricFleetModule()); //bind custom EVFleet stuff -// bind(ElectricFleetUpdater.class).in(Singleton.class); addControlerListenerBinding().to(ElectricFleetUpdater.class).in( Singleton.class ); // (this takes the matsim modal vehicles for each leg and gives them to the ElectricFleetSpecification. Don't know why it has to be in // this ad-hoc way. kai, apr'23) @@ -74,6 +65,7 @@ protected void configureQSim() { // bind(UrbanVehicleChargingHandler.class); addMobsimScopeEventHandlerBinding().to(UrbanVehicleChargingHandler.class); // (I think that this takes the plugin/plugout activities, and actually initiates the charging. kai, apr'23) + // yes it does, just like the regular VehicleChargingHandler in the ev contrib. schlenther, feb'24. } }); @@ -81,14 +73,6 @@ protected void configureQSim() { addMobsimListenerBinding().to(UrbanEVTripsPlanner.class); // (I think that this inserts the charging activities just before the mobsim starts (i.e. it is not in the plans). kai, apr'23) - //TODO find a better solution for this yyyy yes. We do not want automagic. kai, apr'23 done. kai, apr'23 -// Collection whileChargingActTypes = urbanEVConfig.getWhileChargingActivityTypes().isEmpty() ? -// config.planCalcScore().getActivityTypes() : -// urbanEVConfig.getWhileChargingActivityTypes(); - -// bind(ActivityWhileChargingFinder.class).toInstance(new ActivityWhileChargingFinder(whileChargingActTypes, -// urbanEVConfig.getMinWhileChargingActivityDuration_s())); - bind( ActivityWhileChargingFinder.class ).in( Singleton.class ); //bind custom analysis: @@ -98,16 +82,4 @@ protected void configureQSim() { addControlerListenerBinding().to(ActsWhileChargingAnalyzer.class); } -// private Set getOpenBerlinActivityTypes() { -// Set activityTypes = new HashSet<>(); -// for (long ii = 600; ii <= 97200; ii += 600) { -// activityTypes.add("home_" + ii + ".0"); -// activityTypes.add("work_" + ii + ".0"); -// activityTypes.add("leisure_" + ii + ".0"); -// activityTypes.add("shopping_" + ii + ".0"); -// activityTypes.add("other_" + ii + ".0"); -// } -// return activityTypes; -// } - } diff --git a/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVTripsPlanner.java b/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVTripsPlanner.java index b68f4d5bcff..2b9b7dafc6d 100644 --- a/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVTripsPlanner.java +++ b/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVTripsPlanner.java @@ -20,20 +20,12 @@ package playground.vsp.ev; -import static java.util.stream.Collectors.*; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; +import com.sun.istack.Nullable; import jakarta.inject.Provider; - +import one.util.streamex.StreamEx; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; import org.apache.logging.log4j.LogManager; @@ -43,18 +35,16 @@ import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; -import org.matsim.api.core.v01.population.Activity; -import org.matsim.api.core.v01.population.Leg; -import org.matsim.api.core.v01.population.Person; -import org.matsim.api.core.v01.population.Plan; -import org.matsim.api.core.v01.population.PlanElement; -import org.matsim.api.core.v01.population.PopulationFactory; +import org.matsim.api.core.v01.population.*; import org.matsim.contrib.common.util.StraightLineKnnFinder; import org.matsim.contrib.ev.charging.ChargingLogic; import org.matsim.contrib.ev.charging.ChargingPower; import org.matsim.contrib.ev.discharging.AuxEnergyConsumption; import org.matsim.contrib.ev.discharging.DriveEnergyConsumption; -import org.matsim.contrib.ev.fleet.*; +import org.matsim.contrib.ev.fleet.ElectricFleetSpecification; +import org.matsim.contrib.ev.fleet.ElectricFleetUtils; +import org.matsim.contrib.ev.fleet.ElectricVehicle; +import org.matsim.contrib.ev.fleet.ElectricVehicleSpecification; import org.matsim.contrib.ev.infrastructure.ChargerSpecification; import org.matsim.contrib.ev.infrastructure.ChargingInfrastructureSpecification; import org.matsim.core.config.Config; @@ -69,11 +59,7 @@ import org.matsim.core.network.NetworkUtils; import org.matsim.core.population.PopulationUtils; import org.matsim.core.population.routes.NetworkRoute; -import org.matsim.core.router.LinkWrapperFacility; -import org.matsim.core.router.SingleModeNetworksCache; -import org.matsim.core.router.StageActivityTypeIdentifier; -import org.matsim.core.router.TripRouter; -import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.router.*; import org.matsim.core.router.util.TravelTime; import org.matsim.core.utils.misc.OptionalTime; import org.matsim.core.utils.timing.TimeInterpretation; @@ -82,15 +68,15 @@ import org.matsim.utils.objectattributes.attributable.AttributesImpl; import org.matsim.vehicles.Vehicle; import org.matsim.vehicles.VehicleUtils; -import org.matsim.vehicles.Vehicles; import org.matsim.withinday.utils.EditPlans; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.inject.Inject; -import com.sun.istack.Nullable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Collectors; -import one.util.streamex.StreamEx; +import static java.util.stream.Collectors.*; class UrbanEVTripsPlanner implements MobsimInitializedListener { @@ -100,9 +86,6 @@ class UrbanEVTripsPlanner implements MobsimInitializedListener { @Inject Scenario scenario; - @Inject - Vehicles vehicles; - @Inject private SingleModeNetworksCache singleModeNetworksCache; @@ -151,9 +134,10 @@ public void notifyMobsimInitialized(MobsimInitializedEvent e) { if (!(e.getQueueSimulation() instanceof QSim)) { throw new IllegalStateException(UrbanEVTripsPlanner.class + " only works with a mobsim of type " + QSim.class); } + UrbanEVConfigGroup configGroup = (UrbanEVConfigGroup)config.getModules().get(UrbanEVConfigGroup.GROUP_NAME); //collect all selected plans that contain ev legs and map them to the set of ev used Map>> selectedEVPlans = StreamEx.of(scenario.getPopulation().getPersons().values()) - .mapToEntry(p -> p.getSelectedPlan(), p -> getUsedEV(p.getSelectedPlan())) + .mapToEntry(p -> p.getSelectedPlan(), p -> getUsedEVToPreplan(p.getSelectedPlan())) .filterValues(evSet -> !evSet.isEmpty()) .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); @@ -169,16 +153,17 @@ public void notifyMobsimInitialized(MobsimInitializedEvent e) { * @param plan * @return */ - private Set> getUsedEV(Plan plan) { + private Set> getUsedEVToPreplan(Plan plan) { return TripStructureUtils.getLegs(plan) .stream() .map(leg -> VehicleUtils.getVehicleId(plan.getPerson(), leg.getMode())) - .filter(vehicleId -> isEV(vehicleId)) + .filter(vehicleId -> isEVAndToBeChargedWhileActivities(vehicleId)) .collect(toSet()); } - private boolean isEV(Id vehicleId) { - return this.electricFleetSpecification.getVehicleSpecifications().containsKey(Id.create(vehicleId, Vehicle.class)); + private boolean isEVAndToBeChargedWhileActivities(Id vehicleId) { + ElectricVehicleSpecification ev = this.electricFleetSpecification.getVehicleSpecifications().getOrDefault(Id.create(vehicleId, Vehicle.class), null); + return (ev != null && UrbanEVUtils.isChargingDuringActivities(ev)); } private void processPlans(Map>> selectedEVPlans) { diff --git a/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVUtils.java b/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVUtils.java new file mode 100644 index 00000000000..4bf58d29f09 --- /dev/null +++ b/contribs/vsp/src/main/java/playground/vsp/ev/UrbanEVUtils.java @@ -0,0 +1,67 @@ +/* *********************************************************************** * + * project: org.matsim.* + * Controler.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2007 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package playground.vsp.ev; + +import org.matsim.contrib.ev.fleet.ElectricVehicleSpecification; +import org.matsim.vehicles.Vehicle; + +public final class UrbanEVUtils { + + public static final String PLAN_CHARGING_DURING_ACTS_ATTRIBUTE_NAME = "planChargingDuringActivities"; + + /** + * Checks whether {@code electricVehicleSpecification} should be charged during the drivers' activities.
+ * If this is the case, [@code {@link UrbanEVTripsPlanner} will pre-plan charging according to the driver's selected plan.
+ *

+ * Checks for the corresponding attribute with key={@code PLAN_CHARGING_DURING_ACTS_ATTRIBUTE_NAME} in the MATSim input vehicle. + * If the attribute was not provided, true is returned! + * This means that by default, all electricVehicleSpecifications are planned to get charged during activities and not during trips + * (planning for the latter is done by {@code org.matsim.contrib.ev.routing.EVNetworkRoutingModule}. + *

+ * @param electricVehicleSpecification + * @return + */ + public static boolean isChargingDuringActivities(ElectricVehicleSpecification electricVehicleSpecification) { + Object attribute = electricVehicleSpecification.getMatsimVehicle().getAttributes().getAttribute(PLAN_CHARGING_DURING_ACTS_ATTRIBUTE_NAME); + return attribute == null ? true : (Boolean) attribute; + } + + /** + * see description for {@code setChargingDuringActivities(Vehicle vehicle, boolean value)} + * @param electricVehicleSpecification + * @param value + */ + public static void setChargingDuringActivities(ElectricVehicleSpecification electricVehicleSpecification, boolean value) { + setChargingDuringActivities(electricVehicleSpecification.getMatsimVehicle(), value); + } + + /** + * defines whether the vehicle shall be included for planning recharging during the driver's activities (with {@code UrbanEVTripsPlanner}, or not.
+ * In the latter case, recharging may be planned to take place during a trip, if the vehicle is taken for a trip with a mode that is + * routed by {@code org.matsim.contrib.ev.routing.EVNetworkRoutingModule}. + * @param vehicle + * @param value + */ + public static void setChargingDuringActivities(Vehicle vehicle, boolean value) { + vehicle.getAttributes().putAttribute(PLAN_CHARGING_DURING_ACTS_ATTRIBUTE_NAME, value); + } + +} diff --git a/contribs/vsp/src/test/java/playground/vsp/ev/UrbanEVTests.java b/contribs/vsp/src/test/java/playground/vsp/ev/UrbanEVTests.java index a71bfc6ce8a..fcbff65ded3 100644 --- a/contribs/vsp/src/test/java/playground/vsp/ev/UrbanEVTests.java +++ b/contribs/vsp/src/test/java/playground/vsp/ev/UrbanEVTests.java @@ -1,11 +1,5 @@ package playground.vsp.ev; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -30,6 +24,13 @@ import org.matsim.testcases.MatsimTestUtils; import org.matsim.vehicles.Vehicle; import org.matsim.vehicles.VehicleType; +import org.matsim.vehicles.VehicleUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; // TODO translate and complete @@ -88,6 +89,12 @@ public static void run() { // EVUtils.setInitialEnergy(scenario.getVehicles().getVehicleTypes().get(Id.create("Not enough time so charging early", VehicleType.class)).getEngineInformation(), // 5.0); + //this guy shall not be pre-plan charging during activities + Id carNotToCharge = VehicleUtils.getVehicleIds( + scenario.getPopulation().getPersons().get(Id.createPersonId("Do not charge during activities"))) + .get(TransportMode.car); + UrbanEVUtils.setChargingDuringActivities(scenario.getVehicles().getVehicles().get(carNotToCharge), false); + ///controler with Urban EV module Controler controler = RunUrbanEVExample.prepareControler(scenario); handler = new UrbanEVTestHandler(); @@ -119,7 +126,7 @@ void testAgentsExecuteSameNumberOfActsAsPlanned() { } } Assertions.assertFalse(fail, - "the following persons do not execute the same amount of activities as they plan to:" + personsWithDifferingActCount); + "the following persons do not execute the same number of activities as they plan to:" + personsWithDifferingActCount); } @Test @@ -174,8 +181,8 @@ void testChargerSelectionShopping() { Assertions.assertEquals(1, plugins.size(), 0); ActivityStartEvent pluginActStart = plugins.get(0); - //starts at 10am at work and travels 8 links à 99s - Assertions.assertEquals(10 * 3600 + 8 * 99, pluginActStart.getTime(), MatsimTestUtils.EPSILON, "wrong charging start time"); + //starts at 10am at work and travels 8 links à 99s + 3s waiting time to enter traffic + Assertions.assertEquals(10 * 3600 + 8 * 99 + 3, pluginActStart.getTime(), MatsimTestUtils.EPSILON, "wrong charging start time"); Assertions.assertEquals("172", pluginActStart.getLinkId().toString(), "wrong charging start location"); List plugouts = this.handler.plugOutCntPerPerson.getOrDefault(Id.createPersonId("Charging during shopping"), List.of()); @@ -186,6 +193,15 @@ void testChargerSelectionShopping() { } + @Test + void testDoNotChargeDuringActivities() { + List plugins = this.handler.plugInCntPerPerson.getOrDefault(Id.createPersonId("Do not charge during activities"), List.of()); + Assertions.assertEquals(0, plugins.size(), 0); + + List plugouts = this.handler.plugOutCntPerPerson.getOrDefault(Id.createPersonId("Do not charge during activities"), List.of()); + Assertions.assertEquals(0, plugouts.size(), 0); + } + @Test void testLongDistance() { List plugins = this.handler.plugInCntPerPerson.getOrDefault(Id.createPersonId("Charger Selection long distance leg"), @@ -193,8 +209,8 @@ void testLongDistance() { Assertions.assertEquals(1, plugins.size(), 0); ActivityStartEvent pluginActStart = plugins.get(0); - //starts at 8 am and travels 19 links à 99s + 3s waiting time to enter traffic - Assertions.assertEquals(8 * 3600 + 19 * 99 + 3, pluginActStart.getTime(), MatsimTestUtils.EPSILON, "wrong charging start time"); + //starts at 8 am and travels 19 links à 99s + 7s waiting time to enter traffic + Assertions.assertEquals(8 * 3600 + 19 * 99 + 7, pluginActStart.getTime(), MatsimTestUtils.EPSILON, "wrong charging start time"); Assertions.assertEquals("89", pluginActStart.getLinkId().toString(), "wrong charging start location"); List plugouts = this.handler.plugOutCntPerPerson.getOrDefault(Id.createPersonId("Charger Selection long distance leg"), @@ -421,6 +437,50 @@ private static void overridePopulation(Scenario scenario) { scenario.getPopulation().addPerson(person2); } + { + Person person2a = factory.createPerson(Id.createPersonId("Do not charge during activities")); + + Plan plan2a = factory.createPlan(); + + Activity home21 = factory.createActivityFromLinkId("home", Id.createLinkId("1")); + home21.setEndTime(8 * 3600); + plan2a.addActivity(home21); + plan2a.addLeg(factory.createLeg(TransportMode.car)); + + Activity work21 = factory.createActivityFromLinkId("work", Id.createLinkId("176")); + work21.setEndTime(10 * 3600); + plan2a.addActivity(work21); + + plan2a.addLeg(factory.createLeg(TransportMode.car)); + + // Activity work22 = factory.createActivityFromLinkId("work", Id.createLinkId("60")); + // work22.setEndTime(12 * 3600); + // plan2a.addActivity(work22); + // + // plan2a.addLeg(factory.createLeg(TransportMode.car)); + + Activity shopping21 = factory.createActivityFromLinkId("shopping", Id.createLinkId("9")); + shopping21.setMaximumDuration(1200); + + plan2a.addActivity(shopping21); + + plan2a.addLeg(factory.createLeg(TransportMode.car)); + + Activity work23 = factory.createActivityFromLinkId("work", Id.createLinkId("5")); + work23.setEndTime(13 * 3600); + plan2a.addActivity(work23); + + plan2a.addLeg(factory.createLeg(TransportMode.car)); + + Activity home22 = factory.createActivityFromLinkId("home", Id.createLinkId("1")); + home22.setEndTime(15 * 3600); + plan2a.addActivity(home22); + person2a.addPlan(plan2a); + person2a.setSelectedPlan(plan2a); + + scenario.getPopulation().addPerson(person2a); + } + { Person person3 = factory.createPerson(Id.createPersonId("Charger Selection long distance leg"));