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

Last change on this file since 1133 was 1133, checked in by pharms, 11 years ago
  • removed find bug warnings and TODOs
File size: 25.0 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.IIteration;
24import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection;
25import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
26import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeBuilder;
27import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNode;
28import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNodeFactory;
29import de.ugoe.cs.autoquest.usageprofiles.Trie;
30import de.ugoe.cs.autoquest.usageprofiles.TrieProcessor;
31import de.ugoe.cs.util.StopWatch;
32import de.ugoe.cs.util.console.Console;
33
34/**
35 * <p>
36 * TODO comment
37 * </p>
38 *
39 * @author Patrick Harms
40 */
41class SequenceForTaskDetectionRule implements TemporalRelationshipRule {
42   
43    /**
44     * <p>
45     * the task tree node factory to be used for creating substructures for the temporal
46     * relationships identified during rule
47     * </p>
48     */
49    private ITaskTreeNodeFactory taskTreeNodeFactory;
50    /**
51     * <p>
52     * the task tree builder to be used for creating substructures for the temporal relationships
53     * identified during rule application
54     * </p>
55     */
56    private ITaskTreeBuilder taskTreeBuilder;
57
58    /**
59     * <p>
60     * the node comparator to be used for comparing task tree nodes
61     * </p>
62     */
63    private TaskTreeNodeComparator nodeComparator;
64
65    /**
66     * <p>
67     * instantiates the rule and initializes it with a node equality rule manager and the minimal
68     * node equality identified sublist must have to consider them as iterated.
69     * </p>
70     */
71    SequenceForTaskDetectionRule(NodeEqualityRuleManager nodeEqualityRuleManager,
72                                 NodeEquality            minimalNodeEquality,
73                                 ITaskTreeNodeFactory    taskTreeNodeFactory,
74                                 ITaskTreeBuilder        taskTreeBuilder)
75    {
76        this.taskTreeNodeFactory = taskTreeNodeFactory;
77        this.taskTreeBuilder = taskTreeBuilder;
78       
79        this.nodeComparator =
80            new TaskTreeNodeComparator(nodeEqualityRuleManager, minimalNodeEquality);
81    }
82
83    /* (non-Javadoc)
84     * @see java.lang.Object#toString()
85     */
86    @Override
87    public String toString() {
88        return "SequenceForTaskDetectionRule";
89    }
90
91    /*
92     * (non-Javadoc)
93     *
94     * @see de.ugoe.cs.tasktree.temporalrelation.TemporalRelationshipRule#apply(TaskTreeNode,
95     * boolean)
96     */
97    @Override
98    public RuleApplicationResult apply(ITaskTreeNode parent, boolean finalize) {
99        if (!(parent instanceof ISequence)) {
100            return null;
101        }
102
103        if (!finalize) {
104            // the rule is always feasible as tasks may occur at any time
105            RuleApplicationResult result = new RuleApplicationResult();
106            result.setRuleApplicationStatus(RuleApplicationStatus.FEASIBLE);
107            return result;
108        }
109
110        List<ITaskTreeNode> children = parent.getChildren();
111        List<ISequence> sessions = new LinkedList<ISequence>();
112       
113        for (ITaskTreeNode child : children) {
114            if (child instanceof ISequence) {
115                sessions.add((ISequence) child);
116            }
117            else {
118                Console.println("provided parent is no parent of sessions");
119                return null;
120            }
121        }
122       
123        RuleApplicationData appData = new RuleApplicationData(sessions);
124
125        boolean finished = false;
126       
127        // this is the real rule application. Loop while something is replaced.
128        do {
129            System.out.println();
130           
131            appData.getStopWatch().start("whole loop");
132            detectAndReplaceIterations(appData);
133
134            appData.getStopWatch().start("task replacement");
135            detectAndReplaceTasks(appData);
136            appData.getStopWatch().stop("task replacement");
137            appData.getStopWatch().stop("whole loop");
138           
139            //((TaskTreeNodeComparator) nodeComparator).getStopWatch().dumpStatistics(System.out);
140            //((TaskTreeNodeComparator) nodeComparator).getStopWatch().reset();
141           
142            appData.getStopWatch().dumpStatistics(System.out);
143            appData.getStopWatch().reset();
144           
145            finished = (appData.getReplacementCounter() == 0);
146        }
147        while (!finished);
148       
149        System.out.println("created " + appData.getResult().getNewlyCreatedParentNodes().size() +
150                           " new parent nodes\n");
151       
152        if (appData.getResult().getNewlyCreatedParentNodes().size() > 0) {
153            appData.getResult().setRuleApplicationStatus(RuleApplicationStatus.FINISHED);
154        }
155       
156        return appData.getResult();
157    }
158
159    /**
160     * @param appData
161     */
162    private void detectAndReplaceIterations(RuleApplicationData appData) {
163        System.out.print("detecting iterations");
164        appData.getStopWatch().start("detecting iterations");
165       
166        List<ISequence> sessions = appData.getSessions();
167        int foundIterations = 0;
168       
169        for (ISequence session : sessions) {
170            foundIterations += detectAndReplaceIterations(session, appData);
171        }
172       
173        appData.getStopWatch().stop("detecting iterations");
174        System.out.println(" --> found " + foundIterations);
175    }
176
177    /**
178     * @param appData
179     */
180    private int detectAndReplaceIterations(ISequence           session,
181                                           RuleApplicationData appData)
182    {
183        int count = 0;
184       
185        TemporalRelationshipRule rule = new SimpleIterationDetectionRule
186            (nodeComparator, taskTreeNodeFactory, taskTreeBuilder);
187
188        RuleApplicationResult result = rule.apply(session, true);
189           
190        if ((result != null) && (result.getNewlyCreatedParentNodes() != null)) {
191            for (ITaskTreeNode newParent : result.getNewlyCreatedParentNodes()) {
192                appData.getResult().addNewlyCreatedParentNode(newParent);
193                if (newParent instanceof IIteration) {
194                    count++;
195                }
196            }
197        }
198       
199        return count;
200    }
201
202    /**
203     * @param appData
204     */
205    private void detectAndReplaceTasks(RuleApplicationData appData) {
206        System.out.println("detecting and replacing tasks");
207        appData.getStopWatch().start("detecting tasks");
208       
209        getSequencesOccuringMostOften(appData);
210
211        appData.getStopWatch().stop("detecting tasks");
212        appData.getStopWatch().start("replacing tasks");
213       
214        replaceSequencesOccurringMostOften(appData);
215       
216        appData.getStopWatch().stop("replacing tasks");
217        System.out.println("detected and replaced " + appData.getLastFoundTasks().size() + " tasks");
218    }
219
220    /**
221     * @return
222     */
223    private void getSequencesOccuringMostOften(RuleApplicationData appData) {
224        System.out.println("determining most prominent tasks");
225
226        Tasks tasks;
227        boolean createNewTrie = (appData.getLastTrie() == null) ||
228            appData.getReplacementCounter() > 0; // tree has changed
229       
230        do {
231            if (createNewTrie) {
232                createNewTrie(appData);
233            }
234           
235            MaxCountAndLongestTasksFinder finder = new MaxCountAndLongestTasksFinder();
236            appData.getLastTrie().process(finder);
237       
238            tasks = finder.getFoundTasks();
239           
240            createNewTrie = false;
241           
242            for (List<ITaskTreeNode> task : tasks) {
243                if (task.size() >= appData.getTrainedSequenceLength()) {
244                    // Trie must be recreated with a longer sequence length to be sure that
245                    // the found sequences occurring most often are found in their whole length
246                    appData.setTrainedSequenceLength(appData.getTrainedSequenceLength() + 1);
247                    createNewTrie = true;
248                    break;
249                }
250            }
251        }
252        while (createNewTrie);
253       
254        appData.setLastFoundTasks(tasks);
255
256        System.out.println("found " + appData.getLastFoundTasks().size() + " tasks occurring " +
257                           appData.getLastFoundTasks().getOccurrenceCount() + " times");
258    }
259
260    /**
261     * @param parent
262     * @return
263     */
264    private void createNewTrie(RuleApplicationData appData) {
265        System.out.println("training trie with a maximum sequence length of " +
266                           appData.getTrainedSequenceLength());
267
268        appData.getStopWatch().start("training trie");
269        appData.setLastTrie(new Trie<ITaskTreeNode>(nodeComparator));
270   
271        List<ISequence> sessions = appData.getSessions();
272       
273        for (ISequence session : sessions) {
274            trainTrie(session, appData);
275        }
276       
277        appData.getStopWatch().stop("training trie");
278    }
279
280    /**
281     * @param trie
282     * @param parent
283     */
284    private void trainTrie(ISequence session, RuleApplicationData appData) {
285        List<ITaskTreeNode> children = session.getChildren();
286       
287        if ((children != null) && (children.size() > 0)) {
288            appData.getLastTrie().train(children, appData.getTrainedSequenceLength());
289        }
290    }
291
292    /**
293     * @param appData
294     */
295    private void replaceSequencesOccurringMostOften(RuleApplicationData appData) {
296        appData.resetReplacementCounter();
297
298        if ((appData.getLastFoundTasks().size() > 0) &&
299            (appData.getLastFoundTasks().getOccurrenceCount() > 1))
300        {
301            System.out.println("replacing tasks occurrences with merged variants of all versions");
302
303            for (List<ITaskTreeNode> task : appData.getLastFoundTasks()) {
304                String taskId = "task " + RuleUtils.getNewId();
305                System.out.println("replacing " + taskId + ": " + task);
306
307                appData.clearTaskOccurrences();
308                determineVariantsOfTaskOccurrences(task, appData.getSessions(), appData);
309               
310                appData.getStopWatch().start("merging task nodes");
311                ITaskTreeNode taskReplacement = mergeVariantsOfTaskOccurrence(taskId, appData);
312                appData.getStopWatch().stop("merging task nodes");
313
314                appData.resetReplacementCounter();
315                replaceTaskOccurrences(task, taskReplacement, appData.getSessions(), appData);
316
317                if (appData.getReplacementCounter() > 0) {
318                    appData.getResult().addNewlyCreatedParentNode(taskReplacement);
319                }
320
321                if (appData.getReplacementCounter() <
322                    appData.getLastFoundTasks().getOccurrenceCount())
323                {
324                    System.out.println(taskId + ": replaced task only " +
325                                       appData.getReplacementCounter() +
326                                       " times instead of expected " +
327                                       appData.getLastFoundTasks().getOccurrenceCount());
328                }
329            }
330        }
331       
332    }
333
334    /**
335     * @param tree
336     */
337    private void determineVariantsOfTaskOccurrences(List<ITaskTreeNode> task,
338                                                    List<ISequence>     sessions,
339                                                    RuleApplicationData appData)
340    {
341        for (ISequence session : sessions) {
342            int index = -1;
343               
344            List<ITaskTreeNode> children = session.getChildren();
345
346            do {
347                index = getSubListIndex(children, task, ++index);
348
349                if (index > -1) {
350                    ISequence taskOccurrence = RuleUtils.getSubSequenceInRange
351                            (session, index, index + task.size() - 1, null,
352                             taskTreeNodeFactory, taskTreeBuilder);
353
354                    appData.addTaskOccurrence(taskOccurrence);
355
356                    // let the index point to the last element the belongs the identified occurrence
357                    index += task.size() - 1;
358                }
359            }
360            while (index > -1);
361        }
362    }
363
364    /**
365     * @param appData
366     * @return
367     */
368    private ITaskTreeNode mergeVariantsOfTaskOccurrence(String              taskId,
369                                                        RuleApplicationData appData)
370    {
371        return mergeVariantsOfTasks(taskId, appData.getTaskOccurrences());
372    }
373
374    /**
375     * @param appData
376     * @return
377     */
378    private ITaskTreeNode mergeVariantsOfTasks(String description, List<ITaskTreeNode> variants) {
379        // merge but preserve lexically distinct variants
380        TaskTreeNodeMerger merger = new TaskTreeNodeMerger
381            (taskTreeNodeFactory, taskTreeBuilder, nodeComparator);
382       
383        merger.mergeTaskNodes(variants);
384       
385        if (variants.size() == 1) {
386            ITaskTreeNode replacement = variants.get(0);
387            taskTreeBuilder.setDescription(replacement, description);
388            return replacement;
389        }
390        else {
391            ISelection selection = taskTreeNodeFactory.createNewSelection();
392            taskTreeBuilder.setDescription(selection, "variants of task " + description);
393           
394            for (ITaskTreeNode variant : variants) {
395                taskTreeBuilder.addChild(selection, variant);
396            }
397           
398            return selection;
399        }
400    }
401
402    /**
403     * @param task
404     * @param parent
405     * @param treeBuilder
406     * @param nodeFactory
407     * @param result
408     */
409    private void replaceTaskOccurrences(List<ITaskTreeNode> task,
410                                        ITaskTreeNode       replacement,
411                                        List<ISequence>     sessions,
412                                        RuleApplicationData appData)
413    {
414        // now check the children themselves for an occurrence of the task
415        for (int i = 0; i < sessions.size(); i++) {
416            ISequence session = sessions.get(i);
417           
418            int index = -1;
419       
420            List<ITaskTreeNode> children = session.getChildren();
421
422            do {
423                index = getSubListIndex(children, task, ++index);
424
425                if (index > -1) {
426                    if ((!(replacement instanceof ISequence)) ||
427                        (task.size() < children.size()))
428                    {
429                        for (int j = index; j < index + task.size(); j++) {
430                            taskTreeBuilder.removeChild(session, index);
431                        }
432
433                        taskTreeBuilder.addChild(session, index, replacement);
434                        appData.incrementReplacementCounter();
435
436                        children = session.getChildren();
437                    }
438                    else {
439                        // the whole list of children is an occurrence of this task. So ask the
440                        // caller of the method to replace the whole node
441                        sessions.set(i, (ISequence) replacement);
442                        appData.incrementReplacementCounter();
443                        break;
444                    }
445                }
446            }
447            while (index > -1);
448        }
449    }
450
451    /**
452     * @param trie
453     * @param object
454     * @return
455     */
456    private int getSubListIndex(List<ITaskTreeNode> list,
457                                List<ITaskTreeNode> subList,
458                                int                 startIndex)
459    {
460        boolean matchFound;
461        int result = -1;
462       
463        for (int i = startIndex; i <= list.size() - subList.size(); i++) {
464            matchFound = true;
465           
466            for (int j = 0; j < subList.size(); j++) {
467                if (!nodeComparator.equals(list.get(i + j), subList.get(j))) {
468                    matchFound = false;
469                    break;
470                }
471            }
472           
473            if (matchFound) {
474                result = i;
475                break;
476            }
477        }
478       
479        return result;
480    }
481   
482    /**
483     * @author Patrick Harms
484     */
485    private class MaxCountAndLongestTasksFinder implements TrieProcessor<ITaskTreeNode> {
486       
487        /**
488         *
489         */
490        private int currentCount;
491       
492        /**
493         *
494         */
495        private List<List<ITaskTreeNode>> foundTasks = new LinkedList<List<ITaskTreeNode>>();
496
497        /**
498         *
499         */
500        public MaxCountAndLongestTasksFinder() {
501            super();
502            this.currentCount = 0;
503        }
504
505        /* (non-Javadoc)
506         * @see de.ugoe.cs.autoquest.usageprofiles.TrieProcessor#process(java.util.List, int)
507         */
508        @Override
509        public TrieProcessor.Result process(List<ITaskTreeNode> task, int count) {
510            if (task.size() < 2) {
511                // ignore single nodes
512                return TrieProcessor.Result.CONTINUE;
513            }
514           
515            if (count < 2) {
516                // ignore singular occurrences
517                return TrieProcessor.Result.SKIP_NODE;
518            }
519
520            if (this.currentCount > count) {
521                // ignore this children of this node, as they may have only smaller counts than
522                // the already found tasks
523                return TrieProcessor.Result.SKIP_NODE;
524            }
525           
526            if (this.currentCount < count) {
527                // the provided task occurs more often that all tasks found so far.
528                // clear all found tasks and use the new count as the one searched for
529                foundTasks.clear();
530                this.currentCount = count;
531            }
532           
533            if (this.currentCount == count) {
534                // the task is of interest. Sort it into the other found tasks so that
535                // the longest come first
536                boolean added = false;
537                for (int i = 0; i < foundTasks.size(); i++) {
538                    if (foundTasks.get(i).size() < task.size()) {
539                        // defensive copy
540                        foundTasks.add(i, new LinkedList<ITaskTreeNode>(task)); // defensive copy
541                        added = true;
542                        break;
543                    }
544                }
545               
546                if (!added) {
547                    foundTasks.add(new LinkedList<ITaskTreeNode>(task)); // defensive copy
548                }
549            }
550           
551            return TrieProcessor.Result.CONTINUE;
552        }
553
554        /**
555         *  @return
556         */
557        public Tasks getFoundTasks() {
558            removePermutationsOfShorterTasks();
559            return new Tasks(currentCount, foundTasks);
560        }
561
562        /**
563         *
564         */
565        private void removePermutationsOfShorterTasks() {
566            // now iterate the sorted list and for each task remove all other tasks that are shorter
567            // (i.e. come later in the sorted list) and that represent a subpart of the task
568            for (int i = 0; i < foundTasks.size(); i++) {
569                for (int j = i + 1; j < foundTasks.size();) {
570                    if (foundTasks.get(j).size() < foundTasks.get(i).size()) {
571                        // found a task that is a potential subtask. Check for this and remove the
572                        // subtask if needed
573                        List<ITaskTreeNode> longTask = foundTasks.get(i);
574                        List<ITaskTreeNode> shortTask = foundTasks.get(j);
575                       
576                        if (getSubListIndex(longTask, shortTask, 0) > -1) {
577                            foundTasks.remove(j);
578                        }
579                        else {
580                            j++;
581                        }
582                    }
583                    else {
584                        j++;
585                    }
586                }
587            }
588        }
589
590    }
591   
592    /**
593     *
594     */
595    private static class RuleApplicationData {
596       
597        /**
598         *
599         */
600        private List<ISequence> sessions;
601       
602        /**
603         *
604         */
605        private Trie<ITaskTreeNode> lastTrie;
606       
607        /**
608         * default and minimum trained sequence length is 3
609         */
610        private int trainedSequenceLength = 3;
611       
612        /**
613         *
614         */
615        private Tasks lastFoundTasks = new Tasks(Integer.MAX_VALUE, null);
616       
617        /**
618         *
619         */
620        private List<ITaskTreeNode> taskOccurrences = new LinkedList<ITaskTreeNode>();
621       
622        /**
623         *
624         */
625        private int replacementCounter;
626       
627        /**
628         *
629         */
630        private RuleApplicationResult result = new RuleApplicationResult();
631       
632        /**
633         *
634         */
635        private StopWatch stopWatch = new StopWatch();
636       
637        /**
638         *
639         */
640        private RuleApplicationData(List<ISequence> sessions) {
641            this.sessions = sessions;
642        }
643
644        /**
645         * @return the tree
646         */
647        private List<ISequence> getSessions() {
648            return sessions;
649        }
650
651        /**
652         * @param lastTrie the lastTrie to set
653         */
654        private void setLastTrie(Trie<ITaskTreeNode> lastTrie) {
655            this.lastTrie = lastTrie;
656        }
657
658        /**
659         * @return the lastTrie
660         */
661        private Trie<ITaskTreeNode> getLastTrie() {
662            return lastTrie;
663        }
664
665        /**
666         * @param trainedSequenceLength the trainedSequenceLength to set
667         */
668        private void setTrainedSequenceLength(int trainedSequenceLength) {
669            this.trainedSequenceLength = trainedSequenceLength;
670        }
671
672        /**
673         * @return the trainedSequenceLength
674         */
675        private int getTrainedSequenceLength() {
676            return trainedSequenceLength;
677        }
678
679        /**
680         * @param lastFoundSequences the lastFoundSequences to set
681         */
682        private void setLastFoundTasks(Tasks lastFoundSequences) {
683            this.lastFoundTasks = lastFoundSequences;
684        }
685
686        /**
687         * @return the lastFoundSequences
688         */
689        private Tasks getLastFoundTasks() {
690            return lastFoundTasks;
691        }
692
693        /**
694         * @return the taskOccurrences
695         */
696        private void clearTaskOccurrences() {
697            taskOccurrences.clear();
698        }
699
700        /**
701         * @return the taskOccurrences
702         */
703        private void addTaskOccurrence(ITaskTreeNode taskOccurrence) {
704            taskOccurrences.add(taskOccurrence);
705        }
706
707        /**
708         * @return the taskOccurrences
709         */
710        private List<ITaskTreeNode> getTaskOccurrences() {
711            return taskOccurrences;
712        }
713
714        /**
715         *
716         */
717        private void resetReplacementCounter() {
718            replacementCounter = 0;
719        }
720
721        /**
722         *
723         */
724        private void incrementReplacementCounter() {
725            replacementCounter++;
726        }
727
728        /**
729         *
730         */
731        private int getReplacementCounter() {
732            return replacementCounter;
733        }
734       
735        /**
736         * @return the result
737         */
738        private RuleApplicationResult getResult() {
739            return result;
740        }
741
742        /**
743         * @return the stopWatch
744         */
745        private StopWatch getStopWatch() {
746            return stopWatch;
747        }
748
749    }
750   
751
752    /**
753     * @author Patrick Harms
754     */
755    private static class Tasks implements Iterable<List<ITaskTreeNode>> {
756       
757        /**
758         *
759         */
760        private int occurrenceCount;
761       
762        /**
763         *
764         */
765        private List<List<ITaskTreeNode>> sequences;
766
767        /**
768         * @param occurrenceCount
769         * @param sequences
770         */
771        private Tasks(int occurrenceCount, List<List<ITaskTreeNode>> sequences) {
772            super();
773            this.occurrenceCount = occurrenceCount;
774            this.sequences = sequences;
775        }
776
777        /**
778         * @return
779         */
780        private int getOccurrenceCount() {
781            return occurrenceCount;
782        }
783
784        /**
785         * @return
786         */
787        private int size() {
788            return this.sequences.size();
789        }
790
791        /**
792         *
793         */
794
795        /* (non-Javadoc)
796         * @see java.lang.Iterable#iterator()
797         */
798        @Override
799        public Iterator<List<ITaskTreeNode>> iterator() {
800            return this.sequences.iterator();
801        }
802
803    }
804
805}
Note: See TracBrowser for help on using the repository browser.