1 | package felix.operator; |
2 | |
3 | import java.io.BufferedReader; |
4 | import java.io.BufferedWriter; |
5 | import java.io.FileInputStream; |
6 | import java.io.FileWriter; |
7 | import java.io.InputStream; |
8 | import java.io.InputStreamReader; |
9 | import java.sql.Array; |
10 | import java.util.ArrayList; |
11 | import java.util.Arrays; |
12 | import java.util.Date; |
13 | import java.util.HashMap; |
14 | import java.util.HashSet; |
15 | |
16 | import org.apache.commons.collections.Bag; |
17 | import org.apache.commons.collections.bag.HashBag; |
18 | import org.postgresql.PGConnection; |
19 | |
20 | |
21 | import tuffy.db.RDB; |
22 | import tuffy.mln.Literal; |
23 | import tuffy.ra.ConjunctiveQuery; |
24 | import tuffy.util.Config; |
25 | import tuffy.util.ExceptionMan; |
26 | import tuffy.util.FileMan; |
27 | import tuffy.util.StringMan; |
28 | import tuffy.util.Timer; |
29 | import tuffy.util.UIMan; |
30 | import felix.dstruct.DataMovementOperator; |
31 | import felix.dstruct.FelixPredicate; |
32 | import felix.dstruct.FelixQuery; |
33 | import felix.dstruct.StatOperator; |
34 | import felix.dstruct.FelixPredicate.FPProperty; |
35 | import felix.parser.FelixCommandOptions; |
36 | import felix.util.FelixConfig; |
37 | import felix.util.FelixStringMan; |
38 | import felix.util.FelixUIMan; |
39 | |
40 | /** |
41 | * A LR operator in Felix. |
42 | * @author Ce Zhang |
43 | * |
44 | */ |
45 | public class LROperator extends StatOperator{ |
46 | |
47 | /** |
48 | * Target predicate of this LR operator. |
49 | */ |
50 | FelixPredicate lrHead; |
51 | |
52 | |
53 | /** |
54 | * Mapping from label's constant ID to a new label ID. This new label ID is from 0, which |
55 | * is used in the inference of LR (s.t., we can use array to represent |
56 | * labels). |
57 | */ |
58 | HashMap<String, Integer> label2ID = new HashMap<String, Integer>(); |
59 | |
60 | /** |
61 | * The inverse map of {@link LROperator#label2ID}. |
62 | */ |
63 | HashMap<Integer, String[]> id2Label = new HashMap<Integer, String[]>(); |
64 | |
65 | /** |
66 | * All DataMovementOperators used as LR rules. |
67 | */ |
68 | ArrayList<DataMovementOperator> lrDMOs = new ArrayList<DataMovementOperator>(); |
69 | |
70 | /** |
71 | * The DataMovementOperator which is the union of all LR rules. |
72 | */ |
73 | DataMovementOperator lrDMO = null; |
74 | |
75 | /** |
76 | * The DataMovementOperator representing the table/view for all unigram features. |
77 | */ |
78 | DataMovementOperator unigramDMO = null; |
79 | |
80 | /** |
81 | * The DataMovementOperator representing the table/view for all possible labels. |
82 | */ |
83 | DataMovementOperator labelDomainDMO = null; |
84 | |
85 | /** |
86 | * The DataMovementOperator fetching all unigram features. |
87 | */ |
88 | DataMovementOperator getAllUnigramFeaturesDMO = null; |
89 | |
90 | /** |
91 | * @deprecated |
92 | */ |
93 | DataMovementOperator getAllLearningFeatureDMO = null; |
94 | |
95 | /** |
96 | * @deprecated |
97 | */ |
98 | DataMovementOperator getGroundTruthDMO = null; |
99 | |
100 | /** |
101 | * @deprecated |
102 | */ |
103 | DataMovementOperator getFeatureTableDMO = null; |
104 | |
105 | /** |
106 | * The constructor of LROperator. |
107 | * @param _fq Felix query. |
108 | * @param _goalPredicates target predicates of this coref operator. |
109 | * @param _opt Command line options of this Felix run. |
110 | */ |
111 | public LROperator(FelixQuery _fq, HashSet<FelixPredicate> _goalPredicates, |
112 | FelixCommandOptions _opt) { |
113 | super(_fq, _goalPredicates, _opt); |
114 | this.type = OPType.LR; |
115 | this.precedence = 4; |
116 | |
117 | } |
118 | |
119 | /** |
120 | * Adds object to domain if necessary. |
121 | * @param obj |
122 | * @param map |
123 | * @return |
124 | */ |
125 | public <E> Integer getAndAddToDomain(E obj, HashMap<String, Integer> map){ |
126 | int rs; |
127 | if(map.containsKey(obj.toString())){ |
128 | return map.get(obj.toString()); |
129 | } |
130 | rs = map.size() + 1; |
131 | map.put(obj.toString(), rs); |
132 | return rs; |
133 | } |
134 | |
135 | /** |
136 | * @deprecated |
137 | */ |
138 | @Override |
139 | public void learn() { |
140 | |
141 | } |
142 | |
143 | |
144 | boolean prepared = false; |
145 | |
146 | /** |
147 | * Prepares operator for execution. |
148 | */ |
149 | @Override |
150 | public void prepare() { |
151 | |
152 | lrDMOs.clear(); |
153 | allDMOs.clear(); |
154 | |
155 | //if(!prepared){ |
156 | |
157 | db = RDB.getRDBbyConfig(Config.db_schema); |
158 | |
159 | lrHead = this.getTargetPredicateIfHasOnlyOne(); |
160 | |
161 | HashSet<ConjunctiveQuery> lrQueries; |
162 | |
163 | if(options.isDLearningMode){ |
164 | //lrQueries = |
165 | // this.translateFelixClasesIntoLearningQueriesForVictor(lrHead, FPProperty.NON_RECUR); |
166 | //this.prepareDMO4ForLearning(lrQueries); |
167 | |
168 | }else{ |
169 | lrQueries = |
170 | this.translateFelixClasesIntoFactorGraphEdgeQueries(lrHead, false, this.inputPredicateScope, FPProperty.NON_RECUR); |
171 | this.prepareDMO(lrQueries); |
172 | } |
173 | |
174 | prepared = true; |
175 | |
176 | //db.close(); |
177 | |
178 | //} |
179 | |
180 | } |
181 | |
182 | |
183 | /** |
184 | * Returns string representation of string array. |
185 | * @param _array |
186 | * @return |
187 | */ |
188 | String array2str(String[] _array){ |
189 | String ret = ""; |
190 | for(String s : _array){ |
191 | ret = ret + ":" + s; |
192 | } |
193 | return ret; |
194 | } |
195 | |
196 | /** |
197 | * Executes operator. |
198 | */ |
199 | @Override |
200 | public void run() { |
201 | |
202 | if(options.isDLearningMode){ |
203 | this.learn(); |
204 | }else{ |
205 | |
206 | this.isMarginal = belongsToBucket.isMarginal(); |
207 | |
208 | UIMan.println(">>> Start Running " + this); |
209 | |
210 | ArrayList<Integer> nonZeroWeights = new ArrayList<Integer>(); |
211 | |
212 | try{ |
213 | |
214 | Timer.start("LR-Operator-" + lrHead.getName()); |
215 | |
216 | BufferedWriter bw = new BufferedWriter( |
217 | new FileWriter(Config.getLoadingDir() + "/_loading_lr_" + lrHead.getName() + this.getId())); |
218 | |
219 | // get the domain of Label, and assign them an integer ID (start from 0, s.t., we |
220 | // can use array to store them |
221 | int labelID = 0; |
222 | int nLabelFileds = lrHead.getLabelFieldsArgs().size(); |
223 | if(this.labelDomainDMO != null){ |
224 | this.labelDomainDMO.execute(null, new ArrayList<Integer>()); |
225 | while(this.labelDomainDMO.next()){ |
226 | String[] currLabel = new String[nLabelFileds]; |
227 | |
228 | for(int i=0;i<nLabelFileds;i++){ |
229 | currLabel[i] = this.labelDomainDMO.getNext(i+1).toString(); |
230 | } |
231 | |
232 | this.id2Label.put(labelID, currLabel); |
233 | this.label2ID.put(array2str(currLabel), labelID); |
234 | labelID++; |
235 | } |
236 | } |
237 | |
238 | |
239 | if(this.id2Label.size() == 0 && this.isBinaryArbLR == false){ |
240 | return; |
241 | } |
242 | |
243 | if(this.unigramDMO == null){ |
244 | UIMan.warn("There are no LR-rules for predicate " + lrHead.getName()); |
245 | return; |
246 | } |
247 | |
248 | int ct = 0; |
249 | |
250 | String workingSignature = null; |
251 | this.getAllUnigramFeaturesDMO.db.disableAutoCommitForNow(); |
252 | this.getAllUnigramFeaturesDMO.execute(null, new ArrayList<Integer>()); |
253 | double[] weights; |
254 | |
255 | if(this.isBinaryArbLR == false){ |
256 | weights = new double[this.label2ID.keySet().size()]; |
257 | }else{ |
258 | weights = new double[1]; |
259 | } |
260 | |
261 | String[] currLabel = new String[lrHead.getLabelFieldsArgs().size()]; |
262 | |
263 | while(this.getAllUnigramFeaturesDMO.next()){ |
264 | |
265 | String currSignature = ""; |
266 | int lct = 0; |
267 | |
268 | ArrayList<String> toSig = new ArrayList<String>(); |
269 | if(this.isBinaryArbLR == false){ |
270 | for(int i=0;i<lrHead.arity();i++){ |
271 | if(lrHead.getLabelPositions().contains(i)){ |
272 | currLabel[lct++] = this.getAllUnigramFeaturesDMO.getNext(i+1).toString(); |
273 | toSig.add("%s"); |
274 | }else{ |
275 | toSig.add(this.getAllUnigramFeaturesDMO.getNext(i+1).toString()); |
276 | |
277 | //if(this.getAllUnigramFeaturesDMO.getNext(i+1).toString().equals("264014")){ |
278 | // System.out.println(); |
279 | //} |
280 | } |
281 | } |
282 | }else{ |
283 | for(int i=0;i<lrHead.arity();i++){ |
284 | toSig.add(this.getAllUnigramFeaturesDMO.getNext(i+1).toString()); |
285 | } |
286 | } |
287 | |
288 | currSignature = StringMan.commaList(toSig); |
289 | Double weight = this.getAllUnigramFeaturesDMO.getNextDouble(lrHead.arity()+1); |
290 | |
291 | if(! currSignature.equals(workingSignature)){ |
292 | //dump and init. |
293 | if(workingSignature != null){ |
294 | if(ct % 10000 == 0){ |
295 | UIMan.verbose(3, "" + (ct++)); |
296 | } |
297 | this.inferAndDumpToTmpFile(workingSignature, weights, nonZeroWeights, bw); |
298 | for(int i : nonZeroWeights){ |
299 | weights[i] = 0; |
300 | } |
301 | nonZeroWeights.clear(); |
302 | } |
303 | workingSignature = currSignature; |
304 | } |
305 | |
306 | if(this.isBinaryArbLR == false){ |
307 | /* |
308 | System.out.println("! " + array2str(currLabel) + ""); |
309 | System.out.println("@ " + this.label2ID.get(array2str(currLabel)) + ""); |
310 | System.out.println("# " + weight + "\n\n"); |
311 | */ |
312 | currLabel = currLabel; |
313 | array2str(currLabel); |
314 | int wlen = this.label2ID.get(array2str(currLabel)); |
315 | //System.out.print(wlen + " | "); |
316 | //System.out.print(weights.length + " | "); |
317 | //System.out.println(weight); |
318 | weights[this.label2ID.get(array2str(currLabel))] += weight; |
319 | nonZeroWeights.add(this.label2ID.get(array2str(currLabel))); |
320 | }else{ |
321 | weights[0] += weight; |
322 | } |
323 | } |
324 | if(workingSignature != null){ |
325 | this.inferAndDumpToTmpFile(workingSignature, weights, nonZeroWeights, bw); |
326 | } |
327 | |
328 | |
329 | this.getAllUnigramFeaturesDMO.db.setAutoCommit(true); |
330 | bw.close(); |
331 | |
332 | //System.out.println("~\t" + Timer.elapsed("LR-Operator-" + lrHead.getName())); |
333 | |
334 | //UIMan.print("Start COPY..."); |
335 | FileInputStream in = new FileInputStream(Config.getLoadingDir() + "/_loading_lr_" + lrHead.getName() + this.getId()); |
336 | PGConnection con = (PGConnection)db.getConnection(); |
337 | |
338 | String sql; |
339 | |
340 | if(options.useDualDecomposition){ |
341 | for(FelixPredicate fp : this.dd_CommonOutput){ |
342 | if(!fp.getName().equals(this.lrHead.getName())){ |
343 | ExceptionMan.die("LR 583: There must be something wrong with the parser!"); |
344 | continue; |
345 | } |
346 | |
347 | in = new FileInputStream(Config.getLoadingDir() + |
348 | "/_loading_lr_" + lrHead.getName() + this.getId()); |
349 | String tableName = this.dd_commonOutputPredicate_2_tableName.get(fp); |
350 | |
351 | |
352 | sql = "COPY " + tableName + "(truth, prior, club, " + StringMan.commaList(lrHead.getArgs()) + " ) FROM STDIN CSV"; |
353 | con.getCopyAPI().copyIn(sql, in); |
354 | in.close(); |
355 | |
356 | } |
357 | |
358 | if(FelixConfig.isFirstRunOfDD){ |
359 | in = new FileInputStream(Config.getLoadingDir() + |
360 | "/_loading_lr_" + lrHead.getName() + this.getId()); |
361 | |
362 | sql = "COPY " + lrHead.getRelName() + "(truth, prior, club, " + StringMan.commaList(lrHead.getArgs()) + " ) FROM STDIN CSV"; |
363 | con.getCopyAPI().copyIn(sql, in); |
364 | in.close(); |
365 | } |
366 | lrHead.isCurrentlyView = false; |
367 | |
368 | }else{ |
369 | sql = "COPY " + lrHead.getRelName() + "(truth, prior, club, " + StringMan.commaList(lrHead.getArgs()) + " ) FROM STDIN CSV"; |
370 | con.getCopyAPI().copyIn(sql, in); |
371 | in.close(); |
372 | lrHead.isCurrentlyView = false; |
373 | } |
374 | |
375 | FelixUIMan.println(0,0,"\n>>> {" + this + "} uses " + Timer.elapsed("LR-Operator-" + lrHead.getName())); |
376 | |
377 | // for(FelixPredicate fp : this.outputPredicates){ |
378 | // fp.setClosedWorld(true); |
379 | // } |
380 | |
381 | lrHead.setHasSoftEvidence(true); |
382 | |
383 | |
384 | this.db.close(); |
385 | |
386 | if(!options.useDualDecomposition){ |
387 | this.belongsToBucket.runNextOperatorInBucket(); |
388 | } |
389 | |
390 | |
391 | return; |
392 | |
393 | }catch(Exception e){ |
394 | e.printStackTrace(); |
395 | } |
396 | } |
397 | |
398 | } |
399 | |
400 | @Override |
401 | public String explain() { |
402 | |
403 | return null; |
404 | } |
405 | |
406 | /** |
407 | * Generate Data Movement Operator used by this LR Operator. |
408 | * @param rules rules defining this operator. |
409 | */ |
410 | public void prepareDMO(HashSet<ConjunctiveQuery> lrQueries){ |
411 | |
412 | try { |
413 | |
414 | // DMO for LR rules |
415 | for(ConjunctiveQuery cq : lrQueries){ |
416 | |
417 | DataMovementOperator dmo = new DataMovementOperator(db, this); |
418 | dmo.logicQueryPlan.addQuery(cq, cq.head.getPred().getArgs(), |
419 | new ArrayList<String>(Arrays.asList("weight", "prov", "deepprov")) ); |
420 | |
421 | dmo.predictedBB = 0; |
422 | dmo.PredictedFF = 1; |
423 | dmo.PredictedBF = 0; |
424 | |
425 | dmo.allowOptimization = false; |
426 | |
427 | allDMOs.add(dmo); |
428 | lrDMOs.add(dmo); |
429 | } |
430 | |
431 | //the DMO for the union of all LR DMOs |
432 | if(this.lrDMOs.size() > 0){ |
433 | this.lrDMO = DataMovementOperator.UnionAll(db, this, |
434 | this.lrDMOs, StringMan.zeros(this.lrHead.arity() + 3), new ArrayList<Integer>()); |
435 | allDMOs.add(lrDMO); |
436 | |
437 | // if(!db.isTableExists(FelixConfig.db_schema, "_prov_metainfo")){ |
438 | // db.execute("CREATE TABLE _prov_metainfo (predicate TEXT, property TEXT, value TEXT)"); |
439 | // } |
440 | // |
441 | // db.execute("INSERT INTO _prov_metainfo (predicate, property, value) VALUES ('" + lrHead.getName() |
442 | // + "', 'optype', '" + "lr" + "')"); |
443 | // db.execute("INSERT INTO _prov_metainfo (predicate, property, value) VALUES ('" + lrHead.getName() |
444 | // + "', 'provtable', '" + lrDMO.getAllFreeViewName()+ "')"); |
445 | // db.execute("INSERT INTO _prov_metainfo (predicate, property, value) VALUES ('" + lrHead.getName() |
446 | // + "', 'labelfield', '" + StringMan.commaList(lrHead.getLabelFieldsArgs())+ "')"); |
447 | |
448 | |
449 | //the DMO for all unigram features. |
450 | this.unigramDMO = new DataMovementOperator(db, this); |
451 | this.unigramDMO.allowOptimization = false; |
452 | this.unigramDMO.asView = false; |
453 | this.unigramDMO.logicQueryPlan.addQuery(db.getPrepareStatement( |
454 | "SELECT " + StringMan.commaList(lrDMO.selListFromRule) + ", sum(weight) AS sumweight " + |
455 | " FROM " + lrDMO.getAllFreeViewName() + " GROUP BY " + StringMan.commaList(lrDMO.selListFromRule) + |
456 | (lrHead.getKeyFieldsArgs().size() == 0 ? "" : |
457 | " ORDER BY " + StringMan.commaList(lrHead.getKeyFieldsArgs())) ), |
458 | lrDMO.selListFromRule, new ArrayList<String>(Arrays.asList("sumweight"))); |
459 | allDMOs.add(this.unigramDMO); |
460 | } |
461 | |
462 | //the DMO for the label domain |
463 | if(this.isBinaryArbLR == false){ |
464 | this.labelDomainDMO = new DataMovementOperator(db, this); |
465 | this.labelDomainDMO.allowOptimization = false; |
466 | ArrayList<String> fields = lrHead.getLabelFieldsTypeTable(); |
467 | ArrayList<String> fieldsViewName = new ArrayList<String>(); |
468 | for(int i=0;i<fields.size();i++){ |
469 | fieldsViewName.add("t" + i + "." + "constantid AS l" + i); |
470 | fields.set(i, fields.get(i) + " t" + i); |
471 | } |
472 | this.labelDomainDMO.logicQueryPlan.addQuery(db.getPrepareStatement( |
473 | "SELECT " + StringMan.commaList(fieldsViewName) +" FROM " + StringMan.commaList(fields)), |
474 | lrHead.getLabelFieldsArgs(), |
475 | new ArrayList<String>()); |
476 | allDMOs.add(this.labelDomainDMO); |
477 | } |
478 | |
479 | if(this.unigramDMO != null){ |
480 | this.getAllUnigramFeaturesDMO = DataMovementOperator.Select(db, this, |
481 | this.unigramDMO, new ArrayList<String>()); |
482 | this.getAllUnigramFeaturesDMO.isIntermediaDMO = true; |
483 | this.getAllUnigramFeaturesDMO.hasKnownFetchingOrder = true; |
484 | allDMOs.add(this.getAllUnigramFeaturesDMO); |
485 | } |
486 | |
487 | } catch (Exception e) { |
488 | e.printStackTrace(); |
489 | } |
490 | } |
491 | |
492 | /** |
493 | * Returns sum of given log numbers. |
494 | * @param logX |
495 | * @param logY |
496 | * @return |
497 | */ |
498 | //ACKNOWLEDGE: FROM https://facwiki.cs.byu.edu/nlp/index.php/Log_Domain_Computations |
499 | //COPYRIGHT OF THIS FUNCTION BELONGS TO ITS ORIGINAL AUTHOR |
500 | public static double logAdd(double logX, double logY) { |
501 | |
502 | if (logY > logX) { |
503 | double temp = logX; |
504 | logX = logY; |
505 | logY = temp; |
506 | } |
507 | |
508 | if (logX == Double.NEGATIVE_INFINITY) { |
509 | return logX; |
510 | } |
511 | |
512 | double negDiff = logY - logX; |
513 | if (negDiff < -200) { |
514 | return logX; |
515 | } |
516 | |
517 | return logX + java.lang.Math.log(1.0 + java.lang.Math.exp(negDiff)); |
518 | } |
519 | |
520 | /** |
521 | * Infer and dump answers to the given buffered writer (with a format that can be COPY |
522 | * into postgres table directly). |
523 | * @param signature signature of current predicate instance. |
524 | * @param weights array of weights. Each entry corresponding to the weight of a label. |
525 | * @param bw |
526 | */ |
527 | public void inferAndDumpToTmpFile(String signature, double[] weights, ArrayList<Integer> noneZeroWeights, BufferedWriter bw){ |
528 | |
529 | try{ |
530 | |
531 | double sum = Double.NEGATIVE_INFINITY; |
532 | double tmp = Double.NEGATIVE_INFINITY; |
533 | double max = Double.NEGATIVE_INFINITY; |
534 | int nmax = -1; |
535 | |
536 | for(int i : noneZeroWeights){ |
537 | // for(int i=0;i<weights.length;i++){ |
538 | tmp = weights[i]; |
539 | if(tmp > max){ |
540 | max = tmp; |
541 | nmax = i; |
542 | } |
543 | sum = logAdd(tmp, sum); |
544 | } |
545 | |
546 | if(max < 0){ |
547 | for(int i=0;i<weights.length;i++){ |
548 | if(weights[i] >= 0){ |
549 | max = weights[i]; |
550 | nmax = i; |
551 | break; |
552 | } |
553 | } |
554 | } |
555 | |
556 | sum = logAdd(sum, Math.log(this.label2ID.size() - noneZeroWeights.size())); |
557 | |
558 | if(this.isBinaryArbLR == true){ |
559 | sum = logAdd(0,sum); |
560 | } |
561 | |
562 | if(this.isMarginal || (FelixConfig.isFirstRunOfDD && dd_commonOutputPredicate_2_tableName.containsKey(lrHead) ) ){ |
563 | |
564 | if(nmax == -1){ |
565 | return; |
566 | } |
567 | |
568 | for(int i : noneZeroWeights){ |
569 | // for(int i=0;i<weights.length;i++){ |
570 | |
571 | tmp = weights[i]; |
572 | |
573 | double prob = Math.exp(tmp - sum); |
574 | |
575 | if(prob >= Config.soft_evidence_activation_threshold){ |
576 | |
577 | ArrayList<String> parts = new ArrayList<String>(); |
578 | //parts.add(Integer.toString(lrHead.nextTupleIDAndUpdate())); |
579 | parts.add("TRUE"); |
580 | parts.add(Double.toString(prob)); |
581 | |
582 | if(options.useDualDecomposition){ |
583 | parts.add(Integer.toString(2)); |
584 | }else{ |
585 | parts.add(Integer.toString(2)); |
586 | } |
587 | //parts.add("1");//this is for vote |
588 | |
589 | String tmpstr = String.format(signature, (Object[]) id2Label.get(i)); |
590 | tmpstr = tmpstr.replaceAll("INTERNAL_MARGINAL", prob+""); |
591 | |
592 | bw.append(FelixStringMan.commaListNoSpace(parts) + "," + tmpstr + "\n"); |
593 | } |
594 | } |
595 | |
596 | }else{ |
597 | |
598 | if(nmax == -1){ |
599 | return; |
600 | } |
601 | |
602 | // double prob = Math.exp(weights[nmax]- sum); |
603 | |
604 | if(this.isBinaryArbLR == true){ |
605 | if(Math.exp(weights[0] - sum) <= 0.5){ |
606 | return; |
607 | } |
608 | } |
609 | |
610 | ArrayList<String> parts = new ArrayList<String>(); |
611 | //parts.add(Integer.toString(lrHead.nextTupleIDAndUpdate())); |
612 | parts.add("TRUE"); |
613 | parts.add(""); |
614 | |
615 | if(options.useDualDecomposition){ |
616 | parts.add(Integer.toString(2)); |
617 | }else{ |
618 | parts.add(Integer.toString(2)); |
619 | } |
620 | |
621 | |
622 | //parts.add("1");//this is for vote |
623 | |
624 | String tmpstr = String.format(signature, (Object[]) id2Label.get(nmax)); |
625 | |
626 | tmpstr = tmpstr.replaceAll("INTERNAL_MARGINAL", "1"); |
627 | |
628 | bw.append(FelixStringMan.commaListNoSpace(parts) + "," + tmpstr + "\n"); |
629 | |
630 | } |
631 | |
632 | }catch(Exception e){ |
633 | e.printStackTrace(); |
634 | } |
635 | |
636 | } |
637 | |
638 | |
639 | |
640 | |
641 | |
642 | } |