View Javadoc
1   package org.opentrafficsim.road.od;
2   
3   import java.util.Iterator;
4   import java.util.LinkedHashMap;
5   import java.util.Map;
6   import java.util.TreeMap;
7   
8   import org.djunits.unit.DurationUnit;
9   import org.djunits.value.ValueRuntimeException;
10  import org.djunits.value.storage.StorageType;
11  import org.djunits.value.vdouble.scalar.Duration;
12  import org.djunits.value.vdouble.vector.DurationVector;
13  import org.djunits.value.vdouble.vector.base.DoubleVector;
14  import org.djutils.exceptions.Throw;
15  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
16  import org.opentrafficsim.core.gtu.GtuType;
17  import org.opentrafficsim.core.math.Draw;
18  import org.opentrafficsim.core.network.Link;
19  import org.opentrafficsim.core.network.Node;
20  import org.opentrafficsim.road.network.lane.CrossSectionLink;
21  
22  import nl.tudelft.simulation.jstats.streams.StreamInterface;
23  
24  /**
25   * Split fraction at a node with fractions per link, optionally per gtu type.
26   * <p>
27   * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
28   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
29   * </p>
30   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
31   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
32   * @author <a href="https://dittlab.tudelft.nl">Wouter Schakel</a>
33   */
34  public class SplitFraction
35  {
36  
37      /** Node. */
38      private final Node node;
39  
40      /** Interpolation. */
41      private final Interpolation interpolation;
42  
43      /** Random stream. */
44      private final StreamInterface random;
45  
46      /** Simulator. */
47      private final OtsSimulatorInterface simulator;
48  
49      /** Map of fractions by GtuType and Link. */
50      private final Map<GtuType, Map<Link, Map<Duration, Double>>> fractions = new LinkedHashMap<>();
51  
52      /**
53       * Constructor.
54       * @param node Node; node
55       * @param interpolation Interpolation; interpolation
56       * @param random StreamInterface; random stream
57       * @param simulator OtsSimulatorInterface; simulator
58       */
59      public SplitFraction(final Node node, final Interpolation interpolation, final StreamInterface random,
60              final OtsSimulatorInterface simulator)
61      {
62          this.node = node;
63          this.interpolation = interpolation;
64          this.random = random;
65          this.simulator = simulator;
66      }
67  
68      /**
69       * Add fraction to link for gtu type, this will apply to all time.
70       * @param link Link; link
71       * @param gtuType GtuType; gtu type
72       * @param fraction double; fraction
73       */
74      public void addFraction(final Link link, final GtuType gtuType, final double fraction)
75      {
76          double[] fracs = new double[2];
77          fracs[0] = fraction;
78          fracs[1] = fraction;
79          DurationVector time;
80          try
81          {
82              double[] t = new double[2];
83              t[1] = Double.MAX_VALUE;
84              time = DoubleVector.instantiate(t, DurationUnit.SI, StorageType.DENSE);
85          }
86          catch (ValueRuntimeException exception)
87          {
88              // should not happen, input is not null
89              throw new RuntimeException("Input null while creating duration vector.", exception);
90          }
91          addFraction(link, gtuType, time, fracs);
92      }
93  
94      /**
95       * Add fraction to link over time for gtu type.
96       * @param link Link; link
97       * @param gtuType GtuType; gtu type
98       * @param time DurationVector; time
99       * @param fraction double[]; fraction
100      */
101     public void addFraction(final Link link, final GtuType gtuType, final DurationVector time, final double[] fraction)
102     {
103         Throw.when(time.size() != fraction.length, IllegalArgumentException.class,
104                 "Time vector and fraction array require equal length.");
105         Throw.when(!this.node.getLinks().contains(link), IllegalArgumentException.class, "Link %s is not connected to node %s.",
106                 link, this.node);
107         for (double f : fraction)
108         {
109             Throw.when(f < 0.0, IllegalArgumentException.class, "Fraction should be larger than 0.0.");
110         }
111         if (this.fractions.containsKey(gtuType))
112         {
113             this.fractions.put(gtuType, new LinkedHashMap<>());
114         }
115         this.fractions.get(gtuType).put(link, new TreeMap<>());
116         for (int i = 0; i <= time.size(); i++)
117         {
118             try
119             {
120                 this.fractions.get(gtuType).get(link).put(time.get(i), fraction[i]);
121             }
122             catch (ValueRuntimeException exception)
123             {
124                 // should not happen, sizes are checked
125                 throw new RuntimeException("Index out of range.", exception);
126             }
127         }
128     }
129 
130     /**
131      * Draw next link based on split fractions. If no fractions were defined, split fractions are determined based on the number
132      * of lanes per link.
133      * @param gtuType GtuType; gtuType
134      * @return next link
135      */
136     public Link draw(final GtuType gtuType)
137     {
138         for (GtuType gtu : this.fractions.keySet())
139         {
140             if (gtuType.isOfType(gtu))
141             {
142                 Map<Link, Double> currentFractions = new LinkedHashMap<>();
143                 double t = this.simulator.getSimulatorTime().si;
144                 for (Link link : this.fractions.get(gtu).keySet())
145                 {
146                     Iterator<Duration> iterator = this.fractions.get(gtu).get(link).keySet().iterator();
147                     Duration prev = iterator.next();
148                     while (iterator.hasNext())
149                     {
150                         Duration next = iterator.next();
151                         if (prev.si <= t && t < next.si)
152                         {
153                             // TODO let interpolation interpolate itself
154                             double f;
155                             if (this.interpolation.equals(Interpolation.STEPWISE))
156                             {
157                                 f = this.fractions.get(gtuType).get(link).get(prev);
158                             }
159                             else
160                             {
161                                 double r = (t - prev.si) / (next.si - prev.si);
162                                 f = (1 - r) * this.fractions.get(gtuType).get(link).get(prev)
163                                         + r * this.fractions.get(gtuType).get(link).get(next);
164                             }
165                             currentFractions.put(link, f);
166                             break;
167                         }
168                     }
169                 }
170                 return Draw.drawWeighted(currentFractions, this.random);
171             }
172         }
173         // GTU Type not defined, distribute by number of lanes (or weight = 1.0 if not a CrossSectionLink)
174         boolean fractionAdded = false;
175         for (Link link : this.node.getLinks())
176         {
177             if ((link.getStartNode().equals(this.node)))
178             {
179                 if (link instanceof CrossSectionLink)
180                 {
181                     int n = ((CrossSectionLink) link).getLanes().size();
182                     if (n > 0)
183                     {
184                         fractionAdded = true;
185                         addFraction(link, gtuType, n);
186                     }
187                 }
188                 else
189                 {
190                     fractionAdded = true;
191                     addFraction(link, gtuType, 1.0);
192                 }
193             }
194         }
195         Throw.when(!fractionAdded, UnsupportedOperationException.class,
196                 "Split fraction on node %s cannot be derived for gtuType %s as there are no outgoing links.", this.node,
197                 gtuType);
198         // redraw with the information that was just set
199         return draw(gtuType);
200     }
201 
202     /** {@inheritDoc} */
203     @Override
204     public int hashCode()
205     {
206         final int prime = 31;
207         int result = 1;
208         result = prime * result + ((this.fractions == null) ? 0 : this.fractions.hashCode());
209         result = prime * result + ((this.node == null) ? 0 : this.node.hashCode());
210         return result;
211     }
212 
213     /** {@inheritDoc} */
214     @Override
215     public boolean equals(final Object obj)
216     {
217         if (this == obj)
218         {
219             return true;
220         }
221         if (obj == null)
222         {
223             return false;
224         }
225         if (getClass() != obj.getClass())
226         {
227             return false;
228         }
229         SplitFraction other = (SplitFraction) obj;
230         if (this.fractions == null)
231         {
232             if (other.fractions != null)
233             {
234                 return false;
235             }
236         }
237         else if (!this.fractions.equals(other.fractions))
238         {
239             return false;
240         }
241         if (this.node == null)
242         {
243             if (other.node != null)
244             {
245                 return false;
246             }
247         }
248         else if (!this.node.equals(other.node))
249         {
250             return false;
251         }
252         return true;
253     }
254 
255     /** {@inheritDoc} */
256     @Override
257     public String toString()
258     {
259         return "SplitFraction [node=" + this.node + ", fractions=" + this.fractions + "]";
260     }
261 
262 }