SplitFraction.java
package org.opentrafficsim.road.od;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import org.djunits.unit.DurationUnit;
import org.djunits.value.ValueRuntimeException;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.vector.DurationVector;
import org.djutils.exceptions.Throw;
import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
import org.opentrafficsim.core.gtu.GtuType;
import org.opentrafficsim.core.math.Draw;
import org.opentrafficsim.core.network.Link;
import org.opentrafficsim.core.network.Node;
import org.opentrafficsim.road.network.lane.CrossSectionLink;
import nl.tudelft.simulation.jstats.streams.StreamInterface;
/**
* Split fraction at a node with fractions per link, optionally per gtu type.
* <p>
* Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
* @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
*/
public class SplitFraction
{
/** Node. */
private final Node node;
/** Interpolation. */
private final Interpolation interpolation;
/** Random stream. */
private final StreamInterface random;
/** Simulator. */
private final OtsSimulatorInterface simulator;
/** Map of fractions by GtuType and Link. */
private final Map<GtuType, Map<Link, Map<Duration, Double>>> fractions = new LinkedHashMap<>();
/**
* Constructor.
* @param node Node; node
* @param interpolation Interpolation; interpolation
* @param random StreamInterface; random stream
* @param simulator OtsSimulatorInterface; simulator
*/
public SplitFraction(final Node node, final Interpolation interpolation, final StreamInterface random,
final OtsSimulatorInterface simulator)
{
this.node = node;
this.interpolation = interpolation;
this.random = random;
this.simulator = simulator;
}
/**
* Add fraction to link for gtu type, this will apply to all time.
* @param link Link; link
* @param gtuType GtuType; gtu type
* @param fraction double; fraction
*/
public void addFraction(final Link link, final GtuType gtuType, final double fraction)
{
double[] fracs = new double[2];
fracs[0] = fraction;
fracs[1] = fraction;
DurationVector time;
try
{
double[] t = new double[2];
t[1] = Double.MAX_VALUE;
time = new DurationVector(t, DurationUnit.SI);
}
catch (ValueRuntimeException exception)
{
// should not happen, input is not null
throw new RuntimeException("Input null while creating duration vector.", exception);
}
addFraction(link, gtuType, time, fracs);
}
/**
* Add fraction to link over time for gtu type.
* @param link Link; link
* @param gtuType GtuType; gtu type
* @param time DurationVector; time
* @param fraction double[]; fraction
*/
public void addFraction(final Link link, final GtuType gtuType, final DurationVector time, final double[] fraction)
{
Throw.when(time.size() != fraction.length, IllegalArgumentException.class,
"Time vector and fraction array require equal length.");
Throw.when(!this.node.getLinks().contains(link), IllegalArgumentException.class, "Link %s is not connected to node %s.",
link, this.node);
for (double f : fraction)
{
Throw.when(f < 0.0, IllegalArgumentException.class, "Fraction should be larger than 0.0.");
}
if (this.fractions.containsKey(gtuType))
{
this.fractions.put(gtuType, new LinkedHashMap<>());
}
this.fractions.get(gtuType).put(link, new TreeMap<>());
for (int i = 0; i <= time.size(); i++)
{
try
{
this.fractions.get(gtuType).get(link).put(time.get(i), fraction[i]);
}
catch (ValueRuntimeException exception)
{
// should not happen, sizes are checked
throw new RuntimeException("Index out of range.", exception);
}
}
}
/**
* Draw next link based on split fractions. If no fractions were defined, split fractions are determined based on the number
* of lanes per link.
* @param gtuType GtuType; gtuType
* @return next link
*/
public Link draw(final GtuType gtuType)
{
for (GtuType gtu : this.fractions.keySet())
{
if (gtuType.isOfType(gtu))
{
Map<Link, Double> currentFractions = new LinkedHashMap<>();
double t = this.simulator.getSimulatorTime().si;
for (Link link : this.fractions.get(gtu).keySet())
{
Iterator<Duration> iterator = this.fractions.get(gtu).get(link).keySet().iterator();
Duration prev = iterator.next();
while (iterator.hasNext())
{
Duration next = iterator.next();
if (prev.si <= t && t < next.si)
{
// TODO let interpolation interpolate itself
double f;
if (this.interpolation.equals(Interpolation.STEPWISE))
{
f = this.fractions.get(gtuType).get(link).get(prev);
}
else
{
double r = (t - prev.si) / (next.si - prev.si);
f = (1 - r) * this.fractions.get(gtuType).get(link).get(prev)
+ r * this.fractions.get(gtuType).get(link).get(next);
}
currentFractions.put(link, f);
break;
}
}
}
return Draw.drawWeighted(currentFractions, this.random);
}
}
// GTU Type not defined, distribute by number of lanes (or weight = 1.0 if not a CrossSectionLink)
boolean fractionAdded = false;
for (Link link : this.node.getLinks())
{
if ((link.getStartNode().equals(this.node)))
{
if (link instanceof CrossSectionLink)
{
int n = ((CrossSectionLink) link).getLanes().size();
if (n > 0)
{
fractionAdded = true;
addFraction(link, gtuType, n);
}
}
else
{
fractionAdded = true;
addFraction(link, gtuType, 1.0);
}
}
}
Throw.when(!fractionAdded, UnsupportedOperationException.class,
"Split fraction on node %s cannot be derived for gtuType %s as there are no outgoing links.", this.node,
gtuType);
// redraw with the information that was just set
return draw(gtuType);
}
/** {@inheritDoc} */
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((this.fractions == null) ? 0 : this.fractions.hashCode());
result = prime * result + ((this.node == null) ? 0 : this.node.hashCode());
return result;
}
/** {@inheritDoc} */
@Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
SplitFraction other = (SplitFraction) obj;
if (this.fractions == null)
{
if (other.fractions != null)
{
return false;
}
}
else if (!this.fractions.equals(other.fractions))
{
return false;
}
if (this.node == null)
{
if (other.node != null)
{
return false;
}
}
else if (!this.node.equals(other.node))
{
return false;
}
return true;
}
/** {@inheritDoc} */
@Override
public String toString()
{
return "SplitFraction [node=" + this.node + ", fractions=" + this.fractions + "]";
}
}