// Copyright 2010-2019 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using Google.OrTools.Sat; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; /// /// This model solves a multicommodity mono-routing problem with /// capacity constraints and a max usage cost structure. This means /// that given a graph with capacity on edges, and a set of demands /// (source, destination, traffic), the goal is to assign one unique /// path for each demand such that the cost is minimized. The cost is /// defined by the maximum ratio utilization (traffic/capacity) for all /// arcs. There is also a penalty associated with an traffic of an arc /// being above the comfort zone, 85% of the capacity by default. /// Please note that constraint programming is well suited here because /// we cannot have multiple active paths for a single demand. /// Otherwise, a approach based on a linear solver is a better match. /// A random problem generator is also included. /// public class NetworkRoutingSat { private static int clients = 0; // Number of network clients nodes. If equal to zero, then all backbones nodes are also client nodes. private static int backbones = 0; // "Number of backbone nodes" private static int demands = 0; // "Number of network demands." private static int trafficMin = 0; // "Min traffic of a demand." private static int trafficMax = 0; // "Max traffic of a demand." private static int minClientDegree = 0; //"Min number of connections from a client to the backbone." private static int maxClientDegree = 0; //"Max number of connections from a client to the backbone." private static int minBackboneDegree = 0; //"Min number of connections from a backbone node to the rest of the backbone nodes." private static int maxBackboneDegree = 0; // "Max number of connections from a backbone node to the rest of the backbone nodes." private static int maxCapacity = 0; //"Max traffic on any arc." private static int fixedChargeCost = 0; //"Fixed charged cost when using an arc." private static int seed = 0; //"Random seed" private static double comfortZone = 0.85; // "Above this limit in 1/1000th, the link is said to be congested." private static int extraHops = 6; // "When creating all paths for a demand, we look at paths with maximum length 'shortest path + extra_hops'" private static int maxPaths = 1200; //"Max number of possible paths for a demand." private static bool printModel = false; //"Print details of the model." private static string parameters = ""; // "Sat parameters." private const long kDisconnectedDistance = -1L; static void Main(string[] args) { readArgs(args); var builder = new NetworkRoutingDataBuilder(); var data = builder.BuildModelFromParameters(clients, backbones, demands, trafficMin, trafficMax, minClientDegree, maxClientDegree, minBackboneDegree, maxBackboneDegree, maxCapacity, fixedChargeCost, seed); var solver = new NetworkRoutingSolver(); solver.Init(data, extraHops, maxPaths); var cost = solver.Solve(); Console.WriteLine($"Final cost = {cost}"); } private static void readArgs(string[] args) { readInt(args, ref clients, nameof(clients)); readInt(args, ref backbones, nameof(backbones)); readInt(args, ref demands, nameof(demands)); readInt(args, ref trafficMin, nameof(trafficMin)); readInt(args, ref trafficMax, nameof(trafficMax)); readInt(args, ref minClientDegree, nameof(minClientDegree)); readInt(args, ref maxClientDegree, nameof(maxClientDegree)); readInt(args, ref minBackboneDegree, nameof(minBackboneDegree)); readInt(args, ref maxBackboneDegree, nameof(maxBackboneDegree)); readInt(args, ref maxCapacity, nameof(maxCapacity)); readInt(args, ref fixedChargeCost, nameof(fixedChargeCost)); readInt(args, ref seed, nameof(seed)); readDouble(args, ref comfortZone, nameof(comfortZone)); readInt(args, ref extraHops, nameof(extraHops)); readInt(args, ref maxPaths, nameof(maxPaths)); readBoolean(args, ref printModel, nameof(printModel)); readString(args, ref parameters, nameof(parameters)); } private static void readDouble(string[] args, ref double setting, string arg) { var v = getArgValue(args, arg); if (v.IsSet) { setting = Convert.ToDouble(v.Value); } } private static void readInt(string[] args, ref int setting, string arg) { var v = getArgValue(args, arg); if (v.IsSet) { setting = Convert.ToInt32(v.Value); } } private static void readBoolean(string[] args, ref bool setting, string arg) { var v = getArgValue(args, arg); if (v.IsSet) { setting = Convert.ToBoolean(v.Value); } } private static void readString(string[] args, ref string setting, string arg) { var v = getArgValue(args, arg); if (v.IsSet) { setting = v.Value; } } private static (bool IsSet, string Value) getArgValue(string[] args, string arg) { string lookup = $"--{arg}="; var item = args.FirstOrDefault(x => x.StartsWith(lookup)); if (string.IsNullOrEmpty(item)) { return (false, string.Empty); } return (true, item.Replace(lookup, string.Empty)); } /// /// Contains problem data. It assumes capacities are symmetrical: /// (capacity(i->j) == capacity(j->i)). /// Demands are not symmetrical. /// public class NetworkRoutingData { private Dictionary<(int source, int destination), int> _arcs = new Dictionary<(int source, int destination), int>(); private Dictionary<(int node1, int node2), int> _demands = new Dictionary<(int node1, int node2), int>(); public int NumberOfNodes { get; set; } = -1; public int NumberOfArcs { get { return _arcs.Count(); } } public int NumberOfDemands { get { return _demands.Count(); } } public int MaximumCapacity { get; set; } = -1; public int FixedChargeCost { get; set; } = -1; public string Name { get; set; } = string.Empty; public void AddDemand(int source, int destination, int traffic) { var pair = (source, destination); if(!_demands.ContainsKey(pair)) _demands.Add(pair, traffic); } public void AddArc(int node1, int node2, int capacity) { _arcs.Add((Math.Min(node1, node2), Math.Max(node1, node2)), capacity); } public int Demand(int source, int destination) { var pair = (source, destination); if (_demands.TryGetValue(pair, out var demand)) return demand; return 0; } public int Capacity(int node1, int node2) { var pair = (Math.Min(node1, node2), Math.Max(node1, node2)); if (_arcs.TryGetValue(pair, out var capacity)) return capacity; return 0; } } /// /// Random generator of problem. This generator creates a random /// problem. This problem uses a special topology. There are /// 'numBackbones' nodes and 'numClients' nodes. if 'numClients' is /// null, then all backbones nodes are also client nodes. All traffic /// originates and terminates in client nodes. Each client node is /// connected to 'minClientDegree' - 'maxClientDegree' backbone /// nodes. Each backbone node is connected to 'minBackboneDegree' - /// 'maxBackboneDegree' other backbone nodes. There are 'numDemands' /// demands, with a traffic between 'trafficMin' and 'trafficMax'. /// Each arc has a capacity of 'maxCapacity'. Using an arc incurs a /// fixed cost of 'fixedChargeCost'. /// public class NetworkRoutingDataBuilder { private List> _network; private List _degrees; private Random _random; public NetworkRoutingData BuildModelFromParameters(int numClients, int numBackbones, int numDemands, int trafficMin, int trafficMax, int minClientDegree, int maxClientDegree, int minBackboneDegree, int maxBackboneDegree, int maxCapacity, int fixedChargeCost, int seed) { Debug.Assert(numBackbones >= 1); Debug.Assert(numClients >= 0); Debug.Assert(numDemands >= 1); Debug.Assert(numDemands <= (numClients == 0 ? numBackbones * numBackbones : numClients * numBackbones)); Debug.Assert(maxClientDegree >= minClientDegree); Debug.Assert(maxBackboneDegree >= minBackboneDegree); Debug.Assert(trafficMax >= 1); Debug.Assert(trafficMax >= trafficMin); Debug.Assert(trafficMin >= 1); Debug.Assert(maxBackboneDegree >= 2); Debug.Assert(maxClientDegree >= 2); Debug.Assert(maxClientDegree <= numBackbones); Debug.Assert(maxBackboneDegree <= numBackbones); Debug.Assert(maxCapacity >= 1); int size = numBackbones + numClients; initData(size, seed); buildGraph(numClients, numBackbones, minClientDegree, maxClientDegree, minBackboneDegree, maxBackboneDegree); NetworkRoutingData data = new NetworkRoutingData(); createDemands(numClients, numBackbones, numDemands, trafficMin, trafficMax, data); fillData(numClients, numBackbones, numDemands, trafficMin, trafficMax, minClientDegree, maxClientDegree, minBackboneDegree, maxBackboneDegree, maxCapacity, fixedChargeCost, seed, data); return data; } private void initData(int size, int seed) { _network = new List>(size); for (int i = 0; i < size; i++) { _network.Add(new List(size)); for (int j = 0; j < size; j++) { _network[i].Add(false); } } _degrees = new List(size); for (int i = 0; i < size; i++) { _degrees.Add(0); } _random = new Random(seed); } private void buildGraph(int numClients, int numBackbones, int minClientDegree, int maxClientDegree, int minBackboneDegree, int maxBackboneDegree) { int size = numBackbones + numClients; for (int i = 1; i < numBackbones; i++) { int j = randomUniform(i); addEdge(i, j); } List notFull = new List(); HashSet toComplete = new HashSet(); for (int i = 0; i < numBackbones; i++) { if (_degrees[i] < minBackboneDegree) { toComplete.Add(i); } if (_degrees[i] < maxBackboneDegree) { notFull.Add(i); } } while (toComplete.Any() && notFull.Count > 1) { int node1 = getNextToComplete(toComplete); int node2 = node1; while (node2 == node1 || _degrees[node2] >= maxBackboneDegree) { node2 = randomUniform(numBackbones); } addEdge(node1, node2); if (_degrees[node1] >= minBackboneDegree) { toComplete.Remove(node1); } if (_degrees[node2] >= minBackboneDegree) { toComplete.Remove(node2); } if (_degrees[node1] >= maxBackboneDegree) { notFull.Remove(node1); } if (_degrees[node2] >= maxBackboneDegree) { notFull.Remove(node2); } } // Then create the client nodes connected to the backbone nodes. // If numClient is 0, then backbone nodes are also client nodes. for (int i = numBackbones; i < size; i++) { int degree = randomInInterval(minClientDegree, maxClientDegree); while (_degrees[i] < degree) { int j = randomUniform(numBackbones); if (!_network[i][j]) { addEdge(i, j); } } } } private int getNextToComplete(HashSet toComplete) { return toComplete.Last(); } private void createDemands(int numClients, int numBackbones, int numDemands, int trafficMin, int trafficMax, NetworkRoutingData data) { while (data.NumberOfDemands < numDemands) { int source = randomClient(numClients, numBackbones); int dest = source; while (dest == source) { dest = randomClient(numClients, numBackbones); } int traffic = randomInInterval(trafficMin, trafficMax); data.AddDemand(source, dest, traffic); } } private void fillData(int numClients, int numBackbones, int numDemands, int trafficMin, int trafficMax, int minClientDegree, int maxClientDegree, int minBackboneDegree, int maxBackboneDegree, int maxCapacity, int fixedChargeCost, int seed, NetworkRoutingData data) { int size = numBackbones + numClients; string name = $"mp_c{numClients}_b{numBackbones}_d{numDemands}.t{trafficMin}-{trafficMax}.cd{minClientDegree}-{maxClientDegree}.bd{minBackboneDegree}-{maxBackboneDegree}.mc{maxCapacity}.fc{fixedChargeCost}.s{seed}"; data.Name = name; data.NumberOfNodes = size; int numArcs = 0; for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (_network[i][j]) { data.AddArc(i, j, maxCapacity); numArcs++; } } } data.MaximumCapacity = maxCapacity; data.FixedChargeCost = fixedChargeCost; } private void addEdge(int i, int j) { _degrees[i]++; _degrees[j]++; _network[i][j] = true; _network[j][i] = true; } private int randomInInterval(int intervalMin, int intervalMax) { var p = randomUniform(intervalMax - intervalMin + 1) + intervalMin; return p; } private int randomClient(int numClients, int numBackbones) { var p = (numClients == 0) ? randomUniform(numBackbones) : randomUniform(numClients) + numBackbones; return p; } private int randomUniform(int max) { var r = _random.Next(max); return r; } } [DebuggerDisplay("Source {Source} Destination {Destination} Traffic {Traffic}")] public struct Demand { public Demand(int source, int destination, int traffic) { Source = source; Destination = destination; Traffic = traffic; } public int Source { get; } public int Destination { get; } public int Traffic { get; } } public class NetworkRoutingSolver { private List<(long source, long destination, int arcId)> _arcsData = new List<(long source, long destination, int arcId)>(); private List _arcCapacity = new List(); private List _demands = new List(); private List _allMinPathLengths = new List(); private List> _capacity; private List>> _allPaths; public int NumberOfNodes { get; private set; } = -1; private int countArcs { get { return _arcsData.Count / 2; } } public void ComputeAllPathsForOneDemandAndOnePathLength(int demandIndex, int maxLength, int maxPaths) { // We search for paths of length exactly 'maxLength'. CpModel cpModel = new CpModel(); var arcVars = new List(); var nodeVars = new List(); for (int i = 0; i < maxLength; i++) { nodeVars.Add(cpModel.NewIntVar(0, NumberOfNodes - 1, string.Empty)); } for (int i = 0; i < maxLength - 1; i++) { arcVars.Add(cpModel.NewIntVar(-1, countArcs - 1, string.Empty)); } var arcs = getArcsData(); for (int i = 0; i < maxLength - 1; i++) { var tmpVars = new List(); tmpVars.Add(nodeVars[i]); tmpVars.Add(nodeVars[i + 1]); tmpVars.Add(arcVars[i]); var table = cpModel.AddAllowedAssignments(tmpVars, arcs); } var demand = _demands[demandIndex]; cpModel.Add(nodeVars[0] == demand.Source); cpModel.Add(nodeVars[maxLength - 1] == demand.Destination); cpModel.AddAllDifferent(arcVars); cpModel.AddAllDifferent(nodeVars); var solver = new CpSolver(); var solutionPrinter = new FeasibleSolutionChecker(demandIndex, ref _allPaths, maxLength, arcVars, maxPaths, nodeVars); var status = solver.SearchAllSolutions(cpModel, solutionPrinter); } private long[,] getArcsData() { long[,] arcs = new long[_arcsData.Count, 3]; for (int i = 0; i < _arcsData.Count; i++) { var data = _arcsData[i]; arcs[i, 0] = data.source; arcs[i, 1] = data.destination; arcs[i, 2] = data.arcId; } return arcs; } public int ComputeAllPaths(int extraHops, int maxPaths) { int numPaths = 0; for (int demandIndex = 0; demandIndex < _demands.Count; demandIndex++) { int minPathLength = _allMinPathLengths[demandIndex]; for (int maxLength = minPathLength + 1; maxLength <= minPathLength + extraHops+1; maxLength++) { ComputeAllPathsForOneDemandAndOnePathLength(demandIndex, maxLength, maxPaths); if (_allPaths[demandIndex].Count >= maxPaths) break; } numPaths += _allPaths[demandIndex].Count; } return numPaths; } public void AddArcData(long source, long destination, int arcId) { _arcsData.Add((source, destination, arcId)); } public void InitArcInfo(NetworkRoutingData data) { int numArcs = data.NumberOfArcs; _capacity = new List>(NumberOfNodes); for (int nodeIndex = 0; nodeIndex < NumberOfNodes; nodeIndex++) { _capacity.Add(new List(NumberOfNodes)); for (int i = 0; i < NumberOfNodes; i++) { _capacity[nodeIndex].Add(0); } } int arcId = 0; for (int i = 0; i < NumberOfNodes - 1; i++) { for (int j = i + 1; j < NumberOfNodes; j++) { int capacity = data.Capacity(i, j); if (capacity > 0) { AddArcData(i, j, arcId); AddArcData(j, i, arcId); arcId++; _arcCapacity.Add(capacity); _capacity[i][j] = capacity; _capacity[j][i] = capacity; if (printModel) { Console.WriteLine($"Arc {i} <-> {j} with capacity {capacity}"); } } } } Debug.Assert(arcId == numArcs); } public int InitDemandInfo(NetworkRoutingData data) { int numDemands = data.NumberOfDemands; int totalDemand = 0; for (int i = 0; i < NumberOfNodes; i++) { for (int j = 0; j < NumberOfNodes; j++) { int traffic = data.Demand(i, j); if (traffic > 0) { _demands.Add(new Demand(i, j, traffic)); totalDemand += traffic; } } } Debug.Assert(numDemands == _demands.Count); return totalDemand; } public long InitShortestPaths(NetworkRoutingData data) { int numDemands = data.NumberOfDemands; long totalCumulatedTraffic = 0L; _allMinPathLengths.Clear(); var paths = new List(); for (int demandIndex = 0; demandIndex < numDemands; demandIndex++) { paths.Clear(); var demand = _demands[demandIndex]; var r = DijkstraShortestPath(NumberOfNodes, demand.Source, demand.Destination, ((int x, int y) p) => hasArc(p.x, p.y), kDisconnectedDistance, paths); _allMinPathLengths.Add(paths.Count - 1); var minPathLength = _allMinPathLengths[demandIndex]; totalCumulatedTraffic += minPathLength * demand.Traffic; } return totalCumulatedTraffic; } public int InitPaths(NetworkRoutingData data, int extraHops, int maxPaths) { var numDemands = data.NumberOfDemands; Console.WriteLine("Computing all possible paths "); Console.WriteLine($" - extra hops = {extraHops}"); Console.WriteLine($" - max paths per demand = {maxPaths}"); _allPaths =new List>>(numDemands); var numPaths = ComputeAllPaths(extraHops, maxPaths); for (int demandIndex = 0; demandIndex < numDemands; demandIndex++) { var demand = _demands[demandIndex]; Console.WriteLine($"Demand from {demand.Source} to {demand.Destination} with traffic {demand.Traffic}, amd {_allPaths[demandIndex].Count} possible paths."); } return numPaths; } public void Init(NetworkRoutingData data, int extraHops, int maxPaths) { Console.WriteLine($"Model {data.Name}"); NumberOfNodes = data.NumberOfNodes; var numArcs = data.NumberOfArcs; var numDemands = data.NumberOfDemands; InitArcInfo(data); var totalDemand = InitDemandInfo(data); var totalAccumulatedTraffic = InitShortestPaths(data); var numPaths = InitPaths(data, extraHops, maxPaths); Console.WriteLine("Model created:"); Console.WriteLine($" - {NumberOfNodes} nodes"); Console.WriteLine($" - {numArcs} arcs"); Console.WriteLine($" - {numDemands} demands"); Console.WriteLine($" - a total traffic of {totalDemand}"); Console.WriteLine($" - a minimum cumulated traffic of {totalAccumulatedTraffic}"); Console.WriteLine($" - {numPaths} possible paths for all demands"); } private long hasArc(int i, int j) { if (_capacity[i][j] > 0) return 1; else return kDisconnectedDistance; } public long Solve() { Console.WriteLine("Solving model"); var numDemands = _demands.Count; var numArcs = countArcs; CpModel cpModel = new CpModel(); var pathVars = new List>(numDemands); for (int demandIndex = 0; demandIndex < numDemands; demandIndex++) { pathVars.Add(new List()); for (int arc = 0; arc < numArcs; arc++) { pathVars[demandIndex].Add(cpModel.NewBoolVar("")); } long[,] tuples = new long[_allPaths[demandIndex].Count, numArcs]; int pathCount = 0; foreach (var set in _allPaths[demandIndex]) { foreach (var arc in set) { tuples[pathCount, arc] = 1; } pathCount++; } var pathCt = cpModel.AddAllowedAssignments(pathVars[demandIndex], tuples); } var trafficVars = new List(numArcs); var normalizedTrafficVars = new List(numArcs); var comfortableTrafficVars = new List(numArcs); long maxNormalizedTraffic = 0; for (int arcIndex = 0; arcIndex < numArcs; arcIndex++) { long sumOfTraffic = 0; var vars = new List(); var traffics = new List(); for (int i = 0; i < pathVars.Count; i++) { sumOfTraffic += _demands[i].Traffic; vars.Add(pathVars[i][arcIndex]); traffics.Add(_demands[i].Traffic); } var sum = LinearExpr.ScalProd(vars, traffics); var trafficVar = cpModel.NewIntVar(0, sumOfTraffic, $"trafficVar{arcIndex}"); trafficVars.Add(trafficVar); cpModel.Add(sum == trafficVar); var capacity = _arcCapacity[arcIndex]; var scaledTraffic = cpModel.NewIntVar(0, sumOfTraffic * 1000, $"scaledTrafficVar{arcIndex}"); var scaledTrafficVar = trafficVar * 1000; cpModel.Add(scaledTrafficVar == scaledTraffic); var normalizedTraffic = cpModel.NewIntVar(0, sumOfTraffic * 1000 / capacity, $"normalizedTraffic{arcIndex}"); maxNormalizedTraffic = Math.Max(maxNormalizedTraffic, sumOfTraffic * 1000 / capacity); cpModel.AddDivisionEquality(normalizedTraffic, scaledTraffic, cpModel.NewConstant(capacity)); normalizedTrafficVars.Add(normalizedTraffic); var comfort = cpModel.NewBoolVar($"comfort{arcIndex}"); var safeCapacity = (long)(capacity * comfortZone); cpModel.Add(trafficVar > safeCapacity).OnlyEnforceIf(comfort); cpModel.Add(trafficVar <=safeCapacity).OnlyEnforceIf(comfort.Not()); comfortableTrafficVars.Add(comfort); } var maxUsageCost = cpModel.NewIntVar(0, maxNormalizedTraffic, "maxUsageCost"); cpModel.AddMaxEquality(maxUsageCost, normalizedTrafficVars); var obj = new List() {maxUsageCost}; obj.AddRange(comfortableTrafficVars); cpModel.Minimize(LinearExpr.Sum(obj)); CpSolver solver = new CpSolver(); solver.StringParameters = parameters; CpSolverStatus status = solver.SearchAllSolutions(cpModel, new FeasibleSolutionChecker2(maxUsageCost, comfortableTrafficVars, trafficVars)); return (long)solver.ObjectiveValue; } } private class DijkstraSP { private const long kInfinity = long.MaxValue / 2; private readonly Func<(int, int), long> _graph; private readonly int[] _predecessor; private readonly List _elements; private readonly AdjustablePriorityQueue _frontier; private readonly List _notVisited = new List(); private readonly List _addedToFrontier = new List(); public DijkstraSP(int nodeCount, int startNode, Func<(int,int), long> graph, long disconnectedDistance) { NodeCount = nodeCount; StartNode = startNode; this._graph = graph; DisconnectedDistance = disconnectedDistance; _predecessor = new int[nodeCount]; _elements = new List(nodeCount); _frontier = new AdjustablePriorityQueue(); } public int NodeCount { get; } public int StartNode { get; } public long DisconnectedDistance { get; } public bool ShortestPath(int endNode, List nodes) { initialize(); bool found = false; while (!_frontier.IsEmpty) { long distance; int node = selectClosestNode(out distance); if (distance == kInfinity) { found = false; break; } else if (node == endNode) { found = true; break; } update(node); } if (found) { findPath(endNode, nodes); } return found; } private void initialize() { for (int i = 0; i < NodeCount; i++) { _elements.Add(new Element {Node = i}); if (i == StartNode) { _predecessor[i] = -1; _elements[i].Distance = 0; _frontier.Add(_elements[i]); } else { _elements[i].Distance = kInfinity; _predecessor[i] = StartNode; _notVisited.Add(i); } } } private int selectClosestNode(out long distance) { var node = _frontier.Top().Node; distance = _frontier.Top().Distance; _frontier.Pop(); _notVisited.Remove(node); _addedToFrontier.Remove(node); return node; } private void update(int node) { foreach (var otherNode in _notVisited) { var graphNode = _graph((node, otherNode)); if (graphNode != DisconnectedDistance) { if (!_addedToFrontier.Contains(otherNode)) { _frontier.Add(_elements[otherNode]); _addedToFrontier.Add(otherNode); } var otherDistance = _elements[node].Distance + graphNode; if (_elements[otherNode].Distance > otherDistance) { _elements[otherNode].Distance = otherDistance; _frontier.NoteChangedPriority(_elements[otherNode]); _predecessor[otherNode] = node; } } } } private void findPath(int dest, List nodes) { var j = dest; nodes.Add(j); while (_predecessor[j] != -1) { nodes.Add(_predecessor[j]); j = _predecessor[j]; } } } public static bool DijkstraShortestPath(int nodeCount, int startNode, int endNode, Func<(int, int), long> graph, long disconnectedDistance, List nodes) { DijkstraSP bf = new DijkstraSP(nodeCount, startNode, graph, disconnectedDistance); return bf.ShortestPath(endNode, nodes); } [DebuggerDisplay("Node = {Node}, HeapIndex = {HeapIndex}, Distance = {Distance}")] private class Element : IHasHeapIndex, IComparable { public int HeapIndex { get; set; } = -1; public long Distance { get; set; } = 0; public int Node { get; set; } = -1; public int CompareTo(Element other) { if (this.Distance > other.Distance) return -1; if (this.Distance < other.Distance) return 1; return 0; } } private class AdjustablePriorityQueue where T: class, IHasHeapIndex, IComparable { private readonly List _elems = new List(); public void Add(T val) { _elems.Add(val); adjustUpwards(_elems.Count - 1); } public void Remove(T val) { var i = val.HeapIndex; if (i == _elems.Count - 1) { _elems.RemoveAt(_elems.Count - 1); return; } _elems[i] = _elems.Last(); _elems[i].HeapIndex = i; _elems.RemoveAt(_elems.Count - 1); NoteChangedPriority(_elems[i]); } public bool Contains(T val) { var i = val.HeapIndex; if (i < 0 || i >= _elems.Count || _elems[i].CompareTo(val) != 0) return false; return true; } public T Top() { return _elems[0]; } public void Pop() { Remove(Top()); } public int Size() { return _elems.Count; } public bool IsEmpty { get { return !_elems.Any(); } } public void Clear() { _elems.Clear(); } public void CheckValid() { for (int i = 0; i < _elems.Count; i++) { var leftChild = 1 + 2 * i; if (leftChild < _elems.Count) { var compare = _elems[i].CompareTo(_elems[leftChild]); Debug.Assert(compare >=0); } int rightChild = leftChild + 1; if (rightChild < _elems.Count) { var compare = _elems[i].CompareTo(_elems[rightChild]); Debug.Assert(compare >= 0); } } } public void NoteChangedPriority(T val) { if (_elems.Count == 0) return; var i = val.HeapIndex; var parent = (i - 1) / 2; if (_elems[parent].CompareTo(val) == -1) { adjustUpwards(i); } else { adjustDownwards(i); } } private void adjustUpwards(int i) { var t = _elems[i]; while (i > 0) { var parent = (i - 1) / 2; if (_elems[parent].CompareTo(t) != -1) { break; } _elems[i] = _elems[parent]; _elems[i].HeapIndex = i; i = parent; } _elems[i] = t; t.HeapIndex = i; } private void adjustDownwards(int i) { var t = _elems[i]; while (true) { var leftChild = 1 + 2 * i; if (leftChild >= _elems.Count) { break; } var rightChild = leftChild + 1; var next = (rightChild < _elems.Count && _elems[leftChild].CompareTo(_elems[rightChild]) == -1) ? rightChild : leftChild; if (t.CompareTo(_elems[next]) != -1) { break; } _elems[i] = _elems[next]; _elems[i].HeapIndex = i; i = next; } _elems[i] = t; t.HeapIndex = i; } } public interface IHasHeapIndex { int HeapIndex { get; set; } } private class FeasibleSolutionChecker : CpSolverSolutionCallback { public FeasibleSolutionChecker(int demandIndex, ref List>> allPaths, int maxLength, List arcVars, int maxPaths, List nodeVars) { DemandIndex = demandIndex; AllPaths = allPaths; MaxLength = maxLength; ArcVars = arcVars; MaxPaths = maxPaths; NodeVars = nodeVars; } public int DemandIndex { get; } public List>> AllPaths { get; } public int MaxLength { get; } public List ArcVars { get; } public int MaxPaths { get; } public List NodeVars { get; } public override void OnSolutionCallback() { if(AllPaths.Count < DemandIndex + 1) AllPaths.Add(new List>()); int pathId = AllPaths[DemandIndex].Count; AllPaths[DemandIndex].Add(new HashSet()); for (int i = 0; i < MaxLength - 1; i++) { int arc = (int) this.SolutionIntegerValue(ArcVars[i].GetIndex()); AllPaths[DemandIndex][pathId].Add(arc); } if (AllPaths[DemandIndex].Count() >= MaxPaths) { StopSearch(); } } } private class FeasibleSolutionChecker2 : CpSolverSolutionCallback { public IntVar MaxUsageCost { get; } public List ComfortableTrafficVars { get; } public List TrafficVars { get; } private int _numSolutions = 0; public FeasibleSolutionChecker2(IntVar maxUsageCost, List comfortableTrafficVars, List trafficVars) { MaxUsageCost = maxUsageCost; ComfortableTrafficVars = comfortableTrafficVars; TrafficVars = trafficVars; } public override void OnSolutionCallback() { Console.WriteLine($"Solution {_numSolutions}"); var percent = SolutionIntegerValue(MaxUsageCost.GetIndex()) / 10.0; int numNonComfortableArcs = 0; foreach (var comfort in ComfortableTrafficVars) { numNonComfortableArcs += SolutionBooleanValue(comfort.GetIndex()) ? 1 : 0; } if (numNonComfortableArcs > 0) { Console.WriteLine($"*** Found a solution with a max usage of {percent}%, and {numNonComfortableArcs} links above the comfort zone"); } else { Console.WriteLine($"*** Found a solution with a max usage of {percent}%"); } _numSolutions++; } } }