/******************************************************************************* * Copyright (C) 2011 - 2015 Yoav Artzi, All rights reserved. * <p> * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * <p> * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *******************************************************************************/ package edu.cornell.cs.nlp.spf.mr.lambda.exec.naive; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import edu.cornell.cs.nlp.spf.mr.lambda.Lambda; import edu.cornell.cs.nlp.spf.mr.lambda.Literal; import edu.cornell.cs.nlp.spf.mr.lambda.LogicLanguageServices; import edu.cornell.cs.nlp.spf.mr.lambda.LogicalConstant; import edu.cornell.cs.nlp.spf.mr.lambda.LogicalExpression; import edu.cornell.cs.nlp.spf.mr.lambda.Variable; import edu.cornell.cs.nlp.spf.mr.lambda.visitor.ILogicalExpressionVisitor; import edu.cornell.cs.nlp.spf.mr.language.type.RecursiveComplexType; import edu.cornell.cs.nlp.spf.mr.language.type.Type; import edu.cornell.cs.nlp.utils.collections.CollectionUtils; import edu.cornell.cs.nlp.utils.composites.Pair; import edu.cornell.cs.nlp.utils.log.ILogger; import edu.cornell.cs.nlp.utils.log.LoggerFactory; import edu.cornell.cs.nlp.utils.log.thread.InterruptedRuntimeException; /** * Generic evaluation visitor for {@link LogicalExpression}. Stops when * executing thread receives an interrupt and throws a * {@link InterruptedRuntimeException}. * * @author Yoav Artzi */ public class Evaluation implements ILogicalExpressionVisitor { public static final ILogger LOG = LoggerFactory .create(Evaluation.class); private final Map<Variable, Object> denotations = new HashMap<Variable, Object>(); private final IEvaluationServices services; protected Object result = null; protected Evaluation(IEvaluationServices services) { this.services = services; } public static Object of(LogicalExpression exp, IEvaluationServices services) { final Evaluation visitor = new Evaluation(services); visitor.visit(exp); return visitor.result; } /** * Decomposes a logical expression as a SELECT query. * * @param exp * @return Pair of queried variables and SELECT body. If not a SELECT query, * returns null. */ private static Pair<List<Variable>, LogicalExpression> decomposeLogicalExpressionAsSelect( LogicalExpression exp) { LogicalExpression currentBody = exp; final List<Variable> queryVariables = new LinkedList<Variable>(); while (currentBody instanceof Lambda) { final Lambda lambda = (Lambda) currentBody; if (lambda.getArgument().getType().isComplex()) { // Case argument is complex return null; } else { queryVariables.add(lambda.getArgument()); currentBody = lambda.getBody(); } } if (currentBody.getType().isComplex()) { return null; } else { return Pair.of(queryVariables, currentBody); } } private static void testInterruption() { if (Thread.interrupted()) { throw new InterruptedRuntimeException(new InterruptedException( "Evaluation interuppted")); } } @Override public void visit(Lambda lambda) { testInterruption(); if (services.isCached(lambda)) { // Case is cache result = services.getFromCache(lambda); } else { visit(lambda, false); // Cache services.cacheResult(lambda, result); } } @Override public void visit(Literal literal) { testInterruption(); // Try to get from cache if (services.isCached(literal)) { result = services.getFromCache(literal); return; } // If it's a coordination update the result variable with the // default return value (T for conjunction, F for disjunction) final int len = literal.numArgs(); if (LogicLanguageServices.isCoordinationPredicate(literal .getPredicate())) { // Case coordination predicate, can short-circuit // Get short-circuiting argument value final Boolean shortCircuitingValue; if (LogicLanguageServices.getConjunctionPredicate().equals( literal.getPredicate())) { shortCircuitingValue = Boolean.FALSE; } else if (LogicLanguageServices.getDisjunctionPredicate().equals( literal.getPredicate())) { shortCircuitingValue = Boolean.TRUE; } else { throw new IllegalStateException( "unhandled coordination predicate: " + literal); } for (int i = 0; i < len; ++i) { literal.getArg(i).accept(this); if (result == null || shortCircuitingValue.equals(result)) { // Cache services.cacheResult(literal, result); return; } } // Case not short-circuited, so return the default value result = !shortCircuitingValue; } else { // Case not a coordination, no shortcuts, use domain executors to // evaluate // Iterate over the arguments final Object[] evalArgs = new Object[len]; int counter = 0; for (int i = 0; i < len; ++i) { literal.getArg(i).accept(this); if (result == null) { // If failed to evaluate, propagate failure to literal // Cache services.cacheResult(literal, result); return; } else { evalArgs[counter] = result; } ++counter; } // Execute predicate with arguments, return result result = services.evaluateLiteral(literal.getPredicate(), evalArgs); } // Cache services.cacheResult(literal, result); } @Override public void visit(LogicalConstant logicalConstant) { testInterruption(); // Try to get from cache if (services.isCached(logicalConstant)) { result = services.getFromCache(logicalConstant); return; } if (logicalConstant.equals(LogicLanguageServices.getTrue())) { result = true; } else if (logicalConstant.equals(LogicLanguageServices.getFalse())) { result = false; } else { // call domain services to process result = services.evaluateConstant(logicalConstant); } // Cache services.cacheResult(logicalConstant, result); } @Override public void visit(LogicalExpression logicalExpression) { testInterruption(); logicalExpression.accept(this); } @Override public void visit(Variable variable) { testInterruption(); // Variables are not cached, since their denotation constantly changes // Current denotation result = denotations.get(variable); } protected boolean isPartialLiteral(Literal literal) { // Count number of arguments on predicate type Type type = literal.getPredicateType(); int numArgs = 0; while (type.isComplex()) { if (type instanceof RecursiveComplexType) { numArgs += ((RecursiveComplexType) type).getMinArgs(); type = ((RecursiveComplexType) type).getFinalRange(); } else { ++numArgs; type = type.getRange(); } } return literal.numArgs() < numArgs; } /** * This method doesn't support caching. Calls to this method should handle * caching. * * @param lambda * @param shortcircuit */ protected void visit(Lambda lambda, boolean shortcircuit) { // Get the queried variables (the ones we are SELECTing on) and the body // of the select operation. The type of the logical expression has to be // <?,<?,<?...<?,t>..>>>, with all arguments being primitive types and // the final body being type 't'. The query variables are all these // arguments. final Pair<List<Variable>, LogicalExpression> selectDecomposition = decomposeLogicalExpressionAsSelect(lambda); if (selectDecomposition != null) { // Case SELECT expression final LogicalExpression queryBody = selectDecomposition.second(); final List<Variable> queryVariables = selectDecomposition.first(); // Collect all possible denotations final List<Iterable<?>> allDenotations = new ArrayList<Iterable<?>>( queryVariables.size()); for (final Variable variable : queryVariables) { allDenotations.add(services.getAllDenotations(variable)); } LOG.debug("Lambda SELECT execution: query_variables=%s, body=%s", queryVariables, queryBody); LOG.debug("Denotations: %s", allDenotations); final boolean truthTypedBody = LogicLanguageServices .getTypeRepository().getTruthValueType() .equals(queryBody.getType()); // Try all possible combinations of denotations final LambdaResult lambdaResult = new LambdaResult( queryVariables.size()); for (final List<?> tuple : CollectionUtils .cartesianProduct(allDenotations)) { // The two iterables are synced final Iterator<?> denotationsIterator = tuple.iterator(); final Iterator<Variable> variablesIterator = queryVariables .iterator(); while (denotationsIterator.hasNext()) { final Object denotation = denotationsIterator.next(); final Variable variable = variablesIterator.next(); denotations.put(variable, denotation); services.denotationChanged(variable); } LOG.debug("Denotation: %s", denotations); queryBody.accept(this); // Ignore denotations that evaluate the body to null, since it's // an indication towards invalid arrity or typing if (result != null && (!truthTypedBody || Boolean.TRUE.equals(result))) { // Case truth-typed body, return only tuples that // evaluate to 'true', otherwise all tuples are welcome lambdaResult.addTuple(new Tuple(tuple.toArray(), result)); if (shortcircuit) { break; } } } // Clean the cache for all variables for (final Variable variable : queryVariables) { services.denotationChanged(variable); } // Update result result = lambdaResult; // Remove variable from denotations map for (final Variable variable : queryVariables) { denotations.remove(variable); } } else { throw new IllegalArgumentException("invalid lambda: " + lambda); } } }