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

com.yahoo.search.predicate.optimization.FeatureConjunctionTransformer Maven / Gradle / Ivy

There is a newer version: 8.458.13
Show newest version
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.predicate.optimization;

import com.yahoo.document.predicate.Conjunction;
import com.yahoo.document.predicate.Disjunction;
import com.yahoo.document.predicate.FeatureConjunction;
import com.yahoo.document.predicate.FeatureRange;
import com.yahoo.document.predicate.FeatureSet;
import com.yahoo.document.predicate.Negation;
import com.yahoo.document.predicate.Predicate;
import com.yahoo.search.predicate.index.conjunction.ConjunctionIndex;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * Transforms Conjunctions with only (negated) {@link FeatureSet} instances to {@link FeatureConjunction}.
 * The {@link FeatureConjunction}s are indexed by the {@link ConjunctionIndex}.
 *
 * @author bjorncs
 */
public class FeatureConjunctionTransformer implements PredicateProcessor {

    // Only Conjunctions having less or equal number of FeatureSet operands than threshold are converted to FeatureConjunction.
    private static final int CONVERSION_THRESHOLD = Integer.MAX_VALUE;
    private final boolean useConjunctionAlgorithm;

    public FeatureConjunctionTransformer(boolean useConjunctionAlgorithm) {
        this.useConjunctionAlgorithm = useConjunctionAlgorithm;
    }

    @Override
    public Predicate process(Predicate predicate, PredicateOptions options) {
        if (useConjunctionAlgorithm) {
            return transform(predicate);
        }
        return predicate;
    }

    private static Predicate transform(Predicate predicate) {
        if (predicate instanceof Conjunction) {
            Conjunction conjunction = (Conjunction) predicate;
            conjunction.getOperands().replaceAll(FeatureConjunctionTransformer::transform);
            long nValidOperands = numberOfValidFeatureSetOperands(conjunction);
            if (nValidOperands > 1 && nValidOperands <= CONVERSION_THRESHOLD) {
                return convertConjunction(conjunction, nValidOperands);
            }
        } else if (predicate instanceof Disjunction) {
            ((Disjunction)predicate).getOperands().replaceAll(FeatureConjunctionTransformer::transform);
        } else if (predicate instanceof Negation) {
            Negation negation = (Negation) predicate;
            negation.setOperand(transform(negation.getOperand()));
        }
        return predicate;
    }

    /**
     * Conversion rules:
     *  1) A {@link FeatureConjunction} may only consist of FeatureSets having unique keys.
     *     If multiple {@link FeatureSet} share the same key, they have to be placed into separate FeatureConjunctions.
     *  2) A FeatureConjunction must have at least 2 operands.
     *  3) Any operand that is not a FeatureSet, negated or not,
     *     (e.g {@link FeatureRange}) cannot be placed into a FeatureConjunction.
     *  4) All FeatureSets may only have a single value.
     *
     *  See the tests in FeatureConjunctionTransformerTest for conversion examples.
     */
    private static Predicate convertConjunction(Conjunction conjunction, long nValidOperands) {
        List operands = conjunction.getOperands();
        // All operands are instance of FeatureSet are valid and may therefor be placed into a single FeatureConjunction.
        if (nValidOperands == operands.size()) {
            return new FeatureConjunction(operands);
        }

        List invalidFeatureConjunctionOperands = new ArrayList<>();
        List> featureConjunctionOperandsList = new ArrayList<>();
        featureConjunctionOperandsList.add(new TreeMap<>());
        for (Predicate operand : operands) {
            if (FeatureConjunction.isValidFeatureConjunctionOperand(operand)) {
                addFeatureConjunctionOperand(featureConjunctionOperandsList, operand);
            } else {
                invalidFeatureConjunctionOperands.add(operand);
            }
        }

        // Create a Conjunction root.
        Conjunction newConjunction = new Conjunction();
        newConjunction.addOperands(invalidFeatureConjunctionOperands);
        // For all operand partitions: create FeatureConjunction if partition has more than a single predicate.
        for (Map featureConjunctionOperands : featureConjunctionOperandsList) {
            Collection values = featureConjunctionOperands.values();
            if (featureConjunctionOperands.size() == 1) {
                // Add single operand directly to root conjunction.
                newConjunction.addOperands(values);
            } else {
                newConjunction.addOperand(new FeatureConjunction(new ArrayList<>(values)));
            }
        }
        return newConjunction;
    }

    private static void addFeatureConjunctionOperand(List> featureConjunctionOperandsList, Predicate operand) {
        String key = getFeatureSetKey(operand);
        for (Map featureConjunctionOperands : featureConjunctionOperandsList) {
            if (!featureConjunctionOperands.containsKey(key)) {
                featureConjunctionOperands.put(key, operand);
                return;
            }
        }
        Map conjunctionOperands = new TreeMap<>();
        conjunctionOperands.put(key, operand);
        featureConjunctionOperandsList.add(conjunctionOperands);
    }

    private static long numberOfValidFeatureSetOperands(Conjunction conjunction) {
        return conjunction.getOperands().stream()
                .filter(FeatureConjunction::isValidFeatureConjunctionOperand)
                .map(FeatureConjunctionTransformer::getFeatureSetKey)
                .distinct()
                .count();
    }

    private static String getFeatureSetKey(Predicate predicate) {
        if (predicate instanceof FeatureSet) {
            return ((FeatureSet) predicate).getKey();
        } else {
            Negation negation = (Negation) predicate;
            return ((FeatureSet) negation.getOperand()).getKey();
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy