1 | package de.uka.ipd.sdq.reliability.solver.compare; |
2 | |
3 | import java.io.File; |
4 | import java.text.DecimalFormat; |
5 | import java.util.ArrayList; |
6 | import java.util.List; |
7 | |
8 | import org.apache.log4j.Logger; |
9 | import org.eclipse.emf.common.util.EList; |
10 | import org.eclipse.emf.ecore.EObject; |
11 | |
12 | import de.uka.ipd.sdq.markov.Label; |
13 | import de.uka.ipd.sdq.markov.MarkovChain; |
14 | import de.uka.ipd.sdq.markov.State; |
15 | import de.uka.ipd.sdq.markov.Transition; |
16 | import de.uka.ipd.sdq.reliability.core.helper.EMFHelper; |
17 | |
18 | /** |
19 | * Provides functionality to compare two Markov models. |
20 | * |
21 | * The comparison is based on the tracing information which uniquely identifies |
22 | * each Markov state. |
23 | * |
24 | * @author brosch |
25 | * |
26 | */ |
27 | public class MarkovComparator { |
28 | |
29 | /** |
30 | * A logger for printing the comparison result. |
31 | */ |
32 | private static Logger logger = Logger.getLogger(MarkovComparator.class |
33 | .getName()); |
34 | |
35 | /** |
36 | * Counts the overall number of changes in chains #1 and #2. |
37 | */ |
38 | private long changeCount = 0; |
39 | |
40 | /** |
41 | * Counts the number of deleted states in chain #1. |
42 | */ |
43 | private int deletedStateCount = 0; |
44 | |
45 | /** |
46 | * Counts the number of deleted transitions in chain #1. |
47 | */ |
48 | private int deletedTransitionCount = 0; |
49 | |
50 | /** |
51 | * The name of the first Markov chain. |
52 | */ |
53 | private String firstName = ""; |
54 | |
55 | /** |
56 | * Counts the number of newly introduced states in chain #2. |
57 | */ |
58 | private int newStateCount = 0; |
59 | |
60 | /** |
61 | * Counts the number of newly introduced transitions in chain #2. |
62 | */ |
63 | private int newTransitionCount = 0; |
64 | |
65 | /** |
66 | * The name of the second Markov chain. |
67 | */ |
68 | private String secondName = ""; |
69 | |
70 | /** |
71 | * Counts the overall number of state changes in chains #1 and #2. |
72 | */ |
73 | private long stateChangeCount = 0; |
74 | |
75 | /** |
76 | * Counts the number of states with changed properties in chains #1 and #2. |
77 | */ |
78 | private int statePropertyChangeCount = 0; |
79 | |
80 | /** |
81 | * Counts the overall number of transition changes in chains #1 and #2. |
82 | */ |
83 | private long transitionChangeCount = 0; |
84 | |
85 | /** |
86 | * Counts the number of transitions with changed properties in chains #1 and |
87 | * #2. |
88 | */ |
89 | private int transitionPropertyChangeCount = 0; |
90 | |
91 | /** |
92 | * Compares two Markov chains. |
93 | * |
94 | * @param firstChain |
95 | * the first chain |
96 | * @param secondChain |
97 | * the second chain |
98 | */ |
99 | public void compare(final MarkovChain firstChain, |
100 | final MarkovChain secondChain) { |
101 | logger.info("Start Markov compare"); |
102 | if (firstName == "") { |
103 | firstName = firstChain.getName(); |
104 | } |
105 | if (secondName == "") { |
106 | secondName = secondChain.getName(); |
107 | } |
108 | compareProperties(firstChain, secondChain); |
109 | compareStates(firstChain.getStates(), secondChain.getStates()); |
110 | compareTransitions(firstChain.getTransitions(), secondChain |
111 | .getTransitions()); |
112 | printCompareStatistics(firstChain, secondChain); |
113 | logger.info("End Markov compare"); |
114 | } |
115 | |
116 | /** |
117 | * Loads and compares the Markov models identified by the two given file |
118 | * names. |
119 | * |
120 | * The files must exist and contain valid Markov models. |
121 | * |
122 | * @param firstMarkovFileName |
123 | * the file name of the first Markov model |
124 | * @param secondMarkovFileName |
125 | * the file name of the second Markov model |
126 | */ |
127 | public void compare(final String firstMarkovFileName, |
128 | final String secondMarkovFileName) { |
129 | firstName = new File(firstMarkovFileName).getName(); |
130 | secondName = new File(secondMarkovFileName).getName(); |
131 | EObject ch1 = EMFHelper.loadFromXMIFile(firstMarkovFileName); |
132 | EObject ch2 = EMFHelper.loadFromXMIFile(secondMarkovFileName); |
133 | if ((ch1 == null) || !(ch1 instanceof MarkovChain) || (ch2 == null) |
134 | || !(ch2 instanceof MarkovChain)) { |
135 | return; |
136 | } |
137 | compare((MarkovChain) ch1, (MarkovChain) ch2); |
138 | } |
139 | |
140 | /** |
141 | * Compares the general properties of two Markov chains. |
142 | * |
143 | * @param ch1 |
144 | * the first chain |
145 | * @param ch2 |
146 | * the second chain |
147 | */ |
148 | private void compareProperties(final MarkovChain ch1, final MarkovChain ch2) { |
149 | if (!ch1.getName().equals(ch2.getName())) { |
150 | logger.debug("Chain name changed from \"" + ch1.getName() |
151 | + "\" to \"" + ch2.getName() + "\""); |
152 | } |
153 | } |
154 | |
155 | /** |
156 | * Compares the properties of two Markov states. |
157 | * |
158 | * The states are assumed to have the same identity. |
159 | * |
160 | * @param st1 |
161 | * the first state |
162 | * @param st2 |
163 | * the second state |
164 | */ |
165 | private void compareProperties(final State st1, final State st2) { |
166 | |
167 | boolean typeEquals = st1.getType().equals(st2.getType()); |
168 | boolean nameEquals = st1.getName().equals(st2.getName()); |
169 | boolean labelsEqual = testEquality(st1.getLabels(), st2.getLabels()); |
170 | |
171 | if (!typeEquals || !nameEquals || !labelsEqual) { |
172 | logger.debug("State \"" + st1.getName() + "\" changed:"); |
173 | increaseStatePropertyChangeCount(); |
174 | if (!nameEquals) { |
175 | logger.debug("- mame changed from \"" + st1.getName() |
176 | + "\" to \"" + st2.getName() + "\""); |
177 | } |
178 | if (!typeEquals) { |
179 | logger.debug("- type changed from \"" |
180 | + st1.getType().toString() + "\" to \"" |
181 | + st2.getType().toString() + "\""); |
182 | } |
183 | if (!labelsEqual) { |
184 | logger.debug("- labels changed"); |
185 | } |
186 | } |
187 | } |
188 | |
189 | /** |
190 | * Compares the transitions of two Markov chains. |
191 | * |
192 | * @param tr1 |
193 | * the transitions of the first chain |
194 | * @param tr2 |
195 | * the transitions of the second chain |
196 | */ |
197 | private void compareProperties(final Transition tr1, final Transition tr2) { |
198 | |
199 | boolean nameEquals = tr1.getName().equals(tr2.getName()); |
200 | boolean probabilityEquals = (tr1.getProbability() == tr2 |
201 | .getProbability()); |
202 | |
203 | if (!nameEquals || !probabilityEquals) { |
204 | logger.debug("Transition \"" + tr1.getName() + "\" changed:"); |
205 | increaseTransitionPropertyChangeCount(); |
206 | if (!nameEquals) { |
207 | logger.debug("- mame changed from \"" + tr1.getName() |
208 | + "\" to \"" + tr2.getName() + "\""); |
209 | } |
210 | if (!probabilityEquals) { |
211 | logger.debug("- probability changed from " |
212 | + tr1.getProbability() + " to " + tr2.getProbability()); |
213 | } |
214 | } |
215 | } |
216 | |
217 | /** |
218 | * Compares the states of two Markov chains. |
219 | * |
220 | * @param states1 |
221 | * the states of the first chain |
222 | * @param states2 |
223 | * the states of the second chain |
224 | */ |
225 | private void compareStates(final EList<State> states1, |
226 | final EList<State> states2) { |
227 | |
228 | // Create private copies of the lists: |
229 | List<State> statesCopy1 = new ArrayList<State>(); |
230 | statesCopy1.addAll(states1); |
231 | List<State> statesCopy2 = new ArrayList<State>(); |
232 | statesCopy2.addAll(states2); |
233 | |
234 | // Iterate over the states of the first chain: |
235 | while (statesCopy1.size() > 0) { |
236 | |
237 | // Retrieve the first state: |
238 | State st1 = statesCopy1.get(0); |
239 | |
240 | // Search for an identical state in the second chain: |
241 | State st2 = null; |
242 | for (State st2_candidate : statesCopy2) { |
243 | if (testIdentity(st1, st2_candidate)) { |
244 | st2 = st2_candidate; |
245 | break; |
246 | } |
247 | } |
248 | |
249 | // Check if an identical state exists: |
250 | if (st2 != null) { |
251 | |
252 | // check for changed state properties: |
253 | compareProperties(st1, st2); |
254 | |
255 | // delete the states: |
256 | statesCopy1.remove(st1); |
257 | statesCopy2.remove(st2); |
258 | |
259 | } else { |
260 | |
261 | // st1 does not longer exist: |
262 | logger.debug("State \"" + st1.getName() + "\" deleted"); |
263 | increaseDeletedStateCount(); |
264 | |
265 | // delete the state: |
266 | statesCopy1.remove(st1); |
267 | } |
268 | } |
269 | |
270 | // Iterate over the states of the second chain: |
271 | while (statesCopy2.size() > 0) { |
272 | |
273 | // Retrieve the first state: |
274 | State st2 = statesCopy2.get(0); |
275 | |
276 | // st2 is a new state: |
277 | logger.debug("State \"" + st2.getName() + "\" added"); |
278 | increaseNewStateCount(); |
279 | |
280 | // delete the state: |
281 | statesCopy2.remove(st2); |
282 | } |
283 | } |
284 | |
285 | /** |
286 | * Compares the transitions of two Markov chains. |
287 | * |
288 | * @param transitions1 |
289 | * the transitions of the first chain |
290 | * @param transitions2 |
291 | * the transitions of the second chain |
292 | */ |
293 | private void compareTransitions(final EList<Transition> transitions1, |
294 | final EList<Transition> transitions2) { |
295 | |
296 | // Create private copies of the lists: |
297 | List<Transition> transitionsCopy1 = new ArrayList<Transition>(); |
298 | transitionsCopy1.addAll(transitions1); |
299 | List<Transition> transitionsCopy2 = new ArrayList<Transition>(); |
300 | transitionsCopy2.addAll(transitions2); |
301 | |
302 | // Iterate over the transitions of the first chain: |
303 | while (transitionsCopy1.size() > 0) { |
304 | |
305 | // Retrieve the first transition: |
306 | Transition tr1 = transitionsCopy1.get(0); |
307 | |
308 | // Search for an identical transition in the second chain: |
309 | Transition tr2 = null; |
310 | for (Transition tr2_candidate : transitionsCopy2) { |
311 | if (testIdentity(tr1, tr2_candidate)) { |
312 | tr2 = tr2_candidate; |
313 | break; |
314 | } |
315 | } |
316 | |
317 | // Check if an identical transition exists: |
318 | if (tr2 != null) { |
319 | |
320 | // check for changed state properties: |
321 | compareProperties(tr1, tr2); |
322 | |
323 | // delete the states: |
324 | transitionsCopy1.remove(tr1); |
325 | transitionsCopy2.remove(tr2); |
326 | |
327 | } else { |
328 | |
329 | // tr1 does not longer exist: |
330 | logger.debug("Transition \"" + tr1.getName() + "\" deleted"); |
331 | increaseDeletedTransitionCount(); |
332 | |
333 | // delete the state: |
334 | transitionsCopy1.remove(tr1); |
335 | } |
336 | } |
337 | |
338 | // Iterate over the transitions of the second chain: |
339 | while (transitionsCopy2.size() > 0) { |
340 | |
341 | // Retrieve the first transition: |
342 | Transition tr2 = transitionsCopy2.get(0); |
343 | |
344 | // tr2 is a new transition: |
345 | logger.debug("Transition \"" + tr2.getName() + "\" added"); |
346 | increaseNewTransitionCount(); |
347 | |
348 | // delete the state: |
349 | transitionsCopy2.remove(tr2); |
350 | } |
351 | } |
352 | |
353 | /** |
354 | * Creates a decimal format based on the given numbers. |
355 | * |
356 | * @param numbers |
357 | * the list of numbers |
358 | * @return the resulting decimal format |
359 | */ |
360 | private DecimalFormat getDecimalFormat(final ArrayList<Integer> numbers) { |
361 | |
362 | // Search for the maximal number of decimal places: |
363 | int maxPlaces = 0; |
364 | for (Integer number : numbers) { |
365 | int places = number.toString().length(); |
366 | if (places > maxPlaces) { |
367 | maxPlaces = places; |
368 | } |
369 | } |
370 | |
371 | // Generate a format string corresponding to the places: |
372 | String formatString = ""; |
373 | for (int i = 0; i < maxPlaces; i++) { |
374 | formatString += "0"; |
375 | } |
376 | return new DecimalFormat(formatString); |
377 | } |
378 | |
379 | /** |
380 | * Increases the deleted state count. |
381 | */ |
382 | private void increaseDeletedStateCount() { |
383 | changeCount++; |
384 | stateChangeCount++; |
385 | deletedStateCount++; |
386 | } |
387 | |
388 | /** |
389 | * Increases the deleted transition count. |
390 | */ |
391 | private void increaseDeletedTransitionCount() { |
392 | changeCount++; |
393 | transitionChangeCount++; |
394 | deletedTransitionCount++; |
395 | } |
396 | |
397 | /** |
398 | * Increases the new state count. |
399 | */ |
400 | private void increaseNewStateCount() { |
401 | changeCount++; |
402 | stateChangeCount++; |
403 | newStateCount++; |
404 | } |
405 | |
406 | /** |
407 | * Increases the new transition count. |
408 | */ |
409 | private void increaseNewTransitionCount() { |
410 | changeCount++; |
411 | transitionChangeCount++; |
412 | newTransitionCount++; |
413 | } |
414 | |
415 | /** |
416 | * Increases the changed state property count. |
417 | */ |
418 | private void increaseStatePropertyChangeCount() { |
419 | changeCount++; |
420 | stateChangeCount++; |
421 | statePropertyChangeCount++; |
422 | } |
423 | |
424 | /** |
425 | * Increases the deleted transition count. |
426 | */ |
427 | private void increaseTransitionPropertyChangeCount() { |
428 | changeCount++; |
429 | transitionChangeCount++; |
430 | transitionPropertyChangeCount++; |
431 | } |
432 | |
433 | /** |
434 | * Prints the statistics of the compare operation. |
435 | * |
436 | * @param secondChain |
437 | * the first Markov chain |
438 | * @param firstChain |
439 | * the second Markov chain |
440 | */ |
441 | private void printCompareStatistics(final MarkovChain firstChain, |
442 | final MarkovChain secondChain) { |
443 | |
444 | // Assume that the chains are not so big that Integer.MAX_VALUE is |
445 | // reached: |
446 | int numberOfStates1 = firstChain.getStates().size(); |
447 | int numberOfStates2 = secondChain.getStates().size(); |
448 | int numberOfTransitions1 = firstChain.getTransitions().size(); |
449 | int numberOfTransitions2 = secondChain.getTransitions().size(); |
450 | |
451 | // Collect state statistics: |
452 | int numberOfChangedStates1 = statePropertyChangeCount; |
453 | int numberOfDeletedStates1 = deletedStateCount; |
454 | int numberOfUnchangedStates1 = numberOfStates1 - numberOfChangedStates1 |
455 | - numberOfDeletedStates1; |
456 | int numberOfChangedStates2 = statePropertyChangeCount; |
457 | int numberOfAddedStates2 = newStateCount; |
458 | int numberOfUnchangedStates2 = numberOfStates2 - numberOfChangedStates2 |
459 | - numberOfAddedStates2; |
460 | |
461 | // Collect transition statistics: |
462 | int numberOfChangedTransitions1 = transitionPropertyChangeCount; |
463 | int numberOfDeletedTransitions1 = deletedTransitionCount; |
464 | int numberOfUnchangedTransitions1 = numberOfTransitions1 |
465 | - numberOfChangedTransitions1 - numberOfDeletedTransitions1; |
466 | int numberOfChangedTransitions2 = transitionPropertyChangeCount; |
467 | int numberOfAddedTransitions2 = newTransitionCount; |
468 | int numberOfUnchangedTransitions2 = numberOfTransitions2 |
469 | - numberOfChangedTransitions2 - numberOfAddedTransitions2; |
470 | |
471 | // Collect percentages: |
472 | double percentageOfChangedStates1 = 100.0 * ((double) numberOfChangedStates1 / (double) numberOfStates1); |
473 | double percentageOfDeletedStates1 = 100.0 * ((double) numberOfDeletedStates1 / (double) numberOfStates1); |
474 | double percentageOfUnchangedStates1 = 100.0 * ((double) numberOfUnchangedStates1 / (double) numberOfStates1); |
475 | double percentageOfChangedTransitions1 = 100.0 * ((double) numberOfChangedTransitions1 / (double) numberOfTransitions1); |
476 | double percentageOfDeletedTransitions1 = 100.0 * ((double) numberOfDeletedTransitions1 / (double) numberOfTransitions1); |
477 | double percentageOfUnchangedTransitions1 = 100.0 * ((double) numberOfUnchangedTransitions1 / (double) numberOfTransitions1); |
478 | double percentageOfChangedStates2 = 100.0 * ((double) numberOfChangedStates2 / (double) numberOfStates2); |
479 | double percentageOfAddedStates2 = 100.0 * ((double) numberOfAddedStates2 / (double) numberOfStates2); |
480 | double percentageOfUnchangedStates2 = 100.0 * ((double) numberOfUnchangedStates2 / (double) numberOfStates2); |
481 | double percentageOfChangedTransitions2 = 100.0 * ((double) numberOfChangedTransitions2 / (double) numberOfTransitions2); |
482 | double percentageOfAddedTransitions2 = 100.0 * ((double) numberOfAddedTransitions2 / (double) numberOfTransitions2); |
483 | double percentageOfUnchangedTransitions2 = 100.0 * ((double) numberOfUnchangedTransitions2 / (double) numberOfTransitions2); |
484 | |
485 | // Derive string formats: |
486 | ArrayList<Integer> numbers = new ArrayList<Integer>(); |
487 | numbers.add(numberOfStates1); |
488 | numbers.add(numberOfStates2); |
489 | numbers.add(numberOfTransitions1); |
490 | numbers.add(numberOfTransitions2); |
491 | DecimalFormat df1 = getDecimalFormat(numbers); |
492 | DecimalFormat df2 = new DecimalFormat("00.00"); |
493 | |
494 | // Print statistics: |
495 | logger.info("States chain 1 [" + firstName + "]:"); |
496 | logger.info("- total: " + df1.format(numberOfStates1)); |
497 | logger.info("- changed: " + df1.format(numberOfChangedStates1) + " (" |
498 | + df2.format(percentageOfChangedStates1) + "%)"); |
499 | logger.info("- deleted: " + df1.format(numberOfDeletedStates1) + " (" |
500 | + df2.format(percentageOfDeletedStates1) + "%)"); |
501 | logger.info("- unchanged: " + df1.format(numberOfUnchangedStates1) |
502 | + " (" + df2.format(percentageOfUnchangedStates1) + "%)"); |
503 | logger.info("Transitions chain 1 [" + firstName + "]:"); |
504 | logger.info("- total: " + df1.format(numberOfTransitions1)); |
505 | logger.info("- changed: " + df1.format(numberOfChangedTransitions1) |
506 | + " (" + df2.format(percentageOfChangedTransitions1) + "%)"); |
507 | logger.info("- deleted: " + df1.format(numberOfDeletedTransitions1) |
508 | + " (" + df2.format(percentageOfDeletedTransitions1) + "%)"); |
509 | logger.info("- unchanged: " + df1.format(numberOfUnchangedTransitions1) |
510 | + " (" + df2.format(percentageOfUnchangedTransitions1) + "%)"); |
511 | logger.info("States chain 2 [" + secondName + "]:"); |
512 | logger.info("- total: " + df1.format(numberOfStates2)); |
513 | logger.info("- changed: " + df1.format(numberOfChangedStates2) + " (" |
514 | + df2.format(percentageOfChangedStates2) + "%)"); |
515 | logger.info("- added: " + df1.format(numberOfAddedStates2) + " (" |
516 | + df2.format(percentageOfAddedStates2) + "%)"); |
517 | logger.info("- unchanged: " + df1.format(numberOfUnchangedStates2) |
518 | + " (" + df2.format(percentageOfUnchangedStates2) + "%)"); |
519 | logger.info("Transitions chain 2 [" + secondName + "]:"); |
520 | logger.info("- total: " + df1.format(numberOfTransitions2)); |
521 | logger.info("- changed: " + df1.format(numberOfChangedTransitions2) |
522 | + " (" + df2.format(percentageOfChangedTransitions2) + "%)"); |
523 | logger.info("- added: " + df1.format(numberOfAddedTransitions2) |
524 | + " (" + df2.format(percentageOfAddedTransitions2) + "%)"); |
525 | logger.info("- unchanged: " + df1.format(numberOfUnchangedTransitions2) |
526 | + " (" + df2.format(percentageOfUnchangedTransitions2) + "%)"); |
527 | } |
528 | |
529 | /** |
530 | * Searches a list of labels for a given key. |
531 | * |
532 | * @param labels |
533 | * the list of labels |
534 | * @param key |
535 | * the key |
536 | * @return the label; NULL if no corresponding label has been found. |
537 | */ |
538 | private Label searchLabelsForKey(final EList<Label> labels, final String key) { |
539 | for (Label label : labels) { |
540 | if (label.getKey().equals(key)) { |
541 | return label; |
542 | } |
543 | } |
544 | return null; |
545 | } |
546 | |
547 | /** |
548 | * Checks if the labels of two Markov states are equal. |
549 | * |
550 | * It is assumed that each key appears only once in each list of labels |
551 | * |
552 | * @param labels1 |
553 | * the labels of the first state |
554 | * @param labels2 |
555 | * the labels of the second state |
556 | * @return |
557 | */ |
558 | private boolean testEquality(final EList<Label> labels1, |
559 | final EList<Label> labels2) { |
560 | if (labels1.size() != labels2.size()) { |
561 | return false; |
562 | } |
563 | for (int i = 0; i < labels1.size(); i++) { |
564 | Label label1 = labels1.get(i); |
565 | Label label1_2 = searchLabelsForKey(labels2, label1.getKey()); |
566 | Label label2 = labels2.get(i); |
567 | Label label2_1 = searchLabelsForKey(labels1, label2.getKey()); |
568 | if ((label1_2 == null) || (label2_1 == null)) { |
569 | return false; |
570 | } |
571 | if (!label1.getValue().equals(label1_2.getValue()) |
572 | || !label2.getValue().equals(label2_1.getValue())) { |
573 | return false; |
574 | } |
575 | } |
576 | return true; |
577 | } |
578 | |
579 | /** |
580 | * Checks two Markov States for having the same identity. |
581 | * |
582 | * The identity is checked through the trace information. |
583 | * |
584 | * @param st1 |
585 | * the first state |
586 | * @param st2 |
587 | * the second state |
588 | * @return true, if the states are identical; false otherwise |
589 | */ |
590 | private boolean testIdentity(final State st1, final State st2) { |
591 | if (st1.getTraces().size() != st2.getTraces().size()) { |
592 | return false; |
593 | } |
594 | for (int i = 0; i < st1.getTraces().size(); i++) { |
595 | if (!st1.getTraces().get(i).equals(st2.getTraces().get(i))) { |
596 | return false; |
597 | } |
598 | } |
599 | return true; |
600 | } |
601 | |
602 | /** |
603 | * Checks two Markov Transitions for having the same identity. |
604 | * |
605 | * The transitions are considered to have the same identity if they |
606 | * connected the same states. |
607 | * |
608 | * @param t1 |
609 | * the first transition |
610 | * @param t2 |
611 | * the second transition |
612 | * @return true, if the transitions are identical; false otherwise |
613 | */ |
614 | private boolean testIdentity(final Transition t1, final Transition t2) { |
615 | return testIdentity(t1.getFromState(), t2.getFromState()) |
616 | && testIdentity(t1.getToState(), t2.getToState()); |
617 | } |
618 | } |