1 | package tuffy.mln; |
2 | |
3 | import java.io.BufferedWriter; |
4 | import java.io.File; |
5 | import java.io.FileInputStream; |
6 | import java.io.FileOutputStream; |
7 | import java.io.IOException; |
8 | import java.io.OutputStreamWriter; |
9 | import java.sql.ResultSet; |
10 | import java.util.ArrayList; |
11 | import java.util.HashMap; |
12 | import java.util.HashSet; |
13 | import java.util.List; |
14 | |
15 | import org.postgresql.PGConnection; |
16 | |
17 | import tuffy.db.RDB; |
18 | import tuffy.db.SQLMan; |
19 | import tuffy.util.Config; |
20 | import tuffy.util.ExceptionMan; |
21 | import tuffy.util.FileMan; |
22 | import tuffy.util.StringMan; |
23 | import tuffy.util.UIMan; |
24 | |
25 | /** |
26 | * Predicate in First Order Logic. |
27 | */ |
28 | public class Predicate { |
29 | |
30 | private boolean isCompletelySpecified = false; |
31 | |
32 | public boolean isCurrentlyView = false; |
33 | |
34 | public void setCompeletelySpecified(boolean t) { |
35 | isCompletelySpecified = t; |
36 | //if (t) |
37 | // closedWorld = true; |
38 | } |
39 | |
40 | public boolean isCompletelySepcified() { |
41 | return isCompletelySpecified; |
42 | } |
43 | |
44 | private ArrayList<Integer> dependentAttributes = new ArrayList<Integer>(); |
45 | |
46 | /** |
47 | * Set the attribute at position i to be dependent. Non-dependent attributes |
48 | * form a possible-world key. |
49 | * |
50 | * @param i |
51 | */ |
52 | public void addDependentAttrPosition(int i) { |
53 | if (!dependentAttributes.contains(i)) { |
54 | dependentAttributes.add(i); |
55 | } |
56 | } |
57 | |
58 | public ArrayList<Integer> getDependentAttrPositions() { |
59 | return dependentAttributes; |
60 | } |
61 | |
62 | public ArrayList<Integer> getKeyAttrPositions() { |
63 | ArrayList<Integer> kpos = new ArrayList<Integer>(); |
64 | for (int i = 0; i < args.size(); i++) { |
65 | if (!dependentAttributes.contains(i)) { |
66 | kpos.add(i); |
67 | } |
68 | } |
69 | return kpos; |
70 | } |
71 | |
72 | public ArrayList<Integer> getLabelAttrPositions() { |
73 | ArrayList<Integer> kpos = new ArrayList<Integer>(); |
74 | for (int i = 0; i < args.size(); i++) { |
75 | if (dependentAttributes.contains(i)) { |
76 | kpos.add(i); |
77 | } |
78 | } |
79 | return kpos; |
80 | } |
81 | |
82 | public boolean hasDependentAttributes() { |
83 | return !dependentAttributes.isEmpty(); |
84 | } |
85 | |
86 | /** |
87 | * Get attributes whose value depend on other attributes in any possible |
88 | * world. |
89 | * |
90 | * @see #getKeyAttrs |
91 | */ |
92 | public ArrayList<String> getDependentAttrs() { |
93 | ArrayList<String> dargs = new ArrayList<String>(); |
94 | for (int i : dependentAttributes) { |
95 | dargs.add(args.get(i)); |
96 | } |
97 | return dargs; |
98 | } |
99 | |
100 | /** |
101 | * Get attributes that form a possible world key. |
102 | * |
103 | * @see #getDependentAttrs |
104 | */ |
105 | public ArrayList<String> getKeyAttrs() { |
106 | ArrayList<String> dargs = new ArrayList<String>(); |
107 | for (int i = 0; i < args.size(); i++) { |
108 | if (!dependentAttributes.contains(i)) { |
109 | dargs.add(args.get(i)); |
110 | } |
111 | } |
112 | return dargs; |
113 | } |
114 | |
115 | /** |
116 | * Map from name to built-in predicates, e.g., same. |
117 | */ |
118 | private static HashMap<String, Predicate> builtInMap = new HashMap<String, Predicate>(); |
119 | |
120 | /** |
121 | * Return true if the argument is the name of a built-in predicate. |
122 | * |
123 | * @param s |
124 | * name of queried predicate |
125 | * @return true if s is a built-in predicate. |
126 | */ |
127 | public static boolean isBuiltInPredName(String s) { |
128 | return builtInMap.containsKey(s.toLowerCase()); |
129 | } |
130 | |
131 | /** |
132 | * Return the predicate object with the name as the argument string. |
133 | * |
134 | * @param s |
135 | * name of queried predicate |
136 | * @return the predicate object with name s. |
137 | */ |
138 | public static Predicate getBuiltInPredByName(String s) { |
139 | return builtInMap.get(s.toLowerCase()); |
140 | } |
141 | |
142 | // logic related fields |
143 | /** |
144 | * Name of this predicate. |
145 | */ |
146 | private String name; |
147 | |
148 | /** |
149 | * Whether this predicate obeys closed-world assumption. |
150 | */ |
151 | private boolean closedWorld = false; |
152 | |
153 | private boolean hasSoftEvidence = false; |
154 | |
155 | |
156 | public boolean isImmutable() { |
157 | return closedWorld && !hasSoftEvidence; |
158 | } |
159 | |
160 | |
161 | /** |
162 | * List of argument types of this predicate. |
163 | */ |
164 | private ArrayList<Type> types = new ArrayList<Type>(); |
165 | |
166 | /** |
167 | * TODO: if unsat then {if scope then do scope, else do cross product} |
168 | */ |
169 | private boolean safeRefOnly = true; |
170 | |
171 | /** |
172 | * Whether this predicate is a built-in predicate. |
173 | */ |
174 | private boolean isBuiltIn = false; |
175 | |
176 | // DB related fields |
177 | /** |
178 | * DB object associated with this predicate. |
179 | */ |
180 | private RDB db = null; |
181 | |
182 | /** |
183 | * Name of the table of this predicate in DB. |
184 | */ |
185 | private String relName = null; |
186 | |
187 | /** |
188 | * The list of arguments of this predicate. The K-th argument is named |
189 | * "TypeK" by default, where "Type" if the type name of this argument, |
190 | * unless explicitly named. |
191 | */ |
192 | private ArrayList<String> args = new ArrayList<String>(); |
193 | |
194 | /** |
195 | * The assigned ID for this predicate in its parent MLN |
196 | * {@link Predicate#mln}. |
197 | */ |
198 | private int id = -1; |
199 | |
200 | /** |
201 | * The file object used to load evidence to DB |
202 | */ |
203 | protected File loadingFile = null; |
204 | |
205 | /** |
206 | * The buffer writer object used to flush evidence to file |
207 | */ |
208 | protected BufferedWriter loadingFileWriter = null; |
209 | |
210 | // MLN related fields |
211 | /** |
212 | * The parent MLN containing this predicate. |
213 | */ |
214 | private MarkovLogicNetwork mln; |
215 | |
216 | /** |
217 | * Set of clauses referencing this predicate. |
218 | */ |
219 | private HashSet<Clause> iclauses = new HashSet<Clause>(); |
220 | |
221 | /** |
222 | * Set of queries referencing this predicate. |
223 | */ |
224 | private ArrayList<Atom> queries = new ArrayList<Atom>(); |
225 | |
226 | /** |
227 | * Whether all unknown atoms of this predicate are queries. |
228 | */ |
229 | private boolean isAllQuery = false; |
230 | |
231 | /** |
232 | * Return the name of relational table containing the ID of active atoms |
233 | * associated with this predicate. |
234 | */ |
235 | public String getRelAct() { |
236 | return "act_" + relName; |
237 | } |
238 | |
239 | /** |
240 | * Specify that all atoms of this predicate are queries. |
241 | */ |
242 | public void setAllQuery() { |
243 | if (isAllQuery) |
244 | return; |
245 | isAllQuery = true; |
246 | queries.clear(); |
247 | ArrayList<Integer> list = new ArrayList<Integer>(); |
248 | for (int i = 1; i <= arity(); i++) { |
249 | list.add(-i); |
250 | } |
251 | Tuple t = new Tuple(list); |
252 | Atom a = new Atom(this, t); |
253 | a.type = Atom.AtomType.QUERY; |
254 | queries.add(a); |
255 | } |
256 | |
257 | /** |
258 | * Specify whether this predicate obeys the closed world assumption. |
259 | */ |
260 | public void setClosedWorld(boolean t) { |
261 | closedWorld = t; |
262 | } |
263 | |
264 | /** |
265 | * Return the assigned ID of this predicate in its parent MLN. |
266 | */ |
267 | public int getID() { |
268 | return id; |
269 | } |
270 | |
271 | /** |
272 | * Return argument names of this predicate. The K-th argument is named |
273 | * "TypeK", where "Type" if the type name of this argument. |
274 | */ |
275 | public ArrayList<String> getArgs() { |
276 | return args; |
277 | } |
278 | |
279 | /** |
280 | * Check if we need to ground this predicate on top of its evidence. A |
281 | * predicate needs not to ground if 1) it only appears in negative literal |
282 | * and 2) it follows closed world assumption. |
283 | * |
284 | * @return true if the closed world assumption is made on this predicate, |
285 | * and all literals of this predicate are negative |
286 | */ |
287 | public boolean noNeedToGround() { |
288 | return (safeRefOnly && closedWorld) || isCompletelySpecified; |
289 | } |
290 | |
291 | /** |
292 | * Assign an ID for this predicate. This predicate ID is used to encode |
293 | * tuple IDs of this predicate. |
294 | */ |
295 | public void setID(int aid) { |
296 | id = aid; |
297 | } |
298 | |
299 | /** |
300 | * Return query atoms of this predicate. Used by KBMC. |
301 | */ |
302 | public ArrayList<Atom> getQueryAtoms() { |
303 | return queries; |
304 | } |
305 | |
306 | /** |
307 | * Return clauses referencing this predicate. |
308 | */ |
309 | public HashSet<Clause> getRelatedClauses() { |
310 | return iclauses; |
311 | } |
312 | |
313 | /** |
314 | * Register a query atom. |
315 | * |
316 | * @param q |
317 | * the query atom; could contain variables |
318 | * @see Predicate#storeQueries() |
319 | */ |
320 | public void addQuery(Atom q) { |
321 | if (isAllQuery) |
322 | return; |
323 | queries.add(q); |
324 | } |
325 | |
326 | /** |
327 | * Ground query atoms and store the result in the database. |
328 | */ |
329 | public void storeQueries() { |
330 | for (Atom a : queries) { |
331 | groundAndStoreAtom(a); |
332 | } |
333 | } |
334 | |
335 | public boolean hasEvid = false; |
336 | |
337 | /** |
338 | * Ground an atom and store the result in the database. Repetitive |
339 | * invocations of this method could be expensive, since it involves both |
340 | * updates and inserts to the predicate table. |
341 | * |
342 | * First, for the grounded tuples satisfying this atom $a$ and already |
343 | * existing in database, it only update its club values. If this $a$ is |
344 | * query, then add query to club (0->1, 2->3). If this $a$ is evidence, then |
345 | * add evidence to club (0->2, 1->3). |
346 | * |
347 | * Then, for the grounded tuples satisfying this atom $a$ and not existing |
348 | * in database, it 1) select them with a $arity-way join in corresponding |
349 | * type instance table; 2) left join them with current version database; 3) |
350 | * select those not matching with any existing tuples; and 4) insert into |
351 | * the database. |
352 | * |
353 | */ |
354 | public void groundAndStoreAtom(Atom a) { |
355 | // input must be query or KBMC result |
356 | assert (a.club() >= 0 && a.club() <= 2); |
357 | // update. a is query/evidence |
358 | if (a.club() == 1 || a.club() == 2) { |
359 | String sql = ""; |
360 | ArrayList<String> conds = new ArrayList<String>(); |
361 | if (a.club() == 1) { // query |
362 | sql = "UPDATE " + getRelName() + " SET " |
363 | + "truth = truth, club = " + // not change the truthvalue |
364 | "club + 1 " + " WHERE "; |
365 | conds.add("(club = 0 OR club = 2)"); // turn none->query, |
366 | // evidence->query evidence |
367 | } else if (a.club() == 2) { // evidence |
368 | sql = "UPDATE " + getRelName() + " SET " + "truth = '" |
369 | + (a.truth ? 1 : 0) + "', club = " + // override the |
370 | // truth values. |
371 | "club + 2 " + " WHERE "; |
372 | conds.add("(club = 0 OR club = 1)"); // turn none->evidence, |
373 | // query->query evidence |
374 | } |
375 | int[] firstOccur = new int[a.args.dimension + 1]; |
376 | for (int i = 0; i < args.size(); i++) { |
377 | int v = a.args.get(i); // name of i-th element |
378 | if (v > 0) { // constant |
379 | conds.add(args.get(i) + "=" + v); // <i-th name> = v |
380 | } else { // variable |
381 | if (firstOccur[-v] == 0) { |
382 | firstOccur[-v] = i + 1; |
383 | } else { |
384 | int f = firstOccur[-v] - 1; |
385 | conds.add(args.get(i) + "=" + args.get(f)); // <i-th |
386 | // variable> |
387 | // = <f-th |
388 | // variable> |
389 | } |
390 | } |
391 | } |
392 | sql += SQLMan.andSelCond(conds); |
393 | // System.out.println(sql); |
394 | db.update(sql); |
395 | // System.out.println(" updated " + DB.lastUpdateRowCount); |
396 | } |
397 | |
398 | // insert here, queries are not grounded because it has been grounded as NONE/EVIDENCE. |
399 | if (!mln.isScoped(this) && !noNeedToGround() && a.club() != 1) { |
400 | String sql = "INSERT INTO " + getRelName() |
401 | + "(truth,prior,club," + StringMan.commaList(args) |
402 | + ")\n"; |
403 | ArrayList<String> selInsert = new ArrayList<String>(); |
404 | ArrayList<String> selNT = new ArrayList<String>(); |
405 | ArrayList<String> condMatch = new ArrayList<String>(); |
406 | ArrayList<String> condNull = new ArrayList<String>(); |
407 | ArrayList<String> baseTables = new ArrayList<String>(); |
408 | |
409 | // ////////////////////////////////// |
410 | //selInsert.add(nextTupleID()); |
411 | selInsert.add(a.truth == null ? "NULL" : "'" + (a.truth ? 1 : 0) |
412 | + "'"); |
413 | selInsert.add(a.prior != null ? Double.toString(a.prior) : "NULL"); |
414 | selInsert.add(Integer.toString(a.club())); |
415 | |
416 | int[] firstOccur = new int[a.args.dimension + 1]; |
417 | for (int i = 0; i < types.size(); i++) { |
418 | Type t = types.get(i); |
419 | int v = a.args.get(i); |
420 | String sNT; |
421 | if (v > 0) { // constant |
422 | sNT = "" + v; |
423 | } else { // variable |
424 | if (firstOccur[-v] == 0) { |
425 | firstOccur[-v] = i + 1; |
426 | sNT = "t" + i + ".constantID"; |
427 | baseTables.add(t.getRelName() + " t" + i); |
428 | } else { |
429 | int f = firstOccur[-v] - 1; |
430 | sNT = "t" + f + ".constantID"; |
431 | } |
432 | } |
433 | sNT += " AS " + args.get(i); |
434 | selNT.add(sNT); |
435 | selInsert.add("nt." + args.get(i)); |
436 | condMatch.add("nt." + args.get(i) + "=ot." + args.get(i)); |
437 | condNull.add("ot." + args.get(i) + " IS NULL"); |
438 | } |
439 | |
440 | sql += "SELECT " + StringMan.commaList(selInsert) + " FROM \n" |
441 | + "(SELECT " + StringMan.commaList(selNT) |
442 | + (baseTables.isEmpty() ? "" : " FROM ") |
443 | + StringMan.join(" CROSS JOIN ", baseTables) + ") nt \n" |
444 | + "LEFT JOIN\n" + getRelName() + " ot ON (\n" |
445 | + SQLMan.andSelCond(condMatch) + ") " + "WHERE " |
446 | + SQLMan.andSelCond(condNull); |
447 | |
448 | // System.out.println(sql); |
449 | db.update(sql); |
450 | // System.out.println(DB.lastUpdateRowCount); |
451 | } |
452 | } |
453 | |
454 | /** |
455 | * Store an evidence in the "buffer". There is a buffer (in the form of a |
456 | * CSV file) for each predicate that holds the DB tuple formats of its |
457 | * evidence; this buffer will be flushed into the database once all evidence |
458 | * has been read. |
459 | * |
460 | * @param a |
461 | * the evidence; following Alchemy, it must be a ground atom |
462 | * @see Predicate#flushEvidence() |
463 | */ |
464 | public void addEvidence(Atom a) { |
465 | |
466 | hasEvid = true; |
467 | |
468 | if (a.isSoftEvidence()) |
469 | setHasSoftEvidence(true); |
470 | addEvidenceTuple(a); |
471 | } |
472 | |
473 | /** |
474 | * Determine whether this predicate can ground more atoms. This predicate |
475 | * does not have more to ground if the number of unknown grounds (none and |
476 | * query) in this predicate is smaller than the number of active atoms of |
477 | * this predicate. I.e., all the unknown atoms of this predicate is |
478 | * activated. |
479 | * |
480 | * @return whether there are more groundings can be generated |
481 | */ |
482 | public boolean hasMoreToGround() { |
483 | try { |
484 | String sql = "SELECT COUNT(*) from " + relName + " where club < 2"; |
485 | ResultSet rs = db.query(sql); |
486 | rs.next(); |
487 | int unknown = rs.getInt(1); |
488 | sql = "SELECT COUNT(*) FROM " + getRelAct(); |
489 | rs = db.query(sql); |
490 | rs.next(); |
491 | int active = rs.getInt(1); |
492 | if (unknown <= active) |
493 | return false; |
494 | } catch (Exception e) { |
495 | ExceptionMan.handle(e); |
496 | } |
497 | return true; |
498 | } |
499 | |
500 | public void appendToWriter(String str) throws IOException{ |
501 | this.loadingFileWriter.append(str); |
502 | } |
503 | |
504 | /** |
505 | * Add evidence tuple related to this predicate. The output of this function |
506 | * is written to file in format like: |
507 | * |
508 | * $tuple_id,$truth_value,$prior,$club_value,$variable_name,...... |
509 | * |
510 | * @param a |
511 | * the atom as evidence. This atom need to be a grounded atom. |
512 | */ |
513 | protected void addEvidenceTuple(Atom a) { |
514 | ArrayList<String> parts = new ArrayList<String>(); |
515 | if (a.truth == null) { |
516 | parts.add("TRUE"); |
517 | parts.add(a.prior != null ? Double.toString(a.prior) : "\\N"); |
518 | } else { |
519 | parts.add(a.truth ? "TRUE" : "FALSE"); |
520 | parts.add(""); |
521 | } |
522 | parts.add(Integer.toString(a.club())); |
523 | parts.addAll(a.sargs); |
524 | |
525 | try { |
526 | loadingFileWriter.append(StringMan.join(",", parts) + "\n"); |
527 | } catch (IOException e) { |
528 | ExceptionMan.handle(e); |
529 | } |
530 | } |
531 | |
532 | /** |
533 | * Close all file handles. |
534 | */ |
535 | public void closeFiles() { |
536 | try { |
537 | if (loadingFileWriter != null) { |
538 | loadingFileWriter.close(); |
539 | loadingFileWriter = null; |
540 | } |
541 | } catch (IOException e) { |
542 | e.printStackTrace(); |
543 | } |
544 | } |
545 | |
546 | /** |
547 | * Flush the evidence buffer to the predicate table, using the COPY |
548 | * statement in PostgreSQL. |
549 | * |
550 | * @see Predicate#addEvidence(Atom) |
551 | */ |
552 | public void flushEvidence(boolean... specialMode) { |
553 | try { |
554 | |
555 | if(this.hasEvid == true && specialMode.length>0){ |
556 | String sql = "DELETE FROM " + getRelName(); |
557 | db.execute(sql); |
558 | } |
559 | |
560 | // flush the file |
561 | loadingFileWriter.close(); |
562 | loadingFileWriter = null; |
563 | // copy into DB |
564 | ArrayList<String> cols = new ArrayList<String>(); |
565 | cols.add("truth"); |
566 | cols.add("prior"); |
567 | cols.add("club"); |
568 | cols.addAll(args); |
569 | FileInputStream in = new FileInputStream(loadingFile); |
570 | PGConnection con = (PGConnection) db.getConnection(); |
571 | String sql = "COPY " + getRelName() + |
572 | StringMan.commaListParen(cols) + " FROM STDIN CSV"; |
573 | con.getCopyAPI().copyIn(sql, in); |
574 | in.close(); |
575 | db.analyze(getRelName()); |
576 | FileMan.removeFile(loadingFile.getAbsolutePath()); |
577 | } catch (Exception e) { |
578 | ExceptionMan.handle(e); |
579 | } |
580 | } |
581 | |
582 | /** |
583 | * Constructor of Predicate. |
584 | * |
585 | * @param mln |
586 | * the parent MLN that hosts this predicate |
587 | * @param aname |
588 | * the name; must be unique |
589 | * @param aClosedWorld |
590 | * indicates whether to make the closed-world asssumption |
591 | */ |
592 | public Predicate(MarkovLogicNetwork mln, String aname, boolean aClosedWorld) { |
593 | if (aname.startsWith("_")) { |
594 | UIMan.warn("Predicate names starting with an underscore " |
595 | + "are reserved for built-in predicates. " + "Declaring '" |
596 | + aname + "' as user-defined predicates " |
597 | + "may override built-in predicates."); |
598 | } |
599 | this.mln = mln; |
600 | name = aname; |
601 | closedWorld = aClosedWorld; |
602 | relName = "pred_" + name; |
603 | } |
604 | |
605 | /** |
606 | * Checks if there are any queries associated with this predicate. |
607 | */ |
608 | public boolean hasQuery() { |
609 | return !queries.isEmpty(); |
610 | } |
611 | |
612 | public void setDB(RDB adb){ |
613 | db = adb; |
614 | } |
615 | |
616 | |
617 | /** |
618 | * Initialize database objects for this predicate. |
619 | */ |
620 | public void prepareDB(RDB adb) { |
621 | db = adb; |
622 | loadingFile = new File(Config.getLoadingDir(), "loading_" + getName()); |
623 | try { |
624 | loadingFileWriter = new BufferedWriter(new OutputStreamWriter( |
625 | new FileOutputStream(loadingFile), "UTF8")); |
626 | } catch (Exception e) { |
627 | ExceptionMan.handle(e); |
628 | } |
629 | String sql; |
630 | |
631 | createTable(); |
632 | } |
633 | |
634 | /** |
635 | * Create table for storing groundings of this predicate. club: 1 = NONE; 2 |
636 | * = EVIDENCE: atom in evidence; 3 = QUERY: atom in query; 4 = QUEVID: atom |
637 | * in query as evidence. |
638 | */ |
639 | public void createTable(String... isTemplate) { |
640 | db.dropTable(relName); |
641 | db.dropView(relName); |
642 | String sql; |
643 | if(isTemplate.length > 0){ |
644 | sql = "CREATE TEMPORARY TABLE " + getRelName() + "(\n"; |
645 | }else{ |
646 | sql = "CREATE TABLE " + getRelName() + "(\n"; |
647 | } |
648 | sql += "id bigserial PRIMARY KEY,\n"; |
649 | sql += "truth BOOL,\n"; |
650 | sql += "prior FLOAT DEFAULT 0.9,\n"; |
651 | sql += "club INT DEFAULT 0,\n"; |
652 | sql += "atomID INT DEFAULT NULL,\n"; |
653 | ArrayList<String> argDefs = new ArrayList<String>(); |
654 | for (int i = 0; i < args.size(); i++) { |
655 | String attr = args.get(i); |
656 | Type t = types.get(i); |
657 | String ts = " bigint"; |
658 | if (t.isNonSymbolicType()) { |
659 | // TODO: fix expression variable binding |
660 | Type tn = t.getNonSymbolicType(); |
661 | if (tn == Type.Float) { |
662 | ts = " FLOAT8"; |
663 | } else if (tn == Type.Integer) { |
664 | ts = " bigint"; |
665 | } |
666 | } |
667 | argDefs.add(attr + ts); |
668 | } |
669 | sql += StringMan.commaList(argDefs) + ")"; |
670 | db.update(sql); |
671 | //sql = "CREATE INDEX " + getRelName() + "_truth_idx on " + getRelName() |
672 | //+ "(truth)"; |
673 | //db.update(sql); |
674 | |
675 | if (Config.build_predicate_table_indexes) { |
676 | for (String a : args) { |
677 | sql = "CREATE INDEX idx_" + id + "_" + a + " ON " + relName |
678 | + "(" + a + ")"; |
679 | db.update(sql); |
680 | } |
681 | } |
682 | |
683 | } |
684 | |
685 | /** |
686 | * Return the arity of this predicate. |
687 | */ |
688 | public int arity() { |
689 | return args.size(); |
690 | } |
691 | |
692 | /** |
693 | * Register a clause referencing this predicate |
694 | * |
695 | * @param c |
696 | * a clause referencing this predicate |
697 | */ |
698 | public void addRelatedClause(Clause c) { |
699 | iclauses.add(c); |
700 | } |
701 | |
702 | /** |
703 | * Append a new argument without a user-provided name. |
704 | * |
705 | * @param t |
706 | * the type of the new argument |
707 | */ |
708 | public void appendArgument(Type t) { |
709 | types.add(t); |
710 | args.add(t.name() + types.size()); |
711 | argNameList.add(null); |
712 | } |
713 | |
714 | private HashMap<String, Integer> argNameMap = new HashMap<String, Integer>(); |
715 | private ArrayList<String> argNameList = new ArrayList<String>(); |
716 | |
717 | /** |
718 | * Append a new argument with a user provided name. |
719 | * |
720 | * @param t |
721 | * the type of the new argument |
722 | * @param name |
723 | * user-provided name for this argument/attribute |
724 | */ |
725 | public void appendArgument(Type t, String name) { |
726 | types.add(t); |
727 | args.add(t.name() + types.size()); |
728 | if (argNameMap.containsKey(name)) { |
729 | ExceptionMan.die("duplicate argument name '" + name |
730 | + "' in predicate '" + this.name + "'"); |
731 | } else if (name != null) { |
732 | argNameList.add(name); |
733 | argNameMap.put(name, args.size() - 1); |
734 | } |
735 | } |
736 | |
737 | /** |
738 | * Return the position of the given argument name. |
739 | * |
740 | * @param aname |
741 | * argument name |
742 | */ |
743 | public int getArgPositionByName(String aname) { |
744 | if (!argNameMap.containsKey(aname)) |
745 | return -1; |
746 | return argNameMap.get(aname); |
747 | } |
748 | |
749 | /** |
750 | * Mark the point when all arguments have been given. Go through the |
751 | * arguments again to try to give unnamed arguments names. |
752 | */ |
753 | public void sealDefinition() { |
754 | HashSet<Type> tset = new HashSet<Type>(); |
755 | HashSet<Type> dset = new HashSet<Type>(); |
756 | for (Type t : types) { |
757 | if (tset.contains(t)) |
758 | dset.add(t); |
759 | tset.add(t); |
760 | } |
761 | tset.removeAll(dset); |
762 | for (int i = 0; i < argNameList.size(); i++) { |
763 | if (argNameList.get(i) == null) { |
764 | Type t = types.get(i); |
765 | String tn = t.name(); |
766 | if (tset.contains(t) && !argNameMap.containsKey(tn)) { |
767 | argNameList.set(i, tn); |
768 | argNameMap.put(tn, i); |
769 | } |
770 | } |
771 | } |
772 | } |
773 | |
774 | /** |
775 | * TODO: look into the implications of FDs |
776 | * |
777 | */ |
778 | @SuppressWarnings("unused") |
779 | private class FunctionalDependency { |
780 | HashSet<Integer> determinant = null; |
781 | public int dependent = -1; |
782 | } |
783 | |
784 | /** |
785 | * Functional dependencies for this predicate. |
786 | */ |
787 | private ArrayList<FunctionalDependency> fds = new ArrayList<FunctionalDependency>(); |
788 | |
789 | public void setMLN(MarkovLogicNetwork _mln){ |
790 | mln = _mln; |
791 | } |
792 | |
793 | /** |
794 | * Add a functional dependency for the attributes of this predicate |
795 | * |
796 | * @param determinant |
797 | * @param dependent |
798 | */ |
799 | public void addFunctionalDependency(List<String> determinant, |
800 | String dependent) { |
801 | HashSet<Integer> det = new HashSet<Integer>(); |
802 | for (String s : determinant) { |
803 | int idx = argNameMap.get(s); |
804 | if (idx < 0) { |
805 | ExceptionMan.die("unknown attribute name '" + s |
806 | + "' for predicate '" + name + "' when defining " |
807 | + "functional dependency."); |
808 | } else { |
809 | det.add(idx); |
810 | } |
811 | } |
812 | int dep = argNameMap.get(dependent); |
813 | if (dep < 0) { |
814 | ExceptionMan.die("unknown attribute name '" + dependent |
815 | + "' for predicate '" + name + "' when defining " |
816 | + "functional dependency."); |
817 | } |
818 | FunctionalDependency fd = new FunctionalDependency(); |
819 | fd.dependent = dep; |
820 | fd.determinant = det; |
821 | fds.add(fd); |
822 | } |
823 | |
824 | /** |
825 | * Return the type of the k-th argument. |
826 | */ |
827 | public Type getTypeAt(int k) { |
828 | return types.get(k); |
829 | } |
830 | |
831 | /** |
832 | * Check if this predicate makes the closed-world assumption. |
833 | */ |
834 | public boolean isClosedWorld() { |
835 | return closedWorld; |
836 | } |
837 | |
838 | /** |
839 | * Return the name of this predicate. |
840 | */ |
841 | public String getName() { |
842 | return name; |
843 | } |
844 | |
845 | /** |
846 | * Return the relational table name of this predicate.. |
847 | */ |
848 | public String getRelName() { |
849 | return relName; |
850 | } |
851 | |
852 | /** |
853 | * Set whether all references to this predicate are safe; i.e., all |
854 | * variables in corresponding positive literals are bound to other literals |
855 | * in the same clause. |
856 | * |
857 | * @param safeRefOnly |
858 | */ |
859 | public void setSafeRefOnly(boolean safeRefOnly) { |
860 | this.safeRefOnly = safeRefOnly; |
861 | } |
862 | |
863 | public boolean isSafeRefOnly() { |
864 | return safeRefOnly; |
865 | } |
866 | |
867 | public void setHasSoftEvidence(boolean hasSoftEvidence) { |
868 | this.hasSoftEvidence = hasSoftEvidence; |
869 | } |
870 | |
871 | public boolean hasSoftEvidence() { |
872 | return hasSoftEvidence; |
873 | } |
874 | |
875 | public boolean isBuiltIn() { |
876 | return isBuiltIn; |
877 | } |
878 | |
879 | } |