ConflictBuilder.java

  1. package org.opentrafficsim.road.network.lane.conflict;

  2. import java.util.ArrayList;
  3. import java.util.Iterator;
  4. import java.util.LinkedHashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7. import java.util.Set;
  8. import java.util.SortedSet;
  9. import java.util.TreeSet;
  10. import java.util.concurrent.Executors;
  11. import java.util.concurrent.ThreadPoolExecutor;
  12. import java.util.concurrent.atomic.AtomicInteger;

  13. import org.djunits.value.vdouble.scalar.Length;
  14. import org.djutils.draw.line.Polygon2d;
  15. import org.djutils.draw.point.Point2d;
  16. import org.djutils.exceptions.Throw;
  17. import org.djutils.immutablecollections.ImmutableMap;
  18. import org.opentrafficsim.base.geometry.OtsLine2d;
  19. import org.opentrafficsim.core.definitions.DefaultsNl;
  20. import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
  21. import org.opentrafficsim.core.network.Link;
  22. import org.opentrafficsim.core.network.NetworkException;
  23. import org.opentrafficsim.road.network.RoadNetwork;
  24. import org.opentrafficsim.road.network.lane.CrossSectionElement;
  25. import org.opentrafficsim.road.network.lane.CrossSectionLink;
  26. import org.opentrafficsim.road.network.lane.Lane;
  27. import org.opentrafficsim.road.network.lane.Shoulder;
  28. import org.pmw.tinylog.Level;

  29. /**
  30.  * Conflict builder allows automatic generation of conflicts. This happens based on the geometry of lanes. Parallel execution
  31.  * allows this algorithm to run faster. There are two parallel implementations:
  32.  * <ul>
  33.  * <li>Small; between two lanes.</li>
  34.  * <li>Big; between one particular lane, and all lanes further in a list (i.e. similar to a triangular matrix procedure).</li>
  35.  * </ul>
  36.  * <p>
  37.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  38.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  39.  * </p>
  40.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  41.  * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  42.  * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  43.  * @see <a href="https://opentrafficsim.org/manual/99-appendices/conflict-areas/">Generation of conflics</a>
  44.  */
  45. // TODO use z-coordinate for intersections of lines
  46. // TODO use remove big parallel type, and use fibers for small tasks.
  47. public final class ConflictBuilder
  48. {
  49.     /** number of merge onflicts. */
  50.     private static AtomicInteger numberMergeConflicts = new AtomicInteger(0);

  51.     /** number of split onflicts. */
  52.     private static AtomicInteger numberSplitConflicts = new AtomicInteger(0);

  53.     /** number of cross onflicts. */
  54.     private static AtomicInteger numberCrossConflicts = new AtomicInteger(0);

  55.     /** Default width generator for conflicts which uses 80% of the lane width. */
  56.     public static final WidthGenerator DEFAULT_WIDTH_GENERATOR = new RelativeWidthGenerator(0.8);

  57.     /**
  58.      * Empty constructor.
  59.      */
  60.     private ConflictBuilder()
  61.     {
  62.         //
  63.     }

  64.     /**
  65.      * Build conflicts on network.
  66.      * @param network network
  67.      * @param simulator simulator
  68.      * @param widthGenerator width generator
  69.      */
  70.     public static void buildConflicts(final RoadNetwork network, final OtsSimulatorInterface simulator,
  71.             final WidthGenerator widthGenerator)
  72.     {
  73.         buildConflicts(network, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
  74.     }

  75.     /**
  76.      * Build conflicts on network.
  77.      * @param network network
  78.      * @param simulator simulator
  79.      * @param widthGenerator width generator
  80.      * @param ignoreList lane combinations to ignore
  81.      * @param permittedList lane combinations that are permitted by traffic control
  82.      */
  83.     public static void buildConflicts(final RoadNetwork network, final OtsSimulatorInterface simulator,
  84.             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
  85.     {
  86.         buildConflicts(getLanes(network), simulator, widthGenerator, ignoreList, permittedList, null);
  87.     }

  88.     /**
  89.      * Returns all the lanes in the network.
  90.      * @param network network.
  91.      * @return list if all lanes.
  92.      */
  93.     private static List<Lane> getLanes(final RoadNetwork network)
  94.     {
  95.         ImmutableMap<String, Link> links = network.getLinkMap();
  96.         List<Lane> lanes = new ArrayList<>();
  97.         for (String linkId : links.keySet())
  98.         {
  99.             Link link = links.get(linkId);
  100.             if (link instanceof CrossSectionLink)
  101.             {
  102.                 for (CrossSectionElement element : ((CrossSectionLink) link).getCrossSectionElementList())
  103.                 {
  104.                     if (element instanceof Lane lane && !(element instanceof Shoulder))
  105.                     {
  106.                         lanes.add((Lane) element);
  107.                     }
  108.                 }
  109.             }
  110.         }
  111.         return lanes;
  112.     }

  113.     /**
  114.      * Build conflicts on list of lanes.
  115.      * @param lanes lanes
  116.      * @param simulator simulator
  117.      * @param widthGenerator width generator
  118.      */
  119.     public static void buildConflicts(final List<Lane> lanes, final OtsSimulatorInterface simulator,
  120.             final WidthGenerator widthGenerator)
  121.     {
  122.         buildConflicts(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList(), null);
  123.     }

  124.     /**
  125.      * Build conflicts on list of lanes.
  126.      * @param lanes list of Lanes
  127.      * @param simulator the simulator
  128.      * @param widthGenerator the width generator
  129.      * @param ignoreList lane combinations to ignore
  130.      * @param permittedList lane combinations that are permitted by traffic control
  131.      * @param conflictId identification of the conflict (null value permitted)
  132.      */
  133.     public static void buildConflicts(final List<Lane> lanes, final OtsSimulatorInterface simulator,
  134.             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList,
  135.             final String conflictId)
  136.     {
  137.         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
  138.         simulator.getLogger().always().trace("GENERATING CONFLICTS (NON-PARALLEL MODE). {} COMBINATIONS", totalCombinations);
  139.         long lastReported = 0;
  140.         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
  141.         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();

  142.         for (int i = 0; i < lanes.size(); i++)
  143.         {
  144.             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i)) / 2;
  145.             if (combinationsDone / 100000000 > lastReported)
  146.             {
  147.                 simulator.getLogger().always()
  148.                         .debug(String.format(
  149.                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
  150.                                         + "conflicts, %d crossing conflicts)",
  151.                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
  152.                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
  153.                 lastReported = combinationsDone / 100000000;
  154.             }
  155.             Lane lane1 = lanes.get(i);
  156.             Set<Lane> down1 = lane1.nextLanes(null);
  157.             Set<Lane> up1 = lane1.prevLanes(null);

  158.             for (int j = i + 1; j < lanes.size(); j++)
  159.             {
  160.                 Lane lane2 = lanes.get(j);
  161.                 if (ignoreList.contains(lane1, lane2))
  162.                 {
  163.                     continue;
  164.                 }
  165.                 boolean permitted = permittedList.contains(lane1, lane2);

  166.                 Set<Lane> down2 = lane2.nextLanes(null);
  167.                 Set<Lane> up2 = lane2.prevLanes(null);
  168.                 // See if conflict needs to be build, and build if so
  169.                 try
  170.                 {
  171.                     buildConflicts(lane1, down1, up1, lane2, down2, up2, permitted, simulator, widthGenerator, leftEdges,
  172.                             rightEdges, true, conflictId);
  173.                 }
  174.                 catch (NetworkException ne)
  175.                 {
  176.                     throw new RuntimeException("Conflict build with bad combination of types / rules.", ne);
  177.                 }
  178.             }
  179.         }
  180.         simulator.getLogger().always()
  181.                 .trace(String.format(
  182.                         "generating conflicts complete (generated %d merge conflicts, %d split "
  183.                                 + "conflicts, %d crossing conflicts)",
  184.                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
  185.     }

  186.     /**
  187.      * Build conflict on single lane pair. Connecting lanes are determined.
  188.      * @param lane1 lane 1
  189.      * @param lane2 lane 2
  190.      * @param simulator simulator
  191.      * @param widthGenerator width generator
  192.      */
  193.     @SuppressWarnings("checkstyle:parameternumber")
  194.     public static void buildConflicts(final Lane lane1, final Lane lane2, final OtsSimulatorInterface simulator,
  195.             final WidthGenerator widthGenerator)
  196.     {
  197.         buildConflicts(lane1, lane2, simulator, widthGenerator, false);
  198.     }

  199.     /**
  200.      * Build conflict on single lane pair. Connecting lanes are determined.
  201.      * @param lane1 lane 1
  202.      * @param lane2 lane 2
  203.      * @param simulator simulator
  204.      * @param widthGenerator width generator
  205.      * @param permitted conflict permitted by traffic control
  206.      */
  207.     @SuppressWarnings("checkstyle:parameternumber")
  208.     public static void buildConflicts(final Lane lane1, final Lane lane2, final OtsSimulatorInterface simulator,
  209.             final WidthGenerator widthGenerator, final boolean permitted)
  210.     {
  211.         Set<Lane> down1 = lane1.nextLanes(null);
  212.         Set<Lane> up1 = lane1.prevLanes(null);
  213.         Set<Lane> down2 = lane2.nextLanes(null);
  214.         Set<Lane> up2 = lane2.prevLanes(null);
  215.         try
  216.         {
  217.             buildConflicts(lane1, down1, up1, lane2, down2, up2, permitted, simulator, widthGenerator, new LinkedHashMap<>(),
  218.                     new LinkedHashMap<>(), true, null);
  219.         }
  220.         catch (NetworkException ne)
  221.         {
  222.             throw new RuntimeException("Conflict build with bad combination of types / rules.", ne);
  223.         }
  224.     }

  225.     /**
  226.      * Build conflicts on single lane pair.
  227.      * @param lane1 lane 1
  228.      * @param down1 downstream lanes 1
  229.      * @param up1 upstream lanes 1
  230.      * @param lane2 lane 2
  231.      * @param down2 downstream lane 2
  232.      * @param up2 upstream lanes 2
  233.      * @param permitted conflict permitted by traffic control
  234.      * @param simulator simulator
  235.      * @param widthGenerator width generator
  236.      * @param leftEdges cache of left edge lines
  237.      * @param rightEdges cache of right edge lines
  238.      * @param intersectionCheck indicate whether we have to do a contour intersection check still
  239.      * @param conflictId identification of the conflict (may be null)
  240.      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
  241.      */
  242.     @SuppressWarnings({"checkstyle:parameternumber", "checkstyle:methodlength"})
  243.     static void buildConflicts(final Lane lane1, final Set<Lane> down1, final Set<Lane> up1, final Lane lane2,
  244.             final Set<Lane> down2, final Set<Lane> up2, final boolean permitted, final OtsSimulatorInterface simulator,
  245.             final WidthGenerator widthGenerator, final Map<Lane, OtsLine2d> leftEdges, final Map<Lane, OtsLine2d> rightEdges,
  246.             final boolean intersectionCheck, final String conflictId) throws NetworkException
  247.     {
  248.         // Quick contour check, skip if not overlapping -- Don't repeat if it has taken place
  249.         if (intersectionCheck)
  250.         {
  251.             if (!lane1.getContour().intersects(lane2.getContour()))
  252.             {
  253.                 return;
  254.             }
  255.         }

  256.         // TODO: we cache, but the width generator may be different

  257.         String paddedConflictId = null == conflictId ? "" : (" in conflict group " + conflictId);
  258.         // Get left and right lines at specified width
  259.         OtsLine2d left1;
  260.         OtsLine2d right1;
  261.         synchronized (lane1)
  262.         {
  263.             left1 = leftEdges.get(lane1);
  264.             right1 = rightEdges.get(lane1);
  265.             OtsLine2d line1 = lane1.getCenterLine();
  266.             if (null == left1)
  267.             {
  268.                 left1 = line1.offsetLine(widthGenerator.getWidth(lane1, 0.0) / 2, widthGenerator.getWidth(lane1, 1.0) / 2);
  269.                 leftEdges.put(lane1, left1);
  270.             }
  271.             if (null == right1)
  272.             {
  273.                 right1 = line1.offsetLine(-widthGenerator.getWidth(lane1, 0.0) / 2, -widthGenerator.getWidth(lane1, 1.0) / 2);
  274.                 rightEdges.put(lane1, right1);
  275.             }
  276.         }

  277.         OtsLine2d left2;
  278.         OtsLine2d right2;
  279.         synchronized (lane2)
  280.         {
  281.             left2 = leftEdges.get(lane2);
  282.             right2 = rightEdges.get(lane2);
  283.             OtsLine2d line2 = lane2.getCenterLine();
  284.             if (null == left2)
  285.             {
  286.                 left2 = line2.offsetLine(widthGenerator.getWidth(lane2, 0.0) / 2, widthGenerator.getWidth(lane2, 1.0) / 2);
  287.                 leftEdges.put(lane2, left2);
  288.             }
  289.             if (null == right2)
  290.             {
  291.                 right2 = line2.offsetLine(-widthGenerator.getWidth(lane2, 0.0) / 2, -widthGenerator.getWidth(lane2, 1.0) / 2);
  292.                 rightEdges.put(lane2, right2);
  293.             }
  294.         }

  295.         // Get list of all intersection fractions
  296.         SortedSet<Intersection> intersections = Intersection.getIntersectionList(left1, left2, 0);
  297.         intersections.addAll(Intersection.getIntersectionList(left1, right2, 1));
  298.         intersections.addAll(Intersection.getIntersectionList(right1, left2, 2));
  299.         intersections.addAll(Intersection.getIntersectionList(right1, right2, 3));

  300.         // Create merge
  301.         Iterator<Lane> iterator1 = down1.iterator();
  302.         Iterator<Lane> iterator2 = down2.iterator();
  303.         boolean merge = false;
  304.         while (iterator1.hasNext() && !merge)
  305.         {
  306.             Lane next1 = iterator1.next();
  307.             while (iterator2.hasNext() && !merge)
  308.             {
  309.                 Lane next2 = iterator2.next();
  310.                 if (next1.equals(next2))
  311.                 {
  312.                     // Same downstream lane, so a merge
  313.                     double fraction1 = Double.NaN;
  314.                     double fraction2 = Double.NaN;
  315.                     for (Intersection intersection : intersections)
  316.                     {
  317.                         // Only consider left/right and right/left intersections (others may or may not be at the end)
  318.                         if (intersection.getCombo() == 1 || intersection.getCombo() == 2)
  319.                         {
  320.                             fraction1 = intersection.getFraction1();
  321.                             fraction2 = intersection.getFraction2();
  322.                         }
  323.                     }
  324.                     // Remove all intersections beyond this point, these are the result of line starts/ends matching
  325.                     Iterator<Intersection> iterator = intersections.iterator();
  326.                     while (iterator.hasNext())
  327.                     {
  328.                         if (iterator.next().getFraction1() >= fraction1)
  329.                         {
  330.                             iterator.remove();
  331.                         }
  332.                     }
  333.                     if (Double.isNaN(fraction1))
  334.                     {
  335.                         simulator.getLogger().always().info("Fixing fractions of merge conflict{}", paddedConflictId);
  336.                         fraction1 = 0;
  337.                         fraction2 = 0;
  338.                     }
  339.                     // Build conflict
  340.                     buildMergeConflict(lane1, fraction1, lane2, fraction2, simulator, widthGenerator, permitted);
  341.                     // Skip loop for efficiency, and do not create multiple merges in case of multiple same downstream lanes
  342.                     merge = true;
  343.                 }
  344.             }
  345.         }

  346.         // Create split
  347.         iterator1 = up1.iterator();
  348.         iterator2 = up2.iterator();
  349.         boolean split = false;
  350.         while (iterator1.hasNext() && !split)
  351.         {
  352.             Lane prev1 = iterator1.next();
  353.             while (iterator2.hasNext() && !split)
  354.             {
  355.                 Lane prev2 = iterator2.next();
  356.                 if (prev1.equals(prev2))
  357.                 {
  358.                     // Same upstream lane, so a split
  359.                     double fraction1 = Double.NaN;
  360.                     double fraction2 = Double.NaN;
  361.                     for (Intersection intersection : intersections)
  362.                     {
  363.                         // Only consider left/right and right/left intersections (others may or may not be at the start)
  364.                         if (intersection.getCombo() == 1 || intersection.getCombo() == 2)
  365.                         {
  366.                             fraction1 = intersection.getFraction1();
  367.                             fraction2 = intersection.getFraction2();
  368.                             break; // Split so first, not last
  369.                         }
  370.                     }
  371.                     // Remove all intersections up to this point, these are the result of line starts/ends matching
  372.                     Iterator<Intersection> iterator = intersections.iterator();
  373.                     while (iterator.hasNext())
  374.                     {
  375.                         if (iterator.next().getFraction1() <= fraction1)
  376.                         {
  377.                             iterator.remove();
  378.                         }
  379.                         else
  380.                         {
  381.                             // May skip further fraction
  382.                             break;
  383.                         }
  384.                     }
  385.                     if (Double.isNaN(fraction1))
  386.                     {
  387.                         simulator.getLogger().always().info("Fixing fractions of split conflict{}", paddedConflictId);
  388.                         fraction1 = 1;
  389.                         fraction2 = 1;
  390.                     }
  391.                     // Build conflict
  392.                     buildSplitConflict(lane1, fraction1, lane2, fraction2, simulator, widthGenerator);
  393.                     // Skip loop for efficiency, and do not create multiple splits in case of multiple same upstream lanes
  394.                     split = true;
  395.                 }
  396.             }
  397.         }

  398.         // Create crossings
  399.         if (!lane1.getLink().equals(lane2.getLink())) // tight inner-curves with dedicated Bezier ignored
  400.         {
  401.             boolean[] crossed = new boolean[4];
  402.             Iterator<Intersection> iterator = intersections.iterator();
  403.             double f1Start = Double.NaN;
  404.             double f2Start = Double.NaN;
  405.             double f2End = Double.NaN;
  406.             while (iterator.hasNext())
  407.             {
  408.                 Intersection intersection = iterator.next();
  409.                 // First fraction found is start of conflict
  410.                 if (Double.isNaN(f1Start))
  411.                 {
  412.                     f1Start = intersection.getFraction1();
  413.                 }
  414.                 f2Start = Double.isNaN(f2Start) ? intersection.getFraction2() : Math.min(f2Start, intersection.getFraction2());
  415.                 f2End = Double.isNaN(f2End) ? intersection.getFraction2() : Math.max(f2End, intersection.getFraction2());
  416.                 // Flip crossed state of intersecting line combination
  417.                 crossed[intersection.getCombo()] = !crossed[intersection.getCombo()];
  418.                 // If all crossed or all not crossed, end of conflict
  419.                 if ((crossed[0] && crossed[1] && crossed[2] && crossed[3])
  420.                         || (!crossed[0] && !crossed[1] && !crossed[2] && !crossed[3]))
  421.                 {
  422.                     if (Double.isNaN(f1Start) || Double.isNaN(f2Start) || Double.isNaN(f2End))
  423.                     {
  424.                         simulator.getLogger().always().warn("NOT YET Fixing fractions of crossing conflict{}",
  425.                                 paddedConflictId);
  426.                     }
  427.                     buildCrossingConflict(lane1, f1Start, intersection.getFraction1(), lane2, f2Start, f2End, simulator,
  428.                             widthGenerator, permitted);
  429.                     f1Start = Double.NaN;
  430.                     f2Start = Double.NaN;
  431.                     f2End = Double.NaN;
  432.                 }
  433.             }
  434.         }

  435.     }

  436.     /**
  437.      * Build a merge conflict.
  438.      * @param lane1 lane 1
  439.      * @param f1start start fraction 1
  440.      * @param lane2 lane 2
  441.      * @param f2start start fraction 2
  442.      * @param simulator simulator
  443.      * @param widthGenerator width generator
  444.      * @param permitted conflict permitted by traffic control
  445.      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
  446.      */
  447.     @SuppressWarnings("checkstyle:parameternumber")
  448.     private static void buildMergeConflict(final Lane lane1, final double f1start, final Lane lane2, final double f2start,
  449.             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator, final boolean permitted)
  450.             throws NetworkException
  451.     {

  452.         // Determine lane end from direction
  453.         double f1end = 1.0;
  454.         double f2end = 1.0;

  455.         // Get locations and length
  456.         Length longitudinalPosition1 = lane1.getLength().times(f1start);
  457.         Length longitudinalPosition2 = lane2.getLength().times(f2start);
  458.         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
  459.         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));

  460.         // Get geometries
  461.         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
  462.         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);

  463.         // Determine conflict rule
  464.         ConflictRule conflictRule;
  465.         if (lane1.getLink().getPriority().isBusStop() || lane2.getLink().getPriority().isBusStop())
  466.         {
  467.             Throw.when(lane1.getLink().getPriority().isBusStop() && lane2.getLink().getPriority().isBusStop(),
  468.                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
  469.             // TODO: handle bus priority on the model side
  470.             conflictRule = new BusStopConflictRule(simulator, DefaultsNl.BUS);
  471.         }
  472.         else
  473.         {
  474.             conflictRule = new DefaultConflictRule();
  475.         }

  476.         // Make conflict
  477.         Conflict.generateConflictPair(ConflictType.MERGE, conflictRule, permitted, lane1, longitudinalPosition1, length1,
  478.                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);

  479.         numberMergeConflicts.incrementAndGet();
  480.     }

  481.     /**
  482.      * Build a split conflict.
  483.      * @param lane1 lane 1
  484.      * @param f1end end fraction 1
  485.      * @param lane2 lane 2
  486.      * @param f2end end fraction 2
  487.      * @param simulator simulator
  488.      * @param widthGenerator width generator
  489.      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
  490.      */
  491.     @SuppressWarnings("checkstyle:parameternumber")
  492.     private static void buildSplitConflict(final Lane lane1, final double f1end, final Lane lane2, final double f2end,
  493.             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator) throws NetworkException
  494.     {

  495.         // Determine lane start from direction
  496.         double f1start = 0.0;
  497.         double f2start = 0.0;

  498.         // Get locations and length
  499.         Length longitudinalPosition1 = lane1.getLength().times(f1start);
  500.         Length longitudinalPosition2 = lane2.getLength().times(f2start);
  501.         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
  502.         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));

  503.         // Get geometries
  504.         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
  505.         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);

  506.         // Make conflict
  507.         Conflict.generateConflictPair(ConflictType.SPLIT, new SplitConflictRule(), false, lane1, longitudinalPosition1, length1,
  508.                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);

  509.         numberSplitConflicts.incrementAndGet();
  510.     }

  511.     /**
  512.      * Build a crossing conflict.
  513.      * @param lane1 lane 1
  514.      * @param f1start start fraction 1
  515.      * @param f1end end fraction 1
  516.      * @param lane2 lane 2
  517.      * @param f2start start fraction 2
  518.      * @param f2end end fraction 2
  519.      * @param simulator simulator
  520.      * @param widthGenerator width generator
  521.      * @param permitted conflict permitted by traffic control
  522.      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
  523.      */
  524.     @SuppressWarnings("checkstyle:parameternumber")
  525.     private static void buildCrossingConflict(final Lane lane1, final double f1start, final double f1end, final Lane lane2,
  526.             final double f2start, final double f2end, final OtsSimulatorInterface simulator,
  527.             final WidthGenerator widthGenerator, final boolean permitted) throws NetworkException
  528.     {

  529.         // Fractions may be in opposite direction, for the start location this needs to be correct
  530.         // Note: for geometry (real order, not considering direction) and length (absolute value) this does not matter
  531.         double f1startDirected;
  532.         double f2startDirected;
  533.         if (f1end < f1start)
  534.         {
  535.             f1startDirected = f1end;
  536.         }
  537.         else
  538.         {
  539.             f1startDirected = f1start;
  540.         }
  541.         if (f2end < f2start)
  542.         {
  543.             f2startDirected = f2end;
  544.         }
  545.         else
  546.         {
  547.             f2startDirected = f2start;
  548.         }

  549.         // Get locations and length
  550.         Length longitudinalPosition1 = lane1.getLength().times(f1startDirected);
  551.         Length longitudinalPosition2 = lane2.getLength().times(f2startDirected);
  552.         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
  553.         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));

  554.         // Get geometries
  555.         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
  556.         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);

  557.         // Determine conflict rule
  558.         ConflictRule conflictRule;
  559.         if (lane1.getLink().getPriority().isBusStop() || lane2.getLink().getPriority().isBusStop())
  560.         {
  561.             Throw.when(lane1.getLink().getPriority().isBusStop() && lane2.getLink().getPriority().isBusStop(),
  562.                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
  563.             // TODO: handle bus priority on the model side
  564.             conflictRule = new BusStopConflictRule(simulator, DefaultsNl.BUS);
  565.         }
  566.         else
  567.         {
  568.             conflictRule = new DefaultConflictRule();
  569.         }

  570.         // Make conflict
  571.         Conflict.generateConflictPair(ConflictType.CROSSING, conflictRule, permitted, lane1, longitudinalPosition1, length1,
  572.                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);

  573.         numberCrossConflicts.incrementAndGet();
  574.     }

  575.     /**
  576.      * Creates geometry for conflict.
  577.      * @param lane lane
  578.      * @param fStart longitudinal fraction of start
  579.      * @param fEnd longitudinal fraction of end
  580.      * @param widthGenerator width generator
  581.      * @return geometry for conflict
  582.      */
  583.     private static Polygon2d getGeometry(final Lane lane, final double fStart, final double fEnd,
  584.             final WidthGenerator widthGenerator)
  585.     {
  586.         // extractFractional needs ordered fractions, irrespective of driving direction
  587.         double f1;
  588.         double f2;
  589.         if (fEnd > fStart)
  590.         {
  591.             f1 = fStart;
  592.             f2 = fEnd;
  593.         }
  594.         else
  595.         {
  596.             f1 = fEnd;
  597.             f2 = fStart;
  598.         }
  599.         if (Math.abs(f1 - f2) < 1E-8)
  600.         {
  601.             lane.getLink().getSimulator().getLogger().always()
  602.                     .debug("f1 (" + f1 + ") equals f2 (" + f2 + "); problematic lane is " + lane.toString());
  603.             // Fix up
  604.             if (f1 > 0)
  605.             {
  606.                 f1 = f1 - f1 / 1000;
  607.             }
  608.             else
  609.             {
  610.                 f2 = f2 + f2 / 1000;
  611.             }
  612.         }
  613.         OtsLine2d centerLine = lane.getCenterLine().extractFractional(f1, f2);
  614.         OtsLine2d left = centerLine.offsetLine(widthGenerator.getWidth(lane, f1) / 2, widthGenerator.getWidth(lane, f2) / 2);
  615.         OtsLine2d right =
  616.                 centerLine.offsetLine(-widthGenerator.getWidth(lane, f1) / 2, -widthGenerator.getWidth(lane, f2) / 2).reverse();
  617.         List<Point2d> points = new ArrayList<>(left.size() + right.size());
  618.         points.addAll(left.getPointList());
  619.         points.addAll(right.getPointList());
  620.         return new Polygon2d(points);
  621.     }

  622.     /**
  623.      * Intersection holds two fractions where two lines have crossed. There is also a combo to identify which lines have been
  624.      * used to find the intersection.
  625.      * <p>
  626.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  627.      * <br>
  628.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  629.      * </p>
  630.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  631.      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  632.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  633.      */
  634.     private static class Intersection implements Comparable<Intersection>
  635.     {

  636.         /** Fraction on lane 1. */
  637.         private final double fraction1;

  638.         /** Fraction on lane 2. */
  639.         private final double fraction2;

  640.         /** Edge combination number. */
  641.         private final int combo;

  642.         /**
  643.          * @param fraction1 fraction on lane 1
  644.          * @param fraction2 fraction on lane 1
  645.          * @param combo edge combination number
  646.          */
  647.         Intersection(final double fraction1, final double fraction2, final int combo)
  648.         {
  649.             this.fraction1 = fraction1;
  650.             this.fraction2 = fraction2;
  651.             this.combo = combo;
  652.         }

  653.         /**
  654.          * @return fraction1.
  655.          */
  656.         public final double getFraction1()
  657.         {
  658.             return this.fraction1;
  659.         }

  660.         /**
  661.          * @return fraction2.
  662.          */
  663.         public final double getFraction2()
  664.         {
  665.             return this.fraction2;
  666.         }

  667.         /**
  668.          * @return combo.
  669.          */
  670.         public final int getCombo()
  671.         {
  672.             return this.combo;
  673.         }

  674.         @Override
  675.         public int compareTo(final Intersection o)
  676.         {
  677.             int out = Double.compare(this.fraction1, o.fraction1);
  678.             if (out != 0)
  679.             {
  680.                 return out;
  681.             }
  682.             out = Double.compare(this.fraction2, o.fraction2);
  683.             if (out != 0)
  684.             {
  685.                 return out;
  686.             }
  687.             return Integer.compare(this.combo, o.combo);
  688.         }

  689.         @Override
  690.         public int hashCode()
  691.         {
  692.             final int prime = 31;
  693.             int result = 1;
  694.             result = prime * result + this.combo;
  695.             long temp;
  696.             temp = Double.doubleToLongBits(this.fraction1);
  697.             result = prime * result + (int) (temp ^ (temp >>> 32));
  698.             temp = Double.doubleToLongBits(this.fraction2);
  699.             result = prime * result + (int) (temp ^ (temp >>> 32));
  700.             return result;
  701.         }

  702.         @Override
  703.         public boolean equals(final Object obj)
  704.         {
  705.             if (this == obj)
  706.             {
  707.                 return true;
  708.             }
  709.             if (obj == null)
  710.             {
  711.                 return false;
  712.             }
  713.             if (getClass() != obj.getClass())
  714.             {
  715.                 return false;
  716.             }
  717.             Intersection other = (Intersection) obj;
  718.             if (this.combo != other.combo)
  719.             {
  720.                 return false;
  721.             }
  722.             if (Double.doubleToLongBits(this.fraction1) != Double.doubleToLongBits(other.fraction1))
  723.             {
  724.                 return false;
  725.             }
  726.             if (Double.doubleToLongBits(this.fraction2) != Double.doubleToLongBits(other.fraction2))
  727.             {
  728.                 return false;
  729.             }
  730.             return true;
  731.         }

  732.         /**
  733.          * Returns a set of intersections, sorted by the fraction on line 1.
  734.          * @param line1 line 1
  735.          * @param line2 line 2
  736.          * @param combo edge combination number
  737.          * @return set of intersections, sorted by the fraction on line 1
  738.          */
  739.         public static SortedSet<Intersection> getIntersectionList(final OtsLine2d line1, final OtsLine2d line2, final int combo)
  740.         {
  741.             SortedSet<Intersection> out = new TreeSet<>();
  742.             double cumul1 = 0.0;
  743.             Point2d start1 = null;
  744.             Point2d end1 = line1.get(0);
  745.             for (int i = 0; i < line1.size() - 1; i++)
  746.             {
  747.                 start1 = end1;
  748.                 end1 = line1.get(i + 1);

  749.                 double cumul2 = 0.0;
  750.                 Point2d start2 = null;
  751.                 Point2d end2 = line2.get(0);

  752.                 for (int j = 0; j < line2.size() - 1; j++)
  753.                 {
  754.                     start2 = end2;
  755.                     end2 = line2.get(j + 1);

  756.                     Point2d p = Point2d.intersectionOfLineSegments(start1, end1, start2, end2);
  757.                     if (p != null)
  758.                     {
  759.                         // Segments intersect
  760.                         double dx = p.x - start1.x;
  761.                         double dy = p.y - start1.y;
  762.                         double length1 = cumul1 + Math.hypot(dx, dy);
  763.                         dx = p.x - start2.x;
  764.                         dy = p.y - start2.y;
  765.                         double length2 = cumul2 + Math.hypot(dx, dy);
  766.                         out.add(new Intersection(length1 / line1.getLength(), length2 / line2.getLength(), combo));
  767.                     }

  768.                     double dx = end2.x - start2.x;
  769.                     double dy = end2.y - start2.y;
  770.                     cumul2 += Math.hypot(dx, dy);
  771.                 }

  772.                 double dx = end1.x - start1.x;
  773.                 double dy = end1.y - start1.y;
  774.                 cumul1 += Math.hypot(dx, dy);
  775.             }

  776.             return out;
  777.         }

  778.         @Override
  779.         public String toString()
  780.         {
  781.             return "Intersection [fraction1=" + this.fraction1 + ", fraction2=" + this.fraction2 + ", combo=" + this.combo
  782.                     + "]";
  783.         }

  784.     }

  785.     /**
  786.      * Generator for width.
  787.      * <p>
  788.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  789.      * <br>
  790.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  791.      * </p>
  792.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  793.      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  794.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  795.      */
  796.     public interface WidthGenerator
  797.     {

  798.         /**
  799.          * Returns the begin width of this lane.
  800.          * @param lane lane
  801.          * @param fraction fraction
  802.          * @return begin width of this lane
  803.          */
  804.         double getWidth(Lane lane, double fraction);

  805.     }

  806.     /**
  807.      * Generator with fixed width.
  808.      * <p>
  809.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  810.      * <br>
  811.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  812.      * </p>
  813.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  814.      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  815.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  816.      */
  817.     public static class FixedWidthGenerator implements WidthGenerator
  818.     {

  819.         /** Fixed width. */
  820.         private final double width;

  821.         /**
  822.          * Constructor with width.
  823.          * @param width width
  824.          */
  825.         public FixedWidthGenerator(final Length width)
  826.         {
  827.             this.width = width.si;
  828.         }

  829.         @Override
  830.         public final double getWidth(final Lane lane, final double fraction)
  831.         {
  832.             return this.width;
  833.         }

  834.         @Override
  835.         public final String toString()
  836.         {
  837.             return "FixedWidthGenerator [width=" + this.width + "]";
  838.         }

  839.     }

  840.     /**
  841.      * Generator with width factor on actual lane width.
  842.      * <p>
  843.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  844.      * <br>
  845.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  846.      * </p>
  847.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  848.      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  849.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  850.      */
  851.     public static class RelativeWidthGenerator implements WidthGenerator
  852.     {

  853.         /** Width factor. */
  854.         private final double factor;

  855.         /**
  856.          * Constructor with width factor.
  857.          * @param factor width factor
  858.          */
  859.         public RelativeWidthGenerator(final double factor)
  860.         {
  861.             this.factor = factor;
  862.         }

  863.         @Override
  864.         public final double getWidth(final Lane lane, final double fraction)
  865.         {
  866.             return lane.getWidth(fraction).si * this.factor;
  867.         }

  868.         @Override
  869.         public final String toString()
  870.         {
  871.             return "RelativeWidthGenerator [factor=" + this.factor + "]";
  872.         }

  873.     }

  874.     /* ******************************************************************************************************************** */
  875.     /* ******************************************************************************************************************** */
  876.     /* ******************************************************************************************************************** */
  877.     /* ********************************************* PARALLEL IMPLEMENTATION ********************************************** */
  878.     /* ******************************************************************************************************************** */
  879.     /* ******************************************************************************************************************** */
  880.     /* ******************************************************************************************************************** */

  881.     /**
  882.      * Build conflicts on network; parallel implementation.
  883.      * @param network network
  884.      * @param simulator simulator
  885.      * @param widthGenerator width generator
  886.      */
  887.     public static void buildConflictsParallel(final RoadNetwork network, final OtsSimulatorInterface simulator,
  888.             final WidthGenerator widthGenerator)
  889.     {
  890.         buildConflictsParallel(network, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
  891.     }

  892.     /**
  893.      * Build conflicts on network; parallel implementation.
  894.      * @param network network
  895.      * @param simulator simulator
  896.      * @param widthGenerator width generator
  897.      * @param ignoreList lane combinations to ignore
  898.      * @param permittedList lane combinations that are permitted by traffic control
  899.      */
  900.     public static void buildConflictsParallel(final RoadNetwork network, final OtsSimulatorInterface simulator,
  901.             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
  902.     {
  903.         buildConflictsParallelBig(getLanes(network), simulator, widthGenerator, ignoreList, permittedList);
  904.     }

  905.     /**
  906.      * Build conflicts on list of lanes; parallel implementation.
  907.      * @param lanes lanes
  908.      * @param simulator simulator
  909.      * @param widthGenerator width generator
  910.      */
  911.     public static void buildConflictsParallel(final List<Lane> lanes, final OtsSimulatorInterface simulator,
  912.             final WidthGenerator widthGenerator)
  913.     {
  914.         buildConflictsParallelBig(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
  915.     }

  916.     /**
  917.      * Build conflicts on list of lanes; parallel implementation. Small jobs.
  918.      * @param lanes list of Lanes
  919.      * @param simulator the simulator
  920.      * @param widthGenerator the width generator
  921.      * @param ignoreList lane combinations to ignore
  922.      * @param permittedList lane combinations that are permitted by traffic control
  923.      */
  924.     public static void buildConflictsParallelSmall(final List<Lane> lanes, final OtsSimulatorInterface simulator,
  925.             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
  926.     {
  927.         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
  928.         System.out.println("PARALLEL GENERATING OF CONFLICTS (SMALL JOBS). " + totalCombinations + " COMBINATIONS");
  929.         long lastReported = 0;
  930.         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
  931.         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();

  932.         // make a threadpool and execute buildConflicts for all records
  933.         int cores = Runtime.getRuntime().availableProcessors();
  934.         System.out.println("USING " + cores + " CORES");
  935.         ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(cores);
  936.         AtomicInteger numberOfJobs = new AtomicInteger(0);
  937.         final int maxqueue = 2 * cores;

  938.         for (int i = 0; i < lanes.size(); i++)
  939.         {
  940.             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i - 1)) / 2;
  941.             if (combinationsDone / 100000000 > lastReported)
  942.             {
  943.                 simulator.getLogger().always()
  944.                         .debug(String.format(
  945.                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
  946.                                         + "conflicts, %d crossing conflicts)",
  947.                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
  948.                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
  949.                 lastReported = combinationsDone / 100000000;
  950.             }
  951.             Lane lane1 = lanes.get(i);
  952.             Set<Lane> down1 = lane1.nextLanes(null);
  953.             Set<Lane> up1 = lane1.prevLanes(null);

  954.             for (int j = i + 1; j < lanes.size(); j++)
  955.             {
  956.                 Lane lane2 = lanes.get(j);
  957.                 if (ignoreList.contains(lane1, lane2))
  958.                 {
  959.                     continue;
  960.                 }
  961.                 // Quick contour check, skip if non-overlapping envelopes
  962.                 try
  963.                 {
  964.                     if (!lane1.getContour().intersects(lane2.getContour()))
  965.                     {
  966.                         continue;
  967.                     }
  968.                 }
  969.                 catch (Exception e)
  970.                 {
  971.                     System.err.println("Contour problem - lane1 = [" + lane1.getFullId() + "], lane2 = [" + lane2.getFullId()
  972.                             + "]; skipped");
  973.                     continue;
  974.                 }

  975.                 boolean permitted = permittedList.contains(lane1, lane2);

  976.                 while (numberOfJobs.get() > maxqueue) // keep max maxqueue jobs in the pool
  977.                 {
  978.                     try
  979.                     {
  980.                         Thread.sleep(1);
  981.                     }
  982.                     catch (InterruptedException exception)
  983.                     {
  984.                         // ignore
  985.                     }
  986.                 }
  987.                 numberOfJobs.incrementAndGet();
  988.                 Set<Lane> down2 = lane2.nextLanes(null);
  989.                 Set<Lane> up2 = lane2.prevLanes(null);
  990.                 ConflictBuilderRecordSmall cbr = new ConflictBuilderRecordSmall(lane1, down1, up1, lane2, down2, up2, permitted,
  991.                         simulator, widthGenerator, leftEdges, rightEdges);
  992.                 executor.execute(new CbrTaskSmall(numberOfJobs, cbr));
  993.             }
  994.         }

  995.         long time = System.currentTimeMillis();
  996.         // wait max 60 sec for last maxqueue jobs
  997.         while (numberOfJobs.get() > 0 && System.currentTimeMillis() - time < 60000)
  998.         {
  999.             try
  1000.             {
  1001.                 Thread.sleep(10);
  1002.             }
  1003.             catch (InterruptedException exception)
  1004.             {
  1005.                 // ignore
  1006.             }
  1007.         }

  1008.         executor.shutdown();
  1009.         while (!executor.isTerminated())
  1010.         {
  1011.             try
  1012.             {
  1013.                 Thread.sleep(1);
  1014.             }
  1015.             catch (InterruptedException exception)
  1016.             {
  1017.                 // ignore
  1018.             }
  1019.         }

  1020.         simulator.getLogger().always()
  1021.                 .debug(String.format(
  1022.                         "generating conflicts complete (generated %d merge conflicts, %d split "
  1023.                                 + "conflicts, %d crossing conflicts)",
  1024.                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
  1025.     }

  1026.     /**
  1027.      * Build conflicts on list of lanes; parallel implementation. Big jobs.
  1028.      * @param lanes list of Lanes
  1029.      * @param simulator the simulator
  1030.      * @param widthGenerator the width generator
  1031.      * @param ignoreList lane combinations to ignore
  1032.      * @param permittedList lane combinations that are permitted by traffic control
  1033.      */
  1034.     public static void buildConflictsParallelBig(final List<Lane> lanes, final OtsSimulatorInterface simulator,
  1035.             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
  1036.     {
  1037.         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
  1038.         System.out.println("PARALLEL GENERATING OF CONFLICTS (BIG JOBS). " + totalCombinations + " COMBINATIONS");
  1039.         long lastReported = 0;
  1040.         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
  1041.         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();

  1042.         // make a threadpool and execute buildConflicts for all records
  1043.         int cores = Runtime.getRuntime().availableProcessors();
  1044.         System.out.println("USING " + cores + " CORES");
  1045.         ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(cores);
  1046.         AtomicInteger numberOfJobs = new AtomicInteger(0);
  1047.         final int maxqueue = 200;

  1048.         for (int i = 0; i < lanes.size(); i++)
  1049.         {
  1050.             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i - 1)) / 2;
  1051.             if (combinationsDone / 100000000 > lastReported)
  1052.             {
  1053.                 simulator.getLogger().always()
  1054.                         .debug(String.format(
  1055.                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
  1056.                                         + "conflicts, %d crossing conflicts)",
  1057.                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
  1058.                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
  1059.                 lastReported = combinationsDone / 100000000;
  1060.             }

  1061.             while (numberOfJobs.get() > maxqueue) // keep max maxqueue jobs in the pool
  1062.             {
  1063.                 try
  1064.                 {
  1065.                     Thread.sleep(0, 10);
  1066.                 }
  1067.                 catch (InterruptedException exception)
  1068.                 {
  1069.                     // ignore
  1070.                 }
  1071.             }
  1072.             numberOfJobs.incrementAndGet();

  1073.             ConflictBuilderRecordBig cbr = new ConflictBuilderRecordBig(i, lanes, ignoreList, permittedList, simulator,
  1074.                     widthGenerator, leftEdges, rightEdges);
  1075.             executor.execute(new CbrTaskBig(numberOfJobs, cbr));

  1076.         }

  1077.         long time = System.currentTimeMillis();
  1078.         // wait max 60 sec for last maxqueue jobs
  1079.         while (numberOfJobs.get() > 0 && System.currentTimeMillis() - time < 60000)
  1080.         {
  1081.             try
  1082.             {
  1083.                 Thread.sleep(10);
  1084.             }
  1085.             catch (InterruptedException exception)
  1086.             {
  1087.                 // ignore
  1088.             }
  1089.         }

  1090.         executor.shutdown();
  1091.         while (!executor.isTerminated())
  1092.         {
  1093.             try
  1094.             {
  1095.                 Thread.sleep(1);
  1096.             }
  1097.             catch (InterruptedException exception)
  1098.             {
  1099.                 // ignore
  1100.             }
  1101.         }

  1102.         simulator.getLogger().always()
  1103.                 .debug(String.format(
  1104.                         "generating conflicts complete (generated %d merge conflicts, %d split "
  1105.                                 + "conflicts, %d crossing conflicts)",
  1106.                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
  1107.     }

  1108.     /**
  1109.      * Build conflicts on network using only the groups of links that have been identified as candidates with conflicts;
  1110.      * parallel implementation.
  1111.      * @param network network
  1112.      * @param conflictCandidateMap the map of the conflicting links to implement
  1113.      * @param simulator simulator
  1114.      * @param widthGenerator width generator
  1115.      */
  1116.     public static void buildConflictsParallel(final RoadNetwork network, final Map<String, Set<Link>> conflictCandidateMap,
  1117.             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator)
  1118.     {
  1119.         for (String conflictId : conflictCandidateMap.keySet())
  1120.         {
  1121.             // System.out.println(conflictId);
  1122.             List<Lane> lanes = new ArrayList<>();
  1123.             for (Link link : conflictCandidateMap.get(conflictId))
  1124.             {
  1125.                 if (link instanceof CrossSectionLink)
  1126.                 {
  1127.                     for (CrossSectionElement element : ((CrossSectionLink) link).getCrossSectionElementList())
  1128.                     {
  1129.                         if (element instanceof Lane lane && !(element instanceof Shoulder))
  1130.                         {
  1131.                             lanes.add((Lane) element);
  1132.                         }
  1133.                     }
  1134.                 }
  1135.             }
  1136.             // TODO: make parallel
  1137.             // simulator.getLogger().setAllLogLevel(Level.WARNING);
  1138.             buildConflicts(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList(), conflictId);
  1139.             simulator.getLogger().setAllLogLevel(Level.DEBUG);
  1140.         }
  1141.     }

  1142.     /**
  1143.      * Small conflict builder task. A small task is finding all conflicts between two lanes.
  1144.      * <p>
  1145.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  1146.      * <br>
  1147.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  1148.      * </p>
  1149.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  1150.      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  1151.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  1152.      */
  1153.     static class CbrTaskSmall implements Runnable
  1154.     {
  1155.         /** Small conflict builder record. */
  1156.         final ConflictBuilderRecordSmall cbr;

  1157.         /** Number of jobs to do. */
  1158.         final AtomicInteger nrOfJobs;

  1159.         /**
  1160.          * Constructor.
  1161.          * @param nrOfJobs number of jobs to do.
  1162.          * @param cbr the record to execute.
  1163.          */
  1164.         CbrTaskSmall(final AtomicInteger nrOfJobs, final ConflictBuilderRecordSmall cbr)
  1165.         {
  1166.             this.nrOfJobs = nrOfJobs;
  1167.             this.cbr = cbr;
  1168.         }

  1169.         @Override
  1170.         public void run()
  1171.         {
  1172.             try
  1173.             {
  1174.                 buildConflicts(this.cbr.lane1, this.cbr.down1, this.cbr.up1, this.cbr.lane2, this.cbr.down2, this.cbr.up2,
  1175.                         this.cbr.permitted, this.cbr.simulator, this.cbr.widthGenerator, this.cbr.leftEdges,
  1176.                         this.cbr.rightEdges, false, null);
  1177.             }
  1178.             catch (NetworkException ne)
  1179.             {
  1180.                 throw new RuntimeException("Conflict build with bad combination of types / rules.", ne);
  1181.             }
  1182.             this.nrOfJobs.decrementAndGet();
  1183.         }
  1184.     }

  1185.     /**
  1186.      * Small conflict builder record. Small means this holds the information to create conflicts between two lanes.
  1187.      * <p>
  1188.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  1189.      * <br>
  1190.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  1191.      * </p>
  1192.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  1193.      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  1194.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  1195.      * @param lane1 lane 1
  1196.      * @param down1 downstream lanes 1
  1197.      * @param up1 upstream lanes 1
  1198.      * @param lane2 lane 2
  1199.      * @param down2 downstream lane 2
  1200.      * @param up2 upstream lanes 2
  1201.      * @param permitted conflict permitted by traffic control
  1202.      * @param simulator simulator
  1203.      * @param widthGenerator width generator
  1204.      * @param leftEdges cache of left edge lines
  1205.      * @param rightEdges cache of right edge lines
  1206.      */
  1207.     @SuppressWarnings("checkstyle:visibilitymodifier")
  1208.     static record ConflictBuilderRecordSmall(Lane lane1, Set<Lane> down1, Set<Lane> up1, Lane lane2, Set<Lane> down2,
  1209.             Set<Lane> up2, boolean permitted, OtsSimulatorInterface simulator, WidthGenerator widthGenerator,
  1210.             Map<Lane, OtsLine2d> leftEdges, Map<Lane, OtsLine2d> rightEdges)
  1211.     {
  1212.     }

  1213.     /**
  1214.      * Large conflict builder task. A large task is finding all conflicts between one particular lane, and all lanes further in
  1215.      * a list.
  1216.      * <p>
  1217.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  1218.      * <br>
  1219.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  1220.      * </p>
  1221.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  1222.      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  1223.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  1224.      */
  1225.     static class CbrTaskBig implements Runnable
  1226.     {
  1227.         /** Big conflict builder record. */
  1228.         final ConflictBuilderRecordBig cbr;

  1229.         /** Number of jobs to do. */
  1230.         final AtomicInteger nrOfJobs;

  1231.         /**
  1232.          * Constructor.
  1233.          * @param nrOfJobs number of jobs to do.
  1234.          * @param cbr the record to execute.
  1235.          */
  1236.         CbrTaskBig(final AtomicInteger nrOfJobs, final ConflictBuilderRecordBig cbr)
  1237.         {
  1238.             this.nrOfJobs = nrOfJobs;
  1239.             this.cbr = cbr;
  1240.         }

  1241.         @Override
  1242.         public void run()
  1243.         {
  1244.             try
  1245.             {
  1246.                 Lane lane1 = this.cbr.lanes.get(this.cbr.starti);
  1247.                 Set<Lane> up1 = lane1.prevLanes(null);
  1248.                 Set<Lane> down1 = lane1.nextLanes(null);
  1249.                 for (int j = this.cbr.starti + 1; j < this.cbr.lanes.size(); j++)
  1250.                 {
  1251.                     Lane lane2 = this.cbr.lanes.get(j);
  1252.                     if (this.cbr.ignoreList.contains(lane1, lane2))
  1253.                     {
  1254.                         continue;
  1255.                     }
  1256.                     // Quick contour check, skip if non-overlapping envelopes
  1257.                     try
  1258.                     {
  1259.                         if (!lane1.getContour().intersects(lane2.getContour()))
  1260.                         {
  1261.                             continue;
  1262.                         }
  1263.                     }
  1264.                     catch (Exception e)
  1265.                     {
  1266.                         System.err.println("Contour problem - lane1 = [" + lane1.getFullId() + "], lane2 = ["
  1267.                                 + lane2.getFullId() + "]; skipped");
  1268.                         continue;
  1269.                     }

  1270.                     boolean permitted = this.cbr.permittedList.contains(lane1, lane2);

  1271.                     Set<Lane> down2 = lane2.nextLanes(null);
  1272.                     Set<Lane> up2 = lane2.prevLanes(null);

  1273.                     try
  1274.                     {
  1275.                         buildConflicts(lane1, down1, up1, lane2, down2, up2, permitted, this.cbr.simulator,
  1276.                                 this.cbr.widthGenerator, this.cbr.leftEdges, this.cbr.rightEdges, false, null);
  1277.                     }
  1278.                     catch (NetworkException ne)
  1279.                     {
  1280.                         lane2.getLink().getSimulator().getLogger().always().error(ne,
  1281.                                 "Conflict build with bad combination of types / rules.");
  1282.                     }
  1283.                 }

  1284.             }
  1285.             catch (Exception e)
  1286.             {
  1287.                 e.printStackTrace();
  1288.             }
  1289.             this.nrOfJobs.decrementAndGet();
  1290.         }
  1291.     }

  1292.     /**
  1293.      * Big conflict builder record. Big means this holds the information to create conflicts between one particular lane, and
  1294.      * all lanes further in a list.
  1295.      * <p>
  1296.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  1297.      * <br>
  1298.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  1299.      * </p>
  1300.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  1301.      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  1302.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  1303.      * @param starti the start index
  1304.      * @param lanes List of lanes
  1305.      * @param ignoreList list of lane combinations to ignore
  1306.      * @param permittedList list of lane combinations to permit
  1307.      * @param simulator simulator
  1308.      * @param widthGenerator width generator
  1309.      * @param leftEdges cache of left edge lines
  1310.      * @param rightEdges cache of right edge lines
  1311.      */
  1312.     @SuppressWarnings("checkstyle:visibilitymodifier")
  1313.     static record ConflictBuilderRecordBig(int starti, List<Lane> lanes, LaneCombinationList ignoreList,
  1314.             LaneCombinationList permittedList, OtsSimulatorInterface simulator, WidthGenerator widthGenerator,
  1315.             Map<Lane, OtsLine2d> leftEdges, Map<Lane, OtsLine2d> rightEdges)
  1316.     {
  1317.     }

  1318. }