From 1b2eb00299635cd42415359a4defb66bd84ff168 Mon Sep 17 00:00:00 2001 From: FishJoy Date: Wed, 18 Sep 2024 15:53:55 +0800 Subject: [PATCH] feat(reasoner): support ProntoQA and ProofWriter. (#352) Co-authored-by: kejian --- .../openspg/reasoner/thinker/TripleStore.java | 5 +- .../thinker/engine/DefaultThinker.java | 14 +- .../reasoner/thinker/engine/Graph.java | 5 +- .../reasoner/thinker/engine/GraphStore.java | 26 ++- .../reasoner/thinker/engine/InfGraph.java | 221 ++++++++---------- .../thinker/engine/MemTripleStore.java | 39 +--- .../reasoner/thinker/engine/TripleGroup.java | 69 ++++++ .../reasoner/thinker/logic/LogicNetwork.java | 26 +-- .../reasoner/thinker/logic/graph/Any.java | 39 +++- .../logic/graph/CombinationEntity.java | 5 - .../reasoner/thinker/logic/graph/Element.java | 10 +- .../reasoner/thinker/logic/graph/Entity.java | 7 +- .../reasoner/thinker/logic/graph/Node.java | 2 + .../thinker/logic/graph/Predicate.java | 7 +- .../reasoner/thinker/logic/graph/Triple.java | 15 +- .../thinker/logic/rule/ClauseEntry.java | 4 +- .../thinker/logic/rule/EntityPattern.java | 12 +- .../thinker/logic/rule/TriplePattern.java | 11 +- .../thinker/SimplifyThinkerParser.scala | 2 +- .../reasoner/thinker/ThinkerRuleParser.scala | 124 ++++++---- .../openspg/reasoner/thinker/GraphUtil.java | 14 +- .../reasoner/thinker/HypertensionTest.java | 2 + .../openspg/reasoner/thinker/MedTests.java | 15 ++ .../reasoner/thinker/ProntoQATests.java | 71 ++++++ .../reasoner/thinker/ProofWriterTest.java | 72 ++++++ .../thinker/src/test/resources/Medical.txt | 10 +- .../thinker/src/test/resources/ProntoQA.txt | 59 +++++ .../src/test/resources/ProofWriter.txt | 35 +++ .../thinker/SimplifyThinkerParserTest.scala | 98 ++++++-- 29 files changed, 720 insertions(+), 299 deletions(-) create mode 100644 reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/TripleGroup.java create mode 100644 reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/ProntoQATests.java create mode 100644 reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/ProofWriterTest.java create mode 100644 reasoner/thinker/src/test/resources/ProntoQA.txt create mode 100644 reasoner/thinker/src/test/resources/ProofWriter.txt diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/TripleStore.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/TripleStore.java index d797a1e70..7d40fd783 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/TripleStore.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/TripleStore.java @@ -14,7 +14,6 @@ package com.antgroup.openspg.reasoner.thinker; import com.antgroup.openspg.reasoner.thinker.logic.graph.Element; -import com.antgroup.openspg.reasoner.thinker.logic.graph.Entity; import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; import java.util.Collection; import java.util.Map; @@ -22,9 +21,7 @@ public interface TripleStore { void init(Map param); - Collection find(final Element pattern); - - void addEntity(Entity entity); + Collection find(final Triple triple); void addTriple(Triple triple); diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/DefaultThinker.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/DefaultThinker.java index ebcb4522e..ef434b94f 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/DefaultThinker.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/DefaultThinker.java @@ -18,9 +18,7 @@ import com.antgroup.openspg.reasoner.thinker.Thinker; import com.antgroup.openspg.reasoner.thinker.catalog.LogicCatalog; import com.antgroup.openspg.reasoner.thinker.logic.Result; -import com.antgroup.openspg.reasoner.thinker.logic.graph.Element; -import com.antgroup.openspg.reasoner.thinker.logic.graph.Node; -import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; +import com.antgroup.openspg.reasoner.thinker.logic.graph.*; import java.util.*; public class DefaultThinker implements Thinker { @@ -46,6 +44,7 @@ public List find(Element s, Element p, Element o) { public List find(Element s, Element p, Element o, Map context) { this.infGraph.clear(); Triple pattern = Triple.create(s, p, o); + this.infGraph.prepare(context); List result = this.infGraph.find(pattern, context == null ? new HashMap<>() : context); return result; } @@ -53,6 +52,13 @@ public List find(Element s, Element p, Element o, Map co @Override public List find(Node s, Map context) { this.infGraph.clear(); - return this.infGraph.find(s, context == null ? new HashMap<>() : context); + this.infGraph.prepare(context); + List triples = + this.infGraph.find(Triple.create(s), context == null ? new HashMap<>() : context); + List results = new LinkedList<>(); + for (Result triple : triples) { + results.add(new Result(((Triple) triple.getData()).getObject(), triple.getTraceLog())); + } + return results; } } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/Graph.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/Graph.java index 670adbd01..629d37d03 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/Graph.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/Graph.java @@ -14,7 +14,6 @@ package com.antgroup.openspg.reasoner.thinker.engine; import com.antgroup.openspg.reasoner.thinker.logic.Result; -import com.antgroup.openspg.reasoner.thinker.logic.graph.Node; import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; import java.util.List; import java.util.Map; @@ -22,7 +21,7 @@ public interface Graph { void init(Map param); - List find(Triple pattern, Map context); + void prepare(Map context); - List find(Node s, Map context); + List find(Triple pattern, Map context); } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/GraphStore.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/GraphStore.java index 751ca631a..c157e7661 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/GraphStore.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/GraphStore.java @@ -37,6 +37,9 @@ public void init(Map param) { this.graphState.init(param); } + @Override + public void prepare(Map context) {} + @Override public List find(Triple pattern, Map context) { List data; @@ -62,11 +65,6 @@ public List find(Triple pattern, Map context) { return matchInGraph(pattern, data); } - @Override - public List find(Node s, Map context) { - return Collections.emptyList(); - } - protected List getTriple(Entity s, Element predicate, Element o, Direction direction) { List triples = new LinkedList<>(); if (direction == Direction.OUT) { @@ -80,7 +78,7 @@ protected List getTriple(Entity s, Element predicate, Element o, Directi } } List> edges; - String spo = toSPO(s, predicate, o); + String spo = toSPO(s, predicate, o, direction); if (StringUtils.isNotBlank(spo)) { edges = this.graphState.getEdges( @@ -101,17 +99,25 @@ protected List getTriple(Entity s, Element predicate, Element o, Directi return triples; } - private String toSPO(Entity s, Element predicate, Element o) { + private String toSPO(Entity s, Element predicate, Element o, Direction direction) { if (!(predicate instanceof Predicate) || StringUtils.isBlank(((Predicate) predicate).getName())) { return null; } SPO spo; if (o instanceof Node) { - spo = new SPO(s.getType(), ((Predicate) predicate).getName(), ((Node) o).getType()); + if (direction == Direction.OUT) { + spo = new SPO(s.getType(), ((Predicate) predicate).getName(), ((Node) o).getType()); + } else { + spo = new SPO(((Node) o).getType(), ((Predicate) predicate).getName(), s.getType()); + } return spo.toString(); } else if (o instanceof Entity) { - spo = new SPO(s.getType(), ((Predicate) predicate).getName(), ((Entity) o).getType()); + if (direction == Direction.OUT) { + spo = new SPO(s.getType(), ((Predicate) predicate).getName(), ((Entity) o).getType()); + } else { + spo = new SPO(((Entity) o).getType(), ((Predicate) predicate).getName(), s.getType()); + } return spo.toString(); } else { return null; @@ -124,7 +130,7 @@ protected List getTriple(Triple triple, Predicate predicate) { Predicate p = (Predicate) triple.getPredicate(); Entity o = (Entity) triple.getObject(); List> edges; - String edgeType = toSPO(s, p, o); + String edgeType = toSPO(s, p, o, Direction.OUT); if (StringUtils.isNotBlank(edgeType)) { edges = this.graphState.getEdges( diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/InfGraph.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/InfGraph.java index 7603ec32a..ae1353fdf 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/InfGraph.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/InfGraph.java @@ -23,7 +23,6 @@ import com.antgroup.openspg.reasoner.thinker.logic.rule.TreeLogger; import com.antgroup.openspg.reasoner.thinker.logic.rule.visitor.RuleExecutor; import java.util.*; -import java.util.concurrent.LinkedBlockingDeque; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -54,7 +53,6 @@ public void init(Map param) { public List find(Triple pattern, Map context) { logger.info("InfGraph find pattern={}, context={}", pattern, context); List result = new LinkedList<>(); - prepareContext(context); // Step1: find pattern in graph List dataInGraph = graphStore.find(pattern, context); logger.info("GraphStore find pattern={}, result={}", pattern, dataInGraph); @@ -72,17 +70,7 @@ public List find(Triple pattern, Map context) { } @Override - public List find(Node s, Map context) { - prepareContext(context); - return inference(s, context); - } - - public List find(Entity s, Map context) { - prepareContext(context); - return inference(s, context); - } - - private void prepareContext(Map context) { + public void prepare(Map context) { if (context != null) { for (Object val : context.values()) { if (val instanceof Entity) { @@ -94,14 +82,14 @@ private void prepareContext(Map context) { } } - private List inference(Element pattern, Map context) { + private List inference(Triple pattern, Map context) { List rst = new LinkedList<>(); boolean strictMode = (Boolean) context.getOrDefault(Constants.SPG_REASONER_THINKER_STRICT, false); for (Rule rule : logicNetwork.getBackwardRules(pattern)) { - List body = - rule.getBody().stream().map(ClauseEntry::toElement).collect(Collectors.toList()); - List> data = prepareElements(body, rule.getHead().toElement(), pattern, context); + List body = + rule.getBody().stream().map(ClauseEntry::toTriple).collect(Collectors.toList()); + List> data = prepareElements(body, rule.getHead().toTriple(), pattern, context); if (CollectionUtils.isEmpty(data)) { TreeLogger traceLogger = new TreeLogger(rule.getRoot().toString()); Boolean ret = @@ -109,7 +97,7 @@ private List inference(Element pattern, Map context) { .accept(new LinkedList<>(), context, new RuleExecutor(strictMode), traceLogger); traceLogger.setCurrentNodeMsg(rule.getDesc()); if (ret) { - Element ele = rule.getHead().toElement(); + Element ele = rule.getHead().toTriple(); rst.add(new Result(ele, traceLogger)); if (ele instanceof Triple) { addTriple((Triple) ele); @@ -136,7 +124,7 @@ private List inference(Element pattern, Map context) { } traceLogger.setCurrentNodeMsg(StringUtils.join(msgs, ";")); if (ret) { - Element ele = rule.getHead().toElement(); + Element ele = rule.getHead().toTriple(); rst.add(new Result(bindResult(dList, ele), traceLogger)); if (ele instanceof Triple) { addTriple((Triple) ele); @@ -172,70 +160,57 @@ private Element bindResult(List data, Element pattern) { map.getOrDefault(t.getObject().alias(), ((Triple) pattern).getObject())); } + private Triple binding(Triple triple, Triple pattern) { + Map aliasToElement = new HashMap<>(); + aliasToElement.put(pattern.getSubject().alias(), pattern.getSubject()); + aliasToElement.put(pattern.getObject().alias(), pattern.getObject()); + Element sub = aliasToElement.getOrDefault(triple.getSubject().alias(), triple.getSubject()); + Element pre = aliasToElement.getOrDefault(triple.getPredicate().alias(), triple.getPredicate()); + Element obj = aliasToElement.getOrDefault(triple.getObject().alias(), triple.getObject()); + return new Triple(sub, pre, obj); + } + private List> prepareElements( - List body, Element head, Element pattern, Map context) { + List body, Triple head, Triple pattern, Map context) { List> elements = new ArrayList<>(); - Set choose = new HashSet<>(); - Queue starts = new LinkedBlockingDeque<>(); - Element start = getStart(pattern, head); - starts.add(start); - while (choose.size() < body.size()) { - Element s = starts.poll(); - if (s == null) { - break; - } - if (s instanceof Node && elements.isEmpty()) { - break; - } - for (Element e : body) { - if (choose.contains(e)) { - continue; - } - if (e instanceof Entity) { - choose.add(e); - if (CollectionUtils.isEmpty(elements)) { - elements.add(new LinkedList<>(prepareElement(e, context))); - } else { - for (List evidence : elements) { - evidence.addAll(prepareElement(e, context)); - } - } - } else if (!e.canInstantiated()) { - List> singeRst = prepareElement(null, (Triple) e, context); - if (CollectionUtils.isEmpty(elements)) { - elements.addAll(singeRst); - } else if (CollectionUtils.isNotEmpty(singeRst)) { - List> tmpElements = new LinkedList<>(); - for (List ele : elements) { - for (List single : singeRst) { - List cp = new LinkedList<>(ele); - cp.addAll(single); - tmpElements.add(cp); - } - } - elements.clear(); - elements = tmpElements; + Triple bindingPattern = (Triple) pattern.bind(head); + List bindingBody = new ArrayList<>(body.size()); + for (Triple e : body) { + bindingBody.add(binding(e, bindingPattern)); + } + TripleGroup tripleGroup = new TripleGroup(bindingBody); + List> groups = tripleGroup.group(); + for (List group : groups) { + Map starts = getStart(group); + Set choose = new HashSet<>(); + while (choose.size() < group.size()) { + for (Triple e : group) { + if (choose.contains(e)) { + continue; } - } else { - Triple t = (Triple) e; - if (t.getSubject().alias().equals(s.alias())) { - starts.add(t.getObject()); - } else if (t.getObject().alias().equals(s.alias())) { - starts.add(t.getSubject()); - } else if (t.getSubject() instanceof Predicate) { + Collection curStart = + CollectionUtils.intersection(starts.keySet(), tripleAlias(e)); + if (curStart.isEmpty()) { + continue; + } else if (e.getSubject() instanceof Predicate) { if (choose.stream() .filter(ele -> ele instanceof Triple) - .filter(ele -> ((Triple) ele).getPredicate().alias() == t.getSubject().alias()) + .filter(ele -> ele.getPredicate().alias() == e.getSubject().alias()) .count() == 0) { continue; } - } else { - continue; } choose.add(e); + Element s = starts.get(curStart.iterator().next()); + if (s.alias().equals(e.getSubject().alias())) { + starts.put(e.getObject().alias(), e.getObject()); + } else { + starts.put(e.getSubject().alias(), e.getSubject()); + } + starts.put(e.alias(), e); if (CollectionUtils.isEmpty(elements)) { - Triple triple = buildTriple(null, s, t); + Triple triple = bindTriple(null, s, e); if (triple != null) { List> singeRst = prepareElement(null, triple, context); if (CollectionUtils.isNotEmpty(singeRst)) { @@ -245,7 +220,7 @@ private List> prepareElements( } else { List> tmpElements = new LinkedList<>(); for (List evidence : elements) { - Triple triple = buildTriple(evidence, s, t); + Triple triple = bindTriple(evidence, s, e); if (triple != null) { List> singeRst = prepareElement(evidence, triple, context); if (CollectionUtils.isNotEmpty(singeRst)) { @@ -262,55 +237,55 @@ private List> prepareElements( return elements; } - private Triple buildTriple(List evidence, Element s, Triple triple) { - Entity entity = null; - Triple trip = null; - if (CollectionUtils.isEmpty(evidence)) { - entity = (Entity) s; - } else { + private Set tripleAlias(Triple triple) { + return new HashSet<>(Arrays.asList(triple.getSubject().alias(), triple.getObject().alias())); + } + + private Triple bindTriple(List evidence, Element s, Triple triple) { + Map aliasToElement = new HashMap<>(); + if (CollectionUtils.isNotEmpty(evidence)) { for (Result r : evidence) { - Element e = r.getData(); - if (triple.getSubject() instanceof Predicate) { - if (e instanceof Triple - && ((Triple) e).getPredicate().alias() == triple.getSubject().alias()) { - trip = (Triple) e; - } - } else { - if (e instanceof Entity && e.alias() == s.alias()) { - entity = (Entity) r.getData(); - } else if (e instanceof Triple && ((Triple) e).getSubject().alias() == s.alias()) { - entity = (Entity) ((Triple) e).getSubject(); - } else if (e instanceof Triple && ((Triple) e).getObject().alias() == s.alias()) { - entity = (Entity) ((Triple) e).getObject(); - } + Element data = r.getData(); + aliasToElement.put(data.alias(), data); + if (data instanceof Triple) { + aliasToElement.put(((Triple) data).getSubject().alias(), ((Triple) data).getSubject()); + aliasToElement.put(((Triple) data).getObject().alias(), ((Triple) data).getObject()); } } } - - if (entity == null && trip == null) { - return null; - } - if (triple.getSubject().alias() == s.alias()) { - return new Triple(entity, triple.getPredicate(), triple.getObject()); - } else if (triple.getObject().alias() == s.alias()) { - return new Triple(triple.getSubject(), triple.getPredicate(), entity); - } else if (triple.getSubject() instanceof Predicate && trip != null) { - return new Triple(trip, triple.getPredicate(), triple.getObject()); - } else { - return null; + if (!aliasToElement.containsKey(s.alias())) { + aliasToElement.put(s.alias(), s); } + Element sub = aliasToElement.getOrDefault(triple.getSubject().alias(), triple.getSubject()); + Element pre = aliasToElement.getOrDefault(triple.getPredicate().alias(), triple.getPredicate()); + Element obj = aliasToElement.getOrDefault(triple.getObject().alias(), triple.getObject()); + return new Triple(sub, pre, obj); } - private Element getStart(Element element, Element pattern) { - if (element instanceof Entity || element instanceof Node) { - return element.bind(pattern); - } else if (((Triple) element).getSubject() instanceof Entity) { - Element subject = ((Triple) pattern).getSubject(); - return ((Triple) element).getSubject().bind(subject); - } else { - Element object = ((Triple) pattern).getObject(); - return ((Triple) element).getObject().bind(object); + private Map getStart(List triples) { + Map starts = new HashMap<>(); + for (Triple triple : triples) { + if (triple.getSubject() instanceof Entity) { + starts.put(triple.getSubject().alias(), triple.getSubject()); + break; + } else if (triple.getObject() instanceof Entity) { + starts.put(triple.getObject().alias(), triple.getObject()); + break; + } + } + if (!starts.isEmpty()) { + return starts; + } + for (Triple triple : triples) { + if (triple.getSubject() instanceof Node) { + starts.put(triple.getSubject().alias(), triple.getSubject()); + break; + } else if (triple.getObject() instanceof Node) { + starts.put(triple.getObject().alias(), triple.getObject()); + break; + } } + return starts; } private List> prepareElement( @@ -373,29 +348,27 @@ private Boolean reserve(List evidence) { private Collection prepareElement(Element pattern, Map context) { Collection result = new LinkedList<>(); - Collection spo = this.tripleStore.find(pattern); + Triple triple = Triple.create(pattern); + Collection spo = this.tripleStore.find(triple); if (spo == null || spo.isEmpty()) { - if (pattern instanceof Node) { - result = find((Node) pattern, context); - } else if (pattern instanceof Entity) { - result = find((Entity) pattern, context); - } else if (!recorder.contains(pattern.cleanAlias())) { - result = find((Triple) pattern, context); + if (!recorder.contains(triple.cleanAlias())) { + result = find(triple, context); } } else { result = spo.stream().map(e -> new Result(e, null)).collect(Collectors.toList()); } for (Result r : result) { - r.setData(r.getData().bind(pattern)); + r.setData(r.getData().bind(triple)); } return result; } - public void addEntity(Entity entity) { - this.tripleStore.addEntity(entity); + private void addEntity(Entity entity) { + Triple triple = new Triple(Element.ANY, Predicate.CONCLUDE, entity); + this.addTriple(triple); } - public void addTriple(Triple triple) { + private void addTriple(Triple triple) { this.tripleStore.addTriple(triple); } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/MemTripleStore.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/MemTripleStore.java index 8fb2596df..07768dcce 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/MemTripleStore.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/MemTripleStore.java @@ -21,12 +21,10 @@ import java.util.*; public class MemTripleStore implements TripleStore { - private Map entities; private Map> sToTriple; private Map> oToTriple; public MemTripleStore() { - this.entities = new HashMap<>(); this.sToTriple = new HashMap<>(); this.oToTriple = new HashMap<>(); } @@ -35,28 +33,12 @@ public MemTripleStore() { public void init(Map param) {} @Override - public Collection find(Element pattern) { + public Collection find(Triple pattern) { List elements = new LinkedList<>(); - if (pattern instanceof Entity) { - Collection collection = findEntity((Entity) pattern); - if (collection != null) { - elements.addAll(collection); - } - } else if (pattern instanceof Triple) { - elements.addAll(findTriple((Triple) pattern)); - } + elements.addAll(findTriple(pattern)); return elements; } - private Collection findEntity(Entity pattern) { - Entity entity = entities.getOrDefault(getKey(pattern), null); - if (entity != null) { - return Arrays.asList(entity); - } else { - return null; - } - } - private Collection findTriple(Triple tripleMatch) { Triple t = (Triple) tripleMatch.cleanAlias(); List elements = new LinkedList<>(); @@ -73,21 +55,15 @@ private Collection findTriple(Triple tripleMatch) { } } } else { - throw new RuntimeException("Cannot support " + t); + for (Triple tri : sToTriple.getOrDefault(t.getSubject(), new LinkedList<>())) { + if (t.matches(tri)) { + elements.add(tri); + } + } } return elements; } - private String getKey(Entity entity) { - return entity.getId() + entity.getType(); - } - - @Override - public void addEntity(Entity entity) { - String key = getKey(entity); - entities.put(key, (Entity) entity.cleanAlias()); - } - @Override public void addTriple(Triple triple) { Triple t = (Triple) triple.cleanAlias(); @@ -102,7 +78,6 @@ public void addTriple(Triple triple) { @Override public void clear() { - this.entities.clear(); this.sToTriple.clear(); this.oToTriple.clear(); } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/TripleGroup.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/TripleGroup.java new file mode 100644 index 000000000..8c0a99f0a --- /dev/null +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/engine/TripleGroup.java @@ -0,0 +1,69 @@ +/* + * Copyright 2023 OpenSPG Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + */ +package com.antgroup.openspg.reasoner.thinker.engine; + +import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; +import java.util.*; + +public class TripleGroup { + private List triples; + + public TripleGroup(List triples) { + this.triples = triples; + } + + public List> group() { + Map resp = new HashMap<>(); + for (Triple triple : triples) { + String s = findRootRep(triple.getSubject().alias(), resp); + String o = findRootRep(triple.getObject().alias(), resp); + if (s.compareTo(o) > 0) { + resp.put(o, s); + resp.put(triple.alias(), s); + } else { + resp.put(s, o); + resp.put(triple.alias(), o); + } + } + Set cluster = new HashSet<>(); + for (String key : resp.keySet()) { + cluster.add(findRootRep(key, resp)); + } + List> groups = new ArrayList<>(cluster.size()); + for (String g : cluster) { + List group = new ArrayList<>(); + for (Triple triple : triples) { + String c = findRootRep(triple.getSubject().alias(), resp); + if (g.equals(c)) { + group.add(triple); + } + } + groups.add(group); + } + return groups; + } + + private String findRootRep(String cur, Map rep) { + if (!rep.containsKey(cur)) { + rep.put(cur, cur); + return cur; + } + if (rep.get(cur).equals(cur)) { + return cur; + } + + String repStr = findRootRep(rep.get(cur), rep); + rep.put(cur, repStr); + return repStr; + } +} diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/LogicNetwork.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/LogicNetwork.java index 6827d91fe..2c56652cd 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/LogicNetwork.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/LogicNetwork.java @@ -13,7 +13,7 @@ package com.antgroup.openspg.reasoner.thinker.logic; -import com.antgroup.openspg.reasoner.thinker.logic.graph.Element; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; import com.antgroup.openspg.reasoner.thinker.logic.rule.ClauseEntry; import com.antgroup.openspg.reasoner.thinker.logic.rule.Rule; import java.util.*; @@ -26,8 +26,8 @@ public class LogicNetwork { private static final Logger logger = LoggerFactory.getLogger(LogicNetwork.class); - private Map>> forwardRules; - private Map, List>> backwardRules; + private Map>> forwardRules; + private Map, List>> backwardRules; public LogicNetwork() { this.forwardRules = new HashMap<>(); @@ -36,24 +36,24 @@ public LogicNetwork() { public void addRule(Rule rule) { for (ClauseEntry body : rule.getBody()) { - Map> rules = - forwardRules.computeIfAbsent(body.toElement(), (key) -> new HashMap<>()); + Map> rules = + forwardRules.computeIfAbsent(body.toTriple(), (key) -> new HashMap<>()); List rList = - rules.computeIfAbsent(rule.getHead().toElement(), (k) -> new LinkedList<>()); + rules.computeIfAbsent(rule.getHead().toTriple(), (k) -> new LinkedList<>()); rList.add(rule); } - Map, List> rules = - backwardRules.computeIfAbsent(rule.getHead().toElement(), (key) -> new HashMap<>()); + Map, List> rules = + backwardRules.computeIfAbsent(rule.getHead().toTriple(), (key) -> new HashMap<>()); List rList = rules.computeIfAbsent( - rule.getBody().stream().map(ClauseEntry::toElement).collect(Collectors.toList()), + rule.getBody().stream().map(ClauseEntry::toTriple).collect(Collectors.toList()), (k) -> new LinkedList<>()); rList.add(rule); } - public Collection getForwardRules(Element e) { + public Collection getForwardRules(Triple e) { Set rules = new HashSet<>(); - for (Map.Entry>> entry : forwardRules.entrySet()) { + for (Map.Entry>> entry : forwardRules.entrySet()) { if (entry.getKey().matches(e)) { entry.getValue().values().stream().forEach(list -> rules.addAll(list)); } @@ -61,9 +61,9 @@ public Collection getForwardRules(Element e) { return rules; } - public Collection getBackwardRules(Element triple) { + public Collection getBackwardRules(Triple triple) { Set rules = new HashSet<>(); - for (Map.Entry, List>> entry : backwardRules.entrySet()) { + for (Map.Entry, List>> entry : backwardRules.entrySet()) { if (entry.getKey().matches(triple)) { entry.getValue().values().stream().forEach(list -> rules.addAll(list)); } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Any.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Any.java index 2ec8f5ad9..7e0c0dc36 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Any.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Any.java @@ -12,10 +12,19 @@ */ package com.antgroup.openspg.reasoner.thinker.logic.graph; +import java.util.Objects; import lombok.Data; @Data public class Any extends Element { + private String alias; + + public Any() {} + + public Any(String alias) { + this.alias = alias; + } + @Override public boolean matches(Element other) { return other != null; @@ -26,14 +35,42 @@ public boolean equals(Object obj) { if (obj == null) { return false; } else if (obj instanceof Any) { - return true; + return Objects.equals(alias, ((Any) obj).alias); } else { return false; } } + @Override + public Element bind(Element pattern) { + return pattern; + } + + @Override + public String alias() { + return this.alias; + } + @Override public int hashCode() { return HASH_ANY; } + + /** + * Getter method for property alias. + * + * @return property value of alias + */ + public String getAlias() { + return alias; + } + + /** + * Setter method for property alias. + * + * @param alias value to be assigned to property alias + */ + public void setAlias(String alias) { + this.alias = alias; + } } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/CombinationEntity.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/CombinationEntity.java index 1fb24d48e..497d3d61f 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/CombinationEntity.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/CombinationEntity.java @@ -34,11 +34,6 @@ public CombinationEntity(List entityList, String alias) { this.alias = alias; } - @Override - public boolean canInstantiated() { - return false; - } - public Set types() { return entityList.stream().map(Entity::getType).collect(Collectors.toSet()); } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Element.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Element.java index 1aff3fbd2..919a46cb8 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Element.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Element.java @@ -13,7 +13,6 @@ package com.antgroup.openspg.reasoner.thinker.logic.graph; -import com.antgroup.openspg.reasoner.common.exception.UnsupportedOperationException; import java.io.Serializable; public abstract class Element implements Serializable { @@ -29,14 +28,7 @@ public Element bind(Element pattern) { return this; } - public boolean canInstantiated() { - return true; - } - - public String alias() { - throw new UnsupportedOperationException( - this.getClass().getSimpleName() + " cannot support", null); - } + public abstract String alias(); public Element cleanAlias() { return this; diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Entity.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Entity.java index 05212ad53..2ae08b1cf 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Entity.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Entity.java @@ -121,11 +121,6 @@ public String alias() { return this.alias; } - @Override - public boolean canInstantiated() { - return false; - } - @Override public Element cleanAlias() { return new Entity(this.id, this.type); @@ -138,7 +133,7 @@ public int hashCode() { @Override public Element bind(Element pattern) { - if (pattern instanceof Entity || pattern instanceof Node) { + if (pattern instanceof Entity || pattern instanceof Node || pattern instanceof Any) { return new Entity(this.id, this.type, pattern.alias()); } else { return this; diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Node.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Node.java index 9d75fce30..3261f81de 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Node.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Node.java @@ -71,6 +71,8 @@ public Element bind(Element pattern) { } else if (pattern instanceof CombinationEntity) { Entity entity = ((CombinationEntity) pattern).getEntityList().get(0); return new Entity(entity.getId(), entity.getType(), entity.getAlias()); + } else if (pattern instanceof Node || pattern instanceof Any) { + return new Node(this.type, pattern.alias()); } else { return this; } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Predicate.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Predicate.java index 7129b26e4..91cfeb3a5 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Predicate.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Predicate.java @@ -19,6 +19,8 @@ @Data public class Predicate extends Element { + public static final Predicate CONCLUDE = new Predicate("conclude"); + private String name; private String alias; @@ -96,11 +98,6 @@ public boolean matches(Element other) { return equals(other); } - @Override - public boolean canInstantiated() { - return false; - } - public String alias() { return alias; } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Triple.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Triple.java index b0e1f27d9..b97adc5be 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Triple.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/graph/Triple.java @@ -53,13 +53,6 @@ public Element bind(Element pattern) { } } - @Override - public boolean canInstantiated() { - return !(!subject.canInstantiated() - && !predicate.canInstantiated() - && !object.canInstantiated()); - } - public String alias() { return predicate.alias(); } @@ -73,6 +66,14 @@ public static Triple create(Element s, Element p, Element o) { return new Triple(nullToAny(s), nullToAny(p), nullToAny(o)); } + public static Triple create(Element element) { + if (element instanceof Node || element instanceof Entity) { + return new Triple(Element.ANY, Predicate.CONCLUDE, element); + } else { + return (Triple) element; + } + } + private static Element nullToAny(Element n) { return n == null ? ANY : n; } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/ClauseEntry.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/ClauseEntry.java index dd7c946a5..8091593bd 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/ClauseEntry.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/ClauseEntry.java @@ -13,9 +13,9 @@ package com.antgroup.openspg.reasoner.thinker.logic.rule; -import com.antgroup.openspg.reasoner.thinker.logic.graph.Element; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; import java.io.Serializable; public interface ClauseEntry extends Serializable { - Element toElement(); + Triple toTriple(); } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/EntityPattern.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/EntityPattern.java index 0ab937f86..6d086059e 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/EntityPattern.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/EntityPattern.java @@ -13,18 +13,13 @@ package com.antgroup.openspg.reasoner.thinker.logic.rule; -import com.antgroup.openspg.reasoner.thinker.logic.graph.Element; import com.antgroup.openspg.reasoner.thinker.logic.graph.Entity; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; import java.util.Objects; public class EntityPattern implements ClauseEntry { private Entity entity; - @Override - public Element toElement() { - return entity; - } - public EntityPattern() {} public EntityPattern(Entity entity) { @@ -65,4 +60,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(entity); } + + @Override + public Triple toTriple() { + return Triple.create(entity); + } } diff --git a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/TriplePattern.java b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/TriplePattern.java index a7b46b177..3bcd23652 100644 --- a/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/TriplePattern.java +++ b/reasoner/thinker/src/main/java/com/antgroup/openspg/reasoner/thinker/logic/rule/TriplePattern.java @@ -13,7 +13,6 @@ package com.antgroup.openspg.reasoner.thinker.logic.rule; -import com.antgroup.openspg.reasoner.thinker.logic.graph.Element; import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; import java.util.Objects; @@ -26,11 +25,6 @@ public TriplePattern(Triple triple) { this.triple = triple; } - @Override - public Element toElement() { - return triple; - } - /** * Getter method for property triple. * @@ -65,4 +59,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(triple); } + + @Override + public Triple toTriple() { + return triple; + } } diff --git a/reasoner/thinker/src/main/scala/com/antgroup/openspg/reasoner/thinker/SimplifyThinkerParser.scala b/reasoner/thinker/src/main/scala/com/antgroup/openspg/reasoner/thinker/SimplifyThinkerParser.scala index 99ee0cbaf..450c523a8 100644 --- a/reasoner/thinker/src/main/scala/com/antgroup/openspg/reasoner/thinker/SimplifyThinkerParser.scala +++ b/reasoner/thinker/src/main/scala/com/antgroup/openspg/reasoner/thinker/SimplifyThinkerParser.scala @@ -75,7 +75,7 @@ class SimplifyThinkerParser { val rule = new Rule() val concept_nameContext = ctx.define_rule_on_concept_structure().concept_declaration().concept_name() - rule.setHead(new EntityPattern(thinkerRuleParser.constructConceptEntity(concept_nameContext))) + rule.setHead(thinkerRuleParser.get_concept_full_form(concept_nameContext)) if (null != ctx.description()) { rule.setDesc(ctx.description().unbroken_character_string_literal().getText) } diff --git a/reasoner/thinker/src/main/scala/com/antgroup/openspg/reasoner/thinker/ThinkerRuleParser.scala b/reasoner/thinker/src/main/scala/com/antgroup/openspg/reasoner/thinker/ThinkerRuleParser.scala index f9caf0f72..4774dffcc 100644 --- a/reasoner/thinker/src/main/scala/com/antgroup/openspg/reasoner/thinker/ThinkerRuleParser.scala +++ b/reasoner/thinker/src/main/scala/com/antgroup/openspg/reasoner/thinker/ThinkerRuleParser.scala @@ -13,30 +13,21 @@ package com.antgroup.openspg.reasoner.thinker +import java.util.Locale + +import scala.collection.JavaConverters._ +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + import com.antgroup.openspg.reasoner.KGDSLParser._ import com.antgroup.openspg.reasoner.lube.common.expr._ import com.antgroup.openspg.reasoner.lube.utils.transformer.impl.Expr2QlexpressTransformer import com.antgroup.openspg.reasoner.parser.expr.RuleExprParser import com.antgroup.openspg.reasoner.thinker.logic.graph -import com.antgroup.openspg.reasoner.thinker.logic.graph.{ - CombinationEntity, - Element, - Entity, - Predicate, - Value -} -import com.antgroup.openspg.reasoner.thinker.logic.rule.{ - ClauseEntry, - EntityPattern, - Node, - TriplePattern -} +import com.antgroup.openspg.reasoner.thinker.logic.graph._ +import com.antgroup.openspg.reasoner.thinker.logic.rule.{ClauseEntry, EntityPattern, Node, TriplePattern} import com.antgroup.openspg.reasoner.thinker.logic.rule.exact.{Not, _} -import java.util.Locale import org.apache.commons.lang3.StringUtils -import scala.collection.JavaConverters._ -import scala.collection.mutable -import scala.collection.mutable.ListBuffer class ThinkerRuleParser extends RuleExprParser { val expr2StringTransformer = new Expr2QlexpressTransformer() @@ -93,8 +84,18 @@ class ThinkerRuleParser extends RuleExprParser { val existElementSet: mutable.Set[ClauseEntry] = conditionToElementMap.getOrElseUpdate(condition, mutable.HashSet()) for (e <- element) { - if (e.isInstanceOf[EntityPattern]) { - existElementSet += e + e match { + case e: EntityPattern => existElementSet += e + case t: TriplePattern => + val s = t.getTriple.getSubject + val p = t.getTriple.getPredicate + val o = t.getTriple.getObject + if (s.isInstanceOf[logic.graph.Any] + && o.isInstanceOf[Entity] + && p.isInstanceOf[Predicate] + && p.asInstanceOf[Predicate].getName.equals("conclude")) { + existElementSet += new EntityPattern(o.asInstanceOf[Entity]) + } } } } @@ -107,8 +108,7 @@ class ThinkerRuleParser extends RuleExprParser { newBody += new TriplePattern(new logic.graph.Triple(subject, predicate, object_)) new QlExpressCondition(expr2StringTransformer.transform(parseLogicTest(ctx)).head) case c: Concept_nameContext => - val conceptEntity = constructConceptEntity(c) - newBody += new EntityPattern(conceptEntity) + newBody += get_concept_full_form(c) new QlExpressCondition(expr2StringTransformer.transform(parseLogicTest(ctx)).head) case c: ExprContext => thinkerParseExpr(c, newBody) } @@ -119,12 +119,50 @@ class ThinkerRuleParser extends RuleExprParser { resultNode } + private def isDefaultAlias(alias: String): Boolean = { + if (alias.toLowerCase().startsWith("anonymous")) { + return true + } + false + } + + def get_concept_full_form( + concept_name: Concept_nameContext, + conceptAlias: String = ""): TriplePattern = { + val conceptEntity: Entity = constructConceptEntity(concept_name) + val entity_str = concept_name.getText + if (spoRuleToSpoSetMap.contains(entity_str)) { + val (s, p, o) = spoRuleToSpoSetMap(entity_str) + if (StringUtils.isNotBlank(conceptAlias) && + !isDefaultAlias(conceptAlias) && !isDefaultAlias(o.alias())) { + throw new IllegalArgumentException( + "The same entity %s has different alias".format(entity_str)) + } + if (StringUtils.isNotBlank(conceptAlias) + && !isDefaultAlias(conceptAlias)) { + o.asInstanceOf[Entity].setAlias(conceptAlias) + } + return new TriplePattern(new logic.graph.Triple(s, p, o)) + } + val default_s = new Any(getDefaultAliasNum) + val default_p = new Predicate("conclude", getDefaultAliasNum) + var newConceptAlias: String = conceptAlias + if (StringUtils.isBlank(newConceptAlias)) { + newConceptAlias = getDefaultAliasNum + } + conceptEntity.setAlias(newConceptAlias) + val nested_value = (default_s, default_p, conceptEntity) + spoRuleToSpoSetMap += (entity_str -> nested_value) + new TriplePattern(new logic.graph.Triple(default_s, default_p, conceptEntity)) + } + def getAliasFromElement(element: Element): String = { element match { case e: Entity => e.getAlias case p: Predicate => p.getAlias case n: logic.graph.Node => n.getAlias case v: Value => v.getAlias + case a: logic.graph.Any => a.getAlias case _ => throw new IllegalArgumentException("%s element has no alias".format(element)) } } @@ -136,16 +174,21 @@ class ThinkerRuleParser extends RuleExprParser { metaConceptName + "/" + conceptInstanceIdName } + private def formTripleExpr(subject: Element, predicate: Element, object_ : Element) = { + TripleExpr( + getAliasFromElement(subject), + getAliasFromElement(predicate), + getAliasFromElement(object_)) + } + override def parseLogicTest(ctx: Logic_testContext): Expr = { val bExpr: Expr = ctx.getChild(0) match { case c: Spo_ruleContext => val (subject, predicate, object_) = parseSpoRule(c) - TripleExpr( - getAliasFromElement(subject), - getAliasFromElement(predicate), - getAliasFromElement(object_)) + formTripleExpr(subject, predicate, object_) case concept: Concept_nameContext => - ConceptExpr(refactorConceptName(concept)) + val triple: Triple = get_concept_full_form(concept).getTriple + formTripleExpr(triple.getSubject, triple.getPredicate, triple.getObject) case expr: ExprContext => parseExpr(expr) } Option(ctx.getChild(1)) match { @@ -212,7 +255,7 @@ class ThinkerRuleParser extends RuleExprParser { body: ListBuffer[ClauseEntry]): Unit = { ctx.getChild(0) match { case c: Concept_nameContext => - body += new EntityPattern(constructConceptEntity(c)) + body += get_concept_full_form(c) case c: Value_expression_primaryContext => thinkerParseValueExpressionPrimary(c, body) case c: Numeric_value_functionContext => @@ -223,7 +266,8 @@ class ThinkerRuleParser extends RuleExprParser { override def parseProjectPrimary(ctx: Project_primaryContext): Expr = { ctx.getChild(0) match { case c: Concept_nameContext => - ConceptExpr(refactorConceptName(c)) + val triple: Triple = get_concept_full_form(c).getTriple + formTripleExpr(triple.getSubject, triple.getPredicate, triple.getObject) case c: Value_expression_primaryContext => parseValueExpressionPrimary(c) case c: Numeric_value_functionContext => @@ -269,7 +313,7 @@ class ThinkerRuleParser extends RuleExprParser { val propertyName: String = propertyNameList.head.getText val subject = aliasToElementMap(alias) val predicate = new Predicate(propertyName) - val o = new Value(null, "anonymous_" + getDefaultAliasNum) + val o = new Value(null, getDefaultAliasNum) body += new TriplePattern(new logic.graph.Triple(subject, predicate, o)) } } @@ -330,7 +374,12 @@ class ThinkerRuleParser extends RuleExprParser { if (aliasToElementMap.contains(sAlias)) { return aliasToElementMap(sAlias) } - var sNode: Element = new graph.Node(sType, sAlias) + var sNode: Element = null + if (StringUtils.isBlank(sType) || sType.toUpperCase().equals("THING")) { + sNode = new graph.Any(sAlias) + } else { + sNode = new graph.Node(sType, sAlias) + } if (StringUtils.isNotEmpty(sType)) { val conceptContext = elementPatternDeclaration .element_lookup() @@ -338,8 +387,7 @@ class ThinkerRuleParser extends RuleExprParser { .label_name() .concept_name() if (conceptContext != null) { - val conceptEntity = constructConceptEntity(conceptContext) - conceptEntity.setAlias(sAlias) + val conceptEntity = get_concept_full_form(conceptContext, sAlias).getTriple.getObject sNode = conceptEntity } else if (valueTypeSet.contains(sType.toUpperCase())) { sNode = new Value(null, sAlias) @@ -355,8 +403,9 @@ class ThinkerRuleParser extends RuleExprParser { .concept_name() .asScala .foreach(concept => { - val conceptEntity = constructConceptEntity(concept) - conceptEntityList += conceptEntity + val conceptTriple = + get_concept_full_form(concept).getTriple.getObject.asInstanceOf[Entity] + conceptEntityList += conceptTriple }) val combinationEntity = new CombinationEntity(conceptEntityList.asJava) combinationEntity.setAlias(sAlias) @@ -387,21 +436,18 @@ class ThinkerRuleParser extends RuleExprParser { if (ctx.element_variable_declaration() != null) { alias = ctx.element_variable_declaration().element_variable().getText } else { - alias = "anonymous_" + getDefaultAliasNum + alias = getDefaultAliasNum } var labelName = "" if (ctx.element_lookup() != null) { labelName = ctx.element_lookup().label_expression().label_name().getText } - if (!isHead && StringUtils.isBlank(labelName) && !aliasToElementMap.contains(alias)) { - throw new IllegalArgumentException("alias " + alias + " is not defined") - } (alias, labelName) } def getDefaultAliasNum: String = { defaultAliasNum += 1 - defaultAliasNum.toString + s"anonymous_$defaultAliasNum" } } diff --git a/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/GraphUtil.java b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/GraphUtil.java index b89f6be50..4621bb8e1 100644 --- a/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/GraphUtil.java +++ b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/GraphUtil.java @@ -78,7 +78,19 @@ public static MemGraphState buildMemState( if (v.getId().equals(e.getSourceId())) { outEdges.add(e); } else if (v.getId().equals(e.getTargetId())) { - inEdges.add(e.reverse()); + IEdge reverseEdge = e.reverse(); + IProperty reverseProperty = e.getValue().clone(); + String fromId = (String) reverseProperty.get(Constants.EDGE_FROM_ID_KEY); + String fromIdType = (String) reverseProperty.get(Constants.EDGE_FROM_ID_TYPE_KEY); + reverseProperty.put( + Constants.EDGE_FROM_ID_KEY, reverseEdge.getValue().get(Constants.EDGE_TO_ID_KEY)); + reverseProperty.put( + Constants.EDGE_FROM_ID_TYPE_KEY, + reverseEdge.getValue().get(Constants.EDGE_TO_ID_TYPE_KEY)); + reverseProperty.put(Constants.EDGE_TO_ID_KEY, fromId); + reverseProperty.put(Constants.EDGE_TO_ID_TYPE_KEY, fromIdType); + reverseEdge.setValue(reverseProperty); + inEdges.add(reverseEdge); } } graphState.addEdges(v.getId(), inEdges, outEdges); diff --git a/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/HypertensionTest.java b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/HypertensionTest.java index 0f82cd851..f3b6bb08b 100644 --- a/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/HypertensionTest.java +++ b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/HypertensionTest.java @@ -13,6 +13,7 @@ package com.antgroup.openspg.reasoner.thinker; +import com.antgroup.openspg.reasoner.common.constants.Constants; import com.antgroup.openspg.reasoner.common.graph.vertex.IVertexId; import com.antgroup.openspg.reasoner.graphstate.GraphState; import com.antgroup.openspg.reasoner.graphstate.impl.MemGraphState; @@ -67,6 +68,7 @@ public void combinationDrug() { context.put("目标收缩压上界", 140); context.put("BMI", 35); context.put("GFR", 35); + context.put(Constants.SPG_REASONER_THINKER_STRICT, true); List triples = thinker.find(null, new Predicate("基本用药方案"), new Node("药品"), context); Assert.assertTrue(triples.size() == 2); } diff --git a/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/MedTests.java b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/MedTests.java index a837b2883..834cd94f6 100644 --- a/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/MedTests.java +++ b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/MedTests.java @@ -117,4 +117,19 @@ public void testHepatitis() { thinker.find(null, new Predicate("确诊"), new Node("Medical.DiseaseTerm"), context); Assert.assertTrue(triples.size() == 1); } + + @Test + public void testMultiAny() { + ResourceLogicCatalog logicCatalog = new ResourceLogicCatalog("/Medical.txt"); + logicCatalog.init(); + Thinker thinker = new DefaultThinker(buildGraphState(), logicCatalog); + Map context = new HashMap<>(); + context.put("spg.reasoner.thinker.strict", true); + context.put("孕酮", "1.25"); + context.put("a", "12"); + List triples = + thinker.find( + null, new Predicate("diagnosis"), new Node("Medical.DiagnosisRecommend"), context); + Assert.assertTrue(triples.size() > 0); + } } diff --git a/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/ProntoQATests.java b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/ProntoQATests.java new file mode 100644 index 000000000..28f80f1bd --- /dev/null +++ b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/ProntoQATests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2023 OpenSPG Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + */ + +package com.antgroup.openspg.reasoner.thinker; + +import com.antgroup.openspg.reasoner.common.graph.vertex.IVertexId; +import com.antgroup.openspg.reasoner.graphstate.GraphState; +import com.antgroup.openspg.reasoner.graphstate.impl.MemGraphState; +import com.antgroup.openspg.reasoner.thinker.catalog.ResourceLogicCatalog; +import com.antgroup.openspg.reasoner.thinker.engine.DefaultThinker; +import com.antgroup.openspg.reasoner.thinker.logic.Result; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Entity; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Node; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Predicate; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; + +public class ProntoQATests { + private GraphState buildGraphState() { + GraphState graphState = new MemGraphState(); + return graphState; + } + + private Thinker buildThinker() { + ResourceLogicCatalog logicCatalog = new ResourceLogicCatalog("/ProntoQA.txt"); + logicCatalog.init(); + Thinker thinker = new DefaultThinker(buildGraphState(), logicCatalog); + return thinker; + } + + private Triple makeTriple(String sType, String rType, String oType) { + return new Triple(new Node(sType), new Predicate(rType), new Node(oType)); + } + + private Triple makeTriple(Entity s, String rType, String oType) { + return new Triple(s, new Predicate(rType), new Node(oType)); + } + + @Test + public void testCase1() { + Thinker thinker = buildThinker(); + Map context = new HashMap<>(); + Entity entity = new Entity("Polly", "Thing"); + Triple t1 = makeTriple(entity, "iss", "rompus"); + Triple t2 = makeTriple(entity, "iss", "zumpus"); + Triple t3 = makeTriple(entity, "iss", "impus"); + Triple t4 = makeTriple(entity, "iss", "yumpus"); + + List triples = Arrays.asList(t1, t2, t3, t4); + triples.forEach(t -> context.put(t.toString(), t)); + List result = + thinker.find( + new Entity("Polly", "Thing"), new Predicate("iss"), new Node("lorpus"), context); + Assert.assertTrue(result.size() == 1); + } +} diff --git a/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/ProofWriterTest.java b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/ProofWriterTest.java new file mode 100644 index 000000000..f55b0d172 --- /dev/null +++ b/reasoner/thinker/src/test/java/com/antgroup/openspg/reasoner/thinker/ProofWriterTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2023 OpenSPG Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. + */ + +package com.antgroup.openspg.reasoner.thinker; + +import com.antgroup.openspg.reasoner.common.graph.vertex.IVertexId; +import com.antgroup.openspg.reasoner.graphstate.GraphState; +import com.antgroup.openspg.reasoner.graphstate.impl.MemGraphState; +import com.antgroup.openspg.reasoner.thinker.catalog.ResourceLogicCatalog; +import com.antgroup.openspg.reasoner.thinker.engine.DefaultThinker; +import com.antgroup.openspg.reasoner.thinker.logic.Result; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Node; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Predicate; +import com.antgroup.openspg.reasoner.thinker.logic.graph.Triple; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; + +public class ProofWriterTest { + private GraphState buildGraphState() { + GraphState graphState = new MemGraphState(); + return graphState; + } + + private Thinker buildThinker() { + ResourceLogicCatalog logicCatalog = new ResourceLogicCatalog("/ProofWriter.txt"); + logicCatalog.init(); + Thinker thinker = new DefaultThinker(buildGraphState(), logicCatalog); + return thinker; + } + + private Triple makeTriple(String sType, String rType, String oType) { + return new Triple(new Node(sType), new Predicate(rType), new Node(oType)); + } + + @Test + public void testCase1() { + Thinker thinker = buildThinker(); + Map context = new HashMap<>(); + Triple t1 = makeTriple("cow", "iss", "round"); + Triple t2 = makeTriple("lion", "iss", "round"); + Triple t3 = makeTriple("rabbit", "iss", "kind"); + Triple t4 = makeTriple("tiger", "iss", "big"); + Triple t5 = makeTriple("tiger", "iss", "kind"); + + Triple t6 = makeTriple("cow", "needs", "lion"); + Triple t7 = makeTriple("cow", "needs", "rabbit"); + Triple t8 = makeTriple("cow", "sees", "lion"); + Triple t9 = makeTriple("cow", "visits", "tiger"); + Triple t10 = makeTriple("rabbit", "visits", "tiger"); + Triple t11 = makeTriple("tiger", "sees", "rabbit"); + Triple t12 = makeTriple("tiger", "visits", "rabbit"); + List triples = Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12); + triples.forEach(t -> context.put(t.toString(), t)); + List result = + thinker.find(new Node("tiger"), new Predicate("iss"), new Node("young"), context); + Assert.assertTrue(result.size() == 1); + } +} diff --git a/reasoner/thinker/src/test/resources/Medical.txt b/reasoner/thinker/src/test/resources/Medical.txt index 98db803ed..faa4e8a50 100644 --- a/reasoner/thinker/src/test/resources/Medical.txt +++ b/reasoner/thinker/src/test/resources/Medical.txt @@ -40,4 +40,12 @@ Define (a:Medical.ExaminationTerm/`乙肝e抗原`)-[:abnormalValue]->(c: Medical Define (a:Medical.ExaminationTerm/`乙肝核心抗体`)-[:abnormalValue]->(c: Medical.AbnormalExaminationIndicator/`阳性`) { R1: (乙肝核心抗体>rule_value(乙肝核心抗体_upper != null, 乙肝核心抗体_upper, 2.1)) -} \ No newline at end of file +} + +Define()-[:abnormalValue]->(:Medical.AbnormalExaminationIndicator/`缺失`) { + a > 10 +} + +Define()-[:diagnosis]->(:Medical.DiagnosisRecommend/`随访`) { + R1: 孕酮 < 5.0 AND ()-[:abnormalValue]->(:Medical.AbnormalExaminationIndicator/`缺失`) +} diff --git a/reasoner/thinker/src/test/resources/ProntoQA.txt b/reasoner/thinker/src/test/resources/ProntoQA.txt new file mode 100644 index 000000000..614629d60 --- /dev/null +++ b/reasoner/thinker/src/test/resources/ProntoQA.txt @@ -0,0 +1,59 @@ +Define (x:Thing)-[:iss]->(:wumpus) { + (x)-[:iss]->(:zumpus) +} + +Define (x:Thing)-[:iss]->(:sterpus) { + (x)-[:iss]->(:zumpus) +} + +Define (x:Thing)-[:iss]->(:yumpus) { + (x)-[:iss]->(:jompus) +} + +Define (x:Thing)-[:iss]->(:impus) { + (x)-[:iss]->(:jompus) +} + +Define (x:Thing)-[:iss]->(:tumpus) { + (x)-[:iss]->(:brimpus) +} + +Define (x:Thing)-[:iss]->(:numpus) { + (x)-[:iss]->(:brimpus) +} + +Define (x:Thing)-[:iss]->(:sour) { + (x)-[:iss]->(:vumpus) +} + +Define (x:Thing)-[:iss]->(:gorpus) { + (x)-[:iss]->(:vumpus) +} + +Define (x:Thing)-[:iss]->(:grimpus) { + (x)-[:iss]->(:rompus) +} + +Define (x:Thing)-[:iss]->(:zumpus) { + (x)-[:iss]->(:rompus) +} + +Define (x:Thing)-[:iss]->(:brimpus) { + (x)-[:iss]->(:grimpus) +} + +Define (x:Thing)-[:iss]->(:lorpus) { + (x)-[:iss]->(:grimpus) +} + +Define (x:Thing)-[:iss]->(:lempus) { + (x)-[:iss]->(:lorpus) +} + +Define (x:Thing)-[:iss]->(:jompus) { + (x)-[:iss]->(:lorpus) +} + +Define (x:Thing)-[:iss]->(:rompus) { + (x)-[:iss]->(:shumpus) +} \ No newline at end of file diff --git a/reasoner/thinker/src/test/resources/ProofWriter.txt b/reasoner/thinker/src/test/resources/ProofWriter.txt new file mode 100644 index 000000000..0126b2bde --- /dev/null +++ b/reasoner/thinker/src/test/resources/ProofWriter.txt @@ -0,0 +1,35 @@ +Define (x:Thing)-[:iss]->(:young) { + (x)-[:iss]->(:kind) AND (x)-[:visits]->(:rabbit) +} + +Define(x:Thing)-[:sees]->(:rabbit) { + (x)-[:sees]->(:tiger) AND (x)-[:visits]->(:lion) +} + +Define(x:Thing)-[:sees]->(:lion) { + (x:Thing)-[:iss]->(:big) AND (x)-[:iss]->(:young) +} + +Define(y:rabbit)-[:needs]->(:lion) { + (x:Thing)-[:visits]->(y) +} + +Define(x:Thing)-[:visits]->(:rabbit) { + (x)-[:iss]->(y:big) +} + +Define(x:Thing)-[:iss]->(:rough) { + (x)-[:sees]->(y:tiger) +} + +Define(y:rabbit)-[:needs]->(:lion) { + (x:Thing)-[:visits]->(y) AND (x)-[:iss]->(:kind) +} + +Define(x:Thing)-[:visits]->(:lion) { + (x)-[:iss]->(y:rough) AND (x)-[:iss]->(:kind) +} + +Define(x:Thing)-[:iss]->(:big) { + (x)-[:needs]->(y:lion) +} \ No newline at end of file diff --git a/reasoner/thinker/src/test/scala/com/antgroup/openspg/reasoner/thinker/SimplifyThinkerParserTest.scala b/reasoner/thinker/src/test/scala/com/antgroup/openspg/reasoner/thinker/SimplifyThinkerParserTest.scala index c75d3de59..b00c682da 100644 --- a/reasoner/thinker/src/test/scala/com/antgroup/openspg/reasoner/thinker/SimplifyThinkerParserTest.scala +++ b/reasoner/thinker/src/test/scala/com/antgroup/openspg/reasoner/thinker/SimplifyThinkerParserTest.scala @@ -16,13 +16,7 @@ package com.antgroup.openspg.reasoner.thinker import scala.collection.JavaConverters._ import scala.collection.mutable -import com.antgroup.openspg.reasoner.thinker.logic.graph.{ - CombinationEntity, - Entity, - Predicate, - Triple, - Value -} +import com.antgroup.openspg.reasoner.thinker.logic.graph.{CombinationEntity, Entity, Predicate, Triple, Value} import com.antgroup.openspg.reasoner.thinker.logic.graph import com.antgroup.openspg.reasoner.thinker.logic.rule._ import com.antgroup.openspg.reasoner.thinker.logic.rule.exact._ @@ -45,12 +39,18 @@ class SimplifyThinkerParserTest extends AnyFunSpec { val ruleList: List[Rule] = parser.parseSimplifyDsl(thinkerDsl) assert(ruleList.size == 1) val rule: Rule = ruleList.head - assert(rule.getHead.isInstanceOf[EntityPattern]) + val head = rule.getHead + assert(head.isInstanceOf[TriplePattern]) + val headTriple = head.asInstanceOf[TriplePattern].getTriple + assert(headTriple.getSubject.isInstanceOf[logic.graph.Any]) + assert(headTriple.getPredicate.asInstanceOf[Predicate].getName.equals("conclude")) + val body = rule.getBody.asScala assert(body.size == 1) // body val clauseCount = calculateClauseCount(body.toList) - assert(clauseCount.entityPattern == 1) + assert(clauseCount.entityPattern == 0) + assert(clauseCount.triplePattern == 1) val root = rule.getRoot assert(root.isInstanceOf[Or]) @@ -66,8 +66,8 @@ class SimplifyThinkerParserTest extends AnyFunSpec { val conditionToElementMap: Map[Condition, Set[ClauseEntry]] = parser.getConditionToElementMap() assert( - conditionToElementMap(new QlExpressCondition("get_value(\"高血压分层/临床并发症\")")).head - .equals(new EntityPattern(new Entity("临床并发症", "高血压分层")))) + conditionToElementMap(new QlExpressCondition("get_spo(anonymous_4, anonymous_5, anonymous_6)")).head + .equals(new EntityPattern(new Entity("临床并发症", "高血压分层", "anonymous_6")))) } def getAllConditionInNode(node: Node): List[Condition] = { @@ -111,7 +111,10 @@ class SimplifyThinkerParserTest extends AnyFunSpec { |} |""".stripMargin val rule: Rule = parser.parseSimplifyDsl(thinkerDsl).head - assert(rule.getBody.size() == 3) + val body = rule.getBody.asScala + assert(body.size == 3) + val clauseCount = calculateClauseCount(body.toList) + assert(clauseCount.triplePattern == 3) val root = rule.getRoot assert(root.isInstanceOf[Or]) val outermostOrChildrenList = root.asInstanceOf[Or].getChildren.asScala @@ -124,8 +127,8 @@ class SimplifyThinkerParserTest extends AnyFunSpec { assert(firstLineChildrenList.size == 3) assert( firstLineChildrenList.head.equals( - new QlExpressCondition("hits(get_value(\"高血压分层/心血管危险因素\")) >= 3"))) - assert(firstLineChildrenList(1).equals(new QlExpressCondition("get_value(\"高血压分层/靶器官损害\")"))) + new QlExpressCondition("hits(get_spo(anonymous_4, anonymous_5, anonymous_6)) >= 3"))) + assert(firstLineChildrenList(1).equals(new QlExpressCondition("get_spo(anonymous_7, anonymous_8, anonymous_9)"))) assert(firstLineChildrenList(2).equals(new QlExpressCondition("\"无并发症的糖尿病\" in 症状"))) // second line @@ -210,8 +213,8 @@ class SimplifyThinkerParserTest extends AnyFunSpec { assert(rule.getBody.size() == 6) val (entityCount, tripleCount) = countClauseCount(rule.getBody.asScala.toList) - assert(entityCount == 2) - assert(tripleCount == 4) + assert(entityCount == 0) + assert(tripleCount == 6) val expectTriplePatternSet = Set.apply( new TriplePattern( new graph.Triple( @@ -221,17 +224,17 @@ class SimplifyThinkerParserTest extends AnyFunSpec { new TriplePattern( new graph.Triple( new graph.Node("InsDiseaseDisclaim", "b"), - new Predicate("clauseVersion", "anonymous_2"), + new Predicate("clauseVersion", "anonymous_8"), new graph.Node("InsClause", "c"))), new TriplePattern( new graph.Triple( new Predicate("disclaimClause", "p"), new Predicate("disclaimType"), - new Value(null, "anonymous_3"))), + new Value(null, "anonymous_9"))), new TriplePattern( new graph.Triple( new graph.Node("InsClause", "c"), - new Predicate("insClauseVersion", "anonymous_4"), + new Predicate("insClauseVersion", "anonymous_10"), new graph.Node("InsComProd", "d")))) assert(rule.getBody.containsAll(expectTriplePatternSet.asJava)) assert(rule.getRoot.isInstanceOf[Or]) @@ -246,7 +249,7 @@ class SimplifyThinkerParserTest extends AnyFunSpec { child .asInstanceOf[QlExpressCondition] .getQlExpress - .equals("hits(get_spo(a, p, b),get_spo(c, anonymous_4, d)) > 2")) + .equals("hits(get_spo(a, p, b),get_spo(c, anonymous_10, d)) > 2")) } }) } @@ -314,4 +317,59 @@ class SimplifyThinkerParserTest extends AnyFunSpec { }) } + + it("convert_concept_to_triple_1") { + val thinkerDsl = + """ + |Define(a:InsDisease)-[:abnormalRule]->(o:A/`a1`) { + | R1: (a)-[p: disclaimClause]->(o) AND A/`a1` + |} + |""".stripMargin + val rule: Rule = parser.parseSimplifyDsl(thinkerDsl).head + var conceptCount = 0 + rule.getBody.asScala.foreach(clause => { + val triple = clause.asInstanceOf[TriplePattern].getTriple + if (triple.getSubject.isInstanceOf[logic.graph.Any]) { + assert(triple.getObject.alias().equals("o")) + conceptCount += 1 + } + }) + assert(conceptCount == 1) + } + + it("define_rule_on_relation_to_concept5") { + val thinkerDsl = + """ + |Define(a:InsDisease)-[:abnormalRule]->(o:String) { + | R1: A/`a2` AND (a)-[p: disclaimClause]->(b: A/`a2`) + |} + |""".stripMargin + val rule: Rule = parser.parseSimplifyDsl(thinkerDsl).head + var conceptCount = 0 + rule.getBody.asScala.foreach(clause => { + val triple = clause.asInstanceOf[TriplePattern].getTriple + if (triple.getSubject.isInstanceOf[logic.graph.Any]) { + assert(triple.getObject.alias().equals("b")) + conceptCount += 1 + } + }) + assert(conceptCount == 1) + } + + it("define_rule_on_relation_to_concept6") { + val thinkerDsl = + """ + |Define()-[: 确诊]->(: Medical.DiseaseTerm/`乙型肝炎大三阳`) { + | r: (: Medical.ExaminationTerm/`乙肝表面抗原`)-[: abnormalValue]->(: Medical.AbnormalExaminationIndicator/`阳性`) and (: Medical.ExaminationTerm/`乙肝e抗原`)-[: abnormalValue]->(: Medical.AbnormalExaminationIndicator/`阳性`) and (: Medical.ExaminationTerm/`乙肝核心抗体`)-[: abnormalValue]->(: Medical.AbnormalExaminationIndicator/`阳性`) + |} + |""".stripMargin + val rule: Rule = parser.parseSimplifyDsl(thinkerDsl).head + val triplePatternList = rule.getBody.asScala.toList + assert(triplePatternList.size == 3) + val expectAlias = triplePatternList.head.asInstanceOf[TriplePattern].getTriple.getObject.alias() + triplePatternList.foreach(clause => { + val triple = clause.asInstanceOf[TriplePattern].getTriple + assert(triple.getObject.alias().equals(expectAlias)) + }) + } }