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