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.core.definitions.DefaultsNl;
  19. import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
  20. import org.opentrafficsim.core.geometry.OtsGeometryException;
  21. import org.opentrafficsim.core.geometry.OtsLine2d;
  22. import org.opentrafficsim.core.network.Link;
  23. import org.opentrafficsim.core.network.NetworkException;
  24. import org.opentrafficsim.road.network.RoadNetwork;
  25. import org.opentrafficsim.road.network.lane.CrossSectionElement;
  26. import org.opentrafficsim.road.network.lane.CrossSectionLink;
  27. import org.opentrafficsim.road.network.lane.Lane;
  28. import org.opentrafficsim.road.network.lane.Shoulder;
  29. import org.pmw.tinylog.Level;

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

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

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

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

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

  65.     /**
  66.      * Build conflicts on network.
  67.      * @param network RoadNetwork; network
  68.      * @param simulator OtsSimulatorInterface; simulator
  69.      * @param widthGenerator WidthGenerator; width generator
  70.      * @throws OtsGeometryException in case of geometry exception
  71.      */
  72.     public static void buildConflicts(final RoadNetwork network, final OtsSimulatorInterface simulator,
  73.             final WidthGenerator widthGenerator) throws OtsGeometryException
  74.     {
  75.         buildConflicts(network, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
  76.     }

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

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

  117.     /**
  118.      * Build conflicts on list of lanes.
  119.      * @param lanes List&lt;Lane&gt;; lanes
  120.      * @param simulator OtsSimulatorInterface; simulator
  121.      * @param widthGenerator WidthGenerator; width generator
  122.      * @throws OtsGeometryException in case of geometry exception
  123.      */
  124.     public static void buildConflicts(final List<Lane> lanes, final OtsSimulatorInterface simulator,
  125.             final WidthGenerator widthGenerator) throws OtsGeometryException
  126.     {
  127.         buildConflicts(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList(), null);
  128.     }

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

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

  164.             for (int j = i + 1; j < lanes.size(); j++)
  165.             {
  166.                 Lane lane2 = lanes.get(j);
  167.                 if (ignoreList.contains(lane1, lane2))
  168.                 {
  169.                     continue;
  170.                 }
  171.                 boolean permitted = permittedList.contains(lane1, lane2);

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

  192.     /**
  193.      * Build conflict on single lane pair. Connecting lanes are determined.
  194.      * @param lane1 Lane; lane 1
  195.      * @param lane2 Lane; lane 2
  196.      * @param simulator OtsSimulatorInterface; simulator
  197.      * @param widthGenerator WidthGenerator; width generator
  198.      * @throws OtsGeometryException in case of geometry exception
  199.      */
  200.     @SuppressWarnings("checkstyle:parameternumber")
  201.     public static void buildConflicts(final Lane lane1, final Lane lane2, final OtsSimulatorInterface simulator,
  202.             final WidthGenerator widthGenerator) throws OtsGeometryException
  203.     {
  204.         buildConflicts(lane1, lane2, simulator, widthGenerator, false);
  205.     }

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

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

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

  266.         String paddedConflictId = null == conflictId ? "" : (" in conflict group " + conflictId);
  267.         // Get left and right lines at specified width
  268.         OtsLine2d left1;
  269.         OtsLine2d right1;
  270.         synchronized (lane1)
  271.         {
  272.             left1 = leftEdges.get(lane1);
  273.             right1 = rightEdges.get(lane1);
  274.             OtsLine2d line1 = lane1.getCenterLine();
  275.             if (null == left1)
  276.             {
  277.                 left1 = line1.offsetLine(widthGenerator.getWidth(lane1, 0.0) / 2, widthGenerator.getWidth(lane1, 1.0) / 2);
  278.                 leftEdges.put(lane1, left1);
  279.             }
  280.             if (null == right1)
  281.             {
  282.                 right1 = line1.offsetLine(-widthGenerator.getWidth(lane1, 0.0) / 2, -widthGenerator.getWidth(lane1, 1.0) / 2);
  283.                 rightEdges.put(lane1, right1);
  284.             }
  285.         }

  286.         OtsLine2d left2;
  287.         OtsLine2d right2;
  288.         synchronized (lane2)
  289.         {
  290.             left2 = leftEdges.get(lane2);
  291.             right2 = rightEdges.get(lane2);
  292.             OtsLine2d line2 = lane2.getCenterLine();
  293.             if (null == left2)
  294.             {
  295.                 left2 = line2.offsetLine(widthGenerator.getWidth(lane2, 0.0) / 2, widthGenerator.getWidth(lane2, 1.0) / 2);
  296.                 leftEdges.put(lane2, left2);
  297.             }
  298.             if (null == right2)
  299.             {
  300.                 right2 = line2.offsetLine(-widthGenerator.getWidth(lane2, 0.0) / 2, -widthGenerator.getWidth(lane2, 1.0) / 2);
  301.                 rightEdges.put(lane2, right2);
  302.             }
  303.         }

  304.         // Get list of all intersection fractions
  305.         SortedSet<Intersection> intersections = Intersection.getIntersectionList(left1, left2, 0);
  306.         intersections.addAll(Intersection.getIntersectionList(left1, right2, 1));
  307.         intersections.addAll(Intersection.getIntersectionList(right1, left2, 2));
  308.         intersections.addAll(Intersection.getIntersectionList(right1, right2, 3));

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

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

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

  444.     }

  445.     /**
  446.      * Build a merge conflict.
  447.      * @param lane1 Lane; lane 1
  448.      * @param f1start double; start fraction 1
  449.      * @param lane2 Lane; lane 2
  450.      * @param f2start double; start fraction 2
  451.      * @param simulator OtsSimulatorInterface; simulator
  452.      * @param widthGenerator WidthGenerator; width generator
  453.      * @param permitted boolean; conflict permitted by traffic control
  454.      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
  455.      * @throws OtsGeometryException in case of geometry exception
  456.      */
  457.     @SuppressWarnings("checkstyle:parameternumber")
  458.     private static void buildMergeConflict(final Lane lane1, final double f1start, final Lane lane2, final double f2start,
  459.             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator, final boolean permitted)
  460.             throws NetworkException, OtsGeometryException
  461.     {

  462.         // Determine lane end from direction
  463.         double f1end = 1.0;
  464.         double f2end = 1.0;

  465.         // Get locations and length
  466.         Length longitudinalPosition1 = lane1.getLength().times(f1start);
  467.         Length longitudinalPosition2 = lane2.getLength().times(f2start);
  468.         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
  469.         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));

  470.         // Get geometries
  471.         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
  472.         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);

  473.         // Determine conflict rule
  474.         ConflictRule conflictRule;
  475.         if (lane1.getLink().getPriority().isBusStop() || lane2.getLink().getPriority().isBusStop())
  476.         {
  477.             Throw.when(lane1.getLink().getPriority().isBusStop() && lane2.getLink().getPriority().isBusStop(),
  478.                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
  479.             // TODO: handle bus priority on the model side
  480.             conflictRule = new BusStopConflictRule(simulator, DefaultsNl.BUS);
  481.         }
  482.         else
  483.         {
  484.             conflictRule = new DefaultConflictRule();
  485.         }

  486.         // Make conflict
  487.         Conflict.generateConflictPair(ConflictType.MERGE, conflictRule, permitted, lane1, longitudinalPosition1, length1,
  488.                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);

  489.         numberMergeConflicts.incrementAndGet();
  490.     }

  491.     /**
  492.      * Build a split conflict.
  493.      * @param lane1 Lane; lane 1
  494.      * @param f1end double; end fraction 1
  495.      * @param lane2 Lane; lane 2
  496.      * @param f2end double; end fraction 2
  497.      * @param simulator OtsSimulatorInterface; simulator
  498.      * @param widthGenerator WidthGenerator; width generator
  499.      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
  500.      * @throws OtsGeometryException in case of geometry exception
  501.      */
  502.     @SuppressWarnings("checkstyle:parameternumber")
  503.     private static void buildSplitConflict(final Lane lane1, final double f1end, final Lane lane2, final double f2end,
  504.             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator)
  505.             throws NetworkException, OtsGeometryException
  506.     {

  507.         // Determine lane start from direction
  508.         double f1start = 0.0;
  509.         double f2start = 0.0;

  510.         // Get locations and length
  511.         Length longitudinalPosition1 = lane1.getLength().times(f1start);
  512.         Length longitudinalPosition2 = lane2.getLength().times(f2start);
  513.         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
  514.         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));

  515.         // Get geometries
  516.         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
  517.         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);

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

  521.         numberSplitConflicts.incrementAndGet();
  522.     }

  523.     /**
  524.      * Build a crossing conflict.
  525.      * @param lane1 Lane; lane 1
  526.      * @param f1start double; start fraction 1
  527.      * @param f1end double; end fraction 1
  528.      * @param lane2 Lane; lane 2
  529.      * @param f2start double; start fraction 2
  530.      * @param f2end double; end fraction 2
  531.      * @param simulator OtsSimulatorInterface; simulator
  532.      * @param widthGenerator WidthGenerator; width generator
  533.      * @param permitted boolean; conflict permitted by traffic control
  534.      * @throws NetworkException if the combination of conflict type and both conflict rules is not correct
  535.      * @throws OtsGeometryException in case of geometry exception
  536.      */
  537.     @SuppressWarnings("checkstyle:parameternumber")
  538.     private static void buildCrossingConflict(final Lane lane1, final double f1start, final double f1end, final Lane lane2,
  539.             final double f2start, final double f2end, final OtsSimulatorInterface simulator,
  540.             final WidthGenerator widthGenerator, final boolean permitted) throws NetworkException, OtsGeometryException
  541.     {

  542.         // Fractions may be in opposite direction, for the start location this needs to be correct
  543.         // Note: for geometry (real order, not considering direction) and length (absolute value) this does not matter
  544.         double f1startDirected;
  545.         double f2startDirected;
  546.         if (f1end < f1start)
  547.         {
  548.             f1startDirected = f1end;
  549.         }
  550.         else
  551.         {
  552.             f1startDirected = f1start;
  553.         }
  554.         if (f2end < f2start)
  555.         {
  556.             f2startDirected = f2end;
  557.         }
  558.         else
  559.         {
  560.             f2startDirected = f2start;
  561.         }

  562.         // Get locations and length
  563.         Length longitudinalPosition1 = lane1.getLength().times(f1startDirected);
  564.         Length longitudinalPosition2 = lane2.getLength().times(f2startDirected);
  565.         Length length1 = lane1.getLength().times(Math.abs(f1end - f1start));
  566.         Length length2 = lane2.getLength().times(Math.abs(f2end - f2start));

  567.         // Get geometries
  568.         Polygon2d geometry1 = getGeometry(lane1, f1start, f1end, widthGenerator);
  569.         Polygon2d geometry2 = getGeometry(lane2, f2start, f2end, widthGenerator);

  570.         // Determine conflict rule
  571.         ConflictRule conflictRule;
  572.         if (lane1.getLink().getPriority().isBusStop() || lane2.getLink().getPriority().isBusStop())
  573.         {
  574.             Throw.when(lane1.getLink().getPriority().isBusStop() && lane2.getLink().getPriority().isBusStop(),
  575.                     IllegalArgumentException.class, "Merge conflict between two links with bus stop priority not supported.");
  576.             // TODO: handle bus priority on the model side
  577.             conflictRule = new BusStopConflictRule(simulator, DefaultsNl.BUS);
  578.         }
  579.         else
  580.         {
  581.             conflictRule = new DefaultConflictRule();
  582.         }

  583.         // Make conflict
  584.         Conflict.generateConflictPair(ConflictType.CROSSING, conflictRule, permitted, lane1, longitudinalPosition1, length1,
  585.                 geometry1, lane2, longitudinalPosition2, length2, geometry2, simulator);

  586.         numberCrossConflicts.incrementAndGet();
  587.     }

  588.     /**
  589.      * Creates geometry for conflict.
  590.      * @param lane Lane; lane
  591.      * @param fStart double; longitudinal fraction of start
  592.      * @param fEnd double; longitudinal fraction of end
  593.      * @param widthGenerator WidthGenerator; width generator
  594.      * @return geometry for conflict
  595.      * @throws OtsGeometryException in case of geometry exception
  596.      */
  597.     private static Polygon2d getGeometry(final Lane lane, final double fStart, final double fEnd,
  598.             final WidthGenerator widthGenerator) throws OtsGeometryException
  599.     {
  600.         // extractFractional needs ordered fractions, irrespective of driving direction
  601.         double f1;
  602.         double f2;
  603.         if (fEnd > fStart)
  604.         {
  605.             f1 = fStart;
  606.             f2 = fEnd;
  607.         }
  608.         else
  609.         {
  610.             f1 = fEnd;
  611.             f2 = fStart;
  612.         }
  613.         if (Math.abs(f1 - f2) < 1E-8)
  614.         {
  615.             lane.getLink().getSimulator().getLogger().always()
  616.                     .debug("f1 (" + f1 + ") equals f2 (" + f2 + "); problematic lane is " + lane.toString());
  617.             // Fix up
  618.             if (f1 > 0)
  619.             {
  620.                 f1 = f1 - f1 / 1000;
  621.             }
  622.             else
  623.             {
  624.                 f2 = f2 + f2 / 1000;
  625.             }
  626.         }
  627.         OtsLine2d centerLine = lane.getCenterLine().extractFractional(f1, f2);
  628.         OtsLine2d left = centerLine.offsetLine(widthGenerator.getWidth(lane, f1) / 2, widthGenerator.getWidth(lane, f2) / 2);
  629.         OtsLine2d right =
  630.                 centerLine.offsetLine(-widthGenerator.getWidth(lane, f1) / 2, -widthGenerator.getWidth(lane, f2) / 2).reverse();
  631.         Point2d[] points = new Point2d[left.size() + right.size()];
  632.         System.arraycopy(left.getPoints(), 0, points, 0, left.size());
  633.         System.arraycopy(right.getPoints(), 0, points, left.size(), right.size());
  634.         return new Polygon2d(points);
  635.     }

  636.     /**
  637.      * Intersection holds two fractions where two lines have crossed. There is also a combo to identify which lines have been
  638.      * used to find the intersection.
  639.      * <p>
  640.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  641.      * <br>
  642.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  643.      * </p>
  644.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  645.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  646.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  647.      */
  648.     private static class Intersection implements Comparable<Intersection>
  649.     {

  650.         /** Fraction on lane 1. */
  651.         private final double fraction1;

  652.         /** Fraction on lane 2. */
  653.         private final double fraction2;

  654.         /** Edge combination number. */
  655.         private final int combo;

  656.         /**
  657.          * @param fraction1 double; fraction on lane 1
  658.          * @param fraction2 double; fraction on lane 1
  659.          * @param combo int; edge combination number
  660.          */
  661.         Intersection(final double fraction1, final double fraction2, final int combo)
  662.         {
  663.             this.fraction1 = fraction1;
  664.             this.fraction2 = fraction2;
  665.             this.combo = combo;
  666.         }

  667.         /**
  668.          * @return fraction1.
  669.          */
  670.         public final double getFraction1()
  671.         {
  672.             return this.fraction1;
  673.         }

  674.         /**
  675.          * @return fraction2.
  676.          */
  677.         public final double getFraction2()
  678.         {
  679.             return this.fraction2;
  680.         }

  681.         /**
  682.          * @return combo.
  683.          */
  684.         public final int getCombo()
  685.         {
  686.             return this.combo;
  687.         }

  688.         /** {@inheritDoc} */
  689.         @Override
  690.         public int compareTo(final Intersection o)
  691.         {
  692.             int out = Double.compare(this.fraction1, o.fraction1);
  693.             if (out != 0)
  694.             {
  695.                 return out;
  696.             }
  697.             out = Double.compare(this.fraction2, o.fraction2);
  698.             if (out != 0)
  699.             {
  700.                 return out;
  701.             }
  702.             return Integer.compare(this.combo, o.combo);
  703.         }

  704.         /** {@inheritDoc} */
  705.         @Override
  706.         public int hashCode()
  707.         {
  708.             final int prime = 31;
  709.             int result = 1;
  710.             result = prime * result + this.combo;
  711.             long temp;
  712.             temp = Double.doubleToLongBits(this.fraction1);
  713.             result = prime * result + (int) (temp ^ (temp >>> 32));
  714.             temp = Double.doubleToLongBits(this.fraction2);
  715.             result = prime * result + (int) (temp ^ (temp >>> 32));
  716.             return result;
  717.         }

  718.         /** {@inheritDoc} */
  719.         @Override
  720.         public boolean equals(final Object obj)
  721.         {
  722.             if (this == obj)
  723.             {
  724.                 return true;
  725.             }
  726.             if (obj == null)
  727.             {
  728.                 return false;
  729.             }
  730.             if (getClass() != obj.getClass())
  731.             {
  732.                 return false;
  733.             }
  734.             Intersection other = (Intersection) obj;
  735.             if (this.combo != other.combo)
  736.             {
  737.                 return false;
  738.             }
  739.             if (Double.doubleToLongBits(this.fraction1) != Double.doubleToLongBits(other.fraction1))
  740.             {
  741.                 return false;
  742.             }
  743.             if (Double.doubleToLongBits(this.fraction2) != Double.doubleToLongBits(other.fraction2))
  744.             {
  745.                 return false;
  746.             }
  747.             return true;
  748.         }

  749.         /**
  750.          * Returns a set of intersections, sorted by the fraction on line 1.
  751.          * @param line1 OtsLine2d; line 1
  752.          * @param line2 OtsLine2d; line 2
  753.          * @param combo int; edge combination number
  754.          * @return set of intersections, sorted by the fraction on line 1
  755.          * @throws OtsGeometryException in case of geometry exception
  756.          */
  757.         public static SortedSet<Intersection> getIntersectionList(final OtsLine2d line1, final OtsLine2d line2, final int combo)
  758.                 throws OtsGeometryException
  759.         {
  760.             SortedSet<Intersection> out = new TreeSet<>();
  761.             double cumul1 = 0.0;
  762.             Point2d start1 = null;
  763.             Point2d end1 = line1.get(0);
  764.             for (int i = 0; i < line1.size() - 1; i++)
  765.             {
  766.                 start1 = end1;
  767.                 end1 = line1.get(i + 1);

  768.                 double cumul2 = 0.0;
  769.                 Point2d start2 = null;
  770.                 Point2d end2 = line2.get(0);

  771.                 for (int j = 0; j < line2.size() - 1; j++)
  772.                 {
  773.                     start2 = end2;
  774.                     end2 = line2.get(j + 1);

  775.                     Point2d p = Point2d.intersectionOfLineSegments(start1, end1, start2, end2);
  776.                     if (p != null)
  777.                     {
  778.                         // Segments intersect
  779.                         double dx = p.x - start1.x;
  780.                         double dy = p.y - start1.y;
  781.                         double length1 = cumul1 + Math.hypot(dx, dy);
  782.                         dx = p.x - start2.x;
  783.                         dy = p.y - start2.y;
  784.                         double length2 = cumul2 + Math.hypot(dx, dy);
  785.                         out.add(new Intersection(length1 / line1.getLength().si, length2 / line2.getLength().si, combo));
  786.                     }

  787.                     double dx = end2.x - start2.x;
  788.                     double dy = end2.y - start2.y;
  789.                     cumul2 += Math.hypot(dx, dy);
  790.                 }

  791.                 double dx = end1.x - start1.x;
  792.                 double dy = end1.y - start1.y;
  793.                 cumul1 += Math.hypot(dx, dy);
  794.             }

  795.             return out;
  796.         }

  797.         /** {@inheritDoc} */
  798.         @Override
  799.         public String toString()
  800.         {
  801.             return "Intersection [fraction1=" + this.fraction1 + ", fraction2=" + this.fraction2 + ", combo=" + this.combo
  802.                     + "]";
  803.         }

  804.     }

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

  818.         /**
  819.          * Returns the begin width of this lane.
  820.          * @param lane Lane; lane
  821.          * @param fraction double; fraction
  822.          * @return begin width of this lane
  823.          */
  824.         double getWidth(Lane lane, double fraction);

  825.     }

  826.     /**
  827.      * Generator with fixed width.
  828.      * <p>
  829.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  830.      * <br>
  831.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  832.      * </p>
  833.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  834.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  835.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  836.      */
  837.     public static class FixedWidthGenerator implements WidthGenerator
  838.     {

  839.         /** Fixed width. */
  840.         private final double width;

  841.         /**
  842.          * Constructor with width.
  843.          * @param width Length; width
  844.          */
  845.         public FixedWidthGenerator(final Length width)
  846.         {
  847.             this.width = width.si;
  848.         }

  849.         /** {@inheritDoc} */
  850.         @Override
  851.         public final double getWidth(final Lane lane, final double fraction)
  852.         {
  853.             return this.width;
  854.         }

  855.         /** {@inheritDoc} */
  856.         @Override
  857.         public final String toString()
  858.         {
  859.             return "FixedWidthGenerator [width=" + this.width + "]";
  860.         }

  861.     }

  862.     /**
  863.      * Generator with width factor on actual lane width.
  864.      * <p>
  865.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  866.      * <br>
  867.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  868.      * </p>
  869.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  870.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  871.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  872.      */
  873.     public static class RelativeWidthGenerator implements WidthGenerator
  874.     {

  875.         /** Width factor. */
  876.         private final double factor;

  877.         /**
  878.          * Constructor with width factor.
  879.          * @param factor double; width factor
  880.          */
  881.         public RelativeWidthGenerator(final double factor)
  882.         {
  883.             this.factor = factor;
  884.         }

  885.         /** {@inheritDoc} */
  886.         @Override
  887.         public final double getWidth(final Lane lane, final double fraction)
  888.         {
  889.             return lane.getWidth(fraction).si * this.factor;
  890.         }

  891.         /** {@inheritDoc} */
  892.         @Override
  893.         public final String toString()
  894.         {
  895.             return "RelativeWidthGenerator [factor=" + this.factor + "]";
  896.         }

  897.     }

  898.     /* ******************************************************************************************************************** */
  899.     /* ******************************************************************************************************************** */
  900.     /* ******************************************************************************************************************** */
  901.     /* ********************************************* PARALLEL IMPLEMENTATION ********************************************** */
  902.     /* ******************************************************************************************************************** */
  903.     /* ******************************************************************************************************************** */
  904.     /* ******************************************************************************************************************** */

  905.     /**
  906.      * Build conflicts on network; parallel implementation.
  907.      * @param network RoadNetwork; network
  908.      * @param simulator OtsSimulatorInterface; simulator
  909.      * @param widthGenerator WidthGenerator; width generator
  910.      * @throws OtsGeometryException in case of geometry exception
  911.      */
  912.     public static void buildConflictsParallel(final RoadNetwork network, final OtsSimulatorInterface simulator,
  913.             final WidthGenerator widthGenerator) throws OtsGeometryException
  914.     {
  915.         buildConflictsParallel(network, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
  916.     }

  917.     /**
  918.      * Build conflicts on network; parallel implementation.
  919.      * @param network RoadNetwork; network
  920.      * @param simulator OtsSimulatorInterface; simulator
  921.      * @param widthGenerator WidthGenerator; width generator
  922.      * @param ignoreList LaneCombinationList; lane combinations to ignore
  923.      * @param permittedList LaneCombinationList; lane combinations that are permitted by traffic control
  924.      * @throws OtsGeometryException in case of geometry exception
  925.      */
  926.     public static void buildConflictsParallel(final RoadNetwork network, final OtsSimulatorInterface simulator,
  927.             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
  928.             throws OtsGeometryException
  929.     {
  930.         buildConflictsParallelBig(getLanes(network), simulator, widthGenerator, ignoreList, permittedList);
  931.     }

  932.     /**
  933.      * Build conflicts on list of lanes; parallel implementation.
  934.      * @param lanes List&lt;Lane&gt;; lanes
  935.      * @param simulator OtsSimulatorInterface; simulator
  936.      * @param widthGenerator WidthGenerator; width generator
  937.      * @throws OtsGeometryException in case of geometry exception
  938.      */
  939.     public static void buildConflictsParallel(final List<Lane> lanes, final OtsSimulatorInterface simulator,
  940.             final WidthGenerator widthGenerator) throws OtsGeometryException
  941.     {
  942.         buildConflictsParallelBig(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList());
  943.     }

  944.     /**
  945.      * Build conflicts on list of lanes; parallel implementation. Small jobs.
  946.      * @param lanes List&lt;Lane&gt;; list of Lanes
  947.      * @param simulator OtsSimulatorInterface; the simulator
  948.      * @param widthGenerator WidthGenerator; the width generator
  949.      * @param ignoreList LaneCombinationList; lane combinations to ignore
  950.      * @param permittedList LaneCombinationList; lane combinations that are permitted by traffic control
  951.      * @throws OtsGeometryException in case of geometry exception
  952.      */
  953.     public static void buildConflictsParallelSmall(final List<Lane> lanes, final OtsSimulatorInterface simulator,
  954.             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
  955.             throws OtsGeometryException
  956.     {
  957.         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
  958.         System.out.println("PARALLEL GENERATING OF CONFLICTS (SMALL JOBS). " + totalCombinations + " COMBINATIONS");
  959.         long lastReported = 0;
  960.         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
  961.         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();

  962.         // make a threadpool and execute buildConflicts for all records
  963.         int cores = Runtime.getRuntime().availableProcessors();
  964.         System.out.println("USING " + cores + " CORES");
  965.         ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(cores);
  966.         AtomicInteger numberOfJobs = new AtomicInteger(0);
  967.         final int maxqueue = 2 * cores;

  968.         for (int i = 0; i < lanes.size(); i++)
  969.         {
  970.             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i - 1)) / 2;
  971.             if (combinationsDone / 100000000 > lastReported)
  972.             {
  973.                 simulator.getLogger().always()
  974.                         .debug(String.format(
  975.                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
  976.                                         + "conflicts, %d crossing conflicts)",
  977.                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
  978.                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
  979.                 lastReported = combinationsDone / 100000000;
  980.             }
  981.             Lane lane1 = lanes.get(i);
  982.             Set<Lane> down1 = lane1.nextLanes(null);
  983.             Set<Lane> up1 = lane1.prevLanes(null);

  984.             for (int j = i + 1; j < lanes.size(); j++)
  985.             {
  986.                 Lane lane2 = lanes.get(j);
  987.                 if (ignoreList.contains(lane1, lane2))
  988.                 {
  989.                     continue;
  990.                 }
  991.                 // Quick contour check, skip if non-overlapping envelopes
  992.                 try
  993.                 {
  994.                     if (!lane1.getContour().intersects(lane2.getContour()))
  995.                     {
  996.                         continue;
  997.                     }
  998.                 }
  999.                 catch (Exception e)
  1000.                 {
  1001.                     System.err.println("Contour problem - lane1 = [" + lane1.getFullId() + "], lane2 = [" + lane2.getFullId()
  1002.                             + "]; skipped");
  1003.                     continue;
  1004.                 }

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

  1006.                 while (numberOfJobs.get() > maxqueue) // keep max maxqueue jobs in the pool
  1007.                 {
  1008.                     try
  1009.                     {
  1010.                         Thread.sleep(1);
  1011.                     }
  1012.                     catch (InterruptedException exception)
  1013.                     {
  1014.                         // ignore
  1015.                     }
  1016.                 }
  1017.                 numberOfJobs.incrementAndGet();
  1018.                 Set<Lane> down2 = lane2.nextLanes(null);
  1019.                 Set<Lane> up2 = lane2.prevLanes(null);
  1020.                 ConflictBuilderRecordSmall cbr = new ConflictBuilderRecordSmall(lane1, down1, up1, lane2, down2, up2, permitted,
  1021.                         simulator, widthGenerator, leftEdges, rightEdges);
  1022.                 executor.execute(new CbrTaskSmall(numberOfJobs, cbr));
  1023.             }
  1024.         }

  1025.         long time = System.currentTimeMillis();
  1026.         // wait max 60 sec for last maxqueue jobs
  1027.         while (numberOfJobs.get() > 0 && System.currentTimeMillis() - time < 60000)
  1028.         {
  1029.             try
  1030.             {
  1031.                 Thread.sleep(10);
  1032.             }
  1033.             catch (InterruptedException exception)
  1034.             {
  1035.                 // ignore
  1036.             }
  1037.         }

  1038.         executor.shutdown();
  1039.         while (!executor.isTerminated())
  1040.         {
  1041.             try
  1042.             {
  1043.                 Thread.sleep(1);
  1044.             }
  1045.             catch (InterruptedException exception)
  1046.             {
  1047.                 // ignore
  1048.             }
  1049.         }

  1050.         simulator.getLogger().always()
  1051.                 .debug(String.format(
  1052.                         "generating conflicts complete (generated %d merge conflicts, %d split "
  1053.                                 + "conflicts, %d crossing conflicts)",
  1054.                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
  1055.     }

  1056.     /**
  1057.      * Build conflicts on list of lanes; parallel implementation. Big jobs.
  1058.      * @param lanes List&lt;Lane&gt;; list of Lanes
  1059.      * @param simulator OtsSimulatorInterface; the simulator
  1060.      * @param widthGenerator WidthGenerator; the width generator
  1061.      * @param ignoreList LaneCombinationList; lane combinations to ignore
  1062.      * @param permittedList LaneCombinationList; lane combinations that are permitted by traffic control
  1063.      * @throws OtsGeometryException in case of geometry exception
  1064.      */
  1065.     public static void buildConflictsParallelBig(final List<Lane> lanes, final OtsSimulatorInterface simulator,
  1066.             final WidthGenerator widthGenerator, final LaneCombinationList ignoreList, final LaneCombinationList permittedList)
  1067.             throws OtsGeometryException
  1068.     {
  1069.         long totalCombinations = ((long) lanes.size()) * ((long) lanes.size() - 1) / 2;
  1070.         System.out.println("PARALLEL GENERATING OF CONFLICTS (BIG JOBS). " + totalCombinations + " COMBINATIONS");
  1071.         long lastReported = 0;
  1072.         Map<Lane, OtsLine2d> leftEdges = new LinkedHashMap<>();
  1073.         Map<Lane, OtsLine2d> rightEdges = new LinkedHashMap<>();

  1074.         // make a threadpool and execute buildConflicts for all records
  1075.         int cores = Runtime.getRuntime().availableProcessors();
  1076.         System.out.println("USING " + cores + " CORES");
  1077.         ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(cores);
  1078.         AtomicInteger numberOfJobs = new AtomicInteger(0);
  1079.         final int maxqueue = 200;

  1080.         for (int i = 0; i < lanes.size(); i++)
  1081.         {
  1082.             long combinationsDone = totalCombinations - ((long) (lanes.size() - i)) * ((long) (lanes.size() - i - 1)) / 2;
  1083.             if (combinationsDone / 100000000 > lastReported)
  1084.             {
  1085.                 simulator.getLogger().always()
  1086.                         .debug(String.format(
  1087.                                 "generating conflicts at %.0f%% (generated %d merge conflicts, %d split "
  1088.                                         + "conflicts, %d crossing conflicts)",
  1089.                                 100.0 * combinationsDone / totalCombinations, numberMergeConflicts.get(),
  1090.                                 numberSplitConflicts.get(), numberCrossConflicts.get()));
  1091.                 lastReported = combinationsDone / 100000000;
  1092.             }

  1093.             while (numberOfJobs.get() > maxqueue) // keep max maxqueue jobs in the pool
  1094.             {
  1095.                 try
  1096.                 {
  1097.                     Thread.sleep(0, 10);
  1098.                 }
  1099.                 catch (InterruptedException exception)
  1100.                 {
  1101.                     // ignore
  1102.                 }
  1103.             }
  1104.             numberOfJobs.incrementAndGet();

  1105.             ConflictBuilderRecordBig cbr = new ConflictBuilderRecordBig(i, lanes, ignoreList, permittedList, simulator,
  1106.                     widthGenerator, leftEdges, rightEdges);
  1107.             executor.execute(new CbrTaskBig(numberOfJobs, cbr));

  1108.         }

  1109.         long time = System.currentTimeMillis();
  1110.         // wait max 60 sec for last maxqueue jobs
  1111.         while (numberOfJobs.get() > 0 && System.currentTimeMillis() - time < 60000)
  1112.         {
  1113.             try
  1114.             {
  1115.                 Thread.sleep(10);
  1116.             }
  1117.             catch (InterruptedException exception)
  1118.             {
  1119.                 // ignore
  1120.             }
  1121.         }

  1122.         executor.shutdown();
  1123.         while (!executor.isTerminated())
  1124.         {
  1125.             try
  1126.             {
  1127.                 Thread.sleep(1);
  1128.             }
  1129.             catch (InterruptedException exception)
  1130.             {
  1131.                 // ignore
  1132.             }
  1133.         }

  1134.         simulator.getLogger().always()
  1135.                 .debug(String.format(
  1136.                         "generating conflicts complete (generated %d merge conflicts, %d split "
  1137.                                 + "conflicts, %d crossing conflicts)",
  1138.                         numberMergeConflicts.get(), numberSplitConflicts.get(), numberCrossConflicts.get()));
  1139.     }

  1140.     /**
  1141.      * Build conflicts on network using only the groups of links that have been identified as candidates with conflicts;
  1142.      * parallel implementation.
  1143.      * @param network RoadNetwork; network
  1144.      * @param conflictCandidateMap Map&lt;String, Set&lt;Link&gt;&gt;; the map of the conflicting links to implement
  1145.      * @param simulator OtsSimulatorInterface; simulator
  1146.      * @param widthGenerator WidthGenerator; width generator
  1147.      * @throws OtsGeometryException in case of geometry exception
  1148.      */
  1149.     public static void buildConflictsParallel(final RoadNetwork network, final Map<String, Set<Link>> conflictCandidateMap,
  1150.             final OtsSimulatorInterface simulator, final WidthGenerator widthGenerator) throws OtsGeometryException
  1151.     {
  1152.         for (String conflictId : conflictCandidateMap.keySet())
  1153.         {
  1154.             // System.out.println(conflictId);
  1155.             List<Lane> lanes = new ArrayList<>();
  1156.             for (Link link : conflictCandidateMap.get(conflictId))
  1157.             {
  1158.                 if (link instanceof CrossSectionLink)
  1159.                 {
  1160.                     for (CrossSectionElement element : ((CrossSectionLink) link).getCrossSectionElementList())
  1161.                     {
  1162.                         if (element instanceof Lane lane && !(element instanceof Shoulder))
  1163.                         {
  1164.                             lanes.add((Lane) element);
  1165.                         }
  1166.                     }
  1167.                 }
  1168.             }
  1169.             // TODO: make parallel
  1170.             // simulator.getLogger().setAllLogLevel(Level.WARNING);
  1171.             buildConflicts(lanes, simulator, widthGenerator, new LaneCombinationList(), new LaneCombinationList(), conflictId);
  1172.             simulator.getLogger().setAllLogLevel(Level.DEBUG);
  1173.         }
  1174.     }

  1175.     /**
  1176.      * Small conflict builder task. A small task is finding all conflicts between two lanes.
  1177.      * <p>
  1178.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  1179.      * <br>
  1180.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  1181.      * </p>
  1182.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  1183.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  1184.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  1185.      */
  1186.     static class CbrTaskSmall implements Runnable
  1187.     {
  1188.         /** Small conflict builder record. */
  1189.         final ConflictBuilderRecordSmall cbr;

  1190.         /** Number of jobs to do. */
  1191.         final AtomicInteger nrOfJobs;

  1192.         /**
  1193.          * Constructor.
  1194.          * @param nrOfJobs AtomicInteger; number of jobs to do.
  1195.          * @param cbr ConflictBuilderRecordSmall; the record to execute.
  1196.          */
  1197.         CbrTaskSmall(final AtomicInteger nrOfJobs, final ConflictBuilderRecordSmall cbr)
  1198.         {
  1199.             this.nrOfJobs = nrOfJobs;
  1200.             this.cbr = cbr;
  1201.         }

  1202.         /** {@inheritDoc} */
  1203.         @Override
  1204.         public void run()
  1205.         {
  1206.             try
  1207.             {
  1208.                 buildConflicts(this.cbr.lane1, this.cbr.down1, this.cbr.up1, this.cbr.lane2, this.cbr.down2, this.cbr.up2,
  1209.                         this.cbr.permitted, this.cbr.simulator, this.cbr.widthGenerator, this.cbr.leftEdges,
  1210.                         this.cbr.rightEdges, false, null);
  1211.             }
  1212.             catch (NetworkException | OtsGeometryException ne)
  1213.             {
  1214.                 throw new RuntimeException("Conflict build with bad combination of types / rules.", ne);
  1215.             }
  1216.             this.nrOfJobs.decrementAndGet();
  1217.         }
  1218.     }

  1219.     /**
  1220.      * Small conflict builder record. Small means this holds the information to create conflicts between two lanes.
  1221.      * <p>
  1222.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  1223.      * <br>
  1224.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  1225.      * </p>
  1226.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  1227.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  1228.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  1229.      * @param lane1 Lane; lane 1
  1230.      * @param down1 Set&lt;Lane&gt;; downstream lanes 1
  1231.      * @param up1 Set&lt;Lane&gt;; upstream lanes 1
  1232.      * @param lane2 Lane; lane 2
  1233.      * @param down2 Set&lt;Lane&gt;; downstream lane 2
  1234.      * @param up2 Set&lt;Lane&gt;; upstream lanes 2
  1235.      * @param permitted boolean; conflict permitted by traffic control
  1236.      * @param simulator OtsSimulatorInterface; simulator
  1237.      * @param widthGenerator WidthGenerator; width generator
  1238.      * @param leftEdges Map&lt;Lane, OtsLine2d&gt;; cache of left edge lines
  1239.      * @param rightEdges Map&lt;Lane, OtsLine2d&gt;; cache of right edge lines
  1240.      */
  1241.     @SuppressWarnings("checkstyle:visibilitymodifier")
  1242.     static record ConflictBuilderRecordSmall(Lane lane1, Set<Lane> down1, Set<Lane> up1, Lane lane2, Set<Lane> down2,
  1243.             Set<Lane> up2, boolean permitted, OtsSimulatorInterface simulator, WidthGenerator widthGenerator,
  1244.             Map<Lane, OtsLine2d> leftEdges, Map<Lane, OtsLine2d> rightEdges)
  1245.     {
  1246.     }

  1247.     /**
  1248.      * Large conflict builder task. A large task is finding all conflicts between one particular lane, and all lanes further in
  1249.      * a list.
  1250.      * <p>
  1251.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  1252.      * <br>
  1253.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  1254.      * </p>
  1255.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  1256.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  1257.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  1258.      */
  1259.     static class CbrTaskBig implements Runnable
  1260.     {
  1261.         /** Big conflict builder record. */
  1262.         final ConflictBuilderRecordBig cbr;

  1263.         /** Number of jobs to do. */
  1264.         final AtomicInteger nrOfJobs;

  1265.         /**
  1266.          * Constructor.
  1267.          * @param nrOfJobs AtomicInteger; number of jobs to do.
  1268.          * @param cbr ConflictBuilderRecordBig; the record to execute.
  1269.          */
  1270.         CbrTaskBig(final AtomicInteger nrOfJobs, final ConflictBuilderRecordBig cbr)
  1271.         {
  1272.             this.nrOfJobs = nrOfJobs;
  1273.             this.cbr = cbr;
  1274.         }

  1275.         /** {@inheritDoc} */
  1276.         @Override
  1277.         public void run()
  1278.         {
  1279.             try
  1280.             {
  1281.                 Lane lane1 = this.cbr.lanes.get(this.cbr.starti);
  1282.                 Set<Lane> up1 = lane1.prevLanes(null);
  1283.                 Set<Lane> down1 = lane1.nextLanes(null);
  1284.                 for (int j = this.cbr.starti + 1; j < this.cbr.lanes.size(); j++)
  1285.                 {
  1286.                     Lane lane2 = this.cbr.lanes.get(j);
  1287.                     if (this.cbr.ignoreList.contains(lane1, lane2))
  1288.                     {
  1289.                         continue;
  1290.                     }
  1291.                     // Quick contour check, skip if non-overlapping envelopes
  1292.                     try
  1293.                     {
  1294.                         if (!lane1.getContour().intersects(lane2.getContour()))
  1295.                         {
  1296.                             continue;
  1297.                         }
  1298.                     }
  1299.                     catch (Exception e)
  1300.                     {
  1301.                         System.err.println("Contour problem - lane1 = [" + lane1.getFullId() + "], lane2 = ["
  1302.                                 + lane2.getFullId() + "]; skipped");
  1303.                         continue;
  1304.                     }

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

  1306.                     Set<Lane> down2 = lane2.nextLanes(null);
  1307.                     Set<Lane> up2 = lane2.prevLanes(null);

  1308.                     try
  1309.                     {
  1310.                         buildConflicts(lane1, down1, up1, lane2, down2, up2, permitted, this.cbr.simulator,
  1311.                                 this.cbr.widthGenerator, this.cbr.leftEdges, this.cbr.rightEdges, false, null);
  1312.                     }
  1313.                     catch (NetworkException | OtsGeometryException ne)
  1314.                     {
  1315.                         lane2.getLink().getSimulator().getLogger().always().error(ne,
  1316.                                 "Conflict build with bad combination of types / rules.");
  1317.                     }
  1318.                 }

  1319.             }
  1320.             catch (Exception e)
  1321.             {
  1322.                 e.printStackTrace();
  1323.             }
  1324.             this.nrOfJobs.decrementAndGet();
  1325.         }
  1326.     }

  1327.     /**
  1328.      * Big conflict builder record. Big means this holds the information to create conflicts between one particular lane, and
  1329.      * all lanes further in a list.
  1330.      * <p>
  1331.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  1332.      * <br>
  1333.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  1334.      * </p>
  1335.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  1336.      * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  1337.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  1338.      * @param starti int; the start index
  1339.      * @param lanes List of lanes
  1340.      * @param ignoreList list of lane combinations to ignore
  1341.      * @param permittedList list of lane combinations to permit
  1342.      * @param simulator OtsSimulatorInterface; simulator
  1343.      * @param widthGenerator WidthGenerator; width generator
  1344.      * @param leftEdges Map&lt;Lane, OtsLine2d&gt;; cache of left edge lines
  1345.      * @param rightEdges Map&lt;Lane, OtsLine2d&gt;; cache of right edge lines
  1346.      */
  1347.     @SuppressWarnings("checkstyle:visibilitymodifier")
  1348.     static record ConflictBuilderRecordBig(int starti, List<Lane> lanes, LaneCombinationList ignoreList,
  1349.             LaneCombinationList permittedList, OtsSimulatorInterface simulator, WidthGenerator widthGenerator,
  1350.             Map<Lane, OtsLine2d> leftEdges, Map<Lane, OtsLine2d> rightEdges)
  1351.     {
  1352.     }

  1353. }