All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentrafficsim.road.gtu.generator.Injections Maven / Gradle / Ivy

The newest version!
package org.opentrafficsim.road.gtu.generator;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Function;
import java.util.function.Supplier;

import org.djunits.unit.DurationUnit;
import org.djunits.unit.LengthUnit;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.djutils.data.ListTable;
import org.djutils.data.Row;
import org.djutils.data.Table;
import org.djutils.exceptions.Throw;
import org.djutils.immutablecollections.ImmutableLinkedHashMap;
import org.djutils.immutablecollections.ImmutableMap;
import org.djutils.multikeymap.MultiKeyMap;
import org.opentrafficsim.base.parameters.ParameterException;
import org.opentrafficsim.core.distributions.Generator;
import org.opentrafficsim.core.distributions.ProbabilityException;
import org.opentrafficsim.core.gtu.GtuCharacteristics;
import org.opentrafficsim.core.gtu.GtuException;
import org.opentrafficsim.core.gtu.GtuType;
import org.opentrafficsim.core.network.Link;
import org.opentrafficsim.core.network.Network;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.core.network.Node;
import org.opentrafficsim.core.network.route.Route;
import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator.Placement;
import org.opentrafficsim.road.gtu.generator.LaneBasedGtuGenerator.RoomChecker;
import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristics;
import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristicsGenerator;
import org.opentrafficsim.road.gtu.lane.VehicleModel;
import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtu;
import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
import org.opentrafficsim.road.network.lane.CrossSectionLink;
import org.opentrafficsim.road.network.lane.Lane;
import org.opentrafficsim.road.network.lane.LanePosition;
import org.pmw.tinylog.Logger;

import nl.tudelft.simulation.jstats.streams.StreamInterface;

/**
 * Injections can be used to have a large degree of control over GTU generation. Depending on the information provided in an
 * injections table, this class may be used in conjunction with {@code LaneBasedGtuGenerator} as a:
 * 
    *
  1. {@code Generator} for inter-arrival times
  2. *
  3. {@code LaneBasedGtuCharacteristicsGenerator} through {@code asLaneBasedGtuCharacteristicsGenerator}
  4. *
  5. {@code GeneratorPositions}
  6. *
  7. {@code RoomChecker}
  8. *
  9. {@code Supplier} for GTU ids
  10. *
* It is assumed that for each next GTU, first an inter-arrival time is requested. Functions 2 and 3 will not check order and * simply return information from the current row in the injections table. Function 4 and 5 are tracked independently and * asynchronous with the rest, as these occur at later times when GTUs are (attempted to be) placed. *

* Copyright (c) 2022-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License. *

* @author Alexander Verbraeck * @author Peter Knoppers * @author Wouter Schakel */ public class Injections implements Generator, Supplier, GeneratorPositions, RoomChecker { /** Time column id. */ public static final String TIME_COLUMN = "time"; /** Id column id. */ public static final String ID_COLUMN = "id"; /** GTU type column id. */ public static final String GTU_TYPE_COLUMN = "gtuType"; /** Position (on lane) column id. */ public static final String POSITION_COLUMN = "position"; /** Lane column id. */ public static final String LANE_COLUMN = "lane"; /** Link column id. */ public static final String LINK_COLUMN = "link"; /** Speed column id. */ public static final String SPEED_COLUMN = "speed"; /** Origin column id. */ public static final String ORIGIN_COLUMN = "origin"; /** Destination column id. */ public static final String DESTINATION_COLUMN = "destination"; /** Route column id. */ public static final String ROUTE_COLUMN = "route"; /** Length column id. */ public static final String LENGTH_COLUMN = "length"; /** Width column id. */ public static final String WIDTH_COLUMN = "width"; /** Maximum speed column id. */ public static final String MAX_SPEED_COLUMN = "maxSpeed"; /** Maximum acceleration column id. */ public static final String MAX_ACCELERATION_COLUMN = "maxAcceleration"; /** Maximum deceleration column id. */ public static final String MAX_DECELERATION_COLUMN = "maxDeceleration"; /** Front column id. */ public static final String FRONT_COLUMN = "front"; /** Network. */ private final Network network; /** GTU types per their id. */ private final ImmutableMap gtuTypes; /** Strategical planner factory. */ private final LaneBasedStrategicalPlannerFactory strategicalPlannerFactory; /** Critical time-to-collision for GTU placement. */ private final Duration timeToCollision; /** Random number stream. */ private final StreamInterface stream; /** Stored column numbers for present columns in injection table. */ private final Map columnNumbers = new LinkedHashMap<>(); /** Separate iterator to obtain the id, as this is requested asynchronously with the other characteristics. */ private final Iterator idIterator; /** Separate iterator to obtain the speed, as this is requested asynchronously with the other characteristics. */ private final Iterator speedIterator; /** Next speed to generate next GTU with. */ private Speed nextSpeed; /** Iterator over all injections. */ private final Iterator characteristicsIterator; /** Current row with characteristics from injection. */ private Row characteristicsRow; /** Previous arrival time to calculate inter-arrival time. */ private Duration previousArrival = Duration.ZERO; /** Positions per link, lane and position (on lane). */ private MultiKeyMap lanePositions; /** All lane positions, returned as {@code GeneratorPositions}. */ private Set allLanePositions; /** Cached characteristics generator, to always return the same. */ private LaneBasedGtuCharacteristicsGenerator characteristicsGenerator; /** Boolean to check inter-arrival time and characteristics drawing consistency. */ private boolean readyForCharacteristicsDraw = false; /** * Constructor. Depending on what information is provided in the injections table, some arguments may or should not be * {@code null}. In particular: *
    *
  • "time": always required, allows the {@code Injections} to be used as a {@code Generator}.
  • *
  • "id": allows the {@code Injections} to be used as a {@code Supplier} for GTU ids.
  • *
  • "position", "lane", "link": allows the {@code Injections} to be used as a {@code GeneratorPositions}, requires * network.
  • *
  • "speed": allows the {@code Injections} to be used as a {@code RoomChecker}, requires timeToCollision.
  • *
  • all other columns: allows the {@code Injections} to be used as a {@code LaneBasedGtuCharacteristicsGenerator} * through {@code asLaneBasedGtuCharacteristicsGenerator()}, requires gtuTypes, network, * strategicalPlannerFactory and stream. *
* Time should be in increasing order. If length is provided, but no front, front will be 75% of the length. * @param table Table; table with at least a "time" column. * @param network Network; network, may be {@code null}. * @param gtuTypes ImmutableMap<String, GtuType>; GTU types, as obtained from {@code Definitions}, may be * {@code null}. * @param strategicalPlannerFactory LaneBasedStrategicalPlannerFactory<?>; strategical planner factory, may be * {@code null}. * @param stream StreamInterface; random number stream, may be {@code null}. * @param timeToCollision Duration; critical time-to-collision to allow GTU generation, may be {@code null}. * @throws IllegalArgumentException when the right arguments are not provided for the columns in the injection table. */ public Injections(final Table table, final Network network, final ImmutableMap gtuTypes, final LaneBasedStrategicalPlannerFactory strategicalPlannerFactory, final StreamInterface stream, final Duration timeToCollision) throws IllegalArgumentException { Throw.whenNull(table, "Table may not be null."); Table sortedTable = sortTable(table); this.idIterator = sortedTable.iterator(); this.speedIterator = sortedTable.iterator(); this.characteristicsIterator = sortedTable.iterator(); this.network = network; this.gtuTypes = gtuTypes == null ? new ImmutableLinkedHashMap<>(Collections.emptyMap()) : gtuTypes; this.strategicalPlannerFactory = strategicalPlannerFactory; this.timeToCollision = timeToCollision; this.stream = stream; sortedTable.getColumns().forEach((c) -> this.columnNumbers.put(c.getId(), sortedTable.getColumnNumber(c))); boolean needStrategicalPlannerFactory = checkColumnTypesNeedStrategicalPlannerFactory(sortedTable); Throw.when(needStrategicalPlannerFactory && (gtuTypes == null || gtuTypes.isEmpty()), IllegalArgumentException.class, "Injection table contains columns that require GTU types."); Throw.when(needStrategicalPlannerFactory && strategicalPlannerFactory == null, IllegalArgumentException.class, "Injection table contains columns that require a strategical planner factory."); Throw.when(needStrategicalPlannerFactory && network == null, IllegalArgumentException.class, "Injection table contains columns that require a network."); Throw.when(needStrategicalPlannerFactory && stream == null, IllegalArgumentException.class, "Injection table contains columns that require a stream of random numbers."); Throw.when(!this.columnNumbers.containsKey(TIME_COLUMN), IllegalArgumentException.class, "Injection table contains no time column."); createLanePositions(sortedTable); } /** * Makes sure the table is sorted by the time column. * @param table Table; input table. * @return Table; table sorted by time column. */ private static Table sortTable(final Table table) { int timeColumn = table.getColumnNumber(TIME_COLUMN); Iterator iterator = table.iterator(); Duration prev = iterator.hasNext() ? (Duration) iterator.next().getValue(timeColumn) : null; while (iterator.hasNext()) { Duration next = (Duration) iterator.next().getValue(timeColumn); if (next.lt(prev)) { // data is not in order List data = new ArrayList<>(); for (Row row : table) { data.add(row); } Collections.sort(data, new Comparator() { /** {@inheritDoc} */ @Override public int compare(final Row o1, final Row o2) { return ((Duration) o1.getValue(timeColumn)).compareTo((Duration) o2.getValue(timeColumn)); } }); ListTable out = new ListTable(table.getId(), table.getDescription(), table.getColumns().toList()); for (Row row : data) { out.addRow(row.getValues()); } return out; } prev = next; } return table; } /** * Checks whether all columns have the right value type. * @param table Table; injection table. * @return boolean; whether columns are present that require a strategical planner factory in order to be processed. */ private boolean checkColumnTypesNeedStrategicalPlannerFactory(final Table table) { boolean needStrategicalPlannerFactory = false; for (Entry entry : this.columnNumbers.entrySet()) { Class needClass; switch (entry.getKey()) { case TIME_COLUMN: needClass = Duration.class; break; case ID_COLUMN: case LANE_COLUMN: case LINK_COLUMN: needClass = String.class; break; case GTU_TYPE_COLUMN: case ORIGIN_COLUMN: case DESTINATION_COLUMN: case ROUTE_COLUMN: needClass = String.class; needStrategicalPlannerFactory = true; break; case SPEED_COLUMN: needClass = Speed.class; break; case MAX_SPEED_COLUMN: needClass = Speed.class; needStrategicalPlannerFactory = true; break; case POSITION_COLUMN: needClass = Length.class; break; case LENGTH_COLUMN: case WIDTH_COLUMN: case FRONT_COLUMN: needClass = Length.class; needStrategicalPlannerFactory = true; break; case MAX_ACCELERATION_COLUMN: case MAX_DECELERATION_COLUMN: needClass = Acceleration.class; needStrategicalPlannerFactory = true; break; default: Logger.info("Column " + entry.getKey() + " for GTU injection not supported. It is ignored."); needClass = null; } if (needClass != null) { Class columnValueClass = table.getColumn(entry.getValue()).getValueType(); Throw.when(!needClass.isAssignableFrom(columnValueClass), IllegalArgumentException.class, "Column %s has value type %s, but type %s is required.", entry.getKey(), columnValueClass, needClass); } } return needStrategicalPlannerFactory; } /** * Creates all the lane positions for GTU generation. * @param table Table; injection table. */ private void createLanePositions(final Table table) { if (this.columnNumbers.containsKey(POSITION_COLUMN) && this.columnNumbers.containsKey(LANE_COLUMN) && this.columnNumbers.containsKey(LINK_COLUMN)) { this.lanePositions = new MultiKeyMap<>(String.class, String.class, Length.class); this.allLanePositions = new LinkedHashSet<>(); for (Row row : table) { String linkId = (String) row.getValue(this.columnNumbers.get(LINK_COLUMN)); Link link = this.network.getLink(linkId); Throw.when(link == null, IllegalArgumentException.class, "Link %s in injections is not in the network.", linkId); Throw.when(!(link instanceof CrossSectionLink), IllegalArgumentException.class, "Injection table contains link that is not a CrossSectionLink."); String laneId = (String) row.getValue(this.columnNumbers.get(LANE_COLUMN)); // get and sort lanes to get the lane number (1 = right-most lane) List lanes = ((CrossSectionLink) link).getLanes(); Collections.sort(lanes, new Comparator() { /** {@inheritDoc} */ @Override public int compare(final Lane o1, final Lane o2) { return o1.getOffsetAtBegin().compareTo(o2.getOffsetAtBegin()); } }); int laneNumber = 0; for (int i = 0; i < lanes.size(); i++) { if (lanes.get(i).getId().equals(laneId)) { laneNumber = i + 1; break; } } Throw.when(laneNumber == 0, IllegalArgumentException.class, "Injection table contains lane %s on link %s, but the link has no such lane.", laneId, linkId); Length position = (Length) row.getValue(this.columnNumbers.get(POSITION_COLUMN)); Throw.when(position.lt0() || position.gt(lanes.get(laneNumber - 1).getLength()), IllegalArgumentException.class, "Injection table contains position %s on lane %s on link %s, but the position is negative or " + "beyond the length of the lane.", position, laneId, linkId); GeneratorLanePosition generatorLanePosition = new GeneratorLanePosition(laneNumber, new LanePosition(lanes.get(laneNumber - 1), position), (CrossSectionLink) link); if (this.allLanePositions.add(generatorLanePosition)) { this.lanePositions.put(generatorLanePosition, linkId, laneId, position); } } } else if (this.columnNumbers.containsKey(POSITION_COLUMN) || this.columnNumbers.containsKey(LANE_COLUMN) || this.columnNumbers.containsKey(LINK_COLUMN)) { // only partial information is provided (if none, we assume the user intends to use an external fixed position) // as the user may still use another source for generator positions, we do not throw an exception Logger.info("For injections to be used as GeneratorPositions, define a link, lane and position (on lane) column."); } } /** {@inheritDoc} */ @Override public String get() { // This method implements Supplier as an id generator. Throw.when(!this.idIterator.hasNext(), NoSuchElementException.class, "No more ids to draw."); Throw.when(!this.columnNumbers.containsKey(ID_COLUMN), IllegalStateException.class, "Using Injections as id generator, but the injection table has no id column."); return (String) this.idIterator.next().getValue(this.columnNumbers.get(ID_COLUMN)); } /** * Returns whether the column of given id is present. * @param columnId String; column id. * @return boolean; whether the column of given id is present. */ public boolean hasColumn(final String columnId) { return this.columnNumbers.containsKey(columnId); } /** {@inheritDoc} */ @Override public synchronized Duration draw() throws ProbabilityException, ParameterException { if (!this.characteristicsIterator.hasNext()) { return null; // stops LaneBasedGtuGenerator } this.characteristicsRow = this.characteristicsIterator.next(); this.readyForCharacteristicsDraw = true; Duration t = (Duration) getCharacteristic(TIME_COLUMN); Throw.when(t.lt(this.previousArrival), IllegalStateException.class, "Arrival times in injection not increasing."); Duration interArrivalTime = t.minus(this.previousArrival); this.previousArrival = t; return interArrivalTime; } /** * Returns a characteristics generator view of the injections, as used by {@code LaneBasedGtuGenerator}. This requires at * the least that a GTU type column, a strategical planner factory, a network, and a stream of random numbers are provided. * @return LaneBasedGtuCharacteristicsGenerator; characteristics generator view of the injections. */ public LaneBasedGtuCharacteristicsGenerator asLaneBasedGtuCharacteristicsGenerator() { if (this.characteristicsGenerator == null) { Throw.when(!this.columnNumbers.containsKey(GTU_TYPE_COLUMN), IllegalStateException.class, "A GTU type column is required for generation of characteristics."); this.characteristicsGenerator = new LaneBasedGtuCharacteristicsGenerator() { /** Default characteristics, generated as needed. */ private GtuCharacteristics defaultCharacteristics; /** {@inheritDoc} */ @Override public LaneBasedGtuCharacteristics draw() throws ProbabilityException, ParameterException, GtuException { synchronized (Injections.this) { Throw.when(Injections.this.characteristicsRow == null, IllegalStateException.class, "Must draw inter-arrival time before drawing GTU characteristics."); Throw.when(!Injections.this.readyForCharacteristicsDraw, IllegalStateException.class, "Should not draw GTU characteristics again before inter-arrival time was drawn in between."); Injections.this.readyForCharacteristicsDraw = false; GtuType gtuType = Injections.this.gtuTypes.get((String) getCharacteristic(GTU_TYPE_COLUMN)); Length length = (Length) assureCharacteristic(LENGTH_COLUMN, gtuType, (g) -> g.getLength()); Length width = (Length) assureCharacteristic(WIDTH_COLUMN, gtuType, (g) -> g.getWidth()); Speed maxSpeed = (Speed) assureCharacteristic(MAX_SPEED_COLUMN, gtuType, (g) -> g.getMaximumSpeed()); Acceleration maxAcceleration = (Acceleration) assureCharacteristic(MAX_ACCELERATION_COLUMN, gtuType, (g) -> g.getMaximumAcceleration()); Acceleration maxDeceleration = (Acceleration) assureCharacteristic(MAX_DECELERATION_COLUMN, gtuType, (g) -> g.getMaximumDeceleration()); this.defaultCharacteristics = null; // reset for next draw Length front = Injections.this.columnNumbers.containsKey(FRONT_COLUMN) ? (Length) getCharacteristic(FRONT_COLUMN) : length.times(0.75); GtuCharacteristics characteristics = new GtuCharacteristics(gtuType, length, width, maxSpeed, maxAcceleration, maxDeceleration, front); Route route = Injections.this.columnNumbers.containsKey(ROUTE_COLUMN) ? (Route) Injections.this.network.getRoute((String) getCharacteristic(ROUTE_COLUMN)) : null; Node origin = Injections.this.columnNumbers.containsKey(ORIGIN_COLUMN) ? (Node) Injections.this.network.getNode((String) getCharacteristic(ORIGIN_COLUMN)) : null; Node destination = Injections.this.columnNumbers.containsKey(DESTINATION_COLUMN) ? (Node) Injections.this.network.getNode((String) getCharacteristic(DESTINATION_COLUMN)) : null; return new LaneBasedGtuCharacteristics(characteristics, Injections.this.strategicalPlannerFactory, route, origin, destination, VehicleModel.MINMAX); } } /** * Tries to obtain a column value. If it is not provided, takes the value from generated default * characteristics. * @param column String; characteristic column name. * @param gtuType GtuType; GTU type of the GTU to be generated. * @param supplier Function<GtuCharacteristics, ?>; takes value from default characteristics. * @return Object; object value for the characteristic. * @throws GtuException; if there are no default characteristics for the GTU type, but these are required. */ private Object assureCharacteristic(final String column, final GtuType gtuType, final Function supplier) throws GtuException { if (Injections.this.columnNumbers.containsKey(column)) { return getCharacteristic(column); } if (this.defaultCharacteristics == null) { this.defaultCharacteristics = GtuType.defaultCharacteristics(gtuType, Injections.this.network, Injections.this.stream); } return supplier.apply(this.defaultCharacteristics); } }; } return this.characteristicsGenerator; } /** {@inheritDoc} */ @Override public GeneratorLanePosition draw(final GtuType gtuType, final LaneBasedGtuCharacteristics characteristics, final Map> unplaced) throws GtuException { Throw.when(this.lanePositions == null, IllegalStateException.class, "Injection table without position, lane and link column cannot be used to draw generator positions."); String link = (String) getCharacteristic(LINK_COLUMN); String lane = (String) getCharacteristic(LANE_COLUMN); Length position = (Length) getCharacteristic(POSITION_COLUMN); return this.lanePositions.get(link, lane, position); } /** {@inheritDoc} */ @Override public Set getAllPositions() { Throw.when(this.lanePositions == null, IllegalStateException.class, "Injection table without position, lane and link column cannot be used to draw generator positions."); return this.allLanePositions; } /** * Returns placement for injected GTUs, as used by {@code LaneBasedGtuGenerator}. This needs speed to be provided in the * injections, and a minimum time-to-collision value. Besides the time-to-collision value, the minimum headway for a * successful placement is t*v + 3m, where t = 1s and v the generation speed. * @param leaders SortedSet<HeadwayGtu>; leaders, usually 1, possibly more after a branch * @param characteristics LaneBasedGtuCharacteristics; characteristics of the proposed new GTU * @param since Duration; time since the GTU wanted to arrive * @param initialPosition LanePosition; initial position * @return Speed; maximum safe speed, or null if a GTU with the specified characteristics cannot be placed at the current * time * @throws NetworkException this method may throw a NetworkException if it encounters an error in the network structure * @throws GtuException on parameter exception */ @Override public Placement canPlace(final SortedSet leaders, final LaneBasedGtuCharacteristics characteristics, final Duration since, final LanePosition initialPosition) throws NetworkException, GtuException { Throw.when(!this.columnNumbers.containsKey(SPEED_COLUMN), IllegalStateException.class, "Injection table without speed cannot be used to determine a GTU placement."); Throw.when(this.timeToCollision == null, IllegalStateException.class, "Injections used to place GTUs, but no acceptable time-to-collision is provided."); if (this.nextSpeed == null) { Throw.when(!this.speedIterator.hasNext(), NoSuchElementException.class, "No more speed to draw."); this.nextSpeed = (Speed) this.speedIterator.next().getValue(this.columnNumbers.get(SPEED_COLUMN)); } if (leaders.isEmpty()) { // no leaders: free Placement placement = new Placement(this.nextSpeed, initialPosition); this.nextSpeed = null; return placement; } HeadwayGtu leader = leaders.first(); if ((this.nextSpeed.le(leader.getSpeed()) || leader.getDistance().divide(this.nextSpeed.minus(leader.getSpeed())).gt(this.timeToCollision)) && leader.getDistance() .gt(this.nextSpeed.times(new Duration(1.0, DurationUnit.SI)).plus(new Length(3.0, LengthUnit.SI)))) { Placement placement = new Placement(this.nextSpeed, initialPosition); this.nextSpeed = null; return placement; } return Placement.NO; } /** * Shorthand to retrieve a column value from the current characteristics row. * @param column String; characteristic column name. * @return Object; object value for the characteristic. */ private Object getCharacteristic(final String column) { return this.characteristicsRow.getValue(this.columnNumbers.get(column)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy