/// <summary> /// Group of methods and operator overloads that add some (0-...) arcs to the graph, /// AddArc(IArc arc) only is basic method /// </summary> /// <param name="arc"></param> public virtual void AddArc(IArc arc) { if (arc == null) { throw new ArgumentNullException(); } if (_insideGraph == null) { throw new NullReferenceException(); } _insideGraph.AddArc(arc.Clone()); }
// IGraph extension methods: public static void AddArc(this IGraph graph, Arc arc) => graph.AddArc(arc.Tail, arc.Head, arc.MachineId, arc.Length);
/// <summary> /// Sets the route for a job, on the graph layer. Ie. insert/remove/change length of all conjunctive arcs, /// propagate the entry time changes into the cost layer and keep track the effects this has on the rest of /// the graph (by inserting into _necessaryFixes when necessary). Note, that it is not the graph layers job /// to handle machine occupations, but we do not want to delete and reinsert those that do not change /// (with a route swap). Hence, this method expects that all disjunctive arcs present (adjacent to the 'job') /// correspond to machine occupations that do not change with the route swap. /// </summary> /// <param name="job">The job, whose route we set</param> /// <param name="route">The route to insert, null if the route of this job is to be removed.</param> public void SetRoute(Job job, Route route) { Route newRoute = route; Route oldRoute = Solution.GetRoute(job, canBeNull: true); Assert(GraphLayerChecks, !(oldRoute == null && newRoute == null)); Assert(GraphLayerChecks, (oldRoute == null || job.Contains(oldRoute)) && (newRoute == null || job.Contains(newRoute))); if (oldRoute != null) { var report = new StringBuilder(); Assert(GraphLayerChecks, ConjunctiveRouteProperlyLoaded(oldRoute, report), report.ToString()); //Assert(GraphLayerChecks, oldRoute.Pairwise((o0, o1) => ArcExists(o0.Id, o1.Id, -1, out _)).All(x => x)); } // Precondition: // // AdjacentArcs(job) == ArcsOf (Intersect (MachineOcc (newRoute), MachineOcc (oldRoute))) // // This is, all disjunctive arcs adjacent to the job in question, are disjunctive arcs of // machine occupations that occur in both routes. if (GraphLayerChecks.On) { // ReSharper disable once InvokeAsExtensionMethod (more readable) var relevantOccupations = Enumerable.Intersect( newRoute?.SelectMany(op => op.EndingMachineOccupations) ?? new MachineOccupation[0], oldRoute?.SelectMany(op => op.EndingMachineOccupations) ?? new MachineOccupation[0]); IEnumerable <Arc> incomingDisjunctive = Enumerable.Range(job.FirstOperation, job.LongestRoute).SelectMany(IncomingArcs) .Where(a => a.MachineId != -1); IEnumerable <Arc> outgoingDisjunctive = Enumerable.Range(job.FirstOperation, job.LongestRoute).SelectMany(OutgoingArcs) .Where(a => a.MachineId != -1); Assert(GraphLayerChecks, incomingDisjunctive.All(a => relevantOccupations.Any(occ => occ.FirstOperation == a.Head && occ.MachineId == a.MachineId))); Assert(GraphLayerChecks, outgoingDisjunctive.All(a => relevantOccupations.Any(occ => occ.LastOperation == a.Tail - 1 && occ.MachineId == a.MachineId))); } // end precondition. // 1) For conjunctive arcs: // insert/delete/change length // // 2) Given the incoming disjunctive arcs and earliest entry times, // update the entryTimes within the job // // 3) For all leaving disjunctive arcs: // If the head entry time changes, update and set a necessary fix. // Note, _graph.<someMethod>(...) bypasses the registration of the necessaryFixes. // (as opposed to this.<someMethod>()) // Step 1: for (int i = 0; i < job.LongestRoute - 1; i++) { var oldOp = oldRoute == null || (oldRoute.Count - 1) <= i ? null : oldRoute[i]; var newOp = newRoute == null || (newRoute.Count - 1) <= i ? null : newRoute[i]; // On the '-1': arcs are inserted for the operations [0, ..., n-1] ; there is no arc for the // last (dummy) operation var vertexIndex = job.FirstOperation + i; var arc = new Arc(vertexIndex, vertexIndex + 1, newOp?.Runtime ?? TimeSpan.Zero, -1); if (newOp == null && oldOp == null) // only if both routes are shorter than the longest route: // oldRoute.length < i && newRoute.length < i, but there is a route longer than 'old' and 'new' { Assert(GraphLayerChecks, oldRoute == null || vertexIndex > (oldRoute.Count - 1)); Assert(GraphLayerChecks, newRoute == null || vertexIndex > (newRoute.Count - 1)); break; } else if (newOp == null /* && oldOp != null */) { _graph.RemoveArc(arc); } else if (/* newOp != null && */ oldOp == null) { _graph.AddArc(arc); } else /* if (newOp != null && oldOp != null) */ if (oldOp.Runtime != newOp.Runtime) { _graph.RemoveArc(arc); _graph.AddArc(arc); } } CostLayer.SetRoute(job, route); // Step 2: TimeSpan ArcBasedIncomingTime(int index) => IncomingArcs(index).Select(a => Solution.GetEntryTime(a.Tail) + a.Length).Prepend(TimeSpan.MinValue) .Max(); TimeSpan ShouldBeEntryTime(int index) => (Solution.GetOperation(index, canBeNull: true)?.EarliestEarliestEntry ?? TimeSpan.Zero).Max( ArcBasedIncomingTime(index)); if (newRoute == null) { // ReSharper disable once PossibleNullReferenceException (see assert above) foreach (var op in oldRoute) { CostLayer.SetEntryTime(op.Id, TimeSpan.Zero); } } else { foreach (var op in newRoute) { var time = ShouldBeEntryTime(op.Id); if (Solution.GetEntryTime(op.Id) != time) { CostLayer.SetEntryTime(op.Id, time); } } } // Step 3: if (newRoute != null) { var outgoingDisjunctive = Enumerable.Range(job.FirstOperation, newRoute.Count) .SelectMany(OutgoingArcs).Where(a => a.MachineId != -1); foreach (Arc arc in outgoingDisjunctive) { var shouldBe = ShouldBeEntryTime(arc.Head); var actual = Solution.GetEntryTime(arc.Head); if (actual != shouldBe) { CostLayer.SetEntryTime(arc.Head, shouldBe); _necessaryFixes.Add(new TimeAtVertex(arc.Head, shouldBe)); } } } // Checks: if (newRoute != null && GraphLayerChecks.On) { StringBuilder report = new StringBuilder(); Assert(GraphLayerChecks, ConjunctiveRouteProperlyLoaded(newRoute, report), report.ToString()); } }