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