source: trunk/autoquest-core-tasktrees/src/main/java/de/ugoe/cs/autoquest/tasktrees/temporalrelation/DefaultTaskSequenceDetectionRule.java @ 1119

Last change on this file since 1119 was 1119, checked in by pharms, 11 years ago
  • improved and corrected task detection
File size: 21.6 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.tasktrees.temporalrelation;
16
17import java.util.Iterator;
18import java.util.LinkedList;
19import java.util.List;
20
21import de.ugoe.cs.autoquest.tasktrees.nodeequality.NodeEquality;
22import de.ugoe.cs.autoquest.tasktrees.nodeequality.NodeEqualityRuleManager;
23import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
24import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeBuilder;
25import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNode;
26import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNodeFactory;
27import de.ugoe.cs.autoquest.usageprofiles.SymbolComparator;
28import de.ugoe.cs.autoquest.usageprofiles.Trie;
29import de.ugoe.cs.autoquest.usageprofiles.TrieProcessor;
30
31/**
32 * <p>
33 * TODO comment
34 * </p>
35 *
36 * @author Patrick Harms
37 */
38class DefaultTaskSequenceDetectionRule implements TemporalRelationshipRule {
39   
40    /**
41     * <p>
42     * the task tree node factory to be used for creating substructures for the temporal
43     * relationships identified during rule
44     * </p>
45     */
46    private ITaskTreeNodeFactory taskTreeNodeFactory;
47    /**
48     * <p>
49     * the task tree builder to be used for creating substructures for the temporal relationships
50     * identified during rule application
51     * </p>
52     */
53    private ITaskTreeBuilder taskTreeBuilder;
54
55    /**
56     * <p>
57     * the node comparator to be used for comparing task tree nodes
58     * </p>
59     */
60    private SymbolComparator<ITaskTreeNode> nodeComparator;
61
62    /**
63     * <p>
64     * instantiates the rule and initializes it with a node equality rule manager and the minimal
65     * node equality identified sublist must have to consider them as iterated.
66     * </p>
67     */
68    DefaultTaskSequenceDetectionRule(NodeEqualityRuleManager nodeEqualityRuleManager,
69                                     NodeEquality            minimalNodeEquality,
70                                     ITaskTreeNodeFactory    taskTreeNodeFactory,
71                                     ITaskTreeBuilder        taskTreeBuilder)
72    {
73        this.taskTreeNodeFactory = taskTreeNodeFactory;
74        this.taskTreeBuilder = taskTreeBuilder;
75       
76        this.nodeComparator =
77            new TaskTreeNodeComparator(nodeEqualityRuleManager, minimalNodeEquality);
78    }
79
80    /* (non-Javadoc)
81     * @see java.lang.Object#toString()
82     */
83    @Override
84    public String toString() {
85        return "DefaultTaskSequenceDetectionRule";
86    }
87
88    /*
89     * (non-Javadoc)
90     *
91     * @see de.ugoe.cs.tasktree.temporalrelation.TemporalRelationshipRule#apply(TaskTreeNode,
92     * boolean)
93     */
94    @Override
95    public RuleApplicationResult apply(ITaskTreeNode parent, boolean finalize) {
96        if (!(parent instanceof ISequence)) {
97            return null;
98        }
99
100        if (!finalize) {
101            // the rule is always feasible as tasks may occur at any time
102            RuleApplicationResult result = new RuleApplicationResult();
103            result.setRuleApplicationStatus(RuleApplicationStatus.RULE_APPLICATION_FEASIBLE);
104            return result;
105        }
106
107        RuleApplicationData<ITaskTreeNode> appData = new RuleApplicationData<ITaskTreeNode>(parent);
108       
109        appData.getStopWatch().start("whole rule application");
110       
111        do {
112            getSequencesOccuringMostOften(appData);
113           
114            if ((appData.getLastFoundSequences().size() > 0) &&
115                (appData.getLastFoundSequences().getOccurrenceCount() > 1))
116            {
117                System.out.println("found " + appData.getLastFoundSequences().size() +
118                                   " tasks occurring " +
119                                   appData.getLastFoundSequences().getOccurrenceCount() + " times");
120
121                for (List<ITaskTreeNode> task : appData.getLastFoundSequences()) {
122                    // only tasks occurring more often than once are of interest
123                    appData.stopWatch.start("creating task sequences");
124
125                    String taskId = "task " + RuleUtils.getNewId();
126                    System.out.println(taskId + ": " + task);
127                   
128                    appData.resetReplacementCounter();
129                    createSequenceForTaskOccurrences(taskId, task, parent, appData);
130                   
131                    if (appData.getReplacementCounter() <
132                        appData.getLastFoundSequences().getOccurrenceCount())
133                    {
134                        System.out.println(taskId + ": replaced task only " +
135                                           appData.getReplacementCounter() +
136                                           " times instead of expected " +
137                                           appData.getLastFoundSequences().getOccurrenceCount());
138                       
139                    }
140
141                    appData.stopWatch.stop("creating task sequences");
142                }
143            }
144           
145        }
146        while (appData.getLastFoundSequences().getOccurrenceCount() > 2);
147       
148        appData.getStopWatch().stop("whole rule application");
149        appData.getStopWatch().dumpStatistics(System.out);
150       
151        if (appData.getResult().getNewlyCreatedParentNodes().size() > 0) {
152            appData.getResult().setRuleApplicationStatus
153                (RuleApplicationStatus.RULE_APPLICATION_FINISHED);
154        }
155       
156        System.out.println(appData.getResult().getNewlyCreatedParentNodes().size());
157       
158        for (ITaskTreeNode node : appData.getResult().getNewlyCreatedParentNodes()) {
159            for (ITaskTreeNode child : node.getChildren()) {
160                System.out.println(child);
161            }
162            System.out.println();
163        }
164       
165        return appData.getResult();
166    }
167
168    /**
169     * <p>
170     * TODO: comment
171     * </p>
172     *
173     * @param i
174     * @return
175     */
176    private void getSequencesOccuringMostOften(RuleApplicationData<ITaskTreeNode> appData) {
177        System.out.println("determining most prominent tasks with a maximum of " +
178                           (appData.getLastFoundSequences().getOccurrenceCount() - 1) +
179                           " occurrences");
180
181        Sequences<ITaskTreeNode> sequences;
182        boolean createNewTrie = (appData.getLastTrie() == null) ||
183            appData.getReplacementCounter() > 0; // tree has changed
184       
185        do {
186            if (createNewTrie) {
187                createNewTrie(appData);
188            }
189           
190            /*PathDumper dumper = new PathDumper();
191            trie.process(dumper);
192       
193            dumper.dump();*/
194       
195            appData.getStopWatch().start("determining tasks");
196
197            MaxCountAndLongestSequenceFinder finder = new MaxCountAndLongestSequenceFinder
198                  (appData.getLastFoundSequences().getOccurrenceCount() - 1);
199            appData.getLastTrie().process(finder);
200       
201            sequences = finder.getFoundSequences();
202           
203            createNewTrie = false;
204           
205            for (List<ITaskTreeNode> task : sequences) {
206                if (task.size() >= appData.getTrainedSequenceLength()) {
207                    // Trie must be recreated with a longer sequence length to be sure that
208                    // the found sequences occurring most often are found in their whole length
209                    appData.setTrainedSequenceLength(appData.getTrainedSequenceLength() + 1);
210                    createNewTrie = true;
211                    break;
212                }
213            }
214           
215            appData.getStopWatch().stop("determining tasks");
216        }
217        while (createNewTrie);
218       
219        appData.setLastFoundSequences(sequences);
220    }
221
222    /**
223     * <p>
224     * TODO: comment
225     * </p>
226     *
227     * @param parent
228     * @return
229     */
230    private void createNewTrie(RuleApplicationData<ITaskTreeNode> appData) {
231        System.out.println("training trie with a maximum sequence length of " +
232                           appData.getTrainedSequenceLength());
233
234        appData.getStopWatch().start("training trie");
235        appData.setLastTrie(new Trie<ITaskTreeNode>(nodeComparator));
236   
237        trainTrie(appData.getTree(), appData);
238        appData.getStopWatch().stop("training trie");
239    }
240
241    /**
242     * <p>
243     * TODO: comment
244     * </p>
245     *
246     * @param trie
247     * @param parent
248     */
249    private void trainTrie(ITaskTreeNode parent, RuleApplicationData<ITaskTreeNode> appData) {
250        // prevent training of already replaces sequences as those shall not be replaced anymore
251        if (!appData.getResult().getNewlyCreatedParentNodes().contains(parent)) {
252            List<ITaskTreeNode> children = parent.getChildren();
253       
254            if ((children != null) && (children.size() > 0)) {
255               
256                /*System.out.println();
257                for (int i = 0; i < children.size(); i++) {
258                    System.out.println(children.get(i));
259                }*/
260               
261                appData.getLastTrie().train(children, appData.getTrainedSequenceLength());
262           
263                for (ITaskTreeNode child : children) {
264                    trainTrie(child, appData);
265                }
266            }
267        }
268    }
269
270    /**
271     * <p>
272     * TODO: comment
273     * </p>
274     *
275     * @param task
276     * @param parent
277     * @param treeBuilder
278     * @param nodeFactory
279     * @param result
280     */
281    private void createSequenceForTaskOccurrences(String                             taskId,
282                                                  List<ITaskTreeNode>                task,
283                                                  ITaskTreeNode                      parent,
284                                                  RuleApplicationData<ITaskTreeNode> appData)
285    {
286        List<ITaskTreeNode> children = parent.getChildren();
287       
288        if ((children == null) || (children.size() == 0)) {
289            return;
290        }
291       
292        // first traverse the children
293        for (ITaskTreeNode child : children) {
294            createSequenceForTaskOccurrences(taskId, task, child, appData);
295        }
296       
297        // now check the children themselves for an occurrence of the task
298        int index = -1;
299       
300        do {
301            index = getSubListIndex(children, task, ++index);
302           
303            if (index > -1) {
304                if (task.size() < children.size()) {
305                    ISequence sequence = RuleUtils.createNewSubSequenceInRange
306                        (parent, index, index + task.size() - 1, taskId,
307                         taskTreeNodeFactory, taskTreeBuilder);
308                   
309                    appData.getResult().addNewlyCreatedParentNode(sequence);
310                    appData.incrementReplacementCounter();
311                 
312                    children = parent.getChildren();
313                }
314                else {
315                    // the whole list of children is an occurrence of this task. Just set the
316                    // description of the parent and break up
317                    String description = parent.getDescription();
318                   
319                    if ((description != null) && (description.length() > 0)) {
320                        description += "; " + taskId;
321                    }
322                    else {
323                        description = taskId;
324                    }
325                   
326                    taskTreeBuilder.setDescription(parent, description);
327                    break;
328                }
329            }
330        }
331        while (index > -1);
332    }
333
334    /**
335     * <p>
336     * TODO: comment
337     * </p>
338     *
339     * @param trie
340     * @param object
341     * @return
342     */
343    private int getSubListIndex(List<ITaskTreeNode> list,
344                                List<ITaskTreeNode> subList,
345                                int                 startIndex)
346    {
347        boolean matchFound;
348        int result = -1;
349       
350        for (int i = startIndex; i <= list.size() - subList.size(); i++) {
351            matchFound = true;
352           
353            for (int j = 0; j < subList.size(); j++) {
354                if (!nodeComparator.equals(list.get(i + j), subList.get(j))) {
355                    matchFound = false;
356                    break;
357                }
358                else if (!nodeComparator.equals(subList.get(j), list.get(i + j))){
359                    throw new RuntimeException("comparator not commutative");
360                }
361            }
362           
363            if (matchFound) {
364                result = i;
365                break;
366            }
367        }
368       
369        return result;
370    }
371   
372    /**
373     * <p>
374     * TODO comment
375     * </p>
376     *
377     * @author Patrick Harms
378     */
379    private class MaxCountAndLongestSequenceFinder implements TrieProcessor<ITaskTreeNode> {
380       
381        /**
382         *
383         */
384        private int maxCount;
385       
386        /**
387         *
388         */
389        private int currentCount;
390       
391        /**
392         *
393         */
394        private List<List<ITaskTreeNode>> foundSequences = new LinkedList<List<ITaskTreeNode>>();
395
396        /**
397         * <p>
398         * TODO: comment
399         * </p>
400         *
401         * @param maxCount
402         */
403        public MaxCountAndLongestSequenceFinder(int maxCount) {
404            super();
405            this.maxCount = maxCount;
406            this.currentCount = 0;
407        }
408
409        /* (non-Javadoc)
410         * @see de.ugoe.cs.autoquest.usageprofiles.TrieProcessor#process(java.util.List, int)
411         */
412        @Override
413        public TrieProcessor.Result process(List<ITaskTreeNode> sequence, int count) {
414            if (sequence.size() < 2) {
415                // ignore single nodes
416                return TrieProcessor.Result.CONTINUE;
417            }
418           
419            if (count < 2) {
420                // ignore singular occurrences
421                return TrieProcessor.Result.SKIP_NODE;
422            }
423
424            if (count > this.maxCount) {
425                // ignore sequences that occur too often
426                return TrieProcessor.Result.CONTINUE;
427            }
428           
429            if (this.currentCount > count) {
430                // ignore this children of this node, as they may have only smaller counts than
431                // the already found sequences
432                return TrieProcessor.Result.SKIP_NODE;
433            }
434           
435            if (this.currentCount < count) {
436                // the provided sequence occurs more often that all sequences found so far.
437                // clear all found sequences and use the new count as the one searched for
438                foundSequences.clear();
439                this.currentCount = count;
440            }
441           
442            if (this.currentCount == count) {
443                // the sequence is of interest. Sort it into the other found sequences so that
444                // the longest come first
445                boolean added = false;
446                for (int i = 0; i < foundSequences.size(); i++) {
447                    if (foundSequences.get(i).size() < sequence.size()) {
448                        // defensive copy
449                        foundSequences.add(i, new LinkedList<ITaskTreeNode>(sequence)); // defensive copy
450                        added = true;
451                        break;
452                    }
453                }
454               
455                if (!added) {
456                    foundSequences.add(new LinkedList<ITaskTreeNode>(sequence)); // defensive copy
457                }
458            }
459           
460            return TrieProcessor.Result.CONTINUE;
461        }
462
463        /**
464         * <p>
465         * TODO: comment
466         * </p>
467         *
468         * @return
469         */
470        public Sequences<ITaskTreeNode> getFoundSequences() {
471            removePermutationsOfShorterTasks();
472            return new Sequences<ITaskTreeNode>(currentCount, foundSequences);
473        }
474
475        /**
476         * <p>
477         * TODO: comment
478         * </p>
479         *
480         */
481        private void removePermutationsOfShorterTasks() {
482            // now iterate the sorted list and for each task remove all other tasks that are shorter
483            // (i.e. come later in the sorted list) and that represent a subpart of the task
484            for (int i = 0; i < foundSequences.size(); i++) {
485                for (int j = i + 1; j < foundSequences.size();) {
486                    if (foundSequences.get(j).size() < foundSequences.get(i).size()) {
487                        // found a task that is a potential subtask. Check for this and remove the
488                        // subtask if needed
489                        List<ITaskTreeNode> longTask = foundSequences.get(i);
490                        List<ITaskTreeNode> shortTask = foundSequences.get(j);
491                       
492                        if (getSubListIndex(longTask, shortTask, 0) > -1) {
493                            foundSequences.remove(j);
494                        }
495                        else {
496                            j++;
497                        }
498                    }
499                    else {
500                        j++;
501                    }
502                }
503            }
504        }
505
506    }
507   
508    /**
509     *
510     */
511    private class RuleApplicationData<T> {
512       
513        /**
514         *
515         */
516        private T tree;
517       
518        /**
519         *
520         */
521        private RuleApplicationResult result = new RuleApplicationResult();
522       
523        /**
524         *
525         */
526        private Sequences<T> lastFoundSequences = new Sequences<T>(Integer.MAX_VALUE, null);
527       
528        /**
529         *
530         */
531        private Trie<T> lastTrie;
532       
533        /**
534         * default and minimum trained sequence length is 3
535         */
536        private int trainedSequenceLength = 3;
537       
538        /**
539         *
540         */
541        private int replacementCounter;
542       
543        /**
544         *
545         */
546        private StopWatch stopWatch = new StopWatch();
547       
548        /**
549         *
550         */
551        private RuleApplicationData(T tree) {
552            this.tree = tree;
553        }
554
555        /**
556         * @return the lastFoundSequences
557         */
558        private Sequences<T> getLastFoundSequences() {
559            return lastFoundSequences;
560        }
561
562        /**
563         * @param lastFoundSequences the lastFoundSequences to set
564         */
565        private void setLastFoundSequences(Sequences<T> lastFoundSequences) {
566            this.lastFoundSequences = lastFoundSequences;
567        }
568
569        /**
570         * @return the lastTrie
571         */
572        private Trie<T> getLastTrie() {
573            return lastTrie;
574        }
575
576        /**
577         * @return the trainedSequenceLength
578         */
579        private int getTrainedSequenceLength() {
580            return trainedSequenceLength;
581        }
582
583        /**
584         * @param trainedSequenceLength the trainedSequenceLength to set
585         */
586        private void setTrainedSequenceLength(int trainedSequenceLength) {
587            this.trainedSequenceLength = trainedSequenceLength;
588        }
589
590        /**
591         * @param lastTrie the lastTrie to set
592         */
593        private void setLastTrie(Trie<T> lastTrie) {
594            this.lastTrie = lastTrie;
595        }
596
597        /**
598         * @return the tree
599         */
600        private T getTree() {
601            return tree;
602        }
603
604        /**
605         * @return the result
606         */
607        private RuleApplicationResult getResult() {
608            return result;
609        }
610
611        /**
612         * @return the stopWatch
613         */
614        private StopWatch getStopWatch() {
615            return stopWatch;
616        }
617
618        /**
619         *
620         */
621        private void resetReplacementCounter() {
622            replacementCounter = 0;
623        }
624
625        /**
626         *
627         */
628        private void incrementReplacementCounter() {
629            replacementCounter++;
630        }
631
632        /**
633         *
634         */
635        private int getReplacementCounter() {
636            return replacementCounter;
637        }
638    }
639   
640
641    /**
642     * <p>
643     * TODO comment
644     * </p>
645     *
646     * @author Patrick Harms
647     */
648    private class Sequences<T> implements Iterable<List<T>> {
649       
650        /**
651         *
652         */
653        private int occurrenceCount;
654       
655        /**
656         *
657         */
658        private List<List<T>> sequences;
659
660        /**
661         * <p>
662         * TODO: comment
663         * </p>
664         *
665         * @param occurrenceCount
666         * @param sequences
667         */
668        private Sequences(int occurrenceCount, List<List<T>> sequences) {
669            super();
670            this.occurrenceCount = occurrenceCount;
671            this.sequences = sequences;
672        }
673
674        /**
675         * <p>
676         * TODO: comment
677         * </p>
678         *
679         * @return
680         */
681        private int getOccurrenceCount() {
682            return occurrenceCount;
683        }
684
685        /**
686         * <p>
687         * TODO: comment
688         * </p>
689         *
690         * @return
691         */
692        private int size() {
693            return this.sequences.size();
694        }
695
696        /**
697         *
698         */
699
700        /* (non-Javadoc)
701         * @see java.lang.Iterable#iterator()
702         */
703        @Override
704        public Iterator<List<T>> iterator() {
705            return this.sequences.iterator();
706        }
707
708    }
709
710}
Note: See TracBrowser for help on using the repository browser.