package edu.stanford.nlp.ie.machinereading.structure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import edu.stanford.nlp.util.CoreMap;
import edu.stanford.nlp.util.IdentityHashSet;
/**
* Each relation has a type and set of arguments
*
* @author Andrey Gusev
* @author Mihai
* @author David McClosky
*
*/
public class RelationMention extends ExtractionObject {
private static final long serialVersionUID = 8962289597607972827L;
public static final Logger logger = Logger.getLogger(RelationMention.class.getName());
// index of the next unique id
private static int MENTION_COUNTER = 0;
public static final String UNRELATED = "_NR";
/**
* List of argument names in this relation
*/
protected List<String> argNames;
/**
* List of arguments in this relation
* If unnamed, arguments MUST be stored in semantic order, e.g., ARG0 must be a person in a employed-by relation
*/
protected List<ExtractionObject> args;
/**
* A signature for a given relation mention, e.g., a concatenation of type and argument strings
* This is used in KBP, where we merge all RelationMentions corresponding to the same abstract relation
*/
protected String signature;
public RelationMention(String objectId,
CoreMap sentence,
Span span,
String type,
String subtype,
List<ExtractionObject> args) {
super(objectId, sentence, span, type, subtype);
this.args = args;
this.argNames = null;
this.signature = null;
}
public RelationMention(String objectId,
CoreMap sentence,
Span span,
String type,
String subtype,
List<ExtractionObject> args,
List<String> argNames) {
super(objectId, sentence, span, type, subtype);
this.args = args;
this.argNames = argNames;
this.signature = null;
}
public RelationMention(String objectId,
CoreMap sentence,
Span span,
String type,
String subtype,
ExtractionObject... args) {
this(objectId, sentence, span, type, subtype, Arrays.asList(args));
}
public boolean argsMatch(RelationMention rel) {
return argsMatch(rel.getArgs());
}
public boolean argsMatch(ExtractionObject... inputArgs) {
return argsMatch(Arrays.asList(inputArgs));
}
/**
* Verifies if the two sets of arguments match
* @param inputArgs List of arguments
*/
public boolean argsMatch(List<ExtractionObject> inputArgs) {
if (inputArgs.size() != this.args.size()) {
return false;
}
for (int ind = 0; ind < this.args.size(); ind++) {
ExtractionObject a1 = this.args.get(ind);
ExtractionObject a2 = inputArgs.get(ind);
if(! a1.equals(a2)) return false;
}
return true;
}
public List<ExtractionObject> getArgs() {
return Collections.unmodifiableList(this.args);
}
public void setArgs(List<ExtractionObject> args) {
this.args = args;
}
/**
* Fetches the arguments of this relation that are entity mentions
* @return List of entity-mention args sorted in semantic order
*/
public List<EntityMention> getEntityMentionArgs() {
List<EntityMention> ents = new ArrayList<>();
for(ExtractionObject o: args) {
if(o instanceof EntityMention){
ents.add((EntityMention) o);
}
}
return ents;
}
public ExtractionObject getArg(int argpos) {
return this.args.get(argpos);
}
public List<String> getArgNames() {
return argNames;
}
public void setArgNames(List<String> argNames) {
this.argNames = argNames;
}
public void addArg(ExtractionObject a) {
this.args.add(a);
}
public boolean isNegativeRelation() {
return isUnrelatedLabel(getType());
}
/**
* Find the left-most position of an argument's syntactic head
*/
public int getFirstSyntacticHeadPosition() {
int pos = Integer.MAX_VALUE;
for (ExtractionObject obj : args) {
if(obj instanceof EntityMention){
EntityMention em = (EntityMention) obj;
if(em.getSyntacticHeadTokenPosition() < pos) {
pos = em.getSyntacticHeadTokenPosition();
}
}
}
if(pos != Integer.MAX_VALUE) return pos;
return -1;
}
/**
* Find the right-most position of an argument's syntactic head
*/
public int getLastSyntacticHeadPosition() {
int pos = Integer.MIN_VALUE;
for (ExtractionObject obj : args) {
if(obj instanceof EntityMention){
EntityMention em = (EntityMention) obj;
if(em.getSyntacticHeadTokenPosition() > pos) {
pos = em.getSyntacticHeadTokenPosition();
}
}
}
if(pos != Integer.MIN_VALUE) return pos;
return -1;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("RelationMention [type=" + type
+ (subType != null ? ", subType=" + subType : "")
+ ", start=" + getExtentTokenStart() + ", end=" + getExtentTokenEnd());
if(typeProbabilities != null){
sb.append(", " + probsToString());
}
if(args != null){
for(int i = 0; i < args.size(); i ++){
sb.append("\n\t");
if(argNames != null) sb.append(argNames.get(i) + " ");
sb.append(args.get(i));
}
}
sb.append("\n]");
return sb.toString();
}
/**
* Replaces the arguments of this relations with equivalent mentions from the predictedMentions list
* This works only for arguments that are EntityMention!
* @param predictedMentions
*/
public boolean replaceGoldArgsWithPredicted(List<EntityMention> predictedMentions) {
List<ExtractionObject> newArgs = new ArrayList<>();
for(ExtractionObject arg: args){
if(! (arg instanceof EntityMention)){
continue;
}
EntityMention goldEnt = (EntityMention) arg;
EntityMention newArg = null;
for(EntityMention pred: predictedMentions){
if(goldEnt.textEquals(pred)){
newArg = pred;
break;
}
}
if(newArg != null){
newArgs.add(newArg);
logger.info("Replacing relation argument: [" + goldEnt + "] with predicted mention [" + newArg + "]");
} else {
/*
logger.info("Failed to match relation argument: " + goldEnt);
return false;
*/
newArgs.add(goldEnt);
predictedMentions.add(goldEnt);
logger.info("Failed to match relation argument, so keeping gold: " + goldEnt);
}
}
this.args = newArgs;
return true;
}
public void removeArgument(ExtractionObject argToRemove, boolean removeParent) {
Set<ExtractionObject> thisEvent = new IdentityHashSet<>();
thisEvent.add(argToRemove);
removeArguments(thisEvent, removeParent);
}
public void removeArguments(Set<ExtractionObject> argsToRemove, boolean removeParent) {
List<ExtractionObject> newArgs = new ArrayList<>();
List<String> newArgNames = new ArrayList<>();
for(int i = 0; i < args.size(); i ++){
ExtractionObject a = args.get(i);
String n = argNames.get(i);
if(! argsToRemove.contains(a)){
newArgs.add(a);
newArgNames.add(n);
} else {
if(a instanceof EventMention && removeParent){
((EventMention) a).removeParent(this);
}
}
}
args = newArgs;
argNames = newArgNames;
}
public boolean printableObject(double beam) {
return printableObject(beam, RelationMention.UNRELATED);
}
public void setSignature(String s) { signature = s; }
public String getSignature() { return signature; }
/*
* Static utility functions
*/
public static Collection<RelationMention> filterUnrelatedRelations(Collection<RelationMention> relationMentions) {
Collection<RelationMention> filtered = new ArrayList<>();
for (RelationMention relation : relationMentions) {
if (!relation.getType().equals(UNRELATED)) {
filtered.add(relation);
}
}
return filtered;
}
/**
* Creates a new unique id for a relation mention
* @return the new id
*/
public static synchronized String makeUniqueId() {
MENTION_COUNTER++;
return "RelationMention-" + MENTION_COUNTER;
}
public static RelationMention createUnrelatedRelation(RelationMentionFactory factory, ExtractionObject ... args) {
return createUnrelatedRelation(factory, "",args);
}
private static RelationMention createUnrelatedRelation(RelationMentionFactory factory, String type, ExtractionObject ... args) {
return factory.constructRelationMention(
RelationMention.makeUniqueId(), args[0].getSentence(), ExtractionObject.getSpan(args),
RelationMention.UNRELATED + type, null, Arrays.asList(args), null);
}
public static boolean isUnrelatedLabel(String label) {
return label.startsWith(UNRELATED);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof RelationMention)) return false;
if (!super.equals(o)) return false;
RelationMention that = (RelationMention) o;
if (argNames != null ? !argNames.equals(that.argNames) : that.argNames != null) return false;
if (args != null ? !args.equals(that.args) : that.args != null) return false;
if (signature != null ? !signature.equals(that.signature) : that.signature != null) return false;
return true;
}
@Override
public int hashCode() {
int result = argNames != null ? argNames.hashCode() : 0;
result = 31 * result + (args != null ? args.hashCode() : 0);
result = 31 * result + (signature != null ? signature.hashCode() : 0);
return result;
}
}