All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.clulab.reach.darpa.DarpaActions.scala Maven / Gradle / Ivy

The newest version!
package org.clulab.reach.darpa

import com.typesafe.scalalogging.LazyLogging
import org.clulab.odin._
import org.clulab.reach._
import org.clulab.reach.mentions._
import org.clulab.struct.DirectedGraph
import scala.annotation.tailrec


class DarpaActions extends Actions with LazyLogging {

  import DarpaActions._

  /** Converts mentions to biomentions.
    * They are returned as mentions but they are biomentions with grounding, modifications, etc
    */
  def mkBioMention(mentions: Seq[Mention], state: State): Seq[Mention] =
    mentions.map(_.toBioMention)

  /** Unpacks RelationMentions into its arguments. A new BioTextBoundMention
    * will be created for each argument with the labels of the original RelationMention.
    * This is relying on Odin's behavior of assigning the same label of the RelationMention
    * to its arguments captured with a pattern (not mention captures).
    * This is required for RelationMentions whose arguments are used directly
    * by subsequent rules.
    * WARNING This method only handles RelationMentions. Other types of Mentions are deleted.
    */
  def unpackRelations(mentions: Seq[Mention], state: State): Seq[Mention] = mentions flatMap {
    case rel: RelationMention => for {
      (k, v) <- rel.arguments
      m <- v
    } yield m.toBioMention
    case _ => Nil
  }

  val mkEntities: Action = unpackRelations

  /** This action handles the creation of mentions from labels generated by the NER system.
    * Rules that use this action should run in an iteration following and rules recognizing
    * "custom" entities. This action will only create mentions if no other mentions overlap
    * with a NER label sequence.
    */
  def mkNERMentions(mentions: Seq[Mention], state: State): Seq[Mention] = {
    mentions flatMap { m =>
      val candidates = state.mentionsFor(m.sentence, m.tokenInterval)
      // do any candidates overlap the mention?
      val overlap = candidates.exists(_.tokenInterval.overlaps(m.tokenInterval))
      if (overlap) None else Some(m.toBioMention)
    }
  }

  /** This action gets RelationMentions that represents a PTM,
    * and attaches the modification to the target entity in place.
    * This action modifies mentions in-place. This action always returns
    * Nil, it assumes that the arguments are already in the state.
    */
  def storePTM(mentions: Seq[Mention], state: State): Seq[Mention] = {
    mentions foreach {
      case ptm: RelationMention if ptm matches "PTM" =>
        // convert first relation("entity") into BioMention
        val bioMention = ptm.arguments("entity").head.toBioMention
        // retrieve optional first relation("site")
        val site = ptm.arguments.get("site").map(_.head)
        // retrieves first relation("mod")
        // this is the TextBoundMention for the ModificationTrigger
        val evidence = ptm.arguments("mod").head
        // assigns label from mod
        val label = getModificationLabel(evidence.text)
        // if label is not unknown then add PTM modification to entity in-place
        if (label != "UNKNOWN") bioMention.modifications += PTM(label, Some(evidence), site)
      case _ => ()
    }
    // this action never returns anything
    // mutates mentions in-place
    // :(
    Nil
  }

  /** Gets RelationMentions that represent an EventSite,
    * and attaches the site to the corresponding entities in-place.
    * Later, if these entities are matched as participants in an event,
    * these sites will be "promoted" to that event and removed from the entity
    * (see siteSniffer for details)
    * This action always returns Nil and assumes that the arguments are already
    * in the state.
    */
  def storeEventSite(mentions: Seq[Mention], state: State): Seq[Mention] = {
    mentions foreach {
      case es: RelationMention if es matches "EventSite" =>
        // convert each relation("entity") into BioMention
        val bioMentions = es.arguments("entity").map(_.toBioMention)
        // retrieves all the captured sites
        val sites = es.arguments("site")
        // add all sites to each entity
        for {
          b <- bioMentions
          s <- sites
        } b.modifications += EventSite(site = s)
      case _ => ()
    }
    Nil
  }

  /** Gets RelationMentions that represent a Mutant,
    * and attaches the mutation to the corresponding event in-place.
    * This action always returns Nil and assumes that the arguments are already
    * in the state.
    */
  def storeMutants(mentions: Seq[Mention], state: State): Seq[Mention] = {
    mentions foreach {
      case m: RelationMention if m matches "Mutant" =>
        val bioMention = m.arguments("entity").head.toBioMention
        val mutants = m.arguments("mutant")
        mutants foreach { mutant =>
          bioMention.modifications += Mutant(evidence = mutant, foundBy = m.foundBy)
        }
    }
    Nil
  }

  /** Gets a sequence of mentions that are candidates for becoming Ubiquitination
    * events and filters out the ones that have ubiquitin as a theme, since
    * a ubiquitin can't be ubiquitinated. Events that have ubiquitin as a cause
    * are also filtered out.
    */
  def mkUbiquitination(mentions: Seq[Mention], state: State): Seq[Mention] = {
    val filteredMentions = mentions.filterNot { ev =>
      // Only keep mentions that don't have ubiquitin as a theme
      ev.arguments("theme").exists(_.text.toLowerCase == "ubiquitin") ||
        // mention shouldn't have ubiquitin as a cause either, if there is a cause
        ev.arguments.get("cause").exists(_.exists(_.text.toLowerCase == "ubiquitin"))
    }
    // return biomentions
    filteredMentions.map(_.toBioMention)
  }

  /**
    * 1. Checks that the cause and theme of an "auto" event (ex. autophosphorylation) are the same 
* 2. Splits valid event into BioEventMention (sans cause) and a BioRelationMention for a Regulation where * the original cause serves as the controller.
* NOTE: we cannot call splitSimpleEvent, as it requires that the controlled and controller do not overlap */ def handleAutoEvent(mentions: Seq[Mention], state: State): Seq[Mention] = mentions flatMap { // only events with a theme and a cause are valid case e: EventMention if (e.arguments contains "cause") && (e.arguments contains "theme") => val pairs = for { c <- e.arguments("cause") t <- e.arguments("theme") // remove cause from SimpleEvent ev = new BioEventMention(e - "cause" + ("theme" -> Seq(t))) // use cause of SimpleEvent to create a Regulation reg = new BioRelationMention( DarpaActions.REG_LABELS, Map("controller" -> Seq(c), "controlled" -> Seq(ev)), Map.empty, e.sentence, e.document, e.keep, e.foundBy ) // negations should be propagated to the newly created Positive_regulation (negMods, otherMods) = e.toBioMention.modifications.partition(_.isInstanceOf[Negation]) } yield { reg.modifications = negMods ev.modifications = otherMods Seq(reg, ev) } pairs.flatten case _ => Nil } def mkRegulation(mentions: Seq[Mention], state: State): Seq[Mention] = for { mention <- mentions // bioprocesses can't be controllers of regulations if bioprocessValid(mention) // controller/controlled paths shouldn't overlap. // NOTE this needs to be done on mentions coming directly from Odin // if !hasSynPathOverlap(mention) // do not enable this: there are legitimate cases // switch label if needed based on negations regulation = removeDummy(switchLabel(mention.toBioMention)) // If the Mention has both a controller and controlled, their grounding should be distinct if hasDistinctControllerControlled(regulation) // If the Mention has both a controller and controlled, their token spans should NOT overlap if ! overlappingSpansControllerControlled(regulation) _ = logger.debug(s"mkRegulation yields: ${display.summarizeMention(regulation)}") } yield regulation /** * Identical to mkRegulation, except mkActivation * will only allow activations where no overlapping regulation is present */ def mkActivation(mentions: Seq[Mention], state: State): Seq[Mention] = for { // Prefer Activations with Events as the controller mention <- preferEventControllers(mentions) // bioprocesses can't activate biochemical entities if bioprocessValid(mention) // controller/controlled paths shouldn't overlap. // NOTE this needs to be done on mentions coming directly from Odin if !hasSynPathOverlap(mention) // switch label if needed based on negations activation = removeDummy(switchLabel(mention.toBioMention)) // retrieve regulations that overlap this mention's controlled (Controller may be nested) controlleds = activation.arguments("controlled") regs = controlleds.flatMap(c => state.mentionsFor(activation.sentence, c.tokenInterval, "Regulation")) // Don't report an Activation if an Regulation intersects with one of the activation's controlleds // or if the Activation has no controller // or if it's controller and controlled are not distinct if regs.isEmpty && hasController(activation) && hasDistinctControllerControlled(activation) } yield activation /** For bindings that should not be split into pairs */ def mkNaryBinding(mentions: Seq[Mention], state: State): Seq[Mention] = mentions map { case m: EventMention if m matches "Binding" => // get the binding event participants // note that they could be called either "theme1" or "theme2" val themes = m.arguments.getOrElse("theme1", Nil) ++ m.arguments.getOrElse("theme2", Nil) val arguments = Map("theme" -> themes) m.copy(arguments = arguments) } def mkBinding(mentions: Seq[Mention], state: State): Seq[Mention] = mentions flatMap { case m: EventMention if m.matches("Binding") => // themes in a subject position val theme1s = m.arguments.getOrElse("theme1", Nil).map(_.toBioMention) // themes in an object position val theme2s = m.arguments.getOrElse("theme2", Nil).map(_.toBioMention) (theme1s, theme2s) match { case (t1s, Nil) if t1s.length > 1 => mkBindingsFromPairs(t1s.combinations(2).toList, m) case (Nil, t2s) if t2s.length > 1 => mkBindingsFromPairs(t2s.combinations(2).toList, m) case (gen1, Nil) if gen1.exists(t => t matches "Generic_entity") => Seq(new BioEventMention(m - "theme1" - "theme2" + ("theme" -> gen1))) case (Nil, gen2) if gen2.exists(t => t matches "Generic_entity") => Seq(new BioEventMention(m - "theme1" - "theme2" + ("theme" -> gen2))) case (t1s, t2s) => val pairs = for { t1 <- t1s t2 <- t2s } yield List(t1, t2) mkBindingsFromPairs(pairs, m) // bindings with 0 or 1 themes should be deleted case _ => Nil } } def mkBindingsFromPairs(pairs: Seq[Seq[BioMention]], original: EventMention): Seq[Mention] = for { Seq(theme1, theme2) <- pairs if !sameEntityID(theme1, theme2) if !(theme1.tokenInterval overlaps theme2.tokenInterval) } yield { if (theme1.text.toLowerCase == "ubiquitin") { val arguments = Map("theme" -> Seq(theme2)) new BioEventMention(original.copy(labels = taxonomy.hypernymsFor("Ubiquitination"), arguments = arguments)) } else if (theme2.text.toLowerCase == "ubiquitin") { val arguments = Map("theme" -> Seq(theme1)) new BioEventMention(original.copy(labels = taxonomy.hypernymsFor("Ubiquitination"), arguments = arguments)) } else { val arguments = Map("theme" -> Seq(theme1, theme2)) new BioEventMention(original.copy(arguments = arguments)) } } /** * Promote any Sites in the Modifications of a SimpleEvent argument to an event argument "site" */ def siteSniffer(mentions: Seq[Mention], state: State): Seq[Mention] = mentions flatMap { case m: BioEventMention if m matches "SimpleEvent" => val additionalSites: Seq[Mention] = m.arguments.values.flatten.toSeq.flatMap { case m: BioMention => // get EventSite Modifications val eventSites: Seq[EventSite] = m.modifications.toSeq flatMap { case es:EventSite => Some(es) case _ => None } // Remove EventSite modifications eventSites.foreach(m.modifications -= _) // get sites from EventSites eventSites.map(_.site) } // Gather up our sites val allSites = additionalSites ++ m.arguments.getOrElse("site", Nil) // Do we have any sites? if (allSites.isEmpty) Seq(m) else { // Create a separate EventMention for each Site // FIXME this splitting seems arbitrary // why do each SimpleEvent has a single site? // if it is the theme's site, then why are we extracting sites for all args? for (site <- allSites.distinct) yield { new BioEventMention(m + ("site" -> Seq(site))) } } // If it isn't a SimpleEvent, assume there is nothing more to do case m => Seq(m) } def keepIfValidArgs(mentions: Seq[Mention], state: State): Seq[Mention] = mentions.filter(validArguments(_, state)) /** * Splits a SimpleEvent with a theme and a cause into a ComplexEvent. * Requires that the controlled and controller do not overlap */ def splitSimpleEvents(mentions: Seq[Mention], state: State): Seq[Mention] = mentions flatMap { case m: EventMention if (m matches "SimpleEvent") & (m.arguments.keySet contains "cause") => // Do we have a regulation? val causes: Seq[Mention] = m.arguments("cause") val themes: Seq[Mention] = m.arguments("theme") val (negMods, otherMods) = m.toBioMention.modifications.partition(_.isInstanceOf[Negation]) val nonCauseArgs = m.arguments - "cause" val controlledArgs = nonCauseArgs.values.flatten.toSet val splitEvs = for { theme <- themes } yield { val evArgs = m.arguments - "cause" - "theme" ++ Map("theme" -> Seq(theme)) val ev = new BioEventMention(m.copy(arguments = evArgs), direct = true) // modifications other than negations belong to the SimpleEvent ev.modifications = otherMods ev } val splitRegs = for { ev <- splitEvs cause <- causes // controller shouldn't overlap with controlled arguments if !controlledArgs.contains(cause) } yield { val regArgs = Map("controlled" -> Seq(ev), "controller" -> Seq(cause)) val reg = new BioRelationMention(m.copy(labels = DarpaActions.REG_LABELS, arguments = regArgs).toRelationMention) // negations should be propagated to the newly created Positive_regulation reg.modifications = negMods reg } splitEvs ++ splitRegs case m => Seq(m.toBioMention) } /** global action for EventEngine */ def cleanupEvents(mentions: Seq[Mention], state: State): Seq[Mention] = { logger.debug("Running global action cleanupEvents...") val r1 = siteSniffer(mentions, state) val r2 = keepIfValidArgs(r1, state) val r3 = NegationHandler.detectNegations(r2, state) val r4 = HypothesisHandler.detectHypotheses(r3, state) val r5 = splitSimpleEvents(r4, state) r5 } } object DarpaActions { def hasNegativePolarity(m: Mention): Boolean = if (m.label.toLowerCase startsWith "negative") true else false // These labels are given to the Regulation created when splitting a SimpleEvent with a cause val REG_LABELS = taxonomy.hypernymsFor("Positive_regulation") // These are used to detect semantic inversions of regulations/activations. See DarpaActions.countSemanticNegatives val SEMANTIC_NEGATIVE_PATTERN = "attenu|block|deactiv|decreas|degrad|delet|diminish|disrupt|impair|imped|inhibit|knockdown|knockout|limit|loss|lower|negat|reduc|reliev|repress|restrict|revers|silenc|slow|starv|suppress|supress".r val MODIFIER_LABELS = "amod".r // patterns for "reverse" modifications val deAcetylatPat = "(?i)de-?acetylat".r val deFarnesylatPat = "(?i)de-?farnesylat".r val deGlycosylatPat = "(?i)de-?glycosylat".r val deHydrolyPat = "(?i)de-?hydroly".r val deHydroxylatPat = "(?i)de-?hydroxylat".r val deMethylatPat = "(?i)de-?methylat".r val dePhosphorylatPat = "(?i)de-?phosphorylat".r val deRibosylatPat = "(?i)de-?ribosylat".r val deSumoylatPat = "(?i)de-?sumoylat".r val deUbiquitinatPat = "(?i)de-?ubiquitinat".r // HELPER FUNCTIONS /** retrieves the appropriate modification label */ def getModificationLabel(text: String): String = text.toLowerCase match { case string if deAcetylatPat.findPrefixOf(string).isDefined => "Deacetylation" case string if deFarnesylatPat.findPrefixOf(string).isDefined => "Defarnesylation" case string if deGlycosylatPat.findPrefixOf(string).isDefined => "Deglycosylation" case string if deHydrolyPat.findPrefixOf(string).isDefined => "Dehydrolysis" case string if deHydroxylatPat.findPrefixOf(string).isDefined => "Dehydroxylation" case string if deMethylatPat.findPrefixOf(string).isDefined => "Demethylation" case string if dePhosphorylatPat.findPrefixOf(string).isDefined => "Dephosphorylation" case string if deRibosylatPat.findPrefixOf(string).isDefined => "Deribosylation" case string if deSumoylatPat.findPrefixOf(string).isDefined => "Desumoylation" case string if deUbiquitinatPat.findPrefixOf(string).isDefined => "Deubiquitination" case string if string contains "acetylat" => "Acetylation" case string if string contains "farnesylat" => "Farnesylation" case string if string contains "glycosylat" => "Glycosylation" case string if string contains "hydroly" => "Hydrolysis" case string if string contains "hydroxylat" => "Hydroxylation" case string if string contains "methylat" => "Methylation" case string if string contains "phosphorylat" => "Phosphorylation" case string if string contains "ribosylat" => "Ribosylation" case string if string contains "sumoylat" => "Sumoylation" case string if string contains "ubiquitinat" => "Ubiquitination" case _ => "UNKNOWN" } /** Gets a sequence of mentions and returns only the ones that have * Event controllers. If none is found, returns all mentions. */ def preferEventControllers(mentions: Seq[Mention]): Seq[Mention] = { // get events that have a SimpleEvent as a controller // assuming that events can only have one controller val eventsWithSimpleController = mentions.filter { m => m.arguments.contains("controller") && m.arguments("controller").head.matches("Event") } if (eventsWithSimpleController.nonEmpty) eventsWithSimpleController else mentions } /** Gets a mention. If it is an EventMention with a polarized label * and it is negated an odd number of times, returns a new mention * with the label flipped. Or else it returns the mention unmodified */ def switchLabel(mention: Mention): BioMention = mention.toBioMention match { // We can only attempt to flip the polarity of ComplexEvents with a trigger case ce: BioEventMention if ce matches "ComplexEvent" => val trigger = ce.trigger val arguments = ce.arguments.values.flatten // get token indices to exclude in the negation search // do not exclude args as they may involve regulations val excluded = trigger.tokenInterval.toSet // count total number of negatives between trigger and each argument val numNegatives = arguments.map(arg => countSemanticNegatives(trigger, arg, excluded)).sum // does the label need to be flipped? numNegatives % 2 != 0 match { // odd number of negatives case true => val newLabels = flipLabel(ce.label) +: ce.labels.tail // trigger labels should match event labels val newTrigger = ce.trigger.copy(labels = newLabels) // return new mention with flipped label new BioEventMention(ce.copy(labels = newLabels, trigger = newTrigger)) // return mention unmodified case false => ce } case m => m } /** Gets a trigger, an argument and a set of tokens to be ignored. * Returns the number of semantic negatives found in the shortest possible path * between the trigger and the argument. */ def countSemanticNegatives(trigger: Mention, arg: Mention, excluded: Set[Int]): Int = { // it is possible for the trigger and the arg to be in different sentences because of coreference if (trigger.sentence != arg.sentence) return 0 val deps = trigger.sentenceObj.dependencies.get // find the shortest path between any token in the trigger and any token in the argument var shortestPath: Seq[Int] = null for (tok1 <- trigger.tokenInterval; tok2 <- arg.tokenInterval) { val path = deps.shortestPath(tok1, tok2, ignoreDirection = true) if (shortestPath == null || path.length < shortestPath.length) { shortestPath = path } } val shortestPathWithMods = addAdjectivalModifiers(shortestPath, deps) // get all tokens considered negatives val negatives = for { tok <- shortestPathWithMods if !excluded.contains(tok) lemma = trigger.sentenceObj.lemmas.get(tok) if SEMANTIC_NEGATIVE_PATTERN.findFirstIn(lemma).isDefined } yield tok // return number of negatives negatives.size } /** * Adds adjectival modifiers to all elements in the given path * This is necessary so we can properly inspect the semantic negatives, * which are often not in the path, but modify tokens in it, * "*decreased* PTPN13 expression increases phosphorylation of EphrinB1" */ def addAdjectivalModifiers(tokens: Seq[Int], deps: DirectedGraph[String]): Seq[Int] = for { t <- tokens token <- t +: getModifiers(t, deps) } yield token def getModifiers(token: Int, deps: DirectedGraph[String]): Seq[Int] = for { (tok, dep) <- deps.getOutgoingEdges(token) if MODIFIER_LABELS.findFirstIn(dep).isDefined } yield tok /** gets a polarized label and returns it flipped */ def flipLabel(label: String): String = if (label startsWith "Positive_") "Negative_" + label.substring(9) else if (label startsWith "Negative_") "Positive_" + label.substring(9) else sys.error("ERROR: Must have a polarized label here!") /** Test whether the given mention has a controller argument. */ def hasController(mention: Mention): Boolean = mention.arguments.get("controller").isDefined /** Test whether the mention has a bioprocess controller and, if so, if its use is legitimate */ def bioprocessValid(m: Mention): Boolean = { (m.arguments.getOrElse("controller", Nil).map(_.label), m.arguments.getOrElse("controlled", Nil).map(_.label)) match { case (irrelevant, _) if !irrelevant.contains("BioProcess") => true case (procController, procControlled) if procController.contains("BioProcess") & procControlled.contains("BioProcess") => true case other => false // e.g. BioProcess controller but BioChemical controlled } } /** Gets a mention and checks that the controller and controlled are different. * Returns true if either the controller or the controlled is missing, * or if they are both present and are distinct. */ def hasDistinctControllerControlled(m: Mention): Boolean = { val controlled = m.arguments.getOrElse("controlled", Nil).flatMap(_.toBioMention.grounding).toSet val controller = m.arguments.getOrElse("controller", Nil).flatMap(_.toBioMention.grounding).toSet controlled.intersect(controller).isEmpty } /** * Checks if the token spans of the controlled and controller overlap. * Returns true if they do */ def overlappingSpansControllerControlled(m: Mention): Boolean = { val controlleds = m.arguments.getOrElse("controlled", Set()) val controllers = m.arguments.getOrElse("controller", Set()) controllers.foreach(cr => controlleds.foreach(cd => if(cr.tokenInterval.overlaps(cd.tokenInterval)) return true )) false } /** checks if a mention has a controller/controlled * arguments with syntactic paths from the trigger * that overlap */ def hasSynPathOverlap(m: Mention): Boolean = { val controlled = m.arguments.getOrElse("controlled", Nil) val controller = m.arguments.getOrElse("controller", Nil) if (m.paths.isEmpty) false else if (controlled.isEmpty || controller.isEmpty) false else { // we are only concerned with the first controlled and controller val p1 = m.getPath("controlled", controlled.head) val p2 = m.getPath("controller", controller.head) if (p1.nonEmpty && p2.nonEmpty) { p1.head == p2.head } else false } } /** Returns true if both mentions are grounded to the same entity */ def sameEntityID(m1: BioMention, m2: BioMention): Boolean = { require(m1.isGrounded, "mention must be grounded") require(m2.isGrounded, "mention must be grounded") m1.grounding == m2.grounding } def removeDummy(m: BioMention): BioMention = m match { // we only need to do this for events case em: BioEventMention => new BioEventMention(em - "dummy") case _ => m } def validArguments(mention: Mention, state: State): Boolean = mention.toBioMention match { // TextBoundMentions don't have arguments case _: BioTextBoundMention => true // RelationMentions don't have triggers, so we can't inspect the path case _: BioRelationMention => true // EventMentions are the only ones we can really check case m: BioEventMention => // get simple chemicals in arguments val args = m.arguments.values.flatten val simpleChemicals = args.filter(_ matches "Simple_chemical") // if there are no simple chemicals then we are done if (simpleChemicals.isEmpty) true else { for (chem <- simpleChemicals) { if (proteinBetween(m.trigger, chem, state)) { return false } } true } } def proteinBetween(trigger: Mention, arg: Mention, state: State): Boolean = { // it is possible for the trigger and the arg to be in different sentences // because of coreference if (trigger.sentence != arg.sentence) false else trigger.sentenceObj.dependencies match { // if for some reason we don't have dependencies // then there is nothing we can do case None => false case Some(deps) => for { tok1 <- trigger.tokenInterval tok2 <- arg.tokenInterval path = deps.shortestPath(tok1, tok2, ignoreDirection = true) node <- path // FIXME: Why is this Gene_or_gene_product? What about Protein, GENE, etc.? if state.mentionsFor(trigger.sentence, node, "Gene_or_gene_product").nonEmpty if !consecutivePreps(path, deps) } return true // if we reach this point then we are good false } } // hacky solution to the prepositional attachment problem // that affects the proteinBetween method def consecutivePreps(path: Seq[Int], deps: DirectedGraph[String]): Boolean = { val pairs = for (i <- path.indices.tail) yield (path(i-1), path(i)) val edges = for ((n1, n2) <- pairs) yield { deps.getEdges(n1, n2, ignoreDirection = true).map(_._3) } for { i <- edges.indices.tail if edges(i-1).exists(_.startsWith("prep")) if edges(i).exists(_.startsWith("prep")) } return true false } /** Recursively converts an Event to an Entity with the appropriate modifications representing its state. * SimpleEvent -> theme + PTM
* Binding -> Complex (treated as an Entity)
* ComplexEvent -> recursive call on controlled (the event's "output")
* * @param asOutput value of true to generate a Mention's output (ex. theme or controlled) */ @tailrec final def convertEventToEntity(m: Mention, asOutput: Boolean = true, negated: Boolean = false): BioMention = m.toBioMention match { // // cases appropriate to either output (ex. theme/controlled) or other (ex. controller) // // no conversion needed // FIXME: consider case of "activated RAS". // We may want to add a Negation mod to this entity conditionally // or add a PTM with the label "UNKNOWN" and negate it (depending on value of negated) case entity if entity matches "Entity" => entity // These are event triggers used by coref (a SimpleEvent without a theme) // FIXME: should these be handled differently? case generic if generic matches "Generic_event" => generic // convert a binding into a Complex so that it is treated as an entity case binding if binding matches "Binding" => new BioRelationMention( taxonomy.hypernymsFor("Complex"), binding.arguments, binding.paths, binding.sentence, binding.document, binding.keep, binding.foundBy ) // convert event to PTM on its theme. // negate PTM according to current value of "negated" // (this SimpleEvent may have been the controlled to some negative ComplexEvent) case se: BioEventMention if se matches "SimpleEvent" => // get the theme of the event (assume only one theme) val entity = se.arguments("theme").head.toBioMention // get an optional site (assume only one site) val siteOption = se.arguments.get("site").map(_.head) // create new mention for the entity val modifiedEntity = new BioTextBoundMention(entity) // attach a modification based on the event trigger val label = DarpaActions.getModificationLabel(se.label) BioMention.copyAttachments(entity, modifiedEntity) modifiedEntity.modifications += PTM(label, evidence = Some(se.trigger), site = siteOption, negated) modifiedEntity // // cases for the generation of output // // dig into the controlled (event's "output" or the part that is altered in some way) case posEvent if asOutput && (posEvent matches "ComplexEvent") && (!DarpaActions.hasNegativePolarity(posEvent)) => // get the controlled of the event (assume only one controlled) val controlled = posEvent.arguments("controlled").head.toBioMention convertEventToEntity(controlled, asOutput, negated) // dig into the controlled (event's "output" or the part that is altered in some way) // ComplexEvents with negative polarity "negate" the ptm of the contained entity // (see https://github.com/clulab/reach/issues/184) case negEvent if asOutput && (negEvent matches "ComplexEvent") && DarpaActions.hasNegativePolarity(negEvent) => // get the controlled of the event (assume only one controlled) val controlled = negEvent.arguments("controlled").head.toBioMention // negate the underlying PTM // if received event has negative polarity (see issue #184) convertEventToEntity(controlled, asOutput, negated=true) // // cases for the uncovering of controllers (i.e., asOutput == false) // // dig into the controller case posEvent if (posEvent matches "ComplexEvent") && (!DarpaActions.hasNegativePolarity(posEvent)) => // get the controller of the event (assume only one controller) val controller = posEvent.arguments("controller").head.toBioMention convertEventToEntity(controller, asOutput = false, negated) // dig into the controller case negEvent if (negEvent matches "ComplexEvent") && DarpaActions.hasNegativePolarity(negEvent) => // get the controller of the event (assume only one controller) val controller = negEvent.arguments("controller").head.toBioMention // negate the underlying PTM // if received event has negative polarity (see issue #184) convertEventToEntity(controller, asOutput = false, negated=true) } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy