/* * SPDX-FileCopyrightText: 2023 The Refinery Authors * * SPDX-License-Identifier: EPL-2.0 */ package tools.refinery.store.reasoning.translator.multiobject; import org.jetbrains.annotations.NotNull; import tools.refinery.store.model.Model; import tools.refinery.store.reasoning.ReasoningAdapter; import tools.refinery.store.reasoning.refinement.PartialModelInitializer; import tools.refinery.store.reasoning.seed.ModelSeed; import tools.refinery.store.reasoning.translator.TranslationException; import tools.refinery.store.representation.Symbol; import tools.refinery.store.representation.TruthValue; import tools.refinery.store.representation.cardinality.CardinalityInterval; import tools.refinery.store.representation.cardinality.CardinalityIntervals; import tools.refinery.store.tuple.Tuple; import java.util.Arrays; import java.util.HashMap; import java.util.function.Function; class MultiObjectInitializer implements PartialModelInitializer { private final Symbol countSymbol; public MultiObjectInitializer(Symbol countSymbol) { this.countSymbol = countSymbol; } @Override public void initialize(Model model, ModelSeed modelSeed) { var intervals = initializeIntervals(model, modelSeed); initializeExists(intervals, model, modelSeed); initializeEquals(intervals, model, modelSeed); var countInterpretation = model.getInterpretation(countSymbol); var uniqueTable = new HashMap(); for (int i = 0; i < intervals.length; i++) { var interval = intervals[i]; if (interval.isEmpty()) { throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL, "Inconsistent existence or equality for node " + i); } var uniqueInterval = uniqueTable.computeIfAbsent(intervals[i], Function.identity()); countInterpretation.put(Tuple.of(i), uniqueInterval); } } @NotNull private CardinalityInterval[] initializeIntervals(Model model, ModelSeed modelSeed) { var intervals = new CardinalityInterval[modelSeed.getNodeCount()]; if (modelSeed.containsSeed(MultiObjectTranslator.COUNT_SYMBOL)) { Arrays.fill(intervals, CardinalityIntervals.ONE); var cursor = modelSeed.getCursor(MultiObjectTranslator.COUNT_SYMBOL, CardinalityIntervals.ONE); while (cursor.move()) { model.checkCancelled(); int i = cursor.getKey().get(0); checkNodeId(intervals, i); intervals[i] = cursor.getValue(); } } else { Arrays.fill(intervals, CardinalityIntervals.SET); if (!modelSeed.containsSeed(ReasoningAdapter.EXISTS_SYMBOL) || !modelSeed.containsSeed(ReasoningAdapter.EQUALS_SYMBOL)) { throw new TranslationException(MultiObjectTranslator.COUNT_SYMBOL, "Seed for %s and %s is required if there is no seed for %s".formatted( ReasoningAdapter.EXISTS_SYMBOL, ReasoningAdapter.EQUALS_SYMBOL, MultiObjectTranslator.COUNT_SYMBOL)); } } return intervals; } private void initializeExists(CardinalityInterval[] intervals, Model model, ModelSeed modelSeed) { if (!modelSeed.containsSeed(ReasoningAdapter.EXISTS_SYMBOL)) { return; } var cursor = modelSeed.getCursor(ReasoningAdapter.EXISTS_SYMBOL, TruthValue.UNKNOWN); while (cursor.move()) { model.checkCancelled(); int i = cursor.getKey().get(0); checkNodeId(intervals, i); switch (cursor.getValue()) { case TRUE -> intervals[i] = intervals[i].meet(CardinalityIntervals.SOME); case FALSE -> intervals[i] = intervals[i].meet(CardinalityIntervals.NONE); case ERROR -> throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL, "Inconsistent existence for node " + i); default -> throw new TranslationException(ReasoningAdapter.EXISTS_SYMBOL, "Invalid existence truth value %s for node %d".formatted(cursor.getValue(), i)); } } } private void initializeEquals(CardinalityInterval[] intervals, Model model, ModelSeed modelSeed) { if (!modelSeed.containsSeed(ReasoningAdapter.EQUALS_SYMBOL)) { return; } var seed = modelSeed.getSeed(ReasoningAdapter.EQUALS_SYMBOL); var cursor = seed.getCursor(TruthValue.FALSE, modelSeed.getNodeCount()); while (cursor.move()) { model.checkCancelled(); var key = cursor.getKey(); int i = key.get(0); int otherIndex = key.get(1); if (i != otherIndex) { throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, "Off-diagonal equivalence (%d, %d) is not permitted".formatted(i, otherIndex)); } checkNodeId(intervals, i); switch (cursor.getValue()) { case TRUE -> intervals[i] = intervals[i].meet(CardinalityIntervals.LONE); case UNKNOWN -> { // Nothing do to, {@code intervals} is initialized with unknown equality. } case ERROR -> throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, "Inconsistent equality for node " + i); default -> throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, "Invalid equality truth value %s for node %d".formatted(cursor.getValue(), i)); } } for (int i = 0; i < intervals.length; i++) { model.checkCancelled(); if (seed.get(Tuple.of(i, i)) == TruthValue.FALSE) { throw new TranslationException(ReasoningAdapter.EQUALS_SYMBOL, "Inconsistent equality for node " + i); } } } private void checkNodeId(CardinalityInterval[] intervals, int nodeId) { if (nodeId < 0 || nodeId >= intervals.length) { throw new IllegalArgumentException("Expected node id %d to be lower than model size %d" .formatted(nodeId, intervals.length)); } } }