#region License Information /* HeuristicLab * Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Linq; using HeuristicLab.Common; using HeuristicLab.Core; using HeuristicLab.Encodings.IntegerVectorEncoding; using HeuristicLab.Encodings.RealVectorEncoding; using HeuristicLab.Optimization; using HeuristicLab.Parameters; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using SimSharp; using Environment = SimSharp.Environment; using IRandom = HeuristicLab.Core.IRandom; namespace HeuristicLab.Problems.Scheduling.MRCPSP { [Item("Multi-mode Resource-Constrained Project Scheduling Problem (MRCPSP)", "")] [Creatable(CreatableAttribute.Categories.CombinatorialProblems)] [StorableClass] public sealed class MultiModeResourceConstrainedProjectSchedulingProblem : SingleObjectiveBasicProblem { //public MultiModeResourceConstrainedProjectSchedulingProblem() // : this() { //} [Storable] private readonly IValueParameter> activitiesParam; [Storable] private readonly IValueParameter> resourceCapacitiesParam; public MultiModeResourceConstrainedProjectSchedulingProblem() { Parameters.Add(activitiesParam = new ValueParameter>(nameof(Activities), new ItemList())); Parameters.Add(resourceCapacitiesParam = new ValueParameter>(nameof(ResourceCapacities), new ItemList())); Operators.Add(new MrcpspTwoPointCrossover { RandomKeyParents = { ActualName = "RandomKey" }, ModeListParents = { ActualName = "ModeList" }, RandomKeyChild = { ActualName = "RandomKey" }, ModeListChild = { ActualName = "ModeList" } }); Operators.Add(new MrcpspImprover()); Operators.Add(new FeasibilityImprover { Problem = this, EvaluateFunc = Evaluate }); Operators.Add(new WorkContentImprover { Problem = this, EvaluateFunc = Evaluate }); Operators.Add(new CriticalPathImprover { Problem = this, EvaluateFunc = Evaluate }); } [StorableConstructor] private MultiModeResourceConstrainedProjectSchedulingProblem(bool deserializing) : base(deserializing) { } private MultiModeResourceConstrainedProjectSchedulingProblem( MultiModeResourceConstrainedProjectSchedulingProblem original, Cloner cloner) : base(original, cloner) { activitiesParam = cloner.Clone(original.activitiesParam); resourceCapacitiesParam = cloner.Clone(original.resourceCapacitiesParam); } public ItemList Activities { get => activitiesParam.Value; set => activitiesParam.Value = value; } public override bool Maximization => false; public ItemList ResourceCapacities { get => resourceCapacitiesParam.Value; set => resourceCapacitiesParam.Value = value; } public override IDeepCloneable Clone(Cloner cloner) => new MultiModeResourceConstrainedProjectSchedulingProblem(this, cloner); public void InitEncoding() { var activities = Activities.Skip(1).Reverse().Skip(1).Reverse().ToList(); Encoding = new MultiEncoding(); Encoding.Add(new RealVectorEncoding("RandomKey", activities.Count, 0.0, 1.0)); Encoding.Add(new IntegerVectorEncoding("ModeList", activities.Count, Enumerable.Repeat(1, activities.Count).ToList(), activities.Select(a => a.Modes.Count + 1).ToList())); } // work content public int CalculateWC(RealVector randomKey, IntegerVector modeList) { ExtractSolution(randomKey, modeList, out var activities, out _); var workContent = 0; foreach (var activity in activities) { var mode = activity.Item1.Modes.Single(m => m.Number == activity.Item3); workContent += mode.ResourceDemands.Where(d => IsRenewable(d.Number)).Sum(demand => demand.Demand); } return workContent; } public class CpActivity { public int Id { get; set; } public int Duration { get; set; } public List Predecessors { get; set; } = new List(); public List Successors { get; set; } = new List(); public bool IsStartNode { get; set; } public bool IsEndNode { get; set; } public int EarliestStart { get; set; } public int EarliestEnd { get; set; } public int LatestStart { get; set; } public int LatestEnd { get; set; } } public int GetCriticalPathLength(ref List activities) { ForwardScheduling(activities.First(x => x.IsStartNode)); var endNode = activities.First(x => x.IsEndNode); return endNode.EarliestEnd; } public List GetCriticalActivities(ref List activities) { ForwardScheduling(activities.First(x => x.IsStartNode)); var endNode = activities.First(x => x.IsEndNode); endNode.LatestEnd = endNode.EarliestEnd; endNode.LatestStart = endNode.LatestEnd - endNode.Duration; BackwardScheduling(endNode); return activities .Where(a => a.EarliestEnd - a.LatestEnd == 0 && a.EarliestStart - a.LatestStart == 0) .ToList(); } private static void ForwardScheduling(CpActivity position) { foreach (var successor in position.Successors) { foreach (var predecessor in successor.Predecessors) { if (successor.EarliestStart < predecessor.EarliestEnd) successor.EarliestStart = predecessor.EarliestEnd; } successor.EarliestEnd = successor.EarliestStart + successor.Duration; } foreach (var successor in position.Successors) { ForwardScheduling(successor); } } private static void BackwardScheduling(CpActivity cpActivity) { foreach (var predecessor in cpActivity.Predecessors) { foreach (var successor in predecessor.Successors) { if (predecessor.LatestEnd == 0) { predecessor.LatestEnd = successor.LatestStart; } else if (predecessor.LatestEnd > successor.LatestStart) { predecessor.LatestEnd = successor.LatestStart; } } predecessor.LatestStart = predecessor.LatestEnd - predecessor.Duration; } foreach (var predecessor in cpActivity.Predecessors) { BackwardScheduling(predecessor); } } public int CalculateCP(RealVector randomKey, IntegerVector modeList) { ExtractSolution(randomKey, modeList, out var activities, out _); var successors = activities.SelectMany(a => a.Item1.Successors.Select(s => s.Value)); var startNode = activities.Single(a => successors.All(s => s != a.Item1.Number)).Item1.Number; var act = new List(); foreach (var activity in activities) { var a = activity.Item1; var m = activity.Item1.Modes.Single(x => x.Number == activity.Item3); act.Add(new CpActivity { Id = a.Number, Duration = m.Duration, IsStartNode = a.Number == startNode, IsEndNode = !a.Successors.Any() }); } foreach (var activity in activities) { var a = activity.Item1; var ax = act.Single(x => x.Id == a.Number); foreach (var s in a.Successors) { var sx = act.Single(x => x.Id == s.Value); ax.Successors.Add(sx); sx.Predecessors.Add(ax); } } return GetCriticalPathLength(ref act); } public int CalculateERR(Individual individual) => CalculateERR(individual.RealVector("RandomKey"), individual.IntegerVector("ModeList")); // excess of resource request public int CalculateERR(RealVector randomKey, IntegerVector modeList) { ExtractSolution(randomKey, modeList, out var activities, out _); var totalDemands = ResourceCapacities.Where(c => !c.IsRenewable).ToDictionary(c => c.Number, c => 0); foreach (var activity in activities) { var mode = activity.Item1.Modes.Single(m => m.Number == activity.Item3); foreach (var demand in mode.ResourceDemands) { if (totalDemands.ContainsKey(demand.Number)) totalDemands[demand.Number] += demand.Demand; } } return totalDemands.Sum(d => Math.Max(0, d.Value - ResourceCapacities.Single(c => c.Number == d.Key).Capacity)); } private void ExtractSolution(Individual individual, out List> activities, out Dictionary> dependencies) => ExtractSolution(individual.RealVector("RandomKey"), individual.IntegerVector("ModeList"), out activities, out dependencies); private void ExtractSolution(RealVector randomKey, IntegerVector modeList, out List> activities, out Dictionary> dependencies) { activities = new List>(Activities.Count); dependencies = new Dictionary>(Activities.Count); var act = Activities[0]; activities.Add(Tuple.Create(act, double.MinValue, act.Modes.First().Number)); dependencies[act.Number] = new List(); for (var i = 1; i < Activities.Count - 1; i++) { activities.Add(Tuple.Create(Activities[i], randomKey[i - 1], modeList[i - 1])); dependencies[Activities[i].Number] = new List(); } act = Activities[Activities.Count - 1]; activities.Add(Tuple.Create(act, double.MaxValue, act.Modes.First().Number)); dependencies[act.Number] = new List(); foreach (var activity in Activities) { foreach (var successor in activity.Successors) { dependencies[successor.Value].Add(activity.Number); } } activities = activities.OrderBy(a => a.Item2).ToList(); } public override void Analyze(Individual[] individuals, double[] qualities, ResultCollection results, IRandom random) { var bestIndividual = GetBestIndividual(individuals, qualities, Maximization); results.AddOrUpdateResult("BestSolution.RandomKey", bestIndividual.Item1.RealVector("RandomKey")); results.AddOrUpdateResult("BestSolution.ModeList", bestIndividual.Item1.IntegerVector("ModeList")); base.Analyze(individuals, qualities, results, random); } public override double Evaluate(Individual individual, IRandom random) { ExtractSolution(individual, out var activities, out var dependencies); var env = new Environment(); var procActivities = new Dictionary(); var resources = ResourceCapacities .ToDictionary(resource => resource.Number, resource => new Container(env, resource.Capacity, resource.Capacity)); foreach (var activity in activities) { procActivities[activity.Item1.Number] = env.Process(Job(env, procActivities, activity, resources, dependencies[activity.Item1.Number])); } // fitness function by Hartmann (2001) try { env.Run(); return env.NowD; } catch (InvalidOperationException) { var T = Activities.Sum(a => a.Modes.Max(m => m.Duration)); return T + CalculateERR(individual); } } private bool IsRenewable(int resourceNumber) => ResourceCapacities.Single(r => r.Number == resourceNumber).IsRenewable; private IEnumerable Job(Environment env, IReadOnlyDictionary procActivities, Tuple activity, IReadOnlyDictionary resources, IEnumerable deps) { var mode = activity.Item1.Modes[activity.Item3 - 1]; var demands = mode.ResourceDemands.Where(d => d.Demand > 0).ToList(); var rres = demands.Where(d => IsRenewable(d.Number)).Select(d => Tuple.Create(resources[d.Number], d.Demand)); var nres = demands.Where(d => !IsRenewable(d.Number)).Select(d => Tuple.Create(resources[d.Number], d.Demand)); yield return new AllOf(env, deps.Select(d => procActivities[d])); foreach (var nr in nres) { if (nr.Item2 > nr.Item1.Level) throw new InvalidOperationException(); yield return nr.Item1.Get(nr.Item2); } foreach (var rr in rres) { yield return rr.Item1.Get(rr.Item2); yield return env.TimeoutD(mode.Duration); yield return rr.Item1.Put(rr.Item2); } } } }