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

com.sri.ai.grinder.helper.GrinderUtil Maven / Gradle / Ivy

/*
 * 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.helper;

import static com.sri.ai.expresso.api.Tuple.EMPTY_TUPLE;
import static com.sri.ai.expresso.helper.Expressions.FALSE;
import static com.sri.ai.expresso.helper.Expressions.INFINITY;
import static com.sri.ai.expresso.helper.Expressions.MINUS_INFINITY;
import static com.sri.ai.expresso.helper.Expressions.TRUE;
import static com.sri.ai.expresso.helper.Expressions.apply;
import static com.sri.ai.expresso.helper.Expressions.makeSymbol;
import static com.sri.ai.expresso.helper.Expressions.parse;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.CARDINALITY;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.DIVISION;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.EXPONENTIATION;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.FUNCTION_TYPE;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.GREATER_THAN;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.GREATER_THAN_OR_EQUAL_TO;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.INTEGER_INTERVAL;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.LESS_THAN;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.LESS_THAN_OR_EQUAL_TO;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.MAX;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.MINUS;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.PLUS;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.PRODUCT;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.SUM;
import static com.sri.ai.grinder.sgdpllt.library.FunctorConstants.TIMES;
import static com.sri.ai.util.Util.arrayList;
import static com.sri.ai.util.Util.getFirstOrNull;
import static com.sri.ai.util.Util.getFirstSatisfyingPredicateOrNull;
import static com.sri.ai.util.Util.ifAllTheSameOrNull;
import static com.sri.ai.util.Util.list;
import static com.sri.ai.util.Util.mapIntoArrayList;
import static com.sri.ai.util.Util.mapIntoList;
import static com.sri.ai.util.Util.thereExists;
import static com.sri.ai.util.collect.FunctionIterator.functionIterator;
import static java.lang.Integer.parseInt;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.sri.ai.expresso.api.CountingFormula;
import com.sri.ai.expresso.api.Expression;
import com.sri.ai.expresso.api.FunctionApplication;
import com.sri.ai.expresso.api.IndexExpressionsSet;
import com.sri.ai.expresso.api.IntensionalSet;
import com.sri.ai.expresso.api.LambdaExpression;
import com.sri.ai.expresso.api.QuantifiedExpressionWithABody;
import com.sri.ai.expresso.api.Symbol;
import com.sri.ai.expresso.api.Tuple;
import com.sri.ai.expresso.api.Type;
import com.sri.ai.expresso.core.AbstractExtensionalSet;
import com.sri.ai.expresso.core.DefaultIntensionalMultiSet;
import com.sri.ai.expresso.core.DefaultUniversallyQuantifiedFormula;
import com.sri.ai.expresso.core.ExtensionalIndexExpressionsSet;
import com.sri.ai.expresso.helper.AbstractExpressionWrapper;
import com.sri.ai.expresso.helper.Expressions;
import com.sri.ai.expresso.type.Categorical;
import com.sri.ai.expresso.type.FunctionType;
import com.sri.ai.expresso.type.IntegerExpressoType;
import com.sri.ai.expresso.type.IntegerInterval;
import com.sri.ai.expresso.type.RealExpressoType;
import com.sri.ai.expresso.type.RealInterval;
import com.sri.ai.expresso.type.TupleType;
import com.sri.ai.grinder.api.Registry;
import com.sri.ai.grinder.polynomial.core.DefaultPolynomial;
import com.sri.ai.grinder.sgdpllt.api.Context;
import com.sri.ai.grinder.sgdpllt.library.Disequality;
import com.sri.ai.grinder.sgdpllt.library.Equality;
import com.sri.ai.grinder.sgdpllt.library.FormulaUtil;
import com.sri.ai.grinder.sgdpllt.library.FunctorConstants;
import com.sri.ai.grinder.sgdpllt.library.controlflow.IfThenElse;
import com.sri.ai.grinder.sgdpllt.library.indexexpression.IndexExpressions;
import com.sri.ai.grinder.sgdpllt.library.number.GreaterThan;
import com.sri.ai.grinder.sgdpllt.library.number.LessThan;
import com.sri.ai.grinder.sgdpllt.library.set.Sets;
import com.sri.ai.grinder.sgdpllt.library.set.extensional.ExtensionalSets;
import com.sri.ai.util.Util;
import com.sri.ai.util.math.Rational;

/**
 * General purpose utility routines related to grinder libraries.
 * 
 * @author oreilly
 */
@Beta
public class GrinderUtil {

	/**
	 * Makes a list of expressions representing symbols and their types from a list of their strings.
	 * @param symbolsAndTypes
	 * @return
	 */
	public static Expression[] makeListOfSymbolsAndTypesExpressionsFromSymbolsAndTypesStrings(String... symbolsAndTypes) {
		Expression symbolsAndTypesExpressions[] = new Expression[symbolsAndTypes.length];
		for (int i = 0; i != symbolsAndTypes.length; i++) {
			symbolsAndTypesExpressions[i] = parse(symbolsAndTypes[i]);
		}
		return symbolsAndTypesExpressions;
	}
	
	/**
	 * Makes list of index expressions from symbols and types.
	 * @param symbolsAndTypes
	 * @return
	 */
	public static List makeIndexExpressionsFromSymbolsAndTypes(Expression... symbolsAndTypes) {
		List indexExpressions = list();
		for (int i = 0; i != symbolsAndTypes.length/2; i++) {
			Expression indexExpression = apply(FunctorConstants.IN, symbolsAndTypes[2*i], symbolsAndTypes[2*i + 1]);
			indexExpressions.add(indexExpression);
		}
		return indexExpressions;
	}
	
	/**
	 * Returns a list of index expressions corresponding to the free variables in an expressions and their types per the registry, if any.
	 */
	public static IndexExpressionsSet getIndexExpressionsOfFreeVariablesIn(Expression expression, Registry registry) {
		Set freeVariables = Expressions.freeVariables(expression, registry);
		IndexExpressionsSet result = makeIndexExpressionsForIndicesInListAndTypesInRegistry(freeVariables, registry);
		return result;
	}

	/**
	 * Returns a list of index expressions corresponding to the given indices and their types per the registry, if any.
	 */
	public static ExtensionalIndexExpressionsSet makeIndexExpressionsForIndicesInListAndTypesInRegistry(Collection indices, Registry registry) {
		List indexExpressions = new LinkedList();
		for (Expression index : indices) {
			Expression type = registry.getTypeExpressionOfRegisteredSymbol(index);
			Expression indexExpression = IndexExpressions.makeIndexExpression(index, type);
			indexExpressions.add(indexExpression);
		}
		return new ExtensionalIndexExpressionsSet(indexExpressions);
	}

	/**
	 * @param mapFromSymbolNameToTypeName
	 * @param additionalTypes
	 * @param mapFromCategoricalTypeNameToSizeString
	 * @param registry
	 * @return
	 */
	public static Registry extendRegistryWith(Map mapFromSymbolNameToTypeName, Collection additionalTypes, Map mapFromCategoricalTypeNameToSizeString, Predicate isUniquelyNamedConstantPredicate, Registry registry) {
		Collection allTypes =
				getCategoricalTypes(
						mapFromSymbolNameToTypeName,
						mapFromCategoricalTypeNameToSizeString,
						isUniquelyNamedConstantPredicate,
						registry);
		allTypes.addAll(additionalTypes);
		
		return extendRegistryWith(mapFromSymbolNameToTypeName, allTypes, registry);
	}

	/**
	 * @param mapFromSymbolNameToTypeName
	 * @param types
	 * @param registry
	 * @return
	 */
	public static Registry extendRegistryWith(Map mapFromSymbolNameToTypeName, Collection types, Registry registry) {
		List symbolDeclarations = new ArrayList<>();
		for (Map.Entry variableNameAndTypeName : mapFromSymbolNameToTypeName.entrySet()) {
			String symbolName = variableNameAndTypeName.getKey();
			String typeName   = variableNameAndTypeName.getValue();
			
			symbolDeclarations.add(parse(symbolName + " in " + typeName));
		}
		Registry result = registry.extendWith(new ExtensionalIndexExpressionsSet(symbolDeclarations));
		registry = result;
					
		for (Type type : types) {
			registry = registry.makeCloneWithAddedType(type);
			// System.out.println("Cardinality of type being computed: " + type);
			Expression cardinality = type.cardinality();
			registry = registry.putGlobalObject(parse("|" + type.getName() + "|"), cardinality);
		}
		
		return registry;
	}

	/**
	 * Returns a universal quantification of given expression over its free variables,
	 * with types as registered in registry.
	 * @param expression
	 * @param registry
	 * @return
	 */
	public static Expression universallyQuantifyFreeVariables(Expression expression, Registry registry) {
		IndexExpressionsSet indexExpressions = getIndexExpressionsOfFreeVariablesIn(expression, registry);
		Expression universallyQuantified = new DefaultUniversallyQuantifiedFormula(indexExpressions, expression);
		return universallyQuantified;
	}

	/**
	 * @param mapFromSymbolNameToTypeName
	 * @param mapFromCategoricalTypeNameToSizeString
	 * @param isUniquelyNamedConstantPredicate
	 * @param registry
	 * @return
	 */
	public static Collection getCategoricalTypes(
			Map mapFromSymbolNameToTypeName,
			Map mapFromCategoricalTypeNameToSizeString,
			Predicate isUniquelyNamedConstantPredicate,
			Registry registry) {
		
		Collection categoricalTypes = new LinkedList();
		for (Map.Entry typeNameAndSizeString : mapFromCategoricalTypeNameToSizeString.entrySet()) {
			String typeExpressionString = typeNameAndSizeString.getKey();
			String sizeString = typeNameAndSizeString.getValue();
			
			// check if already present and, if not, make it
			Categorical type = (Categorical) registry.getType(typeExpressionString);
			if (type == null) {
				if (typeExpressionString.equals("Boolean")) {
					type = BOOLEAN_TYPE;
				}
				else {
					ArrayList knownConstants = 
							getKnownUniquelyNamedConstantsOf(
									typeExpressionString,
									mapFromSymbolNameToTypeName,
									isUniquelyNamedConstantPredicate,
									registry);
					type = 
							new Categorical(
									typeExpressionString,
									parseInt(sizeString),
									knownConstants);
				}
			}
			categoricalTypes.add(type);
		}
		return categoricalTypes;
	}

	/**
	 * @param typeName
	 * @param mapFromSymbolNameToTypeName
	 * @param registry
	 * @return
	 */
	public static ArrayList getKnownUniquelyNamedConstantsOf(String typeName, Map mapFromSymbolNameToTypeName, Predicate isUniquelyNamedConstantPredicate, Registry registry) {
		ArrayList knownConstants = new ArrayList();
		for (Map.Entry symbolNameAndTypeName : mapFromSymbolNameToTypeName.entrySet()) {
			if (symbolNameAndTypeName.getValue().equals(typeName)) {
				Expression symbol = makeSymbol(symbolNameAndTypeName.getKey());
				if (isUniquelyNamedConstantPredicate.apply(symbol)) {
					knownConstants.add(symbol);
				}
			}
		}
		return knownConstants;
	}

	/**
	 * Gets a function application and its type T, and returns the inferred type of its functor,
	 * which is '->'('x'(T1, ..., Tn), T), where T1,...,Tn are the types.
	 */
	public static Expression getTypeOfFunctor(Expression functionApplication, Expression functionApplicationType, Registry registry) {
		Expression result;
		if (functionApplication.getSyntacticFormType().equals(FunctionApplication.SYNTACTIC_FORM_TYPE)) {
			List argumentTypes = Util.mapIntoArrayList(functionApplication.getArguments(), new GetType(registry));
			if (argumentTypes.contains(null)) {
				result = null; // unknown type
			}
			else {
				result = FunctionType.make(functionApplicationType, argumentTypes);
			}
		}
		else {
			throw new Error("getTypeOfFunctor applicable to function applications only, but invoked on " + functionApplication);
		}
		return result;
	}

	/**
	 * Returns the type of given expression according to registry.
	 */
	public static Expression getTypeExpressionOfExpression(Expression expression, Registry registry) {
		Expression result;
		
		// TODO: this method is horribly hard-coded to a specific language; need to clean this up
		
		if (FormulaUtil.isApplicationOfBooleanConnective(expression)) {
			result = makeSymbol("Boolean");
		}
		else if (expression.getSyntacticFormType().equals(FunctionApplication.SYNTACTIC_FORM_TYPE) &&
				list(SUM, PRODUCT, MAX).contains(expression.getFunctor().toString())) {
			Expression argument = expression.get(0);
			if (argument.getSyntacticFormType().equals(IntensionalSet.SYNTACTIC_FORM_TYPE)) {
				IntensionalSet intensionalSetArgument = (IntensionalSet) argument;
				Expression head = intensionalSetArgument.getHead();
				// NOTE: Need to extend the registry as the index expressions in the quantifier may
				// declare new types (i.e. function types).
				Registry headRegistry = registry.extendWith(intensionalSetArgument.getIndexExpressions());
				result = getTypeExpressionOfExpression(head, headRegistry);
			}
			else if (argument.getSyntacticFormType().equals(ExtensionalSets.SYNTACTIC_FORM_TYPE)) {
				List arguments = ((AbstractExtensionalSet)argument).getElementsDefinitions();
				result = getTypeOfCollectionOfNumericExpressionsWithDefaultInteger(arguments, registry);
			}
			else if (expression.hasFunctor(MAX)) { // MAX can also be applied to a bunch of numbers
				result = getTypeOfCollectionOfNumericExpressionsWithDefaultInteger(expression.getArguments(), registry);
			}
			else {
				throw new Error(expression.getFunctor() + " defined for sets only but got " + expression.get(0));
			}
		}
		else if (Equality.isEquality(expression) || Disequality.isDisequality(expression)) {
			result = makeSymbol("Boolean");
		}
		else if (
				expression.equals(FunctorConstants.REAL_INTERVAL_CLOSED_CLOSED) ||
				expression.equals(FunctorConstants.REAL_INTERVAL_CLOSED_OPEN) ||
				expression.equals(FunctorConstants.REAL_INTERVAL_OPEN_CLOSED) ||
				expression.equals(FunctorConstants.REAL_INTERVAL_OPEN_OPEN)
				) {
			result = FunctionType.make(parse("Set"), parse("Number"), parse("Number"));
		}
		else if (IfThenElse.isIfThenElse(expression)) {
			Expression thenType = getTypeExpressionOfExpression(IfThenElse.thenBranch(expression), registry);
			Expression elseType = getTypeExpressionOfExpression(IfThenElse.elseBranch(expression), registry);
			if (thenType != null && elseType != null && (thenType.equals("Number") && isIntegerOrReal(elseType) || isIntegerOrReal(thenType) && elseType.equals("Number"))) {
				result = makeSymbol("Number");
			}
			else if (thenType != null && elseType != null && (thenType.equals("Integer") && elseType.equals("Real") || thenType.equals("Real") && elseType.equals("Integer"))) {
				result = makeSymbol("Real");
			}
			else if (thenType != null && (elseType == null || thenType.equals(elseType))) {
				result = thenType;
			}
			else if (elseType != null && (thenType == null || elseType.equals(thenType))) {
				result = elseType;
			}
			else if (thenType == null) {
				throw new Error("Could not determine the types of then and else branches of '" + expression + "'.");
			}
			else if (thenType.equals("Integer") && elseType.hasFunctor(INTEGER_INTERVAL)) { // TODO: I know, I know, this treatment of integers and interval is terrible... will fix at some point
				result = thenType;
			}
			else if (thenType.hasFunctor(INTEGER_INTERVAL) && elseType.equals("Integer")) {
				result = elseType;
			}
			else if (thenType.hasFunctor(INTEGER_INTERVAL) && elseType.hasFunctor(INTEGER_INTERVAL)) {
				IntegerInterval thenInterval = (IntegerInterval) thenType;
				IntegerInterval elseInterval = (IntegerInterval) elseType;
				Expression minimumLowerBound = 
						LessThan.simplify(apply(LESS_THAN, thenInterval.getNonStrictLowerBound(), elseInterval.getNonStrictLowerBound()), registry).booleanValue()
						? thenInterval.getNonStrictLowerBound()
								: elseInterval.getNonStrictLowerBound();
				Expression maximumUpperBound =
						GreaterThan.simplify(apply(GREATER_THAN, thenInterval.getNonStrictUpperBound(), elseInterval.getNonStrictUpperBound()), registry).booleanValue()
						? thenInterval.getNonStrictUpperBound()
								: elseInterval.getNonStrictUpperBound();
				if (minimumLowerBound.equals(MINUS_INFINITY) && maximumUpperBound.equals(INFINITY)) {
					result = makeSymbol("Integer");
				}
				else {
					result = apply(INTEGER_INTERVAL, minimumLowerBound, maximumUpperBound);
				}
			}
			else {
				throw new Error("'" + expression + "' then and else branches have different types (" + thenType + " and " + elseType + " respectively).");
			}
		}
		else if (isCardinalityExpression(expression)) {
			result = makeSymbol("Integer");
		}
		else if (isNumericFunctionApplication(expression)) {
			
			List argumentTypes = mapIntoList(expression.getArguments(), e -> getTypeExpressionOfExpression(e, registry));
			
			int firstNullArgumentTypeIndexIfAny = Util.getIndexOfFirstSatisfyingPredicateOrMinusOne(argumentTypes, t -> t == null);
			if (firstNullArgumentTypeIndexIfAny != -1) {
				throw new Error("Cannot determine type of " + expression.getArguments().get(firstNullArgumentTypeIndexIfAny) + ", which is needed for determining type of " + expression);
			}
			
			/**
			 * commonDomain is the co-domain shared by all argument function types, or empty tuple for arguments that are not function-typed.
			 * Therefore, if no argument is function-typed, it will be equal to the empty tuple.
			 */
			Expression commonDomain = getCommonDomainIncludingConversionOfNonFunctionTypesToNullaryFunctions(argumentTypes, registry);
			
			if (commonDomain == null) {
				throw new Error("Operator " + expression.getFunctor() + " applied to arguments of non-compatible types: " + expression + ", types of arguments are " + argumentTypes);
			}
			
			boolean noArgumentIsFunctionTyped = 
					commonDomain.equals(EMPTY_TUPLE) &&
					! thereExists(argumentTypes, t -> t.hasFunctor(FunctorConstants.FUNCTION_TYPE));
			
			Expression resultCoDomain;
			if (thereExists(argumentTypes, t -> Util.equals(getCoDomainOrItself(t), "Number"))) {
				resultCoDomain = makeSymbol("Number");
			}
			else if (thereExists(argumentTypes, t -> Util.equals(getCoDomainOrItself(t), "Real"))) {
				resultCoDomain = makeSymbol("Real");
			}
			else if (thereExists(argumentTypes, t -> isRealInterval(getCoDomainOrItself(t)))) {
				resultCoDomain = makeSymbol("Real");
			}
			else {
				resultCoDomain = makeSymbol("Integer");
			}
			
			if (noArgumentIsFunctionTyped) {
				result = resultCoDomain;
			}
			else {
				result = apply(FUNCTION_TYPE, commonDomain, resultCoDomain);
			}
		}
		else if (
				expression.hasFunctor(FunctorConstants.INTEGER_INTERVAL) ||
				expression.hasFunctor(FunctorConstants.REAL_INTERVAL_CLOSED_CLOSED) ||
				expression.hasFunctor(FunctorConstants.REAL_INTERVAL_OPEN_CLOSED) ||
				expression.hasFunctor(FunctorConstants.REAL_INTERVAL_CLOSED_OPEN) ||
				expression.hasFunctor(FunctorConstants.REAL_INTERVAL_OPEN_OPEN)
				) {
			result = makeSymbol("Set");
		}
		else if (isComparisonFunctionApplication(expression)) {
				result = makeSymbol("Boolean");
		}
		else if (expression.hasFunctor(FunctorConstants.FUNCTION_TYPE)) {
			// very vague type for now
			result = apply(FUNCTION_TYPE, makeSymbol("Set"), makeSymbol("Set"));
		}
		else if (Sets.isIntensionalMultiSet(expression)) {
			IntensionalSet set = (IntensionalSet) expression;
			// NOTE: Need to extend the registry as the index expressions in the quantifier may
			// declare new types (i.e. function types).
			Registry headRegistry = registry.extendWith(set.getIndexExpressions());
			Expression headType = getTypeExpressionOfExpression(set.getHead(), headRegistry);
			result = new DefaultIntensionalMultiSet(list(), headType, TRUE);
		}
		else if (Sets.isExtensionalSet(expression)) {
			// very vague type for now
			result = apply(FUNCTION_TYPE, makeSymbol("Set"));
		}
		else if (expression.hasFunctor(FunctorConstants.INTERSECTION) || expression.hasFunctor(FunctorConstants.UNION) || expression.hasFunctor(FunctorConstants.INTENSIONAL_UNION)) {
			// very vague type for now
			result = apply(FUNCTION_TYPE, makeSymbol("Set"));
		}
		else if (expression.getSyntacticFormType().equals(Symbol.SYNTACTIC_FORM_TYPE)) {
			if (expression.getValue() instanceof Integer) {
				result = makeSymbol("Integer");
			}
			else if (expression.getValue() instanceof Double) {
				result = makeSymbol("Real");
			}
			else if (expression.getValue() instanceof Rational) {
				Rational rational = (Rational) expression.getValue();
				boolean isInteger = rational.isInteger();
				result = makeSymbol(isInteger? "Integer" : "Real");
			}
			else if (expression.getValue() instanceof Number) {
				result = makeSymbol("Number");
			}
			else if (expression.getValue() instanceof String && expression.isStringLiteral()) {
				result = makeSymbol("String");
			}
			else if (expression.getValue() instanceof Boolean) {
				result = makeSymbol("Boolean");
			}
			else if (expression.equals(Expressions.INFINITY) || expression.equals(Expressions.MINUS_INFINITY)) {
				result = makeSymbol("Number");
			}
			else {
				result = registry.getTypeExpressionOfRegisteredSymbol(expression);

				if (result == null) {
					Type type = getFirstSatisfyingPredicateOrNull(registry.getTypes(), t -> t.contains(expression));
					if (type != null) {
						result = parse(type.getName());
					}
				}
			}
		}
		else if (expression.hasFunctor(FunctorConstants.GET) && expression.numberOfArguments() == 2 && Expressions.isNumber(expression.get(1))) {
			Expression argType = getTypeExpressionOfExpression(expression.get(0), registry);
			if (TupleType.isTupleType(argType)) {
				TupleType tupleType = (TupleType) GrinderUtil.fromTypeExpressionToItsIntrinsicMeaning(argType, registry);
				result = parse(tupleType.getElementTypes().get(expression.get(1).intValue()-1).toString());			
			}
			else {
				throw new Error("get type from tuple for '" + expression + "' currently not supported.");
			}
		}
		else if (expression.hasFunctor(FunctorConstants.TUPLE_TYPE)) {
			result = expression; // Is a type expression already.
		}
		else if (expression.getSyntacticFormType().equals(FunctionApplication.SYNTACTIC_FORM_TYPE)) {
			Expression functionType = getTypeExpressionOfExpression(expression.getFunctor(), registry);
			if (functionType == null) {
				throw new Error("Type of '" + expression.getFunctor() + "' required but unknown.");
			}
			
			Expression coDomain = FunctionType.getCodomain(functionType);
			List argumentsTypesList = FunctionType.getArgumentList(functionType);

			if (expression.getArguments().size() != argumentsTypesList.size()) {
				throw new Error("Function " + expression.getFunctor() + " is of type " + functionType + " but has incorrect number of arguments = "+ expression.getArguments());
			}
			for (int idx = 0; idx < expression.getArguments().size(); idx++) {
				Expression arg         = expression.get(idx);
				Expression argExprType = argumentsTypesList.get(idx);
				Type       argType     = registry.getTypeFromTypeExpression(argExprType);
				if (!isSubtypeOf(arg, argType, registry)) {
					throw new Error("Function " + expression.getFunctor() + " is of type " + functionType + " but has arguments that are not legal subtypes [#"+idx+"] = "+ expression.getArguments());
				}
			}

			result = coDomain;
		}
		else if (Tuple.isTuple(expression)) {
			List elementTypes =
					expression.getArguments().stream()
						.map(element -> getTypeExpressionOfExpression(element, registry))
						.collect(Collectors.toList());
			result = TupleType.make(elementTypes);
		}
		else if (expression instanceof QuantifiedExpressionWithABody) {
			QuantifiedExpressionWithABody quantifiedExpressionWithABody = (QuantifiedExpressionWithABody) expression;
			// NOTE: Need to extend the registry as the index expressions in the quantifier may
			// declare new types (i.e. function types).
			Registry quantifiedExpressionWithABodyRegistry = registry.extendWith(quantifiedExpressionWithABody.getIndexExpressions());
			result = getTypeExpressionOfExpression(quantifiedExpressionWithABody.getBody(), quantifiedExpressionWithABodyRegistry);
		}
		else if (expression instanceof LambdaExpression) {
			LambdaExpression lambdaExpression = (LambdaExpression) expression;
			Collection domain = IndexExpressions.getIndexDomainsOfQuantifiedExpression(lambdaExpression);

			IndexExpressionsSet indexExpressions = lambdaExpression.getIndexExpressions();
			Registry lambdaExpressionWithABodyRegistry = registry.extendWith(indexExpressions);
			Expression coDomain = getTypeExpressionOfExpression(lambdaExpression.getBody(), lambdaExpressionWithABodyRegistry);
			
			result = Expressions.apply(FUNCTION_TYPE, domain, coDomain);
		}
		else if (expression instanceof AbstractExpressionWrapper) {
			Expression innerExpression = ((AbstractExpressionWrapper) expression).getInnerExpression();
			result = getTypeExpressionOfExpression(innerExpression, registry);
		}
		else {
			throw new Error("GrinderUtil.getType does not yet know how to determine the type of this sort of expression: " + expression);
		} 
		return result;
	}

	/**
	 * Given a type, returns the type's co-domain if it is a function type,
	 * or the type itself.
	 * @param type
	 * @return
	 */
	public static Expression getCoDomainOrItself(Expression type) {
		Expression result;
		if (type.hasFunctor(FUNCTION_TYPE)) {
			result = type.get(type.numberOfArguments() - 1);
		}
		else {
			result = type;
		}
		return result;
	}
	
	private static Expression getCommonDomainIncludingConversionOfNonFunctionTypesToNullaryFunctions(Collection types, Registry registry) {
		Expression result = 
				ifAllTheSameOrNull(
						functionIterator(
								types, 
								t -> getDomainIncludingConversionOfNonFunctionTypesToNullaryFunctions(t, registry)
								));
		return result;
	}
	
	private static Expression getDomainIncludingConversionOfNonFunctionTypesToNullaryFunctions(Expression type, Registry registry) {
		if (type.hasFunctor(FunctorConstants.FUNCTION_TYPE)) {
			return type.get(0);
		}
		return EMPTY_TUPLE;
	}

	/**
	 * @param e
	 * @return
	 */
	private static boolean isRealInterval(Expression e) {
		return e.hasFunctor(FunctorConstants.REAL_INTERVAL_CLOSED_CLOSED)
		|| e.hasFunctor(FunctorConstants.REAL_INTERVAL_OPEN_CLOSED)
		|| e.hasFunctor(FunctorConstants.REAL_INTERVAL_CLOSED_OPEN)
		|| e.hasFunctor(FunctorConstants.REAL_INTERVAL_OPEN_OPEN);
	}

	/**
	 * @param type
	 * @return
	 */
	public static boolean isIntegerOrReal(Expression type) {
		return type.equals("Integer") || type.equals("Real");
	}
	
	/**
	 * Test if a given expression is equivalent to a cardinality expression.
	 * 
	 * @param expression
	 *            the expression to be tested.
	 * @return true if the given expression is equivalent to a cardinality
	 *         expression.
	 */
	public static boolean isCardinalityExpression(Expression expression) {
		return (expression instanceof CountingFormula) || expression.hasFunctor(CARDINALITY);
	}

	/**
	 * @param arguments
	 * @param registry
	 * @return
	 */
	private static Expression getTypeOfCollectionOfNumericExpressionsWithDefaultInteger(List arguments, Registry registry) {
		Expression result;
		Expression first = getFirstOrNull(arguments);
		if (first == null) {
			result = makeSymbol("Integer");
		}
		else {
			result = getTypeExpressionOfExpression(first, registry);
		}
		return result;
	}

	private static Collection arithmeticFunctors = Util.set(PLUS, TIMES, MINUS, DIVISION, EXPONENTIATION);
	
	public static boolean isNumericFunctionApplication(Expression expression) {
		boolean result =
				expression.getSyntacticFormType().equals(FunctionApplication.SYNTACTIC_FORM_TYPE)
				&& arithmeticFunctors.contains(expression.getFunctor().toString());
		return result;
	}

	private static Collection comparisonFunctors = Util.set(LESS_THAN, LESS_THAN_OR_EQUAL_TO, GREATER_THAN, GREATER_THAN_OR_EQUAL_TO);
	
	public static boolean isComparisonFunctionApplication(Expression expression) {
		boolean result =
				expression.getSyntacticFormType().equals(FunctionApplication.SYNTACTIC_FORM_TYPE)
				&& comparisonFunctors.contains(expression.getFunctor().toString());
		return result;
	}

	/**
	 * Returns the cardinality of the type of a given variable in the given registry,
	 * looking for | Type |, for Type the type of the variable,
	 * in the registry global objects.
	 * If the size cannot be determined, returns -1.
	 * If the size is infinite, returns -2.
	 * @param symbol a variable
	 * @param registry the registry
	 * @return the cardinality of the type of the variable according to the registry or -1 if it cannot be determined.
	 */
	public static long getTypeCardinality(Expression symbol, Registry registry) {
		long result = -1;
	
		Expression variableType = registry.getTypeExpressionOfRegisteredSymbol(symbol);
		if (variableType != null) {
			Expression typeCardinality = Expressions.apply(FunctorConstants.CARDINALITY, variableType);
			Expression typeCardinalityValue = (Expression) registry.getGlobalObject(typeCardinality);
			if (typeCardinalityValue != null) {
				result = typeCardinalityValue.intValueExact();
			}
		}
		
		// If that didn't work, we try find the Type object:
		if (result == -1) {
			variableType = registry.getTypeExpressionOfRegisteredSymbol(symbol);
			if (variableType != null) {
				Type type = registry.getTypeFromTypeExpression(variableType);
				if (type != null) {
					Expression sizeExpression = type.cardinality();
					if (sizeExpression.equals(apply(CARDINALITY, type.getName()))) {
						result = -1;
					}
					else if (sizeExpression.equals(Expressions.INFINITY)) {
						result = -2;
					}
					else {
						result = sizeExpression.intValue();
					}
				}
			}
		}
		
		return result;
	}

	
	static final Expression _booleanType1 = parse("Boolean");
	static final Expression _booleanType2 = parse("'->'(Boolean)");
	static final Expression _booleanType3 = parse("bool");
	static final Expression _booleanType4 = parse("boolean");
	/**
	 * Indicates whether an expression is boolean-typed by having its {@link getType}
	 * type be "Boolean", "'->'(Boolean)", or "bool", or "boolean".
	 * @param expression
	 * @param registry
	 * @return
	 */
	public static boolean isBooleanTyped(Expression expression, Registry registry) {
		Expression type = getTypeExpressionOfExpression(expression, registry);
		boolean result =
				type != null &&
				(
				type.equals(_booleanType1) ||
				type.equals(_booleanType2) ||
				type.equals(_booleanType3) ||
				type.equals(_booleanType4));
		return result;
	}

	public static final Categorical BOOLEAN_TYPE = new Categorical("Boolean", 2, arrayList(TRUE, FALSE));
	public static final IntegerExpressoType INTEGER_TYPE = new IntegerExpressoType();
	public static final RealExpressoType REAL_TYPE = new RealExpressoType();
	
	/**
	 * A method mapping type expressions to their intrinsic {@link Type} objects,
	 * where "intrinsic" means there is only one possible {@link Type} object
	 * for them in the registry of grinder
	 * (therefore, it cannot be used for, say, categorical types defined
	 * by the user and registered in the registry by name only).
	 * Current recognized type expressions are
	 * Boolean, Integer, and function applications
	 * of the type m..n.
	 * If there is no such meaning, the method returns null.
	 * @param typeExpression
	 * @param registry TODO
	 * @return
	 */
	public static Type fromTypeExpressionToItsIntrinsicMeaning(Expression typeExpression, Registry registry) throws Error {
		Type type;
		if (typeExpression.equals("Boolean")) {
			type = BOOLEAN_TYPE;
		}
		else if (typeExpression.equals("Integer")) {
			type = INTEGER_TYPE;
		}
		else if (typeExpression.equals("Real")) {
			type = REAL_TYPE;
		}
		else if (typeExpression.hasFunctor(INTEGER_INTERVAL) && typeExpression.numberOfArguments() == 2) {
			type = new IntegerInterval(typeExpression.get(0), typeExpression.get(1));
		}
		else if (
				(
						typeExpression.hasFunctor(FunctorConstants.REAL_INTERVAL_CLOSED_CLOSED) ||
						typeExpression.hasFunctor(FunctorConstants.REAL_INTERVAL_OPEN_CLOSED) ||
						typeExpression.hasFunctor(FunctorConstants.REAL_INTERVAL_CLOSED_OPEN) ||
						typeExpression.hasFunctor(FunctorConstants.REAL_INTERVAL_OPEN_OPEN)
				)
				&& typeExpression.numberOfArguments() == 2) {
			type = new RealInterval(typeExpression.toString());
		}
		else if (FunctionType.isFunctionType(typeExpression)) {
			Function getType = e -> registry.getTypeFromTypeExpression(e);
			
			Type codomain = getType.apply(FunctionType.getCodomain(typeExpression));
			
			List argumentTypeExpressions = FunctionType.getArgumentList(typeExpression);
			
			ArrayList argumentTypes = mapIntoArrayList(argumentTypeExpressions, getType);
			Type[] argumentTypesArray = new Type[argumentTypes.size()];
			
			type = new FunctionType(codomain, argumentTypes.toArray(argumentTypesArray));
		}
		else if (TupleType.isTupleType(typeExpression)) {
			List elementTypes = typeExpression.getArguments().stream()
				.map(elementTypeExpression -> registry.getTypeFromTypeExpression(elementTypeExpression))
				.collect(Collectors.toList());
			type = new TupleType(elementTypes);
		}
		else {
			type = null;
		}
		return type;
	}
	
	/**
	 * Test if a type is a subtype of another type.
	 * 
	 * @param type the type to test if it is a subtype.
	 * @param ofType type to be tested if a subtype of.
	 * 
	 * @return true if 'type' is a subtype of 'ofType', false otherwise.
	 */
	public static boolean isTypeSubtypeOf(Type type, Type ofType) {
		boolean result = false;
		
		if (type.equals(ofType)) {
			result = true;
		}
		else {
			if (type instanceof FunctionType && ofType instanceof FunctionType) {
				FunctionType typeFunctionType   = (FunctionType) type;
				FunctionType ofTypeFunctionType = (FunctionType) ofType;
				if (typeFunctionType.getArity() == ofTypeFunctionType.getArity()) {
					result = isTypeSubtypeOf(typeFunctionType.getCodomain(), ofTypeFunctionType.getCodomain())
							 &&
							 IntStream.range(0, typeFunctionType.getArity())
							 // NOTE: we intentionally flip the values passed to isTypeSubtypeOf due
							 // to the function type arguments being contravariant, see:
							 // https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
								.allMatch(idx -> isTypeSubtypeOf(ofTypeFunctionType.getArgumentTypes().get(idx), typeFunctionType.getArgumentTypes().get(idx)));
				}
			}
			else if (type instanceof TupleType && ofType instanceof TupleType) {
				TupleType typeTupleType   = (TupleType) type;
				TupleType ofTypeTupleType = (TupleType) ofType;
				if (typeTupleType.getArity() == ofTypeTupleType.getArity()) {
					result = IntStream.range(0, typeTupleType.getArity())
								.allMatch(idx -> isTypeSubtypeOf(typeTupleType.getElementTypes().get(idx), ofTypeTupleType.getElementTypes().get(idx)));
				}
			}
			else if (type instanceof IntegerInterval) {
				IntegerInterval typeIntegerInterval = (IntegerInterval) type;
				if (ofType instanceof IntegerInterval) {
					IntegerInterval ofTypeIntegerInterval = (IntegerInterval) ofType;
					result = ofTypeIntegerInterval.isSuperset(typeIntegerInterval.getNonStrictLowerBound(), typeIntegerInterval.getNonStrictUpperBound());
				}
				else if (ofType instanceof RealInterval) {
					RealInterval ofTypeRealInterval = (RealInterval) ofType;
					result = ofTypeRealInterval.isSuperset(typeIntegerInterval.getNonStrictLowerBound(), typeIntegerInterval.getNonStrictUpperBound());
				}
				else if (ofType instanceof IntegerExpressoType || ofType instanceof RealExpressoType) {
					result = true;
				}
			}
			else if (type instanceof IntegerExpressoType) {
				if (ofType instanceof IntegerInterval) {
					IntegerInterval ofTypeIntegerInterval = (IntegerInterval) ofType;
					result = ofTypeIntegerInterval.noLowerBound() && ofTypeIntegerInterval.noUpperBound();
				}
				else if (ofType instanceof RealInterval) {
					RealInterval ofTypeRealInterval = (RealInterval) ofType;
					result = ofTypeRealInterval.noLowerBound() && ofTypeRealInterval.noUpperBound();
				}
				else if (ofType instanceof RealExpressoType) {
					result = true;
				}
			}
			else if (type instanceof RealInterval) {
				RealInterval typeRealInterval = (RealInterval) type;
				if (ofType instanceof RealInterval) {
					RealInterval ofTypeRealInterval = (RealInterval) ofType;
					result = ofTypeRealInterval.isSuperset(typeRealInterval.getLowerBound(), typeRealInterval.getUpperBound());
				}
				else if (ofType instanceof RealExpressoType) {
					result = true;
				}
			}
			else if (type instanceof RealExpressoType) {
				if (ofType instanceof RealInterval) {
					RealInterval ofTypeRealInterval = (RealInterval) ofType;
					result = ofTypeRealInterval.noLowerBound() && ofTypeRealInterval.noUpperBound();
				}
				else if (ofType instanceof RealExpressoType) {
					result = true;
				}
			}
		}

		return result;
	}
	
	public static boolean isSubtypeOf(Expression expression, Type ofType, Registry registry) {			
		boolean result = false;
		
		if (ofType.contains(expression)) {
			result = true;
		}
		else {
			result = isTypeSubtypeOf(registry.getTypeFromTypeExpression(getTypeExpressionOfExpression(expression, registry)), ofType);
		}
		
		return result;
	}

	public static Type getTypeOfExpression(Expression expression, Context context) {
		Expression typeExpression = getTypeExpressionOfExpression(expression, context);
		if (typeExpression == null) {
			throw new Error("Type of expression unknown: " + expression);
		}
		Type type = context.getTypeFromTypeExpression(typeExpression);
		return type;
	}

	/**
	 * Return a {@link DefaultPolynomial} equivalent to given expression,
	 * or the expression itself if that is not possible.
	 * @param expression
	 * @return
	 */
	public static Expression tryToNormalizeAsPolynomial(Expression expression) {
		Expression result;
		try {
			result = DefaultPolynomial.make(expression);
		}
		catch (IllegalArgumentException exception) {
			result = expression;
		}
		return result;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy