![JAR search and dependency download from the Maven repository](/logo.png)
com.sri.ai.grinder.sgdpllt.library.CommutativeAssociative Maven / Gradle / Ivy
Show all versions of aic-expresso Show documentation
/*
* Copyright (c) 2013, SRI International
* All rights reserved.
* Licensed under the The BSD 3-Clause License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://opensource.org/licenses/BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of the aic-expresso nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.sri.ai.grinder.sgdpllt.library;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.google.common.annotations.Beta;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.sri.ai.expresso.api.Expression;
import com.sri.ai.expresso.helper.Expressions;
import com.sri.ai.grinder.sgdpllt.api.Context;
import com.sri.ai.grinder.sgdpllt.library.set.Sets;
import com.sri.ai.grinder.sgdpllt.rewriter.api.Simplifier;
import com.sri.ai.util.Util;
import com.sri.ai.util.base.Equals;
/**
* Implements the basics for commutative, associative operators with a neutral
* element such as addition, multiplication, conjunction, etc. It relies on
* actual implementations providing the functor of the operation, its neutral
* element, a predicate identifying arguments that can be operated on (for
* example, addition operates on constants, but not variables), and a method
* applying the operation on these operable arguments.
*
* It works by either
* returning the result of the operation on all arguments, if all arguments are
* operable (for addition, e.g., 1 + 2 + 3 returns 6), or returning an
* application of the operation on the result on operable arguments together
* with the non-operable arguments (for addition, 1 + X + 3 becomes 4 + X).
*
* @author braz
*/
@Beta
public abstract class CommutativeAssociative implements Simplifier {
public abstract Object getFunctor();
protected abstract Expression getNeutralElement();
protected abstract Expression getAbsorbingElement();
protected abstract boolean isIdempotent();
protected abstract Predicate getIsOperableArgumentSyntaxTreePredicate();
protected abstract Expression operationOnOperableArguments(LinkedList operableArguments);
public Expression getNeutralElementExpression() {
return getNeutralElement();
}
public Predicate getIsOperableArgumentExpressionPredicate() {
Predicate result = new Predicate() {
@Override
public boolean apply(Expression expression) {
boolean result = getIsOperableArgumentSyntaxTreePredicate().apply(expression);
return result;
}
};
return result;
}
@Override
public Expression apply(Expression expression, Context context) {
if ( ! expression.hasFunctor(getFunctor())) {
return expression;
}
if ( ! isExtensional(expression)) {
return expression;
}
List arguments = expression.getArguments();
if (arguments.isEmpty()) {
return getNeutralElementExpression();
}
if (arguments.size() == 1) {
return expression.get(0);
}
LinkedList operableArguments = new LinkedList();
LinkedList nonOperableArguments = new LinkedList();
int indexOfFirstOperable =
Util.collect(
arguments,
operableArguments, getIsOperableArgumentExpressionPredicate(),
nonOperableArguments);
if (operableArguments.size() == 0) {
return expression; // everything is an non-operable argument, nothing that can be done.
}
if (operableArguments.contains(getAbsorbingElement())) {
return getAbsorbingElement();
}
// get result on operable arguments
Expression resultOnOperableArgumentsExpression = operationOnOperableArguments(operableArguments);
// this next if then else is both an optimization for the case in which there is a single operable,
// and a way to make sure
// we return the same expression instance when it doesn't change (like x + 2), lest we generate
// a distinct but equal instance that would keep being re-evaluated by the same manipulator.
if (operableArguments.size() == 1) {
if (operableArguments.getFirst().equals(getNeutralElementExpression())) {
// we don't need to include the neutral element, and are left with non-operable arguments only, done.
Expression result = makeExpressionWithSameFunctorAsThis(nonOperableArguments);
return result;
}
else {
// the expression is formed by non-operable arguments and a single non-neutral operable argument, nothing else to do.
return expression;
}
}
// now we deal with the case of more than one operable argument
// if there are no non-operable arguments, that's it.
if (nonOperableArguments.size() == 0) {
return resultOnOperableArgumentsExpression;
}
// if there are non-operable arguments, put them together with operables's result, unless this is the neutral element.
LinkedList argumentsOfResultingExpression = nonOperableArguments; // changing semantics, and therefore, for clarity, the name. It's ok to modify it since it's been created locally.
if ( ! resultOnOperableArgumentsExpression.equals(getNeutralElement())) {
argumentsOfResultingExpression.add(indexOfFirstOperable, resultOnOperableArgumentsExpression);
}
// return operation on result on operable and non-operable arguments.
Expression result = makeExpressionWithSameFunctorAsThis(argumentsOfResultingExpression);
return result;
}
/**
* Tests whether this is extensional version of the function, that is, it is NOT applied to an intensional set
* (commutative associative functions usually have both an extensional and intensional
* version, and this class is only about the extensional).
* @param expression
* @return
*/
public boolean isExtensional(Expression expression) {
boolean result = expression.hasFunctor(getFunctor()) && !(expression.numberOfArguments() == 1 && Sets.isIntensionalSet(expression.get(0)));
return result;
}
/**
* An instance method version of {@link CommutativeAssociative#make(Object, List, Object, boolean)}
* that uses the current object for obtaining the functor and neutral element.
*/
public Expression makeWithSameFunctorAsThis(List arguments) {
return make(Expressions.wrap(getFunctor()), arguments, getNeutralElement(), isIdempotent());
}
/**
* An instance method version of {@link CommutativeAssociative#make(Object, List, Object, boolean)}
* that uses the current object for obtaining the functor and neutral element.
*/
public Expression makeExpressionWithSameFunctorAsThis(List arguments) {
return make(Expressions.wrap(getFunctor()), arguments, getNeutralElement(), isIdempotent());
}
/**
* Similar to {@link Expressions#apply(Object, Object...)},
* but makes a simplified function application of an associative commutative operator,
* that is, its application but for the cases in which there are no arguments, or a single argument.
* When there are no arguments, a given neutral element value is returned.
* When a single argument is provided, it is returned itself.
* @param isIdempotent TODO
*/
public static Expression make(Object functor, List arguments, Expression neutralElement, boolean isIdempotent) {
Predicate notEqualToNeutralElement = Predicates.not(new Equals(neutralElement));
arguments = Util.collectToList(arguments, notEqualToNeutralElement);
if (isIdempotent) {
Set argumentsSet = new LinkedHashSet(arguments);
arguments = new ArrayList(argumentsSet);
}
if (arguments.isEmpty()) {
return Expressions.wrap(neutralElement);
}
if (arguments.size() == 1) {
return arguments.get(0);
}
Expression result = Expressions.makeExpressionOnSyntaxTreeWithLabelAndSubTrees(Expressions.wrap(functor), arguments);
result = Associative.associateWhenSureOperatorIsAssociative(result);
return result;
}
public static Expression make(Object functor, Collection extends Expression> arguments, Expression absorbingElement, Expression neutralElement, boolean isIdempotent) {
return make(functor, arguments.iterator(), absorbingElement, neutralElement, isIdempotent);
}
/**
* Makes a commutative associative application from arguments in an iterator's range.
* This is potentially efficient in that it will stop as soon as the result is determined
* when it finds an absorbing element.
* If the arguments are being computed on the fly (for example, the iterator is a UnaryFunctionIterator),
* this can save a lot of time.
*/
public static Expression make(Object functor, Iterator extends Expression> argumentsIterator, Expression absorbingElement, Expression neutralElement, boolean isIdempotent) {
absorbingElement = Expressions.wrap(absorbingElement);
List arguments = new LinkedList();
while (argumentsIterator.hasNext()) {
Expression argument = argumentsIterator.next();
if (argument.equals(absorbingElement)) {
return absorbingElement;
}
if ( ! argument.equals(neutralElement)) {
arguments.add(argument);
}
}
Expression result = make(functor, arguments, neutralElement, isIdempotent);
return result;
}
/**
* If expression is an application of functor, returns its arguments.
* If not, assumes that it is equivalent to functor(expression)
* (a property of commutative associative functions)
* and returns its arguments, that is, a singleton collection containing expression.
*/
public static List getArgumentsOfNormalizedApplicationOf(Object functor, Expression expression) {
if (expression.hasFunctor(functor)) {
return expression.getArguments();
}
return Util.list(expression);
}
}