0001: /*--------------------------------------------------------------------------
0002: * <copyright>
0003: *
0004: * Copyright 2000-2004 BBNT Solutions, LLC
0005: * under sponsorship of the Defense Advanced Research Projects
0006: * Agency (DARPA).
0007: *
0008: * You can redistribute this software and/or modify it under the
0009: * terms of the Cougaar Open Source License as published on the
0010: * Cougaar Open Source Website (www.cougaar.org).
0011: *
0012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
0015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
0016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
0022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0023: *
0024: * </copyright>
0025: * --------------------------------------------------------------------------*/
0026: package org.cougaar.glm.plugins.inventory;
0027:
0028: import org.cougaar.core.blackboard.IncrementalSubscription;
0029: import org.cougaar.glm.ldm.Constants;
0030: import org.cougaar.glm.ldm.asset.DueOut;
0031: import org.cougaar.glm.ldm.asset.Inventory;
0032: import org.cougaar.glm.ldm.asset.InventoryPG;
0033: import org.cougaar.glm.ldm.asset.Organization;
0034: import org.cougaar.glm.ldm.asset.ProjectionWeight;
0035: import org.cougaar.glm.ldm.asset.ProjectionWeightImpl;
0036: import org.cougaar.glm.ldm.plan.GeolocLocation;
0037: import org.cougaar.glm.plugins.AssetUtils;
0038: import org.cougaar.glm.plugins.MaintainedItem;
0039: import org.cougaar.glm.plugins.TaskUtils;
0040: import org.cougaar.glm.plugins.TimeUtils;
0041: import org.cougaar.planning.ldm.asset.Asset;
0042: import org.cougaar.planning.ldm.asset.TypeIdentificationPG;
0043: import org.cougaar.planning.ldm.measure.AbstractMeasure;
0044: import org.cougaar.planning.ldm.measure.Count;
0045: import org.cougaar.planning.ldm.measure.CountRate;
0046: import org.cougaar.planning.ldm.measure.FlowRate;
0047: import org.cougaar.planning.ldm.measure.Mass;
0048: import org.cougaar.planning.ldm.measure.MassTransferRate;
0049: import org.cougaar.planning.ldm.measure.Measure;
0050: import org.cougaar.planning.ldm.measure.Rate;
0051: import org.cougaar.planning.ldm.measure.Scalar;
0052: import org.cougaar.planning.ldm.measure.Volume;
0053: import org.cougaar.planning.ldm.plan.Allocation;
0054: import org.cougaar.planning.ldm.plan.AllocationResult;
0055: import org.cougaar.planning.ldm.plan.AspectType;
0056: import org.cougaar.planning.ldm.plan.AspectValue;
0057: import org.cougaar.planning.ldm.plan.NewTask;
0058: import org.cougaar.planning.ldm.plan.PlanElement;
0059: import org.cougaar.planning.ldm.plan.Preference;
0060: import org.cougaar.planning.ldm.plan.ScoringFunction;
0061: import org.cougaar.planning.ldm.plan.Task;
0062: import org.cougaar.planning.ldm.plan.TimeAspectValue;
0063: import org.cougaar.planning.plugin.deletion.DeletionPolicyBase;
0064: import org.cougaar.util.MoreMath;
0065: import org.cougaar.util.UnaryPredicate;
0066: import org.cougaar.util.log.Logger;
0067: import org.cougaar.util.log.Logging;
0068:
0069: import java.util.Collection;
0070: import java.util.Date;
0071: import java.util.Enumeration;
0072: import java.util.HashSet;
0073: import java.util.Iterator;
0074: import java.util.List;
0075: import java.util.Set;
0076: import java.util.Vector;
0077:
0078: public abstract class InventoryManager extends InventoryProcessor {
0079:
0080: protected IncrementalSubscription inventoryAllocSubscription_ = null;
0081: protected IncrementalSubscription modifiedInventorySubscription_ = null;
0082: /**
0083: * Subscription to policies
0084: */
0085: protected IncrementalSubscription inventoryPolicySubscription_;
0086: private Inventory selectedInventory = null;
0087: protected boolean forcePrintConcise = false;
0088: private static final int DONE = -1;
0089: private static Logger logger = Logging
0090: .getLogger(InventoryManager.class);
0091:
0092: /**
0093: * daysOnHand_ keep enough inventory on hand to cover N days of demand
0094: * daysForward_ When calculating average daily demand, look N days forward
0095: * from this day.
0096: * daysBackward_ When calculating average daily demand, look N days backward
0097: * from this day.
0098: * goalLevelMultiplier_ Multiplier for reorder level which yields goal level
0099: */
0100: protected int daysOnHand_ = 3;
0101: protected int daysForward_ = 15;
0102: protected int daysBackward_ = 15;
0103: protected double goalLevelMultiplier_ = 2.0;
0104: private double shortfall; // For debugging only
0105: private Set changedSet_;
0106:
0107: public final int REFILL_ALTER_TASK = 0;
0108: public final int REFILL_REPLACE_TASK = 1;
0109: public final int REFILL_ADD_TASK = 2; // This one doesn't work. Don't use it.
0110: public final int REFILL_CHANGE_METHOD = REFILL_REPLACE_TASK;
0111: private NewTask defaultSupplyTask = (NewTask) buildNewTask(null,
0112: Constants.Verb.SUPPLY, null);
0113:
0114: static class PolicyPredicate implements UnaryPredicate {
0115: String type_;
0116:
0117: public PolicyPredicate(String type) {
0118: type_ = type;
0119: }
0120:
0121: public boolean execute(Object o) {
0122: if (o instanceof InventoryPolicy) {
0123: String type = ((InventoryPolicy) o).getResourceType();
0124: if (type.equals(type_)) {
0125: return true;
0126: }
0127: }
0128: return false;
0129: }
0130: }
0131:
0132: // Allocations of tasks with quantity > 0 to Inventory objects
0133: static class AllocToInventoryPredicate implements UnaryPredicate {
0134: String type_;
0135:
0136: public AllocToInventoryPredicate(String type) {
0137: type_ = type;
0138: }
0139:
0140: public boolean execute(Object o) {
0141: if (o instanceof Allocation) {
0142: Task task = ((Allocation) o).getTask();
0143: if (task.getVerb().equals(Constants.Verb.WITHDRAW)
0144: || task.getVerb().equals(
0145: Constants.Verb.PROJECTWITHDRAW)) {
0146: if (TaskUtils.isDirectObjectOfType(task, type_)) {
0147: // need to check if alloced to inventory
0148: if (((Allocation) o).getAsset() instanceof Inventory) {
0149: return true;
0150: }
0151: }
0152: }
0153: }
0154: return false;
0155: }
0156: }
0157:
0158: static class ModifiedInventoryPredicate implements UnaryPredicate {
0159: String type_;
0160:
0161: public ModifiedInventoryPredicate(String type) {
0162: type_ = type;
0163: }
0164:
0165: public boolean execute(Object o) {
0166: if (o instanceof Inventory) {
0167: InventoryPG invpg = (InventoryPG) ((Inventory) o)
0168: .getInventoryPG();
0169: return ((invpg != null) && AssetUtils
0170: .isSupplyClassOfType(invpg.getResource(), type_));
0171: }
0172: return false;
0173: }
0174: }
0175:
0176: public InventoryManager(InventoryPlugin plugin, Organization org,
0177: String type) {
0178: super (plugin, org, type);
0179: inventoryAllocSubscription_ = subscribe(new AllocToInventoryPredicate(
0180: supplyType_));
0181: modifiedInventorySubscription_ = subscribe(new ModifiedInventoryPredicate(
0182: supplyType_));
0183: inventoryPolicySubscription_ = subscribe(new PolicyPredicate(
0184: type));
0185:
0186: checkDeletionPolicy();
0187: }
0188:
0189: // ********************************************************
0190: // *
0191: // Point Of Entry to InventoryManager *
0192: // *
0193: // ********************************************************
0194:
0195: /**
0196: * This method is called everytime a subscription has changed.
0197: */
0198:
0199: public void update() {
0200: // Skeleton algorithm (Template Method pattern) (learned at JavaOne)
0201: // Used by all concrete subclasses
0202: super .update();
0203: // RJB keep track of inventories that need to be run, until you actually run them
0204: changedSet_ = needUpdate(changedSet_);
0205: Iterator changedInventories = changedSet_.iterator();
0206:
0207: if (!changedSet_.isEmpty()) {
0208: if (logger.isDebugEnabled()) {
0209: logger
0210: .debug("\n\n\nBEGIN CYCLE___________________________________________\n");
0211: }
0212:
0213: if (inventoryPlugin_.getDetermineRequirementsTask() == null) {
0214: Enumeration inventories = inventoryPlugin_
0215: .getInventoryBins(supplyType_);
0216: changedSet_ = new HashSet();
0217: while (inventories.hasMoreElements()) {
0218: changedSet_.add(inventories.nextElement());
0219: }
0220: if (logger.isDebugEnabled()) {
0221: logger
0222: .debug("#####"
0223: + clusterId_
0224: + " is running because (inventoryPlugin_.getDetermineRequirementsTask() == null)");
0225: }
0226:
0227: handleRescinds();
0228:
0229: // RJB now we have handled all inventories
0230: changedSet_ = null;
0231:
0232: } else if (inventoryPlugin_.hasSeenAllConsumers()) {
0233: handleChangedInventories();
0234: // RJB now we have handled all inventories
0235: changedSet_ = null;
0236: }
0237: if (logger.isDebugEnabled()) {
0238: logger
0239: .debug("\n\nEND CYCLE___________________________________________\n\n");
0240: }
0241: }
0242: }
0243:
0244: private void handleRescinds() {
0245: //nominal handling of rescinds
0246: resetInventories();
0247: accountForWithdraws();
0248: addPreviousRefills();
0249: refreshInventorySchedule();
0250:
0251: // MWD hook for any additional handling of rescinds by subclasses
0252: handleGLSRescind();
0253: }
0254:
0255: private void handleChangedInventories() {
0256: resetInventories();
0257: accountForWithdraws();
0258: generateHandleDueIns();
0259: adjustForInadequateInventory();
0260: updateWithdrawAllocations();
0261: refreshInventorySchedule();
0262: }
0263:
0264: public static class IMDeletionPolicy extends DeletionPolicyBase {
0265: public String supplyType_;
0266: }
0267:
0268: private IMDeletionPolicy createDeletionPolicy(long deletionDelay) {
0269: IMDeletionPolicy policy = (IMDeletionPolicy) ldmFactory_
0270: .newPolicy(IMDeletionPolicy.class.getName());
0271: policy.supplyType_ = supplyType_;
0272: policy.init(supplyType_ + " Due Out Deletion", inventoryPlugin_
0273: .getDueOutPredicate(supplyType_), deletionDelay);
0274: return policy;
0275: }
0276:
0277: /**
0278: * This predicate finds our deletion policies. They must be
0279: * instances of IMDeletionPolicy and have a supplyType_ matching
0280: * our supplyType_;
0281: */
0282: private UnaryPredicate deletionPolicyPredicate = new UnaryPredicate() {
0283: public boolean execute(Object o) {
0284: if (o instanceof IMDeletionPolicy) {
0285: IMDeletionPolicy policy = (IMDeletionPolicy) o;
0286: return policy.supplyType_.equals(supplyType_);
0287: }
0288: return false;
0289: }
0290: };
0291:
0292: /**
0293: * Checks the current deletion policy and insures that it is
0294: * consistent with the current on hand policy. Generally, due out
0295: * tasks must not be deleted until the daysBackward_ days have
0296: * passed. The DeletionPolicy is created or updated to reflect
0297: * this.
0298: */
0299: protected void checkDeletionPolicy() {
0300: long deletionDelay = daysBackward_ * TimeUtils.MSEC_PER_DAY;
0301: Collection policies = delegate_.query(deletionPolicyPredicate);
0302: if (policies.isEmpty()) {
0303: IMDeletionPolicy policy = createDeletionPolicy(deletionDelay);
0304: delegate_.publishAdd(policy);
0305: } else {
0306: IMDeletionPolicy policy = (IMDeletionPolicy) policies
0307: .iterator().next();
0308: policy.setDeletionDelay(deletionDelay);
0309: delegate_.publishChange(policy);
0310: }
0311: }
0312:
0313: // ********************************************************
0314: // *
0315: // Need Update / Reset Section *
0316: // *
0317: // ********************************************************
0318:
0319: protected abstract Set needUpdate(Set inv);
0320:
0321: // Reset Inventories
0322:
0323: private void resetInventories() {
0324: Inventory inventory;
0325: Iterator list = changedSet_.iterator();
0326: if (logger.isDebugEnabled()) {
0327: logger.debug("STEP 1: RESETINVENTORIES(), Today: "
0328: + TimeUtils.dateString(startTime_));
0329: }
0330: while (list.hasNext()) {
0331: inventory = (Inventory) list.next();
0332: InventoryPG invpg = (InventoryPG) inventory
0333: .getInventoryPG();
0334: // if (selectedInventory == null) {
0335: // if (invpg.getResource().getTypeIdentificationPG().getTypeIdentification().equals("NSN/9150001806383")) {
0336: // selectedInventory = inventory;
0337: // }
0338: // }
0339: invpg.resetInventory(inventory, startTime_);
0340: }
0341: }
0342:
0343: // ********************************************************
0344: // *
0345: // Account for withdraws *
0346: // *
0347: // ********************************************************
0348:
0349: private void accountForWithdraws() {
0350: if (logger.isDebugEnabled()) {
0351: logger.debug("STEP 2: ACCOUNTFORWITHDRAWS()");
0352: }
0353: Iterator inventories = changedSet_.iterator();
0354: while (inventories.hasNext()) {
0355: Inventory inventory = (Inventory) inventories.next();
0356: InventoryPG invpg = (InventoryPG) inventory
0357: .getInventoryPG();
0358: invpg.withdrawFromInventory(inventory, clusterId_);
0359: invpg.determineInventoryLevels();
0360: computeThresholdSchedule(inventory);
0361: // invpg.printInventoryLevels(inventory, clusterId_);
0362: }
0363: }
0364:
0365: private void computeThresholdSchedule(Inventory inventory) {
0366: InventoryPG invpg = (InventoryPG) inventory.getInventoryPG();
0367: invpg.computeThresholdSchedule(daysOnHand_, daysForward_,
0368: daysBackward_, getMinReorderLevel(inventory),
0369: getMaxReorderLevel(inventory), goalLevelMultiplier_);
0370: }
0371:
0372: private Vector generateInactiveProjections(Inventory inventory,
0373: int switchoverDay) {
0374: if (logger.isDebugEnabled()) {
0375: logger.debug("STEP 2: GenerateInactiveProjections() for "
0376: + AssetUtils.getAssetIdentifier(inventory));
0377: }
0378: Vector projections = new Vector();
0379: InventoryPG invpg = inventory.getInventoryPG();
0380: int today = invpg.getFirstPlanningDay();
0381: int periodBegin = today;
0382: Scalar previous = invpg.getProjected(periodBegin);
0383: /* Loop from tomorrow to the switchover day. The extra step at
0384: * the end insures that the final segment is processed. */
0385: for (int day = today + 1; day <= switchoverDay; day++) {
0386: Scalar current = (day < switchoverDay) ? invpg
0387: .getProjected(day) : null;
0388:
0389: if (previous == null) {
0390: System.out.println("#####"
0391: + clusterId_
0392: + " current projection is null on day "
0393: + day
0394: + " for "
0395: + inventory.getItemIdentificationPG()
0396: .getItemIdentification());
0397: break;
0398: }
0399:
0400: if (!previous.equals(current)) {
0401: double value = convertScalarToDouble(previous);
0402: if (!Double.isNaN(value) && value > 0.0) {
0403: long start = invpg.getStartOfDay(periodBegin);
0404: int nDays = day - periodBegin;
0405: long end = start + nDays * TimeUtils.MSEC_PER_DAY;
0406: Rate dailyRate = createDailyRate(previous);
0407: Task t = newProjectSupplyTask(inventory, start,
0408: end, dailyRate);
0409: projections.add(t);
0410: }
0411:
0412: previous = current;
0413: periodBegin = day;
0414: }
0415: }
0416: return projections;
0417: }
0418:
0419: protected Rate createDailyRate(Measure qty) {
0420: Rate rate = null;
0421: if (qty instanceof Volume) {
0422: rate = FlowRate.newGallonsPerDay(((Volume) qty)
0423: .getGallons());
0424: } else if (qty instanceof Count) {
0425: rate = CountRate.newEachesPerDay(((Count) qty).getEaches());
0426: } else if (qty instanceof Mass) {
0427: rate = MassTransferRate.newShortTonsPerDay(((Mass) qty)
0428: .getShortTons());
0429: }
0430: return rate;
0431: }
0432:
0433: protected double ignoreSmallIncrement(double increment, double x) {
0434: double t = x + increment;
0435: double diff = (x - increment) / t;
0436: if (diff < 0.0001 && diff > -0.0001)
0437: return x;
0438: return x + increment;
0439: }
0440:
0441: private Rate createIncrementedDailyRate(Measure qty,
0442: double increment) {
0443: Rate rate = null;
0444: if (qty instanceof Volume) {
0445: double d = ignoreSmallIncrement(increment, ((Volume) qty)
0446: .getGallons());
0447: if (d <= 0.0)
0448: return null;
0449: rate = FlowRate.newGallonsPerDay(d);
0450: } else if (qty instanceof Count) {
0451: double d = ignoreSmallIncrement(increment, ((Count) qty)
0452: .getEaches());
0453: if (d <= 0.0)
0454: return null;
0455: rate = CountRate.newEachesPerDay(d);
0456: } else if (qty instanceof Mass) {
0457: double d = ignoreSmallIncrement(increment, ((Mass) qty)
0458: .getShortTons());
0459: if (d <= 0.0)
0460: return null;
0461: rate = MassTransferRate.newShortTonsPerDay(d);
0462: }
0463: return rate;
0464: }
0465:
0466: private Task newProjectSupplyTask(Inventory inventory, long start,
0467: long end, Rate rate) {
0468: Task parentTask = inventoryPlugin_.findOrMakeMILTask(inventory);
0469: // Create start and end time preferences (strictly at)
0470: ScoringFunction score;
0471: Vector prefs = new Vector();
0472: score = ScoringFunction.createStrictlyAtValue(TimeAspectValue
0473: .create(AspectType.START_TIME, start));
0474: prefs.addElement(ldmFactory_.newPreference(
0475: AspectType.START_TIME, score));
0476: score = ScoringFunction.createStrictlyAtValue(TimeAspectValue
0477: .create(AspectType.END_TIME, end));
0478: prefs.addElement(ldmFactory_.newPreference(AspectType.END_TIME,
0479: score));
0480: Vector prep_phrases = new Vector();
0481: prep_phrases.add(newPrepositionalPhrase(
0482: Constants.Preposition.FOR, myOrgName_));
0483: prep_phrases.add(newPrepositionalPhrase(
0484: Constants.Preposition.OFTYPE, supplyType_));
0485: Asset resource = inventory.getInventoryPG().getResource();
0486: TypeIdentificationPG tip = ((Asset) resource)
0487: .getTypeIdentificationPG();
0488: MaintainedItem itemID = MaintainedItem
0489: .findOrMakeMaintainedItem("Inventory", tip
0490: .getTypeIdentification(), null, tip
0491: .getNomenclature());
0492: prep_phrases.add(newPrepositionalPhrase(
0493: Constants.Preposition.MAINTAINING, itemID));
0494: prep_phrases
0495: .add(newPrepositionalPhrase(Constants.Preposition.REFILL));
0496:
0497: InventoryPG invpg = (InventoryPG) inventory.getInventoryPG();
0498: NewTask t = (NewTask) buildTask(parentTask,
0499: Constants.Verb.PROJECTSUPPLY, invpg.getResource(),
0500: prep_phrases, prefs.elements());
0501: t.setPreference(TaskUtils.createDemandRatePreference(
0502: ldmFactory_, rate));
0503: t.setPreference(TaskUtils.createDemandMultiplierPreference(
0504: ldmFactory_, 1.0));
0505: t.setCommitmentDate(new Date(end));
0506: // printDebug("newProjectSupplyTask(), created new ProjectSupply task "+TaskUtils.taskDesc(t));
0507: return t;
0508: }
0509:
0510: private Inventory getInventoryForTask(Task task) {
0511: return inventoryPlugin_.findOrMakeInventory(supplyType_,
0512: (Asset) task.getDirectObject());
0513: }
0514:
0515: // ********************************************************
0516: // *
0517: // Generate/Handle Due Ins Section *
0518: // *
0519: // ********************************************************
0520:
0521: private void generateHandleDueIns() {
0522: if (logger.isDebugEnabled()) {
0523: logger.debug("Step 3: generateHandleDueIns()");
0524: }
0525: addPreviousRefills();
0526: }
0527:
0528: private void addPreviousRefills() {
0529:
0530: if (logger.isDebugEnabled()) {
0531: logger.debug(" : addPreviousRefills()");
0532: }
0533: int total = 0;
0534: Iterator inventories = changedSet_.iterator();
0535: Inventory inv;
0536: InventoryPG invpg;
0537: while (inventories.hasNext()) {
0538: inv = (Inventory) inventories.next();
0539: invpg = (InventoryPG) inv.getInventoryPG();
0540: // maintainInventory Task is the parent of all the refills for this inventory
0541: Task maintainInventory = inventoryPlugin_
0542: .findOrMakeMILTask(inv);
0543: // should never be null but may want to add a check AHF
0544: total += invpg
0545: .addPreviousRefillsToInventory(maintainInventory);
0546: invpg.determineInventoryLevels();
0547: // invpg.printInventoryLevels(inv, clusterId_);
0548: }
0549: if (logger.isDebugEnabled()) {
0550: logger.debug("end addDueIns(), number of refillTasks is "
0551: + total);
0552: }
0553: }
0554:
0555: public long defaultRefillEndTime(long time, Inventory inv) {
0556: if (time == startTime_) {
0557: time = TimeUtils.addNDays(startTime_, 1);
0558: }
0559: return time;
0560: }
0561:
0562: private Task createRefillTask(Inventory inv, double refill_qty,
0563: long time) {
0564: Asset item = getInventoryAsset(inv);
0565: // create request task
0566: Vector prefs = new Vector();
0567: Preference p_start, p_end, p_qty;
0568:
0569: long end_time = defaultRefillEndTime(time, inv);
0570: // long start_time = defaultRefillStartTime(time,inv);
0571:
0572: // p_start = createDateAfterPreference(AspectType.START_TIME, start_time);
0573: p_end = createDateBeforePreference(AspectType.END_TIME,
0574: end_time);
0575:
0576: p_qty = createRefillQuantityPreference(refill_qty);
0577: // prefs.addElement(p_start);
0578: prefs.addElement(p_end);
0579: prefs.addElement(p_qty);
0580:
0581: Vector pp_vector = new Vector();
0582: pp_vector.addElement(newPrepositionalPhrase(
0583: Constants.Preposition.FOR, myOrgName_));
0584: pp_vector.add(newPrepositionalPhrase(
0585: Constants.Preposition.OFTYPE, supplyType_));
0586:
0587: Object io;
0588: Enumeration geolocs = AssetUtils.getGeolocLocationAtTime(
0589: myOrganization_, end_time);
0590: if (geolocs.hasMoreElements()) {
0591: io = (GeolocLocation) geolocs.nextElement();
0592: } else {
0593: io = this Geoloc_;
0594: }
0595: pp_vector.addElement(newPrepositionalPhrase(
0596: Constants.Preposition.TO, io));
0597: Asset resource = inv.getInventoryPG().getResource();
0598: TypeIdentificationPG tip = ((Asset) resource)
0599: .getTypeIdentificationPG();
0600: MaintainedItem itemID = MaintainedItem
0601: .findOrMakeMaintainedItem("Inventory", tip
0602: .getTypeIdentification(), null, tip
0603: .getNomenclature());
0604: pp_vector.addElement(newPrepositionalPhrase(
0605: Constants.Preposition.MAINTAINING, itemID));
0606: pp_vector
0607: .addElement(newPrepositionalPhrase(Constants.Preposition.REFILL));
0608:
0609: NewTask task = (NewTask) buildTask(null, Constants.Verb.SUPPLY,
0610: item, pp_vector, prefs.elements());
0611: return task;
0612:
0613: }
0614:
0615: protected Preference createRefillQuantityPreference(
0616: double refill_qty) {
0617: AspectValue lowAV = AspectValue.newAspectValue(
0618: AspectType.QUANTITY, 0.01);
0619: AspectValue bestAV = AspectValue.newAspectValue(
0620: AspectType.QUANTITY, refill_qty);
0621: AspectValue highAV = AspectValue.newAspectValue(
0622: AspectType.QUANTITY, refill_qty + 1.0);
0623: ScoringFunction qtySF = ScoringFunction.createVScoringFunction(
0624: lowAV, bestAV, highAV);
0625: return ldmFactory_.newPreference(AspectType.QUANTITY, qtySF);
0626: // return createQuantityPreference(AspectType.QUANTITY, refill_qty);
0627: }
0628:
0629: // Refill Inventories
0630:
0631: /**
0632: * Generate a refill order to replenish the item in storage,
0633: * assuming we already have determined we have at least an
0634: * Economic Reorder Quantity. Allocate task to provider Org.
0635: * @return true if an order was placed or changed
0636: */
0637: private boolean orderRefill(Inventory inventory, int day) {
0638: InventoryPG invpg = (InventoryPG) inventory.getInventoryPG();
0639: double currentInventory = convertScalarToDouble(invpg
0640: .getLevel(day));
0641: double goal_level = invpg.getGoalLevel(day);
0642: double refill_qty = goal_level - currentInventory;
0643: double reorder_level = invpg.getReorderLevel(day);
0644:
0645: defaultSupplyTask.setDirectObject(invpg.getResource());
0646: if (invpg.getProjectionWeight().getProjectionWeight(
0647: defaultSupplyTask, invpg.getImputedDay(day)) <= 0.0) {
0648: // This test is to avoid generating refill SUPPLY tasks for times when the system
0649: // will ignore their effect on inventory
0650: return false; // Can't do a refill
0651: }
0652:
0653: if (!invpg.getFillToCapacity()) {
0654: // ** THIS IS A KLUDGE FOR SUBSISTENCE -- WITHOUT IT WE FAIL THE FIRST ORDER.
0655: // SHOULDN'T HAPPEN LIKE THAT -- FIX ASAP -- RUSTY AND AMY!!
0656: // refill_qty = refill_qty*1.3;
0657: // *** KLUDGE ********
0658: }
0659:
0660: boolean isCount = invpg.getCapacity() instanceof Count;
0661: if (refill_qty > 0.0) {
0662: if (logger.isDebugEnabled()) {
0663: logger.debug("orderRefill goal=" + goal_level
0664: + " reorder level=" + reorder_level
0665: + " current level=" + currentInventory);
0666: }
0667: Task task = null;
0668: Task prev_refill = invpg.refillAlreadyFailedOnDay(day);
0669: if (prev_refill != null) {
0670: double min_qty = reorder_level - currentInventory;
0671: if (min_qty <= 0.0) { // Refill unneeded
0672: min_qty = 1e-10;
0673: // invpg.removeDueIn(prev_refill);
0674: // plugin_.publishRemoveFromExpansion(prev_refill);
0675: // return true;
0676: }
0677: double prev_qty = TaskUtils.getQuantity(prev_refill);
0678: if (isCount)
0679: min_qty = Math.ceil(min_qty);
0680: if (min_qty < prev_qty) {
0681: refill_qty = min_qty; // Retry the refill with the min needed
0682: } else {
0683: return false; // Can't refill on this day.
0684: }
0685: } else {
0686: prev_refill = invpg.getRefillOnDay(day);
0687: }
0688: if (isCount) {
0689: refill_qty = Math.ceil(refill_qty);
0690: }
0691: if (prev_refill != null) {
0692: return orderRefillWithPrevious(inventory, day, invpg,
0693: refill_qty);
0694: } else {
0695: return orderNewRefill(inventory, day, invpg, refill_qty);
0696: }
0697: } else {
0698: if (logger.isDebugEnabled()) {
0699: logger
0700: .debug("OrderRefill qty < 0: " + refill_qty
0701: + " = " + goal_level + " - "
0702: + currentInventory);
0703: }
0704: }
0705: return true;
0706: }
0707:
0708: private boolean orderNewRefill(Inventory inventory, int day,
0709: InventoryPG invpg, double refill_qty) {
0710: long time = invpg.convertDayToTime(day);
0711: Task task = createRefillTask(inventory, refill_qty, time);
0712: // FIX ME - sets to today??? check dates.
0713: // task.setCommitmentDate(date);
0714: // printDebug(1,"orderRefill task:"+TaskUtils.taskDesc(task));
0715: Task parentTask = inventoryPlugin_.findOrMakeMILTask(inventory);
0716: plugin_.publishAddToExpansion(parentTask, task);
0717: if (logger.isDebugEnabled()) {
0718: logger
0719: .debug("orderNewRefill() "
0720: + task.getUID()
0721: + " "
0722: + refill_qty
0723: + " on "
0724: + TimeUtils.dateString(invpg
0725: .convertDayToTime(day)));
0726: }
0727: invpg.addDueIn(task);
0728: return true;
0729: }
0730:
0731: /**
0732: * Modify an existing refill task if possible to increase the
0733: * refill as indicated. If the previous refill has failed we don't
0734: * expect an additional refill to succeed, but if the new amount
0735: * is smaller than the previous one, then it might succeed so we go
0736: * ahead with the change. Otherwise, we increase the amount of the
0737: * existing refill (or replace with a larger refill, or add an
0738: * additional refill depending on REFILL_CHANGE_METHOD).
0739: * Note -- This routine is actually never called with a failed
0740: * refill on day (See refillInventory).
0741: * @param inventory the Inventory -- not used
0742: * @param day the day of the refill
0743: * @param invpg the InventoryPG of the inventory.
0744: * @param refill_qty the amount needed to be added to the inventory
0745: */
0746: private boolean orderRefillWithPrevious(Inventory inventory,
0747: int day, InventoryPG invpg, double refill_qty) {
0748: Task refill_task = invpg.getRefillOnDay(day);
0749: double prev_qty = TaskUtils.getQuantity(refill_task);
0750: boolean failed = false;
0751: Allocation alloc = (Allocation) refill_task.getPlanElement();
0752: if (alloc != null) {
0753: AllocationResult report = alloc.getReportedResult();
0754: if (report != null) {
0755: failed = !report.isSuccess();
0756: }
0757: }
0758: if (failed) {
0759: if (prev_qty <= refill_qty) {
0760: System.out.println("Known failed refill: "
0761: + TaskUtils.taskDesc(refill_task));
0762: // printLog("Known failed refill: "+TaskUtils.taskDesc(refill_task));
0763: return false;
0764: } // If quantity reduced make the change
0765: }
0766: // Send orders for whole items, i.e. do not order 0.5 O-rings
0767: if (invpg.getCapacity() instanceof Count) {
0768: refill_qty = Math.ceil(refill_qty);
0769: }
0770:
0771: if (logger.isDebugEnabled()) {
0772: logger
0773: .debug("orderRefillWithPrevious() "
0774: + refill_task.getUID()
0775: + " "
0776: + prev_qty
0777: + "-->"
0778: + refill_qty
0779: + " on "
0780: + TimeUtils.dateString(invpg
0781: .convertDayToTime(day)));
0782: }
0783: invpg.removeDueIn(refill_task);
0784: switch (REFILL_CHANGE_METHOD) {
0785: case REFILL_ALTER_TASK:
0786: /* Change the quantity preference on the existing task.
0787: refill_qty is the additional amount needed, prev_qty is
0788: the effective quantity of the current task. */
0789: if (!failed)
0790: refill_qty += prev_qty;
0791: Preference qpref = createRefillQuantityPreference(refill_qty);
0792: ((NewTask) refill_task).setPreference(qpref);
0793: PlanElement pe = refill_task.getPlanElement();
0794: if (pe != null) {
0795: pe
0796: .setEstimatedResult(createEstimatedAllocationResult(refill_task));
0797: }
0798: delegate_.publishChange(refill_task);
0799: invpg.addDueIn(refill_task);
0800: return true;
0801: case REFILL_REPLACE_TASK:
0802: if (!failed) {
0803: refill_qty += prev_qty;
0804: }
0805: plugin_.publishRemoveFromExpansion(refill_task);
0806: return orderNewRefill(inventory, day, invpg, refill_qty);
0807: case REFILL_ADD_TASK:
0808: invpg.addDueIn(refill_task);
0809: return orderNewRefill(inventory, day, invpg, refill_qty);
0810: }
0811: return false;
0812: }
0813:
0814: // ********************************************************
0815: // *
0816: // Adjust Withdraws Section *
0817: // *
0818: // ********************************************************
0819:
0820: private void adjustForInadequateInventory() {
0821: if (logger.isDebugEnabled()) {
0822: logger.debug("adjustForInadequateInventory()");
0823: }
0824: // For each inventory
0825: Iterator inventories = changedSet_.iterator();
0826: while (inventories.hasNext()) {
0827: Inventory inventory = (Inventory) inventories.next();
0828: // forcePrintConcise(inventory == selectedInventory);
0829: adjustForInadequateInventory(inventory);
0830: }
0831: }
0832:
0833: private void adjustForInadequateInventory(Inventory inventory) {
0834: InventoryPG invpg = (InventoryPG) inventory.getInventoryPG();
0835: int firstDay = invpg.getFirstPlanningDay();
0836: int days = invpg.getPlanningDays();
0837: int switchoverDay = days;
0838: {
0839: Task testTask = buildNewTask(null,
0840: Constants.Verb.PROJECTSUPPLY, null);
0841: int imputedDay0 = invpg.getImputedDay(0);
0842: for (switchoverDay = 0; switchoverDay < days; switchoverDay++) {
0843: double weight = invpg.getProjectionWeight()
0844: .getProjectionWeight(testTask,
0845: imputedDay0 + switchoverDay);
0846: if (weight > 0.0)
0847: break; // found switchoverDay
0848: }
0849: }
0850: Vector projections = generateInactiveProjections(inventory,
0851: switchoverDay);
0852: for (int day = firstDay; day < switchoverDay; day++) {
0853: if (checkFailedRefill(inventory, invpg, day)) { // Check and update any failed refills
0854: invpg.determineInventoryLevels();
0855: }
0856: }
0857: int refillDay = refillNeeded(inventory, firstDay);
0858: int periodBegin = switchoverDay;
0859: Rate currentRate = null;
0860: double pendingDelta = 0.0;
0861: for (int day = firstDay; day <= days; day++) {
0862: Scalar projected = invpg.getProjected(day);
0863: double projectedDemand = convertScalarToDouble(projected);
0864: if (day < switchoverDay) {
0865: if (day == refillDay) {
0866: // Try to do a refill
0867: if (orderRefill(inventory, day)) {
0868: invpg.determineInventoryLevels();
0869: // If the refill succeeded, then we are above
0870: // the reorder level so we advance to the next
0871: // day and loop.
0872: refillDay = refillNeeded(inventory, day + 1);
0873: } else {
0874: while (day == refillDay) {
0875: if (failDueOut(inventory, invpg, day)) {
0876: invpg.determineInventoryLevels();
0877: refillDay = refillNeeded(inventory, day);
0878: } else {
0879: // Can this happen? If it does, just go to the next day
0880: refillDay = refillNeeded(inventory,
0881: day + 1);
0882: }
0883: }
0884: }
0885: }
0886: } else {
0887: Scalar nextProjection = invpg.getProjectedRefill(day);
0888: Rate newRate = null;
0889: double delta = 0.0;
0890: if (day < days) {
0891: double target = 0.5 * (invpg.getReorderLevel(day) + invpg
0892: .getGoalLevel(day));
0893: if (logger.isDebugEnabled()) {
0894: logger.debug("target("
0895: + TimeUtils.dateString(invpg
0896: .convertDayToTime(day)) + ")="
0897: + target);
0898: }
0899: double qty = convertScalarToDouble(invpg
0900: .getLevel(day))
0901: + pendingDelta;
0902: double nextRefill = convertScalarToDouble(nextProjection);
0903: if (!MoreMath.nearlyEquals(qty, target, 0.0001)) {
0904: delta = Math.max(target - qty, -nextRefill); // Can only reduce by projection amount
0905: }
0906: newRate = createIncrementedDailyRate(
0907: nextProjection, delta);
0908: if (newRate == null)
0909: delta = 0.0; // Can't do negative, cancel the delta (I think this does nothing)
0910: }
0911: if (isSameRate(currentRate, newRate)) {
0912: /* If we use the currentRate instead of newRate,
0913: then the delta we achieve is not the delta we
0914: computed above. Compute a new delta that
0915: reflects what the currentRate achieves. */
0916: // delta += (TaskUtils.getDailyQuantity(currentRate)
0917: // - TaskUtils.getDailyQuantity(newRate));
0918: pendingDelta += delta;
0919: continue;
0920: }
0921: for (int i = periodBegin; i < day; i++) {
0922: invpg.removeRefillProjection(i);
0923: }
0924: if (currentRate != null) { // Terminate the current rate
0925: long start = invpg.getStartOfDay(periodBegin);
0926: long end = invpg.getStartOfDay(day);
0927: Task t = newProjectSupplyTask(inventory, start,
0928: end, currentRate);
0929: projections.add(t);
0930: invpg.addDueIn(t);
0931: }
0932: currentRate = newRate;
0933: periodBegin = day;
0934: pendingDelta = delta;
0935: }
0936: }
0937: publishChangeProjection(inventory, projections.elements());
0938: }
0939:
0940: private boolean isSameRate(Rate rate1, Rate rate2) {
0941: if (rate1 == rate2)
0942: return true;
0943: if (rate1 == null || rate2 == null)
0944: return false;
0945: double val1 = rate1.getValue(rate1.getCommonUnit());
0946: double val2 = rate2.getValue(rate2.getCommonUnit());
0947: return MoreMath.nearlyEquals(val1, val2, 0.0001);
0948: }
0949:
0950: private boolean checkFailedRefill(Inventory inventory,
0951: InventoryPG invpg, int day) {
0952: Task prev_refill = invpg.refillAlreadyFailedOnDay(day);
0953: if (prev_refill != null) {
0954: return orderRefill(inventory, day);
0955: }
0956: return false;
0957: }
0958:
0959: // protected abstract boolean orderRefill(Inventory inventory, int day);
0960:
0961: private boolean failDueOut(Inventory inventory, InventoryPG invpg,
0962: int day) {
0963: DueOut lowestPriorityDueOut = invpg
0964: .getLowestPriorityDueOutBeforeDay(day);
0965: if (lowestPriorityDueOut == null)
0966: return false;
0967: // printDebug("Inventory level before failing allocation is "+
0968: // TimeUtils.dateString(invpg.convertDayToTime(day)) + " : "+invpg.getLevel(day)+
0969: // ", Reorder level :"+invpg.getReorderLevel(day));
0970: invpg.setDueOutFilled(lowestPriorityDueOut, false);
0971: lowestPriorityDueOut.setFilled(false);
0972: if (logger.isDebugEnabled()) {
0973: if (lowestPriorityDueOut.getPreviouslyFilled() != false) {
0974: Task task = lowestPriorityDueOut.getTask();
0975: logger
0976: .debug("adjustForInadequateInventory() shortfall="
0977: + shortfall
0978: + " on "
0979: + day
0980: + "="
0981: + TimeUtils.dateString(invpg
0982: .getStartOfDay(day))
0983: + " failed "
0984: + task.getUID()
0985: + " having "
0986: + TaskUtils.getDailyQuantity(task)
0987: + " on "
0988: + TimeUtils
0989: .dateString(invpg
0990: .convertDayToTime(lowestPriorityDueOut
0991: .getDay())));
0992: }
0993: }
0994: // printDebug("Inventory level after failing allocation is "
0995: // + TimeUtils.dateString(invpg.convertDayToTime(day))
0996: // + " : "+invpg.getLevel(day));
0997: return true;
0998: }
0999:
1000: private void updateWithdrawAllocations() {
1001: if (logger.isDebugEnabled()) {
1002: logger.debug("updateWithdrawAllocations()");
1003: }
1004: Iterator inventories = changedSet_.iterator();
1005: while (inventories.hasNext()) {
1006: Inventory inventory = (Inventory) inventories.next();
1007: InventoryPG invpg = (InventoryPG) inventory
1008: .getInventoryPG();
1009: List changes = invpg.updateDueOutAllocations();
1010: for (int i = 0, n = changes.size(); i < n; i++) {
1011: PlanElement pe = (PlanElement) changes.get(i);
1012: delegate_.publishChange(pe);
1013: }
1014: }
1015: }
1016:
1017: /**
1018: * Computes the next day on which a refill is needed. This simply
1019: * looks for the next day that the inventory drops below the
1020: * reorder level. It is likely that this is overridden in a
1021: * subclass. Indeed, GeneralInventoryManager _does_ override.
1022: */
1023: private int refillNeeded(Inventory inventory, int startDay) {
1024: InventoryPG invpg = (InventoryPG) inventory.getInventoryPG();
1025: int days = invpg.getPlanningDays();
1026: for (int day = startDay; day < days; day++) {
1027: if (needRefill(inventory, day))
1028: return day;
1029: }
1030: return DONE;
1031: }
1032:
1033: private boolean needRefill(Inventory inventory, int day) {
1034: InventoryPG invpg = inventory.getInventoryPG();
1035: double qty = convertScalarToDouble(invpg.getLevel(day));
1036: double level = invpg.getReorderLevel(day);
1037: shortfall = level - qty;
1038: return (shortfall > 0.0);
1039: }
1040:
1041: // ********************************************************
1042: // *
1043: // CheckForOverflow Section - only relevent for limited *
1044: // capacity inventories *
1045: // *
1046: // ********************************************************
1047:
1048: // ********************************************************
1049: // *
1050: // Refresh ScheduledContentPG on Inventory Section *
1051: // *
1052: // ********************************************************
1053:
1054: private void refreshInventorySchedule() {
1055: Inventory inventory;
1056: InventoryPG invpg;
1057: Iterator inventories = changedSet_.iterator();
1058:
1059: if (logger.isDebugEnabled()) {
1060: logger.debug("LAST STEP: REFRESHINVENTORYSCHEDULE()");
1061: }
1062: while (inventories.hasNext()) {
1063: inventory = (Inventory) inventories.next();
1064: invpg = (InventoryPG) inventory.getInventoryPG();
1065: // invpg.printInventoryLevels(inventory, clusterId_);
1066: invpg.updateContentSchedule(inventory);
1067:
1068: // detailed Inventory Schedule for demo purposes only
1069: Boolean detailed = (Boolean) inventoryPlugin_
1070: .getParam("Detailed");
1071: if ((detailed != null) && detailed.booleanValue()) {
1072: invpg.updateDetailedContentSchedule(inventory);
1073: }
1074: invpg.updateInventoryLevelsSchedule(inventory);
1075: }
1076: }
1077:
1078: /**
1079: * method called from update when a GLS Rescind is detected. This
1080: * is an entry point for any additional handling by subclasses.
1081: */
1082: protected void handleGLSRescind() {
1083: }
1084:
1085: // ********************************************************
1086: // *
1087: // Utilities Section *
1088: // *
1089: // ********************************************************
1090:
1091: /**
1092: * @return a double indicating the amount requests by the task (in terms of the standard unit of measure for the item)
1093: */
1094: protected double getAmountRequested(Task task) {
1095: return TaskUtils.getPreference(task, AspectType.QUANTITY);
1096: }
1097:
1098: /**
1099: * Given a Scalar, return a double value representing
1100: * Gallons for Volume,
1101: * Eaches for Count and
1102: * Short Tons for Mass.
1103: */
1104: static protected double convertScalarToDouble(Scalar measure) {
1105: double d = Double.NaN;
1106: if (measure instanceof Volume) {
1107: d = ((Volume) measure).getGallons();
1108: } else if (measure instanceof Count) {
1109: d = ((Count) measure).getEaches();
1110: } else if (measure instanceof Mass) {
1111: d = ((Mass) measure).getShortTons();
1112: }
1113: return d;
1114: }
1115:
1116: static protected Scalar newScalarFromOldToDouble(Scalar old,
1117: double newVal) {
1118: if (old instanceof Volume) {
1119: return Volume.newGallons(newVal);
1120: } else if (old instanceof Count) {
1121: return Count.newEaches(newVal);
1122: } else if (old instanceof Mass) {
1123: return Mass.newShortTons(newVal);
1124: }
1125:
1126: String oldUnitName = old.getUnitName(old.getCommonUnit());
1127: return (Scalar) AbstractMeasure.newMeasure(oldUnitName,
1128: (int) newVal);
1129: }
1130:
1131: //Max at the capacity
1132: protected double getMaxReorderLevel(Inventory inventory) {
1133: InventoryPG invpg = (InventoryPG) inventory
1134: .searchForPropertyGroup(InventoryPG.class);
1135: return convertScalarToDouble(invpg.getCapacity());
1136: }
1137:
1138: //Min at zero unless maintainAtCapacity inventory, then min is capacity.
1139: protected double getMinReorderLevel(Inventory inventory) {
1140: double mrl = 0.0;
1141: InventoryPG invpg = (InventoryPG) inventory
1142: .searchForPropertyGroup(InventoryPG.class);
1143: if (invpg.getMaintainAtCapacity()) {
1144: mrl = convertScalarToDouble(invpg.getCapacity());
1145: }
1146: return mrl;
1147: }
1148:
1149: /**
1150: * Update the current InventoryPolicy
1151: */
1152: protected boolean updateInventoryPolicy(Enumeration policies) {
1153: InventoryPolicy pol;
1154: boolean changed = false;
1155: // printDebug("updateInventoryPolicy(), Days On Hand Policy for "+supplyType_+". DaysOnHand: "+daysOnHand_+
1156: // ", Days Forward: "+daysForward_+", Days Backward: "+daysBackward_+", Window size: "+
1157: // (daysForward_+daysBackward_));
1158: while (policies.hasMoreElements()) {
1159: pol = (InventoryPolicy) policies.nextElement();
1160: int days = pol.getDaysOnHand();
1161: if ((days >= 0) && (days != daysOnHand_)) {
1162: daysOnHand_ = days;
1163: changed = true;
1164: }
1165: int forward = pol.getDaysForward();
1166: if ((forward >= 0) && (forward != daysForward_)) {
1167: daysForward_ = forward;
1168: changed = true;
1169: }
1170: int backward = pol.getDaysBackward();
1171: if ((backward >= 0) && (backward != daysBackward_)) {
1172: daysBackward_ = backward;
1173: checkDeletionPolicy(); // Changed daysBackward, need to change deletion policy
1174: changed = true;
1175: }
1176: double multiplier = pol.getGoalLevelMultiplier();
1177: if ((multiplier > 1.0)
1178: && (multiplier != goalLevelMultiplier_)) {
1179: goalLevelMultiplier_ = multiplier;
1180: changed = true;
1181: }
1182: if (pol.hasFillToCapacityRule()) {
1183: inventoryPlugin_.setFillToCapacity(supplyType_, pol
1184: .getFillToCapacity());
1185: changed = true;
1186: }
1187: if (pol.hasMaintainAtCapacityRule()) {
1188: inventoryPlugin_.setMaintainAtCapacity(supplyType_, pol
1189: .getMaintainAtCapacity());
1190: changed = true;
1191: }
1192: if (pol.hasSwitchoverRule()) {
1193: ProjectionWeight newWeight = new ProjectionWeightImpl(
1194: pol.getWithdrawSwitchoverDay(), pol
1195: .getRefillSwitchoverDay(), pol
1196: .getTurnOffProjections());
1197: inventoryPlugin_.setProjectionWeight(supplyType_,
1198: newWeight);
1199: changed = true;
1200: }
1201: }
1202: if (changed) {
1203: if (logger.isDebugEnabled()) {
1204: logger
1205: .debug("updateInventoryPolicy(), Days On Hand Policy CHANGED for "
1206: + supplyType_
1207: + ". DaysOnHand: "
1208: + daysOnHand_
1209: + ", Days Forward: "
1210: + daysForward_
1211: + ", Days Backward: "
1212: + daysBackward_
1213: + ", Window size: "
1214: + (daysForward_ + daysBackward_)
1215: + ", goal level multiplier: "
1216: + goalLevelMultiplier_);
1217: }
1218: }
1219: return changed;
1220: }
1221:
1222: }
|