/// <summary> /// Solves this model. /// </summary> public void Solve() { // Init LogLine("Solving ..."); DateTime before = DateTime.Now; LinearModel model = new LinearModel(SolverType.Gurobi, (string msg) => { Log(msg); }); VariableCollection <int> xVar = new VariableCollection <int>(model, VariableType.Continuous, 0, double.PositiveInfinity, (int j) => { return("x" + j.ToString()); }); // Modify some control parameters foreach (var param in _solverArgs) { model.SetParam(param.Key, param.Value); } // Prepare non-zero indices Dictionary <int, List <int> > nnzs = _coefficientMatrix.Keys.GroupBy(k => (int)k[0]).ToDictionary(k => k.Key, v => v.Select(e => (int)e[1]).ToList()); // Add objective model.SetObjective( LinearExpression.Sum( Enumerable.Range(0, N).Select(j => _objCoeffcientVector[j]), Enumerable.Range(0, N).Select(j => xVar[j])), OptimizationSense.Minimize); // Add constraint foreach (var i in nnzs.Keys) { model.AddConstr(LinearExpression.Sum( nnzs[i].Select(j => _coefficientMatrix[i, j]), nnzs[i].Select(j => xVar[j])) == _rhsVector[i], "Con" + i); } // TODO remove debug model.Update(); model.ExportMPS("mdp.mps"); // Solve model.Optimize(); TimeSpan solutionTime = DateTime.Now - before; // Get solution if (model.HasSolution()) { LogLine("Solution found!"); _solutionVector = Enumerable.Range(0, N).Select(j => xVar[j].Value).ToList(); _solutionValue = model.GetObjectiveValue(); SolutionAvailable = true; } else { LogLine("No solution!"); } // Log performance LogPerformance(before, Filename, solutionTime, SolutionAvailable, _solutionValue, _solverArgs.Select(a => a.Key + "=" + a.Value)); }
/// <summary> /// Transforms the object-model into a mathematical formulation /// </summary> /// <returns>The model</returns> internal override LinearModel Transform() { // Init LinearModel model = new LinearModel(ChosenSolver, Config.Log); double bigMSideLengthOverall = Instance.Containers.Max(c => Math.Max(Math.Max(c.Mesh.Length, c.Mesh.Width), c.Mesh.Height)); Dictionary <int, double> bigMSideLength = new Dictionary <int, double>() { { _betas[0], Instance.Containers.Max(c => c.Mesh.Length) }, { _betas[1], Instance.Containers.Max(c => c.Mesh.Width) }, { _betas[2], Instance.Containers.Max(c => c.Mesh.Height) } }; double slantBigM = Instance.Containers.Max(c => Math.Sqrt(Math.Pow(c.Mesh.Length, 2) + Math.Pow(c.Mesh.Width, 2) + Math.Pow(c.Mesh.Height, 2))) / Instance.Containers.Min(c => Math.Sqrt(Math.Pow(c.Mesh.Length, 2) + Math.Pow(c.Mesh.Width, 2) + Math.Pow(c.Mesh.Height, 2))); // --> Variables _itemIsPicked = new VariableCollection <Piece, Container>(model, VariableType.Integer, 0, 1, (Piece piece, Container container) => { return("ItemPicked" + piece.ToIdentString() + container.ToIdentString()); }); _containerUsed = new VariableCollection <Container>(model, VariableType.Integer, 0, 1, (Container container) => { return("ContainerUsed" + container.ToIdentString()); }); _itemOrientation = new VariableCollection <int, Piece>(model, VariableType.Integer, 0, 1, (int orientation, Piece piece) => { return("ItemOrientation" + piece.ToIdentString() + "O" + orientation); }); _localReferenceFrameOrigin = new VariableCollection <int, Piece>(model, VariableType.Continuous, 0, double.PositiveInfinity, (int beta, Piece piece) => { return("LocalReferenceFrameOrigin" + piece.ToIdentString() + "B" + beta); }); _vertexPosition = new VariableCollection <int, int, MeshCube, Piece>(model, VariableType.Continuous, 0, double.PositiveInfinity, (int beta, int vertexID, MeshCube cube, Piece piece) => { return("VertexPosition" + piece.ToIdentString() + cube.ToIdentString() + "VID" + vertexID + "Beta" + beta); }); _lambda = new VariableCollection <Container, int, int, MeshCube, Piece>(model, VariableType.Continuous, 0, double.PositiveInfinity, (Container container, int gamma, int vertexID, MeshCube cube, Piece piece) => { return("Lambda" + container.ToIdentString() + piece.ToIdentString() + cube.ToIdentString() + "VID" + vertexID + "Gamma" + gamma); }); _sigmaPlus = new VariableCollection <int, MeshCube, MeshCube, Piece, Piece>(model, VariableType.Integer, 0, 1, (int beta, MeshCube cube1, MeshCube cube2, Piece piece1, Piece piece2) => { return("SigmaPlus" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString() + "Beta" + beta); }); _sigmaMinus = new VariableCollection <int, MeshCube, MeshCube, Piece, Piece>(model, VariableType.Integer, 0, 1, (int beta, MeshCube cube1, MeshCube cube2, Piece piece1, Piece piece2) => { return("SigmaMinus" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString() + "Beta" + beta); }); _locatedOnGround = new VariableCollection <Piece>(model, VariableType.Integer, 0, 1, (Piece cluster) => { return("LocatedOnGround" + cluster.ToIdentString()); }); _locatedOn = new VariableCollection <MeshCube, MeshCube, Piece, Piece>(model, VariableType.Integer, 0, 1, (MeshCube cube1, MeshCube cube2, Piece piece1, Piece piece2) => { return("LocatedOn" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); }); _bothInContainer = new VariableCollection <Container, Piece, Piece>(model, VariableType.Integer, 0, 1, (Container container, Piece piece1, Piece piece2) => { return("BothInContainer" + container.ToIdentString() + piece1.ToIdentString() + piece2.ToIdentString()); }); // Keep model up-to-date model.Update(); // --> Objective switch (Config.Goal) { case OptimizationGoal.MinContainer: { // Minimize container count model.SetObjective( LinearExpression.Sum(Instance.Containers.Select(container => _containerUsed[container])), OptimizationSense.Minimize); } break; case OptimizationGoal.MaxUtilization: { // Maximize utilization model.SetObjective( LinearExpression.Sum(Instance.Containers.Select(container => LinearExpression.Sum(Instance.Pieces.Select(piece => _itemIsPicked[piece, container] * piece.Volume)))), OptimizationSense.Maximize); } break; default: break; } // --> Constraints // Container usage foreach (var container in Instance.Containers) { foreach (var piece in Instance.Pieces) { model.AddConstr( _itemIsPicked[piece, container] <= _containerUsed[container], "ContainerActivity" + container.ToIdentString() + piece.ToIdentString()); } } // Orthogonality constraints foreach (var piece in Instance.Pieces) { model.AddConstr( (Config.Goal == OptimizationGoal.MaxUtilization) ? LinearExpression.Sum(Instance.Containers.Select(container => _itemIsPicked[piece, container])) <= 1 : LinearExpression.Sum(Instance.Containers.Select(container => _itemIsPicked[piece, container])) == 1, "ItemSingularity" + piece.ToIdentString()); } foreach (var piece in Instance.Pieces) { model.AddConstr( LinearExpression.Sum(MeshConstants.ORIENTATIONS.Select(orientation => _itemOrientation[orientation, piece])) == LinearExpression.Sum(Instance.Containers.Select(container => _itemIsPicked[piece, container])), "UseOneOrientationPerItem" + piece.ToIdentString()); } foreach (var beta in _betas) { foreach (var piece in Instance.Pieces) { foreach (var cube in piece.Original.Components) { foreach (var vertexID in MeshConstants.VERTEX_IDS) { model.AddConstr( _vertexPosition[beta, vertexID, cube, piece] == _localReferenceFrameOrigin[beta, piece] + LinearExpression.Sum( MeshConstants.ORIENTATIONS.Select(orientation => piece[orientation][cube.ID][vertexID][beta] * _itemOrientation[orientation, piece])), "LinkVerticesToLocalReferenceFrame" + piece.ToIdentString() + cube.ToIdentString() + "VID" + vertexID + "Beta" + beta); } } } } // Domain constraints foreach (var beta in _betas) { foreach (var piece in Instance.Pieces) { foreach (var cube in piece.Original.Components) { foreach (var vertexID in MeshConstants.VERTEX_IDS.Skip(1)) { model.AddConstr( _vertexPosition[beta, vertexID, cube, piece] == LinearExpression.Sum(Instance.Containers.Select(container => LinearExpression.Sum(MeshConstants.VERTEX_IDS.Skip(1).Select(gamma => container.Mesh[gamma][beta] * _lambda[container, gamma, vertexID, cube, piece])))), "Domain1" + piece.ToIdentString() + cube.ToIdentString() + "VID" + vertexID + "Beta" + beta); } } } } foreach (var piece in Instance.Pieces) { foreach (var cube in piece.Original.Components) { foreach (var vertexID in MeshConstants.VERTEX_IDS.Skip(1)) { foreach (var container in Instance.Containers) { model.AddConstr( LinearExpression.Sum(MeshConstants.VERTEX_IDS.Skip(1).Select(gamma => _lambda[container, gamma, vertexID, cube, piece])) == _itemIsPicked[piece, container], "Domain2" + container.ToIdentString() + piece.ToIdentString() + cube.ToIdentString() + "VID" + vertexID); } } } } // Non-intersection HashSet <Piece> seenPieces = new HashSet <Piece>(); foreach (var piece1 in Instance.PiecesWithVirtuals.OrderBy(p => p.ID)) { seenPieces.Add(piece1); foreach (var piece2 in Instance.PiecesWithVirtuals.OrderBy(p => p.ID).Except(seenPieces)) { foreach (var cube1 in piece1.Original.Components.OrderBy(c => c.ID)) { foreach (var cube2 in piece2.Original.Components.OrderBy(c => c.ID)) { foreach (var beta in _betas) { model.AddConstr( _vertexPosition[beta, 0, cube1, piece1] - _vertexPosition[beta, 0, cube2, piece2] >= (0.5 * LinearExpression.Sum(MeshConstants.ORIENTATIONS.Select(orientation => piece1[orientation][cube1.ID].SideLength(beta) * _itemOrientation[orientation, piece1] + piece2[orientation][cube2.ID].SideLength(beta) * _itemOrientation[orientation, piece2]))) - (bigMSideLength[beta] * (1 - _sigmaPlus[beta, cube1, cube2, piece1, piece2])) , "NonOverlapPos" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString() + "Beta" + beta); model.AddConstr( _vertexPosition[beta, 0, cube2, piece2] - _vertexPosition[beta, 0, cube1, piece1] >= (0.5 * LinearExpression.Sum(MeshConstants.ORIENTATIONS.Select(orientation => piece1[orientation][cube1.ID].SideLength(beta) * _itemOrientation[orientation, piece1] + piece2[orientation][cube2.ID].SideLength(beta) * _itemOrientation[orientation, piece2]))) - (bigMSideLength[beta] * (1 - _sigmaMinus[beta, cube1, cube2, piece1, piece2])) , "NonOverlapNeg" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString() + "Beta" + beta); } } } } } foreach (var container in Instance.Containers) { seenPieces.Clear(); foreach (var piece1 in Instance.PiecesWithVirtuals.OrderBy(p => p.ID)) { seenPieces.Add(piece1); foreach (var piece2 in Instance.PiecesWithVirtuals.OrderBy(p => p.ID).Except(seenPieces)) { foreach (var cube1 in piece1.Original.Components.OrderBy(c => c.ID)) { foreach (var cube2 in piece2.Original.Components.OrderBy(c => c.ID)) { model.AddConstr( LinearExpression.Sum(_betas.Select(beta => _sigmaPlus[beta, cube1, cube2, piece1, piece2] + _sigmaMinus[beta, cube1, cube2, piece1, piece2])) >= _itemIsPicked[piece1, container] + _itemIsPicked[piece2, container] - 1, "NonOverlapLink" + container.ToIdentString() + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); if (Instance.Containers.Count() <= 1) { model.AddConstr( LinearExpression.Sum(_betas.Select(beta => _sigmaPlus[beta, cube1, cube2, piece1, piece2] + _sigmaMinus[beta, cube1, cube2, piece1, piece2])) <= _itemIsPicked[piece1, container], "NonOverlapRedundant1" + container.ToIdentString() + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); model.AddConstr( LinearExpression.Sum(_betas.Select(beta => _sigmaPlus[beta, cube1, cube2, piece1, piece2] + _sigmaMinus[beta, cube1, cube2, piece1, piece2])) <= _itemIsPicked[piece2, container], "NonOverlapRedundant2" + container.ToIdentString() + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); } } } } } } // Redundant volume limitation constraint foreach (var container in Instance.Containers) { model.AddConstr( LinearExpression.Sum(Instance.PiecesWithVirtuals.Select(piece => _itemIsPicked[piece, container] * piece.Volume)) <= (container.Mesh.Length * container.Mesh.Width * container.Mesh.Height), "RedundantVolumeLimitation" + container.ToIdentString()); } // Ensure that pieces do not protrude slants foreach (var container in Instance.Containers) { foreach (var slant in container.Slants) { foreach (var piece in Instance.Pieces) { foreach (var component in piece.Original.Components) { // Use vertex depending on the normal vector of the slant model.AddConstr( ((slant.NormalVector.X >= 0 ? _vertexPosition[1, 8, component, piece] : _vertexPosition[1, 1, component, piece]) - slant.Position.X) * slant.NormalVector.X + ((slant.NormalVector.Y >= 0 ? _vertexPosition[2, 8, component, piece] : _vertexPosition[2, 1, component, piece]) - slant.Position.Y) * slant.NormalVector.Y + ((slant.NormalVector.Z >= 0 ? _vertexPosition[3, 8, component, piece] : _vertexPosition[3, 1, component, piece]) - slant.Position.Z) * slant.NormalVector.Z <= 0 + (1 - _itemIsPicked[piece, container]) * slantBigM, "StayInSlants" + slant.ToIdentString() + piece.ToIdentString() + component.ToIdentString() + container.ToIdentString() ); } } } } // Fix virtual pieces to the predefined positions and orientations foreach (var container in Instance.Containers) { foreach (var virtualPiece in container.VirtualPieces) { model.AddConstr( _itemIsPicked[virtualPiece, container] == 1, "VirtualPieceFixContainer" + container.ToIdentString() + virtualPiece.ToIdentString()); foreach (var orientation in MeshConstants.ORIENTATIONS) { model.AddConstr( _itemOrientation[orientation, virtualPiece] == ((orientation == virtualPiece.FixedOrientation) ? 1 : 0), "VirtualPieceFixOrientation" + container.ToIdentString() + virtualPiece.ToIdentString() + "O" + orientation.ToString()); } foreach (var beta in _betas) { model.AddConstr( _localReferenceFrameOrigin[beta, virtualPiece] == virtualPiece.FixedPosition[beta], "VirtualPieceLocalReferenceFix" + container.ToIdentString() + virtualPiece.ToIdentString() + beta.ToString()); foreach (var component in virtualPiece.Original.Components) { foreach (var vertexID in MeshConstants.VERTEX_IDS) { model.AddConstr( _vertexPosition[beta, vertexID, component, virtualPiece] == virtualPiece.FixedPosition[beta] + virtualPiece[virtualPiece.FixedOrientation][component.ID][vertexID][beta], "VirtualPieceVertexFix" + container.ToIdentString() + virtualPiece.ToIdentString() + component.ToIdentString() + "VID" + vertexID.ToString() + "Beta" + beta.ToString()); } } } } } // Ensure gravity-handling only if desired if (Config.HandleGravity) { // Gravity constraints foreach (var piece in Instance.Pieces.OrderBy(p => p.ID)) { model.AddConstr( _localReferenceFrameOrigin[3, piece] <= (1 - _locatedOnGround[piece]) * bigMSideLengthOverall, "GravityGround" + piece.ToIdentString()); } foreach (var piece1 in Instance.Pieces.OrderBy(p => p.ID)) { foreach (var piece2 in Instance.PiecesWithVirtuals.Where(p => p != piece1)) { // Track whether items are put into the same container foreach (var container in Instance.Containers) { model.AddConstr( _itemIsPicked[piece1, container] + _itemIsPicked[piece2, container] >= 2 * _bothInContainer[container, piece1, piece2], "BothInSameContainer" + container.ToIdentString() + piece1.ToIdentString() + piece2.ToIdentString()); } foreach (var cube1 in piece1.Original.Components.OrderBy(c => c.ID)) { foreach (var cube2 in piece2.Original.Components.OrderBy(c => c.ID)) { // Ensure that the lower side of piece one has the height of the upper side of piece two if the corresponding gravity variable is activated model.AddConstr( _vertexPosition[3, 1, cube1, piece1] <= _vertexPosition[3, 8, cube2, piece2] + (1 - _locatedOn[cube1, cube2, piece1, piece2]) * bigMSideLengthOverall, "GravityZ1" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); model.AddConstr( _vertexPosition[3, 1, cube1, piece1] + (1 - _locatedOn[cube1, cube2, piece1, piece2]) * bigMSideLengthOverall >= _vertexPosition[3, 8, cube2, piece2], "GravityZ2" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); // Ensure that the piece is located above the other piece respecting X when activated model.AddConstr( _vertexPosition[1, 0, cube1, piece1] >= _vertexPosition[1, 1, cube2, piece2] - (1 - _locatedOn[cube1, cube2, piece1, piece2]) * bigMSideLengthOverall, "GravityX1" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); model.AddConstr( _vertexPosition[1, 0, cube1, piece1] <= _vertexPosition[1, 8, cube2, piece2] + (1 - _locatedOn[cube1, cube2, piece1, piece2]) * bigMSideLengthOverall, "GravityX2" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); // Ensure that the piece is located above the other piece respecting Y when activated model.AddConstr( _vertexPosition[2, 0, cube1, piece1] >= _vertexPosition[2, 1, cube2, piece2] - (1 - _locatedOn[cube1, cube2, piece1, piece2]) * bigMSideLengthOverall, "GravityY1" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); model.AddConstr( _vertexPosition[2, 0, cube1, piece1] <= _vertexPosition[2, 8, cube2, piece2] + (1 - _locatedOn[cube1, cube2, piece1, piece2]) * bigMSideLengthOverall, "GravityY2" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); } } // Ensure the same container when located on another piece model.AddConstr( LinearExpression.Sum(Instance.Containers.Select(container => _bothInContainer[container, piece1, piece2])) >= LinearExpression.Sum(piece1.Original.Components.Select(cube1 => LinearExpression.Sum(piece2.Original.Components.Select(cube2 => _locatedOn[cube1, cube2, piece1, piece2])))), "SameContainerWhenLocatedOn" + piece1.ToIdentString() + piece2.ToIdentString()); } } // Ensure that one gravity requirement is met foreach (var piece1 in Instance.Pieces) { model.AddConstr( LinearExpression.Sum(Instance.PiecesWithVirtuals.Where(p => p != piece1).Select(piece2 => LinearExpression.Sum(piece1.Original.Components.Select(cube1 => LinearExpression.Sum(piece2.Original.Components.Select(cube2 => _locatedOn[cube1, cube2, piece1, piece2])))))) + _locatedOnGround[piece1] == 1, "GravityEnsurance" + piece1.ToIdentString()); } } // Ensure material compatibility only if desired if (Config.HandleCompatibility) { HashSet <VariablePiece> seenVariablePieces = new HashSet <VariablePiece>(); foreach (var piece1 in Instance.Pieces.OrderBy(p => p.ID)) { seenVariablePieces.Add(piece1); foreach (var piece2 in Instance.Pieces.OrderBy(p => p.ID).Except(seenVariablePieces).Where(p => p.Material.IncompatibleMaterials.Contains(piece1.Material.MaterialClass))) { foreach (var container in Instance.Containers) { model.AddConstr( _itemIsPicked[piece1, container] + _itemIsPicked[piece2, container] <= 1, "MaterialCompatibility" + container.ToIdentString() + piece1.ToIdentString() + piece2.ToIdentString()); } } } } // Ensure that items which are not marked as stackable won't get stacked on if (Config.HandleStackability) { foreach (var piece1 in Instance.Pieces.Where(p => !p.Stackable)) { foreach (var piece2 in Instance.Pieces.Where(p => p != piece1)) { foreach (var cube1 in piece1.Original.Components) { foreach (var cube2 in piece2.Original.Components) { model.AddConstr( _locatedOn[cube2, cube1, piece2, piece1] == 0, "Stackability" + piece1.ToIdentString() + piece2.ToIdentString() + cube1.ToIdentString() + cube2.ToIdentString()); } } } } } // Ensure that no forbidden orientations get used if (Config.HandleForbiddenOrientations) { foreach (var piece in Instance.Pieces) { foreach (var forbiddenOrientation in piece.ForbiddenOrientations) { model.AddConstr( _itemOrientation[forbiddenOrientation, piece] == 0, "ForbiddenOrientation" + piece.ToIdentString() + "O" + forbiddenOrientation.ToString()); } } } // Forbid any kind of rotation if rotatability is not desired if (!Config.HandleRotatability) { foreach (var piece in Instance.Pieces) { foreach (var orientation in MeshConstants.ORIENTATIONS.Skip(1)) { model.AddConstr( _itemOrientation[orientation, piece] == 0, "NoRotatability" + piece.ToIdentString() + "O" + orientation.ToString()); } } } // Keep model up-to-date model.Update(); // Output some model statistics Config.Log("Model statistics:" + Environment.NewLine); Config.Log("ItemIsPicked: " + _itemIsPicked.Count + Environment.NewLine); Config.Log("ContainerUsed: " + _containerUsed.Count + Environment.NewLine); Config.Log("ItemOrientation: " + _itemOrientation.Count + Environment.NewLine); Config.Log("LocalReferenceFrameOrigin: " + _localReferenceFrameOrigin.Count + Environment.NewLine); Config.Log("VertexPosition: " + _vertexPosition.Count + Environment.NewLine); Config.Log("Lambda: " + _lambda.Count + Environment.NewLine); Config.Log("SigmaPlus: " + _sigmaPlus.Count + Environment.NewLine); Config.Log("SigmaMinus: " + _sigmaMinus.Count + Environment.NewLine); Config.Log("LocatedOnGround: " + _locatedOnGround.Count + Environment.NewLine); Config.Log("LocatedOn: " + _locatedOn.Count + Environment.NewLine); Config.Log("BothInContainer: " + _bothInContainer.Count + Environment.NewLine); // Return return(model); }
/// <summary> /// Transforms the object-model into a mathematical formulation /// </summary> /// <returns>The model</returns> internal override LinearModel Transform() { // Init LinearModel model = new LinearModel(ChosenSolver, Config.Log); // TODO retrieve this stuff in a better way!? double excessLengthShare = 0.1; // Set big Ms double bigMContainerLength = Instance.Containers.Max(c => c.Mesh.Length); double bigMContainerWidth = Instance.Containers.Max(c => c.Mesh.Width); double bigMContainerHeight = Instance.Containers.Max(c => c.Mesh.Height); double bigM = Instance.Containers.Max(c => Math.Max(Math.Max(c.Mesh.Length, c.Mesh.Width), c.Mesh.Height)); double slantBigM = Instance.Containers.Max(c => Math.Sqrt(Math.Pow(c.Mesh.Length, 2) + Math.Pow(c.Mesh.Width, 2) + Math.Pow(c.Mesh.Height, 2))) / Instance.Containers.Min(c => Math.Sqrt(Math.Pow(c.Mesh.Length, 2) + Math.Pow(c.Mesh.Width, 2) + Math.Pow(c.Mesh.Height, 2))); // --> Variables _pieceIsInContainer = new VariableCollection <Piece, Container>(model, VariableType.Integer, 0, 1, (Piece piece, Container container) => { return("PieceInContainer" + piece.ToIdentString() + container.ToIdentString()); }); _containerUsed = new VariableCollection <Container>(model, VariableType.Integer, 0, 1, (Container container) => { return("ContainerIsUsed" + container.ToIdentString()); }); _frontLeftBottomX = new VariableCollection <Piece>(model, VariableType.Continuous, 0, bigMContainerLength, (Piece piece) => { return("FrontLeftBottomPositionX" + piece.ToIdentString()); }); _frontLeftBottomY = new VariableCollection <Piece>(model, VariableType.Continuous, 0, bigMContainerWidth, (Piece piece) => { return("FrontLeftBottomPositionY" + piece.ToIdentString()); }); _frontLeftBottomZ = new VariableCollection <Piece>(model, VariableType.Continuous, 0, bigMContainerHeight, (Piece piece) => { return("FrontLeftBottomPositionZ" + piece.ToIdentString()); }); _rearRightTopX = new VariableCollection <Piece>(model, VariableType.Continuous, 0, bigMContainerLength, (Piece piece) => { return("RearRightTopPositionX" + piece.ToIdentString()); }); _rearRightTopY = new VariableCollection <Piece>(model, VariableType.Continuous, 0, bigMContainerWidth, (Piece piece) => { return("RearRightTopPositionY" + piece.ToIdentString()); }); _rearRightTopZ = new VariableCollection <Piece>(model, VariableType.Continuous, 0, bigMContainerHeight, (Piece piece) => { return("RearRightTopPositionZ" + piece.ToIdentString()); }); _left = new VariableCollection <Piece, Piece>(model, VariableType.Integer, 0, 1, (Piece piece1, Piece piece2) => { return("LeftFrom" + piece1.ToIdentString() + piece2.ToIdentString()); }); _right = new VariableCollection <Piece, Piece>(model, VariableType.Integer, 0, 1, (Piece piece1, Piece piece2) => { return("RightFrom" + piece1.ToIdentString() + piece2.ToIdentString()); }); _behind = new VariableCollection <Piece, Piece>(model, VariableType.Integer, 0, 1, (Piece piece1, Piece piece2) => { return("BehindFrom" + piece1.ToIdentString() + piece2.ToIdentString()); }); _front = new VariableCollection <Piece, Piece>(model, VariableType.Integer, 0, 1, (Piece piece1, Piece piece2) => { return("FrontFrom" + piece1.ToIdentString() + piece2.ToIdentString()); }); _above = new VariableCollection <Piece, Piece>(model, VariableType.Integer, 0, 1, (Piece piece1, Piece piece2) => { return("AboveFrom" + piece1.ToIdentString() + piece2.ToIdentString()); }); _below = new VariableCollection <Piece, Piece>(model, VariableType.Integer, 0, 1, (Piece piece1, Piece piece2) => { return("BelowFrom" + piece1.ToIdentString() + piece2.ToIdentString()); }); _rotation = new VariableCollection <Piece, int, int>(model, VariableType.Integer, 0, 1, (Piece piece, int p, int q) => { return("Rotation" + piece.ToIdentString() + "p" + p + "q" + q); }); _bothInContainer = new VariableCollection <Container, Piece, Piece>(model, VariableType.Integer, 0, 1, (Container container, Piece piece1, Piece piece2) => { return("SameContainer" + container.ToIdentString() + piece1.ToIdentString() + piece2.ToIdentString()); }); _locatedOn = new VariableCollection <Piece, Piece>(model, VariableType.Integer, 0, 1, (Piece piece1, Piece piece2) => { return("LocatedOn" + piece1.ToIdentString() + piece2.ToIdentString()); }); _locatedOnGround = new VariableCollection <Piece>(model, VariableType.Integer, 0, 1, (Piece piece) => { return("LocatedOnGround" + piece.ToIdentString()); }); // Keep model up-to-date model.Update(); // --> Objective switch (Config.Goal) { case OptimizationGoal.MinContainer: { // Minimize container count model.SetObjective( LinearExpression.Sum(Instance.Containers.Select(container => _containerUsed[container])), OptimizationSense.Minimize); } break; case OptimizationGoal.MaxUtilization: { // Maximize utilization model.SetObjective( LinearExpression.Sum(Instance.Pieces.Select(p => (p.Volume) * LinearExpression.Sum(Instance.Containers.Select(c => _pieceIsInContainer[p, c])))), OptimizationSense.Maximize); } break; default: break; } // --> Constraints // Only assign to container if the container is in use foreach (var container in Instance.Containers) { foreach (var piece in Instance.Pieces) { model.AddConstr( _pieceIsInContainer[piece, container] <= _containerUsed[container], "OnlyAssignIfContainerIsUsed" + container.ToIdentString() + piece.ToIdentString()); } } // Ensure that piece is only added to one container foreach (var piece in Instance.Pieces) { model.AddConstr( (Config.Goal == OptimizationGoal.MaxUtilization) ? LinearExpression.Sum(Instance.Containers.Select(c => _pieceIsInContainer[piece, c])) <= 1 : LinearExpression.Sum(Instance.Containers.Select(c => _pieceIsInContainer[piece, c])) == 1, "AssignToSingleContainer" + piece.ToIdentString()); } // Ensure that the gross weight is not exceeded // TODO enable again!? //foreach (var container in _instance.Containers) //{ // model.AddConstraint( // Expression.Sum(_instance.Pieces.Select(p => _pieceIsInContainer[p, container] * p.Weight)) <= containerGrossWeight, // "GrossWeightCapacityLimitation" + container.ToIdentString()); //} // Ensure that the pieces stay in the container foreach (var piece in Instance.Pieces) { foreach (var container in Instance.Containers) { // Consider X-value model.AddConstr( _rearRightTopX[piece] <= bigMContainerLength - ((bigMContainerLength - container.Mesh.Length) * _pieceIsInContainer[piece, container]), "StayInsideX" + piece.ToIdentString() + container.ToIdentString()); // Consider Y-value model.AddConstr( _rearRightTopY[piece] <= bigMContainerWidth - ((bigMContainerWidth - container.Mesh.Width) * _pieceIsInContainer[piece, container]), "StayInsideY" + piece.ToIdentString() + container.ToIdentString()); // Consider Z-value model.AddConstr( _rearRightTopZ[piece] <= bigMContainerHeight - ((bigMContainerHeight - container.Mesh.Height) * _pieceIsInContainer[piece, container]), "StayInsideZ" + piece.ToIdentString() + container.ToIdentString()); } } // Ensure that pieces do not protrude slants foreach (var container in Instance.Containers) { foreach (var slant in container.Slants) { foreach (var piece in Instance.Pieces) { // Use vertex depending on the normal vector of the slant model.AddConstr( ((slant.NormalVector.X >= 0 ? _rearRightTopX[piece] : _frontLeftBottomX[piece]) - slant.Position.X) * slant.NormalVector.X + ((slant.NormalVector.Y >= 0 ? _rearRightTopY[piece] : _frontLeftBottomY[piece]) - slant.Position.Y) * slant.NormalVector.Y + ((slant.NormalVector.Z >= 0 ? _rearRightTopZ[piece] : _frontLeftBottomZ[piece]) - slant.Position.Z) * slant.NormalVector.Z <= 0 + (1 - _pieceIsInContainer[piece, container]) * slantBigM, "StayInSlants" + slant.ToIdentString() + piece.ToIdentString() + container.ToIdentString() ); } } } // Ensure that the boxes can rotate by 90 degrees foreach (var piece in Instance.Pieces) { // Set x-value model.AddConstr( _rearRightTopX[piece] - _frontLeftBottomX[piece] == _rotation[piece, 1, 1] * piece.Original.BoundingBox.Length + _rotation[piece, 1, 2] * piece.Original.BoundingBox.Width + _rotation[piece, 1, 3] * piece.Original.BoundingBox.Height, "RotationXValue" + piece.ToIdentString()); // Set y-value model.AddConstr( _rearRightTopY[piece] - _frontLeftBottomY[piece] == _rotation[piece, 2, 1] * piece.Original.BoundingBox.Length + _rotation[piece, 2, 2] * piece.Original.BoundingBox.Width + _rotation[piece, 2, 3] * piece.Original.BoundingBox.Height, "RotationYValue" + piece.ToIdentString()); // Set z-value model.AddConstr( _rearRightTopZ[piece] - _frontLeftBottomZ[piece] == _rotation[piece, 3, 1] * piece.Original.BoundingBox.Length + _rotation[piece, 3, 2] * piece.Original.BoundingBox.Width + _rotation[piece, 3, 3] * piece.Original.BoundingBox.Height, "RotationZValue" + piece.ToIdentString()); } foreach (var piece in Instance.Pieces) { foreach (var q in _rotationIndexes) { model.AddConstr( LinearExpression.Sum(_rotationIndexes.Select(p => _rotation[piece, p, q])) == 1, "RotationHelper1" + piece.ToIdentString() + "q" + q); } foreach (var p in _rotationIndexes) { model.AddConstr( LinearExpression.Sum(_rotationIndexes.Select(q => _rotation[piece, p, q])) == 1, "RotationHelper2" + piece.ToIdentString() + "p" + p); } } // Link FLB corner point with RRT corner point for virtual pieces foreach (var container in Instance.Containers) { foreach (var virtualPiece in container.VirtualPieces) { model.AddConstr( _rearRightTopX[virtualPiece] - _frontLeftBottomX[virtualPiece] == virtualPiece[virtualPiece.FixedOrientation].BoundingBox.Length, "LinkFLBRRTX" + container.ToIdentString() + virtualPiece.ToIdentString()); model.AddConstr( _rearRightTopY[virtualPiece] - _frontLeftBottomY[virtualPiece] == virtualPiece[virtualPiece.FixedOrientation].BoundingBox.Width, "LinkFLBRRTY" + container.ToIdentString() + virtualPiece.ToIdentString()); model.AddConstr( _rearRightTopZ[virtualPiece] - _frontLeftBottomZ[virtualPiece] == virtualPiece[virtualPiece.FixedOrientation].BoundingBox.Height, "LinkFLBRRTZ" + container.ToIdentString() + virtualPiece.ToIdentString()); } } // Link the variables foreach (var container in Instance.Containers) { // Remember seen pieces HashSet <Piece> seenPieces = new HashSet <Piece>(); // Iterate pieces foreach (var piece in Instance.PiecesWithVirtuals) { // Remember piece seenPieces.Add(piece); // Iterate pieces (inner) foreach (var secondPiece in Instance.PiecesWithVirtuals.Except(seenPieces)) { model.AddConstr( _frontLeftBottomX[piece] - _rearRightTopX[secondPiece] + ((1 - _left[secondPiece, piece]) * bigM) + ((2 - (_pieceIsInContainer[piece, container] + _pieceIsInContainer[secondPiece, container])) * bigM) >= 0, "Link1" + container.ToIdentString() + piece.ToIdentString() + secondPiece.ToIdentString()); model.AddConstr( _frontLeftBottomX[secondPiece] - _rearRightTopX[piece] + ((1 - _right[secondPiece, piece]) * bigM) + ((2 - (_pieceIsInContainer[piece, container] + _pieceIsInContainer[secondPiece, container])) * bigM) >= 0, "Link2" + container.ToIdentString() + piece.ToIdentString() + secondPiece.ToIdentString()); model.AddConstr( _frontLeftBottomY[piece] - _rearRightTopY[secondPiece] + ((1 - _behind[secondPiece, piece]) * bigM) + ((2 - (_pieceIsInContainer[piece, container] + _pieceIsInContainer[secondPiece, container])) * bigM) >= 0, "Link3" + container.ToIdentString() + piece.ToIdentString() + secondPiece.ToIdentString()); model.AddConstr( _frontLeftBottomY[secondPiece] - _rearRightTopY[piece] + ((1 - _front[secondPiece, piece]) * bigM) + ((2 - (_pieceIsInContainer[piece, container] + _pieceIsInContainer[secondPiece, container])) * bigM) >= 0, "Link4" + container.ToIdentString() + piece.ToIdentString() + secondPiece.ToIdentString()); model.AddConstr( _frontLeftBottomZ[piece] - _rearRightTopZ[secondPiece] + ((1 - _above[secondPiece, piece]) * bigM) + ((2 - (_pieceIsInContainer[piece, container] + _pieceIsInContainer[secondPiece, container])) * bigM) >= 0, "Link5" + container.ToIdentString() + piece.ToIdentString() + secondPiece.ToIdentString()); model.AddConstr( _frontLeftBottomZ[secondPiece] - _rearRightTopZ[piece] + ((1 - _below[secondPiece, piece]) * bigM) + ((2 - (_pieceIsInContainer[piece, container] + _pieceIsInContainer[secondPiece, container])) * bigM) >= 0, "Link6" + container.ToIdentString() + piece.ToIdentString() + secondPiece.ToIdentString()); } } } // Ensure non-overlapping foreach (var piece in Instance.PiecesWithVirtuals) { foreach (var secondPiece in Instance.PiecesWithVirtuals) { model.AddConstr( _left[secondPiece, piece] + _right[secondPiece, piece] + _behind[secondPiece, piece] + _front[secondPiece, piece] + _above[secondPiece, piece] + _below[secondPiece, piece] >= 1, "EnsureNonOverlapping" + piece.ToIdentString() + secondPiece.ToIdentString()); } } // Redundant volume limitation constraints foreach (var container in Instance.Containers) { model.AddConstr( LinearExpression.Sum(Instance.PiecesWithVirtuals.Select(piece => _pieceIsInContainer[piece, container] * piece.Original.BoundingBox.Volume)) <= (container.Mesh.Length * container.Mesh.Width * container.Mesh.Height), "RedundantVolumeLimitation" + container.ToIdentString()); } // Fix virtual pieces to the predefined positions and orientations foreach (var container in Instance.Containers) { foreach (var virtualPiece in container.VirtualPieces) { model.AddConstr( _pieceIsInContainer[virtualPiece, container] == 1, "VirtualPieceFixContainer" + container.ToIdentString() + virtualPiece.ToIdentString()); model.AddConstr( _frontLeftBottomX[virtualPiece] == virtualPiece.FixedPosition.X, "VirtualPieceFixPositionX" + container.ToIdentString() + virtualPiece.ToIdentString()); model.AddConstr( _frontLeftBottomY[virtualPiece] == virtualPiece.FixedPosition.Y, "VirtualPieceFixPositionY" + container.ToIdentString() + virtualPiece.ToIdentString()); model.AddConstr( _frontLeftBottomZ[virtualPiece] == virtualPiece.FixedPosition.Z, "VirtualPieceFixPositionZ" + container.ToIdentString() + virtualPiece.ToIdentString()); } } // Ensure gravity only if desired if (Config.HandleGravity) { // Gravity Piece[] supporteeArray = Instance.Pieces.ToArray(); Piece[] supporterArray = Instance.PiecesWithVirtuals.ToArray(); _pieceTuples = new List <Tuple <Piece, Piece> >(); for (int i = 0; i < supporteeArray.Length; i++) { for (int j = 0; j < supporterArray.Length; j++) { if (supporteeArray[i] != supporterArray[j]) { _pieceTuples.Add(new Tuple <Piece, Piece>(supporteeArray[i], supporterArray[j])); } } } foreach (var tuple in _pieceTuples) { // Z-distance has to equal 0 if located on the specified piece model.AddConstr( _frontLeftBottomZ[tuple.Item1] <= _rearRightTopZ[tuple.Item2] + (1 - _locatedOn[tuple.Item1, tuple.Item2]) * bigM, "EnsureGravityZ1-" + tuple.Item1.ToIdentString() + tuple.Item2.ToIdentString()); model.AddConstr( _frontLeftBottomZ[tuple.Item1] + (1 - _locatedOn[tuple.Item1, tuple.Item2]) * bigM >= _rearRightTopZ[tuple.Item2], "EnsureGravityZ2-" + tuple.Item1.ToIdentString() + tuple.Item2.ToIdentString()); // Located on another piece regarding X and Y - respectively stability model.AddConstr( _rearRightTopY[tuple.Item1] <= _rearRightTopY[tuple.Item2] + (excessLengthShare * (_rearRightTopY[tuple.Item1] - _frontLeftBottomY[tuple.Item1])) + (bigM * (1 - _locatedOn[tuple.Item1, tuple.Item2])), "EnsureStabilityY1-" + tuple.Item1.ToIdentString() + tuple.Item2.ToIdentString()); model.AddConstr( _rearRightTopX[tuple.Item1] <= _rearRightTopX[tuple.Item2] + (excessLengthShare * (_rearRightTopX[tuple.Item1] - _frontLeftBottomX[tuple.Item1])) + (bigM * (1 - _locatedOn[tuple.Item1, tuple.Item2])), "EnsureStabilityX1-" + tuple.Item1.ToIdentString() + tuple.Item2.ToIdentString()); model.AddConstr( _frontLeftBottomY[tuple.Item1] >= _frontLeftBottomY[tuple.Item2] - (excessLengthShare * (_rearRightTopY[tuple.Item1] - _frontLeftBottomY[tuple.Item1])) - (bigM * (1 - _locatedOn[tuple.Item1, tuple.Item2])), "EnsureStabilityY2-" + tuple.Item1.ToIdentString() + tuple.Item2.ToIdentString()); model.AddConstr( _frontLeftBottomX[tuple.Item1] >= _frontLeftBottomX[tuple.Item2] - (excessLengthShare * (_rearRightTopX[tuple.Item1] - _frontLeftBottomX[tuple.Item1])) - (bigM * (1 - _locatedOn[tuple.Item1, tuple.Item2])), "EnsureStabilityX2-" + tuple.Item1.ToIdentString() + tuple.Item2.ToIdentString()); // Track whether items are put into the same container foreach (var container in Instance.Containers) { model.AddConstr( _pieceIsInContainer[tuple.Item1, container] + _pieceIsInContainer[tuple.Item2, container] >= 2 * _bothInContainer[container, tuple.Item1, tuple.Item2], "BothInSameContainer" + container.ToIdentString() + tuple.Item1.ToIdentString() + tuple.Item2.ToIdentString()); } // Ensure the same container when located on another piece model.AddConstr( LinearExpression.Sum(Instance.Containers.Select(container => _bothInContainer[container, tuple.Item1, tuple.Item2])) >= _locatedOn[tuple.Item1, tuple.Item2], "SameContainerWhenLocatedOn" + tuple.Item1.ToIdentString() + tuple.Item2.ToIdentString()); } // Located on ground if no piece is below the specified one foreach (var piece in Instance.Pieces) { model.AddConstr( _frontLeftBottomZ[piece] <= (1 - _locatedOnGround[piece]) * bigM, "LocatedOnGround" + piece.ToIdentString()); } // Locate pieces on other pieces or the ground foreach (var piece in Instance.Pieces) { model.AddConstr( LinearExpression.Sum(_pieceTuples .Where(t => t.Item1 == piece) .Select(tuple => _locatedOn[tuple.Item1, tuple.Item2])) + _locatedOnGround[piece] == 1, "LocatedOnAnotherPiece-" + piece.ToIdentString()); } } // Ensure material compatibility only if desired if (Config.HandleCompatibility) { HashSet <VariablePiece> seenPieces = new HashSet <VariablePiece>(); foreach (var piece1 in Instance.Pieces.OrderBy(p => p.ID)) { seenPieces.Add(piece1); foreach (var piece2 in Instance.Pieces.OrderBy(p => p.ID).Except(seenPieces).Where(p => p.Material.IncompatibleMaterials.Contains(piece1.Material.MaterialClass))) { foreach (var container in Instance.Containers) { model.AddConstr( _pieceIsInContainer[piece1, container] + _pieceIsInContainer[piece2, container] <= 1, "MaterialCompatibility" + container.ToIdentString() + piece1.ToIdentString() + piece2.ToIdentString()); } } } } // Ensure that items which are not marked as stackable won't get stacked on if (Config.HandleStackability) { foreach (var piece1 in Instance.Pieces.Where(p => !p.Stackable)) { foreach (var piece2 in Instance.Pieces.Where(p => p != piece1)) { model.AddConstr( _locatedOn[piece2, piece1] == 0, "Stackability" + piece1.ToIdentString() + piece2.ToIdentString()); } } } // Ensure that no forbidden orientations get used if (Config.HandleForbiddenOrientations) { foreach (var piece in Instance.Pieces) { foreach (var forbiddenOrientation in piece.ForbiddenOrientations) { switch (forbiddenOrientation) { case 0: model.AddConstr( _rotation[piece, 1, 1] + _rotation[piece, 2, 2] + _rotation[piece, 3, 3] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + forbiddenOrientation.ToString()); break; case 2: model.AddConstr( _rotation[piece, 1, 1] + _rotation[piece, 2, 3] + _rotation[piece, 3, 2] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + forbiddenOrientation.ToString()); break; case 8: model.AddConstr( _rotation[piece, 1, 2] + _rotation[piece, 2, 1] + _rotation[piece, 3, 3] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + forbiddenOrientation.ToString()); break; case 10: model.AddConstr( _rotation[piece, 1, 3] + _rotation[piece, 2, 1] + _rotation[piece, 3, 2] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + forbiddenOrientation.ToString()); break; case 16: model.AddConstr( _rotation[piece, 1, 2] + _rotation[piece, 2, 3] + _rotation[piece, 3, 1] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + forbiddenOrientation.ToString()); break; case 18: model.AddConstr( _rotation[piece, 1, 3] + _rotation[piece, 2, 2] + _rotation[piece, 3, 1] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + forbiddenOrientation.ToString()); break; default: break; } } } } // Forbid any kind of rotation if rotatability is not desired if (!Config.HandleRotatability) { foreach (var piece in Instance.Pieces) { foreach (var orientation in MeshConstants.ORIENTATIONS.Skip(1)) { switch (orientation) { case 0: model.AddConstr( _rotation[piece, 1, 1] + _rotation[piece, 2, 2] + _rotation[piece, 3, 3] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + orientation.ToString()); break; case 2: model.AddConstr( _rotation[piece, 1, 1] + _rotation[piece, 2, 3] + _rotation[piece, 3, 2] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + orientation.ToString()); break; case 8: model.AddConstr( _rotation[piece, 1, 2] + _rotation[piece, 2, 1] + _rotation[piece, 3, 3] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + orientation.ToString()); break; case 10: model.AddConstr( _rotation[piece, 1, 3] + _rotation[piece, 2, 1] + _rotation[piece, 3, 2] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + orientation.ToString()); break; case 16: model.AddConstr( _rotation[piece, 1, 2] + _rotation[piece, 2, 3] + _rotation[piece, 3, 1] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + orientation.ToString()); break; case 18: model.AddConstr( _rotation[piece, 1, 3] + _rotation[piece, 2, 2] + _rotation[piece, 3, 1] <= 2, "ForbiddenOrientation" + piece.ToIdentString() + "O" + orientation.ToString()); break; default: break; } } } } // Keep model up-to-date model.Update(); // Output some model statistics Config.Log("Model statistics:" + Environment.NewLine); Config.Log("PieceIsInContainer: " + _pieceIsInContainer.Count + Environment.NewLine); Config.Log("ContainerUsed: " + _containerUsed.Count + Environment.NewLine); Config.Log("FrontLeftBottomX: " + _frontLeftBottomX.Count + Environment.NewLine); Config.Log("FrontLeftBottomY: " + _frontLeftBottomY.Count + Environment.NewLine); Config.Log("FrontLeftBottomZ: " + _frontLeftBottomZ.Count + Environment.NewLine); Config.Log("RearRightTopX: " + _rearRightTopX.Count + Environment.NewLine); Config.Log("RearRightTopY: " + _rearRightTopY.Count + Environment.NewLine); Config.Log("RearRightTopZ: " + _rearRightTopZ.Count + Environment.NewLine); Config.Log("Left: " + _left.Count + Environment.NewLine); Config.Log("Right: " + _right.Count + Environment.NewLine); Config.Log("Behind: " + _behind.Count + Environment.NewLine); Config.Log("Front: " + _front.Count + Environment.NewLine); Config.Log("Above: " + _above.Count + Environment.NewLine); Config.Log("Below: " + _below.Count + Environment.NewLine); Config.Log("Rotation: " + _rotation.Count + Environment.NewLine); Config.Log("BothInContainer: " + _bothInContainer.Count + Environment.NewLine); Config.Log("LocatedOn: " + _locatedOn.Count + Environment.NewLine); Config.Log("LocatedOnGround: " + _locatedOnGround.Count + Environment.NewLine); // Return return(model); }
private void AnalyzeServiceUnits() { // Keep track of count int overallCount = _serviceUnits.Count * _config.GroupsIndividuals.Count; int counter = 0; // --> Solve all _config.LogLine("Analyzing all service units within all given groups - " + overallCount + " to go!"); // Iterate all groups (use a dummy group if no grouping has to be done - this should work ok with the interior part of the loops) foreach (var groupIdents in _config.GroupsIndividuals) { // Investigate all service units for this group within the given scenario foreach (var serviceUnitInFocus in _serviceUnits) { // Log _config.Log((++counter) + "/" + overallCount + ": " + serviceUnitInFocus.Ident + "/" + (groupIdents.Any() ? string.Join(",", groupIdents.Select(i => i.Item2)) : "Overall") + " (SU/group)"); // Init LinearModel model = new LinearModel(_config.SolverChoice, null /* Disable output flood for now, use following to enable: (string s) => { _config.Log(s); } */); // --> Init variables Variable efficiencyRating = new Variable(model, VariableType.Continuous, double.NegativeInfinity, double.PositiveInfinity, "EfficiencyRating"); VariableCollection <ServiceUnit> weights = new VariableCollection <ServiceUnit>(model, VariableType.Continuous, 0, double.PositiveInfinity, (ServiceUnit u) => { return(u.Ident); }); // --> Build model // Add objective if (_config.InputOriented) { model.SetObjective(efficiencyRating + 0, OptimizationSense.Minimize); } else { model.SetObjective(efficiencyRating + 0, OptimizationSense.Maximize); } // Add input constraints if (_config.Inputs.Any()) { foreach (var inputEntry in _config.Inputs) { // Shift and flip (if required) all values Dictionary <ServiceUnit, double> inputValues = _serviceUnits.ToDictionary( k => k, s => s.GetValue(inputEntry.Item1, (FootprintDatapoint f) => { return(groupIdents.All(g => g.Item2 == f[g.Item1].ToString())); })); // In case of loss values, convert them if (inputEntry.Item2 == InputType.Hindrance) { switch (inputEntry.Item1) { case FootprintDatapoint.FootPrintEntry.SKUs: // Simply inverse them foreach (var serviceUnit in inputValues.Keys.ToList()) { inputValues[serviceUnit] = 1.0 / inputValues[serviceUnit]; } break; default: // Simply flip them (next step will convert the numbers to positive ones again) foreach (var serviceUnit in inputValues.Keys.ToList()) { inputValues[serviceUnit] *= -1; } break; } } // If there is a negative value, shift all values to positive ones double minOutputValue = inputValues.Min(v => v.Value); if (minOutputValue < 0) { foreach (var serviceUnit in inputValues.Keys.ToList()) { inputValues[serviceUnit] += Math.Abs(minOutputValue); } } // Add constraint if (_config.InputOriented) { model.AddConstr( // Sum of all other weighted inputs LinearExpression.Sum(_serviceUnits.Select(s => inputValues[s]), _serviceUnits.Select(s => weights[s])) <= // Shall be smaller than the weighted input of the service unit in focus efficiencyRating * inputValues[serviceUnitInFocus], "Input" + inputEntry); } else { model.AddConstr( // Sum of all other weighted inputs LinearExpression.Sum(_serviceUnits.Select(s => inputValues[s]), _serviceUnits.Select(s => weights[s])) <= // Shall be smaller than the weighted input of the service unit in focus inputValues[serviceUnitInFocus], "Input" + inputEntry); } } } else { // Add constant uniform inputs for all service units if (_config.InputOriented) { model.AddConstr( // Sum of all other weighted inputs LinearExpression.Sum(_serviceUnits.Select(s => 1.0), _serviceUnits.Select(s => weights[s])) <= // Shall be smaller than the weighted input of the service unit in focus efficiencyRating * 1.0, "InputConstant"); } else { model.AddConstr( // Sum of all other weighted inputs LinearExpression.Sum(_serviceUnits.Select(s => 1.0), _serviceUnits.Select(s => weights[s])) <= // Shall be smaller than the weighted input of the service unit in focus 1.0, "InputConstant"); } } // Add output constraints if (_config.Outputs.Any()) { // Add output constraint using the actual entry foreach (var outputEntry in _config.Outputs) { // Shift and flip (if required) all values Dictionary <ServiceUnit, double> outputValues = _serviceUnits.ToDictionary( k => k, s => s.GetValue(outputEntry.Item1, (FootprintDatapoint f) => { return(groupIdents.All(g => g.Item2 == f[g.Item1].ToString())); })); // Store performance for this measure foreach (var serviceUnit in _serviceUnits) { _serviceUnitOutputMeasures[serviceUnit, outputEntry.Item1] = outputValues[serviceUnit]; } // In case of loss values, convert them if (outputEntry.Item2 == OutputType.Loss) { switch (outputEntry.Item1) { case FootprintDatapoint.FootPrintEntry.OrderTurnoverTimeAvg: case FootprintDatapoint.FootPrintEntry.OrderTurnoverTimeMed: case FootprintDatapoint.FootPrintEntry.OrderTurnoverTimeLQ: case FootprintDatapoint.FootPrintEntry.OrderTurnoverTimeUQ: case FootprintDatapoint.FootPrintEntry.OrderThroughputTimeAvg: case FootprintDatapoint.FootPrintEntry.OrderThroughputTimeMed: case FootprintDatapoint.FootPrintEntry.OrderThroughputTimeLQ: case FootprintDatapoint.FootPrintEntry.OrderThroughputTimeUQ: case FootprintDatapoint.FootPrintEntry.BundleTurnoverTimeAvg: case FootprintDatapoint.FootPrintEntry.BundleTurnoverTimeMed: case FootprintDatapoint.FootPrintEntry.BundleTurnoverTimeLQ: case FootprintDatapoint.FootPrintEntry.BundleTurnoverTimeUQ: case FootprintDatapoint.FootPrintEntry.BundleThroughputTimeAvg: case FootprintDatapoint.FootPrintEntry.BundleThroughputTimeMed: case FootprintDatapoint.FootPrintEntry.BundleThroughputTimeLQ: case FootprintDatapoint.FootPrintEntry.BundleThroughputTimeUQ: // Simply inverse them foreach (var serviceUnit in outputValues.Keys.ToList()) { outputValues[serviceUnit] = 1.0 / outputValues[serviceUnit]; } break; case FootprintDatapoint.FootPrintEntry.OSIdleTimeAvg: case FootprintDatapoint.FootPrintEntry.OSIdleTimeMed: case FootprintDatapoint.FootPrintEntry.OSIdleTimeLQ: case FootprintDatapoint.FootPrintEntry.OSIdleTimeUQ: case FootprintDatapoint.FootPrintEntry.ISIdleTimeAvg: case FootprintDatapoint.FootPrintEntry.ISIdleTimeMed: case FootprintDatapoint.FootPrintEntry.ISIdleTimeLQ: case FootprintDatapoint.FootPrintEntry.ISIdleTimeUQ: case FootprintDatapoint.FootPrintEntry.OSDownTimeAvg: case FootprintDatapoint.FootPrintEntry.OSDownTimeMed: case FootprintDatapoint.FootPrintEntry.OSDownTimeLQ: case FootprintDatapoint.FootPrintEntry.OSDownTimeUQ: case FootprintDatapoint.FootPrintEntry.ISDownTimeAvg: case FootprintDatapoint.FootPrintEntry.ISDownTimeMed: case FootprintDatapoint.FootPrintEntry.ISDownTimeLQ: case FootprintDatapoint.FootPrintEntry.ISDownTimeUQ: case FootprintDatapoint.FootPrintEntry.LateOrdersFractional: // As these should be values between 0.0 and 1.0 just flip them within the range { if (outputValues.Values.Any(v => v < 0 || v > 1)) { throw new ArgumentException("Expected values to be within the range [0,1], but found one out of range!"); } foreach (var serviceUnit in outputValues.Keys.ToList()) { outputValues[serviceUnit] = 1.0 - outputValues[serviceUnit]; } } break; case FootprintDatapoint.FootPrintEntry.TimingDecisionsOverall: case FootprintDatapoint.FootPrintEntry.TimingPathPlanningAvg: case FootprintDatapoint.FootPrintEntry.TimingPathPlanningOverall: case FootprintDatapoint.FootPrintEntry.TimingPathPlanningCount: case FootprintDatapoint.FootPrintEntry.TimingTaskAllocationAvg: case FootprintDatapoint.FootPrintEntry.TimingTaskAllocationOverall: case FootprintDatapoint.FootPrintEntry.TimingTaskAllocationCount: case FootprintDatapoint.FootPrintEntry.TimingItemStorageAvg: case FootprintDatapoint.FootPrintEntry.TimingItemStorageOverall: case FootprintDatapoint.FootPrintEntry.TimingItemStorageCount: case FootprintDatapoint.FootPrintEntry.TimingPodStorageAvg: case FootprintDatapoint.FootPrintEntry.TimingPodStorageOverall: case FootprintDatapoint.FootPrintEntry.TimingPodStorageCount: case FootprintDatapoint.FootPrintEntry.TimingRepositioningAvg: case FootprintDatapoint.FootPrintEntry.TimingRepositioningOverall: case FootprintDatapoint.FootPrintEntry.TimingRepositioningCount: case FootprintDatapoint.FootPrintEntry.TimingReplenishmentBatchingAvg: case FootprintDatapoint.FootPrintEntry.TimingReplenishmentBatchingOverall: case FootprintDatapoint.FootPrintEntry.TimingReplenishmentBatchingCount: case FootprintDatapoint.FootPrintEntry.TimingOrderBatchingAvg: case FootprintDatapoint.FootPrintEntry.TimingOrderBatchingOverall: case FootprintDatapoint.FootPrintEntry.TimingOrderBatchingCount: // Simply inverse them foreach (var serviceUnit in outputValues.Keys.ToList()) { outputValues[serviceUnit] = 1.0 / outputValues[serviceUnit]; } break; default: // Simply flip them (next step will convert the numbers to positive ones again) foreach (var serviceUnit in outputValues.Keys.ToList()) { outputValues[serviceUnit] *= -1; } break; } } // If there is a negative value, shift all values to positive ones double minOutputValue = outputValues.Min(v => v.Value); if (minOutputValue < 0) { foreach (var serviceUnit in outputValues.Keys.ToList()) { outputValues[serviceUnit] += Math.Abs(minOutputValue); } } // Add constraint if (_config.InputOriented) { model.AddConstr( // Sum of all other weighted inputs LinearExpression.Sum(_serviceUnits.Select(s => outputValues[s]), _serviceUnits.Select(s => weights[s])) >= // Shall be smaller than the weighted input of the service unit in focus outputValues[serviceUnitInFocus], "Output" + outputEntry); } else { model.AddConstr( // Sum of all other weighted inputs LinearExpression.Sum(_serviceUnits.Select(s => outputValues[s]), _serviceUnits.Select(s => weights[s])) >= // Shall be smaller than the weighted input of the service unit in focus efficiencyRating * outputValues[serviceUnitInFocus], "Output" + outputEntry); } } } else { // Add constant uniform outputs for all service units if (_config.InputOriented) { model.AddConstr( // Sum of all other weighted inputs LinearExpression.Sum(_serviceUnits.Select(s => 1.0), _serviceUnits.Select(s => weights[s])) >= // Shall be smaller than the weighted input of the service unit in focus 1.0, "OutputConstant"); } else { model.AddConstr( // Sum of all other weighted inputs LinearExpression.Sum(_serviceUnits.Select(s => 1.0), _serviceUnits.Select(s => weights[s])) >= // Shall be smaller than the weighted input of the service unit in focus efficiencyRating * 1.0, "OutputConstant"); } } // Sum weights if (_config.WeightsSumToOne) { // Summed weights have to be equal to one model.AddConstr(LinearExpression.Sum(_serviceUnits.Select(s => weights[s])) == 1.0, "WeightSum"); } // Commit changes model.Update(); // --> Solve model model.Optimize(); // --> Get solution if (!model.HasSolution()) { throw new InvalidOperationException("Model is infeasible for service unit: " + serviceUnitInFocus.Ident); } _serviceUnitScores[groupIdents, serviceUnitInFocus] = efficiencyRating.Value; _config.LogLine(" - Efficiency: " + (efficiencyRating.Value * 100).ToString(IOConstants.FORMATTER) + " %"); } // Transform efficiency if (!_config.InputOriented && _config.TransformOutputOrientedEfficiency) { //double maxEfficiency = _serviceUnits.Max(s => _serviceUnitScores[groupIdents, s]); foreach (var serviceUnit in _serviceUnits) { _serviceUnitScores[groupIdents, serviceUnit] = 1.0 / _serviceUnitScores[groupIdents, serviceUnit]; } // _serviceUnitScores[groupIdents, serviceUnit] /= maxEfficiency; } } }