Initializing help system before first use

Capital budgeting - Using multi-objective optimization


Type: Capital budgeting
Rating: 2 (easy-medium)
Description: Capital budgeting example, solved using three multi-objective approaches:
  • Lexicographic approach, solving first to minimize capital expended and second to maximize return
  • Blended approach, solving a weighted combination of both objectives simultaneously
  • Lexicographic approach, with the objective priorities reversed
File(s): CapitalBudgeting.cs, CapitalBudgeting.csproj


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

using System;
using Optimizer;
using Optimizer.Objects;
using static Optimizer.Objects.Utils;

namespace XpressExamples
{
    /// <summary>Capital budgeting example, solved using three multi-objective approaches</summary>
    /// <remarks>
    /// <list type='number'>
    /// <item><description>
    ///   Lexicographic approach, solving first to minimize capital expended
    ///   and second to maximize return
    /// </description></item>
    /// <item><description>
    ///   Blended approach, solving a weighted combination of both objectives
    ///   simultaneously
    /// </description></item>
    /// <item><description>
    ///   Lexicographic approach, with the objective priorities reversed
    /// </description></item>
    /// </list>
    class CapitalBudgeting : IDisposable
    {
        /// Required capital for each project
        static readonly double[] CAPITAL = { 104000, 53000, 29000, 187000, 98000, 32000, 75000, 200000 };
        /// Required personnel for each project
        static readonly double[] PERSONNEL = { 22, 12, 7, 36, 24, 10, 20, 41 };
        /// Expected return of each project
        static readonly double[] RETURN = { 124000, 74000, 42000, 188000, 108000, 56000, 88000, 225000 };
        /// Target capital to invest
        static readonly double CAPITAL_TARGET = 478000;
        /// Target return on investment
        static readonly double ROI_TARGET = 550000;
        /// Available personnel
        static readonly int PERSONNEL_MAX = 106;

        private readonly XpressProblem prob;

        /// Binary decision variables indicating which projects will be implemented
        private Variable[] select;

        /// Constraint for the number of personnel available
        private Inequality personnel;

        /// Primary objective: minimize capital expended
        private LinExpression capital;

        /// Secondary objective: maximize return on investment
        private LinExpression roi;

        public CapitalBudgeting()
        {
            prob = new XpressProblem();
        }

        public void Dispose()
        {
            prob.Dispose();
        }

        public static void Main(string[] args)
        {
            using (CapitalBudgeting capBudget = new CapitalBudgeting())
            {
                capBudget.Solve();
            }
        }

        public void Solve()
        {
            // Binary decision variables indicating which projects will be implemented
            select = prob.AddVariables(CAPITAL.Length)
                .WithType(ColumnType.Binary)
                .WithName("select_{0}")
                .ToArray();

            // Constraint: at least 3 projects must be implemented
            prob.AddConstraint(Sum(select) <= 3);

            // Constraint for the number of personnel available
            personnel = prob.AddConstraint(ScalarProduct(select, PERSONNEL) <= PERSONNEL_MAX);

            // Primary objective: minimize capital expended
            capital = ScalarProduct(select, CAPITAL);
            // The first objective has priority=1 (weight=1 by default)
            prob.SetObjective(capital, ObjSense.Minimize);
            prob.SetObjIntControl(0, ObjControl.Priority, 1);

            // Secondary objective: maximize return on investment
            roi = ScalarProduct(select, RETURN);
            // The second objective has weight=-1 to maximize this expression, and priority=0
            // to cause this objective to be solved in a second optimization
            prob.AddObjective(roi, 0, -1);

            prob.Optimize();
            PrintSolution("Higher priority for 'Minimize Capital' objective:");

            // Now set the same priority for both objectives. This will result in a single solve
            // using the weighted sum of the two objectives.
            prob.SetObjIntControl(1, ObjControl.Priority, 1);
            prob.Optimize();
            PrintSolution("Equal priority for objectives:");

            // Finally, give the first objective a lower priority
            prob.SetObjIntControl(0, ObjControl.Priority, 0);
            prob.Optimize();
            PrintSolution("Higher priority for 'Maximize Return' objective:");
        }

        /// <summary>
        /// Prints the current solution to the problem.
        /// </summary>
        /// <param name="title">a title to print before the solution</param>
        private void PrintSolution(String title)
        {
            Console.WriteLine(title);
            if (prob.SolveStatus != SolveStatus.Completed ||
                prob.SolStatus != SolStatus.Optimal)
            {
                Console.WriteLine("  Problem not solved");
            }
            else
            {
                Console.WriteLine($"  Objectives: {prob.Objectives}, solved objectives: {prob.SolvedObjs}");

                double[] sol = prob.GetSolution();
                double capitalUsed = capital.Evaluate(sol);
                double roiGained = roi.Evaluate(sol);
                if (capitalUsed > CAPITAL_TARGET)
                {
                    Console.WriteLine("  Unable to meet Capital target");
                }
                if (roiGained < ROI_TARGET)
                {
                    Console.WriteLine("  Unable to meet Return target");
                }

                Console.Write("  Projects undertaken:");
                for (int p = 0; p < select.Length; p++)
                {
                    if (select[p].GetSolution() >= 0.99)
                    {
                        Console.Write($" {p}");
                    }
                }
                Console.WriteLine();

                Console.WriteLine($"  Used capital: ${capitalUsed}," +
                    (CAPITAL_TARGET >= capitalUsed ? " unused: $" : " overused: $") +
                    Math.Abs(CAPITAL_TARGET - capitalUsed));
                Console.WriteLine($"  Total return: ${roiGained}");
                Console.WriteLine($"  Unused personnel: {(int)personnel.GetSlack()} persons");
            }
        }
    }
}

CapitalBudgeting.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.