| |||||||||||
Multi-period, multi-site production planning Description Multi-period production planning for multiple production facilities, including opening/closing decisions for sites. Implementation of helper routines for enumeration of arrays with multiple indices.
Source Files By clicking on a file name, a preview is opened at the bottom of this page.
ProductionPlanning_Index.java // (c) 2023-2024 Fair Isaac Corporation import static com.dashoptimization.objects.Utils.sum; import java.util.Locale; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; import com.dashoptimization.ColumnType; import com.dashoptimization.XPRSenumerations; import com.dashoptimization.objects.Expression; import com.dashoptimization.objects.LinExpression; import com.dashoptimization.objects.Variable; import com.dashoptimization.objects.XpressProblem; /** Production planning problem. */ public class ProductionPlanning_Index { static final int PMAX = 2; // number of products static final int FMAX = 2; // number of factories static final int RMAX = 2; // number of raw material static final int TMAX = 4; // number of time periods static final double CPSTOCK = 2.0; // unit cost to store a product static final double CRSTOCK = 2.0; // unit cost to store a raw material static final double MAXRSTOCK = 300; // maximum number of raw material that can be stocked in a factory static final double[][] REV = // REV[p][t] equals unit price for selling product p in period t { { 400, 380, 405, 350 }, { 410, 397, 412, 397 } }; static final double[][] CMAK = // CMAK[p][f] unit cost for producing product p at factory f { { 150, 153 }, { 75, 68 } }; static final double[][] CBUY = // CBUY[r][t] unit cost to buy raw material r in period t { { 100, 98, 97, 100 }, { 200, 195, 198, 200 } }; static final double[] COPEN = // COPEN[f] fixed cost for factory f being open for one period { 50000, 63000 }; static final double[][] REQ = // REQ[p][r] raw material requirement (in units) of r to make one unit of p { { 1.0, 0.5 }, { 1.3, 0.4 } }; static final double[][] MAXSELL = // MAXSELL[p][t] maximum number of units that can be sold of product p in period // t { { 650, 600, 500, 400 }, { 600, 500, 300, 250 } }; static final double[] MAXMAKE = // MAXMAKE[f] maximum number of units (over all products) a factory can produce // per period { 400, 500 }; static final double[][] PSTOCK0 = // PSTOCK0[p][f] initial stock of product p at factory f { { 50, 100 }, { 50, 50 } }; static final double[][] RSTOCK0 = // RSTOCK0[r][f] initial stock of raw material r at factor f { { 100, 150 }, { 50, 100 } }; public static class Index2 { public final int i1; public final int i2; public Index2(int i1, int i2) { this.i1 = i1; this.i2 = i2; } @Override public String toString() { return String.format("Index %d %d", i1, i2); } } /** Helper class for convenient index iteration */ public static class Index3 { public final int i1; public final int i2; public final int i3; public Index3(int i1, int i2, int i3) { this.i1 = i1; this.i2 = i2; this.i3 = i3; } @Override public String toString() { return String.format("Index %d %d %d", i1, i2, i3); } } /** * Loop through a 2-dimensional range, starting at 0 * * @param max1 First index varies between 0 and max1 * @param max2 Second index varies between 0 and max2 * @return a stream that can be used to loop over the cross product of the two * ranges. */ public static Stream<Index2> loopThrough2D(int max1, int max2) { return IntStream.range(0, max1) .mapToObj(i1 -> Stream.iterate(new Index2(i1, 0), ind -> new Index2(ind.i1, ind.i2 + 1)).limit(max2)) .flatMap(Function.identity()); } /** * Loop through a 3-dimensional range, starting at 0 * * @param max1 First index varies between 0 and max1 * @param max2 Second index varies between 0 and max2 * @param max3 Third index varies between 0 and max2 * @return a stream that can be used to loop over the cross product of the three * ranges. */ public static Stream<Index3> loopThrough3D(int max1, int max2, int max3) { return loopThrough2D(max1, max2).flatMap(ind2 -> Stream .iterate(new Index3(ind2.i1, ind2.i2, 0), ind3 -> new Index3(ind3.i1, ind3.i2, ind3.i3 + 1)) .limit(max3)); } /** * Convenience function for printing solution values stored in a 3-dimensional * Variable array * * @param sol solution object, obtained via prob.getSolution() * @param array 3-dimensional array of Xpress Variables * @param max1 First index varies between 0 and max1 * @param max2 Second index varies between 0 and max2 * @param max3 Third index varies between 0 and max3 * @param dimNames An array with a name for every dimension * @param name The name of the array */ public static void writeSol3D(double[] sol, Variable[][][] array, int max1, int max2, int max3, String[] dimNames, String name) { loopThrough3D(max1, max2, max3).forEach(idx -> { int i1 = idx.i1, i2 = idx.i2, i3 = idx.i3; System.out.printf(Locale.US, "%s %d\t%s %d\t%s %d : %s = %g%n", dimNames[0], i1, dimNames[1], i2, dimNames[2], i3, name, array[i1][i2][i3].getValue(sol)); }); } public static void main(String[] args) { System.out.println("Formulating the production planning problem"); try (XpressProblem prob = new XpressProblem()) { // make[p][f][t]: Amount of product p to make at factory f in period t Variable[][][] make = prob.addVariables(PMAX, FMAX, TMAX).withName("make_p%d_f%d_t%d").toArray(); // sell[p][f][t]: Amount of product p to sell from factory f in period t Variable[][][] sell = prob.addVariables(PMAX, FMAX, TMAX).withName("sell_p%d_f%d_t%d").toArray(); // pstock[p][f][t]: Stock level of product p at factor f at start of period t Variable[][][] pstock = prob.addVariables(PMAX, FMAX, TMAX).withName("pstock_p%d_f%d_t%d").toArray(); // buy[r][f][t]: Amount of raw material r bought for factory f in period t Variable[][][] buy = prob.addVariables(RMAX, FMAX, TMAX).withName("buy_p%d_f%d_t%d").toArray(); // rstock[r][f][t]: Stock level of raw material r at factory f at start of // period t Variable[][][] rstock = prob.addVariables(RMAX, FMAX, TMAX).withName("rstock_p%d_f%d_t%d").toArray(); // openm[f][t]: If factory f is open in period t Variable[][] openm = prob.addVariables(FMAX, TMAX).withType(ColumnType.Binary).withUB(1) .withName("openm_f%d_t%d").toArray(); // ## Objective: // Maximize total profit // revenue from selling products // + REV[p][t] * sell[p][f][t] LinExpression revenue = LinExpression.create(); loopThrough3D(PMAX, FMAX, TMAX).forEach(idx -> { int p = idx.i1, f = idx.i2, t = idx.i3; revenue.addTerm(sell[p][f][t].mul(REV[p][t])); }); // cost for making products (must be subtracted from profit) // - CMAK[p, f] * make[p][f][t] LinExpression prodCost = LinExpression.create(); loopThrough3D(PMAX, FMAX, TMAX).forEach(idx -> { int p = idx.i1, f = idx.i2, t = idx.i3; prodCost.addTerm(make[p][f][t].mul(-CMAK[p][f])); }); // cost for storing products (must be subtracted from profit) // - CPSTOCK * pstock[p][f][t] LinExpression prodStorageCost = LinExpression.create(); loopThrough3D(PMAX, FMAX, TMAX).forEach(idx -> { int p = idx.i1, f = idx.i2, t = idx.i3; prodStorageCost.addTerm(pstock[p][f][t].mul(-CPSTOCK)); }); // cost for opening a factory in a time period // - openm[f][t] * COPEN[f] LinExpression factoryCost = LinExpression.create(); loopThrough2D(FMAX, TMAX).forEach(idx -> { int f = idx.i1, t = idx.i2; factoryCost.addTerm(openm[f][t].mul(-COPEN[f])); }); // cost for buying raw material in time period t // - buy[r][f][t] * CBUY[r, t] LinExpression rawMaterialBuyCost = LinExpression.create(); loopThrough3D(PMAX, FMAX, TMAX).forEach(idx -> { int r = idx.i1, f = idx.i2, t = idx.i3; rawMaterialBuyCost.addTerm(buy[r][f][t].mul(-CBUY[r][t])); }); // cost for storing raw material (must be subtracted from profit) // - rstock[r][f][t] * CRSTOCK // an alternative way of setting an objective Expression is to pass // the stream directly to the sum function Expression rawMaterialStorageCost = sum(FMAX, f -> sum(RMAX, r -> sum(TMAX, t -> rstock[r][f][t].mul(-CRSTOCK)))); // sum up the 6 individual contributions to the overall profit Expression profit = sum(revenue, prodCost, prodStorageCost, factoryCost, rawMaterialStorageCost, rawMaterialBuyCost); // set maximization of profit as objective function prob.setObjective(profit, XPRSenumerations.ObjSense.MAXIMIZE); // constraints // Product stock balance loopThrough3D(PMAX, FMAX, TMAX).forEach(idx -> { int p = idx.i1, f = idx.i2, t = idx.i3; // for each time period except the last time period, surplus is available as // stock for the next time period if (t < TMAX - 1) { prob.addConstraint(pstock[p][f][t].plus(make[p][f][t]).eq(sell[p][f][t].plus(pstock[p][f][t + 1])) .setName(String.format("prod_stock_balance_p%d_f%d_t%d", p, f, t))); } else { prob.addConstraint(pstock[p][f][t].plus(make[p][f][t]).geq(sell[p][f][t]) .setName(String.format("prod_stock_balance_p%d_f%d_t%d", p, f, t))); } }); // Raw material stock balance loopThrough3D(PMAX, FMAX, TMAX).forEach(idx -> { int r = idx.i1, f = idx.i2, t = idx.i3; if (t < TMAX - 1) { prob.addConstraint(rstock[r][f][t].plus(buy[r][f][t]) .eq(rstock[r][f][t + 1].plus(sum(PMAX, p -> make[p][f][t].mul(REQ[p][r])))) .setName(String.format("raw_material_stock_balance_r%d_f%d_t%d", r, f, t))); } else { prob.addConstraint( rstock[r][f][t].plus(buy[r][f][t]).geq(sum(PMAX, p -> make[p][f][t].mul(REQ[p][r]))) .setName(String.format("raw_material_stock_balance_r%d_f%d_t%d", r, f, t))); } }); // Limit on the amount of product p to be sold // exemplifies how to loop through multiple ranges prob.addConstraints(PMAX, TMAX, (p, t) -> (sum(FMAX, f -> sell[p][f][t]).leq(MAXSELL[p][t])) .setName(String.format("maxsell_p%d_t%d", p, t))); // Capacity limit at factory f // exemplifies how to loop through multiple ranges prob.addConstraints(FMAX, TMAX, (f, t) -> sum(PMAX, p -> make[p][f][t]).leq(openm[f][t].mul(MAXMAKE[f])) .setName(String.format("capacity_f%d_t%d", f, t))); // Raw material stock limit // exemplifies how to loop through multiple ranges prob.addConstraints(FMAX, TMAX, (f, t) -> sum(RMAX, r -> rstock[r][f][t]).leq(MAXRSTOCK) .setName(String.format("raw_material_stock_limit_f%d_t%d", f, t))); // Initial product storage // loop through a custom IEnumerable, in our case our custom index object. prob.addConstraints(loopThrough2D(PMAX, FMAX), idx -> // pstock is indexed (p, f, t), PSTOCK0 is indexed (p, f) (pstock[idx.i1][idx.i2][0].eq(PSTOCK0[idx.i1][idx.i2])) .setName(String.format("initial_product_stock_p%d_f%d", idx.i1, idx.i2))); // Initial raw material storage // classic for loop loopThrough2D(PMAX, FMAX).forEach(idx -> { int r = idx.i1, f = idx.i2; prob.addConstraint((rstock[r][f][0].eq(RSTOCK0[r][f])) .setName(String.format("initial_raw_material_stock_r%d_f%d", r, f))); }); // write the problem in LP format for manual inspection System.out.println("Writing the problem to 'ProductionPlanning.lp'"); prob.writeProb("ProductionPlanning.lp", "l"); // Solve the problem System.out.println("Solving the problem"); prob.optimize(); System.out.println("Problem finished with SolStatus " + prob.attributes().getSolStatus()); if (prob.attributes().getSolStatus() != XPRSenumerations.SolStatus.OPTIMAL) { throw new RuntimeException("Problem not solved to optimality"); } System.out.println("Solution has objective value (profit) of " + prob.attributes().getMIPObjVal()); System.out.println(""); System.out.println("*** Solution ***"); double[] sol = prob.getSolution(); // Is factory f open at time period t? for (int f = 0; f < FMAX; f++) { for (int t = 0; t < TMAX; t++) { System.out.printf(Locale.US, "Factory %d\tTime Period %d : Open = %g%n", f, t, openm[f][t].getValue(sol)); } } System.out.println(""); // Production plan for producing writeSol3D(sol, make, PMAX, FMAX, TMAX, new String[] { "Product", "Factory", "Time Period" }, "Make"); System.out.println(""); // Production plan for selling products writeSol3D(sol, sell, PMAX, FMAX, TMAX, new String[] { "Product", "Factory", "Time Period" }, "Sell"); System.out.println(""); // Production plan for keeping products in stock writeSol3D(sol, pstock, PMAX, FMAX, TMAX, new String[] { "Product", "Factory", "Time Period" }, "Pstock"); System.out.println(""); // Production plan for keeping raw material in stock writeSol3D(sol, rstock, RMAX, FMAX, TMAX, new String[] { "Material", "Factory", "Time Period" }, "Rstock"); System.out.println(""); // Buying plan for raw material writeSol3D(sol, buy, RMAX, FMAX, TMAX, new String[] { "Material", "Factory", "Time Period" }, "Buy"); System.out.println(""); } } } | |||||||||||
© Copyright 2024 Fair Isaac Corporation. |