// (c) 2023-2025 Fair Isaac Corporation
using System;
using Optimizer;
using Optimizer.Objects;
using static Optimizer.Objects.Utils;
namespace XpressExamples
{
/// 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
///
///
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:"); } ///
/// Prints the current solution to the problem.
///
/// a title to print before the solution
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");
}
}
}
}