RYA-417 Batch forward-chaining rules engine. Closes #255.
Project: http://git-wip-us.apache.org/repos/asf/incubator-rya/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-rya/commit/9f611019 Tree: http://git-wip-us.apache.org/repos/asf/incubator-rya/tree/9f611019 Diff: http://git-wip-us.apache.org/repos/asf/incubator-rya/diff/9f611019 Branch: refs/heads/master Commit: 9f611019fca682148483ae31a1123b8e92b5fda2 Parents: d5ebb73 Author: Jesse Hatfield <[email protected]> Authored: Fri Dec 22 12:02:33 2017 -0500 Committer: caleb <[email protected]> Committed: Thu Jan 11 09:50:38 2018 -0500 ---------------------------------------------------------------------- .../AggregationPipelineQueryNode.java | 8 +- .../mongodb/aggregation/PipelineQueryIT.java | 32 ++ .../SparqlToPipelineTransformVisitorTest.java | 14 + .../apache/rya/sail/config/RyaSailFactory.java | 64 ++-- extras/pom.xml | 1 + extras/rya.forwardchain/pom.xml | 119 +++++++ .../rya/forwardchain/ForwardChainConstants.java | 37 ++ .../rya/forwardchain/ForwardChainException.java | 54 +++ .../batch/AbstractForwardChainTool.java | 148 ++++++++ .../batch/ForwardChainSpinTool.java | 77 +++++ .../rule/AbstractConstructRule.java | 65 ++++ .../rule/AbstractInconsistencyRule.java | 51 +++ .../forwardchain/rule/AbstractUpdateRule.java | 34 ++ .../forwardchain/rule/AntecedentVisitor.java | 51 +++ .../rule/ConstructConsequentVisitor.java | 138 ++++++++ .../org/apache/rya/forwardchain/rule/Rule.java | 75 ++++ .../apache/rya/forwardchain/rule/Ruleset.java | 166 +++++++++ .../forwardchain/rule/SpinConstructRule.java | 344 +++++++++++++++++++ .../strategy/AbstractForwardChainStrategy.java | 82 +++++ .../strategy/AbstractRuleExecutionStrategy.java | 108 ++++++ .../strategy/MongoPipelineStrategy.java | 276 +++++++++++++++ .../strategy/RoundRobinStrategy.java | 212 ++++++++++++ .../strategy/SailExecutionStrategy.java | 223 ++++++++++++ .../rya/forwardchain/batch/MongoSpinIT.java | 169 +++++++++ .../rule/AntecedentVisitorTest.java | 156 +++++++++ .../rule/ConstructConsequentVisitorTest.java | 164 +++++++++ .../rya/forwardchain/rule/RulesetTest.java | 137 ++++++++ .../rule/SpinConstructRuleTest.java | 213 ++++++++++++ .../src/test/resources/data.ttl | 56 +++ .../src/test/resources/owlrl.ttl | 106 ++++++ .../src/test/resources/query.sparql | 32 ++ .../src/test/resources/university.ttl | 58 ++++ 32 files changed, 3442 insertions(+), 28 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/dao/mongodb.rya/src/main/java/org/apache/rya/mongodb/aggregation/AggregationPipelineQueryNode.java ---------------------------------------------------------------------- diff --git a/dao/mongodb.rya/src/main/java/org/apache/rya/mongodb/aggregation/AggregationPipelineQueryNode.java b/dao/mongodb.rya/src/main/java/org/apache/rya/mongodb/aggregation/AggregationPipelineQueryNode.java index 7a84f5d..45092e4 100644 --- a/dao/mongodb.rya/src/main/java/org/apache/rya/mongodb/aggregation/AggregationPipelineQueryNode.java +++ b/dao/mongodb.rya/src/main/java/org/apache/rya/mongodb/aggregation/AggregationPipelineQueryNode.java @@ -531,7 +531,9 @@ public class AggregationPipelineQueryNode extends ExternalSet { * The number of documents produced by the pipeline after this operation * will be the number of documents entering this stage (the number of * intermediate results) multiplied by the number of - * {@link ProjectionElemList}s supplied here. + * {@link ProjectionElemList}s supplied here. Empty projections are + * unsupported; if one or more projections given binds zero variables, then + * the pipeline will be unchanged and the method will return false. * @param projections One or more projections, i.e. mappings from the result * at this stage of the query into a set of variables. * @return true if the projection(s) were added to the pipeline. @@ -544,6 +546,10 @@ public class AggregationPipelineQueryNode extends ExternalSet { Set<String> bindingNamesUnion = new HashSet<>(); Set<String> bindingNamesIntersection = null; for (ProjectionElemList projection : projections) { + if (projection.getElements().isEmpty()) { + // Empty projections are unsupported -- fail when seen + return false; + } Document valueDoc = new Document(); Document hashDoc = new Document(); Document typeDoc = new Document(); http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/PipelineQueryIT.java ---------------------------------------------------------------------- diff --git a/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/PipelineQueryIT.java b/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/PipelineQueryIT.java index 45855a0..0552ac0 100644 --- a/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/PipelineQueryIT.java +++ b/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/PipelineQueryIT.java @@ -49,8 +49,10 @@ import org.openrdf.model.vocabulary.RDFS; import org.openrdf.model.vocabulary.XMLSchema; import org.openrdf.query.BindingSet; import org.openrdf.query.QueryEvaluationException; +import org.openrdf.query.algebra.Projection; import org.openrdf.query.algebra.QueryRoot; import org.openrdf.query.algebra.evaluation.QueryBindingSet; +import org.openrdf.query.impl.EmptyBindingSet; import org.openrdf.query.impl.ListBindingSet; import org.openrdf.query.parser.sparql.SPARQLParser; @@ -135,6 +137,36 @@ public class PipelineQueryIT extends MongoITBase { } @Test + public void testNoVariableSP() throws Exception { + // Insert data + insert(OWL.THING, RDF.TYPE, OWL.CLASS); + insert(FOAF.PERSON, RDF.TYPE, OWL.CLASS, 1); + insert(FOAF.PERSON, RDFS.SUBCLASSOF, OWL.THING); + insert(VF.createURI("urn:Alice"), RDF.TYPE, FOAF.PERSON); + dao.flush(); + // Define query and expected results + final String query = "SELECT * WHERE {\n" + + " owl:Thing a owl:Class .\n" + + "}"; + Multiset<BindingSet> expectedSolutions = HashMultiset.create(); + expectedSolutions.add(new EmptyBindingSet()); + // Execute pipeline and verify results + QueryRoot queryTree = new QueryRoot(PARSER.parseQuery(query, null).getTupleExpr()); + SparqlToPipelineTransformVisitor visitor = new SparqlToPipelineTransformVisitor(getRyaCollection()); + queryTree.visit(visitor); + Assert.assertTrue(queryTree.getArg() instanceof Projection); + Projection projection = (Projection) queryTree.getArg(); + Assert.assertTrue(projection.getArg() instanceof AggregationPipelineQueryNode); + AggregationPipelineQueryNode pipelineNode = (AggregationPipelineQueryNode) projection.getArg(); + Multiset<BindingSet> solutions = HashMultiset.create(); + CloseableIteration<BindingSet, QueryEvaluationException> iter = pipelineNode.evaluate(new QueryBindingSet()); + while (iter.hasNext()) { + solutions.add(iter.next()); + } + Assert.assertEquals(expectedSolutions, solutions); + } + + @Test public void testJoinTwoSharedVariables() throws Exception { // Insert data URI person = VF.createURI("urn:Person"); http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/SparqlToPipelineTransformVisitorTest.java ---------------------------------------------------------------------- diff --git a/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/SparqlToPipelineTransformVisitorTest.java b/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/SparqlToPipelineTransformVisitorTest.java index cc9349b..506b8af 100644 --- a/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/SparqlToPipelineTransformVisitorTest.java +++ b/dao/mongodb.rya/src/test/java/org/apache/rya/mongodb/aggregation/SparqlToPipelineTransformVisitorTest.java @@ -29,6 +29,7 @@ import org.mockito.Mockito; import org.openrdf.model.URI; import org.openrdf.model.ValueFactory; import org.openrdf.model.impl.ValueFactoryImpl; +import org.openrdf.model.vocabulary.OWL; import org.openrdf.model.vocabulary.RDF; import org.openrdf.query.algebra.Extension; import org.openrdf.query.algebra.ExtensionElem; @@ -153,6 +154,19 @@ public class SparqlToPipelineTransformVisitorTest { } @Test + public void testEmptyProjection() throws Exception { + StatementPattern isClass = new StatementPattern(constant(UNDERGRAD), constant(RDF.TYPE), constant(OWL.CLASS)); + QueryRoot queryTree = new QueryRoot(new Projection(isClass, new ProjectionElemList())); + SparqlToPipelineTransformVisitor visitor = new SparqlToPipelineTransformVisitor(collection); + queryTree.visit(visitor); + Assert.assertTrue(queryTree.getArg() instanceof Projection); + Projection projectNode = (Projection) queryTree.getArg(); + Assert.assertTrue(projectNode.getArg() instanceof AggregationPipelineQueryNode); + AggregationPipelineQueryNode pipelineNode = (AggregationPipelineQueryNode) projectNode.getArg(); + Assert.assertEquals(Sets.newHashSet(), pipelineNode.getAssuredBindingNames()); + } + + @Test public void testMultiProjection() throws Exception { StatementPattern isUndergrad = new StatementPattern(new Var("x"), constant(RDF.TYPE), constant(UNDERGRAD)); StatementPattern isCourse = new StatementPattern(new Var("course"), constant(RDF.TYPE), constant(COURSE)); http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/indexing/src/main/java/org/apache/rya/sail/config/RyaSailFactory.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/org/apache/rya/sail/config/RyaSailFactory.java b/extras/indexing/src/main/java/org/apache/rya/sail/config/RyaSailFactory.java index b5adee3..56af9b4 100644 --- a/extras/indexing/src/main/java/org/apache/rya/sail/config/RyaSailFactory.java +++ b/extras/indexing/src/main/java/org/apache/rya/sail/config/RyaSailFactory.java @@ -88,33 +88,10 @@ public class RyaSailFactory { // Get a reference to a Mongo DB configuration object. final MongoDBRdfConfiguration mongoConfig = (config instanceof MongoDBRdfConfiguration) ? (MongoDBRdfConfiguration)config : new MongoDBRdfConfiguration(config); - - // Create the MongoClient that will be used by the Sail object's components. - final MongoClient client = createMongoClient(mongoConfig); - - // Add the Indexer and Optimizer names to the configuration object that are configured to be used. - ConfigUtils.setIndexers(mongoConfig); - - // Populate the configuration using previously stored Rya Details if this instance uses them. - try { - final MongoRyaInstanceDetailsRepository ryaDetailsRepo = new MongoRyaInstanceDetailsRepository(client, mongoConfig.getRyaInstanceName()); - RyaDetailsToConfiguration.addRyaDetailsToConfiguration(ryaDetailsRepo.getRyaInstanceDetails(), mongoConfig); - } catch (final RyaDetailsRepositoryException e) { - LOG.info("Instance does not have a rya details collection, skipping."); - } - - // Set the configuration to the stateful configuration that is used to pass the constructed objects around. - final StatefulMongoDBRdfConfiguration statefulConfig = new StatefulMongoDBRdfConfiguration(mongoConfig, client); - final List<MongoSecondaryIndex> indexers = statefulConfig.getInstances(AccumuloRdfConfiguration.CONF_ADDITIONAL_INDEXERS, MongoSecondaryIndex.class); - statefulConfig.setIndexers(indexers); - rdfConfig = statefulConfig; - - // Create the DAO that is able to interact with MongoDB. - final MongoDBRyaDAO mongoDao = new MongoDBRyaDAO(); - mongoDao.setConf(statefulConfig); - mongoDao.init(); - dao = mongoDao; - + // Instantiate a Mongo client and Mongo DAO. + dao = getMongoDAO(mongoConfig); + // Then use the DAO's newly-created stateful conf in place of the original + rdfConfig = dao.getConf(); } else { rdfConfig = new AccumuloRdfConfiguration(config); user = rdfConfig.get(ConfigUtils.CLOUDBASE_USER); @@ -237,4 +214,37 @@ public class RyaSailFactory { LOG.info("Instance does not have a rya details collection, skipping."); } } + + /** + * Connects to MongoDB and creates a MongoDBRyaDAO. + * @param config - user configuration + * @return - MongoDBRyaDAO with Indexers configured according to user's specification + * @throws RyaDAOException if the DAO can't be initialized + */ + public static MongoDBRyaDAO getMongoDAO(MongoDBRdfConfiguration mongoConfig) throws RyaDAOException { + // Create the MongoClient that will be used by the Sail object's components. + final MongoClient client = createMongoClient(mongoConfig); + + // Add the Indexer and Optimizer names to the configuration object that are configured to be used. + ConfigUtils.setIndexers(mongoConfig); + + // Populate the configuration using previously stored Rya Details if this instance uses them. + try { + final MongoRyaInstanceDetailsRepository ryaDetailsRepo = new MongoRyaInstanceDetailsRepository(client, mongoConfig.getRyaInstanceName()); + RyaDetailsToConfiguration.addRyaDetailsToConfiguration(ryaDetailsRepo.getRyaInstanceDetails(), mongoConfig); + } catch (final RyaDetailsRepositoryException e) { + LOG.info("Instance does not have a rya details collection, skipping."); + } + + // Set the configuration to the stateful configuration that is used to pass the constructed objects around. + final StatefulMongoDBRdfConfiguration statefulConfig = new StatefulMongoDBRdfConfiguration(mongoConfig, client); + final List<MongoSecondaryIndex> indexers = statefulConfig.getInstances(AccumuloRdfConfiguration.CONF_ADDITIONAL_INDEXERS, MongoSecondaryIndex.class); + statefulConfig.setIndexers(indexers); + + // Create the DAO that is able to interact with MongoDB. + final MongoDBRyaDAO mongoDao = new MongoDBRyaDAO(); + mongoDao.setConf(statefulConfig); + mongoDao.init(); + return mongoDao; + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/pom.xml ---------------------------------------------------------------------- diff --git a/extras/pom.xml b/extras/pom.xml index 62220ca..4ebcb82 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -45,6 +45,7 @@ under the License. <module>rya.merger</module> <module>rya.giraph</module> <module>rya.streams</module> + <module>rya.forwardchain</module> </modules> <profiles> http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/pom.xml ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/pom.xml b/extras/rya.forwardchain/pom.xml new file mode 100644 index 0000000..7acabca --- /dev/null +++ b/extras/rya.forwardchain/pom.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you 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. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.rya</groupId> + <artifactId>rya.extras</artifactId> + <version>3.2.12-incubating-SNAPSHOT</version> + </parent> + + <artifactId>rya.forwardchain</artifactId> + <name>Apache Rya Forward Chaining Inference</name> + + <dependencies> + <dependency> + <groupId>org.openrdf.sesame</groupId> + <artifactId>sesame-runtime</artifactId> + </dependency> + <dependency> + <groupId>org.apache.rya</groupId> + <artifactId>rya.api</artifactId> + </dependency> + <dependency> + <groupId>org.apache.rya</groupId> + <artifactId>rya.sail</artifactId> + </dependency> + <dependency> + <groupId>org.apache.rya</groupId> + <artifactId>rya.indexing</artifactId> + </dependency> + <dependency> + <groupId>org.apache.rya</groupId> + <artifactId>mongodb.rya</artifactId> + </dependency> + + <!-- Testing --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <index>true</index> + <manifest> + <addClasspath>true</addClasspath> + <mainClass>org.apache.rya.forwardchain.batch.ForwardChainSpinTool</mainClass> + </manifest> + </archive> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <configuration> + <shadedArtifactAttached>true</shadedArtifactAttached> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <shadedArtifactAttached>true</shadedArtifactAttached> + <shadedClassifierName>shaded</shadedClassifierName> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/ForwardChainConstants.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/ForwardChainConstants.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/ForwardChainConstants.java new file mode 100644 index 0000000..f1fe8b3 --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/ForwardChainConstants.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain; + +import org.apache.rya.api.RdfCloudTripleStoreConstants; +import org.apache.rya.api.domain.RyaSchema; +import org.apache.rya.api.domain.RyaURI; +import org.apache.rya.api.resolver.RdfToRyaConversions; +import org.openrdf.model.URI; +import org.openrdf.model.ValueFactory; + +public class ForwardChainConstants { + private static final ValueFactory VF = RdfCloudTripleStoreConstants.VALUE_FACTORY; + private static final String NAMESPACE = RyaSchema.NAMESPACE; + + public static final URI DERIVATION_TIME = VF.createURI(NAMESPACE, "forwardChainIteration"); + public static final URI DERIVATION_RULE = VF.createURI(NAMESPACE, "forwardChainRule"); + + public static final RyaURI RYA_DERIVATION_RULE = RdfToRyaConversions.convertURI(DERIVATION_RULE); + public static final RyaURI RYA_DERIVATION_TIME = RdfToRyaConversions.convertURI(DERIVATION_TIME); +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/ForwardChainException.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/ForwardChainException.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/ForwardChainException.java new file mode 100644 index 0000000..64b05a4 --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/ForwardChainException.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain; + +/** + * Broad exception representing an error during forward chaining. Useful for + * wrapping the diverse kinds of exceptions that may be thrown by + * implementations of reasoning logic. + */ +public class ForwardChainException extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Constructs a new ForwardChainException with a message and a cause. + * @param string Detail message + * @param e Underlying cause + */ + public ForwardChainException(String string, Exception e) { + super(string , e); + } + + /** + * Constructs a new ForwardChainException with a message only. + * @param string Detail message + */ + public ForwardChainException(String string) { + super(string); + } + + /** + * Constructs a new ForwardChainException with a root cause and no + * additional message. + * @param e Underlying cause + */ + public ForwardChainException(Exception e) { + super(e); + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/batch/AbstractForwardChainTool.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/batch/AbstractForwardChainTool.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/batch/AbstractForwardChainTool.java new file mode 100644 index 0000000..db08407 --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/batch/AbstractForwardChainTool.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.batch; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.util.Tool; +import org.apache.log4j.Logger; +import org.apache.rya.accumulo.AccumuloRdfConfiguration; +import org.apache.rya.api.RdfCloudTripleStoreConfiguration; +import org.apache.rya.forwardchain.ForwardChainException; +import org.apache.rya.forwardchain.rule.Ruleset; +import org.apache.rya.forwardchain.strategy.AbstractForwardChainStrategy; +import org.apache.rya.forwardchain.strategy.AbstractRuleExecutionStrategy; +import org.apache.rya.forwardchain.strategy.MongoPipelineStrategy; +import org.apache.rya.forwardchain.strategy.RoundRobinStrategy; +import org.apache.rya.forwardchain.strategy.SailExecutionStrategy; +import org.apache.rya.indexing.accumulo.ConfigUtils; +import org.apache.rya.mongodb.MongoDBRdfConfiguration; +import com.google.common.base.Preconditions; + +/** + * Base class for a {@link Tool} that executes forward-chaining rules until + * completion (when no more new information can be derived). + * <p> + * Subclasses must implement {@link #getRuleset()} to yield the specific set of + * {@link Rule}s to materialize. + * <p> + * Subclasses may additionally override {@link #getStrategy()} and/or + * {@link #getRuleStrategy()} to provide specific forward chaining execution + * logic. + */ +public abstract class AbstractForwardChainTool implements Tool { + private static final Logger logger = Logger.getLogger(AbstractForwardChainTool.class); + + private RdfCloudTripleStoreConfiguration conf; + + private long numInferences = 0; + + /** + * Set the {@link Configuration} for this tool, which will be converted to + * an {@link RdfCloudTripleStoreConfiguration}. + * @param conf Configuration object that specifies Rya connection details. + * Should not be null. + */ + @Override + public void setConf(Configuration conf) { + Preconditions.checkNotNull(conf); + if (conf.getBoolean(ConfigUtils.USE_MONGO, false)) { + this.conf = new MongoDBRdfConfiguration(conf); + } + else { + this.conf = new AccumuloRdfConfiguration(conf); + } + } + + /** + * Get the RdfCloudTripleStoreConfiguration used by this tool. + * @return Rya configuration object. + */ + @Override + public RdfCloudTripleStoreConfiguration getConf() { + return conf; + } + + @Override + public int run(String[] args) throws Exception { + numInferences = getStrategy().executeAll(getRuleset()); + logger.info("Forward chaining complete; made " + numInferences + " inferences."); + return 0; + } + + /** + * Gets the number of inferences that have been made. + * @return zero before forward chaining, or the total number of inferences + * after. + */ + public long getNumInferences() { + return numInferences; + } + + /** + * Get the high-level {@link AbstractForwardChainStrategy} that governs how + * reasoning will proceed. By default, returns a {@link RoundRobinStrategy} + * which executes each relevant rule one-by-one, then moves to the next + * iteration and repeats, until no rules are still relevant. Subclasses may + * override this method to provide alternative strategies. + * @return The high-level forward chaining logic. + * @throws ForwardChainException if the strategy can't be instantiated. + */ + protected AbstractForwardChainStrategy getStrategy() throws ForwardChainException { + return new RoundRobinStrategy(getRuleStrategy()); + } + + /** + * Get the low-level {@link AbstractRuleExecutionStrategy} that governs the + * application of rules on an individual basis. This is used by the default + * ForwardChainStrategy (RoundRobinStrategy) and may be used by any + * high-level strategy that executes rules individually. By default, returns + * a {@link MongoPipelineStrategy} if the configuration object specifies a + * MongoDB connection with aggregation pipelines enabled, and a + * {@link SailExecutionStrategy} otherwise. Subclasses may override this + * method to provide alternative strategies. + * @return The low-level rule execution logic. + * @throws ForwardChainExceptionthe strategy can't be instantiated. + */ + protected AbstractRuleExecutionStrategy getRuleStrategy() throws ForwardChainException { + if (ConfigUtils.getUseMongo(conf)) { + final MongoDBRdfConfiguration mongoConf; + if (conf instanceof MongoDBRdfConfiguration) { + mongoConf = (MongoDBRdfConfiguration) conf; + } + else { + mongoConf = new MongoDBRdfConfiguration(conf); + } + if (mongoConf.getUseAggregationPipeline()) { + return new MongoPipelineStrategy(mongoConf); + } + } + return new SailExecutionStrategy(conf); + } + + /** + * Get the set of rules for this tool to apply. Subclasses should implement + * this for their specific domains. The subclass should ensure that the + * ruleset returned only contains rules whose types are supported by the + * forward chaining strategy. The default strategy supports only CONSTRUCT + * rules, so the ruleset should only contain {@link AbstractConstructRule}s. + * @return A set of forward-chaining rules. + * @throws ForwardChainException if rules couldn't be retrieved. + */ + protected abstract Ruleset getRuleset() throws ForwardChainException; +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/batch/ForwardChainSpinTool.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/batch/ForwardChainSpinTool.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/batch/ForwardChainSpinTool.java new file mode 100644 index 0000000..c35f37e --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/batch/ForwardChainSpinTool.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.batch; + +import org.apache.hadoop.util.Tool; +import org.apache.hadoop.util.ToolRunner; +import org.apache.rya.api.RdfCloudTripleStoreConfiguration; +import org.apache.rya.forwardchain.ForwardChainException; +import org.apache.rya.forwardchain.rule.Ruleset; +import org.apache.rya.forwardchain.rule.SpinConstructRule; + +/** + * {@link Tool} to load SPIN Construct rules from a Rya data store, then apply + * those rules to the same store using forward-chaining inference + * (materialization), adding triples back to Rya until no more information can + * be derived. + */ +public class ForwardChainSpinTool extends AbstractForwardChainTool { + private Ruleset ruleset; + + /** + * Constructor that takes in an {@link RdfCloudTripleStoreConfiguration}. + * @param conf Configuration object containing Rya connection information. + */ + public ForwardChainSpinTool(RdfCloudTripleStoreConfiguration conf) { + setConf(conf); + } + + /** + * Default constructor that does not take in a configuration object. Rya + * connection details should be provided via an + * RdfCloudTripleStoreConfiguration, either using + * {@link AbstractForwardChainTool#setConf} or a {@link ToolRunner}. + */ + public ForwardChainSpinTool() { } + + /** + * Load SPIN Construct rules from Rya. + * @return A set of construct query rules. + * @throws ForwardChainException if loading rules from Rya fails. + */ + @Override + protected Ruleset getRuleset() throws ForwardChainException { + if (ruleset == null) { + ruleset = SpinConstructRule.loadSpinRules(getConf()); + } + return ruleset; + } + + public static void main(String[] args) throws Exception { + long start = System.currentTimeMillis(); + ForwardChainSpinTool tool = new ForwardChainSpinTool(); + ToolRunner.run(tool, args); + long end = System.currentTimeMillis(); + double seconds = (end - start) / 1000.0; + long inferences = tool.getNumInferences(); + long rules = tool.getRuleset().getRules().size(); + System.out.println(String.format("ForwardChainSpinTool: %d rules, %d inferences, %.3f seconds", + rules, inferences, seconds)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractConstructRule.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractConstructRule.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractConstructRule.java new file mode 100644 index 0000000..c4c12c7 --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractConstructRule.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.rule; + +import org.apache.rya.api.domain.StatementMetadata; +import org.apache.rya.forwardchain.ForwardChainException; +import org.apache.rya.forwardchain.strategy.AbstractRuleExecutionStrategy; +import org.openrdf.query.algebra.StatementPattern; +import org.openrdf.query.parser.ParsedGraphQuery; + +import com.google.common.base.Preconditions; + +/** + * A rule that produces new triples, and can be expressed as a graph query + * (SPARQL "CONSTRUCT"). Should not modify existing triples. + */ +public abstract class AbstractConstructRule implements Rule { + /** + * Get the query tree corresponding to this construct rule. + * @return The query algebra representation of this rule. + */ + public abstract ParsedGraphQuery getQuery(); + + @Override + public long execute(AbstractRuleExecutionStrategy strategy, + StatementMetadata metadata) throws ForwardChainException { + Preconditions.checkNotNull(strategy); + Preconditions.checkNotNull(metadata); + return strategy.executeConstructRule(this, metadata); + } + + /** + * Whether any of the possible consequents of this rule include anonymous + * variables. Care should be taken when executing such rules, so that + * repeated application doesn't continually produce new bnodes. + * @return true if any subject, predicate, or object variable involved in a + * consequent is flagged as anonymous. + */ + public boolean hasAnonymousConsequent() { + for (StatementPattern sp : getConsequentPatterns()) { + if (sp.getSubjectVar().isAnonymous() + || sp.getPredicateVar().isAnonymous() + || sp.getObjectVar().isAnonymous()) { + return true; + } + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractInconsistencyRule.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractInconsistencyRule.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractInconsistencyRule.java new file mode 100644 index 0000000..451c5e4 --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractInconsistencyRule.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.rule; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.rya.api.domain.StatementMetadata; +import org.apache.rya.forwardchain.ForwardChainException; +import org.apache.rya.forwardchain.strategy.AbstractRuleExecutionStrategy; +import org.openrdf.query.algebra.StatementPattern; + +/** + * A rule that identifies an inconsistency in the data, but does not add or + * modify any triples. + */ +public abstract class AbstractInconsistencyRule implements Rule { + + @Override + public boolean canConclude(StatementPattern sp) { + return false; + } + + @Override + public Collection<StatementPattern> getConsequentPatterns() { + return Arrays.asList(); + } + + @Override + public long execute(AbstractRuleExecutionStrategy strategy, + StatementMetadata metadata) throws ForwardChainException { + return strategy.executeInconsistencyRule(this, metadata); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractUpdateRule.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractUpdateRule.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractUpdateRule.java new file mode 100644 index 0000000..d87aeae --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AbstractUpdateRule.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.rule; + +import org.apache.rya.api.domain.StatementMetadata; +import org.apache.rya.forwardchain.ForwardChainException; +import org.apache.rya.forwardchain.strategy.AbstractRuleExecutionStrategy; + +/** + * A rule that modifies existing data. + */ +public abstract class AbstractUpdateRule implements Rule { + @Override + public long execute(AbstractRuleExecutionStrategy strategy, + StatementMetadata metadata) throws ForwardChainException { + return strategy.executeUpdateRule(this, metadata); + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AntecedentVisitor.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AntecedentVisitor.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AntecedentVisitor.java new file mode 100644 index 0000000..1f2cbba --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/AntecedentVisitor.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.rule; + +import java.util.HashSet; +import java.util.Set; + +import org.openrdf.query.algebra.StatementPattern; +import org.openrdf.query.algebra.helpers.QueryModelVisitorBase; + +/** + * Query visitor that identifies all triple patterns represented as + * {@link StatementPattern}s in a query, which therefore represent triples + * that could potentially contribute to a solution. Considers only the statement + * patterns themselves, i.e. the leaves of the query tree, and does not consider + * other constraints that may restrict the set of triples that may be relevant. + * This means relying on this analysis to determine whether a fact can be part + * of a solution can yield false positives, but not false negatives. + */ +class AntecedentVisitor extends QueryModelVisitorBase<RuntimeException> { + private Set<StatementPattern> antecedentStatementPatterns = new HashSet<>(); + + /** + * Get the StatementPatterns used by this query. + * @return A set of patterns that can contribute to query solutions. + */ + public Set<StatementPattern> getAntecedents() { + return antecedentStatementPatterns; + } + + @Override + public void meet(StatementPattern sp) { + antecedentStatementPatterns.add(sp.clone()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/ConstructConsequentVisitor.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/ConstructConsequentVisitor.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/ConstructConsequentVisitor.java new file mode 100644 index 0000000..e28dbe3 --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/ConstructConsequentVisitor.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.rule; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.openrdf.model.Value; +import org.openrdf.query.algebra.BNodeGenerator; +import org.openrdf.query.algebra.Extension; +import org.openrdf.query.algebra.ExtensionElem; +import org.openrdf.query.algebra.MultiProjection; +import org.openrdf.query.algebra.Projection; +import org.openrdf.query.algebra.ProjectionElem; +import org.openrdf.query.algebra.ProjectionElemList; +import org.openrdf.query.algebra.StatementPattern; +import org.openrdf.query.algebra.ValueConstant; +import org.openrdf.query.algebra.Var; +import org.openrdf.query.algebra.helpers.QueryModelVisitorBase; + +/** + * Query visitor that identifies all triple patterns produced by a "CONSTRUCT" + * query. Finds the topmost instance of a {@link Projection} or + * {@link MultiProjection}, and expects the variables projected to include + * "subject", "predicate", and "object". Each projection is converted to a + * {@link StatementPattern}, where any constant values are expected to be + * provided by an Extension directly underneath the projection, if applicable. + * <p> + * Undefined behavior if applied to a query other than a CONSTRUCT query. + * <p> + * Does not report any constraints on possible consequent triples beyond the + * constant values, where appropriate, of each part of the triple. Therefore, + * this analysis may produce an overly broad set of possible consequents + * compared to some more sophisticated method. + */ +public class ConstructConsequentVisitor extends QueryModelVisitorBase<RuntimeException> { + private Set<StatementPattern> consequentStatementPatterns = new HashSet<>(); + + private static final String SUBJECT_VAR_NAME = "subject"; + private static final String PREDICATE_VAR_NAME = "predicate"; + private static final String OBJECT_VAR_NAME = "object"; + + /** + * Get the possible conclusions of this construct rule. + * @return StatementPatterns representing the possible triple patterns that + * can be inferred. + */ + public Set<StatementPattern> getConsequents() { + return consequentStatementPatterns; + } + + /** + * Get the names of any bnodes generated by this construct rule. + * @return Variable names corresponding to new entities + */ + public Set<StatementPattern> getBnodes() { + return consequentStatementPatterns; + } + + @Override + public void meet(Projection projection) { + if (projection.getArg() instanceof Extension) { + recordConsequent(projection.getProjectionElemList(), + ((Extension) projection.getArg()).getElements()); + } + else { + recordConsequent(projection.getProjectionElemList(), Arrays.asList()); + } + } + + @Override + public void meet(MultiProjection projection) { + List<ExtensionElem> bindings; + if (projection.getArg() instanceof Extension) { + bindings = ((Extension) projection.getArg()).getElements(); + } + else { + bindings = Arrays.asList(); + } + for (ProjectionElemList template : projection.getProjections()) { + recordConsequent(template, bindings); + } + } + + private void recordConsequent(ProjectionElemList variables, List<ExtensionElem> extensionElements) { + Map<String, Value> bindings = new ConcurrentHashMap<>(); + Map<String, Value> values = new ConcurrentHashMap<>(); + Set<String> queryBnodes = new HashSet<>(); + Set<String> projectedBnodes = new HashSet<>(); + for (ExtensionElem ee : extensionElements) { + if (ee.getExpr() instanceof ValueConstant) { + bindings.put(ee.getName(), ((ValueConstant) ee.getExpr()).getValue()); + } + else if (ee.getExpr() instanceof BNodeGenerator) { + queryBnodes.add(ee.getName()); + } + } + for (ProjectionElem var : variables.getElements()) { + String sourceName = var.getSourceName(); + String targetName = var.getTargetName(); + Value constValue = bindings.get(sourceName); + if (constValue != null) { + values.put(targetName, constValue); + } + else if (queryBnodes.contains(sourceName)) { + projectedBnodes.add(targetName); + } + } + Var subjVar = new Var(SUBJECT_VAR_NAME, values.get(SUBJECT_VAR_NAME)); + Var predVar = new Var(PREDICATE_VAR_NAME, values.get(PREDICATE_VAR_NAME)); + Var objVar = new Var(OBJECT_VAR_NAME, values.get(OBJECT_VAR_NAME)); + subjVar.setAnonymous(projectedBnodes.contains(SUBJECT_VAR_NAME)); + predVar.setAnonymous(projectedBnodes.contains(PREDICATE_VAR_NAME)); + objVar.setAnonymous(projectedBnodes.contains(OBJECT_VAR_NAME)); + StatementPattern sp = new StatementPattern(subjVar, predVar, objVar); + consequentStatementPatterns.add(sp); + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/Rule.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/Rule.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/Rule.java new file mode 100644 index 0000000..74004b9 --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/Rule.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.rule; + +import java.util.Collection; + +import org.apache.rya.api.domain.StatementMetadata; +import org.apache.rya.forwardchain.ForwardChainException; +import org.apache.rya.forwardchain.strategy.AbstractRuleExecutionStrategy; +import org.openrdf.query.algebra.StatementPattern; + +/** + * Represents a forward-chaining inference rule. A rule is triggered by some + * combination of triples, and may produce some combination of triples when + * applied. Potential triggers (antecedents) and potential results (consequents) + * are represented in a general form as {@link StatementPattern}s and can be + * used to determine relationships between rules. + */ +public interface Rule { + /** + * Whether this rule, if applied, could produce triples of a given form. + * @param sp A statement pattern describing a possible inferred triple; + * assumed not null. + * @return true if a consequent of this rule could match the pattern. + */ + abstract public boolean canConclude(StatementPattern sp); + + /** + * All {@link StatementPattern}s that can, in some combination, trigger this + * rule. Should be a complete set, such that if no statements matching any + * of the patterns exist, the rule cannot derive any new information. + * @return Any number of statement patterns. + */ + abstract public Collection<StatementPattern> getAntecedentPatterns(); + + /** + * {@link StatementPattern}s completely describing the possible conclusions + * of this rule. Any derived statement should match one of these patterns. + * @return Any number of statement patterns. + */ + abstract public Collection<StatementPattern> getConsequentPatterns(); + + /** + * Given an {@link AbstractRuleExecutionStrategy}, executes this rule. + * Associates any new or modified triples with the specified statement + * metadata. + * @param strategy A strategy capable of applying individual rules; should + * not be null. + * @param metadata StatementMetadata to add to any results. Can be used to + * record the circumstances of the derivation. Should not be null; use + * {@link StatementMetadata#EMPTY_METADATA} to add none. Implementing + * classes may add additional metadata specific to the rule. + * @return The number of new inferences made during rule execution. + * @throws ForwardChainException if an error was encountered during + * rule application. + */ + abstract public long execute(AbstractRuleExecutionStrategy strategy, + StatementMetadata metadata) throws ForwardChainException; +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/Ruleset.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/Ruleset.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/Ruleset.java new file mode 100644 index 0000000..965d2d3 --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/Ruleset.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.rule; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.log4j.Logger; +import org.openrdf.query.algebra.StatementPattern; + +import com.google.common.base.Preconditions; + +/** + * Represents a set of forward-chaining {@link Rule}s and their relationships. + */ +public class Ruleset { + private final Set<Rule> rules; + private final Map<Rule, Set<Rule>> successors; + private final Map<Rule, Set<Rule>> predecessors; + + private final Logger logger = Logger.getLogger(this.getClass()); + + /** + * Constructor. Takes in a set of rules and determines their dependencies. + * @param rules The complete set of rules to process; should not be null. + */ + public Ruleset(Collection<Rule> rules) { + Preconditions.checkNotNull(rules); + this.rules = new HashSet<>(); + for (Rule rule : rules) { + if (rule != null) { + this.rules.add(rule); + } + } + successors = new ConcurrentHashMap<>(); + predecessors = new ConcurrentHashMap<>(); + // Build the dependency graph of all the rules, in both directions + for (Rule rule : rules) { + successors.put(rule, new HashSet<>()); + predecessors.put(rule, new HashSet<>()); + } + for (Rule rule1 : rules) { + for (Rule rule2 : rules) { + if (canTrigger(rule1, rule2)) { + logger.trace("\t" + rule1.toString() + " can trigger " + rule2.toString()); + successors.get(rule1).add(rule2); + predecessors.get(rule2).add(rule1); + } + } + } + } + + /** + * Get the rules associated with this ruleset. + * @return The complete set of rules. + */ + public Set<Rule> getRules() { + return rules; + } + + /** + * Given a rule, return the set of all rules that it may trigger. That is, + * if the rule were to produce inferences, those inferences might directly + * cause other rules to apply in turn. + * @param precedingRule The potentially triggering rule; not null. + * @return All rules that could be triggered by the given rule. + */ + public Collection<Rule> getSuccessorsOf(Rule precedingRule) { + Preconditions.checkNotNull(precedingRule); + return successors.get(precedingRule); + } + + /** + * Given a rule, return the set of all rules that could trigger it. That is, + * if any one of those rules were applied, their potential conclusions could + * directly cause the specified rule to apply in turn. + * @param dependentRule The potentially triggered rule; not null. + * @return All rules that could trigger the given rule. + */ + public Collection<Rule> getPredecessorsOf(Rule dependentRule) { + Preconditions.checkNotNull(dependentRule); + return predecessors.get(dependentRule); + } + + /** + * Given a pair of rules, determine whether a path exists from the first to + * the second. That is, whether the first rule precedes the second rule + * either directly or transitively. If either rule is null, no path exists. + * @param r1 The start of the path + * @param r2 The end of the path + * @return whether a forward path exists. + */ + public boolean pathExists(Rule r1, Rule r2) { + if (r1 == null || r2 == null) { + return false; + } + Set<Rule> forwardFrontier = new HashSet<>(); + Set<Rule> backwardFrontier = new HashSet<>(); + Set<Rule> visitedForward = new HashSet<>(); + Set<Rule> visitedBackward = new HashSet<>(); + forwardFrontier.addAll(getSuccessorsOf(r1)); + backwardFrontier.add(r2); + while (!forwardFrontier.isEmpty() && !backwardFrontier.isEmpty()) { + Set<Rule> currentGoals = new HashSet<>(backwardFrontier); + for (Rule goal : currentGoals) { + if (forwardFrontier.contains(goal)) { + return true; + } + else { + visitedBackward.add(goal); + backwardFrontier.addAll(getPredecessorsOf(goal)); + } + } + backwardFrontier.removeAll(visitedBackward); + Set<Rule> currentSources = new HashSet<>(forwardFrontier); + for (Rule source : currentSources) { + if (backwardFrontier.contains(source)) { + return true; + } + else { + visitedForward.add(source); + forwardFrontier.addAll(getSuccessorsOf(source)); + } + } + forwardFrontier.removeAll(visitedForward); + } + return false; + } + + /** + * Whether the first rule can, in any circumstance, directly trigger the second. + * @param rule1 The first rule, which may produce some inferences + * @param rule2 The second rule, which may use the first rule's conclusions + * @return True if the first rule's conclusions could be used by the second. + */ + private boolean canTrigger(Rule rule1, Rule rule2) { + if (rule1 == null || rule2 == null) { + return false; + } + for (StatementPattern antecedent : rule2.getAntecedentPatterns()) { + if (rule1.canConclude(antecedent)) { + return true; + } + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/SpinConstructRule.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/SpinConstructRule.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/SpinConstructRule.java new file mode 100644 index 0000000..44e15e6 --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/rule/SpinConstructRule.java @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.rule; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.log4j.Logger; +import org.apache.rya.api.RdfCloudTripleStoreConfiguration; +import org.apache.rya.api.domain.StatementMetadata; +import org.apache.rya.api.resolver.RdfToRyaConversions; +import org.apache.rya.forwardchain.ForwardChainConstants; +import org.apache.rya.forwardchain.ForwardChainException; +import org.apache.rya.forwardchain.strategy.AbstractRuleExecutionStrategy; +import org.apache.rya.sail.config.RyaSailFactory; +import org.openrdf.model.Literal; +import org.openrdf.model.Resource; +import org.openrdf.model.Value; +import org.openrdf.model.vocabulary.OWL; +import org.openrdf.model.vocabulary.RDF; +import org.openrdf.model.vocabulary.RDFS; +import org.openrdf.model.vocabulary.SP; +import org.openrdf.model.vocabulary.SPIN; +import org.openrdf.query.BindingSet; +import org.openrdf.query.MalformedQueryException; +import org.openrdf.query.QueryEvaluationException; +import org.openrdf.query.QueryLanguage; +import org.openrdf.query.TupleQuery; +import org.openrdf.query.TupleQueryResultHandlerBase; +import org.openrdf.query.TupleQueryResultHandlerException; +import org.openrdf.query.algebra.Extension; +import org.openrdf.query.algebra.Join; +import org.openrdf.query.algebra.QueryModelNode; +import org.openrdf.query.algebra.SingletonSet; +import org.openrdf.query.algebra.StatementPattern; +import org.openrdf.query.algebra.TupleExpr; +import org.openrdf.query.algebra.UnaryTupleOperator; +import org.openrdf.query.algebra.ValueExpr; +import org.openrdf.query.algebra.Var; +import org.openrdf.query.algebra.helpers.QueryModelVisitorBase; +import org.openrdf.query.parser.ParsedGraphQuery; +import org.openrdf.query.parser.ParsedQuery; +import org.openrdf.query.parser.sparql.SPARQLParser; +import org.openrdf.repository.RepositoryException; +import org.openrdf.repository.sail.SailRepository; +import org.openrdf.repository.sail.SailRepositoryConnection; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; + +/** + * Represents a SPIN Construct rule extracted from the data store, providing + * access to its associated query tree and providing methods to apply the rule. + */ +public class SpinConstructRule extends AbstractConstructRule { + private static Logger logger = Logger.getLogger(SpinConstructRule.class); + + private final Resource ruleId; + private final ParsedGraphQuery graphQuery; + private Set<StatementPattern> antecedentStatementPatterns = null; + private Set<StatementPattern> consequentStatementPatterns = null; + + /** + * Instantiate a SPIN construct rule given its associated type, URI or bnode + * identifier, and construct query tree. Modifies the query tree to + * incorporate the fact that ?this must belong to the associated type, and + * traverses the modified tree to find antecedent and consequent triple + * patterns. + * @param type This rule applies to objects of this type. Should not be + * null. If the type is owl:Thing or rdfs:Resource, it will be applied to + * any objects. Otherwise, a statement pattern will be added that + * effectively binds ?this to members of the type. Therefore, passing + * owl:Thing or rdfs:Resource yields the intended behavior of + * sp:thisUnbound. + * @param ruleId The Resource representing this rule in the RDF data; + * should not be null. + * @param graphQuery The query tree corresponding to the "construct" text; + * should not be null. + */ + public SpinConstructRule(Resource type, Resource ruleId, + ParsedGraphQuery graphQuery) { + Preconditions.checkNotNull(type); + Preconditions.checkNotNull(ruleId); + Preconditions.checkNotNull(graphQuery); + this.ruleId = ruleId; + this.graphQuery = graphQuery; + // Add the type requirement: ?this must belong to the type + graphQuery.getTupleExpr().visit(new TypeRequirementVisitor("this", type)); + // Find all statement patterns that could trigger this rule + AntecedentVisitor aVisitor = new AntecedentVisitor(); + graphQuery.getTupleExpr().visit(aVisitor); + antecedentStatementPatterns = aVisitor.getAntecedents(); + // Construct statement patterns for all possible conclusions of this rule + ConstructConsequentVisitor cVisitor = new ConstructConsequentVisitor(); + graphQuery.getTupleExpr().visit(cVisitor); + consequentStatementPatterns = cVisitor.getConsequents(); + } + + /** + * Get the URI or bnode associated with this rule in the data. + * @return The rule's identifier. + */ + public Resource getId() { + return ruleId; + } + + @Override + public String toString() { + return "SpinConstructRule{" + ruleId.stringValue() + "}"; + } + + @Override + public ParsedGraphQuery getQuery() { + return graphQuery; + } + + @Override + public boolean canConclude(StatementPattern sp) { + Preconditions.checkNotNull(sp); + Value s1 = getVarValue(sp.getSubjectVar()); + Value p1 = getVarValue(sp.getPredicateVar()); + Value o1 = getVarValue(sp.getObjectVar()); + Value c1 = getVarValue(sp.getContextVar()); + for (StatementPattern consequent : consequentStatementPatterns) { + Value s2 = getVarValue(consequent.getSubjectVar()); + Value p2 = getVarValue(consequent.getPredicateVar()); + Value o2 = getVarValue(consequent.getObjectVar()); + Value c2 = getVarValue(consequent.getContextVar()); + if ((s1 == null || s2 == null || s1.equals(s2)) + && (p1 == null || p2 == null || p1.equals(p2)) + && (o1 == null || o2 == null || o1.equals(o2)) + && (c1 == null || c2 == null || c1.equals(c2))) { + return true; + } + } + return false; + } + + @Override + public Collection<StatementPattern> getAntecedentPatterns() { + return antecedentStatementPatterns; + } + + @Override + public Collection<StatementPattern> getConsequentPatterns() { + return consequentStatementPatterns; + } + + @Override + public long execute(AbstractRuleExecutionStrategy strategy, + StatementMetadata metadata) throws ForwardChainException { + metadata.addMetadata(ForwardChainConstants.RYA_DERIVATION_RULE, + RdfToRyaConversions.convertResource(ruleId)); + return super.execute(strategy, metadata); + } + + private static Value getVarValue(Var var) { + return var == null ? null : var.getValue(); + } + + private static class TypeRequirementVisitor extends QueryModelVisitorBase<RuntimeException> { + private static final Var RDF_TYPE_VAR = new Var("-const-" + RDF.TYPE.stringValue(), RDF.TYPE); + private static final Set<Resource> BASE_TYPES = Sets.newHashSet(RDFS.RESOURCE, OWL.THING); + static { + RDF_TYPE_VAR.setConstant(true); + } + + private final String varName; + private final StatementPattern typeRequirement; + public TypeRequirementVisitor(String varName, Resource requiredType) { + final Var typeVar = new Var("-const-" + requiredType.stringValue(), requiredType); + typeVar.setConstant(true); + this.varName = varName; + if (BASE_TYPES.contains(requiredType)) { + this.typeRequirement = null; + } + else { + this.typeRequirement = new StatementPattern(new Var(varName), RDF_TYPE_VAR, typeVar); + } + } + @Override + public void meet(SingletonSet node) { + if (typeRequirement != null) { + node.replaceWith(typeRequirement); + } + } + @Override + public void meet(Extension node) { + Set<String> argBindings = node.getArg().getBindingNames(); + if (typeRequirement != null) { + node.getElements().removeIf(elem -> { + if (varName.equals(elem.getName())) { + ValueExpr expr = elem.getExpr(); + if (expr == null) { + return true; + } + else if (expr instanceof Var) { + String fromName = ((Var) expr).getName(); + if (getVarValue((Var) expr) == null && !argBindings.contains(fromName)) { + return true; + } + } + } + return false; + }); + meetUnaryTupleOperator(node); + } + } + @Override + public void meetNode(QueryModelNode node) { + if (typeRequirement != null) { + if (node instanceof TupleExpr && ((TupleExpr) node).getBindingNames().contains(varName)) { + final Join withType = new Join((TupleExpr) node.clone(), typeRequirement); + node.replaceWith(withType); + } + else { + node.visitChildren(this); + } + } + } + @Override + public void meetUnaryTupleOperator(UnaryTupleOperator node) { + if (typeRequirement != null) { + if (node.getArg().getBindingNames().contains(varName)) { + node.visitChildren(this); + } + else { + meetNode(node); + } + } + } + } + + /** + * Load a set of SPIN rules from a data store. + * @param conf Contains the connection information. Not null. + * @return A map of rule identifiers to rule objects. + * @throws ForwardChainException if connecting, querying for rules, or + * parsing rules fails. + */ + public static Ruleset loadSpinRules(RdfCloudTripleStoreConfiguration conf) + throws ForwardChainException { + Preconditions.checkNotNull(conf); + Map<Resource, Rule> rules = new ConcurrentHashMap<>(); + // Connect to Rya + SailRepository repository = null; + SailRepositoryConnection conn = null; + try { + repository = new SailRepository(RyaSailFactory.getInstance(conf)); + } catch (Exception e) { + throw new ForwardChainException("Couldn't initialize SAIL from configuration", e); + } + // Load and parse the individual SPIN rules from the data store + String ruleQueryString = "SELECT ?type ?rule ?text WHERE {\n" + + " ?type <" + SPIN.RULE_PROPERTY.stringValue() + "> ?rule .\n" + + " {\n" + + " ?rule a <" + SP.CONSTRUCT_CLASS.stringValue() + "> .\n" + + " ?rule <" + SP.TEXT_PROPERTY.stringValue() + "> ?text .\n" + + " } UNION {\n" + + " ?rule a ?template .\n" + + " ?template <" + SPIN.BODY_PROPERTY + ">? ?body .\n" + + " ?body a <" + SP.CONSTRUCT_CLASS.stringValue() + "> .\n" + + " ?body <" + SP.TEXT_PROPERTY.stringValue() + "> ?text .\n" + + " }\n" + + "}"; + SPARQLParser parser = new SPARQLParser(); + try { + conn = repository.getConnection(); + TupleQuery ruleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, ruleQueryString); + ruleQuery.evaluate(new TupleQueryResultHandlerBase() { + @Override + public void handleSolution(BindingSet bs) throws TupleQueryResultHandlerException { + // For each rule identifier found, instantiate a SpinRule + Value requiredType = bs.getValue("type"); + Value ruleIdentifier = bs.getValue("rule"); + Value ruleText = bs.getValue("text"); + if (requiredType instanceof Resource + && ruleIdentifier instanceof Resource + && ruleText instanceof Literal) { + ParsedQuery parsedRule; + try { + parsedRule = parser.parseQuery(ruleText.stringValue(), null); + if (parsedRule instanceof ParsedGraphQuery) { + SpinConstructRule rule = new SpinConstructRule( + (Resource) requiredType, + (Resource) ruleIdentifier, + (ParsedGraphQuery) parsedRule); + if (rule.hasAnonymousConsequent()) { + logger.error("Skipping unsupported rule " + ruleIdentifier + + " -- consequent refers to bnode, which is not" + + " currently supported (creating new bnodes at each" + + " application could lead to infinite recursion)."); + } + else { + rules.put((Resource) ruleIdentifier, rule); + } + } + } catch (Exception e) { + throw new TupleQueryResultHandlerException(e); + } + } + } + }); + } catch (TupleQueryResultHandlerException | QueryEvaluationException + | MalformedQueryException | RepositoryException e) { + throw new ForwardChainException("Couldn't retrieve SPIN rules", e); + } + finally { + if (conn != null) { + try { + conn.close(); + } catch (RepositoryException e) { + logger.warn("Error closing repository connection", e); + } + } + if (repository.isInitialized()) { + try { + repository.shutDown(); + } catch (RepositoryException e) { + logger.warn("Error shutting down repository", e); + } + } + } + return new Ruleset(rules.values()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/AbstractForwardChainStrategy.java ---------------------------------------------------------------------- diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/AbstractForwardChainStrategy.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/AbstractForwardChainStrategy.java new file mode 100644 index 0000000..fb0314e --- /dev/null +++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/AbstractForwardChainStrategy.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.forwardchain.strategy; + +import org.apache.rya.forwardchain.ForwardChainException; +import org.apache.rya.forwardchain.rule.Ruleset; + +/** + * Base class for high-level strategies which define how to conduct + * forward-chaining reasoning (materialization). + */ +public abstract class AbstractForwardChainStrategy { + /** + * A running count of new inferences so far. + */ + protected long totalInferences; + + /** + * Initializes reasoning with respect to a given ruleset. + * @param ruleset The complete set of rules to materialize. Should not be + * null. + * @throws ForwardChainException if initialization fails. + */ + abstract public void initialize(Ruleset ruleset) throws ForwardChainException; + + /** + * Whether forward chaining is both initialized and yet to finish. + * @return true if a ruleset has been provided and some rules may still + * yield new information. + */ + abstract protected boolean isActive(); + + /** + * Execute the next step of reasoning, such as a single rule if the strategy + * proceeds one rule at a time. + * @return The number of inferences made during this step. + * @throws ForwardChainException if any error is encountered during rule + * application. + */ + abstract protected long executeNext() throws ForwardChainException; + + /** + * Execute an entire ruleset until no new rules can be derived. Initializes + * strategy and proceeds until completion. + * @param rules The complete set of rules; not null. + * @return The number of total inferences made. + * @throws ForwardChainException if any error is encountered during + * initialization or application. + */ + public long executeAll(Ruleset rules) throws ForwardChainException { + initialize(rules); + totalInferences = 0; + while (isActive()) { + totalInferences += executeNext(); + } + return totalInferences; + } + + /** + * Get the running total of inferences made so far. + * @return The number of inferences made since initialization. + */ + public long getNumInferences() { + return totalInferences; + } +}
