/* * This file is part of ELKI: * Environment for Developing KDD-Applications Supported by Index-Structures * * Copyright (C) 2017 * ELKI Development Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.lmu.ifi.dbs.elki.database.relation; import de.lmu.ifi.dbs.elki.database.QueryUtil; import de.lmu.ifi.dbs.elki.database.query.DatabaseQuery; import de.lmu.ifi.dbs.elki.database.query.distance.DistanceQuery; import de.lmu.ifi.dbs.elki.database.query.knn.KNNQuery; import de.lmu.ifi.dbs.elki.database.query.range.RangeQuery; import de.lmu.ifi.dbs.elki.database.query.rknn.LinearScanRKNNQuery; import de.lmu.ifi.dbs.elki.database.query.rknn.RKNNQuery; import de.lmu.ifi.dbs.elki.database.query.similarity.SimilarityQuery; import de.lmu.ifi.dbs.elki.distance.distancefunction.DBIDDistanceFunction; import de.lmu.ifi.dbs.elki.distance.distancefunction.DistanceFunction; import de.lmu.ifi.dbs.elki.distance.similarityfunction.DBIDSimilarityFunction; import de.lmu.ifi.dbs.elki.distance.similarityfunction.SimilarityFunction; import de.lmu.ifi.dbs.elki.index.DistanceIndex; import de.lmu.ifi.dbs.elki.index.KNNIndex; import de.lmu.ifi.dbs.elki.index.RKNNIndex; import de.lmu.ifi.dbs.elki.index.RangeIndex; import de.lmu.ifi.dbs.elki.index.SimilarityIndex; import de.lmu.ifi.dbs.elki.index.SimilarityRangeIndex; import de.lmu.ifi.dbs.elki.logging.Logging; import de.lmu.ifi.dbs.elki.result.AbstractHierarchicalResult; import de.lmu.ifi.dbs.elki.utilities.datastructures.iterator.It; import de.lmu.ifi.dbs.elki.utilities.exceptions.AbortException; /** * Abstract base class for relations. * * @author Erich Schubert * @since 0.7.0 * * @param <O> Data type */ public abstract class AbstractRelation<O> extends AbstractHierarchicalResult implements Relation<O> { /** * Constructor. */ public AbstractRelation() { super(); } @Override public DistanceQuery<O> getDistanceQuery(DistanceFunction<? super O> distanceFunction, Object... hints) { if(distanceFunction == null) { throw new AbortException("Distance query requested for 'null' distance!"); } for(It<DistanceIndex<O>> it = getHierarchy().iterChildrenReverse(this).filter(DistanceIndex.class); it.valid(); it.advance()) { DistanceQuery<O> q = it.get().getDistanceQuery(distanceFunction, hints); if(getLogger().isDebuggingFinest()) { getLogger().debugFinest((q != null ? "Using" : "Not using") + " index for distance query: " + it.get()); } if(q != null) { return q; } } for(Object o : hints) { if(o == DatabaseQuery.HINT_OPTIMIZED_ONLY && !(distanceFunction instanceof DBIDDistanceFunction)) { return null; // Linear scan is not desirable. } } return distanceFunction.instantiate(this); } @Override public SimilarityQuery<O> getSimilarityQuery(SimilarityFunction<? super O> similarityFunction, Object... hints) { if(similarityFunction == null) { throw new AbortException("Similarity query requested for 'null' similarity!"); } for(It<SimilarityIndex<O>> it = getHierarchy().iterChildrenReverse(this).filter(SimilarityIndex.class); it.valid(); it.advance()) { SimilarityQuery<O> q = it.get().getSimilarityQuery(similarityFunction, hints); if(getLogger().isDebuggingFinest()) { getLogger().debugFinest((q != null ? "Using" : "Not using") + " index for similarity query: " + it.get()); } if(q != null) { return q; } } for(Object o : hints) { if(o == DatabaseQuery.HINT_OPTIMIZED_ONLY && !(similarityFunction instanceof DBIDSimilarityFunction)) { return null; // Linear scan is not desirable. } } return similarityFunction.instantiate(this); } @Override public KNNQuery<O> getKNNQuery(DistanceQuery<O> distanceQuery, Object... hints) { if(distanceQuery == null) { throw new AbortException("kNN query requested for 'null' distance!"); } for(It<KNNIndex<O>> it = getHierarchy().iterChildrenReverse(this).filter(KNNIndex.class); it.valid(); it.advance()) { KNNQuery<O> q = it.get().getKNNQuery(distanceQuery, hints); if(getLogger().isDebuggingFinest()) { getLogger().debugFinest((q != null ? "Using" : "Not using") + " index for kNN query: " + it.get()); } if(q != null) { return q; } } // Default for(Object hint : hints) { if(hint == DatabaseQuery.HINT_OPTIMIZED_ONLY) { return null; } } if(getLogger().isDebuggingFinest()) { StringBuilder buf = new StringBuilder(); buf.append("Fallback to linear scan - no index was able to accelerate this query.\n"); buf.append("Distance query: ").append(distanceQuery).append('\n'); if(hints.length > 0) { buf.append("Hints:"); for(Object o : hints) { buf.append(' ').append(o); } } getLogger().debugFinest(buf.toString()); } return QueryUtil.getLinearScanKNNQuery(distanceQuery); } @Override public RangeQuery<O> getRangeQuery(DistanceQuery<O> distanceQuery, Object... hints) { if(distanceQuery == null) { throw new AbortException("Range query requested for 'null' distance!"); } for(It<RangeIndex<O>> it = getHierarchy().iterChildrenReverse(this).filter(RangeIndex.class); it.valid(); it.advance()) { RangeQuery<O> q = it.get().getRangeQuery(distanceQuery, hints); if(getLogger().isDebuggingFinest()) { getLogger().debugFinest((q != null ? "Using" : "Not using") + " index for range query: " + it.get()); } if(q != null) { return q; } } // Default for(Object hint : hints) { if(hint == DatabaseQuery.HINT_OPTIMIZED_ONLY) { return null; } } if(getLogger().isDebuggingFinest()) { StringBuilder buf = new StringBuilder(); buf.append("Fallback to linear scan - no index was able to accelerate this query.\n"); buf.append("Distance query: ").append(distanceQuery).append('\n'); if(hints.length > 0) { buf.append("Hints:"); for(Object o : hints) { buf.append(' ').append(o); } } getLogger().debugFinest(buf.toString()); } return QueryUtil.getLinearScanRangeQuery(distanceQuery); } @Override public RangeQuery<O> getSimilarityRangeQuery(SimilarityQuery<O> simQuery, Object... hints) { if(simQuery == null) { throw new AbortException("Range query requested for 'null' distance!"); } for(It<SimilarityRangeIndex<O>> it = getHierarchy().iterChildrenReverse(this).filter(SimilarityRangeIndex.class); it.valid(); it.advance()) { RangeQuery<O> q = it.get().getSimilarityRangeQuery(simQuery, hints); if(getLogger().isDebuggingFinest()) { getLogger().debugFinest((q != null ? "Using" : "Not using") + " index for range query: " + it.get()); } if(q != null) { return q; } } // Default for(Object hint : hints) { if(hint == DatabaseQuery.HINT_OPTIMIZED_ONLY) { return null; } } if(getLogger().isDebuggingFinest()) { StringBuilder buf = new StringBuilder(); buf.append("Fallback to linear scan - no index was able to accelerate this query.\n"); buf.append("Distance query: ").append(simQuery).append('\n'); if(hints.length > 0) { buf.append("Hints:"); for(Object o : hints) { buf.append(' ').append(o); } } getLogger().debugFinest(buf.toString()); } return QueryUtil.getLinearScanSimilarityRangeQuery(simQuery); } @Override public RKNNQuery<O> getRKNNQuery(DistanceQuery<O> distanceQuery, Object... hints) { if(distanceQuery == null) { throw new AbortException("RKNN query requested for 'null' distance!"); } for(It<RKNNIndex<O>> it = getHierarchy().iterChildrenReverse(this).filter(RKNNIndex.class); it.valid(); it.advance()) { RKNNQuery<O> q = it.get().getRKNNQuery(distanceQuery, hints); if(getLogger().isDebuggingFinest()) { getLogger().debugFinest((q != null ? "Using" : "Not using") + " index for RkNN query: " + it.get()); } if(q != null) { return q; } } Integer maxk = null; // Default for(Object hint : hints) { if(hint == DatabaseQuery.HINT_OPTIMIZED_ONLY) { return null; } if(hint instanceof Integer) { maxk = (Integer) hint; } } if(getLogger().isDebuggingFinest()) { StringBuilder buf = new StringBuilder(); buf.append("Fallback to linear scan - no index was able to accelerate this query.\n"); buf.append("Distance query: ").append(distanceQuery).append('\n'); if(hints.length > 0) { buf.append("Hints:"); for(Object o : hints) { buf.append(' ').append(o); } } getLogger().debugFinest(buf.toString()); } KNNQuery<O> knnQuery = getKNNQuery(distanceQuery, DatabaseQuery.HINT_BULK, maxk); return new LinearScanRKNNQuery<>(distanceQuery, knnQuery, maxk); } /** * Get the class logger. * * @return Logger */ abstract protected Logging getLogger(); }