aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java')
-rw-r--r--subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java532
1 files changed, 532 insertions, 0 deletions
diff --git a/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java
new file mode 100644
index 00000000..1ae24d2d
--- /dev/null
+++ b/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/matcher/integration/AbstractLocalSearchResultProvider.java
@@ -0,0 +1,532 @@
1/*******************************************************************************
2 * Copyright (c) 2010-2017, Zoltan Ujhelyi, IncQuery Labs Ltd.
3 * This program and the accompanying materials are made available under the
4 * terms of the Eclipse Public License v. 2.0 which is available at
5 * http://www.eclipse.org/legal/epl-v20.html.
6 *
7 * SPDX-License-Identifier: EPL-2.0
8 *******************************************************************************/
9package tools.refinery.viatra.runtime.localsearch.matcher.integration;
10
11import java.lang.reflect.InvocationTargetException;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.HashMap;
16import java.util.HashSet;
17import java.util.LinkedHashSet;
18import java.util.LinkedList;
19import java.util.List;
20import java.util.Map;
21import java.util.Objects;
22import java.util.Optional;
23import java.util.Queue;
24import java.util.Set;
25import java.util.concurrent.Callable;
26import java.util.stream.Collectors;
27import java.util.stream.IntStream;
28import java.util.stream.Stream;
29
30import tools.refinery.viatra.runtime.localsearch.exceptions.LocalSearchException;
31import tools.refinery.viatra.runtime.localsearch.matcher.CallWithAdornment;
32import tools.refinery.viatra.runtime.localsearch.matcher.ISearchContext;
33import tools.refinery.viatra.runtime.localsearch.matcher.LocalSearchMatcher;
34import tools.refinery.viatra.runtime.localsearch.matcher.MatcherReference;
35import tools.refinery.viatra.runtime.localsearch.plan.IPlanDescriptor;
36import tools.refinery.viatra.runtime.localsearch.plan.IPlanProvider;
37import tools.refinery.viatra.runtime.localsearch.plan.SearchPlan;
38import tools.refinery.viatra.runtime.localsearch.plan.SearchPlanForBody;
39import tools.refinery.viatra.runtime.localsearch.planner.compiler.IOperationCompiler;
40import tools.refinery.viatra.runtime.matchers.ViatraQueryRuntimeException;
41import tools.refinery.viatra.runtime.matchers.backend.IMatcherCapability;
42import tools.refinery.viatra.runtime.matchers.backend.IQueryBackend;
43import tools.refinery.viatra.runtime.matchers.backend.IQueryResultProvider;
44import tools.refinery.viatra.runtime.matchers.backend.IUpdateable;
45import tools.refinery.viatra.runtime.matchers.backend.QueryEvaluationHint;
46import tools.refinery.viatra.runtime.matchers.backend.QueryHintOption;
47import tools.refinery.viatra.runtime.matchers.backend.ResultProviderRequestor;
48import tools.refinery.viatra.runtime.matchers.context.IInputKey;
49import tools.refinery.viatra.runtime.matchers.context.IQueryBackendContext;
50import tools.refinery.viatra.runtime.matchers.context.IQueryRuntimeContext;
51import tools.refinery.viatra.runtime.matchers.context.IndexingService;
52import tools.refinery.viatra.runtime.matchers.planning.QueryProcessingException;
53import tools.refinery.viatra.runtime.matchers.planning.helpers.FunctionalDependencyHelper;
54import tools.refinery.viatra.runtime.matchers.psystem.IQueryReference;
55import tools.refinery.viatra.runtime.matchers.psystem.PBody;
56import tools.refinery.viatra.runtime.matchers.psystem.basicenumerables.PositivePatternCall;
57import tools.refinery.viatra.runtime.matchers.psystem.queries.PParameter;
58import tools.refinery.viatra.runtime.matchers.psystem.queries.PQueries;
59import tools.refinery.viatra.runtime.matchers.psystem.queries.PQuery;
60import tools.refinery.viatra.runtime.matchers.psystem.rewriters.IFlattenCallPredicate;
61import tools.refinery.viatra.runtime.matchers.tuple.ITuple;
62import tools.refinery.viatra.runtime.matchers.tuple.Tuple;
63import tools.refinery.viatra.runtime.matchers.tuple.TupleMask;
64import tools.refinery.viatra.runtime.matchers.util.Accuracy;
65
66/**
67 * @author Zoltan Ujhelyi
68 * @since 1.7
69 *
70 */
71public abstract class AbstractLocalSearchResultProvider implements IQueryResultProvider {
72
73 protected final LocalSearchBackend backend;
74 protected final IQueryBackendContext backendContext;
75 protected final IQueryRuntimeContext runtimeContext;
76 protected final PQuery query;
77 protected final QueryEvaluationHint userHints;
78 protected final Map<PQuery, LocalSearchHints> hintCache = new HashMap<>();
79 protected final IPlanProvider planProvider;
80 private static final String PLAN_CACHE_KEY = AbstractLocalSearchResultProvider.class.getName() + "#planCache";
81 private final Map<MatcherReference, IPlanDescriptor> planCache;
82 protected final ISearchContext searchContext;
83 /**
84 * @since 2.1
85 */
86 protected ResultProviderRequestor resultProviderRequestor;
87
88 /**
89 * @since 1.5
90 */
91 @SuppressWarnings({ "unchecked"})
92 public AbstractLocalSearchResultProvider(LocalSearchBackend backend, IQueryBackendContext context, PQuery query,
93 IPlanProvider planProvider, QueryEvaluationHint userHints) {
94 this.backend = backend;
95 this.backendContext = context;
96 this.query = query;
97
98 this.planProvider = planProvider;
99 this.userHints = userHints;
100 this.runtimeContext = context.getRuntimeContext();
101 this.resultProviderRequestor = backend.getResultProviderRequestor(query, userHints);
102 this.searchContext = new ISearchContext.SearchContext(backendContext, backend.getCache(), resultProviderRequestor);
103 this.planCache = backend.getCache().getValue(PLAN_CACHE_KEY, Map.class, HashMap::new);
104 }
105
106 protected abstract IOperationCompiler getOperationCompiler(IQueryBackendContext backendContext, LocalSearchHints configuration);
107
108 private IQueryRuntimeContext getRuntimeContext() {
109 return backend.getRuntimeContext();
110 }
111
112 private LocalSearchMatcher createMatcher(IPlanDescriptor plan, final ISearchContext searchContext) {
113 List<SearchPlan> executors = plan.getPlan().stream()
114 .map(input -> new SearchPlan(input.getBody(), input.getCompiledOperations(), input.calculateParameterMask(),
115 input.getVariableKeys()))
116 .collect(Collectors.toList());
117 return new LocalSearchMatcher(searchContext, plan, executors);
118 }
119
120 private IPlanDescriptor getOrCreatePlan(MatcherReference key, IQueryBackendContext backendContext, IOperationCompiler compiler, LocalSearchHints configuration, IPlanProvider planProvider) {
121 if (planCache.containsKey(key)){
122 return planCache.get(key);
123 } else {
124 IPlanDescriptor plan = planProvider.getPlan(backendContext, compiler,
125 resultProviderRequestor, configuration, key);
126 planCache.put(key, plan);
127 return plan;
128 }
129 }
130
131 private IPlanDescriptor getOrCreatePlan(MatcherReference key, IPlanProvider planProvider) {
132 if (planCache.containsKey(key)){
133 return planCache.get(key);
134 } else {
135 LocalSearchHints configuration = overrideDefaultHints(key.getQuery());
136 IOperationCompiler compiler = getOperationCompiler(backendContext, configuration);
137 IPlanDescriptor plan = planProvider.getPlan(backendContext, compiler,
138 resultProviderRequestor, configuration, key);
139 planCache.put(key, plan);
140 return plan;
141 }
142 }
143
144 private LocalSearchHints overrideDefaultHints(PQuery pQuery) {
145 if (hintCache.containsKey(pQuery)) {
146 return hintCache.get(pQuery);
147 } else {
148 LocalSearchHints hint = LocalSearchHints.getDefaultOverriddenBy(
149 computeOverridingHints(pQuery));
150 hintCache.put(pQuery, hint);
151 return hint;
152 }
153 }
154
155 /**
156 * Combine with {@link QueryHintOption#getValueOrDefault(QueryEvaluationHint)} to access
157 * hint settings not covered by {@link LocalSearchHints}
158 */
159 private QueryEvaluationHint computeOverridingHints(PQuery pQuery) {
160 return backendContext.getHintProvider().getQueryEvaluationHint(pQuery).overrideBy(userHints);
161 }
162
163 /**
164 * Prepare this result provider. This phase is separated from the constructor to allow the backend to cache its instance before
165 * requesting preparation for its dependencies.
166 * @since 1.5
167 */
168 public void prepare() {
169 try {
170 runtimeContext.coalesceTraversals(() -> {
171 LocalSearchHints configuration = overrideDefaultHints(query);
172 if (configuration.isUseBase()) {
173 indexInitializationBeforePlanning();
174 }
175 prepareDirectDependencies();
176 runtimeContext.executeAfterTraversal(AbstractLocalSearchResultProvider.this::preparePlansForExpectedAdornments);
177 return null;
178 });
179 } catch (InvocationTargetException e) {
180 throw new QueryProcessingException("Error while building required indexes: {1}", new String[]{e.getTargetException().getMessage()}, "Error while building required indexes.", query, e);
181 }
182 }
183
184 protected void preparePlansForExpectedAdornments() {
185 // Plan for possible adornments
186 for (Set<PParameter> adornment : overrideDefaultHints(query).getAdornmentProvider().getAdornments(query)) {
187 MatcherReference reference = new MatcherReference(query, adornment, userHints);
188 LocalSearchHints configuration = overrideDefaultHints(query);
189 IOperationCompiler compiler = getOperationCompiler(backendContext, configuration);
190 IPlanDescriptor plan = getOrCreatePlan(reference, backendContext, compiler, configuration, planProvider);
191 // Index keys
192 try {
193 if (configuration.isUseBase()) {
194 indexKeys(plan.getIteratedKeys());
195 }
196 } catch (InvocationTargetException e) {
197 throw new QueryProcessingException(e.getMessage(), null, e.getMessage(), query, e);
198 }
199 //Prepare dependencies
200 for(SearchPlanForBody body: plan.getPlan()){
201 for(CallWithAdornment dependency : body.getDependencies()){
202 searchContext.getMatcher(dependency);
203 }
204 }
205 }
206 }
207
208 protected void prepareDirectDependencies() {
209 // Do not prepare for any adornment at this point
210 IAdornmentProvider adornmentProvider = input -> Collections.emptySet();
211 QueryEvaluationHint adornmentHint = IAdornmentProvider.toHint(adornmentProvider);
212
213 for(IQueryReference call : getDirectDependencies()){
214 resultProviderRequestor.requestResultProvider(call, adornmentHint);
215 }
216 }
217
218 /**
219 * This method is called before planning start to allow indexing. It is important to note that this method is called
220 * inside a coalesceTraversals block, meaning (1) it is safe to add multiple registration requests as necessary, but
221 * (2) no value or statistics is available from the index.
222 *
223 * @throws ViatraQueryRuntimeException
224 */
225 protected void indexInitializationBeforePlanning() {
226 // By default, no indexing is necessary
227 }
228
229 /**
230 * Collects and indexes all types _directly_ referred by the PQuery {@link #query}. Types indirect
231 * @param requiredIndexingServices
232 */
233 protected void indexReferredTypesOfQuery(PQuery query, IndexingService requiredIndexingServices) {
234 PQueries.directlyRequiredTypesOfQuery(query, true /*only enumerables are considered for indexing */).forEach(
235 inputKey -> runtimeContext.ensureIndexed(inputKey, requiredIndexingServices)
236 );
237 }
238
239 private Set<IQueryReference> getDirectDependencies() {
240 IFlattenCallPredicate flattenPredicate = overrideDefaultHints(query).getFlattenCallPredicate();
241 Queue<PQuery> queue = new LinkedList<>();
242 Set<PQuery> visited = new HashSet<>();
243 Set<IQueryReference> result = new HashSet<>();
244 queue.add(query);
245
246 while(!queue.isEmpty()){
247 PQuery next = queue.poll();
248 visited.add(next);
249 for(PBody body : next.getDisjunctBodies().getBodies()){
250 for (IQueryReference call : body.getConstraintsOfType(IQueryReference.class)) {
251 if (call instanceof PositivePatternCall &&
252 flattenPredicate.shouldFlatten((PositivePatternCall) call))
253 {
254 PQuery dep = ((PositivePatternCall) call).getReferredQuery();
255 if (!visited.contains(dep)){
256 queue.add(dep);
257 }
258 } else {
259 result.add(call);
260 }
261 }
262 }
263 }
264 return result;
265 }
266
267 private LocalSearchMatcher initializeMatcher(Object[] parameters) {
268 return newLocalSearchMatcher(parameters);
269 }
270
271 private LocalSearchMatcher initializeMatcher(TupleMask parameterSeedMask) {
272 return newLocalSearchMatcher(parameterSeedMask.transformUnique(query.getParameters()));
273
274 }
275
276
277 /**
278 * @throws ViatraQueryRuntimeException
279 */
280 public LocalSearchMatcher newLocalSearchMatcher(ITuple parameters) {
281 final Set<PParameter> adornment = new HashSet<>();
282 for (int i = 0; i < parameters.getSize(); i++) {
283 if (parameters.get(i) != null) {
284 adornment.add(query.getParameters().get(i));
285 }
286 }
287
288 return newLocalSearchMatcher(adornment);
289 }
290
291 /**
292 * @throws ViatraQueryRuntimeException
293 */
294 public LocalSearchMatcher newLocalSearchMatcher(Object[] parameters) {
295 final Set<PParameter> adornment = new HashSet<>();
296 for (int i = 0; i < parameters.length; i++) {
297 if (parameters[i] != null) {
298 adornment.add(query.getParameters().get(i));
299 }
300 }
301
302 return newLocalSearchMatcher(adornment);
303 }
304
305 private LocalSearchMatcher newLocalSearchMatcher(final Set<PParameter> adornment) {
306 final MatcherReference reference = new MatcherReference(query, adornment, userHints);
307
308 IPlanDescriptor plan = getOrCreatePlan(reference, planProvider);
309 if (overrideDefaultHints(reference.getQuery()).isUseBase()){
310 try {
311 indexKeys(plan.getIteratedKeys());
312 } catch (InvocationTargetException e) {
313 throw new LocalSearchException("Could not index keys", e);
314 }
315 }
316
317 LocalSearchMatcher matcher = createMatcher(plan, searchContext);
318 matcher.addAdapters(backend.getAdapters());
319 return matcher;
320 }
321
322 private void indexKeys(final Iterable<IInputKey> keys) throws InvocationTargetException {
323 final IQueryRuntimeContext qrc = getRuntimeContext();
324 qrc.coalesceTraversals(new Callable<Void>() {
325
326 @Override
327 public Void call() throws Exception {
328 for(IInputKey key : keys){
329 if (key.isEnumerable()) {
330 qrc.ensureIndexed(key, IndexingService.INSTANCES);
331 }
332 }
333 return null;
334 }
335 });
336 }
337
338 @Override
339 public boolean hasMatch(Object[] parameters) {
340 final LocalSearchMatcher matcher = initializeMatcher(parameters);
341 return matcher.streamMatches(parameters).findAny().isPresent();
342 }
343
344 @Override
345 public boolean hasMatch(TupleMask parameterSeedMask, ITuple parameters) {
346 final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask);
347 return matcher.streamMatches(parameterSeedMask, parameters).findAny().isPresent();
348 }
349
350 @Override
351 public Optional<Tuple> getOneArbitraryMatch(Object[] parameters) {
352 final LocalSearchMatcher matcher = initializeMatcher(parameters);
353 return matcher.streamMatches(parameters).findAny();
354 }
355
356 @Override
357 public Optional<Tuple> getOneArbitraryMatch(TupleMask parameterSeedMask, ITuple parameters) {
358 final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask);
359 return matcher.streamMatches(parameterSeedMask, parameters).findAny();
360 }
361
362 @Override
363 public int countMatches(Object[] parameters) {
364 final LocalSearchMatcher matcher = initializeMatcher(parameters);
365 // Count returns long; casting to int - in case of integer overflow casting will throw the exception
366 return (int) matcher.streamMatches(parameters).count();
367 }
368
369 @Override
370 public int countMatches(TupleMask parameterSeedMask, ITuple parameters) {
371 final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask);
372 // Count returns long; casting to int - in case of integer overflow casting will throw the exception
373 return (int) matcher.streamMatches(parameterSeedMask, parameters).count();
374 }
375
376 private static final double ESTIMATE_CEILING = Long.MAX_VALUE / 16.0;
377
378 @Override
379 public Optional<Long> estimateCardinality(TupleMask groupMask, Accuracy requiredAccuracy) {
380 if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // approximate using parameter types
381 final List<PParameter> parameters = query.getParameters();
382 final Map<Set<Integer>, Set<Integer>> dependencies = backendContext.getQueryAnalyzer()
383 .getProjectedFunctionalDependencies(query, false);
384
385 List<Integer> projectionIndices = groupMask.getIndicesAsList();
386
387 return estimateParameterCombinations(requiredAccuracy, parameters, dependencies,
388 projectionIndices,
389 Collections.emptySet() /* No parameters with fixed value */).map(Double::longValue);
390 }
391 else return Optional.empty();
392 }
393
394 @Override
395 public Optional<Double> estimateAverageBucketSize(TupleMask groupMask, Accuracy requiredAccuracy) {
396 if (Accuracy.BEST_UPPER_BOUND.atLeastAsPreciseAs(requiredAccuracy)) { // approximate using parameter types
397 final List<PParameter> parameters = query.getParameters();
398 final Map<Set<Integer>, Set<Integer>> dependencies = backendContext.getQueryAnalyzer()
399 .getProjectedFunctionalDependencies(query, false);
400
401 // all parameters used for the estimation - determinized order
402 final List<Integer> allParameterIndices =
403 IntStream.range(0, parameters.size()).boxed().collect(Collectors.toList());
404
405 // some free parameters are functionally determined by bound parameters
406 final Set<Integer> boundOrImplied = FunctionalDependencyHelper.closureOf(groupMask.getIndicesAsList(),
407 dependencies);
408
409 return estimateParameterCombinations(requiredAccuracy, parameters, dependencies,
410 allParameterIndices,
411 boundOrImplied);
412 }
413 else return Optional.empty();
414 }
415
416 /**
417 * @since 2.1
418 * @noreference This method is not intended to be referenced by clients.
419 */
420 public double estimateCost(TupleMask inputBindingMask) {
421 // TODO this is currently an abstract cost, not really a branching factor
422
423 HashSet<PParameter> adornment = new HashSet<>(inputBindingMask.transform(query.getParameters()));
424 final MatcherReference reference = new MatcherReference(query, adornment, userHints);
425 IPlanDescriptor plan = getOrCreatePlan(reference, planProvider);
426
427 return plan.getPlan().stream().mapToDouble(SearchPlanForBody::getCost).sum();
428 }
429
430 /**
431 * Approximates using parameter types
432 */
433 private Optional<Double> estimateParameterCombinations(
434 Accuracy requiredAccuracy,
435 final List<PParameter> parameters,
436 final Map<Set<Integer>, Set<Integer>> functionalDependencies,
437 final Collection<Integer> parameterIndicesToEstimate,
438 final Set<Integer> otherDeterminingIndices)
439 {
440 // keep order deterministic
441 LinkedHashSet<Integer> freeParameterIndices = new LinkedHashSet<>(parameterIndicesToEstimate);
442
443 // determining indices are bound
444 freeParameterIndices.removeAll(otherDeterminingIndices);
445
446 // some free parameters are functionally determined by other free parameters
447 for (Integer candidateForRemoval : new ArrayList<>(freeParameterIndices)) {
448 List<Integer> others = Stream.concat(
449 otherDeterminingIndices.stream(),
450 freeParameterIndices.stream().filter(index -> !Objects.equals(index, candidateForRemoval))
451 ).collect(Collectors.toList());
452 Set<Integer> othersClosure = FunctionalDependencyHelper.closureOf(others, functionalDependencies);
453 if (othersClosure.contains(candidateForRemoval)) {
454 // other parameters functionally determine this mone, does not count towards estimate
455 freeParameterIndices.remove(candidateForRemoval);
456 }
457 }
458
459
460 Optional<Double> result = Optional.of(1.0);
461 // TODO this is currently works with declared types only. For better results, information from
462 // the Type inferrer should be included in the PSystem
463 for (int i = 0; (i < parameters.size()); i++) {
464 final IInputKey type = parameters.get(i).getDeclaredUnaryType();
465 if (freeParameterIndices.contains(i) && type != null) {
466 result = result.flatMap(accumulator ->
467 runtimeContext.estimateCardinality(type, TupleMask.identity(1), requiredAccuracy).map(multiplier ->
468 Math.min(accumulator * multiplier, ESTIMATE_CEILING /* avoid overflow */)
469 ));
470 }
471 }
472 // TODO better approximate cardinality based on plan, branching factors, etc.
473 return result;
474 }
475
476
477 @Override
478 public Stream<Tuple> getAllMatches(Object[] parameters) {
479 final LocalSearchMatcher matcher = initializeMatcher(parameters);
480 return matcher.streamMatches(parameters);
481 }
482
483 @Override
484 public Stream<Tuple> getAllMatches(TupleMask parameterSeedMask, ITuple parameters) {
485 final LocalSearchMatcher matcher = initializeMatcher(parameterSeedMask);
486 return matcher.streamMatches(parameterSeedMask, parameters);
487 }
488
489 @Override
490 public IQueryBackend getQueryBackend() {
491 return backend;
492 }
493
494 @Override
495 public void addUpdateListener(IUpdateable listener, Object listenerTag, boolean fireNow) {
496 // throw new UnsupportedOperationException(UPDATE_LISTENER_NOT_SUPPORTED);
497 }
498
499 @Override
500 public void removeUpdateListener(Object listenerTag) {
501 // throw new UnsupportedOperationException(UPDATE_LISTENER_NOT_SUPPORTED);
502 }
503
504 /**
505 * @since 1.4
506 */
507 public IMatcherCapability getCapabilites() {
508 LocalSearchHints configuration = overrideDefaultHints(query);
509 return configuration;
510 }
511
512 /**
513 * Forgets all stored plans in this result provider. If no plans are stored, nothing happens.
514 *
515 * @since 2.0
516 * @noreference This method is not intended to be referenced by clients; it should only used by {@link LocalSearchBackend}.
517 */
518 public void forgetAllPlans() {
519 planCache.clear();
520 }
521
522 /**
523 * Returns a search plan for a given adornment if exists
524 *
525 * @return a search plan for the pattern with the given adornment, or null if none exists
526 * @since 2.0
527 * @noreference This method is not intended to be referenced by clients; it should only used by {@link LocalSearchBackend}.
528 */
529 public IPlanDescriptor getSearchPlan(Set<PParameter> adornment) {
530 return planCache.get(new MatcherReference(query, adornment));
531 }
532} \ No newline at end of file