Initializing help system before first use

Burglar - Formulating logical constraints


Type: Knapsack problem
Rating: 1 (simple)
Description: This example shows how to setup a simple knapsack type problem in which an item selection should be made maximally profitable under some weight restriction. We first solve a pure knapsack problem. Then, we refine the constraints by explicitly forbidding certain item combinations, thereby showing multiple ways how to create indicator constraints.
File(s): BinBurglar.cs, BinBurglar.csproj


BinBurglar.cs
// (c) 2023-2024 Fair Isaac Corporation

using System;
using Optimizer;
using Optimizer.Maps;
using System.Linq;
using ColumnType = Optimizer.ColumnType;
using static Optimizer.Objects.Utils;
using System.Collections.Generic;
using Optimizer.Objects;

namespace XpressExamples
{
    /// <summary>Burglar problem, binary variable formulation.</summary>
    /// <remarks>
    /// Select items to maximize profit while respecting a maximum weight constraint.
    ///
    /// This example shows how to setup a simple knapsack type problem in which an item
    /// selection should be made maximally profitable under some weight restriction.
    /// We first solve a pure knapsack problem. Then, we refine the constraints by
    /// explicitly forbidding certain item combinations, thereby showing multiple ways
    /// how to create indicator constraints.
    /// </remarks>
    class BinBurglar
    {
        /// <summary>Item values.</summary>
        /// <remarks>The goal is to maximize the sum of selected items.</remarks>
        private static readonly double[] values = new double[]{
            15, 100, 90, 60, 40, 15, 10, 1
        };

        /// <summary>Item weights.</summary>
        /// <remarks>The weights of selected items must not exceed <c>WTMAX</c>.</remarks>
        private static readonly double[] weights = new double[]{
            2, 20, 20, 30, 40, 30, 60, 10
        };

        private static readonly string[] itemNames = new string[] {
            "camera", "necklace", "vase", "picture", "tv", "video", "chest", "brick"
        };

        /// <summary>Maximum weight for selected items.</summary>
        private static readonly double WTMAX = 102;

        public static void Main(string[] args)
        {
            using (XpressProblem prob = new XpressProblem())
            {
                // Output all messages.
                prob.callbacks.AddMessageCallback(DefaultMessageListener.Console);

                Variable[] x = prob.AddVariables(values.Length)
                    // 1 if we take item i; 0 otherwise
                    .WithType(ColumnType.Binary)
                    .WithName(i => itemNames[i])
                    .ToArray();

                // Weight restriction
                prob.AddConstraint(ScalarProduct(x, weights) <= WTMAX);

                // Set objective: maximize total value
                prob.SetObjective(ScalarProduct(x, values), Optimizer.ObjSense.Maximize);

                // Solve
                prob.Optimize();
                if (prob.SolStatus != Optimizer.SolStatus.Optimal)
                    throw new Exception("optimization failed with status " + prob.SolStatus);
                double[] sol = prob.GetSolution();
                // Print out the solution
                Console.WriteLine("Objective: {0}", prob.ObjVal);
                foreach (Variable v in x)
                {
                    Console.Write($"{v.GetName()}: {v.GetValue(sol)} ");
                }
                Console.WriteLine();

                Console.WriteLine("Amending some additional constraints");

                // create a dictionary of key value pairs for easy access to variables by name
                Dictionary<string, Variable> xByName = itemNames.Zip(x).ToDictionary(kvp => kvp.First, kvp => kvp.Second);

                Variable vase = xByName["vase"];

                //
                // Now add constraints to consider the items vase and picture as one pair and
                // the items "tv" and "video" as a second pair so that the pairs are always
                // taken together
                //

                // x["vase"] == x["picture"]
                prob.AddConstraint(vase == xByName["picture"]);

                // x["tv"] == x["video"]
                prob.AddConstraint(xByName["tv"] == xByName["video"]);

                //
                // Logical constraint: Either take the first pair "vase" and "picture" or the
                // second pair "tv" and "video" (but not both pairs).
                //
                Expression tvPlusVideo = 1.0 * xByName["tv"] + 1.0 * xByName["video"];

                // x["vase"] = 1 -> x["tv"] + x["video"]=0
                prob.AddConstraint(vase.IfThen(tvPlusVideo.Leq(0.0)));

                //
                // x["vase"]=0 -> x["tv"]+x["video"]=2
                // Below, we use SetIndicator as an alternative way to create
                // the indicator
                //
                Inequality enablePairTwo = prob.AddConstraint(tvPlusVideo >= 2.0);
                // note that using "false" in the below function
                // means that the inequality is enabled if x["vase"] is 0
                prob.SetIndicator(vase, false, enablePairTwo);

                // save the final problem to file for manual inspection
                prob.WriteProb("BinBurglarIndicator.lp", "l");

                // Solve
                prob.Optimize();
                if (prob.SolStatus != Optimizer.SolStatus.Optimal)
                    throw new Exception("optimization failed with status " + prob.SolStatus);

                sol = prob.GetSolution();
                // Print out the solution
                Console.WriteLine("Objective: {0}", prob.ObjVal);
                foreach (Variable v in x)
                {
                    Console.Write($"{v.GetName()}: {v.GetValue(sol)} ");
                }
                Console.WriteLine();



            }
        }
    }
}

BinBurglar.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>

    <IsPackable>false</IsPackable>
    <XpressExampleFiles Condition="'$(XpressExampleFiles)'==''">../../../data</XpressExampleFiles>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="FICO.Xpress.XPRSdn" Version="41.1.1" /> <!-- Version 41.1.1 or later -->
  </ItemGroup>
  
</Project>

© 2001-2024 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.