From 05122a844e1d66467d1a6adad08b5fc5a6f1756f Mon Sep 17 00:00:00 2001 From: austin Date: Tue, 31 Dec 2024 11:58:35 -0500 Subject: [PATCH] argument querying in copular transfer eval --- .../evaluators/transfer/TransferEval.java | 66 ++++++++--------- .../parser/PennTreebankTagSet.java | 20 ++++- .../conversation/parser/depend/Argument.java | 15 ++++ .../ai/conversation/parser/depend/Clause.java | 2 + .../conversation/parser/depend/Predicate.java | 21 ++++++ .../ai/conversation/tracking/Quote.java | 8 ++ .../ai/conversation/web/ArgumentQuery.java | 34 ++++++++- .../ai/conversation/web/QuoteQuery.java | 18 +++++ .../ai/knowledge/KnowledgeWeb.java | 8 ++ .../ai/linguistics/PosessionQuery.java | 74 +++++++++++++++++++ .../ai/linguistics/ProperNounQuery.java | 33 +++++++++ 11 files changed, 258 insertions(+), 41 deletions(-) create mode 100644 src/main/java/org/studiorailgun/ai/linguistics/PosessionQuery.java create mode 100644 src/main/java/org/studiorailgun/ai/linguistics/ProperNounQuery.java diff --git a/src/main/java/org/studiorailgun/ai/conversation/evaluators/transfer/TransferEval.java b/src/main/java/org/studiorailgun/ai/conversation/evaluators/transfer/TransferEval.java index f81ee22..08c20e4 100644 --- a/src/main/java/org/studiorailgun/ai/conversation/evaluators/transfer/TransferEval.java +++ b/src/main/java/org/studiorailgun/ai/conversation/evaluators/transfer/TransferEval.java @@ -1,20 +1,18 @@ package org.studiorailgun.ai.conversation.evaluators.transfer; -import org.studiorailgun.Globals; -import org.studiorailgun.ai.conversation.evaluators.query.NounStack; import org.studiorailgun.ai.conversation.parser.NLPDependencies; import org.studiorailgun.ai.conversation.parser.PennTreebankTagSet; +import org.studiorailgun.ai.conversation.parser.depend.Argument; import org.studiorailgun.ai.conversation.parser.depend.Clause; +import org.studiorailgun.ai.conversation.parser.depend.Predicate; import org.studiorailgun.ai.conversation.tracking.Conversation; import org.studiorailgun.ai.conversation.tracking.Quote; import org.studiorailgun.ai.conversation.tracking.Sentence; import org.studiorailgun.ai.conversation.web.ArgumentQuery; import org.studiorailgun.ai.knowledge.Node; -import org.studiorailgun.ai.knowledge.query.NodePropQuery; import edu.stanford.nlp.ling.IndexedWord; import edu.stanford.nlp.semgraph.SemanticGraph; -import edu.stanford.nlp.trees.GrammaticalRelation; /** * Evaluates a transfer statement @@ -35,21 +33,22 @@ public class TransferEval { throw new UnsupportedOperationException(message); } IndexedWord root = semanticGraph.getFirstRoot(); + Clause mainClause = sentence.getMainClause(); + Predicate pred = mainClause.getPredicate(); if(PennTreebankTagSet.isNoun(root.tag())){ - if(TransferEval.isCopularStatement(semanticGraph)){ - NounStack noun1 = NounStack.parse(semanticGraph, root); - NounStack noun2 = null; - for(IndexedWord child : semanticGraph.getChildList(root)){ - if(PennTreebankTagSet.isNoun(child.tag())){ - noun2 = NounStack.parse(semanticGraph, child); - break; - } + if(TransferEval.isCopularStatement(pred)){ + //this is a copular predicate + Argument argument1 = mainClause.getSubject(); + Argument argument2 = mainClause.getPredicate().getCopularArg(); + if(argument1 == null || argument2 == null){ + String message = "Failed to parse arguments for copular statement! \n" + + argument1 + " " + argument2 + "\n" + + sentence.getRaw() + "\n" + + semanticGraph; + throw new Error(message); } - if(noun2 == null){ - throw new Error("Failed to parse second noun! " + semanticGraph); - } - TransferEval.evaluateCopularStatement(conversation, quote, sentence, noun1, null); + TransferEval.evaluateCopularStatement(conversation, quote, sentence, argument1, argument2); } else { String message = "Unsupported root type!\n" + "\"" + sentence.getRaw() + "\"\n" + @@ -72,11 +71,11 @@ public class TransferEval { * @param noun1 The first noun stack * @param noun2 The second noun stack */ - private static void evaluateCopularStatement(Conversation conversation, Quote quote, Sentence sentence, NounStack noun1, NounStack noun2){ + private static void evaluateCopularStatement(Conversation conversation, Quote quote, Sentence sentence, Argument subject, Argument object){ SemanticGraph graph = sentence.getGraph(); IndexedWord verb = NLPDependencies.getCopular(graph); if(PennTreebankTagSet.isBe(verb.tag())){ - TransferEval.evaluateAssignmentStatement(conversation, quote, sentence, noun1, noun2); + TransferEval.evaluateAssignmentStatement(conversation, quote, sentence, subject, object); } else { throw new Error("Unsupported verb type! " + graph); } @@ -88,18 +87,20 @@ public class TransferEval { * @param quote The quote * @param sentence The sentence */ - private static void evaluateAssignmentStatement(Conversation conversation, Quote quote, Sentence sentence, NounStack noun1, NounStack noun2){ + private static void evaluateAssignmentStatement(Conversation conversation, Quote quote, Sentence sentence, Argument subjectArg, Argument objectArg){ //if there is no node with this name already, create one System.out.println(sentence.getGraph()); - Clause mainClause = sentence.getMainClause(); - String noun1RootText = noun1.getIndexedWord().originalText(); - if(NodePropQuery.byName(noun1RootText).size() < 1){ - Globals.web.createNode(noun1RootText); + + Node subjectNode = ArgumentQuery.getArgument(quote, subjectArg); + if(subjectNode == null){ + throw new Error("Error querying subject!"); } - Node subject = ArgumentQuery.getArgument(quote, mainClause.getSubject()); - // Node object = ArgumentQuery.getArgument(quote, mainClause.getDirectObject()); - //make an equivalence link here + Node objectNode = ArgumentQuery.getArgument(quote, objectArg); + if(objectNode == null){ + throw new Error("Error querying object!"); + } + //make an equivalence link here if it doesn't exist already //for the moment, assume the other noun is a concept @@ -107,18 +108,11 @@ public class TransferEval { /** * Checks if this is a copular statement - * @param graph The graph + * @param pred The predicate of the sentence * @return true if it is a copular statement, false otherwise */ - private static boolean isCopularStatement(SemanticGraph graph){ - IndexedWord root = graph.getFirstRoot(); - for(IndexedWord child : graph.getChildList(root)){ - GrammaticalRelation relation = graph.reln(root,child); - if(NLPDependencies.isCopular(relation.getLongName())){ - return true; - } - } - return false; + private static boolean isCopularStatement(Predicate pred){ + return pred.getCopular() != null; } } diff --git a/src/main/java/org/studiorailgun/ai/conversation/parser/PennTreebankTagSet.java b/src/main/java/org/studiorailgun/ai/conversation/parser/PennTreebankTagSet.java index b39c7cc..e6fc130 100644 --- a/src/main/java/org/studiorailgun/ai/conversation/parser/PennTreebankTagSet.java +++ b/src/main/java/org/studiorailgun/ai/conversation/parser/PennTreebankTagSet.java @@ -79,6 +79,23 @@ public class PennTreebankTagSet { } } + /** + * Checks if this is a proper noun or not + * @param tag The tag + * @return true if it is a proper noun, false otherwise + */ + public static boolean isCommonNoun(String tag){ + switch(tag){ + case "NN": + case "NNS": { + return true; + } + default: { + return false; + } + } + } + /** * Checks if this tag is a proper noun or not * @param tag The tag @@ -87,7 +104,8 @@ public class PennTreebankTagSet { public static boolean isProperNoun(String tag){ switch(tag){ case "NP": - case "NPS": { + case "NPS": + case "NNP": { return true; } default: { diff --git a/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Argument.java b/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Argument.java index 4d1a32e..7d59263 100644 --- a/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Argument.java +++ b/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Argument.java @@ -123,6 +123,21 @@ public class Argument { rVal.adverb = child; } break; + + //this will have already been parsed at the clause level, want to ignore now + case "advcl_preposition": + //this will have already been parsed at the clause level, want to ignore now + case "parataxis": + //this will have already been parsed at the clause level, want to ignore now + case "punctuation": + //this will have already been parsed at the clause level, want to ignore now + case "copula": + //this can occur when we are parsing the root nominal of a non-verbal copular sentence -- we would expect the root to point at the subject of the copular and we don't want that subject to be a part of this argument + case "nominal subject": { + //cases to ignore + continue; + } + //unhandled cases default: { throw new Error("Unsupported relation type! " + relation.getLongName() + "\n" + "for " + child.originalText() + "\n" + graph); diff --git a/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Clause.java b/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Clause.java index acb38c9..371d1bb 100644 --- a/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Clause.java +++ b/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Clause.java @@ -131,6 +131,8 @@ public class Clause { case "copula": { //this means the root is a noun, but this related word is turning it into a copular predicate pred.setCopular(child); + Argument copularArg = Argument.parse(graph, pred.getRoot(), ArgumentType.NOMINAL); + pred.setCopularArg(copularArg); } break; //a prepositional adjunct (oblique) diff --git a/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Predicate.java b/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Predicate.java index 47c7819..9e73134 100644 --- a/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Predicate.java +++ b/src/main/java/org/studiorailgun/ai/conversation/parser/depend/Predicate.java @@ -87,6 +87,11 @@ public class Predicate { */ IndexedWord copularVerb; + /** + * The argument that this predicate is storing given that it is a copular predicate + */ + Argument copularArg; + /** * Constructor * @param root The root of the predicate @@ -135,6 +140,22 @@ public class Predicate { return this.copularVerb; } + /** + * Gets the copular argument for this predicate + * @param arg The copular argument + */ + public void setCopularArg(Argument arg){ + this.copularArg = arg; + } + + /** + * Gets the copular argument for this predicate + * @return The copular argument + */ + public Argument getCopularArg(){ + return copularArg; + } + /** * Sets the existential status of the predicate * @param existential true if existential, false otherwise diff --git a/src/main/java/org/studiorailgun/ai/conversation/tracking/Quote.java b/src/main/java/org/studiorailgun/ai/conversation/tracking/Quote.java index 262a75b..a6a82f1 100644 --- a/src/main/java/org/studiorailgun/ai/conversation/tracking/Quote.java +++ b/src/main/java/org/studiorailgun/ai/conversation/tracking/Quote.java @@ -84,6 +84,14 @@ public class Quote { public void setNode(Node node){ this.node = node; } + + /** + * Gets the node corresponding to this quote + * @return The node + */ + public Node getNode(){ + return node; + } } diff --git a/src/main/java/org/studiorailgun/ai/conversation/web/ArgumentQuery.java b/src/main/java/org/studiorailgun/ai/conversation/web/ArgumentQuery.java index 01b7ae1..e077b0b 100644 --- a/src/main/java/org/studiorailgun/ai/conversation/web/ArgumentQuery.java +++ b/src/main/java/org/studiorailgun/ai/conversation/web/ArgumentQuery.java @@ -1,8 +1,17 @@ package org.studiorailgun.ai.conversation.web; +import java.util.List; +import java.util.stream.Collectors; + +import org.studiorailgun.Globals; +import org.studiorailgun.ai.conversation.parser.PennTreebankTagSet; import org.studiorailgun.ai.conversation.parser.depend.Argument; import org.studiorailgun.ai.conversation.tracking.Quote; import org.studiorailgun.ai.knowledge.Node; +import org.studiorailgun.ai.knowledge.Relation; +import org.studiorailgun.ai.knowledge.query.InstanceQuery; +import org.studiorailgun.ai.knowledge.types.RelationTypes; +import org.studiorailgun.ai.linguistics.ProperNounQuery; import org.studiorailgun.ai.philosophy.ConceptQuery; /** @@ -18,19 +27,22 @@ public class ArgumentQuery { */ public static Node getArgument(Quote quote, Argument argument){ String argumentRoot = argument.getRoot().originalText(); + String argumentTag = argument.getRoot().tag(); //the root node which then had modifiers applied to it Node rootNode = null; - Node conceptNode = ConceptQuery.getConcept(argumentRoot); - if(conceptNode != null){ + if(PennTreebankTagSet.isCommonNoun(argumentTag)){ + Node conceptNode = ConceptQuery.getConcept(argumentRoot); rootNode = conceptNode; //apply possessive modifier if(argument.getPossessiveModifier() != null){ rootNode = ArgumentQuery.getPossessionModified(quote, rootNode, argument); } + } else if(PennTreebankTagSet.isProperNoun(argumentTag)) { + rootNode = ProperNounQuery.getProperNoun(argumentRoot); } else { - throw new Error("TODO: solve for proper nouns"); + throw new Error("Undefined case! " + argumentTag); } return rootNode; @@ -47,7 +59,21 @@ public class ArgumentQuery { String possessionModifier = argument.getPossessiveModifier().originalText().toLowerCase(); switch(possessionModifier){ case "my": { - + List instances = InstanceQuery.getInstances(rootNode); + List possessedInstances = instances.stream().filter(node -> { + List relations = Globals.web.getRelationsOfChildNode(node); + List possessionRelationsToRoot = relations.stream().filter(relation -> relation.getName().equals(RelationTypes.POSSESSION_OF) && relation.getParent().equals(rootNode)).collect(Collectors.toList()); + return possessionRelationsToRoot.size() > 0; + }).collect(Collectors.toList()); + if(possessedInstances.size() == 1){ + //already exists, return it + rVal = possessedInstances.get(0); + } else if(possessedInstances.size() > 1) { + //already exists, return it + throw new Error("Can't currently handle plural instances!"); + } else { + //does not already exist, see if we can generate a node to encapsulate it + } } break; default: { throw new Error("Unsupported possession modifier! " + possessionModifier); diff --git a/src/main/java/org/studiorailgun/ai/conversation/web/QuoteQuery.java b/src/main/java/org/studiorailgun/ai/conversation/web/QuoteQuery.java index 2eb3c25..84bc8a0 100644 --- a/src/main/java/org/studiorailgun/ai/conversation/web/QuoteQuery.java +++ b/src/main/java/org/studiorailgun/ai/conversation/web/QuoteQuery.java @@ -1,7 +1,11 @@ package org.studiorailgun.ai.conversation.web; +import java.util.List; +import java.util.stream.Collectors; + import org.studiorailgun.Globals; import org.studiorailgun.ai.knowledge.Node; +import org.studiorailgun.ai.knowledge.Relation; import org.studiorailgun.ai.knowledge.types.RelationTypes; /** @@ -27,4 +31,18 @@ public class QuoteQuery { Globals.web.createRelation(RelationTypes.MEMBER_OF, conversation, quote); } + /** + * Gets the speaker of a quote + * @param quote The quote + * @return The speaker of the quote + */ + public static Node getSpeaker(Node quote){ + List relations = Globals.web.getRelationsOfChildNode(quote); + List speakers = relations.stream().filter(relation -> relation.getName().equals(RelationTypes.QUOTE_OF)).map(relation -> relation.getParent()).collect(Collectors.toList()); + if(speakers.size() != 1){ + throw new Error("Invalid number of speakers! " + speakers.size()); + } + return speakers.get(0); + } + } diff --git a/src/main/java/org/studiorailgun/ai/knowledge/KnowledgeWeb.java b/src/main/java/org/studiorailgun/ai/knowledge/KnowledgeWeb.java index b7fd8c3..bc2312a 100644 --- a/src/main/java/org/studiorailgun/ai/knowledge/KnowledgeWeb.java +++ b/src/main/java/org/studiorailgun/ai/knowledge/KnowledgeWeb.java @@ -254,6 +254,14 @@ public class KnowledgeWeb { return nodes.values(); } + /** + * Gets the list of all nodes + * @return The collection + */ + public List getNodesList() { + return new LinkedList(nodes.values()); + } + /** * Gets the collection of all relations * @return The collection of all relations diff --git a/src/main/java/org/studiorailgun/ai/linguistics/PosessionQuery.java b/src/main/java/org/studiorailgun/ai/linguistics/PosessionQuery.java new file mode 100644 index 0000000..1ab2139 --- /dev/null +++ b/src/main/java/org/studiorailgun/ai/linguistics/PosessionQuery.java @@ -0,0 +1,74 @@ +package org.studiorailgun.ai.linguistics; + +import java.util.List; +import java.util.stream.Collectors; + +import org.studiorailgun.Globals; +import org.studiorailgun.ai.conversation.parser.depend.Argument; +import org.studiorailgun.ai.conversation.tracking.Quote; +import org.studiorailgun.ai.conversation.web.QuoteQuery; +import org.studiorailgun.ai.knowledge.Node; +import org.studiorailgun.ai.knowledge.Relation; +import org.studiorailgun.ai.knowledge.query.InstanceQuery; +import org.studiorailgun.ai.knowledge.types.RelationTypes; + +/** + * Queries related to possession + */ +public class PosessionQuery { + + /** + * Gets the root node with the possession modifier applied + * @param rootNode The root node + * @param argument The argument + * @return The node that represents the possession modifier applied to the root node + */ + public static Node getPossessionModified(Quote quote, Node rootNode, Argument argument){ + Node rVal = rootNode; + String possessionModifier = argument.getPossessiveModifier().originalText().toLowerCase(); + switch(possessionModifier){ + case "my": { + List instances = InstanceQuery.getInstances(rootNode); + List possessedInstances = instances.stream().filter((Node node) -> { + List relations = Globals.web.getRelationsOfChildNode(node); + List possessionRelationsToRoot = relations.stream().filter(relation -> relation.getName().equals(RelationTypes.POSSESSION_OF) && relation.getParent().equals(rootNode)).collect(Collectors.toList()); + return possessionRelationsToRoot.size() > 0; + }).collect(Collectors.toList()); + if(possessedInstances.size() == 1){ + //already exists, return it + rVal = possessedInstances.get(0); + } else if(possessedInstances.size() > 1) { + //already exists, return it + throw new Error("Can't currently handle plural instances!"); + } else { + rVal = PosessionQuery.generatePossessionModified(quote, rootNode, argument); + } + } break; + default: { + throw new Error("Unsupported possession modifier! " + possessionModifier); + } + } + return rVal; + } + + /** + * Generates a node to fill in the instance of the possessor owning an instance of the root node + * @param quote The quote + * @param rootNode The root node + * @param argument The argument + * @return The generated node + */ + private static Node generatePossessionModified(Quote quote, Node rootNode, Argument argument){ + Node possession = Globals.web.createNode("possession(inst)"); + + //attach to concept + Globals.web.createRelation(RelationTypes.INSTANCE_OF, rootNode, possession); + + //attach to speaker + Node possessor = QuoteQuery.getSpeaker(quote.getNode()); + Globals.web.createRelation(RelationTypes.POSSESSION_OF, possessor, possession); + + return possession; + } + +} diff --git a/src/main/java/org/studiorailgun/ai/linguistics/ProperNounQuery.java b/src/main/java/org/studiorailgun/ai/linguistics/ProperNounQuery.java new file mode 100644 index 0000000..4f5a366 --- /dev/null +++ b/src/main/java/org/studiorailgun/ai/linguistics/ProperNounQuery.java @@ -0,0 +1,33 @@ +package org.studiorailgun.ai.linguistics; + +import java.util.List; +import java.util.stream.Collectors; + +import org.studiorailgun.Globals; +import org.studiorailgun.ai.knowledge.Node; + +/** + * Queries related to proper nouns + */ +public class ProperNounQuery { + + /** + * Tries to locate a proper noun + * @param properNoun The proper noun + * @return The node representing the proper noun + */ + public static Node getProperNoun(String properNoun){ + Node rVal = null; + List nodes = Globals.web.getNodesList().stream().filter(node -> node.getName().equals(properNoun)).collect(Collectors.toList()); + if(nodes.size() < 1){ + //create a name node to represent this name + rVal = Globals.web.createNode(properNoun); + } else if(nodes.size() == 1){ + rVal = nodes.get(0); + } else { + throw new Error("More than one proper match found! " + nodes.size()); + } + return rVal; + } + +}