org.eclipse.viatra.query.runtime.localsearch.planner.PConstraintInfoInferrer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.viatra.query.runtime.localsearch.planner.PConstraintInfoInferrer.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2016, Grill Balzs, IncQuery Labs Ltd.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Grill Balzs - initial API and implementation
 *******************************************************************************/
package org.eclipse.viatra.query.runtime.localsearch.planner;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.viatra.query.runtime.base.api.BaseIndexOptions;
import org.eclipse.viatra.query.runtime.base.comprehension.EMFModelComprehension;
import org.eclipse.viatra.query.runtime.emf.types.EStructuralFeatureInstancesKey;
import org.eclipse.viatra.query.runtime.localsearch.planner.cost.IConstraintEvaluationContext;
import org.eclipse.viatra.query.runtime.matchers.backend.ResultProviderRequestor;
import org.eclipse.viatra.query.runtime.matchers.context.IInputKey;
import org.eclipse.viatra.query.runtime.matchers.context.IQueryBackendContext;
import org.eclipse.viatra.query.runtime.matchers.psystem.PConstraint;
import org.eclipse.viatra.query.runtime.matchers.psystem.PVariable;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.AggregatorConstraint;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExportedParameter;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.ExpressionEvaluation;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.Inequality;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.PatternMatchCounter;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicdeferred.TypeFilterConstraint;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.AbstractTransitiveClosure;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.ConstantValue;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
import org.eclipse.viatra.query.runtime.matchers.psystem.basicenumerables.TypeConstraint;
import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameter;
import org.eclipse.viatra.query.runtime.matchers.psystem.queries.PParameterDirection;
import org.eclipse.viatra.query.runtime.matchers.tuple.Tuple;

import com.google.common.collect.Sets;

/**
 * @author Grill Balzs
 * @noreference This class is not intended to be referenced by clients.
 */
class PConstraintInfoInferrer {

    private static final Predicate<PVariable> SINGLE_USE_VARIABLE = input -> input != null
            && input.getReferringConstraints().size() == 1;

    private final boolean useIndex;
    private final Function<IConstraintEvaluationContext, Double> costFunction;
    private final EMFModelComprehension modelComprehension;
    private final IQueryBackendContext context;
    private final ResultProviderRequestor resultRequestor;

    public PConstraintInfoInferrer(boolean useIndex, IQueryBackendContext backendContext,
            ResultProviderRequestor resultRequestor, Function<IConstraintEvaluationContext, Double> costFunction) {
        this.useIndex = useIndex;
        this.context = backendContext;
        this.resultRequestor = resultRequestor;
        this.costFunction = costFunction;
        this.modelComprehension = new EMFModelComprehension(new BaseIndexOptions());
    }

    /**
     * Create all possible application condition for all constraint
     * 
     * @param constraintSet the set of constraints
     * @param runtimeContext the model dependent runtime contest
     * @return a collection of the wrapper PConstraintInfo objects with all the allowed application conditions
     */
    public List<PConstraintInfo> createPConstraintInfos(Set<PConstraint> constraintSet) {
        List<PConstraintInfo> constraintInfos = new ArrayList<>();

        for (PConstraint pConstraint : constraintSet) {
            createPConstraintInfoDispatch(constraintInfos, pConstraint);
        }
        return constraintInfos;
    }

    private void createPConstraintInfoDispatch(List<PConstraintInfo> resultList, PConstraint pConstraint) {
        if (pConstraint instanceof ExportedParameter) {
            createConstraintInfoExportedParameter(resultList, (ExportedParameter) pConstraint);
        } else if (pConstraint instanceof TypeConstraint) {
            createConstraintInfoTypeConstraint(resultList, (TypeConstraint) pConstraint);
        } else if (pConstraint instanceof TypeFilterConstraint) {
            createConstraintInfoTypeFilterConstraint(resultList, (TypeFilterConstraint) pConstraint);
        } else if (pConstraint instanceof ConstantValue) {
            createConstraintInfoConstantValue(resultList, (ConstantValue) pConstraint);
        } else if (pConstraint instanceof Inequality) {
            createConstraintInfoInequality(resultList, (Inequality) pConstraint);
        } else if (pConstraint instanceof ExpressionEvaluation) {
            createConstraintInfoExpressionEvaluation(resultList, (ExpressionEvaluation) pConstraint);
        } else if (pConstraint instanceof AggregatorConstraint) {
            createConstraintInfoAggregatorConstraint(resultList, pConstraint,
                    ((AggregatorConstraint) pConstraint).getResultVariable());
        } else if (pConstraint instanceof PatternMatchCounter) {
            createConstraintInfoAggregatorConstraint(resultList, pConstraint,
                    ((PatternMatchCounter) pConstraint).getResultVariable());
        } else if (pConstraint instanceof PositivePatternCall) {
            createConstraintInfoPositivePatternCall(resultList, (PositivePatternCall) pConstraint);
        } else if (pConstraint instanceof AbstractTransitiveClosure) {
            createConstraintInfoBinaryTransitiveClosure(resultList, (AbstractTransitiveClosure) pConstraint);
        } else {
            createConstraintInfoGeneric(resultList, pConstraint);
        }
    }

    private void createConstraintInfoConstantValue(List<PConstraintInfo> resultList, ConstantValue pConstraint) {
        // A ConstantValue constraint has a single variable, which is allowed to be unbound 
        // (extending through ConstantValue is considered a cheap operation)
        Set<PVariable> affectedVariables = pConstraint.getAffectedVariables();
        Set<Set<PVariable>> bindings = Sets.powerSet(affectedVariables);
        doCreateConstraintInfos(resultList, pConstraint, affectedVariables, bindings);
    }

    private void createConstraintInfoPositivePatternCall(List<PConstraintInfo> resultList,
            PositivePatternCall pCall) {
        // A pattern call can have any of its variables unbound
        Set<PVariable> affectedVariables = pCall.getAffectedVariables();
        // IN parameters cannot be unbound and
        // OUT parameters cannot be bound
        Tuple variables = pCall.getVariablesTuple();
        final Set<PVariable> inVariables = new HashSet<>();
        Set<PVariable> inoutVariables = new HashSet<>();
        List<PParameter> parameters = pCall.getReferredQuery().getParameters();
        for (int i = 0; i < parameters.size(); i++) {
            switch (parameters.get(i).getDirection()) {
            case IN:
                inVariables.add((PVariable) variables.get(i));
                break;
            case INOUT:
                inoutVariables.add((PVariable) variables.get(i));
                break;
            case OUT:
            default:
                break;

            }
        }
        Iterable<Set<PVariable>> bindings = Sets.powerSet(inoutVariables).stream()
                .map(input -> Stream.concat(input.stream(), inVariables.stream()).collect(Collectors.toSet()))
                .collect(Collectors.toSet());

        doCreateConstraintInfos(resultList, pCall, affectedVariables, bindings);
    }

    private void createConstraintInfoBinaryTransitiveClosure(List<PConstraintInfo> resultList,
            AbstractTransitiveClosure closure) {
        // A pattern call can have any of its variables unbound

        List<PParameter> parameters = closure.getReferredQuery().getParameters();
        Tuple variables = closure.getVariablesTuple();

        Set<Set<PVariable>> bindings = new HashSet<>();
        PVariable firstVariable = (PVariable) variables.get(0);
        PVariable secondVariable = (PVariable) variables.get(1);
        // Check is always supported
        bindings.add(new HashSet<>(Arrays.asList(firstVariable, secondVariable)));
        // If first parameter is not bound mandatorily, it can be left out
        if (parameters.get(0).getDirection() != PParameterDirection.IN) {
            bindings.add(Collections.singleton(secondVariable));
        }
        // If second parameter is not bound mandatorily, it can be left out
        if (parameters.get(1).getDirection() != PParameterDirection.IN) {
            bindings.add(Collections.singleton(firstVariable));
        }

        doCreateConstraintInfos(resultList, closure, closure.getAffectedVariables(), bindings);
    }

    private void createConstraintInfoExportedParameter(List<PConstraintInfo> resultList,
            ExportedParameter parameter) {
        // In case of an exported parameter constraint, the parameter must be bound in order to execute
        Set<PVariable> affectedVariables = parameter.getAffectedVariables();
        doCreateConstraintInfos(resultList, parameter, affectedVariables, Collections.singleton(affectedVariables));
    }

    private void createConstraintInfoExpressionEvaluation(List<PConstraintInfo> resultList,
            ExpressionEvaluation expressionEvaluation) {
        // An expression evaluation can only have its output variable unbound. All other variables shall be bound
        PVariable output = expressionEvaluation.getOutputVariable();
        Set<Set<PVariable>> bindings = new HashSet<>();
        Set<PVariable> affectedVariables = expressionEvaluation.getAffectedVariables();
        // All variables bound -> check
        bindings.add(affectedVariables);
        // Output variable is not bound -> extend
        bindings.add(
                affectedVariables.stream().filter(var -> !Objects.equals(var, output)).collect(Collectors.toSet()));
        doCreateConstraintInfos(resultList, expressionEvaluation, affectedVariables, bindings);
    }

    private void createConstraintInfoTypeFilterConstraint(List<PConstraintInfo> resultList,
            TypeFilterConstraint filter) {
        // In case of type filter, all affected variables must be bound in order to execute
        Set<PVariable> affectedVariables = filter.getAffectedVariables();
        doCreateConstraintInfos(resultList, filter, affectedVariables, Collections.singleton(affectedVariables));
    }

    private void createConstraintInfoInequality(List<PConstraintInfo> resultList, Inequality inequality) {
        // In case of inequality, all affected variables must be bound in order to execute
        Set<PVariable> affectedVariables = inequality.getAffectedVariables();
        doCreateConstraintInfos(resultList, inequality, affectedVariables,
                Collections.singleton(affectedVariables));
    }

    private void createConstraintInfoAggregatorConstraint(List<PConstraintInfo> resultList, PConstraint pConstraint,
            PVariable resultVariable) {
        Set<PVariable> affectedVariables = pConstraint.getAffectedVariables();

        // The only variables which can be unbound are single-use
        Set<PVariable> canBeUnboundVariables = Stream
                .concat(Stream.of(resultVariable), affectedVariables.stream().filter(SINGLE_USE_VARIABLE))
                .collect(Collectors.toSet());

        Set<Set<PVariable>> bindings = calculatePossibleBindings(canBeUnboundVariables, affectedVariables);

        doCreateConstraintInfos(resultList, pConstraint, affectedVariables, bindings);
    }

    /**
     * 
     * @param canBeUnboundVariables Variables which are allowed to be unbound
     * @param affectedVariables All affected variables
     * @return The set of possible bound variable sets
     */
    private Set<Set<PVariable>> calculatePossibleBindings(Set<PVariable> canBeUnboundVariables,
            Set<PVariable> affectedVariables) {
        final Set<PVariable> mustBindVariables = affectedVariables.stream()
                .filter(input -> !canBeUnboundVariables.contains(input)).collect(Collectors.toSet());
        return Sets.powerSet(canBeUnboundVariables).stream().map(input -> {
            //some variables have to be bound before executing this constraint
            Set<PVariable> result = new HashSet<>(input);
            result.addAll(mustBindVariables);
            return result;
        }).collect(Collectors.toSet());
    }

    private void createConstraintInfoGeneric(List<PConstraintInfo> resultList, PConstraint pConstraint) {
        Set<PVariable> affectedVariables = pConstraint.getAffectedVariables();

        // The only variables which can be unbound are single use variables
        Set<PVariable> canBeUnboundVariables = affectedVariables.stream().filter(SINGLE_USE_VARIABLE)
                .collect(Collectors.toSet());

        Set<Set<PVariable>> bindings = calculatePossibleBindings(canBeUnboundVariables, affectedVariables);

        doCreateConstraintInfos(resultList, pConstraint, affectedVariables, bindings);
    }

    private boolean canPerformInverseNavigation(EStructuralFeature feature) {
        return ( // Feature has opposite (this only possible for references)
        hasEOpposite(feature) || (feature instanceof EReference) && ((EReference) feature).isContainment() || ( // Indexing is enabled, and the feature can be indexed (not a non-well-behaving derived feature).
        useIndex && modelComprehension.representable(feature)));
    }

    private void createConstraintInfoTypeConstraint(List<PConstraintInfo> resultList,
            TypeConstraint typeConstraint) {
        Set<PVariable> affectedVariables = typeConstraint.getAffectedVariables();
        Set<Set<PVariable>> bindings = null;

        IInputKey inputKey = typeConstraint.getSupplierKey();
        if (inputKey.isEnumerable()) {
            bindings = Sets.powerSet(affectedVariables);
        } else {
            // For not enumerable types, this constraint can only be a check
            bindings = Collections.singleton(affectedVariables);
        }

        if (inputKey instanceof EStructuralFeatureInstancesKey) {
            final EStructuralFeature feature = ((EStructuralFeatureInstancesKey) inputKey).getEmfKey();
            if (!canPerformInverseNavigation(feature)) {
                // When inverse navigation is not allowed or not possible, filter out operation masks, where
                // the first variable would be free AND the feature is an EReference and has no EOpposite
                bindings = excludeUnnavigableOperationMasks(typeConstraint, bindings);
            }
        }
        doCreateConstraintInfos(resultList, typeConstraint, affectedVariables, bindings);
    }

    private void doCreateConstraintInfos(List<PConstraintInfo> constraintInfos, PConstraint pConstraint,
            Set<PVariable> affectedVariables, Iterable<Set<PVariable>> bindings) {
        Set<PConstraintInfo> sameWithDifferentBindings = new HashSet<>();
        for (Set<PVariable> boundVariables : bindings) {

            PConstraintInfo info = new PConstraintInfo(
                    pConstraint, boundVariables, affectedVariables.stream()
                            .filter(input -> !boundVariables.contains(input)).collect(Collectors.toSet()),
                    sameWithDifferentBindings, context, resultRequestor, costFunction);
            constraintInfos.add(info);
            sameWithDifferentBindings.add(info);
        }
    }

    private Set<Set<PVariable>> excludeUnnavigableOperationMasks(TypeConstraint typeConstraint,
            Set<Set<PVariable>> bindings) {
        PVariable firstVariable = typeConstraint.getVariableInTuple(0);
        return bindings.stream().filter(
                boundVariablesSet -> (boundVariablesSet.isEmpty() || boundVariablesSet.contains(firstVariable)))
                .collect(Collectors.toSet());
    }

    private boolean hasEOpposite(EStructuralFeature feature) {
        if (feature instanceof EReference) {
            EReference eOpposite = ((EReference) feature).getEOpposite();
            if (eOpposite != null) {
                return true;
            }
        }
        return false;
    }

}