/* * Copyright (C) 2007 Snorre Gylterud, Stein Magnus Jodal, Johannes Knutsen, * Erik Bagge Ottesen, Ralf Bjarne Taraldset, and Iterate AS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. */ package no.ntnu.mmfplanner.model; import java.util.List; import no.ntnu.mmfplanner.util.ProjectSorterUtil; /** * This class uses brute-force to find the optimal NPV of a project. This is the * only method that will give an accurate list of all the top X results. * * Due to the high complexity of O(n!), this is fairly slow on large projects. * The class has been optimized some, and as such it will now handle up to about * 13-15 MMFs in a project with several precursors. Without any precursors any * more than 10 MMFs will take some time to complete. */ public class OptimalProjectSorter extends ProjectSorter { private int[][] sanpv; private int maxMmfs; private int maxPeriod; private int mmfLengths[]; private int mmfPrecursors[][]; /** * @param project */ public OptimalProjectSorter(Project project) { super(project); } /** * Perform the actual brute-force sorting. Uses chooseNext() and * handleFinishedOrder() to find and add the actual results. */ @Override protected void sort() { // initialize fields mmfLengths = new int[project.size()]; mmfPrecursors = new int[project.size()][]; int initialCount[] = new int[project.getPeriods()]; int initialPeriods[] = new int[project.size()]; int initialNpv = 0; int unusedCount = project.size(); sanpv = project.getSaNpvTable(); maxMmfs = project.getMaxMmfsPerPeriod(); maxPeriod = project.getPeriods(); boolean mmfUsed[] = new boolean[project.size()]; // find information about all mmfs in order to save work later // specifically we find the information for locked mmfs here, as these // can not be moved later anyway for (int i = 0; i < project.size(); i++) { Mmf mmf = project.get(i); // if the project has no revenue we can ignore it boolean hasRevenue = false; for (int p = 1; p <= mmf.getRevenueLength(); p++) { if (mmf.getRevenue(p) != 0) { hasRevenue = true; break; } } if (!hasRevenue) { initialPeriods[i] = maxPeriod + 1; mmfUsed[i] = true; unusedCount--; continue; } // find development length (minimum 1) mmfLengths[i] = mmf.getPeriodCount(); // precursor matrix List<Mmf> pre = mmf.getPrecursors(); if (pre.size() > 0) { mmfPrecursors[i] = new int[pre.size()]; for (int j = 0; j < mmfPrecursors[i].length; j++) { mmfPrecursors[i][j] = project.getMmfs().indexOf(pre.get(j)); } } // locked MMFs are added directly to initial* and marked as used if (mmf.isLocked()) { initialPeriods[i] = mmf.getPeriod(); for (int p = initialPeriods[i] - 1; p < initialPeriods[i] + mmfLengths[i] - 1; p++) { initialCount[p]++; } if (initialPeriods[i] <= maxPeriod) { initialNpv += sanpv[i][initialPeriods[i] - 1]; } mmfUsed[i] = true; unusedCount--; } } setProgressMax(alternativeCount(unusedCount)); chooseNext(mmfUsed, unusedCount, new int[unusedCount], 0, initialCount, initialPeriods, new int[] { 1, initialNpv }); } /** * Returns the number of alternative orderings for a project with size MMFs. * This does not take into account precursors or locked MMFs. * * @param size the number of unlocked MMFs * @return the number of alternative orderings */ private long alternativeCount(int size) { long result = 1; if (size > 0) { result = 2 * ProjectSorterUtil.factorial(size); } if (size >= 2) { long emptyAdd = 1; for (int i = 3; i <= size; i++) { emptyAdd = emptyAdd * i + 1; } result += emptyAdd; } return result; } /** * Tries all possible orderings for the given level, calling itself * recursively for subsequent levels. */ private void chooseNext(boolean mmfUsed[], int unusedCount, int order[], int orderLen, int initialCount[], int initialPeriods[], int initialPeriod_npv[]) { if (isStopFlag()) { throw new RuntimeException("Stop flag set"); } // copy initial data and add order int count[] = new int[initialCount.length]; System.arraycopy(initialCount, 0, count, 0, count.length); int periods[] = new int[initialPeriods.length]; System.arraycopy(initialPeriods, 0, periods, 0, periods.length); int period_npv[] = new int[initialPeriod_npv.length]; System .arraycopy(initialPeriod_npv, 0, period_npv, 0, period_npv.length); handleFinishedOrder(order, orderLen, count, periods, period_npv); // if we have no mmfs left to place, or there was no room for the last // mmf, return if (unusedCount <= 0) { return; } else if (period_npv[0] <= 0) { setProgress(getProgress() + alternativeCount(unusedCount) - 1); return; } for (int mmfIndex = 0; mmfIndex < mmfUsed.length; mmfIndex++) { if (mmfUsed[mmfIndex]) { continue; } if (!checkPrecursors(mmfIndex, mmfUsed, periods, period_npv)) { setProgress(getProgress() + alternativeCount(unusedCount - 1)); continue; } mmfUsed[mmfIndex] = true; order[orderLen] = mmfIndex; chooseNext(mmfUsed, unusedCount - 1, order, orderLen + 1, count, periods, period_npv); mmfUsed[mmfIndex] = false; } } /** * Check that the ordering is valid, specifically that precursors are before * its successor. Returns true of valid, false if invalid. */ private boolean checkPrecursors(int mmfIndex, boolean[] mmfUsed, int[] periods, int[] period_npv) { if (mmfPrecursors[mmfIndex] != null) { for (int i = 0; i < mmfPrecursors[mmfIndex].length; i++) { int pre = mmfPrecursors[mmfIndex][i]; if (!mmfUsed[pre]) { return false; } period_npv[0] = Math.max(period_npv[0], periods[pre] + mmfLengths[pre]); } } return true; } /** * Find the npv and sequence for the given ordering. Adds it to the result * (if npv is high enough). * * @see ProjectSorter#addResult(int, int[]) */ private void handleFinishedOrder(int order[], int orderLen, int count[], int periods[], int period_npv[]) { setProgress(getProgress() + 1); // place the first mmf in period 1 int period = period_npv[0]; int npv = period_npv[1]; if (orderLen > 0) { // index and length of current mmf int mmfIndex = order[orderLen - 1]; int mmfLength = mmfLengths[mmfIndex]; // find the first position where we have room for (int p = period - 1; p < period + mmfLength - 1; p++) { // if we can't place the mmf, no point in continuing if (period + mmfLength > maxPeriod) { period_npv[0] = 0; return; } // if there is no room, try placing the mmf starting at the next // position if (count[p] >= maxMmfs) { period = p + 2; } } // place the mff in the given period periods[mmfIndex] = period; for (int j = period - 1; j < period + mmfLength - 1; j++) { count[j]++; } // increase npv npv += sanpv[mmfIndex][period - 1]; } // add the final result addResult(npv, periods); // update period and npv period_npv[0] = period; period_npv[1] = npv; } }