All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sri.ai.grinder.sgdpllt.library.CommutativeAssociative Maven / Gradle / Ivy

Go to download

SRI International's AIC Symbolic Manipulation and Evaluation Library (for Java 1.8+)

The newest version!
/*
 * 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 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 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); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy