Beispiel #1
0
        /// <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));
        }
Beispiel #2
0
        /// <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);
        }
Beispiel #4
0
        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;
                }
            }
        }