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
The model version Capbgt2l demonstrates the formuation of logical constraints.
File(s): CapitalBudgeting.java, Capbgt2l.java


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

import static com.dashoptimization.objects.Utils.scalarProduct;
import static com.dashoptimization.objects.Utils.sum;

import java.io.IOException;

import com.dashoptimization.ColumnType;
import com.dashoptimization.XPRSenumerations;
import com.dashoptimization.objects.Inequality;
import com.dashoptimization.objects.LinExpression;
import com.dashoptimization.objects.Variable;
import com.dashoptimization.objects.XpressProblem;

/**
 * Capital budgeting example. The problem is 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
 */
public class CapitalBudgeting {
    /**
     * Required capital for each project
     */
    static final double[] CAPITAL = { 104000, 53000, 29000, 187000, 98000, 32000, 75000, 200000 };
    /**
     * Required personnel for each project
     */
    static final double[] PERSONNEL = { 22, 12, 7, 36, 24, 10, 20, 41 };
    /**
     * Expected return of each project
     */
    static final double[] RETURN = { 124000, 74000, 42000, 188000, 108000, 56000, 88000, 225000 };
    /**
     * Target capital to invest
     */
    static final double CAPITAL_TARGET = 478000;
    /**
     * Target return on investment
     */
    static final double ROI_TARGET = 550000;
    /**
     * Available personnel
     */
    static final int PERSONNEL_MAX = 106;

    private final 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(XpressProblem prob) {
        this.prob = prob;
    }

    public static void main(String[] args) throws IOException {
        try (XpressProblem prob = new XpressProblem()) {
            new CapitalBudgeting(prob).solve();
        }
    }

    public void solve() {
        // Binary decision variables indicating which projects will be implemented
        select = prob.addVariables(CAPITAL.length).withType(ColumnType.Binary).withName("select_%d").toArray();

        // Constraint: at least 3 projects must be implemented
        prob.addConstraint(sum(select).geq(3));

        // Constraint for the number of personnel available
        personnel = prob.addConstraint(scalarProduct(select, PERSONNEL).leq(PERSONNEL_MAX));

        // Primary objective: minimize capital expended
        capital = scalarProduct(select, CAPITAL);
        // The first objective has priority=1 (weight=1 by default)
        prob.setObjective(capital, XPRSenumerations.ObjSense.MINIMIZE);
        prob.setObjIntControl(0, XPRSenumerations.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, XPRSenumerations.ObjControl.PRIORITY, 1);
        prob.optimize();
        printSolution("Equal priority for objectives:");

        // Finally, give the first objective a lower priority
        prob.setObjIntControl(0, XPRSenumerations.ObjControl.PRIORITY, 0);
        prob.optimize();
        printSolution("Higher priority for 'Maximize Return' objective:");
    }

    /**
     * Prints the current solution to the problem.
     *
     * @param title a title to print before the solution
     */
    private void printSolution(String title) {
        System.out.println(title);
        if (prob.attributes().getSolveStatus() != XPRSenumerations.SolveStatus.COMPLETED
                || prob.attributes().getSolStatus() != XPRSenumerations.SolStatus.OPTIMAL) {
            System.out.println("  Problem not solved");
        } else {
            System.out.println("  Objectives: " + prob.attributes().getObjectives() + " solved objectives: "
                    + prob.attributes().getSolvedObjs());

            double[] sol = prob.getSolution();
            double capitalUsed = capital.evaluate(sol);
            double roiGained = roi.evaluate(sol);
            if (capitalUsed > CAPITAL_TARGET) {
                System.out.println("  Unable to meet Capital target");
            }
            if (roiGained < ROI_TARGET) {
                System.out.println("  Unable to meet Return target");
            }

            System.out.print("  Projects undertaken:");
            for (int p = 0; p < select.length; p++) {
                if (select[p].getSolution() >= 0.99) {
                    System.out.print(" " + p);
                }
            }
            System.out.println();

            System.out.println(
                    "  Used capital: $" + capitalUsed + (CAPITAL_TARGET >= capitalUsed ? " unused: $" : " overused: $")
                            + Math.abs(CAPITAL_TARGET - capitalUsed));
            System.out.println("  Total return: $" + roiGained);
            System.out.println("  Unused personnel: " + (int) personnel.getSlack() + " persons");
        }
    }
}

Capbgt2l.java
// (c) 2023-2024 Fair Isaac Corporation

import static com.dashoptimization.objects.Utils.sum;

import com.dashoptimization.ColumnType;
import com.dashoptimization.DefaultMessageListener;
import com.dashoptimization.XPRSenumerations;
import com.dashoptimization.objects.Expression;
import com.dashoptimization.objects.Inequality;
import com.dashoptimization.objects.Variable;
import com.dashoptimization.objects.XpressProblem;

/**
 * Capital budgeting problem. Illustrates - Logical conditions, formulation using logic constraints -
 */
public class Capbgt2l {

    /** A project. */
    public static final class Project {
        /** The name of this project. */
        public final String name;
        /** Payout for the project. */
        public final double payout;
        /** Capital investment for the project. */
        public final double investment;
        /** Number of personnel required for the project. */
        public final int personnel;

        public Project(String name, double payout, double investment, int personnel) {
            this.name = name;
            this.payout = payout;
            this.investment = investment;
            this.personnel = personnel;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    /** The projects used in this example. */
    private static final Project[] projectArray = new Project[] { new Project("Alpha", 124000.0, 104000.0, 22),
            new Project("Beta", 74000.0, 53000.0, 12), new Project("Gamma", 42000.0, 29000.0, 7),
            new Project("Delta", 188000.0, 187000.0, 36), new Project("Epsilon", 108000.0, 98000.0, 24),
            new Project("Zeta", 56000.0, 32000.0, 10), new Project("Eta", 88000.0, 75000.0, 20),
            new Project("Theta", 225000.0, 200000.0, 41) };

    /** The resource constraints used in this example. */
    static final double budget = 478000.0;
    static final int workforce = 106;

    public static void main(String[] args) {
        try (XpressProblem prob = new XpressProblem()) {
            // Output all messages.
            prob.callbacks.addMessageCallback(DefaultMessageListener::console);

            /**** VARIABLES ****/

            // Whether to undertake a specific project
            Variable[] x = prob.addVariables(projectArray.length).withName(i -> String.format("x_%d", i))
                    .withType(ColumnType.Binary).toArray();

            // objective function: sum of payouts of all undertaken projects
            Expression totalProfit = sum(projectArray.length, i -> (x[i].mul(projectArray[i].payout)));

            prob.setObjective(totalProfit, XPRSenumerations.ObjSense.MAXIMIZE);

            // limits on resource availability
            // sum of investments of all undertaken projects should not exceed budget
            Inequality investmentLimit = prob
                    .addConstraint(sum(projectArray.length, i -> (x[i].mul(projectArray[i].investment))).leq(budget));
            // sum of personnel committed of all undertaken projects should not exceed
            // workforce
            Inequality workforceLimit = prob
                    .addConstraint(sum(projectArray.length, i -> (x[i].mul(projectArray[i].personnel))).leq(workforce));

            // project alpha can only be done if both gamma and zeta happen
            prob.addConstraint(x[0].leq(x[2]));
            prob.addConstraint(x[0].leq(x[5]));

            // project zeta can only be done if project epsilon happens
            prob.addConstraint(x[5].leq(x[4]));

            // projects alpha and beta as well as gamma and delta can only happen together
            prob.addConstraint(x[0].eq(x[1]));
            prob.addConstraint(x[2].eq(x[3]));

            // exactly one of those pairs should be invested in, i.e., if project alpha is
            // performed,
            // neither gamma nor delta can be invested in, and if project alpha does not
            // happen, then
            // projects gamma and delta have to be performed
            prob.addConstraint(x[0].ifThen(sum(x[2], x[3]).eq(0.0)));
            prob.addConstraint(x[0].ifNotThen(sum(x[2], x[3]).eq(2.0)));

            // Dump the problem to disk so that we can inspect it.
            prob.writeProb("capbgt2l.lp", "l");

            // Solve
            prob.optimize();
            if (prob.attributes().getSolStatus() != XPRSenumerations.SolStatus.OPTIMAL
                    && prob.attributes().getSolStatus() != XPRSenumerations.SolStatus.FEASIBLE)
                throw new RuntimeException("optimization failed with status " + prob.attributes().getSolStatus());
            double[] sol = prob.getSolution();

            // Print the objective
            System.out.println("Objective: " + prob.attributes().getObjVal());

            // Print the interesting slacks
            System.out.println("Remaining Budget: " + investmentLimit.getSlack());
            System.out.println("Remaining Workers: " + (int) workforceLimit.getSlack());

            // Print out the solution
            for (int i = 0; i < projectArray.length; ++i) {
                if (x[i].getValue(sol) > 0.0) {
                    System.out.println("Undertaking project " + projectArray[i].toString() + ".");
                }
            }
        }
    }
}

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