1 | package tuffy.mln; |
2 | |
3 | import java.sql.PreparedStatement; |
4 | import java.sql.SQLException; |
5 | import java.util.ArrayList; |
6 | import java.util.HashMap; |
7 | import java.util.HashSet; |
8 | import java.util.Hashtable; |
9 | import java.util.Set; |
10 | |
11 | import tuffy.db.RDB; |
12 | import tuffy.db.SQLMan; |
13 | import tuffy.infer.ds.GClause; |
14 | import tuffy.main.Infer; |
15 | import tuffy.parse.CommandOptions; |
16 | import tuffy.ra.Expression; |
17 | import tuffy.util.Config; |
18 | import tuffy.util.ExceptionMan; |
19 | import tuffy.util.StringMan; |
20 | import tuffy.util.UIMan; |
21 | /** |
22 | * A first-order logic clause, namely a disjunct of literals. |
23 | */ |
24 | public class Clause implements Cloneable{ |
25 | |
26 | @SuppressWarnings("unchecked") |
27 | public Clause clone(){ |
28 | Clause ret = new Clause(); |
29 | |
30 | ret.bilits = (ArrayList<Literal>) this.bilits.clone(); |
31 | |
32 | ret.constraints = new ArrayList<Expression>(); |
33 | for(Expression sub : this.constraints){ |
34 | ret.constraints.add(sub.clone()); |
35 | } |
36 | |
37 | ret.cost = this.cost; |
38 | ret.existentialVars = (ArrayList<String>) this.existentialVars.clone(); |
39 | ret.exprWeight = this.exprWeight; |
40 | ret.id = this.id; |
41 | ret.instances = (ArrayList<ClauseInstance>) this.instances.clone(); |
42 | ret.isTemplate = this.isTemplate; |
43 | ret.lits = (ArrayList<Literal>) this.lits.clone(); |
44 | ret.metaTypes = (ArrayList<Type>) this.metaTypes.clone(); |
45 | ret.metaVars = (ArrayList<String>) this.metaVars.clone(); |
46 | ret.name = this.name; |
47 | ret.predIndex = (HashMap<Predicate, ArrayList<Literal>>) this.predIndex.clone(); |
48 | ret.reglits = (ArrayList<Literal>) this.reglits.clone(); |
49 | ret.relIntanceClauses = this.relIntanceClauses; |
50 | ret.signature = this.signature; |
51 | ret.specText = (ArrayList<String>) this.specText.clone(); |
52 | ret.sqlFromList = this.sqlFromList; |
53 | ret.sqlPivotAttrsList = this.sqlPivotAttrsList; |
54 | ret.sqlWhereBindings = this.sqlWhereBindings; |
55 | ret.uNames = (ArrayList<String>) this.uNames.clone(); |
56 | ret.varWeight = this.varWeight; |
57 | ret.violatedGClauses = (ArrayList<GClause>) this.violatedGClauses.clone(); |
58 | ret.weight = this.weight; |
59 | |
60 | |
61 | return ret; |
62 | } |
63 | |
64 | |
65 | /** |
66 | * Map from clause ID to its description. This is used in |
67 | * learning part to dump out the answers. Here by id it means |
68 | * a string like <Clause ID>.<Instance ID or 0 if not a template>. |
69 | * This variable is materialized in {@link Infer#setUp(CommandOptions)}. |
70 | */ |
71 | public static HashMap<String, String> mappingFromID2Desc = null; |
72 | // new HashMap<String, String>(); |
73 | |
74 | /** |
75 | * Map from Constant ID to Constant Name. This map is filled |
76 | * in {@link MarkovLogicNetwork#getSymbolID(String, Type)}. |
77 | * This variable is materialized in {@link Infer#setUp(CommandOptions)}. |
78 | */ |
79 | public static HashMap<Integer, String> mappingFromID2Const = null; |
80 | // new HashMap<Integer, String>(); |
81 | |
82 | /** |
83 | * The set of boolean expressions that must all be TRUE; |
84 | * otherwise the corresponding grounding is always true, and |
85 | * is useless for inference -- and will be discarded. |
86 | * |
87 | * In other words, this is the set of constraints that must be |
88 | * satisfied by the grounding process. |
89 | */ |
90 | private ArrayList<Expression> constraints = new ArrayList<Expression>(); |
91 | |
92 | /** |
93 | * Add a constraint that must hold. |
94 | * @param e A bool expression that must be TRUE. |
95 | */ |
96 | public void addConstraint(Expression e){ |
97 | constraints.add(e); |
98 | } |
99 | |
100 | public ArrayList<Expression> getConstraints(){ |
101 | return constraints; |
102 | } |
103 | |
104 | |
105 | private String getConstaintStringAsLits(){ |
106 | if(constraints.isEmpty()) return ""; |
107 | StringBuilder sb = new StringBuilder(); |
108 | ArrayList<String> clines = new ArrayList<String>(); |
109 | for(Expression e : constraints){ |
110 | clines.add(" v [" + Expression.not(e).toString() + "]"); |
111 | } |
112 | sb.append(StringMan.join("\n", clines)); |
113 | return sb.toString(); |
114 | } |
115 | |
116 | |
117 | /** |
118 | * List of literals in this clause. |
119 | */ |
120 | private ArrayList<Literal> lits = new ArrayList<Literal>(); |
121 | |
122 | |
123 | |
124 | /** |
125 | * List of regular literals in this clause. |
126 | */ |
127 | private ArrayList<Literal> reglits = new ArrayList<Literal>(); //regular |
128 | |
129 | /** |
130 | * List of built-in literals in this clause. |
131 | */ |
132 | private ArrayList<Literal> bilits = new ArrayList<Literal>(); //built in |
133 | |
134 | /** |
135 | * The index of predicate to set of literals referencing that predicate. |
136 | */ |
137 | private HashMap<Predicate, ArrayList<Literal>> predIndex = |
138 | new HashMap<Predicate, ArrayList<Literal>>(); |
139 | |
140 | /** |
141 | * List of variables that are existentially quantified. |
142 | */ |
143 | private ArrayList<String> existentialVars = new ArrayList<String>(); |
144 | |
145 | |
146 | /** |
147 | * Variables corresponding to constants in this clause. |
148 | */ |
149 | private ArrayList<String> metaVars = new ArrayList<String>(); |
150 | |
151 | /** |
152 | * Types of meta variables. |
153 | */ |
154 | private ArrayList<Type> metaTypes = new ArrayList<Type>(); |
155 | |
156 | /** |
157 | * List of instances of this clause. Here by instance, we mean |
158 | * the possible bindings of meta-variables to constants. |
159 | */ |
160 | private ArrayList<ClauseInstance> instances = new ArrayList<ClauseInstance>(); |
161 | |
162 | /** |
163 | * weight of this clause. |
164 | */ |
165 | private double weight = 0; |
166 | |
167 | /** |
168 | * name of this clause. |
169 | */ |
170 | private String name = null; |
171 | |
172 | /** |
173 | * id of this cluase. |
174 | */ |
175 | private int id = 0; |
176 | |
177 | /** |
178 | * user provided names |
179 | */ |
180 | private ArrayList<String> uNames = new ArrayList<String>(); |
181 | |
182 | /** |
183 | * Lines in the MLN rule file specifying this clause. |
184 | */ |
185 | private ArrayList<String> specText = new ArrayList<String>(); |
186 | |
187 | private String getSpecTextFlat(){ |
188 | return StringMan.join("\n", specText); |
189 | } |
190 | |
191 | |
192 | /** |
193 | * The database table storing the clause instances. |
194 | */ |
195 | private String relIntanceClauses = null; |
196 | |
197 | |
198 | /** |
199 | * Indicates whether this clause contains constants. |
200 | */ |
201 | private boolean isTemplate = false; |
202 | |
203 | /** |
204 | * The signature of this clause. Clauses with the same |
205 | * signature have the same pattern, and thus can be consolidated. |
206 | * See {@link Clause#normalize()}. |
207 | */ |
208 | private String signature = null; |
209 | |
210 | /** |
211 | * The From sub-clause of SQL for grounding. |
212 | */ |
213 | public String sqlFromList = null; |
214 | |
215 | /** |
216 | * The Where sub-clause of SQL for grounding. |
217 | */ |
218 | public String sqlWhereBindings = null; |
219 | |
220 | /** |
221 | * The list of attributes that are NOT existential variables. |
222 | */ |
223 | public String sqlPivotAttrsList = null; |
224 | |
225 | /** |
226 | * The cost ascribed to this clause. |
227 | * For auditing purposes. |
228 | * |
229 | * @see tuffy.infer.MRF#auditClauseViolations() |
230 | */ |
231 | public double cost = 0; |
232 | |
233 | /** |
234 | * The number of violations on this clause. |
235 | * For auditing purposes. |
236 | * |
237 | * @see tuffy.infer.MRF#auditClauseViolations() |
238 | */ |
239 | public double violations = 0; |
240 | |
241 | /** |
242 | * Violated ground clauses. |
243 | * |
244 | * @see tuffy.infer.MRF#auditClauseViolations() |
245 | */ |
246 | public ArrayList<GClause> violatedGClauses = new ArrayList<GClause>(); |
247 | |
248 | /** |
249 | * FO variable that is used as clause weights |
250 | */ |
251 | private String varWeight = null; |
252 | |
253 | public void setVarWeight(String vw){ |
254 | varWeight = vw; |
255 | } |
256 | |
257 | /** |
258 | * Get the variable in this clause that is used as clause weights |
259 | */ |
260 | public String getVarWeight(){ |
261 | return varWeight; |
262 | } |
263 | |
264 | /** |
265 | * Check if the weight of this clause comes from a variable in the clause |
266 | */ |
267 | public boolean hasEmbeddedWeight(){ |
268 | return (varWeight != null); |
269 | } |
270 | |
271 | |
272 | /** |
273 | * Return the weight of this clause. |
274 | * If this clause contains multiple instances, the returned |
275 | * value only indicates the signum. |
276 | */ |
277 | public double getWeight(){ |
278 | return weight; |
279 | } |
280 | |
281 | /** |
282 | * Return true iff this clause contains constant. Note that |
283 | * the result of this function is meaningful iff this |
284 | * clause is a normalized clause. |
285 | */ |
286 | public boolean isTemplate(){ |
287 | return isTemplate; |
288 | } |
289 | |
290 | |
291 | /** |
292 | * Add user provided names to this clause. |
293 | * @param nm user provided name |
294 | */ |
295 | public void addUserProvidedName(String nm){ |
296 | if(nm != null){ |
297 | uNames.add(nm); |
298 | } |
299 | } |
300 | |
301 | |
302 | /** |
303 | * Class of an instance of a clause. |
304 | */ |
305 | private class ClauseInstance{ |
306 | /** |
307 | * list of constant ID in this clause instance. |
308 | */ |
309 | public ArrayList<Integer> conList; |
310 | |
311 | /** |
312 | * weight of this clause instance. |
313 | */ |
314 | public double weight; |
315 | |
316 | /** |
317 | * Constructor of ClauseInstance. |
318 | * @param conList list of constant in this clause instance. |
319 | * @param weight weight of this clause instance. |
320 | */ |
321 | public ClauseInstance(ArrayList<Integer> conList, double weight){ |
322 | this.conList = conList; |
323 | this.weight = weight; |
324 | } |
325 | } |
326 | |
327 | /** |
328 | * Return the assigned name of this clause. |
329 | */ |
330 | public String getName(){ |
331 | return name; |
332 | } |
333 | |
334 | /** |
335 | * Assign a name for this clause. |
336 | */ |
337 | public void setName(String aname) { |
338 | name = aname; |
339 | relIntanceClauses = name + "_instances"; |
340 | } |
341 | |
342 | /** |
343 | * Return the "signature" of this clause. |
344 | * |
345 | * @see Clause#normalize() |
346 | */ |
347 | public String getSignature(){ |
348 | return signature; |
349 | } |
350 | |
351 | /** |
352 | * Return a normalized version of this clause. |
353 | * |
354 | * The variables and constants are replaced standardized |
355 | * variable names, yielding a signature that can be used to |
356 | * identify clauses of the same pattern. |
357 | * If there are constants in the original clause, |
358 | * the resulting clause is called a template. |
359 | * Clauses of the same pattern will be consolidated under |
360 | * the same template. |
361 | * |
362 | * For example, clauses |
363 | * "!likes(x, Candy) v has(x, Diabetes)" and |
364 | * "!likes(x, WeightLifting) v has(x, Muscles)" |
365 | * would be consolidated into the template |
366 | * "!likes(v1, c1) v has(v1, c2)". |
367 | * |
368 | * Zero-weight clauses will be ignored. |
369 | * |
370 | * @see MarkovLogicNetwork#registerClause(Clause) |
371 | */ |
372 | public Clause normalize() { |
373 | Hashtable<String, Integer> varIndex = new Hashtable<String, Integer>(); |
374 | Hashtable<Integer, Integer> conIndex = new Hashtable<Integer, Integer>(); |
375 | ArrayList<Integer> conList = new ArrayList<Integer>(); |
376 | ArrayList<Type> conTypeList = new ArrayList<Type>(); |
377 | |
378 | if(this.weight == 0) return null; |
379 | |
380 | // normalization |
381 | // order into {enlits, eplits, unlits, uplits}. |
382 | if(Config.reorder_literals){ |
383 | ArrayList<Literal> enlits = new ArrayList<Literal>(); //closed, neg |
384 | ArrayList<Literal> eplits = new ArrayList<Literal>(); //closed, pos |
385 | ArrayList<Literal> unlits = new ArrayList<Literal>(); //open, neg |
386 | ArrayList<Literal> uplits = new ArrayList<Literal>(); //open, pos |
387 | for(Literal lit : lits){ |
388 | Predicate p = lit.getPred(); |
389 | if(p.isClosedWorld()){ |
390 | if(lit.getSense()){ |
391 | eplits.add(lit); |
392 | }else{ |
393 | enlits.add(lit); |
394 | } |
395 | }else{ |
396 | if(lit.getSense()){ |
397 | uplits.add(lit); |
398 | }else{ |
399 | unlits.add(lit); |
400 | } |
401 | } |
402 | } |
403 | enlits.addAll(eplits); |
404 | enlits.addAll(unlits); |
405 | enlits.addAll(uplits); |
406 | lits = enlits; |
407 | } |
408 | |
409 | // TODO: cleaner object cloning |
410 | ArrayList<String> litlist = new ArrayList<String>(); |
411 | for(Literal lit : lits){ |
412 | StringBuilder sb = new StringBuilder(); |
413 | if(!lit.getSense()){ |
414 | sb.append("!"); |
415 | }else{ |
416 | sb.append(" "); |
417 | } |
418 | sb.append(lit.getPred().getName()); |
419 | sb.append("("); |
420 | ArrayList<String> tlist = new ArrayList<String>(); |
421 | for(int i=0; i<lit.getTerms().size(); i++){ |
422 | Term t = lit.getTerms().get(i); |
423 | if(t.isVariable()){ |
424 | Integer vi = varIndex.get(t.var()); |
425 | if(vi == null){ |
426 | vi = varIndex.size(); |
427 | varIndex.put(t.var(), vi); |
428 | } |
429 | tlist.add("v" + vi); |
430 | }else{ |
431 | Integer ci = conIndex.get(t.constant()); |
432 | if(ci == null){ |
433 | ci = conIndex.size(); |
434 | conIndex.put(t.constant(), ci); |
435 | conList.add(t.constant()); |
436 | conTypeList.add(lit.getPred().getTypeAt(i)); |
437 | } |
438 | tlist.add("c" + ci); |
439 | } |
440 | } |
441 | sb.append(StringMan.commaList(tlist)); |
442 | sb.append(")"); |
443 | litlist.add(sb.toString()); |
444 | } |
445 | |
446 | StringBuilder sigb = new StringBuilder(); |
447 | sigb.append(this.weight>0 ? "sign='+' " : "sign='-' "); |
448 | if(!existentialVars.isEmpty()){ |
449 | ArrayList<String> evlist = new ArrayList<String>(); |
450 | for(String ev : existentialVars){ |
451 | evlist.add("v" + varIndex.get(ev)); |
452 | } |
453 | if(Config.clause_display_multiline){ |
454 | sigb.append("\n "); |
455 | } |
456 | sigb.append("EXIST " + StringMan.join(",", evlist) + " "); |
457 | } |
458 | if(Config.clause_display_multiline){ |
459 | sigb.append("\n "); |
460 | } |
461 | sigb.append(StringMan.join((Config.clause_display_multiline ? "\n" : "") + " v ", litlist)); |
462 | |
463 | HashMap<String, String> mapVarVar = new HashMap<String, String>(); |
464 | for(String v : varIndex.keySet()){ |
465 | mapVarVar.put(v, "v" + varIndex.get(v)); |
466 | } |
467 | for(Expression e : constraints){ |
468 | // if(e.changeName == true){ |
469 | String es = e.renameVariables(mapVarVar); |
470 | if(es != null){ |
471 | ExceptionMan.die("Encountered a dangling variable '" + es + |
472 | "' in clause\n" + this.getSpecTextFlat()); |
473 | } |
474 | // } |
475 | } |
476 | sigb.append("\n"); |
477 | sigb.append(getConstaintStringAsLits()); |
478 | String sig = sigb.toString(); |
479 | |
480 | // generate normalized clause |
481 | Clause c = new Clause(); |
482 | c.signature = sig; |
483 | c.uNames = uNames; |
484 | c.specText = specText; |
485 | c.constraints = constraints; |
486 | this.signature = sig; |
487 | for(Literal lit : this.lits){ |
488 | Literal nlit = new Literal(lit.getPred(), lit.getSense()); |
489 | for(Term term : lit.getTerms()){ |
490 | Term nterm; |
491 | if(term.isVariable()){ |
492 | nterm = new Term("v" + varIndex.get(term.var())); |
493 | }else{ |
494 | nterm = new Term("c" + conIndex.get(term.constant())); |
495 | nterm.isOriAConstant = true; |
496 | nterm.oriConstantID = term.constant(); |
497 | } |
498 | nlit.appendTerm(nterm); |
499 | } |
500 | c.addLiteral(nlit); |
501 | } |
502 | for(String ev : this.existentialVars){ |
503 | c.addExistentialVariable("v" + varIndex.get(ev)); |
504 | } |
505 | if(conTypeList.isEmpty()){ |
506 | c.isTemplate = false; |
507 | c.weight = this.weight; |
508 | }else{ |
509 | c.isTemplate = true; |
510 | c.weight = (this.weight > 0 ? 1 : -1); |
511 | for(int i=0; i<conTypeList.size(); i++){ |
512 | c.addMetaVariable("c"+i, conTypeList.get(i)); |
513 | } |
514 | c.instances.add(new ClauseInstance(conList, this.weight)); |
515 | } |
516 | return c; |
517 | } |
518 | |
519 | /** |
520 | * "Absorb" another clause of the same pattern into this clause. |
521 | * If this clause is a template, then adding instances into |
522 | * the instance list. Otherwise, add its weight to current clause. |
523 | * |
524 | * @param c the clause to be absorbed |
525 | * @see Clause#normalize() |
526 | */ |
527 | public void absorb(Clause c) { |
528 | if(!this.signature.equals(c.signature)){ |
529 | ExceptionMan.die("clauses of different patterns cannot be consolidated!"); |
530 | } |
531 | if(isTemplate){ |
532 | this.instances.addAll(c.instances); |
533 | }else{ |
534 | this.weight += c.weight; |
535 | this.specText.addAll(c.specText); |
536 | } |
537 | this.uNames.addAll(c.uNames); |
538 | } |
539 | |
540 | /** |
541 | * Flush the instance of this clause into database. |
542 | * Here "instance" means all the constant ID in instance with |
543 | * its weight as a row in DB. |
544 | * @param db the database object used to store clause instances. |
545 | */ |
546 | private void sealClauseInstances(RDB db) { |
547 | if(!isTemplate){ |
548 | Clause.mappingFromID2Desc.put(this.id+ ".0", this.toString(-1)); |
549 | return; |
550 | } |
551 | // cosntruct SQL of adding instances |
552 | ArrayList<String> atts = new ArrayList<String>(); |
553 | ArrayList<String> pholders = new ArrayList<String>(); |
554 | atts.add("weight FLOAT8"); |
555 | pholders.add("?"); |
556 | for(String v : metaVars) { |
557 | atts.add(v + " INT"); |
558 | pholders.add("?"); |
559 | } |
560 | |
561 | |
562 | db.dropTable(relIntanceClauses); |
563 | String sql = "CREATE TABLE " + relIntanceClauses + |
564 | StringMan.commaListParen(atts); |
565 | db.update(sql); |
566 | |
567 | //TODO: |
568 | db.dropSequence(relIntanceClauses+"_seq"); |
569 | sql = "CREATE SEQUENCE " + relIntanceClauses + "_seq;"; |
570 | db.update(sql); |
571 | sql = "ALTER TABLE " + relIntanceClauses + " ADD myid INT;"; |
572 | db.update(sql); |
573 | sql = "ALTER TABLE " + relIntanceClauses + " ALTER COLUMN myid SET " + |
574 | "DEFAULT NEXTVAL('" + relIntanceClauses + "_seq');"; |
575 | db.update(sql); |
576 | |
577 | int instanceCount = 0; |
578 | |
579 | sql = "INSERT INTO " + relIntanceClauses + " VALUES" + |
580 | StringMan.commaListParen(pholders); |
581 | PreparedStatement psAddMeta = db.getPrepareStatement(sql); |
582 | |
583 | int ni = 0; |
584 | try { |
585 | for(ClauseInstance ins : instances){ |
586 | double weight = ins.weight; |
587 | ArrayList<Integer> meta = ins.conList; |
588 | psAddMeta.setDouble(1, weight); |
589 | for(int k=0; k<meta.size(); k++) { |
590 | psAddMeta.setInt(k+2, meta.get(k)); |
591 | } |
592 | instanceCount ++; |
593 | Clause.mappingFromID2Desc.put(this.id+ "." + instanceCount, this.toString(ni++)); |
594 | psAddMeta.addBatch(); |
595 | } |
596 | psAddMeta.executeBatch(); |
597 | psAddMeta.close(); |
598 | } catch (SQLException e) { |
599 | ExceptionMan.handle(e); |
600 | } |
601 | psAddMeta = null; |
602 | } |
603 | |
604 | /** |
605 | * Add a meta variable into this clause. |
606 | * @param v name this this meta variable |
607 | * @param t type of this meta variable |
608 | * @see Clause#normalize() |
609 | * @return whether this inserting succeeded. |
610 | */ |
611 | private boolean addMetaVariable(String v, Type t) { |
612 | if(metaVars.contains(v)) return false; |
613 | metaVars.add(v); |
614 | metaTypes.add(t); |
615 | return true; |
616 | } |
617 | |
618 | /** |
619 | * Existentially quantify a variable. |
620 | * |
621 | * @param v the variable to be existentially quantified |
622 | */ |
623 | public boolean addExistentialVariable(String v) { |
624 | if(existentialVars.contains(v)) return false; |
625 | existentialVars.add(v); |
626 | return true; |
627 | } |
628 | |
629 | /** |
630 | * Construct an empty clause. Initial weight = 0. |
631 | * |
632 | */ |
633 | public Clause(){ |
634 | weight = 0; |
635 | } |
636 | |
637 | /** |
638 | * Specify this clause as a hard rule. |
639 | * Currently hard rules are treated as soft rules with |
640 | * a very large weight. |
641 | * |
642 | * @see Config#hard_weight |
643 | */ |
644 | public void setHardWeight() { |
645 | weight = Config.hard_weight; |
646 | } |
647 | |
648 | /** |
649 | * Return whether this clause is a hard rule. |
650 | */ |
651 | public boolean isHardClause(){ |
652 | return weight >= Config.hard_weight; |
653 | } |
654 | |
655 | /** |
656 | * Set the weight of this clause. |
657 | */ |
658 | public void setWeight(double wt) { |
659 | weight = wt; |
660 | } |
661 | |
662 | /** |
663 | * Return the expression of clause weights to be used in SQL. |
664 | * For template clauses, it's the name of a table attribute; |
665 | * for non-template clauses, it's a floating number. |
666 | */ |
667 | public String getWeightExp() { |
668 | return exprWeight; |
669 | } |
670 | |
671 | /** |
672 | * Check if the weight is positive. |
673 | */ |
674 | public boolean isPositiveClause(){ |
675 | return weight > 0; |
676 | } |
677 | |
678 | /** |
679 | * Initialize database objects for this clause. |
680 | */ |
681 | public void prepareForDB(RDB db) { |
682 | sealClauseInstances(db); |
683 | generateSQL(); |
684 | } |
685 | |
686 | /** |
687 | * Check for unsafe variables in the clause, and mark the corresponding |
688 | * Predicates. |
689 | * A variable is unsafe if it appears only in a positive literal; |
690 | * i.e., if it does not appear in the body in the Datalog form. |
691 | */ |
692 | public void checkVariableSafety(){ |
693 | HashSet<String> vars = new HashSet<String>(); |
694 | HashSet<String> safeVars = new HashSet<String>(); |
695 | ArrayList<Literal> posLits = new ArrayList<Literal>(); |
696 | for(Literal lit : reglits){ |
697 | vars.addAll(lit.getVars()); |
698 | if(!lit.getSense()){ |
699 | safeVars.addAll(lit.getVars()); |
700 | }else{ |
701 | posLits.add(lit); |
702 | } |
703 | } |
704 | for(Literal lit : posLits){ |
705 | for(Term t : lit.getTerms()){ |
706 | if(t.isVariable()){ |
707 | if(!safeVars.contains(t.var())){ |
708 | lit.getPred().setSafeRefOnly(false); |
709 | // TODO: note that order of positive lits is important |
710 | safeVars.add(t.var()); |
711 | } |
712 | } |
713 | } |
714 | } |
715 | } |
716 | |
717 | private String exprWeight = null; |
718 | |
719 | /** |
720 | * Generate the SQL command for grounding this clause. |
721 | * For each meta-variable, bind them to the clause instance (in FROM and WHERE clause) |
722 | * For each regular-predicate's variable that is not meta-variable, use |
723 | * their grounded atom table to bind the clause (in FROM CLAUSE), |
724 | * with inter-predicate constraints |
725 | * introduced in the clause (in WHERE CLAUSE / JOIN CONDITION IN FROM CLAUSE). |
726 | * For each built-in predicate, write its semantic into SQL conditions |
727 | * directly. |
728 | * |
729 | * The sqlPivotAttrsList contains a list of variables that are not |
730 | * existential variables in the form of $table_name.$column_name. Here |
731 | * $table_name is consistent to other SQL sub-clauses generated by this |
732 | * function. |
733 | */ |
734 | public void generateSQL() { |
735 | |
736 | HashMap<String, String> mapVarAttr = new HashMap<String, String>(); |
737 | ArrayList<String> groundAttrs = new ArrayList<String>(); |
738 | ArrayList<String> whereList = new ArrayList<String>(); |
739 | whereList.add("1=1"); |
740 | |
741 | StringBuilder from = new StringBuilder(); |
742 | int nJoinedTables = 0; |
743 | if(isTemplate) { |
744 | from.append(relIntanceClauses + " metaTable "); |
745 | nJoinedTables ++; |
746 | groundAttrs.add("metaTable.weight"); |
747 | for(String var : metaVars) { |
748 | mapVarAttr.put(var, "metaTable." + var); |
749 | } |
750 | exprWeight = "metaTable.weight"; |
751 | }else if(!hasEmbeddedWeight()){ |
752 | exprWeight = "CAST("+Double.toString(weight)+" AS FLOAT8)"; |
753 | } |
754 | |
755 | // reorder the literals to put antecendents first |
756 | ArrayList<Literal> ordlits = new ArrayList<Literal>(); |
757 | ArrayList<Literal> poslits = new ArrayList<Literal>(); |
758 | for(Literal lit : reglits){ |
759 | if(lit.getSense()) poslits.add(lit); |
760 | else ordlits.add(lit); |
761 | } |
762 | ordlits.addAll(poslits); |
763 | |
764 | // build variable bindings; distinguish pos lits that rely on partial materialization |
765 | |
766 | HashMap<String, Type> var2type = new HashMap<String, Type>(); |
767 | |
768 | ArrayList<String> attachConds = new ArrayList<String>(); |
769 | for(Literal lit : ordlits){ |
770 | int idx = lit.getIdx(); |
771 | String relP = lit.getPred().getRelName(); |
772 | |
773 | ArrayList<Term> terms = lit.getTerms(); |
774 | boolean hasNewVars = false; |
775 | for(int j=0; j<terms.size(); j++) { |
776 | Term t = terms.get(j); |
777 | String var = t.var(); |
778 | |
779 | var2type.put(var, lit.getPred().getTypeAt(j)); |
780 | |
781 | String attr = "t" + idx + "."+lit.getPred().getArgs().get(j); |
782 | if(t.isConstant()) { |
783 | attachConds.add(attr + "=" + t.constant()); |
784 | }else { |
785 | String cattr = mapVarAttr.get(var); |
786 | if(cattr == null) { |
787 | hasNewVars = true; |
788 | mapVarAttr.put(var, attr); |
789 | if(!existentialVars.contains(var)) { |
790 | groundAttrs.add(attr); |
791 | } |
792 | if(var.equals(varWeight)){ |
793 | exprWeight = attr; |
794 | } |
795 | }else { |
796 | attachConds.add(attr + "=" + cattr); |
797 | } |
798 | } |
799 | } |
800 | if(nJoinedTables >= 1){ |
801 | // if hasNewVars, the pred should've been fully materialized |
802 | from.append(lit.getSense() && !hasNewVars ? |
803 | " LEFT OUTER JOIN " : " JOIN "); |
804 | from.append(relP + " t" +idx + " ON "); |
805 | if(attachConds.isEmpty()){ |
806 | from.append("1=1"); |
807 | }else{ |
808 | String ac = SQLMan.andSelCond(attachConds); |
809 | from.append(ac); |
810 | attachConds.clear(); |
811 | } |
812 | }else{ |
813 | from.append(relP + " t" +idx); |
814 | } |
815 | nJoinedTables++; |
816 | } |
817 | |
818 | if(hasEmbeddedWeight() && exprWeight == null){ |
819 | UIMan.warn("The following clause is specified to have embedded weights, but the variable '" + |
820 | varWeight + "' doesn't appear in the formula:" + this.toString()); |
821 | } |
822 | |
823 | // this could happen only for unit clauses |
824 | if(!attachConds.isEmpty()){ |
825 | whereList.addAll(attachConds); |
826 | attachConds.clear(); |
827 | } |
828 | |
829 | |
830 | // express constraints in SQL |
831 | HashSet<String> cvars = new HashSet<String>(); |
832 | int nChangeName = 0; |
833 | for(Expression e : constraints){ |
834 | if(e.changeName == true){ |
835 | nChangeName ++; |
836 | } |
837 | cvars.addAll(e.getVars()); |
838 | } |
839 | HashMap<String, String> mapVarVal = new HashMap<String, String>(); |
840 | HashMap<String, String> mapVarValNotChangeName = new HashMap<String, String>(); |
841 | int idx = 0; |
842 | for(String v : cvars){ |
843 | ++ idx; |
844 | String attr = mapVarAttr.get(v); |
845 | if(attr == null){ |
846 | ExceptionMan.die("unsafe constraints in clause\n" + getSpecTextFlat()); |
847 | } |
848 | |
849 | if(nChangeName > 0){ |
850 | if(!var2type.get(v).isNonSymbolicType()){ |
851 | |
852 | from.append(" JOIN " + var2type.get(v).getRelName() + |
853 | " s" + idx + " ON s" + idx + ".constantid = " + attr); |
854 | //from.append(" JOIN " + Config.relConstants + |
855 | // " s" + idx + " ON s" + idx + ".id = " + attr); |
856 | nJoinedTables++; |
857 | } |
858 | } |
859 | if(!var2type.get(v).isNonSymbolicType()){ |
860 | mapVarVal.put(v, "s" + idx + ".constantvalue"); |
861 | }else{ |
862 | mapVarVal.put(v, attr); |
863 | } |
864 | mapVarValNotChangeName.put(v, attr); |
865 | } |
866 | for(Expression e : constraints){ |
867 | |
868 | if(e.changeName == true){ |
869 | e.bindVariables(mapVarVal); |
870 | whereList.add(e.toSQL()); |
871 | }else{ |
872 | Expression tmpE = e.clone(); |
873 | tmpE.renameVariables(mapVarValNotChangeName); |
874 | whereList.add(tmpE.toString()); |
875 | } |
876 | } |
877 | |
878 | sqlFromList = from.toString(); |
879 | sqlWhereBindings = SQLMan.andSelCond(whereList); |
880 | sqlPivotAttrsList = StringMan.commaList(groundAttrs); |
881 | /* |
882 | boolean print = false; |
883 | if(print){ |
884 | System.out.println("=====\n" + this.getSpecText()); |
885 | System.out.println(sqlFromList); |
886 | System.out.println(sqlWhereBindings); |
887 | } |
888 | */ |
889 | } |
890 | |
891 | /** |
892 | * Return the definition of this clause. |
893 | */ |
894 | public String toString() { |
895 | String s = (name == null ? "" : name); |
896 | if(Config.clause_display_multiline){ |
897 | s += "\n"; |
898 | } |
899 | if(!uNames.isEmpty()){ |
900 | s += "// " + StringMan.commaList(uNames) + "\n"; |
901 | } |
902 | String w = (weight >= Config.hard_weight ? "infty" : "" + weight); |
903 | s += (isTemplate ? "[#instances="+instances.size()+"]" : "[weight="+w+"]"); |
904 | return s + " " + (signature == null? getSpecTextFlat() : signature); |
905 | } |
906 | |
907 | /** |
908 | * Return the definition of clause instance. |
909 | * @param ni The ID of instance. |
910 | */ |
911 | public String toString(int ni) { |
912 | String s = ""; //= (name == null ? "" : name); |
913 | |
914 | String tmps = (signature == null? getSpecTextFlat() : signature); |
915 | if(ni >= 0 && !mappingFromID2Const.isEmpty()){ |
916 | for(int i=0;i<this.instances.get(ni).conList.size();i++){ |
917 | tmps = tmps.replaceAll("c"+(i), |
918 | mappingFromID2Const.get(instances.get(ni).conList.get(i))); |
919 | } |
920 | } |
921 | |
922 | tmps = tmps.replaceAll("[\n|\r]", " "); |
923 | tmps = tmps.replaceAll("sign=\'.\'", ""); |
924 | |
925 | return s + " " + tmps; |
926 | } |
927 | |
928 | /** |
929 | * Return the member literals of a particular predicate. |
930 | */ |
931 | public ArrayList<Literal> getLiteralsOfPredicate(Predicate pred){ |
932 | return predIndex.get(pred); |
933 | } |
934 | |
935 | /** |
936 | * Return the set of predicates referenced by this clause. |
937 | */ |
938 | public Set<Predicate> getReferencedPredicates(){ |
939 | return predIndex.keySet(); |
940 | } |
941 | |
942 | /** |
943 | * Return the list of non-built-in literals (i.e., regular literals). |
944 | */ |
945 | public ArrayList<Literal> getRegLiterals(){ |
946 | return reglits; |
947 | } |
948 | |
949 | /** |
950 | * Check if any variable in this clause is existentially quantified. |
951 | */ |
952 | public boolean hasExistentialQuantifiers(){ |
953 | return !existentialVars.isEmpty(); |
954 | } |
955 | |
956 | /** |
957 | * Add a literal to this clause. |
958 | */ |
959 | public void addLiteral(Literal lit){ |
960 | if(lit == null) return; |
961 | // update the predicate-literal index |
962 | ArrayList<Literal> plits = predIndex.get(lit.getPred()); |
963 | if(plits == null) { |
964 | plits = new ArrayList<Literal>(); |
965 | predIndex.put(lit.getPred(), plits); |
966 | }else{ |
967 | for(Literal elit : plits){ |
968 | // duplicate lit, ignore |
969 | if(elit.isSameAs(lit)) return; |
970 | } |
971 | } |
972 | if(lit.isBuiltIn()){ |
973 | bilits.add(lit); |
974 | lit.setIdx(1000000 + bilits.size()); |
975 | }else{ |
976 | reglits.add(lit); |
977 | lit.setIdx(reglits.size()); |
978 | } |
979 | lits.add(lit); |
980 | plits.add(lit); |
981 | |
982 | } |
983 | |
984 | /** |
985 | * Set clause ID. |
986 | * @param id |
987 | */ |
988 | public void setId(int id) { |
989 | this.id = id; |
990 | } |
991 | |
992 | /** |
993 | * Get clause ID. |
994 | */ |
995 | public int getId() { |
996 | return id; |
997 | } |
998 | |
999 | public void addSpecText(String s){ |
1000 | specText.add(s); |
1001 | } |
1002 | |
1003 | public ArrayList<String> getSpecText() { |
1004 | return specText; |
1005 | } |
1006 | |
1007 | } |