org.optaplanner.constraint.streams.bavet.common.AbstractIfExistsNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of optaplanner-constraint-streams-bavet Show documentation
Show all versions of optaplanner-constraint-streams-bavet Show documentation
OptaPlanner solves planning problems.
This lightweight, embeddable planning engine implements powerful and scalable algorithms
to optimize business resource scheduling and planning.
This module contains implementation of Constraint streams (Bavet).
package org.optaplanner.constraint.streams.bavet.common;
import java.util.ArrayDeque;
import java.util.LinkedHashSet;
import java.util.Queue;
import java.util.Set;
import java.util.function.Function;
import org.optaplanner.constraint.streams.bavet.common.index.IndexProperties;
import org.optaplanner.constraint.streams.bavet.common.index.Indexer;
import org.optaplanner.constraint.streams.bavet.uni.UniTuple;
import org.optaplanner.constraint.streams.bavet.uni.UniTupleImpl;
public abstract class AbstractIfExistsNode
extends AbstractNode
implements LeftTupleLifecycle, RightTupleLifecycle> {
private final boolean shouldExist;
private final Function mappingRight;
private final int inputStoreIndexLeft;
private final int inputStoreIndexRight;
/**
* Calls for example {@link AbstractScorer#insert(Tuple)}, and/or ...
*/
private final TupleLifecycle nextNodesTupleLifecycle;
// No outputStoreSize because this node is not a tuple source, even though it has a dirtyCounterQueue.
private final Indexer> indexerLeft;
private final Indexer, Set>> indexerRight;
private final Queue> dirtyCounterQueue;
protected AbstractIfExistsNode(boolean shouldExist,
Function mappingRight,
int inputStoreIndexLeft, int inputStoreIndexRight,
TupleLifecycle nextNodeTupleLifecycle,
Indexer> indexerLeft,
Indexer, Set>> indexerRight) {
this.shouldExist = shouldExist;
this.mappingRight = mappingRight;
this.inputStoreIndexLeft = inputStoreIndexLeft;
this.inputStoreIndexRight = inputStoreIndexRight;
this.nextNodesTupleLifecycle = nextNodeTupleLifecycle;
this.indexerLeft = indexerLeft;
this.indexerRight = indexerRight;
dirtyCounterQueue = new ArrayDeque<>(1000);
}
@Override
public final void insertLeft(LeftTuple_ leftTuple) {
Object[] tupleStore = leftTuple.getStore();
if (tupleStore[inputStoreIndexLeft] != null) {
throw new IllegalStateException("Impossible state: the input for the tuple (" + leftTuple
+ ") was already added in the tupleStore.");
}
IndexProperties indexProperties = createIndexProperties(leftTuple);
tupleStore[inputStoreIndexLeft] = indexProperties;
Counter counter = new Counter<>(leftTuple);
indexerLeft.put(indexProperties, leftTuple, counter);
counter.countRight = 0;
indexerRight.visit(indexProperties, (rightTuple, counterSetRight) -> {
if (!isFiltering() || isFiltered(leftTuple, rightTuple)) {
counter.countRight++;
counterSetRight.add(counter);
}
});
if (shouldExist ? counter.countRight > 0 : counter.countRight == 0) {
counter.state = BavetTupleState.CREATING;
dirtyCounterQueue.add(counter);
}
}
@Override
public final void updateLeft(LeftTuple_ leftTuple) {
// TODO Implement actual update
retractLeft(leftTuple);
insertLeft(leftTuple);
}
@Override
public final void retractLeft(LeftTuple_ leftTuple) {
Object[] tupleStore = leftTuple.getStore();
IndexProperties indexProperties = (IndexProperties) tupleStore[inputStoreIndexLeft];
if (indexProperties == null) {
// No fail fast if null because we don't track which tuples made it through the filter predicate(s)
return;
}
tupleStore[inputStoreIndexLeft] = null;
Counter counter = indexerLeft.remove(indexProperties, leftTuple);
indexerRight.visit(indexProperties, (rightTuple, counterSetRight) -> {
boolean changed = counterSetRight.remove(counter);
// If filtering is active, not all counterSets contain the counter and we don't track which ones do
if (!changed && !isFiltering()) {
throw new IllegalStateException("Impossible state: the tuple (" + leftTuple
+ ") with indexProperties (" + indexProperties
+ ") has a counter on the AB side that doesn't exist on the C side.");
}
});
if (shouldExist ? counter.countRight > 0 : counter.countRight == 0) {
retractCounter(counter);
}
}
@Override
public final void updateRight(UniTupleImpl rightTuple) {
// TODO Implement actual update
retractRight(rightTuple);
insertRight(rightTuple);
}
@Override
public final void insertRight(UniTupleImpl rightTuple) {
if (rightTuple.store[inputStoreIndexRight] != null) {
throw new IllegalStateException("Impossible state: the input for the tuple (" + rightTuple
+ ") was already added in the tupleStore.");
}
IndexProperties indexProperties = mappingRight.apply(rightTuple.factA);
rightTuple.store[inputStoreIndexRight] = indexProperties;
// TODO Maybe predict capacity with Math.max(16, counterMapA.size())
Set> counterSetRight = new LinkedHashSet<>();
indexerRight.put(indexProperties, rightTuple, counterSetRight);
indexerLeft.visit(indexProperties, (leftTuple, counter) -> {
if (!isFiltering() || isFiltered(leftTuple, rightTuple)) {
if (counter.countRight == 0) {
if (shouldExist) {
insertCounter(counter);
} else {
retractCounter(counter);
}
}
counter.countRight++;
counterSetRight.add(counter);
}
});
}
@Override
public final void retractRight(UniTupleImpl rightTuple) {
Object[] tupleStore = rightTuple.store;
IndexProperties indexProperties = (IndexProperties) tupleStore[inputStoreIndexRight];
if (indexProperties == null) {
// No fail fast if null because we don't track which tuples made it through the filter predicate(s)
return;
}
tupleStore[inputStoreIndexRight] = null;
Set> counterSetRight = indexerRight.remove(indexProperties, rightTuple);
for (Counter counter : counterSetRight) {
counter.countRight--;
if (counter.countRight == 0) {
if (shouldExist) {
retractCounter(counter);
} else {
insertCounter(counter);
}
}
}
}
protected abstract IndexProperties createIndexProperties(LeftTuple_ leftTuple);
protected abstract boolean isFiltering();
protected abstract boolean isFiltered(LeftTuple_ leftTuple, UniTuple rightTuple);
public static final class Counter {
public final Tuple_ leftTuple;
public BavetTupleState state = BavetTupleState.DEAD;
public int countRight = 0;
public Counter(Tuple_ leftTuple) {
this.leftTuple = leftTuple;
}
@Override
public String toString() {
return "Counter(" + leftTuple + ")";
}
}
private void insertCounter(Counter counter) {
switch (counter.state) {
case DYING:
counter.state = BavetTupleState.UPDATING;
break;
case DEAD:
counter.state = BavetTupleState.CREATING;
dirtyCounterQueue.add(counter);
break;
case ABORTING:
counter.state = BavetTupleState.CREATING;
break;
default:
throw new IllegalStateException("Impossible state: the counter (" + counter
+ ") has an impossible insert state (" + counter.state + ").");
}
}
private void retractCounter(Counter counter) {
switch (counter.state) {
case CREATING:
// Kill it before it propagates
counter.state = BavetTupleState.ABORTING;
break;
case UPDATING:
// Kill the original propagation
counter.state = BavetTupleState.DYING;
break;
case OK:
counter.state = BavetTupleState.DYING;
dirtyCounterQueue.add(counter);
break;
default:
throw new IllegalStateException("Impossible state: The counter (" + counter
+ ") has an impossible retract state (" + counter.state + ").");
}
}
@Override
public void calculateScore() {
dirtyCounterQueue.forEach(counter -> {
switch (counter.state) {
case CREATING:
nextNodesTupleLifecycle.insert(counter.leftTuple);
counter.state = BavetTupleState.OK;
break;
case UPDATING:
nextNodesTupleLifecycle.update(counter.leftTuple);
counter.state = BavetTupleState.OK;
break;
case DYING:
nextNodesTupleLifecycle.retract(counter.leftTuple);
counter.state = BavetTupleState.DEAD;
break;
case ABORTING:
counter.state = BavetTupleState.DEAD;
break;
case OK:
case DEAD:
default:
throw new IllegalStateException("Impossible state: The dirty counter (" + counter
+ ") has an non-dirty state (" + counter.state + ").");
}
});
dirtyCounterQueue.clear();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy