001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026: package org.cougaar.mlm.plugin.assessor;
027:
028: import java.text.MessageFormat;
029: import java.util.ArrayList;
030: import java.util.Enumeration;
031: import java.util.HashMap;
032: import java.util.HashSet;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Set;
036:
037: import org.cougaar.core.agent.service.alarm.Alarm;
038: import org.cougaar.core.blackboard.CollectionSubscription;
039: import org.cougaar.core.blackboard.IncrementalSubscription;
040: import org.cougaar.glm.ldm.Constants;
041: import org.cougaar.glm.ldm.asset.Inventory;
042: import org.cougaar.glm.ldm.asset.Organization;
043: import org.cougaar.glm.ldm.policy.ACRPolicy;
044: import org.cougaar.glm.plugins.MaintainedItem;
045: import org.cougaar.glm.plugins.TaskUtils;
046: import org.cougaar.planning.ldm.asset.Asset;
047: import org.cougaar.planning.ldm.measure.CountRate;
048: import org.cougaar.planning.ldm.measure.FlowRate;
049: import org.cougaar.planning.ldm.measure.Rate;
050: import org.cougaar.planning.ldm.plan.Alert;
051: import org.cougaar.planning.ldm.plan.AlertParameter;
052: import org.cougaar.planning.ldm.plan.NewAlert;
053: import org.cougaar.planning.ldm.plan.NewAlertParameter;
054: import org.cougaar.planning.ldm.plan.PrepositionalPhrase;
055: import org.cougaar.planning.ldm.plan.Task;
056: import org.cougaar.planning.ldm.plan.Verb;
057: import org.cougaar.planning.ldm.policy.RuleParameterIllegalValueException;
058: import org.cougaar.planning.plugin.legacy.SimplePlugin;
059: import org.cougaar.util.UnaryPredicate;
060:
061: /**
062: * Watch for supply tasks for MEI consumers/sonsumed and estimate the
063: * apparent failure/consumption rate. Upon command through a GUI,
064: * update the consumer spec for the consumer/consumed to match the
065: * apparent rate. Runs daily and uses a sliding window of the past 14
066: * days.
067: **/
068: public class ConsumptionAssessorPlugin extends SimplePlugin {
069: private static final String ALERT_TEXT = "Failure/Consumption Rate Deviation";
070: private static final int YES_PARAM = 0;
071: private static final int NO_PARAM = 1;
072: private static final int BUCKET_PARAM = 2;
073: private static final int FACTOR_PARAM = 3;
074:
075: private static final int WINDOW_SIZE_PARAM = 0;
076: private static final int ASSESSMENT_PERIOD_PARAM = 1;
077: private static final int MAX_DEVIATION_PARAM = 2;
078: private static final int N_FIXED_PARAMS = 3;
079:
080: private MessageFormat alertMessageFormat = new MessageFormat(
081: "Failure/Consumption Rate Deviation\n\n"
082: + "Consumer: {0}\n"
083: + "Consumable: {1}\n"
084: + "Projected rate: {2,number,#,###.##}\n"
085: + "Apparent rate: {3,number,#,###.##}\n"
086: + "Deviation: {4,number,0.##}\n"
087: + "Total Consumption: {5,number,#,###}\n"
088: + "Total Projection: {6,number,#,###}\n"
089: + "Current Adjustment:{7,number,#0.##}\n"
090: + "New Adjustment: {8,number,#0.##}\n\n"
091: + "Do you want to change the rate to {3,number,#,###.##}?\n");
092:
093: private Alarm timer;
094: private long assessmentPeriod;
095: private long windowBegin;
096: private long windowEnd;
097: private long windowSize;
098: private double maxDeviation;
099: private HashMap alerts = new HashMap();
100: private String selfOrgName;
101: private Set resourceTypes = null; // Null means all
102:
103: /**
104: * The key to a particular consumption assessment consisting of the
105: * consumer and consumed assets.
106: **/
107: private static class Bucket implements java.io.Serializable {
108: MaintainedItem consumer;
109: Asset consumed;
110:
111: public Bucket(Task task) {
112: consumer = getConsumer(task);
113: consumed = getConsumed(task);
114: }
115:
116: public Bucket(MaintainedItem consumer, Asset consumed) {
117: this .consumer = consumer;
118: this .consumed = consumed;
119: }
120:
121: public int hashCode() {
122: return consumer.hashCode() + consumed.hashCode();
123: }
124:
125: public boolean matches(Task task) {
126: return getConsumer(task).equals(consumer)
127: && getConsumed(task).equals(consumed);
128: }
129:
130: public boolean equals(Object o) {
131: if (o instanceof Bucket) {
132: Bucket other = (Bucket) o;
133: return consumer.equals(other.consumer)
134: && consumed.equals(other.consumed);
135: }
136: return false;
137: }
138:
139: public String toString() {
140: return consumer + "/" + consumed;
141: }
142: }
143:
144: private IncrementalSubscription selfOrgs;
145: private static UnaryPredicate selfOrgPredicate = new UnaryPredicate() {
146: public boolean execute(Object o) {
147: if (o instanceof Organization) {
148: return ((Organization) o).isSelf();
149: }
150: return false;
151: }
152: };
153:
154: private boolean taskPredicate(Object o, Verb verb, Set resourceTypes) {
155: if (o instanceof Task) {
156: Task task = (Task) o;
157: if (task.getVerb().equals(verb)) {
158: boolean maintainingOk = false;
159: boolean forOk = false;
160: boolean forSelf = false;
161: boolean isRefill = false;
162: boolean resourceTypesOk = resourceTypes == null;
163: for (Enumeration e = task.getPrepositionalPhrases(); e
164: .hasMoreElements();) {
165: PrepositionalPhrase pp = (PrepositionalPhrase) e
166: .nextElement();
167: String prep = pp.getPreposition();
168: if (prep.equals(Constants.Preposition.MAINTAINING)) {
169: try {
170: MaintainedItem item = (MaintainedItem) pp
171: .getIndirectObject();
172: if (!(item.getMaintainedItemType()
173: .equals("Inventory"))) {
174: return true;
175: }
176: } catch (ClassCastException exc) {
177: return false;
178: }
179:
180: if (pp.getIndirectObject() instanceof Inventory)
181: return false;
182: maintainingOk = true;
183: } else if (prep.equals(Constants.Preposition.FOR)) {
184: String orgName = (String) pp
185: .getIndirectObject();
186: forSelf = orgName.equals(selfOrgName);
187: if (forSelf && isRefill)
188: return false;
189: forOk = true;
190: } else if (prep
191: .equals(Constants.Preposition.REFILL)) {
192: isRefill = true;
193: if (forSelf)
194: return false;
195: } else if (prep
196: .equals(Constants.Preposition.OFTYPE)) {
197: Object type = pp.getIndirectObject();
198: if (resourceTypes != null
199: && !resourceTypes.contains(type)) {
200: return false;
201: }
202: resourceTypesOk = true;
203: }
204: if (maintainingOk && resourceTypesOk && forOk
205: && !(isRefill && forSelf))
206: return true;
207: }
208: }
209: }
210: return false;
211: }
212:
213: private CollectionSubscription projectionTasks;
214: private UnaryPredicate projectionTaskPredicate = new UnaryPredicate() {
215: public boolean execute(Object o) {
216: return taskPredicate(o, Constants.Verb.ProjectSupply,
217: resourceTypes);
218: }
219: };
220:
221: private CollectionSubscription supplyTasks;
222: private UnaryPredicate supplyTaskPredicate = new UnaryPredicate() {
223: public boolean execute(Object o) {
224: return taskPredicate(o, Constants.Verb.Supply, null);
225: }
226: };
227:
228: private IncrementalSubscription alertSubscription;
229: private static UnaryPredicate alertPredicate = new UnaryPredicate() {
230: public boolean execute(Object o) {
231: if (o instanceof Alert) {
232: Alert alert = (Alert) o;
233: return (alert.getType() == Alert.CONSUMPTION_DEVIATION_TYPE);
234: }
235: return false;
236: }
237: };
238:
239: public void setupSubscriptions() {
240: List params = getParameters();
241: windowSize = parseInterval((String) params
242: .get(WINDOW_SIZE_PARAM));
243: assessmentPeriod = parseInterval((String) params
244: .get(ASSESSMENT_PERIOD_PARAM));
245: maxDeviation = Double.parseDouble((String) params
246: .get(MAX_DEVIATION_PARAM));
247: int nParams = params.size();
248: if (nParams > N_FIXED_PARAMS) {
249: resourceTypes = new HashSet();
250: for (int i = N_FIXED_PARAMS; i < nParams; i++) {
251: resourceTypes.add(params.get(i));
252: }
253: }
254: selfOrgs = (IncrementalSubscription) subscribe(selfOrgPredicate);
255: processSelfOrgs(selfOrgs.elements());
256: alertSubscription = (IncrementalSubscription) subscribe(alertPredicate);
257: initializeAlertsMap(alertSubscription.elements());
258: }
259:
260: public void processSelfOrgs(Enumeration en) {
261: if (en.hasMoreElements()) {
262: Organization self = (Organization) en.nextElement();
263: selfOrgName = self.getItemIdentificationPG()
264: .getItemIdentification();
265: projectionTasks = (CollectionSubscription) subscribe(
266: projectionTaskPredicate, false);
267: supplyTasks = (CollectionSubscription) subscribe(
268: supplyTaskPredicate, false);
269: startTimer();
270: }
271: }
272:
273: private void startTimer() {
274: timer = wakeAfter(assessmentPeriod);
275: }
276:
277: public void execute() {
278: if (selfOrgName == null) {
279: if (selfOrgs.hasChanged()) {
280: processSelfOrgs(selfOrgs.getAddedList());
281: }
282: } else {
283: if (timer.hasExpired()) {
284: computeConsumptionRates();
285: startTimer();
286: }
287: }
288: if (alertSubscription.hasChanged()) {
289: processAlerts(alertSubscription.getChangedList());
290: }
291: }
292:
293: private void processAlerts(Enumeration en) {
294: while (en.hasMoreElements()) {
295: Alert alert = (Alert) en.nextElement();
296: if (alert.getAcknowledged()) {
297: AlertParameter response = (AlertParameter) alert
298: .getOperatorResponse();
299: if ("Yes".equals(response.getParameter())) {
300: Bucket bucket = (Bucket) alert.getAlertParameters()[BUCKET_PARAM]
301: .getParameter();
302: Double newFactor = (Double) alert
303: .getAlertParameters()[FACTOR_PARAM]
304: .getParameter();
305: ACRPolicy policy = (ACRPolicy) theLDMF
306: .newPolicy(ACRPolicy.class.getName());
307: try {
308: policy
309: .setConsumerTypeIdentification(bucket.consumer
310: .getTypeIdentification());
311: policy
312: .setConsumedTypeIdentification(bucket.consumed
313: .getTypeIdentificationPG()
314: .getTypeIdentification());
315: policy.setAdjustmentFactor(newFactor
316: .doubleValue());
317: publishAdd(policy);
318: } catch (RuleParameterIllegalValueException rpive) {
319: rpive.printStackTrace();
320: }
321: } else {
322: // System.out.println("Operator responded " + response.getParameter());
323: }
324: }
325: }
326: }
327:
328: /**
329: * Loop through all projection tasks for MEI consumers
330: **/
331: private void computeConsumptionRates() {
332: String types = " of all types";
333: if (resourceTypes != null) {
334: types = " of type";
335: for (Iterator i = resourceTypes.iterator(); i.hasNext();) {
336: types += " " + i.next();
337: }
338: }
339: // System.out.println("Starting consumption assessment for " + selfOrgName + types);
340: HashMap buckets = new HashMap();
341: windowEnd = currentTimeMillis(); // Window ends now
342: windowBegin = windowEnd - windowSize;
343: Enumeration en = projectionTasks.elements();
344: // if (!en.hasMoreElements()) {
345: // System.out.println("No projections for " + selfOrgName);
346: // }
347: while (en.hasMoreElements()) {
348: Task projectionTask = (Task) en.nextElement();
349: long startTime = TaskUtils.getStartTime(projectionTask);
350: long endTime = TaskUtils.getEndTime(projectionTask);
351: if (startTime > windowBegin || endTime < windowEnd) {
352: continue; // Rate doesn't apply for entire window
353: }
354: Bucket bucket = new Bucket(projectionTask);
355: List projectionTasks = (List) buckets.get(bucket);
356: if (projectionTasks == null) {
357: projectionTasks = new ArrayList();
358: buckets.put(bucket, projectionTasks);
359: }
360: projectionTasks.add(projectionTask);
361: }
362: // if (buckets.isEmpty()) {
363: // System.out.println("No valid projections for " + selfOrgName);
364: // }
365: // if (supplyTasks.isEmpty()) {
366: // System.out.println("No supply tasks for " + selfOrgName);
367: // }
368: for (Iterator keys = buckets.keySet().iterator(); keys
369: .hasNext();) {
370: Bucket bucket = (Bucket) keys.next();
371: List projectionTasks = (List) buckets.get(bucket);
372: // System.out.println("Bucket " + bucket + " has " + projectionTasks.size() + " projections");
373: checkConsumption(bucket, projectionTasks);
374: }
375: }
376:
377: private static MaintainedItem getConsumer(Task task) {
378: return (MaintainedItem) task.getPrepositionalPhrase(
379: Constants.Preposition.MAINTAINING).getIndirectObject();
380: }
381:
382: private static Asset getConsumed(Task task) {
383: return task.getDirectObject();
384: }
385:
386: private void checkConsumption(Bucket bucket, List projectionTasks) {
387: // System.out.println("checkConsumption " + bucket);
388: double totalConsumption = 0.0;
389: double totalProjection = 0.0;
390: double totalBase = 0.0;
391: // Now we accumulate the statistics about this Consumer/Consumed rate
392: for (Iterator i = projectionTasks.iterator(); i.hasNext();) {
393: Task projectionTask = (Task) i.next();
394: PrepositionalPhrase pp;
395: Rate rate = TaskUtils.getRate(projectionTask);
396: double projectedRate;
397: if (rate instanceof CountRate) {
398: projectedRate = ((CountRate) rate)
399: .getValue(CountRate.EACHES_PER_DAY);
400: } else if (rate instanceof FlowRate) {
401: projectedRate = ((FlowRate) rate)
402: .getValue(FlowRate.GALLONS_PER_DAY);
403: } else {
404: System.err.println("Don't understand this rate: "
405: + rate);
406: continue; // Don't understand this kind of rate
407: }
408: double multiplier = TaskUtils.getMultiplier(projectionTask);
409: Enumeration tasks = supplyTasks.elements();
410: boolean valid = false;
411: double this Consumption = 0.0;
412: while (tasks.hasMoreElements()) {
413: Task supplyTask = (Task) tasks.nextElement();
414: long endTime = TaskUtils.getEndTime(supplyTask);
415: if (!bucket.matches(supplyTask)) {
416: continue; // Not for this bucket
417: }
418: if (endTime < windowBegin) {
419: valid = true;
420: continue; // Outside the window
421: }
422: if (endTime > windowEnd) {
423: continue; // Outside the window
424: }
425: double q = TaskUtils.getQuantity(supplyTask);
426: // if (q == 0.0) System.err.println("Supply task has 0 quantity: " + supplyTask);
427: this Consumption += q;
428: }
429: if (valid) {
430: double this Projection = projectedRate * windowSize
431: / TaskUtils.MSEC_PER_DAY;
432: double this Base = this Projection / multiplier;
433: totalConsumption += this Consumption;
434: totalProjection += this Projection;
435: totalBase += this Base;
436: // } else {
437: // System.out.println("No valid supply tasks for " + selfOrgName + " of " + bucket);
438: }
439: }
440: // if (totalConsumption == 0.0) {
441: // System.out.println("No consumption for " + selfOrgName + " of " + bucket);
442: // }
443:
444: double apparentRate = totalConsumption * TaskUtils.MSEC_PER_DAY
445: / windowSize;
446: double projectedRate = totalProjection * TaskUtils.MSEC_PER_DAY
447: / windowSize;
448: double currentAdjustmentFactor = totalProjection / totalBase;
449: double newAdjustmentFactor = (apparentRate / projectedRate)
450: * currentAdjustmentFactor;
451: double d = (apparentRate + projectedRate);
452: if (d < Double.MIN_VALUE)
453: return; // Rates are too small to consider
454: double deviation = (apparentRate - projectedRate) / d;
455: if (Math.abs(deviation) > maxDeviation) {
456: sendAlert(bucket, deviation, apparentRate, projectedRate,
457: currentAdjustmentFactor, newAdjustmentFactor,
458: totalConsumption, totalProjection);
459: } else {
460: // System.out.println("Minor deviation " + deviation + " for " + selfOrgName + " of " + bucket);
461: removeAlert(bucket);
462: }
463: }
464:
465: private void initializeAlertsMap(Enumeration e) {
466: while (e.hasMoreElements()) {
467: Alert alert = (Alert) e.nextElement();
468: Bucket bucket = (Bucket) alert.getAlertParameters()[2]
469: .getParameter();
470: alerts.put(bucket, alert);
471: }
472: }
473:
474: private void removeAlert(Bucket bucket) {
475: Alert alert = (Alert) alerts.get(bucket);
476: if (alert != null)
477: publishRemove(alert);
478: }
479:
480: /**
481: * Publish an alert requesting adjustment of this rate.
482: **/
483: private void sendAlert(Bucket bucket, double deviation,
484: double apparentRate, double projectedRate,
485: double currentAdjustmentFactor, double newAdjustmentFactor,
486: double totalConsumption, double totalProjection) {
487: removeAlert(bucket);
488: Double newFactor = new Double(newAdjustmentFactor);
489: Object[] args = new Object[] {
490: bucket.consumer.getNomenclature(),
491: bucket.consumed.getTypeIdentificationPG()
492: .getNomenclature(), new Double(projectedRate),
493: new Double(apparentRate), new Double(deviation),
494: new Double(totalConsumption),
495: new Double(totalProjection),
496: new Double(currentAdjustmentFactor), newFactor };
497: NewAlert alert = theLDMF.newAlert();
498: alert.setAlertText(alertMessageFormat.format(args));
499: alert.setType(Alert.CONSUMPTION_DEVIATION_TYPE);
500: AlertParameter[] alertParameters = new AlertParameter[] {
501: theLDMF.newAlertParameter(),
502: theLDMF.newAlertParameter(),
503: theLDMF.newAlertParameter(),
504: theLDMF.newAlertParameter() };
505: ((NewAlertParameter) alertParameters[YES_PARAM])
506: .setDescription("Yes");
507: ((NewAlertParameter) alertParameters[YES_PARAM])
508: .setParameter("Yes");
509: ((NewAlertParameter) alertParameters[NO_PARAM])
510: .setDescription("No");
511: ((NewAlertParameter) alertParameters[NO_PARAM])
512: .setParameter("No");
513: ((NewAlertParameter) alertParameters[BUCKET_PARAM])
514: .setParameter(bucket);
515: ((NewAlertParameter) alertParameters[BUCKET_PARAM])
516: .setVisible(false);
517: ((NewAlertParameter) alertParameters[FACTOR_PARAM])
518: .setParameter(newFactor);
519: ((NewAlertParameter) alertParameters[FACTOR_PARAM])
520: .setVisible(false);
521: alert.setAlertParameters(alertParameters);
522: alert.setOperatorResponseRequired(true);
523: publishAdd(alert);
524: alerts.put(bucket, alert);
525: System.out.println(alert.getAlertText());
526: }
527: }
|