1 | package felix.compiler; |
2 | |
3 | import java.util.ArrayList; |
4 | import java.util.HashMap; |
5 | import java.util.HashSet; |
6 | import java.util.Iterator; |
7 | |
8 | import tuffy.mln.Literal; |
9 | import tuffy.mln.Term; |
10 | import tuffy.ra.Expression; |
11 | import tuffy.ra.Function; |
12 | import tuffy.util.ExceptionMan; |
13 | import tuffy.util.StringMan; |
14 | import tuffy.util.Timer; |
15 | |
16 | |
17 | import felix.dstruct.FelixClause; |
18 | import felix.dstruct.FelixPredicate; |
19 | import felix.dstruct.FelixQuery; |
20 | import felix.dstruct.FelixPredicate.FPProperty; |
21 | import felix.dstruct.StatOperator.OPType; |
22 | import felix.parser.FelixCommandOptions; |
23 | |
24 | /** |
25 | * The class of static analyzer that parses a given |
26 | * MLN program. It takes as input |
27 | * the whole MLN program, and assign properties to each |
28 | * predicate. Properties include |
29 | * whether a predicate is SYMM, REFLEX, TRANS etc., or |
30 | * whether a clause specifies a CRF |
31 | * chain etc. |
32 | * |
33 | * @author Ce Zhang |
34 | * |
35 | */ |
36 | public class StaticAnalyzer { |
37 | |
38 | /** |
39 | * Felix Query. |
40 | */ |
41 | FelixQuery fq; |
42 | |
43 | /** |
44 | * Command line options. |
45 | */ |
46 | FelixCommandOptions options; |
47 | |
48 | /** |
49 | * The constructor. |
50 | * @param _fq |
51 | * @param _opt |
52 | */ |
53 | public StaticAnalyzer(FelixQuery _fq, FelixCommandOptions _opt){ |
54 | fq = _fq; |
55 | options = _opt; |
56 | } |
57 | |
58 | |
59 | /** |
60 | * Analyze the input MLN program and assign properties to each predicate. |
61 | */ |
62 | public void parse(){ |
63 | |
64 | //classify clauses and register them to predicates |
65 | //Note: the invocation order matters! |
66 | this.parseKeyConstraintRelation(); |
67 | this.parseReflexiveRelation(); |
68 | this.parseSymmetricRelation(); |
69 | this.parseTransitiveRelation(); |
70 | this.parseChainRecursiveRelation(); |
71 | this.parseNonRecursiveRelation(); |
72 | this.parseOtherRecursiveRelation(); |
73 | this.parseSpecialPredicate(); |
74 | this.parseEmbededWeightRule(); |
75 | //this.parseSymmetricRelationPi2P(); |
76 | } |
77 | |
78 | /** |
79 | * Parse the predicate serves as the linear-representation of COREF. |
80 | */ |
81 | public void parseSpecialPredicate(){ |
82 | |
83 | for(FelixPredicate p : fq.getAllPred()){ |
84 | String name = p.getName(); |
85 | if(name.endsWith("_map")){ |
86 | |
87 | String oriName = name.replaceAll("_map$", ""); |
88 | |
89 | if(fq.getPredByName(oriName) == null){ |
90 | ExceptionMan.die("Cannot use predicate P_map without predicate P, please change to another name."); |
91 | } |
92 | |
93 | FelixPredicate orip = fq.getPredByName(oriName); |
94 | |
95 | if(orip.isClosedWorld()){ |
96 | ExceptionMan.die("Cannot use predicate P with predicate P as closed. Please change P to co-ref predicate, or" + |
97 | " change the name of P_map"); |
98 | } |
99 | |
100 | if(!p.isClosedWorld()){ |
101 | ExceptionMan.die("Cannot use predicate P_map with open-world setting. Plase change P_map to closed-world, or" + |
102 | " change the name of P_map"); |
103 | } |
104 | |
105 | orip.mustbe = OPType.COREF; |
106 | p.oriCorefPredicate = orip; |
107 | p.isCorefMapPredicate = true; |
108 | |
109 | orip.corefMAPPredicate = p; |
110 | |
111 | } |
112 | } |
113 | |
114 | } |
115 | |
116 | /** |
117 | * Parse predicate with key constraints. |
118 | */ |
119 | public void parseKeyConstraintRelation(){ |
120 | |
121 | // get key constraint from predicate declaration |
122 | for(FelixPredicate fp : fq.getAllPred()){ |
123 | if(fp.hasDependentAttributes()){ |
124 | |
125 | Object[] _labelpos = fp.getLabelAttrPositions().toArray(); |
126 | int[] labelpos = new int[_labelpos.length]; |
127 | |
128 | for(int i=0;i<_labelpos.length;i++){ |
129 | labelpos[i] = (Integer) _labelpos[i]; |
130 | } |
131 | |
132 | fp.registerProperty( |
133 | FelixPredicate.FPProperty.KEY_CONSTRAINT, |
134 | null, |
135 | labelpos); |
136 | } |
137 | } |
138 | |
139 | // discover rules like R(a,b1), [b1!=b2], => !R(a,b2) |
140 | for(FelixClause fc : fq.getAllClause()){ |
141 | |
142 | if(fc.getRegLiterals().size() != 2 || |
143 | fc.isHardClause() == false){ |
144 | continue; |
145 | } |
146 | |
147 | HashSet<FelixPredicate> processedPred = new HashSet<FelixPredicate>(); |
148 | |
149 | for(Literal l : fc.getRegLiterals()){ |
150 | |
151 | FelixPredicate p = fq.getPredByName(l.getPred().getName()); |
152 | |
153 | if(processedPred.contains(p)){ |
154 | continue; |
155 | } |
156 | |
157 | processedPred.add(p); |
158 | |
159 | if(p.isClosedWorld()){ |
160 | continue; |
161 | } |
162 | |
163 | Literal l1 = fc.getRegLiterals().get(0); |
164 | Literal l2 = fc.getRegLiterals().get(1); |
165 | |
166 | if(fc.getConstraints().size() != 1 || |
167 | l1.getPred().equals(l2.getPred()) == false){ |
168 | continue; |
169 | } |
170 | |
171 | Expression e = fc.getConstraints().get(0); |
172 | String var1 = "", var2 = ""; |
173 | |
174 | boolean eqNeqFlag = false; |
175 | if(e.getFunction() == Function.NOT){ |
176 | e = e.getArgs().get(0); |
177 | eqNeqFlag = true; |
178 | } |
179 | |
180 | if ( ( eqNeqFlag == false && e.getFunction() == Function.Neq ) || |
181 | ( eqNeqFlag == true && e.getFunction() == Function.Eq ) ){ |
182 | |
183 | if(l1.getSense() == true || l2.getSense() == true){ |
184 | continue; |
185 | } |
186 | |
187 | if(e.getArgs().size() != 2){ |
188 | continue; |
189 | } |
190 | |
191 | Expression e1 = e.getArgs().get(0); |
192 | Expression e2 = e.getArgs().get(1); |
193 | |
194 | if(e1.getFunction() != Function.VariableBinding |
195 | || e2.getFunction() != Function.VariableBinding){ |
196 | continue; |
197 | } |
198 | |
199 | var1 = e1.toString(); |
200 | var2 = e2.toString(); |
201 | }else{ |
202 | continue; |
203 | } |
204 | |
205 | //check |
206 | ArrayList<Term> ta = l1.getTerms(); |
207 | ArrayList<Term> tb = l2.getTerms(); |
208 | |
209 | if(ta.size() != tb.size()) continue; |
210 | |
211 | boolean isLR = true; |
212 | boolean isFirst = false; |
213 | int firstI = -1; |
214 | ArrayList<Integer> __labelpos = new ArrayList<Integer>(); |
215 | for(int i=0;i<ta.size();i++){ |
216 | if(ta.get(i).toString().equals(tb.get(i).toString()) == false){ |
217 | |
218 | if( (ta.get(i).toString().equals(var1) && tb.get(i).toString().equals(var2) && isFirst ==false) || |
219 | (tb.get(i).toString().equals(var1) && ta.get(i).toString().equals(var2) && isFirst ==false) ){ |
220 | firstI = i; |
221 | isFirst = true; |
222 | }else{ |
223 | isLR = false; |
224 | } |
225 | |
226 | __labelpos.add(i); |
227 | |
228 | }else{ |
229 | //__keypos.add(i); |
230 | } |
231 | } |
232 | |
233 | if(firstI == -1 || isLR == false){ |
234 | continue; |
235 | } |
236 | |
237 | Object[] _labelpos = __labelpos.toArray(); |
238 | int[] labelpos = new int[_labelpos.length]; |
239 | |
240 | for(int i=0;i<_labelpos.length;i++){ |
241 | labelpos[i] = (Integer) _labelpos[i]; |
242 | } |
243 | |
244 | p.registerProperty( |
245 | FelixPredicate.FPProperty.KEY_CONSTRAINT, |
246 | fc, |
247 | labelpos); |
248 | } |
249 | } |
250 | |
251 | } |
252 | |
253 | /** |
254 | * Generates all permutations of the given terms. |
255 | * @param terms |
256 | * @return |
257 | */ |
258 | public HashSet<ArrayList<String>> generateAllPermutations(HashSet<String> terms){ |
259 | HashSet<ArrayList<String>> ret = new HashSet<ArrayList<String>>(); |
260 | |
261 | if(terms.size() == 1){ |
262 | ArrayList<String> tmp = new ArrayList<String>(); |
263 | tmp.add(terms.iterator().next()); |
264 | ret.add(tmp); |
265 | return ret; |
266 | } |
267 | |
268 | for(String first : terms){ |
269 | |
270 | HashSet<String> newTerms = (HashSet<String>) terms.clone(); |
271 | newTerms.remove(first); |
272 | |
273 | HashSet<ArrayList<String>> others = this.generateAllPermutations(newTerms); |
274 | for(ArrayList<String> a : others){ |
275 | a.add(first); |
276 | ret.add(a); |
277 | } |
278 | } |
279 | |
280 | return ret; |
281 | } |
282 | |
283 | |
284 | /** |
285 | * Parse predicate which is symmetric. |
286 | */ |
287 | public void parseSymmetricRelation(){ |
288 | |
289 | for(FelixClause fc : fq.getAllClause()){ |
290 | |
291 | if(fc.getRegLiterals().size() != 2 || |
292 | fc.isHardClause() == false){ |
293 | continue; |
294 | } |
295 | |
296 | HashSet<FelixPredicate> processedPred = new HashSet<FelixPredicate>(); |
297 | |
298 | for(Literal l : fc.getRegLiterals()){ |
299 | |
300 | FelixPredicate p = fq.getPredByName(l.getPred().getName()); |
301 | |
302 | if(processedPred.contains(p)){ |
303 | continue; |
304 | } |
305 | |
306 | processedPred.add(p); |
307 | |
308 | if(p.isClosedWorld()){ |
309 | continue; |
310 | } |
311 | |
312 | Literal l1 = fc.getRegLiterals().get(0); |
313 | Literal l2 = fc.getRegLiterals().get(1); |
314 | |
315 | if(!l1.getPred().equals(l2.getPred())){ |
316 | continue; |
317 | } |
318 | |
319 | if(p.arity() != 2){ |
320 | continue; |
321 | } |
322 | |
323 | if(l1.getSense() != l2.getSense()){ |
324 | if(l1.getTerms().get(0).toString().equals(l2.getTerms().get(1).toString()) && |
325 | l1.getTerms().get(1).toString().equals(l2.getTerms().get(0).toString()) && |
326 | !l1.getTerms().get(1).toString().equals(l1.getTerms().get(0).toString())){ |
327 | p.registerProperty( |
328 | FPProperty.SYMM, |
329 | fc); |
330 | } |
331 | } |
332 | |
333 | } |
334 | } |
335 | |
336 | } |
337 | |
338 | /** |
339 | * Parse predicate which is reflexive. |
340 | */ |
341 | public void parseReflexiveRelation(){ |
342 | |
343 | for(FelixClause fc : fq.getAllClause()){ |
344 | |
345 | if(fc.getRegLiterals().size() != 1 || |
346 | fc.isHardClause() == false){ |
347 | continue; |
348 | } |
349 | |
350 | HashSet<FelixPredicate> processedPred = new HashSet<FelixPredicate>(); |
351 | |
352 | for(Literal l : fc.getRegLiterals()){ |
353 | |
354 | FelixPredicate p = fq.getPredByName(l.getPred().getName()); |
355 | |
356 | if(processedPred.contains(p)){ |
357 | continue; |
358 | } |
359 | |
360 | processedPred.add(p); |
361 | |
362 | if(p.isClosedWorld()){ |
363 | continue; |
364 | } |
365 | |
366 | if(l.getTerms().size() == 2 && |
367 | (l.getSense() == true)){ |
368 | if(l.getTerms().get(0).var() != null && |
369 | l.getTerms().get(0).var().equals(l.getTerms().get(1).var())){ |
370 | p.registerProperty( |
371 | FPProperty.REFLEX, |
372 | fc); |
373 | } |
374 | } |
375 | |
376 | } |
377 | } |
378 | } |
379 | |
380 | /** |
381 | * Parse predicate which is transitive. |
382 | */ |
383 | public void parseTransitiveRelation(){ |
384 | |
385 | for(FelixClause fc : fq.getAllClause()){ |
386 | |
387 | if(fc.getRegLiterals().size() != 3 || |
388 | fc.isHardClause() == false){ |
389 | continue; |
390 | } |
391 | |
392 | HashSet<FelixPredicate> processedPred = new HashSet<FelixPredicate>(); |
393 | |
394 | for(Literal l : fc.getRegLiterals()){ |
395 | |
396 | FelixPredicate p = fq.getPredByName(l.getPred().getName()); |
397 | |
398 | if(processedPred.contains(p)){ |
399 | continue; |
400 | } |
401 | |
402 | processedPred.add(p); |
403 | |
404 | if(p.arity() != 2){ |
405 | continue; |
406 | } |
407 | |
408 | if(p.isClosedWorld()){ |
409 | continue; |
410 | } |
411 | |
412 | if(fc.getRegLiterals().size() == 3){ |
413 | |
414 | ArrayList<Literal> ls = new ArrayList<Literal>(); |
415 | ls.add(fc.getRegLiterals().get(0)); |
416 | ls.add(fc.getRegLiterals().get(1)); |
417 | ls.add(fc.getRegLiterals().get(2)); |
418 | |
419 | if(!ls.get(0).getPred().equals(ls.get(1).getPred()) || |
420 | !ls.get(2).getPred().equals(ls.get(1).getPred()) || |
421 | !ls.get(0).getPred().equals(ls.get(2).getPred())){ |
422 | continue; |
423 | } |
424 | |
425 | int head = -1; |
426 | int[] aa = new int[2]; |
427 | int ct = 0; |
428 | for(int i=0;i<3;i++){ |
429 | if(ls.get(i).getSense() == true){ |
430 | if(head != -1){ |
431 | head = -1; |
432 | break; |
433 | } |
434 | head = i; |
435 | }else{ |
436 | if(ct < 2){ |
437 | aa[ct++] = i; |
438 | } |
439 | } |
440 | } |
441 | |
442 | if(head == -1){ |
443 | continue; |
444 | } |
445 | |
446 | Literal l1 = ls.get(aa[0]); |
447 | Literal l2 = ls.get(aa[1]); |
448 | Literal h = ls.get(head); |
449 | |
450 | String common = ""; |
451 | String o1 = ""; |
452 | String o2 = ""; |
453 | for(int i=0;i<2;i++){ |
454 | for(int j=0;j<2;j++){ |
455 | if(l1.getTerms().get(i).toString().equals(l2.getTerms().get(j).toString())){ |
456 | common = l1.getTerms().get(i).toString(); |
457 | o1 = l1.getTerms().get((i+1)%2).toString(); |
458 | o2 = l2.getTerms().get((j+1)%2).toString(); |
459 | break; |
460 | } |
461 | } |
462 | if(common.equals("") == false){ |
463 | break; |
464 | } |
465 | } |
466 | |
467 | if(common.equals("")){ |
468 | continue; |
469 | } |
470 | |
471 | if( h.getTerms().get(0).toString().equals(o1) && h.getTerms().get(1).toString().equals(o2) ){ |
472 | p.registerProperty( |
473 | FPProperty.TRANS, |
474 | fc); |
475 | } |
476 | |
477 | if( h.getTerms().get(1).toString().equals(o1) && h.getTerms().get(0).toString().equals(o2) ){ |
478 | p.registerProperty( |
479 | FPProperty.TRANS, |
480 | fc); |
481 | } |
482 | } |
483 | } |
484 | } |
485 | |
486 | } |
487 | |
488 | /** |
489 | * Parse clause which specifies a CRF chain rule. |
490 | */ |
491 | public void parseChainRecursiveRelation(){ |
492 | |
493 | for(FelixClause fc : fq.getAllClause()){ |
494 | |
495 | HashSet<FelixPredicate> processedPred = new HashSet<FelixPredicate>(); |
496 | |
497 | for(Literal l : fc.getRegLiterals()){ |
498 | |
499 | FelixPredicate p = fq.getPredByName(l.getPred().getName()); |
500 | |
501 | if(processedPred.contains(p)){ |
502 | continue; |
503 | } |
504 | |
505 | processedPred.add(p); |
506 | |
507 | if(p.isClosedWorld()){ |
508 | continue; |
509 | } |
510 | |
511 | if(fc.getLiteralsOfPredicate(l.getPred()).size() != 2){ |
512 | continue; |
513 | } |
514 | |
515 | Literal l1 = fc.getLiteralsOfPredicate(l.getPred()).get(0); |
516 | Literal l2 = fc.getLiteralsOfPredicate(l.getPred()).get(1); |
517 | |
518 | boolean isCRFRule = true; |
519 | |
520 | int numberOfDifference = 0; |
521 | for(int i=0;i<l1.getTerms().size();i++){ |
522 | Term t1 = l1.getTerms().get(i); |
523 | Term t2 = l2.getTerms().get(i); |
524 | |
525 | if(t1.toString().equals(t2.toString())){ |
526 | // if these two terms are the same, good! |
527 | continue; |
528 | }else{ |
529 | // if not, we need to see whether in this rule, |
530 | // t1 and t2 is representing a chain. Here we know |
531 | // there can be only one label-field, so, only one |
532 | // such t1-t2 pair can exist. |
533 | |
534 | if(!p.getKeyPositions().contains(i)){ |
535 | continue; |
536 | } |
537 | |
538 | numberOfDifference ++; |
539 | |
540 | boolean isChain = false; |
541 | |
542 | // find in condition [t1 = t2 + 1] |
543 | //TODO: We may want to change it smarter in the future |
544 | for(Expression e : fc.getConstraints()){ |
545 | if(e.toString().equals(t1+" = (" + t2 + " + 1)") || |
546 | e.toString().equals(t2+" = (" + t1 + " + 1)") || |
547 | e.toString().equals("(" + t1 + " + 1) = " + t2) || |
548 | e.toString().equals("(" + t2 + " + 1) = " + t1) || |
549 | e.toString().equals(t1+" = (" + t2 + " - 1)") || |
550 | e.toString().equals(t2+" = (" + t1 + " - 1)") || |
551 | //TODO |
552 | e.toString().equals("(" + t1 + " - 1) = (" + t2 + " + 0)") || |
553 | e.toString().equals("(" + t2 + " - 1) = (" + t1 + " + 0)") || |
554 | e.toString().equals("(" + t1 + " - " + t2 + ") = " + 1) || |
555 | e.toString().equals(1 + " = (" + t1 + " - " + t2 + ")") || |
556 | e.toString().equals("(" + t2 + " - " + t1 + ") = " + 1) || |
557 | e.toString().equals(1 + " = (" + t2 + " - " + t1 + ")")){ |
558 | isChain = true; |
559 | } |
560 | } |
561 | |
562 | if(!isChain){ |
563 | isCRFRule = false; |
564 | } |
565 | |
566 | if(numberOfDifference != 1){ |
567 | isCRFRule = false; |
568 | } |
569 | } |
570 | } |
571 | |
572 | if(isCRFRule == true && numberOfDifference > 0){ |
573 | if(p.getKeyPositions().size() != 0){ |
574 | p.registerProperty( |
575 | FPProperty.CHAIN_RECUR, |
576 | fc); |
577 | } |
578 | } |
579 | |
580 | } |
581 | } |
582 | |
583 | } |
584 | |
585 | // rules other than special rules |
586 | /** |
587 | * Parse clause which does NOT specify 1) NON-RECURSIVE rule and |
588 | * 2) Chain rule. |
589 | * |
590 | */ |
591 | public void parseOtherRecursiveRelation(){ |
592 | for(FelixClause fc : fq.getAllClause()){ |
593 | |
594 | HashSet<FelixPredicate> processedPred = new HashSet<FelixPredicate>(); |
595 | |
596 | HashSet<FelixPredicate> appeared = new HashSet<FelixPredicate>(); |
597 | |
598 | for(Literal l : fc.getRegLiterals()){ |
599 | FelixPredicate p = fq.getPredByName(l.getPred().getName()); |
600 | |
601 | if(p.isClosedWorld()){ |
602 | continue; |
603 | } |
604 | |
605 | appeared.add(p); |
606 | } |
607 | |
608 | |
609 | |
610 | |
611 | for(Literal l : fc.getRegLiterals()){ |
612 | |
613 | FelixPredicate p = fq.getPredByName(l.getPred().getName()); |
614 | |
615 | if(p.isClosedWorld()){ |
616 | continue; |
617 | } |
618 | |
619 | if(processedPred.contains(p)){ |
620 | continue; |
621 | } |
622 | |
623 | processedPred.add(p); |
624 | |
625 | if(appeared.size() == 1){ |
626 | if(fc.getLiteralsOfPredicate(l.getPred()).size() > 1){ |
627 | p.registerProperty( |
628 | FPProperty.OTHER_RECUR, |
629 | fc); |
630 | } |
631 | }else{ |
632 | if(fc.getLiteralsOfPredicate(l.getPred()).size() > 1){ |
633 | p.registerProperty( |
634 | FPProperty.OTHER_RECUR_WITHOTHER_OPENPRED, |
635 | fc); |
636 | } |
637 | } |
638 | |
639 | } |
640 | } |
641 | } |
642 | |
643 | |
644 | /** |
645 | * Parse clause which specifies non-recursive rules. |
646 | */ |
647 | public void parseNonRecursiveRelation(){ |
648 | |
649 | for(FelixClause fc : fq.getAllClause()){ |
650 | |
651 | HashSet<FelixPredicate> processedPred = new HashSet<FelixPredicate>(); |
652 | |
653 | for(Literal l : fc.getRegLiterals()){ |
654 | |
655 | FelixPredicate p = fq.getPredByName(l.getPred().getName()); |
656 | |
657 | if(p.isClosedWorld()){ |
658 | continue; |
659 | } |
660 | |
661 | if(processedPred.contains(p)){ |
662 | continue; |
663 | } |
664 | |
665 | processedPred.add(p); |
666 | |
667 | if(fc.getLiteralsOfPredicate(l.getPred()).size() == 1){ |
668 | p.registerProperty( |
669 | FPProperty.NON_RECUR, |
670 | fc); |
671 | } |
672 | |
673 | } |
674 | } |
675 | } |
676 | |
677 | /** |
678 | * Parse clause whose weights are specified by embedded weight. |
679 | */ |
680 | public void parseEmbededWeightRule(){ |
681 | |
682 | for(FelixClause fc : fq.getAllClause()){ |
683 | |
684 | for(Literal l : fc.getRegLiterals()){ |
685 | |
686 | FelixPredicate p = fq.getPredByName(l.getPred().getName()); |
687 | |
688 | if(p.isClosedWorld()){ |
689 | continue; |
690 | } |
691 | |
692 | if(fc.hasEmbeddedWeight()){ |
693 | p.registerProperty(FPProperty.EMBED_WEIGHT_RULE, fc); |
694 | } |
695 | |
696 | } |
697 | |
698 | } |
699 | } |
700 | |
701 | } |