| // (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");
            }
        }
    }
}
 |