001package org.biopax.paxtools.io.sif; 002 003import org.apache.commons.lang.StringUtils; 004import org.apache.commons.logging.Log; 005import org.apache.commons.logging.LogFactory; 006import org.biopax.paxtools.controller.PathAccessor; 007import org.biopax.paxtools.io.sif.level3.Group; 008import org.biopax.paxtools.io.sif.level3.InteractionSetL3; 009import org.biopax.paxtools.model.BioPAXElement; 010import org.biopax.paxtools.model.BioPAXLevel; 011import org.biopax.paxtools.model.Model; 012import org.biopax.paxtools.model.level2.complex; 013import org.biopax.paxtools.model.level2.physicalEntity; 014import org.biopax.paxtools.model.level2.physicalEntityParticipant; 015import org.biopax.paxtools.model.level3.EntityReference; 016import org.biopax.paxtools.model.level3.PhysicalEntity; 017import org.biopax.paxtools.util.IllegalBioPAXArgumentException; 018 019import java.io.IOException; 020import java.io.OutputStream; 021import java.io.OutputStreamWriter; 022import java.io.Writer; 023import java.util.*; 024 025/** 026 * Converts a BioPAX model to SIF (simple interaction format), by inferring the interactions in the 027 * model, and describing them in terms of simple interactions. 028 * 029 * @deprecated use the pattern (new Paxtools' module) api instead 030 */ 031public class SimpleInteractionConverter 032{ 033 /** 034 * Rules used during generation of interactions. 035 */ 036 private final InteractionRule[] rules; 037 038 /** 039 * Log for logging. 040 */ 041 private final Log log = LogFactory.getLog(SimpleInteractionConverter.class); 042 043 /** 044 * Options for interaction generation. 045 */ 046 private final Map options; 047 048 /** 049 * Option to reduce complexes or use them as they are in interactions. 050 */ 051 public static final String REDUCE_COMPLEXES = "REDUCE_COMPLEXES"; 052 053 /** 054 * Option to reduce complexes or use them as they are in interactions. 055 */ 056 public static final String REDUCE_GENERICS = "REDUCE_GENERICS"; 057 058 /** 059 * IDs of unwanted elements in SIF graph. 060 */ 061 private Set<String> blackList; 062 063 /** 064 * Constructor with the mining rules to use. 065 * @param rules interaction rule set to be used in the conversion 066 */ 067 public SimpleInteractionConverter(InteractionRule... rules) 068 { 069 this(new HashMap(), rules); 070 } 071 072 /** 073 * Constructor with mining rules and options. 074 * @param options options to be used during the conversion process 075 * @param rules interaction rule set to be used in the conversion 076 */ 077 public SimpleInteractionConverter(Map options, InteractionRule... rules) 078 { 079 this(options, null, rules); 080 } 081 082 /** 083 * Constructor with mining rules, options, and black list. 084 * @param options options to be used during the conversion process 085 * @param blackList ids of molecules that we do not want them in SIF 086 * @param rules interaction rule set to be used in the conversion 087 */ 088 public SimpleInteractionConverter(Map options, Set<String> blackList, InteractionRule... rules) 089 { 090 091 this.blackList = blackList; 092 this.rules = rules; 093 this.options = options; 094 for (InteractionRule rule : rules) 095 { 096 rule.initOptions(options); 097 } 098 } 099 100 /** 101 * Infers simple interactions from the interactions found in the <em>model</em> for every 102 * interaction rule given; and returns this inferred simple interactions. 103 * @param model model from which simple interactions are going to be inferred 104 * @return a set of inferred simple interactions 105 */ 106 public Set<SimpleInteraction> inferInteractions(Model model) 107 { 108 109 switch (model.getLevel()) 110 { 111 case L1: 112 case L2: 113 return inferL2(model); 114 case L3: 115 return inferL3(model); 116 default: 117 throw new IllegalBioPAXArgumentException("Unknown BioPAX Level"); 118 } 119 } 120 121 /** 122 * Infers interactions for L3 models. 123 * @param model L3 model 124 * @return inferred interactions 125 */ 126 private Set<SimpleInteraction> inferL3(Model model) 127 { 128 InteractionSetL3 interactions = new InteractionSetL3(model); 129 130 Set<PhysicalEntity> bioPAXElements = model.getObjects(PhysicalEntity.class); 131 for (PhysicalEntity er : bioPAXElements) 132 { 133 for (InteractionRule rule : rules) 134 { 135 tryInferringRule(model, interactions, er, rule); 136 } 137 } 138 interactions.convertGroupsToInteractions(); 139 if(options.containsKey(REDUCE_COMPLEXES)) 140 { 141 System.out.println("reducing groups"); 142 InteractionSetL3 reduced = new InteractionSetL3(model); 143 for (SimpleInteraction interaction : interactions) 144 { 145 reduceL3Groups(interaction, reduced); 146 } 147 interactions = reduced; 148 } 149 if (blackList != null) 150 { 151 removeInteractionsWithBlackListMolecules(interactions, blackList); 152 } 153 return interactions; 154 } 155 156 /** 157 * Tries to use a rule to infer interactions. If an exception occurs other than 158 * MaximumInteractionThresholdExceedException, logs the error and continues. 159 * @param model model to use 160 * @param interactions inferred interactions 161 * @param bpe base element for inference 162 * @param rule current interaction rule 163 * @exception MaximumInteractionThresholdExceedException if generated rules are over a certain 164 * number 165 */ 166 private void tryInferringRule(Model model, InteractionSet interactions, BioPAXElement bpe, 167 InteractionRule rule) 168 { 169 try 170 { 171 rule.inferInteractions(interactions, bpe, model); 172 } 173 catch (MaximumInteractionThresholdExceedException e) 174 { 175 throw e; 176 } 177 catch (Exception e) 178 { 179 log.error("Exception while applying rule :" + this.getClass().getSimpleName() + "to the element: " + 180 bpe.getRDFId(), e); 181 } 182 } 183 184 /** 185 */ 186 private void reduceL3Groups(SimpleInteraction int2reduce, Set<SimpleInteraction> reducedInts) 187 { 188 if (!(int2reduce.getType() == BinaryInteractionType.COMPONENT_OF)) 189 { 190 HashSet<EntityReference> sourceSet = new HashSet<EntityReference>(); 191 HashSet<EntityReference> targetSet = new HashSet<EntityReference>(); 192 193 reduceL3Groups(int2reduce.getSource(), sourceSet); 194 reduceL3Groups(int2reduce.getTarget(), targetSet); 195 for (EntityReference source : sourceSet) 196 { 197 for (EntityReference target : targetSet) 198 { 199 SimpleInteraction interaction = new SimpleInteraction(source, target, int2reduce.getType()); 200 interaction.getMediators().addAll(int2reduce.getMediators()); 201 reducedInts.add(interaction); 202 } 203 } 204 } 205 } 206 207 /** 208 */ 209 private static void reduceL3Groups(BioPAXElement bpe, Set<EntityReference> reduced) 210 { 211 if (bpe instanceof Group) 212 { 213 reduced.addAll(((Group) bpe).getAllSimpleMembers()); 214 } 215 else 216 { 217 reduced.add((EntityReference) bpe); 218 } 219 } 220 221 222 223 /** 224 * Infers interactions on L2 models. 225 * @param model L2 model 226 * @return inferred interactions 227 */ 228 private Set<SimpleInteraction> inferL2(Model model) 229 { 230 InteractionSet interactions = new InteractionSet(); 231 Set<physicalEntity> bioPAXElements = model.getObjects(physicalEntity.class); 232 233 for (physicalEntity pe : bioPAXElements) 234 { 235 for (InteractionRule rule : rules) 236 { 237 tryInferringRule(model, interactions, pe, rule); 238 } 239 } 240 if (this.options.containsKey(REDUCE_COMPLEXES)) 241 { 242 InteractionSet reduced = new InteractionSet(); 243 for (SimpleInteraction si : interactions) 244 { 245 reduceL2Complexes(si, reduced); 246 } 247 interactions = reduced; 248 } 249 250 log.info(interactions.size() + " interactions inferred"); 251 return interactions; 252 } 253 254 255 /** 256 * If an interaction contains a complex as source or target, then this method creates new 257 * interactions using the members of the complex. If both ends are complex with members of size 258 * n and m, there will be n x m reduced interactions. 259 * @param reducedInts new interactions generated with complex members 260 */ 261 private void reduceL2Complexes(SimpleInteraction int2reduce, Set<SimpleInteraction> reducedInts) 262 { 263 if (!(int2reduce.getType() == BinaryInteractionType.COMPONENT_OF)) 264 { 265 Set<physicalEntity> sourceSet = new HashSet<physicalEntity>(); 266 Set<physicalEntity> targetSet = new HashSet<physicalEntity>(); 267 268 recursivelyReduceL2Complexes(int2reduce.getSource(), sourceSet); 269 recursivelyReduceL2Complexes(int2reduce.getTarget(), targetSet); 270 for (physicalEntity source : sourceSet) 271 { 272 for (physicalEntity target : targetSet) 273 { 274 SimpleInteraction interaction = new SimpleInteraction(source, target, int2reduce.getType()); 275 interaction.getMediators().addAll(int2reduce.getMediators()); 276 reducedInts.add(interaction); 277 } 278 } 279 } 280 } 281 282 /** 283 * Gets member physicalEntity of the complex recursively. 284 * @param bpe element to get the related physicalEntity 285 * @param reduced related physicalEntity set 286 */ 287 private static void recursivelyReduceL2Complexes(BioPAXElement bpe, Set<physicalEntity> reduced) 288 { 289 if (bpe instanceof physicalEntityParticipant) 290 { 291 recursivelyReduceL2Complexes(((physicalEntityParticipant) bpe).getPHYSICAL_ENTITY(), reduced); 292 } else 293 { 294 if (bpe instanceof complex) 295 { 296 for (physicalEntityParticipant pep : ((complex) bpe).getCOMPONENTS()) 297 { 298 physicalEntity pe = pep.getPHYSICAL_ENTITY(); 299 recursivelyReduceL2Complexes(pe, reduced); 300 301 } 302 303 } else if (bpe instanceof physicalEntity) 304 { 305 reduced.add((physicalEntity) bpe); 306 } 307 } 308 } 309 310 311 /** 312 * Filters out interactions whose source or target are in black list. 313 * @param interactions interactions to filter 314 * @param blackList IDs of unwanted elements 315 */ 316 protected void removeInteractionsWithBlackListMolecules(Set<SimpleInteraction> interactions, 317 Set<String> blackList) 318 { 319 Iterator<SimpleInteraction> iter = interactions.iterator(); 320 while (iter.hasNext()) 321 { 322 SimpleInteraction inter = iter.next(); 323 if (blackList.contains(inter.getSource().getRDFId()) || 324 blackList.contains(inter.getTarget().getRDFId()) || 325 intersects(blackList, inter.getMediators())) 326 { 327 iter.remove(); 328 } 329 } 330 } 331 332 /** 333 * Checks if the blacklist contains id of any mediator 334 * @param ids blacklist 335 * @param mediators mediators of inferred interactions 336 * @return true if any mediator is blacklisted 337 */ 338 private boolean intersects(Set<String> ids, Set<BioPAXElement> mediators) 339 { 340 for (BioPAXElement mediator : mediators) 341 { 342 if (ids.contains(mediator.getRDFId())) return true; 343 } 344 return false; 345 } 346 347 /** 348 * Infers simple interactions from the <em>model</em> using {@link 349 * #inferInteractions(org.biopax.paxtools.model.Model)} and wrties them to an output stream. 350 * @param model model from which simple interactions are going to be inferred 351 * @param out output stream to which simple interactions will be written 352 * @exception IOException in case of problems with output. 353 */ 354 public void writeInteractionsInSIF(Model model, OutputStream out) throws IOException 355 { 356 Set<SimpleInteraction> interactions = inferInteractions(model); 357 Writer writer = new OutputStreamWriter(out); 358 for (SimpleInteraction simpleInteraction : interactions) 359 { 360 writer.write(simpleInteraction.toString() + "\n"); 361 } 362 writer.close(); 363 } 364 365 /** 366 * This method outputs inferred interactions in SIF annotation extended format (Sifnx). 367 * 368 * It's a tab-delimited text data format. The result is written to two separate files or streams - 369 * for edges and nodes, respectively (also, files can later be merged into one having there 370 * two sections separated by single blank line; we still call it Sifnx format). 371 * 372 * The first section/file (that describes interactions) is similar to SIF, however, 373 * there might be publication references (optional) next to each interaction line, etc. 374 * 375 * The second one (about interacting entities, i.e., nodes), lists the IDs and corresponding 376 * BioPAX property values users wanted from a BioPAX model, for each participant. 377 * It is in the form: "ID\tproperty_value\tproperty_value\tproperty_values...". 378 * If the cardinality of property is multiple, the values are separated by a semi column; 379 * e.g.: 380 * 381 * id aName uniprot:P12345;entrez-gene:1234 382 * 383 * @param model model to convert 384 * @param edgeStream output stream for interactions (edges) 385 * @param nodeStream output stream for nodes (second section) 386 * @param interactorPropertyPaths interactor property paths 387 * @param mediatorPropertyPaths mediator property paths 388 * @param writeEntityTypes whether to output participants' BioPAX types 389 * @exception IOException when there is an output stream error 390 */ 391 public void writeInteractionsInSIFNX(Model model, OutputStream edgeStream, OutputStream nodeStream, 392 List<String> interactorPropertyPaths, List<String> mediatorPropertyPaths, boolean writeEntityTypes) 393 throws IOException 394 { 395 Set<SimpleInteraction> interactions = inferInteractions(model); 396 Set<BioPAXElement> entities = new HashSet<BioPAXElement>(); 397 List<PathAccessor> interactorAccessors = null; 398 List<PathAccessor> mediatorAccessors = null; 399 400 if (interactorPropertyPaths != null) 401 { 402 interactorAccessors = new ArrayList<PathAccessor>(interactorPropertyPaths.size()); 403 for (String s : interactorPropertyPaths) 404 { 405 interactorAccessors.add(new PathAccessor(s, model.getLevel())); 406 } 407 } 408 if (mediatorPropertyPaths != null) 409 { 410 mediatorAccessors = new ArrayList<PathAccessor>(mediatorPropertyPaths.size()); 411 for (String s : mediatorPropertyPaths) 412 { 413 mediatorAccessors.add(new PathAccessor(s, model.getLevel())); 414 } 415 } 416 417 Set<String> lines = new TreeSet<String>(); //this is also to avoid duplicate records 418 for (SimpleInteraction si : interactions) 419 { 420 StringBuilder sb = new StringBuilder(si.toString()); 421 entities.add(si.getSource()); 422 entities.add(si.getTarget()); 423 if (mediatorAccessors != null) 424 { 425 for (PathAccessor mediatorAccessor : mediatorAccessors) 426 { 427 428 HashSet values = new HashSet(); 429 430 for (BioPAXElement mediator : si.getMediators()) 431 { 432 if (mediatorAccessor.applies(mediator)) 433 { 434 values.add(valuesToString(mediatorAccessor.getValueFromBean(mediator))); 435 } 436 } 437 sb.append("\t").append(values.isEmpty() ? "not applicable" : valuesToString(values)); 438 } 439 } 440 lines.add(sb.toString()); 441 } 442 443 Writer writer = new OutputStreamWriter(edgeStream); 444 writer.write(StringUtils.join(lines, "\n")); 445 writer.flush(); 446 447 // now write nodes info 448 writer = new OutputStreamWriter(nodeStream); 449 for (BioPAXElement entity : entities) 450 { 451 if (entity != null) 452 { 453 454 writer.write(entity.getRDFId()); 455 if (writeEntityTypes) writer.write("\t" + getEntityTypeString(entity)); 456 if (interactorAccessors != null) 457 { 458 for (PathAccessor accessor : interactorAccessors) 459 { 460 writer.write("\t"); 461 if (accessor == null ||!accessor.applies(entity)) writer.write("(not applicable)"); 462 else if (accessor.isUnknown(entity)) writer.write("(not specified)"); 463 else 464 { 465 Set values = accessor.getValueFromBean(entity); 466 writer.write(valuesToString(values)); 467 } 468 } 469 } 470 471 writer.write("\n"); 472 } 473 } 474 475 writer.close(); 476 } 477 478 /** 479 * Gets the type of the element. 480 * @param entity entity to ask its type 481 * @return type in string 482 */ 483 private String getEntityTypeString(BioPAXElement entity) 484 { 485 if(entity instanceof Group) 486 { 487 return ((Group) entity).groupTypeToString(); 488 } 489 else 490 return entity.getModelInterface().getSimpleName(); 491 } 492 493 /** 494 * Prepared a semicolon separated string of values. 495 * @param values values to list 496 * @return string representing values 497 */ 498 private String valuesToString(Set values) 499 { 500 StringBuilder bldr = new StringBuilder(); 501 for (Object value : values) 502 { 503 bldr.append(value).append(";"); 504 } 505 if (bldr.length() > 0) bldr.deleteCharAt(bldr.length() - 1); 506 return bldr.toString(); 507 } 508 509 /** 510 * Gets all available interaction rules for the given level. 511 * @param level BioPAX level 512 * @return available rules 513 */ 514 public static List<InteractionRule> getRules(BioPAXLevel level) 515 { 516 List<InteractionRule> list = new ArrayList<InteractionRule>(5); 517 if (level == BioPAXLevel.L2) 518 { 519 list.add(new org.biopax.paxtools.io.sif.level2.ComponentRule()); 520 list.add(new org.biopax.paxtools.io.sif.level2.ConsecutiveCatalysisRule()); 521 list.add(new org.biopax.paxtools.io.sif.level2.ControlRule()); 522 list.add(new org.biopax.paxtools.io.sif.level2.ControlsTogetherRule()); 523 list.add(new org.biopax.paxtools.io.sif.level2.ParticipatesRule()); 524 list.add(new org.biopax.paxtools.io.sif.level2.AffectsRule()); 525 } else if (level == BioPAXLevel.L3) 526 { 527 list.add(new org.biopax.paxtools.io.sif.level3.ComponentRule()); 528 list.add(new org.biopax.paxtools.io.sif.level3.ConsecutiveCatalysisRule()); 529 list.add(new org.biopax.paxtools.io.sif.level3.ControlRule()); 530 list.add(new org.biopax.paxtools.io.sif.level3.ControlsTogetherRule()); 531 list.add(new org.biopax.paxtools.io.sif.level3.ParticipatesRule()); 532 list.add(new org.biopax.paxtools.io.sif.level3.ExpressionRule()); 533 } 534 return list; 535 } 536}